pax_global_header00006660000000000000000000000064151115356310014513gustar00rootroot0000000000000052 comment=0dac7fe434d029a4f0b819cba8eb7963df291990 tmux-tmux-f222026/000077500000000000000000000000001511153563100137505ustar00rootroot00000000000000tmux-tmux-f222026/.github/000077500000000000000000000000001511153563100153105ustar00rootroot00000000000000tmux-tmux-f222026/.github/CONTRIBUTING.md000066400000000000000000000040631511153563100175440ustar00rootroot00000000000000## What should I do before opening an issue? Before opening an issue, please ensure that: - Your problem is a specific problem or question or suggestion, not a general complaint. - `$TERM` inside tmux is screen, screen-256color, tmux or tmux-256color. Check by running `echo $TERM` inside tmux. - You can reproduce the problem with the latest tmux release, or a build from Git master. - Your question or issue is not covered [in the manual](https://man.openbsd.org/tmux.1) (run `man tmux`). - Your problem is not mentioned in [the CHANGES file](https://raw.githubusercontent.com/tmux/tmux/master/CHANGES). - Nobody else has opened the same issue recently. ## What should I include in an issue? Please include the output of: ~~~bash uname -sp && tmux -V && echo $TERM ~~~ Also include: - Your platform (Linux, macOS, or whatever). - A brief description of the problem with steps to reproduce. - A minimal tmux config, if you can't reproduce without a config. - Your terminal, and `$TERM` inside and outside of tmux. - Logs from tmux (see below). Please attach logs to the issue directly rather than using a download site or pastebin. Put in a zip file if necessary. - At most one or two screenshots, if helpful. ## How do I test without a .tmux.conf? Run a separate tmux server with `-f/dev/null` to skip loading `.tmux.conf`: ~~~bash tmux -Ltest kill-server tmux -Ltest -f/dev/null new ~~~ ## How do I get logs from tmux? Add `-vv` to tmux to create three log files in the current directory. If you can reproduce without a configuration file: ~~~bash tmux -Ltest kill-server tmux -vv -Ltest -f/dev/null new ~~~ Or if you need your configuration: ~~~base tmux kill-server tmux -vv new ~~~ The log files are: - `tmux-server*.log`: server log file. - `tmux-client*.log`: client log file. - `tmux-out*.log`: output log file. Please attach the log files to your issue. ## What does it mean if an issue is closed? All it means is that work on the issue is not planned for the near future. See the issue's comments to find out if contributions would be welcome. tmux-tmux-f222026/.github/FUNDING.yml000066400000000000000000000000351511153563100171230ustar00rootroot00000000000000github: nicm liberapay: tmux tmux-tmux-f222026/.github/ISSUE_TEMPLATE/000077500000000000000000000000001511153563100174735ustar00rootroot00000000000000tmux-tmux-f222026/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000000341511153563100214600ustar00rootroot00000000000000blank_issues_enabled: false tmux-tmux-f222026/.github/ISSUE_TEMPLATE/use-this-issue-template.md000066400000000000000000000021101511153563100245070ustar00rootroot00000000000000--- name: Use this issue template about: Please read https://github.com/tmux/tmux/blob/master/.github/CONTRIBUTING.md title: '' labels: '' assignees: '' --- ### Issue description Please read https://github.com/tmux/tmux/blob/master/.github/CONTRIBUTING.md before opening an issue. If you have upgraded, make sure your issue is not covered in the CHANGES file for your version: https://raw.githubusercontent.com/tmux/tmux/master/CHANGES Describe the problem and the steps to reproduce. Add a minimal tmux config if necessary. Screenshots can be helpful, but no more than one or two. Do not report bugs (crashes, incorrect behaviour) without reproducing on a tmux built from the latest code in Git. ### Required information Please provide the following information. These are **required**. Note that bug reports without logs may be ignored or closed without comment. * tmux version (`tmux -V`). * Platform (`uname -sp`). * Terminal in use (xterm, rxvt, etc). * $TERM *inside* tmux (`echo $TERM`). * $TERM *outside* tmux (`echo $TERM`). * Logs from tmux (`tmux kill-server; tmux -vv new`). tmux-tmux-f222026/.github/README.md000066400000000000000000000052111511153563100165660ustar00rootroot00000000000000# 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](https://libevent.org) 2.x, available from [this page](https://github.com/libevent/libevent/releases/latest). It also depends on [ncurses](https://www.gnu.org/software/ncurses/), available from [this page](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 ### Binary packages Some platforms provide binary packages for tmux, although these are sometimes out of date. Examples are listed on [this page](https://github.com/tmux/tmux/wiki/Installing). ### From release tarball To build and install tmux from a release tarball, use: ~~~bash ./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. For more detailed instructions on building and installing tmux, see [this page](https://github.com/tmux/tmux/wiki/Installing). ### From version control To get and build the latest from version control - note that this requires `autoconf`, `automake` and `pkg-config`: ~~~bash 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. **Please read [this document](CONTRIBUTING.md) before opening an issue.** There is [a list of suggestions for contributions](https://github.com/tmux/tmux/wiki/Contributing). Please feel free to ask on the mailing list if you're thinking of working on something or need further information. ## Documentation For documentation on using tmux, see the tmux.1 manpage. View it from the source tree with: ~~~bash nroff -mdoc tmux.1|less ~~~ A small example configuration is in `example_tmux.conf`. And a bash(1) completion file at: https://github.com/scop/bash-completion/blob/main/completions/tmux For debugging, run tmux with `-v` or `-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 tmux-tmux-f222026/.github/travis/000077500000000000000000000000001511153563100166205ustar00rootroot00000000000000tmux-tmux-f222026/.github/travis/before-install.sh000066400000000000000000000007111511153563100220610ustar00rootroot00000000000000#!/bin/sh if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get update -qq sudo apt-get -y install bison \ autotools-dev \ libncurses5-dev \ libevent-dev \ pkg-config \ libutempter-dev \ build-essential if [ "$BUILD" = "musl" -o "$BUILD" = "musl-static" ]; then sudo apt-get -y install musl-dev \ musl-tools fi fi if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then sudo pkg install -y \ automake \ libevent \ pkgconf fi tmux-tmux-f222026/.github/travis/build-all.sh000066400000000000000000000020271511153563100210220ustar00rootroot00000000000000#!/bin/sh BUILD=$PWD/build LIBEVENT=https://github.com/libevent/libevent/releases/download/release-2.1.11-stable/libevent-2.1.11-stab\ le.tar.gz NCURSES=https://ftp.gnu.org/gnu/ncurses/ncurses-6.2.tar.gz wget -4q $LIBEVENT || exit 1 tar -zxf libevent-*.tar.gz || exit 1 (cd libevent-*/ && ./configure --prefix=$BUILD \ --enable-shared \ --disable-libevent-regress \ --disable-samples && make && make install) || exit 1 wget -4q $NCURSES || exit 1 tar -zxf ncurses-*.tar.gz || exit 1 (cd ncurses-*/ && CPPFLAGS=-P ./configure --prefix=$BUILD \ --with-shared \ --with-termlib \ --without-ada \ --without-cxx \ --without-manpages \ --without-progs \ --without-tests \ --without-tack \ --disable-database \ --enable-termcap \ --enable-pc-files \ --with-pkg-config-libdir=$BUILD/lib/pkgconfig && make && make install) || exit 1 sh autogen.sh || exit 1 PKG_CONFIG_PATH=$BUILD/lib/pkgconfig ./configure --prefix=$BUILD "$@" make && make install || (cat config.log; exit 1) tmux-tmux-f222026/.github/travis/build.sh000066400000000000000000000005761511153563100202630ustar00rootroot00000000000000#!/bin/sh sh autogen.sh || exit 1 case "$BUILD" in static) ./configure --enable-static || exit 1 exec make ;; all) sh $(dirname $0)/build-all.sh exec make ;; musl) CC=musl-gcc sh $(dirname $0)/build-all.sh exec make ;; musl-static) CC=musl-gcc sh $(dirname $0)/build-all.sh --enable-static exec make ;; *) ./configure || exit 1 exec make ;; esac tmux-tmux-f222026/.github/workflows/000077500000000000000000000000001511153563100173455ustar00rootroot00000000000000tmux-tmux-f222026/.github/workflows/lock.yml000066400000000000000000000016561511153563100210300ustar00rootroot00000000000000name: 'Lock Threads' on: schedule: - cron: '0 0 * * *' workflow_dispatch: permissions: issues: write pull-requests: write discussions: write concurrency: group: lock-threads jobs: action: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v5 with: github-token: ${{ github.token }} issue-inactive-days: '30' issue-comment: > This issue has been automatically locked since there has not been any recent activity after it was closed. pr-inactive-days: '60' pr-comment: > This pull request has been automatically locked since there has not been any recent activity after it was closed. discussion-inactive-days: '60' discussion-comment: > This discussion has been automatically locked since there has not been any recent activity after it was closed. tmux-tmux-f222026/.gitignore000066400000000000000000000003261511153563100157410ustar00rootroot00000000000000*.core *.dSYM *.diff *.o *.patch *.swp *~ .deps/ .dirstamp Makefile Makefile.in aclocal.m4 autom4te.cache/ cmd-parse.c compat/.dirstamp config.log config.status configure core etc/ fuzz/*-fuzzer tags tmux tmux.1.* tmux-tmux-f222026/.mailmap000066400000000000000000000037331511153563100153770ustar00rootroot00000000000000Bob Beck beck Claudio Jeker claudio Igor Sobrado sobrado Ingo Schwarze schwarze Jacek Masiulaniec jacekm Jason McIntyre jmc Joel Sing jsing Jonathan Gray jsg Kenneth R Westerback krw Marc Espie espie Matthew Dempsky matthew Matthias Kilian kili Matthieu Herrb matthieu Michael McConville mmcc Miod Vallat miod Nicholas Marriott Nicholas Marriott Nicholas Marriott nicm Nicholas Marriott no_author Okan Demirmen okan Philip Guenther guenther Pierre-Yves Ritschard pyr Ray Lai ray Ryan McBride mcbride Sebastian Benoit benno Sebastien Marie semarie Stefan Sperling stsp Stuart Henderson sthen Ted Unangst tedu Theo de Raadt Theo Deraadt Theo de Raadt deraadt Thomas Adam Thomas Thomas Adam Thomas Adam Thomas Adam n6tadam Tim van der Molen tim Tobias Stoeckmann tobias Todd C Miller millert William Yodlowsky william tmux-tmux-f222026/.travis.yml000066400000000000000000000027651511153563100160730ustar00rootroot00000000000000language: c os: - linux - freebsd - osx compiler: - gcc - clang arch: - amd64 - arm64 env: - BUILD= - BUILD=static - BUILD=all - BUILD=musl - BUILD=musl-static jobs: exclude: # Static builds are broken on OS X (by Apple) - os: osx compiler: gcc env: BUILD=static - os: osx compiler: clang env: BUILD=static # No musl on FreeBSD - os: freebsd compiler: gcc env: BUILD=musl - os: freebsd compiler: clang env: BUILD=musl - os: freebsd compiler: gcc env: BUILD=musl-static - os: freebsd compiler: clang env: BUILD=musl-static # No musl on OS X - os: osx compiler: gcc env: BUILD=musl - os: osx compiler: clang env: BUILD=musl - os: osx compiler: gcc env: BUILD=musl-static - os: osx compiler: clang env: BUILD=musl-static # arm64 doesn't link ncurses - os: linux compiler: gcc arch: arm64 env: BUILD=all - os: linux compiler: clang arch: arm64 env: BUILD=all - os: linux compiler: gcc arch: arm64 env: BUILD=musl - os: linux compiler: clang arch: arm64 env: BUILD=musl - os: linux compiler: gcc arch: arm64 env: BUILD=musl-static - os: linux compiler: clang arch: arm64 env: BUILD=musl-static before_install: - sh .github/travis/before-install.sh script: - sh .github/travis/build.sh tmux-tmux-f222026/CHANGES000066400000000000000000004555361511153563100147650ustar00rootroot00000000000000CHANGES FROM 3.5a TO 3.6 * Add seconds options for clock mode (issue 4697). * Add a resize callback for menus so that they are correctly moved on resize (issue 4696). * Make -v to source-file pass through to subsequent source-file commands (issue 4216). * If display-popup is used inside a popup, modify that popup (issue 4678). * Add selection-mode command to expilcitly set the selection mode in copy mode (issue 3842). * Save and restore images in alternate screen (issue 3732). * Ignore Hangul filler character (issue 3998). * Improve handling of regional indicators and emoji modifiers (issue 3998). * Preserve marked pane with swap-window and move-window (issue 3443). * Set and check COLORTERM as a hint for RGB colour. * If tmux receives a palette request (OSC 4) in a pane and the palette entry has not been set, send a request to the most recently used client and forward any response instead (based on change from Tim Culverhouse, issue 4665). * Add -l flag to command-prompt to disable splitting into multiple prompts (issue 4483). * Don't enter copy mode on mouse wheel in alternate screen (issue 3705). * Add commands to centre the cursor in copy mode (issue 4662). * Support case insensitive search in modes in the same way as copy mode (like emacs, so all-lowercase means case insensitive) (issue 4396). * Fix the logic of the no-detached case for the detach-on-destroy option (from Martin Louazel, issue 4649). * Add buffer_full format variable (from Mohammad AlSaleh, issue 4630). * Introduce a new window option, tiled-layout-max-columns, which configures the maximum number of columns in the tiled layout. * Add support for DECRQSS SP q (report cursor style), DECRQM ?12 (report cursor blink state) and DECRQM ?2004, ?1004, ?1006 (report mouse state) ( rom Andrea Alberti, issue 4618). * Fix missing argument from OSC 4 reply (issue 4596). * Add -k flag to display-popup which allows any key to dismiss the popup once the command has exited (from Meriel Luna Mittelbach, issue 4612). * Add nicer default second and third status lines (from Michael Grant, issue 4490). * Add a pane-border-lines "spaces" value to use spaces for pane borders (issue 4587). * Replace invalid UTF-8 characters with the placeholder instead of ignoring them (issue 4514). * Fix incorrect handling of Korean Hangul Jamo characters (from Roy Jung, issue 4546). * Allow uppercase letters in gray/grey color names (from Pavel Roskin, issue 4560). * Add sorting to W, P, L loop operators (from Michael Grant, issue 4516). * Detect support for OSC 52 using the device attributes report (from James Holderness, issue 4539). * Add noattr for styles and use in mode-style to allow whether attributes are ignored or used to be configured (issue 4498). * Add a set-default style attribute which replaces the current default colours and attributes completely. * Add -E to run-shell to forward stderr as well as stdout (issue 4246). * Add an option variation-selector-always-wide to instruct tmux not to always interpret VS16 as a wide character and assume the terminal does likewise. * Switch to getopt_long from OpenSSH (from Koichi Murase, issue 4492). * Add more features for boolean expressions in formats: 1) extend && and || to support arbitrarily many arguments and 2) add ! and !! for not and not-not (from David Mandelberg). * Do not mistake other DCS sequences for SIXEL sequences (from James Holderness, issue 4488). * Improve #? conditional expression in formats: add support for else if and default empty string if no else value (from David Mandelberg, issue 4451). * Add default-client-command to set the command used if tmux is run without a command; the default stays new-session (from David Mandelberg, issue 4422). * Add S-Up and S-Down to move windows in tree mode (from David Mandelberg, issue 4415). * Add mode 2031 support to automatically report dark or light theme. tmux will guess the theme from the background colour on terminals which do not themselves support the escape sequence (from Jonathan Slenders, issue 4353). * Add -M flag to capture-pane to use the copy mode screen (issue 4358). * Align index numbers in trees (from David Mandelberg, issue 4360). * Add display-message -C flag to update pane while message is displayed (from Vitaly Ostrosablin, issue 4363). * Make list-commands command show only one command if an argument is given (from Ilya Grigoriev, issue 4352). * Count line numbers correctly inside strings in configuration files (reported by Pedro Navarro, issue 4325). * Map bright black (colour 8) to white (7) if the background is black on terminals with only eight colours so the text is not invisible (from Dmytro Bagrii, issue 4322). * New codepoint-widths option allowing users to override the width of individual Unicode codepoints. * Add a nesting limit to source-file (from Fadi Afani, issue 4223). * Add copy-mode-position-style and copy-mode-selection-style options for copy mode. * Add no-detach-on-destroy client option (issue 4242). * Add input-buffer-size option (from Ken Lau). * Add support for a scrollbar at the side of each pane. New options pane-scrollbars turn them on or off, pane-scrollbars-position sets the position (left or right), and pane-scrollbars-style to set the colours (from Michael Grant, issue 4221). * Allow control characters to be entered at the command prompt by prefixing with C-v (from Alexander Arch, issue 4206). * Do not attempt to search for zero length strings (from Alexander Arch, issue 4209). * Preserve tabs for copying and capture-pane (from Alexander Arch, issue 4201). * Increase the maximum for repeat-time. * Adjust how Ctrl and Meta keys are sent to use standard representation if available in mode 1 (from Stanislav Kljuhhin, issue 4188). * Allow attributes in menu style (from Japin Li, issue 4194). * Add a sixel_support format variable which is 1 if SIXEL is supported, always 0 on OpenBSD (requested by Misaki Masa, issue 4177). * Add prompt-cursor-colour and prompt-cursor-style to set the style of the cursor in the command prompt and remove the emulated cursor (from Alexander Arch, issue 4170). * Add initial-repeat-time option to allow the first repeat time to be increased and later reduced (from David le Blanc, issue 4164). * Send focus events to pane when entering or leaving popup (issue 3991). * Add copy-mode-position-format to configure the position indicator. * Add -y flag to disable confirmation prompts in modes (issue 4152). * Add -C and -P flags to the copy commands in copy mode: -C prevents the commands from sending the text to the clipboard and -P prevents them from adding the text as a paste buffer (issue 4153). * Preserve transparency and raster attribute dimensions when sending a SIXEL image, and avoid collapsing empty lines (issue 4149). * Bypass permission check for Cygwin (based on a change by Yuya Adachi via Rafael Kitover, issue 4148). * Add MSYSTEM to default update-environment (for Cgywin). * Set client stdout file descriptor also for Cgywin (from Michael Wild via Rafael Kitover, issue 4148). * Use global cursor style and colour options for modes instead of default (issue 4117). * Fix pasting so it does not interpret keys but instead copies the input without interpretation (reported by Mark Kelly). * Try to query window pixel size from the outside terminal if the values returned by TIOCGWINSZ are zero (Dmitry Galchinsky, issue 4099). CHANGES FROM 3.5 TO 3.5a * Do not translate BSpace as Unicode with extended keys. * Fix so that keys with Shift are represented correctly with extended keys. * Revert to using /bin/sh for #() and run-shell and if-shell; the change to use default-shell only applies now to popups. * Fix grey colour without a number suffix in styles. CHANGES FROM 3.4 TO 3.5 * Revamp extended keys support to more closely match xterm and support mode 2 as well as mode 1. This is a substantial change to key handling which changes tmux to always request mode 2 from parent terminal, changes to an unambiguous internal representation of keys, and adds an option (extended-keys-format) to control the format similar to the xterm(1) formatOtherKeys resource. * Clear an overlay (popup or menu) when command prompt is entered. * Add copy-mode -d flag to scroll a page down if in copy mode already (matching -e). * Display hyperlinks in copy mode and add copy_cursor_hyperlink format to get the hyperlink under the cursor. * Add a prefix timeout option. * Mouse move keys are not useful as key bindings because we do not turn them on unless the application requests them. Ignore them so they do not cause the prefix to be canceled * Add search_count and search_count_partial formats in copy mode. * Do not reset mouse pane if clicked on status line, * Add mirrored versions of the main-horizontal and main-vertical layouts where the main pane is bottom or right instead of top or left. * Allow REP to work with Unicode characters. * Fix size calculation of terminators for clipboard escape sequences. * Treat CRLF as LF in config files where it is easy to do so. * The Linux console has some bugs with bright colours, so add some workarounds for it. * If built with systemd, remove some environment variables it uses. * Adjust the logic when deleting last buffer to better preserve the selection: if selecting the element below the deleted one fails (because as the last one), select the one above it instead. * Add --enable-jemalloc to build with jemalloc memory allocator (since glibc malloc is so poor). * Add a way (refresh-client -r) for control mode clients to provide OSC 10 and 11 responses to tmux so they can set the default foreground and background colours. * Add N to search backwards in tree modes. * Use default-shell for command prompt, #() and popups. * Revert part of a change intended to improve search performance by skipping parts of lines already searched, but which in fact skipped the ends of lines altogether. * Add a command-error hook when a command fails. * Add an option allow-set-title to forbid applications from changing the pane title. * Correct handling of mouse up events (don't ignore all but the last released button), and always process down event for double click. * Fix a crash if focusing a pane that is exiting. * Pick newest session (as documented) when looking for next session for detach-on-destroy. * Reduce default escape-time to 10 milliseconds. * Add display-menu -M to always turn mouse on in a menu. * Look for feature code 21 for DECSLRM and 28 for DECFRA in the device attributes and also accept level 1. * Fix crash if built with SIXEL and the SIXEL colour register is invalid; also remove SIXEL images before reflow. * Do not notify window-layout-changed if the window is about to be destroyed. * Do not consider a selection present if it is empty for the selection_active and selection_present format variables. * Fix split-window -p. CHANGES FROM 3.3a TO 3.4 * Add options keep-last and keep-group to destroy-unattached to keep the last session whether in a group. * Don't allow paste-buffer into dead panes. * Add -t to source-file. * Rewrite combined character handling to be more consistent and to support newer Unicode combined characters. * Add basic support for SIXEL if built with --enable-sixel. * Add a session, pane and user mouse range types for the status line and add format variables for mouse_status_line and mouse_status_range so they can be associated with different commands in the key bindings. * Add flag (-o) to next-prompt/previous-prompt to go to OSC 133 command output. * Add options and flags for menu styles (menu-style, menu-border-style) similar to those existing for popups. * Add support for marking lines with a shell prompt based on the OSC 133 extension. * Check for libterminfo for NetBSD. * Add "us" to styles for underscore colour. * Add flags (-c and -y) to change the confirm key and default behaviour of confirm-before. * Use ncurses' new tparm_s function (added in 6.4-20230424) instead of tparm so it does not object to string arguments in c apabilities it doesn't already know. Also ignore errors from tparm if using previous ncurses versions. * Set default lock command to vlock on Linux if present at build time. * Discard mouse sequences that have the right form but actually are invalid. * Add support for spawning panes in separate cgroups with systemd and a configure flag (--disable-cgroups) to turn off. * Add a format (pane_unseen_changes) to show if there are unseen changes while in a mode. * Remove old buffer when renaming rather than complaining. * Add an L modifier like P, W, S to loop over clients. * Add -f to list-clients like the other list commands. * Extend display-message to work for control clients. * Add a flag to display-menu to select the manu item selected when the menu is open. * Have tmux recognise pasted text wrapped in bracket paste sequences, rather than only forwarding them to the program inside. * Have client return 1 if process is interrupted to an input pane. * Query the client terminal for foreground and background colours and if OSC 10 or 11 is received but no colour has been set inside tmux, return the colour from the first attached client. * Add send-keys -K to handle keys directly as if typed (so look up in key table). * Process escape sequences in show-buffer. * Add a -l flag to display-message to disable format expansion. * Add paste-buffer-deleted notification and fix name of paste-buffer-changed. * Do not attempt to connect to the socket as a client if systemd is active. * Add scroll-top and scroll-bottom commands to scroll so cursor is at top or bottom. * Add a -T flag to capture-pane to stop at the last used cell instead of the full width. Restore the previous behaviour by making it default to off unless -J is used. * Add message-line option to control where message and prompt go. * Notification when a paste buffer is deleted. * Add a Nobr terminfo(5) capability to tell tmux the terminal does not use bright colours for bold. * Change g and G to go to top and bottom in menus. * Add a third state "all" to allow-passthrough to work even in invisible panes. * Add support for OSC 8 hyperlinks. * Store the time lines are scrolled into history and display in copy mode. * Add a %config-error reply to control mode for configuration file errors since reporting them in view mode is useless. * A new feature flag (ignorefkeys) to ignore terminfo(5) function key definitions for rxvt. * Pass through first argument to OSC 52 (which clipboards to set) if the application provides it. * Expand arguments to send-keys, capture-pane, split-window, join-pane where it makes sense to do so. * Ignore named buffers when choosing a buffer if one is not specified by the user. CHANGES FROM 3.3 TO 3.3a * Do not crash when run-shell produces output from a config file. * 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, cross compiling, 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 commands available with send -X, such as copy-pipe-and-cancel. * set-remain-on-exit has gone -- can be achieved with hooks instead. * Hooks: before hooks have been removed and only a selection of commands now 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. tmux-tmux-f222026/COPYING000066400000000000000000000017011511153563100150020ustar00rootroot00000000000000THIS 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) 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. tmux-tmux-f222026/Makefile.am000066400000000000000000000117751511153563100160170ustar00rootroot00000000000000# 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_LOCK_CMD='"@DEFAULT_LOCK_CMD@"' \ -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 -Wno-macro-redefined 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 # Set flags for Cygwin. if IS_CYGWIN AM_CPPFLAGS += -DTMUX_SOCK_PERM=0 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 \ hyperlinks.c \ input-keys.c \ input.c \ job.c \ key-bindings.c \ key-string.c \ layout-custom.c \ layout-set.c \ layout.c \ log.c \ menu.c \ mode-tree.c \ names.c \ notify.c \ options-table.c \ options.c \ paste.c \ popup.c \ proc.c \ regsub.c \ resize.c \ screen-redraw.c \ screen-write.c \ screen.c \ server-acl.c \ server-client.c \ server-fn.c \ server.c \ session.c \ spawn.c \ status.c \ style.c \ tmux.c \ tmux.h \ tmux-protocol.h \ tty-acs.c \ tty-features.c \ tty-keys.c \ tty-term.c \ tty.c \ utf8-combined.c \ utf8.c \ window-buffer.c \ window-client.c \ window-clock.c \ window-copy.c \ window-customize.c \ window-tree.c \ window.c \ xmalloc.c \ xmalloc.h 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 # Enable sixel support. if ENABLE_SIXEL dist_tmux_SOURCES += image.c image-sixel.c endif if NEED_FUZZING check_PROGRAMS = fuzz/input-fuzzer fuzz_input_fuzzer_LDFLAGS = $(FUZZING_LIBS) 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 tmux-tmux-f222026/README000066400000000000000000000042671511153563100146410ustar00rootroot00000000000000Welcome 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 $ sudo make install * 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/scop/bash-completion/blob/main/completions/tmux 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. tmux-tmux-f222026/README.ja000066400000000000000000000053531511153563100152270ustar00rootroot00000000000000tmuxã¸ã‚ˆã†ã“ã! 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ã‚’ã”覧ãã ã•ã„。修正ã¯ãƒ¡ãƒ¼ãƒ«å®›ã€ã‚‚ã—ãã¯https://github.com/tmux/tmux/issuesã«ã¦å—ã‘付ã‘ã¦ã„ã¾ã™ã€‚ tmuxã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã«ã¤ã„ã¦ã¯tmux.1マニュアルをã”覧ãã ã•ã„。ã“ã¡ã‚‰ã®ã‚³ãƒžãƒ³ãƒ‰ã§å‚ç…§å¯èƒ½ã§ã™ã€‚ $ nroff -mdoc tmux.1|less ã‚µãƒ³ãƒ—ãƒ«è¨­å®šã¯æœ¬ãƒªãƒã‚¸ãƒˆãƒªã®example_tmux.confã« ã¾ãŸã€bash-completionファイルã¯ä¸‹è¨˜ã«ã‚りã¾ã™ã€‚ https://github.com/scop/bash-completion/blob/main/completions/tmux 「-vã€ã‚„「-vvã€ã‚’指定ã™ã‚‹ã“ã¨ã§ãƒ‡ãƒãƒƒã‚°ãƒ¢ãƒ¼ãƒ‰ã§ã®èµ·å‹•ãŒå¯èƒ½ã§ã™ã€‚カレントディレクトリã«ã‚µãƒ¼ãƒãƒ¼ã‚„クライアントã®ãƒ­ã‚°ãƒ•ァイルãŒç”Ÿæˆã•れã¾ã™ã€‚ è­°è«–ã‚„ãƒã‚°ãƒ¬ãƒãƒ¼ãƒˆç”¨ã®ãƒ¡ãƒ¼ãƒªãƒ³ã‚°ãƒªã‚¹ãƒˆã«ã¯ã“ã¡ã‚‰ã‹ã‚‰å‚加å¯èƒ½ã§ã™ã€‚ https://groups.google.com/forum/#!forum/tmux-users gitコミットã«ã¤ã„ã¦ã®é€£çµ¡å…ˆ https://groups.google.com/forum/#!forum/tmux-git 購読ã¯ã¾ã§ãƒ¡ãƒ¼ãƒ«ã‚’ãŠé¡˜ã„ã—ã¾ã™ã€‚ ãƒã‚°ãƒ¬ãƒãƒ¼ãƒˆã‚„機能追加(特ã«ã‚³ãƒ¼ãƒ‰ã¸ã®è²¢çŒ®)ã¯å¤§æ­“迎ã§ã™ã€‚ã“ã¡ã‚‰ã«ã”連絡ãã ã•ã„。 tmux-users@googlegroups.com 本ファイルã€CHANGES〠FAQã€SYNCINGãã—ã¦TODOã¯ISC licenseã§ä¿è­·ã•れã¦ã„ã¾ã™ã€‚ ãã®ä»–ã®ãƒ•ァイルã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ã‚„著作権ã«ã¤ã„ã¦ã¯ã€ãƒ•ァイルã®ä¸Šéƒ¨ã«æ˜Žè¨˜ã•れã¦ã„ã¾ã™ã€‚ -- Nicholas Marriott tmux-tmux-f222026/SYNCING000066400000000000000000000130011511153563100150000ustar00rootroot00000000000000Preamble ======== Tmux portable relies on repositories "tmux" and "tmux-obsd". Here's a description of them: * "tmux" is the portable version, the one which contains code for other operating systems, and autotools, etc., which isn't found or needed in the OpenBSD base system. * "tmux-obsd" is the version of tmux in OpenBSD base system which provides the basis of the portable tmux version. Note: The "tmux-obsd" repository is actually handled by "git cvsimport" running at 15 minute intervals, so a commit made to OpenBSD's tmux CVS repository will take at least that long to appear in this git repository. (It might take longer, depending on the CVS mirror used to import the OpenBSD code). If you've never used git before, git tracks meta-data about the committer and the author, as part of a commit, hence: % git config [--global] user.name "Your name" % git config [--global] user.email "you@yourdomain.com" Note that, if you already have this in the global ~/.gitconfig option, then this will be used. Setting this per-repository would involve not using the "--global" flag above. If you wish to use the same credentials always, pass the "--global" option, as shown. This is a one-off operation once the repository has been cloned, assuming this information has ever been set before. Cloning repositories ==================== This involves having both tmux and tmux-obsd cloned, as in: % cd /some/where/useful % git clone https://github.com/tmux/tmux.git % git clone https://github.com/ThomasAdam/tmux-obsd.git Note that you do not need additional checkouts to manage the sync -- an existing clone of either repositories will suffice. So if you already have these checkouts existing, skip that. Adding in git-remotes ===================== Because the portable "tmux" git repository and the "tmux-obsd" repository do not inherently share any history between each other, the history has been faked between them. This "faking of history" is something which has to be told to git for the purposes of comparing the "tmux" and "tmux-obsd" repositories for syncing. To do this, we must reference the clone of the "tmux-obsd" repository from the "tmux" repository, as shown by the following command: % cd /path/to/tmux % git remote add obsd-tmux file:///path/to/tmux-obsd So that now, the remote "obsd-tmux" can be used to reference branches and commits from the "tmux-obsd" repository, but from the context of the portable "tmux" repository, which makes sense because it's the "tmux" repository which will have the updates applied to them. Fetching updates ================ To ensure the latest commits from "tmux-obsd" can be found from within "tmux", we have to ensure the "master" branch from "tmux-obsd" is up-to-date first, and then reference that update in "tmux", as in: % cd /path/to/tmux-obsd % git checkout master % git pull Then back in "tmux": % cd /path/to/tmux % git fetch obsd-tmux Creating the necessary branches =============================== Now that "tmux" can see commits and branches from "tmux-obsd" by way of the remote name "obsd-tmux", we can now create the master branch from "tmux-obsd" in the "tmux" repository: % git checkout -b obsd-master obsd-tmux/master Adding in the fake history points ================================= To tie both the "master" branch from "tmux" and the "obsd-master" branch from "tmux-obsd" together, the fake history points added to the "tmux" repository need to be added. To do this, we must add an additional refspec line, as in: % cd /path/to/tmux % git config --add remote.origin.fetch '+refs/replace/*:refs/replace/*' % git fetch origin Performing the Sync =================== Make sure the "master" branch is checked out: % git checkout master The following will show commits on OpenBSD not yet synched with "tmux": % git log master..obsd-master From there, merge the result in, fixing up any conflicts which might arise. % git merge obsd-master Then ensure things look correct by BUILDING the result of that sync: % make clean && ./autogen.sh && ./configure && make Compare the git merge result with what's on origin/master -- that is, check which commits you're about to push: % git log origin/master..master And if happy: % git push origin master Keeping an eye on libutil in OpenBSD ==================================== A lot of the compat/ code in tmux comes from libutil, especially imsg. Sometimes the API can change, etc., which might cause interesting problems trying to run the portable version of tmux. It's worth checking periodically for any changes to libutil in OpenBSD and syncing those files to compat/ as and when appropriate. Release tmux for next version ============================= 1. Update and commit README and CHANGES. The former should be checked for anything outdated and updated with a list of things that might break upgrades and the latter should mention all the major changes since the last version. 2. Make sure configure.ac has the new version number. 3. Tag with: % git tag -a 2.X Where "2.X" is the next version. Push the tag out with: % git push --tags 4. Build the tarball with 'make dist'. 5. Check the tarball. If it's good, go here to select the tag just pushed: https://github.com/tmux/tmux/tags Click the "Add release notes", upload the tarball and add a link in the description field to the CHANGES file. 6. Clone the tmux.github.io repository, and change the RELEASE version in the Makefile. Commit it, and run 'make' to replace %%RELEASE%%. Push the result out. 7. Change version back to master in configure.ac. tmux-tmux-f222026/alerts.c000066400000000000000000000173751511153563100154230ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2015 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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, 0, "%s in current window", type); } else { status_message_set(c, -1, 1, 0, 0, "%s in window %d", type, wl->idx); } } } tmux-tmux-f222026/arguments.c000066400000000000000000000577171511153563100161420ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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; int flags; #define ARGS_ENTRY_OPTIONAL_VALUE 0x1 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; } } /* Type to string. */ static const char * args_type_to_string (enum args_type type) { switch (type) { case ARGS_NONE: return "NONE"; case ARGS_STRING: return "STRING"; case ARGS_COMMANDS: return "COMMANDS"; } return "INVALID"; } /* Get value as string. */ static const char * args_value_as_string(struct args_value *value) { 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 a single flag. */ static int args_parse_flag_argument(struct args_value *values, u_int count, char **cause, struct args *args, u_int *i, const char *string, int flag, int optional_argument) { struct args_value *argument, *new; const char *s; new = xcalloc(1, sizeof *new); if (*string != '\0') { new->type = ARGS_STRING; new->string = xstrdup(string); goto out; } if (*i == count) argument = NULL; else { argument = &values[*i]; if (argument->type != ARGS_STRING) { xasprintf(cause, "-%c argument must be a string", flag); args_free_value(new); free(new); return (-1); } } if (argument == NULL) { args_free_value(new); free(new); if (optional_argument) { log_debug("%s: -%c (optional)", __func__, flag); args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE); return (0); /* either - or end */ } xasprintf(cause, "-%c expects an argument", flag); return (-1); } args_copy_value(new, argument); (*i)++; out: s = args_value_as_string(new); log_debug("%s: -%c = %s", __func__, flag, s); args_set(args, flag, new, 0); return (0); } /* Parse flags argument. */ static int args_parse_flags(const struct args_parse *parse, struct args_value *values, u_int count, char **cause, struct args *args, u_int *i) { struct args_value *value; u_char flag; const char *found, *string; int optional_argument; value = &values[*i]; if (value->type != ARGS_STRING) return (1); string = value->string; log_debug("%s: next %s", __func__, string); if (*string++ != '-' || *string == '\0') return (1); (*i)++; if (string[0] == '-' && string[1] == '\0') return (1); for (;;) { flag = *string++; if (flag == '\0') return (0); if (flag == '?') return (-1); if (!isalnum(flag)) { xasprintf(cause, "invalid flag -%c", flag); return (-1); } found = strchr(parse->template, flag); if (found == NULL) { xasprintf(cause, "unknown flag -%c", flag); return (-1); } if (found[1] != ':') { log_debug("%s: -%c", __func__, flag); args_set(args, flag, NULL, 0); continue; } optional_argument = (found[2] == ':'); return (args_parse_flag_argument(values, count, cause, args, i, string, flag, optional_argument)); } } /* Parse arguments into a new argument set. */ struct args * args_parse(const struct args_parse *parse, struct args_value *values, u_int count, char **cause) { struct args *args; u_int i; enum args_parse_type type; struct args_value *value, *new; const char *s; int stop; if (count == 0) return (args_create()); args = args_create(); for (i = 1; i < count; /* nothing */) { stop = args_parse_flags(parse, values, count, cause, args, &i); if (stop == -1) { args_free(args); return (NULL); } if (stop == 1) break; } log_debug("%s: flags end at %u of %u", __func__, i, count); if (i != count) { for (/* nothing */; i < count; i++) { value = &values[i]; s = args_value_as_string(value); log_debug("%s: %u = %s (type %s)", __func__, i, s, args_type_to_string (value->type)); if (parse->cb != NULL) { type = parse->cb(args, args->count, cause); 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, 0); continue; } TAILQ_FOREACH(value, &entry->values, entry) { new_value = xcalloc(1, sizeof *new_value); args_copy_copy_value(new_value, value, argc, argv); args_set(new_args, entry->flag, new_value, 0); } } 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_entry *last = NULL; struct args_value *value; len = 1; buf = xcalloc(1, len); /* Process the flags first. */ RB_FOREACH(entry, args_tree, &args->tree) { if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) continue; if (!TAILQ_EMPTY(&entry->values)) continue; 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) { if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); else args_print_add(&buf, &len, "-%c", entry->flag); last = entry; continue; } if (TAILQ_EMPTY(&entry->values)) continue; TAILQ_FOREACH(value, &entry->values, entry) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); else args_print_add(&buf, &len, "-%c", entry->flag); args_print_add_value(&buf, &len, value); } last = entry; } if (last && (last->flags & ARGS_ENTRY_OPTIONAL_VALUE)) args_print_add(&buf, &len, " --"); /* And finally the argument vector. */ for (i = 0; i < args->count; i++) 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, int flags) { struct args_entry *entry; entry = args_find(args, flag); if (entry == NULL) { entry = xcalloc(1, sizeof *entry); entry->flag = flag; entry->count = 1; entry->flags = flags; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); } else entry->count++; if (value != NULL && value->type != ARGS_NONE) TAILQ_INSERT_TAIL(&entry->values, value, entry); else free(value); } /* 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; const char *file; 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, &file, &state->pi.line); if (file != NULL) state->pi.file = xstrdup(file); state->pi.c = tc; if (state->pi.c != NULL) state->pi.c->references++; 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); log_debug("%s: %s", __func__, cmd); cmd_log_argv(argc, argv, __func__); for (i = 0; i < argc; i++) { new_cmd = cmd_template_replace(cmd, argv[i], i + 1); log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); 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((void *)state->pi.file); 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 value to a number, and expand formats. */ long long args_strtonum_and_expand(struct args *args, u_char flag, long long minval, long long maxval, struct cmdq_item *item, char **cause) { const char *errstr; char *formatted; long long ll; struct args_entry *entry; struct args_value *value; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } value = TAILQ_LAST(&entry->values, args_values); if (value == NULL || value->type != ARGS_STRING || value->string == NULL) { *cause = xstrdup("missing"); return (0); } formatted = format_single_from_target(item, value->string); ll = strtonum(formatted, minval, maxval, &errstr); free(formatted); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } *cause = NULL; return (ll); } /* Convert an argument to a number which may be a percentage. */ long long args_percentage(struct args *args, u_char flag, long long minval, 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); } if (TAILQ_EMPTY(&entry->values)) { *cause = xstrdup("empty"); return (0); } value = TAILQ_LAST(&entry->values, args_values)->string; return (args_string_percentage(value, minval, maxval, curval, cause)); } /* 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 (valuelen == 0) { *cause = xstrdup("empty"); return (0); } 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); } /* * Convert an argument to a number which may be a percentage, and expand * formats. */ long long args_percentage_and_expand(struct args *args, u_char flag, long long minval, long long maxval, long long curval, struct cmdq_item *item, char **cause) { const char *value; struct args_entry *entry; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } if (TAILQ_EMPTY(&entry->values)) { *cause = xstrdup("empty"); return (0); } value = TAILQ_LAST(&entry->values, args_values)->string; return (args_string_percentage_and_expand(value, minval, maxval, curval, item, cause)); } /* * Convert a string to a number which may be a percentage, and expand formats. */ long long args_string_percentage_and_expand(const char *value, long long minval, long long maxval, long long curval, struct cmdq_item *item, char **cause) { const char *errstr; long long ll; size_t valuelen = strlen(value); char *copy, *f; if (value[valuelen - 1] == '%') { copy = xstrdup(value); copy[valuelen - 1] = '\0'; f = format_single_from_target(item, copy); ll = strtonum(f, 0, 100, &errstr); free(f); free(copy); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } ll = (curval * ll) / 100; if (ll < minval) { *cause = xstrdup("too small"); return (0); } if (ll > maxval) { *cause = xstrdup("too large"); return (0); } } else { f = format_single_from_target(item, value); ll = strtonum(f, minval, maxval, &errstr); free(f); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } } *cause = NULL; return (ll); } tmux-tmux-f222026/attributes.c000066400000000000000000000063021511153563100163030ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Joshua Elsasser * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/autogen.sh000077500000000000000000000005711511153563100157540ustar00rootroot00000000000000#!/bin/sh if [ "x$(uname)" = "xOpenBSD" ]; then [ -z "$AUTOMAKE_VERSION" ] && export AUTOMAKE_VERSION=1.15 [ -z "$AUTOCONF_VERSION" ] && export AUTOCONF_VERSION=2.69 fi die() { echo "$@" >&2 exit 1 } mkdir -p etc aclocal || die "aclocal failed" automake --add-missing --force-missing --copy --foreign || die "automake failed" autoreconf || die "autoreconf failed" tmux-tmux-f222026/cfg.c000066400000000000000000000150761511153563100146640ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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; cfg_show_causes(NULL); 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; int flags = 0; /* * 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); } if (cfg_quiet) flags = CMD_PARSE_QUIET; for (i = 0; i < cfg_nfiles; i++) load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL); cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); } int load_cfg(const char *path, struct client *c, struct cmdq_item *item, struct cmd_find_state *current, 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), current); 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, struct cmd_find_state *current, 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), current); 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) { struct client *c = cmdq_get_client(item); u_int i; for (i = 0; i < cfg_ncauses; i++) { if (c != NULL && (c->flags & CLIENT_CONTROL)) control_write(c, "%%config-error %s", cfg_causes[i]); else 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 client *c = TAILQ_FIRST(&clients); struct window_pane *wp; struct window_mode_entry *wme; u_int i; if (cfg_ncauses == 0) return; if (c != NULL && (c->flags & CLIENT_CONTROL)) { for (i = 0; i < cfg_ncauses; i++) { control_write(c, "%%config-error %s", cfg_causes[i]); free(cfg_causes[i]); } goto out; } if (s == NULL) { if (c != NULL && c->session != NULL) s = c->session; else s = RB_MIN(sessions, &sessions); } if (s == NULL || s->attached == 0) /* wait for an attached session */ return; wp = s->curw->window->active; 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]); } out: free(cfg_causes); cfg_causes = NULL; cfg_ncauses = 0; } tmux-tmux-f222026/client.c000066400000000000000000000514521511153563100154010ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #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; /* 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. */ #ifdef HAVE_SYSTEMD if (systemd_activated()) { /* socket-based activation, do not even try to be a client. */ fd = server_start(client_proc, flags, base, 0, NULL); } else #endif fd = client_connect(base, socket_path, client_flags); if (fd == -1) { if (errno == ECONNREFUSED) { 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; uint64_t flags = client_flags; pid_t pid; u_int i; proc_send(client_peer, MSG_IDENTIFY_LONGFLAGS, -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) { char *argv0; log_debug("shell %s, command %s", shell, shellcmd); argv0 = shell_argv0(shell, !!(client_flags & CLIENT_LOGIN)); 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; pid_t pid; log_debug("%s: %s", __func__, strsignal(sig)); if (sig == SIGCHLD) { for (;;) { pid = waitpid(WAIT_ANY, &status, WNOHANG); if (pid == 0) break; if (pid == -1) { if (errno == ECHILD) break; log_debug("waitpid failed: %s", strerror(errno)); } } } else if (!client_attached) { if (sig == SIGTERM || sig == SIGHUP) proc_exit(client_proc); } else { 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_READ_CANCEL: file_read_cancel(&client_files, imsg); break; case MSG_WRITE_OPEN: file_write_open(&client_files, client_peer, imsg, 1, !(client_flags & CLIENT_CONTROL), client_file_check_cb, 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; } } tmux-tmux-f222026/cmd-attach-session.c000066400000000000000000000110341511153563100176010ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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; } if (cfg_finished) cfg_show_causes(s); 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'))); } tmux-tmux-f222026/cmd-bind-key.c000066400000000000000000000057151511153563100163670ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 [argument ...]]", .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); } tmux-tmux-f222026/cmd-break-pane.c000066400000000000000000000104431511153563100166640ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); wl = winlink_find_by_window(&dst_s->windows, w); if (wl == NULL) return (CMD_RETURN_ERROR); goto out; } 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|PANE_THEMECHANGED); 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; colour_palette_from_option(&wp->palette, wp->options); 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); out: 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); } tmux-tmux-f222026/cmd-capture-pane.c000066400000000000000000000152021511153563100172410ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Jonathan Alvarado * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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:JMNpPqS:Tt:", 0, 0, NULL }, .usage = "[-aCeJMNpPqT] " 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 = { "Ht:", 0, 0, NULL }, .usage = "[-H] " 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 screen *s; struct grid_cell *gc = NULL; struct window_mode_entry *wme; int n, join_lines, flags = 0; 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("")); } s = &wp->base; } else if (args_has(args, 'M')) { wme = TAILQ_FIRST(&wp->modes); if (wme != NULL && wme->mode->get_screen != NULL) { s = wme->mode->get_screen (wme); gd = s->grid; } else { s = &wp->base; gd = wp->base.grid; } } else { s = &wp->base; gd = wp->base.grid; } Sflag = args_get(args, 'S'); if (Sflag != NULL && strcmp(Sflag, "-") == 0) top = 0; else { n = args_strtonum_and_expand(args, 'S', INT_MIN, SHRT_MAX, item, &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_and_expand(args, 'E', INT_MIN, SHRT_MAX, item, &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; } join_lines = args_has(args, 'J'); if (args_has(args, 'e')) flags |= GRID_STRING_WITH_SEQUENCES; if (args_has(args, 'C')) flags |= GRID_STRING_ESCAPE_SEQUENCES; if (!join_lines && !args_has(args, 'T')) flags |= GRID_STRING_EMPTY_CELLS; if (!join_lines && !args_has(args, 'N')) flags |= GRID_STRING_TRIM_SPACES; buf = NULL; for (i = top; i <= bottom; i++) { line = grid_string_cells(gd, 0, i, sx, &gc, flags, s); 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); if (args_has(args, 'H')) screen_reset_hyperlinks(wp->screen); 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); } tmux-tmux-f222026/cmd-choose-tree.c000066400000000000000000000065341511153563100171020ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2012 Thomas Adam * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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:wyZ", 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:yZ", 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:yZ", 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:yZ", 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_is_empty()) 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); } tmux-tmux-f222026/cmd-command-prompt.c000066400000000000000000000147761511153563100176310ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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 = { "1bFkliI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse }, .usage = "[-1bFkliN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " [-T prompt-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; if (args_has(args, 'l')) { cdata->prompts = xcalloc(1, sizeof *cdata->prompts); cdata->prompts[0].prompt = prompts; cdata->prompts[0].input = inputs; cdata->count = 1; } else { 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); cmd_command_prompt_free(cdata); 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); if (!done) cmd_append_argv(&argc, &argv, s); if (done) { cmd_free_argv(cdata->argc, cdata->argv); cdata->argc = argc; cdata->argv = cmd_copy_argv(argc, argv); } 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); } tmux-tmux-f222026/cmd-confirm-before.c000066400000000000000000000104061511153563100175530ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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 = { "bc:p:t:y", 1, 1, cmd_confirm_before_args_parse }, .usage = "[-by] [-c confirm-key] [-p prompt] " CMD_TARGET_CLIENT_USAGE " command", .flags = CMD_CLIENT_TFLAG, .exec = cmd_confirm_before_exec }; struct cmd_confirm_before_data { struct cmdq_item *item; struct cmd_list *cmdlist; u_char confirm_key; int default_yes; }; static enum args_parse_type 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 *confirm_key, *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) { free(cdata); return (CMD_RETURN_ERROR); } if (wait) cdata->item = item; cdata->default_yes = args_has(args, 'y'); if ((confirm_key = args_get(args, 'c')) != NULL) { if (confirm_key[1] == '\0' && confirm_key[0] > 31 && confirm_key[0] < 127) cdata->confirm_key = confirm_key[0]; else { cmdq_error(item, "invalid confirm key"); free(cdata); return (CMD_RETURN_ERROR); } } else cdata->confirm_key = 'y'; if ((prompt = args_get(args, 'p')) != NULL) xasprintf(&new_prompt, "%s ", prompt); else { cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name; xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", cmd, cdata->confirm_key); } 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) goto out; if (s[0] != cdata->confirm_key && (s[0] != '\r' || !cdata->default_yes)) 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); } tmux-tmux-f222026/cmd-copy-mode.c000066400000000000000000000055461511153563100165630ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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 = { "deHMqSs:t:u", 0, 0, NULL }, .usage = "[-deHMqSu] [-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); if (args_has(args, 'd')) window_copy_pagedown(wp, 0, args_has(args, 'e')); if (args_has(args, 'S')) { window_copy_scroll(wp, c->tty.mouse_slider_mpos, event->m.y, args_has(args, 'e')); return (CMD_RETURN_NORMAL); } return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-detach-client.c000066400000000000000000000054411511153563100173650ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-display-menu.c000066400000000000000000000337371511153563100173010ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 = { "b:c:C:H:s:S:MOt:T:x:y:", 1, -1, cmd_display_menu_args_parse }, .usage = "[-MO] [-b border-lines] [-c target-client] " "[-C starting-choice] [-H selected-style] [-s style] " "[-S border-style] " CMD_TARGET_PANE_USAGE " [-T title] " "[-x position] [-y position] name [key] [command] ...", .target = { 't', CMD_FIND_PANE, 0 }, .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:kNs:S:t:T:w:x:y:", 0, -1, NULL }, .usage = "[-BCEkN] [-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 [argument ...]]", .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_pos(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); format_free(ft); 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, *value; const char *style = args_get(args, 's'); const char *border_style = args_get(args, 'S'); const char *selected_style = args_get(args, 'H'); enum box_lines lines = BOX_LINES_DEFAULT; char *title, *cause; int flags = 0, starting_choice = 0; u_int px, py, i, count = args_count(args); struct options *o = target->s->curw->window->options; struct options_entry *oe; if (tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); if (args_has(args, 'C')) { if (strcmp(args_get(args, 'C'), "-") == 0) starting_choice = -1; else { starting_choice = args_strtonum(args, 'C', 0, UINT_MAX, &cause); if (cause != NULL) { cmdq_error(item, "starting choice %s", cause); free(cause); return (CMD_RETURN_ERROR); } } } if (args_has(args, 'T')) title = format_single_from_target(item, args_get(args, 'T')); else title = xstrdup(""); menu = menu_create(title); free(title); for (i = 0; i != count; /* nothing */) { name = args_string(args, i++); if (*name == '\0') { menu_add_item(menu, NULL, item, tc, target); continue; } if (count - i < 2) { cmdq_error(item, "not enough arguments"); 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); } 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_pos(tc, item, args, &px, &py, menu->width + 4, menu->count + 2)) { menu_free(menu); return (CMD_RETURN_NORMAL); } value = args_get(args, 'b'); if (value != NULL) { oe = options_get(o, "menu-border-lines"); lines = options_find_choice(options_table_entry(oe), value, &cause); if (lines == -1) { cmdq_error(item, "menu-border-lines %s", cause); free(cause); return (CMD_RETURN_ERROR); } } if (args_has(args, 'O')) flags |= MENU_STAYOPEN; if (!event->m.valid && !args_has(args, 'M')) flags |= MENU_NOMOUSE; if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines, style, selected_style, border_style, target, NULL, NULL) != 0) return (CMD_RETURN_NORMAL); return (CMD_RETURN_WAIT); } 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 = NULL, *cause = NULL, **argv = NULL; char *title; int modify = popup_present(tc); int flags = -1, 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 (!modify && tc->overlay_draw != NULL) return (CMD_RETURN_NORMAL); if (!modify) { 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_pos(tc, item, args, &px, &py, w, h)) return (CMD_RETURN_NORMAL); 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); } } } 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); } } if (args_has(args, 'T')) title = format_single_from_target(item, args_get(args, 'T')); else title = xstrdup(""); if (args_has(args, 'N') || !modify) flags = 0; if (args_has(args, 'E') > 1) { if (flags == -1) flags = 0; flags |= POPUP_CLOSEEXITZERO; } else if (args_has(args, 'E')) { if (flags == -1) flags = 0; flags |= POPUP_CLOSEEXIT; } if (args_has(args, 'k')) { if (flags == -1) flags = 0; flags |= POPUP_CLOSEANYKEY; } if (modify) { popup_modify(tc, title, style, border_style, lines, flags); free(title); return (CMD_RETURN_NORMAL); } 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(cwd); free(title); return (CMD_RETURN_NORMAL); } if (env != NULL) environ_free(env); free(cwd); free(title); cmd_free_argv(argc, argv); return (CMD_RETURN_WAIT); } tmux-tmux-f222026/cmd-display-message.c000066400000000000000000000104701511153563100177460ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 = { "aCc:d:lINpt:F:v", 0, 1, NULL }, .usage = "[-aCIlNpv] [-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, Nflag = args_has(args, 'N'); int Cflag = args_has(args, 'C'); struct format_tree *ft; u_int count = args_count(args); struct evbuffer *evb; 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); } if (args_has(args, 'l')) msg = xstrdup(template); else msg = format_expand_time(ft, template); if (cmdq_get_client(item) == NULL) cmdq_error(item, "%s", msg); else if (args_has(args, 'p')) cmdq_print(item, "%s", msg); else if (tc != NULL && (tc->flags & CLIENT_CONTROL)) { evb = evbuffer_new(); if (evb == NULL) fatalx("out of memory"); evbuffer_add_printf(evb, "%%message %s", msg); server_client_print(tc, 0, evb); evbuffer_free(evb); } else if (tc != NULL) status_message_set(tc, delay, 0, Nflag, Cflag, "%s", msg); free(msg); format_free(ft); return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-display-panes.c000066400000000000000000000200001511153563100174160ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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, 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, 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, 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, 1); 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); } tmux-tmux-f222026/cmd-find-window.c000066400000000000000000000063401511153563100171050ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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 = ""; const char *star = "*"; 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')) star = ""; 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%s%s,#{window_name}}," "#{m%s:%s%s%s,#{pane_title}}}}", suffix, s, suffix, star, s, star, suffix, star, s, star); } else if (C && N) { xasprintf(&filter->string, "#{||:#{C%s:%s},#{m%s:%s%s%s,#{window_name}}}", suffix, s, suffix, star, s, star); } else if (C && T) { xasprintf(&filter->string, "#{||:#{C%s:%s},#{m%s:%s%s%s,#{pane_title}}}", suffix, s, suffix, star, s, star); } else if (N && T) { xasprintf(&filter->string, "#{||:#{m%s:%s%s%s,#{window_name}}," "#{m%s:%s%s%s,#{pane_title}}}", suffix, star, s, star, suffix, star, s, star); } else if (C) { xasprintf(&filter->string, "#{C%s:%s}", suffix, s); } else if (N) { xasprintf(&filter->string, "#{m%s:%s%s%s,#{window_name}}", suffix, star, s, star); } else { xasprintf(&filter->string, "#{m%s:%s%s%s,#{pane_title}}", suffix, star, s, star); } new_args = args_create(); if (args_has(args, 'Z')) args_set(new_args, 'Z', NULL, 0); args_set(new_args, 'f', filter, 0); window_pane_set_mode(wp, NULL, &window_tree_mode, target, new_args); args_free(new_args); return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-find.c000066400000000000000000000756601511153563100156130ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2015 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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 = TAILQ_FIRST(&fs->w->last_panes); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{up-of}") == 0) { fs->wp = window_pane_find_up(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{down-of}") == 0) { fs->wp = window_pane_find_down(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{left-of}") == 0) { fs->wp = window_pane_find_left(fs->w->active); if (fs->wp == NULL) return (-1); return (0); } else if (strcmp(pane, "{right-of}") == 0) { fs->wp = window_pane_find_right(fs->w->active); 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->w->active; 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(¤t, cmdq_get_client(item), flags) == 0) { fs->current = ¤t; 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); } tmux-tmux-f222026/cmd-if-shell.c000066400000000000000000000120161511153563100163600ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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); cmd_if_shell_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, 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); } tmux-tmux-f222026/cmd-join-pane.c000066400000000000000000000114101511153563100165320ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2011 George Nachman * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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, dst_idx; int flags; enum layout_type type; struct layout_cell *lc; u_int curval = 0; 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; /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ if (args_has(args, 'l') || args_has(args, 'p')) { if (args_has(args, 'f')) { if (type == LAYOUT_TOPBOTTOM) curval = dst_w->sy; else curval = dst_w->sx; } else { if (type == LAYOUT_TOPBOTTOM) curval = dst_wp->sy; else curval = dst_wp->sx; } } size = -1; if (args_has(args, 'l')) { size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, item, &cause); } else if (args_has(args, 'p')) { size = args_strtonum_and_expand(args, 'l', 0, 100, item, &cause); if (cause == NULL) size = curval * size / 100; } if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); return (CMD_RETURN_ERROR); } 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|PANE_THEMECHANGED); 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); colour_palette_from_option(&src_wp->palette, src_wp->options); 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); } tmux-tmux-f222026/cmd-kill-pane.c000066400000000000000000000036131511153563100165340ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-kill-server.c000066400000000000000000000031371511153563100171200ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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); } tmux-tmux-f222026/cmd-kill-session.c000066400000000000000000000041611511153563100172730ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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); } tmux-tmux-f222026/cmd-kill-window.c000066400000000000000000000055431511153563100171240ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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); } tmux-tmux-f222026/cmd-list-buffers.c000066400000000000000000000042051511153563100172630ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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); } tmux-tmux-f222026/cmd-list-clients.c000066400000000000000000000052701511153563100172730ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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:f:t:", 0, 0, NULL }, .usage = "[-F format] [-f filter] " 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, *filter; u_int idx; char *line, *expanded; int flag; if (args_has(args, 't')) s = target->s; else s = NULL; if ((template = args_get(args, 'F')) == NULL) template = LIST_CLIENTS_TEMPLATE; filter = args_get(args, 'f'); 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); 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); idx++; } return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-list-keys.c000066400000000000000000000233441511153563100166070ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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, 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 client *tc = cmdq_get_target_client(item); 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); if (args_has(args, '1') && tc != NULL) { status_message_set(tc, -1, 1, 0, 0, "bind-key %s", tmp); } else cmdq_print(item, "bind-key %s", tmp); free(key); if (args_has(args, '1')) break; bd = key_bindings_next(table, bd); } table = key_bindings_next_table(table); } 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 void cmd_list_single_command(const struct cmd_entry *entry, struct format_tree *ft, const char *template, struct cmdq_item *item) { const char *s; char *line; 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); } 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, *command; char *cause; 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); if (command == NULL) { for (entryp = cmd_table; *entryp != NULL; entryp++) cmd_list_single_command(*entryp, ft, template, item); } else { entry = cmd_find(command, &cause); if (entry != NULL) cmd_list_single_command(entry, ft, template, item); else { cmdq_error(item, "%s", cause); free(cause); format_free(ft); return (CMD_RETURN_ERROR); } } format_free(ft); return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-list-panes.c000066400000000000000000000101141511153563100167310ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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++; } } tmux-tmux-f222026/cmd-list-sessions.c000066400000000000000000000045401511153563100174770ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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); } tmux-tmux-f222026/cmd-list-windows.c000066400000000000000000000066461511153563100173340ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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++; } } tmux-tmux-f222026/cmd-load-buffer.c000066400000000000000000000060211511153563100170420ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * 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", strerror(error), path); 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); } tmux-tmux-f222026/cmd-lock-server.c000066400000000000000000000041061511153563100171120ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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); } tmux-tmux-f222026/cmd-move-window.c000066400000000000000000000064231511153563100171350ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-new-session.c000066400000000000000000000227421511153563100171360ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * 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 [argument ...]]", .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); } /* 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 (cfg_finished) cfg_show_causes(s); 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); } tmux-tmux-f222026/cmd-new-window.c000066400000000000000000000106231511153563100167550ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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 [argument ...]]", .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, *expanded; 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) { expanded = format_single(item, name, c, s, NULL, NULL); RB_FOREACH(wl, winlinks, &s->windows) { if (strcmp(wl->window->name, expanded) != 0) continue; if (new_wl == NULL) { new_wl = wl; continue; } cmdq_error(item, "multiple windows named %s", name); free(expanded); return (CMD_RETURN_ERROR); } free(expanded); if (new_wl != NULL) { if (args_has(args, 'd')) return (CMD_RETURN_NORMAL); 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); } tmux-tmux-f222026/cmd-parse.y000066400000000000000000001067471511153563100160340ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ %{ #include #include #include #include #include #include #include #include #include "tmux.h" static int yylex(void); static int yyparse(void); static void 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 FORMAT TOKEN EQUALS %type expanded format %type arguments %type argument %type if_open if_elif %type elif elif1 %type argument_statements statements statement %type commands condition condition1 %type 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; int flag = 1; struct cmd_parse_scope *scope; if (ps->scope != NULL) { flag = ps->scope->flag; TAILQ_FOREACH(scope, &ps->stack, entry) flag = flag && scope->flag; } if ((~flags & CMD_PARSE_PARSEONLY) && 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; int flag = 1; struct cmd_parse_scope *scope; if (ps->scope != NULL) { flag = ps->scope->flag; TAILQ_FOREACH(scope, &ps->stack, entry) flag = flag && scope->flag; } if ((~flags & CMD_PARSE_PARSEONLY) && 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, pi->flags, &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 free(copy); } 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 void 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; va_start(ap, fmt); xvasprintf(&error, fmt, ap); va_end(ap); ps->error = cmd_parse_get_error(pi->file, pi->line, error); free(error); } 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 == '\r') { /* * Treat \r\n as \n. */ ch = yylex_getc(); if (ch != '\n') { yylex_ungetc(ch); ch = '\r'; } } 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) { struct cmd_parse_state *ps = &parse_state; 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) { log_debug("%s: end at EOF", __func__); break; } if (state == NONE && ch == '\r') { ch = yylex_getc(); if (ch != '\n') { yylex_ungetc(ch); ch = '\r'; } } if (ch == '\n') { if (state == NONE) { log_debug("%s: end at EOL", __func__); break; } ps->input->line++; } /* Whitespace or ; or } ends a token unless inside quotes. */ if (state == NONE && (ch == ' ' || ch == '\t')) { log_debug("%s: end at WS", __func__); break; } if (state == NONE && (ch == ';' || ch == '}')) { log_debug("%s: end at %c", __func__, ch); break; } /* * Spaces and comments inside quotes after \n are removed but * 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); } tmux-tmux-f222026/cmd-paste-buffer.c000066400000000000000000000057171511153563100172520ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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'); if (window_pane_exited(wp)) { cmdq_error(item, "target pane has exited"); return (CMD_RETURN_ERROR); } bufname = NULL; if (args_has(args, 'b')) bufname = args_get(args, 'b'); 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); } tmux-tmux-f222026/cmd-pipe-pane.c000066400000000000000000000137151511153563100165420ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #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 (window_pane_exited(wp)) { 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); } tmux-tmux-f222026/cmd-queue.c000066400000000000000000000473431511153563100160140ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* 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 (""); 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, struct cmd_find_state *current) { if (current != NULL) return (cmdq_new_state(current, &state->event, state->flags)); return (cmdq_new_state(&state->current, &state->event, state->flags)); } /* 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) { fsp = NULL; 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; cmdq_insert_hook(fsp != NULL ? fsp->s : NULL, item, fsp, "command-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_data(struct cmdq_item *item, struct evbuffer *evb) { server_client_print(item->client, 1, evb); } /* Show message from command. */ void cmdq_print(struct cmdq_item *item, const char *fmt, ...) { va_list ap; struct evbuffer *evb; evb = evbuffer_new(); if (evb == NULL) fatalx("out of memory"); va_start(ap, fmt); evbuffer_add_vprintf(evb, fmt, ap); va_end(ap); cmdq_print_data(item, evb); evbuffer_free(evb); } /* 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, 0, "%s", msg); } free(msg); } tmux-tmux-f222026/cmd-refresh-client.c000066400000000000000000000210631511153563100175710ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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:r:F:l::LRSt:U", 0, 1, NULL }, .usage = "[-cDlLRSU] [-A pane:state] [-B name:what:format] " "[-C XxY] [-f flags] [-r pane:report] " 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 void cmd_refresh_report(struct tty *tty, const char *value) { struct window_pane *wp; u_int pane; size_t size = 0; char *copy, *split; 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; tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg, &wp->control_bg); out: free(copy); } 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, 'r')) cmd_refresh_report(tty, args_get(args, 'r')); 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); } tmux-tmux-f222026/cmd-rename-session.c000066400000000000000000000042671511153563100176160ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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); } tmux-tmux-f222026/cmd-rename-window.c000066400000000000000000000034461511153563100174400ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-resize-pane.c000066400000000000000000000133031511153563100170770ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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, 1); 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); } tmux-tmux-f222026/cmd-resize-window.c000066400000000000000000000060251511153563100174660ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2018 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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, xpixel = 0, ypixel = 0; 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); } tmux-tmux-f222026/cmd-respawn-pane.c000066400000000000000000000051731511153563100172630ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * Copyright (c) 2011 Marcel P. Partap * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 [argument ...]]", .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); } tmux-tmux-f222026/cmd-respawn-window.c000066400000000000000000000050431511153563100176430ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 [argument ...]]", .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); } tmux-tmux-f222026/cmd-rotate-window.c000066400000000000000000000066131511153563100174660ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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); } tmux-tmux-f222026/cmd-run-shell.c000066400000000000000000000170611511153563100165730ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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:Es:c:", 0, 1, cmd_run_shell_args_parse }, .usage = "[-bCE] [-c start-directory] [-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) { if (cdata->item != NULL) { cmdq_print(cdata->item, "%s", msg); return; } if (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 *c = cmdq_get_client(item); 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 = c; cdata->item = item; } else { cdata->client = tc; cdata->flags |= JOB_NOWAIT; } if (cdata->client != NULL) cdata->client->references++; if (args_has(args, 'c')) cdata->cwd = xstrdup(args_get(args, 'c')); else cdata->cwd = xstrdup(server_client_get_cwd(c, s)); if (args_has(args, 'E')) cdata->flags |= JOB_SHOWSTDERR; 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, 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); } tmux-tmux-f222026/cmd-save-buffer.c000066400000000000000000000061031511153563100170620ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * 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", strerror(error), path); 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; struct evbuffer *evb; 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)) { evb = evbuffer_new(); if (evb == NULL) fatalx("out of memory"); evbuffer_add(evb, bufdata, bufsize); cmdq_print_data(item, evb); evbuffer_free(evb); 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); } tmux-tmux-f222026/cmd-select-layout.c000066400000000000000000000067461511153563100174640ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-select-pane.c000066400000000000000000000156721511153563100170700ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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')) { /* * Check for no last pane found in case the other pane was * spawned without being visited (for example split-window -d). */ lastwp = TAILQ_FIRST(&w->last_panes); if (lastwp == NULL && window_count_panes(w) == 2) { lastwp = TAILQ_PREV(w->active, window_panes, entry); if (lastwp == NULL) 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| PANE_THEMECHANGED); server_redraw_window_borders(lastwp->window); server_status_window(lastwp->window); } if (markedwp != NULL) { markedwp->flags |= (PANE_REDRAW|PANE_STYLECHANGED| PANE_THEMECHANGED); 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|PANE_THEMECHANGED); } 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); } tmux-tmux-f222026/cmd-select-window.c000066400000000000000000000076731511153563100174560ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-send-keys.c000066400000000000000000000143471511153563100165700ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 = { "c:FHKlMN:Rt:X", 0, -1, NULL }, .usage = "[-FHKlMRX] [-c target-client] [-N repeat-count] " CMD_TARGET_PANE_USAGE " [key ...]", .target = { 't', CMD_FIND_PANE, 0 }, .flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL, .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, struct args *args, 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 = NULL; struct key_binding *bd; struct key_event *event; if (args_has(args, 'K')) { if (tc == NULL) return (item); event = xcalloc(1, sizeof *event); event->key = key|KEYC_SENT; memset(&event->m, 0, sizeof event->m); if (server_client_handle_key(tc, event) == 0) { free(event->buf); free(event); } return (item); } 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, args, KEYC_LITERAL|n)); } literal = args_has(args, 'l'); if (!literal) { key = key_string_lookup_string(s); if (key != KEYC_NONE && key != KEYC_UNKNOWN) { after = cmd_send_keys_inject_key(item, after, args, 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, args, 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_and_expand(args, 'N', 1, UINT_MAX, item, &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, args, 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_THEMECHANGED|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, args, 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); } tmux-tmux-f222026/cmd-server-access.c000066400000000000000000000077671511153563100174430ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2021 Dallas Lyons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * Controls access to session. */ static enum cmd_retval cmd_server_access_exec(struct cmd *, struct cmdq_item *); const struct cmd_entry cmd_server_access_entry = { .name = "server-access", .alias = NULL, .args = { "adlrw", 0, 1, NULL }, .usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]", .flags = CMD_CLIENT_CANFAIL, .exec = cmd_server_access_exec }; static enum cmd_retval cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw) { struct client *loop; struct server_acl_user *user; uid_t uid; if ((user = server_acl_user_find(pw->pw_uid)) == NULL) { cmdq_error(item, "user %s not found", pw->pw_name); return (CMD_RETURN_ERROR); } TAILQ_FOREACH(loop, &clients, entry) { uid = proc_get_peer_uid(loop->peer); if (uid == server_acl_get_uid(user)) { loop->exit_message = xstrdup("access not allowed"); loop->flags |= CLIENT_EXIT; } } server_acl_user_deny(pw->pw_uid); return (CMD_RETURN_NORMAL); } static enum cmd_retval cmd_server_access_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = cmd_get_args(self); struct client *c = cmdq_get_target_client(item); char *name; struct passwd *pw = NULL; if (args_has(args, 'l')) { server_acl_display(item); return (CMD_RETURN_NORMAL); } if (args_count(args) == 0) { cmdq_error(item, "missing user argument"); return (CMD_RETURN_ERROR); } name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL); if (*name != '\0') pw = getpwnam(name); if (pw == NULL) { cmdq_error(item, "unknown user: %s", name); return (CMD_RETURN_ERROR); } free(name); if (pw->pw_uid == 0 || pw->pw_uid == getuid()) { cmdq_error(item, "%s owns the server, can't change access", pw->pw_name); return (CMD_RETURN_ERROR); } if (args_has(args, 'a') && args_has(args, 'd')) { cmdq_error(item, "-a and -d cannot be used together"); return (CMD_RETURN_ERROR); } if (args_has(args, 'w') && args_has(args, 'r')) { cmdq_error(item, "-r and -w cannot be used together"); return (CMD_RETURN_ERROR); } if (args_has(args, 'd')) return (cmd_server_access_deny(item, pw)); if (args_has(args, 'a')) { if (server_acl_user_find(pw->pw_uid) != NULL) { cmdq_error(item, "user %s is already added", pw->pw_name); return (CMD_RETURN_ERROR); } server_acl_user_allow(pw->pw_uid); /* Do not return - allow -r or -w with -a. */ } else if (args_has(args, 'r') || args_has(args, 'w')) { /* -r or -w implies -a if user does not exist. */ if (server_acl_user_find(pw->pw_uid) == NULL) server_acl_user_allow(pw->pw_uid); } if (args_has(args, 'w')) { if (server_acl_user_find(pw->pw_uid) == NULL) { cmdq_error(item, "user %s not found", pw->pw_name); return (CMD_RETURN_ERROR); } server_acl_user_allow_write(pw->pw_uid); return (CMD_RETURN_NORMAL); } if (args_has(args, 'r')) { if (server_acl_user_find(pw->pw_uid) == NULL) { cmdq_error(item, "user %s not found", pw->pw_name); return (CMD_RETURN_ERROR); } server_acl_user_deny_write(pw->pw_uid); return (CMD_RETURN_NORMAL); } return (CMD_RETURN_NORMAL); } tmux-tmux-f222026/cmd-set-buffer.c000066400000000000000000000070471511153563100167270ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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) { if (bufname != NULL) { cmdq_error(item, "unknown buffer: %s", bufname); return (CMD_RETURN_ERROR); } pb = paste_get_top(&bufname); } if (pb == NULL) { cmdq_error(item, "no buffer"); return (CMD_RETURN_ERROR); } paste_free(pb); return (CMD_RETURN_NORMAL); } if (args_has(args, 'n')) { if (pb == NULL) { if (bufname != NULL) { cmdq_error(item, "unknown buffer: %s", bufname); return (CMD_RETURN_ERROR); } pb = paste_get_top(&bufname); } if (pb == NULL) { cmdq_error(item, "no buffer"); return (CMD_RETURN_ERROR); } 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); } tmux-tmux-f222026/cmd-set-environment.c000066400000000000000000000061011511153563100200100ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 " variable [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); } tmux-tmux-f222026/cmd-set-option.c000066400000000000000000000141711511153563100167620ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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); } tmux-tmux-f222026/cmd-show-environment.c000066400000000000000000000074531511153563100202100ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 " [variable]", .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); } tmux-tmux-f222026/cmd-show-messages.c000066400000000000000000000055601511153563100174500ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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|CMD_CLIENT_CANFAIL, .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') && tc != NULL && 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); } tmux-tmux-f222026/cmd-show-options.c000066400000000000000000000150131511153563100173260ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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 " [hook]", .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); } tmux-tmux-f222026/cmd-show-prompt-history.c000066400000000000000000000060331511153563100206550ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2021 Anindya Mukherjee * * 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 /* * 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 prompt-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 prompt-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); } tmux-tmux-f222026/cmd-source-file.c000066400000000000000000000147011511153563100170750ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "tmux.h" /* * Sources a configuration file. */ #define CMD_SOURCE_FILE_DEPTH_LIMIT 50 static u_int cmd_source_file_depth; 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 = { "t:Fnqv", 1, -1, NULL }, .usage = "[-Fnqv] " CMD_TARGET_PANE_USAGE " path ...", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .flags = 0, .exec = cmd_source_file_exec }; 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) { struct client *c = cmdq_get_client(item); if (c == NULL) { cmd_source_file_depth--; log_debug("%s: depth now %u", __func__, cmd_source_file_depth); } else { c->source_file_depth--; log_debug("%s: depth now %u", __func__, c->source_file_depth); } 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; struct cmd_find_state *target = cmdq_get_target(item); if (!closed) return; if (error != 0) cmdq_error(item, "%s: %s", strerror(error), path); else if (bsize != 0) { if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after, target, 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 char * cmd_source_file_quote_for_glob(const char *path) { char *quoted = xmalloc(2 * strlen(path) + 1), *q = quoted; const char *p = path; while (*p != '\0') { if ((u_char)*p < 128 && !isalnum((u_char)*p) && *p != '/') *q++ = '\\'; *q++ = *p++; } *q = '\0'; return (quoted); } 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, parse_flags; u_int i, j; if (c == NULL) { if (cmd_source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) { cmdq_error(item, "too many nested files"); return (CMD_RETURN_ERROR); } cmd_source_file_depth++; log_debug("%s: depth now %u", __func__, cmd_source_file_depth); } else { if (c->source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) { cmdq_error(item, "too many nested files"); return (CMD_RETURN_ERROR); } c->source_file_depth++; log_debug("%s: depth now %u", __func__, c->source_file_depth); } 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 (c == NULL || ~c->flags & CLIENT_CONTROL) { parse_flags = cmd_get_parse_flags(self); if (args_has(args, 'v') || (parse_flags & CMD_PARSE_VERBOSE)) cdata->flags |= CMD_PARSE_VERBOSE; } cwd = cmd_source_file_quote_for_glob(server_client_get_cwd(c, NULL)); 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", error, path); 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); } tmux-tmux-f222026/cmd-split-window.c000066400000000000000000000122501511153563100173150ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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 [argument ...]]", .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, flags, input; const char *template; char *cause = NULL, *cp; struct args_value *av; u_int count = args_count(args), curval = 0; type = LAYOUT_TOPBOTTOM; if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; /* If the 'p' flag is dropped then this bit can be moved into 'l'. */ if (args_has(args, 'l') || args_has(args, 'p')) { if (args_has(args, 'f')) { if (type == LAYOUT_TOPBOTTOM) curval = w->sy; else curval = w->sx; } else { if (type == LAYOUT_TOPBOTTOM) curval = wp->sy; else curval = wp->sx; } } size = -1; if (args_has(args, 'l')) { size = args_percentage_and_expand(args, 'l', 0, INT_MAX, curval, item, &cause); } else if (args_has(args, 'p')) { size = args_strtonum_and_expand(args, 'p', 0, 100, item, &cause); if (cause == NULL) size = curval * size / 100; } if (cause != NULL) { cmdq_error(item, "size %s", cause); free(cause); return (CMD_RETURN_ERROR); } window_push_zoom(wp->window, 1, args_has(args, 'Z')); input = (args_has(args, 'I') && count == 0); 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); } tmux-tmux-f222026/cmd-swap-pane.c000066400000000000000000000107101511153563100165470ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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|PANE_THEMECHANGED); dst_wp->window = src_w; options_set_parent(dst_wp->options, src_w->options); dst_wp->flags |= (PANE_STYLECHANGED|PANE_THEMECHANGED); 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) { window_pane_stack_remove(&src_w->last_panes, src_wp); window_pane_stack_remove(&dst_w->last_panes, dst_wp); colour_palette_from_option(&src_wp->palette, src_wp->options); colour_palette_from_option(&dst_wp->palette, dst_wp->options); layout_fix_panes(src_w, NULL); server_redraw_window(src_w); } layout_fix_panes(dst_w, NULL); 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); } tmux-tmux-f222026/cmd-swap-window.c000066400000000000000000000053741511153563100171450ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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 (marked_pane.wl == wl_src) marked_pane.wl = wl_dst; 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); } tmux-tmux-f222026/cmd-switch-client.c000066400000000000000000000077421511153563100174440ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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' || strcmp(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); } tmux-tmux-f222026/cmd-unbind-key.c000066400000000000000000000052211511153563100167220ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } tmux-tmux-f222026/cmd-wait-for.c000066400000000000000000000144271511153563100164150ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2013 Nicholas Marriott * Copyright (c) 2013 Thiago de Arruda * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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); } } tmux-tmux-f222026/cmd.c000066400000000000000000000514641511153563100146710ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" 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_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_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; int parse_flags; 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; } /* Get parse flags for command. */ int cmd_get_parse_flags(struct cmd *cmd) { return (cmd->parse_flags); } /* 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. */ 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, int parse_flags, 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; cmd->parse_flags = parse_flags; 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(const 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(const 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 (m->wp == -1) wp = wl->window->active; else { if ((wp = window_pane_find_by_id(m->wp)) == NULL) return (NULL); if (!window_has_pane(wl->window, wp)) return (NULL); } if (wlp != NULL) *wlp = wl; 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); } tmux-tmux-f222026/colour.c000066400000000000000000000773411511153563100154330ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * Copyright (c) 2016 Avi Halachmi * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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 background colour to theme. */ enum client_theme colour_totheme(int c) { int r, g, b, brightness; if (c == -1) return (THEME_UNKNOWN); if (c & COLOUR_FLAG_RGB) { r = (c >> 16) & 0xff; g = (c >> 8) & 0xff; b = (c >> 0) & 0xff; brightness = r + g + b; if (brightness > 382) return (THEME_LIGHT); return (THEME_DARK); } if (c & COLOUR_FLAG_256) return (colour_totheme(colour_256toRGB(c))); switch (c) { case 0: case 90: return (THEME_DARK); case 7: case 97: return (THEME_LIGHT); default: if (c >= 0 && c <= 7) return (colour_totheme(colour_256toRGB(c))); if (c >= 90 && c <= 97) return (colour_totheme(colour_256toRGB(8 + c - 90))); break; } return (THEME_UNKNOWN); } /* 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; const char *errstr; if (strncasecmp(name, "grey", 4) == 0 || strncasecmp(name, "gray", 4) == 0) { if (name[4] == '\0') return (0xbebebe|COLOUR_FLAG_RGB); c = strtonum(name + 4, 0, 100, &errstr); if (errstr != NULL) return (-1); c = round(2.55 * c); 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); } /* Parse colour from an X11 string. */ int colour_parseX11(const char *p) { double c, m, y, k = 0; u_int r, g, b; size_t len = strlen(p); int colour = -1; char *copy; if ((len == 12 && sscanf(p, "rgb:%02x/%02x/%02x", &r, &g, &b) == 3) || (len == 7 && sscanf(p, "#%02x%02x%02x", &r, &g, &b) == 3) || sscanf(p, "%d,%d,%d", &r, &g, &b) == 3) colour = colour_join_rgb(r, g, b); else if ((len == 18 && sscanf(p, "rgb:%04x/%04x/%04x", &r, &g, &b) == 3) || (len == 13 && sscanf(p, "#%04x%04x%04x", &r, &g, &b) == 3)) colour = colour_join_rgb(r >> 8, g >> 8, b >> 8); else if ((sscanf(p, "cmyk:%lf/%lf/%lf/%lf", &c, &m, &y, &k) == 4 || sscanf(p, "cmy:%lf/%lf/%lf", &c, &m, &y) == 3) && c >= 0 && c <= 1 && m >= 0 && m <= 1 && y >= 0 && y <= 1 && k >= 0 && k <= 1) { colour = colour_join_rgb( (1 - c) * (1 - k) * 255, (1 - m) * (1 - k) * 255, (1 - y) * (1 - k) * 255); } else { while (len != 0 && *p == ' ') { p++; len--; } while (len != 0 && p[len - 1] == ' ') len--; copy = xstrndup(p, len); colour = colour_byname(copy); free(copy); } log_debug("%s: %s = %s", __func__, p, colour_tostring(colour)); return (colour); } /* Initialize palette. */ void colour_palette_init(struct colour_palette *p) { 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); } } tmux-tmux-f222026/compat.h000066400000000000000000000227161511153563100154140ustar00rootroot00000000000000/* * Copyright (c) 2007 Nicholas Marriott * * 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 #include #include #include #include #include #include #include #ifdef HAVE_EVENT2_EVENT_H #include #include #include #include #include #include #include #include #else #include #ifndef EVBUFFER_EOL_LF /* * This doesn't really work because evbuffer_readline is broken, but gets us to * build with very old (older than 1.4.14) libevent. */ #define EVBUFFER_EOL_LF #define evbuffer_readln(a, b, c) evbuffer_readline(a) #endif #endif #ifdef HAVE_MALLOC_TRIM #include #endif #ifdef HAVE_UTF8PROC #include #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 #endif #ifdef HAVE_ERR_H #include #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 #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 #else #include #endif #ifdef HAVE_QUEUE_H #include #else #include "compat/queue.h" #endif #ifdef HAVE_TREE_H #include #else #include "compat/tree.h" #endif #ifdef HAVE_BITSTRING_H #include #else #include "compat/bitstring.h" #endif #ifdef HAVE_LIBUTIL_H #include #endif #ifdef HAVE_PTY_H #include #endif #ifdef HAVE_UTIL_H #include #endif #ifdef HAVE_VIS #include #else #include "compat/vis.h" #endif #ifdef HAVE_IMSG #include #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_GETDTABLESIZE /* getdtablesize.c */ int getdtablesize(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_HTONLL /* htonll.c */ #undef htonll uint64_t htonll(uint64_t); #endif #ifndef HAVE_NTOHLL /* ntohll.c */ #undef ntohll uint64_t ntohll(uint64_t); #endif #ifndef HAVE_GETPEEREID /* getpeereid.c */ int getpeereid(int, uid_t *, gid_t *); #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_activated(void); int systemd_create_socket(int, char **); int systemd_move_to_new_cgroup(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 */ tmux-tmux-f222026/compat/000077500000000000000000000000001511153563100152335ustar00rootroot00000000000000tmux-tmux-f222026/compat/asprintf.c000066400000000000000000000026611511153563100172320ustar00rootroot00000000000000/* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/base64.c000066400000000000000000000242471511153563100164740ustar00rootroot00000000000000/* $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 #include #include #include #include #include #include #include #include #include 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); } tmux-tmux-f222026/compat/bitstring.h000066400000000000000000000103361511153563100174140ustar00rootroot00000000000000/* $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_ */ tmux-tmux-f222026/compat/cfmakeraw.c000066400000000000000000000022421511153563100173370ustar00rootroot00000000000000/* * Copyright (c) 2013 Dagobert Michelsen * Copyright (c) 2013 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "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; } tmux-tmux-f222026/compat/clock_gettime.c000066400000000000000000000022551511153563100202140ustar00rootroot00000000000000/* * Copyright (c) 2021 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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(__unused int clock, struct timespec *ts) { struct timeval tv; gettimeofday(&tv, NULL); TIMEVAL_TO_TIMESPEC(&tv, ts); return 0; } tmux-tmux-f222026/compat/closefrom.c000066400000000000000000000074761511153563100174060ustar00rootroot00000000000000/* * Copyright (c) 2004-2005 Todd C. Miller * * 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 #include #include #include #ifdef HAVE_FCNTL_H # include #endif #include #include #include #include #include #ifdef HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # ifdef HAVE_SYS_NDIR_H # include # endif # ifdef HAVE_SYS_DIR_H # include # endif # ifdef HAVE_NDIR_H # include # endif #endif #if defined(HAVE_LIBPROC_H) # include #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 */ tmux-tmux-f222026/compat/daemon-darwin.c000066400000000000000000000060261511153563100201300ustar00rootroot00000000000000/* * Copyright (c) 2017 Nicholas Marriott * * 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 * 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 #include #include #include void daemon_darwin(void); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 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 tmux-tmux-f222026/compat/daemon.c000066400000000000000000000043621511153563100166470ustar00rootroot00000000000000/* $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 #include #include #include #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); } tmux-tmux-f222026/compat/err.c000066400000000000000000000036441511153563100161760ustar00rootroot00000000000000/* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/explicit_bzero.c000066400000000000000000000003621511153563100204220ustar00rootroot00000000000000/* $OpenBSD: explicit_bzero.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */ /* * Public domain. * Written by Matthew Dempsky. */ #include #include "compat.h" void explicit_bzero(void *buf, size_t len) { memset(buf, 0, len); } tmux-tmux-f222026/compat/fdforkpty.c000066400000000000000000000020621511153563100174070ustar00rootroot00000000000000/* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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)); } tmux-tmux-f222026/compat/fgetln.c000066400000000000000000000027311511153563100166610ustar00rootroot00000000000000/* * Copyright (c) 2015 Joerg Jung * * 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 #include #include #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; } tmux-tmux-f222026/compat/forkpty-aix.c000066400000000000000000000050431511153563100176560ustar00rootroot00000000000000/* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/forkpty-haiku.c000066400000000000000000000037261511153563100202040ustar00rootroot00000000000000/* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/forkpty-hpux.c000066400000000000000000000042161511153563100200620ustar00rootroot00000000000000/* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/forkpty-sunos.c000066400000000000000000000042701511153563100202450ustar00rootroot00000000000000/* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/compat/freezero.c000066400000000000000000000017511511153563100172240ustar00rootroot00000000000000/* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "compat.h" void freezero(void *ptr, size_t size) { if (ptr != NULL) { memset(ptr, 0, size); free(ptr); } } tmux-tmux-f222026/compat/getdtablecount.c000066400000000000000000000030761511153563100204110ustar00rootroot00000000000000/* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #if defined(HAVE_LIBPROC_H) #include #endif #include "compat.h" void fatal(const char *, ...); void fatalx(const char *, ...); #if defined(HAVE_LIBPROC_H) && defined(HAVE_PROC_PIDINFO) int getdtablecount(void) { int sz; pid_t pid = getpid(); sz = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); if (sz == -1) return (0); return (sz / PROC_PIDLISTFD_SIZE); } #elif defined(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 tmux-tmux-f222026/compat/getdtablesize.c000066400000000000000000000017071511153563100202320ustar00rootroot00000000000000/* * Copyright (c) 2020 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "compat.h" #ifdef HAVE_SYSCONF int getdtablesize(void) { return (sysconf(_SC_OPEN_MAX)); } #endif tmux-tmux-f222026/compat/getline.c000066400000000000000000000052261511153563100170330ustar00rootroot00000000000000/* $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 #include #include #include #include #include #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); } tmux-tmux-f222026/compat/getopt_long.c000066400000000000000000000407611511153563100177300ustar00rootroot00000000000000/* This file is obtained from OpenSSH: * Repository: https://github.com/openssh/openssh-portable * Commit: b5b405fee7f3e79d44e2d2971a4b6b4cc53f112e * File: /openbsd-compat/getopt_long.c */ /* * Copyright (c) 2002 Todd C. Miller * * 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. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ /*- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * 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. */ /* OPENBSD ORIGINAL: lib/libc/stdlib/getopt_long.c */ #include "compat.h" /* The following macro constants are taken from getopt.h of OpenSSH: * Repository: https://github.com/openssh/openssh-portable * Commit: b5b405fee7f3e79d44e2d2971a4b6b4cc53f112e * File: /openbsd-compat/getopt.h * * ---- BEGIN - Copyright notice and license of getopt.h ---- * Copyright (c) 2000 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * 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. * ---- END ---- */ #define no_argument 0 #define required_argument 1 #define optional_argument 2 #if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_OPTRESET) #if 0 #include #include #endif #include #include #include #include struct option { /* name of long option */ const char *name; /* * one of no_argument, required_argument, and optional_argument: * whether option takes an argument */ int has_arg; /* if not NULL, set *flag to val when option found */ int *flag; /* if flag not NULL, value to set *flag to; else return value */ int val; }; int opterr = 1; /* if error message should be printed */ int optind = 1; /* index into parent argv vector */ int optopt = '?'; /* character checked for validity */ int optreset; /* reset getopt */ char *optarg; /* argument associated with option */ #define PRINT_ERROR ((opterr) && (*options != ':')) #define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ #define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ /* return values */ #define BADCH (int)'?' #define BADARG ((*options == ':') ? (int)':' : (int)'?') #define INORDER (int)1 #define EMSG (char *)"" static int getopt_internal(int, char * const *, const char *, const struct option *, int *, int); static int parse_long_options(char * const *, const char *, const struct option *, int *, int); static int gcd(int, int); static void permute_args(int, int, int, char * const *); static char *place = EMSG; /* option letter processing */ /* XXX: set optreset to 1 rather than these two */ static int nonopt_start = -1; /* first non option argument (for permute) */ static int nonopt_end = -1; /* first option after non options (for permute) */ /* Error messages */ static const char recargchar[] = "option requires an argument -- %c"; static const char recargstring[] = "option requires an argument -- %s"; static const char ambig[] = "ambiguous option -- %.*s"; static const char noarg[] = "option doesn't take an argument -- %.*s"; static const char illoptchar[] = "unknown option -- %c"; static const char illoptstring[] = "unknown option -- %s"; /* * Compute the greatest common divisor of a and b. */ static int gcd(int a, int b) { int c; c = a % b; while (c != 0) { a = b; b = c; c = a % b; } return (b); } /* * Exchange the block from nonopt_start to nonopt_end with the block * from nonopt_end to opt_end (keeping the same order of arguments * in each block). */ static void permute_args(int panonopt_start, int panonopt_end, int opt_end, char * const *nargv) { int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; char *swap; /* * compute lengths of blocks and number and size of cycles */ nnonopts = panonopt_end - panonopt_start; nopts = opt_end - panonopt_end; ncycle = gcd(nnonopts, nopts); cyclelen = (opt_end - panonopt_start) / ncycle; for (i = 0; i < ncycle; i++) { cstart = panonopt_end+i; pos = cstart; for (j = 0; j < cyclelen; j++) { if (pos >= panonopt_end) pos -= nnonopts; else pos += nopts; swap = nargv[pos]; /* LINTED const cast */ ((char **) nargv)[pos] = nargv[cstart]; /* LINTED const cast */ ((char **)nargv)[cstart] = swap; } } } /* * parse_long_options -- * Parse long options in argc/argv argument vector. * Returns -1 if short_too is set and the option does not match long_options. */ static int parse_long_options(char * const *nargv, const char *options, const struct option *long_options, int *idx, int short_too) { char *current_argv, *has_equal; size_t current_argv_len; int i, match; current_argv = place; match = -1; optind++; if ((has_equal = strchr(current_argv, '=')) != NULL) { /* argument found (--option=arg) */ current_argv_len = has_equal - current_argv; has_equal++; } else current_argv_len = strlen(current_argv); for (i = 0; long_options[i].name; i++) { /* find matching long option */ if (strncmp(current_argv, long_options[i].name, current_argv_len)) continue; if (strlen(long_options[i].name) == current_argv_len) { /* exact match */ match = i; break; } /* * If this is a known short option, don't allow * a partial match of a single character. */ if (short_too && current_argv_len == 1) continue; if (match == -1) /* partial match */ match = i; else { /* ambiguous abbreviation */ if (PRINT_ERROR) warnx(ambig, (int)current_argv_len, current_argv); optopt = 0; return (BADCH); } } if (match != -1) { /* option found */ if (long_options[match].has_arg == no_argument && has_equal) { if (PRINT_ERROR) warnx(noarg, (int)current_argv_len, current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; return (BADARG); } if (long_options[match].has_arg == required_argument || long_options[match].has_arg == optional_argument) { if (has_equal) optarg = has_equal; else if (long_options[match].has_arg == required_argument) { /* * optional argument doesn't use next nargv */ optarg = nargv[optind++]; } } if ((long_options[match].has_arg == required_argument) && (optarg == NULL)) { /* * Missing argument; leading ':' indicates no error * should be generated. */ if (PRINT_ERROR) warnx(recargstring, current_argv); /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) optopt = long_options[match].val; else optopt = 0; --optind; return (BADARG); } } else { /* unknown option */ if (short_too) { --optind; return (-1); } if (PRINT_ERROR) warnx(illoptstring, current_argv); optopt = 0; return (BADCH); } if (idx) *idx = match; if (long_options[match].flag) { *long_options[match].flag = long_options[match].val; return (0); } else return (long_options[match].val); } /* * getopt_internal -- * Parse argc/argv argument vector. Called by user level routines. */ static int getopt_internal(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx, int flags) { char *oli; /* option letter list index */ int optchar, short_too; static int posixly_correct = -1; if (options == NULL) return (-1); /* * XXX Some GNU programs (like cvs) set optind to 0 instead of * XXX using optreset. Work around this braindamage. */ if (optind == 0) optind = optreset = 1; /* * Disable GNU extensions if POSIXLY_CORRECT is set or options * string begins with a '+'. */ if (posixly_correct == -1 || optreset) posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); if (*options == '-') flags |= FLAG_ALLARGS; else if (posixly_correct || *options == '+') flags &= ~FLAG_PERMUTE; if (*options == '+' || *options == '-') options++; optarg = NULL; if (optreset) nonopt_start = nonopt_end = -1; start: if (optreset || !*place) { /* update scanning pointer */ optreset = 0; if (optind >= nargc) { /* end of argument vector */ place = EMSG; if (nonopt_end != -1) { /* do permutation, if we have to */ permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } else if (nonopt_start != -1) { /* * If we skipped non-options, set optind * to the first of them. */ optind = nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } if (*(place = nargv[optind]) != '-' || (place[1] == '\0' && strchr(options, '-') == NULL)) { place = EMSG; /* found non-option */ if (flags & FLAG_ALLARGS) { /* * GNU extension: * return non-option as argument to option 1 */ optarg = nargv[optind++]; return (INORDER); } if (!(flags & FLAG_PERMUTE)) { /* * If no permutation wanted, stop parsing * at first non-option. */ return (-1); } /* do permutation */ if (nonopt_start == -1) nonopt_start = optind; else if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); nonopt_start = optind - (nonopt_end - nonopt_start); nonopt_end = -1; } optind++; /* process next argument */ goto start; } if (nonopt_start != -1 && nonopt_end == -1) nonopt_end = optind; /* * If we have "-" do nothing, if "--" we are done. */ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { optind++; place = EMSG; /* * We found an option (--), so if we skipped * non-options, we have to permute. */ if (nonopt_end != -1) { permute_args(nonopt_start, nonopt_end, optind, nargv); optind -= nonopt_end - nonopt_start; } nonopt_start = nonopt_end = -1; return (-1); } } /* * Check long options if: * 1) we were passed some * 2) the arg is not just "-" * 3) either the arg starts with -- we are getopt_long_only() */ if (long_options != NULL && place != nargv[optind] && (*place == '-' || (flags & FLAG_LONGONLY))) { short_too = 0; if (*place == '-') place++; /* --foo long option */ else if (*place != ':' && strchr(options, *place) != NULL) short_too = 1; /* could be short option too */ optchar = parse_long_options(nargv, options, long_options, idx, short_too); if (optchar != -1) { place = EMSG; return (optchar); } } if ((optchar = (int)*place++) == (int)':' || (optchar == (int)'-' && *place != '\0') || (oli = strchr(options, optchar)) == NULL) { /* * If the user specified "-" and '-' isn't listed in * options, return -1 (non-option) as per POSIX. * Otherwise, it is an unknown option character (or ':'). */ if (optchar == (int)'-' && *place == '\0') return (-1); if (!*place) ++optind; if (PRINT_ERROR) warnx(illoptchar, optchar); optopt = optchar; return (BADCH); } if (long_options != NULL && optchar == 'W' && oli[1] == ';') { /* -W long-option */ if (*place) /* no space */ /* NOTHING */; else if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else /* white space */ place = nargv[optind]; optchar = parse_long_options(nargv, options, long_options, idx, 0); place = EMSG; return (optchar); } if (*++oli != ':') { /* doesn't take argument */ if (!*place) ++optind; } else { /* takes (optional) argument */ optarg = NULL; if (*place) /* no white space */ optarg = place; else if (oli[1] != ':') { /* arg not optional */ if (++optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) warnx(recargchar, optchar); optopt = optchar; return (BADARG); } else optarg = nargv[optind]; } place = EMSG; ++optind; } /* dump back option letter */ return (optchar); } /* * getopt -- * Parse argc/argv argument vector. * * [eventually this will replace the BSD getopt] */ int getopt(int nargc, char * const *nargv, const char *options) { /* * We don't pass FLAG_PERMUTE to getopt_internal() since * the BSD getopt(3) (unlike GNU) has never done this. * * Furthermore, since many privileged programs call getopt() * before dropping privileges it makes sense to keep things * as simple (and bug-free) as possible. */ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); } #if 0 /* * getopt_long -- * Parse argc/argv argument vector. */ int getopt_long(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE)); } /* * getopt_long_only -- * Parse argc/argv argument vector. */ int getopt_long_only(int nargc, char * const *nargv, const char *options, const struct option *long_options, int *idx) { return (getopt_internal(nargc, nargv, options, long_options, idx, FLAG_PERMUTE|FLAG_LONGONLY)); } #endif #endif /* !defined(HAVE_GETOPT) || !defined(HAVE_OPTRESET) */ tmux-tmux-f222026/compat/getpeereid.c000066400000000000000000000031131511153563100175120ustar00rootroot00000000000000/* * Copyright (c) 2022 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #ifdef HAVE_UCRED_H #include #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 *uid = geteuid(); *gid = getegid(); return (0); #endif } tmux-tmux-f222026/compat/getprogname.c000066400000000000000000000022371511153563100177130ustar00rootroot00000000000000/* * Copyright (c) 2016 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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 tmux-tmux-f222026/compat/htonll.c000066400000000000000000000020171511153563100166770ustar00rootroot00000000000000/* * Copyright (c) 2024 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "compat.h" uint64_t htonll(uint64_t v) { uint32_t b; uint32_t t; b = htonl (v & 0xffffffff); t = htonl (v >> 32); return ((uint64_t)b << 32 | t); } tmux-tmux-f222026/compat/imsg-buffer.c000066400000000000000000000457601511153563100176210ustar00rootroot00000000000000/* $OpenBSD: imsg-buffer.c,v 1.35 2025/06/04 09:06:56 claudio Exp $ */ /* * Copyright (c) 2023 Claudio Jeker * Copyright (c) 2003, 2004 Henning Brauer * * 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 #include #include #include #include #include #include #include #include #include #include "compat.h" #include "imsg.h" #undef htobe16 #define htobe16 htons #undef htobe32 #define htobe32 htonl #undef htobe64 #define htobe64 htonll #undef be16toh #define be16toh ntohs #undef be32toh #define be32toh ntohl #undef be64toh #define be64toh ntohll struct ibufqueue { TAILQ_HEAD(, ibuf) bufs; uint32_t queued; }; struct msgbuf { struct ibufqueue bufs; struct ibufqueue rbufs; char *rbuf; struct ibuf *rpmsg; struct ibuf *(*readhdr)(struct ibuf *, void *, int *); void *rarg; size_t roff; size_t hdrsize; }; static void msgbuf_drain(struct msgbuf *, size_t); static void ibufq_init(struct ibufqueue *); #define IBUF_FD_MARK_ON_STACK -2 struct ibuf * ibuf_open(size_t len) { struct ibuf *buf; if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) return (NULL); if (len > 0) { if ((buf->buf = calloc(len, 1)) == 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 == 0 || max < len) { errno = EINVAL; return (NULL); } if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) return (NULL); if (len > 0) { if ((buf->buf = calloc(len, 1)) == NULL) { free(buf); return (NULL); } } buf->size = len; buf->max = max; buf->fd = -1; return (buf); } void * ibuf_reserve(struct ibuf *buf, size_t len) { void *b; if (len > SIZE_MAX - buf->wpos) { errno = ERANGE; return (NULL); } if (buf->fd == IBUF_FD_MARK_ON_STACK) { /* can not grow stack buffers */ errno = EINVAL; return (NULL); } if (buf->wpos + len > buf->size) { unsigned char *nb; /* check if buffer is allowed to grow */ if (buf->wpos + len > buf->max) { errno = ERANGE; return (NULL); } nb = realloc(buf->buf, buf->wpos + len); if (nb == NULL) return (NULL); memset(nb + buf->size, 0, buf->wpos + len - buf->size); buf->buf = nb; buf->size = buf->wpos + len; } b = buf->buf + buf->wpos; buf->wpos += len; return (b); } int ibuf_add(struct ibuf *buf, const void *data, size_t len) { void *b; if (len == 0) return (0); if ((b = ibuf_reserve(buf, len)) == NULL) return (-1); memcpy(b, data, len); return (0); } int ibuf_add_ibuf(struct ibuf *buf, const struct ibuf *from) { return ibuf_add(buf, ibuf_data(from), ibuf_size(from)); } int ibuf_add_n8(struct ibuf *buf, uint64_t value) { uint8_t v; if (value > UINT8_MAX) { errno = EINVAL; return (-1); } v = value; return ibuf_add(buf, &v, sizeof(v)); } int ibuf_add_n16(struct ibuf *buf, uint64_t value) { uint16_t v; if (value > UINT16_MAX) { errno = EINVAL; return (-1); } v = htobe16(value); return ibuf_add(buf, &v, sizeof(v)); } int ibuf_add_n32(struct ibuf *buf, uint64_t value) { uint32_t v; if (value > UINT32_MAX) { errno = EINVAL; return (-1); } v = htobe32(value); return ibuf_add(buf, &v, sizeof(v)); } int ibuf_add_n64(struct ibuf *buf, uint64_t value) { value = htobe64(value); return ibuf_add(buf, &value, sizeof(value)); } int ibuf_add_h16(struct ibuf *buf, uint64_t value) { uint16_t v; if (value > UINT16_MAX) { errno = EINVAL; return (-1); } v = value; return ibuf_add(buf, &v, sizeof(v)); } int ibuf_add_h32(struct ibuf *buf, uint64_t value) { uint32_t v; if (value > UINT32_MAX) { errno = EINVAL; return (-1); } v = value; return ibuf_add(buf, &v, sizeof(v)); } int ibuf_add_h64(struct ibuf *buf, uint64_t value) { return ibuf_add(buf, &value, sizeof(value)); } int ibuf_add_zero(struct ibuf *buf, size_t len) { void *b; if (len == 0) return (0); if ((b = ibuf_reserve(buf, len)) == NULL) return (-1); memset(b, 0, len); return (0); } int ibuf_add_strbuf(struct ibuf *buf, const char *str, size_t len) { char *b; size_t n; if ((b = ibuf_reserve(buf, len)) == NULL) return (-1); n = strlcpy(b, str, len); if (n >= len) { /* also covers the case where len == 0 */ errno = EOVERFLOW; return (-1); } memset(b + n, 0, len - n); return (0); } void * ibuf_seek(struct ibuf *buf, size_t pos, size_t len) { /* only allow seeking between rpos and wpos */ if (ibuf_size(buf) < pos || SIZE_MAX - pos < len || ibuf_size(buf) < pos + len) { errno = ERANGE; return (NULL); } return (buf->buf + buf->rpos + pos); } int ibuf_set(struct ibuf *buf, size_t pos, const void *data, size_t len) { void *b; if ((b = ibuf_seek(buf, pos, len)) == NULL) return (-1); if (len == 0) return (0); memcpy(b, data, len); return (0); } int ibuf_set_n8(struct ibuf *buf, size_t pos, uint64_t value) { uint8_t v; if (value > UINT8_MAX) { errno = EINVAL; return (-1); } v = value; return (ibuf_set(buf, pos, &v, sizeof(v))); } int ibuf_set_n16(struct ibuf *buf, size_t pos, uint64_t value) { uint16_t v; if (value > UINT16_MAX) { errno = EINVAL; return (-1); } v = htobe16(value); return (ibuf_set(buf, pos, &v, sizeof(v))); } int ibuf_set_n32(struct ibuf *buf, size_t pos, uint64_t value) { uint32_t v; if (value > UINT32_MAX) { errno = EINVAL; return (-1); } v = htobe32(value); return (ibuf_set(buf, pos, &v, sizeof(v))); } int ibuf_set_n64(struct ibuf *buf, size_t pos, uint64_t value) { value = htobe64(value); return (ibuf_set(buf, pos, &value, sizeof(value))); } int ibuf_set_h16(struct ibuf *buf, size_t pos, uint64_t value) { uint16_t v; if (value > UINT16_MAX) { errno = EINVAL; return (-1); } v = value; return (ibuf_set(buf, pos, &v, sizeof(v))); } int ibuf_set_h32(struct ibuf *buf, size_t pos, uint64_t value) { uint32_t v; if (value > UINT32_MAX) { errno = EINVAL; return (-1); } v = value; return (ibuf_set(buf, pos, &v, sizeof(v))); } int ibuf_set_h64(struct ibuf *buf, size_t pos, uint64_t value) { return (ibuf_set(buf, pos, &value, sizeof(value))); } int ibuf_set_maxsize(struct ibuf *buf, size_t max) { if (buf->fd == IBUF_FD_MARK_ON_STACK) { /* can't fiddle with stack buffers */ errno = EINVAL; return (-1); } if (max > buf->max) { errno = ERANGE; return (-1); } buf->max = max; return (0); } void * ibuf_data(const struct ibuf *buf) { return (buf->buf + buf->rpos); } size_t ibuf_size(const struct ibuf *buf) { return (buf->wpos - buf->rpos); } size_t ibuf_left(const struct ibuf *buf) { /* on stack buffers have no space left */ if (buf->fd == IBUF_FD_MARK_ON_STACK) return (0); return (buf->max - buf->wpos); } int ibuf_truncate(struct ibuf *buf, size_t len) { if (ibuf_size(buf) >= len) { buf->wpos = buf->rpos + len; return (0); } if (buf->fd == IBUF_FD_MARK_ON_STACK) { /* only allow to truncate down for stack buffers */ errno = ERANGE; return (-1); } return ibuf_add_zero(buf, len - ibuf_size(buf)); } void ibuf_rewind(struct ibuf *buf) { buf->rpos = 0; } void ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) { ibufq_push(&msgbuf->bufs, buf); } void ibuf_from_buffer(struct ibuf *buf, void *data, size_t len) { memset(buf, 0, sizeof(*buf)); buf->buf = data; buf->size = buf->wpos = len; buf->fd = IBUF_FD_MARK_ON_STACK; } void ibuf_from_ibuf(struct ibuf *buf, const struct ibuf *from) { ibuf_from_buffer(buf, ibuf_data(from), ibuf_size(from)); } int ibuf_get(struct ibuf *buf, void *data, size_t len) { if (ibuf_size(buf) < len) { errno = EBADMSG; return (-1); } memcpy(data, ibuf_data(buf), len); buf->rpos += len; return (0); } int ibuf_get_ibuf(struct ibuf *buf, size_t len, struct ibuf *new) { if (ibuf_size(buf) < len) { errno = EBADMSG; return (-1); } ibuf_from_buffer(new, ibuf_data(buf), len); buf->rpos += len; return (0); } int ibuf_get_h16(struct ibuf *buf, uint16_t *value) { return ibuf_get(buf, value, sizeof(*value)); } int ibuf_get_h32(struct ibuf *buf, uint32_t *value) { return ibuf_get(buf, value, sizeof(*value)); } int ibuf_get_h64(struct ibuf *buf, uint64_t *value) { return ibuf_get(buf, value, sizeof(*value)); } int ibuf_get_n8(struct ibuf *buf, uint8_t *value) { return ibuf_get(buf, value, sizeof(*value)); } int ibuf_get_n16(struct ibuf *buf, uint16_t *value) { int rv; rv = ibuf_get(buf, value, sizeof(*value)); *value = be16toh(*value); return (rv); } int ibuf_get_n32(struct ibuf *buf, uint32_t *value) { int rv; rv = ibuf_get(buf, value, sizeof(*value)); *value = be32toh(*value); return (rv); } int ibuf_get_n64(struct ibuf *buf, uint64_t *value) { int rv; rv = ibuf_get(buf, value, sizeof(*value)); *value = be64toh(*value); return (rv); } char * ibuf_get_string(struct ibuf *buf, size_t len) { char *str; if (ibuf_size(buf) < len) { errno = EBADMSG; return (NULL); } str = strndup(ibuf_data(buf), len); if (str == NULL) return (NULL); buf->rpos += len; return (str); } int ibuf_get_strbuf(struct ibuf *buf, char *str, size_t len) { if (len == 0) { errno = EINVAL; return (-1); } if (ibuf_get(buf, str, len) == -1) return -1; if (str[len - 1] != '\0') { str[len - 1] = '\0'; errno = EOVERFLOW; return -1; } return 0; } int ibuf_skip(struct ibuf *buf, size_t len) { if (ibuf_size(buf) < len) { errno = EBADMSG; return (-1); } buf->rpos += len; return (0); } void ibuf_free(struct ibuf *buf) { int save_errno = errno; if (buf == NULL) return; /* if buf lives on the stack abort before causing more harm */ if (buf->fd == IBUF_FD_MARK_ON_STACK) abort(); if (buf->fd >= 0) close(buf->fd); freezero(buf->buf, buf->size); free(buf); errno = save_errno; } int ibuf_fd_avail(struct ibuf *buf) { return (buf->fd >= 0); } int ibuf_fd_get(struct ibuf *buf) { int fd; /* negative fds are internal use and equivalent to -1 */ if (buf->fd < 0) return (-1); fd = buf->fd; buf->fd = -1; return (fd); } void ibuf_fd_set(struct ibuf *buf, int fd) { /* if buf lives on the stack abort before causing more harm */ if (buf->fd == IBUF_FD_MARK_ON_STACK) abort(); if (buf->fd >= 0) close(buf->fd); buf->fd = -1; if (fd >= 0) buf->fd = fd; } struct msgbuf * msgbuf_new(void) { struct msgbuf *msgbuf; if ((msgbuf = calloc(1, sizeof(*msgbuf))) == NULL) return (NULL); ibufq_init(&msgbuf->bufs); ibufq_init(&msgbuf->rbufs); return msgbuf; } struct msgbuf * msgbuf_new_reader(size_t hdrsz, struct ibuf *(*readhdr)(struct ibuf *, void *, int *), void *arg) { struct msgbuf *msgbuf; char *buf; if (hdrsz == 0 || hdrsz > IBUF_READ_SIZE / 2) { errno = EINVAL; return (NULL); } if ((buf = malloc(IBUF_READ_SIZE)) == NULL) return (NULL); msgbuf = msgbuf_new(); if (msgbuf == NULL) { free(buf); return (NULL); } msgbuf->rbuf = buf; msgbuf->hdrsize = hdrsz; msgbuf->readhdr = readhdr; msgbuf->rarg = arg; return (msgbuf); } void msgbuf_free(struct msgbuf *msgbuf) { if (msgbuf == NULL) return; msgbuf_clear(msgbuf); free(msgbuf->rbuf); free(msgbuf); } uint32_t msgbuf_queuelen(struct msgbuf *msgbuf) { return ibufq_queuelen(&msgbuf->bufs); } void msgbuf_clear(struct msgbuf *msgbuf) { struct ibuf *buf; /* write side */ ibufq_flush(&msgbuf->bufs); /* read side */ ibufq_flush(&msgbuf->rbufs); msgbuf->roff = 0; ibuf_free(msgbuf->rpmsg); msgbuf->rpmsg = NULL; } struct ibuf * msgbuf_get(struct msgbuf *msgbuf) { return ibufq_pop(&msgbuf->rbufs); } void msgbuf_concat(struct msgbuf *msgbuf, struct ibufqueue *from) { ibufq_concat(&msgbuf->bufs, from); } int ibuf_write(int fd, 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.bufs, entry) { if (i >= IOV_MAX) break; iov[i].iov_base = ibuf_data(buf); iov[i].iov_len = ibuf_size(buf); i++; } if (i == 0) return (0); /* nothing queued */ again: if ((n = writev(fd, iov, i)) == -1) { if (errno == EINTR) goto again; if (errno == EAGAIN || errno == ENOBUFS) /* lets retry later again */ return (0); return (-1); } msgbuf_drain(msgbuf, n); return (0); } int msgbuf_write(int fd, struct msgbuf *msgbuf) { struct iovec iov[IOV_MAX]; struct ibuf *buf, *buf0 = NULL; 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.bufs, entry) { if (i >= IOV_MAX) break; if (i > 0 && buf->fd != -1) break; iov[i].iov_base = ibuf_data(buf); iov[i].iov_len = ibuf_size(buf); i++; if (buf->fd != -1) buf0 = buf; } if (i == 0) return (0); /* nothing queued */ msg.msg_iov = iov; msg.msg_iovlen = i; if (buf0 != NULL) { msg.msg_control = (caddr_t)&cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; *(int *)CMSG_DATA(cmsg) = buf0->fd; } again: if ((n = sendmsg(fd, &msg, 0)) == -1) { if (errno == EINTR) goto again; if (errno == EAGAIN || errno == ENOBUFS) /* lets retry later again */ return (0); return (-1); } /* * assumption: fd got sent if sendmsg sent anything * this works because fds are passed one at a time */ if (buf0 != NULL) { close(buf0->fd); buf0->fd = -1; } msgbuf_drain(msgbuf, n); return (0); } static int ibuf_read_process(struct msgbuf *msgbuf, int fd) { struct ibuf rbuf, msg; ssize_t sz; ibuf_from_buffer(&rbuf, msgbuf->rbuf, msgbuf->roff); do { if (msgbuf->rpmsg == NULL) { if (ibuf_size(&rbuf) < msgbuf->hdrsize) break; /* get size from header */ ibuf_from_buffer(&msg, ibuf_data(&rbuf), msgbuf->hdrsize); if ((msgbuf->rpmsg = msgbuf->readhdr(&msg, msgbuf->rarg, &fd)) == NULL) goto fail; } if (ibuf_left(msgbuf->rpmsg) <= ibuf_size(&rbuf)) sz = ibuf_left(msgbuf->rpmsg); else sz = ibuf_size(&rbuf); /* neither call below can fail */ if (ibuf_get_ibuf(&rbuf, sz, &msg) == -1 || ibuf_add_ibuf(msgbuf->rpmsg, &msg) == -1) goto fail; if (ibuf_left(msgbuf->rpmsg) == 0) { ibufq_push(&msgbuf->rbufs, msgbuf->rpmsg); msgbuf->rpmsg = NULL; } } while (ibuf_size(&rbuf) > 0); if (ibuf_size(&rbuf) > 0) memmove(msgbuf->rbuf, ibuf_data(&rbuf), ibuf_size(&rbuf)); msgbuf->roff = ibuf_size(&rbuf); if (fd != -1) close(fd); return (1); fail: /* XXX how to properly clean up is unclear */ if (fd != -1) close(fd); return (-1); } int ibuf_read(int fd, struct msgbuf *msgbuf) { struct iovec iov; ssize_t n; if (msgbuf->rbuf == NULL) { errno = EINVAL; return (-1); } iov.iov_base = msgbuf->rbuf + msgbuf->roff; iov.iov_len = IBUF_READ_SIZE - msgbuf->roff; again: if ((n = readv(fd, &iov, 1)) == -1) { if (errno == EINTR) goto again; if (errno == EAGAIN) /* lets retry later again */ return (1); return (-1); } if (n == 0) /* connection closed */ return (0); msgbuf->roff += n; /* new data arrived, try to process it */ return (ibuf_read_process(msgbuf, -1)); } int msgbuf_read(int fd, struct msgbuf *msgbuf) { struct msghdr msg; struct cmsghdr *cmsg; union { struct cmsghdr hdr; char buf[CMSG_SPACE(sizeof(int) * 1)]; } cmsgbuf; struct iovec iov; ssize_t n; int fdpass = -1; if (msgbuf->rbuf == NULL) { errno = EINVAL; return (-1); } memset(&msg, 0, sizeof(msg)); memset(&cmsgbuf, 0, sizeof(cmsgbuf)); iov.iov_base = msgbuf->rbuf + msgbuf->roff; iov.iov_len = IBUF_READ_SIZE - msgbuf->roff; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); again: if ((n = recvmsg(fd, &msg, 0)) == -1) { if (errno == EINTR) goto again; if (errno == EMSGSIZE) /* * Not enough fd slots: fd passing failed, retry * to receive the message without fd. * imsg_get_fd() will return -1 in that case. */ goto again; if (errno == EAGAIN) /* lets retry later again */ return (1); return (-1); } if (n == 0) /* connection closed */ return (0); msgbuf->roff += 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, j, f; /* * 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++) { f = ((int *)CMSG_DATA(cmsg))[i]; if (i == 0) fdpass = f; else close(f); } } /* we do not handle other ctl data level */ } /* new data arrived, try to process it */ return (ibuf_read_process(msgbuf, fdpass)); } static void msgbuf_drain(struct msgbuf *msgbuf, size_t n) { struct ibuf *buf; while ((buf = TAILQ_FIRST(&msgbuf->bufs.bufs)) != NULL) { if (n >= ibuf_size(buf)) { n -= ibuf_size(buf); TAILQ_REMOVE(&msgbuf->bufs.bufs, buf, entry); msgbuf->bufs.queued--; ibuf_free(buf); } else { buf->rpos += n; return; } } } static void ibufq_init(struct ibufqueue *bufq) { TAILQ_INIT(&bufq->bufs); bufq->queued = 0; } struct ibufqueue * ibufq_new(void) { struct ibufqueue *bufq; if ((bufq = calloc(1, sizeof(*bufq))) == NULL) return NULL; ibufq_init(bufq); return bufq; } void ibufq_free(struct ibufqueue *bufq) { if (bufq == NULL) return; ibufq_flush(bufq); free(bufq); } struct ibuf * ibufq_pop(struct ibufqueue *bufq) { struct ibuf *buf; if ((buf = TAILQ_FIRST(&bufq->bufs)) == NULL) return NULL; TAILQ_REMOVE(&bufq->bufs, buf, entry); bufq->queued--; return buf; } void ibufq_push(struct ibufqueue *bufq, struct ibuf *buf) { /* if buf lives on the stack abort before causing more harm */ if (buf->fd == IBUF_FD_MARK_ON_STACK) abort(); TAILQ_INSERT_TAIL(&bufq->bufs, buf, entry); bufq->queued++; } uint32_t ibufq_queuelen(struct ibufqueue *bufq) { return (bufq->queued); } void ibufq_concat(struct ibufqueue *to, struct ibufqueue *from) { to->queued += from->queued; TAILQ_CONCAT(&to->bufs, &from->bufs, entry); from->queued = 0; } void ibufq_flush(struct ibufqueue *bufq) { struct ibuf *buf; while ((buf = TAILQ_FIRST(&bufq->bufs)) != NULL) { TAILQ_REMOVE(&bufq->bufs, buf, entry); ibuf_free(buf); } bufq->queued = 0; } tmux-tmux-f222026/compat/imsg.c000066400000000000000000000207501511153563100163420ustar00rootroot00000000000000/* $OpenBSD: imsg.c,v 1.42 2025/06/16 13:56:11 claudio Exp $ */ /* * Copyright (c) 2023 Claudio Jeker * Copyright (c) 2003, 2004 Henning Brauer * * 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 #include #include #include #include #include #include #include #include "compat.h" #include "imsg.h" #define IMSG_ALLOW_FDPASS 0x01 #define IMSG_FD_MARK 0x80000000U static struct ibuf *imsg_parse_hdr(struct ibuf *, void *, int *); int imsgbuf_init(struct imsgbuf *imsgbuf, int fd) { imsgbuf->w = msgbuf_new_reader(IMSG_HEADER_SIZE, imsg_parse_hdr, imsgbuf); if (imsgbuf->w == NULL) return (-1); imsgbuf->pid = getpid(); imsgbuf->maxsize = MAX_IMSGSIZE; imsgbuf->fd = fd; imsgbuf->flags = 0; return (0); } void imsgbuf_allow_fdpass(struct imsgbuf *imsgbuf) { imsgbuf->flags |= IMSG_ALLOW_FDPASS; } int imsgbuf_set_maxsize(struct imsgbuf *imsgbuf, uint32_t max) { if (max > UINT32_MAX - IMSG_HEADER_SIZE) { errno = ERANGE; return (-1); } max += IMSG_HEADER_SIZE; if (max & IMSG_FD_MARK) { errno = EINVAL; return (-1); } imsgbuf->maxsize = max; return (0); } int imsgbuf_read(struct imsgbuf *imsgbuf) { if (imsgbuf->flags & IMSG_ALLOW_FDPASS) return msgbuf_read(imsgbuf->fd, imsgbuf->w); else return ibuf_read(imsgbuf->fd, imsgbuf->w); } int imsgbuf_write(struct imsgbuf *imsgbuf) { if (imsgbuf->flags & IMSG_ALLOW_FDPASS) return msgbuf_write(imsgbuf->fd, imsgbuf->w); else return ibuf_write(imsgbuf->fd, imsgbuf->w); } int imsgbuf_flush(struct imsgbuf *imsgbuf) { while (imsgbuf_queuelen(imsgbuf) > 0) { if (imsgbuf_write(imsgbuf) == -1) return (-1); } return (0); } void imsgbuf_clear(struct imsgbuf *imsgbuf) { msgbuf_free(imsgbuf->w); imsgbuf->w = NULL; } uint32_t imsgbuf_queuelen(struct imsgbuf *imsgbuf) { return msgbuf_queuelen(imsgbuf->w); } int imsgbuf_get(struct imsgbuf *imsgbuf, struct imsg *imsg) { struct imsg m; struct ibuf *buf; if ((buf = msgbuf_get(imsgbuf->w)) == NULL) return (0); if (ibuf_get(buf, &m.hdr, sizeof(m.hdr)) == -1) return (-1); if (ibuf_size(buf)) m.data = ibuf_data(buf); else m.data = NULL; m.buf = buf; m.hdr.len &= ~IMSG_FD_MARK; *imsg = m; return (1); } ssize_t imsg_get(struct imsgbuf *imsgbuf, struct imsg *imsg) { int rv; if ((rv = imsgbuf_get(imsgbuf, imsg)) != 1) return rv; return (imsg_get_len(imsg) + IMSG_HEADER_SIZE); } int imsg_ibufq_pop(struct ibufqueue *bufq, struct imsg *imsg) { struct imsg m; struct ibuf *buf; if ((buf = ibufq_pop(bufq)) == NULL) return (0); if (ibuf_get(buf, &m.hdr, sizeof(m.hdr)) == -1) return (-1); if (ibuf_size(buf)) m.data = ibuf_data(buf); else m.data = NULL; m.buf = buf; m.hdr.len &= ~IMSG_FD_MARK; *imsg = m; return (1); } void imsg_ibufq_push(struct ibufqueue *bufq, struct imsg *imsg) { ibuf_rewind(imsg->buf); ibufq_push(bufq, imsg->buf); memset(imsg, 0, sizeof(*imsg)); } int imsg_get_ibuf(struct imsg *imsg, struct ibuf *ibuf) { if (ibuf_size(imsg->buf) == 0) { errno = EBADMSG; return (-1); } return ibuf_get_ibuf(imsg->buf, ibuf_size(imsg->buf), ibuf); } int imsg_get_data(struct imsg *imsg, void *data, size_t len) { if (len == 0) { errno = EINVAL; return (-1); } if (ibuf_size(imsg->buf) != len) { errno = EBADMSG; return (-1); } return ibuf_get(imsg->buf, data, len); } int imsg_get_buf(struct imsg *imsg, void *data, size_t len) { return ibuf_get(imsg->buf, data, len); } int imsg_get_strbuf(struct imsg *imsg, char *str, size_t len) { return ibuf_get_strbuf(imsg->buf, str, len); } int imsg_get_fd(struct imsg *imsg) { return ibuf_fd_get(imsg->buf); } uint32_t imsg_get_id(struct imsg *imsg) { return (imsg->hdr.peerid); } size_t imsg_get_len(struct imsg *imsg) { return ibuf_size(imsg->buf); } pid_t imsg_get_pid(struct imsg *imsg) { return (imsg->hdr.pid); } uint32_t imsg_get_type(struct imsg *imsg) { return (imsg->hdr.type); } int imsg_compose(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, int fd, const void *data, size_t datalen) { struct ibuf *wbuf; if ((wbuf = imsg_create(imsgbuf, type, id, pid, datalen)) == NULL) goto fail; if (ibuf_add(wbuf, data, datalen) == -1) goto fail; ibuf_fd_set(wbuf, fd); imsg_close(imsgbuf, wbuf); return (1); fail: ibuf_free(wbuf); return (-1); } int imsg_composev(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, int fd, const struct iovec *iov, int iovcnt) { struct ibuf *wbuf; int i; size_t datalen = 0; for (i = 0; i < iovcnt; i++) datalen += iov[i].iov_len; if ((wbuf = imsg_create(imsgbuf, type, id, pid, datalen)) == NULL) goto fail; for (i = 0; i < iovcnt; i++) if (ibuf_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) goto fail; ibuf_fd_set(wbuf, fd); imsg_close(imsgbuf, wbuf); return (1); fail: ibuf_free(wbuf); return (-1); } /* * Enqueue imsg with payload from ibuf buf. fd passing is not possible * with this function. */ int imsg_compose_ibuf(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, struct ibuf *buf) { struct ibuf *hdrbuf = NULL; struct imsg_hdr hdr; if (ibuf_size(buf) + IMSG_HEADER_SIZE > imsgbuf->maxsize) { errno = ERANGE; goto fail; } hdr.type = type; hdr.len = ibuf_size(buf) + IMSG_HEADER_SIZE; hdr.peerid = id; if ((hdr.pid = pid) == 0) hdr.pid = imsgbuf->pid; if ((hdrbuf = ibuf_open(IMSG_HEADER_SIZE)) == NULL) goto fail; if (ibuf_add(hdrbuf, &hdr, sizeof(hdr)) == -1) goto fail; ibuf_close(imsgbuf->w, hdrbuf); ibuf_close(imsgbuf->w, buf); return (1); fail: ibuf_free(buf); ibuf_free(hdrbuf); return (-1); } /* * Forward imsg to another channel. Any attached fd is closed. */ int imsg_forward(struct imsgbuf *imsgbuf, struct imsg *msg) { struct ibuf *wbuf; size_t len; ibuf_rewind(msg->buf); ibuf_skip(msg->buf, sizeof(msg->hdr)); len = ibuf_size(msg->buf); if ((wbuf = imsg_create(imsgbuf, msg->hdr.type, msg->hdr.peerid, msg->hdr.pid, len)) == NULL) return (-1); if (len != 0) { if (ibuf_add_ibuf(wbuf, msg->buf) == -1) { ibuf_free(wbuf); return (-1); } } imsg_close(imsgbuf, wbuf); return (1); } struct ibuf * imsg_create(struct imsgbuf *imsgbuf, uint32_t type, uint32_t id, pid_t pid, size_t datalen) { struct ibuf *wbuf; struct imsg_hdr hdr; datalen += IMSG_HEADER_SIZE; if (datalen > imsgbuf->maxsize) { errno = ERANGE; return (NULL); } hdr.len = 0; hdr.type = type; hdr.peerid = id; if ((hdr.pid = pid) == 0) hdr.pid = imsgbuf->pid; if ((wbuf = ibuf_dynamic(datalen, imsgbuf->maxsize)) == NULL) goto fail; if (ibuf_add(wbuf, &hdr, sizeof(hdr)) == -1) goto fail; return (wbuf); fail: ibuf_free(wbuf); return (NULL); } int imsg_add(struct ibuf *msg, const void *data, size_t datalen) { if (datalen) if (ibuf_add(msg, data, datalen) == -1) { ibuf_free(msg); return (-1); } return (datalen); } void imsg_close(struct imsgbuf *imsgbuf, struct ibuf *msg) { uint32_t len; len = ibuf_size(msg); if (ibuf_fd_avail(msg)) len |= IMSG_FD_MARK; (void)ibuf_set_h32(msg, offsetof(struct imsg_hdr, len), len); ibuf_close(imsgbuf->w, msg); } void imsg_free(struct imsg *imsg) { ibuf_free(imsg->buf); } int imsg_set_maxsize(struct ibuf *msg, size_t max) { if (max > UINT32_MAX - IMSG_HEADER_SIZE) { errno = ERANGE; return (-1); } return ibuf_set_maxsize(msg, max + IMSG_HEADER_SIZE); } static struct ibuf * imsg_parse_hdr(struct ibuf *buf, void *arg, int *fd) { struct imsgbuf *imsgbuf = arg; struct imsg_hdr hdr; struct ibuf *b; uint32_t len; if (ibuf_get(buf, &hdr, sizeof(hdr)) == -1) return (NULL); len = hdr.len & ~IMSG_FD_MARK; if (len < IMSG_HEADER_SIZE || len > imsgbuf->maxsize) { errno = ERANGE; return (NULL); } if ((b = ibuf_open(len)) == NULL) return (NULL); if (hdr.len & IMSG_FD_MARK) { ibuf_fd_set(b, *fd); *fd = -1; } return b; } tmux-tmux-f222026/compat/imsg.h000066400000000000000000000143731511153563100163530ustar00rootroot00000000000000/* $OpenBSD: imsg.h,v 1.24 2025/06/05 08:55:07 tb Exp $ */ /* * Copyright (c) 2023 Claudio Jeker * Copyright (c) 2006, 2007 Pierre-Yves Ritschard * Copyright (c) 2006, 2007, 2008 Reyk Floeter * Copyright (c) 2003, 2004 Henning Brauer * * 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 #include #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 ibufqueue; struct msgbuf; struct imsgbuf { struct msgbuf *w; pid_t pid; uint32_t maxsize; int fd; int flags; }; struct imsg_hdr { uint32_t type; uint32_t len; uint32_t peerid; uint32_t pid; }; struct imsg { struct imsg_hdr hdr; void *data; struct ibuf *buf; }; struct iovec; /* imsg-buffer.c */ struct ibuf *ibuf_open(size_t); struct ibuf *ibuf_dynamic(size_t, size_t); int ibuf_add(struct ibuf *, const void *, size_t); int ibuf_add_ibuf(struct ibuf *, const struct ibuf *); int ibuf_add_zero(struct ibuf *, size_t); int ibuf_add_n8(struct ibuf *, uint64_t); int ibuf_add_n16(struct ibuf *, uint64_t); int ibuf_add_n32(struct ibuf *, uint64_t); int ibuf_add_n64(struct ibuf *, uint64_t); int ibuf_add_h16(struct ibuf *, uint64_t); int ibuf_add_h32(struct ibuf *, uint64_t); int ibuf_add_h64(struct ibuf *, uint64_t); int ibuf_add_strbuf(struct ibuf *, const char *, size_t); void *ibuf_reserve(struct ibuf *, size_t); void *ibuf_seek(struct ibuf *, size_t, size_t); int ibuf_set(struct ibuf *, size_t, const void *, size_t); int ibuf_set_n8(struct ibuf *, size_t, uint64_t); int ibuf_set_n16(struct ibuf *, size_t, uint64_t); int ibuf_set_n32(struct ibuf *, size_t, uint64_t); int ibuf_set_n64(struct ibuf *, size_t, uint64_t); int ibuf_set_h16(struct ibuf *, size_t, uint64_t); int ibuf_set_h32(struct ibuf *, size_t, uint64_t); int ibuf_set_h64(struct ibuf *, size_t, uint64_t); int ibuf_set_maxsize(struct ibuf *, size_t); void *ibuf_data(const struct ibuf *); size_t ibuf_size(const struct ibuf *); size_t ibuf_left(const struct ibuf *); int ibuf_truncate(struct ibuf *, size_t); void ibuf_rewind(struct ibuf *); void ibuf_close(struct msgbuf *, struct ibuf *); void ibuf_from_buffer(struct ibuf *, void *, size_t); void ibuf_from_ibuf(struct ibuf *, const struct ibuf *); int ibuf_get(struct ibuf *, void *, size_t); int ibuf_get_ibuf(struct ibuf *, size_t, struct ibuf *); int ibuf_get_n8(struct ibuf *, uint8_t *); int ibuf_get_n16(struct ibuf *, uint16_t *); int ibuf_get_n32(struct ibuf *, uint32_t *); int ibuf_get_n64(struct ibuf *, uint64_t *); int ibuf_get_h16(struct ibuf *, uint16_t *); int ibuf_get_h32(struct ibuf *, uint32_t *); int ibuf_get_h64(struct ibuf *, uint64_t *); char *ibuf_get_string(struct ibuf *, size_t); int ibuf_get_strbuf(struct ibuf *, char *, size_t); int ibuf_skip(struct ibuf *, size_t); void ibuf_free(struct ibuf *); int ibuf_fd_avail(struct ibuf *); int ibuf_fd_get(struct ibuf *); void ibuf_fd_set(struct ibuf *, int); struct msgbuf *msgbuf_new(void); struct msgbuf *msgbuf_new_reader(size_t, struct ibuf *(*)(struct ibuf *, void *, int *), void *); void msgbuf_free(struct msgbuf *); void msgbuf_clear(struct msgbuf *); void msgbuf_concat(struct msgbuf *, struct ibufqueue *); uint32_t msgbuf_queuelen(struct msgbuf *); int ibuf_write(int, struct msgbuf *); int msgbuf_write(int, struct msgbuf *); int ibuf_read(int, struct msgbuf *); int msgbuf_read(int, struct msgbuf *); struct ibuf *msgbuf_get(struct msgbuf *); struct ibufqueue *ibufq_new(void); void ibufq_free(struct ibufqueue *); struct ibuf *ibufq_pop(struct ibufqueue *bufq); void ibufq_push(struct ibufqueue *, struct ibuf *); uint32_t ibufq_queuelen(struct ibufqueue *); void ibufq_concat(struct ibufqueue *, struct ibufqueue *); void ibufq_flush(struct ibufqueue *); /* imsg.c */ int imsgbuf_init(struct imsgbuf *, int); void imsgbuf_allow_fdpass(struct imsgbuf *imsgbuf); int imsgbuf_set_maxsize(struct imsgbuf *, uint32_t); int imsgbuf_read(struct imsgbuf *); int imsgbuf_write(struct imsgbuf *); int imsgbuf_flush(struct imsgbuf *); void imsgbuf_clear(struct imsgbuf *); uint32_t imsgbuf_queuelen(struct imsgbuf *); int imsgbuf_get(struct imsgbuf *, struct imsg *); ssize_t imsg_get(struct imsgbuf *, struct imsg *); int imsg_ibufq_pop(struct ibufqueue *, struct imsg *); void imsg_ibufq_push(struct ibufqueue *, struct imsg *); int imsg_get_ibuf(struct imsg *, struct ibuf *); int imsg_get_data(struct imsg *, void *, size_t); int imsg_get_buf(struct imsg *, void *, size_t); int imsg_get_strbuf(struct imsg *, char *, size_t); int imsg_get_fd(struct imsg *); uint32_t imsg_get_id(struct imsg *); size_t imsg_get_len(struct imsg *); pid_t imsg_get_pid(struct imsg *); uint32_t imsg_get_type(struct imsg *); int imsg_forward(struct imsgbuf *, struct imsg *); int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const void *, size_t); int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, int, const struct iovec *, int); int imsg_compose_ibuf(struct imsgbuf *, uint32_t, uint32_t, pid_t, struct ibuf *); struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, size_t); int imsg_add(struct ibuf *, const void *, size_t); void imsg_close(struct imsgbuf *, struct ibuf *); void imsg_free(struct imsg *); int imsg_set_maxsize(struct ibuf *, size_t); #endif tmux-tmux-f222026/compat/memmem.c000066400000000000000000000044271511153563100166630ustar00rootroot00000000000000/* $OpenBSD: memmem.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */ /*- * Copyright (c) 2005 Pascal Gloor * * 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 #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; } tmux-tmux-f222026/compat/ntohll.c000066400000000000000000000020171511153563100166770ustar00rootroot00000000000000/* * Copyright (c) 2024 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "compat.h" uint64_t ntohll(uint64_t v) { uint32_t b; uint32_t t; b = ntohl (v & 0xffffffff); t = ntohl (v >> 32); return ((uint64_t)b << 32 | t); } tmux-tmux-f222026/compat/queue.h000066400000000000000000000436321511153563100165400ustar00rootroot00000000000000/* $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 _COMPAT_QUEUE_H_ #define _COMPAT_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 /* !_COMPAT_QUEUE_H_ */ tmux-tmux-f222026/compat/reallocarray.c000066400000000000000000000025501511153563100200610ustar00rootroot00000000000000/* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */ /* * Copyright (c) 2008 Otto Moerbeek * * 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 #include #include #include #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); } tmux-tmux-f222026/compat/recallocarray.c000066400000000000000000000043511511153563100202250ustar00rootroot00000000000000/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ /* * Copyright (c) 2008, 2017 Otto Moerbeek * * 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 #include #include #include #include #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; } tmux-tmux-f222026/compat/setenv.c000066400000000000000000000026461511153563100167130ustar00rootroot00000000000000/* * Copyright (c) 2010 Dagobert Michelsen * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "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); } tmux-tmux-f222026/compat/setproctitle.c000066400000000000000000000026301511153563100201210ustar00rootroot00000000000000/* * Copyright (c) 2016 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "compat.h" #if defined(HAVE_PRCTL) && defined(HAVE_PR_SET_NAME) #include 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 tmux-tmux-f222026/compat/strcasestr.c000066400000000000000000000043731511153563100176030ustar00rootroot00000000000000/* $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 #include #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); } tmux-tmux-f222026/compat/strlcat.c000066400000000000000000000032501511153563100170530ustar00rootroot00000000000000/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * 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 #include #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 */ } tmux-tmux-f222026/compat/strlcpy.c000066400000000000000000000030711511153563100171000ustar00rootroot00000000000000/* $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ */ /* * Copyright (c) 1998 Todd C. Miller * * 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 #include #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 */ } tmux-tmux-f222026/compat/strndup.c000066400000000000000000000022571511153563100171040ustar00rootroot00000000000000/* $OpenBSD: strndup.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ /* * Copyright (c) 2010 Todd C. Miller * * 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 #include #include #include #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; } tmux-tmux-f222026/compat/strnlen.c000066400000000000000000000021111511153563100170570ustar00rootroot00000000000000/* $OpenBSD: strnlen.c,v 1.8 2016/10/16 17:37:39 dtucker Exp $ */ /* * Copyright (c) 2010 Todd C. Miller * * 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 #include #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); } tmux-tmux-f222026/compat/strsep.c000066400000000000000000000047501511153563100167250ustar00rootroot00000000000000/* $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 #include #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 */ } tmux-tmux-f222026/compat/strtonum.c000066400000000000000000000033761511153563100173030ustar00rootroot00000000000000/* $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 #include #include #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); } tmux-tmux-f222026/compat/systemd.c000066400000000000000000000176761511153563100171100ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2022 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "tmux.h" #ifndef SD_ID128_UUID_FORMAT_STR #define SD_ID128_UUID_FORMAT_STR \ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" #endif int systemd_activated(void) { return (sd_listen_fds(0) >= 1); } int systemd_create_socket(int flags, char **cause) { int fds; int fd; struct sockaddr_un sa; socklen_t 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); } struct systemd_job_watch { const char *path; int done; }; static int job_removed_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { struct systemd_job_watch *watch = userdata; const char *path = NULL; uint32_t id; int r; /* This handler could be called during the sd_bus_call. */ if (watch->path == NULL) return 0; r = sd_bus_message_read(m, "uo", &id, &path); if (r < 0) return (r); if (strcmp(path, watch->path) == 0) watch->done = 1; return (0); } int systemd_move_to_new_cgroup(char **cause) { sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message *m = NULL, *reply = NULL; sd_bus *bus = NULL; sd_bus_slot *slot = NULL; char *name, *desc, *slice; sd_id128_t uuid; int r; uint64_t elapsed_usec; pid_t pid, parent_pid; struct timeval start, now; struct systemd_job_watch watch = {}; gettimeofday(&start, NULL); /* Connect to the session bus. */ r = sd_bus_default_user(&bus); if (r < 0) { xasprintf(cause, "failed to connect to session bus: %s", strerror(-r)); goto finish; } /* Start watching for JobRemoved events */ r = sd_bus_match_signal(bus, &slot, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved", job_removed_handler, &watch); if (r < 0) { xasprintf(cause, "failed to create match signal: %s", strerror(-r)); goto finish; } /* Start building the method call. */ r = sd_bus_message_new_method_call(bus, &m, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartTransientUnit"); if (r < 0) { xasprintf(cause, "failed to create bus message: %s", strerror(-r)); goto finish; } /* Generate a unique name for the new scope, to avoid collisions. */ r = sd_id128_randomize(&uuid); if (r < 0) { xasprintf(cause, "failed to generate uuid: %s", strerror(-r)); goto finish; } xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope", SD_ID128_FORMAT_VAL(uuid)); r = sd_bus_message_append(m, "s", name); free(name); if (r < 0) { xasprintf(cause, "failed to append to bus message: %s", strerror(-r)); goto finish; } /* Mode: fail if there's a queued unit with the same name. */ r = sd_bus_message_append(m, "s", "fail"); if (r < 0) { xasprintf(cause, "failed to append to bus message: %s", strerror(-r)); goto finish; } /* Start properties array. */ r = sd_bus_message_open_container(m, 'a', "(sv)"); if (r < 0) { xasprintf(cause, "failed to start properties array: %s", strerror(-r)); goto finish; } pid = getpid(); parent_pid = getppid(); xasprintf(&desc, "tmux child pane %ld launched by process %ld", (long)pid, (long)parent_pid); r = sd_bus_message_append(m, "(sv)", "Description", "s", desc); free(desc); if (r < 0) { xasprintf(cause, "failed to append to properties: %s", strerror(-r)); goto finish; } /* * Make sure that the session shells are terminated with SIGHUP since * bash and friends tend to ignore SIGTERM. */ r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", 1); if (r < 0) { xasprintf(cause, "failed to append to properties: %s", strerror(-r)); goto finish; } /* * Inherit the slice from the parent process, or default to * "app-tmux.slice" if that fails. */ r = sd_pid_get_user_slice(parent_pid, &slice); if (r < 0) { slice = xstrdup("app-tmux.slice"); } r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); free(slice); if (r < 0) { xasprintf(cause, "failed to append to properties: %s", strerror(-r)); goto finish; } /* PIDs to add to the scope: length - 1 array of uint32_t. */ r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); if (r < 0) { xasprintf(cause, "failed to append to properties: %s", strerror(-r)); goto finish; } /* Clean up the scope even if it fails. */ r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", "inactive-or-failed"); if (r < 0) { xasprintf(cause, "failed to append to properties: %s", strerror(-r)); goto finish; } /* End properties array. */ r = sd_bus_message_close_container(m); if (r < 0) { xasprintf(cause, "failed to end properties array: %s", strerror(-r)); goto finish; } /* aux is currently unused and should be passed an empty array. */ r = sd_bus_message_append(m, "a(sa(sv))", 0); if (r < 0) { xasprintf(cause, "failed to append to bus message: %s", strerror(-r)); goto finish; } /* Call the method with a timeout of 1 second = 1e6 us. */ r = sd_bus_call(bus, m, 1000000, &error, &reply); if (r < 0) { if (error.message != NULL) { /* We have a specific error message from sd-bus. */ xasprintf(cause, "StartTransientUnit call failed: %s", error.message); } else { xasprintf(cause, "StartTransientUnit call failed: %s", strerror(-r)); } goto finish; } /* Get the job (object path) from the reply */ r = sd_bus_message_read(reply, "o", &watch.path); if (r < 0) { xasprintf(cause, "failed to parse method reply: %s", strerror(-r)); goto finish; } while (!watch.done) { /* Process events including callbacks. */ r = sd_bus_process(bus, NULL); if (r < 0) { xasprintf(cause, "failed waiting for cgroup allocation: %s", strerror(-r)); goto finish; } /* * A positive return means we handled an event and should keep * processing; zero indicates no events available, so wait. */ if (r > 0) continue; gettimeofday(&now, NULL); elapsed_usec = (now.tv_sec - start.tv_sec) * 1000000 + now.tv_usec - start.tv_usec; if (elapsed_usec >= 1000000) { xasprintf(cause, "timeout waiting for cgroup allocation"); goto finish; } r = sd_bus_wait(bus, 1000000 - elapsed_usec); if (r < 0) { xasprintf(cause, "failed waiting for cgroup allocation: %s", strerror(-r)); goto finish; } } finish: sd_bus_error_free(&error); sd_bus_message_unref(m); sd_bus_message_unref(reply); sd_bus_slot_unref(slot); sd_bus_unref(bus); return (r); } tmux-tmux-f222026/compat/tree.h000066400000000000000000000610641511153563100163520ustar00rootroot00000000000000/* $OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $ */ /* * Copyright 2002 Niels Provos * 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_ */ tmux-tmux-f222026/compat/unvis.c000066400000000000000000000140251511153563100165450ustar00rootroot00000000000000/* $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 #include #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); } tmux-tmux-f222026/compat/utf8proc.c000066400000000000000000000032111511153563100171460ustar00rootroot00000000000000/* * Copyright (c) 2016 Joshua Rubin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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, (utf8proc_int32_t*)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)); } tmux-tmux-f222026/compat/vis.c000066400000000000000000000134671511153563100162130ustar00rootroot00000000000000/* $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 #include #include #include #include #include #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); } tmux-tmux-f222026/compat/vis.h000066400000000000000000000063551511153563100162160ustar00rootroot00000000000000/* $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_ */ tmux-tmux-f222026/configure.ac000066400000000000000000000536371511153563100162540ustar00rootroot00000000000000# configure.ac AC_INIT([tmux], 3.6) 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 m4_version_prereq(2.70, [AC_PROG_CC], [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? AC_ARG_ENABLE( debug, AS_HELP_STRING(--enable-debug, enable debug build flags), , [case "x$VERSION" in xnext*) enable_debug=yes;; esac] ) 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 \ htonll \ memmem \ ntohll \ 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 ], [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 ], [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 ], [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_long) # 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 yacc. AC_CHECK_PROG(found_yacc, $YACC, yes, no) if test "x$found_yacc" = xno; then AC_MSG_ERROR("yacc not found") fi # Look for ncurses or curses. Try pkg-config first then directly for the # library. PKG_CHECK_MODULES( 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 terminfo 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 AC_CHECK_FUNCS([ \ tiparm \ tiparm_s \ ]) # 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 PKG_CHECK_MODULES( LIBUTF8PROC, libutf8proc, [ AM_CPPFLAGS="$LIBUTF8PROC_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$LIBUTF8PROC_CFLAGS $SAVED_CPPFLAGS" LIBS="$LIBUTF8PROC_LIBS $LIBS" ] ) AC_CHECK_HEADER(utf8proc.h, enable_utf8proc=yes, enable_utf8proc=no) if test "x$enable_utf8proc" = xyes; then AC_SEARCH_LIBS( 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]) AC_ARG_ENABLE( cgroups, AS_HELP_STRING(--disable-cgroups, disable adding panes to new cgroups with systemd) ) if test "x$enable_cgroups" = x; then # Default to the same as $enable_systemd. enable_cgroups=$enable_systemd fi if test "x$enable_cgroups" = xyes; then if test "x$found_systemd" = xyes; then AC_DEFINE(ENABLE_CGROUPS) else AC_MSG_ERROR("cgroups requires systemd to be enabled") fi fi # Enable sixel support. AC_ARG_ENABLE( sixel, AS_HELP_STRING(--enable-sixel, enable sixel images) ) if test "x$enable_sixel" = xyes; then AC_DEFINE(ENABLE_SIXEL) fi AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) AC_LINK_IFELSE([AC_LANG_PROGRAM( [ #include #include #include ], [ 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 #include #include ], [ 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 #include #include ], [ 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 #ifdef __GLIBC__ #include 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 # Build against jemalloc if requested. AC_ARG_ENABLE( jemalloc, AS_HELP_STRING(--enable-jemalloc, use jemalloc if it is installed) ) if test "x$enable_jemalloc" = xyes; then PKG_CHECK_MODULES( JEMALLOC, jemalloc, [ AM_CPPFLAGS="$JEMALLOC_CFLAGS $AM_CPPFLAGS" CPPFLAGS="$AM_CPPFLAGS $SAVED_CPPFLAGS" LIBS="$LIBS $JEMALLOC_LIBS" ], AC_MSG_ERROR("jemalloc not found") ) 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 #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 #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 #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_add in libutil. AC_SEARCH_LIBS(imsg_add, util, found_imsg_add=yes, found_imsg_add=no) if test "x$found_imsg_add" = 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 #include ] ) 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 #include ] ) 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 ] ) AC_CHECK_DECL( TAILQ_PREV, , found_queue_h=no, [#include ] ) AC_CHECK_DECL( TAILQ_REPLACE, , found_queue_h=no, [#include ] ) 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 #include 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 #include #include 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 ] ) # Look for setsockopt(SO_PEERCRED). AC_CHECK_DECL( SO_PEERCRED, AC_DEFINE(HAVE_SO_PEERCRED), , [#include ] ) # Look for fcntl(F_CLOSEM). AC_CHECK_DECL( F_CLOSEM, AC_DEFINE(HAVE_FCNTL_CLOSEM), , [#include ] ) # 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 #include #if defined(HAVE_CURSES_H) #include #elif defined(HAVE_NCURSES_H) #include #endif #include 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 #include #if defined(HAVE_CURSES_H) #include #elif defined(HAVE_NCURSES_H) #include #endif #include 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 #include #if defined(HAVE_CURSES_H) #include #elif defined(HAVE_NCURSES_H) #include #endif #include 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 ;; *) if test `uname -o 2>/dev/null` = illumos; then # Illumos uses mandoc. MANFORMAT=mdoc else # Solaris 2.0 to 11.3 use AT&T nroff. MANFORMAT=man fi ;; 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_CYGWIN, test "x$PLATFORM" = xcygwin) AM_CONDITIONAL(IS_HAIKU, test "x$PLATFORM" = xhaiku) AM_CONDITIONAL(IS_UNKNOWN, test "x$PLATFORM" = xunknown) # Set the default lock command DEFAULT_LOCK_CMD="lock -np" if test "x$PLATFORM" = xlinux; then AC_CHECK_PROG(found_vlock, vlock, yes, no) if test "x$found_vlock" = xyes; then DEFAULT_LOCK_CMD="vlock" fi fi AC_MSG_CHECKING(lock-command) AC_MSG_RESULT($DEFAULT_LOCK_CMD) AC_SUBST(DEFAULT_LOCK_CMD) # Save our CFLAGS/CPPFLAGS/LDFLAGS for the Makefile and restore the old user # variables. AC_SUBST(AM_CPPFLAGS) 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 tmux-tmux-f222026/control-notify.c000066400000000000000000000133661511153563100171130ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2012 Nicholas Marriott * Copyright (c) 2012 George Nachman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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); } } void control_notify_paste_buffer_changed(const char *name) { struct client *c; TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) continue; control_write(c, "%%paste-buffer-changed %s", name); } } void control_notify_paste_buffer_deleted(const char *name) { struct client *c; TAILQ_FOREACH(c, &clients, entry) { if (!CONTROL_SHOULD_NOTIFY_CLIENT(c)) continue; control_write(c, "%%paste-buffer-deleted %s", name); } } tmux-tmux-f222026/control.c000066400000000000000000000664521511153563100156110ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2012 Nicholas Marriott * Copyright (c) 2012 George Nachman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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); if (cs->read_event == NULL) fatalx("out of memory"); if (c->flags & CLIENT_CONTROLCONTROL) cs->write_event = cs->read_event; else { cs->write_event = bufferevent_new(c->out_fd, NULL, control_write_callback, control_error_callback, c); if (cs->write_event == NULL) fatalx("out of memory"); } bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, 0); if (c->flags & CLIENT_CONTROLCONTROL) { bufferevent_write(cs->write_event, "\033P1000p", 7); bufferevent_enable(cs->write_event, EV_WRITE); } } /* Control client ready. */ void control_ready(struct client *c) { bufferevent_enable(c->control_state->read_event, EV_READ); } /* Discard all output for a client. */ void control_discard(struct client *c) { 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); } tmux-tmux-f222026/environ.c000066400000000000000000000147701511153563100156050ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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 environ_entry *envent1; struct options_entry *o; struct options_array_item *a; union options_value *ov; int found; o = options_get(oo, "update-environment"); if (o == NULL) return; a = options_array_first(o); while (a != NULL) { ov = options_array_item_value(a); found = 0; RB_FOREACH_SAFE(envent, environ, src, envent1) { if (fnmatch(ov->string, envent->name, 0) == 0) { environ_set(dst, envent->name, 0, "%s", envent->value); found = 1; } } if (!found) environ_clear(dst, ov->string); 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()); environ_set(env, "COLORTERM", 0, "truecolor"); } else { environ_unset(env, "TERM"); environ_unset(env, "TERM_PROGRAM"); environ_unset(env, "TERM_PROGRAM_VERSION"); environ_unset(env, "COLORTERM"); } #ifdef HAVE_SYSTEMD environ_clear(env, "LISTEN_PID"); environ_clear(env, "LISTEN_FDS"); environ_clear(env, "LISTEN_FDNAMES"); #endif if (s != NULL) idx = s->id; else idx = -1; environ_set(env, "TMUX", 0, "%s,%ld,%d", socket_path, (long)getpid(), idx); return (env); } tmux-tmux-f222026/example_tmux.conf000066400000000000000000000034331511153563100173320ustar00rootroot00000000000000# # 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-features ",xterm*:RGB" # 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 tmux-tmux-f222026/file.c000066400000000000000000000475611511153563100150500ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * 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) { const char *home; char *path, *full_path; if (strncmp(file, "~/", 2) != 0) path = xstrdup(file); else { home = find_home(); if (home == NULL) home = ""; xasprintf(&path, "%s%s", home, file + 1); } if (*path == '/') return (path); xasprintf(&full_path, "%s/%s", server_client_get_cwd(c, NULL), path); return (full_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 && (cf->closed || 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 || (c->flags & CLIENT_ATTACHED) || (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. */ struct client_file * 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 = NULL; 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; } 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 cf; done: if (f != NULL) fclose(f); file_fire_done(cf); return NULL; } /* Cancel a file read. */ void file_cancel(struct client_file *cf) { struct msg_read_cancel msg; log_debug("read cancel file %d", cf->stream); if (cf->closed) return; cf->closed = 1; msg.stream = cf->stream; proc_send(cf->peer, MSG_READ_CANCEL, -1, &msg, sizeof msg); } /* Push event, fired if there is more writing to be done. */ 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); if (cf->event == NULL) fatalx("out of memory"); 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); if (cf->event == NULL) fatalx("out of memory"); 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 read cancel message (client). */ void file_read_cancel(struct client_files *files, struct imsg *imsg) { struct msg_read_cancel *msg = imsg->data; size_t msglen = imsg->hdr.len - IMSG_HEADER_SIZE; struct client_file find, *cf; if (msglen != sizeof *msg) fatalx("bad MSG_READ_CANCEL size"); find.stream = msg->stream; if ((cf = RB_FIND(client_files, files, &find)) == NULL) fatalx("unknown stream number"); log_debug("cancel file %d", cf->stream); file_read_error_callback(NULL, 0, cf); } /* Handle a write ready message (server). */ void file_write_ready(struct client_files *files, struct imsg *imsg) { 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 && !cf->closed) { 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); } tmux-tmux-f222026/format-draw.c000066400000000000000000001005271511153563100163440ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* 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; char string[16]; 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); switch (fr->type) { case STYLE_RANGE_NONE: case STYLE_RANGE_LEFT: case STYLE_RANGE_RIGHT: return (1); case STYLE_RANGE_PANE: case STYLE_RANGE_WINDOW: case STYLE_RANGE_SESSION: return (fr->argument == sy->range_argument); case STYLE_RANGE_USER: return (strcmp(fr->string, sy->range_string) == 0); } return (1); } /* 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, base_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(&base_default, base, sizeof base_default); memcpy(¤t_default, base, sizeof current_default); base = &base_default; style_set(&sy, ¤t_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, ¤t_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(¤t_default, &saved_sy.gc, sizeof current_default); sy.default_type = STYLE_DEFAULT_BASE; } else if (sy.default_type == STYLE_DEFAULT_POP) { memcpy(¤t_default, base, sizeof current_default); sy.default_type = STYLE_DEFAULT_BASE; } else if (sy.default_type == STYLE_DEFAULT_SET) { memcpy(&base_default, &saved_sy.gc, sizeof base_default); memcpy(¤t_default, &saved_sy.gc, 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; strlcpy(fr->string, sy.range_string, sizeof fr->string); } } 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; strlcpy(sr->string, fr->string, sizeof sr->string); sr->start = fr->start; sr->end = fr->end; TAILQ_INSERT_TAIL(srs, sr, entry); switch (sr->type) { case STYLE_RANGE_NONE: break; case STYLE_RANGE_LEFT: log_debug("%s: range left at %u-%u", __func__, sr->start, sr->end); break; case STYLE_RANGE_RIGHT: log_debug("%s: range right at %u-%u", __func__, sr->start, sr->end); break; case STYLE_RANGE_PANE: log_debug("%s: range pane|%%%u at %u-%u", __func__, sr->argument, sr->start, sr->end); break; case STYLE_RANGE_WINDOW: log_debug("%s: range window|%u at %u-%u", __func__, sr->argument, sr->start, sr->end); break; case STYLE_RANGE_SESSION: log_debug("%s: range session|$%u at %u-%u", __func__, sr->argument, sr->start, sr->end); break; case STYLE_RANGE_USER: log_debug("%s: range user|%u at %u-%u", __func__, sr->argument, sr->start, sr->end); break; } format_free_range(&frs, fr); } 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(2, 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(2, 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); } tmux-tmux-f222026/format.c000066400000000000000000004024231511153563100154110ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2011 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #define FORMAT_CLIENTS 0x40000 #define FORMAT_NOT 0x80000 #define FORMAT_NOT_NOT 0x100000 #define FORMAT_REPEAT 0x200000 /* 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 }; /* Format loop sort type. */ enum format_loop_sort_type { FORMAT_LOOP_BY_INDEX, FORMAT_LOOP_BY_NAME, FORMAT_LOOP_BY_TIME, }; static struct format_loop_sort_criteria { enum format_loop_sort_type field; int reversed; } format_loop_sort_criteria; 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_alert. */ static void * format_cb_session_alert(struct format_tree *ft) { struct session *s = ft->s; struct winlink *wl; char alerts[1024]; int alerted = 0; if (s == NULL) return (NULL); *alerts = '\0'; RB_FOREACH(wl, winlinks, &s->windows) { if ((wl->flags & WINLINK_ALERTFLAGS) == 0) continue; if (~alerted & wl->flags & WINLINK_ACTIVITY) { strlcat(alerts, "#", sizeof alerts); alerted |= WINLINK_ACTIVITY; } if (~alerted & wl->flags & WINLINK_BELL) { strlcat(alerts, "!", sizeof alerts); alerted |= WINLINK_BELL; } if (~alerted & wl->flags & WINLINK_SILENCE) { strlcat(alerts, "~", sizeof alerts); alerted |= WINLINK_SILENCE; } } return (xstrdup(alerts)); } /* 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 cursor_colour. */ static void * format_cb_cursor_colour(struct format_tree *ft) { struct window_pane *wp = ft->wp; if (wp == NULL || wp->screen == NULL) return (NULL); if (wp->screen->ccolour != -1) return (xstrdup(colour_tostring(wp->screen->ccolour))); return (xstrdup(colour_tostring(wp->screen->default_ccolour))); } /* 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; 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 (window_pane_mode(wp) != WINDOW_PANE_NO_MODE) return (window_copy_get_word(wp, x, y)); return (NULL); } gd = wp->base.grid; return (format_grid_word(gd, x, gd->hsize + y)); } /* Callback for mouse_hyperlink. */ static void * format_cb_mouse_hyperlink(struct format_tree *ft) { struct window_pane *wp; struct grid *gd; u_int x, y; if (!ft->m.valid) return (NULL); wp = cmd_mouse_pane(&ft->m, NULL, NULL); if (wp == NULL) return (NULL); if (cmd_mouse_at(wp, &ft->m, &x, &y, 0) != 0) return (NULL); if (!TAILQ_EMPTY(&wp->modes)) { if (window_pane_mode(wp) != WINDOW_PANE_NO_MODE) return (window_copy_get_hyperlink(wp, x, y)); return (NULL); } gd = wp->base.grid; return (format_grid_hyperlink(gd, x, gd->hsize + y, wp->screen)); } /* Callback for mouse_line. */ static void * format_cb_mouse_line(struct format_tree *ft) { 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 (window_pane_mode(wp) != WINDOW_PANE_NO_MODE) return (window_copy_get_line(wp, y)); return (NULL); } gd = wp->base.grid; return (format_grid_line(gd, gd->hsize + y)); } /* Callback for mouse_status_line. */ static void * format_cb_mouse_status_line(struct format_tree *ft) { char *value; u_int y; if (!ft->m.valid) return (NULL); if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED)) return (NULL); if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) { y = ft->m.y; } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) { y = ft->m.y - ft->m.statusat; } else return (NULL); xasprintf(&value, "%u", y); return (value); } /* Callback for mouse_status_range. */ static void * format_cb_mouse_status_range(struct format_tree *ft) { struct style_range *sr; u_int x, y; if (!ft->m.valid) return (NULL); if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED)) return (NULL); if (ft->m.statusat == 0 && ft->m.y < ft->m.statuslines) { x = ft->m.x; y = ft->m.y; } else if (ft->m.statusat > 0 && ft->m.y >= (u_int)ft->m.statusat) { x = ft->m.x; y = ft->m.y - ft->m.statusat; } else return (NULL); sr = status_get_range(ft->c, x, y); if (sr == NULL) return (NULL); switch (sr->type) { case STYLE_RANGE_NONE: return (NULL); case STYLE_RANGE_LEFT: return (xstrdup("left")); case STYLE_RANGE_RIGHT: return (xstrdup("right")); case STYLE_RANGE_PANE: return (xstrdup("pane")); case STYLE_RANGE_WINDOW: return (xstrdup("window")); case STYLE_RANGE_SESSION: return (xstrdup("session")); case STYLE_RANGE_USER: return (xstrdup(sr->string)); } return (NULL); } /* Callback for alternate_on. */ static void * format_cb_alternate_on(struct format_tree *ft) { 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_full. */ static void * format_cb_buffer_full(struct format_tree *ft) { size_t size; const char *s; if (ft->pb != NULL) { s = paste_buffer_data(ft->pb, &size); if (s != NULL) return (xstrndup(s, size)); } 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 client_theme. */ static void * format_cb_client_theme(struct format_tree *ft) { if (ft->c != NULL) { switch (ft->c->theme) { case THEME_DARK: return (xstrdup("dark")); case THEME_LIGHT: return (xstrdup("light")); case THEME_UNKNOWN: return (NULL); } } 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_shape. */ static void * format_cb_cursor_shape(struct format_tree *ft) { if (ft->wp != NULL && ft->wp->screen != NULL) { switch (ft->wp->screen->cstyle) { case SCREEN_CURSOR_BLOCK: return (xstrdup("block")); case SCREEN_CURSOR_UNDERLINE: return (xstrdup("underline")); case SCREEN_CURSOR_BAR: return (xstrdup("bar")); default: return (xstrdup("default")); } } return (NULL); } /* Callback for cursor_very_visible. */ static void * format_cb_cursor_very_visible(struct format_tree *ft) { if (ft->wp != NULL && ft->wp->screen != NULL) { if (ft->wp->screen->mode & MODE_CURSOR_VERY_VISIBLE) 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 cursor_blinking. */ static void * format_cb_cursor_blinking(struct format_tree *ft) { if (ft->wp != NULL && ft->wp->screen != NULL) { if (ft->wp->screen->mode & MODE_CURSOR_BLINKING) return (xstrdup("1")); return (xstrdup("0")); } 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 loop_last_flag. */ static void * format_cb_loop_last_flag(struct format_tree *ft) { if (ft->flags & FORMAT_LAST) return (xstrdup("1")); return (xstrdup("0")); } /* 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_unseen_changes. */ static void * format_cb_pane_unseen_changes(struct format_tree *ft) { if (ft->wp != NULL) { if (ft->wp->flags & PANE_UNSEENCHANGES) return (xstrdup("1")); return (xstrdup("0")); } return (NULL); } /* Callback for pane_key_mode. */ static void * format_cb_pane_key_mode(struct format_tree *ft) { if (ft->wp != NULL && ft->wp->screen != NULL) { switch (ft->wp->screen->mode & EXTENDED_KEY_MODES) { case MODE_KEYS_EXTENDED: return (xstrdup("Ext 1")); case MODE_KEYS_EXTENDED_2: return (xstrdup("Ext 2")); default: return (xstrdup("VT10x")); } } return (NULL); } /* Callback for pane_last. */ static void * format_cb_pane_last(struct format_tree *ft) { if (ft->wp != NULL) { if (ft->wp == TAILQ_FIRST(&ft->wp->window->last_panes)) 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 server_sessions. */ static void * format_cb_server_sessions(__unused struct format_tree *ft) { struct session *s; u_int n = 0; RB_FOREACH(s, sessions, &sessions) n++; return (format_printf("%u", n)); } /* Callback for session_active. */ static void * format_cb_session_active(struct format_tree *ft) { if (ft->s == NULL || ft->c == NULL) return (NULL); if (ft->c->session == ft->s) return (xstrdup("1")); return (xstrdup("0")); } /* Callback for session_activity_flag. */ static void * format_cb_session_activity_flag(struct format_tree *ft) { struct winlink *wl; if (ft->s != NULL) { RB_FOREACH(wl, winlinks, &ft->s->windows) { if (ft->wl->flags & WINLINK_ACTIVITY) return (xstrdup("1")); return (xstrdup("0")); } } return (NULL); } /* Callback for session_bell_flag. */ static void * format_cb_session_bell_flag(struct format_tree *ft) { struct winlink *wl; if (ft->s != NULL) { RB_FOREACH(wl, winlinks, &ft->s->windows) { if (wl->flags & WINLINK_BELL) return (xstrdup("1")); return (xstrdup("0")); } } return (NULL); } /* Callback for session_silence_flag. */ static void * format_cb_session_silence_flag(struct format_tree *ft) { struct winlink *wl; if (ft->s != NULL) { RB_FOREACH(wl, winlinks, &ft->s->windows) { if (ft->wl->flags & WINLINK_SILENCE) return (xstrdup("1")); return (xstrdup("0")); } } 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 sixel_support. */ static void * format_cb_sixel_support(__unused struct format_tree *ft) { #ifdef ENABLE_SIXEL return (xstrdup("1")); #else return (xstrdup("0")); #endif } /* 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) { struct winlink *wl; struct session *s; int found = 0; if (ft->wl != NULL) { RB_FOREACH(s, sessions, &sessions) { RB_FOREACH(wl, winlinks, &s->windows) { if (wl->window == ft->wl->window) { if (found) return (xstrdup("1")); found = 1; } } } return (xstrdup("0")); } return (NULL); } /* Callback for window_linked_sessions. */ static void * format_cb_window_linked_sessions(struct format_tree *ft) { struct window *w; struct session_group *sg; struct session *s; u_int n = 0; if (ft->wl == NULL) return (NULL); w = ft->wl->window; RB_FOREACH(sg, session_groups, &session_groups) { s = TAILQ_FIRST(&sg->sessions); if (winlink_find_by_window(&s->windows, w) != NULL) n++; } RB_FOREACH(s, sessions, &sessions) { if (session_group_contains(s) != NULL) continue; if (winlink_find_by_window(&s->windows, w) != NULL) n++; } return (format_printf("%u", n)); } /* 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_full", FORMAT_TABLE_STRING, format_cb_buffer_full }, { "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_theme", FORMAT_TABLE_STRING, format_cb_client_theme }, { "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_blinking", FORMAT_TABLE_STRING, format_cb_cursor_blinking }, { "cursor_character", FORMAT_TABLE_STRING, format_cb_cursor_character }, { "cursor_colour", FORMAT_TABLE_STRING, format_cb_cursor_colour }, { "cursor_flag", FORMAT_TABLE_STRING, format_cb_cursor_flag }, { "cursor_shape", FORMAT_TABLE_STRING, format_cb_cursor_shape }, { "cursor_very_visible", FORMAT_TABLE_STRING, format_cb_cursor_very_visible }, { "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 }, { "loop_last_flag", FORMAT_TABLE_STRING, format_cb_loop_last_flag }, { "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_hyperlink", FORMAT_TABLE_STRING, format_cb_mouse_hyperlink }, { "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_status_line", FORMAT_TABLE_STRING, format_cb_mouse_status_line }, { "mouse_status_range", FORMAT_TABLE_STRING, format_cb_mouse_status_range }, { "mouse_utf8_flag", FORMAT_TABLE_STRING, format_cb_mouse_utf8_flag }, { "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_key_mode", FORMAT_TABLE_STRING, format_cb_pane_key_mode }, { "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_unseen_changes", FORMAT_TABLE_STRING, format_cb_pane_unseen_changes }, { "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 }, { "server_sessions", FORMAT_TABLE_STRING, format_cb_server_sessions }, { "session_active", FORMAT_TABLE_STRING, format_cb_session_active }, { "session_activity", FORMAT_TABLE_TIME, format_cb_session_activity }, { "session_activity_flag", FORMAT_TABLE_STRING, format_cb_session_activity_flag }, { "session_alert", FORMAT_TABLE_STRING, format_cb_session_alert }, { "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_bell_flag", FORMAT_TABLE_STRING, format_cb_session_bell_flag }, { "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_silence_flag", FORMAT_TABLE_STRING, format_cb_session_silence_flag }, { "session_stack", FORMAT_TABLE_STRING, format_cb_session_stack }, { "session_windows", FORMAT_TABLE_STRING, format_cb_session_windows }, { "sixel_support", FORMAT_TABLE_STRING, format_cb_sixel_support }, { "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. */ char * format_pretty_time(time_t t, int seconds) { struct tm now_tm, tm; time_t now, age; char s[9]; 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) { if (seconds) strftime(s, sizeof s, "%H:%M:%S", &tm); else 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, 0); 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 = format_quote_shell(saved); free(saved); } if (modifiers & FORMAT_QUOTE_STYLE) { saved = found; found = format_quote_style(saved); free(saved); } return (found); } /* Unescape escaped characters. */ static char * format_unescape(const char *s) { char *out, *cp; int brackets = 0; cp = out = xmalloc(strlen(s) + 1); for (; *s != '\0'; s++) { if (*s == '#' && s[1] == '{') brackets++; if (brackets == 0 && *s == '#' && strchr(",#{}:", s[1]) != NULL) { *cp++ = *++s; continue; } if (*s == '}') brackets--; *cp++ = *s; } *cp = '\0'; return (out); } /* Remove escaped characters. */ static char * format_strip(const char *s) { 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 == '#' && s[1] != '\0' && 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,R,<,> * =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("labcdnwETSWPL!<>", 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 || 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("mCLNPSst=pReqW", 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((u_char)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); } /* Handle unary boolean operators, "!" and "!!". */ static char * format_bool_op_1(struct format_expand_state *es, const char *fmt, int not) { int result; char *expanded; expanded = format_expand1(es, fmt); result = format_true(expanded); if (not) result = !result; free(expanded); return (xstrdup(result ? "1" : "0")); } /* Handle n-ary boolean operators, "&&" and "||". */ static char * format_bool_op_n(struct format_expand_state *es, const char *fmt, int and) { int result; const char *cp1, *cp2; char *raw, *expanded; result = and ? 1 : 0; cp1 = fmt; while (and ? result : !result) { cp2 = format_skip(cp1, ","); if (cp2 == NULL) raw = xstrdup(cp1); else raw = xstrndup(cp1, cp2 - cp1); expanded = format_expand1(es, raw); free(raw); format_log(es, "operator %s has operand: %s", and ? "&&" : "||", expanded); if (and) result = result && format_true(expanded); else result = result || format_true(expanded); free(expanded); if (cp2 == NULL) break; else cp1 = cp2 + 1; } return (xstrdup(result ? "1" : "0")); } /* 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")); } static int format_cmp_session(const void *a0, const void *b0) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; 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 (sc->field) { case FORMAT_LOOP_BY_INDEX: result = sa->id - sb->id; break; case FORMAT_LOOP_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 FORMAT_LOOP_BY_NAME: result = strcmp(sa->name, sb->name); break; } if (sc->reversed) result = -result; return (result); } /* 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 *all, *active, *use, *expanded, *value; size_t valuelen; struct session *s; int i, n, last = 0; static struct session **l = NULL; static int lsz = 0; if (format_choose(es, fmt, &all, &active, 0) != 0) { all = xstrdup(fmt); active = NULL; } n = 0; RB_FOREACH(s, sessions, &sessions) { if (lsz <= n) { lsz += 100; l = xreallocarray(l, lsz, sizeof *l); } l[n++] = s; } qsort(l, n, sizeof *l, format_cmp_session); value = xcalloc(1, 1); valuelen = 1; for (i = 0; i < n; i++) { s = l[i]; format_log(es, "session loop: $%u", s->id); if (active != NULL && s->id == ft->c->session->id) use = active; else use = all; if (i == n - 1) last = FORMAT_LAST; nft = format_create(c, item, FORMAT_NONE, ft->flags|last); format_defaults(nft, ft->c, s, NULL, NULL); format_copy_state(&next, es, 0); next.ft = nft; expanded = format_expand1(&next, use); 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")); } static int format_cmp_window(const void *a0, const void *b0) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; const struct winlink *const *a = a0; const struct winlink *const *b = b0; const struct window *wa = (*a)->window; const struct window *wb = (*b)->window; int result = 0; switch (sc->field) { case FORMAT_LOOP_BY_INDEX: break; case FORMAT_LOOP_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 FORMAT_LOOP_BY_NAME: result = strcmp(wa->name, wb->name); break; } if (sc->reversed) result = -result; return (result); } /* Loop over windows. */ static char * format_loop_windows(struct format_expand_state *es, const char *fmt) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; 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; int i, n, last = 0; static struct winlink **l = NULL; static int lsz = 0; 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; } n = 0; RB_FOREACH(wl, winlinks, &ft->s->windows) { if (lsz <= n) { lsz += 100; l = xreallocarray(l, lsz, sizeof *l); } l[n++] = wl; } if (sc->field != FORMAT_LOOP_BY_INDEX) qsort(l, n, sizeof *l, format_cmp_window); else { /* Use order in the tree as index order. */ if (sc->reversed) { for (i = 0; i < n / 2; i++) { wl = l[i]; l[i] = l[n - 1 - i]; l[n - 1 - i] = wl; } } } value = xcalloc(1, 1); valuelen = 1; for (i = 0; i < n; i++) { wl = l[i]; 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; if (i == n - 1) last = FORMAT_LAST; nft = format_create(c, item, FORMAT_WINDOW|w->id, ft->flags|last); 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); } static int format_cmp_pane(const void *a0, const void *b0) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; const struct window_pane *const *a = a0; const struct window_pane *const *b = b0; const struct window_pane *wpa = *a; const struct window_pane *wpb = *b; int result = 0; if (sc->reversed) result = wpb->id - wpa->id; else result = wpa->id - wpb->id; return (result); } /* 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; int i, n, last = 0; static struct window_pane **l = NULL; static int lsz = 0; 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; } n = 0; TAILQ_FOREACH(wp, &ft->w->panes, entry) { if (lsz <= n) { lsz += 100; l = xreallocarray(l, lsz, sizeof *l); } l[n++] = wp; } qsort(l, n, sizeof *l, format_cmp_pane); value = xcalloc(1, 1); valuelen = 1; for (i = 0; i < n; i++) { wp = l[i]; format_log(es, "pane loop: %%%u", wp->id); if (active != NULL && wp == ft->w->active) use = active; else use = all; if (i == n - 1) last = FORMAT_LAST; nft = format_create(c, item, FORMAT_PANE|wp->id, ft->flags|last); 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 int format_cmp_client(const void *a0, const void *b0) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; const struct client *const *a = a0; const struct client *const *b = b0; const struct client *ca = *a; const struct client *cb = *b; int result = 0; switch (sc->field) { case FORMAT_LOOP_BY_INDEX: break; case FORMAT_LOOP_BY_TIME: if (timercmp(&ca->activity_time, &cb->activity_time, >)) { result = -1; break; } if (timercmp(&ca->activity_time, &cb->activity_time, <)) { result = 1; break; } /* FALLTHROUGH */ case FORMAT_LOOP_BY_NAME: result = strcmp(ca->name, cb->name); break; } if (sc->reversed) result = -result; return (result); } /* Loop over clients. */ static char * format_loop_clients(struct format_expand_state *es, const char *fmt) { struct format_loop_sort_criteria *sc = &format_loop_sort_criteria; struct format_tree *ft = es->ft; struct client *c; struct cmdq_item *item = ft->item; struct format_tree *nft; struct format_expand_state next; char *expanded, *value; size_t valuelen; int i, n, last = 0; static struct client **l = NULL; static int lsz = 0; value = xcalloc(1, 1); valuelen = 1; n = 0; TAILQ_FOREACH(c, &clients, entry) { if (lsz <= n) { lsz += 100; l = xreallocarray(l, lsz, sizeof *l); } l[n++] = c; } if (sc->field != FORMAT_LOOP_BY_INDEX) qsort(l, n, sizeof *l, format_cmp_client); else { /* Use order in the list as index order. */ if (sc->reversed) { for (i = 0; i < n / 2; i++) { c = l[i]; l[i] = l[n - 1 - i]; l[n - 1 - i] = c; } } } for (i = 0; i < n; i++) { c = l[i]; format_log(es, "client loop: %s", c->name); if (i == n - 1) last = FORMAT_LAST; nft = format_create(c, item, 0, ft->flags|last); format_defaults(nft, c, ft->s, ft->wl, ft->wp); format_copy_state(&next, es, 0); next.ft = nft; expanded = format_expand1(&next, fmt); format_free(nft); valuelen += strlen(expanded); value = xrealloc(value, valuelen); strlcat(value, expanded, valuelen); free(expanded); } return (value); } static char * format_replace_expression(struct format_modifier *mexp, struct format_expand_state *es, const char *copy) { 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_loop_sort_criteria *sc = &format_loop_sort_criteria; struct format_tree *ft = es->ft; struct window_pane *wp = ft->wp; const char *errstr, *copy, *cp, *cp2; const char *marker = NULL; 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; struct format_modifier *bool_op_n = NULL; u_int i, count, nsub = 0, nrep; struct format_expand_state next; /* Make a copy of the key. */ copy = copy0 = xstrndup(key, keylen); /* Process modifier list. */ list = format_build_modifiers(es, ©, &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 '!': modifiers |= FORMAT_NOT; 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; if (fm->argc < 1) { sc->field = FORMAT_LOOP_BY_INDEX; sc->reversed = 0; break; } if (strchr(fm->argv[0], 'i') != NULL) sc->field = FORMAT_LOOP_BY_INDEX; else if (strchr(fm->argv[0], 'n') != NULL) sc->field = FORMAT_LOOP_BY_NAME; else if (strchr(fm->argv[0], 't') != NULL) sc->field = FORMAT_LOOP_BY_TIME; else sc->field = FORMAT_LOOP_BY_INDEX; if (strchr(fm->argv[0], 'r') != NULL) sc->reversed = 1; else sc->reversed = 0; break; case 'W': modifiers |= FORMAT_WINDOWS; if (fm->argc < 1) { sc->field = FORMAT_LOOP_BY_INDEX; sc->reversed = 0; break; } if (strchr(fm->argv[0], 'i') != NULL) sc->field = FORMAT_LOOP_BY_INDEX; else if (strchr(fm->argv[0], 'n') != NULL) sc->field = FORMAT_LOOP_BY_NAME; else if (strchr(fm->argv[0], 't') != NULL) sc->field = FORMAT_LOOP_BY_TIME; else sc->field = FORMAT_LOOP_BY_INDEX; if (strchr(fm->argv[0], 'r') != NULL) sc->reversed = 1; else sc->reversed = 0; break; case 'P': modifiers |= FORMAT_PANES; if (fm->argc < 1) { sc->reversed = 0; break; } if (strchr(fm->argv[0], 'r') != NULL) sc->reversed = 1; else sc->reversed = 0; break; case 'L': modifiers |= FORMAT_CLIENTS; if (fm->argc < 1) { sc->field = FORMAT_LOOP_BY_INDEX; sc->reversed = 0; break; } if (strchr(fm->argv[0], 'i') != NULL) sc->field = FORMAT_LOOP_BY_INDEX; else if (strchr(fm->argv[0], 'n') != NULL) sc->field = FORMAT_LOOP_BY_NAME; else if (strchr(fm->argv[0], 't') != NULL) sc->field = FORMAT_LOOP_BY_TIME; else sc->field = FORMAT_LOOP_BY_INDEX; if (strchr(fm->argv[0], 'r') != NULL) sc->reversed = 1; else sc->reversed = 0; break; case 'R': modifiers |= FORMAT_REPEAT; break; } } else if (fm->size == 2) { if (strcmp(fm->modifier, "||") == 0 || strcmp(fm->modifier, "&&") == 0) bool_op_n = fm; else if (strcmp(fm->modifier, "!!") == 0) modifiers |= FORMAT_NOT_NOT; else if (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) { format_log(es, "literal string is '%s'", copy); value = format_unescape(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, operator, 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_CLIENTS) { value = format_loop_clients(es, copy); if (value == NULL) goto fail; } else if (modifiers & FORMAT_WINDOW_NAME) { value = format_window_name(es, copy); if (value == NULL) 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 (modifiers & FORMAT_REPEAT) { /* Repeat multiple times. */ if (format_choose(es, copy, &left, &right, 1) != 0) { format_log(es, "repeat syntax error: %s", copy); goto fail; } nrep = strtonum(right, 1, 10000, &errstr); if (errstr != NULL) value = xstrdup(""); else { value = xstrdup(""); for (i = 0; i < nrep; i++) { xasprintf(&new, "%s%s", value, left); free(value); value = new; } } free(right); free(left); } else if (modifiers & FORMAT_NOT) { value = format_bool_op_1(es, copy, 1); } else if (modifiers & FORMAT_NOT_NOT) { value = format_bool_op_1(es, copy, 0); } else if (bool_op_n != NULL) { /* n-ary boolean operator. */ if (strcmp(bool_op_n->modifier, "||") == 0) value = format_bool_op_n(es, copy, 0); else if (strcmp(bool_op_n->modifier, "&&") == 0) value = format_bool_op_n(es, copy, 1); } 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 (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: For each pair of (condition, value), check the * condition and return the value if true. If no condition * matches, return the last unpaired arg if there is one, or the * empty string if not. */ cp = copy + 1; while (1) { cp2 = format_skip(cp, ","); if (cp2 == NULL) { format_log(es, "no condition matched in '%s'; using last " "arg", copy + 1); value = format_expand1(es, cp); break; } condition = xstrndup(cp, cp2 - cp); 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); } cp = cp2 + 1; cp2 = format_skip(cp, ","); if (format_true(found)) { format_log(es, "condition '%s' is true", condition); if (cp2 == NULL) value = format_expand1(es, cp); else { right = xstrndup(cp, cp2 - cp); value = format_expand1(es, right); free(right); } free(condition); free(found); break; } else { format_log(es, "condition '%s' is false", condition); } free(condition); free(found); if (cp2 == NULL) { format_log(es, "no condition matched in '%s'; using empty " "string", copy + 1); value = xstrdup(""); break; } cp = cp2 + 1; } } 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); free(time_format); return (0); fail: format_log(es, "failed %s", copy0); free(sub); format_free_modifiers(list, count); free(copy0); free(time_format); 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, *style_end = NULL; 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 '[': case '#': /* * If ##[ (with two or more #s), then it is a style and * can be left for format_draw to handle. */ ptr = fmt - (ch == '['); n = 2 - (ch == '['); while (*ptr == '#') { ptr++; n++; } if (*ptr == '[') { style_end = format_skip(fmt - 2, "]"); format_log(es, "found #*%zu[", n); while (len - off < n + 2) { buf = xreallocarray(buf, 2, len); 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 (fmt > style_end) { /* skip inside #[] */ if (ch >= 'A' && ch <= 'Z') s = format_upper[ch - 'A']; else if (ch >= 'a' && ch <= 'z') s = format_lower[ch - 'a']; } if (s == NULL) { while (len - off < 3) { buf = xreallocarray(buf, 2, len); 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; } static int format_is_word_separator(const char *ws, const struct grid_cell *gc) { if (utf8_cstrhas(ws, &gc->data)) return (1); if (gc->flags & GRID_FLAG_TAB) return (1); return gc->data.size == 1 && *gc->data.data == ' '; } /* 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) && format_is_word_separator(ws, &gc)) { 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) continue; if (format_is_word_separator(ws, &gc)) 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) continue; ud = xreallocarray(ud, size + 2, sizeof *ud); if (gc.flags & GRID_FLAG_TAB) utf8_set(&ud[size++], '\t'); else memcpy(&ud[size++], &gc.data, sizeof *ud); } if (size != 0) { ud[size].size = 0; s = utf8_tocstr(ud); free(ud); } return (s); } /* Return hyperlink at given coordinates. Caller frees. */ char * format_grid_hyperlink(struct grid *gd, u_int x, u_int y, struct screen* s) { const char *uri; struct grid_cell gc; for (;;) { grid_get_cell(gd, x, y, &gc); if (~gc.flags & GRID_FLAG_PADDING) break; if (x == 0) return (NULL); x--; } if (s->hyperlinks == NULL || gc.link == 0) return (NULL); if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL)) return (NULL); return (xstrdup(uri)); } tmux-tmux-f222026/fuzz/000077500000000000000000000000001511153563100147465ustar00rootroot00000000000000tmux-tmux-f222026/fuzz/input-fuzzer.c000066400000000000000000000054011511153563100175740ustar00rootroot00000000000000/* * Copyright (c) 2020 Sergey Nizovtsev * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" #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 parameter 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; } tmux-tmux-f222026/fuzz/input-fuzzer.dict000066400000000000000000000001021511153563100202660ustar00rootroot00000000000000"\x1b[" "1000" "2004" "1049" "38;2" "100;" "tmux;" "rgb:00/00/00" tmux-tmux-f222026/fuzz/input-fuzzer.options000066400000000000000000000000321511153563100210400ustar00rootroot00000000000000[libfuzzer] max_len = 512 tmux-tmux-f222026/grid-reader.c000066400000000000000000000251141511153563100163040ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2020 Anindya Mukherjee * * 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 /* 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) { return (grid_in_set(gr->gd, gr->cx, gr->cy, set)); } /* 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, width; /* 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) && (width = grid_reader_in_set(gr, WHITESPACE))) gr->cx += width; } /* 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; } /* Compare grid cell to UTF-8 data. Return 1 if equal, 0 if not. */ static int grid_reader_cell_equals_data(const struct grid_cell *gc, const struct utf8_data *ud) { if (gc->flags & GRID_FLAG_PADDING) return (0); if (gc->flags & GRID_FLAG_TAB && ud->size == 1 && *ud->data == '\t') return (1); if (gc->data.size != ud->size) return (0); return (memcmp(gc->data.data, ud->data, gc->data.size) == 0); } /* 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 (grid_reader_cell_equals_data(&gc, jc)) { 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 (grid_reader_cell_equals_data(&gc, jc)) { 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 != ' ') && ~gc.flags & GRID_FLAG_TAB && ~gc.flags & GRID_FLAG_PADDING) { 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; } tmux-tmux-f222026/grid-view.c000066400000000000000000000125671511153563100160240ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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, 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, 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, NULL)); } tmux-tmux-f222026/grid.c000066400000000000000000001072141511153563100150460ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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, 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, 8, 0 }; /* Cleared grid cell data. */ static const struct grid_cell grid_cleared_cell = { { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0 }; static const struct grid_cell_entry grid_cleared_entry = { { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; /* 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 != 8) /* only supports 256 or RGB */ return (1); if (gc->link != 0) return (1); if (gc->flags & GRID_FLAG_TAB) 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; if (gc->flags & GRID_FLAG_TAB) uc = gc->data.width; else 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; gee->link = gc->link; 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) { int flags1 = gc1->flags, flags2 = gc2->flags;; if (gc1->fg != gc2->fg || gc1->bg != gc2->bg) return (0); if (gc1->attr != gc2->attr) return (0); if ((flags1 & ~GRID_FLAG_CLEARED) != (flags2 & ~GRID_FLAG_CLEARED)) return (0); if (gc1->link != gc2->link) 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); } /* Set grid cell to a tab. */ void grid_set_tab(struct grid_cell *gc, u_int width) { memset(gc->data.data, 0, sizeof gc->data.data); gc->flags |= GRID_FLAG_TAB; gc->flags &= ~GRID_FLAG_PADDING; gc->data.width = gc->data.size = gc->data.have = width; memset(gc->data.data, ' ', gc->data.size); } /* 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->linedata[gd->hsize].time = current_time; 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); gl_history->time = current_time; /* Then move the region up and clear the bottom line. */ memmove(gl_upper, gl_upper + 1, (lower - upper) * sizeof *gl_upper); 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; gc->link = gee->link; if (gc->flags & GRID_FLAG_TAB) grid_set_tab(gc, gee->data); else 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 = 8; utf8_set(&gc->data, gce->data.data); gc->link = 0; } /* 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 flags) { u_int i; char tmp[64]; int reset = (n != 0 && s[0] == 0); if (nnewc == 0) return; /* no code to add */ if (!reset && nnewc == noldc && memcmp(newc, oldc, nnewc * sizeof newc[0]) == 0) return; /* no reset and colour unchanged */ if (reset && (newc[0] == 49 || newc[0] == 39)) return; /* reset and colour default */ if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\033[", len); else strlcat(buf, "\033[", len); for (i = 0; i < nnewc; i++) { if (i + 1 < nnewc) xsnprintf(tmp, sizeof tmp, "%d;", newc[i]); else xsnprintf(tmp, sizeof tmp, "%d", newc[i]); strlcat(buf, tmp, len); } strlcat(buf, "m", len); } static int grid_string_cells_add_hyperlink(char *buf, size_t len, const char *id, const char *uri, int flags) { char *tmp; if (strlen(uri) + strlen(id) + 17 >= len) return (0); if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\033]8;", len); else strlcat(buf, "\033]8;", len); if (*id != '\0') { xasprintf(&tmp, "id=%s;", id); strlcat(buf, tmp, len); free(tmp); } else strlcat(buf, ";", len); strlcat(buf, uri, len); if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\033\\\\", len); else strlcat(buf, "\033\\", len); return (1); } /* * 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 flags, struct screen *sc, int *has_link) { int oldc[64], newc[64], s[128]; size_t noldc, nnewc, n, i; u_int attr = gc->attr, lastattr = lastgc->attr; char tmp[64]; const char *uri, *id; static const 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 != 8 && gc->us == 8)) { 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 (flags & GRID_STRING_ESCAPE_SEQUENCES) 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, flags); /* If the background colour changed, append its parameters. */ nnewc = grid_string_cells_bg(gc, newc); noldc = grid_string_cells_bg(lastgc, oldc); grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, flags); /* If the underscore colour changed, append its parameters. */ nnewc = grid_string_cells_us(gc, newc); noldc = grid_string_cells_us(lastgc, oldc); grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc, flags); /* Append shift in/shift out if needed. */ if ((attr & GRID_ATTR_CHARSET) && !(lastattr & GRID_ATTR_CHARSET)) { if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\016", len); /* SO */ else strlcat(buf, "\016", len); /* SO */ } if (!(attr & GRID_ATTR_CHARSET) && (lastattr & GRID_ATTR_CHARSET)) { if (flags & GRID_STRING_ESCAPE_SEQUENCES) strlcat(buf, "\\017", len); /* SI */ else strlcat(buf, "\017", len); /* SI */ } /* Add hyperlink if changed. */ if (sc != NULL && sc->hyperlinks != NULL && lastgc->link != gc->link) { if (hyperlinks_get(sc->hyperlinks, gc->link, &uri, &id, NULL)) { *has_link = grid_string_cells_add_hyperlink(buf, len, id, uri, flags); } else if (*has_link) { grid_string_cells_add_hyperlink(buf, len, "", "", flags); *has_link = 0; } } } /* Convert cells into a string. */ char * grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, struct grid_cell **lastgc, int flags, struct screen *s) { struct grid_cell gc; static struct grid_cell lastgc1; const char *data; char *buf, code[8192]; size_t len, off, size, codelen; u_int xx, end; int has_link = 0; 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); if (flags & GRID_STRING_EMPTY_CELLS) end = gl->cellsize; else end = gl->cellused; for (xx = px; xx < px + nx; xx++) { if (gl == NULL || xx >= end) break; grid_get_cell(gd, xx, py, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; if (flags & GRID_STRING_WITH_SEQUENCES) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, flags, s, &has_link); codelen = strlen(code); memcpy(*lastgc, &gc, sizeof **lastgc); } else codelen = 0; if (gc.flags & GRID_FLAG_TAB) { data = "\t"; size = 1; } else { data = gc.data.data; size = gc.data.size; if ((flags & GRID_STRING_ESCAPE_SEQUENCES) && 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 (has_link) { grid_string_cells_add_hyperlink(code, sizeof code, "", "", flags); codelen = strlen(code); while (len < off + size + codelen + 1) { buf = xreallocarray(buf, 2, len); len *= 2; } memcpy(buf + off, code, codelen); off += codelen; } if (flags & GRID_STRING_TRIM_SPACES) { while (off > 0 && buf[off - 1] == ' ') off--; } 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 || from == NULL) 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); } /* Check if character is in set. */ int grid_in_set(struct grid *gd, u_int px, u_int py, const char *set) { struct grid_cell gc, tmp_gc; u_int pxx; grid_get_cell(gd, px, py, &gc); if (strchr(set, '\t')) { if (gc.flags & GRID_FLAG_PADDING) { pxx = px; do grid_get_cell(gd, --pxx, py, &tmp_gc); while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING); if (tmp_gc.flags & GRID_FLAG_TAB) return (tmp_gc.data.width - (px - pxx)); } else if (gc.flags & GRID_FLAG_TAB) return (gc.data.width); } if (gc.flags & GRID_FLAG_PADDING) return (0); return (utf8_cstrhas(set, &gc.data)); } tmux-tmux-f222026/hyperlinks.c000066400000000000000000000146521511153563100163140ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2021 Will * Copyright (c) 2022 Jeff Chiang * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * OSC 8 hyperlinks, described at: * * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda * * Each hyperlink and ID combination is assigned a number ("inner" in this * file) which is stored in an extended grid cell and maps into a tree here. * * Each URI has one inner number and one external ID (which tmux uses to send * the hyperlink to the terminal) and one internal ID (which is received from * the sending application inside tmux). * * Anonymous hyperlinks are each unique and are not reused even if they have * the same URI (terminals will not want to tie them together). */ #define MAX_HYPERLINKS 5000 static long long hyperlinks_next_external_id = 1; static u_int global_hyperlinks_count; struct hyperlinks_uri { struct hyperlinks *tree; u_int inner; const char *internal_id; const char *external_id; const char *uri; TAILQ_ENTRY(hyperlinks_uri) list_entry; RB_ENTRY(hyperlinks_uri) by_inner_entry; RB_ENTRY(hyperlinks_uri) by_uri_entry; /* by internal ID and URI */ }; RB_HEAD(hyperlinks_by_uri_tree, hyperlinks_uri); RB_HEAD(hyperlinks_by_inner_tree, hyperlinks_uri); TAILQ_HEAD(hyperlinks_list, hyperlinks_uri); static struct hyperlinks_list global_hyperlinks = TAILQ_HEAD_INITIALIZER(global_hyperlinks); struct hyperlinks { u_int next_inner; struct hyperlinks_by_inner_tree by_inner; struct hyperlinks_by_uri_tree by_uri; u_int references; }; static int hyperlinks_by_uri_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) { int r; if (*left->internal_id == '\0' || *right->internal_id == '\0') { /* * If both URIs are anonymous, use the inner for comparison so * that they do not match even if the URI is the same - each * anonymous URI should be unique. */ if (*left->internal_id != '\0') return (-1); if (*right->internal_id != '\0') return (1); return (left->inner - right->inner); } r = strcmp(left->internal_id, right->internal_id); if (r != 0) return (r); return (strcmp(left->uri, right->uri)); } RB_PROTOTYPE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, hyperlinks_by_uri_cmp); RB_GENERATE_STATIC(hyperlinks_by_uri_tree, hyperlinks_uri, by_uri_entry, hyperlinks_by_uri_cmp); static int hyperlinks_by_inner_cmp(struct hyperlinks_uri *left, struct hyperlinks_uri *right) { return (left->inner - right->inner); } RB_PROTOTYPE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, hyperlinks_by_inner_cmp); RB_GENERATE_STATIC(hyperlinks_by_inner_tree, hyperlinks_uri, by_inner_entry, hyperlinks_by_inner_cmp); /* Remove a hyperlink. */ static void hyperlinks_remove(struct hyperlinks_uri *hlu) { struct hyperlinks *hl = hlu->tree; TAILQ_REMOVE(&global_hyperlinks, hlu, list_entry); global_hyperlinks_count--; RB_REMOVE(hyperlinks_by_inner_tree, &hl->by_inner, hlu); RB_REMOVE(hyperlinks_by_uri_tree, &hl->by_uri, hlu); free((void *)hlu->internal_id); free((void *)hlu->external_id); free((void *)hlu->uri); free(hlu); } /* Store a new hyperlink or return if it already exists. */ u_int hyperlinks_put(struct hyperlinks *hl, const char *uri_in, const char *internal_id_in) { struct hyperlinks_uri find, *hlu; char *uri, *internal_id, *external_id; /* * Anonymous URI are stored with an empty internal ID and the tree * comparator will make sure they never match each other (so each * anonymous URI is unique). */ if (internal_id_in == NULL) internal_id_in = ""; utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE); utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE); if (*internal_id_in != '\0') { find.uri = uri; find.internal_id = internal_id; hlu = RB_FIND(hyperlinks_by_uri_tree, &hl->by_uri, &find); if (hlu != NULL) { free (uri); free (internal_id); return (hlu->inner); } } xasprintf(&external_id, "tmux%llX", hyperlinks_next_external_id++); hlu = xcalloc(1, sizeof *hlu); hlu->inner = hl->next_inner++; hlu->internal_id = internal_id; hlu->external_id = external_id; hlu->uri = uri; hlu->tree = hl; RB_INSERT(hyperlinks_by_uri_tree, &hl->by_uri, hlu); RB_INSERT(hyperlinks_by_inner_tree, &hl->by_inner, hlu); TAILQ_INSERT_TAIL(&global_hyperlinks, hlu, list_entry); if (++global_hyperlinks_count == MAX_HYPERLINKS) hyperlinks_remove(TAILQ_FIRST(&global_hyperlinks)); return (hlu->inner); } /* Get hyperlink by inner number. */ int hyperlinks_get(struct hyperlinks *hl, u_int inner, const char **uri_out, const char **internal_id_out, const char **external_id_out) { struct hyperlinks_uri find, *hlu; find.inner = inner; hlu = RB_FIND(hyperlinks_by_inner_tree, &hl->by_inner, &find); if (hlu == NULL) return (0); if (internal_id_out != NULL) *internal_id_out = hlu->internal_id; if (external_id_out != NULL) *external_id_out = hlu->external_id; *uri_out = hlu->uri; return (1); } /* Initialize hyperlink set. */ struct hyperlinks * hyperlinks_init(void) { struct hyperlinks *hl; hl = xcalloc(1, sizeof *hl); hl->next_inner = 1; RB_INIT(&hl->by_uri); RB_INIT(&hl->by_inner); hl->references = 1; return (hl); } /* Copy hyperlink set. */ struct hyperlinks * hyperlinks_copy(struct hyperlinks *hl) { hl->references++; return (hl); } /* Free all hyperlinks but not the set itself. */ void hyperlinks_reset(struct hyperlinks *hl) { struct hyperlinks_uri *hlu, *hlu1; RB_FOREACH_SAFE(hlu, hyperlinks_by_inner_tree, &hl->by_inner, hlu1) hyperlinks_remove(hlu); } /* Free hyperlink set. */ void hyperlinks_free(struct hyperlinks *hl) { if (--hl->references == 0) { hyperlinks_reset(hl); free(hl); } } tmux-tmux-f222026/image-sixel.c000066400000000000000000000355531511153563100163330ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" #define SIXEL_WIDTH_LIMIT 10000 #define SIXEL_HEIGHT_LIMIT 10000 struct sixel_line { u_int x; uint16_t *data; }; struct sixel_image { u_int x; u_int y; u_int xpixel; u_int ypixel; u_int set_ra; u_int ra_x; u_int ra_y; u_int *colours; u_int ncolours; u_int used_colours; u_int p2; u_int dx; u_int dy; u_int dc; struct sixel_line *lines; }; struct sixel_chunk { u_int next_x; u_int next_y; u_int count; char pattern; char next_pattern; size_t len; size_t used; char *data; }; static int sixel_parse_expand_lines(struct sixel_image *si, u_int y) { if (y <= si->y) return (0); if (y > SIXEL_HEIGHT_LIMIT) return (1); si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines); si->y = y; return (0); } static int sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) { if (x <= sl->x) return (0); if (x > SIXEL_WIDTH_LIMIT) return (1); if (x > si->x) si->x = x; sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data); sl->x = si->x; return (0); } static u_int sixel_get_pixel(struct sixel_image *si, u_int x, u_int y) { struct sixel_line *sl; if (y >= si->y) return (0); sl = &si->lines[y]; if (x >= sl->x) return (0); return (sl->data[x]); } static int sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c) { struct sixel_line *sl; if (sixel_parse_expand_lines(si, y + 1) != 0) return (1); sl = &si->lines[y]; if (sixel_parse_expand_line(si, sl, x + 1) != 0) return (1); sl->data[x] = c; return (0); } static int sixel_parse_write(struct sixel_image *si, u_int ch) { struct sixel_line *sl; u_int i; if (sixel_parse_expand_lines(si, si->dy + 6) != 0) return (1); sl = &si->lines[si->dy]; for (i = 0; i < 6; i++) { if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0) return (1); if (ch & (1 << i)) sl->data[si->dx] = si->dc; sl++; } return (0); } static const char * sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) { const char *last; char *endptr; u_int x, y; last = cp; while (last != end) { if (*last != ';' && (*last < '0' || *last > '9')) break; last++; } strtoul(cp, &endptr, 10); if (endptr == last || *endptr != ';') return (last); strtoul(endptr + 1, &endptr, 10); if (endptr == last) return (last); if (*endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } x = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } if (x > SIXEL_WIDTH_LIMIT) { log_debug("%s: image is too wide", __func__); return (NULL); } y = strtoul(endptr + 1, &endptr, 10); if (endptr != last) { log_debug("%s: extra ;", __func__); return (NULL); } if (y > SIXEL_HEIGHT_LIMIT) { log_debug("%s: image is too tall", __func__); return (NULL); } si->x = x; sixel_parse_expand_lines(si, y); si->set_ra = 1; si->ra_x = x; si->ra_y = y; return (last); } static const char * sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) { const char *last; char *endptr; u_int c, type, c1, c2, c3; last = cp; while (last != end) { if (*last != ';' && (*last < '0' || *last > '9')) break; last++; } c = strtoul(cp, &endptr, 10); if (c > SIXEL_COLOUR_REGISTERS) { log_debug("%s: too many colours", __func__); return (NULL); } if (si->used_colours <= c) si->used_colours = c + 1; si->dc = c + 1; if (endptr == last || *endptr != ';') return (last); type = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } c1 = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } c2 = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } c3 = strtoul(endptr + 1, &endptr, 10); if (endptr != last) { log_debug("%s: missing ;", __func__); return (NULL); } if ((type != 1 && type != 2) || (type == 1 && (c1 > 360 || c2 > 100 || c3 > 100)) || (type == 2 && (c1 > 100 || c2 > 100 || c3 > 100))) { log_debug("%s: invalid color %u;%u;%u;%u", __func__, type, c1, c2, c3); return (NULL); } if (c + 1 > si->ncolours) { si->colours = xrecallocarray(si->colours, si->ncolours, c + 1, sizeof *si->colours); si->ncolours = c + 1; } si->colours[c] = (type << 25) | (c1 << 16) | (c2 << 8) | c3; return (last); } static const char * sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) { const char *last; char tmp[32], ch; u_int n = 0, i; const char *errstr = NULL; last = cp; while (last != end) { if (*last < '0' || *last > '9') break; tmp[n++] = *last++; if (n == (sizeof tmp) - 1) { log_debug("%s: repeat not terminated", __func__); return (NULL); } } if (n == 0 || last == end) { log_debug("%s: repeat not terminated", __func__); return (NULL); } tmp[n] = '\0'; n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr); if (n == 0 || errstr != NULL) { log_debug("%s: repeat too wide", __func__); return (NULL); } ch = (*last++) - 0x3f; for (i = 0; i < n; i++) { if (sixel_parse_write(si, ch) != 0) { log_debug("%s: width limit reached", __func__); return (NULL); } si->dx++; } return (last); } struct sixel_image * sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel) { struct sixel_image *si; const char *cp = buf, *end = buf + len; char ch; if (len == 0 || len == 1 || *cp++ != 'q') { log_debug("%s: empty image", __func__); return (NULL); } si = xcalloc (1, sizeof *si); si->xpixel = xpixel; si->ypixel = ypixel; si->p2 = p2; while (cp != end) { ch = *cp++; switch (ch) { case '"': cp = sixel_parse_attributes(si, cp, end); if (cp == NULL) goto bad; break; case '#': cp = sixel_parse_colour(si, cp, end); if (cp == NULL) goto bad; break; case '!': cp = sixel_parse_repeat(si, cp, end); if (cp == NULL) goto bad; break; case '-': si->dx = 0; si->dy += 6; break; case '$': si->dx = 0; break; default: if (ch < 0x20) break; if (ch < 0x3f || ch > 0x7e) goto bad; if (sixel_parse_write(si, ch - 0x3f) != 0) { log_debug("%s: width limit reached", __func__); goto bad; } si->dx++; break; } } if (si->x == 0 || si->y == 0) goto bad; return (si); bad: free(si); return (NULL); } void sixel_free(struct sixel_image *si) { u_int y; for (y = 0; y < si->y; y++) free(si->lines[y].data); free(si->lines); free(si->colours); free(si); } void sixel_log(struct sixel_image *si) { struct sixel_line *sl; char s[SIXEL_WIDTH_LIMIT + 1]; u_int i, x, y, cx, cy; sixel_size_in_cells(si, &cx, &cy); log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy); for (i = 0; i < si->ncolours; i++) log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]); for (y = 0; y < si->y; y++) { sl = &si->lines[y]; for (x = 0; x < si->x; x++) { if (x >= sl->x) s[x] = '_'; else if (sl->data[x] != 0) s[x] = '0' + (sl->data[x] - 1) % 10; else s[x] = '.'; } s[x] = '\0'; log_debug("%s: %4u: %s", __func__, y, s); } } void sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) { if ((si->x % si->xpixel) == 0) *x = (si->x / si->xpixel); else *x = 1 + (si->x / si->xpixel); if ((si->y % si->ypixel) == 0) *y = (si->y / si->ypixel); else *y = 1 + (si->y / si->ypixel); } struct sixel_image * sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, u_int oy, u_int sx, u_int sy, int colours) { struct sixel_image *new; u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; u_int x, y, i; /* * We want to get the section of the image at ox,oy in image cells and * map it onto the same size in terminal cells. */ sixel_size_in_cells(si, &cx, &cy); if (ox >= cx) return (NULL); if (oy >= cy) return (NULL); if (ox + sx >= cx) sx = cx - ox; if (oy + sy >= cy) sy = cy - oy; if (xpixel == 0) xpixel = si->xpixel; if (ypixel == 0) ypixel = si->ypixel; pox = ox * si->xpixel; poy = oy * si->ypixel; psx = sx * si->xpixel; psy = sy * si->ypixel; tsx = sx * xpixel; tsy = sy * ypixel; new = xcalloc (1, sizeof *si); new->xpixel = xpixel; new->ypixel = ypixel; new->p2 = si->p2; new->set_ra = si->set_ra; /* clamp to slice end */ new->ra_x = si->ra_x < psx ? si->ra_x : psx; new->ra_y = si->ra_y < psy ? si->ra_y : psy; /* subtract slice origin */ new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0; new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0; /* resize */ new->ra_x = new->ra_x * xpixel / si->xpixel; new->ra_y = new->ra_y * ypixel / si->ypixel; new->used_colours = si->used_colours; for (y = 0; y < tsy; y++) { py = poy + ((double)y * psy / tsy); for (x = 0; x < tsx; x++) { px = pox + ((double)x * psx / tsx); sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py)); } } if (colours && si->ncolours != 0) { new->colours = xmalloc(si->ncolours * sizeof *new->colours); for (i = 0; i < si->ncolours; i++) new->colours[i] = si->colours[i]; new->ncolours = si->ncolours; } return (new); } static void sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, size_t slen) { if (*used + slen >= *len + 1) { (*len) *= 2; *buf = xrealloc(*buf, *len); } memcpy(*buf + *used, s, slen); (*used) += slen; } static void sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) { char tmp[16]; size_t tmplen; if (count == 1) sixel_print_add(buf, len, used, &ch, 1); else if (count == 2) { sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); } else if (count == 3) { sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); } else if (count != 0) { tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch); sixel_print_add(buf, len, used, tmp, tmplen); } } static void sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks, u_int y, u_int *active, u_int *nactive) { u_int i, x, c, dx, colors[6]; struct sixel_chunk *chunk = NULL; struct sixel_line *sl; for (x = 0; x < si->x; x++) { for (i = 0; i < 6; i++) { colors[i] = 0; if (y + i < si->y) { sl = &si->lines[y + i]; if (x < sl->x && sl->data[x] != 0) { colors[i] = sl->data[x]; c = sl->data[x] - 1; chunks[c].next_pattern |= 1 << i; } } } for (i = 0; i < 6; i++) { if (colors[i] == 0) continue; c = colors[i] - 1; chunk = &chunks[c]; if (chunk->next_x == x + 1) continue; if (chunk->next_y < y + 1) { chunk->next_y = y + 1; active[(*nactive)++] = c; } dx = x - chunk->next_x; if (chunk->pattern != chunk->next_pattern || dx != 0) { sixel_print_repeat(&chunk->data, &chunk->len, &chunk->used, chunk->count, chunk->pattern + 0x3f); sixel_print_repeat(&chunk->data, &chunk->len, &chunk->used, dx, '?'); chunk->pattern = chunk->next_pattern; chunk->count = 0; } chunk->count++; chunk->next_pattern = 0; chunk->next_x = x + 1; } } } char * sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) { char *buf, tmp[64]; size_t len, used = 0, tmplen; u_int *colours, ncolours, used_colours, i, c, y; u_int *active, nactive; struct sixel_chunk *chunks, *chunk; if (map != NULL) { colours = map->colours; ncolours = map->ncolours; } else { colours = si->colours; ncolours = si->ncolours; } used_colours = si->used_colours; if (used_colours == 0) return (NULL); len = 8192; buf = xmalloc(len); tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2); sixel_print_add(&buf, &len, &used, tmp, tmplen); if (si->set_ra) { tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x, si->ra_y); sixel_print_add(&buf, &len, &used, tmp, tmplen); } chunks = xcalloc(used_colours, sizeof *chunks); active = xcalloc(used_colours, sizeof *active); for (i = 0; i < ncolours; i++) { c = colours[i]; tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", i, c >> 25, (c >> 16) & 0x1ff, (c >> 8) & 0xff, c & 0xff); sixel_print_add(&buf, &len, &used, tmp, tmplen); } for (i = 0; i < used_colours; i++) { chunk = &chunks[i]; chunk->len = 8; chunk->data = xmalloc(chunk->len); } for (y = 0; y < si->y; y += 6) { nactive = 0; sixel_print_compress_colors(si, chunks, y, active, &nactive); for (i = 0; i < nactive; i++) { c = active[i]; chunk = &chunks[c]; tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); sixel_print_add(&buf, &len, &used, tmp, tmplen); sixel_print_add(&buf, &len, &used, chunk->data, chunk->used); sixel_print_repeat(&buf, &len, &used, chunk->count, chunk->pattern + 0x3f); sixel_print_add(&buf, &len, &used, "$", 1); chunk->used = chunk->next_x = chunk->count = 0; } if (buf[used - 1] == '$') used--; sixel_print_add(&buf, &len, &used, "-", 1); } if (buf[used - 1] == '-') used--; sixel_print_add(&buf, &len, &used, "\033\\", 2); buf[used] = '\0'; if (size != NULL) *size = used; for (i = 0; i < used_colours; i++) free(chunks[i].data); free(active); free(chunks); return (buf); } struct screen * sixel_to_screen(struct sixel_image *si) { struct screen *s; struct screen_write_ctx ctx; struct grid_cell gc; u_int x, y, sx, sy; sixel_size_in_cells(si, &sx, &sy); s = xmalloc(sizeof *s); screen_init(s, sx, sy, 0); memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM); utf8_set(&gc.data, '~'); screen_write_start(&ctx, s); if (sx == 1 || sy == 1) { for (y = 0; y < sy; y++) { for (x = 0; x < sx; x++) grid_view_set_cell(s->grid, x, y, &gc); } } else { screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL); for (y = 1; y < sy - 1; y++) { for (x = 1; x < sx - 1; x++) grid_view_set_cell(s->grid, x, y, &gc); } } screen_write_stop(&ctx); return (s); } tmux-tmux-f222026/image.c000066400000000000000000000077631511153563100152130ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); static u_int all_images_count; static void image_free(struct image *im) { struct screen *s = im->s; TAILQ_REMOVE(&all_images, im, all_entry); all_images_count--; TAILQ_REMOVE(&s->images, im, entry); sixel_free(im->data); free(im->fallback); free(im); } int image_free_all(struct screen *s) { struct image *im, *im1; int redraw = !TAILQ_EMPTY(&s->images); TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) image_free(im); return (redraw); } /* Create text placeholder for an image. */ static void image_fallback(char **ret, u_int sx, u_int sy) { char *buf, *label; u_int py, size, lsize; /* Allocate first line. */ lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1; if (sx < lsize - 3) size = lsize - 1; else size = sx + 2; /* Remaining lines. Every placeholder line has \r\n at the end. */ size += (sx + 2) * (sy - 1) + 1; *ret = buf = xmalloc(size); /* Render first line. */ if (sx < lsize - 3) { memcpy(buf, label, lsize); buf += lsize - 1; } else { memcpy(buf, label, lsize - 3); buf += lsize - 3; memset(buf, '+', sx - lsize + 3); buf += sx - lsize + 3; snprintf(buf, 3, "\r\n"); buf += 2; } /* Remaining lines. */ for (py = 1; py < sy; py++) { memset(buf, '+', sx); buf += sx; snprintf(buf, 3, "\r\n"); buf += 2; } free(label); } struct image* image_store(struct screen *s, struct sixel_image *si) { struct image *im; im = xcalloc(1, sizeof *im); im->s = s; im->data = si; im->px = s->cx; im->py = s->cy; sixel_size_in_cells(si, &im->sx, &im->sy); image_fallback(&im->fallback, im->sx, im->sy); TAILQ_INSERT_TAIL(&s->images, im, entry); TAILQ_INSERT_TAIL(&all_images, im, all_entry); if (++all_images_count == 10/*XXX*/) image_free(TAILQ_FIRST(&all_images)); return (im); } int image_check_line(struct screen *s, u_int py, u_int ny) { struct image *im, *im1; int redraw = 0; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { if (py + ny > im->py && py < im->py + im->sy) { image_free(im); redraw = 1; } } return (redraw); } int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { struct image *im, *im1; int redraw = 0; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { if (py + ny <= im->py || py >= im->py + im->sy) continue; if (px + nx <= im->px || px >= im->px + im->sx) continue; image_free(im); redraw = 1; } return (redraw); } int image_scroll_up(struct screen *s, u_int lines) { struct image *im, *im1; int redraw = 0; u_int sx, sy; struct sixel_image *new; TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { if (im->py >= lines) { im->py -= lines; redraw = 1; continue; } if (im->py + im->sy <= lines) { image_free(im); redraw = 1; continue; } sx = im->sx; sy = (im->py + im->sy) - lines; new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); sixel_free(im->data); im->data = new; im->py = 0; sixel_size_in_cells(im->data, &im->sx, &im->sy); free(im->fallback); image_fallback(&im->fallback, im->sx, im->sy); redraw = 1; } return (redraw); } tmux-tmux-f222026/input-keys.c000066400000000000000000000474451511153563100162420ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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_START|KEYC_IMPLIED_META, .data = "\033[200~" }, { .key = KEYC_PASTE_END, .data = "\033[201~" }, { .key = KEYC_PASTE_END|KEYC_IMPLIED_META, .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;_~" }, { .key = KEYC_REPORT_DARK_THEME, .data = "\033[?997;1n" }, { .key = KEYC_REPORT_LIGHT_THEME, .data = "\033[?997;2n" }, }; 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); } /* * Encode and write an extended key escape sequence in one of the two * possible formats, depending on the configured output mode. */ static int input_key_extended(struct bufferevent *bev, key_code key) { char tmp[64], modifier; struct utf8_data ud; wchar_t wc; switch (key & KEYC_MASK_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: return (-1); } if (KEYC_IS_UNICODE(key)) { utf8_to_data(key & KEYC_MASK_KEY, &ud); if (utf8_towc(&ud, &wc) == UTF8_DONE) key = wc; else return (-1); } else key &= KEYC_MASK_KEY; if (options_get_number(global_options, "extended-keys-format") == 1) xsnprintf(tmp, sizeof tmp, "\033[27;%c;%llu~", modifier, key); else xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", key, modifier); input_key_write(__func__, bev, tmp, strlen(tmp)); return (0); } /* * Outputs the key in the "standard" mode. This is by far the most * complicated output mode, with a lot of remapping in order to * emulate quirks of terminals that today can be only found in museums. */ static int input_key_vt10x(struct bufferevent *bev, key_code key) { struct utf8_data ud; key_code onlykey; char *p; static const char *standard_map[2] = { "1!9(0)=+;:'\",<.>/-8? 2", "119900=+;;'',,..\x1f\x1f\x7f\x7f\0\0", }; log_debug("%s: key in %llx", __func__, key); if (key & KEYC_META) input_key_write(__func__, bev, "\033", 1); /* * There's no way to report modifiers for unicode keys in standard mode * so lose the modifiers. */ if (KEYC_IS_UNICODE(key)) { utf8_to_data(key, &ud); input_key_write(__func__, bev, ud.data, ud.size); return (0); } /* * Prevent TAB, CR and LF from being swallowed by the C0 remapping * logic. */ onlykey = key & KEYC_MASK_KEY; if (onlykey == '\r' || onlykey == '\n' || onlykey == '\t') key &= ~KEYC_CTRL; /* * Convert keys with Ctrl modifier into corresponding C0 control codes, * with the exception of *some* keys, which are remapped into printable * ASCII characters. * * There is no special handling for Shift modifier, which is pretty * much redundant anyway, as no terminal will send |SHIFT, * but only |SHIFT. */ if (key & KEYC_CTRL) { p = strchr(standard_map[0], onlykey); if (p != NULL) key = standard_map[1][p - standard_map[0]]; else if (onlykey >= '3' && onlykey <= '7') key = onlykey - '\030'; else if (onlykey >= '@' && onlykey <= '~') key = onlykey & 0x1f; else return (-1); } log_debug("%s: key out %llx", __func__, key); ud.data[0] = key & 0x7f; input_key_write(__func__, bev, &ud.data[0], 1); return (0); } /* Pick keys that are reported as vt10x keys in modifyOtherKeys=1 mode. */ static int input_key_mode1(struct bufferevent *bev, key_code key) { key_code onlykey; log_debug("%s: key in %llx", __func__, key); /* A regular or shifted key + Meta. */ if ((key & (KEYC_CTRL | KEYC_META)) == KEYC_META) return (input_key_vt10x(bev, key)); /* * As per * https://invisible-island.net/xterm/modified-keys-us-pc105.html. */ onlykey = key & KEYC_MASK_KEY; if ((key & KEYC_CTRL) && (onlykey == ' ' || onlykey == '/' || onlykey == '@' || onlykey == '^' || (onlykey >= '2' && onlykey <= '8') || (onlykey >= '@' && onlykey <= '~'))) return (input_key_vt10x(bev, key)); return (-1); } /* 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 = NULL; key_code newkey; struct utf8_data ud; /* 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"); log_debug("%s: key 0x%llx is backspace -> 0x%llx", __func__, key, newkey); if ((key & KEYC_MASK_MODIFIERS) == 0) { ud.data[0] = 255; if ((newkey & KEYC_MASK_MODIFIERS) == 0) ud.data[0] = newkey; else if ((newkey & KEYC_MASK_MODIFIERS) == KEYC_CTRL) { newkey &= KEYC_MASK_KEY; if (newkey == '?') ud.data[0] = 0x7f; else if (newkey >= '@' && newkey <= '_') ud.data[0] = newkey - 0x40; else if (newkey >= 'a' && newkey <= 'z') ud.data[0] = newkey - 0x60; } if (ud.data[0] != 255) input_key_write(__func__, bev, &ud.data[0], 1); return (0); } key = newkey|(key & (KEYC_MASK_FLAGS|KEYC_MASK_MODIFIERS)); } /* Is this backtab? */ if ((key & KEYC_MASK_KEY) == KEYC_BTAB) { if (s->mode & MODE_KEYS_EXTENDED_2) { /* When in xterm extended mode, remap into S-Tab. */ key = '\011' | (key & ~KEYC_MASK_KEY) | KEYC_SHIFT; } else { /* Otherwise clear modifiers. */ key &= ~KEYC_MASK_MODIFIERS; } } /* * A trivial case, that is a 7-bit key, excluding C0 control characters * that can't be entered from the keyboard, and no modifiers; or a UTF-8 * key and no modifiers. */ if (!(key & ~KEYC_MASK_KEY)) { if (key == C0_HT || key == C0_CR || key == C0_ESC || (key >= 0x20 && key <= 0x7f)) { ud.data[0] = key; input_key_write(__func__, bev, &ud.data[0], 1); return (0); } if (KEYC_IS_UNICODE(key)) { utf8_to_data(key, &ud); input_key_write(__func__, bev, ud.data, ud.size); return (0); } } /* * Look up the standard VT10x keys in the tree. If not in application * keypad or cursor mode, remove the respective flags from the key. */ if (~s->mode & MODE_KKEYPAD) key &= ~KEYC_KEYPAD; if (~s->mode & MODE_KCURSOR) key &= ~KEYC_CURSOR; if (ike == NULL) ike = input_key_get(key); if (ike == NULL && (key & KEYC_META) && (~key & KEYC_IMPLIED_META)) ike = input_key_get(key & ~KEYC_META); if (ike == NULL && (key & KEYC_CURSOR)) ike = input_key_get(key & ~KEYC_CURSOR); if (ike == NULL && (key & KEYC_KEYPAD)) ike = input_key_get(key & ~KEYC_KEYPAD); if (ike != NULL) { log_debug("%s: found key 0x%llx: \"%s\"", __func__, key, ike->data); if (KEYC_IS_PASTE(key) && (~s->mode & MODE_BRACKETPASTE)) return (0); if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META)) input_key_write(__func__, bev, "\033", 1); input_key_write(__func__, bev, ike->data, strlen(ike->data)); return (0); } /* Ignore internal function key codes. */ if ((key >= KEYC_BASE && key < KEYC_BASE_END) || (key >= KEYC_USER && key < KEYC_USER_END)) { log_debug("%s: ignoring key 0x%llx", __func__, key); return (0); } /* * No builtin key sequence; construct an extended key sequence * depending on the client mode. * * If something invalid reaches here, an invalid output may be * produced. For example Ctrl-Shift-2 is invalid (as there's * no way to enter it). The correct form is Ctrl-Shift-@, at * least in US English keyboard layout. */ switch (s->mode & EXTENDED_KEY_MODES) { case MODE_KEYS_EXTENDED_2: /* * The simplest mode to handle - *all* modified keys are * reported in the extended form. */ return (input_key_extended(bev, key)); case MODE_KEYS_EXTENDED: /* * Some keys are still reported in standard mode, to maintain * compatibility with applications unaware of extended keys. */ if (input_key_mode1(bev, key) == -1) return (input_key_extended(bev, key)); return (0); default: /* The standard mode. */ return (input_key_vt10x(bev, key)); } } /* 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); } tmux-tmux-f222026/input.c000066400000000000000000002347141511153563100152660ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" /* * 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. */ /* Type of terminator. */ enum input_end_type { INPUT_END_ST, INPUT_END_BEL }; /* Request sent by a pane. */ struct input_request { struct client *c; struct input_ctx *ictx; enum input_request_type type; time_t t; enum input_end_type end; int idx; void *data; TAILQ_ENTRY(input_request) entry; TAILQ_ENTRY(input_request) centry; }; #define INPUT_REQUEST_TIMEOUT 2 /* 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 u_char *input_buf; size_t input_len; size_t input_space; enum input_end_type input_end; struct input_param param_list[24]; u_int param_list_len; struct utf8_data utf8data; int utf8started; int ch; struct utf8_data last; const struct input_state *state; int flags; #define INPUT_DISCARD 0x1 #define INPUT_LAST 0x2 struct input_requests requests; u_int request_count; struct event request_timer; /* * All input received since we were last in the ground state. Sent to * control clients on connection. */ struct evbuffer *since_ground; struct event ground_timer; }; /* Helper functions. */ struct input_transition; static void input_request_timer_callback(int, short, void *); static void input_start_request_timer(struct input_ctx *); static struct input_request *input_make_request(struct input_ctx *, enum input_request_type); static void input_free_request(struct input_request *); static int input_add_request(struct input_ctx *, enum input_request_type, int); static int input_split(struct input_ctx *); static int input_get(struct input_ctx *, u_int, int, int); static void input_set_state(struct input_ctx *, const struct input_transition *); static void input_reset_cell(struct input_ctx *); static void input_report_current_theme(struct input_ctx *); static void input_osc_4(struct input_ctx *, const char *); static void input_osc_8(struct input_ctx *, const char *); static void input_osc_10(struct input_ctx *, const char *); static void input_osc_11(struct input_ctx *, const char *); static void input_osc_12(struct input_ctx *, const char *); static void input_osc_52(struct input_ctx *, const char *); static void input_osc_104(struct input_ctx *, const char *); static void input_osc_110(struct input_ctx *, const char *); static void input_osc_111(struct input_ctx *, const char *); static void input_osc_112(struct input_ctx *, const char *); static void input_osc_133(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_sm_graphics(struct input_ctx *); static void input_csi_dispatch_winops(struct input_ctx *); static void input_csi_dispatch_sgr_256(struct input_ctx *, int, u_int *); static void input_csi_dispatch_sgr_rgb(struct input_ctx *, int, u_int *); 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_DSR_PRIVATE, 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_QUERY_PRIVATE, 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_GRAPHICS, 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 }, { 'S', "?", INPUT_CSI_SM_GRAPHICS }, { '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 }, { 'n', "?", INPUT_CSI_DSR_PRIVATE }, { 'p', "?$", INPUT_CSI_QUERY_PRIVATE }, { '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 } }; /* Maximum of bytes allowed to read in a single input. */ static size_t input_buffer_size = INPUT_BUF_DEFAULT_SIZE; /* 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)); } /* Stop UTF-8 and enter an invalid character. */ static void input_stop_utf8(struct input_ctx *ictx) { struct screen_write_ctx *sctx = &ictx->ctx; static struct utf8_data rc = { "\357\277\275", 3, 3, 1 }; if (ictx->utf8started) { utf8_copy(&ictx->cell.cell.data, &rc); screen_write_collect_add(sctx, &ictx->cell.cell); } ictx->utf8started = 0; } /* * Timer - if this expires then have been waiting for a terminator for too * long, so reset to ground. */ static void input_ground_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_ground_timer(struct input_ctx *ictx) { struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; event_del(&ictx->ground_timer); event_add(&ictx->ground_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->ground_timer, input_ground_timer_callback, ictx); TAILQ_INIT(&ictx->requests); evtimer_set(&ictx->request_timer, input_request_timer_callback, ictx); input_reset(ictx, 0); return (ictx); } /* Destroy input parser. */ void input_free(struct input_ctx *ictx) { struct input_request *ir, *ir1; 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); } TAILQ_FOREACH_SAFE(ir, &ictx->requests, entry, ir1) input_free_request(ir); event_del(&ictx->request_timer); free(ictx->input_buf); evbuffer_free(ictx->since_ground); event_del(&ictx->ground_timer); 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->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; /* Flag new input while in a mode. */ if (!TAILQ_EMPTY(&wp->modes)) wp->flags |= PANE_UNSEENCHANGES; /* NULL wp if there is a mode set as don't want to update the tty. */ if (TAILQ_EMPTY(&wp->modes)) screen_write_start_pane(sctx, wp, &wp->base); 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); } /* Send reply. */ static void input_send_reply(struct input_ctx *ictx, const char *reply) { struct bufferevent *bev = ictx->event; if (bev != NULL) { log_debug("%s: %s", __func__, reply); bufferevent_write(bev, reply, strlen(reply)); } } /* Reply to terminal query. */ static void printflike(3, 4) input_reply(struct input_ctx *ictx, int add, const char *fmt, ...) { struct input_request *ir; va_list ap; char *reply; va_start(ap, fmt); xvasprintf(&reply, fmt, ap); va_end(ap); if (add && !TAILQ_EMPTY(&ictx->requests)) { ir = input_make_request(ictx, INPUT_REQUEST_QUEUE); ir->data = reply; } else { input_send_reply(ictx, reply); free(reply); } } /* Clear saved state. */ static void input_clear(struct input_ctx *ictx) { event_del(&ictx->ground_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->ground_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; input_stop_utf8(ictx); /* 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); utf8_copy(&ictx->last, &ictx->cell.cell.data); ictx->flags |= INPUT_LAST; 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_buffer_size) { 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; struct grid_cell gc, first_gc; u_int cx, line; u_int width; int has_content = 0; input_stop_utf8(ictx); /* 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. */ cx = s->cx; if (cx >= screen_size_x(s) - 1) break; /* Find the next tab point, or use the last column if none. */ line = s->cy + s->grid->hsize; grid_get_cell(s->grid, cx, line, &first_gc); do { if (!has_content) { grid_get_cell(s->grid, cx, line, &gc); if (gc.data.size != 1 || *gc.data.data != ' ' || !grid_cells_look_equal(&gc, &first_gc)) has_content = 1; } cx++; if (bit_test(s->tabs, cx)) break; } while (cx < screen_size_x(s) - 1); width = cx - s->cx; if (has_content || width > sizeof gc.data.data) s->cx = cx; else { grid_get_cell(s->grid, s->cx, line, &gc); grid_set_tab(&gc, width); screen_write_collect_add(sctx, &gc); } 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->flags &= ~INPUT_LAST; 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->flags &= ~INPUT_LAST; 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; struct options *oo; int i, n, m, ek, set, p; 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); if (n != 4) break; m = input_get(ictx, 1, 0, 0); /* * Set the extended key reporting mode as per the client * request, unless "extended-keys" is set to "off". */ ek = options_get_number(global_options, "extended-keys"); if (ek == 0) break; screen_write_mode_clear(sctx, EXTENDED_KEY_MODES); if (m == 2) screen_write_mode_set(sctx, MODE_KEYS_EXTENDED_2); else if (m == 1 || ek == 2) screen_write_mode_set(sctx, MODE_KEYS_EXTENDED); break; case INPUT_CSI_MODOFF: n = input_get(ictx, 0, 0, 0); if (n != 4) break; /* * Clear the extended key reporting mode as per the client * request, unless "extended-keys always" forces into mode 1. */ screen_write_mode_clear(sctx, MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2); if (options_get_number(global_options, "extended-keys") == 2) screen_write_mode_set(sctx, MODE_KEYS_EXTENDED); 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: #ifdef ENABLE_SIXEL input_reply(ictx, 1, "\033[?1;2;4c"); #else input_reply(ictx, 1, "\033[?1;2c"); #endif 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, 1, "\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_PRIVATE: switch (input_get(ictx, 0, 0, 0)) { case 996: input_report_current_theme(ictx); break; } break; case INPUT_CSI_QUERY_PRIVATE: switch (input_get(ictx, 0, 0, 0)) { case 12: /* cursor blink: 1 = blink, 2 = steady */ if (s->cstyle != SCREEN_CURSOR_DEFAULT || s->mode & MODE_CURSOR_BLINKING_SET) n = (s->mode & MODE_CURSOR_BLINKING) ? 1 : 2; else { if (ictx->wp != NULL) oo = ictx->wp->options; else oo = global_options; p = options_get_number(oo, "cursor-style"); /* blink for 1,3,5; steady for 0,2,4,6 */ n = (p == 1 || p == 3 || p == 5) ? 1 : 2; } input_reply(ictx, 1, "\033[?12;%d$y", n); break; case 2004: /* bracketed paste */ n = (s->mode & MODE_BRACKETPASTE) ? 1 : 2; input_reply(ictx, 1, "\033[?2004;%d$y", n); break; case 1004: /* focus reporting */ n = (s->mode & MODE_FOCUSON) ? 1 : 2; input_reply(ictx, 1, "\033[?1004;%d$y", n); break; case 1006: /* SGR mouse */ n = (s->mode & MODE_MOUSE_SGR) ? 1 : 2; input_reply(ictx, 1, "\033[?1006;%d$y", n); break; case 2031: input_reply(ictx, 1, "\033[?2031;2$y"); break; } break; case INPUT_CSI_DSR: switch (input_get(ictx, 0, 0, 0)) { case -1: break; case 5: input_reply(ictx, 1, "\033[0n"); break; case 6: input_reply(ictx, 1, "\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->flags & INPUT_LAST) break; 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_copy(&ictx->cell.cell.data, &ictx->last); for (i = 0; i < n; i++) screen_write_collect_add(sctx, &ictx->cell.cell); 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_SM_GRAPHICS: input_csi_dispatch_sm_graphics(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) break; screen_set_cursor_style(n, &s->cstyle, &s->mode); if (n == 0) { /* Go back to default blinking state. */ screen_write_mode_clear(sctx, MODE_CURSOR_BLINKING_SET); } break; case INPUT_CSI_XDA: n = input_get(ictx, 0, 0, 0); if (n == 0) { input_reply(ictx, 1, "\033P>|tmux %s\033\\", getversion()); } break; } ictx->flags &= ~INPUT_LAST; 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; case 2031: screen_write_mode_clear(sctx, MODE_THEME_UPDATES); 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 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: screen_write_mode_set(sctx, MODE_FOCUSON); 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; case 2031: screen_write_mode_set(sctx, MODE_THEME_UPDATES); break; default: log_debug("%s: unknown '%c'", __func__, ictx->ch); break; } } } /* Handle CSI graphics SM. */ static void input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx) { #ifdef ENABLE_SIXEL int n, m, o; if (ictx->param_list_len > 3) return; n = input_get(ictx, 0, 0, 0); m = input_get(ictx, 1, 0, 0); o = input_get(ictx, 2, 0, 0); if (n == 1 && (m == 1 || m == 2 || m == 4)) { input_reply(ictx, 1, "\033[?%d;0;%uS", n, SIXEL_COLOUR_REGISTERS); } else input_reply(ictx, 1, "\033[?%d;3;%dS", n, o); #endif } /* 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; struct window *w = NULL; u_int x = screen_size_x(s), y = screen_size_y(s); int n, m; if (wp != NULL) w = wp->window; m = 0; while ((n = input_get(ictx, m, 0, -1)) != -1) { switch (n) { case 1: case 2: case 5: case 6: case 7: case 11: case 13: 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 14: if (w == NULL) break; input_reply(ictx, 1, "\033[4;%u;%ut", y * w->ypixel, x * w->xpixel); break; case 15: if (w == NULL) break; input_reply(ictx, 1, "\033[5;%u;%ut", y * w->ypixel, x * w->xpixel); break; case 16: if (w == NULL) break; input_reply(ictx, 1, "\033[6;%u;%ut", w->ypixel, w->xpixel); break; case 18: input_reply(ictx, 1, "\033[8;%u;%ut", y, x); break; case 19: input_reply(ictx, 1, "\033[9;%u;%ut", y, x); 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(w); server_status_window(w); break; } 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, link; 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: link = gc->link; memcpy(gc, &grid_default_cell, sizeof *gc); gc->link = link; 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 = 8; 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_ground_timer(ictx); ictx->flags &= ~INPUT_LAST; } /* Handle DECRQSS query. */ static int input_handle_decrqss(struct input_ctx *ictx) { struct window_pane *wp = ictx->wp; struct options *oo; struct screen_write_ctx *sctx = &ictx->ctx; u_char *buf = ictx->input_buf; size_t len = ictx->input_len; struct screen *s = sctx->s; int ps, opt_ps, blinking; if (len < 3 || buf[1] != ' ' || buf[2] != 'q') goto not_recognized; /* * Cursor style query: DCS $ q SP q * Reply: DCS 1 $ r SP q SP q ST */ if (s->cstyle == SCREEN_CURSOR_BLOCK || s->cstyle == SCREEN_CURSOR_UNDERLINE || s->cstyle == SCREEN_CURSOR_BAR) { blinking = (s->mode & MODE_CURSOR_BLINKING) != 0; switch (s->cstyle) { case SCREEN_CURSOR_BLOCK: ps = blinking ? 1 : 2; break; case SCREEN_CURSOR_UNDERLINE: ps = blinking ? 3 : 4; break; case SCREEN_CURSOR_BAR: ps = blinking ? 5 : 6; break; default: ps = 0; break; } } else { /* * No explicit runtime style: fall back to the configured * cursor-style option (integer Ps 0..6). Pane options inherit. */ if (wp != NULL) oo = wp->options; else oo = global_options; opt_ps = options_get_number(oo, "cursor-style"); /* Sanity clamp: valid Ps are 0..6 per DECSCUSR. */ if (opt_ps < 0 || opt_ps > 6) opt_ps = 0; ps = opt_ps; } log_debug("%s: DECRQSS cursor -> Ps=%d (cstyle=%d mode=%#x)", __func__, ps, s->cstyle, s->mode); input_reply(ictx, 1, "\033P1$r q%d q\033\\", ps); return (0); not_recognized: /* Unrecognized DECRQSS: send DCS 0 $ r Pt ST. */ input_reply(ictx, 1, "\033P0$r\033\\"); return (0); } /* DCS terminator (ST) received. */ static int input_dcs_dispatch(struct input_ctx *ictx) { struct window_pane *wp = ictx->wp; struct options *oo; 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; long long allow_passthrough = 0; #ifdef ENABLE_SIXEL struct window *w; struct sixel_image *si; int p2; #endif if (wp == NULL) return (0); oo = wp->options; if (ictx->flags & INPUT_DISCARD) { log_debug("%s: %zu bytes (discard)", __func__, len); return (0); } #ifdef ENABLE_SIXEL w = wp->window; if (buf[0] == 'q' && ictx->interm_len == 0) { if (input_split(ictx) != 0) return (0); p2 = input_get(ictx, 1, 0, 0); if (p2 == -1) p2 = 0; si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel); if (si != NULL) screen_write_sixelimage(sctx, si, ictx->cell.cell.bg); } #endif /* DCS sequences with intermediate byte '$' (includes DECRQSS). */ if (ictx->interm_len == 1 && ictx->interm_buf[0] == '$') { /* DECRQSS is DCS $ q Pt ST. */ if (len >= 1 && buf[0] == 'q') return (input_handle_decrqss(ictx)); /* * Not DECRQSS. DCS '$' is currently only used by DECRQSS, but * leave other '$' DCS (if any appear in future) to existing * handlers. */ } allow_passthrough = options_get_number(oo, "allow-passthrough"); if (!allow_passthrough) return (0); log_debug("%s: \"%s\"", __func__, buf); if (len >= prefixlen && strncmp(buf, prefix, prefixlen) == 0) { screen_write_rawstring(sctx, buf + prefixlen, len - prefixlen, allow_passthrough == 2); } return (0); } /* OSC string started. */ static void input_enter_osc(struct input_ctx *ictx) { log_debug("%s", __func__); input_clear(ictx); input_start_ground_timer(ictx); ictx->flags &= ~INPUT_LAST; } /* 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 != '\0') return; if (*p == ';') p++; switch (option) { case 0: case 2: if (wp != NULL && options_get_number(wp->options, "allow-set-title") && screen_set_title(sctx->s, p)) { 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 8: input_osc_8(ictx, p); 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; case 133: input_osc_133(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_ground_timer(ictx); ictx->flags &= ~INPUT_LAST; } /* 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 (wp != NULL && options_get_number(wp->options, "allow-set-title") && screen_set_title(sctx->s, ictx->input_buf)) { 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_ground_timer(ictx); ictx->flags &= ~INPUT_LAST; } /* 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->flags &= ~INPUT_LAST; if (!ictx->utf8started) { ictx->utf8started = 1; if (utf8_open(ud, ictx->ch) != UTF8_MORE) input_stop_utf8(ictx); return (0); } switch (utf8_append(ud, ictx->ch)) { case UTF8_MORE: return (0); case UTF8_ERROR: input_stop_utf8(ictx); 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); utf8_copy(&ictx->last, &ictx->cell.cell.data); ictx->flags |= INPUT_LAST; return (0); } /* Reply to a colour request. */ static void input_osc_colour_reply(struct input_ctx *ictx, int add, u_int n, int idx, int c, enum input_end_type end_type) { 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 (end_type == INPUT_END_BEL) end = "\007"; else end = "\033\\"; if (n == 4) { input_reply(ictx, add, "\033]%u;%d;rgb:%02hhx%02hhx/%02hhx%02hhx/%02hhx%02hhx%s", n, idx, r, r, g, g, b, b, end); } else { input_reply(ictx, add, "\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; struct colour_palette *palette = ictx->palette; 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(palette, idx|COLOUR_FLAG_256); if (c != -1) { input_osc_colour_reply(ictx, 1, 4, idx, c, ictx->input_end); s = next; continue; } input_add_request(ictx, INPUT_REQUEST_PALETTE, idx); s = next; continue; } if ((c = colour_parseX11(s)) == -1) { s = next; continue; } if (colour_palette_set(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 8 sequence for embedding hyperlinks. */ static void input_osc_8(struct input_ctx *ictx, const char *p) { struct hyperlinks *hl = ictx->ctx.s->hyperlinks; struct grid_cell *gc = &ictx->cell.cell; const char *start, *end, *uri; char *id = NULL; for (start = p; (end = strpbrk(start, ":;")) != NULL; start = end + 1) { if (end - start >= 4 && strncmp(start, "id=", 3) == 0) { if (id != NULL) goto bad; id = xstrndup(start + 3, end - start - 3); } /* The first ; is the end of parameters and start of the URI. */ if (*end == ';') break; } if (end == NULL || *end != ';') goto bad; uri = end + 1; if (*uri == '\0') { gc->link = 0; free(id); return; } gc->link = hyperlinks_put(hl, uri, id); if (id == NULL) log_debug("hyperlink (anonymous) %s = %u", uri, gc->link); else log_debug("hyperlink (id=%s) %s = %u", id, uri, gc->link); free(id); return; bad: log_debug("bad OSC 8 %s", p); free(id); } /* Handle the OSC 10 sequence for setting and querying foreground colour. */ static void input_osc_10(struct input_ctx *ictx, const char *p) { struct window_pane *wp = ictx->wp; struct grid_cell defaults; int c; if (strcmp(p, "?") == 0) { if (wp == NULL) return; c = window_pane_get_fg_control_client(wp); if (c == -1) { tty_default_colours(&defaults, wp); if (COLOUR_DEFAULT(defaults.fg)) c = window_pane_get_fg(wp); else c = defaults.fg; } input_osc_colour_reply(ictx, 1, 10, 0, c, ictx->input_end); return; } if ((c = colour_parseX11(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; int c; if (strcmp(p, "?") == 0) { if (wp == NULL) return; c = window_pane_get_bg(wp); input_osc_colour_reply(ictx, 1, 11, 0, c, ictx->input_end); return; } if ((c = colour_parseX11(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|PANE_THEMECHANGED); 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|PANE_THEMECHANGED); 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, 1, 12, 0, c, ictx->input_end); } return; } if ((c = colour_parseX11(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 133 sequence. */ static void input_osc_133(struct input_ctx *ictx, const char *p) { struct grid *gd = ictx->ctx.s->grid; u_int line = ictx->ctx.s->cy + gd->hsize; struct grid_line *gl; if (line > gd->hsize + gd->sy - 1) return; gl = grid_get_line(gd, line); switch (*p) { case 'A': gl->flags |= GRID_LINE_START_PROMPT; break; case 'C': gl->flags |= GRID_LINE_START_OUTPUT; break; } } /* Handle the OSC 52 sequence for setting the clipboard. */ static void 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; const char* allow = "cpqs01234567"; char flags[sizeof "cpqs01234567"] = ""; u_int i, j = 0; 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); for (i = 0; p + i != end; i++) { if (strchr(allow, p[i]) != NULL && strchr(flags, p[i]) == NULL) flags[j++] = p[i]; } log_debug("%s: %.*s %s", __func__, (int)(end - p - 1), p, flags); if (strcmp(end, "?") == 0) { if ((pb = paste_get_top(NULL)) != NULL) buf = paste_buffer_data(pb, &len); 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, flags, 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; int outlen = 0; if (buf != NULL && len != 0) { if (len >= ((size_t)INT_MAX * 3 / 4) - 1) return; outlen = 4 * ((len + 2) / 3) + 1; out = xmalloc(outlen); if ((outlen = b64_ntop(buf, len, out, outlen)) == -1) { 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); } /* Set input buffer size. */ void input_set_buffer_size(size_t buffer_size) { log_debug("%s: %lu -> %lu", __func__, input_buffer_size, buffer_size); input_buffer_size = buffer_size; } /* Request timer. Remove any requests that are too old. */ static void input_request_timer_callback(__unused int fd, __unused short events, void *arg) { struct input_ctx *ictx = arg; struct input_request *ir, *ir1; time_t t = time(NULL); TAILQ_FOREACH_SAFE(ir, &ictx->requests, entry, ir1) { if (ir->t >= t - INPUT_REQUEST_TIMEOUT) continue; if (ir->type == INPUT_REQUEST_QUEUE) input_send_reply(ir->ictx, ir->data); input_free_request(ir); } if (ictx->request_count != 0) input_start_request_timer(ictx); } /* Start the request timer. */ static void input_start_request_timer(struct input_ctx *ictx) { struct timeval tv = { .tv_sec = 0, .tv_usec = 500000 }; event_del(&ictx->request_timer); event_add(&ictx->request_timer, &tv); } /* Create a request. */ static struct input_request * input_make_request(struct input_ctx *ictx, enum input_request_type type) { struct input_request *ir; ir = xcalloc (1, sizeof *ir); ir->type = type; ir->ictx = ictx; ir->t = time(NULL); if (++ictx->request_count == 1) input_start_request_timer(ictx); TAILQ_INSERT_TAIL(&ictx->requests, ir, entry); return (ir); } /* Free a request. */ static void input_free_request(struct input_request *ir) { struct input_ctx *ictx = ir->ictx; if (ir->c != NULL) TAILQ_REMOVE(&ir->c->input_requests, ir, centry); ictx->request_count--; TAILQ_REMOVE(&ictx->requests, ir, entry); free(ir->data); free(ir); } /* Add a request. */ static int input_add_request(struct input_ctx *ictx, enum input_request_type type, int idx) { struct window_pane *wp = ictx->wp; struct window *w; struct client *c = NULL, *loop; struct input_request *ir; char s[64]; if (wp == NULL) return (-1); w = wp->window; TAILQ_FOREACH(loop, &clients, entry) { if (loop->flags & CLIENT_UNATTACHEDFLAGS) continue; if (loop->session == NULL || !session_has(loop->session, w)) continue; if (~loop->tty.flags & TTY_STARTED) continue; if (c == NULL) c = loop; else if (timercmp(&loop->activity_time, &c->activity_time, >)) c = loop; } if (c == NULL) return (-1); ir = input_make_request(ictx, type); ir->c = c; ir->idx = idx; ir->end = ictx->input_end; TAILQ_INSERT_TAIL(&c->input_requests, ir, centry); switch (type) { case INPUT_REQUEST_PALETTE: xsnprintf(s, sizeof s, "\033]4;%d;?\033\\", idx); tty_puts(&c->tty, s); break; case INPUT_REQUEST_QUEUE: break; } return (0); } /* Handle a reply to a request. */ void input_request_reply(struct client *c, enum input_request_type type, void *data) { struct input_request *ir, *ir1, *found = NULL; struct input_request_palette_data *pd = data; int complete = 0; TAILQ_FOREACH_SAFE(ir, &c->input_requests, centry, ir1) { if (ir->type == type && pd->idx == ir->idx) { found = ir; break; } input_free_request(ir); } if (found == NULL) return; TAILQ_FOREACH_SAFE(ir, &found->ictx->requests, entry, ir1) { if (complete && ir->type != INPUT_REQUEST_QUEUE) break; if (ir->type == INPUT_REQUEST_QUEUE) input_send_reply(ir->ictx, ir->data); else if (ir == found && ir->type == INPUT_REQUEST_PALETTE) { input_osc_colour_reply(ir->ictx, 0, 4, pd->idx, pd->c, ir->end); complete = 1; } input_free_request(ir); } } /* Cancel pending requests for client. */ void input_cancel_requests(struct client *c) { struct input_request *ir, *ir1; TAILQ_FOREACH_SAFE(ir, &c->input_requests, entry, ir1) input_free_request(ir); } /* Report current theme. */ static void input_report_current_theme(struct input_ctx *ictx) { switch (window_pane_get_theme(ictx->wp)) { case THEME_DARK: input_reply(ictx, 0, "\033[?997;1n"); break; case THEME_LIGHT: input_reply(ictx, 0, "\033[?997;2n"); break; case THEME_UNKNOWN: break; } } tmux-tmux-f222026/job.c000066400000000000000000000233771511153563100147020ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #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, do_close = 1; const char *home, *shell; sigset_t set, oldset; struct winsize ws; char **argvp, tty[TTY_NAME_MAX], *argv0; struct options *oo; /* * Do not set TERM during .tmux.conf (second argument here), 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); if (~flags & JOB_DEFAULTSHELL) shell = _PATH_BSHELL; else { if (s != NULL) oo = s->options; else oo = global_s_options; shell = options_get_string(oo, "default-shell"); if (!checkshell(shell)) shell = _PATH_BSHELL; } argv0 = shell_argv0(shell, 0); 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, shell=%s", __func__, cwd == NULL ? "" : cwd, shell); } else { log_debug("%s: cmd=%s, cwd=%s, shell=%s", __func__, cmd, cwd == NULL ? "" : cwd, shell); } 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) { if (chdir(cwd) == 0) environ_set(env, "PWD", 0, "%s", cwd); else if ((home = find_home()) != NULL && chdir(home) == 0) environ_set(env, "PWD", 0, "%s", home); else if (chdir("/") == 0) environ_set(env, "PWD", 0, "/"); else fatal("chdir failed"); } environ_push(env); environ_free(env); if (~flags & JOB_PTY) { if (dup2(out[1], STDIN_FILENO) == -1) fatal("dup2 failed"); do_close = do_close && out[1] != STDIN_FILENO; if (dup2(out[1], STDOUT_FILENO) == -1) fatal("dup2 failed"); do_close = do_close && out[1] != STDOUT_FILENO; if (flags & JOB_SHOWSTDERR) { if (dup2(out[1], STDERR_FILENO) == -1) fatal("dup2 failed"); do_close = do_close && out[1] != STDERR_FILENO; } else { 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); } if (do_close) close(out[1]); close(out[0]); } closefrom(STDERR_FILENO + 1); if (cmd != NULL) { if (flags & JOB_DEFAULTSHELL) setenv("SHELL", shell, 1); execl(shell, argv0, "-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); free(argv0); job = xcalloc(1, 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; if (flags & JOB_PTY) 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); free(argv0); 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++; } } tmux-tmux-f222026/key-bindings.c000066400000000000000000000731031511153563100165030ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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}\"}" \ " ''" \ " '#{?mouse_hyperlink,Type #[underscore]#{=/9/...:mouse_hyperlink},}' 'C-h' {copy-mode -q; send-keys -l -- \"#{q:mouse_hyperlink}\"}" \ " '#{?mouse_hyperlink,Copy #[underscore]#{=/9/...:mouse_hyperlink},}' 'h' {copy-mode -q; set-buffer -- \"#{q:mouse_hyperlink}\"}" \ " ''" \ " 'Horizontal Split' 'h' {split-window -h}" \ " 'Vertical Split' 'v' {split-window -v}" \ " ''" \ " '#{?#{>:#{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) { if (note != NULL) { free((void *)bd->note); bd->note = xstrdup(note); } if (repeat) bd->flags |= KEY_BINDING_REPEAT; } 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 *const 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 and detach a client from a list' D { choose-client -Z }", "bind -N 'Spread panes out evenly' E { select-layout -E }", "bind -N 'Switch to the last client' L { switch-client -l }", "bind -N 'Clear the marked pane' M { select-pane -M }", "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 'Set the main-horizontal-mirrored layout' M-6 { select-layout main-horizontal-mirrored }", "bind -N 'Set the main-vertical-mirrored layout' M-7 { select-layout main-vertical-mirrored }", "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 -N 'Display window menu' < { display-menu -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU " }", "bind -N 'Display pane menu' > { 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 '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", /* Mouse wheel up on pane. */ "bind -n WheelUpPane { if -F '#{||:#{alternate_on},#{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 { switch-client -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 " }", "bind -n M-MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }", /* Mouse button 3 down on status line. */ "bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", "bind -n M-MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}", /* Mouse button 3 down on pane. */ "bind -n MouseDown3Pane { if -Ft= '#{||:#{mouse_any_flag},#{&&:#{pane_in_mode},#{?#{m/r:(copy|view)-mode,#{pane_mode}},0,1}}}' { select-pane -t=; send -M } { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " } }", "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", /* Mouse on scrollbar. */ "bind -n MouseDown1ScrollbarUp { copy-mode -u }", "bind -n MouseDown1ScrollbarDown { copy-mode -d }", "bind -n MouseDrag1ScrollbarSlider { copy-mode -S }", /* 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-l { send -X cursor-centre-vertical }", "bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }", "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 z { send -X scroll-middle }", "bind -Tcopy-mode-vi l { send -X cursor-right }", "bind -Tcopy-mode-vi n { send -X search-again }", "bind -Tcopy-mode-vi o { send -X other-end }", "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 Home { send -X start-of-line }", "bind -Tcopy-mode-vi End { send -X end-of-line }", "bind -Tcopy-mode-vi MouseDown1Pane { select-pane }", "bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }", "bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }", "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); } tmux-tmux-f222026/key-string.c000066400000000000000000000316071511153563100162170ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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 }, { "BTab", KEYC_BTAB }, { "Space", ' ' }, { "BSpace", KEYC_BSPACE }, /* * C0 control characters, with the exception of Tab, Enter, * and Esc, should never appear as keys. We still render them, * so to be able to spot them in logs in case of an abnormality. */ { "[NUL]", C0_NUL }, { "[SOH]", C0_SOH }, { "[STX]", C0_STX }, { "[ETX]", C0_ETX }, { "[EOT]", C0_EOT }, { "[ENQ]", C0_ENQ }, { "[ASC]", C0_ASC }, { "[BEL]", C0_BEL }, { "[BS]", C0_BS }, { "Tab", C0_HT }, { "[LF]", C0_LF }, { "[VT]", C0_VT }, { "[FF]", C0_FF }, { "Enter", C0_CR }, { "[SO]", C0_SO }, { "[SI]", C0_SI }, { "[DLE]", C0_DLE }, { "[DC1]", C0_DC1 }, { "[DC2]", C0_DC2 }, { "[DC3]", C0_DC3 }, { "[DC4]", C0_DC4 }, { "[NAK]", C0_NAK }, { "[SYN]", C0_SYN }, { "[ETB]", C0_ETB }, { "[CAN]", C0_CAN }, { "[EM]", C0_EM }, { "[SUB]", C0_SUB }, { "Escape", C0_ESC }, { "[FS]", C0_FS }, { "[GS]", C0_GS }, { "[RS]", C0_RS }, { "[US]", C0_US }, /* 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) { key_code key, modifiers = 0; 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 short Ctrl key. */ if (string[0] == '^' && string[1] != '\0') { if (string[2] == '\0') return (tolower((u_char)string[1])|KEYC_CTRL); modifiers |= KEYC_CTRL; string++; } /* Check for modifiers. */ 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; } 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; } /* 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_END) { 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; } /* Printable ASCII keys. */ 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); if (saved & KEYC_SENT) strlcat(out, "S", sizeof out); strlcat(out, "]", sizeof out); } return (out); } tmux-tmux-f222026/layout-custom.c000066400000000000000000000202771511153563100167510ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" 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) { *cause = xstrdup("invalid layout"); 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"); goto fail; } /* 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); } tmux-tmux-f222026/layout-set.c000066400000000000000000000432171511153563100162310ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* * 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_h_mirrored(struct window *); static void layout_set_main_v(struct window *); static void layout_set_main_v_mirrored(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-horizontal-mirrored", layout_set_main_h_mirrored }, { "main-vertical", layout_set_main_v }, { "main-vertical-mirrored", layout_set_main_v_mirrored }, { "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 (strcmp(layout_sets[i].name, name) == 0) return (i); } 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_h_mirrored(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 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); } /* 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); /* 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); } static void layout_set_main_v_mirrored(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 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); } /* 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); /* 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 options *oo = w->options; struct window_pane *wp; struct layout_cell *lc, *lcrow, *lcchild; u_int n, width, height, used, sx, sy; u_int i, j, columns, rows, max_columns; layout_print_cell(w->layout_root, __func__, 1); /* Get number of panes. */ n = window_count_panes(w); if (n <= 1) return; /* Get maximum columns from window option. */ max_columns = options_get_number(oo, "tiled-layout-max-columns"); /* How many rows and columns are wanted? */ rows = columns = 1; while (rows * columns < n) { rows++; if (rows * columns < n && (max_columns == 0 || columns < max_columns)) 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); } tmux-tmux-f222026/layout.c000066400000000000000000000723631511153563100154440ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * Copyright (c) 2016 Stephen Kent * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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_horizontal_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, scrollbars, sb_pos, sb_w, sb_pad; u_int sx, sy; status = options_get_number(w->options, "pane-border-status"); scrollbars = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); TAILQ_FOREACH(wp, &w->panes, entry) { if ((lc = wp->layout_cell) == NULL || wp == skip) continue; wp->xoff = lc->xoff; wp->yoff = lc->yoff; sx = lc->sx; sy = lc->sy; if (layout_add_horizontal_border(w, lc, status)) { if (status == PANE_STATUS_TOP) wp->yoff++; sy--; } if (window_pane_show_scrollbar(wp, scrollbars)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; if (sb_w < 1) sb_w = 1; if (sb_pad < 0) sb_pad = 0; if (sb_pos == PANE_SCROLLBARS_LEFT) { if ((int)sx - sb_w < PANE_MINIMUM) { wp->xoff = wp->xoff + (int)sx - PANE_MINIMUM; sx = PANE_MINIMUM; } else { sx = sx - sb_w - sb_pad; wp->xoff = wp->xoff + sb_w + sb_pad; } } else /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((int)sx - sb_w - sb_pad < PANE_MINIMUM) sx = PANE_MINIMUM; else sx = sx - sb_w - sb_pad; wp->flags |= PANE_REDRAWSCROLLBAR; } window_pane_resize(wp, sx, 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; struct style *sb_style = &w->active->scrollbar_style; u_int available, minimum; int status, scrollbars; status = options_get_number(w->options, "pane-border-status"); scrollbars = options_get_number(w->options, "pane-scrollbars"); if (lc->type == LAYOUT_WINDOWPANE) { /* Space available in this cell only. */ if (type == LAYOUT_LEFTRIGHT) { available = lc->sx; if (scrollbars) minimum = PANE_MINIMUM + sb_style->width + sb_style->pad; else minimum = PANE_MINIMUM; } else { available = lc->sy; if (layout_add_horizontal_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; struct style *sb_style = &wp->scrollbar_style; 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; int scrollbars; /* * 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"); scrollbars = options_get_number(wp->window->options, "pane-scrollbars"); /* 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 (scrollbars) { minimum = PANE_MINIMUM * 2 + sb_style->width + sb_style->pad; } else minimum = PANE_MINIMUM * 2 + 1; if (sx < minimum) return (NULL); break; case LAYOUT_TOPBOTTOM: if (layout_add_horizontal_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; struct style *sb_style = &w->active->scrollbar_style; u_int number, each, size, this, remainder; int change, changed, status, scrollbars; number = 0; TAILQ_FOREACH (lc, &parent->cells, entry) number++; if (number <= 1) return (0); status = options_get_number(w->options, "pane-border-status"); scrollbars = options_get_number(w->options, "pane-scrollbars"); if (parent->type == LAYOUT_LEFTRIGHT) { if (scrollbars) size = parent->sx - sb_style->width + sb_style->pad; else size = parent->sx; } else if (parent->type == LAYOUT_TOPBOTTOM) { if (layout_add_horizontal_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); /* * Remaining space after assigning that which can be evenly * distributed. */ remainder = size - (number * (each + 1)) + 1; changed = 0; TAILQ_FOREACH (lc, &parent->cells, entry) { change = 0; if (parent->type == LAYOUT_LEFTRIGHT) { change = each - (int)lc->sx; if (remainder > 0) { change++; remainder--; } layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change); } else if (parent->type == LAYOUT_TOPBOTTOM) { if (layout_add_horizontal_border(w, lc, status)) this = each + 1; else this = each; if (remainder > 0) { this++; remainder--; } 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); } tmux-tmux-f222026/log.c000066400000000000000000000060611511153563100147000ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/logo/000077500000000000000000000000001511153563100147105ustar00rootroot00000000000000tmux-tmux-f222026/logo/LICENSE000066400000000000000000000013561511153563100157220ustar00rootroot00000000000000Copyright (c) 2015, Jason Long Permission to use, copy, modify, and/or 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. tmux-tmux-f222026/logo/favicon.ico000066400000000000000000000145661511153563100170450ustar00rootroot00000000000000 (&  (N(  Ãl½ûÁÿÀÿÀÿÁÿÀÿÅÿÅÿÀÿÁÿÀÿÀÿÁÿ½ûÅk,Ž*â*’)î-)ç+‘*è+*è,*æ,)ó%¢#«%¢#«,)ó,*æ+*è+‘*è-)ç*’)î,*â;;ù; ;÷:;ø:;ø:;ø::óBBÿ<<\<<\BBÿ::ó:;ø:;ø:;ø; ;÷;;ù;A<ÿ;B<ÿ;B<ÿ;B<ÿ;B<ÿ9?9üAHBÿ9E9n9E9nAHBÿ9?9ü;B<ÿ;B<ÿ;B<ÿ;B<ÿ;A<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ999j999jBBBÿ;;;ú<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ666k666kBBBÿ;;;ú<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ666k888hAAAÿ:::õ;;;ú;;;ú;;;ú;;;ú;;;ú<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ888h999wIIIÿAAAÿBBBÿBBBÿBBBÿBBBÿBBBÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úAAAÿ999w777 999x777i666k666k666k666k666k<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úAAAÿ999w777 999x777i666k666k666k666k666k<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ888h999wIIIÿAAAÿBBBÿBBBÿBBBÿBBBÿBBBÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ666k888hAAAÿ:::õ;;;ú;;;ú;;;ú;;;ú;;;ú:::þ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ666k666kBBBÿ;;;ú<<<ÿ<<<ÿ<<<ÿ<<<ÿ:::þ<<<ÿ:::þ<<<ÿ<<<ÿ<<<ÿ;;;úBBBÿ666k666kBBBÿ;;;ú<<<ÿ<<<ÿ<<<ÿ:::þ<<<ÿ:::÷>>>ÿ:::þ<<<ÿ<<<ÿ;;;úBBBÿ666k666kBBBÿ;;;ú<<<ÿ<<<ÿ:::þ>>>ÿ:::÷888h:::÷<<<ÿ:::þ<<<ÿ;;;úBBBÿ666k666kBBBÿ;;;ú<<<ÿ:::þ<<<ÿ:::÷888h( @ ¸K¹Ë¸ù¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¸ù¹Ë¸K¸K¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¸K¹Ë¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¹ÿ¸Ê<<<Í<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Ì<<<Í<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ù<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ù;;;Ë<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;Ë:::K<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ:::K:::K;;;Ë;;;ù<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ù;;;Ë:::Ktmux-tmux-f222026/logo/icons/000077500000000000000000000000001511153563100160235ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/128x128/000077500000000000000000000000001511153563100167605ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/128x128/tmux.png000066400000000000000000000034751511153563100204740ustar00rootroot00000000000000‰PNG  IHDR€€ôà‘ùgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<ëPLTEcccoooyyyvvviiijjj]]]^^^DDD<<<;;;:::QQQ´´´ìììýýýÅÅÅeee888;:;3[47\8² ;¼>¹ sÓv'¼*”Þ–bÎdÜ’€×‚¥ã¨ŒÛŽ–ß™×á ¤â¥ÿÿÿ¦ä©öüõ®æ±bbbeee\\\]]]YYYGGG<<<;;;:::KKKªªªíííýýýÈÈÈppp888===>>>TTTaaauuu^^^]]]OOOBBBMMM]]]ggg€€€žžžnnnWWW@@@WWWnnnoooTTTnnnTTT£££QQQPPP¨¨¨```IIIDDD<<<;;;:::QQQ´´´ìììýýýÅÅÅeee888;:;3[47\8² ;¼?¹ xÕz'¼+šàgÏi6À:xÕ{–ß™ÿþÿIÆLׂ¤ã§‹ÛKÇNIÇM‡ÚŠµè·¢á¥pÒsGÆJGÆK„Ù‡¸é»“Þ—~Ö€]Ì`HÆL‚Ø…ÂìŽ꾌ÛIÆLDÅHƒØ…ÏðÑýþû¬å­–Þ˜‰Ú‹KÇO'¼+¹¸!»%.¾2 º$gÐj“Þ–’Ý•ºê½DDDCCC>>><<<;;;:::KKKªªªíííýýýÈÈÈppp888AAA???ooo666ÉÉÉ|||MMMQQQûûûÒÒÒ³³³´´´þþþððð÷÷÷ëëëìììÿÿÿüüüÄÄÄÅÅÅÊÊÊŠŠŠbbbeeennn444;:;:8:KIK­«­ÜÛÜâàâÈÆÈqoq868<:<2[42[31Y2>f?†¯‡ŸÈ Æž™ÂšZ‚[0X13[44[57\8² ²²³ !¸%#¹'"¹&µ" ³$4º8¹¹¸DÅGº#¸"º&RÉV·¸ <Â@#»'¸0¾4¹"¹!¹ *½.0¿48Á;'¼+5À8þê›&štRNSx›™™™™™™™™™™™™™™\ÌóûÑ[ò÷é\\ó]í¢¢^ýgggggggggggggggggggf^ý¥¢Ì]íú€\óý€[òü\ÌóûÑx›™™™™™™™™/^bKGD&Z˜µtIMEå  6dS™ˆIDATxÚíÔeTQÆñ0[ìnÅZìnÅ¢ Ê»»‹µ•ÙUADQÄvm±»ÅFÖ\Áüèà~᜙=gÞqvŽržÿç{çýÍ»Ë0!„ÊP÷jÕkÔ¬¥óðÐÉ©vºõê7hØHÖbðÔÆMtM›5w°9ß‘iÑ²Õæ-¬ÁÀÊɸuÛöȨѲ³Ö§¶nÓ¶]{GéùNž:î–ÉOü!ØN»x:I¼¼}¬KäF„ô >Þ^Ró|ýì?_‰{µk·Ú|¥ÖÓ½G6 »€Q+€1À?‡àAœ¯ A ³àD¼`ƒ\þ=€k0u¾rkv•:í6>ü§€]»ôzæT »go½^¹Ôì‹Ý€^ïÜj:|ä(½>yÔ3™"Ô7¯ZS|ÔñôúåSíž~2¨PxðÓgÄŸ¿HJzù*‘¼ïõ›!ED€¢C¹dz<Ï+ØÅ½VL(><…ç4‰7¿{?¢„Prä¨ÚøŸF)%”;î³E“ùü—ñÊ0âÊNœdÖàx>urèFª©Ó¦§Yx.ÙŽÓÓ/í×3g•“”›=ç›Ù"¬±ÛË ðûÜyó+0ÒU¬´`á¢?S9»„wOIûµxIèÒÊŒ­ª,[¾båªÕáz}¸úéõkÖ®[¿a㦪 B!„2öÀü)\0öùw%tEXtdate:create2021-09-07T10:48:52+00:00Ùpª×%tEXtdate:modify2021-09-07T10:48:52+00:00¨-ktEXtSoftwareXPaint 3.1.4*œIEND®B`‚tmux-tmux-f222026/logo/icons/16x16/000077500000000000000000000000001511153563100166105ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/16x16/tmux.png000066400000000000000000000014271511153563100203170ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<ÿPLTEIIIDDDÏ®EÉHJÌMNNN@@@;;;<<<999rrr```>>>JJJ@@@<<<;;;<<<999rrr```@@@===<<<;;;;;;<<>>>>><:<<:<:::9W95U67U8NYO(±,±#° ³$²# ±$1³5KÍO&¿*»»º(À,PÎS;;;<<<999rrr```qqq___jjjFFFHHHvvv˜˜˜‚‚‚ƒƒƒaaa>>><:<989rqr`_`:8:5T55T63R4_`QqR3S4ÿÿÿ¨8J¤9tRNS ,.....* îííííí}««««««««îíííër ,...* ·qbKGDT䈥tIMEå  |µJ¯IDATÓc`@Œœ\ÜÜ<¼|<ÜÜ\üL Ì‚BÂ""¢bâ¢""Â" ’R–VVVÖ6¶Ö@ÊRJ’AZÆ !`%#Í +°³·¶„€ƒ£ÈÉÜ]\Ý€@A®ÂÝÊ””á¶èf@l‘’ePQõ/o_­ªÆ ®áçïï ¤ü4µX´ut@OD²2°›˜šššs v ß&³)Ï(º‡2%tEXtdate:create2021-09-07T10:58:21+00:00#Óf“%tEXtdate:modify2021-09-07T10:58:21+00:00RŽÞ/tEXtSoftwareXPaint 3.1.4*œIEND®B`‚tmux-tmux-f222026/logo/icons/24x24/000077500000000000000000000000001511153563100166065ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/24x24/tmux.png000066400000000000000000000016051511153563100203130ustar00rootroot00000000000000‰PNG  IHDRשÍÊgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<>PLTEªªªKKKCCCNNN???;;;<<<999ttt‰‰‰<;<;=;,Ž/E’G?ËCkßoØòÚTÊW`ÍcTTTDDD;;;<<<:::lll999???PPPQQQCCC===;;;<<<:::lll999<<>>¯¯¯sss999<:<<9<1_26a7%¸)?¿BHÇKtÕw[Ì^qÓtXXXIII<<<;;;>>>­­­vvv999AAAUUUVVVFFF>>><<<;;;>>>­­­vvv999===EEEBBB===;;;<<<>>>¯¯¯sss999<:<<9<1`39b:)¹,¶!%¸)KÃNOÉR3À7!»%¹¹$¼(=ÃA†Û‰yÕ{>ÃB¸¸¹¹!DÅH}×€;;;<<<>>>­­­vvv999888xxx===°°°ÇÇÇ®®®¯¯¯œœœqqqsssttt666<:<<9<><>®¬®wuw9792_33a4~¬Z‡[0]13`4µ¶¸!· µÿÿÿ fµ°JtRNS<@@@@@@7 ­ö÷÷÷÷÷÷ò¦^þiiiiiiiiii^þöO­ö÷÷ò <@@@@7 k™bKGDm»­tIMEå  ;*ðÙaáIDAT8Ëc` `— I)iq([BVŽ $Ï,¯ ¨¤¬Ê*ªjêJP¶²†¦<3P‹–¶——7xùøúù{À—¶ P«ŽBE·—+P›®7.ÞºlƒTA€`óE ·è±£+  C} ¡á‘`À‰iET´?r Ò€¢n£8ˆ‹OHDâóðš$!@rJj‚—nÊTÀofž‘ YÙNŽ…¥P •µ-6`gïà(ÊÂNÎ.®nÀÕÝÃS„.9gYÍäAœ,9%tEXtdate:create2021-09-07T10:58:21+00:00#Óf“%tEXtdate:modify2021-09-07T10:58:21+00:00RŽÞ/tEXtSoftwareXPaint 3.1.4*œIEND®B`‚tmux-tmux-f222026/logo/icons/48x48/000077500000000000000000000000001511153563100166225ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/48x48/tmux.png000066400000000000000000000021261511153563100203260ustar00rootroot00000000000000‰PNG  IHDR00`Ü µgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<žPLTEÿÿÿ+++‰‰‰HHHXXXCCC;;;<<<žžžÎÎÎIII<9<;9;.l/*½-kÑnWÊYŽÜ‘ÆíÇ-¾1oÓs·ŠÛŒ^^^QQQ@@@;;;<<<:::UUUÙÙÙ‰‰‰999<<>>GGG]]]]]]GGGCCC===;;;;;;<<<<<<žžžžžžÎÎÎÎÎÎIIIIII<9<<9<;9;.l/.l03m4=q>)½-¹!4À8mÒpcÎe3À6$»(TÊXÿÿÿ[Ì^@ÄC'¼*¹ ¸¹¹!-¾1IÇLƒØ†’Ý“WËZ!º%¸¹(¼,aÎd—ß™<<<;;;:::UUUÙÙÙ‰‰‰999888TTTÚÚÚÃÃÞžžÛÛÛÝÝÝÍÍÍÎÎÎGGGIII<9<:8:USUÛØÚ‹ˆ‹:7:.l0-k/>{?„†[™\-j.¸¹¹ ¹¸ÿÿÿbÖ$ËdtRNSNTSSSSSSSTB ,Âüþþþþþþþõ±+¸,ì-îî-î-î-î--î--îî-,ìä&¸þ€,Âüþþþþö—NTSSTB ³m„bKGDÿ-ÞtIMEå  :$§Å'$IDATHÇc`¡€‘IJZFV È+(*)# Èɪ¨ª13BÔ³°ªkhjik!]=}d ´¡‘1+ X›‰iJjj 2HKÏÈÌB©05ak`73GUŽUP‹¹;X‡…e 1R,-8 ¬ˆÓ`ÕÀiM¬kÎQ  ! Xâ¸ìTR4ääæå [.Ü ‹ŠKÐ=7'•–•£Gêùa°Fܨ†Á­×ɹ TVU×Ô¢ V8»ð5ð»ºÕ¡ú†Æ¦ftÁ:w°AO¯–VtЂEÈÛG¬AØ×¯­¥¥´¶¢ñ[:üEÀDÅ‚‚CBÃÂñ°ðˆÈ¨hqX¥(Ÿ€Ä'$&%Ktí=`ÿ½Y/]Yµ%tEXtdate:create2021-09-07T10:58:21+00:00#Óf“%tEXtdate:modify2021-09-07T10:58:21+00:00RŽÞ/tEXtSoftwareXPaint 3.1.4*œIEND®B`‚tmux-tmux-f222026/logo/icons/64x64/000077500000000000000000000000001511153563100166165ustar00rootroot00000000000000tmux-tmux-f222026/logo/icons/64x64/tmux.png000066400000000000000000000024141511153563100203220ustar00rootroot00000000000000‰PNG  IHDR@@·ìgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<õPLTEqqqtttSSS:::VVVbbbMMM<<<;;;………ððð–––<:<5R68S9¯!WÀZ<Ã?ˆÛ‹nÑpGÆK™ßœÕòÖ?ÃB‰ÚŒœàÝ“éøêßõáqqqoooHHH:::<<<999ððð;;;>>>OOOxxxaaaSSSJJJ???;;;<<<999ððð<<"»&EÆHŠÜŒƒØ…?ÃB2¿6pÓshÐkCÅG1¿5YÌ\¬å®àžfÏi@ÄCº"¸¹¹!#»'KÇOrÓu“Ý–íùî á¢(¼,¸¹¸"»&7Á;¯æ²øýú???<<<;;;999ððð===œœœ:::ïïï½½½†††………ñññôôôÁÁÁ–––<:<:8:€~€ðï𞜞5R63P4kˆlµÓ¶ž®!® "´&(º,$µ(® ¹¸º ¹!¸¹¸&¼*¹"ÿÿÿdÚë²{tRNS"ŒÃÂÂÂÂÂÂÂùa%Áû»& °8ò>ø>øø>ø>ø>ø>>ø>øø>>øø>8òØ& °ø`%Áù"ŒÃÂÂÂÂù` iÂÛ‰bKGD¦·°•tIMEå  4¢ü„JlIDATXÃc`ƒ0Ê+(*)cU5ue Lq M-m&dýÌ:ºzú†ÀÈØÄÔ‹¸™¹…% \;+›•uuMMm ¨«ohÄ&6¶vl¬PØíšj±*Ãm@mm“ƒ=;ÔG'ì¶àsAM­“#ÔNgr pqæ„ÀåêVCº5n®\c£Œ0j= hnÁÜ=Xˆ0 µ­½ðôâ&€ήÎÀÛ‡‡t÷à¾~¼D…A-.@|‘F±C=!0jÀ¨t4€Ï? ;èëŸ0±ðçƒÀ4 ;˜'>>;;;<<<999^^^ÎÎÎûûû»»»MMM:::===OOOfffsss\\\RRRJJJDDD<<<;;;999^^^ÎÎÎûûû»»»MMM:::<<>><<<<<<;;;;;;999999ffffffÒÒÒÒÒÒûûûûûû´´´´´´HHHHHH::::::<:<<:<4W54W58X96W7°!° >ºA^Äa&½)º"hÑkóüôà@ÃCAÄEzÖ~_Íb3À6yÕ|ÿÿÿ©äª\Ì_+½/VËZŠÛ‹ÛŽdÏg=Ã@TÊX‘Ý”¸è¹œàZÌ]CÅG&¼*¹¸¹! º$SÉVzÖ|ŠÛ á¢„Ù‡9Á=¹!¸¹!º%*½.@ÄD—ßš•Þ˜===;;;<<<999^^^ÎÎÎûûû»»»MMM:::ºººKKK777úúúÁÁÁrrrdddfffåååÙÙÙÑÑÑÒÒÒþþþ¸¸¸´´´½½½YYYGGGHHHLLL888MNM<:<979_]_ÍËÍóñ󼺼NLN:8:4W42U3OrPÀ¦Ê§“¶”BeC3W46W7° ²"%¹)&º*$¸(±!$²(¹¸¹#¼'¸¹!¸0¿4¹#»'¹"¸ ÿÿÿKÁ_tRNS$>============6 zÖýûûûûûûûûûûüñ¡8 ‘üî ’üIûI¶ÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃöõmIû¡ ’üÏ1 ‘üÏ. zÖýûûûûûñ¡2$>=======6ÑÕ¿ÈbKGDºÙtIMEå  6?@IDAThÞc`£` +À("*&.!‰HIËÈÊÉ+àS")¡¨¨¤Ì„ÅxfU5u M-M<@[GWOßÀŸ FÆ&¦fÌèæ³˜[XN˜8qâ$|`ò”©Ó¦Ï˜‰Wͤ‰“&N°²6gA5ŸÕÆÖŽ€éÄZ²c¢­ +Šlö„õmÁ¤I3ÙQ,pržDÐý¤X0q’³ Š®nD˜O‚“&º¹¢XàîAŒ.,˜äáŽb§µ-ðò¤¯F-µ`Ô‚Q F-µ`Ô‚Q Ð,˜5{Îdb€·yÌ7ÁBb€¯™,Z¼d)1À?€< –ÍF "ÓK–¯ ‡É+W­^C  #7™®]7“1XóÁ¨£ŒZ0jÁ¨£ŒZ0jÁ¨ÃÆ‚õdYµ°qÓæ-[·m'F醨H ¢cvvîÚ½gï¾D¨Ü 6Å‚ø„ƒÄ€C‡=F”Êã‰I($§œ œ_^ÁÇfƒ@eUuMm]}E ¾¾¡±°©¹E jmkïèì¢tvv÷ôöõ 3Œ‚Q0 †³.™×»À%tEXtdate:create2021-09-07T10:48:52+00:00Ùpª×%tEXtdate:modify2021-09-07T10:48:52+00:00¨-ktEXtSoftwareXPaint 3.1.4*œIEND®B`‚tmux-tmux-f222026/logo/tmux-logo-1-color.eps000066400000000000000000000517661511153563100206450ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %APL_DSC_Encoding: UTF8 %APLProducer: (Version 10.10.3 (Build 14D136) Quartz PS Context) %%Title: (Unknown) %%Creator: (Unknown) %%CreationDate: (Unknown) %%For: (Unknown) %%DocumentData: Clean7Bit %%LanguageLevel: 2 %%Pages: 1 %%BoundingBox: 0 0 608 160 %%EndComments %%BeginProlog %%BeginFile: cg-pdf.ps %%Copyright: Copyright 2000-2004 Apple Computer Incorporated. %%Copyright: All Rights Reserved. currentpacking true setpacking /cg_md 141 dict def cg_md begin /L3? languagelevel 3 ge def /bd{bind def}bind def /ld{load def}bd /xs{exch store}bd /xd{exch def}bd /cmmtx matrix def mark /sc/setcolor /scs/setcolorspace /dr/defineresource /fr/findresource /T/true /F/false /d/setdash /w/setlinewidth /J/setlinecap /j/setlinejoin /M/setmiterlimit /i/setflat /rc/rectclip /rf/rectfill /rs/rectstroke /f/fill /f*/eofill /sf/selectfont /s/show /xS/xshow /yS/yshow /xyS/xyshow /S/stroke /m/moveto /l/lineto /c/curveto /h/closepath /n/newpath /q/gsave /Q/grestore counttomark 2 idiv {ld}repeat pop /SC{ /ColorSpace fr scs }bd /sopr /setoverprint where{pop/setoverprint}{/pop}ifelse ld /soprm /setoverprintmode where{pop/setoverprintmode}{/pop}ifelse ld /cgmtx matrix def /sdmtx{cgmtx currentmatrix pop}bd /CM {cgmtx setmatrix}bd /cm {cmmtx astore CM concat}bd /W{clip newpath}bd /W*{eoclip newpath}bd statusdict begin product end dup (HP) anchorsearch{ pop pop pop true }{ pop (hp) anchorsearch{ pop pop true }{ pop false }ifelse }ifelse { { { pop pop (0)dup 0 4 -1 roll put F charpath }cshow } }{ {F charpath} }ifelse /cply exch bd /cps {cply stroke}bd /pgsave 0 def /bp{/pgsave save store}bd /ep{pgsave restore showpage}def /re{4 2 roll m 1 index 0 rlineto 0 exch rlineto neg 0 rlineto h}bd /scrdict 10 dict def /scrmtx matrix def /patarray 0 def /createpat{patarray 3 1 roll put}bd /makepat{ scrmtx astore pop gsave initgraphics CM patarray exch get scrmtx makepattern grestore setpattern }bd /cg_BeginEPSF{ userdict save/cg_b4_Inc_state exch put userdict/cg_endepsf/cg_EndEPSF load put count userdict/cg_op_count 3 -1 roll put countdictstack dup array dictstack userdict/cg_dict_array 3 -1 roll put 3 sub{end}repeat /showpage {} def 0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit [] 0 setdash newpath false setstrokeadjust false setoverprint }bd /cg_EndEPSF{ countdictstack 3 sub { end } repeat cg_dict_array 3 1 index length 3 sub getinterval {begin}forall count userdict/cg_op_count get sub{pop}repeat userdict/cg_b4_Inc_state get restore F setpacking }bd /cg_biproc{currentfile/RunLengthDecode filter}bd /cg_aiproc{currentfile/ASCII85Decode filter/RunLengthDecode filter}bd /ImageDataSource 0 def L3?{ /cg_mibiproc{pop pop/ImageDataSource{cg_biproc}def}bd /cg_miaiproc{pop pop/ImageDataSource{cg_aiproc}def}bd }{ /ImageBandMask 0 def /ImageBandData 0 def /cg_mibiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd /cg_miaiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/ASCII85Decode filter/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd }ifelse /imsave 0 def /BI{save/imsave xd mark}bd /EI{imsave restore}bd /ID{ counttomark 2 idiv dup 2 add dict begin {def} repeat pop /ImageType 1 def /ImageMatrix[Width 0 0 Height neg 0 Height]def currentdict dup/ImageMask known{ImageMask}{F}ifelse exch L3?{ dup/MaskedImage known { pop << /ImageType 3 /InterleaveType 2 /DataDict currentdict /MaskDict << /ImageType 1 /Width Width /Height Height /ImageMatrix ImageMatrix /BitsPerComponent 1 /Decode [0 1] currentdict/Interpolate known {/Interpolate Interpolate}if >> >> }if }if exch {imagemask}{image}ifelse end }bd /cguidfix{statusdict begin mark version end {cvr}stopped{cleartomark 0}{exch pop}ifelse 2012 lt{dup findfont dup length dict begin {1 index/FID ne 2 index/UniqueID ne and {def} {pop pop} ifelse}forall currentdict end definefont pop }{pop}ifelse }bd /t_array 0 def /t_i 0 def /t_c 1 string def /x_proc{ exch t_array t_i get add exch moveto /t_i t_i 1 add store }bd /y_proc{ t_array t_i get add moveto /t_i t_i 1 add store }bd /xy_proc{ t_array t_i 2 copy 1 add get 3 1 roll get 4 -1 roll add 3 1 roll add moveto /t_i t_i 2 add store }bd /sop 0 def /cp_proc/x_proc ld /base_charpath { /t_array xs /t_i 0 def { t_c 0 3 -1 roll put currentpoint t_c cply sop cp_proc }forall /t_array 0 def }bd /sop/stroke ld /nop{}def /xsp/base_charpath ld /ysp{/cp_proc/y_proc ld base_charpath/cp_proc/x_proc ld}bd /xysp{/cp_proc/xy_proc ld base_charpath/cp_proc/x_proc ld}bd /xmp{/sop/nop ld /cp_proc/x_proc ld base_charpath/sop/stroke ld}bd /ymp{/sop/nop ld /cp_proc/y_proc ld base_charpath/sop/stroke ld}bd /xymp{/sop/nop ld /cp_proc/xy_proc ld base_charpath/sop/stroke ld}bd /refnt{ findfont dup length dict copy dup /Encoding 4 -1 roll put definefont pop }bd /renmfont{ findfont dup length dict copy definefont pop }bd L3? dup dup{save exch}if /Range 0 def /DataSource 0 def /val 0 def /nRange 0 def /mulRange 0 def /d0 0 def /r0 0 def /di 0 def /ri 0 def /a0 0 def /a1 0 def /r1 0 def /r2 0 def /dx 0 def /Nsteps 0 def /sh3tp 0 def /ymax 0 def /ymin 0 def /xmax 0 def /xmin 0 def /setupFunEval { begin /nRange Range length 2 idiv store /mulRange [ 0 1 nRange 1 sub { 2 mul/nDim2 xd Range nDim2 get Range nDim2 1 add get 1 index sub 255 div exch }for ]store end }bd /FunEval { begin nRange mul /val xd 0 1 nRange 1 sub { dup 2 mul/nDim2 xd val add DataSource exch get mulRange nDim2 get mul mulRange nDim2 1 add get add }for end }bd /max { 2 copy lt {exch pop}{pop}ifelse }bd /sh2 { /Coords load aload pop 3 index 3 index translate 3 -1 roll sub 3 1 roll exch sub 2 copy dup mul exch dup mul add sqrt dup scale atan rotate /Function load setupFunEval clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs currentdict/Extend known { /Extend load 0 get { 0/Function load FunEval sc xmin ymin xmin abs ymax ymin sub rectfill }if }if /Nsteps/Function load/Size get 0 get 1 sub store /dx 1 Nsteps div store gsave /di ymax ymin sub store /Function load 0 1 Nsteps { 1 index FunEval sc 0 ymin dx di rectfill dx 0 translate }for pop grestore currentdict/Extend known { /Extend load 1 get { Nsteps/Function load FunEval sc 1 ymin xmax 1 sub abs ymax ymin sub rectfill }if }if }bd /shp { 4 copy dup 0 gt{ 0 exch a1 a0 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a0 a1 arcn }{ pop 0 lineto }ifelse fill dup 0 gt{ 0 exch a0 a1 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a1 a0 arcn }{ pop 0 lineto }ifelse fill }bd /calcmaxs { xmin dup mul ymin dup mul add sqrt xmax dup mul ymin dup mul add sqrt xmin dup mul ymax dup mul add sqrt xmax dup mul ymax dup mul add sqrt max max max }bd /sh3 { /Coords load aload pop 5 index 5 index translate 3 -1 roll 6 -1 roll sub 3 -1 roll 5 -1 roll sub 2 copy dup mul exch dup mul add sqrt /dx xs 2 copy 0 ne exch 0 ne or { exch atan rotate }{ pop pop }ifelse /r2 xs /r1 xs /Function load dup/Size get 0 get 1 sub /Nsteps xs setupFunEval dx r2 add r1 lt{ 0 }{ dx r1 add r2 le { 1 }{ r1 r2 eq { 2 }{ 3 }ifelse }ifelse }ifelse /sh3tp xs clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs dx dup mul r2 r1 sub dup mul sub dup 0 gt { sqrt r2 r1 sub atan /a0 exch 180 exch sub store /a1 a0 neg store }{ pop /a0 0 store /a1 360 store }ifelse currentdict/Extend known { /Extend load 0 get r1 0 gt and { 0/Function load FunEval sc { { dx 0 r1 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { r1 0 gt{0 0 r1 0 360 arc fill}if } { 0 r1 xmin abs r1 add neg r1 shp } { r2 r1 gt{ 0 r1 r1 neg r2 r1 sub div dx mul 0 shp }{ 0 r1 calcmaxs dup r2 add dx mul dx r1 r2 sub sub div neg exch 1 index abs exch sub shp }ifelse } }sh3tp get exec }if }if /d0 0 store /r0 r1 store /di dx Nsteps div store /ri r2 r1 sub Nsteps div store /Function load 0 1 Nsteps { 1 index FunEval sc d0 di add r0 ri add d0 r0 shp { d0 0 r0 a1 a0 arc d0 di add 0 r0 ri add a0 a1 arcn fill d0 0 r0 a0 a1 arc d0 di add 0 r0 ri add a1 a0 arcn fill }pop /d0 d0 di add store /r0 r0 ri add store }for pop currentdict/Extend known { /Extend load 1 get r2 0 gt and { Nsteps/Function load FunEval sc { { dx 0 r2 0 360 arc fill } { dx 0 r2 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { xmax abs r1 add r1 dx r1 shp } { r2 r1 gt{ calcmaxs dup r1 add dx mul dx r2 r1 sub sub div exch 1 index exch sub dx r2 shp }{ r1 neg r2 r1 sub div dx mul 0 dx r2 shp }ifelse } } sh3tp get exec }if }if }bd /sh { begin /ShadingType load dup dup 2 eq exch 3 eq or { gsave newpath /ColorSpace load scs currentdict/BBox known { /BBox load aload pop 2 index sub 3 index 3 -1 roll exch sub exch rectclip }if 2 eq {sh2}{sh3}ifelse grestore }{ pop (DEBUG: shading type unimplemented\n)print flush }ifelse end }bd {restore}if not dup{save exch}if L3?{ /sh/shfill ld /csq/clipsave ld /csQ/cliprestore ld }if {restore}if end setpacking %%EndFile %%EndProlog %%BeginSetup %%EndSetup %%Page: 1 1 %%PageBoundingBox: 0 0 608 160 %%BeginPageSetup cg_md begin bp sdmtx [ /CIEBasedABC 4 dict dup begin /WhitePoint [ 0.9505 1.0000 1.0891 ] def /DecodeABC [ { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind ] def /MatrixABC [ 0.4124 0.2126 0.0193 0.3576 0.7151 0.1192 0.1805 0.0722 0.9508 ] def /RangeLMN [ 0.0 0.9505 0.0 1.0000 0.0 1.0891 ] def end ] /Cs1 exch/ColorSpace dr pop %%EndPageSetup 0.60000002 i /Cs1 SC 0.23529412 0.23529412 0.23529412 sc q 2 15.003872 m 2 7.8149743 7.8157911 2 14.998466 2 c 145.00154 2 l 152.17584 2 158 7.8244047 158 15.003872 c 160 15.003872 m 160 6.7174625 153.27803 0 145.00154 0 c 14.998466 0 l 6.7150416 0 0 6.7065849 0 15.003872 c 2 14 m 0 16 l 160 16 l 158 14 l 160 14 m 0 14 l W 0 0 608 160 rc -5 21 m 165 21 l 165 -5 l -5 -5 l h f Q q 83 90 m 83 160 l 77 160 l 77 14 l 83 14 l 83 84 l 160 84 l 160 90 l 83 90 l h 0 144.99352 m 0 153.28137 6.7219648 160 14.998466 160 c 145.00154 160 l 153.28496 160 160 153.27509 160 144.99352 c 160 14 l 0 14 l 0 144.99352 l h 0 144.99352 m W* 0 0 608 160 rc -5 165 m 165 165 l 165 9 l -5 9 l h f Q q 242 12 m 230.85426 12.165432 222.63789 15.032893 217.12346 20.7679 c 211.60902 26.502909 208.85185 34.995007 209 46.244446 c 209 98 l 189 98 l 189 113 l 209 113 l 209 146 l 225 146 l 225 113 l 262 112.91358 l 262 98.024689 l 225 98 l 225 46.079014 l 225.39507 39.571983 226.99422 34.802074 230.1926 31.769136 c 233.39096 28.736198 237.58186 27.219753 242.76543 27.219753 c 245.19179 27.219753 247.89381 27.49547 250.87161 28.046913 c 253.8494 28.598356 256.88229 29.425508 259.97037 30.528395 c 263.27902 15.97037 l 259.52921 14.646907 255.86215 13.681896 252.27777 13.075309 c 248.69341 12.468721 245.19179 12.165432 241.77284 12.165432 c 242 12 l h 295 14 m 278.15918 14 l 278 112.91358 l 295 112.91358 l 295 96.370369 l 295 96.370369 304.76617 106.29628 309.67401 109.60493 c 314.58185 112.9136 320.34436 114.5679 326.96167 114.5679 c 332.80695 114.5679 337.79745 112.94117 341.93326 109.68765 c 346.06909 106.43414 349.01926 101.99509 350.78387 96.370369 c 350.78387 96.370369 361.92294 106.29628 366.99622 109.60493 c 372.06952 112.9136 378.08014 114.5679 385.02832 114.5679 c 392.74854 114.5679 398.92459 111.72801 403.55673 106.04815 c 408.18884 100.36829 410.50488 92.730911 410 83.139999 c 410 14 l 394 14 l 393.96167 80.488892 l 393.96167 87.106209 392.69336 91.820976 390.15674 94.633331 c 387.62009 97.445694 383.98062 98.851852 379.23822 98.851852 c 374.49579 98.851852 369.78104 97.418121 365.09375 94.550621 c 360.40649 91.683113 356.24316 87.878212 353 83.135803 c 353 14 l 336.06042 14 l 336.06042 80.488892 l 336.06042 87.106209 334.79211 91.820976 332.25549 94.633331 c 329.71884 97.445694 326.07938 98.851852 321.33698 98.851852 c 316.59457 98.851852 311.87979 97.418121 307.19254 94.550621 c 302.50525 91.683113 298.34192 87.878212 295 83.135803 c 295 14 l h 438.9505 21.264198 m 433.10519 27.440361 430.18259 35.656738 430 46 c 430 113 l 447 113 l 447 49.220001 l 446.7258 42.16375 448.3801 36.814835 451.68875 33.175308 c 454.99741 29.535784 459.85004 27.716049 466.2468 27.716049 c 471.32007 27.716049 476.36569 29.177351 481.38382 32.099998 c 486.40195 35.022648 491.39243 39.35141 496 45.086418 c 496 112.91358 l 513 113 l 512.89862 14 l 496 14 l 496 30.197531 l 496 30.197531 485.13364 20.271622 479.89493 16.962963 c 474.65622 13.654305 468.78345 12 462.2764 12 c 452.57101 12 444.79578 15.088035 438.9505 21.264198 c h 588.65784 14 m 564.50476 51.041977 l 541.84052 14 l 523.4776 14 l 556.56403 63.449383 l 524.63562 113 l 543.164 113 l 566.15906 77.180244 l 589.48499 113 l 607.84796 113 l 574.43066 64.441978 l 607.18622 14 l 588.65784 14 l h 588.65784 14 m W* 0 0 608 160 rc 184 151 m 612.84796 151 l 612.84796 7 l 184 7 l h f ep end %%Trailer %%EOF tmux-tmux-f222026/logo/tmux-logo-1-color.svg000066400000000000000000000101611511153563100206350ustar00rootroot00000000000000 logomark + wordmark copy 3 Created with Sketch. tmux-tmux-f222026/logo/tmux-logo-huge.png000066400000000000000000001340211511153563100203000ustar00rootroot00000000000000‰PNG  IHDR àíÞzwsRGB®Îé@IDATxìÝ €]ey0þ÷Ü;“…%$$s'3Q[÷ZµTÁº}þ3 JùhÝPq×B-¶¸µnÕRpG•Zmc‹¥@2qû\P¨KU¬u«Ú™ÉÜIšdæžÿ{C‚ d™åÜ{Ï9÷wÛ˜;çžó¾Ïó;‡œ;ç9ç}“£×‘GÙ»`Á‚§iúÐ$ICkþy`|¿8.;8¾ßþ'þ ùÞ[o½õí×]wÝí]-!y @€ @€ÌP 2Ãõ§µz|êñ™±øøãX|<7n ø8-5+ @€@ŽjžÃ>øàÿ>öØcŸ•£¸„B€ @€ @€Ü ôda¼H;ŸùP¼h»:ËvµE€:!Ïgƒ±ßËâ<Ækãû—Å'“G;‡>  @€ @€ P$Ìž€ŒgŸÿ¾âc‘v¿X  @`:;Îmßßq®›Î&Ö!@€ @€ @€@× Ì¹çzìdÏg/Їv­¤Ä  @ ì‡6ÏuÍs^óÜWödåG€ @€ @€Ù Ì©ù¸Ç=î~q®Ç¯Æ ²gÍ6Û @€" 4ÏyÍs_óX¤¸ÅJ€ @€ @€v ̺ٜﱷ·÷êx!öèv« 湯ylž ó @€ @€ 'Y ãðsˆI|#^€}hž’ h—@ó˜¦é7wœÛÕ­~ @€ @€ { w\hýZÌlyî³ h¡@,BöÇæ¿¦ÙBdM @€ @€ P8 ›CÍÅ‹­ÃñÏa…ËTÀ @ ÍsbóÜh8Öàj’ @€ @€B L»ù¸Ç=î~q¨¹õ1KO>rW šZ(°¼yŽlž+[؇¦  @€ @€ Pi <òÈÞyóæý{|ÂÜ…Ø­‚$@€v 4Ï‘ÍseóœÙî¾õG€ @€ @€< L«¹páÂwÆ Ÿ§ÀÅB€r(ð„ç̆&$ @€ @€´G`¿ÈcŽ9æ1”³ÛŽ^ @€@áÎÞqî,|" @€ @€ @€ÀlöY€<öØcãrŸ˜Mö!@€],ðñæ9´‹ó—: @€ @€],°Ïdtù`üshûHÌX Þ¼sXܨyõ"@€ @€ @€@× ìµyôÑG?3j×u"&@€Ù·ã\šMkZ!@€ @€ @€@AöX€<òÈ#¨T*$a @€\ 4Ï¥Ísj.ƒ @€ @€Z$°ÇäÂ… ÏÃÇ™»ªEèš%@€îhžK›çÔîÈV– @€ @€¸[à>Èc=öÐ4M_ ˆ˜»@óœÚ<·Î½%- @€ @€ @ ÷)@6WÇ'6.Fø¢$@€ùhžS›çÖ|G): @€ @€d'°[ò˜cŽ98ÎWõšìš× Ð<·6ϱ$ @€ @€ Ð » ãSgƤ × {^Ž ÐNæðæ/ng‡ú"@€ @€ @€@§v+@Æ‹£§u*ý @€2 Ä›|^TæüäF€ @€ @€÷ >úèÇÄ‹£Øù¿  @€ìšçØæ¹6»µD€ @€ @€| ÜS€ŒóSšÏEE€Ê!à\[Žý(  @€ @€ö-°³Ùüû9û^Õ§ @€Àž·ßyîcS6'@€ @€ @€@>¶_=ꨣ‡†;"Ÿ!ŠŠ”C žk—6ϹåÈF @€ @€سÀödµZ}êž?¶”ÈRÀ97KMm @€ @€ GÃÀ)@æqÊ(àœ[ƽ*' @€ @€îh {âpO¼g‰7 @€@Ëvœs{ZÖ†  @€ @€ ÐaÊ1ÇóÃAŽC÷ @ [Úqîí–|åI€ @€ @€@— 4Ÿ€l ½ @€ö 8÷¶ÏZO @€ @€´Y@²Íàº#@€Q@Òa@€ @€ @€@i K»k%F€9P€ÌñÎ @€ @€sh gnMØš˜¡€sï Á¬N€ @€ @€@q*I’,.N¸"%@€Åpî-þ>” @€ @€{èIÓôàx!tïkø„ÈT yîÍ´Au­@ߺ³*•ÊùEØ–N>úú¡ñ=ñ @€ @€Ü-ЂÕEPGh¯€so{½õF€ @€ @€@ Ûˆ­+ °C@Ò¡@€ @€ @€@išs@Î+mv#@€9pîÍáN @€ @€™ 4Ÿ€ô"@€ @€ @€ @€@& ™0j„ @€ @€ @€¦€¤ã€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€€¤c€ @€ @€ @€Ì 3£Ô @€ @€ @€ Ž @€ @€ @€2P€ÌŒRC @€ @€ @€(@: @€ @€ @€ÈL@23J  @€ @€ @€  é @€ @€ @€ @ 3ÈÌ(5D€ @€ @€ @€@ @€b Ä;É’bF.jäW`ÑðòÕ¾øol_ÿ$i²,„ôÀø/î‚øî‚æßqù‚Æ?ÛNñý!iÜy÷ßÉö¿“nNÓ0ÿ©ÞÐ[7lÝ8^¶å7s‘Và¼ÐsøcŽ˜7¿²¬‘†e•J#³Éñx\sš¿Ûq’ù;ŽÝx(§·¦!¹-$émqÙmIšÞÖ¨$ñïÆmP½%™Üúˉ;&~N [ k#p @€Ž (@vŒ^Ç @€¹ 4âqÃYÌÍÐÖt§À’«–,ë­.ü½XH|D,Îü^¬Ä<,¤I|ßßÏß©Þq«Çöw;ÇUv.ß±hû‚]n Ù±B²c³jl²vÿÁ4 '›bÑg4ö{]HÓï%¡ñ½É&¯Ûtê¦ÛïiÜ{X²fÉA=.xhZM´‡ÇU¯¤Íy’ž„»¶êÎc±ÙÆŽãogs÷ü¸Ë›»ßÆÿm¾‰Mlÿ^‘TCµ¹Qï¼P[48•¬#±ŸŸÅc÷§išþ,®÷³»S?¹iõø†mû› @€À½ ï-âg @€(ÀÒË–‘,˜ÿG•$üQ¬®<*I›Çä°f‚÷ÔaîóCöéo/%¡ùdÚ²Øïãî®UBõˆj£6¼âg± ùݸü [’äó7Ld‹"°øÊÁCê ÇÄ'h1?:+‹÷ ì,2îšÇÝeÇÝŽä]?žóû$‰ÕÈûy`ޏ£Ö·#¥œ¸Û£Jv&_\©ÎKãq{]áKÍ¢derÛWê'Ôï(âþ3 @€@6 Ù8j… @€:$ÐUÿƒÕÊÉqÉ“cÑæ1!ÖôòX®™6OŸÔ É£ª¡çœþEƒ7¥ÃégÒ©ð‰‰ãƾ3í6¬˜gdÙðòÇVÒʳbñYñX}Èoظçóù^bÛñ„ï£b5ýQ1³ÓÞÞÍñ ÉÏ4&Ó‹·{A³˜ PräØc7×y 7«¯¾:“ß;ý7ž·=+w dõß8Ïû Ä‹]_ºïÒ’.IÃ`¼pù»…Ï.M¿/VÞVø<º(t*}W}õØç‹œrmÝŠÓ“Júœ"çÐŒ=Ý:ù'ñ)£‹žÇlâﻲïa•žy'ÇmOŽÿ†üþlÚ(Ú6qþ½Å÷?±mòŽOÝpÜ ‹WÇ{^è©5øä» Žé‰ñ Çþ®ñHÃui’^|û¶ð©[ŽýM×ä]ÐDû× þß8\õK þ=aOnIÎØôŒ‘_ݳÀs¨ ^or:hŽÍttóFH¿9±rì â·'qúÅ'ÿWüvQ™Þ%?_9rv™2’KkâŒÿ<¤5­w¸ÕÉ©?÷d‡÷î  @€¬’§fÝbnÛËäV•d—$ËAB˜@£>9ƒÕs¹j¼€ÔœÏ¬ðÿ^l •æˆ]óZzéÒ+Kç?·’&/EÇGuMâ;crÆ¡9û{{¾£xðÓi2ùŽúÊúOºÍ¡Hù.[;øðJ5¼$Î=ú¼X|Ü>÷hü·§H)Ì=Öxƒ@|Bò½q^Ë¿=pxÅe1ýÔWŽ|sî k¡%I:B¥ðçÇP*t¡¨%ûV£sˆçà?Š 2§F:¼q<ûÜÞáví>>•~:ž¿°ë½jmxà«õ¡±ËK”“T2è_7ðÂø½èeeünoœüçúqã߯dl¦9 @€ ©@sˆÕXp» gé‚ñ8¸êEÝX|Ü4szb1ëÔöü(>ùÿÙÚÚÚcvýÜû |<,è[7ø¼ø´Ì׫ÕäGq½ú·ÅÇÇÖÙîÄ›?ž/€#Ú\±ôªåìl8z'@€N Ô‡F¿ç ¾¤“1´¶ïä}‡_~øÁ­íCëEXzÙÒ#ÒJòž¢Æ¿¯¸ãݛ¶ÉW6×Q€Ü—”Ï @€ @ cˆW ŸO{ª?‰œ×Ä@w,˜vŸÆˆ¿Ó'''ÕÞÿì_¿bm,Ô>:‡avMHK¯|`Üç÷÷ ŽW*É?Äýsl×$?ÃD£ÍñÕjåûñÆ‚O.[·ìþ3ÜÜê PäŽÉsb²”ÃÊÇsÝÀü”²ÀT’ï£iô°àCñ&µûu4ˆuÞ˜š:mç4! -BÖ, @€ÌN ¶vàÿ‹…‰ÿ¨&áÊxñæéñ—ó.³rVn«Òjõ»µõ+>°hxùŽ¡>gÕŽf(pÄÚ߉ó÷\Rí ?›žåiÇéÆÿ¶ãÍÉ©ÕdþO›O8/[»lÉô¶´”E`ü¤ñ_§éÔ+ʒϽóˆO‚½¸¶vð˜{/÷sw lŸo9 '•Q¡‘¦Þ¸zú¹)@î”ð7 @€tT`ÙÚåOj[™T+ëcaÂü°3ÜÍ‚N¬Ô¾ü ¤ò³¾õƒg†óŒz4C­¾äŠåª­üDO%ùi,‘Ÿ¶}hܵ`åíI˜ÿ{M¥2ÿÑóUq™è"‰U.‹OAþ[SÞ~]%ùXXºjÞö2îˬrZ|åà¡qèÕ÷eÕ^žÚICú«Æõ[^»kL »jxO€ @€mè»jàcáñ‹Õjõ+±ˆfØÊ9îæpN͹2kG~ëˆuËŽÍÙü^Í'ãÜ›ÿÐÛ[ùq´~AOáq® {Þ>º>}þüä‡Û‡'Ûó*– @€@Én™HáÏK–Ö=éÄ1*þ|éUËyÏoºR oÝÀÊæwÈ2&ïxçøªñkî›ä½EüL€ @€-¨­øÓ…‹ý¤9dh,8øÝ´UâI˜_I’ úׯ¸jéeKhU7eo·¶~à™µEÕÇCõ±”kµVïð$9,T’5Í'M›7*´º;í @€@çê«F.‰C8~­ó‘´$‚ÞžjåbÃã·Ä¶.Y³$Î’PùH!‚qéÇ7¼yO›ù%oO*– @€ @€@K– /ÿÝ8Äâçã/àŸ‰CR-kI'Ý“Àªê ®ë^>´§-Û³Àöy‡W¬KBåsñx]±çµ,m@ò¼…‹]W[7hhæÖ!k™yH“É©‡Þ™—€2#Îo^{üŠÒ>噩U ›·xá{â—ƒ¥K- ['·¥Ï §„8’ò}_ ÷5±„ @€²Hâ°CgÍKª?l±˜}óZÜŸ@¼è±4>Á·6€_·¿u»þó5¡Ú¿~àõóz«ÿ ж< šëâ¿_ŒO¡þIÃÐ5´A`ü¸ñŸ…4}cºêLIzÞÒ_ñ€Ît®×N ,[·üÉigvªÿVö›&áõ›Žû¯½õ¡¹7Ë  @€ @ ÇWôÅ¢×úJ¥r~lpA&jdVq~–æë±˜sq¸(ôΪ‘’oT[W{Hÿ¢Áo†Pù›8Üê¼’§[Œô¶{›|¦ÝÀÙÅX” 0[ñkÇ.ŒÅšïÏvûäs‡’qÓ…n.ÎMúò¤Òó½xèq…N¤„Áo¿pW©ü]ÿðàæÐ*á–v œ&“ÉÉÓbqcjç¢2ý‹O¯­[qz™r’ËÞúúV¼-„äA{_£˜ŸÄùZoŸš ÏßÉûÊ@r_:>#@€ @€Ù œ*}ëÞZ©V®Š…ƒûÍ®[µV yê _c(° /?,>¥ûoqnÒÄ‹D [ë®õ9 $Ékúüç°6ÌŸS;6&@€Ü Ä¡X¿Ÿ†ä¹ pŽ%Iú·G\~ÄÒ96cóœ ,[7ðØ$¤¯Éy˜³ ¯‘¼fÓñ£¿Ü߯ ûò9 @€ÌH`ÙÚeKjG ~¾*o(ãpC3ÂÈùÊq0¨‡Æ¡À¾ºôÊÁæ<Ô–…ŸÒ}ÒÁIåºøD‰-ëDà $'×*ƒ_X|åà¡7¬9ȉÀÄÄÈߤiøYNÂÉ6Œ$9¬wþ‚xÓ“WiÖ„yÕJò±øý²ZÂ×ÖW|l:y)@NGÉ: @€ 0-ÚÚÚc*Õù߉¿l?mZX©ãq_ T{ÂWº°™ô œç&ýR|걿ã;B3ˆÇíìI®ª]Q;`FZ™Š!pZ¸«Ñ˜:3õ§„,á+Iþ¸¶vð„f&¥(P[4ð†øýò‘¥ÃHÓ›¶¤áŒéæ¥9])ë @€ @€À>âüyÏNª=ßHBÜçŠ>Ì@·!û/ë¿_ÿúWU’ÊÛbîe¼3=wÇX+ŠOðôö¬‰óõ´¢}m @€@g6®ÞðÕXü`g£h]ïñȇ[{Ø¢Öõ åN,½rà÷’¤ò—è»å}¦á¥7LL·ÈéJY @€ö,ç{Œóç½+Ο÷ióç홨K·!{“ÿWö9!—^µüñéÕïÅ}²ªûEŒûH’ãjG^¼Ÿµ|L€¸sóm¯EÈ  ?a'ý *½g?+ù¸HkBµÚ›|"†Ü[¤°§kš^:¾jô³ÓZwÇJ 3Ѳ. @€ì&°ôÒ¥Æù/‹Å«svûÀ…h>½Ú3?]¿hxùa…L`?A÷¼´§ZýZ³ØºŸU}\ 8×ì š7A(d¡ @€À4~sÊonIÓôÓ\½x«%áŒ8$ü‹¸ˆ÷$P[<øñ{É‘{ú¬ØËÒñÛïÜü𙿠9S1ë @€ @€ÀvÚúÚ@Ï ¾‹9'")“@ò ƒ“êeaM˜Wš¬>ÄÕÇCRùPHJ”WivÐÜiÞÑ·nହ·¤È›@}ÕØ¿Ç™ ãHå{ÅbUQ¼òѰ6Ì/_vݕђ+–?(ÞÌ÷¦²eÝœ‡5NÄzÚÍϺùæ™æ¦9S1ë @€ @€@X¶nà±!íýV,æ< G)žïàþH2[¶nÙýk}+¾/ï½° ùÈaïI%ù»Úðòçì} Ÿ @€@Q¦î¸ë¬X¹±¨ñï+îX|H­2ø–}­ã³Ü $½½•KÊ8E’†÷ÖWŽ~a6{@r6j¶!@€ @€@ ÔÖžPI’¯Ä‹%˺˜¡ô©7‡µŒC‚[äD—­[þäJ2ïÛñX}L‘óûô¶?E’T?çù|äô¶°E`ÓI›®¡1ã! ‹’_¼©ïì#†ûÝØW˜¶{ µõƒ¯Œ7»»ûÒâÿ}üYºmrÖ¿(@ÿ @€Ú&P[7ðòP Ÿ‹¿`жNuÔ1¸Ÿÿ¦6  ÐBúІOÇ‚Èp »èXÓñ&šžžP½$_íX:ž•@s´&o›ÕÆ9Þ(>q<ÕHÏ«ŸP¿c¶a*@ÎVÎv @€è.$Ρ÷®¤Rù@,è¸0Ò%ûþîy‰’K[Û¿¼0)ǹ+kë.ŽO=¾¯y1¯0q 43øoÔÃCoÏ…™5¨!È@#Ýò²8'Ýí¹ (Ã@âùëj‹_›a“šjƒ@mãÃñ{çÁmèªÝ]¼e㪱oÏ¥SȹèÙ– @€Ý °6̯­_ñéxQäœnHWŽ÷H’ÃV{>΋Ͼæüµäª%Ëj‹V|% •ÓsªðZ,ÿ½:#¢ÿ¤ÅÝhžÚ,°qÕÆÿO›½®Íݶ³»7-½rðíìP_³ˆß5^¿s¬œ} ùÜ2>ýøŸõ‘ÑwÌ5ºÜÿò0×mO€ @€³X|åࡵêàç“þtö­Ø²Oê?zà¯òœÇ²u׳ð;ñô£ó§ØÚ(V>²ôßW< =êŠÚ P¿väC±›kÛÐUÛ»ˆÅ¬ª=á#mïX‡3hÞø–¤É{f¼aþ7¸«ÑÏ/ Ûæªä\mO€ @€’ 4ç39°7ùFÆòJš¢´f †ä¼¥kûšÁ&m[uÙðà©ÕJåk!$ýmëTG¹ˆÅèE=óÒ …ÞÜ+@˜¾Ày¡1•¦g„4lþFÅY3!ŸÖ7<øââDܑΫð$‡–-û8Äñ9Wþwy)@f¡¨  @€”LàˆáþGU’ù×Ä'V²Ô¤3Kæ|Š=•ê§ÂÇÂY6‘ýfqXØÚðŠ÷T“䓱ñüÄ•}¦Zœ­@’<®ÿþƒožíæ¶#@€| lýQHoÉgtsª’${øðо¹·¤…Vlæ= 'µ¢íN¶‡^ýR}åèû³ŠA2+Ií @€ @ $}W üQoÒóÕøôв’¤$¬’äwj}çfÕÜ\ÚY²fÉAµ£/ÇéŸÏ¥Ûvƒ@òÚÚºÚCº!S9 @ ›Æo{w,˜ü¨¤92?Iß[ÒÜ Ö¢á凅P)ݾIÓ°9l›|QÜ9iV;H2+Ií @€ @ ñnÞgVz*ëc*‹KŽZ „Ê9ýWõ?¸MO»É¾«úVÌ[¼ð›qˆ²ã§½‘»Y 7$½t3€Ü  PJSÂÖ©ÆÔ±Ù(e~!9¹oxÅ—3·âfuP¨¾/ŽsDq3Øsäñ¿£WÔO¨îùÓÙ-U€œ›­ @€ P:8”eœK'ù—˜˜¡,K·w3L( óÓj5³¡™fYmýŠ'$=ó¾ç{|äL·µ~÷ Ä'e‡jkOè^™ @ œ›V_¿ü}9³‹ÏÙŧ ]s¨s²ƒ—­[±:~§xNNÂÉ,Œøôã¿M¬ýTf îhH2kQí @€ @ €}ÃçÆ_¦?Ÿ(«0|!·Y 'Oß>÷M›ûí|~’†/—ñ®ó6SvewI%ü}Xæweò’&@€@‰¦®¿ë¯cåË™bR[¸ø ¿+gnÅÊê°µ‡-ª&éEÅŠzÿÑÆñV¯o4¶œ¹ÿ5g¾†äÌÍlA€ @€2 $µõƒV’ÊÛÊ””\Z/„äüÚµZßÓö’þõƒo¯$É¥!>Ù¦>uS68‡i_eÀœ¡eÛ¯ò!@ ë6ºéö8mÝ‹Ë ‘¼hÙpÿSÊ›_12[P‰…à$Y^ŒhgåTzÆÆÕo˜ÁÓ^UrÚTV$@€ @€@É. ½qØÕŒ…¤W—,3é´E ©…žÞW´º«¥—.=0»ú¯qxµ¿ju_Ú/¿@|z÷Üû]±¼¿ü™ÊÝ%Pýb+cÖñ»zRMª…5Ë–1¿"ä´½œ„Ó‹ëLbŒó>~´¾zôŠ™l3“u g¢e] @€”D ùäZÿýW\‡]}vIR’F'’ôœ%k–Ôª®¿| V=bþ×â«ÏjUÚí.x÷Àù½Õ¿í®¬eK€î¸-MωC±n,g¶Éƒj‹+o.gnùΪù{S5T?Ú,ç;Ò™E—†ôW[·ÜÑÒ‘! g¶O¬M€ @€Â ºæÐÅ¡·g}LdUá“‘@GâÓd‡Ï[¼à5­¢ÿªþGÏ_|+öñ­h_›Ý,þi<¾ÜÍr'@€@6m¸) iK¾—äÄë¬æ÷£œÄÒ5a$½=oµÇß)SÂñÉÇF:™¾ðÆo¼µ•y)@¶RWÛ @€È™ÀÒË–±pñ¢¯Ä¢Î±9 M8…¨¼v{Q;ÃøkkOH{ªWÇaW •™¡«¦îˆÿþUÒjõµ< @ |ãC£kBšÆ¡ÛË÷ŠOàõÄó×ÇšP-_vùÌhéUËŸ†ðª|F7§¨Þ3qܨ׿ÔÂ46V€œ’U @€ P8|Ð`õ€_c¹sº ;4?9²pÑA™ ßÔ7<øšP ÿÖ*3?)Фlñø:uÉUK–•-/ù @€@[¶¤ÍùÍo)£EsdˆÚâÁ¿(cn¹ËiM˜W­V.iÞ¸”»ØæPú_õÆè›æÔÄ47.Ü4³¶ @€ºL`ÉËæõ~=Î\bØÁ.Û÷íH7s^ÙœgN}Å»ùkÃï¯$Éå»Ð3'·B óçõ,l^ ö"@€’ ÜxâX= ³K–Ö=éÄ› ß´dxùïÞ³À›–ô/Zñ×ñ;éÃ[ÒxçÝ–NMžV‡-íA²Êú @€ @€@ŽX·ü÷{çU›O>v0 ]—Y IM{{ž;Û—¬YrPÿ¢ÁËã æ¯˜m¶#0säe‡_~øÁ3ßÎ wúʱ‡4|%ïqÎ.¾dá¼PýHÜ6~½÷j…ÀÃýŠº¥{Ò4ÎýøÆúêú÷Za¶§6 ÷¤b @€J"М·¤7©|%^XZ’”¤‘SJ2»ùq¿| Ö»ø€¯‡$9.§© «¼‡ÌŸ·ðÅåMOfèjtë¶©3£À]¥THÂSúÖ:‡µbçžzzBõ’Øto+šï`›×Ö7¾§ý+@¶S[_ @€h£À²uËŸ\í©|1vmc·ºêZä‘Ícn&é7ŸÎ??ùX 7/éLଛ@’œ.*ÝÆì|´D€ Üp†Ÿ‡FãõNaŸ¡WBò®Ã‡Wôís%ÎX ï¨sšsmÎxÃoŸ|¼cëÖ©SÃ)aªaö´³3} @€ @€@{–­]¾ªZ©ük™ia{zÌW/ñ—ìFœïr"æ?Bº!M“Í!4îˆÅØ;C#Ä¿Ã1â4þß¡ÿ¤itª—߯ˆ¯ˆóÞ/_Yå?šjR}UŒò+Ó‰´oÝÀÊ8äêg£µ!0w‚¥ÍùxÒ Íã6 ¡ßß¾ýXÇl¼vGÛ´‘ö†JÙʱp»0$Í÷ÉÒæ1Û-Ùõ¯$Mžî[€\æÕlÞUÞ,P–úŸp¼->Ǹ6>}øù´±õ‹ÇMŒt2á›V߇ ¾i]Œa]X^ºô Oꩦ§ÄãöyñbìŒ-}7 ç= œcy_âd/0Q{[­oÅÿß×’}ëm1æ´ì€…‹Ï¿9Üü¢ÎFRÜÞ—þûŠÄ‘#ÞVÜ öùËn‰ÓRtæ¥Ùw½ @€ @ S8¤å+â–ï‹Å„Â?V‹7ñÿ“kãÿü󶬹qeç~iÞßNš8~âÇq·Æ¢è;j89T’?û Œs íâ·Ÿ'á÷— >bãÐèv.ŒwåràÂÅ—ÅâãSv.+áßq8àfÑ1ý§ú­+Ã)šóŒæï‡¢ÛF¾ûò¢áåç’—Æ¹Žšwü/Ë_°í‹(þwÛ,Œ+@¶\Oh¯Àiá®ôªÆ™¡'ùJ¾/ß/žÇO[6Üÿ‡Æÿß½?óóþªóâMœ%»)+ÎþõU£köŸ}ëÖ(Í]±­#Ò2 @€ò-пnàìxÑáýÅ¿˜’þ2ÿ*Üö€úÐÈ&†F/ìä»3ÚëñÉÈúª±ª¯}ìTcê)1æ’]ûŠÇbó)Èí¯æœ,\tu‰‹_m4Òço¹ëö#ƇFþx|Õègs[|ܹSvü½yhÃMCco¯oYÑéKbé¿9e·¾¿ôÊÁvkòò&@€@7L7öµx£ÐËšk5©^Ö,_XÖüZ•Wsnò8ÂÿiUûi7M7Üq׿Wv¤ï]:U€ÜÃ[ @€M oÝà_ʼn_þ®hqïŒ7êâ(aÝTŽ¿fôA±€÷ÎNW¹3¶Ùþ½qÕ†¯Ô¯}üö‚NH=Ûvм]œ+v{òˆáþG%¡çÚxQçEÎçÞ±Çãö¶xä~hr[ã‘ã+Gž<±jôS7žxã­÷^¯0?Ÿ¶N¬ýÈwÞòà4m| yÜ•s3÷T“çfŸ ”f%pWãösãynlVç~£äµEÕ·æ>Ìx¿+–÷Ç9ߣ2 %N]ñ¢<Ì ª™ÉîÔ @€Ú/°`^åM•Jòöö÷<÷cñæÖFΟœJ 8«7\ÕѹçžÒî-Äy*›äö©‡ÄbÕ'wÿ°~JØ,Ž÷„ž¯…ÔJ“qšþOÜŸ¯¾kê¶þø”îË7?ö_¥É-&Ò¼PU{e25õ‡ñ΀ï—)·éä’V‚ät ¬C€ 4çEn¤ÉK œÂ~BOÿ¬oýà‘ûYÉÇ;æÏ«|(¾=¤L ñf²÷Ç›:¿‡œ ó°Ä@€ @€Y $gÎj³n ñ¢OxÛmaêþC#~ýê±_t0œ–w=~Òø¯ã€¦¡qzìì®–w˜£šÅñxGù¢…4‡PÒŸO¥é Æ7>4îÏ÷5/^Ρ±Üo:~Üø÷ëõ‘£ã¯—ä>Ø Œè>¬ÿªþGgؤ¦ @ ‡W¬mΗÃÐæRu¢‡Â¿8ÞØ×3çÆJÞ@mxùs¢Õ åJ3ýyØ6õº¼ä¤™—=! @€”[à–x7î[oŸL›…Ç74çž+wº»gW_9vI:µí ñé¹_íþ‰Ÿò, p?mÎï8~ËèÃ6^N Ý34éiá®ø”çéi#œ÷Q×ÏÕÊê<“b#@€léÖ³â÷²R•o¨ytßQçd#UÎVjWÔ¡zA™²k¡?95ujý„úyÉK2/{B @€(£@¶Ä;Ìßyû·Ü?íø¦[ŽýMÓœNNõÕõïÝ–6âЖéw§³¾u:(¦izjýÚ‘‡7çwìªÂã½Øë«F>‡c}Zóéå{}TÊã“O-eb’"@€Ý6®ÞxCH¯Þma‰~¨$•7Ƨú\¢”²M¥·çýq¤Ž%Ù6ÚéÖÒ·oZ=~m§£ØµÈ]5¼'@€ @€ìÒô_'·†‡ÕWþUsn¹ì.nKÍ'?ï¸cóÿ iúíâfQæÈÓ;ãÝãoI·M>dbhôJ5/év[}åÈ7aê鱉[æÐL!6#ŸÖ†ù…V 0'úІOÇl†çÔH~7^zz>ËDzí*P[;xBªöOv]Vô÷ñ8þ^}dì­yËC2o{D< @€(¸@|ZêûSSSO=yÓ3F 9z¯ýÙ,ÆÞ±ùÖf1ç?îõ‘;(/Ü|& “©þuž†®ê Én]oÚð­Æd£Yñø£X4?&ÎwøœúÊúؾÖíöÏ&ŽûN¼Àub¼P;Yf‹J¥bÖ2ï`¹ @`‰ã&FB#}Ý.‹Jõ6 •w~ù@­TIÖ‘åì@IDATÍ!™…‹ íŸC¹Û4~7ûË8WùrX H2{EL @€(˜@,â\¾å®Fs¾¼‹ [9½×|r[Ú8)ÚÝ1½-¬•©@¶ÆBÚyõÍ£Ðb4Ó¶KÜØÄÐØ×ãÓ¢Q⛩)@–|K» ÔÿcìÃñÜvÍ®ËÊò>-¾hÞüÊÊ’Ï\òè_7ø´èñ¢¹´‘·mãwÙ¯M\;ú޼ŵ3Èþ&@€ @€ Ä'Ç6Å»ÆO‰ÃV>óÆÇê3n Ë7¸~Õ†ëâ/Ccµÿ8¸v*¤±ð8úæpJØÚþî‹Ýcœó‚X8ÿçbg±÷èã¼P_zéÒ÷¾†O @ Tç…FÅãŒoN*U^;’‰E·göžRÆÜ¦›Só¼žVÂG§»~Ö‹¿CÜÚhl}AžoþT€,‘$F @€äQ M/½-zøøªÑÏæ1¼¢ÄŸýT,æ”ê‚H~íÓ;iúgã׌“סªòk·{dS×o9=„ôç»/-ÍO½•%óŽ-M6!@€ý l\=úßÐxó~W,è iH.\4¼ü°‚†?ç°«Kæ¿# ÉæÜPŽHÒôUWmüß…tŸP ïCb @€ìGàææSãC£/Ø<´á¦ý¬ëãiÜ>^‡Púõ4VµÊ,âÓºßolÛvd|zïÂ<ß)>ËôÚ¾Ù¦S7Ý>5ÕxMÛ;nS‡ñ)ȧ´©+Ý @€@N&FÆÞo®ùaNÂÉ4Œø䲃Båï2m´ ÕÖ¯xBHÂ+ î´ÂlNoýä´VîàJ Ä×5 @€Š&Мg$ݺíQžzÌvÏÝrüèoâ]ÌoȶU­5â1káïëS#GM?ñc*Ù l\½a]ä½*»sÔR“£h„B€íxIØ6•6ΈÅF;ºkwñæš6çAlw¿íom˜¿ ~,æ^šZXüf{ÃÔ[Îì¨ë4;/ ú4óµ @€ÌB q&C#¼±~ÍèSê'ÔGgÑ„Mö#0~íØGšOéíg5Ï@ ^ Ù˜6ÒUã+GΫÖljÕi lÝÖ8«Œsf%!üÞ4 ¬F€%Ø8´á[ñæšóK”Òî©TÂGjWÔØ}ayª%ƒçŧ?Zª “Æ™›NÚt}rR€,Â^# @€:*Ž'©?_5ò7†®láŽ8/4éäÙ-ì¡«šŽO/|±ÑØòû«ÆÖwUâmNö†6ü<>&òþ6wÛ†î’Z7Ï•Õ`] @ ·õ[Ó7Å"ä/ràœK˜öö¾uNMdãÚðŠ?•ðÚ‚„;­0ãÍu—ÔWŽýÛ´VÎÁJ 9Ø B @€ @€@^šC®n»ë®#ÇW_“×Ëׯ¡ñÿ gÿY¦œ:‘K~, IÏô7Ê÷šñ˜½«qëYùŽr÷è w÷ð @€ìHÓ ã«O»þÄë7í\äïÖ Ä‹ ´¾—rö‹`·…FzJ}åè9á”0UÎ,ó—U}e},ÃzYþ"›[Dq¶(ȹÚš…¨~1ÞvqaØGàq>Äj¥Z¹8ŽlRšâܽÓíüŠ¿ŒÃ©?úÞË‹úsüŽg˜|áM«oÚ\¤ ‹´·ÄJ€ @€¶¤wÆ!Ÿ7>4úgñÂÄd[ºÔÉ=›Gÿ¹9wá= ¼™¦@úóFŽ_5úÙin`µ,’äÂ,›ËE[IE2;BèŒÀí“áœxcX9oÄK£úx]gd[Ûkß•}‹O?¾¡µ½´¹õ49¿9RJ›{sw s&Ô @€J$¦iš;±räK”U±R9%l !ýP±‚îl´ñ …/ÝqË­Ý84ú£ÎFÒ½½×WŽ|3Þœÿír ¤ åÚ¡²!@€ÀŒn9~ô7ñ¦°WÌh£"­œVÞX[W{H‘BÞo¬ç…J¥wÞ%±9¿ëd…ø=÷¿ë‘×$ÜÝÂT€Üà @€èfô¿Òdòñõ¡svú0Ø6ù‰N‡P”þãTŸ¬Œ®úÍ)¿¹¥(1—5ÎxìeÊ-ݦY¦*ÌB`bhä_ã 6ÿ:‹Mó¿I³H—ô|4Oyåxõ?~àÏb&G•#›íYl‹»çùauØRÄœ ‹¸×ÄL€ @€ŒvÈ'ÖÖ ¼l?«âã¥W>0M’·"ØiÙxs‘o-í$£ÓÞV$Pr«¯¾º4w°”|WI Ð94½4>Av†"NçvÁžzNBú¹xÇó‘{ú̲Xpl4ά¯û‹üÜxâX½6¼âÚøÅÑù‰jn‘ôö„ÅÆçÖŠ­  @ È7LÔ†ÎN’Ê%EÎc¯±'•wÄn.ÿõ Š|¾Kzz’‹ãwöšgÑ>HÓoo{gÑÂÞ5^O@îªá= @€ºM o}âcþv|cÛäeù‹ªóÅ'ìnŽW|ìüÎØSiˆ…óò¼*!é+O62!@€Ù Ô‡Æ>Þ1d¶Ûçy»$ ‹æÏ«zþñ¾uƒgÆâãSòì<“Øâ±vÇÖmç…SÂÔL¶ËÛº yÛ#â!@€ @€@›ôÜñ¡‘×·©;ÝÌP`âø‰ÇÁ,2ÃÍJ½zôØ/ìŒÃ®~¡Ô‰8¹É´QªÂyRH²ÀǣР ¥Àd#}IéY¶™—¶’œ‡býÓ¼Ä3“8šÃ¥Ç¡dÿv&Ûä}Ý$I_{à ~ž÷8÷Ÿäþ„|N€ @€’ 4çh‹sç½fbÕè;J–ZùÒIÒÏ—/©Yf”¦7¥S§ÕW~c–-ج ׯûE¼8ûË6tÕ–.â„– m‘Ö ò/°ý×HßÿHga¥rá¢áå‡ÍrëŽm¶ ·rQó)ÎŽqÇñ7µ/Œ¯ûpÆÍv¤9ÈŽ°ë” @€h–CšÄ¹óFßÛ™ô:3ô›3[¿œkÇ"ЦɩƓ'ŽûN93,]V%:n ÁZº£SB˜ƒÀø­cÆ›ù¾;‡&r»i|êÿˆƒCåïsà‹C¯>/$Éq{ø¨¨‹nÞ²­qZ >~ý-þK²øûP @€˜–@,>Æ9Dϯ\<­ ¬Ôy­S×t>ˆNGŽ‡Æ¶'m:nÃ;‰þ§'Ÿ°.Íq›T<9½½n-t‰@œ“o2:=f»­”'É©µõƒO/BnK/[zDÎH-+×o«_oz¸=ÁÌ1ˆ89 çhhs”Q Þ{[š†RÞ ‹zHz{Þ–çýV[?ðÌ’ÿ›çg[šN„Û§^6£m °²dv’  @€ 0'´ñÚñ¡±Ï© wL M’k;Öy‡:ŽônJÓ¡ëWm¸®C!èv.qxºxñ²,óuÖ,_8Û @€@ V‡-¡‘žÞe¤„Ù5' |õ²áåËcn‹¯<4I“æ1¶ÙÆÔŸ4þëÙnŸ×í óºgÄE€ @€ âE‘óbññï2hJh¤áÇêº#ÝÆ¹JïHC㸫ƾݑtš‰@#MKsÜ.]P]– ŠF @ Tõգ߈õÇ”*©É$IR©&•‹ÃE¡7oùØÎIy†H%ìM mΛsñ(@f¡¨  @€äP ®Î¯¯}sCÒ ¦¶Mýl«}Õm±yÒÄÐØ×‹žH×ÇŸ¦¥9n+ÕF‰æ—êú#2غåÎsãÓ‚£™6𛯒Gö¯Xñ—¹ 'Ò·n`e,޾0O1Í)–4ýŸ©ëïú‹9µ‘ã s¼s„F€ @€Ù ¤Ÿyíì··e^nøî†_Å'Y'óOKãh4ΜX5¶¾¥}h¼-•Jòó¶tÔ†N’J²  Ýè‚ (pã‰7Þoú{yCŸnȯ¯­¯=tº+·r½%k–̼¨•}´³íxÓ]#Nµð‚M§n*żÙ{²S€Ü“Še @€(°@,V}c¼>zjL¡”sÒx×Ì.ôóÂd’†ÿÝÆÅÙ*^‚yóøª±O'b‘îS`²DOî&ArŸ;Û‡ènC#Wůݟ*¥Bæ‡ÐsqÌ-ét~½‹xW’„Ž#«þã|ÙאָùfVíå±È<î1 @€ @`–q‘Ÿ%·ON wͲ ›åQ IJ3œåžxcÑü“õU£çíé3ËŠ)0¾aüWñÎþ©bF¿{ÔÄ‹¯^ @`ïéÖɳâyïÆ½¯QÜOb¡ì˜ÚðŠŽ>åY[7xl,ò¾¬¸Š÷Š< ?ßÏÃIò±XíøS˜™äŸ†-“SSϧ„­™´—ãF s¼s„F€ @€i Ä_d§“ϸ~õØ/¦½‹$0Q¤`§k#øÇw5n;)ÄbÕt·±^qâþ-ÇqkÖât"%@€@ê«Æ>‹q8Öò½béïà$íùP'2«Õßûp'únEŸiÞ°é¸ ?lEÛykS2o{D< @€˜…@š4^¾iõøµ³ØÔ&Eh$7!ÌÆxK29ùÌ›Vß´y†ÛY½0I)†¢Kž€,Ì!'PtX Ý6ùò8rÅm£5Ý'ÉqµuÏnMã{nµïª?Œ³ÚŸ½çO‹·4Žüquýš‘ó‹ùì"V€œ›­ @€ ø”Ñë+Ç.ÉM@i@£TÈxñ¥1•†çŽ7^ê¹-[p ¬É´Ç­' vÜ —ˆC±Ž&!=§s´¸ç¤raÿeý÷kq/w7Qè­ô$—$IRmK-î¤Y˜žÚšœÎ w•›æ{r‰@h‰À±Ç¯Gy 7«¯¾ºãÖç V<èBíwÑŽŒþY¦Þ])W*¥x’lçNKÒä¯7®)åe;sôwq®¦R “Ð0¤š¦-0¾rìõõƒÏçÁc¦½QAVŒC¡. V/ˆá>¿Õ!×î?xnü6ñÈV÷Ó¾öÓ×lzÆè¯Ú×_ç{òdç÷ @€ 0Kt|rË]'›?o–|ÚlÛÖ©RršäñÉϯy[ø…:KFÚ(Gáܳ<lF€®ˆ_w&ψ_z¶”S y^ߺ•­Ìmé•¿oX‹Èr¼âM£Wvãˆ5 å8~eA€ @€@ LM5ž{ý‰×oêÂÔ».å­IIž€LÓ‰­wÝþ‚¸ã…9¯ò ”ã È4 óË¿¯dH€Y ÔWÖÒ·dÙfžÚJ’ÊEK/]z`KbZª=qèÕ8”¼–´ßæFcññÆxÓèmî6Ý)@æb7‚ @€3Ø6þgæ[Ù¢ˆ·?zssÞÄ"ƾkÌi’lºñÄoÝu™÷åHCrS²KãzeÈC Ð^‰‘±wÇ[®~ÐÞ^ÛÓ[ŠuEuéü··¢·Úâg…$yl+ÚîD›ñ+üKºõ¦QÈNqú$@€ @€3ˆµ»dëÌ6±6Î TCZÒ¡ç:ëªw(ˆÀK¶ÆTãŒxÙTA"žY˜ixåÒ«–?~fí{í%ÃË7 iyžMÓK'Vm¸lßY—÷SÈòî[™ @€ @€@‰â0 %ÚŸÝJ#I³Ý°£åH€{˜8nì;ñ9úó÷ºB?ˆ7ÇUªÕÊÇÂE¡7£4’yIõâ’…µ×ÑfbáyìŽÍ·¾º£At¸sÈïÝ @€ @€é (æLÏÉZyH=™—]!è¤ÀÖÉóâô׿ìd­ê;!Q»ÿà¹Y´ß¿~ॱ'eÑV§ÛˆßÒ$ §ýæ”ßÜÒéX:Ù¿d'õõM€ @€¦)çÚ1œå4­¬–ꔢy>ö„( @ “õêwLN%/îd ­ì;I“s—­|ø\ú¨­¯ ¤iå]si#OÛÆâã…ã«F¿”§˜:‹d'ÔõI€ @€f(C°ÎÌê˜Jª†`íð>Ð=äC`Óê‘/Ç!9?šh2Ž" ó*•äâp^˜}½)íýH¼ÙîàŒ#ëHsqÚ„ŸŒondòThGȰÓÙ¡) @€ @`?i¢˜³"çK aÖ|íÑ @€@Gî¸só9qdΉŽÑ¢ÎcñðèÚу¯˜MóˆOÛÍfÛ¼m‡^L§Ï§l¸3o±u"ÈN¨ë“ @€ÌP I g9C2«wX gjj[‡CÐ=ÈÀÍϺùæFÚ˜U‘.7Iì;·×®¨ î{•Ý?=âò#–VCøûÝ—ø§4¼u⸱ï8ƒLCW€Ì”Sc @€ @ EiœMÆ‹@‘æÇl‘ö—X  @ å«6|.„ô_ZÞQ:HBrP2¯÷Ã3éºwþ‚„$9l&ÛävÝ4ývýÚÑ·ç6¾¦Ùt] @€ @€ @€Ý'°uòÎWŬo.iæ«úÖ¯xîtr[¶vðäX|üã鬛ÿuÒ;ÓdòÔ8ædþcm_„ í³Ö @€ @€ ÐÅ7wÃÆÐhœUV‚$M/ˆC±¾¯ü /?¬RMÞ¿¯uŠôYòáuõ•õŸ)ævĪÙe} @€ @€ @€¢Àøª±O¤iú¥2b$Irxèé¹`_¹*$!,Ý×:Eù¬¹ë+GKSLÍÒ]2KMm @€ @€ @€ö#05Όū;ö³Z!?N*És—­]¾jOÁo_ž$ÏßÓg\vKH&O‹q›÷z;Or( @€ @€ @€Z%°éøÑ_ÆäZÕ~§Û­T«^²fÉA»Æqøå‡\©T.ÚuY‘ß7BxEzu¬È9´2vÈVêj› @€ @€ °‰[ÇÞÒôÛ{ø¨ð‹â«ƒ½/|û®‰ôÎ?àoã­».+ìû4ý׉•#ÿXØøÛ¸duA€ @€ @€ØMà”059Õ8=.Û¶Ûò²ü„Wô¯ë?º™NzõII^R†ÔÒ4lL·M¾´ ¹´2ÈVêj› @€ @€ °MÇmøaŠõ{ù¸Ð‹ãÓŽ•4©^|Èç9¤Z©\œ„X‚,Á+IÒ×O¨ßX‚TZš‚dKy5N€ @€ @€Ø»@½1úö4„ï}â~‹?ð€EßµÇß-n¿<‹?:¾rôÊß.ñno {“±œ @€ @€ ÐjÕaKìâŒ4NÙê®:Ó~òÀÎô›m¯qÿüjÛæ;ÏζÕò¶¦YÞ}+3 @€ @€ @ õ•#ß ðþ„Ú•!Æ'qÿœzÃ)7ÜÖ•³HZrh6!@€ @€ @€d)°íÖ;Ï@ŽfÙ¦¶2xw}ÕèÕ™µÖ )@vÁN–" @€ @€ oæÓu©©—æ;ÊnŒ.ýa}ó蛺1ó¹ä¬9=Û @€ @€ @€2ظzú8ä?dÔœfæ*†­ÛÒ©ç‡SÂÖ¹6ÕmÛ+@vÛ—/ @€ @€ [tÛäÙinÈm€]XÒ7]?4þƒ.J9³T 3£Ô @€ @€ @`nõê7†´ñê¹µbë¹ ¤iúÍúµ£ïžk;ݺ½d·îyy @€ @€ @€¹¨¯û§8ëU¹ ® ‚ŠO>Þ>ÙHO ç…F¤Û’ [ªQ @€ @€ @€ÀìîlL½4Åzëì[°ålÒξ~õØ/f»½íBP€t @€ @€ @€r&pÓêñ Ihœ“³°º!œµ+G?Ò ‰¶2GÈVêj› @€ @€ 0Kñ¡±‹â\„WÏrs›ÍP ½úë-i8c†›Y} {@±ˆ @€ @€ X›<#¤aKb)}I#¼ìÆ¡‘‰Ò'Ú† Û€¬  @€ @€ @€Àlê«ê?m¤é›g³­m¦/6Ò_5úÙéoaÍ} (@îKÇg @€ @€ÀÿßÞÀÇY•‹?çIºÒ²´M2“LðzÅ AvÔ{m’RPôÆ+‚" « Ѝõ²ÉUQÁ²q½• BÛ,ê·¿,.¨ˆŠz¯d’™$Mé’îIæ=ÿgRJÛd&3óÎ{ÎûËGIfæ}Ïyžï™¶“÷yÏ9 € €TY ÿÑôdä惘»ÝÓ·eÛð‡ÝM0øÌ(@oN € € € € € €@áKÔ˜¯Í™²d®ð“8²YãÖ­Þ¿þ-ë×r<Ç&@²0'ŽB@@@@@ª&п0ýkéüKU ÀÕŽùjvaú‡®¦W­¼(@VKž~@@@@@@"²Ãþ™°÷·"NáÐ ŒQQ£¹K'8„—¦(@rŠpœ† € € € € €*ÐÞ·5§rÌ/h¿v–_ÎÖW¹÷fg·8˜^ÕS¢Yõ! @@@@@@ 0–ÌCÊ¨Û ;š£&¸j ¥ï± ^ç¥(@–€Ç© € € € € € €@Ð[‡7~L)“ º_Wú“¤¿Îö¤¯t%Ÿ0æA2Œ£BL € € € € € €À~Öµ¯Û E´óöó2OO,°ÍŒŽ¾W­F'>ŒWK YŠç"€ € € € € €UÈ.ìý¾Ì‚ü^º¶ºKߘOöŸÜÿ'«“° x  !"€ € € € € €{ ŒnÛv¾2fÝÞÏóx?F=Ôß’¾~?¯òt(@–“¦@@@@@@ VŸºzP —ÕŸÍý£†eéÕ÷IÆæékO {zð@@@@@pB ¾«ñ¥ÌEN$SdZé/%ïMRäi^& e‚¤@@@@@@ 4²ôª§¼üÒ«±ÐÄ` 2ës¾™¿)À.éj7 »að# € € € € € à‚@B7])ÅÇÃ]Èeª9h¥ÞÑÐÕØ2Õó9oê §nÇ™ € € € € € €@èê;›ŽVZ_ºÀªV±¯Í_>vºŽt— #=ü$ € € € € €N ,WµžÖ‘]zuï±”¥X›kæÌüϽŸçqe(@VÖ—Ö@@@@@@ÀsRWÈÒ«/ ¬C+:2g';“Ç[ª#AR€td I@@@@@¢-ÐÐ:JiuI´þ1{)ÈzFÇnW2;ô_å™JP€¬„*m"€ € € € € €A ä—^Uê.­t<ÈnméKЇ'ç4Ö–xm“¤í#Hü € € € € € yÄ©Ï)¥_yˆ‰´úXÝÊ&Œ&2*Ók ËI3 € € € € € €@5]ÍG*O}´}[ÖgM¼Æ»]--¾**pEyi@@@@@¨ ÀRU£µaéÕ‰MÛü‘ÂçÈ©P€œŠç € € € € € €!H67/‘¥W_‚Pì Á3Ÿ­ï¬?Ôž€í‹”¤}cFÄ € € € € € €€Jt$^i´ù8Å h¥gyºöÖâÎâèb(@£Å± € € € € € €@òK¯z5ˤ˜C8¶Å µþWÙ;ó,Ûâ¶%^ ¶Œq"€ € € € € €Ï 4Úô¥Õ+™º€VæÚ÷/¨›z œ¹? û“áy@@@@@@ „ º’¯ð”wiC³+$­®™>ýF»‚¶#Z vŒQ"€ € € € € €JÉÒ«q¿K(jà(‡€þ·†®æ·–£%ÚØ%@r—?!€ € € € € €¡H47}Z+uD¨ƒ´,8O›Z~Ð\ËÂu¸ C=<‡ € € € € €ìXÐÙør­½OàQn˜1gÎuån5ÊíQ€Œòè“; € € € € €Ø!°DÅã:v—ËÒ«•1mΨïh|]%šŽb› £8êäŒ € € € € €V $ŽM]®µz¥UA[¬VZÇbÞmj™šnQØ¡ •dh‡†À@@@@@@¥êV5¾LkýI,*- _hh¾²Ò½D¡} QerD@@@@@;déÕXÜ[¦´ªµ3Ë¢ÖæÂDG‚™¦%È9@@@@@¨”@â¸Ôe²<èQ•jŸv÷ë¸òjîPRøÝó#@²-ŽE@@@@@¨[ÙôR)ˆ}* îèæYü^›Éã›.dê §nÇ™ € € € € € €@e–«X¬F³ôjet hÕ»|þŠÆp ‡ìC€ä>Px @@@@@¨¦@ò€æOÊìÇWU3†ˆ÷=½¶6v›èˆ;L)} Sbã$@@@@@@ 2õ©Ã¥ìuyeZ§Õ"^—èj>·ˆã9ôY ¼@@@@@@°ä—^õÔ]R€œ–"ÇÕ‡¬hLFÜ èô)@MÆ  € € € € € €@eæ4]ª´>º2­Ój±Z«9Ój¼›‹=/êÇS€Œú;€ü@@@@@@  +^ì)ï3¡† žÐZŸ’èj|×sOðä '%â@@@@@@ Â²ôªWS»Ìæ¥W1¹ +U¯yûòœ®Æƒ«€]=S€´k¼ˆ@@@@@HÌM}LÒ:ÖÚÔŒ‘ä"£Ìfks˜ p­Ô‚ÙÆ»a‚Cxi7 »að# € € € € € ´@¢;ñ"­ôgƒî·œýIññšþÖÞnå«ËÊÙn˜ÚÒž~w¢£éMaŠ)¬±P€ ëÈ € € € € €¸/°DyZÕ,“D§Ûš¬1ª'»Ñ¿6öÑôMòí[s™4nÏ[ZwwݬI‹ø #þ }@@@@@¨ž@â¸ñ¥W«^eèÙø¨ö¾­ã--Q~Θ³”,ÉZ†–CׄÖêÐØ‚Úχ.°D2dB8 € € € € €  Dgâ0­õ›³•Ùß϶ö>°{-é'e/È«vέŸõ¹u«íݯ3€Á 2] € € € € €@0&f¸æ 5½ €@©²ôªÒv/½ª”Ùê›íï‹";œ–Y‚æûzÍöç¤hìÅbÞj¹ªµ=—JÅÏ?Æ•’¥]@@@@\@®æO ¼S:D¦ 8¶ù#²œçñS854§ø¾ºb uàé}Ô®FÆÆü³Œ1þ>_·üI)Bž˜Ót¹åiT,| £¥a@@@@Z@çôô û¤?@ Xäªä µg>Wìyá:Þüµß¤¯›(¦ÁE}J¡îú‰Ž±ù5­¼Kë;R‡ÛœC¥b§Y)YÚE@@@@Àd6ÈÀÕ銥WMúF@J0Æ%XsZqq¨„÷§"€ €å0¾3 ó‰üT¹\h'š Ý©Jéà3ÑÌž¬Ë  ãµêNÛ—^•Ù‰k7ÿ²2xì¿ ™Y9æûùBÿèþ²÷©@¾0Ñúœ½L=r S·ãL@@ ªÌ€¬*?#€ €Î Äõ˜3ûp]óR爄Ht6¢º9°éÈ9DWóyJ«“lOÌhó±á–¾µ•Îcukßï}å_[é~ªÖ¾V—$W%¨ZÿUê˜d•àé@(UÀÄܘ)wTÎ+Õ‚ó@@JÕjKé­„£­ Èp …uQ$;“ÇkOWf?Ƭ ž€C!PßY¨ÒæšPSZfö.+­‰ÂÏîÏõ^)7Zÿ¹ð3ì9RfÂÆM,v‡Z®"õ÷ H{Þ£DŠ €ì!wd VÙO"±Gb<@@¨Š@MÎ%Xå3&Ȫ¼‹ìî4Ñ™8Ìx±Ji¶‰°{(«½ŽyµwHÁiv5ƒ(µoÙòÅ—bà¹ÒŽ)µ­‚ÏoSÛ•oÎ’ý ƒë³ààJ?Pnj8217õ±Ò[²§ öŒ‘"€ €{ Œ9rH{ ÷Y!€ €U0qãάFS€¬Ê»ÈÞNV54+¯æR8:ÄÞ,ˆ¼Ú²|ï9RÀ~Cµã(µ¹‰ãÆlKÏoJm§Øó³méÿ'õǯ{ž-Ç‹ëgæw5þ³-ñ–'ÈR9@¨’@n4·­J]—µ[mL²¬ Ò € €ÀÔF¹Ám<{óÕ¡¦M ‚³¢&0¯«¹Á‹ÕüHŠ©¨åN¾å/bkïóåk±:-ÉôÃÕ›·/©NïJoû¤Ä®Vÿ•íWϨU±Û¤ùëÆý/ î1"€ €€£þ˜ïÊ=Ì€tô=JZ € `—@vMv“]ï?Úüþ} ¼ä‹ö¯ °C ±"1¯V™)­#3+‰±¯Œ€ŽÕÜ¡µ: 2­تÉ]¼þ-ëרã] µmòs¹íñ¤K´:)ÙÕt¶K)í/ û“áy@@ äùå²/ƒõEH£U½Z¢ø\ò÷á!€ €@ÎPÛdï­g\É4fbǹ’ yTFàÀûöÌs;dÆÚ+íÍ‚Èà _zÕ(ï?ÃK)1È (c¾¯Â3ópsî"cÔP)9…øÜ¹µÓôÍ!ޝ,¡Q€, # € €ÕÙƒÖ órµ5êeÕ¤W@@=ŒIïñØâráúõ~$öÙ²x˜ªúòƵsf> ÅÇ㫽º$ K¯ÞîÆÒ«êº¶ôÃ26™Ó2Ï(ã_–xʇ̘=5ÑÝôör·¦ö(@†i4ˆ@(Z@;Q€ÔÊ{EÑ©s € €@Œ33 åâî¼]É—W‰&-=g&æx+¤4/Pó…@I Ý©Êß5ÿRR#¡8ÙdF‡·^ŠPv "ÛÚû]YvånO9ö£wýÜ•©ƒKê¹t(@>GÁ € €€}rW·H¹1¤}o?"F@'´33 óÃ3Þœ&’š’@~ÙUUïta¯¾)pRY݉&môÊÚh•ó}ÿ‚¡ö¡MUê~Ân·ù¹säÚÇð„Yú¢Lѯ›W_±4üIæ9) € €áð”ïHÒP€ ïÛŒÈ@@ JÆùaó´fÈ(½'ÈõàŽƒçÔÌ™Ñ-{пv‚Ãx ÂLü6YzuNá'„óH™aøÃþÖ¾{ÃRkÛ2}Zù—†5¾’ãÒúôDWÊY´ÿ(AòMx@°FÀê0µLM·ž@@@WŒ[3 eÏô×å—Ütu¸È«0ï;ðÀÞìÉÌÇ ;ƒ£˜X ¡+õy?-œø( ^5jdtÔ?/ì‘fZz—e~ö8§ŸV·ºøoÈ)¿#8@¨¾€¯Lõ£(=ùÅ-V· ‘Y¥SÒ € Pš@nÌ™= ó2Ûm¶ªñÞ\ gÛ,¼7yÈÌsTZmsÄüÒ«2»Ú‰¥W¥¨÷ù¡Å} î~#1z,÷yuÛ~°øù·êyº&~µÅ)ì3t ûdáI@@À˜oY‚U©x\;¹äˆï$¢D@vdÍÊr|9·®Œ…à}k£/kXÙðâ}½fÛs m1âE@öÐæ÷{?eëcYqÄ¥llâF@D@å^Rë#ë:’Ç1Àn Ôw¥N÷bÞrsã,·3%» ]MgHŸmA÷[‰þ|_]1xJÏß+ÑvPmÊÞ•K¤êÔlýçì´ªÕñšÛÕe}ýÎúž~@@"*`|ó;gR×ú—ö;pf\H@"%06æ^2?€q/vy¤2bÉ6t5]æiuW~e•ˆ¥Nº˜wSBn–ýr…» ¤yÙÂå©þiû—‘•½+ý1s–,Åj ¸™Á}BâØÔ‡î¶ìÝQ€,;) "€ €Á xJ»3R©º†Tãâ`é @@`wÁ“Óÿ'×t×îþœ?k½(¹*y„¹Ä.5-Ùú†,»z•eµ]¾(¯@í4½TZœ[ÞV«Ôšï_ ÚÕH•z/k·ý‹z*åÇüظùå©«+)›“£ióè; € 93âL2? ZÇ>ÀÀ"€ €TW@¦”üººT¨÷xìSj™f« °àþuÉXóå·ˆ÷T¡{ºŒ€@²3õï2ídR5ÆüW¶­÷.ä²3‡íþ¦Ke)ÖÌÎÇ.}—*fëÚš¯ÙœH›GØ@@h’»þÜÁ0ojXÕÐìN>d‚ €Ø' óÈœÛ2? ò¹ù´†• /¶oDˆxoülÖø´é¿0!Ðb7³IDAT”çÙÛso—E`^WsƒÒÊ¥W•Ù´ÍÏ}´,0!jdmÛÚa“Sç„(¤r‡ÒšßÛ¶ÜÕÈ ¤é@¨¨€;û@ÊÝ¥žŽÕ¼¿¢\4Ž € 0¡€ïûN óŸ5½ššÿ˜0y^ ½@Cgãi&û¹ŒgSèƒ%@kjµY*Kôdm»®}óÙµm™¾ÝžræÇl[z…ÌÚÿ®3 핈ìm{]}Gýü½ž¶â!H+†‰ @@I´rjVYBéƒjyãŒI²æe@@ è17 ;¸ôÛê;›Û*DG³•X®bɮ櫤Œ|,O8«’]Ñv´ºRï•÷Øb7Ì™G{op#—}g‘Û¼íB£Ì3û~Õîgå}xˆçÕÞhc m5bF@ö0¾[HYò«>1×»p¯4yˆ € @vq6-Ë•Ô]àÝÈŒ’›ë€¸üÔ;œ¿j~}rNódIÌË䂼žzKœ‰ÀÄù÷šN¾2ñQö¼šËùç«%jÌžˆ‹tð´ÁÕòoÖÅÅŸiÇ2Ûûíõ]Í‹ìˆvW” wYð € `­@NùŽÍ€”9J_:weʉån¬}c8 €D]àÿ¹ å«æØ‚éŸs5?×òªïJ¾¾&>ó·òKÂI®åF>á¨‰ÍøšÔ¸_dÅG$³¿>ÐÖ÷“âÏ´ïŒþ–ô7¤Ùe_ä…ESækóîŸw@aG‡ã( á¢@@JXîû“2j¤¤FÂwò³âêá ‹ˆ@@ˆµÒíLÍEÉUÉ#ÜÎÑúìt²»éSžŠýP¦<ÖYŸ „^ ¡3õ™mvjè-,ÀõcÛ¶]ZØ¡ner#’¢ë&7²Ù+ ­k¦ÍüϽž õC ¡‚C@ 8[Ê‘¿(ðhk“;ÓÏ?dEcÒš€ @pH`Ôß²J.ä‡RÚ#)2ÄTÑÕüm¹Æþ_®‚¢7Švfœèj|—,óû;£ß3êüÌõœ?v®jW¹=_‰È£%Ê7c£g9¸MÍøJòEÉXê³6Œ&HF‰@@úN?.wù­)àPëÑžþb¢;ñ"ë'`@@ÀrQßD 9>«äŠúÎæ6ˇËêðMoš]£ž‹ëï´:‚·N îÞºJ{7Xø~–Bêmƒm™Göór$žî?¹ÿOR€¼ÂÝdõGt6¾<ìùQ€ û € P¨€Üå'‡þO¡‡Ûuœž¡Lü[j©ª±+n¢E@ìXÝÚ÷{Ù²Çî,&^µóbžúvݪƗM~4G”S`NWãÁ²×ãm:æuK)˜ýßˉK[ ÄgN¿E–¶<¤ ƒC~Ì~|f£ñݱþLºçZ¥ÌÅœcѱ5qÏ»S-W±0ÇL2Ì£Cl € €@±ÚÅ} w ÈE¡#‡¦n“GrC'_ € €Á ø+ƒë«ª=ÍŽ‡‰WV5Šu^ß•:}¶ŠýY>럡´I5D²ÝÇ;ä7ÌÓBR‰¡˜·ô­-±7N?[æ|s¦¬åäR´R4?*q@ó%a, abC@ŠØ–ó]Ýr\B>`ÿ{CWó‹dáp@@R|õ@)§Ûtn~”ŽÕ_0í¯rC᧤øXkcÄìŽ@²+Õ.³ßêLFZ}5³(ó[gò)c"ÙÅÙ-c9ý26®¦´z}¢«9”ËXS€ ×[…h@@’r¾ß]r#V4àÝœ¿{ZBeOH+Æ‹ @@ÀfQ“»Åæø§»,Ç:[+ï>¹°ûEµDŧÒç(%{ì’˜Ûü„§ôR)ø4`‚@µòK¯¥oªvåê_¦<nݰQfùñµ?Á¶žòïØßë¶?/7Í|a^Wsèþ~¥iû;‹ø@@½ëû¡ü²z¯§|˜¿{:Ñ•úŽZ¦¦;™ I!€ €!XÝ’ù,ñ÷‹„hra÷#‰ãR?9¸#ÙhÇ–w–ìL/ŸÕª=ï~¹cðÅ–§Cø Äbµ_uiïQùýÿ#ëÚ×mphˆ*’Ê–-?ªŒé¯HãÕoôÀZ™[ý0öŒ€äžús=÷£†öùbž”bÚ<ÓÈÞq+ç¯h|AR.<Å%ÊËv¤øø+Yºöò òõ…ŸÌ‘#X‘˜'¿Ü8³œ´Qf,gŒ³µJ¼+ú[ûî“Yÿ]‰¶ÃЦçé¼ïÀÃK> a â@@Ê(0Ð’~R>T?VÆ&Cß”!ëcZ­Lv§–Î_>vèa€‡¬hL6t¥.’}Ždyµxöôõr%Ëe…p¬ @ª´©íJ™;«Òw˜:ÕzQmMì²¼èç£þ¹sÞýóHv5}(q\óe{„{¤øxT˜†ª±Èò–’÷ýÖr´EU¨‰ßäÒÒ«²òÑuã¿ûW™Õ¶îGr[?,×KÖÙw!ñ毋̜1÷ºBŽ â A(Ó € PhÍ‚ÜE¬?X;wÆïê;OÚõ?íO`|¦cwê|¹€ö³iµ^¯§õ—ó3JåâÑøÌG¹ƒò„ýËó € =܈^*{AúÑË|¯Œµª•‘—ÖΙñ—dwÓ92³jæ^G8ý°¾«ñ˜DwÓíµÓgö+íÝ"Ÿs1a™aöÓÍ£æDYDŠï|Ù,ÐÐÙxšü™}»Í9ì»1}£Ã[¯Øã9$0´hh@þl¤ ƒ->#Ù™zcB§†Q @@ [†7}GšÝV¦-hRÿSÌ‹=$K@= 3ø· ààB\®b²wщ2SôjÙ›çwZÕ¤¥Øxƒü2þêEÇ=ƒÉ_pá @Ø!0xJÏßå.¥N<žкA™»Y×Ä{eùúkò+J¸j“_¾2ÑÙtn¢»ùñ˜Ž=ª•w¦|~œåj¾2óñ»Ù\úMNN;9SÊÕqÛW^É{“‡h/æÔ¶¾Ò µmÚW¾<7¹@¶¥w™ÜLó£É´ô­–†áƘ¸¥|„ € 0‰Àºöuft͹Wî~{×$‡:û²\Yìy¦Mf÷-Ùn>»æÔÞ¬³ÉNX}Gý|O×ü‹Ü¹½H6ah—CÆŸã8Á‰;bädD¼Ž €@ÄrFß,Kß³÷öîã®õÁò¹û²¢ÄGe9ûï)ß|5Û––eíe‘D‹¿æ¯š__Ÿþcô[%“䦵˜ÅéºñÍç³­éËä«Ç¯à„?ÐÌ’¥W•ªs%M)œu÷·¤ÝÇ0¨q’ýŒÕš?ÈßkîÍ`×úù¦¦&?C¶ª3=)@õn¦@@ *ùeXud yòg/’œ5mº~"¿kræ«ý‹zU•á¨ÓüÝé~,~’_Oü…¢—Lµk¹öÂüÙÓ2ÏLµ ÎC@·Z{:’]©ÇäƒÖ1neVz6r£W\iõNÓï”'²R¸»_is_öéôÕÙj´ô*Þ‚^ÐÙø²˜Òo”Ïo‘ÞòËÊ*ýï7ȲŒcRq<¯¿5}k("ˆ’d©à7ËÛ÷%7–ŒõÏK86Ç‘ŸÑßÐÙt¹ü]š=Ëé©•¹°¾³é»­½¿,g»Å´E²-ŽE@,ȶ¤ÿGîÀî‘ Í–…^‰p§Ë/ïÓqý¾ü3Ù¸è¦þþô÷ÔÖ/S«‰?Zks¬äx’\4y‰\4’ß³Ës¥ÈLågA®¨Ä Ð& €Ø)3þ¥² çCvFTÔ:!ŸÈΑÏdç$m^o:Í*_«xzì±ìÂìS…|l«þW}Wê%^þ¦5e^/½N>OΫ~TÁG`ŒÚh”ßÞßÒ×|ïôX 9]ËÉ_«DÛÕjSÞ£×-îûkµúw­ßþG{¯O—z‡‹7ÔÈßå1¹.p»Zª^U­`(@ºö'†|@@=ò5¾(ÿ¿qϧ#þHîÖ—ÍÐïN$R7ë.Õ-ÅÈû7+ÕpKßÚËè†U )åÅŽÒÚ;F.h-´Ž’˜çî(5îþß2f¢È2rÒ €. ´öý8ÙÝœß ²Õ…|Èá@™GønY¿ôÝJÕ(¹IpXnû•ñÕcRPø¥,kû»Õ›z{U»©T,uw×ÍR‡Ô¼Ô‹y/—bãËå‚»|×/—þÜѧ<ªTçaoט¾1ã/ZÝÚ÷û°‡J|… ÌV±å=íÐÒ«êéì°¹¦pŽœT`‰òÇVš3ã5ú7rlͤÇÛv€V/O¦š/ͨž+«:Èj¨Ó' €  @Öï¹-á¥>.w¿5Ø­]É%–Ùr•å­RŒ|ëlãåènþ¹òÕ|¹´Y›_V­ ¹DyõÇÖ§”šv¸ÄöY¶ëpùÅù%F«Ç´®7^€ º×Êö§E3º—Ø*kKë €‘õsŸˆko¡|Δ,|# 7’Í‘Ï"o¹7h“mº•JÌMÕ¥¥8˜–)ißè´L’L{Æl0žÚªrz‹¸U ‡[Tnt«œ¶57‹ë¸™)ŸagH[3dNå Y1u¶|ÐIȹ2û«QÚKJóòé§^>KÊKù¯g¿íxéÿÊÌÇÇe¿ø“לÚÉýâ]üDgÓ)òn—KùÉM ç«ö¾­.å†\Oîýƒl×rü[ö™0ÄSö´º<ѸGfÞÿ¹ìmOÒ ÈI€x@°^ Mm7ê*ùåË©¥gÊ=.ùåI¤Í×ÉE×yòŸäÁ]©ÿ5ZÿÒÿ ¹Ó£ŒŸV¹±tÿ/û3j‰›R KUÍüÆù‡xfú<3ó´\’%¯5ž¨2úP¹Ô,„RrM¨vWû;.Uë2‘ô{´,ÛRS­e[v9”ñ'£ ×ÝÊèIS €‘ÈÏ“¥í¿)ÉŸI€2'=^”"¡|”ÿ«c¼ñòù ?þcþÓêί؎‰:ñóuv¾6~à΃v>Øñ}磯ò]J´Æ¬ÞúÎ5íC›ðpG`îÊÔAòçf©;¿Wï—-VVº”S˜rɧ¯JÌI½M® ¦¸Ê‹VÓ”Šß.m½FþŸ_%+°/ QÓ € P=þtúÎDsó'äÚšՋžµ~¾\¨y¾ÜÔÿŽñèó7÷ÇkUâ¸TNw«©` ËU‹ü¨[äªÐù(¿UŒGä.jù€¯¦É¥¢iòé^~6ò³š+¿ç÷Ó™;ÞÖ^ÿ‘B¤\\ÚëÉÐ<Ô3êSMG ¨êm^ A@=üÜèg¼XíÛåsLþó_X#`”¹!ûHúb¹±PvdàË%Ù5êÉ'_ÈwäËlõÍÈEŽ$Î4dùkÝ™;˨ØÏ]œÕ/×&N”¥¿ÏͶô|5ÈÈÏîç @@Àu³Õ¨Ç®t=Í òÛ1[R'¥^øbùùHùÿ«åý›äû©REü7ù.Ëýè…r!î$)H/?)ÅÇçK|û,>w)ýH'–r>ç"€ €€›ý‹ú{¤zèM7%É*(™õ˜“ÏÏ.L_Hñ1(õàúIt¤Ëïdï ®ÇÊ÷äså@ëÀÓ•ï)Ú=dZ3Ëïð79¬p,ÅèÖ< ~7‘ € °»@öÑž¯Ël½ÿÝý9~F PYì„Bå8@ˆ–€·eìjÉxC´²&[¤ð¸Ñ÷ýÅ2 Èå"ƒCS–˜¼ïÀec¯•¥±Ð4bþÚï÷~)4á8Hnpûeò÷D‹iÊÍÑh¿%ÈÜ(@©M_ € €@5ò{uE5C o‹Œ:Þâè @*(9-óŒ\°½¼‚]Ð4% ÈÖé\.wâ@[_gÉÑ@(f͘s½Ì~L„2¸)%ïÛóT›Ú>ÅÓ9­HÁÓ7ãŸ]äiö®õ¢†îæw0È ¤é@@fcú›rqè/!…lк1±"‘²-lâE@`òûJÉÒ–ÿLoô‚@ÑŽmÛzÌࢾ'Š>“¬¨ïj^$Û^œnE°)§.—¥‚XàáV&þÖÞnY=êî25ºf´1_‘ßíçÈ ”é@‹@»Ê)å<,á‡eñ˰Z6d„‹ €@€Fé±3¤?–b ®&dßÍd{NZ}êêÁÉæòK¯Æ´¹ÕÆØ÷³QfÓöQÿ’ý½Îó•بü‹åïŽÕ•í¥:­k­ç©š™-\ù/ •7¦@@ TÙ–Þû󿄇*(‚±D@S€´d¤@jdf{•o.¬Fßô‰À¾Œo>—]ØóNu†Ú¶¯×yÎ ™3æ|Ùµ¥We–Ú’g÷eÜ!û²né[+cp¾}‘±ìù®úŽÆÖÂŽžúQ §nÇ™ € €€½#£ç»z7Ÿ½ƒbA䞢iÁ0" €ÕÈ´¦¿.ËÞ_Íè¥ÌVãûï̶¦— á¶@¾ˆ"3ºÞçV–æ™Gz™¡æ–[y³É´¤—Ë¿g”·Õð´æyÞÒùËçÏ®dD +©KÛ € €@H²‹³küœ9/¤áVx^!{EÌ oxD† €aÈmÙþAÙw|( ±CLÖ3¯Í¶ö²ê‹ãÃÐòƒæÆ<Ï©¥WóC&ïßóÔ5æøðY‘ž,ƒ{®êäÒâR¸oª™3ýó• •Ô¥m@@ Ämé{äÎàï…8DB ™€V:î×ècCá € €@ÈO\mLîC! ‹p¢ `Ì/·o3G÷/êýUÒzŽ3æÌ¹NiÝ蔃1wËû÷§Nådq2ùep}e>nq “„®ÏIt7Wl¥# “ðó2 € à²@.7rž,)²ÆåÉ­¼1íUì—“òFJk € PMþÖ¾{åf·oV3úŽ–€Ìºýv¦?ýÚ5§öf£•y4³mèlZ(ûؽ߱ì7Œnßæp±ËÎÑê_˜¾Mõc;£Ÿ8j™™¯Þ®:Ô´‰œÚ« §æÆY € €€mCJ›;‘ I$@2 hºA@Àz‘ [Ï‘‹¶¿³>µ€QF¶{4—e[zÞ­ÎPÛB,Á•EààŽƒçHáä¶²4¢F¤ˆþ©Õ§® QH„²CÀŒ¨Üò{˺¢•zqC¬éòJäF²ª´‰ €X$]Øû_raHîPç Œ9NŽ’ßQøB@&jÚ´Õ;YêCýÉ«LM@Š›”oÞÜßš¾fj-p–Óc³®Ëï_gcìû‹YŠg‡{¾¶¿×y¾ºC-}“1ZRÝ(*×»§¼KëV5¾¬Ü=P€,·(í!€ € l~¿QêO†NÈA h}pÃʆÝ-ý!€ €€kÛ2}¾V‹eÙÿ-vf@ÔabÀÓ¹1ÿ„lkïa‘¸Ê/èhz“VÞ™åo¹z-ægñæü±sU»ÊU/ zžL ;œþ’ü[ö›É޳ôõšxÌ»]J¬e­–µ1Ka @ˆ¼ÀºöurÛÕ"ù%~(òL* cµì9© € €ÀNÙ?ëײìÿ»å­¿ó9¾#P’€Q©ÑÑ£õ=QR;œl•À¼ûç bžsK¯ÊŠD·¶e±j0¢¬ˆu.w¦ŒÇœL_ëc’Ç6]TÎÜ(@–S“¶@@‹Oéù»,¬ùfùåg»ÅizZQ€ ™>@pH@–ýÿ¾ÒúR‡R"•ê |93Üó¯ÙÅÙ5Õ ž«!P;mæ—d/ˆT5ú®TŸRÌzFoÉ}²RíÓny2‹2¿•÷àÊÛjxZ3Z_Q÷@óóÊÈrIÒ € à€@vaÏ/äÆô3H…*+@²²¾´Ž €€“òYó‹2 Ò½ÙKNŽV“2[e í{2 {.a©Ê0ŽOecJt¥þEö}ü@e{©Bë¾¾4sZæ™*ôL—SÈdÓÿ!«G=5ÅÓC}šü›«5·–+H å’¤@@ÀÙCå;ræGÒ!JhsØœ®Æƒ+Ñ4m"€ €€ÛÙGÒçJòGngIvåÈï÷¨Ær'ô/ìùV¹Û¦½ð Œ/½ªôíá´èɶöÜYôYœP]3Ô6£üä÷î¬n •é]Šÿ’èj*Ëé +3F´Š €X-]˜þœñ ¿Ü[=Š• ^ËúihY•#¦e@ÜX¢Æ¶o|›\¶}ØÝ$ɬœR°þ½eìUù¥ËÙ.mÙ#0múÌ/h­ší‰xòHå}3¹ÑsåH'‹X“ Ø}DKïϔҷØÅþ£—ßù¿´àþuû?¢°W(@æÄQ € €@ä²&ß\ý§‘Kœ„  Y¨Ç!€ €ÀëÚ×mÞò&¹ìþÐ/ð½¤Pý¥ìpz!KTÉÎÔ¥B÷AçR6êæl[öqçòŠPB#Û6B ɽN¦¬õA5Ó¦µÔÜ(@–*Èù € €€«mj»k•ô:]M‘¼JÐ Káã\@".0Ô>´)ÓßÓ&+Ø­Š8éïC@.êo1¾ÿÎlKÏGÙïq@yjþòù³Ö·çW`q)e)¨nݸñÓ.åÅ\Öœºf£¯ô9Îæ®õ[ÝMo.%? ¥èq. € à¸@vqvKæéžSåÎão;ž*é) …ŽVKT¼ÈÓ8@Ø% ûhezÒo‘¿·ëI~Šº€üîñ”¯Ô1²7ýw£nõükæÌüO)=ꚃlwòÑüLp×òŠb>-=«\¾^"Åÿ¯´ü ¹S[ S•ã<@@ *g«Q¹óø=òKÒQI™<'ég6ÝtÄäGr € 0€|ÖÌlH¿Snnºk‚£x)2æ{#Û7=Ð’~22)“è>ê»’¯WÚ|hŸ/Züd~›“þÖô7-NÐ÷½Pþ [³÷Ón<Ö‰søâTs¡9U9ÎC@¢%`²­é dŸžÏD+m²H@{,Ã:‘¯!€ €@í*—mI¿ß¿äý¦ ì‘ÃÂ'0êsQfaº=¿¬aøÂ#¢ êî®›å騮-½*†ò>WçiI_••£ÖÈRÁU¾§*õ Õ™õ'M¥w SQã@@ ¢™–ž+”òÏ•»ûdU$¾"/ û@FþM €e0Ù–ÞËr¬×”­E²B@f„ý=gr¯îoI_oEÀYqØ‚Úk¥øø¼Šwpò^ÿ ³{F¨»þ…=ß’®:ê.Ðnò7Ä´w›ZÞ8£ØŽ)@+Æñ € €@Ä2 {oÑJ½S¶Eœ"òéËûàÄÈ#€ €ep—)cÞ.{j1 ®¬²!m̘ÿÞ²eøÈ–¾ÇB!a,0>ÓJk÷f Ó7ºaëÌIw 5ú!gÿíÒúŸs½ÏËI²X1ŽG@P™–ôòœ1¯’%YG„´nLt'š",@ê € PügM{•̆|¢ÍÓd8¶É…úËX¿mý[Ö¯GHDQmüÒ«1ÏsqéU•óÕÅCíC›ªmLÿ•È.Ìö*ã¢r=T¹e£.ièNUL ‹ÑâX@@çòKÇdüžcäõëe)óÜ ü1˰FlÄI@ 2‹2Élð•Ï™_¢?úP@nbÌß̘méaÏÏÙmèJ–^ý¼RúŸlˆµ˜åï± ´¥ï)æ޵S ÛÚ{‹lYós;£Ÿ8j_‰UéÛÕŸøÈ]¯R€ÜeÁO € €Å ´©ír×òEÆ7­R‚(ötŽwA@S€taÉ@0 ´÷mÍ.L¿Ov?KÂcùÿ0ŽQ1åoZôº.#ûà‘CV5½V¶š;ϹtÚ>:âËþ¶|ED@þª;KV‹Úîb¾² Ë Ǧ>Vhn •â8@@ý ô·övûþö—Ë~+÷{/¸*@ÒÕ‘%/@B"mí¹C/õ«¿…$$Â(ZÀüŸïûoèoéùˆ’›‹>œH¬HÌôbúN™]%õ ·¾|íahqß_ÝÊŠl&ȶfŸò•ÿcókž§?“\•|a!9P€,D‰c@@&hʶ¤ç÷r‘ýz¶Nz¸"pDþ‚+É €„S@–dýíVÓQòYóÎüLºpFIT{ äÇJ¦=Þ86¸ýå­}?Þûu#0.P¿FjÏwMCÞÿïß`®v-/ò™\ ÿ‘Þÿ”Y¿›üH+˜nb±Û%òIo iåø4 € ^ü^.9äp¹,ôm.…wœÊ™Ü¥÷cÞÑåjv@@ý ¬m[;,Ÿ5ÏÔ~îDù¬ùøþŽãùpÈï¿Êår'd[Ó ž>¸9QEغš^#1¶¸ÊON]¨d)é²´E#v ,Qc¾6gÊ-9»/,ZÙò5É®¦³';šädB¼Ž € P´À@ëÀÓrqèÝÊèWÉlÈ‹n€¬ÐÚcV«FŒ`@°[ Óšy8;Üs´!óûª­·;÷¢—é©«òÏ”ý;lË<â^†dT6å3<åæÒ«Rxz Û–^Q6+²N aú×2Gð:ë/0`£¼kYјœèp éð € €@IR„üMfaúrçs›"Ÿ(©1N­€§Õ‰¡ ŽÀ@@ÀMv•˯¼1¶yÛa2Óî물Šaõºn[nã ² {X*7ÃÞ ’s¼«eéÕo„SÌlõÍÈ…S=›óÜÈnð?ëêþŲcëœiµÞ-ȉtx @@ ,m}™‡ÓGÈ0ï—뙲4J#Õ0æåÎÞkU.wyõƒ!@ˆ¢Ààiƒ«e¦ÝûTN½Æáý¶B?´R^aÔèËû[z>’_*7ô`Õ©¥B}AÕ©Hæªüª@išFí%xs*÷AWo’‘-Y'º›Þ¾¿A¡¹?žG@(¯Àåg[z—e6ø/PÆÿ¨"ÿ¯¼ÐZ0æ¯2vטÜè‘™–ô?g[ÒŸÈ,Êü6˜¾é@Ø·€,uøÿ2Ã=GùÆœ.ŸUXycßLe}6A] H÷å?Jø”ìÂìŸËÚ¹+ K¯ê˜Z&ûÈ9WŸ?Élèý‚»ƒGfÅ ´d’dn/ö<{Ž÷nHÞ›i¸$ù%úÏrqé¿sjì{«[2¿+©1NF@*% ˲ö«ô7¤ùoÔw4¶Æ¼ØÇeÿ­“*Õ]TÛ•²£/¹ß“ËùW.ê£ØÕ7B y7̉])§¿ „&Â{ª6Víj$¼Y5¶oüØÌ¹,RJ'ªÑ%ûÔJ-P3c_–>ä =¿(@îéÁ#@@àLK_—tוèN4û€,ßq–ìÒ\ô´o³U~1zH ¹1Ó1xršÙªû†âY@©@~ ­³¾³é蘧?.ŸkNsq¶U üFm—Ù¥ÿeÆF?ßrÿŸí›ÎœHt7Ÿ “g/’ß7œÉiW"æ{2ø‡»ó;Öµ¯Û0£kö¹òïÐ÷4Ñú½ MßêoííÞ=?ç¦8ïž?#€ €Ø! Ë5õÊò¬ŸÉ<’NåræßäÂÆƒvDîR”²´ª1×û¾ß’ɦÎ,ìY”m鹉â£KcL. €DO` µ÷—™…éUþa²<ë×D`[ôJË8¿¤äø [Æ’²ÿ¿S|,Í3Òg/SÓe)Ê;]¼@–$Þ´mÄ¿8ÒãKò È5ûåZÇ÷&<ÈâeEå¥uw×ÍÚ=f@î®ÁÏ € €ÕX¢ÆTú ➆U ÍÚ‹/Òž'Ë”˜×˲3ªœ[½Ë…¤§µ2?3JÿtÌ÷ZÝÖû¿neH6 € €À.¡–¾¿É£sæt5~j¶‰½Eióv™€õzYƒë£»˜výdÔˆT¾ï«Üׯ÷/Ûõ ?!0eD}ÓZ«Ã¦Ü@ˆOÔÆ,yfqŸì6ÂûݶíüšiÓß(+?¼ÿ£ì|Eþl7Çê¦]-Ñ_¸3ƒøß?–uºõËv>Áw‡@R5ËVK¥ý]õ–Þ- €@™Ìja™›¤9è_Ôß#iÝ<þÿå3êçÄÞ Ëw,’ E‹d±ž”ƒ)W,%¹x$ÿS’_~&[öüTiÿgÙ–,*&Nà € Vá–¾µÃJÝ!ñÝ‘X‘˜çׯO“Ϙo—OK¯‹úžäòqL>3þXksoνg m`(¬ãH\ö Ôu$“¢Ë%öE>yIJ/ê“ÙGz¯ŸüHŽˆºÀêSW&;S—Èu»œ´0êÃògý;ƒm™GòùéDWêgòë«L–¤@¡€|0ýy¶%ýš†FHX#P·²é¥±¸–™‘ùb¤>F>¼O³&øÍÏn“ǵ¯~““ïÞèè£ÙÅÙ5tmOKU:¨Ñþí}ùeäÊrãš=ƒáH;äïºMvo¥2»ÏWmù=ÄøŠˆ€V˧[ŸëûF•¬Ò`}$0¡@ݽu â³jߪŒ×.…¸×º¸Dä~¶Éï¨?ÐFÝ»)§ØprzÝ~Ž‹öÓù¥Cg5Z¾ia_Nµ«‘ª ¤+Ÿ¿÷¸®oL­F÷õÏ!°O¹ÉzŸÏ»ðänt²+õu¹óàtò"@l_f¿.›’¿Ï†X‰+äÙdcò%&æ½Râ=Rî±;RŠo¯Âä{X‘KñAJñÉüÕý„VþoÆ|ïñm¾yœ GÅCr € °S ¾£~¾«9Q>Wž Ï ~•+7¼I±Ñ—kÁ¿—å"âÿ'c·ÿp¨}hÓÎÜùŽ €@¹â¾2Oyò/(_ € Œ€üº÷T0=Ñ ;M3*ó[É6ÿÿeãY/Q^âøÄ åv)HÆŽ”e¤^){¾TŠuó¥0iÕ‡_¹H”“ˆ{¤ÈøÙ«è/êÙïflô/²LmZòeöÛø ó@@ <Ï.=ú}i-ÿ¥–«ÚäÉ£”ö¤ é ¾NÏgõ㯅ý?²—£\ú}\>SþD*?Ý>¼ñçëÚ×m{ØÄ‡ `¿€nèj~«§Õ=ö§B €Ø!àû¹·ö·öÝkG´D‰€c²|aŸJzq“”e¦åR£òL£ÜÕÞ(f’RËk”åKëÚÿgT.^­“òá:),Ja1#ßû¤ÿŒÜmßççr}£ÚdÖnÊôËRI9ÇF‚t@@«êh~žW“;B&v<Ïhõ<¹ÉíyòÙîy’Ô¡òYrfÐÉ=»ã_¤0ú¤Äñ¤Ü¼ö­GŸÌ>œýK=ô‡ Ðùýsâ5Þp € €@09c^:Ð’~2˜ÞèŠX®bóæ4/ð|¦ì¡8CnÖ›®bjFþg)RNWÞ®Ÿ}#Ï)=]{ªVî*ÕÚÉ÷´1¾7âkùÙ˜).n6žYï©ucƬ‹¯]?xúàæ¢cã@@Ð ,¸A®©y^L{Ï'UƒÜø&Û˜ü3¥H(Êüv&_¨œ%ÅÂñïùÇR¼ô冴üÏÛä³çV¹Ym›,ß‘_v›:×Ëóýò8+EÆ~?§ú½˜Îæ¶«þÁþž>ö ¾@B# •왓84µ."{䄞@@¢) w¥nÊ>œ>ˆ;P£9þd € € € €@¼ñ;cŒþY’%G@ª- 7üüŒâcµGþ@@@@@ ’ÞŽÆÍC•ì„¶@@`‡€,“ó  € € € € €€ËãH“3\ uy”É @ 4²+7ý„f4@@@@*!0^€ìÿeïodsã¡Jt@› € °C@f?®î8ý8 € € € € €€Ë;–`]¢|¥Íw\N”Ü@¨¶€6æÛ²ÿ£_í8è@@@@¨¤À³{@*eÆÌ7*Ùm#€ u£4ÿÖFýM@þ € € € €D@@ïžc¢»ùòÄ‹wŽŸ@@ tcٖ̓ôKKo‰@@@@@p <7r;R J@Â)`”JëÑÑ«ÃQ!€ € € € €•Øgr¨}hSÎWW¦KZE@ *þ…ÙÅÙ-QÉ–<@@@@@¼€žˆ!Ñ•êÒZ/œè^C@} tdö,Úç+<‰ € € € €8,°Ï;óÍèsäç ;ó@ 0fÍÿÊ € € € € 9 ƒ§ôüÝ÷sïœ #€ P‚€1æ}²ôjº„&8@@@@°V`Âd>«þÖ¾{on´6CG@ X/g[{¶KzC@@@@Â#0i2jvcú£Ê˜ÇÂ6‘ € >cÔÙ§{. _dD„ € € € €'PPRµ«‘œ?r²Ræ¯Á…FO €Ø# ÅÇ?ë-c‹ÕÙjÔž¨‰@@@@(¿€.¦ÉúÎúCcºöJë†bÎãX@Ü032vû>º=Êd‡ € € € €… 6òÙ¶Zž5~‹<\_Xó… €€ãƬ͵â£ããLz € € € € P°@QÈ|««[û~?êç^'{BöÜ "€ ऀÉä”zí@KúI'Ó#)@@@@@` E-Áº{ûã˱zµ?PJ¿`÷çù@(ä÷|T££Ì|ŒÂ`“# € € € €%Pô È­ç—cÍåFN”™í|Žï €DA@Šë-c¯fÙÕ(Œ69"€ € € € €@±S.@æ;hÊ §_c|sc±s< €– |9ÛÓóºÌi™g,Ÿ°@@@@@Š Ly Ö½£jèl<ÍóbwÊós÷~Ç €X/`Ì:cÌû²­½XŸ € € € € €(iäîqõ·öÝ;¶]½Ò(#ûBò… €€SftìŠN)É € € € € €@…Ê6r÷øê;Ro‹yêËJëÆÝŸçg@l0J¥Ÿ»Hn²¹Ï¦¸‰@@@@¨¦@Ùf@îžÄ@[úž‘á­/–¥ê®•‘›wŸ@»€üÛµIuunpÛáÃ>Zć € € € €a¨È ÈÝ“LÞ›<ÄŸå]ä)ïÃòü»¿ÆÏ €„J ¿Ï£R7nRþõÃ-}kCÁ € € € € €–T¼¹ÓáàŽƒçL÷fH–e}ŸtúâÏó@j ÈŒý?jeîܾ}ë­kN]³±ÚñÐ? € € € € `³@`ÈÝ‘V5½J{út)F¾Ck5÷×ø@ d¦ãjmÌwŒÒwg[z~DŸô € € € €DA *Èç`—«Xì¦Wê˜~ƒ<÷¥Õ«µÒ³ž{@(“ÀøžÄFÿLöv|ÐxæÁþ‡Ó«%Ê/Só4ƒ € € € € ð¬@u {ÃRUS—l:Ì‹{‡)åæ)}˜Ñêùä\côl¥ÍÚè¤PY»÷©sçÜçyÎs¾w™çžU g›µ`Á‚£•RK ~¸”2‚Oý?Í™úä’Z»v­ÌU"0R¹Ò« m'ʘÂõ‘Âçïp}¬£'±oWAÙYD  pM g ‘µ²ÿþû×E£Ñ‹ñý ü¨Î˦Wû'°ÂW‚±Mº=™L.ß°aÃî”   ê!`å+ê1ÇsRccã ¼ncð•ÓóÐ׌¾vô5¤¯¥|rL'  j$+“hJû2~< MÕ…eö”@“¾–ô5«ãÖ¸zš+‘ € ŒÀtðõ}ø{­}¦k•IàÚ¡k‹AXež?zM$@$à!½0ü@ÞÛ§xhŸ¦H`$S†®±‘iÜ'  ª#0€ õÓaÍWÕ]e/ðµìVvæÌH€HÀg2˜íˆ~:ßô™otg’Ðך¾æ&iñX,  (H € M5Á÷qQÀ#MCלGæh†H€H€*‹€Àôÿ•å6½ô5—y˜eaH€H€HÀÌ ÷hâ$«®°QØ”€¾æÐ!ÿhS;Ô'  J$` -/T‰¾Óç 'Àk¯ÂO Ý' (š€n:¼hm*’€^{fü¨M$@$P¡,4éEµ¹‘@Ù ðÚ+;rfH$@$àAøÁÌ''£ÚÜ@dx2”9ÚѬLÊ‘léäê&©K$@H@7AN«@¿éò$ €°é“ , ¸&Ài\#£ ˜`fÆÚ$@$@$@$àš0×Ȩ@$@$@$@f€™ñ£6 ¸&ÀÌ52* €`fü¨M$@$@$@® 0sŒ $@$@$@$`F€˜?j“ €k À\#£ ˜`fÆÚ$@$@$@$àš0×Ȩ@$@$@$@f€™ñ£6 ¸&ÀÌ52* €`fü¨M$@$@$@® 0sŒ $@$@$@$`F€˜?j“ €k À\#£ ˜`fÆÚ$@$@$@$àš0×Ȩ@$@$@$@f€™ñ£6 ¸&ÀÌ52* €`fü¨M$@$@$@® 0sŒ $@$@$@$`F€˜?j“ €k À\#£ ˜`fÆÚ$@$@$@$àš0×Ȩ@$@$@$@f€™ñ£6 ¸&ÀÌ52* € ™:µI€H ² DVGb*x›b_)Å,[Ȩ́P–Ü*„ýö·{æ§’ÏŠeH¯´m™°"GEÞlÛ¡7Xž)-”Ó–³PÞ™B©Ê¹S*µSÙ%Òé”­^H˜ÚUiŤ¿$PI€UÒÙ¢¯$@¦dC{ã±–e+•8BIy¯yøÞ¬ì—ì§@CöCøŒÙ¼]ÄÕ:©äÚ´ý“'týeXÑG;³™5kÊÔ™‹-¥ŽPB¼ þ"…œÈ´y 6|XÙöDa™-ûÄÏ‚RéhGóÓ¶¿ÀчS­Ïø¨xt…&¹`ÁÜŸÜÜX»víð£y¤Yޤál?KgÚÅIá‡Åw×¼R*[«2è›D 6üh*ìeýÝë˜þ•TÙcCòP”QÐÜcŒÒ×ZCÇTöXÖŽÎ~pç ÉõÇRÈ8P”òRª¥³ÞD¤îÜÕC3Àâ4ܼ±‘ÇŒö•ø•­ÒË{vt¯KEÚÈ–©r›¨ ËÆÅ– |¦ãâ©55™ÕÇEñG\6wö<•x5€Ùô‰úôâN¶tæ|ŽOT™ŠÉ7oÞ„[^‡Î:œÆãBïâ&VøŽ·„LšÒ©¨óÄç Œ>®å‘jM8¾Oç¬jŠÔÖYÏCm&þÍ6%¾˜lí¼ÑÌH™µï¡èþ±ßñA¦9ã\Ï0SŠÔ'dùìèÇfÎM?osÐâüÛ¥a±áüjnŽàÑ?&'üùÒÐÞtD@Z_÷ºñß±¬Ç!à9.23ö¢ŠÛô´vÇëz$8gÕœé¡Ú}®FÍÝ…09Ë#³{™Á¹<×èÝÑ#cW‰¸¸6ÙšX¹—¿LÜÊssÝ샷øÐM=ô1ò™€s™ñï]Ù—1GþoYÒ• ÇcWZ¸)Œ/tm¤#ò£TKê¯ã‹ùçh¤9ö9/‚/`:õTâæl%´JHOH€HÀÀ~mÑÆhGì»–%×—,øá#BስG:b+f>ÛwÄ¡Òíâm5 ÕÔîóO_×"£’_{@Ê7!êÿa4ÞüãèÃÑÙ{ã—ª ÐÓš¸µi¿2.¬®¡UÁ{`'"›,¥†xìÿà>×÷™é¶[ÉÓuM20S”Ô'ð‰Ç®®³ÃsýÔqêKâ3ò;cŸ |¾!Þøî’d0d4Ú{_´9ö<*2–Ö‚”2·¶¥8IL <‹ð°G™T)†»¸rXõ‰ô¹hú|Í•VaÔÄ-ˆ´7Ÿã¿’0ˆ% å}kL׫³µ~ ÀLiRŸH` "®š6Æ("¡þáúy¨Šã¡~+þ§aÂD –´~@ðxO Ž4²RÔ è¹=xCMÔ›F*û¾”aœæ'ÂñÆÖ²çÍ '”ÀæÖî ñr™'NHë]c퉭‰Îoº¦ç››W¿L-L,ÏÚa–%ÁO Š%0/}g`Ÿº§ø}À…€Óð¿&oþˆWþ„ׄ›Ñ×ì7ð.‡m_4ÛèrZ"°ª$Á¦Wàh§$ЇéëÂþ`jWòŒº@àNS;¥ÒŸ×ÖôF s0,VÛUßÀ™ðsøå”X©Îí’ ”…š0>”=4Ù»Ñ^xŽæ )ÕÂkšþÍÔœ®e²¡gðnjËsýL³Œ|˜Í‘ž“õ·A=Öî?­˜Æ£bq]Ÿéhú˜ ,C–u5êàtæÖKŒ,#°‘4¸O$à âú–¸Î;Ü;•Aèš×ÊeP€_ˆ ­êáûÅf‡Ú¥«¤°Ö œûk£Ôz¨Å˜ŽæÈ‡ê¬ß§ÔyѾ§†kcбšZ”ú¦%¾½ݱ:Ö3⾺ÆÃ±s½ăگŸ`ðÂwG—™Øh"üN$P¢íÍ×a”ããíÔ×Ï1' 5µòG£]}@Ô鑜(ßWü^F].›æÖÞ⪌®x©ÞėЪöwÓ‚ »~š°¾fjÇ+ýÙ«£˜Jí«¦öán²í=ŸÊeÇ×®\3H "½Y(!F:6-ÇT“Æý2 äãÙaPGG÷oºÊ©Á9ñæp4Òü+=’Ó©Ž/ä0Y¤-vŒ/|¡å!p¦ØNÛç¢ÒÛøžÇ}òI¿ô'¬ Yx¹3Œ!Úö¹½‹z7ç²Ã,¦‘ ø“€žûª£ùû¨ôºÈŸæ÷ ‘_3oÕ¼‚³û£ÏØáµR= KŒºÊïO)Ždjê,y¿@í])ìÓ¦? ô.ê~5X^LΪ‡—Ü…õY'l³&‰7þ®åLi#$½?µ°ë§ùì0ËG†é$@þ"°²q f¢^ý)þrÌ™7ºŸZ°¶nÜZ;ÌïõQ+(š¯¨3«þ“Âè›Ãáæëüç=C »Ù˜îv½²ãj4E&ÝkŽÖo¡Ð £SËõ½¡­« Xw˜æ‡àkCßžWõôy7`yÑð @Ñ<|°k殜;-:3ÐŽ·Ò–¢}ò‡âYáGÃoËå |ó{ýÁ×”\Ç+) Aò%û®Ü×|½ÀJ*t•ûºmé¶W0ÉèÞ`P—êš`ol¹³bY5˜ÜXÎq§µ·´n޵íô'·,Ù²cï#{c¶7~#ðýC^3sêcpëXŸ¹æÚ<Ø2Ú{æïÁ%…îÇ€‚›QK†Ø¥ò7”bú”ÓÏ«ü’°nèæ6,2m¼N¨¾O¬€u/¦z ºÉßTVOiƒ¼Í§ÃPòëºY¶?e-\!gxœH`,‚[ Où0~çØQµUÆ—ëbÉRôºŠSBâgÈÀümX©mˆoÖaØü:[¨ xGÝŠŽü[ÑÜð*쇗Ö9Xd}´äQÒw<>‹ž:b< NNmâJ±HìÙ¯m¿uÖ´# ùy³áämBYÖÁÚ:L Ù-l{ ʼÕVb·´Ôliæ Êk@sÑ{ ó>Ó·ýü^KhÞ–ÿ8LF{v_ª­;ÞxÚ)Þž»²G$Ê2²V¿èIKþ—é9Aú\ª'ᨠž˜)mêWdK§§µ ÑŽf£€ÅküqBôˆ(3Ì:øÚ'(~Ž€°èõáDÁÈ [ˆ•½­‰çÁÆÉ¹úoÍ0º&zˆ >‡b|ÔÓš))÷Ȧïn³×N ÚÀé ÓsÚ/ n~ _ÙéebÚû6äd´=z¤°‚_ÀþB‡zŽÄTî_¿¦qþÆÅÝë)P¨ìJñ²µiɦÑö¦Ë€=`Z Ô qîêÆ‡6ŸØm<ÍE!_¦Ìœ†)0Œ_ºúaãt‘¡…òÓÇÙé„eH€ÊN |Éâ‚/¼…®ÂÿûSë:Hµ&®Gðõ à$ø.grqòÐý˜H†JÆß ðbGZW!øz 뢃/”q¥úê˜Tkç[R-‰[]_ÙR¨äÂä:¼,Bÿ÷ Ã"æÞmÁ`À¼9Ç;wh©L’ »VàúÔÝL·ºššÀ=0bö6WÀ ½¸=&;>»€Xáö¸÷â3…%€9%E9 ÇðCî*ØÉeMcÅÔ|=10>Ó‡ðÿs± o†›ÄDßÀ±øAÉ;œÜm¨:¿)Q·zYyà]PïL¶$NKµt>™M7ùÄ"ÁkwŠôÑ(çZ;#uqp¡î‘@ªh?Ý'Ïõ´Ëƒ"›™‘ÞC¹Lè)/0øEyf›R¿M®ï¼Õ`nhQ–HÀ—ðmH§Ó‹P“s\)š¼°†Û®ÔöÄIhÎüñÄPQiµµ]ÜxB×_¼öe{k÷K);q<¼_{c[½?pF#ʼñƒVÊM`ã;_Dæ¨/T!ß,!o3YÎk<û2Ôƒ_O¦ð1õšH§Oà ß@aÙ×%€½Î‚{$@Hù{ú·ï:£ŽÚKêþR‘»Òx«½%Í'‡q”ÑÆÿmÉtâðÔ¢Äorˆx—„Á¶Ýw‚°¦F3}çBcLíP¿D<¨©ϳžõ]wà¥å·ãÉ8<6³¦ÖÒ}=ÝÐÿñ(ÔÒ^ljT)yjÊ_pk‡˜[b”'(L`ÔÈÊ î%t ¤k½ÐÔø©ÍK7 Neê7-ší?Ž7ÄÔΰ~›¨UVð>tu0Šƒðbôsôû**84Êx¸ Ü! 2@ðõ‡ÝöÀ%¯õÊQ¦Tk—îà¯;õ—c{b‡²ðªŸ—‡“OuÝ‹ÚMntr˪s§3µ KÀ³¨ÀuÕ7*K¨å³™5+ßq7éa«ù èÙÿ67:9d_Þm§ÏDzQ}^€å Ê$ cE=œä ä7í~ÏK‹’ÝNäK"#劒ØaAæýÉ ï×ý²F$—oýYðõ=ã ¥df ±² ¤ì®›pßþ¯q)¤ O:ývS;óâÑwJ©®6µƒ>™<‡‚¦Pÿuk×®ÅóŠ @É(õM4Å}öKà9ñ½`×ÿ„SnEóEÀ‰¼{û:Ô´Ýä^Ï[~;½"d.3±ŠÚ¢G{šäK]GÊsé¦óqºœáG‰›ž.¢!ý^okòñ¢Ì`vý Ü? ãõPOKÂè…5`EA*‘ ”›š1¾•lMè` wjÖ?«—€­lÛ¸æ(CPŠÏFÚ"‡º ©[ï…| 1¢ú>ÅÈŽ9PD°" Q…H L”ø³èë?5(Æ3Ú{í1šD½ À”z½^赞ØSf˜öa³¨M{â xK  ÓÅŒv¸wagšÙúNi›™>\ª•ÂQ?ÌH¼ùB4=.í›ïxéz±oÏ.ÝÕ“˜'i„H`/j«^î·í“ô,ô£lûâkz@$=qD©—ìtÿGÅRÑç‰=`ÅIórnëv5C¸ÇE 9¿¸5IÆMxº/WdzsÁ€(¼&ÜŒÅêo1ÁõdÈglY²e‡‰‘º†£Fšâþ‚ ¼øÑ©*9ZU§ÛUaÓJœºiQ×?])•SØÚƒ±yËZÚ¶Oí]ÜÓYN×Ýä%eå4|W?ÏÝ-nü£låÐý§"ñÆÏ0¯ ³Ô—æµ5=<Þ³BCw£Ælš))¾ÖÓÒõÿŒlŒR6¼«FYãW  47~émí\ãg˜ÖVË“7ቘLÖ ×~Ë“r_n|¦¬ÿ ¤Z»¿/ÛÌ=•SByw>;Ñö¦O"øú@¾ãÎÒÕ_RéÄœÉ:—bæœ%I€H`˜ÀÆÓ7¾ª›%†&éŽÜÓçI 9IñTx±0Ýïnª¯ÿ|ô«ò`1ù¦³Feîš¹ xüúèt—ßûÅÚ.Á2` À\ž Š“ Àë䫯ïOν d69Oí„— M‘ ¡ä5^8"•¼=p0V˜úm`ûŽHr½k+{Úþ£kE À@¢ €kúfíÚÛ"ÐÔ:ékÀR[R“¾ŒEž~ªy@ õTç¡&ùIcS´j‚S–gí4´ÅNÆPÉ“²ß‹ü|ªg{×WŠÔ-¨Æ¬ " ”„¦˜Qýç ×èsûòäH¼iÉŒxã~V@~ËÄ‚Â]}}éÓ1:¹dS¨039CÔ% ÉN Þ|@ÅdGT©åC0_ÔT÷œÐ£ê¾É Ž˜šâÎéÒº NÖÙSêÊÍ'vÿÝÈFe`ñ0 @ðúX„UH€ª”@²³óV,vñ¬yñeã°O6±ƒ¸ôg©…]wšØp¢ËÌ %Ê ”ŽÀy¢?­ìs&|d±RÛöôÙg•® ¯[fö: î‘ xG€5`Þ±¤%¨ ½­Ý¿EâYX€n=±Û|õ…`æEH€H€H`Òð`Âd¯™`n°ëôš‹^ÛubÁ×J4=þ_'²^È0ó‚"m Zûõ|³Îé(I€|G Õ’x µQ+ÊéXZÉs’'%·–3O`å¤Í¼H€H€H€ Ø)ìËÑ‘tcAAOÔݽ ;=X—Ò3 ÀÜñ¢4 Tþ˜+ªº —­´¾,³½µû%i«‹KOBý«ï•×./}>cs`6– SH€L ød‚GÓbPŸH`â$&~„¦ÈU¥ò¶m•§o^ºÙƒÁÝ{ÉÌ=3j ”@ߥ;Æ¿R¢¬¾šZ”øM‰l4ˬ " ¸' }Û´á¾,Ô ˜([–ta5xu•÷ù«gSÛ_ôÞ®s‹ Àœ³¢$ Lª2^”zZ÷úžW¢¯_¥OÃBÛ}žÙ,°" Q…H€H€H lT__ú\ä¶Û‹ÑEõÁM­É?yaËÄ0zÔ%ÈM€ó€åæÂT ¢l>±ûïè4ÿÝ¢”G)a¹£Î^Ý•\ö¯ ÀÊŽœ’ ¸!^nBžâFgÙ™µ!ëÎqŽ—å°²`f&$@$P¡¶q° =sNÜ®”Á2RB÷I)¦;)”Ô‚}0ÒÞôq'²¥’aV*²´KÕM RìÕ}–Xz¨”ÎGÀô>Ï]•Öч£³=·ëÐ 0‡ (F$@$@$P^ í û K~¥¹¢Fm®š\^ ÛNl2sB‰2$@îp&|w¼(M$‹€ ÈÚû¥Órô" AØÇÚ›yaË­ `n‰QžH€H€&¾î*‰7_(¤xo©Q¤ºkΪ9žõ/sê/0§¤(G$@$@$PõÆÞ ¤ºµ,™IÙX[7õ«eÉkD& ÀFÀà. €7ðbíë7koJI+$@%" ƒ!©›÷)‘ý1fñÀúTxMÓ¿9PÂ`%„KÓ$@$@$@îDÚcCãXwZfÒö¤”÷Š•SÌ,9×fæœ%I€H úôø»ŸPõOK컚êymMo–¸ÙÓR:6&ŒL—787dfê$@9ø¼so™D$0ñd0PÞ¦Ç1E–ò²pGì]cÒKÀ¬Pi’H€H€HÀp'OŸq)¥88ÚÜ|±€ÄÃ$@$@$0) jñE¹– +$­¨}šZ¬?Þ–§Z»¿/¤}C±6Fé]ÛÐ{û¨4O¿2ó'‘ T241U8žßô4=S¼+*µgÏ®/jý䆮ÛP ö§âm iJQkY‰àÐØV%3œ'?&“ T.ET g¹¢Ë8§aN]E`’8?wuãtn2*Ž—mY²eGÆÆy¢ß–êlÝ$idÊX)à¨ð‘±KLíäÓg– ÓI€H€&-`(Èl¢Ï®nz Y+ê=÷Þõ~–lM¬Y”ž–ÄïQ æÉÌö¾\ÿÓæFÚ÷jŸ˜W$i§j ˜¾iÍ|4¶oÕÂcÁI t´¶° ,=²é2ôû:º˜K ££Äž~e_˜K?Õ›øz¸ý-×17iz6þ@ºÛŽSY`NIQŽòÀ¤?Ï!Gɵ»É‘` ¡g¯?:÷V3ߺúvÿKÔzl7å%Нu1ÍÛ ýpGÓ^Ø™(‘öÈ[ÐÆgT¼üÞº¹µû9Ëp¦Ø-l4Ez°,žñÇG:šÎÊ™A"0xT%MoYFXȲb$I$à‚€ƒý}\¨Œ Þ<&­Bt0` ëº qw¬›ºc» =`Òôˆï?PË5îbÝ©E‰ß[|k¬îS¤°¾6'Þv¯™_ƒX~6û27Hµ$ƒÞýntòÉ*K.ŸoÜ/ßq7é ÀÜТ, ä €æ9’'¡)dÖ#³f9W¨IÎV'©¢]|ÑÔ{t¬nª´é¦vÊ¡‰7Zc¶÷yåȯTyDfÄ.‡í#Mì+e_¿õÄn×5 »^{årôë1É[ëâÔOÖ7Líh}`^P¤ª& „mtSã‡Àš:uúqU ‘…'7”ýw7âùd!ëü|Çü’^ß=µ^¿D³ãl¿øTŒáGÃoCÌš1Ã}ÏŽ®ÿ,&ÿ—?üòËJª ŠÑ£#åéáxcë˜t— À\£8 Œ%`ýulšËe}Ð¥ÅI j ôíO{Rx%N›»rî4Ol•Àˆîpƒé™%0¯'‹)O_M4=ZÁÐ TÕ[ŽÌtjà|±T=Ã}ª¥ë'˜ºb¯I[‹õGÊÀ]¦×°béS†Ø*ýS¨ÖþÄœUMS;>Ò/σÝGž´®<_¦i·,éJ!zpÝ 5: ÝŸ*4½îôÑé~øéh¾BX² µF¾ rŠÌŒ]‰9¿ÞíT>§œ÷%&×å<æ"1½kÏÅ涺PÉ)Šgv,4£nÜi0r*ŽHd6wI Ö€m€áͰ&TkéþÜH€œPr½±B2èpãìÕÑBre;¾²q ú|}?ð_…o²å[¢ŒÚboG?Ùe&æQkµe§°¯6±‘ÕÝxÒÆMJÈÏd¿}JyA¤-vL±6€KŽz$0D ubj‹óÌ`4ä§æ®™Û@°$@… ØBÅ K9r¿ºëAH"æ™ØM+‘™'Q3÷ñ‰õÄ£ÜuÓc@|ǤéqÐuÕöÖî—<òJô´t~òטÚCí$æg•÷‰¶âšV€™žê“À _˜‚ÐM ¡à”‡ÄJùþ*|“I¹‘@ ¤_[éäIòß#ñØUž˜*ÆÈ2Œ¶7_°äRŒ‰âtJ;__xFÓÕx®^œoƒZ8Ç¿Iµv­0±‘K÷5;ýi<¥¶ç:æ& !Ø["2¶ÌNV–X–?IÀ„€²uGYã «c¢3cË Ñ Lr›oîÅýâI3d•·„ÛcŸ+76=ÅD䍿§1'Áº+B¹ó/U~zŠ,—t½‰}_é[Tõü…î¥EÉnŒŠ¼ÒÄ¿a]K\i‹:üÝá0‡ (Fã°Uÿ/ÐO¡èÑ9{Û–ŸŠtÄnCMXÅöÿÀ[¼ç̽ñ h¶n:ôdC0'-KÞŒu﨑òÄè8FôbÔº¯n•§Ë[ë5ŽS^¿@HêQf¥’wl\Üý¬Wn¶ÓÓ’¸OªÇG§»ýŽk'(¬Ð}n¯`nISžrè]Ô»f-‘C}Lnè+£3š>oÕ¼ú1KY™moú¤ÛH \¡IpL ï•ÝßE3ÒÇ Ñ©çìèQÍkâÑ÷:w-¢¯h<ö ~³ŸÓ}½ô<€® *ô©Wrµèüækð {—QFJu÷oßµÌÈFaeÕoÛçâåyWaÑñ%p. ÏÇhO[±'ÞE%ê N«<-©Çë¦ü)Ò»D`d”§¶a¬¡½aÿp<ö™h¼ùq öb¹æ7¹®F÷Ú/Ú#§2ëüyX 6"ßùü¥¾7ÂkšŒ— ‹>ioº ÚѼNZ¡¿¢²í4“ŽhšÛ©Òö #ü-n·kAê%“Póõ…âz]ËVögÏïëi¥ØÛ´¨ëŸè²jì¯öÍ’òz`;õ³äÕ¬N¡ T:Þ‰ŸFf4£&LÌõª,hš¨Ç]}FF]«âMwˆGzNèù_×öu'ß#¢ïP 2­ÿ†ŸÍY;x[ÍìâGáhì˜Or‰_ˆ!“Ù,øI¥!П¾M„¬sp½=Ég^Çðd­'PcÕûº›Á/ÑÝàñÞõ½ ÔÛùôꮟ˜Rƒì­ùXKïÈ÷à å“w“޶ýø_ܳ¨ë÷èܨ–^V7=ß+³¦G!Ú{v?\z‡sH®ïú&j=—âÛ|£<3×`ð^ØÐA;NÓø°ñùð( 8'°Tô‰¸ºÛ¯8Wr&‰‡÷<<Ìo¡š›ÐoD/þý8ìJb ŒôVaË­¶´ðð³g(%§#¸š… êü`Ä÷7@'4fåÏÇuvG~ ©:ËðCâYãº÷ô0 L÷Ä·pí^î½õ!‹R†±w*^PN ÈZ92fƒÉ6ܘÐSmÁýµ÷ݾÙ÷ålÜ{#&O-t×9÷Í­/¤ÔÂ'$þå\«|’àòy°0­Eß=Я.*Ÿ×È Át:®ÎùŒiðˆkd®Ç S­ß*T`…ñ8 ¸ Þ´çÛúºËñÈçBÍ•(pz®°#°ú8hÍ@_}t&ìO`é™i†7< †÷ì@Z`ÜH ¢ìé›§+ë,\ü:*ù†ûJßn³qÿÍÆøfáë·š»{Ω³z:¹+½dãIIãYÜæéFn^{ãÁàrœ²¶¸i"ÌÞÖÄshrþ2|º!§_n¥ºýjª_ÆS|f'Ác$@Ž l<}㫨/¸Ñ±‚ߥlŒtDš3Ù’ø<ÊãMMU ÀD×DAϵ¦¦QÀ Ðü¹©S}Ì ö{Øøš©­Î!ßo*!`^P¦ E ¹8ù‚Rö9£’+å+kÀ*åLÑϽè&:;-N@â+{¨°/hŠÛl§í÷'v­ðµëhz öÑh„'Ê»½O‰Ïú¥¬©žÄõº9ÔØ¬3ª«ËÛŸ˜1a Üt“ÞÆ—å>êëÔCÐtª¯=¤så$àÛÚ—\z%žGÒɨ3š¸¦¬\Ž9MSâO*ÝwDÏâ®_;U)Z¨¢u¡iŽ]‡ï4±‘Ñ•êº-­=Æv¼2 ›{•:xŒø º#O·7~8—k ÀrQa xD Õ’øjÂò¾y”§fМ´ƒÖ»MŒbH¾.¨[ÍtS–-ÒK*­€îl¯úûîYÜÓé÷ó—YûP Ýÿt:]vŒÞÁ„¬oÌï$P‰ôÉÉÖÄXÀº?ü/ø  ëÓéôqX®æ¬-K¶TBé 2Ýô(ÕwL›µ1<¯xå„Ä6œ‹‚.¼: >‹ëF/g´ÛÁyµ7e0Ë’à' ”‹ú;`®™»6í9жÕçˆu—+ëBùàM¯ UîßFSéRRÿ,$?Îq×oÖãØâ!ð„@jQ×ÏRÛ;ÂüZX7R%=1êÂ~ÄÿjÛé$[:Ì4ºÐõƒhtFóõ¨‡©/x¾üZ·˜Ú)—¾17ë…^ä‡ãÅõmQ,Ð.„Q:/œ¡ ¨V™e‹„¸E¬·Ef6(…u.…¿ µedÒðx(´ãÁº €Ÿ)cÞÌŠÊO3­'E××Å]by4Öô eÉ Q3qx éGÀÇüdöîL<â×ç…Ê^Ót8Êq5X-t¼,Î/$ä·ã= »Žvİ҂<ÙÄ7½ŽhÀ ܇çþ¡í¹Cç°Ìb¢&F©Kn è¦TÁ³™k8Ý? P_ó~¡äb$ë~XoÅ ‹ •Í7<<ñ'1¢G­GM×ziYëS©Îgü¼Ä‰y©i ¨4ö†@¡‘x’ï2 R¯á¥æiÜo+Eÿ1²qKa/(Qm$Vÿ~ÛqÕVp–×”øU²µó½>ðÄ·.d&D­ ¢lu%eLHÃ=†·°¹ˆ§¦á]tš’¢?C!^Eú«è£±2zQðèdû"úZlÊ~1¥RÄ"±Ç·…¥c$àm¢6*£‡)8BXâpéÜgûã~ÚûS…”Sñ&Ä÷—Qy±MâÜ™•-Ÿ¿ÛôTò9±L ø¡8ôÁ¿‚xPÿÎqþu‘žMVèkô»ÉZ6¯Ê54'Г°§ÿ¹‘ ”š^R’"3*q]©³¢ýê&`á-ú'Õ€¥Ÿ8rÕÄåÍœI€H€H`âX©õ‰'Q ¶iâ\`ÎÕH@_súګƲ³Ì$@$@$`¡]DÔ˜)ò‰†JJÀ·g®½’fBã$@$@$àO™yÀR=‰ÿDÖíOéÕd# çšJõv.ŸlåbyH€H€HÀ)L¦‡¡§mq™S%Ê‘€ t¾¿”S˜¤. @¥ ÀPŠÞE‰‡0œöæJ/ý÷7[‰›ô„vþö’Þ‘ @i `н6LÌû&~\ºW*¿€Ðñþ©–Îÿ€)ìr#  ê%0\6„@a)’SXV½D©J®k¾|•Š.í’ TÑ5`Ãþ7´ÅNXâ˜õ·q8‘;$à’ÀÐâΗ²ÙÑ%8Š“ Ljy°L©u‘pìa—CpÞ¤&ÁÂyJ 3·¦šÈŒvÄ OÓ T8ñ°lá– +2?v4±Aá]øq`Mº-–ágõÀºh;±èl ×Fjpy!¹*3ɪžcŽ ÀÿF£ Õ6IJIEND®B`‚tmux-tmux-f222026/logo/tmux-logo-medium.png000066400000000000000000000124301511153563100206270ustar00rootroot00000000000000‰PNG  IHDR1PqºB#sRGB®ÎéÒIDATxí] pÅ™îž]=l#Ù€õØ]í ;¹„”“: c_$‡V~Æ>0y.Ã9!>H BÀ$Ž “çæ°/§ ‚‰¹"°´ yÀ¡‹ rGŽË= °+íXòÛ²-{µÚéûz±Äìj¦g_Ò®¤ª¤íéÿï¿ÿþfæ›~M7g©‡6wîÜåœó/ão&Dµøs¥ªŒÜY{{;ÈmÞ¼yb <¿ ä·_±×0Œí»wïþ%Îö²#IbÖ¬YÓÊËËÿzÙèŽxt‰XJ¹AfÁ_ ßþ’" B€( šÌõ ½Š`ÉXQÐÈ SØ…PÛƒšáô ÔI… †IbÚ™XÍ0ç5–Ì× F&k­É—ÀX*•…m$ûÀà4ÕÀ²¼r²F6gΜ«³LFê„!P`4<Œ_.°ÍqcNÓ´/›ÂRA E@’˜…¤#»@£$„@pÞœFAGnŒv¾¶Fåô’hSxp”9·bP*B`ô" ;¦‹6lôÂ6è9a7â @£kÅÁr%!@$V É !@"±âàN¹„@ +d† Šƒ‘Xqp§\ B @‰H2CÅA€H¬8¸S®„!P ˆÄ $™!â @$VÜ)WB€(Db’Ì„@q +î”+!@"±IfB 8‰wÊ•  „‘X€$3y#À§îœZU³£æ,X©¥…¸ÌÏû¼wbÞÞ“¢! ×£ƒYÖ3·w–ÿrìP°|õ×`¬‚³)œqÎ*ó†Øw€ÊÞÄ^­ñXïS—<ž“’¬Ê«'Ì\\Á™6¶ÎLLNæ‰äÙ‹üº±ß˜&žï;}ª%ß<óñ·èiw°r_•ÀòÅ*_b}‰í‡uFU:¹Ê|mþ¯ ¦MR¦ï‹ï S"4¼B§Å3ÍÝÛ&z–à‚x <øòÏ(ÎþË0~Mö¸ÐàBC¬†€Ên=‘÷ƒYϔ䃠“ÏÊñ`xª7 ¿µð)ecsÕ +æÊ§Qþ!¼´¼râ½ÞÖÆ;õãá{Ùr&÷Íø˜ºÓï-«àßÓ_‰W ïÁ´)aÎemlˆuô+å•“ö{['­Õ›Ã!^‰É A‹@^xYØË%*'–³>â~ õMUžeÚg!_¤ÒÉE†ë½ éþðjYZy9ú†~¯’e-“QdÉ!€‡œÀÝx>Ëá\%Î&â|"«ðW¸Éø›bú›,ã¡s–Ô•éð;¬÷‚g—¦¯:ð6ÖCß„¼S ~)¤Ÿû±¯ºñE¶MÖÕ2:¸/ä¿¥¢’¿ƒ<¿& ,£Tg”ððÔ‘­xA„ØŽ† Ù¤+ºýûcßÅËî}Uypß,ô¶ú¿¨ÒÉV65Ôèö?Q¥ƒ_ÇFìZ¶žÃz㪜 ÙøAÀÓ¸FsiíàÚæUjÎ.ózO±êÕˆ«C ç ö±‹q Ï‹€@ Wøª]-uש›5y¬4w¯è>É cµ£wšv¿ÄÜQ/C…r&ª|é*㦮æ®÷¥‘˜&åj2·i"Ûš]Îxó/ñV¶BnÙÒ¨fTqíuÈ›íldÏÙ¥îÚŠ‡³N7èó;~«Š"k­ULÛ¤ÒÉTæmó_-¯±Jþ<§;¶ è‰ A¿…F͹À&Ôdî.¸aί…í«ÒíúÚ Ñ÷µü6=]–÷9ç+¤ý¼íŒB'˜q :»•®o‹ÿ ¥Žƒ£ÄS™ÐT©¡y ѻެC$fFƒÂ9w`§@‡æj`ÛÐ|Tv §¤Éòã·™“Èþ/Üà;e? 9¾ aÁa[XYAmŽc=ÁÎÃÜÿè誦mÉ«Ù]Vö€c)7þ¡{Y÷~³/Dbf4(œ?rúDuà¨ý½ÊØRÞˆ­²©‚¿ŸáüWø‹¨Ò˜e¸Ù/L¾ùA˜žPàÙÿå88!Ä>&Ä®Á<{™Oà¼Áð_iöc¼„£Í‘§ÛNUyqMÎC³û.•ŽÌÛX„ôÊͨåuÓ›:pÍR9¤NG‘PNG0ù”Ó¹)ýH}sÛÑœ³|Ðq„/aÞÇã]ÁÈÛV~Õ‡f¹˜ëGèñºÌJž§ñïù&7Þ„¸ù)ñ¦c7ìQ!âëÍúÿ˜DƒAok`òÛœ;iàšv#D(ãø;úbâŒö^Š’O¶+=ð¾×p{W°ó5;ôø³wœ=™»PËU¨e‡O'äµrPMl$‘&X¦®mè‹õNÇ\´ïؘÌWÞüÑ=áÏc²Ûf'?@:ó cC`âcÆwݧ? †ï°#0™‡Þi×÷D>‹ZÚNyB>ûÜç|è9•ƒK:t\—o« &kÃ.®=šM³{Bu•E¶Ås…!מ¸Ç*o"1+T(®`à|ÏH—`4i]Æ3à1÷g_Od jn¯ääˆïÄ cv´©ãÉi™YÏú£áÈ*odJooÏEûtìÍÚfæóDB6ݲ:Ðìx¶·çøÌýÍoe•P*¯fñ„kÓ &k€ãõ},ÑAYÓUwx^ð|B¥!¿Yu¹„œ.c{àú¯¨^k«Á¨ékoo·œ¤*ÉŠˆ€÷£é˜×èdtAôß1­áwx_žQI gWsÇëøŽò7¨|Þ>O®|8íÓ É`ç;Àh=:â7Ú–H~âå.“% ]eC^æþ!®í´¡’Á˜8ä_e+ÙéÁ‹ÕÄ,@¡¨<ì™èžÈÍyZI&7 ñB&vðÆÞ F¾•‰®³2–š†lvzªÝÑs¦÷Dîæoª<Æ‹`®7Ôxƒ•ޝÕ7̦ž¶!Ø=Væ!m‰Y!Lq9#€æÜîhOâ†~­œ˜,“~±—u#²É,ßøfs™„EjŠƒ³sÒñ!ú ¹¿ }žý¾ÛÛæõ§è´° ÁÝÉA€”øÔ“W£=aÔÔœQÓœœ7o^AnPgH²Ó fn^ñøb¶\wê/IKdïgz…ê.,vòÔ±/°¥,fo%;I<ï¬(+·M„~ Û)¶‰Æ àLsÿŸP´Û튇&gH‘Ó'èø´Æuè­?à<ý5¼^ô‡®ÈtŦۦsBÀ}‘~ÐQ) …ƒ]TêX,~téÑ£*leŒ¢Úw\£¶IÏÍP£zäNàñ¿JŒ9_€•.’Y½-Þ qÍnUê3~+RmÓd€.† – 1,ŽW¼`5»âø?B¹¢Ó]0ã:9—K™#×î¯ÙUSÏ\eaÊ„mݵ°Ñ–ÕÇöDbJäIHNì v¼ Û¢ÒC³²¦Ü=a/šâ(ôŽÆâÆJÈÕ„˜f€H, :%ìˆ'¾ƒÊX§:¥ý¬|™NˆÄ7rYêšHL:I B ’Ÿqöõ TmTÄÓz°óI¡2šHL  B S¢M‘Ð|*SýA=¹ºÈÉDÎH$6ˆ$B _ŒDìFtòÊÆNÂ0VE—E³Jc¶O$fFƒÂ„!]ó» “?‹/'Ä–®ù­ùdJ$–z”– † €•.ž‘…††D½¿;vËè,#ˆÄ²ŒÔ B #¿yÑÕ¹j+?ž‘5…‘˜„@ö$÷dbƒSJ|;éÂçG2,i’‰©Ð!!@d@9[°IÌÙ™$”“_=”«Å:Ù!sBˆä„!1Ø´å«ø¬hQÆ  ˆ}I×ùvù>–M³.‘˜ „@ÎÈo#A(÷å` R¸]?C:T̲?ˆÄ²ÇŒR„€e® ›ÑŒÌiÁHÔÞ>ƒ½CW[˜uŒ"s„ˆBÀ O[ãWÐQ¿ÄN3ùÿì´ÒV8Ú˜ËNRDbv¨S¬tQ]Q®É³:ˆÄ²‚‹” #°UØâˆ›sWNÀìŠf$÷èMú÷ÅN:®t!¼mþ«³)‘X6hKK0«²–[_©ä$+"ÉZˆ}þS¦L©´—æ/©{ÁÿÉü­Ø[+¶¢*¹ÔNÍȈèë¿KÊ“+æf´Ò…ö@u¨!ã¾5"1;ôK(7Âw¦:ÈI\$p펩²ž8aòt•<YM¨á£.·öb>6Tiëž©«eP¥ƒ]ØoÂ’å½:É•.Û>pnõ R¬­bÚ&+™U‘˜*¥§\CÞpiÓJÏeòH"À™èq@â"yNâÚÿGʘ ûg²úœ dÈ=±ò4ÿT;?µèMϦ›2ŒØMX†Z½ç+<­þ¦ô´VçDbV¨”XnD%‰a²àçJÌerç ‚;Õ¢Åà.@…ͳË?ÓíÒþ€û¦±P6Óí$û­8[–o:?Ý–ûJ&Wºàü›&]Ë vtÛ\÷xÝ$K¡)’HÌFÉ…pØùE» ߟѵ,Í ø¶ƒ[ ‡ìËè@%–äÂÝü%Ù$Séå#K6#He5­»»Fþb§³¯)ü È”Kð€„ÏsÕU8î=I7¾Ê%/v«ÜÁÅ>ß3§1¹%–JdE@À¯©rM~-ÜJBP¥”mc•ÞÿAδ§ÐÄs¬½ ¦Ë!àžTñSømß+Ä»ØÌx£“iÑÿæŽPê ¶¦®Å7[¥C$¦B§Td c“+˜§ó`>ߟ9Ù'yn`ô×R‚ûÚüw8éÙÉë[.õyo¡ùµÆN§Pñ¾P`9zú®TÙ3˜±†ÍW¨Ëôèð`îØm*[ÀFsi®­l³Ý͘HL…`‰È¢'£Â(W·Ò¬ \®ßÔ‡f)õ¬„x‹OÝé÷Z‰ÆqZdù]Ç;ÞĺYagKÚ]ÞPàŸ§üjÊgÝ4¼¡Æ‹¼mçðÿÄòWVéœfÉ[¥±‹«o©¯LÝŒÄfkÏì v†ìl¤ÇË=&á㿥ǛÏAd3¼Uµæ8s˜HÌŒF©†—³ö³zØÉ=\l¿‹»Ú}mÍèg±Ý&vxÝ®†Oá[µ[ðà„ð?\^Áïu²Oò׎9_;i×ïúI'¿‡k²±>仌áå’’ãVVßêÿ´¯Õ3Èk/ºÞ“CStRNÄÿ@oN‰ÊãÄå*G3’ÕØ™<îØaŸ–ïçþë@~ʹÀæöúP`FZÚäi^‹‘Y¤¸áA ÑÛŒ!íµ³wšá]†Ça5ge«QõïÄ›ó?9Çp¶`–µ-/ªØIVϪ˜ pÉðxNVO0cëYBû>ÄL&%OÞ­.æ¾Õçmd"Ää ̲ŸëlÈ\Ñë&ÃVå_OcéÃu–La¥•y\}KMH~•2…`ô Þ¡Ô±ÊÙühNoÀ¢<ÉI±*²ÀåXAq+°.ÁZéTû‹’u/ëÞïÉÊIÎðæ ⸯù¿Cxþ,Åt 9J–b{tž †P˜£'Øy/‰œÖ’Çõª–× ä5u€Àœ¼ÂÈàÏõc‘¿•ù2‘8é¤ï$Ç!S5ÿ©J`ýYGr®ÍGßïø1^´o©ò€l¶gv`È´ "1ÔJIÝþ>nÐöáóÉ5wøloËÑ`ÇfÔ¦žNpo$ !¾¥#+ÙrùÑuòèÍ;Ï2÷C¨ùÕªì¢ÿl5‹«t”2¤5Æ*Y•ýõ­õç™uˆÄÌh”zx=ëÅ/â­W9‘Øð›´Ú{ìøõ¼:Y ùxˆ Ö„†î3ÛœçEbžÖ†e¨*?ÈF¿Û“]ÁèïÍùæÞ· c/ú~•ŸÉé#šV¾ÅlŸHÌŒÆ(ZÔMœ<ýi¼±^w‰Ä†Ô“G–9Õ׺FÁ<Âþ £Í‘ßä5ðkhZÎ$æ{Æw.&8<<`Ëêy÷Ľ95•­ìéÇÅ:Ù»V²8Ùèä_1pN$6€Ä(ú•ýcèøœ0ÄäˆP]¿€VÄ( šV¦V²ÓøújÔ¦¿ ñQ+•Lã@ Çñ2»Mï ÿ :Ç-;Ô==9“˜˜èÆäYV§ò÷ߺ t©t²’-ï<•` YcU.Æ6%¿€‘˜ª¢AoŽÜÎO&q#߉:§ 7!ž'ñ'tªþÐÆ%˜€xª„K=’®9 æã‹Ð›Â?9ÓqíäÌvåJé!ÍA\µ{X<>ý_ÑÿeÛtäÈ‘œH Ÿ/}ýOê¯@û}=‘‡ÒýË÷\6MQÆ­J;X¿Ì5±â©Ã1 ¯cäÊ£L@B„Ž·ªÏF8âÑÞVïÇwÕí9È܃±sð{F#åÐþ!Üøáä݈ßæêb†øcoìøkG—Í«F0âkbßEÏlÿiA¼Tf<ê„à5ø=×ì®ßQôw½+¸ØkþrW8üë¼:Ñ3ÀϳËÓhw•JÕ%]xé©W£PPÈd‹ Á]Ó*IQ÷ÂŽ·9&ÖíDÇÝb'e’Eo‹çð&\2TB1„!0RhhMl©ÌÆ\>\<9æÊD"FšþjÇÔ(Þe~Ý]TûßÐww 뼟¢’ FšœÂŸèãW¢í}`ø[.¢?i"†O0Ò>( çÈ B`œ!ì^~/ÑÇ.¦™óÕ—%bl¶ÄÌY›4B`¸HFÆê Þ‹±]’Æ¿„Ñ™¨qÔfú½Öp;Z,û ­@Úe†÷b4ïIÙü¦X±®åK Eàÿ’¯²ˆpIEND®B`‚tmux-tmux-f222026/logo/tmux-logo-small.png000066400000000000000000000052151511153563100204620ustar00rootroot00000000000000‰PNG  IHDR™(íÊXjsRGB®Îé GIDATxí\ lÅžÙ½;ÿ; ¶÷ì5RŠTŠZ©Â8$ü“¢@TÒ6)T)$@T¹¨¤-Ai‹ÂO)mDC ¶I¡T”Ò&ö]HÓ9 mQÔŸ;ûîì8jŒ“8öùv§ß¬½{³{kÇ1¾óÝiG²æ½7oþÞ~÷fvæ­)Ajnnþ„$Iƒl¢”VrÙlRww7åõV¬XÁfSŸ×aŒ #;¦ëú½===oͶ¯^þX@šØQ€kÕØ\M‰ ?º|ùòÏU»^;óg&ü=â^êI É> ¶)|ñc{ÈÁªÆ”9',¿#ñÖh…É{yî- Á[Ìz–íáæóز=÷bj_*¦ÉxsÉO x ËÏçRT£ò@VT3?'ã,?ŸKQŠ¿]z–”’$ÑÈ"¢“uȽ”S <È”P#¿!˜2á µ<¨4ž·¸UÀ½x2ªÊt0Z¼'ºˆl'ÆÑG0ÜhÝXàDÈÍNÁß/eår¡äBè24X†vN Ù®äØùžZsêŒÕŸHàxE¹¤á*I·bË(%*rè²cŒè»­}!QÝdž~Çb­‘R7½©dJ¸q×2—™å±–ˆqKÃy%¤þöZg–ñó_…ùeSѵ¡úkd*w£ÜŸÖaû ~¹ÄƒºÄù—ž ;E LI¨ )HÊ@/p×Ì”*]j«#há~Ô»O¨U‚þ$¥Ò¶@iÅ{5ë?æ¬YjX¬RO`Ï¡¬u®@^Š| ÚúŒDå.%¬îtÖË%ŸíªN‹}b|ONn§éÓâÎÅ•25æ&ìßc£#[ dÓO}K·¼æÓT¢;¨)w9è|>©«úåÅì]éjØ  —š2·ívà w»•åBvrÍÉÆè7í}Ñ+Tý¶]–É•J v‹óÃj‘J¥ôõÜ«{ Ë´W¦ä…ú²`sãËøU9³ÐMBƒRz/J(–·Çà½v@¶%×­Ö„ŒÞGøržR¼-ò4–ῈÝã²5x0È=¯kª ©› c[f)aÛV÷ãf8q×¶óB(î)ÌaÙ9…‡z)ççàZ‰*•Ò!4µÂh°s”‘Ýš®ÿ>%É'dM«–e©ý}obùåZFº=j¬µv’ç{Á׈Î~J$íí±¤tÆ/“«%YÞï'PE©k¼=N"»Ìz¹Î“ãÚæ@@~ýN€Û &ËO€ÿ´s,µ!õ£%Îeþ¯±žÞMÝ‚™9‘låø…ro?0Æþ ëÉ;úÛû…þ ÿ`¿‰< °iä °Q€ë¶x[ô·B=Nưß9”Ô¿£Ú²tûèyÙà}ïÕu©ß—$ºÃìpC]¸qC¢%²ß”xx™’çù¾Ö’aOÇhêV²oò“É[.MK\ ‡GÜŠ·¬5€Yµâ-ÑW`ìŸY“`$©§ô–X&À&4ÚÉ\ܦ:ÏÒ‰ü|ЉhôQŒ‹{3+a\?ZøÒÂ…¦ X%ý£½Êäy®º)ÞïeYñdfð¢ØQAÓ:Ù/ă:§MºÆvÉ2Ý"*1ʾ‘XÝûª(sÒº6þº$,1£d©ÅÌq'×BÚ&‰I=“Þœƒ¿¦¬¬òÁ!2tw°K½Û,?Ä=‰Öè‹¢ŒÓž'sZÄÁÃpZ¬-bó4‹í?=Îߪ,ˆøûÑ'EÞN &þ3yvgc©5ö“nº¹”õ·ö½`=.ö ~3Þ–¿@$Š·ÉtÂyßqíäØ=iIšÊŠ'û á×é¡MPåùmB˜Œbä3>w3f¹uÂÆMDVž‡Ó¦Ã'‡Ïßç¯*¿àRy=ëQúk[Ø€_?pÛÀ9›|’ñ<™›U<™eÁuƒgu\àìNÿN¼5Â_|\“2W³xBÑým‘N,‡ü¦"#a;ñJ¬µ÷±ŒAàL0†GNmmdô:þOÔÀN% ýdÖ]¯XnÒÈLKxù´ý2$°Î¾ eJ5ydô‚Q-Ȧ5­WhZ€|?Ʀ¿ÚäyÎ4|¥{'H.qOÈÜíâI Ôu5´R_D"Ù†»Ë¯Š'íÌi·Y æ—5¸Yû¹Mè`$BºpÿjW(N1jíÌÓjû´³ÊaÉYÊz_¾ËJÀmìåæŒø¡1Þ47!OŸ‰è¾ç”?*妞˜%Èf2,L²´2T¿Xà šo°) pO3ã !&Ž2&N_“Gºb³ßa×bÏàLlÑ©m‰¯$~Ÿ3èZ” c„ž ³€Ò›D¾i\9‰ãgÕZ‘Ÿ’æ¡ßתûqd¿hJ±àIâG¤ëq%€<ËØ·¸šwF˜-ªºwÔvªŸ›átQ‚ ñöoØ'JQÂJƒ]–g~3ô¨ç“IF¨·XnЂT*Õý-°0CO/o˜¨­mxѯ·öYgeçF†7ÃÓE…jD–ÈSX6+(S^œ #ä€9AžÎÌ‘øðÞ–\RËã¤wy€áü%¬ñ3ꜰ"$J¤­Ó-™5® *êØç„jÓ’J—ò¤mv%v¿eC7 !Nn6ié³2î)ý¾}žÓÔ-JñØ.LüMs’<Çžá DÆ×:ýø*çÕ`H=†<ð•'`„}%’Ü&ê眞¡'ÿä[“¯¦ä%ÜpSu¨±Ž—ñ˜¯Ú®úë1¿çe?Åÿx›ˆù‚ü¯mÃîhh’ÅoÒ·¿Ì«_E%“ú]/êÆ˜lQ*ôJxÎïšjE 2cr,µF_ 1Ö$|]DWu× ·–Pˆ~5 “ó|Æž,õl&PèRx´—J(‰ã›‚óåU§eIþ3÷^1“s9Š«¡&.ýVè2IDÄn6ì#”¡¿}È©kãâÄRã ³ö‹ªŒ•c?÷ªE 2n„þöè»ñ£Ñ«u][‹‡ò n0<›Ó S aüwñ‡¸}ýk4¥­µ.™X[ï^|Ìòy Ñz¨®ÃåÚº¾aã ó¾@ ”HB…/“<&ÎøƒíîÂW÷<^lÚ”XˆÀ¶wBɪ‹-JMEYåNŠo ßS9m óT ãL¦jžºÏûn«¨‹*ü„?Xì'é•ÞB¸·¥ì_ø.þE’ÒöÆoŒ8&â|‹Ê#:ªÍžÈÔCpm«fßDöjÂãÂ&¾%{=x-çÂÑSü#Ôñ\tvQ} ¤K›— ÜR¼=þÓÆ›°7ÁkæÛX®çÇÇ`ŒE¿6¶:öv®û÷ú›{ ü?q4»Ó—½IEND®B`‚tmux-tmux-f222026/logo/tmux-logo.eps000066400000000000000000000526051511153563100173640ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %APL_DSC_Encoding: UTF8 %APLProducer: (Version 10.10.3 (Build 14D136) Quartz PS Context) %%Title: (Unknown) %%Creator: (Unknown) %%CreationDate: (Unknown) %%For: (Unknown) %%DocumentData: Clean7Bit %%LanguageLevel: 2 %%Pages: 1 %%BoundingBox: 0 0 608 160 %%EndComments %%BeginProlog %%BeginFile: cg-pdf.ps %%Copyright: Copyright 2000-2004 Apple Computer Incorporated. %%Copyright: All Rights Reserved. currentpacking true setpacking /cg_md 141 dict def cg_md begin /L3? languagelevel 3 ge def /bd{bind def}bind def /ld{load def}bd /xs{exch store}bd /xd{exch def}bd /cmmtx matrix def mark /sc/setcolor /scs/setcolorspace /dr/defineresource /fr/findresource /T/true /F/false /d/setdash /w/setlinewidth /J/setlinecap /j/setlinejoin /M/setmiterlimit /i/setflat /rc/rectclip /rf/rectfill /rs/rectstroke /f/fill /f*/eofill /sf/selectfont /s/show /xS/xshow /yS/yshow /xyS/xyshow /S/stroke /m/moveto /l/lineto /c/curveto /h/closepath /n/newpath /q/gsave /Q/grestore counttomark 2 idiv {ld}repeat pop /SC{ /ColorSpace fr scs }bd /sopr /setoverprint where{pop/setoverprint}{/pop}ifelse ld /soprm /setoverprintmode where{pop/setoverprintmode}{/pop}ifelse ld /cgmtx matrix def /sdmtx{cgmtx currentmatrix pop}bd /CM {cgmtx setmatrix}bd /cm {cmmtx astore CM concat}bd /W{clip newpath}bd /W*{eoclip newpath}bd statusdict begin product end dup (HP) anchorsearch{ pop pop pop true }{ pop (hp) anchorsearch{ pop pop true }{ pop false }ifelse }ifelse { { { pop pop (0)dup 0 4 -1 roll put F charpath }cshow } }{ {F charpath} }ifelse /cply exch bd /cps {cply stroke}bd /pgsave 0 def /bp{/pgsave save store}bd /ep{pgsave restore showpage}def /re{4 2 roll m 1 index 0 rlineto 0 exch rlineto neg 0 rlineto h}bd /scrdict 10 dict def /scrmtx matrix def /patarray 0 def /createpat{patarray 3 1 roll put}bd /makepat{ scrmtx astore pop gsave initgraphics CM patarray exch get scrmtx makepattern grestore setpattern }bd /cg_BeginEPSF{ userdict save/cg_b4_Inc_state exch put userdict/cg_endepsf/cg_EndEPSF load put count userdict/cg_op_count 3 -1 roll put countdictstack dup array dictstack userdict/cg_dict_array 3 -1 roll put 3 sub{end}repeat /showpage {} def 0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit [] 0 setdash newpath false setstrokeadjust false setoverprint }bd /cg_EndEPSF{ countdictstack 3 sub { end } repeat cg_dict_array 3 1 index length 3 sub getinterval {begin}forall count userdict/cg_op_count get sub{pop}repeat userdict/cg_b4_Inc_state get restore F setpacking }bd /cg_biproc{currentfile/RunLengthDecode filter}bd /cg_aiproc{currentfile/ASCII85Decode filter/RunLengthDecode filter}bd /ImageDataSource 0 def L3?{ /cg_mibiproc{pop pop/ImageDataSource{cg_biproc}def}bd /cg_miaiproc{pop pop/ImageDataSource{cg_aiproc}def}bd }{ /ImageBandMask 0 def /ImageBandData 0 def /cg_mibiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd /cg_miaiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/ASCII85Decode filter/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd }ifelse /imsave 0 def /BI{save/imsave xd mark}bd /EI{imsave restore}bd /ID{ counttomark 2 idiv dup 2 add dict begin {def} repeat pop /ImageType 1 def /ImageMatrix[Width 0 0 Height neg 0 Height]def currentdict dup/ImageMask known{ImageMask}{F}ifelse exch L3?{ dup/MaskedImage known { pop << /ImageType 3 /InterleaveType 2 /DataDict currentdict /MaskDict << /ImageType 1 /Width Width /Height Height /ImageMatrix ImageMatrix /BitsPerComponent 1 /Decode [0 1] currentdict/Interpolate known {/Interpolate Interpolate}if >> >> }if }if exch {imagemask}{image}ifelse end }bd /cguidfix{statusdict begin mark version end {cvr}stopped{cleartomark 0}{exch pop}ifelse 2012 lt{dup findfont dup length dict begin {1 index/FID ne 2 index/UniqueID ne and {def} {pop pop} ifelse}forall currentdict end definefont pop }{pop}ifelse }bd /t_array 0 def /t_i 0 def /t_c 1 string def /x_proc{ exch t_array t_i get add exch moveto /t_i t_i 1 add store }bd /y_proc{ t_array t_i get add moveto /t_i t_i 1 add store }bd /xy_proc{ t_array t_i 2 copy 1 add get 3 1 roll get 4 -1 roll add 3 1 roll add moveto /t_i t_i 2 add store }bd /sop 0 def /cp_proc/x_proc ld /base_charpath { /t_array xs /t_i 0 def { t_c 0 3 -1 roll put currentpoint t_c cply sop cp_proc }forall /t_array 0 def }bd /sop/stroke ld /nop{}def /xsp/base_charpath ld /ysp{/cp_proc/y_proc ld base_charpath/cp_proc/x_proc ld}bd /xysp{/cp_proc/xy_proc ld base_charpath/cp_proc/x_proc ld}bd /xmp{/sop/nop ld /cp_proc/x_proc ld base_charpath/sop/stroke ld}bd /ymp{/sop/nop ld /cp_proc/y_proc ld base_charpath/sop/stroke ld}bd /xymp{/sop/nop ld /cp_proc/xy_proc ld base_charpath/sop/stroke ld}bd /refnt{ findfont dup length dict copy dup /Encoding 4 -1 roll put definefont pop }bd /renmfont{ findfont dup length dict copy definefont pop }bd L3? dup dup{save exch}if /Range 0 def /DataSource 0 def /val 0 def /nRange 0 def /mulRange 0 def /d0 0 def /r0 0 def /di 0 def /ri 0 def /a0 0 def /a1 0 def /r1 0 def /r2 0 def /dx 0 def /Nsteps 0 def /sh3tp 0 def /ymax 0 def /ymin 0 def /xmax 0 def /xmin 0 def /setupFunEval { begin /nRange Range length 2 idiv store /mulRange [ 0 1 nRange 1 sub { 2 mul/nDim2 xd Range nDim2 get Range nDim2 1 add get 1 index sub 255 div exch }for ]store end }bd /FunEval { begin nRange mul /val xd 0 1 nRange 1 sub { dup 2 mul/nDim2 xd val add DataSource exch get mulRange nDim2 get mul mulRange nDim2 1 add get add }for end }bd /max { 2 copy lt {exch pop}{pop}ifelse }bd /sh2 { /Coords load aload pop 3 index 3 index translate 3 -1 roll sub 3 1 roll exch sub 2 copy dup mul exch dup mul add sqrt dup scale atan rotate /Function load setupFunEval clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs currentdict/Extend known { /Extend load 0 get { 0/Function load FunEval sc xmin ymin xmin abs ymax ymin sub rectfill }if }if /Nsteps/Function load/Size get 0 get 1 sub store /dx 1 Nsteps div store gsave /di ymax ymin sub store /Function load 0 1 Nsteps { 1 index FunEval sc 0 ymin dx di rectfill dx 0 translate }for pop grestore currentdict/Extend known { /Extend load 1 get { Nsteps/Function load FunEval sc 1 ymin xmax 1 sub abs ymax ymin sub rectfill }if }if }bd /shp { 4 copy dup 0 gt{ 0 exch a1 a0 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a0 a1 arcn }{ pop 0 lineto }ifelse fill dup 0 gt{ 0 exch a0 a1 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a1 a0 arcn }{ pop 0 lineto }ifelse fill }bd /calcmaxs { xmin dup mul ymin dup mul add sqrt xmax dup mul ymin dup mul add sqrt xmin dup mul ymax dup mul add sqrt xmax dup mul ymax dup mul add sqrt max max max }bd /sh3 { /Coords load aload pop 5 index 5 index translate 3 -1 roll 6 -1 roll sub 3 -1 roll 5 -1 roll sub 2 copy dup mul exch dup mul add sqrt /dx xs 2 copy 0 ne exch 0 ne or { exch atan rotate }{ pop pop }ifelse /r2 xs /r1 xs /Function load dup/Size get 0 get 1 sub /Nsteps xs setupFunEval dx r2 add r1 lt{ 0 }{ dx r1 add r2 le { 1 }{ r1 r2 eq { 2 }{ 3 }ifelse }ifelse }ifelse /sh3tp xs clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs dx dup mul r2 r1 sub dup mul sub dup 0 gt { sqrt r2 r1 sub atan /a0 exch 180 exch sub store /a1 a0 neg store }{ pop /a0 0 store /a1 360 store }ifelse currentdict/Extend known { /Extend load 0 get r1 0 gt and { 0/Function load FunEval sc { { dx 0 r1 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { r1 0 gt{0 0 r1 0 360 arc fill}if } { 0 r1 xmin abs r1 add neg r1 shp } { r2 r1 gt{ 0 r1 r1 neg r2 r1 sub div dx mul 0 shp }{ 0 r1 calcmaxs dup r2 add dx mul dx r1 r2 sub sub div neg exch 1 index abs exch sub shp }ifelse } }sh3tp get exec }if }if /d0 0 store /r0 r1 store /di dx Nsteps div store /ri r2 r1 sub Nsteps div store /Function load 0 1 Nsteps { 1 index FunEval sc d0 di add r0 ri add d0 r0 shp { d0 0 r0 a1 a0 arc d0 di add 0 r0 ri add a0 a1 arcn fill d0 0 r0 a0 a1 arc d0 di add 0 r0 ri add a1 a0 arcn fill }pop /d0 d0 di add store /r0 r0 ri add store }for pop currentdict/Extend known { /Extend load 1 get r2 0 gt and { Nsteps/Function load FunEval sc { { dx 0 r2 0 360 arc fill } { dx 0 r2 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { xmax abs r1 add r1 dx r1 shp } { r2 r1 gt{ calcmaxs dup r1 add dx mul dx r2 r1 sub sub div exch 1 index exch sub dx r2 shp }{ r1 neg r2 r1 sub div dx mul 0 dx r2 shp }ifelse } } sh3tp get exec }if }if }bd /sh { begin /ShadingType load dup dup 2 eq exch 3 eq or { gsave newpath /ColorSpace load scs currentdict/BBox known { /BBox load aload pop 2 index sub 3 index 3 -1 roll exch sub exch rectclip }if 2 eq {sh2}{sh3}ifelse grestore }{ pop (DEBUG: shading type unimplemented\n)print flush }ifelse end }bd {restore}if not dup{save exch}if L3?{ /sh/shfill ld /csq/clipsave ld /csQ/cliprestore ld }if {restore}if end setpacking %%EndFile %%EndProlog %%BeginSetup %%EndSetup %%Page: 1 1 %%PageBoundingBox: 0 0 608 160 %%BeginPageSetup cg_md begin bp sdmtx [ /CIEBasedABC 4 dict dup begin /WhitePoint [ 0.9505 1.0000 1.0891 ] def /DecodeABC [ { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind ] def /MatrixABC [ 0.4124 0.2126 0.0193 0.3576 0.7151 0.1192 0.1805 0.0722 0.9508 ] def /RangeLMN [ 0.0 0.9505 0.0 1.0000 0.0 1.0891 ] def end ] /Cs1 exch/ColorSpace dr pop %%EndPageSetup 0.60000002 i /Cs1 SC 0.10588235 0.72549021 0.12156863 sc q 0 44 m 160 44 l 160 15.003872 l 160 6.7174625 153.27803 0 145.00154 0 c 14.998466 0 l 6.7150416 0 0 6.7065849 0 15.003872 c 0 44 l h 0 44 m 160 44 l 160 14 l 0 14 l 0 44 l h 0 44 m W* 0 0 608 160 rc -5 49 m 165 49 l 165 -5 l -5 -5 l h f Q 0.23529412 0.23529412 0.23529412 sc q 83 90 m 83 160 l 77 160 l 77 14 l 83 14 l 83 84 l 160 84 l 160 90 l 83 90 l h 0 144.99352 m 0 153.28137 6.7219648 160 14.998466 160 c 145.00154 160 l 153.28496 160 160 153.27509 160 144.99352 c 160 14 l 0 14 l 0 144.99352 l h 0 144.99352 m W* 0 0 608 160 rc -5 165 m 165 165 l 165 9 l -5 9 l h f Q 0.10588235 0.72549021 0.12156863 sc q 241.77284 12.165432 m 230.85426 12.165432 222.63789 15.032893 217.12346 20.7679 c 211.60902 26.502909 208.85185 34.995007 208.85185 46.244446 c 208.85185 98.024689 l 189 98.024689 l 189 112.91358 l 208.85185 112.91358 l 208.85185 146 l 225.39507 146 l 225.39507 112.91358 l 261.79013 112.91358 l 261.79013 98.024689 l 225.39507 98.024689 l 225.39507 46.079014 l 225.39507 39.571983 226.99422 34.802074 230.1926 31.769136 c 233.39096 28.736198 237.58186 27.219753 242.76543 27.219753 c 245.19179 27.219753 247.89381 27.49547 250.87161 28.046913 c 253.8494 28.598356 256.88229 29.425508 259.97037 30.528395 c 263.27902 15.97037 l 259.52921 14.646907 255.86215 13.681896 252.27777 13.075309 c 248.69341 12.468721 245.19179 12.165432 241.77284 12.165432 c 241.77284 12.165432 l h 294.70239 13.654321 m 278.15918 13.654321 l 278.15918 112.91358 l 294.70239 112.91358 l 294.70239 96.370369 l 294.70239 96.370369 304.76617 106.29628 309.67401 109.60493 c 314.58185 112.9136 320.34436 114.5679 326.96167 114.5679 c 332.80695 114.5679 337.79745 112.94117 341.93326 109.68765 c 346.06909 106.43414 349.01926 101.99509 350.78387 96.370369 c 350.78387 96.370369 361.92294 106.29628 366.99622 109.60493 c 372.06952 112.9136 378.08014 114.5679 385.02832 114.5679 c 392.74854 114.5679 398.92459 111.72801 403.55673 106.04815 c 408.18884 100.36829 410.50488 92.730911 410.50488 83.135803 c 410.50488 13.654321 l 393.96167 13.654321 l 393.96167 80.488892 l 393.96167 87.106209 392.69336 91.820976 390.15674 94.633331 c 387.62009 97.445694 383.98062 98.851852 379.23822 98.851852 c 374.49579 98.851852 369.78104 97.418121 365.09375 94.550621 c 360.40649 91.683113 356.24316 87.878212 352.60364 83.135803 c 352.60364 13.654321 l 336.06042 13.654321 l 336.06042 80.488892 l 336.06042 87.106209 334.79211 91.820976 332.25549 94.633331 c 329.71884 97.445694 326.07938 98.851852 321.33698 98.851852 c 316.59457 98.851852 311.87979 97.418121 307.19254 94.550621 c 302.50525 91.683113 298.34192 87.878212 294.70239 83.135803 c 294.70239 13.654321 l h 438.9505 21.264198 m 433.10519 27.440361 430.18259 35.656738 430.18259 45.913582 c 430.18259 112.91358 l 446.7258 112.91358 l 446.7258 49.222221 l 446.7258 42.16375 448.3801 36.814835 451.68875 33.175308 c 454.99741 29.535784 459.85004 27.716049 466.2468 27.716049 c 471.32007 27.716049 476.36569 29.177351 481.38382 32.099998 c 486.40195 35.022648 491.39243 39.35141 496.35544 45.086418 c 496.35544 112.91358 l 512.89862 112.91358 l 512.89862 13.654321 l 496.35544 13.654321 l 496.35544 30.197531 l 496.35544 30.197531 485.13364 20.271622 479.89493 16.962963 c 474.65622 13.654305 468.78345 12 462.2764 12 c 452.57101 12 444.79578 15.088035 438.9505 21.264198 c h 588.65784 13.654321 m 564.50476 51.041977 l 541.84052 13.654321 l 523.4776 13.654321 l 556.56403 63.449383 l 524.63562 112.91358 l 543.164 112.91358 l 566.15906 77.180244 l 589.48499 112.91358 l 607.84796 112.91358 l 574.43066 64.441978 l 607.18622 13.654321 l 588.65784 13.654321 l h 588.65784 13.654321 m W* 0 0 608 160 rc 184 151 m 612.84796 151 l 612.84796 7 l 184 7 l h f ep end %%Trailer %%EOF tmux-tmux-f222026/logo/tmux-logo.svg000066400000000000000000000107201511153563100173640ustar00rootroot00000000000000 logomark + wordmark Created with Sketch. tmux-tmux-f222026/logo/tmux-logomark.eps000066400000000000000000000445411511153563100202370ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %APL_DSC_Encoding: UTF8 %APLProducer: (Version 10.10.3 (Build 14D136) Quartz PS Context) %%Title: (Unknown) %%Creator: (Unknown) %%CreationDate: (Unknown) %%For: (Unknown) %%DocumentData: Clean7Bit %%LanguageLevel: 2 %%Pages: 1 %%BoundingBox: 0 0 160 160 %%EndComments %%BeginProlog %%BeginFile: cg-pdf.ps %%Copyright: Copyright 2000-2004 Apple Computer Incorporated. %%Copyright: All Rights Reserved. currentpacking true setpacking /cg_md 141 dict def cg_md begin /L3? languagelevel 3 ge def /bd{bind def}bind def /ld{load def}bd /xs{exch store}bd /xd{exch def}bd /cmmtx matrix def mark /sc/setcolor /scs/setcolorspace /dr/defineresource /fr/findresource /T/true /F/false /d/setdash /w/setlinewidth /J/setlinecap /j/setlinejoin /M/setmiterlimit /i/setflat /rc/rectclip /rf/rectfill /rs/rectstroke /f/fill /f*/eofill /sf/selectfont /s/show /xS/xshow /yS/yshow /xyS/xyshow /S/stroke /m/moveto /l/lineto /c/curveto /h/closepath /n/newpath /q/gsave /Q/grestore counttomark 2 idiv {ld}repeat pop /SC{ /ColorSpace fr scs }bd /sopr /setoverprint where{pop/setoverprint}{/pop}ifelse ld /soprm /setoverprintmode where{pop/setoverprintmode}{/pop}ifelse ld /cgmtx matrix def /sdmtx{cgmtx currentmatrix pop}bd /CM {cgmtx setmatrix}bd /cm {cmmtx astore CM concat}bd /W{clip newpath}bd /W*{eoclip newpath}bd statusdict begin product end dup (HP) anchorsearch{ pop pop pop true }{ pop (hp) anchorsearch{ pop pop true }{ pop false }ifelse }ifelse { { { pop pop (0)dup 0 4 -1 roll put F charpath }cshow } }{ {F charpath} }ifelse /cply exch bd /cps {cply stroke}bd /pgsave 0 def /bp{/pgsave save store}bd /ep{pgsave restore showpage}def /re{4 2 roll m 1 index 0 rlineto 0 exch rlineto neg 0 rlineto h}bd /scrdict 10 dict def /scrmtx matrix def /patarray 0 def /createpat{patarray 3 1 roll put}bd /makepat{ scrmtx astore pop gsave initgraphics CM patarray exch get scrmtx makepattern grestore setpattern }bd /cg_BeginEPSF{ userdict save/cg_b4_Inc_state exch put userdict/cg_endepsf/cg_EndEPSF load put count userdict/cg_op_count 3 -1 roll put countdictstack dup array dictstack userdict/cg_dict_array 3 -1 roll put 3 sub{end}repeat /showpage {} def 0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit [] 0 setdash newpath false setstrokeadjust false setoverprint }bd /cg_EndEPSF{ countdictstack 3 sub { end } repeat cg_dict_array 3 1 index length 3 sub getinterval {begin}forall count userdict/cg_op_count get sub{pop}repeat userdict/cg_b4_Inc_state get restore F setpacking }bd /cg_biproc{currentfile/RunLengthDecode filter}bd /cg_aiproc{currentfile/ASCII85Decode filter/RunLengthDecode filter}bd /ImageDataSource 0 def L3?{ /cg_mibiproc{pop pop/ImageDataSource{cg_biproc}def}bd /cg_miaiproc{pop pop/ImageDataSource{cg_aiproc}def}bd }{ /ImageBandMask 0 def /ImageBandData 0 def /cg_mibiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd /cg_miaiproc{ string/ImageBandMask xs string/ImageBandData xs /ImageDataSource{[currentfile/ASCII85Decode filter/RunLengthDecode filter dup ImageBandMask/readstring cvx /pop cvx dup ImageBandData/readstring cvx/pop cvx]cvx bind}bd }bd }ifelse /imsave 0 def /BI{save/imsave xd mark}bd /EI{imsave restore}bd /ID{ counttomark 2 idiv dup 2 add dict begin {def} repeat pop /ImageType 1 def /ImageMatrix[Width 0 0 Height neg 0 Height]def currentdict dup/ImageMask known{ImageMask}{F}ifelse exch L3?{ dup/MaskedImage known { pop << /ImageType 3 /InterleaveType 2 /DataDict currentdict /MaskDict << /ImageType 1 /Width Width /Height Height /ImageMatrix ImageMatrix /BitsPerComponent 1 /Decode [0 1] currentdict/Interpolate known {/Interpolate Interpolate}if >> >> }if }if exch {imagemask}{image}ifelse end }bd /cguidfix{statusdict begin mark version end {cvr}stopped{cleartomark 0}{exch pop}ifelse 2012 lt{dup findfont dup length dict begin {1 index/FID ne 2 index/UniqueID ne and {def} {pop pop} ifelse}forall currentdict end definefont pop }{pop}ifelse }bd /t_array 0 def /t_i 0 def /t_c 1 string def /x_proc{ exch t_array t_i get add exch moveto /t_i t_i 1 add store }bd /y_proc{ t_array t_i get add moveto /t_i t_i 1 add store }bd /xy_proc{ t_array t_i 2 copy 1 add get 3 1 roll get 4 -1 roll add 3 1 roll add moveto /t_i t_i 2 add store }bd /sop 0 def /cp_proc/x_proc ld /base_charpath { /t_array xs /t_i 0 def { t_c 0 3 -1 roll put currentpoint t_c cply sop cp_proc }forall /t_array 0 def }bd /sop/stroke ld /nop{}def /xsp/base_charpath ld /ysp{/cp_proc/y_proc ld base_charpath/cp_proc/x_proc ld}bd /xysp{/cp_proc/xy_proc ld base_charpath/cp_proc/x_proc ld}bd /xmp{/sop/nop ld /cp_proc/x_proc ld base_charpath/sop/stroke ld}bd /ymp{/sop/nop ld /cp_proc/y_proc ld base_charpath/sop/stroke ld}bd /xymp{/sop/nop ld /cp_proc/xy_proc ld base_charpath/sop/stroke ld}bd /refnt{ findfont dup length dict copy dup /Encoding 4 -1 roll put definefont pop }bd /renmfont{ findfont dup length dict copy definefont pop }bd L3? dup dup{save exch}if /Range 0 def /DataSource 0 def /val 0 def /nRange 0 def /mulRange 0 def /d0 0 def /r0 0 def /di 0 def /ri 0 def /a0 0 def /a1 0 def /r1 0 def /r2 0 def /dx 0 def /Nsteps 0 def /sh3tp 0 def /ymax 0 def /ymin 0 def /xmax 0 def /xmin 0 def /setupFunEval { begin /nRange Range length 2 idiv store /mulRange [ 0 1 nRange 1 sub { 2 mul/nDim2 xd Range nDim2 get Range nDim2 1 add get 1 index sub 255 div exch }for ]store end }bd /FunEval { begin nRange mul /val xd 0 1 nRange 1 sub { dup 2 mul/nDim2 xd val add DataSource exch get mulRange nDim2 get mul mulRange nDim2 1 add get add }for end }bd /max { 2 copy lt {exch pop}{pop}ifelse }bd /sh2 { /Coords load aload pop 3 index 3 index translate 3 -1 roll sub 3 1 roll exch sub 2 copy dup mul exch dup mul add sqrt dup scale atan rotate /Function load setupFunEval clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs currentdict/Extend known { /Extend load 0 get { 0/Function load FunEval sc xmin ymin xmin abs ymax ymin sub rectfill }if }if /Nsteps/Function load/Size get 0 get 1 sub store /dx 1 Nsteps div store gsave /di ymax ymin sub store /Function load 0 1 Nsteps { 1 index FunEval sc 0 ymin dx di rectfill dx 0 translate }for pop grestore currentdict/Extend known { /Extend load 1 get { Nsteps/Function load FunEval sc 1 ymin xmax 1 sub abs ymax ymin sub rectfill }if }if }bd /shp { 4 copy dup 0 gt{ 0 exch a1 a0 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a0 a1 arcn }{ pop 0 lineto }ifelse fill dup 0 gt{ 0 exch a0 a1 arc }{ pop 0 moveto }ifelse dup 0 gt{ 0 exch a1 a0 arcn }{ pop 0 lineto }ifelse fill }bd /calcmaxs { xmin dup mul ymin dup mul add sqrt xmax dup mul ymin dup mul add sqrt xmin dup mul ymax dup mul add sqrt xmax dup mul ymax dup mul add sqrt max max max }bd /sh3 { /Coords load aload pop 5 index 5 index translate 3 -1 roll 6 -1 roll sub 3 -1 roll 5 -1 roll sub 2 copy dup mul exch dup mul add sqrt /dx xs 2 copy 0 ne exch 0 ne or { exch atan rotate }{ pop pop }ifelse /r2 xs /r1 xs /Function load dup/Size get 0 get 1 sub /Nsteps xs setupFunEval dx r2 add r1 lt{ 0 }{ dx r1 add r2 le { 1 }{ r1 r2 eq { 2 }{ 3 }ifelse }ifelse }ifelse /sh3tp xs clippath {pathbbox}stopped {0 0 0 0}if newpath /ymax xs /xmax xs /ymin xs /xmin xs dx dup mul r2 r1 sub dup mul sub dup 0 gt { sqrt r2 r1 sub atan /a0 exch 180 exch sub store /a1 a0 neg store }{ pop /a0 0 store /a1 360 store }ifelse currentdict/Extend known { /Extend load 0 get r1 0 gt and { 0/Function load FunEval sc { { dx 0 r1 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { r1 0 gt{0 0 r1 0 360 arc fill}if } { 0 r1 xmin abs r1 add neg r1 shp } { r2 r1 gt{ 0 r1 r1 neg r2 r1 sub div dx mul 0 shp }{ 0 r1 calcmaxs dup r2 add dx mul dx r1 r2 sub sub div neg exch 1 index abs exch sub shp }ifelse } }sh3tp get exec }if }if /d0 0 store /r0 r1 store /di dx Nsteps div store /ri r2 r1 sub Nsteps div store /Function load 0 1 Nsteps { 1 index FunEval sc d0 di add r0 ri add d0 r0 shp { d0 0 r0 a1 a0 arc d0 di add 0 r0 ri add a0 a1 arcn fill d0 0 r0 a0 a1 arc d0 di add 0 r0 ri add a1 a0 arcn fill }pop /d0 d0 di add store /r0 r0 ri add store }for pop currentdict/Extend known { /Extend load 1 get r2 0 gt and { Nsteps/Function load FunEval sc { { dx 0 r2 0 360 arc fill } { dx 0 r2 360 0 arcn xmin ymin moveto xmax ymin lineto xmax ymax lineto xmin ymax lineto xmin ymin lineto eofill } { xmax abs r1 add r1 dx r1 shp } { r2 r1 gt{ calcmaxs dup r1 add dx mul dx r2 r1 sub sub div exch 1 index exch sub dx r2 shp }{ r1 neg r2 r1 sub div dx mul 0 dx r2 shp }ifelse } } sh3tp get exec }if }if }bd /sh { begin /ShadingType load dup dup 2 eq exch 3 eq or { gsave newpath /ColorSpace load scs currentdict/BBox known { /BBox load aload pop 2 index sub 3 index 3 -1 roll exch sub exch rectclip }if 2 eq {sh2}{sh3}ifelse grestore }{ pop (DEBUG: shading type unimplemented\n)print flush }ifelse end }bd {restore}if not dup{save exch}if L3?{ /sh/shfill ld /csq/clipsave ld /csQ/cliprestore ld }if {restore}if end setpacking %%EndFile %%EndProlog %%BeginSetup %%EndSetup %%Page: 1 1 %%PageBoundingBox: 0 0 160 160 %%BeginPageSetup cg_md begin bp sdmtx [ /CIEBasedABC 4 dict dup begin /WhitePoint [ 0.9505 1.0000 1.0891 ] def /DecodeABC [ { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind { 1.0 0.0 3 -1 roll 1 index 1 index le { exch pop} { pop } ifelse 1 index 1 index ge { exch pop } { pop } ifelse < 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000001010101010101010101010101 0101010101010101010101010101010101010101010101020202020202020202 0202020202020202020202020202020202030303030303030303030303030303 0303030303030304040404040404040404040404040404040404050505050505 0505050505050505050506060606060606060606060606060607070707070707 0707070707070708080808080808080808080808090909090909090909090909 0a0a0a0a0a0a0a0a0a0a0a0b0b0b0b0b0b0b0b0b0b0b0c0c0c0c0c0c0c0c0c0c 0d0d0d0d0d0d0d0d0d0d0e0e0e0e0e0e0e0e0e0f0f0f0f0f0f0f0f0f10101010 1010101010111111111111111112121212121212121313131313131313141414 1414141414151515151515151616161616161616171717171717171818181818 18181919191919191a1a1a1a1a1a1a1b1b1b1b1b1b1c1c1c1c1c1c1c1d1d1d1d 1d1d1e1e1e1e1e1e1f1f1f1f1f1f202020202020212121212121222222222223 2323232323242424242425252525252526262626262727272727282828282829 292929292a2a2a2a2a2b2b2b2b2b2c2c2c2c2c2d2d2d2d2d2e2e2e2e2e2f2f2f 2f2f303030303131313131323232323333333333343434343535353535363636 36373737373838383839393939393a3a3a3a3b3b3b3b3c3c3c3c3d3d3d3d3e3e 3e3e3f3f3f3f4040404041414141424242424343434444444445454545464646 4647474748484848494949494a4a4a4b4b4b4b4c4c4c4d4d4d4d4e4e4e4f4f4f 4f50505051515151525252535353535454545555555656565657575758585859 59595a5a5a5a5b5b5b5c5c5c5d5d5d5e5e5e5f5f5f6060606061616162626263 63636464646565656666666767676868686969696a6a6a6b6b6b6c6c6d6d6d6e 6e6e6f6f6f707070717171727273737374747475757576767677777878787979 797a7a7b7b7b7c7c7c7d7d7e7e7e7f7f7f808081818182828283838484848585 86868687878888888989898a8a8b8b8b8c8c8d8d8d8e8e8f8f90909091919292 9293939494949595969697979798989999999a9a9b9b9c9c9c9d9d9e9e9f9f9f a0a0a1a1a2a2a3a3a3a4a4a5a5a6a6a6a7a7a8a8a9a9aaaaabababacacadadae aeafafb0b0b0b1b1b2b2b3b3b4b4b5b5b6b6b6b7b7b8b8b9b9bababbbbbcbcbd bdbebebebfbfc0c0c1c1c2c2c3c3c4c4c5c5c6c6c7c7c8c8c9c9cacacbcbcccc cdcdcececfcfd0d0d1d1d2d2d3d3d4d4d5d5d6d6d7d7d8d8d9d9dadadbdcdcdd dddededfdfe0e0e1e1e2e2e3e3e4e4e5e6e6e7e7e8e8e9e9eaeaebebecededee eeefeff0f0f1f1f2f3f3f4f4f5f5f6f6f7f8f8f9f9fafafbfcfcfdfdfefeffff > dup length 1 sub 3 -1 roll mul dup dup floor cvi exch ceiling cvi 3 index exch get 4 -1 roll 3 -1 roll get dup 3 1 roll sub 3 -1 roll dup floor cvi sub mul add 255 div } bind ] def /MatrixABC [ 0.4124 0.2126 0.0193 0.3576 0.7151 0.1192 0.1805 0.0722 0.9508 ] def /RangeLMN [ 0.0 0.9505 0.0 1.0000 0.0 1.0891 ] def end ] /Cs1 exch/ColorSpace dr pop %%EndPageSetup 0.60000002 i /Cs1 SC 0.10588235 0.72549021 0.12156863 sc q 0 44 m 160 44 l 160 15.003872 l 160 6.7174625 153.27803 0 145.00154 0 c 14.998466 0 l 6.7150416 0 0 6.7065849 0 15.003872 c 0 44 l h 0 44 m 160 44 l 160 14 l 0 14 l 0 44 l h 0 44 m W* 0 0 160 160 rc -5 49 m 165 49 l 165 -5 l -5 -5 l h f Q 0.23529412 0.23529412 0.23529412 sc q 83 90 m 83 160 l 77 160 l 77 14 l 83 14 l 83 84 l 160 84 l 160 90 l 83 90 l h 0 144.99352 m 0 153.28137 6.7219648 160 14.998466 160 c 145.00154 160 l 153.28496 160 160 153.27509 160 144.99352 c 160 14 l 0 14 l 0 144.99352 l h 0 144.99352 m W* 0 0 160 160 rc -5 165 m 165 165 l 165 9 l -5 9 l h f ep end %%Trailer %%EOF tmux-tmux-f222026/logo/tmux-logomark.svg000066400000000000000000000023671511153563100202470ustar00rootroot00000000000000 logomark copy Created with Sketch. tmux-tmux-f222026/mdoc2man.awk000066400000000000000000000206401511153563100161560ustar00rootroot00000000000000#!/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 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 # # 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(w0;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") if(option) add("]") if(ext&&!extopt&&!match(line," $")) add(OFS) if(!ext&&!extopt&&length(line)) { print line prenl=0 line="" } } tmux-tmux-f222026/menu.c000066400000000000000000000330371511153563100150660ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" struct menu_data { struct cmdq_item *item; int flags; struct grid_cell style; struct grid_cell border_style; struct grid_cell selected_style; enum box_lines border_lines; struct cmd_find_state fs; struct screen s; 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; if (line && menu->items[menu->count - 1].name == NULL) 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, u_int *cx, u_int *cy) { struct menu_data *md = data; *cx = md->px + 2; if (md->choice == -1) *cy = md->py; else *cy = md->py + 1 + md->choice; return (&md->s); } /* 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; screen_write_start(&ctx, s); screen_write_clearscreen(&ctx, 8); if (md->border_lines != BOX_LINES_NONE) { screen_write_box(&ctx, menu->width + 4, menu->count + 2, md->border_lines, &md->border_style, menu->title); } screen_write_menu(&ctx, menu, md->choice, md->border_lines, &md->style, &md->border_style, &md->selected_style); screen_write_stop(&ctx); for (i = 0; i < screen_size_y(&md->s); i++) { 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_BTAB: 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 KEYC_PPAGE: case 'b'|KEYC_CTRL: if (md->choice < 6) md->choice = 0; else { i = 5; while (i > 0) { md->choice--; name = menu->items[md->choice].name; if (md->choice != 0 && (name != NULL && *name != '-')) i--; else if (md->choice == 0) break; } } c->flags |= CLIENT_REDRAWOVERLAY; break; case KEYC_NPAGE: if (md->choice > count - 6) { md->choice = count - 1; name = menu->items[md->choice].name; } else { i = 5; while (i > 0) { md->choice++; name = menu->items[md->choice].name; if (md->choice != count - 1 && (name != NULL && *name != '-')) i--; else if (md->choice == count - 1) break; } } while (name == NULL || *name == '-') { md->choice--; name = menu->items[md->choice].name; } c->flags |= CLIENT_REDRAWOVERLAY; break; case 'g': case KEYC_HOME: md->choice = 0; name = menu->items[md->choice].name; while (name == NULL || *name == '-') { md->choice++; name = menu->items[md->choice].name; } c->flags |= CLIENT_REDRAWOVERLAY; break; case 'G': case KEYC_END: md->choice = count - 1; name = menu->items[md->choice].name; while (name == NULL || *name == '-') { md->choice--; name = menu->items[md->choice].name; } c->flags |= CLIENT_REDRAWOVERLAY; break; case 'f'|KEYC_CTRL: break; case '\r': goto chosen; case '\033': /* Escape */ case 'c'|KEYC_CTRL: case 'g'|KEYC_CTRL: 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); } static void menu_resize_cb(struct client *c, void *data) { struct menu_data *md = data; u_int nx, ny, w, h; if (md == NULL) return; nx = md->px; ny = md->py; w = md->menu->width + 4; h = md->menu->count + 2; if (nx + w > c->tty.sx) { if (c->tty.sx <= w) nx = 0; else nx = c->tty.sx - w; } if (ny + h > c->tty.sy) { if (c->tty.sy <= h) ny = 0; else ny = c->tty.sy - h; } md->px = nx; md->py = ny; } static void menu_set_style(struct client *c, struct grid_cell *gc, const char *style, const char *option) { struct style sytmp; struct options *o = c->session->curw->window->options; memcpy(gc, &grid_default_cell, sizeof *gc); style_apply(gc, o, option, NULL); if (style != NULL) { style_set(&sytmp, &grid_default_cell); if (style_parse(&sytmp, gc, style) == 0) { gc->fg = sytmp.gc.fg; gc->bg = sytmp.gc.bg; } } } struct menu_data * menu_prepare(struct menu *menu, int flags, int starting_choice, struct cmdq_item *item, u_int px, u_int py, struct client *c, enum box_lines lines, const char *style, const char *selected_style, const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; int choice; const char *name; struct options *o = c->session->curw->window->options; if (c->tty.sx < menu->width + 4 || c->tty.sy < menu->count + 2) return (NULL); 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; if (lines == BOX_LINES_DEFAULT) lines = options_get_number(o, "menu-border-lines"); md = xcalloc(1, sizeof *md); md->item = item; md->flags = flags; md->border_lines = lines; menu_set_style(c, &md->style, style, "menu-style"); menu_set_style(c, &md->selected_style, selected_style, "menu-selected-style"); menu_set_style(c, &md->border_style, border_style, "menu-border-style"); if (fs != NULL) cmd_find_copy_state(&md->fs, fs); 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; md->choice = -1; if (md->flags & MENU_NOMOUSE) { if (starting_choice >= (int)menu->count) { starting_choice = menu->count - 1; choice = starting_choice + 1; for (;;) { name = menu->items[choice - 1].name; if (name != NULL && *name != '-') { md->choice = choice - 1; break; } if (--choice == 0) choice = menu->count; if (choice == starting_choice + 1) break; } } else if (starting_choice >= 0) { choice = starting_choice; for (;;) { name = menu->items[choice].name; if (name != NULL && *name != '-') { md->choice = choice; break; } if (++choice == (int)menu->count) choice = 0; if (choice == starting_choice) break; } } } md->cb = cb; md->data = data; return (md); } int menu_display(struct menu *menu, int flags, int starting_choice, struct cmdq_item *item, u_int px, u_int py, struct client *c, enum box_lines lines, const char *style, const char *selected_style, const char *border_style, struct cmd_find_state *fs, menu_choice_cb cb, void *data) { struct menu_data *md; md = menu_prepare(menu, flags, starting_choice, item, px, py, c, lines, style, selected_style, border_style, fs, cb, data); if (md == NULL) return (-1); server_client_set_overlay(c, 0, NULL, menu_mode_cb, menu_draw_cb, menu_key_cb, menu_free_cb, menu_resize_cb, md); return (0); } tmux-tmux-f222026/mode-tree.c000066400000000000000000000750221511153563100160030ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "tmux.h" enum mode_tree_search_dir { MODE_TREE_SEARCH_FORWARD, MODE_TREE_SEARCH_BACKWARD }; enum mode_tree_preview { MODE_TREE_PREVIEW_OFF, MODE_TREE_PREVIEW_NORMAL, MODE_TREE_PREVIEW_BIG }; 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; mode_tree_swap_cb swapcb; 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 maxdepth; u_int width; u_int height; u_int offset; u_int current; struct screen screen; int preview; char *search; char *filter; int no_matches; enum mode_tree_search_dir search_dir; int search_icase; }; 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; int align; 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 int mode_tree_is_lowercase(const char *ptr) { while (*ptr != '\0') { if (*ptr != tolower((u_char)*ptr)) return (0); ++ptr; } return (1); } 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; if (depth > mtd->maxdepth) mtd->maxdepth = 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--; } } int 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 return (0); } else { mtd->current++; if (mtd->current > mtd->offset + mtd->height - 1) mtd->offset++; } return (1); } static void mode_tree_swap(struct mode_tree_data *mtd, int direction) { u_int current_depth = mtd->line_list[mtd->current].depth; u_int swap_with, swap_with_depth; if (mtd->swapcb == NULL) return; /* Find the next line at the same depth with the same parent . */ swap_with = mtd->current; do { if (direction < 0 && swap_with < (u_int)-direction) return; if (direction > 0 && swap_with + direction >= mtd->line_size) return; swap_with += direction; swap_with_depth = mtd->line_list[swap_with].depth; } while (swap_with_depth > current_depth); if (swap_with_depth != current_depth) return; if (mtd->swapcb(mtd->line_list[mtd->current].item->itemdata, mtd->line_list[swap_with].item->itemdata)) { mtd->current = swap_with; mode_tree_build(mtd); } } 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); } if (mtd->current >= mtd->line_size) { mtd->current = mtd->line_size - 1; if (mtd->current > mtd->height - 1) mtd->offset = mtd->current - mtd->height + 1; else 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, mode_tree_swap_cb swapcb, 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; if (args_has(args, 'N') > 1) mtd->preview = MODE_TREE_PREVIEW_BIG; else if (args_has(args, 'N')) mtd->preview = MODE_TREE_PREVIEW_OFF; else mtd->preview = MODE_TREE_PREVIEW_NORMAL; 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; mtd->swapcb = swapcb; 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 { if (mtd->preview == MODE_TREE_PREVIEW_NORMAL) { 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); } else if (mtd->preview == MODE_TREE_PREVIEW_BIG) { mtd->height = screen_size_y(s) / 4; if (mtd->height > mtd->line_size) mtd->height = mtd->line_size; if (mtd->height < 2) mtd->height = 2; } else 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); mtd->maxdepth = 0; mode_tree_build_lines(mtd, &mtd->children, 0); if (mtd->line_list != NULL && 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_PREVIEW_OFF) 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; } /* * Set the alignment of mti->name: -1 to align left, 0 (default) to not align, * or 1 to align right. */ void mode_tree_align(struct mode_tree_item *mti, int align) { mti->align = align; } 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, alignlen[mtd->maxdepth + 1]; 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->maxdepth + 1; i++) alignlen[i] = 0; for (i = 0; i < mtd->line_size; i++) { line = &mtd->line_list[i]; mti = line->item; if (mti->align && (int)strlen(mti->name) > alignlen[line->depth]) alignlen[line->depth] = strlen(mti->name); } 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->align * alignlen[line->depth], 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; } } if (mtd->preview == MODE_TREE_PREVIEW_OFF) goto done; sy = screen_size_y(s); if (sy <= 4 || h < 2 || 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_backward(struct mode_tree_data *mtd) { struct mode_tree_item *mti, *last, *prev; int icase = mtd->search_icase; if (mtd->search == NULL) return (NULL); mti = last = mtd->line_list[mtd->current].item; for (;;) { if ((prev = TAILQ_PREV(mti, mode_tree_list, entry)) != NULL) { /* Point to the last child in the previous subtree. */ while (!TAILQ_EMPTY(&prev->children)) { prev = TAILQ_LAST(&prev->children, mode_tree_list); } mti = prev; } else { /* If prev is NULL, jump to the parent. */ mti = mti->parent; } if (mti == NULL) { /* Point to the last child in the last root subtree. */ prev = TAILQ_LAST(&mtd->children, mode_tree_list); while (!TAILQ_EMPTY(&prev->children)) { prev = TAILQ_LAST(&prev->children, mode_tree_list); } mti = prev; } if (mti == last) break; if (mtd->searchcb == NULL) { if (!icase && strstr(mti->name, mtd->search) != NULL) return (mti); if (icase && strcasestr(mti->name, mtd->search) != NULL) return (mti); continue; } if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search, icase)) return (mti); } return (NULL); } static struct mode_tree_item * mode_tree_search_forward(struct mode_tree_data *mtd) { struct mode_tree_item *mti, *last, *next; int icase = mtd->search_icase; 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 (!icase && strstr(mti->name, mtd->search) != NULL) return (mti); if (icase && strcasestr(mti->name, mtd->search) != NULL) return (mti); continue; } if (mtd->searchcb(mtd->modedata, mti->itemdata, mtd->search, icase)) return (mti); } return (NULL); } static void mode_tree_search_set(struct mode_tree_data *mtd) { struct mode_tree_item *mti, *loop; uint64_t tag; if (mtd->search_dir == MODE_TREE_SEARCH_FORWARD) mti = mode_tree_search_forward(mtd); else mti = mode_tree_search_backward(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); mtd->search_icase = mode_tree_is_lowercase(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, 0, NULL, x, y, c, BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, mode_tree_menu_callback, mtm) != 0) { mode_tree_remove_ref(mtd); free(mtm); 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 == MODE_TREE_PREVIEW_OFF) *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 'g'|KEYC_CTRL: return (1); case KEYC_UP: case 'k': case KEYC_WHEELUP_PANE: case 'p'|KEYC_CTRL: mode_tree_up(mtd, 1); break; case KEYC_DOWN: case 'j': case KEYC_WHEELDOWN_PANE: case 'n'|KEYC_CTRL: mode_tree_down(mtd, 1); break; case KEYC_UP|KEYC_SHIFT: case 'K': mode_tree_swap(mtd, -1); break; case KEYC_DOWN|KEYC_SHIFT: case 'J': mode_tree_swap(mtd, 1); break; case KEYC_PPAGE: case 'b'|KEYC_CTRL: for (i = 0; i < mtd->height; i++) { if (mtd->current == 0) break; mode_tree_up(mtd, 1); } break; case KEYC_NPAGE: case 'f'|KEYC_CTRL: 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(¤t->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 't'|KEYC_CTRL: 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 's'|KEYC_CTRL: mtd->references++; mtd->search_dir = MODE_TREE_SEARCH_FORWARD; status_prompt_set(c, NULL, "(search) ", "", mode_tree_search_callback, mode_tree_search_free, mtd, PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH); break; case 'n': mtd->search_dir = MODE_TREE_SEARCH_FORWARD; mode_tree_search_set(mtd); break; case 'N': mtd->search_dir = MODE_TREE_SEARCH_BACKWARD; 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': switch (mtd->preview) { case MODE_TREE_PREVIEW_OFF: mtd->preview = MODE_TREE_PREVIEW_BIG; break; case MODE_TREE_PREVIEW_NORMAL: mtd->preview = MODE_TREE_PREVIEW_OFF; break; case MODE_TREE_PREVIEW_BIG: mtd->preview = MODE_TREE_PREVIEW_NORMAL; break; } mode_tree_build(mtd); if (mtd->preview != MODE_TREE_PREVIEW_OFF) 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, 0, "%s", error); } free(error); } cmdq_free_state(state); } free(command); } tmux-tmux-f222026/names.c000066400000000000000000000101751511153563100152230ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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); } tmux-tmux-f222026/notify.c000066400000000000000000000210021511153563100154170ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2012 George Nachman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" 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; const char *pbname; }; 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); if (strcmp(ne->name, "paste-buffer-changed") == 0) control_notify_paste_buffer_changed(ne->pbname); if (strcmp(ne->name, "paste-buffer-deleted") == 0) control_notify_paste_buffer_deleted(ne->pbname); notify_insert_hook(item, ne); 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((void *)ne->pbname); 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, const char *pbname) { 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 ? (int)wp->id : -1); ne->pbname = (pbname != NULL ? xstrdup(pbname) : NULL); ne->formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); format_add(ne->formats, "hook", "%s", name); 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 ? (int)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, 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, 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, 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, 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, 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, NULL); } void notify_paste_buffer(const char *pbname, int deleted) { struct cmd_find_state fs; cmd_find_clear_state(&fs, 0); if (deleted) { notify_add("paste-buffer-deleted", &fs, NULL, NULL, NULL, NULL, pbname); } else { notify_add("paste-buffer-changed", &fs, NULL, NULL, NULL, NULL, pbname); } } tmux-tmux-f222026/options-table.c000066400000000000000000001362131511153563100167020ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2011 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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", "12-with-seconds", "24-with-seconds", NULL }; static const char *options_table_status_list[] = { "off", "on", "2", "3", "4", "5", NULL }; static const char *options_table_message_line_list[] = { "0", "1", "2", "3", "4", NULL }; static const char *options_table_status_keys_list[] = { "emacs", "vi", NULL }; 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_scrollbars_list[] = { "off", "modal", "on", NULL }; static const char *options_table_pane_scrollbars_position_list[] = { "right", "left", 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", "spaces", 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_destroy_unattached_list[] = { "off", "on", "keep-last", "keep-group", NULL }; static const char *options_table_detach_on_destroy_list[] = { "off", "on", "no-detached", "previous", "next", NULL }; static const char *options_table_extended_keys_list[] = { "off", "on", "always", NULL }; static const char *options_table_extended_keys_format_list[] = { "csi-u", "xterm", NULL }; static const char *options_table_allow_passthrough_list[] = { "off", "on", "all", 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]" \ "#{?loop_last_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]" \ "#{?loop_last_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=left]#{R: ,#{n:#{session_name}}}P: " \ "#[norange default]" \ "#[list=on align=#{status-justify}]" \ "#[list=left-marker]<#[list=right-marker]>#[list=on]" \ "#{P:" \ "#[range=pane|#{pane_id} " \ "#{E:pane-status-style}" \ "]" \ "#[push-default]" \ "#P[#{pane_width}x#{pane_height}]" \ "#[pop-default]" \ "#[norange list=on default] " \ "," \ "#[range=pane|#{pane_id} list=focus " \ "#{?#{!=:#{E:pane-status-current-style},default}," \ "#{E:pane-status-current-style}," \ "#{E:pane-status-style}" \ "}" \ "]" \ "#[push-default]" \ "#P[#{pane_width}x#{pane_height}]*" \ "#[pop-default]" \ "#[norange list=on default] " \ "}" #define OPTIONS_TABLE_STATUS_FORMAT3 \ "#[align=left]#{R: ,#{n:#{session_name}}}S: " \ "#[norange default]" \ "#[list=on align=#{status-justify}]" \ "#[list=left-marker]<#[list=right-marker]>#[list=on]" \ "#{S:" \ "#[range=session|#{session_id} " \ "#{E:session-status-style}" \ "]" \ "#[push-default]" \ "#S#{session_alert}" \ "#[pop-default]" \ "#[norange list=on default] " \ "," \ "#[range=session|#{session_id} list=focus " \ "#{?#{!=:#{E:session-status-current-style},default}," \ "#{E:session-status-current-style}," \ "#{E:session-status-style}" \ "}" \ "]" \ "#[push-default]" \ "#S*#{session_alert}" \ "#[pop-default]" \ "#[norange list=on default] " \ "}" static const char *options_table_status_format_default[] = { OPTIONS_TABLE_STATUS_FORMAT1, OPTIONS_TABLE_STATUS_FORMAT2, OPTIONS_TABLE_STATUS_FORMAT3, 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" }, { "prompt-cursor-color", "prompt-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 = "codepoint-widths", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", .separator = ",", .text = "Array of override widths for Unicode codepoints." }, { .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-client-command", .type = OPTIONS_TABLE_COMMAND, .scope = OPTIONS_TABLE_SERVER, .default_str = "new-session", .text = "Default command to run when tmux is run without a command." }, { .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 = 10, .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 = "extended-keys-format", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, .choices = options_table_extended_keys_format_list, .default_num = 1, .text = "The format of emitted extended key sequences." }, { .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 = "input-buffer-size", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, .minimum = INPUT_BUF_DEFAULT_SIZE, .maximum = UINT_MAX, .default_num = INPUT_BUF_DEFAULT_SIZE, .text = "Number of bytes accepted in a single input before dropping." }, { .name = "menu-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, .default_str = "default", .separator = ",", .text = "Default style of menu." }, { .name = "menu-selected-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .flags = OPTIONS_TABLE_IS_STYLE, .default_str = "bg=yellow,fg=black", .separator = ",", .text = "Default style of selected menu item." }, { .name = "menu-border-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Default style of menu borders." }, { .name = "menu-border-lines", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_popup_border_lines_list, .default_num = BOX_LINES_SINGLE, .text = "Type of characters used to draw menu border lines. Some of " "these are only supported on terminals with UTF-8 support." }, { .name = "message-limit", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, .default_num = 1000, .text = "Maximum number of server messages to keep." }, { .name = "prefix-timeout", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, .default_num = 0, .unit = "milliseconds", .text = "The timeout for the prefix key if no subsequent key is " "pressed. Zero means disabled." }, { .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 = "linux*:AX@", .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," "rxvt*:ignorefkeys", .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." }, { .name = "variation-selector-always-wide", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, .default_num = 1, .text = "If the Unicode VS16 codepoint should always be treated as a " "wide character." }, /* 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_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_destroy_unattached_list, .default_num = 0, .text = "Whether to destroy sessions when they have no attached " "clients, or keep the last session whether in the group." }, { .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 = "initial-repeat-time", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = 2000000, .default_num = 0, .unit = "milliseconds", .text = "Time to wait for a key binding to repeat the first time the " "key is pressed, if it is bound with the '-r' flag. " "Subsequent presses use the 'repeat-time' option." }, { .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 = TMUX_LOCK_CMD, .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-line", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_message_line_list, .default_num = 0, .text = "Position (line) of messages and the command prompt." }, { .name = "message-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of messages and 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 = 'b'|KEYC_CTRL, .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 = 2000000, .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 = "pane-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 pane in the status line." }, { .name = "pane-status-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of panes in the status line, except the current " "pane." }, { .name = "prompt-cursor-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 6, .text = "Colour of the cursor when in the command prompt." }, { .name = "prompt-cursor-style", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_cursor_style_list, .default_num = 0, .text = "Style of the cursor when in the command prompt." }, { .name = "session-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 session in the status line." }, { .name = "session-status-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of sessions in the status line, except the current " "session." }, { .name = "update-environment", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "DISPLAY KRB5CCNAME MSYSTEM 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_CHOICE, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .choices = options_table_allow_passthrough_list, .default_num = 0, .text = "Whether applications are allowed to use the escape sequence " "to bypass tmux. Can be 'off' (disallowed), 'on' (allowed " "if the pane is visible), or 'all' (allowed even if the pane " "is invisible)." }, { .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 = "allow-set-title", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_num = 1, .text = "Whether applications are allowed to use the escape sequence " "to set the pane title." }, { .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 = "copy-mode-position-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "#[align=right]" "#{t/p:top_line_time}#{?#{e|>:#{top_line_time},0}, ,}" "[#{scroll_position}/#{history_size}]" "#{?search_timed_out, (timed out)," "#{?search_count, (#{search_count}" "#{?search_count_partial,+,} results),}}", .text = "Format of the position indicator in copy mode." }, { .name = "copy-mode-position-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{E:mode-style}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of position indicator in copy mode." }, { .name = "copy-mode-selection-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{E:mode-style}", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of selection 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, .flags = OPTIONS_TABLE_IS_STYLE, .default_str = "noattr,bg=yellow,fg=black", .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 = "pane-scrollbars", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_scrollbars_list, .default_num = PANE_SCROLLBARS_OFF, .text = "Pane scrollbar state." }, { .name = "pane-scrollbars-style", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "bg=black,fg=white,width=1,pad=0", .flags = OPTIONS_TABLE_IS_STYLE, .separator = ",", .text = "Style of the pane scrollbar." }, { .name = "pane-scrollbars-position", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_scrollbars_position_list, .default_num = PANE_SCROLLBARS_RIGHT, .text = "Pane scrollbar position." }, { .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 = "tiled-layout-max-columns", .type = OPTIONS_TABLE_NUMBER, .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = USHRT_MAX, .default_num = 0, .text = "Maximum number of columns in the 'tiled' layout. " "A value of 0 means no limit." }, { .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_HOOK("client-light-theme", ""), OPTIONS_TABLE_HOOK("client-dark-theme", ""), OPTIONS_TABLE_HOOK("command-error", ""), 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 } }; tmux-tmux-f222026/options.c000066400000000000000000000706261511153563100156220ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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; struct cmd_parse_result *pr; 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; case OPTIONS_TABLE_COMMAND: pr = cmd_parse_from_string(oe->default_str, NULL); switch (pr->status) { case CMD_PARSE_ERROR: free(pr->error); break; case CMD_PARSE_SUCCESS: ov->cmdlist = pr->cmdlist; break; } 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; char *result = NULL; char *last = NULL; char *next; if (OPTIONS_IS_ARRAY(o)) { if (idx == -1) { RB_FOREACH(a, options_array, &o->value.array) { next = options_value_to_string(o, &a->value, numeric); if (last == NULL) result = next; else { xasprintf(&result, "%s %s", last, next); free(last); free(next); } last = result; } if (result == NULL) return (xstrdup("")); return (result); } 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); } const struct cmd_list * options_get_command(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_COMMAND(o)) fatalx("option %s is not a command", name); return (o->value.cmdlist); } 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); } struct options_entry * options_set_command(struct options *oo, const char *name, struct cmd_list *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_COMMAND(o)) fatalx("option %s is not a command", name); if (o->value.cmdlist != NULL) cmd_list_free(o->value.cmdlist); o->value.cmdlist = 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; struct cmd_parse_result *pr; 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: pr = cmd_parse_from_string(value, NULL); switch (pr->status) { case CMD_PARSE_ERROR: *cause = pr->error; return (-1); case CMD_PARSE_SUCCESS: options_set_command(oo, name, pr->cmdlist); return (0); } break; } return (-1); } void options_push_changes(const char *name) { struct client *loop; struct session *s; struct window *w; struct window_pane *wp; log_debug("%s: %s", __func__, name); if (strcmp(name, "automatic-rename") == 0) { RB_FOREACH(w, windows, &windows) { if (w->active == NULL) continue; if (options_get_number(w->options, name)) w->active->flags |= PANE_CHANGED; } } if (strcmp(name, "cursor-colour") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) window_pane_default_cursor(wp); } if (strcmp(name, "cursor-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) window_pane_default_cursor(wp); } if (strcmp(name, "fill-character") == 0) { RB_FOREACH(w, windows, &windows) window_set_fill_character(w); } if (strcmp(name, "key-table") == 0) { TAILQ_FOREACH(loop, &clients, entry) server_client_set_key_table(loop, NULL); } 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|PANE_THEMECHANGED); } 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 || strcmp(name, "pane-scrollbars") == 0 || strcmp(name, "pane-scrollbars-position") == 0) { RB_FOREACH(w, windows, &windows) layout_fix_panes(w, NULL); } if (strcmp(name, "pane-scrollbars-style") == 0) { RB_FOREACH(wp, window_pane_tree, &all_window_panes) { style_set_scrollbar_style_from_option( &wp->scrollbar_style, wp->options); } RB_FOREACH(w, windows, &windows) layout_fix_panes(w, NULL); } if (strcmp(name, "codepoint-widths") == 0) utf8_update_width_cache(); if (strcmp(name, "input-buffer-size") == 0) input_set_buffer_size(options_get_number(global_options, name)); 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); } tmux-tmux-f222026/osdep-aix.c000066400000000000000000000020731511153563100160070ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2011 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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()); } tmux-tmux-f222026/osdep-cygwin.c000066400000000000000000000035561511153563100165350ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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()); } tmux-tmux-f222026/osdep-darwin.c000066400000000000000000000053161511153563100165150ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Joshua Elsasser * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 #include #endif #include #include #include #include "compat.h" char *osdep_get_name(int, char *); char *osdep_get_cwd(int); struct event_base *osdep_event_init(void); char * osdep_get_name(int fd, __unused char *tty) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 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) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 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); } #endif 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); } tmux-tmux-f222026/osdep-dragonfly.c000066400000000000000000000057001511153563100172130ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include 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()); } tmux-tmux-f222026/osdep-freebsd.c000066400000000000000000000107111511153563100166360ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #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); } tmux-tmux-f222026/osdep-haiku.c000066400000000000000000000024421511153563100163270ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" 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(__unused int fd) { return (NULL); } struct event_base * osdep_event_init(void) { return (event_init()); } tmux-tmux-f222026/osdep-hpux.c000066400000000000000000000020731511153563100162120ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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()); } tmux-tmux-f222026/osdep-linux.c000066400000000000000000000043311511153563100163640ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); } tmux-tmux-f222026/osdep-netbsd.c000066400000000000000000000070111511153563100165020ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #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()); } tmux-tmux-f222026/osdep-openbsd.c000066400000000000000000000072021511153563100166570ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #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()); } tmux-tmux-f222026/osdep-sunos.c000066400000000000000000000047661511153563100164100ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Todd Carson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" 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); } tmux-tmux-f222026/osdep-unknown.c000066400000000000000000000020621511153563100167230ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "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()); } tmux-tmux-f222026/paste.c000066400000000000000000000162551511153563100152410ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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)); } int paste_is_empty(void) { return RB_ROOT(&paste_by_time) == NULL; } /* Get the most recent automatic buffer. */ struct paste_buffer * paste_get_top(const char **name) { struct paste_buffer *pb; pb = RB_MIN(paste_time_tree, &paste_by_time); while (pb != NULL && !pb->automatic) pb = RB_NEXT(paste_time_tree, &paste_by_time, pb); if (pb == NULL) return (NULL); if (name != NULL) *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) { notify_paste_buffer(pb->name, 1); RB_REMOVE(paste_name_tree, &paste_by_name, pb); RB_REMOVE(paste_time_tree, &paste_by_time, pb); if (pb->automatic) 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); notify_paste_buffer(pb->name, 0); } /* 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 == pb) return (0); if (pb_new != NULL) paste_free(pb_new); 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); notify_paste_buffer(oldname, 1); notify_paste_buffer(newname, 0); 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); notify_paste_buffer(name, 0); 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; notify_paste_buffer(pb->name, 0); } /* 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); } tmux-tmux-f222026/popup.c000066400000000000000000000504401511153563100152620ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2020 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); } screen_free(&s); 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, 1); lc = layout_split_pane(wp, type, -1, 0); if (lc == NULL) return; 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); if (pd->job != NULL) { 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 && !MOUSE_DRAG(m->lb))) { 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 == ('c'|KEYC_CTRL))) return (1); if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) && !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key)) 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, 0, NULL, x, m->y, c, BOX_LINES_DEFAULT, NULL, NULL, NULL, 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_present(struct client *c) { return (c->overlay_draw == popup_draw_cb); } int popup_modify(struct client *c, const char *title, const char *style, const char *border_style, enum box_lines lines, int flags) { struct popup_data *pd = c->overlay_data; struct style sytmp; if (title != NULL) { if (pd->title != NULL) free(pd->title); pd->title = xstrdup(title); } if (border_style != NULL) { style_set(&sytmp, &pd->border_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; } } if (style != NULL) { style_set(&sytmp, &pd->defaults); if (style_parse(&sytmp, &pd->defaults, style) == 0) { pd->defaults.fg = sytmp.gc.fg; pd->defaults.bg = sytmp.gc.bg; } } if (lines != BOX_LINES_DEFAULT) { if (lines == BOX_LINES_NONE && pd->border_lines != lines) { screen_resize(&pd->s, pd->sx, pd->sy, 1); job_resize(pd->job, pd->sx, pd->sy); } else if (pd->border_lines == BOX_LINES_NONE && pd->border_lines != lines) { screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1); job_resize(pd->job, pd->sx - 2, pd->sy - 2); } pd->border_lines = lines; tty_resize(&c->tty); } if (flags != -1) pd->flags = flags; server_redraw_client(c); return (0); } 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); screen_set_default_cursor(&pd->s, global_w_options); 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|JOB_DEFAULTSHELL, 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 (f == NULL) return (-1); 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); } tmux-tmux-f222026/presentations/000077500000000000000000000000001511153563100166465ustar00rootroot00000000000000tmux-tmux-f222026/presentations/tmux_asiabsdcon11.odt000066400000000000000000001056021511153563100227070ustar00rootroot00000000000000PK»[L>^Æ2 ''mimetypeapplication/vnd.oasis.opendocument.textPK»[L>Ï£€ï[[-Pictures/10000000000000200000002000309F1C.png‰PNG  IHDR D¤ŠÆPLTE€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ3f™Ìÿ333f3™3Ì3ÿ3f3fff™fÌfÿf™3™f™™™Ì™ÿ™Ì3ÌfÌ™ÌÌÌÿÌÿ3ÿfÿ™ÿÌÿÿÿ333f3™3Ì3ÿ333333f33™33Ì33ÿ33f33f3ff3™f3Ìf3ÿf3™33™3f™3™™3Ì™3ÿ™3Ì33Ì3fÌ3™Ì3ÌÌ3ÿÌ3ÿ33ÿ3fÿ3™ÿ3Ìÿ3ÿÿ3f3fff™fÌfÿf3f33ff3f™3fÌ3fÿ3fff3fffff™ffÌffÿff™f3™ff™f™™fÌ™fÿ™fÌf3ÌffÌf™ÌfÌÌfÿÌfÿf3ÿffÿf™ÿfÌÿfÿÿf™3™f™™™Ì™ÿ™3™33™f3™™3™Ì3™ÿ3™f™3f™ff™™f™Ìf™ÿf™™™3™™f™™™™™Ì™™ÿ™™Ì™3Ì™fÌ™™Ì™ÌÌ™ÿÌ™ÿ™3ÿ™fÿ™™ÿ™Ìÿ™ÿÿ™Ì3ÌfÌ™ÌÌÌÿÌ3Ì33Ìf3Ì™3ÌÌ3Ìÿ3ÌfÌ3fÌffÌ™fÌÌfÌÿfÌ™Ì3™Ìf™Ì™™ÌÌ™Ìÿ™ÌÌÌ3ÌÌfÌÌ™ÌÌÌÌÌÿÌÌÿÌ3ÿÌfÿÌ™ÿÌÌÿÌÿÿÌÿ3ÿfÿ™ÿÌÿÿÿ3ÿ33ÿf3ÿ™3ÿÌ3ÿÿ3ÿfÿ3fÿffÿ™fÿÌfÿÿfÿ™ÿ3™ÿf™ÿ™™ÿÌ™ÿÿ™ÿÌÿ3ÌÿfÌÿ™ÌÿÌÌÿÿÌÿÿÿ3ÿÿfÿÿ™ÿÿÌÿÿÿÿÿ¸ÿ¸ÒOIDATxœcà'FŒ*U0RËU<4àðIEND®B`‚PK»[L> content.xmlí}Û’ãF²Ø»¿Ñçxcf—dà}´3ŠÑhµÒ ]&vFÞµ HIh@€‹K÷pþ ûÝ~ðgùKœ—*\HHÝì®=qFM P•Uy­¬¬Ì?ùyéw"ß{}c¶Ú7†ð&þÔñæ¯o~þøMsxóå›ÿôg6s&âÕÔŸÄKáE͉ïEð_¾öÂWüöõMx¯|;tÂWž½á«hòÊ_ O}õ*ÛúÅOÂhí–þœg¿ŽÄç¨ìÇØ6÷­=.?25Î~= ìû²c[XÔìç3¿ìÇŸC·9óaÕ—+;r6 øì:Þ§×7‹(Z½º½½¿¿oÝwZ~0¿5G£Ñ-½Mž$íVqàR«éäV¸ oÍ–y«Ú.Ed—…ÛfAòâåX¥—ÆŽì-¬†wóÒq7ß±4“…”¦ jœGogZ½iöÛ¥-vàdxû¼¤~ø>¥…`Yv,l›[ªIà¬JO“[g¿÷}??`%p­v»{Ë¿3­ï÷6¿œH™æ“½Í'¶;IVÜ_-´3o¡ESÜ!™&„ îøÀºå×Iãpº³ëüðý‡ÉB,í´±s¸qÓñÂÈöÒ•  ;gÚ» ÄÊ¢dafå&`ËJ`[DKw7»ã[ÕtL§…MœÎ-°>0^óÎ÷ÿ–“‡ûéatKÂu„«¸$i+§#>¯DààLl ¡¹ aÑ€8üÕ«Ì×L‹òËŒ.‚)Ó ^Eí…ˆMàp|%aƒe [´|Mšä^@þC7ºõ§3+˜ÎZðãæRcÌámò`ê¬9³'¢97|ógGÉcƒã¬^ß|ï/œÈøÖñ¦Žyc€ðQí–Ž»~}ó{å‡_dñƒ›Ûý½þ3ø°^Ž}· ÏÌËݼ Û-‹^ܹoñMs.<@ðhà/m/×båDbw6|‹(?0öG@uhü(î¿É®Š×f£\Ÿ³‚¦f_}UÂ{' Oúkñ»ý_bãðÎÉ´)±á:ŒÄò˜2ÔY‚O‡év¿ÉçvùÈä“&õ“0"ý››Á{3KB½²{Ø«…zÐT¥Mþê½B r£úM¾i®@&‰ r€0g>™‰MÛuæ l&Â#uÆ_ü‡‘3[7C°å Û{?á7³Ýç—wìÖ1°˜~³Ú¿ýé:™š‡ÖVZÔÊÌ<}s— 4moÏ{4§\ñ9iQ~nÚæV7rëŠu/E ¨ag²{n;ß's“-ÊÏ­÷h©¡äúgŸÀÒæŽ×tÅ,"£§gN@ަ/g¾€7mù”æ BÆ”¹{ÍÜ˪S<©>V³¶_³Ëà¡Y`t ì`g4µƒé³“æfû²lÔ~ iae€=¢y>Z:Ý:”Í\'Ì7úÞL'^4òé¶ÛΑ­ý#Ÿní¹³äÓ-”#w÷|ºi±säÞþ‘O×ô;Gîïù(=XnäÁþ‘ÒbeG~¼¸UŸ:z|¢ÒÚ£ƒjT.òi䯶žý(BŸµzì:žh.o¶Ûÿùf—S¤´*Ì|’¸Ò™?C°ÿ ½ß$™ø®¼¾‰‚X¨u ý8B8”–Ͼ"ø¢EàÇóEJ²žÈdÞ‡š¸ó/x:\E7»h^M)\ØS<ÉvL/b˜k@ ä†VÝÝËuËóˆ+"X°æ'xt›n®b¯¼›1Ãc¼jCS¨ÀŸ Öž@¿y¯âI«b 6…@;šÐúŠåja3ž¶×³¤øwE8)j é-¥½*¬ºÇªÒ¬zV5­ëçUóQ0ë(ž0·îÙ‰hnÕŠU3ëcbÖ=›÷GǬGú{¼}ŠZðhÁó$ÏÇ-¿výx½@·¼UོcçãÖ–éxx÷1ëg´eVî‘–‘ÚëqÝrË2Óô®m‚§Lñ³`Ë( ›<Õäç,ßðŸ„+dûó[ñ<\{ŠUpʘÃßÞT½¾hòku‡@Ml·¹òC SµõPðúfl‡WS½]øó/cÀùÃYà/i: ¨ƒÌ L`eOù6’šÑئx¹„@«€Ÿ-#è)à'ÓH½àË7M|¿Óf²š!.HÈ¥c”“AëeGW³ÒÃÕò¾7o² \q'\‰¨qì‚5ø%>ÑsÃ?³˜üŠšá™&Çч™Õj†ñlæ€ÀiɹÏ&^z}óÿþÇÿN( 3z†6ƒÅr0̸i{Óf¸Â0ç¥? \{ ¯Ék‡—"ŠºÝh"û¦‡3ßuý{Àèh?‰ìñMfpø k⯒ñ)ÆnP!Ól·ú^ž“¨¼AÎAQ8ex»peÕ‹«ÿõ®W£v¯*²ø“‹a«S/¶þçÿ½ZlY­^·²äÃUWKA‰«NËTä,õÉŰÕÓr0ÁÖЬŠ,úâb¸êk)(qÕmu»9K}r1l ´”ØêµÚÃjÈ’_\ WC-\ ÌŠœ¥>¹¶FZJlõ[ŠÈ’_\ Wx-E‹A‰¬Ñ°2¶ø“SÑ•y]Æëai¯‡özž)º.îü¨Œ¬"çdzEÖŽ•ÑUäýx¦èº¸û£2² ÝÏ[÷(|Uô ´ÿCû?ž>²´ÿãŠÐ¥ýW„,íÿ¸2tiÿÇÕ Kû?® ]ÚÿqUÈÒþ+B—ö\¶‘ÿƒ{Ûö}Ìw—‰V=Æ2ÃG”Îæ×›å„'€”¨%< ì{‚0:scáÁ¥˜÷ñÉþ,áœ,0PPóÞ‰MD*_ºbô|wÅè=¨"`Ã(ð?AS;Tðó£&>y}ó³öÌñrè×ðHàzßÍU«{g-–ÒDz<ù¿µé²cÀì'ÝÙ̱àaÒ[«ƒým¿gD¨õÉ5Þt³Ox´§G|[ÔßÌq©¡ï:ÓÌ“dF3ú_ö ,íÔ²øü뛿ʟ¸`ƒl»…M²Ñ·ø7¶0ÛÙ&ÎÒž'¡qN´´W´îæv›ìºo¾[ˆ —ÑK¤*;v3C»$ÌRÚηKD¶"Þ¡&vù0e W5Œ\¼YRG[òsË´”(S/¦.x'åEkë…’ò Óèžú÷¯oÎ$gîaÓŸÍB5?籞¹.|©pÖ쟟5MÍšu²fW³æó`ÍÁùYÓzĬù6z¹Bþì]…E«ØS3ànžŸ;škgÀ¾fÀ'€£ó3`÷3àòÞàyO§bM³}~Þì=bÞ¼Vå8ÔÊñ©pà19Ô‡Ä+qáèSÓôäpžƒ*ÒÒµ2æCûn´ùz)Þ<,OGIÖÊ›íÖѼy)Þ<9šç0oê3ÉZyS»}ž ož?”§ó˜%¯7Úã£yóR¼yr,v=Ž®ƒGµCè4=DOç1\cvú^—Vž—âÍóûtt¬A­¼©ow=Þ<Pç1Ç\!o>ôµ.Í›âMëüq@èù×¼Yoê»]Ï…7OŽ:Ì›ƒGÌ›W£×}èð £Wž?ýûškæÀ‡ŽÒX^ Ðç1á]+>t´æÀÚ8ðüá<]jW+óépžç›ççéêP»ZyS‡ó<Þ<jž®µ«•7¯$ŒGóæÉ¼yr$µ{<ôÆRóè¥xôü=ýÇì{½BÞÔûÎç›çèé?f¯ìò¦Þw>Þìœ?¢g ýµµò¦Þw>Þ<DÏ@ûkëäÍ¡¾Aò\xóü±>í¯­•7õ ’ç›çè›—µò¦¾Aò\xóüñAƒÇ|óòÃz¹QàLÒ+ ×>ô®W;ž?$hð˜/[> vÔ7Hž ;ž? hð˜ïW> vÔ×Iž ;ž?…Ïà1_©|ìøÐ!@škcÇóÇøh—k­õG®Ã«Ãü¤½:'pæù#|Fú¤²VÖl_ÉQ¥ÎBygvÏß3ÒJ³^쵩CNfÎóøŒôAe½Ì©O*Ÿ sž?Âgô˜O*¯‘9õÁä“á¾óÇðŒóÁä5rŸ>‡|2Üwþ(Ñc>‡¼FîÓÇŽO†ûΔ3ÒÇŽçæG}îødøñüQ9£Çœ[àiðãC§ÐüX?ž?,g¤ó ÔË}:¡ÀsaÎóéÐÙõ£åΧ¡-:É€Ö–µ1äùcsèÈúÑ2䲟y%Á9Z]žÊ½óÇçЙµæÎ¹Sè<î<€Zk;u„ÎsáÎóGèСö£åÎ'±Õ4uÐΓaÈóíÐ9÷£eÈkd¿‡ŽÚÑêòRÜyþ :õÖÜY#w>tTæÎKqçùƒ~è \sgÜùÐ1>š;/Åç¢3rÍ5rçCGühî¼wž? ˆNÄ5wÖÈ:"è¹pç"‚ÌÇtÜùÐñ?š;/Å2uxP­Üiéð gÂý „™:<¨^îÔáAÏ…;/dêð z¹S‡=î¼@xù˜Ãƒ®‘;:Hg¥¼k^ PÈÔBõ²æC é8½ÚØï‘@¦Žª—ý:H³_mìwPS‡úÔË~ê£Ù¯6ö»@,©cyêe¿‡ŽåÑìWû] XÇÔÁ:õ²ßCëhö«ý.c=æhœ'qiÒzèø͵1äp,€S+ûutÎ3áÎÁp,€S/wêœçÂÀ±sΓ0f;’£ÙÚò17–޹©—ý®$æF«Ë“¹óa7Öc»yêRâ<†¼@ Žõ˜qžCêМ'ÃͱthN½ì÷С9Ú~½w^ rÇÒ‘;õrçCGîhî¼w^ °ÇzÌ=OØա>O†!/êÓÑ¡>çfHêódò¡>êS+ûuu¨Ï3áÎáB}:Ô§Vî>t¨V޵±ßɱ<žï‰ýÜ÷˜Cyêã¾Ì:œ—ù:nÙa§Æ‹‚x[Ê¡Sm¸t¼tÌ–Ù³$cásÙ¸ÛÚä6Í»›¼{°Ÿû©—{:ìG«ÎÚØï丞êó1‡õ\¡ê|èžË¨N³Õï´ò<À½<æ  kTž:äçɰßÉ!?‡•çóˆø¹˜ò|耟Kí;;®Vž¸÷!AT/ÿ>tHVžµ±ßb~9æçÙOGø<ö»@„Ïð1Gø\#ûéxž'Ã~ˆçêxžZÙoôÐñ<šýêb¿Ñv†:`§^öÓ;O†ý.|gø<"v.Ç~²£Ù¯6ö»@ÌÍPÇÜÔË~:ææÉ°ßré ŸGÐÍåØï¡£n4ûÕÆ~šê ™zÙOÍ<ö»@žœá󈚹û=tØŒf¿ÚØïQ/CõR/û騗'Ã~ˆzꨗzÙOG½<ö»@ÔËHG½ÔË~:êåɰߢ^F:ê¥Nö³Ú:êåºØï:w&‚®,mX¤&½ßüY¾ûÓuòÔÀ^Å¡h†þ,j®EcXäO¡¼“òæÏÔ"ÿŒ…7–7,zÈ=MpåÚë¦G®ã XÓ;$ S¡×L=ß¹n ÌúNâ¤Î>Úc@þɽÀŸ'wò5`è…ð²{Õü aF±œ°‘Ð$;ÑëÌ›7B‚·²½í>>BC -Þìmlݼ‰–ñçÒí7;¶\:žíËØœ•+>‹@¾_ÈžVÛݼïÞ¼ùÑ™,|×ì p€†Õ6Mùñj÷Lj"\!¤a†ÞpBÃ6@_€˜[÷ <†íºþ}˜€eH"—оøøñ¿¾Ä/&" AÁD¾1Æ>SÃö¦ÆÒö€¦Æ,ð—ÐyhÙì¥e* `bßeú.õ µáÀú!pK{OEdOªÇh!’~ƤãÅ"÷-´Û“OÌã‚';€mcêÌf"Ù˜NìÞ‰@2Ð< â²VËø.Bøîœ)NúµÇŽëDkìE´­à;ߣ'60e `ÓÇ…´=áÇ¡»&`lõƒy2ÿÀÐ?È%¹xK„ ^MŽ;M;kUEñwR¿¿¢´Ò4ÒµðZpIÀ·oîºVè› Õ,Lò§•ð¾úð5Í„;íÑ„NGƾ á° ?ö¯Úí¦¼#±¬<  Ó¨Ë !ÌÉv8bf„ñÀ\«á–~äÜ‘\¤õD(áãÈO[ÐL%É7 áƒ:¼sÄ}® T£$h ª©-ƒ@Ÿ²DDá$CJ~)?Yåàc\…ƒëä/…j3ñ—+ßÃ)4$5df°´ ]géD4‡›ù2y8K¤F¢ÍEÜ/;ÅB±sóæ[^â§@z ™í¤k„”E|Ĭâ,%{Oã ²&,µí1{0µ;,„ÍþúãÏFòÉFÉ!ì‚Öl lãlAÕâJÖ™Q™Ž2c‘àûÃ?c?úbƒm%ƒö+B²aÀï15 ¾på£ý7)à*œŠ9¶Ã_¬_+Õ,¦’£É"Î|”Ƹz™yãZƒD˜¾ÚÇTŸ—î+¬eü1èt‡V¯o)%›…à{ó&óYӾܣvLPwß ?Ô €_šÍ`…‚‡\¿0QãuªøËÌâÜ_œ¤yykLÓ‹Ì Å´Î†IŒ ¢ á;˜sd ƒ¦r¨²#ãÞAZLÀ­¤! W؇ìƒ'}PA¤£M¡±A! &¤™û -S‡9ì[Ã!ޒߦ«u€ùМöb ŒJ ™ ê/¬'0ÊW‚0”ÄBÅãÄ1‰^3ç³1Žç¡ñÏØ™|r× ålÇ…BRÏ Öý€%*IX`#ù©Òá(’ ”íi ¿gûhiåÿA ;cfOx Ö€» ¡áB@3¤:IØ!Þ¼6X>D®Ú&I§À¼Jà × u?4ŠªaíÁNh`À¦'c”1ÜÔó'†H‚:A—ô€Üã8S£øeÍãÆ«>¤½ Í*’#‚Øp“Ô¡äê ‘¸Á´E™ £Í(Äòa“ç°8V•“AÜIŽÖé`føR©K-]0%\÷ÿˆAø¢í‡öÐ L¨%Øã ´{p“M_v[}0¿]‰¢Ë°š¥ÂdmñÂ|™¨~âŒÐð@;˜®‹»ŠÐ¸ÃàHêa¬¬_¿É» #™é}থS“ÌvæM{„Ÿê]zšûÂæÝ›Ìëœy³`1ÀÔ }¹°¼¬êÛ¶¸N p“â’Pƒ%Å¥œ,loŽ–&ÚŸ.?ØŸÎ '!)’÷".Ù‰SMuÌž Ø$~3Ç•탲k<¹^hÞ3PSF$ð?è8 [Ô>¹Å è·Wè‰ 0i€Rd¡5ˆÞ1¬, BŒ‹æ,Ã9H€àÞ>)«šùÍ.Ñh ·’A S`;؆¬z™ò™giOÊZ%¼OÅAUh­Ñ Ûb¿m]Ø©¤ a‡þs¨¦âð»÷ïêKÐñ@ nGìt¯–ãÂ}A{L$¥Ä> /F/_ÖNFÐÐRz°ÿµƒÚ32â°ÓAÿîw²Z¿è¼lðßvó–2 "ß‹—ð»60Hiß}xgH+±¶¾3ŠB: ¶­Ÿ/ ûÎwp7v¢„ämr#ÈF‰bîûä°}¯¾ÀíK’€ñ¢uÅ<År#Fƒ(dìƒL]Û®ôU°aÏÎPb{ßMÑê'ªÙº-äU ×­äs©’ݽHO…„Š˜,<4TÜ5…Ìœ mU€Ù Ük3ÔhQÀmÅ*à#m’hÂå2Î|‹#¼ßý5Ù‰1'X¿£M9k¡!7¨wV×–]²~ÞÝ|ûækÞ‹ý$ÝUµ?9Y¤¨5æÀ׸C†ý¡©“#ëAoÌ¡z—(À¼·$‘õ,ù]ß_·G¦]{~*í+ã-ÚÖä/KŠ cŠxê'6=ù gÎm¥ú˜À‚Þu:äÞÙ¼Ùžâ2Þ¿ÜÎOý¦íg Q™÷õÚ¤ÿäÉÍ9{,p!¬€tÑ‘ÜÌXŠ0ÄsAÚ¦œUa%7:=% ds:£ ›^–Fâ £©ã¡|о_ö€OEp½´*AU¥ñǬû¦Ê‡/2¾ÕÁËJ=8tŠæeÝo»f\B÷J¦ôT®"têxòÎ0eèÝ+ ]Ùp¢“œt³‘ˆòÇp¶·Þœ.úæàM&tR:¿€‰,!²òCŽb€îr!àðŸ%:¢å©i †ïñR$½æ½Ž¥Â Jøƒ”Éøƒ¬ƒþ P!ÍnËê%vœÔEýV»ÝßvY‡D¼½Û=“w69£òun …{Y/‘iîÖÝÜô-˜þ–ý fMo¸o—ÚÌmS{ƒQnŸŠK Ýòÿ@kíS÷¥½›7Ÿ‘F%¡ÎüëVá‡ð·ÇÍ×ÍZYýœ•Õ?heeŸ}F"Î`Ÿ÷žÃS÷ž€)>£¨{GÑú7$€§ü=®¾î† ý ™‡ŸùwçŠp¼‹¬Þ31¹ö¸s&W7grm2se“ë±³òó0¹ö¸[sÈä?È!?aéB>—è·¤õ•Zc½Á8ƒ‡FÿÓÆüvó½æ{¥0ß߃xô‡CºµÇIžCz'‡ôÎéìÞ‘Çóp6böŠ}6Y#§Æ6=Ë*{>’×áV¦EÑI'÷øàI7ópÇI4:0ä¨mïI‰EÙïjÚ¹(V«km, ï:3‡ŸòAÑ¢ôª-JÇ¢X‹bP Tê]s€Ñä§-ÍaGLáÒ˜{iÅܵ$ƒz–d“NLÖiÉTo‹9ëëì4ßÙ>1SƒÑ­EŒ1ZÄ\»ˆ)í¼­,bjrèh1£ÅŒ3W̨àÃw2¹ÐÆÕL™Ûí!#¿Ë‚ÐHCépò ?¼Ýí‰jÑsxí42»™JœTå²cÁéà!“¥­ò‘¡”U®bâôÂП8in«jsÛt •˜6YXNÓQ¥/‰ß*ŸDEW}|Ó2þBwM£ux×ñ>…r[eµ¸£ðŒéÐÊ%È¥ô@˜jB”ß!½Õ;ó_ô^2o&©Kè²*å~p×”SEÈHa•ð±Ú]gºêíÉëý”‹€6…”SBåÑ‹ |ûþ;NFB÷æ‹OUã`Àp•o0dÓ5Ì›À`º‚%ƆÆt݉Œ0ÆÌmò¾s%¦©Ôzåšð'4‘Y¥¦¾À\:‘á –OÂq²X2\KÌ$²Z¹xŘtPä-®ãvkÅøÛ¼X%â–¬„ĵ- Ñ´ D‹Y^qL- ËÎÿ`ƒ*ü"<Øà+àˆñ­nsªÔ™(øs*HÉ;®+3)¹&|\…‹e&½J„l«\“™1ò¡e|`ð8¿Ä\«òõÝ96²k ˆ¿(ÅRL¼‘žMzYq4ñ*3hHÄÑÚ‘ÌÇÈi¸R°3±Ð<­Ú¯Ç„CwÊ„C'»–ÌUù‚Hèî¡HèN«×9Á0<*:=#8*Ú*Ñ¡Ž€“_¹àk°¼Óiµ3gtÒχXöÚYoöíI»¶GurZ6Pb˜[ÓáæÍè‘uhMÛá´gMÛÉi‘|¾ºÓÓCØ/,‘ÜBKnà+LªÝôò<ÕïåCÓû§^ÿë%’Þh?i!‹NULX±éë­ò¹Ô¥±k•<«ð¢ÚiE%¨úêa&éQÐIgwâžI—Æ<½¬tÔÙlgÿ9bîv¸€‹Ó23n/γ™]<í—:ˆ‚ý‡y9Ïã`0ÚDâ%‚Á`˜s<›ƒ”Wd!?íL"Ýý§u9D´ûýmpæáV0z§WCr ROûÈ´[:c^)$t[£7àûºQû‘è£ÈÀÜ-w9/fÈ)<ÈÝ·EÕ°álÄâ”NÂÜ-wØjõSäÁ”oÀy™Íá±@•;RØ W®·¹R›¶½c¡*ëÓ·ò0Y™ä8Ö&ßL½ÑÑ+UÎÁ¾“Õê´‡{a*ƒ½ƒòæ°sûycµºÃºäõ4åÍa ”Ù¨cš{IVÈœáðdò(mœ¡¬)Šß0³Ôú­öF†@«[CüÆê ÙBDöŽZO^ëÃÿwrÔC7Ôœí®{jÝÎ{jê6Ú÷tC­ÜSÃ[hê¿ Duo-{S ïhñ7ÌUºê÷×ÝFÛ¾¿vè:Ù®ûk‡FÚqíàx[××h¹·uq ü#Þ[+sÿlëöšºl×;0êÖÅ5 í¡Õàáx² ´£]Ð/x]Žá,hó8nåö*l„¬¢¤™]+/ô½|ªµá°†ÈDr1!F:­®ZÙÄði»¬Mµ¶iõÔàø)8˹6$ì²xzåvX½VoÓâi÷s‡|‹ƒ[,Íðw>é¦:›|~pÞP†téxTÀ„²ð&B–M‹-8²,7TS4ƒ<’3|¬d_c!SyHoã=ŒPElèo‰eÙ§êÆ¯*¯Kq‘k,šéL°ü9<ðHT>Êi.émå"³›•]qJXâÚSµ(’êåôoS¾”Õë—è ‡Xâ9)#Jµ?œ {Ì–©G› Bá)PíÖ1Ýä1¶Ïß¡¿eËø*ßË­§Ð!rø(×É Î\™.KòÈtSSÅxØj\®KºQ€„î½ÙÓ)iÎFv\à@?˜txOÔ6îìÀ€¤ ÀjÀ§Œ²øk`õ‹2%+òˆùºŸmDmC‡m̰Æ;ÝW[Åòºxc™É.©Ë0¶'Tѹàl½ààd;ÑU¦òFï˜ãŒÞqWÍzí45\r¨0,qœaPáÇg(óìÈãŒ^…”ýýnÁ}²Œ@ýÌáõf½zèîמ¶ëéwiƒåíèùJñAª©O¯ÛlSM»»±Éåí]ëнÌRv)‰—3 ¸ðÜúÉá¸t2³œH-Þî U­X/Ìc«ÓÎ%‰ÇAÒ'µ$‰'­ÔÔÓÆeé¬Q`·‹œrùnFê”ë¶7ª3ÖåGÓ¹ž*ûÊt®§}.³g•멜p(} SJ8t[–ejá …ƒOA8”w3[ÃAÁio·ÝˇÍÜVËl×PÒm_Õ&méÄq…xºB÷;{óŸœ7¨ñä0sø®ßˆhú¥¼Ð@èY YÁ€”5ȹºý¶´Õ ­†+´ú¥OKw ‡Îá`µz½¥ÐÂA ‡G&Ôù7[g†ç:(§óp×¹Ã#P]l¯é·œ×—U¥ß„9aDÓwÂÅŒoèÄAP§½²ßEY\%§ÄxcÏ;x¶oø3w811¡G|Úü£#`ÆïÿÎyùáÛßÁ$Æn= :<³¦”ÊŒ1jHpÿèL>?€4÷¡ïµŒwɪý´ÞW¾²™¸1à==Ð†í« @â0Âu‰eåƒuŽvlâ-EÄzºžA'V»=b ² f)cå»î ë¥áúþŠgJÇñ¨¨€€8]´Œä€9VcƽÑd€^¨ j8Ÿ¦ŠáŸðdåŠÏØÉTØ®:zwí`. /^Ži0"DEZÈl¶íÀWhÉÔÒ€;ŠB OV"?¨Û73LnAÙ’bOÔ«pa˘“%PÆ?ø„}«\Ô*‚HÞߌ§Þóó Œ’¹sü8tñ"&%¿Æ[ºÕRd·I¸ôŽ9.ÀQ ùZdoŠ“«GYüÌ鵓Y./Âã¨Âx‚át35c+7YØ2í0˜3g‚¹y¬õ(xm _lÁÎáÚ2±ÁÊ"{ì¸N´>Žr1Év /ßbuP ÃŽ­å'*õyÆL$áJ€ts¡ÕƒB×xø™ÃSÌ©Í9‰‰>Âx…3a¾FRX€±Ø\‰U-*'IeÉíZ¼È.W¢&Ð22q.¯6d}”ä5ÃæpØu¬~p³½Vß'uú°iÓÙ°/¾£wóF?ÂÂ}ïx°²˜;Üãô‘š q KsëÅ®Ûâ{Ç”%Áúš’ Z<Šg 9˜ØFª…0š:|@*1­ªÀÊd:%¥¹ÌФ ‰ÜN­ìT¿w>‰{' ã(ØXକ¤:Ó=Ó® 5ÎõЭø·„¹ C±æÙ+ÅŸèÔÄkK'$yoñеöËV2 h gO0a¶š æo¾ûéÇ¿ýåí×i K„µÍñƒñ5)ŠODúšúÄ< $˜æhD?+xîÄÑìƒ)}PÚ4óYVÛ13ꪆÔH(Ž\’¶¡LèCô +7É ª8€éLYˆ!߀ø˜ˆD8dý5Ç'S€1]/·¤ò±Vª]*7Bc8¡ªföØÓLõôKïWæíp‰ü‹u ¨ÃTŠm0]0V$¹CëE‰çÅ‹V|÷þá#ÿì9(EÈ2`´‚Nø†ò7(£I–P@Œç«©2¸Ø ³1.ÒVIAGŽÛ‰ `ŸŽJI°ê´ÀÐÏ Ʀ¡\žÚb‰ÑزwxâÈ›=$·"UXd¦Â‹P”欤`’^jè‘§Êbk(,!Æ­^¯kYÃv‘ïUãýÄÿ»™¿ãOøšÆd´xÚ”ã!µÈ6Ã) Y½ ,¼áÙùXÚÄø7"Öù †#Í,"÷ñ–†t/<¨qà·l‰ "gœæ†”Q˜¯$lŽåÞ÷©åS•mßÊåe+é®ÁdM2?ˆ½0Ñâ ヶÊ7¥eh¿!"úêÛ÷ÍŸÿAOÞ~÷–ñ6€ïþË®á„Ùm ôûÁA‚2—l¿ÆÍ7å1Ñ2–SP?ë£) ¶9ËiDdiXû{îÃÎvó Ær£à´ÀR“Õ{©Îå­¾G€«`£ßÂŽ´+ѬE=•dZÚ¿S¤mXæÉu…7dÊÏì@îkhKF{a`¯£íL¢!ÄΧb…âÔ›àö[i´Ù^‚¹ƒ‡É<“|LJý¼$m“trfì*Ы³™¿)Œw²ZËLO ½(Ýë£ÁðÚä¢í€ëÀ$³sX¸œ:ë/9É?fy½Ù\”Tz¼õ {L\ ¶Ô²¸"¸µÃmK$%cÃ¥ù‚ì­U´Ä—)JR¦¿e‹ ¶Ú}Y}«³2bØaÐ!¢µÚf;­ê’,ý8ÆÌ7¼µ¦]*Æ};¼éd™¹ó )8 ï4 Xùë?ÓcÞ~ʇÈø°!£ëåç ëpÿ@ˆÚOÌœ9ìMYLTûèd¯‡ŒƒíÄš.™Ý¬’‘xG’¨ÜÁÈyÐ5Š¢Û 7ÁúŸWhµä¶àjßM¬¬ %«ï)?n±¬,,‹9ÆœAÝv»¥]„¼Ý±\Ù üÈJ0Kàý4#!B:ñW^8@¯„3 wº,z>|ø–'@ŽEPȸ÷š yé‚Çd‡NªØ—r¬¨ÿ€ÛJØ!_³ßî/¼¿ «›kä°c–šùÚûæ]‚”¶LݱUNJÛ˜‰{dè,Àzñè *BaÓ•¡j3îÏ4í; ÛÞiå2ó±q&Žž ÷S‡ ²êš¤%ya\´È"Ã… «3ùb «Jp¶{W]d,ô%$ž¸oÊË9Í©ñ‡Æ~ô…XÚü7äy~R¢C³}óæ$≂ž >¹³À\£§Â›;OAàß/¥øèoSl)˜IcÕ¬2›~çIIH3—ûà•bòB-m¶›ã3ƒ¥îcÐ3Jr †o¿}/(g-˜ëÿŒÉ'èd NsâÂh,†øâ˜ÃN•ö±")Q†͸ù(cA£O§ÒN´SXÈ,ðm) TŽ;å‡õ„õ¢Ò›jù/pnXV œPz)%ÒXq’zG1냉ˆ\$<Ú’ÍV¼x–èõµT”!M—î¬-ü„ËX$iØÈïÏ:ü3ìž"‘ÎÑCh"D1K½41NOu@Î@é¤_/Æ”Òí“€ešEâm-)™—ôK• J dÿÄãi{¤Š«SÏ[3cruûÃöRS þJL_”^V1è‚TjÆ—^ åí&€ñLè v3Î\{*FQîô‚òDõ2â³n& É¸\¡Ì“G –]ßÿÃQ:~ÁðQ/²ôâÆ!» QžK •“i§ˆÉäœPåÌC/n”‡¡æ1 j`7J×M%Ñk6UÇD@ShÃö£…?UÅÑl*6ˆªäj°'=—™ŽZöpq=¾*8YVKSÈÉH“o3z¹—®m’ÿ“ðâ \<òÕF nü§en(Ú³Ÿ ¾bØ¥™ümäO3ñŸž‘MDEhK"G;2Zÿ?Gnh ¹.ìÍðÈ­8tt!r~·”ÓÑ fm+™ƒm+>s¢™í[ åjñ¡ðú'sÐ;j¹ªâJ;U“·Qp§U1,”(áOG$mÃÑš%BI·²¶9³ Ü"èöËŠyÛ8YÜa€Í-’É l Z惛;ˆ¨ÄèÅaÆ$èoqí|¿;ÜØ*ƒù«Z‚p‹®ßW[lp8´^Ökw6-š\jß^kÐ17mÒ4µÍfq±^îùáâbýÌÓlkiºpq±vªe.LËÞõÍî¶5µUwíN-]±tשƒÍd7‚$n¹¼ñ=Øßæ¯,YƒR“ÚÁüîºÍž½ä[nûÙoõ‡9òmÃùö[íÎÖ–j7ùv’¯U‰|»)ùöD ºëú‹f¿Ûƒ?†'V”Ûçy·h-¬ÍÒ\“²pyú;—gX¸<ÃËÓN—§ €ug ÌEÿSïß’gø{˜çïáŠRÖô‡ù$™¼ãët ONÁëÒ˜×"©;AØL´Œï¢4²”¾H*³g"Óó…Ó9 e‡Ô€…ìP™cÙ ‹ä(ÕäS¼¸”¶´9‰?"ŽëbÀaV½õÌ):|àÍÍ@Ì9p4”¥ "{lP˜šU¸µ4tð†“’ù\@à­¢˜±{%̦ ësŒ íí °×|_ßÃûö}¾ê€ÌK±Ä¸Ñ8”åBç_œïƒ`Éê HW²é‰)"Ž€H¦t-‡ï•ȸgJ~`‡ëœ{Ëø ~OEåñ· }æd"˜¢`¼ÆPTªŒ"jŠy ðÞ^Æ `^Œ3wýX¦l@‚ªLYSÈ3d‹½®©ÐÛ;‡½é: ÐGsWA5šÐ•PÝ<ÂÐS #„+#§t‘xÕÁ1ñª}Ø0›ñªÖáxU.aU¼jç¦hw™t¶kk<<œŠPÕ͹ r&ùÀ4‹ßîÛï@€³ç„6¹ÓÉV:MwýŒ#ƒ’mø¶û² ïÆ^l'6¬Þá-,1*^¨,¦×½û¾ýÈ)W»i 9½½È9Tìu7röœ?æ‘Óm™ý­ÚáEÈÉ'»îË´•u"gAšåÊ:úx¹§ú²vT•¯Ï›CU»Õétö¢Kµ8e{Žüj@YûLèú“âõ$SâûyÐV6¸Óéïá°ìÛ’(Û Q9®C öR¾E ¾ß Q9ïéÖu³ZA”['Õâ(¨ûMw@U°NP½Q9uº Õ~ˆŽ…¦\¼Ó4ƒ˜ëœ€¹Q9­VÕ!ÌuNÁ\Ù»›Pí‡èXhÊÞ騀Æ:ˆ9ëÌ•½Ó±Õ!ÌY§`îHn\+ó”µ*{§cªöA¨Ú§@u¤,7÷¯Ó±Ð)Ã̓ôdî§'ujóW´ÜÉ=äÚk?ñ!Ÿ#Ó*\˜’Ž“Î‘—G]U§ÈˆéʦèÃw6ûbùF§ÊºÆÉ!ïêsj?Úp'©5Ђj¥Ž®ŸÒždj•;5IEáMìU( ¿¡Ùî^±£†'Ó ŒXxŠ€}VSá þ‹<[¾‘nï˧ØJAÅLº}­ò˜qV…taU0}–¼µË³nÉ.p·oO"!¯a/1E$º‡Ú;vÒ˜K'Z4(³¤Ê9„x¢”¹‚¨<ºìÀaÝÓÍzø$𧱺†?……ÄÏ袮LiBI§þòÏS6 ˜¾>e•Ÿw)Tº‘wo©!(9iâ`ÃâåJ¦ä Ï÷šÃ6§å)ÎÐÝéŒÝ(«{0ß%Ö…¿ͱÃ%³t"%sfå4u&³zð'æ<ù„9‚`zœ Í*snùœÃÉä”BÀUNk5«˜ÓPȬ0*!Œ1…d牒NL)“€8–‰œ‰Õ>¦Ü3%máZÑJôpæfœ„Ð “¤tþ$ì%6VêAž#q¬Å8ÍÍËrX?ñßr…fþ‹ÞKƒîvJ.¦ a†I΄.óõ`êrÎ&Ê=—ÿœ4÷=ç¶‘ ÒVy  SPRʈ°@Ô–Ï)ˆF*N7¦#3 áŒ<2[W: þê^¥žÉiFמ«L§³ïlÇ¥w˜¶-—Ú8ÉÏV›¤‰V\Æ UTÊŽ|ò„\™vGÕô¦z¨Ém>µs£8µ=Õlu8‰6*§IÈ7§\ DF(Ã@×Î0Q=¦9ÊœÏ#›½kLïLN[軩kï>|·•³ŸÁzûþ}÷´È¼JÎk3úŽˆ›Ê¬SÂ3✆L@-)xAÏ[—2¬eÁIUïíc“Â|Ö§”RÁ“¨$þ”·3øg{9Š‹¥ FÌÖJÏX ÓðñoØ$sP—¯Lÿ=í ML)•4­åšC¶döÁ ­›K³Ì벑¯˜ÓÚ ÙAœS¦á”‡ÈS søì: ó"'HÁf³>D°1Ó­ÄiöôT•÷•=ù4çtÇÿáK̆ɚ²©¤e,ŠUFàPŠ*¹Uš<œ” íÉî}%ðKdÌícmæŽÙ5‹2æ&¥òÂd"åY“A§ÌI±'©\yvCŰ¡1Cè¢ä_¹|Fü™JYTKæZ áft‹Üè<Éë)[(¡K¶†$ë²@=ÂÎ3RµþíEÊwõfÙ¦lXI>JrªŒX<+N«,uX«ŸdnÎ48'gd r*iˆ“¥c: ”[ÓW€Î|j§(î, Ô¤Q_d´f²Ûañ÷’#PD’öyBð¡4€%\ùH—f”SÒI‰°cµUÈÒþÄþªËPw+w‚×RN±!ççQÖtÀñ8eðßÁiÍ“½ÍCdà± Üá­˜,8³A³Ò”1d=‰D¡¾Öh Å!ùIåã“i¥#ÊF̶˜JÅ­âvAÏáÖóÃw}÷í÷_³ŠŸçNM‚8Ÿ’^Úåd€) !¥ã¾ßT˜¡‘…5ÙmÈ)Qb8”¹ª®:Æ¡Ø*xæÃgüf¥4n(zâäïá÷2‘²ïà£ÄŒ¦h £ œ¸>¥}ã°šHÆôÚ)TÕsì¥[B'L]C_³¬ZЬ_)¨sfâêª|û{ª‘Ê¥6²Ä J³2—Vê÷¯œÆÝÆ ïœtö%#-õ T vô*chZ{dÛY~¶HV1›ÁV¦Ò*¿ª4@\¥±«³»s,6ƽ™j‰9´¯rÓMÂ*x‰Çé*“1Ók´W9¨‡8®M,Y$ˆåfvÍ9òìä£fbÂñžmæû¥óÁÂ0ib×2ùcMRœÀXf’ÜÂg¤ÜÍeTª%¨e|«²‚"›.˜Å¶Hj¤[MÏ7æ1n"³Y¥Uá­ªR÷¬ÙXä¥nf{+¢ÒkÙ\Œ›WûÁ…-³iŠi*¯=ÜD&Ƶœ¦/ä¬k² ßư¯¸&Æß~V9kò½L)`7Tð •2—á<ÌjK†åj’›ïf’|T¦ESC•MHÒ¥*Ÿ‰Ô¨…=.­Å^mEïrɶïôå¡‹EQ#ÉØNÞ!¬RçÎÊ)( C™]%³Ø]¨"€«j¸ŸVÇ!7 »-C$"ýlí©Ôí¦RçáWeŠL°Êºà¼ó¼Þ°L˜Ö—vC¸£,±ËoÌNg0êÖÅTÚånÞ¼·=éš9ŸY… PÛÔÁ5l|“š ¶JË?YøH•t µYq;]?zJà ›D|¾ ûrü†’YíL‡mÊߪCmÌ2†PàÀ‡=áÇO~Çf¡ºŠFG®k¯¤;o¥ªJ%—¸íd™7ßã”ãùóL/{ðFÛ´¥˜¢ß¼[¢j2ÉBRíódmן£@ÚÐøòSÁÇ @ø,#ù:³ØžpÈ’Ô-¤ª,XQ*­ Ái§¹;-(?Ÿ÷Ù©Ë8ç‚P)â7×|‹îûÖhÔ±z¦0¸]¥¡LøÀ@¯oÐþ:+†âEÁêü{•»);) 2¦‹c¬’¨f ˜+‰NÚoz ¾W›Ý “Èš³èÂÁN:ì–„©JÊçô·‚NUüÚðHºÉž”ƒkª3OÒgd{‚ª„Eû*âºtF¥x‚Æ‘K"/8¥Ž×™’‰ÒÍÀž'ô;À%˜×BžðQ„03é–”áTÈJ\VÜ*å¡—ké”n’'%)¯ÎËÊ’÷|‡,^¡».È8¨Ã) „!¿F¼r‰äͤ¤ !U‚-XÞqE:Môßû:È|f¯Ó3ÿzí³sè$>¹ åñ¯ R?ùVÙanÆ µ†»õ2HCúúéÚ˜Ã7$e•ôK\Ï”‡§AöÖ¥cÉSùò%i$·Åß‚Mºâª«4Ž´Ù•Üe£SžçÊ:X+;Ì\ËߢÜ-¢°ÀŽÌþpp ¢ø8±Õ:ÕO”@æ£,âl¶³¦#Ôì´ÙkÈR;dS"ë5Ôù"QäO}ÚkaéÒœˆ€zäù²ÜýÓF ™Õg%µ]gd×ebÿèGÕë'ýbþJµ¹2—X“ÓnÚ¨,¢hõêööþþ¾5÷â–Ìo•A}ËÝV¶˜~±~5>HßIfpéÊ“œåÀsî!Àäyö"`Oåè-ü«é݆Áäöuñ½?¯SçWªÝ‘”3 ˆv‘vtП,A]úÞ'±&ÿûŠ ™ßªÞnÏzuó—î¯éiÍ71V”%æ¸Dê¹éµSñHøÉJßÁî¢ÝòDt+–ñíTL~ÃX߸íIðcåT”_ÙKÇ¡Ä?»ü  oÄýù=˜™€ ödEY:ôa¯6§ŠšcÇ»…[ð÷—hͯ_'V%ŒÙõ1nMPnú ߦ¿¦þ„JÌ4)hËÞüPKНÀG^:PK»[L> layout-cachecd`d(ðf``àd``‘1x$´€ŒŒPQ[˜¨1ʉrôa2¥@†PK²w­%2OPK¹[L>ß=<1ó manifest.rdf¥‘±nƒ0Ew¾Ârfü‚YŠdh”¹j¿ÀCPÀù™’ü}]ŠÒ¢UÕÑöÕ¹÷Èùá:ôìÍ8êÐ<{ÎŒ­°îl[ðÉ7ñ?”QîêF=O,¤-©p*øÙûQÌó,æT k!ɲ ö¤ŒC"¦›õú[Úñ2b,·”¨#VÓ`¬_Aáꪱ"š:Šq4v!Zlš®2 ƒñÆK»ãìc~ÅÉ|¯ø³¦'íüòì áä*Sð ­"trø5MþÖú çð}øj"Õã'ôÔõæ.#ÿ*ƒu³•ù15t¥êe™´©Jÿ]µõÌaýâ2zPK»[L> styles.xmlík“£Æñ{~¥”ó!UH¤6^§üˆãTÏ©œ“¯©Œ$|¼ Ðj׿>ó†ô¢}x`ãû೦{fú5ÝMÏã¾üëC÷¸(£,½[XËõÂÀi…Qz¸[üûçïMoñׯþðe¶ßG¾ ³à”à´2Ëê1Æ¥A:§å-Þ-NEz›¡2*oS”àò¶ n³§u§[û–MÅ[Ø`c»3d¹w…ª±)n§/ÚŸ™!˽ÃÇv¦¸D¦r÷}6¶óC›ûÌ ²$GU¤PñGéç»Å±ªòÛÕê|>/ÏÎ2++Ë÷ýƒ6 ^~*b†+c:Y¹²–ÖªÆMp…ÆÒGqe’ÒS²ÃÅhÑ  ]hµ¼?Œ¶ˆûÀh‚#*FÛCîª× Ç«× å¾ ªŽ:ñV? ûÏZ[(’±sQÜŽ¨‚"ÊG³É±åþY–5¤Ò|2ríõz³â¿%ì3ˆ~.¢ z¢(‰gIŸÐžµ"&¾§fZc”éÁ‘·«çYQ5„ìÇ;("»Y^Ç*‰‡—…Ö¨‡" {Q 9Ί,5bèæ}„ÏìøXþþŠ!- á7%_MhdSÞVJKjd PŒðU.¿&—ÈG!+Ý^¯R­²poá~I~,¾ªÝü>#.~l†8ˆË¯¾ä˳i6øo*ĻŇìUÆQFÖ ‹±ÆK¢øñnñ'”gå_$$Þ°XÁ£þDþô˜ì²¸gL øÄ0_ŠûÈb€…ÑéK!槸ˆˆÍY‚ÒFUYÕ÷ˆô¥*ybîŸ#bcÆG|6þ%†ê—‚'äó¦¤ÕÜ_/•ò•åK¦þÿ‚þs2>‹”ˆ„3BåcYáä%4IÖy…¿œ¦ÕÐzí<ËúêKš<x)ƒýâ„ÿ]´ý×^ÿ×]pH•yŒÍ.†QƒE²EÒŒŠ¦„>CãŒí?®Ù"Þ±ìÙŸNŸ(­pZF“µ^!uꇠô@g¯GßeEH_ 6ˆˆIæñÚ@´zd`ÃêrWFtÑÖˤB)ÍZ×˵e‰h/²J8Ñõ’ÖMk]DÁTi# TàZ› m,[YH–£‹¬ HÖFY[¬­.²n@²nt‘Õ+j²\]dy Yž.²|,_Y 7ÕåLဨ-">!,mÒC¥+ôX`è±t… =–®Ðc¡ÇÒz,0ôXºB†KWè±ÀÐcé =z,]¡t[º¼– †[Wè±ÁÐcë =6¬CmJC­+ôØ`è±u… =¶®Ðcƒ¡ÇÖzl0ôغB †[Wè±ÁÐcë = Ðå0ô8ºB†GWèqÀÐãè =lZÚl =Ž®Ð〡ÇÑz0ô8ºB†GWèqÀÐãè =z]¡\ˆºÖá =]¡ôºœèt¹Ð3èr  _Ðå<0ñt¥3˜ÎxºÒLg<]éŒzQO—õ@‡åéòXè²<]>Ë–§ÍkÁnK›ßÓOW:ベƒ¯+sðA/ïëòò>èå}]^Þ½¼¯ËËû —÷uyyôò¾./ïƒ^Þ×åå}ÐËûº¼¼zy_——÷A/ïkòò6x°…þÆdE-]5-°¤¥«¢´tÕ³Àr–®jXÌÒUËKYº*Y`ÍHWÉhå}MißÊú6š’¾ ”óm4¥|È£o4yô äÑ7š<úòèM}yô&¾<úF“Gß@}£É£»M¹šlÊ…lÊÕdS.dS®&›r!›r5Ù”e ž¦,ÜеîèÚëÿºÊÿ`õ_Wñ¬ýë*ýƒ•ÿ·-üï£86£°LÙ7Q• _Äô*&{tâöXàýÝâŸQP \®¬µüÇ–þvÖþ÷Ö·Ë<=Ô]«Çœ±•ä”-ÞVéÃ8Ùá°nBdhTÌ,ý¡°á AÅg\ÈÔ]Ù™ßí¥ð¿ÉÇÆÚ°×=žEÛûEb‘6Ó¢mG{ý+0Þ§Ç$ÁUTbð™4hÆ–åX†OoÖ4lo6„ú_Ò|¤ð–²*²ÏØ QÙ±šïÈoà:*_ÜF-pPÕØYUZw KþiÆ8=Ð7,ºöBavÕB½0¹Ñ\€l̈þ…"ø°æÃG||Øóá£?$ >œùðÑE›ùðÑxÛùðÑ«7óá£ÿƒMðá·þo<Á‡7>ú? þlø€Âà|¢ ˜\Í(»‚Õ1#}@Y‰z qÊ|@Y‰z¥eÊ|@Y‰z>zÊ|@Y‰zvyÊ|@Y‰zÜyÊ|@Y‰zBzÊ|@Y‰z¨zÊ|@Y‰z%}Â|@ác>ÑcàÖ»`c>YÉÀ5yÁÇ|²’{õ‚Ù”•¨÷ð§Ì”•¨÷§Ì”•¨7ý§Ì”•¨OL™(+Qߘ2PV¢>>0e> ¬D}­`Â|@nw>^wàAÁÆ|²’óÉJ  8Ÿ…ÀùD@ÊÝùdˆ.äªÜùø*Zî|V‡ -wFëÊÕ³ŒSæÊÕãSæÊÕ“SæÊÕC–æcà\&çC=—9e> ÜJ=Ê9e> x®žþœ2P2´<Ÿ]dhy>{È@ЛM̃ʛó©nDñMtª2:ÑB⌣¢8?¢zFÆ®ÀèóÝ¢¤ÏaU5ä\D¥-ÉBÒ=.Ìj×PE¸2£4Ä9á§]ÕDÖ4VhG4å%¡z5LvƒNé¾àæTb"†”j•M.^R«Š^ûŒSTF¿J-;¯X[ŒÒà HNYCÒª æð÷oöqUáÂüŒ‹”‘Δ¸¤cš¨ŒPJF^/·y#Ÿzøú뱆ˆyjÀ·/G 2ú~ÚCM­2b=Fê˜ è­@;+jÌ2k4°M‰íø˜qÊ^¢3c†DZŒ–»Eš½'QCþH‹ËO){Žx&`Â%á;¢/Ì=e’µ)™­s±–öÖj×K×hs"Ìv±<ò$µq§ús”žñÙøW– ômmNYÑwøôŸ“ñ ¥åÛ'›µ1³Ù1ªŒˆ€¢7²bÙÈpíö‘õ”3KPA< YN#íÆf‘¶mßeU•%,[¶1³œ²Œâ3z,Ÿò*’ËøºˆP|á(6í‚yÞZïàÚeKi æ Cé~7夆 ~‰òŸ­×§uÊ#Uˆ 9•U´o(?Mþn«y&ë[漮 ê‰NÏ‘`Ý1r„ÉuLÄk"ÖxÞ¾E9õµ¯¨`ÂL®\¹­*‡´Let›ž’ñÎ4¡(›È ,Çàà;þ¾n¿hÂ;oã_RIðÉËh-p‘hOg@Kã°4KóŒVó?H^ÿðŠJŽØx ’_Ic¿åb ¡¿íZ¸NLajCMbVFü±io¹mÓiþØs@¨'l®žìk¹j×":å#Ñ~ ^#ê2c¼¯úcÇ1d„ߥCQ$ ŠʇÔD0 ë9JRRÎQicg§Š©"Æ÷4…·EwÒ'¢ƒµý¢gL%z·Øeq8X È{Õàþ¾­ÓèÝ °þWy3¡$V’­YIö ”´BøuºS3ð§ûuºí] èo¢}Ö¾£YûÎkiÿyj~™_MIXIÍJÚ<_IžìF_²B=Å=¾êíüíÖçw:?³¬¢|³uR˜§ 9Tá[ØØ¶šmûJÆö,«z‘Õ¼š;¸5t£YC7Ï×{µ†Ü—hh óË5äÂr5kÈý]C¬!O³†¼ß5äÃò5kÈÿ]Cô_[€TÔÂu}à¯ÿO•ô}Aþ¢B ÈXDÀe¿¢š¡â¼Ò6Æ“9çßÒ0Í*üœi_c{V ÙΠïw÷ ®ëµ¼±³/w ³ÁçsÐmX³ƒ!×ý^}Ob­ :'ú€í×éúšý¿oNqŒ™f?=&Äö ˆ£ N³¹ ­6‰P>ÖO9Nù@ X³¡4ŒJH£Ùý[’É×q0boˆ™ìðG(‡?{ãæ#³áŒA=5˜ ªž^«íDýó4CQÒõ“ 48f§ÛäµxÙÛÝn„ß¡ÿÿx±¿]»,{mõ»¬KH½‰ª¶K›¨ëΙ*zH‹Ev£@áÇ(0Ç¡ÙÐ\^ £8&(Ù©h\թ艞²üPРݺm˜j´öŒ%Aì€\B‡†!Üç( Ù¬õÒÚ ‰ðs=ììêÚ£Ìâ(4ê¸õÂ&˜ëDãÒ4~â…‚È2Î#€²Adlî³"AD Ñ£¢«jE2H˜²I‚";™‰ëƒœhGÀìðCB%Ð3¬‚"Æfû,޳3Ñ÷î‘J¨Ðn!MÞœÐk—ÒzéÞØq³…(ÖÌ­œ{™&Ð!yŽ”´ý®$m-×ÖM¯¤eHGÒ5àÍ%í¼3IÛ€"gÖþæbÞ¼31oíÍ€œ[ˆ"hxsIoß™¤]×t Q$Ío.é›w%i{¹vúá éHº¼¹¤Ýw&iÛë‡2D‘4¼¹¤½w&ém¿›–Šœ·¿‰“öß™˜]HÎ-D4¼¹¤Û²ê»µ³\oú㡠鈺¼DÔ],ZÝ(égë>:œ vßÅh¦¨u’˧²*,Á1¿Ñx⽚)뎥$vyJî“Ö#“ŽfÁ‰ébu …¸®ï2HUüæ/z –•ºÈ´®<Ô£®ÿ° *é+ʈÙ[^–åõ˧æŽ*¤@—ƒu]~ U*++G Ò*[e°( œ0#Ü2##ãF(A³?iõ™Œf àÓ1)Äè‘W§6ñcžX‹$¥ªÉ ç(¤}mòÕØÔŽà(öBlér(¦ ¬Ì¬ˆ'HXZVTŠª‹ÕÀ!ÿþ$©·úe·ÔðKÁ´:•òûe;|>Ù‰øšæÒJK¾µÐÖ®€[°|ј zh$@«sµ4¥ ¢„ˆ©)s’éNIZò{ô»wÚDÛñ2ÝV’ƒqB%ŽíÞ¸f½¹×þª®Ò+ÇMÛÒbƒ®ÆŒêõŽ*F¸¹ãÿ›õ,¼J\ŸHóЛï^«©úv§¹ÃÄ|>›R.J68hO×x ée¾bäã߃v»Ã&Là²Ä׿&j¦Žìx9_k+™S©ñr ‹Òþ¿X”/Y^=ö4i•¯=´ˆxÕõÛR똈ºÝ)Ó,yöÕSã(!±w[Ž7 •usÔ€ÿPK°Zî%¿ÐPK»[L>2©[ßmeta.xml 2010-12-20T02:19:042011-02-12T11:29:50P1DT39M34S362OpenOffice.org/3.3$Unix OpenOffice.org_project/330m18$Build-9556The tmux terminal multiplexerPK»[L>Thumbnails/thumbnail.png˜UTÔíQ¤»»CbÉ” AJr``ˆ¡;Gé¤DBa¤¤cèøC‡Ä\ïZ÷ñ>|ë;/ç<ìýrÖyø«§£FBÈHˆ……E¢¡®¬ÿ¯ÿÄÂz‚…ÿôßøæk}†ò›÷þyGðÍvÒó§»VcÑBäʱEnŽ‹’0€~Ónˆ‰l—TPxæ:¤ï6 å&U=Ý^¹zÉ—¥¯fBEâ˜ é æÌû+Á5‹-iË`Ç×ÿK{§ïÔü޽ÚLTÔìžšmÇÇñÊ”òzȵþ zZ·%‡éï]q}’?Kߦ;©Éã\v>Ë5׊+àœM½-Ý[5Ø#™!Ñ…ÓÕÝÿ.˜¸ÇÊÑÒà–ˆV@^¼öí`¬:¶Íè¾6fB+2&ÑxB¦ò`ÑÜñ%¼ ŽG»yÏe;ùm“ÅÛ'¾˜à´L xkò>"ú“+[ÌÆž éûz6a{È7SÒv#Yíð-TS‚9“¿õѶþ•ïÓÂnÑeî>å¾àÓŒÞkù®fDò˜œ`˜Òÿ*ÄÆå}Ë9¸KÑ Ì.¤'µÆÚ8wm)D:.•ù’ˆ“F¿jo×÷°JïñR8/×qm©É[ÂŽ¤ˆ@xïNa7ÀJ±Ÿ– \Âåp»¿ý’-]¿†Š®È£+UýÄ̽¬ãŠuK™eÍ„õ)ãæ6~‹vØÎŸ šð¶Iˆ:‡n5K”?l²-ðúl's±[´X»¡j¾úúyoºˆïÕèÿó6­o(¬­söñåB8R YÕÝèí-¥ƒ:%1 C–×Q[ îkî[™þÚ¥?Æ'VZeW»bœÜ2S#Î^¾ÙAù˜³ÐžO7]7bóPýyø4CÇû}®ùz[äŠÞí$.ï{&Õ^Ç©’©å¨Ò¸˜æóÖû§ÎT©`øx<÷Û¼K΢f5¬²\åêàÉTzS~f šzÑ%]µVºû9¤1Hù_›å¡%]fìø[¤ ¦ö8¥&gO}ØI9f“­Æ0×ôe–:dz˜Þ;ê”:ß;‹Á7Œ+a«ž½Ë÷ØûèzràièÀ‡t_±/¡”&†ÐKý¦T© è uSÓER:ß‘èàfZoì,¶Ö!¼òXL¥ŸTt3ðqÑmʹÍ`€Q’(+Ø“&ZšŠ*%¢¼sâ~¡2§¢• s¶G‘B¡žtÎQ$§­rie0,å‚OÍ©È6 ,þ²¨MØÈjË‘¹å9w³oBn¥î€ŸE¦¡Ú'“üêkˆ)êð÷$ò„?ð8<×9®iÿÝ̉Ý/qNH˜¸Ö©ö‡†tñÃÃf$  p5Åh§¦rì™ z…!P2I+öV´ÐL¨lQv¹—L+X\åvÛGX‹ 0+jpÝÓ§~Á޾;£§|&ÉÔTåý‰bÖTÜ`¼"3%‘F²uôŸÜŸcxí10ä¸U‚¤¹¡‹ÿòáåL _¡Ý kõWÀM(ùG#iÀ7ÊLˆ·àߟL½Í CÖÆ ·ìÐiB„lãÞ—heR¤0ÇýY¬_@\«‰)çÁ ®s)™¼ñ YŠæ¦+‰>©°Ô’¤ró°ìùÉðÍKçôy ¾ÑŽˆ[ð"DøB2iSØ[Û¬}¸'enU¶4›ú®0‘jÐzp.êxÏ‹»Ú[Eo–Bëf:ÇM±ƒ5yXÉ×Óý³R%raë¯U_@^£ ,ðÑ^rÏÈǶ¤Ã †¦ŸWã‡<+uXìK Sº"ð‘ž>œ¶™i–ΟR*»£àY7 ¢ÇG­½iÞÄ,€ð~°ÐЇD^ìå:”Iñ ä'|8;ëçA‚Ì+ùþNô2øeBQÆŠèr:SAÕ—Ý|œä-ñüõª=ÕÔΠ÷]³ð´Lò𢬇ùñ ~~ݰ›eÏQA^aÑB[Ù¯Ñ饯SX…tŽEzW P3š¹%B\ƒ´\²É÷ôèÇ_"9§:˜'fˆÓ¡ZûŒÈÏ¢òÃ×u²ßù=—á%Våà©ÆhlÐÂ+Ór=Ü9˜.1/êçÉ:62¸ìý—–‘Ý‘,­¦v^^¯šWÍšÙóòÁþð'BÓ êµ0Žsáõé‚AÍ9éyÂàyÇá´ø ÷ŒòN&Ì#sy8zš§^/»zZª±ý6g¢˜G^gÁÒof²>QW6ÓÀß½ÜÚµì³Ë}^NùÈ«ý•¿& F ŒR2Š—­ÙÌÜe Ž2†ý„šffT½Ö£•ÒþÓ¤è¯o#©½Ôøˆ)µ4V-ÄÛ²=QÄc˜¥Îgšxy8ÔŠÇÝOY"cdmvX„²ì‰ÆS˜'ªÀÆÊ¢SŠ báµÈ¶M"a½ïhÆ=à ¥je«Š›ñ‘…(}høMjÈHDw‚˜l[±åB£‘¼,T·}ýSëÝ£Ú<  ”QÚžˆç_)‰gÜ'S?‹1íù`S;=`’zžïO©Pm»Î ¯q(Ÿë‡Vøû%¦k-Îä†YÀºÄäàrI¿“¶Ñà7\j8mäðõUH£êã1¡D#Æ….&¸k¢ßm—=#R 3«ÂÞ¸¶¸p©ÁÜ ŽBj šbQ)¶1¹¿# üÅ%©u«®æ[šê¶Ó²ÞCˆýü/¹îw²DTÊêyS:Ö²ÙT/÷2Îúl*>HQúü™§ Åd´à©îž­-S_ìé*"ïêeœ]>ûñì¿›yq9¤9ZEŠ`nwýS¢LI^¡ zHÝ0«¡{ÑÏ£‡S^™ ¾@V»ë铸äEÝÑO Ö6Wê –¥k´†b]=>ð½ÈÉDÚ-íÕÆŸ·žMI[Îtšã@fdÝ[íkÚøtlÛjÑÒéR/FôOŸ†x.˜Í Q~ }ìÉw¼ÌHÐŽ'Ù=Å}|Ñh"<ÑIèS#ÿËSlΆhRa]±¥¦l‚@šV|r©ÛÕÛ¤xqù HÉ.~lO VªÍ!ãW™·â2(é¤ßœ_-äÄ݈œøy°?µíT݆bËËÉ´‹2=õŒ©Ï„ÆvÐŒEOãËËÖd»ü0ã;=­Kòö;7x©ÀÚKîÕ™oˢäeÙc|¥s˜‹\\µÀS}³=¨$vì­û‡@\LÇVó_ª—¾ 4Ÿ.övD€F¬;\ÑŸnGt‰dOOÊx¦­¦9d. #p¼;º_þº®æ g“|*ð„_F½Þõônìm*Ø%“–®&×ÄZê@;¸šy—Å=¡ »„AhNü&#úϸjt ÷QÓå®&ê{ïgz³GÝ |Yxvl! ªÔ¾›;TW„Έø8˜Ò”7xfþÅ$½;°ªTqtRÿùR4s¿Œ¡ccÐ s`65“uÑ1û»¾ýëöìIìí–}W³=¹¬Œsæ É\ á(°þx?Ø“Î-´šŒ„·Û¸³ÅC¯(¬Õœ2ëò„öC™ïð‰³¤¿Ô8‹¼1$•™‚d½²sâ·L¾å^qpÔiÚIÍlhÕA‘ \âŽÃìµyµ„êCÞ%™UŽ&즇gC&ß3éa°Üý÷VW³¾­‡Cˆô…ÿ,Ú5äú`Ü_‰].‹W 8ï¹ï›Ô¹‰/9¶È«i²5Š™u¦ƒ¨0|J5”¹ ÿ¼t;-Ô³ïhµåBÏ·„nCd™ÛˆûÈÔ¾ •Ÿ­™#v“ ©Vn;‡Þ8~¬Ëj¸Éijš!Z8%"@yæ÷­Ï«ÓÕÇÖŒ­ò/ÃlbÛ˜Œ¸êx§u ÑÁŸ)ä{ä5&3ø¾øî\DÛFÆhô ¼ÎûÐ8ðLüh ïBB¬R¸ê“Á¡EfÑà³bk?FV½¢Îʦ4x (øÀÉm¢ÁHÚú³;¶ì™6B‹’>PHȲ¡Zò_;\¾îÇÐôFúæD¹=϶I‡R–…ݧµÖøeñMB¹!•ô©%mœúSžA— |Œ?{ ÖöÔX­Ð_)Ú‹Q¯tÚçoE×gçûkãs¯Áßz°+^'ËÍoZÂ'³p“L™–d9 4ý•–Nª]мš Жx]ábwwbðån;­EˆïÑ5Ió>áì6üÑ@ µ<;¯4ó¾™0z÷ÎÑ>þ‚çHÈäöül!<µ#Õãv¯NÉß“´L`Lò²[V82 *0lÕêÿ¡^Zê-Iaf1GÈ´Fžø˜mr{7xC‘;O)LÜà°ŽæÈÃÿ€l4Eœ×øž$eÀ0F“å1‚FÍ!ó¢Î=OØ® ƒäÂ%œÇÆž§)5·ùþ~.þñÚ&Öœw2M×ä÷( ÄŠè ME¦AèÜèï(>”i•E<åü±1ª¹*ðçÈM‹ˆxasÿ" _ÊB›E¹( yÆ7½×faGd!NŒFèl + UU°!§îÓ¯BaøŽ›oÚ1/uš>Åš©¹¤7­ ¤B¶[ºP‘¦AÎ]^ä#j¼çMØð#™â› Ë=ð^+òšå§Ùb6Ÿ'÷ÒtÞrzás—;,þL‹2=^äãÛ¨ØC?¦üiz¥¾=eª®ú¾w•úð!D°ßª"Û°mWýÞ^äÍNE®pW\$mÐ÷ì ”>~›= 1¡mcü½£±­áD»š¯’ŸÿEŠzΊΔÀØ34§HåÌÛ÷Jµº¾ÅeÛ²ùò:ÍŠ®Õƒo…s†o°h˜—ÙT•ñòTÀ«ÀXiŽ£®½ö¾?Æs+EúG7öDÙô•}4 õñ[^Ç£‰ŸÉ¢öÄ$a"ñ•T:Q÷uÉ“0â“Q†ʼn9¦tZÝâ¡—ñ8@×ø}ƒŒ@î_ßè‰r•[ì}[°ý0nÚóYƒ¿t‹·±ä†ÃWsêTœö0£ ?1]Dwðf¹;}^O4Õ2¢k‰üöØÖÕ<\/MI›2«6"RÏ z×ø$»Rhs¶‹PWç/³8:ìŠx_&ã……¦EëØÀÄdˆ$Ýîù¢óù‚Û±ª³‰@C.¬Vç÷Ê‚ ¨ÃOâLK¥VÒA m(tÖzÀ¡ûÅR¸""öº”{]—a¾ü/y—›’›ó€¹²Åì³’½ví3·ÂïÛË}ý+$Ž£ùÂà»ë´¿WÉ´q9±SôÞÐÅ¢Y“»àñÝ{oÏØêXL‰7ƒŒ<º€õ•òÿÿ'0v”åS‚0$Ö¿ÒPÑQ®V´û?PK–X—ô€´PK¹[L>'Configurations2/accelerator/current.xmlPK»[L>Configurations2/progressbar/PK»[L>Configurations2/floater/PK»[L>Configurations2/popupmenu/PK»[L>Configurations2/toolpanel/PK»[L>Configurations2/menubar/PK»[L>Configurations2/toolbar/PK»[L>Configurations2/images/Bitmaps/PK»[L>Configurations2/statusbar/PK»[L> settings.xml½ZÛrÚH}߯péÝÁ8ñ²8$ÄØP€ãÚ¼ R³M«fFþ>=°^@‘4ûä².Ý=}9}ºÅõçE$Ž^AiŽòÆ«8ñŽ@r9½ñžFíãKïóí_×8™ð!IÒk0†ÑGôºÔìö—(Ù@¦¹nHn˜ 1Èõk·O7ReÙ•…àò寛7jµù|þaþñªi­~uuUKï® PNøôPUÙÓoU!âF‘}!3&Uvzrò©–ýï­Œ|ãšSïví‡õño¯W ²?ÇÜ@d}s´ºlM»ñHeã•Ã|ã5oß{ÿ}ç=ï+`#Œ½õ³ŒéŽ@9õnë'õËó³ëÚ®œÃewabö ?)'÷™‡f¶WðÇÓ³úU9á߀OgûÍ®Ÿ_ž“>œá|!%´fLNAoi# `Ò»5*b::²©p®áCÈ“>aB,þ8bñ1—!, ÜuÖþ,Kß¡úPËÃ\Þ ·LÕFqël›Ð§ÅC™›{g%2$¿\®ÎO §µæcÕWK*ÖM§¢¹•bëð¢”ì&ƒÑ~Ëë—E«ü'b4"YÛI7CeJáR—-11-I$·k»*éMÄ—ÊŠ{×/mTûm¯Ÿ´¾£‡ 0¶](`úž‹oA&ïö ·ö?@òðÖš]H3Ô¨ÿ¤ÇúBà¼Opf¾ã¸Åd¢zÔOå÷Y ª­0‚I¶ë½ŠüðÃ,~½É„Û¼JhHÏa1°ÏÜtG}Júš3ÙOd`’4œ®2Kì`»¹U&Ä#x©¯ÐPµ¶Qmco¦w‰g?Å!3û8ÊiJx¥…„ (œø…ăÚ[^LÃù§&—L-½Ú¢¾D±Y¾›ée3dH %€€Ç…†V¢áï3Ì¿…QÄd“;À%7ÍCP#X˜gÅâžìÿé£ Ëý8Ë' ÊÚ^}žNö™biycXT®„&eüÄ`VK°r*QA›+m¨d¡C­TšŽ|L¢1¨wŽT]FÙ¿CLT°ƒ'UY8¢K7q;Ò¾ŠLõñhs¡Ë€Ó)ôµÄW²x»J+«Ë—aS0ù¢©ÑØ k1$ÂiGö¥D“jÈï=9nšœß¸à^©ùm¶xÜåχWCÆ_û‚0CApëÀÖ;|Dó=цO–6°ú™›Ù“ MìÅJsKWþí±„êè.ƒ¸[-Ýœ¤'ÂÕxŠmðÿ5ï=S1ÙQ¸&V§(•³OrA‘ à'(ü² á€…nB›ÍP uLÜ×ÙH`·%ކËhzEÁŒh»R®|Í‘žâ+Øì¼,AŸJ‚Ò¡Á˜¸¯”$òßxHÎâ…+É–øÆ(Ûåˆ;µÑjdøgñö7œ³tPú6Yð2U˜ìðýjè3:ÍéLü¦Ú‹ëè%Æ®á»4¡‹¿-±Ò.yí&:4Ѹ¬»*eù+zÒ:NÓ|ƒ åö]&§ ¥@ÎÊdAô¢d"ý9R¿6 îM™âl§âï>M˜rikª°„/2|÷ý²›Íßé²Wø*pÌ6¤Å.`œmÖ t0êº`-‚ǾÞ$³/jf>+zTµÅ2]O:;(;Ùï¿Ö‹é6>']gÔæ©£Hl¡Ó¹úT¸ÚÆ‘˜{Ø™aÿp픑K—P—:ŠÆºàÅv:'Ë›læ^5¹¦ƒW „³ßbótÙ_¹ø”%´XlV µÝ“ý]p­Âoä'?Ý­g“=µëÆ©¥rºC§]ð‹>?²_7ôdK vq²l!b¿|Œ ŠE¡åHî¢Úί1jy¿S¹ýPKƒClé"PK»[L>META-INF/manifest.xmlµ–Ínã Çï} ‹ëÊ&É^v­8•¶RÏ=t`‚Ç UýöÅѦñîf?œ_ høý‡aØÞ¿¦xA´¥F¬«•(”m5õøþüX~÷»»í¤; \ŸEšGá½Ûˆè©¶t¨  5«Ú:¤Öª8 qý³}=)íîŠ3¸ÓËdèÇâ,†­†’G‡çŒVÀÉOùBmuÔªæã+‹óìÙª6³á.S:àC#¤‹|Ðô(õ—qOZqôäz5ÿ6³ÿçÕ×ÇõC5!–IÿCq!mŠ”L;r™ª,ñÏÉ`w¾I{MàÇË F¹T ø ßvŸþ¸ŠÓX•¬nžÀ£ÁpEtþŽá èeØó!{m‚äSófY7ƒß„÷`©Ó}ôÇM  J¡ÁÔµ^ªèýu¹¸\+‹€ó¶OöI 3syo]tépyðl­q@hòà'dzÅ}ò=üxÓùMó.S™ýÐÈ ¯Éù_¯ùiªý*êJÍ5þÏÛžûÈœžEï'ÿVþö*Ú½PKÔÁæišP PK»[L>^Æ2 ''mimetypePK»[L>Ï£€ï[[-MPictures/10000000000000200000002000309F1C.pngPK»[L>НÀG^: ócontent.xmlPK»[L>²w­%2O ìKlayout-cachePK¹[L>ß=<1ó XLmanifest.rdfPK»[L>°Zî%¿Ð uMstyles.xmlPK»[L>2©[ßÒ_meta.xmlPK»[L>–X—ô€´dThumbnails/thumbnail.pngPK¹[L>'Ü|Configurations2/accelerator/current.xmlPK»[L>#}Configurations2/progressbar/PK»[L>]}Configurations2/floater/PK»[L>“}Configurations2/popupmenu/PK»[L>Ë}Configurations2/toolpanel/PK»[L>~Configurations2/menubar/PK»[L>9~Configurations2/toolbar/PK»[L>o~Configurations2/images/Bitmaps/PK»[L>¬~Configurations2/statusbar/PK»[L>ƒClé" ä~settings.xmlPK»[L>ÔÁæišP Š„META-INF/manifest.xmlPKg†tmux-tmux-f222026/presentations/tmux_asiabsdcon11.pdf000066400000000000000000003331661511153563100227020ustar00rootroot00000000000000%PDF-1.4 %äüöß 2 0 obj <> stream xœ\M‹ë<–Þß_Që†ÔX’mÉqU²˜]Ã…Y ³ëž^4ô»é¿?Òù–äX©— ¹e[’¥Gçûyútÿþõ¯écÊ-Ûòé?Òì>ÓÇÿõ_ùø'>Ëÿþø¿_ûï_ËšÅ%åÆ¿ÿöñO÷áÖò÷ÿþ÷ur·Ë|üÍ]§pûŸßÿùëñû×_›þiù\lÏýK÷ßÿ8ê²M¯^8ß.ù]KyáZ~â«·:Ÿ>ç¾Öÿ¹½~ó\yñ×)•?XúVþ¼—™ì·KºN_åúZé$¿à§Ðo‡[(Ë?Ó«á>„˜Ñ„É…çiò@÷<þ<-ù¥ù*æ‘Ê ÛtÏïð7þBã%¿Þ_ñ'Ï7–†éé¦[\®ù÷t.~FœÄ¬“¸¹©Œ}Y¦ˆ ž" åÜz¥i•‹ o–‡©\ÏÚ(O÷–`¦±Ì.t °‰‡wÞkïïòÁ3ÑðÏÛe|«7çÎeM¦û•ÉL^Û!ŒØÈ•Ÿ–³a_×LV wç²ØTî¼€Íosæ²·2Ø1-"ÐSÈytymžÂ§\–î<@FK½øi™úŒƒ yrn.=èw¹åM΋sÛ&­jèŸe|I¼Ñ\çö^ûáÎïtÅïŒå¹ƒgÈÊ«p{îôˆv Æk—š9Á¶—¥9xxo‡ÖÞu›˜Ó^PT@i§ËgA—%/“AÊGÈç ÈzYå Z¢Aa<&*ÓP‘ÈŒ\„kä½·íùúŽ#-0˼ݹݣt›“ eö²~Çî¶sˆ{ÄFÇõ3´ÿ„Ð`n±,98ï¼]ó=2·ðÆ(¢'º¹»¶w‘Þ‡WI©Óqs–+L¥Ô´Zˆ@×!1‚nõ,ê-ó{wÀZ9k­¦„‹?$/ìAŸ »¨G„š'ÙFM¶ ‹˜[/æë¢Õ}[a/x|Ljë{˜F¸Î©×^œ¹dSçZXýÞ «ž0jùPõ†¶{¹Øô&4Òk :Äo»•)lEéåéí"\ÝïMePuAë5›lO`^pŽvÞí0÷* §X-î| KÄb!Î"Ô¾íêÉ[xbYÑ=ÍBƆ›‹¶_¼v©hÓ‘v(½]!QaºUp¹½ž‹ª?·D!m(ðÈ54"XøœƒÜa6Ù»^‘åÅ%nð‡åä]Å%Ò{;W¤m±¼T¾˜AÞ9hgIGÕ†l·–=›,_M4rÓ&¨†‰÷@…Œjësa€!;Å^eæ(ÊVšã•ÔT qnõmôÜô-´ ¡¶ZÐÌpå÷ŠŠ×ÄCg ´—mZWyÙ™ýâh—‰²ÎÒfdFõð0œ0Ç`„—-©¹wÄÚBÐÞO½•%†NÞ;`{UU•ÚñAN$¼‹T"oà[Øîˆ·Â¬>Ù°!–‰×l4ýé‹Û£1!t0'–›  ,]STݧvÁN7«‡f´YËÖKóorŠ2:§ŸE&3«—_åCR;6Ç^ïm„U¶«¼p7[š¯=ºë?n™Ÿ}‘Wþôí,––Ç]½ôç¸v0pמˆj1‰€òäÈE'÷¡Ð“W–Ÿ‚MëtÙìo[#Ý3Ž[ÔÁö‚ÕV¿ÀPâ´ò(cl·Y†`sÈöTD÷PŒ° ¾Ñð¦]´++€‘ázÓ›\)¢f t2?6'˜n*§ïôD[TB|m¤ˆïÇ,D˜6¹Î Þ‘ ¡ãÖŒË Äf–Ù@Y&¯-÷®¯µ„Î7§f°9óv`l{ Âh„Å=m qàdr³‹}¼ïgTC໕¥Ñ"«{vvŸVµÀ#¡aÚúžÚ&$—Yìì6ºeÆ9 [1É}ËŒ©´éÀ²Dh{A§‹MÌÅSé¨Ho":ÑÿIMåý„&/jÆ(ȈI¾±¸ÅÒû"…K„˜|/q£š!B¿“‰›P×bö$¦lvJ³=N#`ã5­„”Ý`òúà2ˆ{Lý%²“XxK"““jCî‰qsR_Èf¨ÝˆeåÙòêæ ö"Í.ÖjÀUmŸª–‰) Æ“;Ñm‡ÖÞ¥7©%Z¸"ëzÃÕd2{(Ô­ƒ“Giªð˜Fì ê{®8—rÃ$ÄßÁw’Ƽ(Uo8“Âç0v¨Œ`œìæŒÂ´¨ à7#ù¦%QêV«ua¢&è­Û_t~ˬ‰î5ÁŠØ¡[™£4Äîž½‚ªù\ÏÓ–9%3ãZŸ¨p/à ¬ …@¦Ü“2Ù³1CÚ>×·’Ö.&amìzrk¡ .^Á¼[¯¨ÆÄÇ…È=š“zõÝ&bƒ º…R»§ Gϯ¼ˆ÷n}#ÒÛÃ3 ÇÞ£ãH/R ËÎ&ˆh­v^9ÿk0¡‰;X7×Zs$“UíœÝŽ\ RG’´<ö÷Ûrõ{ùùÒ|t$Ù]g2XŠœª¢£¨«? ž%Èf²ççäƒ}øŽU»ŸžìžMdVÖ@¬}Më{õË«þ§æÀ9ZÍâGXÍé³E««ZÊÁóm b Œd–ë÷ÔÔ2“A@ŸR‹îEœU_fŠðÌh&åÿ´òßeK$d?>þä5LFÉ&LŠžoG·îц¸¹—°§éµ2yÿíþJÔz"v,K‰ÐEëbq›‰U•x øcs=Mþvj">e X.D¡½8[|§´ªÃ@æA¼çÃ’†ÓÔKÕÊI)âÍ?}*¹QØÂ*:'dŸ:+wäæÔÚ/,òàÄn7Î*!ßÇ;NX±*Ú,¾o5‰¾äOðþ©Fláìœ_c'Ž8ˆ´´!M˜kx)ã\œ%¸ð ^ÐøºƒÌ #ïÁ™Fs!°)K™¢ÀÆ,cfbq¥Þ%“X1Ù)6Éjjn a’ц¾I €WH`s<UsV«‚,×£ðzã¶pw=™.V•$`%¿õuù@!hÝ4ð¯0öõÒB÷B6bI'ê ÉÆ9^Λd£lðk±XOmã@8’sê¾ÔÂ@p*À¬£RˆP…N@ŠŠ”®'¿“Eè{ë1ºõ˜¤ÝÒÚËnå\×*y·$ÛZ³†k›J·®<;’Ó0F¤ãí¼¸l–cØÒu”›¦Oª;Ó¦§¬bÀ .®½òÑpêNFˆ¦jaÖÊ"_ˆ}vk·0-g¨^$ðìðkb$¢8ƒõ*ÿÙuÉš M 2¿æKgˆ¨Žz7ùµwŽÍ÷>uêGšÁ…Ÿ1D8`‡¢ËÚ@ eD∠2ì+µM±&k`mÆG©ò½ã$.Ñ-ezs‘ÚA­ô™å0;zɱ­ÊÙÞ­„î•O•- Y#h§pÓÝÚE9 ¾)Óßl†)÷#™b@§FJ8XöˆÇ'× 3zêl6ç~³„_#ˆ,Ï ;Iè$D¨=týC–ù¦·aoÆr«w%`TjW˃$ãuQc‰ƒ8kˆH5ró9fÌRì Iò–£<3âÅÔØ6ØäÊ_;¶ûQqÅ{Öõg&€¶Wš€¼Þ ÈÆ‘Ÿ ZªpC W£B+¯Uõ«Ôì(5ljøj5ŠÖÀP Pî’Ó.’€çYÄž|ΫEZ^ÓE%|3ÚO%‰?W—uu E)תfYLö+I;ò¸Ú7+£Œ ù’,‚ÛÊ|—ð¶T¥j«H˜Q¢Q9“¤]¦(FÔ¼qxˆ e)D7c…ð(x•^ "uø^kÕ%$8óЮu¸ÿaëSÑ#ÒöDÚð?æ­ë Ú…‚ÛÜŽKЍp‹%­aU5ÕBlb/¡&Lé+@‰ÂHМ#Õ­{ˆ”_^9Y&°Ñ:Z <·¤×åAe6H­Î–rÁ%6#‹$ââè– ë¾â<Û׸›unYcâ/ó>ºxĺìŸuölT¾³þéOB©ïN?¦Ç椚ûy³—ë¨Kue8äÒÚ¸‚•(Ť!d-(Ç(¯1Ås5IË_·Ÿ._{¨ª\5dE¥+#ÈsŽ}ol*ÊSfžlŠÌÝÄÛ£Ä lëb4v/²F„IÍiâ÷µ‰|Þ¯vD¬ëÒùÆ«ÕrëÌQ^Ð䉤òq¦Ú­©…£`’ ´æL2´ÞW/Í3·…â%S´sŽÐò®‡MøÌ¾þé⥖ì:%)š-éÌZ(X•g¹[5_ºOàV‰Ðª¢WPì†rf(Á™!P)§XRêÕàñù0›âkê· ŒIß°Á³C®îT©5sD Ô‡$Q¨äA´aéÂfß+kCÂè19! D1“Š<% -mÄ:`XÅéLMj2§B"Û ¨J™åº%FE¹ûéa껣-å@ƒ%þ«ªÖÜ=¼ÌFzCôÖ©/<3‘\µ{xYÛ2J:æ$ Ë/)þž4°ÙË9:®ó:W"_Á?ápžNâ é+¥e|rí}&yÀ"þÔé!‚<¯] Z¦ŠRDʇ`-)j Uªõ¾Qç¶Œ$ÜM |$I”®ù¶Çã<2Ü9(K;5xp>vþL ¡g˲«¼ˆ©-²·wëÐWÚTNÄ Œ“‚ÂY[4¹6ÛG¦¸Z%‰6dSÎ ¡eH.t? ÑEe(ê×*1pYQf‡²ùT{Esz!o†ÃöJöL_‘m,sš°®0+Út,ÈÈ…’|]”f^ò^DäX‡ÓÙìÓže£$ànDZuã¹ð#j|ØÒÆÙLIQe…¾ªéw­û§Tœ"Øã1D0-½&0§±°ª ìæ¥ó«ÍÍ ÁFˆÁì`‘äûSýœå^XoÁÕÇCÕ\upö.Îï9jCԢ뫋ýI”cO>­z¥I´ù{1%üÌç=69cdŠBZ·íË´™"Ë•ý+·J{o2[©wç3þõ\Çç!QTö0 ÕÏKr|áÛIL]£2œz«¨ð@| ×Jº\#N̦AT1ôJͨ"Ù™|w(Ö)G‚쎊ìs í`9Ï}æb¥é¾ë¨«…’ˆt`̾7Àó2EG¨‰óøWUÙþºv¾ü§gyb”›GüAn.¹=æÀ -–·Ù'6¥(É•ä'X› uWk¾éå↱á&ºÞL¼ 'Ø+ÚdjµµæøCÛîÔFv¥çÚb¦/êH€É[WAFGçDß­~„WØ,n#8À¦Ç=Lí{ ®û ªù96JbD¿àâM8þËt•cëbt©n—€{9ìoßvp(ÂêZyfíL‰2O1îbœúGk.h<Ú&¦$7ÞÙ›¶šÞ5ÝN¢ nÝtt!IaǧԫlÖ¡-î8UBGòÒùÈŒ‹Þ>ëýs<;t†x®Y‘ú[ !¥\–PÃ" ÂÇÇ%K-'Se FèëlÆ~Kùðü£Ø¯é¡² ?꤄°0MYˆQøXóÀ kø©³'øÓ‰jMi¾ø¡g˜Èª#0|Ð…œ?w麵Ó bÓx>–ÛÍAšÆå rZ×…WÇîÕö<ÕT=$£HmÈ®[Ë~·H N³œµDz’[ó¿Á–ûͳžxsËM·ò´ž± •/”¯ÃÀÊ4fõY½‹ý¤ƒ”€–ŸðÝŒ¢I½ê6t­µlÉ~ñ£Íõ1õjN>ࢎyý,LUËŠ ×Ÿ²b7»îÇúŽDô¤ÈâîBxdÒøºÕç†L(&˜L|#‰ÖÁΘºEaZû¢ªÃ“a•ƒe2€å«BdàêF„?Ï?,P0=΄rSÊ÷`û2Ë_0aê³rT3PÕ¼ }\€œßywÚãGÅ™…ò¦N€:½›œÀkJv<%Å<§l\Ûg»ü¨Æ/¼'Ò¥(.Å©ªíª5>JhJjÄÅóÿA꤇iìvpDÞÖÜ™O1wë²õÁð¤eUá§r¦©¯0]Scš¯ˆăëö̗͘Â)0"ƒp[ÎѱB#†Ö·5vßïÔ¡ÿsX"‡M¿Y}{R³Á‹³ŸªD–°wìw*ƒ×ïê @ÐñQ‚¾eIa,=Ýâö¶T­ú eÍ.§²¡h„h|u³ýŠ€ñJK©³¸–eM&‰»îȧ´©¬lj ͪŒ¿~ü?"ÅT endstream endobj 3 0 obj 5025 endobj 5 0 obj <> stream xœ½=Ën$7’w}E†of‚•ZZ`n^7°cn3ÞÅÀ^`}™ß_ƃdð‘ɪnµaX]¬$ƒd0ÞÌROúòï‡ÿ»¨‹JŸüîŸÌesúi»üñχÿúËåéYúïÿ~¸~}ð!=ŠÑ¤Î_ÿqù뇾hwùúë/Ï:¾ège_³VjyÔÏz{y´Ïêÿ~Áo¦o7ÿúò÷¯{xÿúðÓtrohjwц¦v/æY½«ðòèŸUL3õ¬<ü ­Ý³Ö/éóþÕ/:=§ÞWm¸_îÿö²ñCý‘>òÈêÿºÒN{¥®i.š‘çzåyöŬW@‹ö[ýF㦦˜ˆ¾6ðÇÂ7ÏÊaËì.f.€ôõ_ÇØµÛÓ€ßMâ!äM¢p†—§ñ‘¡/u¨_šyü+`×”ƒÒ[íE(èÇ©÷ŒYcë“k2¨ hïU_Ûñz£šû¤/ÅJÓô†§ÀEY$KI!ôÍ«Üü9ªÜ­mÜ“ë KV„B&+å-ÀîÒ¿‰—>胋Ä^+\÷U]ñË}ÅIÊ=…~zÚ:¡•à“2V-ua3«UýX™OÛ@(ùÌy­ • މ„d^,qmNÊÓ¡Í á1!i^ ~å~©¿IÌaœiŒZP0Šeõا9`gqšaWéo‡ÎˆëÊÄ¥>8Âoiùy;Då¢FZ´ŽÜœ­2Fb Üë<¢$‰"½ ŽÎTŸY§Z†)ÁЙ7¤zŠÎ;+tÆ8“C@hY4¤ÆózÜ.h$-ßVQ™h°ÈqãlÂ’CwŠXQ—$òHʃv3N—!:¡âà PÛ–Úˆø‚Žïwv@Å sÁ2üBLÌd6âãcÖª¸ƒ÷]§€H<à÷ùùœñk˜b±˜Ô9V€UÄ#†œdsàyœ#oÀÆ }nÏRÙôÅtöæ™þÄ´ó´)—ðÁôû–Œ$¬žÈ$3w]…)PIûX¾É„¤™ðÙª™Üð<“+sé ŽÄ- ‘Ô)V(ùhxÔ‰´:%Z3Ùù ïÖêȾ¯Ûdî“\GòöëÌ/ØáššCzòJtH‰ ZêéQ<¢LaùR4Èõ%D«‘&äÓŒez’¢¼bb}´s3Wœ#pÀÇ F ¨±¡Ø !£Rä½%C êhÃû<¢—öL{!ÛK™é‰G5$`1J ‹u]>ÞnH_YM%`»Ügª¸.ð: i…WuŽU浬ªð«äøj%2›‰~:ïîJ¨¢Ý·´Åh†xwò«+iñr¾Èꃺ„bÜmÍ—·˜rÍB.•½º ÁM-²7vwZÖ¾W[ˆâ½ì q`ýk«fM«ŠÁQ& &Í‚(^ùÍVff©D«JGµÖ5Èvd«ºRü΢‰kž‹*÷QÎé³#ªV¸ÝÔ¨€>ŠaF–ÏNR¡dW<â"u³ß–úX^yñµØêÖ¡º d|f×,TìÔ¹ª[a'XÐ\&±©¢¢QGêûT´Ññ)NQ¤ØCuÅkETQ’1aªë #‰Óßç;¬eu¾!9qW&;&°$²·Êƒ™–ùFw‰Ìc™Ä7Ø€ä­tßr˼½;ðQ: ŒlwâUa\WÚåÒ€,²qìÿ¥JÒ–ùïI#¢V˜õfT§¶5ƒÙ—»MUÎŽ‡’Ë<›wûžxf"Ùß[ebì è;oœêz60ƒ)…IƒÖIVïÙ+š=[D5´ñåÜ|±°B›Ýo i಼Œ'Ñ.öÆó:öZ‰­6ê¤Ù“e+ÎŒÆ=çÈwA25™+ Çò© Óì§‹ÆÿÒcHtd£J2C'ëÞ_´ßÓç?þyùõ/*6ý´§®6a°S–üò·¥¨r¼ÀÓöÜaOÀUî©ÛNCÙDß÷¶N}¡Á £Æííè/3\ñãN:Q6ÓíÁ`ëmÂD{âHº.ÈåfÏÝ÷bÿ¼ÏlO³©fpqC7³vjšÁÅÜÊ¢¢q2 ø}Q ¤»èŽ7%rõIy2‘ å©ÃÐ|çrŽÉm¦SñìýöίPôÉG†]zöš· n%ùØ#§2n'AæÈ01»ÙåAå2Œ¡°P&§(4@fØš—”õð‘•bäj'hËnÞ‡z»£9”I™1t+ÊvQµPR…¹Pü‰8 W"t‘ЧlZ¼XPÍ|Ž@Y5ߤè²WtT¥q4†$Íf`©>ADn(À9‰w4Yè+8Ò8™ou¤~ÛÆ˜1’È9 ùËëèÃ[CÚ´£ðR963L¤Â+ÎöìÅîF®«žl©h8v(œró=årUɇ#¶ÚRëdú/ÒáôìÆU/Ï.žû@úÔoµF% À8ϵ‘qwÙt]7އä0 ù.ŽB)ÝG¡¥LIøçp̵ù~YU T»jýE¤òKM²J£S<|æñÖ(+ŸxЃ!P´{ÕÉ ÞÉ#Ô{‚2Õ]·ü£sŒï­X„4ø°<×&‘Ö,¡­Úõ ÇèœiÞwnØ|ýÄgæ°wcóf×¶‰™rPÂÖÒ!–´ˆhœɰ¿å¡¸8jáC‚×;ç}¬S³á ê\IÒU Ö:“"\’<¶‹*É}§r˜*‡™%¼,×b‚÷±D©_qHaýbJ‡„@úZbySîtŽÕ[¢åo‘)æ$Ôc;‚í¹„x(5l9ÃE­¤$R5•„ë‚0$ÐÔÌu6.dÚ°MI‡î¢<]a~I•¬ ÀÃòTô>êY*ìáÚ³>í¼qųÅ̰~¯u?™?Ûb!¡P·ểS~ë*‡Î˜îÈbð<•Ù^<:ºÔz3+ˆ¬ÖÌm‡þ&„Ør‰’•fBu³øj*ÎOn˜gyrjÂM2zêZy‘K¬Û3å¬~„Cõt¦Šu/ÃßY Õî>NBÈØ±ƒ+ýÖ<¯!S_¾CMnÁåP'QA3ñX'EÁ­Cô‚“Ñ# ~Ç ãúŸ†Ûͤ˜Œ–X4Y¥ò*Nòµ‚ØeíŽnkèºtd…XK]êáZ_',%k9Ž–«GšuÊð*¦0EXö¢õ 'DÇÊcÓ×ðeõrÊ#—8Û¨³ªóY´såÇŽ"Ó65’..dRô"nÜUä7¬D¤d´ª‚½Öœ>ç2‘eÖ¨)ü60Oìè&jqâp¼$Ê;7œŽÜLçV†A#Œ6%_Wày£»Å¥<H9Šýv泌~Ñ_D­×DzA©DsS š]¼9Gˆ$’Ή{˜bIÜÎ-ʪ™íâ@ͳÓ|\ìèú}QÎÚÆb0ÂÇ]ÂŒTžäN‹hÎ ÜzΪƒÝêšqHàK$‘å“í#‰³Ë"ÐÁU3Úš'YÀã’—Gj‡„N§!eaÊ¡&D9F'·&€ÊΖ*n@çSV„c!ÚŽ*Õæ¯dM¡!™}¼Í_³¡‡±3(™-[®!ï/—©œÇXÆÈæg}Zõ¬“ÊÞšÿáƒÒqTæ2ˆ–‹¢täô#ɯo¸Ô›LJk×4™ñ ‰ó44ßÓÉËžFDÚÛfŽ‘¥(çØ°·Ä7Ƈ„Ûc»0¾„˜kËð"/o¬Å‚äöÆ-´å%^]mÊC•g”Õì/ ½Ühí’²” *qÊíµà|«ô4HUŒäsì¸Za×&?¦W˜… Eþ ½€$fõ‰è³®ƒX¥õ[#dãáµÏ.óžm…È>Ã_]ZªØ˜(8j¥Œ*W%!?¡ $kúâ½/"«Q³Üç{è@Fd,$úQµŸ$4…å¸Ò±±Dd¾Ò¨´Jã!Ÿ UÄᢵ}ÚçùJçŸ|ê[êë*Ù|ëw¼¤" ö½—ò·§/ÕH}wä CÕE+DeTcccšÎåï‡l4#Ø´bµÃ2µÖV fn}?A¨åˆ2K‰ˆlE…çÊ”¸°\D5Ê– 0ó}ë#Ûð4µ8ârEØÆ˜±pBÞf1Ì|,lÁæ]VZ Ê “Q”·/­`8w< °ÞPõ gVŒ±šÞ°¼nWÈ*šI×3Šb+¡•XMC§bhÜÝò´Ô6Öj4‰p "A:»D¬[x\{©zÛ†b É.æµ­¸Œ˜‰…ò´c±.Ñk¾W§º93v’ =a§#~).È,*Xsï9ê\j˜S-{»—ažJí¾êÁòÚÓ†%ZoòžkÛ{‚Ÿõ¸8»@ ãHÅGhÞRœsé–÷•÷qÍ8©£ŠŽf Ó:gáétÙåE=9¹#¯»~O±FRúGsÂi6×òO_ÂC†ähyJv¼Ý÷mñËeÃÌ0çï=÷;Üpcœ® Ë åm.9Ç ºÐ~<ÍX¹¥´b¶¾LIJ{[B±\:zo«G¿¢ºÕïÙW×Ëtåmy¦zríîæºÃÃx„Ÿ‚>¾h½š/…}©*)Ì–š‹éËJ>éÕYd”"ówáæô#‡Š#9ïÓ¿ÔØZy)Ó9ŸOŽaAjrÅÅèGeÍYPýÜÙ¼]zêDÐÔ™ÍÔàoåÎ-+#ÎóÚZᔓt2¥ªe{Ć¿L¸ŠiîN®‰±5¾xБ hYÙ>¹ðgY Ë—¾7´²±z!§Uº5Â×Õ*TAß^@ÙËU‹r‘¢OrD¼O'Ƭ‚’íâÎ)¸S2ÁE’{}¤ÔDÓª3xŒ¹Sa<Œ[bœÞ¢ç‘ „©X®£+¾´"‰ïðe7’ì÷òÒ«M”¾µƒD÷z˜cÅ«{ÁIoŽ;;F…‹jŠˆþJ°ØæÌ‹ïʸB㨕 ½cÙ\Å?Þï­µþ:ÖçÛ3âûq zçÐ4FKev¥wú·†M’qt~Þ}9™y³Í*}p¾#ç ¾oyc%æF±U™¢%Q+Þ®Ãräa—ˆ½6 ÈƒtÏï¹¥ãå·Ë/ë‘”Žývá>m Çÿöð?¿þå–òŸÿ«úwúÿoiª]~¹è'çû?OÏÿñ髸yÀI2Ç.:5u"ìÆ +üþð×pÙ9Ów³¦ïf¦Ý˜$²›v÷ÊnnÖÍaD\vóÓnò;²[˜uóÃâ´Û°…mN-1!8(“‚‹+ÔJg@­`à ’å¾%•ßµ¨gå·Ë Íü ùFêÐÉœM°œÊ96Â×Õ:Ï©(ò¿CšìÊ+AÈ1&à™~eÃ[EË¥1mK}?C}Þ*Ö EWR– µ›¾Û”¡0’µf(|OÁ’¡¼Â·c­Ê«A&ÌŠ\Ÿ%Cy=lᘡ6_É{ó…¸=žA&ç¶ÅýÊøSZùäyŽÈ+ýÝì³YäD@^Z*¾ß–ZiØ‚j:ZFY¬ •-îÙ@YH˜2ßAÑ#ÒU‡ +‹BÆÏaîÿç–WM_ØF˜9Ê!BÏÁi˜zyx*aZÓi‰s*XÔ6Š‚ˆOÂæðk§½ÿüP&ôÙLsI½@—@”å! êvE‚¼ôë &qøB£2‰÷(Æi¨9膠Q›ÁD8­W(ídOžªÐqX£iwÔ4Æ(þœN:}¦¦Ï/Â5­ÒO@8gÆÏŸk¤7c í:¡¬÷c6´@-ÚæÆxÊ@å :,\»Ï­æp ¥0(˜Ù[aÇS«ýzô'Æÿë÷ÜÚ<”W¾ýl6D÷j[áÛõæç¯c¢9Ÿ”ÒѲHê‚n‡:™¸m”¦nΆĭWn¯Ë!¡·CÍrˆî5­] {¯NÝrˆéö¢×CT¿—åöýÖkæåö½øgCtoÅ/÷â\¿¿ÒcŒm–“!¶ß¾^Îb»9ÞGKÆá»½"zB‰s܆?Bb˜[‘ԣÌ}×¢ž ”sÅýcæ¹jê#Ǫ̀'_¹øÛAèø’¿wé°Ê[ã ñúÂo²$«­$ŠíoÍoàÔ:» <݈µá¿?Ø[£Öonñví:¢kQÏÊ9²Ì|#²á¥¢Å@üdÛ¯¼ˆõÍ”Eù©{lQ^¯rhYð>±UvÍ„Ãûl[Ô³r–?y¾C,›rê»°¬í÷ã~Ç"–=S+ï9ì-nÚe×¢ž ”[püÙóá8l3óún‡hzÁq–É( ÑK³F9äQ4eÄçà(6ÀcÚBø^óê3×qŸy¥lgÅè}¡úÂ> Ë!®W°+c~@ÈÜgÅ„­ BèõÛW×Õ¸w6ÌÒ 1ô†âraÑõÆÕÊ qÀ×ʼn¹Ã}öXªßËÊR ¾ÇØÒ ¾ßþÒ ~p-²à=º™dQ+[HY¸‘MÔµ¨gå‹ì³ç;²ÈBŒßo‘…°}’Eæ4\(’´{²~;m×dïH´ØB’PÈþ!óM­Ì¡ñûH?$¸a|lâ{a£5›DÒÐÉÛÌ~7m¬m±I$¡Ü„ÖOžï­z{êã¥ß†VµåòŠ»ÐZ¬ iÛdKgÛʶšÏÜKB8v}þ\st „»Ñ¹ “0/7›5vËŠ ’±, 6Š&s+j),D‹[&4¼µú{v_±•ÆQË´”äÔ­¯ð' P F¯¸uâóÔ96f·åwY;Á¾"¾')ï2z#tцÁ· ;°Gë Òì0F)GïÆŒ;Žç'Q“äY‰ ñYa/<…?m ïÁaBì½`2öñ7‹>äÎqðÛ«¾,›ZyAYbÑÒ£Qò™6äBr‹ÈŠa¶-†¹FàŸ¹–#DF=І[QÄâƒXëS^zÐõ‰7âüs ˜¡1µWØ7¡ðOXÃ!êü¨¤–¨s¢’&PRÍb–™Z ò¡eñMJI ñÛ¶¸gåMç.çYËÏ ¥ oZ¦0ª¼P+wÞv­<…„² Š2ß„,䞬.?)ø1 zÃÖ÷ 0·øHÿy®¹ Mʲ£(kŸ¢‡…a N[.²Z³kQÏÊÂü!óÍ —ˆ¿.ÓGwvŽíðU¼rS,'u±V ¦åo\õ ¸ïh%Ђ©U–o)|^°lqÏÊ-èúìùѥʫ¾aïÇ‹}WÞ¶òàUåZ,¹iQÏÊMûäùfK,^vï[Ðõñ‚÷'8à p”à‚:ú1lUè·¡ ˜§AM5Ÿs/aáD|ú\\m³¶ö{3=›C \Ý‚ôRñQ 7ä¯luÎrž¶™å5·˜Wh«]+«.eA”?d¾#D›Í?šM}ÉŽh.H`%©ÁK¼¸yþÜø5l*”NKµÚ‘„£–bÛ^ô](TŽêf¸•1LÚ¶üÖ—»äŸð ‰ÏïLá­™]Ò»#,‚y+P³•årËö •hÉEËYöò©@öò¨eÙË=ç 1›Å` MM­¼ÒÂ…äÖ° ‚rª€Zq¯äËp?]þVH>õ endstream endobj 6 0 obj 6619 endobj 8 0 obj <> stream xœÍ]K$7r¾÷¯¨óÝæ+™$Ph ª§ËÀÞd àƒá›-ÂÈ€÷²ßdD0|d²F«ŒÅÎ +™||ŒwSêM_þþò¿uQé_[ÜÞÌ%8ý.ûÏ—ýËåðYúßßþëåþõeóéѾ»Ôùë\þéá.Ú\¾þòoWõåýÕ\Õç{¸jý®¯Ú¼¿ºxU[úK_Uxÿ÷¯}ùüúòS7`4i¨é€éÍFŒé]­`¼=ýjë¨þýu»ª==ÙóoShë­c"ÍqKcø«Jƒ‡x6ÐŽ ïçÍ«{^'¢‘ל·p<`´oÛ|À;¨€¯.pK$i½>/Þä=ä H°«VG“š´‹ƒ3$Èð˜y&Ãu :?ºDDãøùͯ¿ÎÞ@¢òêÍö zàNÊö aípVÞÁbv'Á+øÍ{D!wÛpØ~ÀNˆJÓ?õá6´SÓU]õí`ÚÙ£}¤%iã®Úå…|N— „Ë»áFy·Ž;ÁFÂ>xøkÝS>¨Ü™°AÆ:§Žò©|÷ü@G$ê|"îˆû„­) *ÓH íhü È-ëè ÄG9ÅôÒžðù·Ü@&·$Õåñ¼í±7H«7MÖ¶-5&Íú1Ðe¤“A ¬Í]‡ò§ÑÑòP_ MëÊìÁSæ»zB+Óc„j…m²Äæ&ºÐ̱–¤/YŽk´G¶Ñ©I¿E”Á¦ÙyîºMpÙÊŒ¦LˆÐÙßâŠgÑ>Û¢±ƒ™)ä Õ-5ÃP39æ"k¤ØKVÊ—ìG’f“E2äj'Ô`êðiî±g ·66jåµryœ½ZåúO¬ªø˜ëÑBg®f?GKÅQk€ x6a‰T-zŸ·jŸ‚Ò¿IH„TÙ®ZK–¬Ð ²ðf4¨ïew¨ø›%ž…æ(6ÍÌùAþ¨®¹iî5Ž-LÔjÀîE¡ß’xÈà Ì25îÊ‚ "ÃÄêê.]žr·ŽAMfB>…Y žQÀüˆÒFÚ¿jÖÆvnƒ%Ô”¶¹—”]Áƒ© H‘‘áøMÔ¡‚½Ép«þ’F»×|+ë]b•¤sÒBŽ¥ƒ Égúžb8BjREf?dLéXìÖ²Kcaù‘8Ñ8èßäÃ̦4¥–fU‹ÄqJð(iw˜Õ7}³mUP9VñÓ"žàüŽR4‚sR‹V¢#ÂÛ°QÙHÆ0ýq´2šç8œˆ4Í)X YKm¤¬ñTñ {‘štdŒOËûÈoð!Ô‘IÙ7–ÅPÒS£²ÿfBgŽ"³åб- :éåj3L<¸°œƒRD¿á;9<…YlâOO©W lÂ÷Ä:·ú<ïÖ5¤ºøÚÚOc¦FL/ª˜îÐl—ÝŠï,×4+ 9w€j®ñ¨¤»äxu‰X# Ò>º”X`91h·²Û&ì˜Ðõç¦B„ ÏÍ.†©ÅP}Ïü¤†ÞÙÒ¦.¹?înÜSÈG—'v°ÙDV 1Lòl+’—¢kOñ”$ÕÀJgLq½ãˆ«îR–SMßzêäƒå¿¥šàèè^ŸÏÜ,ÞúMçÈ8-‘M>ë ísç-GÚ¹~š³ Ù¶4É£9¿sžKñj©Ÿü娠s¬Ùh^›(„Zy;}E³Ò’RÚ‰âÃÆ¸9GvÀi‰lò[ývoL£6î7¯Öaµöœ•)‰0{&´Ó<¶v™%OõWo ­äÕÕ@äAI“ ~°LK`ëA¢`šœ”EùËõ-¸Æ"Õ¼kŸJw£–ü{ß ËZ¹þ¤U'æ1œþæ:>dQ£ŸEµ˜˜+!Ä`­a•+SpÄm‰´U3M·ÕŒj¹2íñ‰,@Wo1F/ŠþöáÈäF4…ÏSGBuI¸h]?©_ ›Ä¸ïª_èö‡ÖÉøaå|=‰SÕrŠTêë¡1+de±a±ÜX¼AñQa%²xïÈ‚Ì6¬Êxèç)\(5/Å # ›³ØÁ8€²„QMÂURiR«”Ú¤Yz«õ†SSØQhÍšd^ Ã;Ç”ŽëÙ"Ý«hVkU¥ÂÆ+0æ£ê϶üëN÷Ùˆiž“uµþë“í)™;¯–+wZ~1ÕR2Ý'µLIbá.,Y®Yˆ,¬¢ËœÜ@ú\Ñßæ'—e>«§ H:©ZB_µDë ×Él‰”¦D:%*£kÎʲiëixžüw àÈX9y¯ÐQfWà‡¾ì‘‰‰`Í}×u¡¨žF –ÐnÛ"è,³©ŒU¶p•¨rdNùçð%GÐô.r|²H¯%Ÿ³ž]—y˜ƒÌ§ée½ö6ݳ,¹ñ€Ó›v2uyoæ¿‹š9|ý½ŠxÎUFË£7·iŸ+¸ÈY™%¥«Hç¸uq}™¡o¢o]¡ZYEæÎEN—kßš”èw=S ’©Y†ñh+fyçœ6€·„[M|™¾¨õ0%%ƒ)WѺjXùl¦/šYfR+,EKðÌ0ÜiQ[·¨>Ù¶OfŸ8 ç<à³BÔÅщ¡]»bH¤dm,‘b_ vW2Tv¨XJ2&PU5$¡Tµ  ÊV½”?ïÇ<´3O·½*L|﻽[“òrײz äHúó¹ð0–ø[‹æ? *êWî½Ü™¢EþjK6’µÏ Û Jú%»·oTyH÷MﵪaãÂ]|t"µmï a!ñ™¥3MNSˆÌ +-ʲÞ[ÛÏÞš’ÅŒtú©ž¦AèÓÜQ˜ pB]ˆ››» 8NNúâCQ+«:¤Ø­p4§v\nÕ[uÐdLGå«Ã%Ê…uˆMGcðÒ€O «qÏK”ì¨mÏë&š=OBEI5öü¡[ÞÖž¨nC::t^G‰øêaΣÒÄî’Ÿ(m\€:@´U¯uj¨'°h · #’YpØž3O3t †lô¥òæI1$Þà=5¢å ç„M#§i‰¢’%+ô[ÚÇ;æPј:’WEœ± ~)éÜVhÃU,Hì*Èìšâ}I_ 5-Ý.²íôüǯÎ߆Á-˜ë8Ü$Í $ Ëêgd$ª7ßw¤¥ Ø÷­&Êg°jʘk Ë»EüU,ú- °©—þ¬Ã £R„vâ*Å'óM3åy% Û]ž¢ cM¯1|ˆ[ãk?ýd(L[!€À¨–/ß›KèQ?é{ ()œQ¶+œ£2ìr‰‹uce®¸t_ëOòuóE ú"ÜOH>Êž³+(+µ‡8‡H¢µô+ =4ÝŇÞÝ•ÚU€wÜð"£†:Û&ˆˆ7Qæ×Äž²®¦×Ééàf I¬œrÎ>ÒÜÖÃOžýß«²>Ehé³YË¢Š¶»HÚ¤cg,vQö³‰CŠwò¤to´òˆJ|ƒÄumý¥çÃõš!ßeBdíÀ,QO×þù“)`îˆ/¸H§Xv>P:§„q׫£4»™^?üÑïLcEoJï…#(Ü®è+@²–Yp =MËl4ÔB ×Þ§¸†ƒÖ1v³™™+áËQˆ›ôÈO è¬Îš'˜kC²£Ô\õ÷ e™ˆ¼85‰|J å*á‚aw£‡Óz÷þr#”#_4%j€Yùü&½7øÓ˜é.|ˆ¼¿6 ÒO| ¿¥pT§á6H”·³×DRtƯ"ÝBžËà€ˆo ðù&MsF à—À7ù–Q7ŸŠê—]†‹tÈ”ìÊÒwan„ÇÞpf½/³â¶´ˆ"‚j¹ÎµãJÀ-•‹Gò°mF“.ßS)yâ‹÷QÁkÂ×(/Ç[žêÃïÑZv5è©ZL\bOy$üA¬ü€Tà+ܶàÒyb>]Ú;|K§‹^Ôhþ*"|âÖË–ïæ£€åÁÉP8%²qó+"K‚£'1ᦠlº;òÄPMIã«ÄHSúÑÝ€%jMãfˆL CNˆþã"¦¯ÉäK‹‚AÜkþDðìÈ”{èUìœÇö¨%°Î¾ˆùx‡ˆnÖåu“ÜÆôº:r*ÏàÎ[IGñWkfê¶d ©ƒ>ëòU€ìQS9‘©ú°ûF_ø/×R¬Z¹™'«`F–°š8¸2OøÀåÓ9Þ“²Åè{•^Ü‘’›“—<8lÌ•äŽ4Ù.6ä|`wýLÍL¢éѰ¶‹Â™sP–0êmæîÈÏ=É‹"7ùÑä" …ÈêªÙGEŒ©+¹Ât§R”»ô®–/i–ú]Ôcÿ‘ŽœU”ÈMŒÐ'Š÷GÈ– +=ºL"¤É—œ'ßšuêüÅWÖËcÁIS£ð;êöÙ•œ­`Zµß*Eüˆ¦¬Ç¯vÉ9ªF+Té¨*¥H?—Òq#;r¹‡™=D–egyJßù)qRdùºlÁ×zÍΕìrÎ5Ø]”’,5 Gß·>…8|¾»ïŠoÊ8hÑW[kUª‰> ˆŽ`H]°úƒíøú¹Ø­)Y·ˆúeþŽ¿¡]Žç{ÓŸ¿½$²Mò[ß.ØJVFÚlj…Ðü›{‰¾½ü÷Ë/yA¬þ埶¿§ÿÿ5Möë˜ëçáh4´¼vÅÎö—@uåú2vNÂÂ=Ì2vOb̃o‘V«MZiÐÚUþZEjùüYŒ¶E=›Q¾]NÑù!óM2ù.ÓnÁGâóL^;Ââß[¦alöýM_ü®ÒB½|Ks¦%)XR4o]»ðkçHü£ûö™$|0ýƪÑ“Þðè IŠ÷ntŽ*{Óp¿½•¿—ƒ­ojAò?µ¬…[ M {6£œ£ñcæñIÖöÅ{x=p²ŽˆÉ8æ%%¢FR¸À²„J{{UGöŠzêÍÀõ‡-?‹;<óðŒ·LØùÒ%ß*ɹIßzD,c¿ çD“8'£cþ枇[o³‰ c§n «‘ycÎj{øÔ+O%…EOvIôùJÐâ÷lLûäƒo[سå)²ûƒç; ;öÍ3dw›]’jéìðÀ#ü3Ø·îÄ£¡KY;¥‰÷ˆÇ Tz°Ã!ˆ^ÃàÞ€dŒE’ÙˆÙ AËEXLÛ‚ž/f³@µ[}²9dfl Óxòߥ¯á\Ùþÿ[ëäì-°Ì¡Ð\ñ>“Á»)˜ÿ³/¿Á27j¥I±EhB¹a&ZسeÁ ?d¾ >_ëL¢ªÄ3ÙzQ†²µÚY°|U¸í%Úáj© .ÛáÀWê‘ÌòCU³ªeQñb«ixÒm‹z6£<%sþàù¦2zì%ÂÏFÐ#b)@L2'‰ŸQÜÐ5Ui¹AôóR°›±äò’!!-ѯ:>ý–Ôeõᲃ­lç–…¯§ƒ¥œж¨g3ÊÂý!óMìPü°¶=‘ Ý(4CO7H¹ñ4A‚ M‹ÿ<Ì=èM¥•š¸õ¢ê Ë­¢† =Æ£'lã³:3Þá©»ÚÊ‚3·ìF„4 ([Ô³eÁ6?d¾ Û¸ÔU¡÷­à˜ÚÀ4ºq®Ž$”˲ÉÄØ ºrL,¦þ­D׊Mf¸›üyi›™¹Ü©ñ}·ZÂQ Ü©hCç¹õíRZ;²½1LRøÄĵ#öSôŽØ¶hÄSÒÿ“Wòsƒ½C“çÿÄÞÎ]K '' EÓã³2=õ,â˜më þôµ4PàŠt5~Ï•D6"Íf@‹·-ÒÔbˆó­þñs¢d–ÙûØ¢èÔVöžÔï¡QT[Rç>ö~å?¢_`õYØllvÛyÃÍ¿©“xýÜuûc癀l TH+_ðË£¯þ¤Z&€ñeÕ3üÑ@ü†?CÙY㯎 ñrî³(6Qüß PZh9û& ²V>3c§Ô¢÷p̶EcžG¹ÿìµü> stream xœµ]Io$»‘¾÷¯ÐÙ€4É-@P%© øæq>s{ ãy€yÿ}3V—LªínUfrýŒ|Ë‹{úÇ·ÿ{Zž–ü+éÅ?íѽìO¿þùÛóô¿ô-ÿï×ÿùvÿþm‹ùÓ¶ù—ãéû?ýÇ#=9ÿôý/¯‹{ûþ·oŸß¿ý¾©pøºB¤ z ïoûër{Þ^Ýž:÷öì]|]Rþ»l¯Ë_®ô} ËrOþuY៻óXýöö쎣ÔÄùSz]¶üêuyo ìoîÕ-¹þ³Ü—(¦Õ…¹ð £ÜšôÏíÿ×÷ßfìøS†¯0JG^#ê[0rqIî ·R~üÌB°[2®K’1&·i 's¶ðÀO笥óЂÜÎfHDöL+?B¥‚N0ƲÈ<6®ÏSÃgû Gy[iÙ‡ëF‹¾{ó™¿l›¾“U/Äð¼ò•+K΃‚µXÞ2ÐÐ$Ð+–øäyx¶ãÊ¡:¤­k¢é š/±%‡=:3:YÚõk+»iô‹+[*”í½¾¥ x¢˜Å9k&Ë·´CѼH¾¯îˆ[9ƒÐ÷¡„ÀSC’Þ è¹ø—8ÄWû (¤©<˜ƒHDGFldy¿„&­y¦ëz¼l4S÷ä"Î4o>œO€µ~ÎÿFl=Ó(*¼8`fÈ„äw|‹ÐëeWñxI=¨4ðçÀ☊#Ñx<^u¾Ráxõá —kàÌ=ìBøû¼l¯îêrŽÏ ŒÔ¯é€6(â™gœ6Ûm nBHZ 9wi$ÙP Ø‘t³ÃH.7LÒù†!TC’TTóÌW᲋ìt¢<³ÅïY>å½Ôºñ¶0}T s – PCY²q¬-ßüðv*ØL©î2ÿð’ö‰ß<||Éôí3fÓC1Ãλ^Š#E¢üÁ¡0föç‚ÇÎEà{»¹-cU¶à˜Céð·ÔRovX~¬<ÇÖ›@­å?ïÍææÇüçðk0º¹ÍÀX]oQP ÍZ@éI× ¿spG=bÈM)^; ](%d@"Jà7R¬(®(¶CY^Ìï˜Â¡õ!é×Fn% ÄéENŠKµ¢GikÜ:¾NNÌ&Vó·K\Pj"?µGJ]ä MÉ>ÌĚ܃7âNËf\Yv`ÆûRl·SLÓñò¥€‡@4Ã4„‘KËÿÀjYåf+´eôðF-›–²8ÜdˆsU´8%ëRßê3©?cg0Á¡æ–2:zîp”>üüЦX»À¦>ˆ#ñ¢^ƒÚa4Õ/WF#ñBK/¦î&ŽlâÜ8±Ä[Òú0?‡üÍ—†Ò%wa±q­ˆ„¥×é™qÿ?p!ô’åP³6J0p›²þ`ÙZ‰4Q,X}uh|h×;×0C {Ölh6E.ZÇsqá˜ØÉ¨õš…u L(Ãï½Æ^‘Ã1ðnVÐ7lB-·ž90ßYg—R¾Ø¥âZw¼Éð;ª*n1†ï•OáoÅSU5\Ë Ç;³é…Å8ãh=b3ˆ·^ûϲñVÔQQ;b½óÕéÊ_YÙ×,÷0Ö§½†-Ñ‹Ëν c¢w1¢®bÛU½Ó‘'_’c?õViﹿaK6©E<õ.‰;쨽ø"ô8‡Ë&TÞ÷:’ÛBcö±ãø”ðžö±­Õ†ÊÇ8†aZökì`šáÓX¨ìôÅÐÙ‰?ƒ5xgkÓJM ±^3çÌÁ$;©äìdº‰¸It‹â•“sü¢d~ûÆç¸àÂ_(×ç†ÿbO¡GLüAI*íSâU¾iÖ¿º ¹Nÿ ̆Ãjã`ãÜÈÀaÒ –M%A Ur/ dyþ%oj­‡Sl¶OÂZM"A;Š/ç§øÂrÛÖ'íh=jŸbjˆÖcc ¬Â<¸Mª7ÚõÙ!÷Z"ª@·Q#ô#›ž‘ö\/ö/yZ?ãDkxY¯E¬˜qóO4G‹V¨ã²×ÛÑe…ÑëvĈ;˜ÃÉõWþ÷þÁf»Æ…>„2Ô Äà@ÿ{‘yè¥7fЉÁ•,;ØîÅ(lÒÄ$+”àjPŒPÖÎ!‰˜0´?u¤á]Õâ0Þ¹Kõ< À™PÈqœDIlŽP2z’Õ¦4v!ê LBc§dÝG+S¡±OÛ>yñPÆÂÌc«üwP)ÍT{ZÌ”µ+;Nf>ž† lYëdš±¸ve:ÖnY_éwåðµö¡‚*sÄIIÜB¤Žz”§¢"ùB1˜¼Ã’ß8ÿax¢ŸšúL>¢xó´s¬ù³I„Åa*|6 w͹´9,Éø#N÷ή ɦ=4¥JmÜ6[ÌB†¥Àƒ&}Ó’*YÝß41ÞŠ¥÷]PúŒ=…=o¾~úç´Ä€­ƒ‘I\ÎK¦T>ZõÔ,ú®kž×zµNE·ª’Åà”töÊ?]v¬Iø-Éú20xh:,¯ƒÃ.}ž{¤¢}òtÅž¥§sØËç1ÿß´¶ÉM¸™ õ* û® n•V“¥qÎnìÇÅRæDIÍ:ÅÊJV‚ !™/×4Õ¡4¥)?ÈU ƒx‘LÁ[¡|%§LZ¢ K_Ò¾QO ÁÔ$ÅÝW5µG:¶bS–lª›]¡ðQ ïÓœãqeƒ¿¼~¯®×w€M!vƒSN‘Ž.Q—òÆ«ÿÔÉàñœŽ2ފ똚ðp±R¬K†Ð–­îZK"i÷{‰äRr°j:’‡·TꊨÊ{ÓS$áê¸Ôë1ˆX—v“o‹N8¶YÀdñyAÞMhÄfú"ñšçFñîûâ“6ë=U%ƒñhÍ&@Ü$×zbUóÅ}€ÉŽKY=BÒö9vdå+2ºÂg“;к»öò‰m…¼34Ñ\£‚Õ‰?Ù› ¼,Û8wùZuM!uCÙ«·§ÛJ»€Œ/'&]üÔS‹®Ž°b xñ')§-™–iWùíu™ñé®p͘\ Ò-(Õa­Wê£ÈÏKªÝ€M!^|o—m2φZ½'Ï‹ª·¿ß]y=Êñ}í½©v’ä  øÄÀlkýñìOÎí<÷j¢>3DÓ¾_H4“h(ûjn<§µY®á–Uz®3ЫÑÏÈ\“õ©à8 SÑ9+Ñ‚CÓr䈈+ôuy”dÆÛí-4™ÃFo7 Ÿ†qÞ¡=s!9ªnHe½Ä²GfŠå vU{Êaíb[?M•ÄbõØúXÙ—xE%׺#g65¦ù”Ë @÷^àv¡ŒÑF¬Šþ%]¯GlŠñæO":0Û®»¥¦°?CLm")’óÉÉ«Þk lÙÕÕL°«8¯î-5ІåD­U;S,¶RSlÓ>9¢*®ªc@)V'è’L6sM}£ûoNíõÙW­Ý{ÕʈD4­³j…—ø Q·àÉ)Å'Ú£+²˜n<2aýçz}ºÖ@à\®OŒÂÝKÿá Së1ÈjÓø¦z‘¥7UÂ*·0ûL|ìCNr–•E™÷ZòQ‚v ùé‡Y–-{•ÅE®Çõm…òY=¯J8å"ÔñÀÚZ½^œén K'9ÅÝóyµÑMTœò¿›QÝQå®:“r¦Æ£gfçDçØMóxKmuâýv\(ì[S¶œF”[„޲ ¯y}‡×a·õò´"šµ“ûæl‘5iX»<õ92uªÄXF‘@6fÔòÊ(w®óÞJÔj“^*>^»w¸ÄWÒúО) KèÃTsB³Ÿ*6Å*âÍ=œˆ ±«ÎÞ)°“5J%±¥ºC¹ƒ©a$±)¦G÷mkÅ*ø¸vôïœfÈÂI¶Þ:œX²sr„³>kÜ”±>y}ê{®îFcfÂ÷xÊS¶[¼=ÈrÍbôËwÓ(_ö…Kˆ½öøLÝúûåz]Ì6ÖD¨r*ü@Æå…á#_ŠòŒrIÙ[t•8m‘r Ì®á,»å«ÔàȄư6K°¦9ÊsI©=JS\×þî9{Ì® §u]þþÉáÿr«ÁA¬;Öü¯wp,À%HöøõÏOùMÛ}Zà{¶ª|£Ã8H_ÜE‹¡K7—²ÌGö"NQÝÙ2À²2É1gõ )بP9¶îŸž‰/ÝÑKˆ¹íè/å,wî^¦1UžIc¤°˜ÞbÙ…%îdö =J_|“t±u›6›£I âÒÕ%^â*Ýý™ÁåïqšÑ¢[S—D}u§‚l©´•ÝfofêVzFÚñ¡h¼¿é‡\m¶æè¨ÌÅ”ÑѾeñÁ3Òv jÑÔ=Š9‹IT´c“7Sùx/5~“ÁTkü•#ð$E{—ü¡“YG÷þ˜9Ô–NÞî¢0†Ip:õÂe^%øZ¸Ç…1æ- @3ž[Pašã#׫Ö57]µ0^³{íSŽË€®šÃÁ&S‡³© iVtiò,˜*oØaoÅ×}êÞ³òâøžì/ä^µ8Lqó±;é3=2`/`ù¢tåo YÑ CÏÞöÒŒ|‰õáÞLuîj§dIëÇó7KmÓ»l˜j¹õœÑ¡0Ù`Œdºvyë îÜö…Óé§½ JÜŠD_¤ƒó6wù|Í=\µÕÅ©ˆœ­‡Îfo– :¡RŽÖÎdÉH€u¤Rs¦ÿ‚˜éÚœ-ê|Íl+O¦å&ÈìÇ{RT«F6«Èê¢uÒivFt ‡Ž)Œ¨¢ÓéÜkR%6Iì‡ô«3ñNÞÑhîFÕ£x“<_®W×Ôl¹vß+_uœ™ôjsçg Á–œ:ç>ÿ€yµÝH`…ÇC;|TÈ’ù¬Â:¬p NDé?è!žV´ÿ§Jéä=~F˜á8Y¶&ñ¿çõÊQ —ù80Sjòù¯Ïså­?®d˜ÿ ǹÐ٠ʧÄKÅ1¬[]×Ù¶sÕψ[QÏþS¾Ù•¸H œ5íGŠ-ºÒ"_64K›<÷©3=z0Ð1 ôw6Úéé—'z )ÐÓæº'.YµòËÓ_¿e Ÿ†óŸ¿Å‘ý#ÿÿw¹Ã¿ý?õ÷‡A\rg {ë2yrŠH!²ˆGÔÅ㺂œ:#¢ñW7‡vàBMx/6ÔR]étM134]|úåI&Ô•vÒÒøtQzÙJÙüû¼¤ÏzjÔ²ôtQzæ«¥ñ‰J‡ôÝà#-À'ø®xÒí^¥¬RðÀCòJóaK2e)íF‡k¼ÀvhåàßX o"pbt|/‘yâr¶…_¾]ÑíÏïk@³Ç†p­?‡bAâåîÝ*\YiÖ—Ú764Щ¨ ˆÓ®tæðÎtø¯ÑÈÊÓDõIë\³‚¿í¸õní.…ë&Ä¥ƒ“]çw®ÅúÓk[uît·*xya¾Äº-¾ïw=“{ÜÊŽÀßη»_/pÇy.¾Ã1Õ€É;Pdxy’ríÎHK®"vwà%[Ëãï˜pŸ%äQ?a9©}½#~^ƒeDÿkk»f ³°Â@¡¼Œ ù…vãaÜæi>}Pø7þ§ ÊS)×Ò9,ŒðHü½­ÈÌúßÔV³ö~݉TvH-ð[â psy’rW„/!-~Ã6Âß\p©~S®9á„?¥ýÁš'àš>¥Ž]=,—Zßtã«väBœñã”ùrH¸ =)Ÿq‹aÖúdêMàøI=Œx„c¼×¼½š¿ã5yöÄ‘X¹æj.J¨íßçj… ð—SŽVýÇ€¾,âm>./Qy[yRpÚÍåÀÓá½G @3Þ71r¨û•ÆDOncÙ"åÚMBV* ûNɺñ“¬°Ãë™ê'*iÚ˜¨Ä?½¯jg´ô/«ÃÈÆFµͨzÁäZÅ ÿ ‹ëvj endstream endobj 12 0 obj 6237 endobj 14 0 obj <> stream xœ]I‹,¹µÞß_‘kC–CCLP$dVU¼kû‚Æ;·˜÷¸7þûÖpFE(Ë4]·"B¡8úttf©¦7wù÷]¦Ë”~›÷ùÍ_¶èÞ¶Ëo¿þøËï.ÿ_Ÿ¥ÿ~ûŸŸ?æ%=Zטÿüûå÷Ïxqþòó}wëm{Ÿ–Ûu~wþv]ßÝvs«º3}Ü®Ëò>ÍõwóïÓ#=ˆåVþçžúp.=~ŸöÜÝ£¼vOM¯«Ç¡Á½´¦&kº¹QßKê»|4êÏ}å—7§oúü#”~"Q˜ß\o¥IÀ”“uSié–wýA7å>êW+}îãö·ŸüñõóÇ/ „~o¡ƒaîëç?{oTЗ)½¦AŸ>o×<*†~Ê$ŠAæaÔ‘´â~^˜/ïf”=}æ Ž' hn%NåÓµ»õæ"€ 8¨¦n—³æiZ‡d~AoçôãZßAjm\ÞæZ øØ Ð23/!vå^fŽ:°9s‡¯CJl’gçtu?@%Ç-•wqS&,/¯Å=o Œ-a[.«åº€±¯ok·¯Ä5yj¹‹PîøÌ؉f÷ᣟO)\öåmi Ë-ìeÒêêX2 •AÓø `™ƒÜB ‘w®=¸—Xƺ*Œx/‹«_~«}–œRsÍ Ü¹\ݺ3XÒ>÷ “r¦{&ÒEAY•!žoÕ9®3Ž£L§‘ûʼozʺØë.›O?ÖMCv¡. —©¿WNÝ뚌•€ràG™(@(wvèï´*üîhÖ)Æ<_΋~­˜­½P×Ás'DQþ^óý$]@ÔŸCmA½ì§â—˜òK©¹Â¾(:ù¾[…¼]Ä2IXyøžßšà#2{‰Tõ§øVý € äiù@«}_™*_Ø%x^åç¨6 0³ÁÑ z.;¶:ÿ›²c]éžPU(x·žÞ]„aÀe¢P‹zPé…!ï.!º;ñݢ緥Iò­}P©zhZ]¨ý‡*rAO"·žCj0šì·heŒ´ÊH½ŽÄ+]ˆBj2qSa¢}~ì a¿•Þk»¡d$é·H¹µyœ)jä_‹L[rë ”4YÙuŽºq„º_N”h=¥Jb®+ Â.kÊoå_×µƒ¤Îv~T8ÚØËÌgÚZ‚UâÃÌ­H»ÈåfÂ(VËë’!†p¨>äÙkÚËÞØtÀXÍùã~¨¢ÏgÍt<š5¬þLËúæÁA¦²ËBúê@$»d|1d¶ ³Û:Üò^ü{–-„‹#å@Hå'Õ¨GKT²û]tœuJ¼-uÕ©ÏyöM`p§Ž(e«Aië4µ úªp} æò3FÏ¢(U&š,ñLÏI9xÕ܉³rÙÄǪRgsó‹ŸöU/“²§§È´4Â÷ëºÒó¶X Ä£w¥t%8­k”24Ù†JTJS•åM_… ûíÈåBG¯¨¹ŠÔ}ÐØú湇‰+zSr@ŠY6F°Пø\Õ·8Ž€_½Ñ²áI"œ¹ƒéú —Ò"Û‘Úb°ÕOëWèRa6ó´’áÏ&;¥ŠŽ§ñ­“ú– žü¨rXIcñ4Øá;XÀ7« Zƒv>Pu³pe@T’WÒµzÐYÚLseY¡…?à¸ôGyþÈÊ…K»¼óL¸ ‰ sôÜéŸÃfQ—P¦|T¡ŽLʯ]Üc¹£ «Ûuæöž=Fˆv¸……ÜwO©]ý:GŠ‚&ʧ}OìÍœf ݈»¼‰öÞ:úä“ýBù“˜GÄÑÒl†eÙ0ü œ‹Rùæ´5ÄL±êØ­ÌìÇ­0i±Î’Ü>êkÙ蚪ŸÓÌΪú—‹+ÿ¥¾¶ém¿„9¤ŸnÛ}nñ©çß~½üãw–þÔ’bolKÆB¸´'S&áWDzN{%$I¯‚Ov:Bèõ€vËÛÚ´ca »HXB$áù yÂËd¯fÎ]šY=Žr­À%{¶3ÝŽÑ´ËUh†;–nH"±TQ³Í!8©üKáeVŒ? Î`yïßL¨¤l7&ÖÐTÅóHº+nô΃$â-#Xä•Ü®àúD'DW¤ï×`Œè»5.ÀIЍÁ9‹NO«Qñ½åÜ!*Ö‡…l„ñ´¤`Tð…¨oãë>Ð}ÎÅ‘ìb ‹N„ëZ ä² Ç}—ipÊ}Þ–(ã¥ä|æèó\—Neï^4r•K(S#õ„¢áœ¡ v°}’"&õ2ˆpÏ+¹eèÓƒQ½Dz¢\~ô8¥Aó€æÚ#t/‚ßiåBÚÅPÁQ;“® zBŠT™õÐòK¬Ås€C¯À›³ ˜ Òcfð;0Û¾Ã}¾t¹£x>LÀÇ»³¶øöÊË+½lÝn¿oÜõiÌ̳±´Ã²×KÂl”%ô`FH.«ÑdÒnCw¿.2«Ó³¼I&„tâÒ ÖܲõKÞká$XÇPè‘W£.ÜǤw´µ5È\p€B —ïðÜñÅÊéÞ÷Í~ N79M—*°º¬ PÓ•Ù…˜Çùb1ÃMq°Ù÷IæU‹¥Íóæ)Æ©ÌÜŨ‘§ÝõM’ëÒµIÕËûµÞÊ3+§4¯.úp»L¹­]eJÊž»ô¾ÔYoSìÄ)>3ÊÕeo$ÉŠ, ¦•éÿ C{2B$3-ª ”Þ´6ªÁcö1÷ÈU U¬óò¯fX½ÔÄ»QþLÅ%ã\†"}lŸø2#('›R—\)£ß µs.uy&XyëéÖL8ç"¢, ºñ‰+`á$ ÎjÂÝSDtZ47wRϲ¯×6À v»·Ÿƒm¤zóGñäÔfå(ôg6Ç‚Û^t4À+-X€€ØCAtñíN,K×H´Óa_–QÊSájÁ¡¹nÖw‘¥0¤(tÊ^ZZKQwµ„™™9±:ñ;ÊÞy‘/JÍjÆf_DØàìË´‘(r»v“Ç%…Ï58€]f«µjòjCTPñò˨f[õR;á@¿Mü¼FöSC«×Á.®õ&êç¬ß{®›Ï8ÇÍÀ0ÂmvÖ÷P‘YQr,Ú1•gÈVϹ<Àù¢ÄºFP2ÛBÇR„tÓ™ç‚5â!ˆRºáêœZ•!fƒ [ª*ÁÀö—ôñE]Ãgu1m°Lô„íþÀ7d˜ SW;ål7³¸‘ÏyÂ7â ×1òÈ_É —QÖB%¸ÜvþÁ´a5iט5‹tëÓ‹½ØµðÔ;K4¥¢Øg½è™g§L:#ß›HùÒ"¡Žš@„ðñÆÃÚ$åÚŠ Î|ghÑ¡X!ÀcLi¢ª%óE—‰=gSÝ€M÷ÉJ|g¨‰ü1P‰óú"ª7šP•zrü4Áš;ÛóªSc|[Ó‘nm¤LEV?³5êõSL DH×¥ˆâ’Û^<™=æI·•m0‹ãÅÝ.‚ÄvîЇ&‹Ë-ÁQN&P®ÇÒ`Žc k@ë Hb¹icêä´”­Ak€nITÛ~nÕÞþe…J™ `´Á6 %[·«þk ƒß®+I{ÅWáŠy¹ÏžÀ?0L®[¤ž¥%æB_Õ çŸ˜£ai“z•ý`7†ó¹Òâ᤻ŒvÞI·+ MÔ”ôûÆV€™Bœ²:ÊÎÂWÞ® c~¯/a\—ƒÔ´¦Å瘉Ñ8Y; Â=¢þÔ#ÍÚ4aŽÿªf´R»ò– Ù‚lwê^^̘=kQÕ9ª£!ªÞ¦mQå¢Åà6¡˜©âRd¼vé” É~%jê* žÀIêöq•ÐòmÏ5F"äÐJ<ØÀ5Øu²†Âæ|ª©&Ǽ{UÑ•]­²Ä55o ŠXÝׯ™¡ú ºšõ‡È€žÇ„¾äç»–N]wÆÜ¨åàÂtñd§@xíÀ5xÙmÊÓ§h=9Å¿.”“ÈŽŠM0œ÷ä@öòK±žŠ¼È¶b%›øö&‰â38~Ù_éõ^%Øa:˜sºëih©r¸…s8['¨Æ$Enåm.X²è%KŠx)m# "%ѱT×£à(nòÁºä¶S<‡!ñ áœÂ±a 謆è®bOÌ¢ ÙÉ6U3ûfñš°E{”ëzù(Ä…M °I%e Ši¤I†ˆÿ©¬°Ð Áœ;È7³È`ç  ^méò˜ªw‹”¡%.‡Þ>§í)r®v{+ ºÖxC½Š‰Ž¼Àû¨hPúZ‘­,2eÊ9¼¬!¼ÑF!Ž£Ì(D}Ê9[‰ˆð‰¡.¸ ¶¤h±èTlˆ¨2èÞ˜ÃNšcÓî•ÝïÁ` LC`C7æð¢1Ëjã’¾®¸0sÒv—¥_ÀØŸ–)– –î«nºÜ W¥oâFÎC ÿm²%Þ½­ˆ¸ßho*˜òˆ ÷[ °å–(¾ïÜi£TŽË¯:!F±«(z±GJ”tïD—¦nгE/†®:Àܧ¬b1±Ê¼,¦~[Ú&¢Òºd±½7̳MÆ5CþèÇÔY lF‹¡Ù_Æ¿0CówýWØ•jóiÉÈ)/ªìz)ü¤Œj£wqižjd‹×áÙ›"n5jU_tg–on‘ø`Ô{¿ŠcšÄVH°)ãÜô¦ò(;½¢ŠO/w^=OÔ"?æÁ9̆(w Ç{E(IÙ·´{‚Øé5±hïî³ð̨tÔIÞcÅ,,Ð¥¢ J:e…5ï¿$·Ïyö•ဦ)€.µ-²/,?Ù-_[ÚKJÍÐÖlÞí‚u#¬Î…ûÎRU$ Äžg–W='6¥£Ôo$¾'4%°ç\ë¿g^ÎÎõ7-iv!Û :*‰”-0"¶s3·­9=SD•¢_Ž‚?‰Ã Ý~ÁGjƒm™”ŒœÃià÷Õnù#ñŽL& ?axÒtp²Å# ªYú‹wÒ}eθŸýº à"±þ©%W»ïS¤ˆEpçˆá½ÝK,2€nÁr²a¦°o1©“(hÁì*^)΄hŒ%‘!W$ôµ@&SÇI€$qŸ ­ƒ °ÅqÈ KgŸ«Ø)$šo<°šÆ„Œ?:cCûl°¹XûWÜhZÍa ú¡Ø‚h*my3—0ÉèSÓ V™Ç ™^ØêPžEiˆëÜntõŸ§E‹þS@pRϹ§ìÆò+2Ô s÷P–xë䊭—ͺn—$IK>$í´±!9Š‹µˆ  vko'ƒ n*ǽ«”`Pt ¡Àóz}Õ{$ˆöQÉ=Ê—žd~ÛbYr h…‹—áªÓ‚Ðgù†|k0¢îíîUƒ¤¨˜ß…P XÀšëˆÏwúUk(àÚ¤Z_}äÖÉ—vâÈ[ˆ$¾xYSÝC¤Ï4«°NÏŠ?Üž€x†€NÝ«, ?\Þ´?#$P²·|Jª7]¾+–ï¸ÍsãǬÄK̯vÛ•;KÒžÂϬv¤0à+gõ­  N#díê²Ezr‡ —ÈȂխWK䱓&ËZ9x°BOÒª¯™ãw \òû¶QU鮣UoB¹™àØQUÓyI®®Fè£Þ¡'\NZóÏåUôB± ÚwWXè4 Xî«å1?Á‘4¿©& %‚S{ÓÆzOùµ…lñ²öN4ê…ä­²yfÇá°ÞIók†vˆÖèWä¡ý»tĿܪÖÝÈ•;ËÄ-›sDN3ÖÚ3ÝñÖ8!ÊâËàX»çFíZëdÃôÐ^v½,pà Á þ÷:BýNl‰´Gƒ€rœ؈Lt]³;b5wðBÊË·¤BÅb£Ô r·í™ŒìÈ%å/äC-CäÜnûÎÅÀoÙ¤-øBDÈËrh%1¤ê6UºU4ñmTF¤e=Çrómh Šé“ ×qÙW"-[³!ÊÓÁArÖuƒ +K÷·9‡Êw}p}Ì¢ô ·]õ%“]ýÈ´ðš…§Bñ(^å-4¨Ûã!Å*;±N¥Eãù,Lßãt¿{ëãìIñÃnƒn€,µÈ›õ0·¿‡ï8“¶[SD5}Êëα.jÿê5®ÔTxÆõ›µxC}y®©®S8× 1‰šô útcI‹_)Àd\tbP,ÛRwªhê­&;f¬óšmâ´’Ïâ•£ œWÛªöºA87œ™[EÙž$xYp„ÊŒªb/v¥t"pd6Xè†`Ï“U~6ÔÂ…üà4p¢çæ ‹©ÓC=1áÐOQþÉà„ÙÉáA¨ÔJ¯ eX³bñ"Ì.UnnzWÐ=AÜž7Œ¾© ª/ÑN×—(•GI„#åÉÕºÕrPJtëk¸cqŽdø^qJ>ãÑx0œw‹Yžåt“Jïúq±¹oÒÕÝ.Myg)Ïà¾_™¢r nÊ> stream xœ]I‹-;rÞß_QkC•SRŽP8§†…w zqé» ^Üÿ}K1‡¤Lj¼ªÊÔ”ŸbŽîô^þï×ÿ¾L/Sþm9–·ø²Ïámùçßýõß^þßåÿþù_¿¿-k~µmsnüû?_þý{~ ñå÷?þ¼Oß·ð>=¦x‹ïù¯Ëû´ç'!ÿº½‡ý¶¿OÛm}÷ò÷´ÜB~:—¶_Ó ­ËÛiñ$ìn¯ùÿû´„#ÏpÏ/Â-LïÓçí5–9—éÈMïùÿ}»…zå0BŠ·¿ýþ__¿ý¥úžü%ù{–ù-¹ïyßy¥¿ÿ»×åˆÝ.ø{ÊÄK:ÊG¾.seÍÃq{Ýßã†8”eå_é˦òÍðÛÍÁ—_E}uŸ¾áWš‹8Ö¼i‡€»’ÿ—¡ù x”ïê’‡º‚cžÞ–‘">2ë k…ŸŸ —‡y-Ÿ—Ÿ.›¶‚¡è[J·჆A6#nI'O!y¤{y³ëÈH[BÍŠëÛV .`fÀß3\#¬ƒ/ù{ñ'îuK;…:ñ6à^!ž‰ú’¦õí¨a0BÓÅ1BØqKÂÑ¥Âü>ê{dŒÀëÆßxÃrÓEšâcÅfÏ8Õ «Ôô{(Í»"€ØƒêšÖ#µBÖ³ÞQòầ’3Rî|Áãn…’_¢4@4-š )"U(âQ^χôxŠ8Ü£®(!IRÞxú’gZFmG#dó¾0íãb>r üdÜQàŸ¼ðe~„Vb#%Íëp|å#ñïS"=¸.G#õæHâj—!ãD‚+ðn“t#•Dm€Ë‡õV¯²ú[‘VˆnòÓß–VÝ”i„p*»E[a~Ö¨e‘4-bä¦ÜñnX*$v°9ßls^i¶iº‡oÝù{a]ä ào¶”€õ·Y%ÆÆlC[\ø4â¡ü$­J*`eµœMmú²íL.kçÒªË>Ó³üÓ 4‚4…F6»%bZFÁ8"€ÇQd:ÓµØh¯! Þ„v_( îDOð¸üi«Âê·Iöad<XZ¹Áö-’4Ȳ1—GŒÚŽí+^-ü6Ë=¯ µi„jØlý¾’˜‰KauƶøVn÷¤mPÌ7<’..A´¬¼©ìß71KªV)žUuGQU”@@ŒWæ¡Éô42ôuÄpê˜Ïk‘E4¾­ÀióΉ2ÔÄÁB±¯IUá…ØàñU—%âU7D”Åx%ƒŒn«uŽ‚#ˆÝc³¶!­jíX¬×ìÞ`4 Ìåèá–À‚ƒ)°È\F»m‚‘œ‹Pæn•løÈoŒ4!Y°×Dµãxè·¡ !G {ô¤ô@ /ÛÔšÖómÅšIùÂO´ÿTI0mÌBÝ¢¡UÊͤ‚gJºõ[¥fˇ~TS*æI¦‰/“~#lÉQ(nží‘…Â¥ôj¿}D$KÇ^ßP ©Vä­ÒoeÝèôjÖ‡ñ€½ ›ˆðm«õ2Û2îš«lü>hì .aN±®Îœ–ŠõŽz¸-~‰F_Ù\jÛØj„ìÜ1ðe›Õ«Q;Lþô!Eò@Œ£üäÊß]ÿîšéìôð„¡^OFHX¯^ÚUâÔßÖÌ%b?çV”k-N#`cë´fvYÙÖ‘w&a"¥F.ÈÁ”"¾ÎûŸDXœË}äh§ ¨_FUØ5=6(Œ` K«ê>hK‹Hê{˜Æ(ÄfÜ\”‚˜{å)…¦\K¦x׆3} £Ü5 :‚Ä'´ÛÐÈD–#Œv¢ðE^n5û5Ø v#°§Ðè X&Ãùc 2½§y-?ç”?)æ_I9µîò̸ºâ’’:U£F¼8ŽÞGÙ?TØìÄVóKæ•|–m6o¨Ï6»½¢ú¶ìø 57x ž÷­Õ[,*YØodªRÀþV¦$Š¥¥ÚxÉG5+yQÎŽÂÀa¯n›bÆ€µ%z«'q’&ÐF•7gªÌîÆ%¸-V#pGý®£È^Ýzî숸!׌ ‡@:Ç`´=¬ÃÏ…þ7ÜQuãW·–BÃýцž>mŸ@ˆ³¹º†ºAnõ:5ÊÌØPfMjµLü @åé½bÿ»]z–;«‹­âtìdpìæã¶nH™v^¤æ šS·ýÔàíc?sè’ò%îƒØÖæ=c·þyü8GøÏk£ÿ9€ÜÈ5â(ÛBšü!Þ7¼þÒ [äÚ5Y(’’ߢœ‚–pã”Û$@]Ea¶›Šk2p æNn"¦2qÕ/ã ¨ƒðU6‹hG³.†v×I_†ß…4ŽÜ:aøçd­a[ËŽwÛ‹Nâ”<õ3¹¨ìÀoÓÇu}[Ϧ¿Nm'ý–2ç\üÈ™7½Gdbú3äÄIáRˆÄñ©ð?kÀbgÏW›¶‚Jg:§ÆÎUVHg”µ”½óñü‚ô—aE–eÕuŒ7ïðg¥çõ½T£@OôÖÍ=|²AÛ’ÀÅòC-x½Å̓-NÇÑ*íUSå¡2ƒtf19ì²I°[APЌʹÐ*¿V‹\ÕºÊG&þJ•1•õÈrÅm1d²è~ãΔúý`{ˆdFƒþÎѸܥvàÑ.íK›ÃÚn¬ß9v–•]ólíçsî©Ûá)èãVÒô·XsÜæL•½‹œkÎúÆRMÒ[}:Y}H¡ÛãO1mÀ(9q@.hÊ•-Å*Úú@äß±|”z£ºLÝX2îeê©ôkJùÁ®!¥l¡±L‚ 7‚âZLŒ™¼eYÉñ&±?LHÍ8Ô”y)ßIj²ˆªCME18„ß§Zƒc¯T5!á i¦8¢&!e ™ˆâY´h`È:i=@¶j„ì²µº7j^-’•.¾}X\¸ei£D¢42@iVÌ98¥QÞóZ¤#f‚I©õsŸ© Ò±*„u!ôʦ¿é‹â6}h<>ÐlH/U†=7­|7ëfDFÊÃ~Bð%óM¶vkò+¾Ë&RDÉf˜ý™ÈRê|Ú‹Nšê'ö˜Tè)ß„Æ$ éPf—<‰õX}¤R UþÄ'Zµª$PEC˜ƒÆ8üðÏEéZhFXvrWXéÉ–š-\ *©cq¬µá Jð¼_flUIxªg®Ì°Ó„—……7 ªyÄi&Ͷà¶ßfYE§Fø†ÇŸEôÐ#•wl¸PqÏåäë’Ýç&¤·8íaC‡5%æÁQXæˆ×CjÄØºÖvaCÛaˆU`ôU÷蘹»Md¬^'o¶®[:±ƒÒ€.ÃRŽFT<~Z°n“«ÎÇ!óâH×ïžp¾” LH€’‰mÅhh–¸€7-U²æƒ:²\—ˆV׸Ào0¦¬É5Ä b#ˆS °Í¿IF‡ ÁLLó‹3O®ê†søJ’LM.ÃÿÈ’ö¬k/ÛlòBnYŠŽÖ'ÎþË£Ec®¶ê&¯þÇô3céÔ%Ô qÆÕ éâh•CüJšŒTÜa©ð.)“ÌpÑ0ˆˆvÉiŸ4iJ6.Uz ÊÅ,¸çÅê ²¾Š}uh™(á½jòðÕÆP|lÆd¦µÙõR‘bœÇ(ð8[Í6íÓuÚÓ¤”: 6ä5-íšTÔ0k£«ºRÿêü L[éÓ ÝÍ$ß8ïqÆÚŒRƒ9x]Ñš´Î£dâ‹à Ê0¥×"Èt·˜\C{ìh“©#È[‹Ûª¡¦"äÓd2\Vª;r·²úî)üÜëc,5bƒ­Z ×™Q¤¡ð„ >„u%‡(~àªá?j‹ÔÊXw–OÌÖø¦•IûpõC¸§l8„n^É™&»[¯=ŽuoŒŠÒ *ÍŸD:»6åˆY¯(£&QSμk´/.lü¨NüH ±›ªr •ðß¼3æ`×N˜ðî'\L‰›?v¤¹;s²ÈÑ‘ WK»{Ù%~q&Òþ¶uÀÂß)Ôp Û(C®Æ"A™â¨„¨„¨T õdTRkФíÄ_6’jñ%Îìühœf—5ÒQ“Aµ%g»¸„FVÕyÓ Ð÷„CÐ;Õœžc»Ô«’‹°vé0¼`×d5|a–ÆäýjÚˆÖ†7õð¾¶Ïˆû)¨¦Ø«ÌRYeº£­w²=U·4p'í]ŸêΔCjªâÚ€°s×^#‰Üèjí¸ØGÕ‹¢/›´.‚8 $ÆÝDDÒâ @É©‚ˆZ:EÚÆ²¯·ì¹}Dví%°ŸRA\{µYA[p>Tì.Ît~ˆ¹Ù8­—Y\´åy@¦ªNwx®ãši÷î©lòv¹–í4䎷ˆ 1î¥æÕ«79Ö“ ”k~uˆ]Ðñ™—o›iҢŶƒÔË‘b7…v,ÍÍ£FWå„mýÞ¥mBÜÏicÕ.Ôå¬ß…õ·6Ç1ǰùB”°ªÕ¢ºO*Èç&6î Ü©ÂÞ¹ÅöÈ…íß@¹ïÁQô™l´“ÊžW´~)Gz’áJìX͟Ĺ˜é&Q÷³eØÄNŠN™mṙ‰Ù–-9SÕ6¥$O̾^c× 1ÄnNÝò扚íÙ…Ö§UAL\ÆI~ΕYi[œwÏeRE™W{ÇeÜ5Mïƒó;YzÃ:£ÏEŒ8¾lršZÅupºÏ›Î+­b%M é.¯L6C:A0ûM ò½ª]ÜhƒÆæe-nþ2J­Ää!£|wÃÒåÆW᯻ƸAlˆqX[ÅÌNÚ©±r-™pŠeoš¾˜‘W>n©Œ”С_wÔ(é†yùPκ ãËTÜ¿ë¹ÖØDÙ WÑ§îæ„0‡@‚–q–ôú2Ævã :Ã: ß#pŸ½ÃÈ>¼cÞ ­š±w»š:Éâ^=ßüÔuH7-l#ºYæ½ ´i‘»«,Á›=‰pš&$È䑌ÝG>@[²×ðäMꓚ8‰Ij—ÊxÔ7a“;]Ü€µ4M±2ð~m‹ãù4·y#àXðý§Ã5b tšøä;¦4mØNôÊ¢£¬¡,ÍÔ¹âîR`À^-t£FŸ2Db~ FhnByÊ,qeWýr“ún!rÈ”HqZ°L~˜³À'b\ʦ ºÊžó¨ÒNæÄÀÀ!½L¡MßÈÍlƒp伤0…’xŸ÷À’"ÑüaЧ+H[·ÏŸBްpÆõ'sÙ-ññÄ#H zØ¥¼ Ríóê™îy#;%Ðx)GxGªb^C#Áš€ƒ9¨èÏŸ›ã–ÅgKü—M—p2ª.Ôj”µql³=H–ÉÄ›0»¶25A<6ѦD‹æ•ôî"²#øæ£² Ô-Z#º›ç­•SæLA}K‹í‡Ã00&ÂL‘"J^q¦uÂÝ7|¦ŒÏ Ü»i©½DÌ#¼dâœ!œ©¶k\¾Ô” ó ¨&fPùÿx#âèІtÎÎf®KuB_¯ôxvì©vúó Él Ãh£#­DÊég¦Î§F€’¯‹Ók¿$3jC°ÓâÐUEZ=”ͤrïd'nÅ´{-Ǧ6±ì#÷­¸iCË…€X±!Òæqs·©[ÞŒ©Ûð÷~™Ä¯úؼw¥)r+XײsXRp]*SæzטFûžöN.ùªz‰ãóÕýƒZ*.k«vU<‡\Á'ø6Û¹®AsŒæoqM¹¯¿Øot²Jó¯Ìn—ø¶h ñÝb« (ðcB-ÍJtšè,[õeÖÉÛ]‰ŽÙ_ aË\iN~kë¢WWä]-óv¤ïÎçk™ŒÛ±[è^¡eÏ lVLUam>:âÊQZaGÕªÕØŒ° GÇp·þ*—.ÿ¡BòIŠD¥«»ôr)z Þ O ¸Â9a[Zø©ƒãJ@8)%Q(=ØÖ1˜2K l•˜Î'¾^ÞÝèÈ¡Àg)ó{óÄÞÚ«‚åù+‘6ZІÔÑ‹þ«¯¦Ç=]áŽ-ä)¦»½ ]Ü®»"7\†²·àÔ7‚ØÓ¸Þ `‡%¬íà}þ͘<Õ{ºÛi·[}ÙúåÕ8üÓá4-p—ÒâÓð‘7AïËFwmñÞLz~< ±^ \¬ùýw!(±*7ͳËBW*JãaÔÕä^äßÐ{Ã馘â19šdP'Ý#Ër¶äëyœ'ÿ¥«1îp蜋w¡²žæ,Ì6¿Ìß›ÿr/øVO¾"“ááR_Q„)'ÛƒüÏEtF» ×¼Ôà6Dºú¿4@•UgšÝ…ì>fôlež«àK„9c¹:c…®ï>±ôOö$“›.J•ÐòË6¿GðC?àz›Ïü1àÂ7' AËåUD„³†dôSOu ULMüú.8\™ÐªÃȾ·²g²ROyfÝ¥yþê…þi”‹"?RèLÚÄÆ;µßQn¹5vÕÒà8.¢®q²Ôü——ÿx„ endstream endobj 18 0 obj 5903 endobj 20 0 obj <> stream xœ•XI«ãF¾¿_¡sÀNwõ¦!°üìCn††Ü²@3—üýÔÖ‹$?ë Ùꥺ–¯¾ª¶9Ûá¿·ïƒ þ 9œa½=ÿÞ~ÿeø&søùñÏÛòx §Rò¸øñçðë݆Çß_'¸Î'˜¬Ÿarn>Ù ÒmA1´ˆM•EdÞpÂNæ2ÿñøííöxû²ñdvOœE‚ÿ>Û!Þ·­½kF9IÔô~RŸØ« h·.8d°5Åø; ³»ÙaºLœ·p‚YlƧ˜ÔN%Ž˜Ž]p´¸+éAeŠvf>ŸÏ ƒeÊ~è 1×›sؘ‹¢‰‚h”‰}ëy~.'qŒL›äDÄ¢ÓhÒ–ŠMœCѱIÀì—HÀ+.I¸žNðtme…§#7­@׳ܦ­yç£ (±¯Ñ²óÆZ žÓ-ϳLÜCÑDôXu©u8œ ýH8¾Ú6üQðKÌOq?{ßI`@.’Ÿ9Ú*2·zQ:·Hð–Œâ_;pç#Z8ç'éf„"i5R:ˆæâSû‚c½î- Å|å €)æ#;aò¦Ù«LA‡˜]a§¦Á >2.ÃÓ-_9‹Å«Î 2!Ðçe–Æäv¤ä"Û‰ÖÆ‘c‰‰’/ï…uªÙäÑ–i0`á–2¶mêÐëµ]…Åp09Lþã‘ ,»»º‰‹…¦Få¡Õª~õä9m\E©P„'>;€b yG…[(ž¼¸!”"Õ%‘Z ¬|ªë³bŒ~è¾Õ‡Ø.E ‡¯ûè‹÷-š©6ño=&pQXÛ>]¤°®†J™>ùíBË»ªž–;)›I<$Øðšöž= …{ZÝ–ÈK-•ïsØÀŽ@‡¼Êꩵž¡€÷Rq‘E„`J5.ãW²”zë¦uŽ0¿È+jÔÝhmá=;X/MÜ¢Æ9=I›ã‹šÅ‚B¦vqÓ Þ0×0÷¢¢•¾á‚NÂ×ã Î(î«lZ4a-¤#ºÐFþ÷5a1µ3ðcÝ¢½ŠÔsŸ½‰ð iës“ðð#;N2w¡nùÂi#)Ï€\d0É´>8|_D]âÜ%~¨}’Í"[D4†ŒrÌ“¬:ÀL#òÚ.Ô†B]{4,Ü@\…š´pë΢Fp ÚRÛÑk^dŠÖ<]–ÉŠ[#ÌTiäºPyjÑ+Åk¥zIì{7ÁÁû³ÿ @€h^¸”iô¢mEƒz±Rmåß6#1…tý]Õ*—q録FÂáxA©Å»Ý%Kö±=5N¿RT?¾+‰›œ9Ç­›ˆõÅ/W î6¶qò®&=8Æš'„ån„âåAø%ù”žP’)˜Ú¿YmaðU/–åB,…¢ë¹kMµ¥±2­¦”Ú3Ö²ïwéf> \=Eå£f·íÚ]`\· žúÛTO*w|iOKoÜÝ‘¾ÂµÚÞ‰GŠî' Q@Ôìc»¡‡z¹,èp× ’¤Ü.}î)@Ê;ÏçõŸ +'…CNðÞí¨nž+›¶øÅ´¿%Ú„vgXƒ[×uæåZë^¼´>•[OÚ xÛ×Én0¶k³TJ:4]›ÃJâ®5íP!hç §Æ¢Ø¨¿ÉÎEG ¼ãÙOvjõ/àƒÂìdš׿ÕÃømpF†¤ «­®èW¥2Ô)ë%q· qÏx»RŠ/^ÎnáÆTX®3…y+©"-É­ôúw*üw˜«÷’Åßê¤csõ¹;¤«Ý}z|þ²Ë»@ endstream endobj 21 0 obj 1519 endobj 23 0 obj <> stream xœåTËoEÿfíµ“æe'nHÙÐÎv“ÔkÇyPJUcB¢ÄI%ì–HíÆÙ8‰ü’óPƒˆZ!e !*!ÁUP4v/=pà•ˆPe¢·^è¥D¾Y/ITå?`´žýý~ó=æÏ~«…5já ¸ šÌù!€ãGÒ˜\_¥bõX â?P{a!ŸÊÜë,=Zñg¦Ò FùZ€û-\ÿjÑ4æy”ê#ÈO/¢ðæö«^ä‹ÈÛ3«—®“ïÝÈßC^•Î% fŠãäÉ—òiW+n@¼œfŒ¹úlÕÈ¿Á=|Ï­¬`yÀ;Æ×ó3ß|t yyìícE@<6ÿŸùÝ®²xV¼ Á»p †ásør° ¸ÝÐ!è× ž„Aˆ…Ë…›¢€Ê )ÎNMjlt]g œmaž vF·µMÞc¤)ÜbD¥¿±Ú`ˆ j<¡½¬èrˆ¹Ô¥Ê¢“šÌ¢zˆ¹Uî*+òëÚ}iK—ÐNÛ–ê’"31¨±¡uÝ^ÐuŒ'ªu³çCÌ£O’«˜^•`¯Zl³¥èžT¥6úéóÝ!V­ÒMžä; C™«}D¡ÌÝ1Ê`R³LË <'ɲ.Y6KTOx¤²;Ÿä“1bJ¶Ë©Ui7óg5J‡•!c™jt~®‚ÛÕñ̘šZtØ2‹ZŠNáÁY-±>.°¨É úÔۙΔ[dY¢e Fp7ÓÎÞdÛ¬AUhÙI®P->%Ɍ蚅(–B­K1¸CÅ…¿BÌÇÿ†FÜ·ŸÀAãcXü¥ËVÂ]›T,Âz‡Ûè¼byÔ¤oq% Þ†(‰Æb$~ÇI°gn<­ñ9¡)s¸{%&á‹(1<ùhB+á=z)+JðÅh’3[ÿËuTe¨â¹àâ—TÀ›¼8]È á"î’×Ýü°·è(¹„PtqYärÉëiýg D¸Þç—ýí²_èNùdgQœþûËA÷ð¯”Ü.ºîC=ÇëŒG±…a ÝÌ]f5[ø}$‘ròéŽgúO÷õ>Ñ|4àõó™Ä+Ÿe3cãcqr÷Üx.{cb<›;gwaa¡ö‹ráBÃÀ_p¢ÊþÎ~ø)vâàwgWƒ=k« ôóÎl_>`òxo„?aPøFWÚ¢ œrš¹ìÒŸzøz/NÃ^LGÇË ÇìB:Øø”ƒE¨Ã>PÁÔ_DKâ®Fvâ&€% ˜wÓÁ.Ô¯9ØøS‹ØOn;؃úVg²‹öFz"t¢`$ÓæDÞÌNmdæré„™ZK…}a͘…•¥\–ö†ûÃ}û2tâ=ìÂRz!‚Í+‚h ` šqç,LÁd`›]› )XCd åa‡i3¨` Ïá Ïƃ Cß¡ÖX­=vSXï!ãÙ}›‘÷!Ϊ&µ"!×õâï:̇ 5@pE »Ã¬¦³@à_üSwÄ endstream endobj 24 0 obj 1154 endobj 25 0 obj <> endobj 26 0 obj <> stream xœ]AOÄ …ïüŠ9î6´œ›&fÍ&=è«?€Â´’ØLé¡ÿÞ)VM<@òxïƒ7èk÷ØQÈú…£ë1ÃÈ3.qe‡0àHÕ|pùPew³MJ ÛoKƹ£16Ò¯â-™78=ø8àYé;{ä@œÞ¯½è~Még¤ •j[ð8Ê=O6=Ûu¡.;äí"È_àmK¦èú»Š‹—d²¥ USU-4·[«ü?ï †Ñ}X–d-ISS²ÇéNícý´·2K“2{©°?¿'Å´Se}~‹my endstream endobj 27 0 obj <> endobj 28 0 obj <> stream xœÝ{m`ŵèœÙ]IkYÖJ²ü%ËZYþ”˱lÇ_²6‰-˘Ärb'¶ƒc;þ·íØJ‚J „8H !@Ò6¤„¯(!$¡”âr¹PJ¸¤…r/…Þ¤-ðn iò¸”W ±ßÌJv> ¼÷î{¿ÞÊ»{æÌ™3gΜ9sffÚÐ…¢Ñ(bÔ±®}ðWϽý „Є@ß±1(^¬z(•ÀçÂq݃=ë²Üïÿ!æo)¹žµ#Ý¿07Æ ¤&Erõvµw®Tr"Tzˆ { bÓÔíJ’þ€¤Óz×oNÓþ~Be$‰®èh_Šl@ÒIzɺö›‹… INÒbûº®¢ígDÈ‹Ê180œ@öi„*%š?8Ô5X¸òå$ÝFäû ÁùÑ+š€ šÆ Ë)”*>J­‰Ñ :½!ÖŸ˜dJ6§XDkª--=#ý{qg¸3è{ÜmȈFäç5[‚bÑ&„¦?¥©+Ï©åÿo¥P…_'Ћè(:xMÖt+y>u î%ôOèIÚîþ¶Ï£'"Ð^´ÝõéV£;ŸC¤þ+WÁŽ Iͧщ¡¤‚›Ôº&’û>zýÛYÁïáutzœPÞ‡N‘ç~by[ðgè>¼õãenC·£¤ ú6tV •¾V¢.4pÓ1´=†6“Q8{q·Mÿ'Ò|ýc"ùNÂgõ¡õW•x¾¤/ÆBd='ãn›ÉTú™Õø$Æ—ï'‰{Q¹Ûá="çÝÌ|TÁéàBReScCýÒ%uÚÅ‹n¬¹¡Ú_嫬X¸`¾ä-÷”•–Í+,˜›ëÊ™ãÌÊÌHO³¥Z- ±:A£QGñ*¥‚c ÈYi󵉡Œ¶›aóûçд­ Ú¯B´…D‚ò]KÛd2ñZJ‰Pv_G)…)¥YJÄ2T6Ç)VÚÄЛ6ñ44×5øî [“º Ëd˜Í’°ZI ±2¡·B A›Xòmì«l« üŽ©£ÚvEÍq¢cQjª ʲ ƒ¬rœUYr #•†VbÒ+Û;CºÆÊ “ÕÚ4ÇYбUÈYh¡Ì2¤XRÊ,Å>*:Ú%sNŽí>- UmŽèN[gûM!¦”c*ÇÆî é¡l[E({ó‡ ¤å]!§­¢2ä \k–ÌÖSs¥Jqé‚Mû+"ͱ]øôZL{£HþŠ(è#êóÙDßXÛXûééÑU6Q°‹Ž¬$FFRêôôOv™B¾ÝM!¡­J"õ-© êV4†pºOìm'òçµY‹LV]Ó Màe#¢¢¢S«•6|×i ­"‰Ðh]c8-¢U¦ãHr9šB¸æLÎähÎèLÎlñ6éÍš¥c!6½ºÓVIt¼«=4ºŠØÓjÚ6!ó…ÉjÓëÄbW“L+©ª;ûÄ—AÔBJ]]€X -2&ȉ˜/¯ &RA†N/ÛʧÒVÙùÛØ›@ˆsœ!¿#Üõõ!©‚R{¤*åºH‰ö6ÒE}r÷…\¶ÁP¬mÁlR±*û–6ÊE"ÅB± C¨­#R*䪬 5‹•cma(/[]ãóÈ=}þX¾hzÖòQS%Ž[Hì*£r¬±³;di3u’‘Ö-6š¬!©‰tp“­±«‰ÑPöyRU®1„Ö7Ö,µÕÔ57E gPvlzåull¦0br!UºJlÄ&¦‰ !ú`[PFž!eºŠÜQ¸Œ¥¦º Llš¡&b„²ÅÊ®ŠM_Ô£æ´Ð?ÃMA“„ÏB¿ÉÚd _sœ˜d‹‘ŠI Uª&‹I'ž€à0a#£¨.¨Í‹¶.[“­W IFÚ6ªYËeÈ:ôUý5©«”EÔ„¬${&A•ò9LW+7T%§g“þ벫g²Å1•­féen‹0DDòê¢&,éLòè§ãÙæk'ƒ˜Œhy<“$:–{é°³UwŽÙ–6–ÉÔă|Ï´™Ö¥G5PS¿`Ž“8³Çl°£î˜;–67>/jG}ãq xaÛ‚¦ci$¯ñy‘Ì2S,EÒ„H”Ó’PÉô¦ç%„Få\VFÈéŽÓ€dœj¨ã4ã„&86Œ“d½H/%ôÿ])vÒþ¹¥©w¬­‰Ú8Š#![9ÑŽ­ü`Et(ÊÖµ ¤¶- x/Å{ÃxÅ+‰e@Ìqn*mM˜C'KŒ*È£“k °åä*;®dUòŽ)¸ÊŽ3˜€èCÑEW*ø¯ËŽÅ»uV]ºUg­ÀâT<8ÕË5üýÉ öM( _ 1—Þ‘¦y^çßã¿ä™x¨æ›ø~'Ï–òÀó‰<þ‚‡}üë<ÞNWó}<ûÚ{üŸxü'yÈ&úH}hYŸt!Ïuann jiØìÎ+œÃØR±.¿œ¤R°2sÀ–ƒ•k« l¸;ñQ§{ßÚK_ݸ-Ô¹ãÔ€ë'Ú=wÍé¨/aá4Œ÷¯ôÏ™³¢Ú)ôàÛÛJ÷ÿzsÂØ“˜oغJ¶í¤‘;Hû,¨\ÊŒ¹Fl4Z£-¾IºþP.:.!N…³ qþÄh½ Ô’µ‡×ëu¿é×Ê–õòtnWËÜ\·.?gºS£»œqçÅÅÃ’u÷Æð0 °GI 8.K­ÏM)_>/‰)O­ZP_Z^[¾¢Ô¬d㸢Žu—ÏDdS؈l¥øåçÉjñü³*µ_<=}^š&@j©!MŽï=×—.|ÒÙ®&×N£pÁa×I×»®]ìNltA“ ®8—ÏÅ(]‰Ñ¾W5 ÐÄi 5k¾Ðp*ÍWxÝóžçOæìóÀ.ôyFC…Ÿ©]=ß4§á{u—ïf–¦Ö,ÌUrÎâR’]`^µ†>7ÝÛyÙ¦sÔߺôòÝÄ¿í$ÍÑçÌaIÃ(ɨb6—eT,u_vc‚ŸeU|Ø™Ÿã!ÄOòøƒü(-²ÿ¿$gð”\—šî¯•çNËÑRâDˆ¯óñÜW4«#Zq¬lq_È››k(p[·óĉœøÔS?Ï–|õ*šñMÌ_ÈøOGëžG©dܧ)ÕþtŸ@pMá3G‘ìiÏg²“™ Í„ÑL®hˆ'ÎÊ7©¤4¹šóšKt¬GŒ%l+áYEî12±¬Šx๹ßÑU´˜âøü¯ÔSuâJÇ$•/Yík¾½>ó»º!ÜWæ•aÒ¶<4ý@Ê!nDïŒÆ8‹×ø£¸$;xŸK6&ãôôŸ”Ãm-/bŠŽÆVi3cÉ~£Ñ[ea€É]8¹\ åö“IÇZ—W\ÇóIîÖXpÅŽÇâØXm IÈqÈ8!Ž{=í 2Ñ™‡Ü.Ǫˆ¼<:‘©ÈÑâ ³—šQ@¦ /D4£Ì”-ר‹£³”1†øz¢([ “™WŽ= ŒaŒ±qðèÕÝñøòÿL.Y^š__ž¡øiTQÏþþ3ÿb/զĤ.ÌpWç$0 såMlËnk°ÿó‚MÍ­±OM¬Ù¹8³¥ W–˜´™ Ý:iÍbÇ Ç¦ru,3¨R™æÕæ×—ŠwyW šXÐå5W7¶Q›#rÙž¤|†Ncggf²KˆU¡óÜ%ŸãÈ{’Ã8äF9¬å,¾ÄÁsT}&jÅL‡³'¹³ÜyŽaDç> £{Öž© ]  [7v‚;ó÷|*Gïô§Ü72ÑÓRíHÌμAs§oIß•ŽWgÀ-i»Óðê4X JhfÀn^mÆÛãÁ¿:s*£ s؈1·"Ô–„&½”„Å$Ð&ARª K¥Æ/Ùb6ÔÚÀfC­i-ÎÕJÚAí¨vR{V«Ðj£Zîhù -d¶v€´€Fô¢ÑÆì¨Ù˱G9i¸ __˜æÎcã•9 “póóß“*o{aÃ’»Ö-³>œ1øÀKŸœš~zÙŠ£€ýrªž‹­èÞÉþ=°÷ìÖ­o?XïX¼fþâÚÅëþ ¢<Q/t…ž.Ë[á³=Y¦/a;ç$¹EZž}1TYL–ú4#šf ¬H†ÄZööaö)–%©hÿ@ÜÖ8­‰c¯çq'rÇ*¹ÑÐ*ÑÞ(ˆâµ†#‡[oRw-´³Üî ñy4dAŽbö-ë[Ö§Sû-ÐÙ ÜóÜF·Ñ1klÏn(ú·ïm+¸ù¿p{“æšUjÍ_ñ¯ïøì³;.7,öªa?õ(éël²>HD;$OS|_u·z»šâ^%¼JMTô\‚Å¯Æ å¤æà™bG1²r–‚çþäߨN ,Ó@… 5¦8 ° þµȬp\Û4{5‡4Ì?"~í |¨w5ðŠNjà }šešíš ÍaÍ«šwIÉOkh¯½pjÒ¿2êÖlÔ0„Y 11aô(ò°æ$¡¦BðÓêa#­´^Ó©a®®ø›õn”ëd:Ãk†,×sEš°,ª}š÷4ø[Ûò®\+ó e@¥ñiØyݲ*=· öäæô¾yøÀ*GN÷ftšI<ƒRQP*wŠ8˜¼-oŒÛ‡Gô;õx"úp4f£c£±š7ñXÍ™8ÌâXŒ•¨JÚ£mÚhn¤É ‰_Î¥Ab•EªØ@”qã€(‚ØÕÕÄKÓ¤€h#+g¸ˆXóßÿ²þø–ùðÑ­§6½˜Y³¶¢r`q¶sQ_yåàb;N™úpêÏ»ßǹ¾Ý¿Þ}ë¡U™Ù‡¶Üúت¬ÌU‡©/&}~’­AóЋRC0g[0n5Ž™5q^vóAž²Õ)¦œVMæd?‰Cs xá !d` Å£êê()1Ååôך[ÍX4ƒ¹­x²C±<«gÚýÞbŠÁàä²"Jƒ=i—ÒpZš(ĸ6õ ªA­æˆ6è¤.\ˆ¼ˆ¥ƒe”_­d|á•Ü%²~c-¯ÓG«fZÃj5ap«†+ÖHK—ùÛˆ×>¨¡Ÿ;GMá4^=I‘LºŠâ•ˆoWi9DÖwòN7¾¨Ë¢Ž—†ÃÄèté]ä&*/r·éŠuîÙ`“tH‹”diåÖÉŽ7wê¾m'NÀûoOUÿÀ_ÖMmåÎ|ÝŽ5S®ËÈ:k!¾åo$–ö¢?HýåPQ‡Ka{!l› dÁ+¨­&«ÃºßÊ6™˜ñ.ìRÂÙ»l+¶Bè3ÂFØ›³³Q³"–4”oVI‚Á¯Rå7#‹`‘,ŒÒb qþ› ; ˜RäÓÃEP›òïÊ Ÿ)ÉC>çj°C“jì °ƒ=imãa ¤s8›¹IC/ÈÏ:0é¸l‘wËfGfd`ÊŽ®™Íä*¦‘ñÉ\Ÿ\Ad0€BäCx;š@¸D{ƒ?¤…>íˆv§–)`*ü}²Jía61w1LŒ ÑùYªÜrây¬‡°EÀ¬~TõÂ6a¯ðŠðŽ z_€+iÎ$+€J`0e1­Æ+0¶cµÞ¤—5úú]úýú7ôïëUÓzxEÿŽÔÃ6ý^=nÓC…¾^E=°úX=~íüŠ ™”P1ÐL…‰fÂû”öSN°‚ò0þoÔ~1„îúúÎSž™jÙž« TªTc®VêW¬˜wµ ¯¾£Îkdº>ôàÒÒ z¬Ôb-á"»êWEf‘hìúàíúPî*Òpšú «…7âášÇå&^Âá 5¬—We-ë­6pR˜ørfžÁ»~3µiò/JC¬N¡0ÄU_¼Ä–\–â¼^£Ñ»À‡_ûX3 Hʸ_"ú¤f¢ QQ¬&Šn¯VEû“´Ä‰ø8„A-f—™ÎÇ[Íãæf¥Öì%àQóKæsæ‹fei+p81KË:ýf)Óé͹æ63sT&b$3h lD“HTh©Ê¼nÂ…· Tt‡àè!G‰Ã¤‡dÙß³›æfp¡ïăÆ•v׉•Iº9ú,·Yý6sêëjæÔ›K»j ÅN†‹Ë.Ël¿ƒ´ùN2‡~BÆc:‘*'H²Ú­%V&1Æ'¹Ôãjü’ÆÕÔÓjF9 ¾siÓ0JÒrIxÁªÒBá‹Pæ¥L< ƒ‘}4 ÝGS,qc4ÒÎlš9"ý·þÂ̲3\_³ÜŒÄ¡¶È¤ªƒ’ïÚ7#=¹dfç ýôõ;g{ºð¯hßrSË™¯I;-ø-©ô~îǰO€ w X^Žn6™23}fx$Rà¿ÏÛ 0d€e†n¾OŒž:Ž4’% ùéR,Â> l·@“|H´€Â*‹^'êVPX3¬…VŸµÛºÑºÝzØzÒúªõcëÖè×è[©¾¦ßû“ÿ+ÐL¼íÚ"ŠX^a#Y>ë2’E3ÂhõŸ[á¼^¶þÚŠOXá n·ÞgÅA+´Yau‰ç[A´¶ê­øCëçV,“²ž°b™²Ó´b™0ÍšoÅßM·Œò™0Žò„™ô]*È´TøvâZé0¡&¢†hó÷Zq›uЊ+¬õV,Zs­˜µÆZñyë%+~ÅúŽ7Ý<ÒøDˆ BFßÈÇÈJ¬lÀ:jÝc´².+ «`ÅJÒÓHLÑi£œIÞ>ѹɟìÒ"›û×ïúG¼Xë?8¸Î ^É–“y¸Ï¸¸–y1êq%¸t²—k¬h¯l¿Yé@ÉÈ, qy¡®v|7eÔ®ºyqj YNèjw¸uSK'?Œ²X0oN‰zçg«(e•d^Ýx›ƒ-¸ü„©¹Ùϫ疤àÕ¦?ÕÇŽM}‚d“â܆GñA|s¸DF!B?ä"¡1‰’çæ2V£…=2Õwûí¤ìeúå!ov昔öQ<”dß7ge?”Í•Þ Ü)|_` Í>3.$ë:^âȤ^œ\Œ‹“!™Nõr`€h¯&)OÞ5¹$¹IJ@ PL5Y§Æ¼Î£‰3'+Ù²lÐhƒ8¥Í¦Œc´ÙvÁNÇYµ+Ï_m‡|;dØáK;¼jÿØŽÙaÂ#v(´ûìÝv&ÑŸÛá$ÍÚfßkÇÝöv\,‰•#7•]ÐʯmÒÒH…Ò¾êüØù…“9ä„ 'Œ8¡Û õN(túœ8Ñ Ÿ;ác'¼â„“NØç„íNÊ$ÅNˆu¦9±Â ¿ü’=餌ؾHQÞ™èĤäóNXæìvnw2¤„ƒRäC'¼;Ãõ‡NØ+3rB'¥†|g…§ÎÐîû /;íÄ'œpØ Ûœ°‘JØéÄ ()Ä93œ˜uœŸ9ñ;NxÕ ¤-÷É”ÝÎN<Óš4J ,m“ô›H«ŽËÄT¾ 'Sá¬wâ™zû¾ <á™Æ1Aç6ší#ÍaÒ(IœN›ð±ïurbÒ†>¹4·Ð‰g›y˜pÀ;å&B•!TÅr¾â|Çù¹“•ÕZã„܈Z¿’‹”U³%¬‘N'crÂ%YyoPUmsîužp²^'qNÁ‰UJ:ÝgÅèü ”¯„T%(“³­Ö–­óÏ!6%¿ãâlL ™¬ãÝô°ƒ¾h°>óÅÀõ§~­ß>ð¿Å'\ç®Ý³º†ïÊoâ×_]Kî(¢i—˵~Hö`îˆCYïÿZèý‘è‰ÉÌŒL…2”ŒJA|\|á¼r ^åš;ñ«§U:UÏG© ªãg§~uü”2FIVѼJP¼òó•U*¥VùRÿÄÈpºæ83–X.ß@fnküB1=3#Í"ñ»œ˜´Àœj#©…Iø³{Èš7š¬ß²H<¶h ’îL›…1w¥Ã²tȶ6Yû¬LŸ ’mh„ ¦;MXa‚Ls¿¤’2ì~Iã*2\Gõý†`ƶ lÈ({”IÉ’’áGªŒ{ôp“~­þ=¥OÒc½f8A Aâô½нh¯ê‹Áåhq»[\áÀÅ‘DO Ãû"3û#î‚rîÚC®²ØŠQ(­Æ÷ý?<0Z›VÑZRØzƒKyš_üÑš¾CëËÜ ƒ›oY·,ŸÛºáÙ{o¹eDz²å–”²¦RÝÛ»JòVíYY5\ÛÓÕÝW¼/£.¡ûeÜmȉž“’é®ñv Ó€»0nfV3¸ÁÑåÀ ö.;Î8=ýoR±b£ Q˜¶/ ïL{/ 3$¾°‰$ǦFý’Å.ã#ÞccÎhš%½ß*Šêþ³Q@ÏA’†³²„´ U! «ïPã5Ä©AÍ%άUåƒjïÑu&°Ó;ææ¶’)pH^¸;äÕ»|_ÙGb®ÞfTÚ V£•)ª8Ö,Ýú–džÆœTgUvù}CuNûâõþ97Î/ˆ¹ëå;L¹{:úß(Á«C[}ù+6ûŒY5¥6GýæÚùëjBrz,þbßÔüôiÃ#û¯$¼ÈÖ ¹¨½'ù7Ï›‹é+î*‡†è®hÜ\²ºg0… ÎÐë>>1~$~g<«0Ç™7š·›YÞå“òRsc`k̹S5ªðÉ'uñÉ~Ž+«Ò&¥‰UR~« P•Xµ§*TÅÎUÁdÔVÁhÕÁ*¬­rUá³U—(ªlmê¼€EÐÎãø@2  kož¬`y/äO”"Ÿ*]½°>²±{•+€«vé<`›Ý›’× nòžçŽaŒºX…áºÍNœÖ²§[ŠyN¿¥ÓÓéËÀ±¥ ƒþžû[ŽöýÃGr0ðøIƒŸsÎ ôVvÌ·X¤U…=Kò¦–gT­*Kª©K­¹yÙ3Ù5%¶Ê±7ïºý콋úÚËçe1¼£¬:óëþãGÌ«ëЛÛóƒÁ VÙs: ÷M€ØµŒuÊDƒR )½/‡C[f™©Û„åã1²€Ý¨Û®Ãô€«£A­‚åN%ÞÈlg0‹AI zOìÁX›=šÒO 8qX«´ S{ìsXÞ;ÁòA4ÐKÙAÿ­`Û¢;Ot÷¿£¦fÛ‰Õ]ǶÝx*‹XäÁÚ¬ìڡꪡZþùSŸñÑW ûd½·½ë‰ÞÓPú™Ôw$~¤†ïG?ŸÐÁ>mÛl8(n÷ŠÌf˘å! ³Ù4fzÈÄlIÚ•´?‰iÎZ>²¢4þB¤£~—ÁkÀµ†— è^¶d8¥¡D=åêåpoR2‰~³!¸5a '$pöáTeÌpü¬ƒ!]¦+¦¹Q÷K·]…ä3s·ü}ÁìÞôÌs¦g­ÞÕ¶ÿÍÕWm{qcÝíÝþ¸§’7­¨iÈÅÆê–µóÚö¯-õÞ|tà“Ï^I¯^ã›ßëÏ´ùz+óz–àßýt꣟®´úkM+*ÇÎîvÝà6UÞrtͺЖS‡Ž.ë.ÍiØ\Sµ¹Éê[¶ímSËY3»e ´WúŠFŠpŸ}ÄŽ·§M¤á4o”QþjK“W+›”x;3ÁÐsÊó’—àQ•t¬«ËFç&k}H„\á’Àª„PxË`°lO¶”ÁtL–/ÃÉÎ@ª§ÕšT….|$/¾ÃDÞeÍ›ŽÈR"ì”32m)Ì·|ºòÍC‘¬–}ƒÁgr8€ÈÐ3Àç(-éòîkÉz1¡tÕ e«ks2ª×új:Jpê–³ XÌ-5O5qŠL©gÒÜ%IùÕ.càÞ7oë|xmQjÛ‘»èáPIÿ:×ÝC¿ãáÎ +ýŽGE¸•j¿Ú'¢á@ôt4޶"Û¤í¬í¼´Ö£6°Í|Ç“œà›L”($æ&žO¼”È©“P¢ÚˆôNï·ÇßòY•-|®OН¾³¼÷ÎEæçt¹>y_âY sÛ¼EyñE]»ëéÇR•½•¶œú›k.ßΙºÕº (S¶yþŸÞ'ñ}sGæâXhg'ÎÞž2‘‚«MM&\Í6±x;Lpµ%ˆ ŒÚ9ƒî²åê/éY•>TÞøÆ±`zÀ,èQ´‘sðÿ†%PSÁJ퀶ÔVÎÈû1 ¥Bé¦NQÏ|憶üÈMæ†X1ƒÔ Xù¼ðÅÙÃAbôp§^þeSGRQn*Kæ&{aª)ež11®·yêÓ©?ÈgƒÝ‡o>Ú92O#æ$YCò –?‚ŸÆxÙÕܧü¡•۔ا\¦ìV2Yô”oQÏ«HSRœP¼£øP¡$²3j(…ÀD”9Ï’.ÖòiGÖMþI5œPÃA5ìUè‚jhSC½$5ä«+ÔêmjV&«—øE5Ī©ÁÎ9¤fYušL&=ë.÷ËÄzK¦ÿ¼š,(Ô¢: T³J­bý &ÀjA G áOòɶ#¼é鸲 p¸ôc7wøÓ“Öõ‘ƒiýê¬Æ¹øû—ïgŠ.¯Æ/ìd2víüú·»d½mjÄ‹C ¤9wià.c¡ƒ.„}} APŒ*°BýýÈI$k|ÁÄ!ú‘q¸gZŠòäUrf"†™¹­öæÝíϬÜÙèp4î\ùLûîf;ŽÝ5õçúú~÷ÉÔ®]SŸèƒ?_Þ-ËMdqȲø%Í ìàay,,'²œžþg©8ä}B–ˆÎt‚E1N¤BŸ“Y@@tÇ…Jtùj‰`Æ:güv|‹HÂîËT¤>¥"}ò;*ÒÔ®p ݉N°Uì£HMÆSÎÛj¸S °0ÆO(,bŒßÃP„w±Oóï*ažò.%VrÏÊq/ t‰®ð—ˆ²HáOÁut¦ÅîŸj„Çï‡ÇqÛT=©aÉïVÍgÚÝÚ'tgôËôŸ^Ž-Š½Õ˜k<Ww&¾7þ™ø¿%ä',‰h!KÞ[ ÏÐr¡fâmå&IïÐÜdX6««•³z¤%)ˆ”R¢u˜AI(YBsOæP4z$+P :•h3:U(æF`Å@eV–Îþ7s D` Ú 3ÔEÛ?¼aÕꮎ  ´]âZÒŽ~R´½g¨«kméYâM½}½âÈÀ±½££k0H4BÉÿçœïvíl!YRâÌÐ AC¨õ ^2ðD2°D”‡rÉo– .âäEäGí$×I jÔ:P棵ä'^UzXNu‘wyo”ËRÊI©¨’p›ê \‹lŸLßNî ¡n'´]Ä ´x Á  îï¬ÿFR~•\Íé#ôý$w)Iõ¾´\Ú@ä£üæLÁôËu º9²TßÅá»s—É9óø¹D¢\Tø­¥¾›ãÿÂúeÞaÊ>™w¡X*Sä’TA¹¶~™ªþ[j¬%5v“òT_W(;dÞA’s poD—«‰ž‡d :år3m&5SóÔæ†ˆÕ \§#*ÝF¹ÎE2>(ÛÍë•SƒdÉç"¿Mò/‡Ð\˹#Â7G†ÖÊÿj¹ ƒ²»äî!´áÞΑy®#VÕ ÛøLÿP]„ûñÛû¸[~SÍË%‚D’v¹¯fú˜èpÑd—¬?Êq —ҬôG¤Öv"-Mûm¦O7\¥ãM²<ä)’¶ óeYþ»Ïn¹äsXZ/n½ˆµk/¶^¿xô"§þèÃËÿà³hÿÒ|q–ߟ÷YÞ:îüÅóŒtÞ]è;ïK°üÎs®áß=LÃ9`>`¦-ÚßX~ƒå‡ô˓ﭗáÅÉ2ËÏ–Ÿþ,Ë2ý<Nž=ÍÈЧõy>Ë)ï©ÚS§¶ž:pêè)åàñƒÇCÇíqØó„žís Ò>ë}öâ³ÌhhO‡B“¡³!ÆuÔ{|:ô4ž|úìÓØõ”÷)|àI˜|âì¸öÈøì:2pä¥#ÓG؇÷§Yûa`^š€ ŸÙòý½ñ–­{Ç÷Nïerï•îÅ£÷Âàøè8Þ3“ãgÇqíîÖÝ»™í¾iË;aÛs-Áa¯e˜´` ¿ÌÒï+°$ABC¢;¡Aéf¤Ím$¯•Ü7ùæZV4û-ÍämÈÓ7pD'lÓ0À€–ñ2øbÝt–ê Š|R]z–ï-©>Õ>Ñâ'<«È}Ôç|}xÔqyÆh„> endobj 31 0 obj <> stream xœ]’Ínƒ0 €ïM_0€YD३h¡sužêé¹À§¬}Ùºå~Y÷.å/à}@(KViÆæ©nÀÖæ^©HŠ"õÀ´ÿÖô‘S®]óY[*]h„‡Ô±"ŽBdM+äÏçÈ!ó9b¾ ÇÌù‘9C>4ò‰kÒ^gÞ—öÊxþ9ç\й0ÈÇ`ð<1ûGgdöi~óÙ_È›?ͳŒ’ýc<—dÿ÷•ìÇÈì¯ñN$ûkÊeÿˆbØ_Ó^ì¯ñN$ûk¬¯Ø?ƵùãÙÕvÿX_mþ'äÍŸbØ_QöWxvÅþ*§Gß^Ÿûó§­Ds·Öµ51õvQoà·Ï§qÂ,ú¾¤eÆg endstream endobj 32 0 obj <> endobj 33 0 obj <> stream xœÝ{i`[Źè|st´[»Ž6Û:²¼Ë²lËKÛñ‰ãENV¼a;8¶ã%vÛ±„°%@J6BPÊZBX a‰)M ÊÒB[nàBÛÛRJÚrÛÒ’Bo¡—[°ü¾9’mß»÷½_ïØÒ™ùæ›93ßþÍÍLm&z²“pDÜ40™«™ !?",ƒ[gÄï´¬ÍÅòB¨42¹nSnø­?¢@¿nãö‘§Ò·ÜJˆq ^ÊÔU ©øÇ(EÀOã·¨YT„õÌÑM3—|G±ë~¬·cýÛ'6Î~òB*MXïß4pɤƒßÌa}ëâøÀ¦áwÜïÁú „¨§''¦g2¶Å ©S°öÉ©áÉo«?¾뙄pï" ð]z,*Yr ^©Rk´:}ŠÁh2[¬6»àpºÜžÔ´t¯èËðgfeçäæå ‚…äÿ¿‹?@ìø]CŒd‚¯áW~¾•{„¸Ø}îýó¿ã¿‰¯œûÛÿËY¨ç cä&ò3ò½d½‘Dɹ!ç_ß%/#Þ6l‹’rôû9‰í슒~r5¹íàýTŸ"ÎäÄìrÙDà“£8‹.(Fòäq²žüœ¼„¥ hs¿!w’7©Äu¤$!#rq„ Ûl+ý8 §ÁßПüß:êÄÄ`A,H°¾½+&ÕcAHò¨áxQ{ ô#‹ÆêeöÅBþɘÍ_·ÀO6­†±¶.¹K²[̶,Fú“½b¡†zöd±a_}b l,ÿª®S$}e*DNJµ3…» éLpWŽ@çö¸nî2a¿@W P%€Îî±ì\³ ªl dGšÌyMÎ̇ÍÔÌ#i §dwFÌN³S)JwD‹ŸU‚ÒœÇeGÓ~áƒRø|i&}”•YµYu•JáSR݃DÅ¡$¢Ùl:›4=–ÊJ$"£!–B¡@a¦ÏË'Ø”þŒìœŠ°#bZ¨HȧàP% “É© 'ž{¡çú¢¼ ÙÝWŒVgTu­ëªªèݾ,rí’¬!õ¢pëÚªžu=U`ûÚ¡<û¯÷¦XòÂ¥%K{êW¬¯K·^ßèò®h)®«(«ïc>ñ÷˜ÅªÑ':ÉqiÂaQ_¤§V=(ô°‡@»qÈHf³¥Ç¡‡V Ð“¢·¥è·§Àh –¡>ªR@‘bKÉLáRRÔNƒÃáf†C¡°)2Èä^µYÓç4詎sX8uŸýØ•)@”æòg)až‘I„Ð-¢Ç¿P8l©t…4Úídœ¦³hÕz“äóùu°à"­Àœf¢J_÷ÿî™s^òÊ¡p£6†åÙ3Ú“Ï(®úôô‚«nÝçZ¹^Ld}nC{íG™È2)ãœsN°˜uú&ƒ”M¡)†“1ÅnQ¶TñçžDMâyb"µ%¡N¯VVcyMŒíèÓlLW6S´¾ôM[‘® ]ÐÕºRÜðÀmhqíÞÜîžÕÜK<÷×™5Ñàì­Ìa¾Yݳ8Í©å|­â*â%¤ú!ÇŒc—ƒK‘Ð}^ÝáûÀGUQ7Ýàv§ºH“Q0ÚlÖ"#£ŠÚèD.ZS£.§Sp•Jn•6á ‘ê!£:R‹æ0VJ˜à†™£ôf1ÃY^á˜Wõò «¿–†Ua•Ÿ3¨ƒþ@$ËÞ~A¾°k•ƒ—©¿²WÊú©é§jícÁ ÉÊ {Š–å¦-Ï Á`ÎÜ>ûV^#[O}ü%ØAÞ v’uBCî30RJz iiPÞÉßk>Bj?BJ†Q+Ñ2_U¶P9Ø<äxcGæÛÚV÷å„rêËR‹3mùU/^î¼ ­½"55-_^iÏ—’;•Üe’K¥œÁ hÌ€]>˜òÁ :|-ˆn››f*á6óÌxŸ”²ÍC ÈÌL×X­0¡Ù¡¡M6€ÕŒ‘U?íPQÿVpÙV"Õ ÄB K%#ihÁV&¯<¨XÂ'=üÖ%ô? •+ûìGî‰ó:3‰ìÀØŒ‰r6:þ‚‚²×Ê G™@Bó-99B(šaj1õ™&LœÉ”²J˜SÑM„äð¥d³ìnQ~溇ó(Ëtñs¡+,„n\_ë]¿û*pÖ¢ÖÚž[ëÒÄÎáÉÅW¶ççF†¶\S/ 7fÍÞµé¾Må5‹«FÛÓO–ÎôÖj5Ùîâ‹#šœ|ÏâPzqÏÕ«ºï=0ÝôV¬ˆŸT—]Ð^Y±õ&Y¾–ÌýQÁcœã%%äF©2otæŒäP 7¶f…:ti Im¤\Õ¨¢*e2¨D4š¸ m?£O.b¦¤”Ö–+}¶ôt©"?2!ì¨à¦¡Õh5 Ú–»²EM’9HI"¼Û¼„Y”„„êí×4$E­à~98‘ƒ{ÅBpo¥ZÅnWCý‡óÑIõÚÆœìÆÁËEKVéüËËÊ×Dk ”x]SÃ=ª?rÿvJŽN /º²¥óÞ¶]T„fÀ]زþÒ«%•4×:w!Š? |‰…\#™ F“Ñ’¢7é+Њ…•Ò¦R)ù*M`2)ˆÊ¤¢‹T RÙlŒ,%{O‘¥sgžD÷£Ñ*‘˜#>’Ñ”÷¤¾á‡[üà÷—© ÇœäÀ*n6/ë_¡e°¸2\ÚT^¶¸ µÖTÞ²È\f÷Û -f;q·ò¦„=­­•Ù.'ÊèJåйU\v¯œÐ”0–Ê_ÎdÌÙ~®’ð¼ˆÌù.líÈkP¨XR£‰¿±Ž%5ùAǢŅ"?ø&9j¾ äXTUbu柅Tß…íy]ô¡h•oð1¿¨DˆS&:öb–èT8óÊ«¥´Ùç¾`žüyÖkbÁ~=ŒtÌ!ëNÒÆ†Äóºš2¸ˆ`&HšPÛ˜ÊÂLX ¥„ä jM“.й¯·%ÃnTs­6“®Å`Ô`¸.“ªä\äñ…Ì/ ÙÝ>ŸþÑ¿°œ®µÑ9žX¬'»­»·èË™}?¸(gIKÎì>n+ø\qt‘÷ï$}¸FG|%—Å!nrµÔ²B ú1ý³úÓz.ªï×Oê9½>Õ˜zw*K…¾Ô‰Ô©ï¤Î¥òR*¸¤‰§YP ÀuGy‡ÑÕ$CÔðšáŒ70qÒ¤û"æZ\= qË‘ #Q^6OÉ»+,ëµúYØ"{,Š%}01Ï<ð«#ê£úÜgî Ö»ýÅÖ´ q–'%íuÃëñ•¼¾8§î×uê§ìÞ¥Cw¶ãºFæ~Ï=É=‚¼Û$Y",:m²f¯.A“'ë.JºF“—Aš¼QŒº.qíqq«\à:9÷š$hS",{[|F#²P@Ú5Æ$ e¢1c1±,ð½É¼+•Ha+¬á„y›wIäô ŸÏÊã8ƒ*¢kB .™ƒßM$äô’Ïnw¿[­D¶,Òç…wgŸ’(ï—ýåôJ\k!ù¡dq‚3Ï‘ÿ[Ç_ôûŽŸ:h‡'ç>”¶¸Ó"*lu\ë Ã+´mïÿ+H_þ[ö!/Xlrê ŠÇXp{po æ?cƒ‡lp™m¿ŽØ`(&W>gË·å§d7¯J…ºTHM-ÚQ|I1aÐ]hÍs*„üMОakŒÌq"Ã-ŽÊù¬¿SU–bÉN]Ãfv™1ëb[ndÒÎncrP2ý²8L½¬‰Ð‹éèjÀŒ%÷‰˜çÔ8¬êSJ­Q,Íó#ÔŸº\*ÕYìõq~ßOž›QÏ‘o¼ï-ô§êM>ˆÒjðå—ûã°ßŒß¾µ¦Ñ¥ÖkT\Pe7V]óJ¸ sfd #Ýj¿ÖN×Ú¡Ô~æ6 QWü™™ \zJJÀ¹š·öi'Œ§ï9‚0£V!ù£þ~?ç÷§õ¡%üü® Qý˜+ÂT^V Ky&ÓuÎ_&{~XØŽ¨…0ê }êàÏo^yùÛPòäÞ7»ƒK—Äú÷üök‚koݰdëºNodiMhzçë…׿|é Pþ“)¯-þÖcЦÄë;²¥Î–p¾É,ºŒ¨'ñFîzäe9!]ªÌƒ& XÖäÈsSnŽ-77ÇC´Šr ÿEáú0ý#å(Í5SžVR¥2žšz"÷Ý\z,îÈ’kÊ¥¹‰F)µÑLZJ¹f(Мœ|ß*Ç’´ ÓÖ¤ý2íOi|Zš#5·E mÔ–”ÈÛ¦]çI†cíǼ$È\·ú<_ä{¼°- !A5.§yþGŸ7!Åñ™ª±±éwŽO´fM®ÊÀªæZWð[“Ü#³™pËâÙk›:Œ¢0Í­!1œJ¾ „Áh°¹2ŸÝ¤㟌Œ0y*ž{ŸoBúz I:dÃJ œ&€Ž* U­Q7ñ Ï+²1ÑNÞ¨Ð!áõ:›^¯ËÇôià) Ìhvih¿Ú4P¯, PhàC oÒw)Fòp…lÚH©“‚ŠÂ~ýSzÚ®ŸÑÓ:=`æ¤Õãs©^O5ï¼Ãô°‰šLii'çvJ¶¢Å‘¡4ˆ¦AfZiZ}gJƒ4æ tb^¤? h4YÏr¤FžâánþO æ+d½†jÄÆ·¨ ˆÀ8ÉRJ´n¨“Œ…Í}›SxŒt 'CèãÿÞ…¶1ØD¾»›˜ËóJn33Uáýœ¬Ûa+„¿È|nWÝåñçQ| ”­¸ô¥tO‚éY…6ºaö«²ž×ÐîÙ{á+Õ«Ø<Ú.Ç6Uɽn+yVZUn† oÐjLrãÝÃ:ÚȸÄÓÂZ¨ÔB.rR»U-£5é%=õêA¯·ŸFë%éM…ÙÂ5qÀYû๠ûàÖÖƒVΪo1Í “N¡²Ú-Z¾E…Æ£c&æ"0“ªE2†åôJÞX-ée¡1°âLª‡ütëÝm Éäƒ9•HœÅ0oàçØÞx`·ŠGÖ¨!S½*þó¿¬U¿ÿó›—<À¶d?[w{‚AGüw³;‚ðÎþø#‰¸Ù‰¶þ´‡•$&m™ ƒT“!h ‚©B !#¸6H'  =€®†²á6ìwÀvT[WZéA3Ô˜¡Ç°A=(¾XŸ{±]Þ¬ÓÛõöJoZZTæêݪ¾röŠŒåVŸ›¤Czº[Af!ñ-oIÊlò–ä—7$Ïß9;ҹ䦤r~SòüÓ¥]>ÊÎY¹L•Ë/ uìè E‡×G‹–ntãà]õÅKyqjyóØ2±¸edl¤¥xéÕ/îºò•:˜~ÖðYó.ØX_•Ê+jÛ66_¼ou¡Ýïawfö²žÒ’5e‹¤Ö Mƒ_¯ çcnµãÊç1vö“Ř“kÇ‚(€Ë  v}‰3RÄô¬‹EEÕÛÃâLQ6mG‰ÓA»Êt :*› ‡Žêò¢^ÁTÖj51RêZ4ždö‘ŒÅdÁèe!uÉüÉË51†0žÛzTÛËaf³B¦˜baWä=P(x(8¬Ž×©·¿µN kÇÝònxVãÐå7¶ÚB¡|­%,°T®ÖäÙ=…U¢»vÙ²tª÷úR5qg(äT¼á ÞÚ¹úܦ8§T¬:E¨uãöÕ,õ´e†Å„¼ížû•ª¯!6Î#}-½ß"EDA] Ài´êôº%oS*ù\d€F QÑ`3 ÓSzIR3õ`’÷‚ 'rt+¥\=G³88@KUe„KŒPo„r#ˆF°ÓsÖê9%éU˜l&ª3™“ Þ>èsœà° ³Kj›Ú) S@“ã÷ '„…w¿A€a—@ûh@À&d ¥÷‘gØ`¬Z/´ IUŠÅ!!Ñ[)ëèZÁ—°æ›FU±¶'×DV'¿1'yMèEâ”.>qR ìYíGp攊‚$D…~á!&<'¨Œ‚W *hTJ“Ž(û )—sÐÂ|”ÊIžôÈ4£Ùý‡znéÑh% dØ_ÆV£žKQð5§ À[· Ð,@¹Jþý=¾'Àì`D€N\rÃ-rµQà·Ü&Àµl•q*Ùá(C;ñ€Œ¤˜GzSîIyÄ[Øò¦0$÷)—‰n;ùy &À}Ü0š ~½¢Ô$óhg‘|û`Mo/þŸó>}‰óíó/tUkÚ“ù’¡òÎ-óo5½èÚ0@G$³œ»B¨„áô% L°kÞ|Þݨ’k0ì?ßn>ÿ{!Ðé=/Ðíe§ ÅEH:A4ðè­ÌÔ«”/Ì>R{c|ÍÒ0È}CsÏup´ðkpïýاQüéoá®þìJ>…¸ÖÅÝöÙ0wË,Wú˜ÅG¨snr‡ä¸L½_M¿é„½ÎÛœ99§Ã¡ÔjSS™ ¬t-ŽØX­Ð£R]^u¹H¿M–5A0ÚB¶Z[ŸmÂvÐv·MuÚöŽmÎÆÙ¤ÌœˆÍ¦´ökóŒª> Ê>…eaç™uy_ŒKØNtx¶$á嘓c[Eò>Ùy¯pþŠBÎïã|a´çð‹ßÒÎ>¯=òúƒZ…XY§ôö¬isÄÿ2ÏTÕDjÐ:ÌþuþÈ$þìÏJf_?:Âömò‹Ü‹ÄF–’[¥‘æ%P¹†C[B´=¤ÐTHÓØó:t +¶è¡ YjçßR %Ü«„íʽJ:Ša]©üJŒ•&/ë/‡òrSÄ[½ªØWÅ]4Ûd'­n!i§åc&–Ù˜$&Ä`þ¼r^Vz³äìùÜáä—^8ó&Žá*àHFS}sÜ™•ï‰î¬°çKàµ5tÕ4dæ._wå¡–ŒÎžßƒÖæÉ;GÊG.j°[‚™™Ó­Ë×-ËÒŒºrKœt½R«ä.¾õ{c%µ™‹„%•¹Å]—-ï¾ÿжÎJgTV·ñ¶þƒÝ¥{÷JA y \¶á›çþÄß1Cì<"dŠ¥"]‡Y”FÜ[Ý׺¹Rw½›í}(•ÌìÉɘOrµ6ÑRd¡K(mµ6˜Û—ŸŸáì3Nؘ@q6›Q‘Ñdzdj> É'oò òþZbÏ!¡¼ „ZHª*¾TÉ[FP<»äÒ{WîûéMì<ÅǯúÙpxWstÃï>2R9rð¢òµÎÚo°Óñ=ÎaÅu/_ù ”¿9.¦ÆÏš?Hyé¦å|MÜ›xÿl%ÆëÏËçEd—dk AWöh6 d&Ë•Eó¶£}FÒ M³Fúr!— HÖrsKH‰©¤¨D*‰–ðEš.ÌZÄHT@`^×ÍÞÝñ‚×u šÀÞÜI¦'ìD£$q ´ùì¼~éµEÂTÍ¿/ØÜ…⯨¡`9§Ñéø¬U­­Ù‹‡—çg7_ys[Öª¶Ö¬GÒZKÚöVô^è­«[š.¿»Ód —…-ÙK‹<çBKMtœÎ5·½42y™=»$ù·Y¤É)´GKпe”‡ÃaNY$Ñ\7¬·]jÛgãºPæ(™H[vM’P©Jç˜zÁpñp_›˜U§·,®oJØÓ™»w×´áÐë—¦ŽN¤W/’Ïb3–5·•ôÝ4Tzì[ “-ùm7¾dÓš=¸Ÿ6¸ìú¼U—¶^¾S«nÕð…nX:>‘<¡Õ˜ôʼ—}õi·¶hÅPÍúÃË™N>=÷¾²u2 ¥«{CTf4gБô­éצsjà”#ÜVîZŽãøÞ¬-ä+„®%@ëH+¡ÅD*â` ^ÎŒ}—ÚOÕ¢68 Ôàà‰¥Ú8±_—0ª7\"¿7‘xß0y,Ћ&¾o^º0Ref^>Ûµ&ë ‡¹¼õ˜^Ñ?`ZuàÕï}¤Tü††:Àó‘©èä‘ IŸxñ³w.~ôÚÖgžpWÙ¸<ð–'ì¬^[ŸU=tuSÂ&]‹6÷}\¿ƒ4K£˜))6{Õh]ZªÕé¸Zú¥ÔHt“˜¡él\¿ž³Ó>’´¦ìý„°¼ÏA {_‹ÅEa4šó<–c°`'¦ýÙéðÒ5õEViÙ"Eá¢z­ÿú-ŧ_×¢­ü§T)^‡ªøK¯«tŸnð°8ެ„[h? ŽäJÎ"*ÑIL¢Ð“€)Ÿ(Sp´ ÇÓËŽi­e>ûJjƒ[n“ÑêÊ{|+z”bÒ@öK)CKf–ìZ‰":ê4f:|ìœÔéqÒåf0²CÓzÚN‡(‡Ny鸠÷û™åè Á/øMd|‹~š‚ã&I㈘xkîtIIzõ´G•>’8±—µ£ráÜ>ñ¶S,%NHÑäÉgÉl›GÎÙ³›àè×!‘äÓùøÈú…:÷^~SoiçÚêËž¹ê…§ß,u”O>Ha;… º(i JQlQóf3˜”JöÆF­||N¾ú’ÈñC%æÊÊÄ¡‘9œ Ì(Nè#Â*N~¿ |ô?*ŽTÌ~ÓF?8|øÂò•Z3{?¼¥ñAé%Ü­ŸM]Mïv׋YÙ³nYÞñc¼ø/¯ü~mŸ±úcâMüFëå'~5ÿ³$¶ƒŒû\«ši6Iè Qùâ 䢅_/ ¡DÒ÷I=ß9÷gZI¦¸¤VAÈrüÔ`}»siÄņSû=–ÛÞƒõzlü´à§9Ùo‰â7s7ðß—û7#žï#ܹßa{*ßÉÆŸëP¹±\Œ°*lwb}ò(Ù}?fcbÿÍ8~«Ã“„ÁV"Î,ŸVL“kéQV'ÆQ¼ È}ä…÷i½™¸'¹'’bŸâ?ø\þNÌê•?Rõ«~¤žTÿZs¡æEí”öŒ®K÷‘^­ÿeʨÁ`8lê1ý›ùj‹ÚRnyÀ*Y`›´}h_bïÔ¨L÷iD}LxuÖzP’^WÜ0Öš É_1²&É)ömÂ${)Éx²Ì‘ 2“,+ÐvÝ’,óDOM–•D §’e¹”œN–ÕÄÍɲ† 7YÖá6,ü’²v'Ë)d|3Y6-e3Vh°ö4ÂDˆÈy’eJô\U²Ì‘z®)YVBn²Ì'w*YV’îõdYE>âf“e5ÉUü9YÖ ß]ɲŽ,âË’e=¹˜ß”,§8ÿJ²l Êï/›˜Ü>5¶ntF|H,)*ª[‡‡ÄÈÀLØ<>X(.ݸQ”›§Å©áéá©­ÃC…âÊæº†Ö¥íÍ-ŠcÓâ€83504¼i`jƒ81òùþ+ÇÖO ÌŒMŒ‹mÃSc#Í3Ç—N O‰Añ‹bã€;‡§¦¬¸°¨üŠŒüRÇÿÍÔp=ëÆ¦g†§86.v¶ŠÑ™áñq`|Hl_èØ22268,‡§fybfg¿~ËÔØôÐØ {ÚtᢖMLMN$g63¼uX¼``ffxzb|tffrq(´mÛ¶Â$ò âNl ý³¶™í“ÃCÃÓcëÆqù…£3›6vL³õÌŒâÏ[ñÈN~zbdfÛÀÔ0[ÿô–µë‡gÄ™ Ä‘>ÃãØu`ÝÔðð&¶Ò-òŒ·Ž ŽŠÛ'¶ˆƒƒÃ“3H†þF.üg“ݸÐIž)YF&È$ÙN¦ÈYGFQ!Eò~J0T/"Xj%Ãh(E!ØZ€¥fTáARˆ¥¥d#þ‰çõž–kÃxÆûV¹/Ã\‰½êÐy·bŸv,· :&ãàg±w˜lÂûÙ€° 2òOŸ¿û¯•ŸÃZÆ[ÛdÈömFèÎo ±—⓱e\~ÆâåYýóÄÏñßÃî”áÓ xÅ8ã"RþwG97Fðÿà‰ÿwTKðg<ÊŒ‹S9 )7Yð¦ÿB¾³&ÏkÄLJÒÀS–z¿ý蜼Ñ>•™Ûx*šé­ý<‰ž=Æc}Ç&ŽÝ}L1ùØÎÇnxŒ+zLzìµÇÎ<öácÊGçi0“0‘0=î©Æ¤Â<®Ãx/„ŽÀÁ#0wúLÙy„{È2ç}›ó[:îÄžw´U{ï[^-?Ù~/³ñî{ŽÝCoÇYÞ¶|Î{ ¶< ià yÄ Î'Úò¼µßÆZ 8¤åô«7W{7︙jnŠÖxoÄÏ!¤Ä ×ÕxÈó^ ÆK@•ñ\‡Ù}užw×ÕÕÞÉ«w^M·n©ö~¸f¦ó½Ó2]ì’g2Ï;1ðŽ7å{Ý:O‡+ììP…¹%NöipƒMž‚뉾°W: ¶Ç3sqÎO Šå7ç¼þÆþ>ÉÛÇÆCÀí½îôÆ‹›Dïêž"oOSž×–+®š®Cå#õq;¸cœ²¿m²mg׺*ä]…œ‰~¥ÆoK¨…[Ñö.Gz47U{#M‹¼Æ&oS¨étÓ;M4)ïnBêx:R#ž!lïÀd¶Ã6v`’ÜaÒ2Î©ÑØgÜa䌤–Ðð˜=ßp¼½-XqR5׺"¦‰®ŽÁžXVû–VõÄ”{b¤£gu×q€ë»¿rà©K[+iëŠõ§u¯ˆ aAb…X0¥H]÷ôôL qÁôv#[0=-Cäzà܉ú â²Â–5Ó 8Þ§­ÓópÜ-r+Þ`šäòtr|&, Œ˜k¦×ÈÓ€ùÞrkeš]€çÿöÙ&— endstream endobj 34 0 obj 12378 endobj 35 0 obj <> endobj 36 0 obj <> stream xœ]“ËNÃ@ E÷ùŠYÂ%ã¼@ª"AK¥.xˆÂ¤‰["ÑI4Mý{æÚH,ZIlϱå¤ËÍjã†9}õc·åÙì×{>gß±Ùñap‰%ÓÝOòßÛ)ICîöršù¸qûq±HÒ·ðî4û‹¹ºïÇ_'é‹ïÙî`®>–ÛpÞž§é‹ìf“%MczÞ‡:OíôÜ9•¬›M^óå&¤ü¼_&6$g«*ÝØóij;ö­;p²È²Æ,Öë&a×ÿ{WTš²ÛwŸ­¡6„fYñØ&áêœ+çàBù\*¯Á•ò\+[ð­p-¹w”ïõ®øAc¼T.À+—šÂ¹Ä¬•QÇfʸ˪U‚£?êXõ/$&úã^ýѯþXýk©ýѯUÿZb¢?ú²Ñ³²ê_b&Výë¬þÕXýKä’ú×è…Ô¿D<©»(Î_ž«!¬þRÿRê¨?IŒúW˜©.÷ª‰IýKÌŠÔŸ0Rÿ½Sœ!‹7+…ÿYUÓ½k*†ì'6spüûíLã„,ù}¼nÖé endstream endobj 37 0 obj <> endobj 38 0 obj <> stream xœåXl×}ÿ¾;RT,[¤Y‘KÛ÷˜³k’HEnÛ“­ERRe[?(f¤íÄ<‘'‘®D²¼“ ¢  êÐQ“®Y[Ôm]¶8h|”Vβ6EÚ¢ØR¤EED²nEã­(â`hkqß÷x”eÙq±lÿí¨w÷ýùù~ß÷ûÞñQF~Vƒ0"(‰5×@àõ©Oœ4è““¯=€ô2€àŸÌMÍìÜõ³ÿ°ípا¦ç&…7^>PÓŽ>Ñ”¦&ŸÞTÕŠüÇ‘ßBÁå•Ï:ù©ã‘i¶!xP=M¨íÀ\kþoU3ê#¹²¿."ÿ;äiFÑŽÄþð(ÀFš9—ÕŸi Ádú\^ËýÃuç‘ÿ!€øQ”àé㌀TqþÿùeÍþmCG;7Ô Yƒ”¹SÎÉ΄ÂÎྡྷ՛XXSlªIsd4 ¸=žXGû Y+¸ ú8¤YÕg:8$M³Ôá,-¶¿RxjÉñ¶I9©‹š¢Š¾1X(|¬k3[å€Ùzú­&œ¹f¶Ë ÙÆP‡ÆVã ÝIL{³K¦…k€Ó‘¯¾}³Dµ$UÍ®kÀÈ–·PÉ4TˆÔ¥Òü„L]r¡¸qc!Ä ÃH½–J/u›¡§b¦+ž"û¬É†Æ†Ì»GFM¡9DS*Jð¯Göìq{êb›‘÷RË5õxØÄÏ.)0Œ9?-ó&Ü‹ øÚb¦gšW*šÍ¦™¯hVÝã2vs(-˜¶æÁ¤ÄŸUÍù \O'X+d—Yû®Û#êëè^_ŒÛRÌj0™¦¦½Ë‚^kp¥0—‚‹3µï–WÝ ¥®žî•†áå`Üú;™jBÚÑn´•[?5•Šjõ(Xìô¡‡Ç¥¼}¦OΙ ²µŸ,­`:å.–›ÙÐgBh[ÂÊž:7ßýl?Ë!71îh¾Ÿ EEa{9ŶmALäp´›[ãäQ÷i«†Èи¿£_fþ¢LÎŒr&|$zÏcôÌxtQ B_Ü+î@]ô Åï .˜” CÃÆ©æöî+ À<×Ú¸€ó‰%\V]‘H, e™«"Pf+Ë.cv©)…5Æ÷w&Yþ:–*ÄclC#VÿˆIäXù@‘UÍ ²æ7kd?“÷0yOY^Åä\¤‘t´Ÿ.¸‚òµ¦ö)@oI{OËð øº¶mW»ŠUöŸw/Š’P™ØÎÄ‹Žªíê^$L¾«ÎS×ì©óº²ƒ|n%eüáù€íP>¥ç±œ¹Ö}ÜÙ} ¤òyéûÓ——×~W[‘«1ò…~Ž+‡¡oÕ$¹î„'oC@ØËéòQ›ðyT0pŽàiâ!û3ˆÍ´[Ƀ«ç懡r†&àDŽX^5xæ*Ó"´À#m7|É¢íx¿bÑU°OgeÚ§á—] $jÑwA-™¶èÌáôêIÞK>cÑ›à1ò‹®…6¡el» ¹—…ŒEØ.þ…E Ð(X´Ň-ڻŠm‡&qÙ¢« Kü/‹vÀ;¶ N5ì´o·è»`«=dÑ5°Ç>aÑá˜ýó½ VìïZt-8|˜¦u<y5©Í¨ùÐìäÍþÓZ^5ÒÙ «ÝŸNÒACN'zõ„–IjyÚA×Ñ5VwP=¨åu&¿ßÛ¹û†³ê`V·øÿ™LqzSiÝÐò(LghÄöÒÕÐ2U3I:¾ê8<9™Nh\˜Ðò†ŠÆY#…91›OëÉt‚EÓ½«óëËæsY+9C;©ÑCªahz6“2ŒÜ>ŸïÔ©S^Õ2N ­7‘ñÝIgÌå´¤¦§§2Xoʘ™Žè›‘Â9®™ñd“׳“Æ)5¯±ùë³'´„A,Újë£eÐUÊkÚ ›é,ÏøT*Hѹì,U -g`E˜ù{!{ï”ìôªÏßYü1yHãé?PxGtâç¤Æ@Ãw…PQÛŽÔ d ^¤za?t·Î9 Ÿ>Or_fy½üD´^Gz÷Pôbö*­U´Õðm¡"ý”eaòŽñ¢ÿÃ4i´Ï 6Œ\qýÈOsÏAÔªH§Ñ«5 ôÉðXyÔvðìî„Dßëýy=È=ôUûûq&°û¶h¬ŽU¬?ÿWÓr÷¦8ŠÁ±Ë–iŽA‹0·ឬŽ–áVã·‰8Œ'y¾ÚËÇfs)#g‘NY9¿²ó<ƒ$÷«ÌMÇÈ·ö­à<®áìºÊ±ìNò˜‡¸Üà«’éRœËÁ>ü>óÁ)þñ¢ÍÍÈ ×Ë©´|¿~£Æû>…¶å5àå˜3ØÏß1•þ°Z”ûxûOò'«¼Î= ÌD彪ô_ÇN`%5^?†˜µp™Í´ÕŒUÅœ˜7ë[¥§³kj|Šç“À;ŹdQÇ|#Ç+›\ƒþ?ÍÙû¾+;}›H7j*”ÞÁqüdýÿ2Øõ-!<øøý"±)]ä‡×É·®×u’ý#QþH毸J”ß’_¿E¥}k¿ô/Ë›¥_-ï—ÞÜÿ‹È/÷‹‘_,‘{»%_o ¹A\x§8bér"mÙú¹X’¾ÿjIê|•|³¿^zi8)]‰—¤¥Ë’k‰¸7¡ß ®é‹Ê‹bürîòüeѹx|Q(ŠÝÒiPJ/tIæ…’ä»Ôsiø’øô%¢\ji I}{.ž¿hs^$ÊÅÚÆÐùÈ×Ðìù ’.Œ´H_:wŸôÅsÍÒpÀ¹ø9aþÜïÎ ç‡J’óYéYÁù¤ô¤ðégZ¤¿ùT‹ôôS-Ò'q8¤áøBvá±…Ò‚MY¸ûžó)òD—tþcäñ™’4s9‰à³8 :ß\ÏÜðœøÑ¬$åúR¶»ôÒÙ²«)âØ%FªÐåïfHë ™FJE‡øñ.é8>ý[¥-¿™èDýæPëÄÞ‰Á ñá~·ôÐÑ’tìè.é(n Jo}äî®úˆˆ[—ÉŠÄ)öˆÂgbäx˜(áí!%¼ý^¼ÝÝÝ)o•FplnbÃéaa‰4^´f_"›á ²YÙ/Œ&_>dzåx¨¿A:ˆ†p|¸_’Jƒd§1€,ô“Ʈ͑:⌸ºœ@„vŽÔ-º·àÃ¥ìǧ [(¸ÜÔÝéιm’³ÇyÜù˜ÓætúœÃάóiç›Î’ÓQ–þ§Ó–rÈ|#±“%òLq<ÜÖ6´ä( ™Ž‘£&9c6‡Ù]=bV1!räh´HÈ'cO,,€ÛÙŽšñm±!3‰„ˆy$\ÛŠàé†nÌêF[ù"Hm€”¡ëm±NtÐg¹žëô¶²M¸ …ºå¯·jg--àzƒÑœE0DÓ¹Âàh³£6§|¡‘Þ„{î¿°}: endstream endobj 39 0 obj 3384 endobj 40 0 obj <> endobj 41 0 obj <> stream xœ]AOÄ …ïüŠ9î6О›&fÍ&=è«?€Â´’ØLé¡ÿÞ)VM<@òxïƒ7èk÷ØQÈú…£ë1ÃÈ3.qe‡0àHU5øàò¡Êîf›”¶ß–ŒsGcl¥_Å[2opzðqÀ³ÒwöÈ&8½_{ÑýšÒ'ÎHŒj[ð8Ê=O6=Ûu¡.;äí"È_àmKuÑÕw=.É:dKªÆ˜šÛ­UHþŸwÃè>,K²’¤1µ)Ùãt§ö±~Ú€[™¥I™½TØ„¿ß“bÚ©²¾}Ymu endstream endobj 42 0 obj <> endobj 43 0 obj <> stream xœÝ{k`SÇ™è|sΑt$Ë’lëØÆÆ’,¿e[ò‘m0¶‘0¶lbÀobŒllƒÍÖmB Á„BÞIÓ$l›>’´A&$˜&7!Û$ÛÝ„@¶i²Ù´…mhÓ6¥a{“>–ï7G4íÞ»÷þº’Ï9ßÌ|óÍ7ß|¯™#Ç7’8²‹pÄÛ¿©/ðùÏ „¼I$ôo Z÷®>;ás„ÐÖÖmÊsð{BøBBÔºÛÖþå±Bt¥„ä ö tÄåRòu¤QÎ*v„oUcù,g m ÞäKyô–ÿŒåÐÆÑþ¾?¾úΫ„ÈßÅr÷¦¾›Ùêdž7^Ä:Ò·iðg7µ?ƒå,B4Mщà{¤`–Ê kŒŽÿtË÷Âýë¿ì‡ Š•)Ç *µFÔêâôñ£)!1É,%§¤ÎIKŸ›a±Ú2íYÙ9¹yùòÿåGxSx“ìv3٦ܯûð HÙJÈìïXéê=|ãÿ[.4ÊR!›|F>¾¦áòcò"§¯Å†\Èg« ä<ù”¼þ·¨"= ,UÀ³ämòyîoàQò$̃TÔóã±:ùzŸ§°n39—aØÈa0*­%H;ø/¡U ³ärw9GîƒZrN˜àR±áßèkäëÜnzмÀ-úÂtž †‡@ ÷"¿€r©<>~‚¬&GèOHùò]+˜àIB¼u]ím­-ÍMË—-m¼aIC½¯®vqÍ"¯gauU傊ùóÊËJ\Îâ¢Â¼Üœì,{¦Í’’d2âõ:­¨Q«ž£@ ëì¾^k(§7ÄçØŠXÙÞ‡}×Tô†¬Xå»'díUЬ×czsí0½LïL0Z«HUQ¡µÎn ªµ[§¡»¥áµö.kè‚/S`>G)è±`³ak]ÊP­5½ÖºoËÐd]o-Ò›ÒiÛj‹ É”V‡ ¡Pž=0y Ah^Ý‚)J4z6lˆË®ë5·tÖÕ¦Ùl]E…KBñöZ¥‰,VH†T‹Cj…¤u˜±Nö[§ ONÞ5m$kzqö¾U!®ûNru““{C&G(ß^Êß~>g>*´×Ö…Œjcë•q¯ !!Ûh·N~Fp:ö ¿»¾¦/Z£Ê6~FèCñNNúìVßdïdßôì®5v«Ñ>97¨C “æNì5=ûƒýi!ß]]!cï,ˆNÖ×ÚJlYÙ¢Ù>ëPÖàŸÇn›Ÿf3uÅpšÿV3AA 8P¦6›øþi/Yƒ…Ю–ÎHÙJÖ¤%^§£+D{YËÉX‹¹ƒµìе\éÞkÇÕllëœ ñÙKìu(ãý}¡]kPŸÖ³¥°CñL³Ù'LÖ g—‚kE®– [CBŠ{]Û5…u™4*…ø?FÒp€S‚µÂŽd:{]oôoËP °†‘¥oï ykðöEרnÊåÄ}½¸DõÊò…œö@(É^se=[uÃmJ—h·PÒâéíö 9ëjÙÈÖºÉÞÚ Œ–½¥óqÏž›*µ¦=ë&¥¤«–!K‹Q¯rê&;Ö†,½ihik­i¶· ¸ËÞ9ØÅ %”‡³)#†èâöÎÆ6{cKwçü(#‘FŽÏ®û{gZ„ ª\H“­±vÒ4® Xaõ!`¯©Â{H­ÁˈWj™ªÖTY;!İ‘P¾µn°6ŠÇÊט:-nˆQS±"ÒYÜfë²E>E…›­Ñ±‡† µ!ÖÄe£'À:Šd”*&˦óÖNû ½Ë>d y›;Ùܘx)G…¡È<ºVíו®Š‰Ø°9V` ùi× 7T¯”¯¾Ð¼$ÖlÔØÛ&q{” AΗ„Saï|SšbýÌží¾>4b´hÅž'§¼^fËCÌl'íK&ímU 6ziÛÙX ¤ÛkŠ Ñ™ÕLÙáΖ)/ÜÙÖÝy£¥õÎöΣèâÞš®©,l뺖?Å’ ’Œ9Ù)ÌÓÔÔå½E”×ñ0ºïàwðûùGx¡Rǧñ+ù üÓü ¼ªR©~ƒÿ€Ò§RiÙmó—x‘‡î7èôcÊ£ £t%}„òûé ô ÊQoAiO“(ÕÑC"ðbPÜ#rOˆÇÄOENœž=éýù4lwˆ´]„J†‘%Ò7/ŠðŽx^¤/ˆ°G¼O|Bä6ˆCHéâÇ" ‰'Eš%¶‹Hõ Q¸$Â9Öžá>VŠP+Bšè)/Â×.1RÇÄwDJEЉ€Ô½b3kÎKEî<òDOŠÀ¸£ûE°!DãjIœá ‰‡Y¯R±Gß# Ÿ²ÚDzLìd|®d¬±A>/‰ôŒ¯ŠðˆÂ‘NLé<ì²CÜ/>"z+_ß…Qb¥Ø(rsCJùŠêV'˜°hã?L8çeRxB3’Twà'yç¶]‡ÏîÕÔnî(ñÔ$ÝXÁMÍõ øn» ×#uö÷ôna>Ú{»×MóD-YçO‚¤$m¼Ss‚ÐÄù¹QŽã¼9… ‡9àôB“J«ÒhTS›ÆÛirQÁœ=ޱ(Œss;K\!3§Ìd/sÏs›Ýf»)Ib31Çw:j G)ÄQžçøÒG6ÞüàÕÜÅ-þù];–ÙÿahuîtJýWôòE®Á~Ãâ2}\™×+ÝØ6±¸{^Š¢½-,GÓÈâ™#Š,oA]…;í4ÜMM™5ž)ªñ„t§¦ÔOÈè†Pº3¤z7$ž ‰Æá§S)i²J8J0†’O…’ ˜ûîT²Á”Âõ˜Ò™H®©MS$­¢ÂU’­²gæâzÛLF·Œ;0fjɨ®NÈ)ã<þsze ¤žôx UþŒÊï}|ÓðKôÈÌ?„?\Ñ9Ik/OOÅ•Y½V ùg¾ùÕ‘<¯IaË­šžýµ¢¯›ADiPs­N¥ô·œ´èKÔpj^Ëè8HDñtl}uZ}ƒN§W å¦g/zç6qs”»¨‡szhÒ?®§ Ör<¯ œ²òwEª3Ù ¸ä)Ξùn· ¿¨(¡Â”ì6î=y’]TsRÃÍIt„üØt`Á–Æîœûr8ü¸+¼œý Jðyî ßDT~ ÖÌüyæ­ˆÎÓ0êü\²ú1Dglfœ§‹q f³åËãzÆN‹ßrÄÂÅÍõÁnQÉž^="’Ôæ¸£ÚÀ¦àñ¸/ÄdÏcž©ªûõä˜zš‹Áž©2›^£‡æ‚ŠæÊÔ}Ú¼œáÍ\•}y}…>®ÒWg®ìYdU«Ä§¾3sùDôíð]ôí,iò–ƒà£/«  ‚³*P‰J =,†Dz‹½"XDøD„]"‹ÏgD^0ðfÒ„9Db3¬±1™ÙU"XB*óÓüÌéÓ—8~Á¥×#òB=i"vS,јB|çìíÔž}(ûp6 (PöÅlá“lfñgô  L‹)úTU3³÷ˆ¹cÌtÿuLùÛÆÍ¬š[zM0¹*Âh¨ùÛf1X&Ã*´Ñ]WÊ0³Û÷|IЧ¸»‹Þl‘,SñÆ¥ˆ:²´\Öù2OæŸÉ§ùù™¾—}Ðä©^ ù攆ääªzÁ«76óZµÚtErJ·H%^’L-éÆ\O“ìTb çéì1E‹@Ž =cÆ7•è$£ä™Ç"’Êbn+;æÉ”ð„>³gæäÚã!QÏ™™£_HË`ßò+Š¿Þùs^eŽ}^^ªþ Î;ö­ÑÁÇF¨íé֌Լ¼¢ŒõƒZÕü#ÿr¨¨¥:«¾²¼³:3ÉѶ}yïm-ÙÀÏ«l’ÍñöÊ¢øúÍ+œrÿ!xKNU¾YõF"~hp0@ELí5Ë‹׸#yFTë¼.ð½L!@á,î¯Ð,ÐÃBH ·Ð+€E€OØ…'…3Èìå: Ä•wGtOxóóR%þï ßÈä[H)'='H&j]:jgÅ[výirqææCQºÉw&¢ºÆžÞÔµ„¼¦t«Q2˜Òä&­€+á¾p­‘ÊcÆŸ) çLWì(ÿ\;jSºœkuN(ÍQ´QY……”?X²öÑ .ÿŠ%sT€úþó?8Ê»n^÷@Ÿ3üA`´ mQ~Þ¢Ö‚òöŠ š¹ãÌIEKÊ…¼²ÊÔpÿÛ¶[³Ôy¥óÍVµßwjû‰gí7­?Ðnw¬üªGö"TãÞ@G–yÝÒ}ýª¾Yß«è¹]úCzêÒ{õ!NÕ\7á]¼}1O{ÕjQàx’ñ¨LÝVÇŒkŒÙ¸ÍtõËK3_ ¿¹t-^«fžvÏœ¦%ŸßÂÆo@± ½W’‡N´h6c4.\TвÏRàË·0qûR­ –|K¾ÖìSÖ¥ IT7Wƒ«:TM½Õà¬O+›gKÖ¿@ ,ÎZPe4¶FUGu:e‰”„ ï=øL¨¨À¬­'âÆXÑét//(seÑæepŠ%0WQÌŲºdu1Ç-®ZrÇ[Ý|lbÝ·w¶›ÿW°°­ÌÕVYÒ1±¨vÏ·*øôhçC7µÿ¢Î*ó ä7®«n¼{¬ª–ß|£3£n¤%»h~†V—V’]PbI6 F;–më(²Õ,Ÿ“ëÎй«² çš FGã–+¹ë½ècÑcfy“’êIvov ›Î©×&7Œ–&AIëXVŠjˆŠÍ&ƾ¢tª+:woÝä[ûöüË>ßâ;ßš¼ç­ÛªÃÿþ•›nÞc÷v—/ì«É¤7¿u[ë}§wn;õ@{ûý§¶¿òLèå¾»ºŽî»ØzÑ–†0Î3~N5dyBq§¦0+ÌJ’œ!ó»d*!NI;0ËH´JÍR2 “Ér€6|9áö–ý_\ZÖáãÎO·CþrK.x­µKÿ#M‚¹`­‹w²ù·³<}Có²Þ‹í;k›íÓ6/0œõ'ãÏÄSW<Ä[HASJ¢±¤I%Å/ ™â¦ºcL6Š©¢ââÊ¢ç»VDÉîÒœR%J’’!3ŒL^ÜR* ”Ù¾öþ>'ÚëÆ²5íµ)RU4üaÞ¢6Gy[ÅÜŸ´×äkòKËÍÃ+Û8µmÇ[÷·KÅ nm®\ž û/Y›vdRû†m¶‚î»úDzVÜÙ›àü„…Pž¹dë ’3û£gµdY&ÍÏ¢eXÙS«<ÿÍ»+ÒS°$±[ÒÊ—Éi$ïÊ÷æ÷æòwåÎWYó/æÓŒ•'µàÒ‚AkÑRmªßÀÛýBbTMÐ Ẍy,‡bTÅÛ3³rP" åYn™—„˜¢Š#|¸f:ü§ïL…ÿt´«ç9¿÷=Ÿ[þqÙÐÃë*-úÚàÆ¯ºè?}+ü‡“C›~q ´/ ®}1ü§'FŸÝUW·ëÙÑ‘ã{nh¼ý8zzæ—Ö£_JÀ 7 x%£=q¥_?ª§z½=½›X \h×Ê.-ÝÞbﶪ\*ª2fú)àOM9˜Â}’))öd¿š³ûU Ê14¢™_@{Çåvà>M©Á[Ô2Ð_±H˜¤Rg i°ry"›,ƒpžëWÜÿú茇޾ó‡wÔÝ~çÓÏ†ïØ¶5‚Öc­…ËÖU…'a÷‚%pφ‡ûŠ„ÝŽÎÛ»oúÖܤ#ðsø›7†ÅùK‹“ÂÍÌ^*q}U¸¾ÕdÓ ,üÚ[ñdYAw¾ÜÍ|E›ØM<ºn¶\MZ¿vT+he¡ÈŸ…n7ú·æÒq.Wñn‰ŠwSÜ[Ô·õ\P&Šn-æÕ®8µlæÔÊ#>M¸¢ñK#«›œ!*ß½¿|ì®wö›an\q˶<î]?ÙXuÓ¦Õu¹í÷¿µ}òµÛ—%„!íýÊòuÕsäî5·nYÛè€z TËkî^ít.¯°¬ì[pƒËjˆÏ(XÐ1¾lø!Ч+w媴âêÌÒÅEc¼¥ êÆ›"ºÏt` u ޤ’Î$eöœ’=§°ýB ÆfƒõÜ0eeHÁ¢~%I;”®4oMê&¨^§Q%âò'Å–_ S=OHbÊÖÎ7ªÕ0) =¶~úÒÝ3¯Cø Hx}$üÞ⛾3øöç]Žzè¡ðgÇV »[Ÿ ~lGh¬ürýÂÏGì5¼œïÆxæ&ulž_ê;ë'óGÎú@=¨|‚°°þdú™têJ‡ôCES–͸¨)1Ù,¶• ÕÝ#;®uNnç5þ "‹J$ªÜš]ç­p[ŠÏy¶x0³³‘Ä+fª$q´}Ã7'êôjèo–Û*m´djûºû×8åunÜø´ }™@AKùÚÛ^Ëó¶”·-È`IFM^x¹cÙ¯mÑ é‹GZÒ¤¤Ï^Ýr÷?oÛùÆ=M«Ö™ç¹óÔY·¶_þ-G‚ ¸Ÿ¯½³5³ óÎÕGŽÚÚ&QÏ5˜×¯Ç¸ "dŠÇýª jgHƒ!Aàc!lì‹ù¦ÃÀX8Þ ùr7^z{Ãì焨ªŠ#É”é¨E·{JgðL‹ÜHPï Å¿*Ÿsb¡þb«6Åñ!rrJÓôw''^ùíÅ-ÚºxJÔhBš“ñSk '9r”pšââbxž½ßWkÄâÈ'äC\%Èv}À6€ø=ö6ž>|>üÞ¿¿þ5hÈ,Wyù5®òÒëÜøå¯"‡˜Wbl:¨Ä¦RÌ®’b©dy $m‘ïL$ESÉ$%•Ä}^’’Jš‰ÉüwSÉ«y$;ÿ²-%8a$UY*YÍ${W4Ìa?] áO8R1FË$‡è+°qÌÑ^“—çmwlRÛŽÓ¶ãö§Ü\îÎÓØw·ñ? ¯±VÏÉH ô¶Ýÿæ¶é£™,¼«=s*’Cò¡ŠÄãÍá8A£ø# BÔ'ó~'å/iš£ œb¢ãÅ9)Ž¸Çø–©Â©7š•œÑ !úöŒ3ü>oà /=?sIØ}>væ¸eª#_õf¥˜p£v– ZÐj@ :˜ƒi¼<@x‘èŒÊ!%«ÓézPs¾3<ðQ±ó±}8¿ökbjUkÔjA |tïXá¬`‡ŽžØ’í¹ç»Ýø4U`àPÎUm V’]ål &|vÛÚ­PÞÃoÛÃë…7/?'ÃÝ3£¾Íˆ2‹Gß¶ü‰›ý³×Éú0WZoZ mWoIƒ$u·UãÒPÊïDuñÆÛ’’Ô‰~‘SQ†c1¯‹cJ$¢1 š±ú+ÿx{øá÷é¶½¯ßê ¿wÛmáÓPQã÷dp?\ùä­ËÂÈТ-O m ÏË\x£²?`ùå<Ì/­dž7Ãè3Ò|Mè·2¥z!±Ik4jÞ4HKK‰bÜQ‚f›l“ýW' ‘¸¢b¹“rJ[w¿²ËëÛóÊŽáonYþe\oçØÐÏš7êa޶~Û÷’šï9uóÞß½t~ßž¥ñmý?˜ Oè÷ {7;:ÜgPÒÈ'ˆwmsYxxy.ìš‹,ù `0¤‚ªpßîÂÝ¢¢ ÆÄšØ‡ZÔ”* ’Ê,0\ÞÙ3¦ˆ¥h/sÇ\«9ç‚Û ï‡ýØc ÛWºz²ŠsÝsã~ÂÝ|ywóKc ‡—jUÏñ‚ÑZbíx)¶ÏR)û¬Vo™CETs¢V9ªôê9A춪…ÆïF…ƒÂYaVp++"ïNô“kÒ—kl#ú˜²¡ef£\ûøöË?¦gŒÜ 4™ðcçÃÎãø,<ˆyF>égg¯V"iæ4ªÝ<¥­š3GX¥r¸½ÒeÜ9©Ñê7§¥™y«à¨Uè ç„‹~ˆ_—Yî±èz÷`XEýSVÜáÈF}cë{mÒ¨¬>óJ\ZÛ¶®ÊDËÀ§ï:þôHGÇ÷@}tòôƒýéáËñ ºnZºákýNçÀ£ï8—ö–ô-½'Pw5k\0|pE}Q›77–52ïÀ9Þ€2N%Þ²ÄîsI“hfÍhP³(jEã:œv2WwTð‰jVE#.*QÅéýDÉÝl2㮜)úËÜk$Ñ-‹¼t ?ÞóÓÇ» ·°5Xß°íFä®þîGIžÛÏâÆ.»kw[nþŠ=+¹æËÝýÎþÅ‘óïfx˜vҘݞ£„ü^$Lñœ‘±fª‡?ý[¡Í•*kUMZÈ 7Å`^8JJGÍY £¿eÔBͳE«œÚrœ ¤­a”å…TëÅ’6˰xsEÅ2çæ9êe›…Ô˜â\`™ z ã[Žˆâ°ä僦Šë·…óÊ®înÍ,À˜38øB¾Ÿø…2_ZuóK·ÝúòÎ*WóмÊÕ5öê-G&v<œo¯ñW-ܰ¼0übBA}y©¯ Á×PQµ$'ΖX²¢¦ª¥Älv·{<­®D8°òáÑ… Gê^¶u…[äõUíëæ·O®)›×?Ù6o]û‚8^[Ö±•Î/¨wÏë®/Èn¨ÈÊZÐ03íh®ÎÎ^ØRTÖéÉ´{»HìlB¨ö¦ô ÀôØ+„]Êéšé2”Cä’Ò†Ç ˜”rvRr×MˆÝWKÈIr†œ#üEÌJÏ!Aà˜A*éÙ8f”ŽÕÊ‹´ñÈÙÒ>Èv³ã äÁ2û;î#ä!™¸¼sŒFÁÐêJ ¤RU·`ô›8Ÿ%}‰Ñ¤/¶MW‚¡pMzÎ^Â)j·þÛÛ›’ [›å®Éí\þˆzn›J*ì}x“£²Å¼e„~øÑåê ßß¶HÉGÝèwîfï~Iý…·)‘bäoåôÜj^Hâ1ÛK¢‚V¢'«Õª$µJÏ«½‰RƒZ¬¦:µôOÒ{}H‚;$PKÉR®t£´U”¾#—>’xIy›û/§4üˆá~$qì­äEúÐ?a›Ç%ø¶Jp“õ¬ø‘Dï”’è:¤HçK,J‚‡þ(Á;Òy‰¾&ÁóÜ+}S¢{$”6K´]‚ÅdI¥5+ÈŸJðK†ÏI¯Iô îWÆ‚],µI´T‚l $ ¨ÿóK o–`­H¹î*e^‚¡_JŸI)#ö³ÃÜ#APÚ#Ñ54K K5ÍRh{ßCê%ø…¯JïHô¨ÈÊ! ne= UZ#ÑZ ÊÙ`Tx9/}*Ñw>|C:*Ñû$Ø"Ý!ц¥R­Ds$HR&9ÿ û%8¦HqCG¦šO¢ aÔêa)Äè¥û$®™Ñ`Ô¸=X<&½ŠóÔ²žŒì¤ ñ'ù3<Ç«ú“Rõ U×í%Tô`„Ä=LÏêÿ•7Íãþñ«Ÿ«o—¯y}ÍëhöñÿbÏ—#þÕ+íXC©0¾‹©b¯c¯ãUãÉ“FòÊ^!%Z(qÙ8ü²¬˜ËÇkã7íœùh'æ<VQ2sO\²I 5%Çí‡ûa(ü³Tî{}eårÔÛîì=E»1‘tð6åm*¹„•:b2L5™<º&Üqg2ÀšáÊ8œÁ%sÝVÞÅSÞHýþäÑä[’$óÉ^ÉÖœL$ÿ±Fó¶È)nGߓٻ(e·Žñg`ŠD ûáJÇ·„O„¿Ó9¹çµ=‹œmã‹agøî½;ÃwÀ­5þjŒ±3¿vWnz¤¯é¶µuÆ™÷öºp¯¥b9ú"Œ9êý8'ýö bÃM´Iyíª¼ts*÷„Ø;9;{UšÑ™+ïçØ1“U)“|›Um4¤¤¨ó9“ÓeT¹$Õ¹\r‚‰uÝf:a¢›Þ7Ñý&MKL4Ë´A† ¹K¦Reø ¯Ë°D–÷ÉOÊ|¥ ¢œ/ÓÓ¢ 7î—a› kY3`Ckƒ×/ÉðÏòodú‚ ¬ ÝÁè0ŠÃ2ÿ¾ü™¾!ÃÃò ™b÷2ÀJùÀCnN®J­Ø2Ø30N$—Ï[ÈÍKÄ)T–ð7^Í¢NÔé­N^ÂÎýàæèâ´†xU¼^/<7UÅÅÅ xéuéñOƒsf¯uÍ«¬œŸâ.)L¸ü¶°ûòEïÍ%¥7ø2¼ +’éïgR*=ž _ýü*÷ö ÎH®{o’ˆÞ!‹¤ˆ¦¢3­: f¯¹ÙÜkææ]fj5»Ì‡Í'ͼ–ZÅ#šÔ«"[:ûåC’bM›ÒÎ$rš˜çe/!Ї±+²}HÉ üürr‹•½?÷áÌמŸC e¨øêœμ˯»/ý  8w̹1+aÿˆUY¿|ý)¿¡ê3b‰üŽþG‹ý€{öóðrôo*ç64ö#uô ÃËÉâ«?[ÿÂï¾ éïH-ÿ!IÆë ?AšéS$U=—ìÅò-XŽç¦Øö!ÖM*ãEXYø'²ënƧŸíª ÒÎê…¤RiÇ:lÓ¨*f?Wh|¨ô9ˆíû¢ãÙŒ¸¬ß,3Z‹p ÖnÁ:7££>@({2ž¡^¤Và$n„û-ïãoå-Ü%¼(¼¨Z Ú®ž£^£þšF«¹Có¹x§ø íˆöí¯u«t÷èþwCÜCq¿×ëë/Æóñßÿ­¡ÀøºÉ•ðnbiT:…¤•ý_‰(Fâ$˜SòÛUÿÀH¬K‡Wd¸úŠ<°Ñ^<Ù…9b!cQ˜'IäPHy" ³L2…Õd;ùaÖ$¨ŽÂ"‰‡–(¬CüWþ»¥vDa=¹žŒÂñÄAóǼˆ¥i[’Á¢0n\¹¢(Ì‘…\YæI7…’Â=…U$‡{6 «É§ÜûQXCòøSQX$éügQXGæ ¦(GV K¢°ž„…oEáx²BõÍÅ£mãÃ놂Ö'­²Ë5ÏÚ:8`mè Z—Œô[mÜhUš'¬ãƒƒã[Š­K—ÔÔµ.j_Ò´Ü:‰—Œ{A™‡P+$øl }ØZˆÐ2BúI1B‹ÈFüZ¯é=¡”ñ9ˆÏ-J_†¹{Õ:¤¶ˆ´#ÜDر۰‚߇W±ûw]B°n”¬ý»ã/Åþk”qXË0â`k–Fn –7bÏE÷#ÖˆB}1Š~þ^_ë•Þÿ»x+œ‰+%ÈŸ‹”iÿXï¢ÿb”ÿ;éDÖaB%¨ÐŽ`+´;£MÁjVz2ù•ÑF¬ö/± G\‹ý™4¯bö+´ƒXŽPEx(*éõd³¢ˆÉúÅæ6#ÿõº0]Gmý‚´w[”1—)õAE¿XÛR ~œd«ò-Fœë)÷Gé+Ð&Äüïö ¢¥9*k½q#ë^¬ÐÜ„«Ù¡è~l}˜,"ëøåk¼Vy2ÉO(=‚ÈIŸ²V±õŸ@®AI*òcG£tÎÆèzŒDGíCžXo¶n±5Ý|Œ·*üôãÝŠsÅ6Ö§_¡P$;p õÿSž‹ÿÛ’Ýø%#]•i4Ï ³¹ä=ò%Ÿ—I3¨1h;•ûà½õpf^žã Œ^ï%ØõÙ¡ÏÆýçÅ2‹óâã©ÿp~âÿdô“Ç?9û‰ð«óVË/ÏW[~q.×òçª-g«Öñój®ãgÓq´Êâ\¤ƒ ö¼[ñòâÅÍž„ o^jºï§Ü¬…|ÿÎWYÞù×tËÿ5ÇÒûö¡·O¾Í±Gso³£·gßNëÃç±·µzŸa$¯^~)Çâ}!‘ÏûBf®ol^ûóÕ2 ÓǵrÈqëqïñÞãã{:~æøÅãÂ4X½úÄ{®÷9zø¹3Ï)çÿñÏéâ}†£þ£tŠ‹ðœJÃ]à½KgðöYöÑ;n7Xü·Cù­¾[édb3^A¼&ðÊ@Z¸|€w¿ Сt€mʃêèHƒeÄ'[æ@JGª;¥Cíæ:T¸:}Ø·×/[üø\ÝÝ`Yå˵¬ì¾ÉÒí+±$Ê p¼ÌuŒr`à<õ··-¯ÐçmËÈÄ[bНµ%ÏÒÒ”niÆ+µ)¿‰v5 7ÑiHðæû²-K|©–ŸÍR“þ³…’lî0¡Ã(:( ³–i0MñaôVãÓÈÞåÓØ±} ·<¿áo08 M†QÃAÃYìA©ýÄÀ–Íî’@€i84ÕÞæp4N«g[Cêæ•!¸3”ÝÆîÞ–îêÎéè^Ù9ðÕ®Û 5sCr[g¨wnWch/v!`œ;%‘š®‰àDpóD0º5…Db›Y-«Ší^A©ž˜ƒ$ÒeÂ1AŽàf¥ H&¢½':£ývÇòfGP!Å'‚ ÇÁ è`D©dd”Ž0‘‚¶þ¿/Í•ë endstream endobj 44 0 obj 11004 endobj 45 0 obj <> endobj 46 0 obj <> stream xœ]“Mo‚@†ïüŠ=Úƒ]¨‰!QÔÄC?RÛ€0Z’ºþûîÌKÛ¤ɳ»ï ›1®ö›½ëÆøÕ÷ÍFuê\ëéÚß|CêHçÎEÚ¨¶kÆi%ÏæRQj÷ëH—½;õËe¿…³ëèïj¶jû#=Dñ‹oÉwî¬fÕ!¬·aø¢ ¹Q%QYª–N¡ÏS=<׊¥j¾oÃq7Þç¡ä/ð~HYk¨4}KסnÈ×îLÑ2IJµÜíʈ\ûï̦(9žšÏÚ‡¨Ñ$ÉlØçÌ)8c¶Â…d2ìo˜sìK¦Kí#Ø0/_0¯À[æ5X3WàŠy#læ-úï„-÷× öSføç;føü.=ù³³†¿• ü-»iøgÒþVòðÏ fø93ü ÙŸü¥ü­dàŸ¯™áŸ±¿†Îï2ðÏ9oàoù ü­dàŸÉ>ü ß¹Ê÷`àoù{Íä¿b†¿å»5ðÏØÓÀ?c»•!™¦Ç…çùg Usó>Œ  ½ÌO]çè÷1ôWÉï|ÿÏÄ endstream endobj 47 0 obj <> endobj 48 0 obj <> stream xœÔ¼{|TÕ¹0¼žµöž™=×=·=™™Lf&³'—™„ ™\ ™lB.‚2@À2`@‘„›¢µDå" /´"ri%õp¬Uz H­Ö¶¦ç §þ¬•ž¢­G[i½½¥êÛWÌð­µgÂE=¿ïŸïŸo³×}¯õ¬çyÖs[³aÝÆåÈŒFAÊ-k–…/ý%„úBà¸eÓ†ÐåV…æá,BXZ1tëš²ä»"Dþž¿õöÍ+¾Úôø0B&Ú%óÞÊåKeÓ‡•­~Ô­¤f7ëºÝEóòÊ5îê+ø÷0Í7Ð1§ß¾ö–¥oüzýs­Ù@ëG×,½kèÝëBw°ö¡;–®Y.þPòÑ| B†ú¡µë7 "ù2B›ngõCë–ý½÷1Í?DçÇÆú}Ì4©cyL8^§7F“Ùbµ‰v‡Óå–<^Ÿ¿0P …‹#r´¤´¬<¯¨œ’¨šZ¬©­«ohœÖ4½9Õ¢ÌhÙÖÞѩΚÝ5ç†Ñÿÿ>üÃÈT¾ÙÐö}݇C^ö¼ü×뿳s.úÿå, ¹Çô$:‰Fï þ|EJ£Uh#-¹öóô ZÊ>iÔ‹žF»þ‡a¡h}®]íAÿ‡viôzýûuoI£5è:—ï¡w`*z¢ÊZô1Ð}è:êÇ´ì†/ [é× -¹âšÒwÑa¼ÍÆçhæ «Á ,¢Sè°„޼®óá++žþ…Aw {é÷´m¢iíÃ7öŸH¸üßtU÷¢Ùè~4Ý~MÂb¤û׎P˜þD+KLVêUr~ã‰GhækèVú)еã‡É ÔÆÛá$BJûâž…Ý æÏKϽñ†9]³g©ím3[g(-©æéMÓêëj§V%¦TV”•–DåHq8Xಋ6«Åd zÏ ¨¢=Ò‘ •dƸ’ˆªV²|d)-XzMAf,D‹:®o3ÊhÍB×·ThËŸk©äZ*WZ‚šŽ¦WV„Ú#¡±7Ú"¡ w^M?ÜY;¯¥oÐÒ\‰–±ÐL8L{„Ú V¶…Æ jëØ´rW{¦ŽwÜdœ™¹ÜXYŽM4i¢©±²ÈÐq(K–ÀeíÓŽcd°°×Ž‘hûÒÁ±ô¼žö68¼¸²bÖ˜5Ò¦U¡™Úcº™czmÈÐ*6u´;t¼b|×C/ˆhY&nŒ .½¹gŒ,¥}w‘ö]»vŒÙãcå‘¶±ò»ÏЕ/«ˆ´µÅÙ¨]󯼧ëê+aŒŠ‘Ю¿#ºœÈù¿^_²4_¢‹ŠG,ÙAÁ»kWG$Ô±+³ké —G–EBbd×q³y×P;…0J÷Ð^/\þÁnÿXÇC‹ÇÄÌJ˜–_lÇü®1ç¼¾ž1í­\JKè_K$ÜàÛO¶IÿOÕˆ‚‚ƒÂ4f ßý‚‚–ÑÌØÈ¼ž\>„–ùO %_<†3¬f|²Æ½ÕŒLÖ\鞉ÐÝìZгkŒ‹ÎŒ´Sï^:6²ŒâÓml+"â˜õ8²Ëa5&kmCtV³W…Æø ÚëÚSX—]¢–±~’{œ÷ӔءƆÓiÏäÿ6­, „*+ÆÔxnë»{Æ”6šP–æ÷¨ýxU‚öXš¡[´ªMÛ¾±DdhÌi½²ŸlZí«ôh]òÝÆ\3ÇPæ–|¯±D{{s¨}W¦-76Vd^Ï‹(yùìñšÿ¹$ªA‹ÛXci&Å«’ö]=ƒ+Æ‚ÿ ¥´¡xLYL7xq¤gùb†hBågéëÂÚÇðÌîž®‘®y½= ù‰ä*Øp\´ýsÃDzü¹a(Ê¢†Pö“Å´¡H B4iN¿ÇôQý/R€k¥ U[§‡zÀ&[ÓiŒ•‡Ú—·åÛ±üuƒò fª“£éX–Ž3Sõ‡‡sŸÊ L«CùÓTu²ŠD)' e˜£1X0œõD–GGV†Æ”t[å<04˜ç÷ªûºÜ5À¢`BaZ=™aÀëˆû¯îX§–¿’U?W=k²:´ËéZ°‹ ɈèÌg!†ÂJƒÝ¯Q?£çHÇRJÄ”¢5zÞu\Q-¯dd»+2kpWdAÏt­5å ÷úïfïr .èên­¬ Ì¬õxœw\ôö¼(R‘êÁîžðÌLëâã2­ëy1DÏ ­³RVÈ2!–a#ͧƒÖÞÿ¢‚ЈVËiZþ–ie†É2@·¼€seâd¦e\®LÑÊØ‡îRÁJ cÊ¿ÛCƒl¾²xå®Ìb†ãH¢¡0‘…N$u°Î/Rªª_Ë}%“ç=Õ-ç!Ñ?|¾zIÿ@ÿp}hIþ3ü…â/©¸®K\+_[y}ƒ/vÎ׊g–ôŸ·7&ÎWO­XÒGýqˆÓÖìîïú¤H²Zò°ï"ì¶{ÜáÚz{MI¤ØŠõÒçòù\im: o˜ß-·o „z—”Ôõ´D6g>ÔµÐ×ÞÞâ¶?œmݽpaasS­ãáì¢;ï'Ɉr‘³¦ÑQrµÞÖ5±ß[YéÅ= Œ®nÆÕ¼Î`â&¼,GBÞJÊgü—ÿJ~C5Î: ,ö(”žÍÂt› Ó Ø`ÓuÚL™°Ë  ŠÁñà٠׈‚¡`UP¡i^ f‚CÁ± Ò#´áXP‡:Ǽ°Ç{ċǽ§½ØûÂåqÅe0©^½/-l:2ßæ’Li«›nyKò|5û‰8ûùþa êañ= h d õ8äX(ƒ ƒQ©•DЧàZ R] îjø_¼3û¡!¾©Gfк ŠÀ¼`±MÄòV~vÈ[9G,.tµ®êÂ+¼•uP=íOtÝ~ø±R¼Éµß…yÿ6?¾Íw·ïð>ÐØ Æù6:ÁñÂå³ÏѤ> M-»,X0ƒAÈ×XMã´¬ a_~'ÒYy½Oïr"‹•7»Í~šrêhÚÚî„mNp¾pùÏÊC%åêlvñ@øšvÃ|Ú§Ëìv™ÍnæÓ.VÞeµòîÙ>ðù\t\ ˜ç a# ”¥ƒ*p°¨ ´[G§gç+—i!+:I Ï.Œ¬´&°!°O+Õ×Я3´‚ Ðz.VÙS©±yÔt€Rs@ `ç2+X­zdÍ!31˜n±¦u¿‹3zDâõÄœ&Fº¡á&žFúÌŸŽÃÕ”(ãqmg×ÅÅŸ-鯶;ÙZ™Ü!ÆwÄOí(Ð`4,îÏý7ŒòßS« N? )â쯿õG5²1ö€"…[ïÏ=€l¾#»èÞw²÷eÿe Ôf/¬…§ïýÞéû`þíÙ´º++=pCö8}Šp¾F‰ÂŸýDútgŸf2=ƒ±ƒŸƒœ”:¾¡,@³MÆÃÆgŒäã%#Þj£·Ó䊻p—«ÏuØuÉű\“ë×K®\:Ñ¥46«® tqãÅ ì Nkô1äöÒ2pWV©Ú³À¯=Ñ"ªüçKl.oÚÃh„rBÊ éÒû‡+ß‹3PNœ¡<æ<…ŠËŠ5š šqr••Üc/*“¤Ò"»½¨T’ÊŠìÆoe½£Û νm)muiÞ¶@飛ÊI¿¤òG-ú–"Ï®ÞU¿â~ȧI³¥»¥]ÇSé&š$Ó}s|_ñ=äã0<‚E-*ÌjTÝj4êì@õ¡z¨g˪* «së꟭'•…&S¡³’¥Ã5%m%¸¤$,Ši¾ÆÔf:j"!˜L<•±¨XEåªMº¢¨‰dB÷Æ7¬îl‡SÒ©±`…Y8;‡ŒQ< £*´WY¾*±9uØjßgÇ:;l5í3abƒ@(V­ÕJ5 ê‘jÜHéê¡ê½Õ§«/Tó¹™[ q‰+ìDa1 ŸsLÀHù§¤R¬t'ˆ(Ml“‡…›Æ¡(Œ¨ðθÓ:ŠZ \yQÀI)hòðÖp¬¤´ˆr¬r ÓÛ5Œ;° ØÝØ:»¤ç¡eÉš•ß\•NÒ}…£YåNŸV¨(MÒ·²­Tìpžt¿L^ÉÞa°8Œ­“üc£sjEÝÛ)=œ s7¡ zH™¾Õ¸Ïˆy#ì66`£vs‡9,p°ïÃX‡Á`V)Ð}Ãb¸*œŸ s,§„IS˜-Aš1[=†¡0(áLx$<æ2aЪ¬Ñ)ª¤êìiAô§‰¤‘$ÎÇ hœ2¿¯ù¸Ø^ÖÖÔQ¡×öÑJ7¸ˆò‹&'Þþý™_ÿú½·ÿó¤¯ypÖìLƒ$5dfÏlöÁ»]FÙ¿}øÙÿùï¥WÕׯ:¸tÙ¡Õ«1 žly‡ó£Ô‰ÃÊgs;,šºb*ž¢kS§öL]9õÁ©ÜT6c–à*U³µR¹K.£E¥¬ÈÂÃ"ƒ‰f5Ø@e‰Pm¡ï,®ž¢‹ph¡-V¼ÊféWq´8Z°ƒžÎµŠ/ ÖÖv©q/!Ðäõu÷Ò5}êƒ>önÇâŒ:Òú Ó'ûNõa­ØC·:Ú\´p}[ûŽö‘£´îLß¹>ŽÕ?7CUµgmsîOhOÅI9û•àP_U~¼)6ŸÚ˜š¨*€]¤:ÁÅHZ 4P>T*5jRM3Óé˜83í,Ô6²ùãúoT‹çížFÆËÎWÇû˜¼ÏIýLLŽ3™\üívR>߯õŒS¾G%ñ<;´¢‡ÑÁ¥cä«§Ô®–ÜטõSHm}ÉäÑé©÷Ð6„ÊäaÆs8Ú‘rMÈŠ}¾|Aa Që@:ðHíã­Þ¿$æ­LÉŽD¬ðñÇk–þ¯ÞÂiÉá7‘ÝÅáòŽÎì>wÄkõ4.›ÝûÀ¢òìskú܉9uõ7L•¤ª9ø'žtØ‹¶n˜qïÒ¦Hj~U¸©¾Æ§óÇê‹OÌ~gîæyå:½@ÖÆ÷–¬ÿìŠ#QSë•›b‘–E¸ñÞ--ýӋЦ÷·´ ´ÏÀ—ÿÊÿ˜Ê¶.ø"®ÆtOÊ+ÕÛÄ»ÅC"11‘Ócµ«]|—ñëøûyò‚ÿWžð/\>­l¤mæ¿ÉãÛø»yÜC¥Òxq9ž…cN²–X;¬‹¬œÎ(KŒDÒ—è1åÍ"Ì]6Ñe£R7Ì·Ú¬»Á˜Ï®Ëlq™-f˜oâM:³K§3óœÙj!lu6°1Œ7ÒYém6˜_¸ü‰ÂY졘ì³6˜a¡y¹ÓüSÊbˬ¤tfÉLåob³3щH’¥6©["¢œ'¥‹• MÚ m•öI\•²ÝÒ Í•8$Ac·t޶"ŠxD‚ÓH §Ë+TöTþ ú²CÒˆ4*‘ B˜NÔa…X‘I¤H; 6ÀœÙÆ3aÅ“Ô$ßD2I‘¶Z;n'¥µþIURüy5EêæD"¡¡q?eãýöd2÷—“€Ïì(¸FÎ?¨Œ—c]ý9 òï°D&EÞdNêåe_¹ñϘ}i-¼ü÷ÿ«û÷o‚ä¶ÏîÇ·Oì#w31wb^>ñ¾¦5›e^àFehìEd¡«SfSîjtáìÏÝiAc£1@116;ãGcb8¦©vñ*51é ÅFb{c„U<,Vµq§¤¢`çˆ Hå<.Ÿ–ÏÊ:ƒM—¡ [”ÓÎbwÏ{ç™ÕäkY¼ÌÂtèk´ç%ymÙÉr<.ÈéìL»‘ži¥hRjÐ?¨Ç˃l¼”Ngi9*‡”R>R>Z~ºüB9_®Iq±Ju üÙr¼(°"€êfãN#6¤]6±´xC M ¦¸sž(¨ˆÿóêÄÔ*´¤4q$zE\Ó¤Z{Nª½zlÕ“}êüű»¿{GÍÌ»þyÙ¼©úxtUãŒ[Ú#Es¸sf“§Ñpg޼¸qäÅ;œæì§Oº}‰ÁC«{¿¶¢Ìú¼¼!R\Ð¥Œï¤'‰ Á´>´݃#z¸õ¡—Ðëˆc¹gè™g:E¥u¶Õ-*{*… Mê^`J#¦´iÔ4f7éöÒÄ1åõ­¡™ê9T†EˆO“¼49’‰Ç©àš³PF½º‘k™î2 ×l •3ÒtO:èž©ä½A©Üé‚N09w;±ä/ñc¡À[P^p°€3”¨A“)X* 5R1Zq¡‚T°Íœ­²§â‰MQ£ >HyJG£ºPÚ+êæÙ¥üÉ”Ðv‡¢b~wÄ+"\Ù÷ä6Õ1” °- Ó³8w˺þ¢ÖÖ”Ï3ãÆžÊ߬øùË],kÌ>Ö0¯Ö _·ÇUxÇ1kû­Í¼Á¨k°ù%‹òÕlþäã²%ošßH,ºgΜ{%röaú ¿£kŽ ç_DÊ=c‚¨r0ؘ¤g;M¦LX2EF‹º¢§¢ç¢\ãQfô²µP’GÁ…ñ(UK£0‰î’hžlµF•Œl}#TaÅÌú¢‰ÓÁ³A!JGl>ÑL[n/B®œùîË wàzÒk ü¿Qm[á ÝÝ×Rljz-¥ØÛ†‡Áœ3x]gê¸J¬“:lov^IqÙƒÚ•‚R7T»Z]ø.Üe‚¾Ç¥<Õu4 †ªXzôñ X&9ÁZZ˜EêüÏâT"ž‹8{ ¦ÛëHfÓ÷<•³oïÚþÊŠòÀ+Û‡·¶;áÃîoyôáxwç·ß…Âýüÿùdg÷Œó S!î'|3òÀߔ˼Åm‰ZˆÑà3`Áæ…¬Í;×;àÝâÝã}Ùû¾÷²×p!gF{ÓK†¼`ói=y“V}ä%c^8â…/½ Ú‰ /ü|­÷YÚó#/—f­Þ/¹ì…Ó^xÙ £^h¡Ý·0ðÀ:èËtØË^>ã…¹^¨bà›i­Þµ´Ý³^Nd=ߤ^ör{½£^¼Å ֲŋϲñ&'ˇ´þ«é|ßÔ^µÇ Wgœ+¥ ³õpU^Å‹•A/Ði¿Ï–1æÅ,WåÅMtÎg'»0€ìñ’*–9ë½à%¹‘µ¶!Úš N× 1äñâ`nátà´yÄ‘´{zðl[¯  ¢R£Šœ§ ôÄ1ˆ†A1pƒ¡/¡Ó„FˆJNT†rÖ¦x&Ë—Öa‡Û…­ÐÚuûìJÁÙ¾uxÛ«ŒŒ_ÍÎéŽ?ðè‘owõ“ß¼8£»óÉÿÌþáG?Êž{÷ÛÚyƒ˜^Kç&Òóf"š"h¶¶Ê“pSè¦Q6‚T}ÈÂb¨B©T1RÅŠ½X©ÈÐÌÞŠ±Šñгú–¯à|¦Î÷c ÉKNª÷Ŭé¨ä3ùyÑž¦2šf¯`þ/UO,(K¥«‹39I y=%o—`À·k2Í]Q{Jq6Ú¾´© ¾®Ú»=¹ë+»wB‚JIPyßãoÔÜþÏÃU·dzKàŠ݋¢œ`6Lx †_sS *³cΩµµ‘ø_þzçËÛU“ÃkÓ`2›ÂäO&>*A|E©]Q¶© 0€`ØiÀßààaÌ(Òé¡:tÒÿJ|$>'¡xFKpqM|ˆOQýê\xOÚçv¦%Tš6Š„Bó4½>ÉSïû5ýžCóF.íµB$dÏ‹ISH3äÝ Ñuõv͈YñÀâì–ämO¬M®¯ÅàqhÛýG6mË4M¿-»#¹mKG¤~·ñ¥ÚÍ&=µl *?}Ñ[ o¬Ú»¸Ô#â?„·)®¶P<Í?Âð¬bt^]¹Ž(VLDØŠ–üã’º;5‘¶È`„lœ‰œ‹\ŒpCpÑ¢nZȱ¯ ‘“Z…ÎñGðÏ.Dà”Ö”h}Y=9:Ù7מ%yíƱçT­Û7´¬ùÀaõp6D¶F°V0uçÃê3`ݶFˆ?\.F॰q´¢xÓÂÕ¬Á¾Ñzí]¾RíšlûLä¥Þx¤µtE0+y=BXš-cC„Ÿv)'éñhä[ðm8¡ÔP¤*’ŽŒDöFÆ"g#"1¢ÙñW`±v’œÕn„Yí Ãé ùÒÄks¤…æ{åLÁ9ÛÆ‡ªÏW'! Lòã<÷_Ããš HÓ&›h% cœ‘ÚúÏŠ­Tjc~{‡ýî‰'âó6Ϊì(œZ)–F*|ÆO?}=Ëí&=SK[oûÖš“á{Œ¦àŒÁŽotöI¸²2œãÓ;.ÿlFo!'*U¼h?22ŠÆ F’Œ¶Ã¼åå¤uLÎýœ6;C•^_EÈé Uø¼•!çÍÞŠ°Ó®ð² ­dïñf;È?¨®_L|ù™/ʬúr^*³*”`LéŽcšý“]¥*ŠH’'PT”ÓØE®@ H‚ù…ÂNäòx¨ôk@Q€ rY°«€§H TË‘«H^,¯’7ˤK¯\.7ÊÄ$Ã?>/Éø üù5™ì–a‘ ´^Vþô¿ÕdxQ†gdØ,ï”qŸ¼ZÆÍò 2öËq¿-ÿQþD&ߑᰠËp lx,É@Gýé%γî¯Éø™\ÍNíÅ‚ ÿW:ò;2¼>9þ¦|߸Ü$wÉÄ+ÃÛtlmRøy·ŒV{€v|Wþ@ƯÉp’uÚ/?)“Y2ÔÉà’eëòýèœö+ÛdØ o•ñ"y…Œ± ËpF>'ãçåWe¼“UBZÎȸZn•ñd÷•Zÿò¿Êø¨ _ϱB†n:dpÈÅrµL8.²WýQÆ'åS2~RkºU†ùò2yLjä6‡Ë,ò Ý®ª¯ÊpT>)ãÉ!YK¬µ+a“úî†OØ A{ùVyŸ|T&ëd¸òîjº)lÚ B´LÕ^.3‚_XVÓ2hÒ©–É#ò^yŒ*ì¼Mž+cCÈ\eVÌÄl.Dž+ž´'ã!È#z°ài€)ŽªÀx£@(P ³4Wb±2SåÐXXØà@ *,"žtÈkç™u…Ìq¨YPìždÿ03ÚRZÍGuu¢2 >øRŸ}|ÒeÿyŸýõbÙe¹«åK®o³1æÿ?k¢Ns"Î<š×gvhžI y&È)Ì%1­°®uK’ýÙ¯…Zç­l÷•»á`}¼£©Jòeeàä¾ì'ÀrÛg}óºµ ó:þõŒ§¤}I£Jf¹Y‹Ѭ6š-"û lA¿¤2RóIázŒg ì*D3Ìó®û&ÿÔZa ~‰q9GœÄ‰¨˜Ûñ<ô'úOÅ'òœG_›b^ .¯¹Â–ð¬ úºŠ"E®¶Úš҂©Mk—Ì)¸!Ð]ïð:Þha]£»¼™Í£”ê4,¦Ý 7)âúGôx®mÀ†ç¢„/\þósBÞs~&,eV»Údî2÷™I“©ËÔg"~õôûôDѧõ˜Ó×èñ *ÃZ­›Î¦É¶yÙV¯áYÝ˺7uD§³¬5úª ÄÆDW¿âÏøñ¨?DÓiÿ¸ÿ¬_×,úÇüXôWÑ‚Œÿ´ÿ‚_‡hrÈ¿—–Ó½Ÿi½‹ûUíyÃÂܳ¶Q{*ÎøTÕ†ÄÎŒ‡ÜÄàÖD‡[uKi/2XDâNÛUϓӭƒœéŠÄ¬Ilš6\×|¸š\|ƒF ·“ŽF{2'A_ñsObu<Î\œÂ¤,s=]«&÷À}Ë`ÎÆìEèY‘ݲ(›½g0»åÎÝ0^#þÊJOöÉ=T+†Gwd?¾Î–5›Ê-˨.aB?VêQ¯‹—ù}üQžãø­4A,üeIJ×BÚ,Ý–A -! æ,pÖrÁ‚OZNYÎXˆEXš¹,Æ•›Úf© ë¤uÁUà,.‹l!Mœ¥ÆÒFÙ`Ùªu+s5á¶0WaczŠ„÷89Dv8ÌëwxÀ£ÃáMP aAÿy`whž!º¾œŽª TbúÅŽrÐT<ÍžÄTíI͇ӇɞÏ^_þËg†7m/jªK8"­sYôÞoâé ûŽâ,yô¾M#ûúïP°9G;¼ßÿöü‡î»wûc}“þL~>² 0Zú½ h²1qAi6ÛÔEînliêN3l6Â]:¸“€u-Š ˆÁJ$¥B/W°V1ì5ŒˆÁ¹Þ¤¬ç½hR›éÖ¼”9O}<L9ÓAÉjù‚›²öö'‡™£òÌ{/x›n™5{ Îí®˜=ë–&/>úTö³ã}0Ýp,ÍþSöÙ½¿?Tž"f_o •+åõU¹¶¹‚®„‹¸øˆÓ6àå&!ÚžYjs ò5¾ß'zzfË£Gä¬ôà ÑØŠc÷ßìÖXìVö\ûÅMO^|üà?ÝÛûô<øñÓ½xô¡ßÞtÓèïÚ}ö›ÝÝß<»ûà§Òé§²Ù'²—ž^°àiàAM—?å>¦06£jBçß! »‡øwûwùùÉݶ]¶C6RÊX†j0©\šü0LÑ£¾r…ž—ååI½«7 áP¹,}()&«’J2KêÌÉæªæ½Íxˆ~65Ÿnæ›Ùž©™A5ÑüQ3¶5C-jÑV™æñf~šØœnƈ¶¾ÐLš¢ÝN7ŸmÖMqdFõôàx3 Žð€.!¡"‡PúGwwø|#hå¼ñáœù¡Ÿ™Åi]ü¼£q sËäÂM4[Dì ÚÎ\RÒâsRrÜ:W¡/"ùðŠùq䯝ô¾ùnÿÝå•ûW]˜R_™\rÿ<ù³ÊÕ—?üÃuUå³VL¿éáeµM_}õ¡¡ß.!ÿhêi dùÒÎÁ‰S+f†'~†éÆn¸#ûï¹ >㬖•sk-BÍ‚u7}íÖFÃà”ž/RlBPžpå`Mžß‡&À>c¯oMÀö)àžR;ψWà*À†r'`2YÁÌÜ ØV,ÂbQ‘¹wj3j††³®¼§›qU³B¤¬×M‘Þ]åVÜi7gp¯(ƒî2x 6U@wÅ`ŽV€T;E˜-Þ-b³XQÆùôuuÎÈ€!ôqWÔß„æä¾"ÛjAv 19©ÅF1åõk~£<Þkò„3é)"yÜ×}™'IwÅ@´{FÄÙ¶àæŠ9C³äæ[¾ºí«·4OßðÛo9Ñ5#R1’ž½º£¸ù–-Û¶ÜÒܸþ»SwÝÖ†Uß+ˆ‡åê²iê’•Smé¿qËâ*Ÿ=û—£¡X¨¾+>cQsE¢©ïLÿþÕf—Ï’óß4]þŒßHiDD%è.¥{£6:Ádõ[W[I?YCp#™E0• üô|¥¬¨Ià‘ÊP´(e*ƒ¡²Ñ²³eDß›¦œGdŠ¿%$2`Îø3αÙuy®ÀÀtB9 ͪ¹" ÀŽA#žCWîèüƒ¿Þv²¨cv—¼å{ê'þñm°üøÖî§³Ï6î¸cé1Š OíûÕCm—îÁ˜@×£¿%åöý£Ù饙ÃEŠws`?ÎàåK)÷â«xhã7P™ë(’§‚hr|Á¢>u„Œxhç/ðXá‡øÍI?þ\“¢jL‘Ru Oyñ‹ðCÍ–²ÐìâϧV9é>»`ÿÁƒ¬[)Σ8_†ÐaeM}=±v–¡ •ÑdSø¥¸„o³Üm9d!Æ_I¬„Ô„×§‰ÓÒÓpzÚд±i¦qk‘˵ma!\55S7T3?(Y./\oJ ©E"’d÷–¯×ñÛùý<1ðzÔB>ÎÇx†øš¬&æŽ>f¤Õ•yÆ9+ãÊVŽEnÔN"o½Æ¤1h!}šIî\ëô¡ÑÁuO®©-íXºáöž=mÉZoÇæÞz¾õÐâ×Éû6‡[úšZ·¦ §/#Go=²º>ýílöø}¯ZÓ´Ûÿ¼W°šø¦­¿<­J.ß?xî;ó×u† ½¼ÿh—†«—_ÉÎÚ¹UåÊÏ=!fè½Úxª?wÁX(w®vÉNWÌóÇpöLì\ ¯ŽÝÛ#r ž‰½ÃÏÄ^Š]Š‘}10ÅàçñØêŽ)'¾§Æ”o?­²R,#@[áS±31ìu±úXÓšX[ ³ðVm€5Z³®XŸö’Ã1>¦ô ¨5¬îž{Õ»±bº¦Ñàsâ‡bU±±ØxìtL—ŽebC4Ãå|ù•Uª-‚M—ÙÂé€×¯)çìPÍÒÖå¸ÿMWÎéÈñœÍ-—cä5<ñ³3T+׌Ó_PÍGÓå4äº\,÷NŒêÉ¥zXpª1>wMk…MøÃÕMÓ†äbV?=wÛc™ £áß×~õ!2žíf²ÅºGåè}eÊfž‰c:»d/±wعh!ð~·?ê'Q xÛõªSñÔf‹ô"/¥/‰wÄWÄ7ŹOâðq€ñ8hêžmj:\\Ž×ÄÛâ\#‡\r_üdüTü\übÜ Æ)˜ãJ<ŠŸóÞ¾*ƒbÀæ\4”˶’`É‘RRRä0‰¢‰+ N-*11IšŸÅ=å"ž˜,/þ6žçí“ÒL4G µ" iP@N%â¹ Ÿî=rùd–ÂÂû?¸m¦¿ý'«7~ÿ«3oÜöìÒ©K¶:ÁGt”ÔÜôÌgÿ èfÏ>9µvÆ×Ï?óÝ?íšfq˜à^_]]^jBˆ?@ùµ*‰}×ë©n0_oÐ÷:.§Cï¼Y‹•1ëÜÈM¹³BnuŸu3sÀO•Fƒ¨: ÂÍi+XyƒÛ€©šGø¶Á:ð–pRmÇ9äÄóœ0Ó Qg­;õf‡¦âä¢`˜zÍ4~Í+_¿Â×'ªwhäLÉi2ö\£1sœ„׎-ýì•“ÙúcÇ(/>Á}È”ÄKîùK‡&•ÆÏ‚¹{X—¾A×D•øJ€Z¡]Àµ†vžf™mÁ†B0‘àÍœÎÅVæÂÐ’Sf—Ž%T-:±!$«!úÞ°+Œ/„éÊÂ#á±ðxøl˜w÷Zï¢k§KïôÝäþŒÁ“ }&Šq&^å5:ŠL¡‹_jóx_$£©\kÏçòb.·ƒ­vʆ×eÿžý?L/.]¸#³jÿÒÊܪ?{ÿæïnŸÿ·ß-÷îü™šš%ÛçQƾ…ÜŒ™~Àîཥ<ø¤ð¼€ «Ü%'ÀÛÂ…O(í(óºUI(êò^`ƒ+˜/„„*A'¸<í´'…Sö ¸F6DÔFÚ N ç„‹Ÿ@j„6œ@k‰3€[„ÓÖpP£QÁ†øƒˆ¹G œ^¤"UîQKõý¹ Þ÷âçû'ï ]±ïåî1ÞE?=’$ÂþcôJjv¶•~ñÍÙ ü.«ÅÑÈ~ˆ3üÃHw)A=‡]¸ wcÎŒMLœ`rA£TÿÖNã.ª¢ÀÖL´¼ãϨc¸€…­Â>ºFN`FWûoΪDÎÐãñ|çòÿx[ÍÐ-f d¼~R8CCè+Øx(ßúñïÿD=,<#¼$­4 ]~IxWø@ ûh 団ô pøa7+zIx]¸$º-q¡IÀÏІx+kvÛÖlÕEº„>6–_`-ú„Õ´ãaÚË`à²øˆð¬ð¾ð‘ÀÑí¡E´ŒÎ[N ø}¶MÀ]¦§ù;Fº¿#”Ð40¶Ã9 ê€vPhè9x.¿baÕ.N±‚|ÑKX(²“ꑼw€ŸfåÙuGåŸÚËlNRýu¯"#J)1Ñœ6ã´yÈL”ó•'þïR«=iË  ©¨Q†vl#`'®íh?å}k«§¯ ºà~××]X¬†=ÕGª±«ÚUm-Z»Ý°Ÿžåbgº§;‡:Ç:/trekwZZ1;Òì$²~ÆŒÊúõ¼×íz·yõò^}åzÁä5•›vššxƒÉ›†ûµ¸2v·¥?gb× Yôpÿðy*Ó ÓQÀ¥Ó”d].l½~ \‘¤>oú|~ç꾺ž–â·ÿöÛ#knZ3uÙ¾ÁÌ×o©Þýùàörwuw*5/át&æ¥RÝÕnòÉË\ÊMkgþxüG?ÙölyÙµsïë›ZÕ{ßDdÖðee7Ïš=<§¬lÎ0~»iYgYY粦é™6YnËhºÆ—ÿ !?Ñlô;e6‹«µ·9j®5¯»Ý½Ð‰Ûã 7n-ÜWH2…ÐVØ]ˆÏÂéBè¦Å' OrJ!È…5…x¬ µ•§ÚUT(† IGû-$ZySý4u¼X;ô¦uguX×"$¡Œ«Ê ^oÂ5àZë".—ΙÌfý€pMðÏù+j[î²{"'vQæÀw5ÄyI?3ôÛ“æ^¼&CžÂõIš„§ÞŸøÉ‘cäÃÖP¨oI·ç]ØlnâÞ‰O&E°ìËïp:¿Í>ÁbL²sÈ»äÊwcè¥×dÛm;l#ËË7–caú–IÙKùsX߉¯Fݤ+ s3ZÁ]¶Qï²?hÇsí`/Kƒ$šd98Ï*a}š÷çìòùLÞª"½‘¹#K–­\²å†PöÆ·&^?r >}øGëªk°‹Œ¥7tÉÛ*»ïÎ>“mDæÕ{Ì?°¡Cƒ‡‹ê#/P9À†è/Šª£[jµ‰6Á‹YóÍ¢¹W¯séõ:æÓŠ Ç»8Ž÷‹ †è‘-ŠÜ=h®Ç¤ˆÞårÁ9¸G©êQÙS±Å§¨§]Ttwv] dÂê¨T¯=}E¹6%¢SpAœÅšqØè4ì`»Dlu>èKøÞô]öq6šØã;â{Ö÷¾O7½Å·Ö÷²ï#÷2­Å>eÁ"uÔ[|òÁ€D¯”± æà±8y4@õ?Í[Ê ˆ¯æÃI4ŠçŒ÷Ú_üЦsÅ$ÉêZ8t sŠfÝxcxƪhk‘³#öP^»ù×¾]ƒ3ËDÑzq·Ç÷㜽±Œ gù6*7ZÑ}Šhnj­®]·PGé@Ç\:§âEZÄiqH/ˆœ…]œ™J §¸¼*g4à«a‡°‚Óxâ1 %`…Nçvºý ¨AiÀJC¦a´ál¬^kdK¯ ]Å@Æ ê†íÅP\\ê[Ÿ'¥'„M_ºþA6óÀœWý_bÒ‹³èDñçýÕƒ¯Fɣɋº  ž º"šFsÂ`ЬÓ%ìb%»hõoµ+ÞñÝ;[ì~~YסTkÄ‘H&=ÃóœúLw÷Ž%ÕÙeJOçÖ¡¶GgÛVÂèÊ'†¦-z]~úyÐ=vÙþ´Ó(šõm»N?\R•È|=Û[¸¥ûè×}{{pî¿0nä;¨T8çEšù³Rê©F Œ‹ÆððÿÆŸb€ h)âaz†jX¡ô/\û>ó‹QÍÓ™‹O°°ñ Ç=Tµ„_-sñ½“ç¾îw½}G Ae=¨ÀdW=Y,†«§·Ê¡8Òâðù͹u®†Í÷™¿f&3Í Ì·˜‰™!bÁ¤š1&7g #ãî·|݂͖B ¶è°½€V³‰‡‡Ø Øàf?¡çsîŠ>ûv4öçòD5óÙÄÅ÷˜-ÅAyO’ßñxøZÓ‰ŒúrY²$’uç-(pÞƒ‘cÇ&Ξäî»ôæ G>«õ]Z¦å§¢IÛ÷Š»q´Rq//w X î‚hÁòõ°¸$-`„Ž(¥4!Š%•J%øúª¨6‡yfCãÝFÄØ«‘+) 0àSù$Ï@4ÃYîöŒöý|±ÌäeG¬]ðs~ÞJÖIb%Dw4{ùøÍýÇ?Ù~÷ÚÁ„®µ¨ãÅ 3“µ~å¹u©á¥7ø¢3t±ÛïÞîºù_.=~ ßí¬cö݉'”¯ÿîß=:[,Œº~š}Åìqs¼æ¶ì+ð MŽ£aňörà€ë1í'1Š vÕ‹äÓ26È#•€*¡e¤ro%­«<]IhQ¥v1ÓåQ}ßô>µ6°%°'@™Ò¡Ò‘RRʶ±KùpžœŒ…D°¨{2ñ%? àü’HŸ/HµBxÖÆ}‹\Er‘«½¦°¶´€¨Ÿ“c?;ýåÁ@€–QÞ4F÷w:{n{=Ô0§æzºeñÝʇ`dOž€¡iZ×4<Í¿½j׆ükýxKhOWøC!Ük7¡í~)ªÛ³ß˜a?Ds6Åå JÂ4ÿúÂÂjÅÅ8ŽÇ£ë«õâzžiØFu#cLù_`‚«]S²Ø)›Ó°±è®Y7n¸±T4àƒ³ÝœAϳµ/"7ƒŸÙ]áéÏ®·_º¬žáÏñyò¯š{pD>þD]į Ë§¹çþú‘æ|îOäü‚ÆßÿAäaQ®ñ÷ß{_¥¢ï»,ýüÛÿ©æŠÇŸûÅ[¹Öæ×ßTµ QŹöÏŸÊóÒËêÞλ$|ïEõOòÏóx«x¨ã;xÌâÆ0ÿŸi;åðã*¯OPøøákJëj?ÌõÃBÌvAÚ5˜¥{Q‡—û!æ›æ›í#/yá¥˜í§ bÎiNlý"6Ùü6³N³bíW'Ž›-jÒ Q3¤MPkj7á´˜sà¢Ê”Ëìöë}Tª·˜Ý:½·ò. û7šÖ[a>­ÈðVÏ[õtÌî^ŸÞå£5«÷8±Óé²øuœÞ7à?‹)³; °-ИK…ügtï.³[J`(0àš”„#½‚Z6œœ èÍûôGõXÿÂåÙfW0iñ|¡Ùê$ú¿Îeáˆ(äö~ €8®ÿ…6;PþB·3ž¿ÒÉl.?c[É~©‚Ŧ_÷ãlÿò]=¯|Ðd„µ¿Ëö¹0vh~2;{ü4{Ïc¸üi þIøþ>Šo˜øÕÓÙQÍý†—`…ñì2ÐB¹³Æ‰­Üp%a7Û{´W)§:®Á Ì`0J½}v¨±·Ù±]ûU¼S’û<¢ýÌÝ´æÜÏÜM)©g€ÓPO8ÁÛ›Æg1ÆAÀ¢‰Êמx%;p±»ÂÕ9óû¾ò£ùß·£êóäÏö÷''CÒíáü3Gì:"±÷¾“ùãY¼úÄÎc“±êÃ6-AÓ›qíÄëWîþ^þÏdb+(Ê“&4n0n5’îævq”qÎ"x3ÙIðr3™}b¾Ùd¾еÄdæ{yŽâ$Çiåûm-²Âœ‰ýðZ·‰3™xó ›ñNŒ3˜Ê( Y¼Dl[ĹâñYñ#Q÷¾xY¤B”¢™Ç¸¦Ñÿ§´gnâȲ»gô±õµÇ–mŒ=cll$¬mÀ|…!¶üÁÒ‚íl Y¶¶¤•d8vëÖºº„lÂ9vewÉ&!!û¹-–@á•lðf“û…$\»•*â…Ûܧî _ŽKåƒå{Ý;ÀrIÝž¤ž~ÓïÓ¯_·zº{zÞÀqÊ*(Ö”uÜ* ë%ë5¶hÆv³ŸYîôòØšÇcÅ`öfGa̦ɇ᫅;…¨XÆBœµ•xU‡žSŽ× ÏhºÍ:Ad;d2>Vrkùþhë;ÐJ矧àÛÝ2íosgößÍ÷¹Cït‡•o1Ïœ8_wû6wÍÄ÷gÿÅ={õ)R:‰à‚•é¢ÏÿK½bU Wø¾½^Ò*<öÏÁÆWQ¨¨èÍ^½ßð}Ε°(á›þ­ô¯™äþ§W´ÙÄ\½˜,…¾BRÈ6t–™³µ$û”aq±A^d>e),Ì ±Þ`1X„üAÏ/)l•»–&sÜn(sagõ;oKN¶èÂÝzÝu€òç— å åÔ ¡Œ ’ ¸óË…* ˆ+é[­­éØn=9µösllhÆ´e åggkáÊ@kÓoc_ºÿœ…>ìîÇO¥Ã,ô§ßÆîLÿY‹?‹„VTŽx-†R¤7z­…z“—o0ŽÉãò„|Iž‘5Ügq–É+™Ž™ˆÉS‰‹N!dì1½±Tw*Zçïwí¾ÌŸ¢sÚù~k }Ò‡ÈW³Ë¹w‘ßt›Áèhó5Öo]RµvY6¤-^ô¹:|M÷×)ËÖ,-Ð÷ëKÖð't­(Y¿kÆï7T¬Ú¸ªBÙØ¹v÷ƒ3k‰sµš¨U7ù•gnoÎ39DÃî`)GFªÇªU Õ2ª†É¢¤zUQ}Él@€*¢_Püï_¡˜z>»åÝG¿CS¡–6RRLí”d1ôg”ÁßS|Ž‘ýÛÞIñ:ÚLÉ Š‹)6Qü!ˆ"oÓiJhê½+Þ!ú§ úÇßxw‰¦.¾ëý%}›’)~šb– ÙM‡(i‘© ¯{g¹çè[ô ž£§(9H£dÝGÉ6–®b”''¼ Ç³ôEzŽ £ãŠ9 M½ðS¯âcW=é :G…·(¾@ñ‹Œë?òre\Y ÅsL_ü.½Ê:GÉ1ŠS¥#,G¼‰ú@ ç»æ½ ¢HFÐaÊr ?(}·Zz™:)\£¼ô\õ„ÂÞ(ÅvºŽî¢‚…Ê”¨Å:ÍÌ·£Ãë£Ø@a¸:7_’©ø0Å1ŠÛh/%.Š+(FÔJÉFÊo ®Ùà½DñOPLB¡*"ˆü4F'è½Fuœ´ÊYJãqйjˆb½K¸äÈÏbCÎg+¥œÓs61³4>ûŽz‡6sCÖžydþÎ}4™©ãín°ï|¦VݘeW§7 8û ӮîêºÝ»¶ûécÓG¾¢è~•×¹ÉraõFáÏ£êªKwo½xÅ¿h¬býò¢5mY,/¯«m»|¹¬ùO¨Ýv÷ú5î­%ºò-ám}ž’Åù?KK…'×ô7çšf?ÿƒgè êÃÇEY8Î÷9Ö{\Z…¸`ü#¢…È„è‰!yP9•‚±“ö”¬Ãh{ŒV¯±gŸ®ÿn÷uÞÏ3ç(¼C£,ˆò­áÕ[ ÄÜׇŸa—0þ.,ly(ž>ÓmYÿßHμ‡éï'ÿiþ]Aso¦ëui~‚ØKšˆš|º²ôýè…÷ atçÇG>B[I-ÊÓÖ"»ˆP1„\Æiv¡µ £È ie䪂¸ƒš¿Eâ<Àµ]Ä~8wM§øª|ÀuåGMo™\D22–BZ“ŠïTå> |mš´NL Ç^4-^GjçÞä8Hƒ`¶¢Àýp“ª¦ÇO€oËÅ@—ú³2-c:éJ ½µ¼Òö€®ŒþþêÐB™>àxÆ»BÍ«ð½"š›2®¸RlCµïÿœ¿7j9J‘IÁ%ÖÁwJÓ©¹©}Xû{]§þù¬³Ù-Ùç 6øq‰qÐôfdn5¿gé°öXûdîo¥—óªóÞËoÊ9ÆÖ[PSðIa¤ðdQË¢ÒEïo-~}q]InIS©«ô÷rDÉVž(³•½¼Ä±ä‰òUå{Ë?ªè­x¿òñûÐ}ß]Ú³ìfÕ¡j›=Ïþøò¦åŸ®0®xÞQíxܵÊáµíCÐJ3«VädO]Ѝmc㎅6ѵÐ>0Pv©0QaÆÃ*,͸ k½ ÂZdA'UX‡¾Î©°åaªÂY0iTaèðÀÂÛÞ8¡Â&4‚¬Âfd' ÓXÌ‚³ó¤Y…1R d–ª°€V T…E Ù§ÂT(Va-*~ªÂ:tSø• 롽ü• g¡Åâ´ Ðñ36¢‡4+U؄ҚQ6£íá-ÑØÁx¸ ©üB©q¹V+ÛC½Š7\®4F‚eóà ÂÑ %J„âûC½¥¥±îþí›Û}­J8¡”d<Ð Ä÷)Ѿ;ù[Â{Bñ@2(;BñpßöPÿð` ¾9 EzCqe…r7ÅÝç¡x‚¬t¸V}‰»›ôkÔÝûÉd(‰áˆÒîØáPüd(’T‘^¥mÑ×׆xb0O€8šE÷ÇÉÞpå–p,è¿%EU•’¡ý!e[ ™ %¢‘d2¶Öéȱø™Õ¾¤ rÙI8ÏHŽ< Zt/X;Î5èå|óeK@ÎhÖöâÐú¢wY‰i·Ÿç¹§'y{b¸~Cká2ãDø×4wJªrÊ?–/ ÿŒ·cˆ×q?ÐfêÛÁeAÛjçm}¾~˜-2õxï:îã1³|‚s$A“¯«ùúO€ ÷€%CÜ~LbT•ËhÕúˆ¨¹@'ÆÍêm¾N‡o³ñ®OŽ ”% 8Æä2bܲ½·Iÿ¿êìø£-;xœ¾´©:ž@se àŸ—²z^ÃÌ3ì&~¼€EÏN|m¿;‹•Y<òöSLþs¦J>=sa†øntß8}CpÝÀ–X®[¯û¯÷\]áº6Ûò6¢qÎ×ÖÈWÝÓí¿s¿ßަñzÿtjzbZ`S›Îi½¡~ íï 6Ù:¥L¹¦bS©©KSצf¦ô©×Æ_#¿<ï”-çåóD>ã;3rFè9Ž-ÇåãÄÿlϳdü(¶•: Ï<íŸn(•Ÿ:²T¾vdæw„O˜r껈Gž{’Ä¥RŒ?BNï¿°Ÿ$üUr4b—# Õr‘»°]çÚµÂ_غ§rY}O·Gî¢].¹³¡J–ܹíPVB‹ ó;‚QaL¸ èôÛý¥ò7!\óÏø‰Å'ûœ>þ·@sjŠ5¥š„Æú*ÙÛ°F¶4È Î†w®6ÜhÐv7àcð«?]¡^ðÔW9ë=õ¥eõ‹½Åí6w~»Õmi'µc7jwZæØž†nˈ…9AÌ™µ¿‚Ç_jÛa·7¿¢›ÛÞ<¡÷ïšÀß›¨ÜÁŽžovNh¿7Ú;w=øÆOì|dtÕ•4OÔìxp¢§dgóD/¤°–¼dCu;‰$÷§Žív‡áˆìÃÔ•È$"û<Ù8‘@‰¶3!%ì,™¥0 œ] Ä kçT J$ »þ”gÒ endstream endobj 49 0 obj 21642 endobj 50 0 obj <> endobj 51 0 obj <> stream xœ]ÔÍŽ›0à=OÁrº×2R„”I&RýQ3}NiˆEÞ¾œ{n[©‹Dsm6àd{ØúnN~LCs s|îúv ·á>5!>…K×G™Äm×Ìv¥ÿ͵£dé{|Üæp=ôça½Ž’ŸË½Û<=â§M;œÂ—(ù>µaêúKüôk{\®÷qü ×ÐÏqUU܆ó2Î×züV_C¢½žír»›ÏK—ï1Ä¢×)ÍІÛX7aªûKˆÖiZÅëý¾ŠBßþw¯\±ËéÜ|ÔÓRš-¥iZújÉ¢¹Ø!;¶çÈžy…œk–¹`ý rɇ¼b{†üÂv­ß0 ò+köÈ[æyÇÍol×y÷Ì?K™·ÈôpfæC¦_Ð7£?Ç3ús˜3úE3ýl™ùµžþ¢@¦_t|úKíK¿¨‡þâ™~¯5ô—Gèw˜KèwØ¡ßaíB¿Çþý^ëÍ}ú=œB¿àÙ ýóŠù7Èô{Ÿ~µý9öPè‹þkúž©£?Ç\ÎüØgg~ÌåÌõ:{´¯ù±^g~m§ßk_óc]Ž~§ãØþcOœùÕ@¿Çž8úÌŽ~§íô{¬ÑÛþk¦_°'žþ\³ùñL½½?Øo~¬×›_3ý9œž~ÁZ<ý9ÌÞÞm§_`óô—xÞüZoþ~ÈöÅâ“Æ™ó稈›û4-Ç„Lz>àdèúð÷쇽ô÷J1"^ endstream endobj 52 0 obj <> endobj 53 0 obj <> endobj 54 0 obj <> endobj 1 0 obj <>/Contents 2 0 R>> endobj 4 0 obj <>/Contents 5 0 R>> endobj 7 0 obj <>/Contents 8 0 R>> endobj 10 0 obj <>/Contents 11 0 R>> endobj 13 0 obj <>/Contents 14 0 R>> endobj 16 0 obj <>/Contents 17 0 R>> endobj 19 0 obj <>/Contents 20 0 R>> endobj 55 0 obj <> endobj 56 0 obj < /Dest[1 0 R/XYZ 56.7 773.3 0]/Parent 55 0 R>> endobj 57 0 obj < /Dest[1 0 R/XYZ 56.7 422.9 0]/Parent 56 0 R/Next 58 0 R>> endobj 58 0 obj < /Dest[1 0 R/XYZ 301.2 621.6 0]/Parent 56 0 R/Prev 57 0 R/Next 59 0 R>> endobj 59 0 obj < /Dest[4 0 R/XYZ 56.7 785.3 0]/Parent 56 0 R/Prev 58 0 R/Next 60 0 R>> endobj 60 0 obj < /Dest[7 0 R/XYZ 56.7 109.6 0]/Parent 56 0 R/Prev 59 0 R/Next 61 0 R>> endobj 61 0 obj < /Dest[7 0 R/XYZ 301.2 204.1 0]/Parent 56 0 R/Prev 60 0 R/Next 62 0 R>> endobj 62 0 obj < /Dest[10 0 R/XYZ 56.7 682.8 0]/Parent 56 0 R/Prev 61 0 R/Next 63 0 R>> endobj 63 0 obj < /Dest[10 0 R/XYZ 56.7 224.5 0]/Parent 56 0 R/Prev 62 0 R/Next 69 0 R>> endobj 64 0 obj < /Dest[10 0 R/XYZ 56.7 143 0]/Parent 63 0 R/Next 65 0 R>> endobj 65 0 obj < /Dest[13 0 R/XYZ 56.7 725.9 0]/Parent 63 0 R/Prev 64 0 R/Next 66 0 R>> endobj 66 0 obj < /Dest[13 0 R/XYZ 301.2 609.5 0]/Parent 63 0 R/Prev 65 0 R/Next 67 0 R>> endobj 67 0 obj < /Dest[13 0 R/XYZ 301.2 100 0]/Parent 63 0 R/Prev 66 0 R/Next 68 0 R>> endobj 68 0 obj < /Dest[16 0 R/XYZ 56.7 194.9 0]/Parent 63 0 R/Prev 67 0 R>> endobj 69 0 obj < /Dest[16 0 R/XYZ 301.2 595.7 0]/Parent 56 0 R/Prev 63 0 R/Next 70 0 R>> endobj 70 0 obj < /Dest[19 0 R/XYZ 56.7 625.1 0]/Parent 56 0 R/Prev 69 0 R>> endobj 22 0 obj <> endobj 71 0 obj <> /Outlines 55 0 R /Lang(en-GB) >> endobj 72 0 obj < /Creator /Producer /CreationDate(D:20110212113044Z')>> endobj xref 0 73 0000000000 65535 f 0000106372 00000 n 0000000019 00000 n 0000005115 00000 n 0000106516 00000 n 0000005136 00000 n 0000011826 00000 n 0000106660 00000 n 0000011847 00000 n 0000018239 00000 n 0000106804 00000 n 0000018260 00000 n 0000024570 00000 n 0000106950 00000 n 0000024592 00000 n 0000030431 00000 n 0000107096 00000 n 0000030453 00000 n 0000036429 00000 n 0000107242 00000 n 0000036451 00000 n 0000038043 00000 n 0000109970 00000 n 0000038065 00000 n 0000039305 00000 n 0000039327 00000 n 0000039519 00000 n 0000039810 00000 n 0000039971 00000 n 0000052330 00000 n 0000052353 00000 n 0000052549 00000 n 0000053014 00000 n 0000053332 00000 n 0000065797 00000 n 0000065820 00000 n 0000066027 00000 n 0000066523 00000 n 0000066877 00000 n 0000070347 00000 n 0000070369 00000 n 0000070581 00000 n 0000070872 00000 n 0000071048 00000 n 0000082139 00000 n 0000082162 00000 n 0000082365 00000 n 0000082847 00000 n 0000083186 00000 n 0000104915 00000 n 0000104938 00000 n 0000105135 00000 n 0000105759 00000 n 0000106234 00000 n 0000106317 00000 n 0000107388 00000 n 0000107445 00000 n 0000107674 00000 n 0000107800 00000 n 0000107975 00000 n 0000108145 00000 n 0000108323 00000 n 0000108506 00000 n 0000108661 00000 n 0000108894 00000 n 0000109023 00000 n 0000109202 00000 n 0000109362 00000 n 0000109532 00000 n 0000109695 00000 n 0000109851 00000 n 0000110110 00000 n 0000110270 00000 n trailer < <57CA71FEF10AB74054D6C6503D04283E> ] /DocChecksum /87B0E98CEEA4C4EB32796D38A8155FEF >> startxref 110581 %%EOF tmux-tmux-f222026/presentations/tmux_linuxtag_2011.odp000066400000000000000000000355641511153563100227420ustar00rootroot00000000000000PKMk®>3&¬¨//mimetypeapplication/vnd.oasis.opendocument.presentationPKMk®>Configurations2/statusbar/PKMk®>'Configurations2/accelerator/current.xmlPKPKMk®>Configurations2/floater/PKMk®>Configurations2/popupmenu/PKMk®>Configurations2/progressbar/PKMk®>Configurations2/toolpanel/PKMk®>Configurations2/menubar/PKMk®>Configurations2/toolbar/PKMk®>Configurations2/images/Bitmaps/PKMk®> content.xmlí][wÜ6’~ß_élr”=}—Úºø’ã(qâ3vììLöiM¢»‘Õê<Í?˜‡}Øÿ·¿d« ›}Q_(Å‘%úøH"q+€úP…BñÙ7×qÄ®„6R%Ͻv·ÁD¨P&ãç^µNß¼ø·gj4’8 UÅ"±­@%~3(˜3—ú¼‘éäLq#ÍYÂcaÎlp¦R‘ä¥ÎʹϨ-÷ÆØY´sqÊ\.mŵݵ0æ](ˇ»·L™Ë¥Cͧ»ƼÀÔrñ‘Úµðµ‰Z#\SnåבL.Ÿ7&Ö¦gÎt:mOÛJ;½ÓÓÓ¥E¾4Óå ƒŽˆ6f:½v¯“ç…å»Ò‡yË$%Y<zgÖpËWF5ÕÂ@è.NÌÝ**—Y˜_Wãg×Õø6®wžg”yqª†»O•ð\6ævrÃøžtÞB"ýxûf>¯t¼k[˜wU–éÎÝt¹Ëå•R©XÀ-v"·ßíuÜs)÷tcö©–VèRö`cö€GAÁq¯cäëu GK\á”/2ÂÜP ßqÉEfÞXõ¯oß\óyf¹=sK&ÆòdÎËhçQ€¼7LZžÈ§æ]Y:'Ãt´H•¶Åvh¥_ðhbãèf†©yÖ±õYœÃˆ3&­+)¦_4ÐióÄ<]š˜$ê·¡LÅ¢“"ÊWx‘׳@\§BKì=p·b[¥g¥Ò‹Ø ãëݪÃɬÂÑrK ;0æÐ®ãÛ‡Ÿ;˜ÖBháï[*m `”ˆégVóÄ`½^(ûº`dM›F¼…¤R¥®ÀX:&² ¯¯ÃQ/òÝ„“¦S¼à™UXyÐ"80/ž9X ŸÌý,xÞÓ^ÿq˜þ3xå¶•ò±hä%Ë/[)pGh+…a H1äÁåX«, aâI;«3Ú›²©áo"°fcöPš4â3Àme 7d!êrÌñÈÜ”&·hYç-ë:%mâWÿîø•S3<ÜÒ³{Ñù±^™,cÍÓ‰ Š~ûçr—]¼°0ÔÏFþ¾G“éj“å>åi)׸›¦‡–+ùñ,‚wÙÐJ‰M$âhèˆPúG Ø÷ňþ5\ .§ÌØ)Œ”Ol> #uˤxÙ{Ò>>> â}z·2›öì]\5Ÿ kýö฿_×oÙµ­£¶Dãa{Ð}²‰G·$Qe´ÑÛƒÊÞaûdpº™ƒ?o’Üz¼_]¿\s¢¡ s‰e#…}QãÚOíîí}Xikß±©£#h*/ž¿n"óä¦DÜ.FâÚ'ïNæÊÂ߃Ìþ&2×'dö÷#seïDæÔO—¡ŠÂZ\BNêMɱ”awbW–ó^Ä&¸!‹6{s†‚`Ÿew’W–öN$ç™ÏU¦%ªu%’\Zk,ØÛ‚–+Ø[$ YRiP„GòZ„å¹Õß4·Ö&ÎçÖÚ•J¦¢H/›úÿEç„2S¢-Ä%~–:¼3rõ 9tt‡:y܇úí£É¡'wÆ¡ÃÉŸã»ãÏ•Ó'wÆ¡£*§OïCSN÷ºwÆ¢Áç%¨KÉ»l§ûüvzýs£t?½gLývá½öàArhý.üŸÿSeµQv|2ß×]øCÅ¿õ»ð*sèÞ÷;žCw· ò@w™w·?~ {¨»Û‡Ÿ>HþÜÝ.¼×} ‚ú·á½Þ}ÕU÷á}-|ÂP…³â¡|b÷â·áy½;yóç|ðÜóDz®ñ’‹½¹±Bûƒþò ßÒAùÁåŽøLeÖzù¦÷¡;÷!,Fn.ó¾ŒT™ÂÀ%¦i4k…ÂÈqÒÂS…<¹ã;4ÒPý"%å~ÐQ>e¤ñ,'½ÏS€Nô^päÂ`^Ïü”èÚ½S’<øråÈÞ^ã>û(‚‰AËp  âúU8,¦Â´Ñ­Â;G„~ÆøþÙCuíWLêÃÄNü/uéC¯ñÂÆÙµŸ9˜)ŸEén€.÷“ & (fo¹ÖRY{«ÚúÝ^o}¥þuæãùâÙ‡è(yÝí7JÓºe'YÍ…9¹;Loý4 —§o;ÝÇíþqo…îüÐ<§œ–‡îúbëˆt§÷K+<a Scî6´4G;KƒØYÅ<2aƒHê‘Ôdzã@V”¹‡Èza:æáñMƒ;£¬›pË$,øov\V»uõ¨ª`ÌÝLÖ÷õ¨}Ú;º©¯Þ«åv²ñƒÐë"\éß® %®Ö¸²W.Ï4º«?sa5©†@ø žÌ(•T`ëW2h¿%OjýŠ:ÿ_$°‘±™Å`SÏÍŠjuáSùPFÒb•B¬"9JÐP3gÒ[.ÞYPÍ ýžB„„ TãG%TXõ4 ÃZì5<'n-ƒNÁp‚æLÖÔÿàeåQ-+«ÊÊA-+·ÊÊoaO'¼—Å’®e¤Û ² ¿ea–Ñê`(a£ ñöF&”Ò0ÜÇG2 4¸ÿ–j™p´¡Üd±‚½ª»ÅÒÐ{|U¬¨Ë­Yž8ôÖ¨HðêCÅA$éšÐ°¯¦:ãâC PAá náX·xIÛ(íαù„ß¿îWb DF‘€t(,Èš"æAʵa´‰Å_¾Þ¿—,RvÙf\ë56VÅÆ'56nÅFJÎ(®"5žÕˆXÑÞÞx¬|Øxáä}ù`Í80¨ \ ‹–¬ŽÓý¥è…4Äp™˜ˆ1hM2 äÄŽú:¶VáüFÖÖE†€¿)¨SÎj·­.H›hÑY’”OhögÐ6 Î ”iðÇN@ «}tØuxV0깶ȸ‚˜Çñw Š×Ú™ÛÔ émF:ãþÍüÍ mî¶Žä×KcpÐm²^“¿¯©ïÛ*¡p1 ®› 5 ‰K3iBW­…Ÿ³ÿÆ”&íÕ±ã_/TUïh?ð¤ÞTÝ×ûÝ\4P€ÓÆ TµŠ|]œ0‚’BΠ΄÷š¬vÕ—Û«Äçe°™CKI©«*þA+IoÇí Øy®ÒÕâÙ¿ü·rÜd£H\cì*Àô8&'Q¡S/sÏKæ¶ Žs7Ã㣂‡ãªÂÃI [áaAN=h¸©·?Qœšhö5S‰÷3sMK«$kÍÊš,˜È(œ{’‘ SL­½øA©ðö¸A¾m^+|Ü–”»}$ý„'°0œ…²@¡iGNxû×ùC¤†ŸÔPùÒ™´ͼwþÜé =–!„d¿·ƒ‘õKT‹¼>o¤£©Ò—tÝ®U•<-,7—Ðk#2…ÇXÉeᯮæŽîÕóõÜïÁ¬ÌÐX—ýÍ«WýÒƒ@ÏDžñŽ¡b_íÓA·Ô)2K²‘˜B³‰ÔîwNk<ªŠGx}µ¤mv²’uåÁ@Ñ]¡ÌüðÜFC–ë¡\Ó36QZþŽ;îzÏÞVàa™—»ÊI=¦@ÿÆ;›»“#ت/µãÞ­ÁŠÀ@ø„°ÒñÊ{d¤ÍûèkìÀÊH„ÍåÆÑ9¥‚sÄ9G±,K©ºZL>Õyf–­ŒHêlƒ*%• fEˆWŸÒŒõ—~€gPØê(‹ÜÕ3؅ͼvdj¨¸<Û­1§2æ|¢ýŸ5æÜh‡¯Q§`:Ò†™¬K˜ân•ú/gHCUÀZE ©ÙêQºÂÕ+Icáüà.ÅŒ %!‰ùKÅ"ÝËZº•„n\x@®ð¬Éd)^…j2 yPÑਵ‰Pn¹w³Ù0\âÙ껆 Ç$Þ5|Í8pʘ[(2è@PЇEý@ÿ ² í‡0jñ r'!)ˆÔ1v‘uǯ÷ A¥žî•ùlîsNªÆI7?Í6Õî¶žç¥ÉG+Rɘ¹0C´Û$7•4¢(ÆS ÜÁ{s¹ô£ï‚ÁQ,…7hús‚ĉˆ¢ 0‚GCdÀsß×ÂNîwœ³ÑKlÐxuKôüûoʲ^÷ïOY˜û‡Wñ3ÈÇ×GÜ ðRÚj»ßÚ áïJ£–;e_ý#Sö©1ÐùŒýw鞟槅‘lÄH+¼É¡JÄú >&Öá#ªcb?b/Ld˦š{¢r•³è™HðÈæ 2—OYþ£ŠqËW™‚~–Ú3&ð,¥p»eD¡wÚ×븲¡)YqÇЄ:sçšîWÞ–oÞÁ¹©å±}/¿t¿]Ž àpç¾ „ù:öQ& –;ŠÝaܬ“üq¬º.Xµ@Ç%~Y ôÛeçQ!ce¢:2Öa&vGFËõX؇sÒu+gð²~•+p<‚çãþTjE|}Ä{¹ M9Ùðxj¼æ¼1L3÷_h.‡•5Ð)JóKt3BÆÖ€R»|uˆ'îâS±´¶|aÙÿþþšãéè~ô#Óhõ,S=ü;w|éŽ~(SP \x"_`Ù'o;&8 '±ÏZ†½ÄoH³–egíþcUwêpÕ…ú'‡ñÉ#Cß;|˜ßÇp;Ü{Ÿ“SÞ»$š1X$ko™TºÁ±þ&– ë~‹ 3èÆÎÈ[0墠—D~µv.ö]”‹„,…®è|Ä`ffÐìUÄÄëàUg|¤àxûWú“HE3ip9Ã?L yŠd»xEàX²a–5?Á­0.w@ìèb\Zú4W (ƒWu‹)#EÀ9òÆ3¦‚äËȨÜXËY€n‰TgÂHr2˜a¨uâCwl¦!ú`]hvůkWŒBë¨Õ!ô“†Í¨!”nÜŠ#À\Sš‚áJE÷äêíQûäää3BÓT¦Î³­°kµÈ·ô5 (÷oc˜±Š“yɪ–1 Í‡¢@F¨…aPV;áÉ¥q •£6qÆN‹*zO`ü]0aƒ*®õè#B {÷Q31ÏþUþ‚N(°ö`!JrEA/ n3 R•ÀC :!¬h¼ UÁq²?xÂðC3™ ¨ññëÖIÎÐ X €ß!;±DÍW%è SÉ¡ói2‘"K´È7òÀLðNƒS%ÊÕÅùë׌Ì "\nrz\@]‡³¨Ôu<‹ÝãYä;õÖâÆ —’Â%ð†•ÆöÐEåö7Žß“O8ª* ÕÖ!©¨xŸÌƒO £í¦‘Ó¹ÐêœÈñ*ºŠHmlt9’^æîWòŸÉC9°rú¶†&øeïn£ºä¯º$ ©A`¬ ÂpŠ%B ·¼ÿj:~ÌÆh| ôrH¨CXT‡„:†Ån‡¥R–>@ø¤ÊÖwr4bç¿\E ¡¦à¹‘<œ±ÞI·Û%­¡ÊåZçƒl‹I,Æ¯Šµ›õò—¢§Ï%±óž#:þá1(Ö^éèPß<ìTñ®Ìì*ƒj%Æý wÐ!^õ•É€sëì©VŒµÿÛÙ"‰AÀ4Å¢†_…ŠÏv䀊—â‘ E|¥)Ÿ9ïzŒ]b [rµ÷ÀÐÁ ñº]‘•òäþˆºÈèƒV`QmR¬šÕ1ª£Yc;šý²Â†J/ ²jjY¨æ_âKÉA¬ãÍÊ™ço‡*œÍŸBÐý¢?ü~ñÿPK¦Î,Эº²PKMk®> styles.xmlí]Kã6¾ï¯0$7½å¶Õ3=AØÅé Èôî–h[I$¹ÝÓþƒ=ìaÿßþ’åK2e=Z²åG{8‚6Y4«>U‹Å2õñû—(œ<Ã4 Pü ˜š¡L`ì!?ˆWÊ?žþªÎ•ï?ýå#Z.ÞûÈÛD0ÎÕ, a6Áƒãìžu>(›4¾G ²ûD0»Ï½{”À¸t/RßÓ©X ý²¾Ã)±8:‡/yßÁ„¶2,úÏL‰ÅÑ~ ¶}ZŒ©8|‰ú~ÉBu‰TE ȃ=.^ þò ¬ó<¹×õív«mm ¥+Ýt]W§½%Ã^I—lÒRùžCH&ËtS3õ‚6‚9èË¡YŠ7Ѧ½¡9¨=Õ$…&Áâ½ì÷E☊~=¯zk×óªfo ÒÞzF‰«ªbûýUÅöűÈ×-Ïw®?âNú¿Ç_vz•F}ç"´¨¼4Hz‹É¨Åñ¡’U2€;e×2 GgŸêm'ù6 r˜ ä^'¹B¯DEM a:SÇ*|&*_Êao©1m‹’€8è =¡­©jJÀo•pª§0Ai^²ìïtñ,Vé2Öy¶» Ò[®Rßo$ÅìØ:vØxÕçn¿Q*«A·"¸{Š@]ë[C(QiŽYfçM|=ý®“>•¸zìÌøj#¬p*Ô}ž‚8#À ÿ.Œ\¦QDUÂýRö-Ø?Z:öŹŽü¥•úK P>‹#[?}$®mú¦ú7y0ÊiжÊ{˜{‚×èåA1&ÆÄ2&¶ÁÚ±‹ˆLܦš¤mm*ú§Ì7úp 6!_y'¬m °*¾>(«$ëÀS Ú¤€¶©IŠeHó¯Õ¬‹À¢‚M޲5;ð!b¤ LÖ@ádÉ&öò FÝâî% ¢„ÀÏúñªÕE ^y²< ¼¼è!&‹:5B>þú0UóEѵD8rbý$QýÂ6– Ì`)~ÚXV”d½]¬’œÈU“v“A/»>Úªtr…¯KyºÁ’,ã¨@ñ; ìÃS€­hò+ÜN~GˆYcEF¯®` ±àJJè*I{Øe?ƒ4`:[L•bP,'Éi[âÕ¬pŒiƒ‡6qžb^þöcÓ”X)A\0ú3üüs3ùŒ•xò"€fГÝâ;²×,‡Që¢Ç»@AØ/˜\¸Eߟ뢇 TtüôkkÄ—†›ÃXc,»¸/I[ù/)Ú%(Ie({×Á¾e×ßUvjY±ÌBE3eöŸå öAê+o/ÿ,*3õ"ØÌÐò=( |æ/XÖpŸD†Uš¹²cÐÊDðK˜W ”5› ­÷{ØHIÇ,³B€-¸c8ém¼ °” l)9u]Ï[.©%$À'{{2ƒiMÉBÇå9Yâ›úB¸ÌIO­# Vk¡‡áº> Cß'f(4ªØ³g0W_ªBV;_; ‘æù?\ºõƒ¬êÁ÷TC$ q„2bu± C˜OX'iÇ ™Â>².•DœÊÿþûïR„/Ù©’Þæ/÷ág¬Ÿ_£ •!¾”98gú-µ.aˆj$*C—9uñ:Ƀº#ŠöDA¬†`Ç”zL:ß%Böháç&rFDh~“MGCÈÒœ›Dèn4„ì›Äg6>7ê§ç£!äܨŸvGDè6ý´iŒÑô}9j¡›ïµô¶-Sg&Ï÷)+"0Û(ð]o,6 ÕV¶©¶•{ÞLókÈÆ›†ñ-m¥È4ãÁH[1í›0mr2a¹Ãº(#ù:E›ÕZåÇ+1Š’?àMr8nzÜóôk+fO#2QG±ëb\ ¬o0P)K 5ñ½åèßÇ5æx¯©â­gLSI"Vך@a0‰TÙyoU>Aô‚ýtÊQbŽ›ii¶ìo·…„ê Œ’5`J'êžÂ0€ËCÏMúÕDÀ og„BÒçdZü½|äØ9³´ocΧȶ‚´éÚ¢-vè+¼nnƒåëp1Yu½B)«ag÷„#È…•öJÙq)2ɼ„L­'óÓ׫kM¨uÖׄþVÄVÙ¾Ãþ8¬Ó<ަðÄØÅTžÁréy®{]Ï ‘³YöwŽšÙ²w,;žï°ícõ¡Ûƒ“5> z/ïYÞ€ž£éXmpZfŒ†FÑE\Õå@ªîç‹ ø…þpèNäV®:§!äØÈ½c È6é9ìU\$‡n­ýM"Þ6Ž«l­Ê‚Py¢Ù{íMä|‡Ù÷AXC ýg^ѱޗUŠ6q-¶KMOR±ÖÆÔÖ’°evœ@ç(™8 .1Êá‘XŒ¹k`Ç^ýö ê zÖúÞ“É–!“É}ªñŒ¯(™Ü"ìÍ&“ gÅm´~Žè¯ºëÔjŒœ¼^­¹ˆ«³|½²SË ”¹™Úô&j.sû×)Qê,˜Ÿ ¡Ùµ–¹ÝjIs™Û!:t5Õ#ëÐxenw7ZÆ5^¡ÛìF‹”Æ+tsoŸñÊÜLãFõˆun¦y-®ú½ºiŽë6bÞ~&¶d&¢G&‚Áô•d"Z„ýZ2µÓœJ&¢ý¢!pf/6؇íe%kåŒb.®±s—½šrÁ¡²ÇAÜ:;âö±ˆ;]ˆ7vVæ{(âÎ8ˆÛgGÜ:ñZ–µ3ß\ÏЊøtij#n¾WÄïÆA|*ï‹ølÄï$â}ŸƒøL"ÞqwÄçñ7Ï6‹ÆºàOY?1 >“‘‹‚Å(ðý°ïùaÉ®¼ïBÞw!ï»÷]\ÏA ¼ïBÞwqþc@yßÅ×ZŽ ï»÷]\×1 ü˜Ÿ!õ@C(ûäqŸ<î»îã¾÷”[‰™X‰™X‰…ëEH&dbA&dbA&Þ}b¡![p㉧éN™ØO 8µš¼N ´{#‰q§¯&ä)!xÅæYÙÿð‹ñd‘z¤Ê[Ø’»É5 ±ÒO*ìŠe °'ØðžW÷¤þA3¦ôfIòù•lfŽ]|æÕ4´é´¼Ó²¸oW54Ëæ×Jǃ‰Ý¹i]˜‰} p$·ãéR@\‚‰} 0OsóÒ@Æ„ÞmOíÍ|2›[‘Éëol–­¹¦¹/•­MÙ!È •JmwnÏ{ñ`ÚÙSk=™ïX.W}ïÝwü#yÁy­ž§EÙŒùo†Ò@ÓuCWKñ^µ¹Ïy¯ÄÔ¦®Ø^`bÍ4—†ø ^4Qì^QJÞ˘‚@,ÞkÒÀVÌë`Þ(½Ù)zˆ½eævË^ÏÙ?úIí2þ¢\ªf%$bcílaw·Ð(õÉ©åÁ÷c¾j?mÄWÅBý ÃÖûª’;ðv7c7’,Êß ¡S/Øå[£FBäx“Ð=Ýõ¸J/wÃaã= äï!žEÅO{[*'Ç€¨7ÖíÒ‰i¶;«8±·¥¾Üå„oIÝz4ÆÌþäØ$uXÌ]¿€îZôÄqÜXû³ÇëÅb¸^ûƒÄscqiKú­nHEfMy+C(Þ)Ú’uiîܽ†cÐ/!«kzÙ>‰AûÃnL=tuÝ=HºawqŸQÀ§š®Ö.¦f]Õ¿œá2¹kJÁËš Y³!k6®!Y³!k6d͆¬Ù¸,B²fCÖlô¯Ù莧-OËxZÆÓ2ž–ñôU"$ãiOËxZÆÓ2ž¾–xZo-^áÈrúŠ3ÖJOŠBðJšÈ7î>³C$‰³ÂPs¿ðޱ4Å!Y+©‡qHQØAÁ_GŠ…(•¾/œ—À©Lèj‰A[=Z)-ÿöûIµOq–÷ZžÒ¢ Îô2ÅM $´2¡<Ç«(Ò“µ(M ‰¥O®fß¹µÊ'ÍvïŠÆ^!Ãë§èß¼d{µ!L Î)_ ôÂm0á–²Ç(ïÅ7ÔËÏØ¥R“qÅßIñw3ë; ÁÓOÓÒ,«¦eíÊX­Á°–ÓœYëRšjÝiSVmØ-+1:­º²9Î êéÕµX¡0kt­eÃw|ú.Ì?°?¿[å l‚C¡§ß‘¯ñ—Ä !jñ IÑ;͉€Š`jŽ=e1Áy'6 ͙ۙy®Mmól3›˜hz1Ä÷g?3ìµéìõ¦§ˆ{xG¸kº_¢+ª1›£³ÕT|‡HºwÛF@¬©fº5oF*É+ÞLs< mnÏÚ\ç ­‡Ý{NNo÷äoм{³á±R›¶6o—ÚÑ\Ói“º¬ ?½Ü¬ r”ð¯I–]CAsœy+ ¦«™ÔÆ‹½†Ç£Å^‚ly®Ígµ_öìƒìjÓÙ]o˜‡Çaö…â°õÒbËÐŒ¿û€¬"}Suç–ºy]¹Á.Ö½›×^ßg¢çÅŽ¸ï™Vgì\=× þêî|Ï4kVÿµ“…ÍÖµL3§®¨dv1¬‰sÎÅY–Œ‹í(o ÷q ®22 ¶̅\\™9r Öëkp¹vŠ»Ãƒ½3‚¢ÙGÞ&*“}ú?PK¿¾ÈÈí x±PKMk®>Vx£kkmeta.xml 2011-05-10T20:00:03PT05H47M17S1002011-05-14T14:26:27OpenOffice.org/3.2$Linux OpenOffice.org_project/320m19$Build-9505PKMk®>Thumbnails/thumbnail.pngíÕû;Ó ð¯JTn%%—veÕ¢$m£‹\™:Ê\NéädcmÙ)%;.%žp¹n.5C$r™a®1‹1 »°l§ó'œ_Ïsžçó¼ÏûãûÛ›xᨾSo'êÎNöî ô½ͪ[¾çM²™¨Üu¶?çEYâóÍìOÃ@%¹T¼‰ëˆB¨ôÅÌu‘*:r(8¹S²6OSwÛÔ0ç@?Ƽs€G¦2Ö4Õ’}Ó-Þí±–Š§J–¡ü#ÐÞºÂ3¨!’Ô1,½Ñ]YZ¦ñè9½ÄÖ¶ù¸j³Ò,ºÇìþlûÚU+sîø_¨¤+YI~nGĆ˜@jêž]]ŠÈ*&?g„Œ>Ö{ Ø_2õ ÙIΦ‡ó‡+i"ŽGQûŠíÏGÒ5Ô^Ã<¬–ï(Lj°7B¹÷â}Ÿd3§‚MU…áV¯1Á×—Ÿ‚á_7Ô‘I³Ð…ÞÁÀNf~¬BàM\ÊV W”MÏš .Ü"ríª+Àßçõµ ŸÌÑìÄ0½!&ÒT¹rŠJË*j—H0ñ–0Þ`JÀWˆ7ÙwKÙCD-ER”Wðû–vw7ÌÑÍúÓ8Er=H1CG%3N¶²Çh²f@]ƒ¿Íäñ7>ÑŠ—yÔ¯8Æ>ÔÜœ×Þ¸c^ÄÒãs‡Â#eé ˜ï›eùÙíf[°Ý7-G ð4d™!Žn›Om)Vòj2§»¹Ê[‹9îI}[¤Ï2¸¬êì|키ηZ¬É¸×ð@¿ŸrlÂ+‘lf×`nE9²ªùø¾ _k½woá¢øè7›ÖÍ/®d•5Ù¶„«KÖÿ6¤Ú\5#€Bhï5W‡W„·È?R7ì¾þŽ“áË'èìç>åãÑ wæú¥EIÌÈ™•.Ê%ÕØaÍjú*Ž0ÌÓ> ®©k@‘P¼¦XËLø¢•øDô­îT^¯U¼Œ ÿr0pñ•ö 0…¦3tÈÁ»âÂúÎ1—"V+zaTN›÷ß5¹s­Óö¶Æ‚(˨¢9¡!/–Ÿ_ûW2[sõ/ÒÒOÕ> ø3P=”/¼]R˃‚Ðô—næI€Á®³ÕƒvÐÖŽ„¬ˆIC¥Æ ;˜ŸÈÛyt΂ éF[ÂÖ‘9©£E UÆJUËAz $™ú"Ì(µ~?Îç~ËÈïßì—¬!£}ù«±•y^Gs÷Y†¡+k¨mµTri4É×£¾‰…>€¶…ä;p=Ñ/¦¾òã­E‹ wCÄu# Ýg¬ÝÓ`,qnFè'(³Ÿ“3«C Fò85ú›Ü']¼Ä*ÅkÙîzä-æœNu„7£»°"ÏMת®ßHÛ—z]ÂÓÙœ¨%ùÉS¿¤›ŽA¤ÛN˜ñÚú £cZ jáË'<»ÊÜ3%Ò†P˜¿Ÿ1òµåð9ë0UÖäù0 C‘µ×(%³‘]quh˜Z@Ûé¦NǺ [ù’`99ù#L7í6iÝ~F‡?biDÂGVܾÇd! EW)Ïÿ”-Çùpj7öÒîðY “µú§KCDd”êã?ø.Þ2æ˜J@§Q)/. Oúåp`[Íml½˜Y•ð¹ì ~ßøb×½»›Õ3OBd³ }VÒQcÀéÿU2ÌY,'Š þ¹eç‹ûòó¿ÞÿPKÆ÷dè¶PKMk®> settings.xmlÝZÝsâ6ï_‘ñô¡} |w LàHraŽKh ×^ß„½5²Ö#É!Ü__ÉÊ82îtš‡KΖ~¿Õîj¿àâãsÀNž@HмãÔOkÎ p=ÊýŽó0½®œ;»?]à|N]h{èFpU‘ ”^"Oôv.Ûë×'¼DRÙæ$ÙVnCà›míÝÕí˜lýä™QþØqJ…íju¹\ž.ߢð«õV«Ußn–†¤†"*8áîž]ZùœúyQÖ«w÷#âVh³a}°XðF­Ö¬®ÿ¿Y-Êòr™µƒPËr’<62vMÙ~¢°Üz“¶ïÇ=_©4 è S ÍKµ õKÊ•Ó=«Ÿ]TaÞ=‚¹Jî4›õÂà¿SO-ÒÐç­w­Âð7@ýEªôúYýöàgˆ wºsÂ$Á¿È÷u| ô¡œpNѰdÁ+D¾áS.Ë#1ÿŽ(‡LŠ#¨énö¸êZ臥³ŒQ_¢r3f‘¼!Üc {lIV%º¯q]ýÇ¥ þD_5–m{¸b4 œ(#[ÅjéªgèÒâRý}­f{šC®2L”ØcDV:ííã š}-„X9ÝjîŸóŽ,c"ù¯H3B÷¼×E©¾u‹=¥ãÿ¬ ÞâŠXÜÙï#¢Ì„c‚õgˆ÷Så…¾;–gL‡4ð x:pÃúJÆÎð½rÂWŸúI¨,þ#mÆ£îãžÕ•G3Ó|‘ „ûpëF¤„ãLõ@ŽAÜã2ÝÊM˯Õb,Uú Sã ÷£ŒÉÊÏ¿DZ€_‚ªLø©Ä™«.bÑ¥¤``æªf*8Õ”G-WãâÝúd®é%‘ kNÏ’æ$bJÓNTz-Qo¼Ï­ÄìÞ5Šõ<à[óŸèt5¥ô‰®€ÛÚ>2ü¨Ÿú–Õ ”ŒðÔÍj|ÊÍxËáŠ{/î/ç^ÏQÄ£ÌÚÅ};Í'¸¼AA¿#W„?ÞåÚßÝ…mBþ ‚÷¤ö«qÄ]•ÕÖ e<ê#>2(g ½KÐ'îc=ð.Ç‹óôÂ$:ß–3ŒX£ ²4U–ô×T½T)?@zK|L‚8Õ˜C”òyCÌ‘6/8âî"Å£õ ŒvnËú#f˜Rå:Ò”–"¿I“:[ZÇs[Z$Þ=9Û¯>Ž5×ÕÝç „.Æ’Ú¬[Žçƺ¦ó “(ÊÊB±Å‹£j_=Eæt1,ˆ¡.cÌ÷t2"«Ãè´±:ÃeEwÈ"sÌB”)·/5—`L@Eûeû[?™'øºþzÅ0”eÜ©íh8=ø'íÚ;ð‡ÐÓWçô` AÈ^Ⱦokóã6½zð]“jÖ·‘ºPKd…<&Ï$PKMk®>META-INF/manifest.xmlµ•KjÃ0@÷9…ÑÞR›U1q-ôéyìôC3 Éí+’¸m(M±V–Ìè½ñH­6GkªDÔÞµì™?± œòvCË>¶ïõ Û¬++ ª¼ÎáuÚ²]ã%jlœ´€ ©ÆpWÉ‚£æk|3šÖ‹êîµ:ÆSu“A§eM§-“!­$å<ÅÁuüìâS0?Ï1ìF™|ÝròºOÆÔAÒ¾e‚‰‡r¹Oyó®×CŠg?.æLîd,ƒ—J<õQ¨ãX\äâ®"‚ÞxIP|H!ŸTýO^¹&ïMLüX™¢¹ƒk+@ñªÉÊ€E²¿w+Lnü9yÒ\MËáA9Á‘ÄØ þ×+~ç" àìX $gëkÛ}²;'µAA—!n˜>oa(_Ç×Ò®ÄÛxý PK’ð§3&¬¨//mimetypePKMk®>UConfigurations2/statusbar/PKMk®>'Configurations2/accelerator/current.xmlPKMk®>äConfigurations2/floater/PKMk®>Configurations2/popupmenu/PKMk®>RConfigurations2/progressbar/PKMk®>ŒConfigurations2/toolpanel/PKMk®>ÄConfigurations2/menubar/PKMk®>úConfigurations2/toolbar/PKMk®>0Configurations2/images/Bitmaps/PKMk®>¦Î,Эº² mcontent.xmlPKMk®>¿¾ÈÈí x± Sstyles.xmlPKMk®>Vx£kkx%meta.xmlPKMk®>Æ÷dè¶ *Thumbnails/thumbnail.pngPKMk®>d…<&Ï$ 70settings.xmlPKMk®>’ð§ * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #if defined(HAVE_NCURSES_H) #include #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 (imsgbuf_read(&peer->ibuf) != 1) { 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) { imsg_free(&imsg); break; } peer->dispatchcb(&imsg, peer->arg); imsg_free(&imsg); } } if (events & EV_WRITE) { if (imsgbuf_write(&peer->ibuf) == -1) { peer->dispatchcb(NULL, peer->arg); return; } } if ((peer->flags & PEER_BAD) && imsgbuf_queuelen(&peer->ibuf) == 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 (imsgbuf_queuelen(&peer->ibuf) > 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", event_get_version(), event_get_method()); #ifdef HAVE_UTF8PROC log_debug("using utf8proc %s", utf8proc_version()); #endif #ifdef NCURSES_VERSION log_debug("using ncurses %s %06u", NCURSES_VERSION, NCURSES_VERSION_PATCH); #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) imsgbuf_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; if (imsgbuf_init(&peer->ibuf, fd) == -1) fatal("imsgbuf_init"); imsgbuf_allow_fdpass(&peer->ibuf); 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); imsgbuf_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) { imsgbuf_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); } tmux-tmux-f222026/regress/000077500000000000000000000000001511153563100154225ustar00rootroot00000000000000tmux-tmux-f222026/regress/Makefile000066400000000000000000000001601511153563100170570ustar00rootroot00000000000000TESTS!= echo *.sh .PHONY: all $(TESTS) .NOTPARALLEL: all $(TESTS) all: $(TESTS) $(TESTS): sh $*.sh sleep 1 tmux-tmux-f222026/regress/UTF-8-test.txt000066400000000000000000000543751511153563100177610ustar00rootroot00000000000000UTF-8 decoder capability and stress test ---------------------------------------- Markus Kuhn - 2015-08-28 - CC BY 4.0 This test file can help you examine, how your UTF-8 decoder handles various types of correct, malformed, or otherwise interesting UTF-8 sequences. This file is not meant to be a conformance test. It does not prescribe any particular outcome. Therefore, there is no way to "pass" or "fail" this test file, even though the text does suggest a preferable decoder behaviour at some places. Its aim is, instead, to help you think about, and test, the behaviour of your UTF-8 decoder on a systematic collection of unusual inputs. Experience so far suggests that most first-time authors of UTF-8 decoders find at least one serious problem in their decoder using this file. The test lines below cover boundary conditions, malformed UTF-8 sequences, as well as correctly encoded UTF-8 sequences of Unicode code points that should never occur in a correct UTF-8 file. According to ISO 10646-1:2000, sections D.7 and 2.3c, a device receiving UTF-8 shall interpret a "malformed sequence in the same way that it interprets a character that is outside the adopted subset" and "characters that are not within the adopted subset shall be indicated to the user" by a receiving device. One commonly used approach in UTF-8 decoders is to replace any malformed UTF-8 sequence by a replacement character (U+FFFD), which looks a bit like an inverted question mark, or a similar symbol. It might be a good idea to visually distinguish a malformed UTF-8 sequence from a correctly encoded Unicode character that is just not available in the current font but otherwise fully legal, even though ISO 10646-1 doesn't mandate this. In any case, just ignoring malformed sequences or unavailable characters does not conform to ISO 10646, will make debugging more difficult, and can lead to user confusion. Please check, whether a malformed UTF-8 sequence is (1) represented at all, (2) represented by exactly one single replacement character (or equivalent signal), and (3) the following quotation mark after an illegal UTF-8 sequence is correctly displayed, i.e. proper resynchronization takes place immediately after any malformed sequence. This file says "THE END" in the last line, so if you don't see that, your decoder crashed somehow before, which should always be cause for concern. All lines in this file are exactly 79 characters long (plus the line feed). In addition, all lines end with "|", except for the two test lines 2.1.1 and 2.2.1, which contain non-printable ASCII controls U+0000 and U+007F. If you display this file with a fixed-width font, these "|" characters should all line up in column 79 (right margin). This allows you to test quickly, whether your UTF-8 decoder finds the correct number of characters in every line, that is whether each malformed sequences is replaced by a single replacement character. Note that, as an alternative to the notion of malformed sequence used here, it is also a perfectly acceptable (and in some situations even preferable) solution to represent each individual byte of a malformed sequence with a replacement character. If you follow this strategy in your decoder, then please ignore the "|" column. Here come the tests: | | 1 Some correct UTF-8 text | | You should see the Greek word 'kosme': "κόσμε" | | 2 Boundary condition test cases | | 2.1 First possible sequence of a certain length | | 2.1.1 1 byte (U-00000000): "" 2.1.2 2 bytes (U-00000080): "€" | 2.1.3 3 bytes (U-00000800): "à €" | 2.1.4 4 bytes (U-00010000): "ð€€" | 2.1.5 5 bytes (U-00200000): "øˆ€€€" | 2.1.6 6 bytes (U-04000000): "ü„€€€€" | | 2.2 Last possible sequence of a certain length | | 2.2.1 1 byte (U-0000007F): "" 2.2.2 2 bytes (U-000007FF): "ß¿" | 2.2.3 3 bytes (U-0000FFFF): "ï¿¿" | 2.2.4 4 bytes (U-001FFFFF): "÷¿¿¿" | 2.2.5 5 bytes (U-03FFFFFF): "û¿¿¿¿" | 2.2.6 6 bytes (U-7FFFFFFF): "ý¿¿¿¿¿" | | 2.3 Other boundary conditions | | 2.3.1 U-0000D7FF = ed 9f bf = "퟿" | 2.3.2 U-0000E000 = ee 80 80 = "" | 2.3.3 U-0000FFFD = ef bf bd = "�" | 2.3.4 U-0010FFFF = f4 8f bf bf = "ô¿¿" | 2.3.5 U-00110000 = f4 90 80 80 = "ô€€" | | 3 Malformed sequences | | 3.1 Unexpected continuation bytes | | Each unexpected continuation byte should be separately signalled as a | malformed sequence of its own. | | 3.1.1 First continuation byte 0x80: "€" | 3.1.2 Last continuation byte 0xbf: "¿" | | 3.1.3 2 continuation bytes: "€¿" | 3.1.4 3 continuation bytes: "€¿€" | 3.1.5 4 continuation bytes: "€¿€¿" | 3.1.6 5 continuation bytes: "€¿€¿€" | 3.1.7 6 continuation bytes: "€¿€¿€¿" | 3.1.8 7 continuation bytes: "€¿€¿€¿€" | | 3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf): | | "€‚ƒ„…†‡ˆ‰Š‹ŒŽ | ‘’“”•–—˜™š›œžŸ |  ¡¢£¤¥¦§¨©ª«¬­®¯ | °±²³´µ¶·¸¹º»¼½¾¿" | | 3.2 Lonely start characters | | 3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf), | each followed by a space character: | | "À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï | Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß " | | 3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef), | each followed by a space character: | | "à á â ã ä å æ ç è é ê ë ì í î ï " | | 3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7), | each followed by a space character: | | "ð ñ ò ó ô õ ö ÷ " | | 3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb), | each followed by a space character: | | "ø ù ú û " | | 3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd), | each followed by a space character: | | "ü ý " | | 3.3 Sequences with last continuation byte missing | | All bytes of an incomplete sequence should be signalled as a single | malformed sequence, i.e., you should see only a single replacement | character in each of the next 10 tests. (Characters as in section 2) | | 3.3.1 2-byte sequence with last byte missing (U+0000): "À" | 3.3.2 3-byte sequence with last byte missing (U+0000): "à€" | 3.3.3 4-byte sequence with last byte missing (U+0000): "ð€€" | 3.3.4 5-byte sequence with last byte missing (U+0000): "ø€€€" | 3.3.5 6-byte sequence with last byte missing (U+0000): "ü€€€€" | 3.3.6 2-byte sequence with last byte missing (U-000007FF): "ß" | 3.3.7 3-byte sequence with last byte missing (U-0000FFFF): "ï¿" | 3.3.8 4-byte sequence with last byte missing (U-001FFFFF): "÷¿¿" | 3.3.9 5-byte sequence with last byte missing (U-03FFFFFF): "û¿¿¿" | 3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF): "ý¿¿¿¿" | | 3.4 Concatenation of incomplete sequences | | All the 10 sequences of 3.3 concatenated, you should see 10 malformed | sequences being signalled: | | "Àà€ð€€ø€€€ü€€€€ßï¿÷¿¿û¿¿¿ý¿¿¿¿" | | 3.5 Impossible bytes | | The following two bytes cannot appear in a correct UTF-8 string | | 3.5.1 fe = "þ" | 3.5.2 ff = "ÿ" | 3.5.3 fe fe ff ff = "þþÿÿ" | | 4 Overlong sequences | | The following sequences are not malformed according to the letter of | the Unicode 2.0 standard. However, they are longer then necessary and | a correct UTF-8 encoder is not allowed to produce them. A "safe UTF-8 | decoder" should reject them just like malformed sequences for two | reasons: (1) It helps to debug applications if overlong sequences are | not treated as valid representations of characters, because this helps | to spot problems more quickly. (2) Overlong sequences provide | alternative representations of characters, that could maliciously be | used to bypass filters that check only for ASCII characters. For | instance, a 2-byte encoded line feed (LF) would not be caught by a | line counter that counts only 0x0a bytes, but it would still be | processed as a line feed by an unsafe UTF-8 decoder later in the | pipeline. From a security point of view, ASCII compatibility of UTF-8 | sequences means also, that ASCII characters are *only* allowed to be | represented by ASCII bytes in the range 0x00-0x7f. To ensure this | aspect of ASCII compatibility, use only "safe UTF-8 decoders" that | reject overlong UTF-8 sequences for which a shorter encoding exists. | | 4.1 Examples of an overlong ASCII character | | With a safe UTF-8 decoder, all of the following five overlong | representations of the ASCII character slash ("/") should be rejected | like a malformed UTF-8 sequence, for instance by substituting it with | a replacement character. If you see a slash below, you do not have a | safe UTF-8 decoder! | | 4.1.1 U+002F = c0 af = "À¯" | 4.1.2 U+002F = e0 80 af = "à€¯" | 4.1.3 U+002F = f0 80 80 af = "ð€€¯" | 4.1.4 U+002F = f8 80 80 80 af = "ø€€€¯" | 4.1.5 U+002F = fc 80 80 80 80 af = "ü€€€€¯" | | 4.2 Maximum overlong sequences | | Below you see the highest Unicode value that is still resulting in an | overlong sequence if represented with the given number of bytes. This | is a boundary test for safe UTF-8 decoders. All five characters should | be rejected like malformed UTF-8 sequences. | | 4.2.1 U-0000007F = c1 bf = "Á¿" | 4.2.2 U-000007FF = e0 9f bf = "àŸ¿" | 4.2.3 U-0000FFFF = f0 8f bf bf = "ð¿¿" | 4.2.4 U-001FFFFF = f8 87 bf bf bf = "ø‡¿¿¿" | 4.2.5 U-03FFFFFF = fc 83 bf bf bf bf = "üƒ¿¿¿¿" | | 4.3 Overlong representation of the NUL character | | The following five sequences should also be rejected like malformed | UTF-8 sequences and should not be treated like the ASCII NUL | character. | | 4.3.1 U+0000 = c0 80 = "À€" | 4.3.2 U+0000 = e0 80 80 = "à€€" | 4.3.3 U+0000 = f0 80 80 80 = "ð€€€" | 4.3.4 U+0000 = f8 80 80 80 80 = "ø€€€€" | 4.3.5 U+0000 = fc 80 80 80 80 80 = "ü€€€€€" | | 5 Illegal code positions | | The following UTF-8 sequences should be rejected like malformed | sequences, because they never represent valid ISO 10646 characters and | a UTF-8 decoder that accepts them might introduce security problems | comparable to overlong UTF-8 sequences. | | 5.1 Single UTF-16 surrogates | | 5.1.1 U+D800 = ed a0 80 = "í €" | 5.1.2 U+DB7F = ed ad bf = "í­¿" | 5.1.3 U+DB80 = ed ae 80 = "í®€" | 5.1.4 U+DBFF = ed af bf = "í¯¿" | 5.1.5 U+DC00 = ed b0 80 = "í°€" | 5.1.6 U+DF80 = ed be 80 = "í¾€" | 5.1.7 U+DFFF = ed bf bf = "í¿¿" | | 5.2 Paired UTF-16 surrogates | | 5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80 = "𐀀" | 5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf = "𐏿" | 5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80 = "í­¿í°€" | 5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf = "í­¿í¿¿" | 5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80 = "󰀀" | 5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf = "󰏿" | 5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80 = "􏰀" | 5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf = "􏿿" | | 5.3 Noncharacter code positions | | The following "noncharacters" are "reserved for internal use" by | applications, and according to older versions of the Unicode Standard | "should never be interchanged". Unicode Corrigendum #9 dropped the | latter restriction. Nevertheless, their presence in incoming UTF-8 data | can remain a potential security risk, depending on what use is made of | these codes subsequently. Examples of such internal use: | | - Some file APIs with 16-bit characters may use the integer value -1 | = U+FFFF to signal an end-of-file (EOF) or error condition. | | - In some UTF-16 receivers, code point U+FFFE might trigger a | byte-swap operation (to convert between UTF-16LE and UTF-16BE). | | With such internal use of noncharacters, it may be desirable and safer | to block those code points in UTF-8 decoders, as they should never | occur legitimately in incoming UTF-8 data, and could trigger unsafe | behaviour in subsequent processing. | | Particularly problematic noncharacters in 16-bit applications: | | 5.3.1 U+FFFE = ef bf be = "￾" | 5.3.2 U+FFFF = ef bf bf = "ï¿¿" | | Other noncharacters: | | 5.3.3 U+FDD0 .. U+FDEF = "ï·ï·‘﷒﷓﷔﷕﷖﷗﷘﷙﷚﷛﷜ï·ï·žï·Ÿï· ï·¡ï·¢ï·£ï·¤ï·¥ï·¦ï·§ï·¨ï·©ï·ªï·«ï·¬ï·­ï·®ï·¯"| | 5.3.4 U+nFFFE U+nFFFF (for n = 1..10) | | "🿾🿿𯿾𯿿𿿾𿿿ñ¿¾ñ¿¿ñŸ¿¾ñŸ¿¿ñ¯¿¾ñ¯¿¿ñ¿¿¾ñ¿¿¿ò¿¾ò¿¿ | òŸ¿¾òŸ¿¿ò¯¿¾ò¯¿¿ò¿¿¾ò¿¿¿ó¿¾ó¿¿óŸ¿¾óŸ¿¿ó¯¿¾ó¯¿¿ó¿¿¾ó¿¿¿ô¿¾ô¿¿" | | THE END | tmux-tmux-f222026/regress/am-terminal.sh000066400000000000000000000013231511153563100201630ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMUX2="$TEST_TMUX -Ltest2" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX2 -f/dev/null new -d || exit 1 $TMUX2 set -as terminal-overrides ',*:am@' || exit 1 $TMUX2 set -g status-right 'RRR' || exit 1 $TMUX2 set -g status-left 'LLL' || exit 1 $TMUX2 set -g window-status-current-format 'WWW' || exit 1 $TMUX -f/dev/null new -x20 -y2 -d "$TMUX2 attach" || exit 1 sleep 1 $TMUX capturep -p|tail -1 >$TMP || exit 1 $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null cat </dev/null do_test() { $TMUX -f/dev/null new -d " printf '$1' $TMUX capturep -peS0 -E1 >$TMP" printf "$2\n" > $TMP2 sleep 1 cmp $TMP $TMP2 || exit 1 return 0 } do_test '\033]8;id=1;https://github.com\033\\test1\033]8;;\033\\\n' '\033]8;id=1;https://github.com\033\\test1\033]8;;\033\\\n' || exit 1 do_test '\033]8;;https://github.com/tmux/tmux\033\\test1\033]8;;\033\\\n' '\033]8;;https://github.com/tmux/tmux\033\\test1\033]8;;\033\\\n' || exit 1 $TMUX has 2>/dev/null && exit 1 exit 0 tmux-tmux-f222026/regress/capture-pane-sgr0.sh000066400000000000000000000011041511153563100212070ustar00rootroot00000000000000#!/bin/sh # 884 # capture-pane should send colours after SGR 0 PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d " printf '\033[31;42;1mabc\033[0;31mdef\n' printf '\033[m\033[100m bright bg \033[m' $TMUX capturep -peS0 -E1 >>$TMP" sleep 1 ( printf '\033[1m\033[31m\033[42mabc\033[0m\033[31mdef\033[39m\n' printf '\033[100m bright bg \033[49m\n' ) | cmp - $TMP || exit 1 $TMUX has 2>/dev/null && exit 1 exit 0 tmux-tmux-f222026/regress/combine-test.result000066400000000000000000000002361511153563100212540ustar00rootroot00000000000000 0 Λ̊1 ðŸ»2 ðŸ‘ðŸ»3 ðŸ‘🻠ðŸ‘ðŸ»4 🤷â€â™‚ï¸5 ♂ï¸7 🤷â€â™‚ï¸8 🤷â€â™‚ï¸9 🤷â€â™‚ï¸10 🇪11 🇸🇪12 🇸🇪13 tmux-tmux-f222026/regress/combine-test.sh000066400000000000000000000022721511153563100203520ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d " printf '\e[H\e[J' printf '\e[3;1H\316\233\e[3;1H\314\2120\n' printf '\e[4;1H\316\233\e[4;2H\314\2121\n' printf '\e[5;1HðŸ‘\e[5;1HðŸ»2\n' printf '\e[6;1HðŸ‘\e[6;3HðŸ»3\n' printf '\e[7;1HðŸ‘\e[7;10HðŸ‘\e[7;3HðŸ»\e[7;12HðŸ»4\n' printf '\e[8;1H\360\237\244\267\342\200\215\342\231\202\357\270\2175\n' printf '\e[9;1H\360\237\244\267\e[9;1H\342\200\215\342\231\202\357\270\2176\n' printf '\e[9;1H\360\237\244\267\e[9;1H\342\200\215\342\231\202\357\270\2177\n' printf '\e[10;1H\360\237\244\267\e[10;3H\342\200\215\342\231\202\357\270\2178\n' printf '\e[11;1H\360\237\244\267\e[11;3H\342\200\215\e[11;3H\342\231\202\357\270\2179\n' printf '\e[12;1H\360\237\244\267\e[12;3H\342\200\215\342\231\202\357\270\21710\n' printf '\e[13;1H\360\237\207\25211\n' printf '\e[14;1H\360\237\207\270\360\237\207\25212\n' printf '\e[15;1H\360\237\207\270 \010\010\360\237\207\25213\n' $TMUX capturep -pe >>$TMP" sleep 1 cmp $TMP combine-test.result || exit 1 $TMUX has 2>/dev/null && exit 1 exit 0 tmux-tmux-f222026/regress/command-order.sh000066400000000000000000000015701511153563100205100ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP new -sfoo -nfoo0; neww -nfoo1; neww -nfoo2 new -sbar -nbar0; neww -nbar1; neww -nbar2 EOF $TMUX -f$TMP start $TMP || exit 1 $TMUX kill-server 2>/dev/null cat <$TMP new -sfoo -nfoo0 neww -nfoo1 neww -nfoo2 new -sbar -nbar0 neww -nbar1 neww -nbar2 EOF $TMUX -f$TMP start $TMP || exit 1 $TMUX kill-server 2>/dev/null cat </dev/null for i in conf/*.conf; do $TMUX -f/dev/null start \; source -n $i || exit 1 done exit 0 tmux-tmux-f222026/regress/conf/000077500000000000000000000000001511153563100163475ustar00rootroot00000000000000tmux-tmux-f222026/regress/conf/01840240e807e837dbf76d85b4b938de.conf000066400000000000000000001552651511153563100234700ustar00rootroot00000000000000# Options {{{1 # Server {{{2 # Don't pile up more than 10 buffers (down from 50 by default).{{{ # # Rationale: Keeping a tidy stack, with relevant information, could help us # integrate tmux buffers in our workflow more often. # # However, maybe we could keep a big stack of buffers, and filter them by # pressing `f` in the window opened by `choose-buffer`. # Alternatively, we could also try to use fzf to fuzzy search through their # contents... #}}} set -s buffer-limit 10 # What does this option control?{{{ # # It sets the time in milliseconds for which tmux waits after an escape is input # to determine if it is part of a function or meta key sequences. # The default is 500 millisec onds. #}}} # Why do you reset it?{{{ # # The default value introduces lag when we use Vim and escape from insert to # normal mode. We want to reduce the timeout. #}}} # Why don't you set it to 0 ?{{{ # # > Some people set it to zero but I consider that risky if you're connecting # > over a wide-area network or there is anything else that might insert small # > delays between the delivery of chars in such a sequence. # # Source: https://github.com/tmux/tmux/issues/353#issuecomment-294570322 # # Basically, we should still let a few ms to be sure all the keys in a control # sequence will have enough time to reach tmux. #}}} set -s escape-time 10 # If the terminal supports focus events, they will be requested by the tmux # client and passed through to the tmux server, then to the programs it runs. # Necessary to be able to listen to `FocusGained` and `FocusLost`. # Also necessary for `pane-focus-[in|out]` hooks. set -s focus-events on # history of tmux commands (pfx :) set -s history-file "$HOME/.tmux/command_history" # What does this do?{{{ # # It makes tmux sometimes send an OSC 52 sequence – which sets the terminal # clipboard content – if there is an `Ms` entry in the terminfo description of # the outer terminal. #}}} # What are the possible values of this option?{{{ # # - `on` # - `external` # - `off` # # --- # # There are 3 ways to create a tmux buffer: # # 1. invoke the `set-buffer` or `load-buffer` tmux commands # 2. copy text in copy mode (`send -X copy-selection`, `copy-pipe`, ...) # 3. send an OSC 52 sequence from an application inside tmux (e.g. `$ printf ...`) # # `1.` always creates a tmux buffer; never sets the X clipboard. # `2.` always creates a tmux buffer; sets the X clipboard via OSC 52 iff `set-clipboard` is not `off`. # `3.` creates a tmux buffer *and* sets the X clipboard via OSC 52 iff `set-clipboard` is `on`. # # IOW, `external` makes tmux *automatically* set the X clipboard when you yank # sth in copy mode via OSC 52, while `on` does the same, but also allows an # application to *manually* set a tmux buffer/the X clipboard via a OSC 52. # # Source: https://unix.stackexchange.com/a/560464/289772 #}}} # What is the possible drawback of the value `on`?{{{ # # https://github.com/tmux/tmux/wiki/Clipboard#security-concerns #}}} # How to disable OSC 52 for some terminals, but not all?{{{ # # # or use 'external' # set -s set-clipboard on # set -as terminal-overrides 'yourTERMname:Ms@' # ^ # man terminfo /Similar Terminals/;/canceled #}}} set -s set-clipboard external # Why do you move your 'terminal-overrides' settings in another file?{{{ # # It makes it easier to source the settings only when the tmux server is started; # not when we manually re-source `tmux.conf`. #}}} # Why do you need them to be sourced only once?{{{ # # We *append* strings to the value of the 'terminal-overrides' option. # I don't want to append the same strings again and again every time I re-source # `tmux.conf`. #}}} # Why don't you simply reset the option before appending a value?{{{ # # That would make us lose the default value of the option: # # terminal-overrides[0] "xterm*:XT:Ms=\\E]52;%p1%s;%p2%s\\007:Cs=\\E]12;%p1%s\\007:Cr=\\E]112\\007:Ss=\\E[%p1%d q:Se=\\E[2 q" # terminal-overrides[1] screen*:XT # # --- # # Besides, one of the value we append to 'terminal-overrides' depends on the value of `$TERM`. # # if '[ "$TERM" != "st-256color" ]' 'set -as terminal-overrides ",*:Cr=\\E]112\\007"' # ^----------------------^ # # And the value of `$TERM` will be correct only the first time we source `tmux.conf`. #}}} # What's the value of `$TERM` here?{{{ # # The first time our `tmux.conf` is sourced, it matches the `$TERM` of the # outer terminal; the next times, it's the value of 'default-terminal' (i.e. # 'tmux-256color'). #}}} # What's the meaning of this `if` guard?{{{ # # The condition `[ "$TERM" != "tmux-256color" ]` is true only the first time our # `tmux.conf` is sourced. # So, this part of the guard means: “if this is the first time the file is sourcedâ€. # This is equivalent to `has('vim_starting')` in Vim. # # The condition `[ -n "$DISPLAY" ]` is true only in a GUI environment. # So, this part of the guard means: “don't source the file if we are in a consoleâ€. # Indeed, I doubt a linux console is able to understand any sequence we might # want to use. #}}} # Could we use `%if` instead of `if`?{{{ # # You could try this: # # %if "#{!=:$TERM,#{default-terminal}}" # source-file "$HOME/.config/tmux/terminal-overrides.conf" # %endif # # But it doesn't seem to work; the guard would not prevent the file from being re-sourced. # I think that's because, in this case, `$TERM` refers to the value in the # environment of the tmux server process; and for the latter, `TERM` always # matches the outer terminal. #}}} if '[ "$TERM" != "#{default-terminal}" -a -n "$DISPLAY" ]' { source "$HOME/.config/tmux/terminal-overrides.conf" } # Leave `default-terminal` at the end.{{{ # # In my limited testing, moving it above would not cause an issue, but better be # safe than sorry. # In particular, I want to be sure that the value of `$TERM` is not # 'tmux-256color' the first time our `tmux.conf` is sourced; otherwise # `terminal-overrides.conf` would never be sourced. #}}} # Why `-s` instead of `-g`? {{{ # # Since tmux 2.1, `default-terminal` is a server option, not a session option. # # > As a side effect this changes default-terminal to be a server rather than a # > session option. # # https://github.com/tmux/tmux/commit/7382ba82c5b366be84ca55c7842426bcf3d1f521 # Confirmed by the fact that `default-terminal` is described in the section of # the server options in the man page. # Also confirmed by the fact that it's listed in the output of: # # tmux show -s # # However, according to nicm: # # > You do not have to use -s or -w for set-option except for user options. # > tmux can work it out from the option name. # > For show-option you do need it. # # So, we could omit `-s`, but I prefer to be explicit. #}}} # Why not let tmux use the default value `screen` (for `$TERM`)?{{{ # # By default, most terminals set `$TERM` to `xterm` because the `xterm` entry is # present and set in the terminfo db of most machines. # tmux sets it to `screen`, again, because it's a popular entry (more than the # `tmux` one). # The `xterm`/`screen` value implies that the terminal will declare supporting # only 8 colors; confirmed by `$ tput colors`. # # Because of this, the theme of some programs might be off (including Vim and # the terminal itself). We want the terminal to declare it supports 256 colors, # which anyway is usually true. #}}} # Do we need `$TERM` to contain `tmux`?{{{ # # Yes. To support italics: # # The `screen-256color` entry in the terminfo db doesn't have a `sitm` field. # IOW, the db reports that screen is unable to support italics, which is true. # So, if we set `$TERM` to `screen-256color`, when an application will want to # make some text appear italicized, it will think it's not possible. # But it *is* possible, because we use tmux, not screen. And tmux *does* # support the italics style. # The solution is to set `$TERM` to `tmux-256color` so that when an application # queries the terminfo db, it finds the field `sitm` with the right value # `\E[3m`. # # See also: # https://github.com/tmux/tmux/wiki/FAQ#i-dont-see-italics-or-italics-and-reverse-are-the-wrong-way-round # https://github.com/tmux/tmux/issues/175#issuecomment-152719805 #}}} # `256color`?{{{ # # For a Vim color scheme to be correctly applied, no. # Because it seems that our current theme automatically sets the number of # colors to 256: # # :runtime colors/seoul256.vim # :echo &t_Co # # But, for the color schemes of other programs, maybe. #}}} set -s default-terminal tmux-256color # Session {{{2 # Don't ring the bell in the current window. set -g bell-action other # Why?{{{ # # If a new window is created without any command to execute, tmux reads the # session option `default-command` to find one. # By default, its value is an empty string which instructs tmux to create a # *login* shell using the value of the default-shell option. # The default value of the latter is `$SHELL` (atm: `/usr/local/bin/zsh`). # # When we create a new window, we want a *non*-login shell, because a login zsh # shell sources `~/.zprofile`, which we use to execute code specific to a # virtual *console* (set the background to white in the console). # This code is not suited to a virtual *terminal*. # # More generally, we don't want a *non*-login shell to source login files # (`.profile`, `.zprofile`, `.zlogin`). # # So, we give the value zsh to `default-command` to prevent tmux from starting a # login shell. #}}} set -g default-command "$SHELL" # Don't detach the client when the current session is killed. set -g detach-on-destroy off # display status line messages and other on-screen indicators for 1s # (or until a key is pressed) set -g display-time 1000 # display the indicators shown by the display-panes command for 5s set -g display-panes-time 5000 # increase scrollback buffer (2000 → 50000) # # `history-limit` has nothing to do with the history of executed tmux commands. # It controls the amount of lines you can scroll back when you enter copy mode. set -g history-limit 50000 # Index options # When we create a new window, tmux looks for an unused index, starting from 0.{{{ # # I prefer 1, because: # # - I usually count from 1, not 0 # - this lets us run `:movew -t :0` to move the current window in first position # # Note that you can't run `:movew -t :1` to move the window in first position, # because 1 is already taken by the first window. # `:movew` expects an index which is “free†(i.e. not used by any existing window). # # Also, note that when running `:movew -t :0`, tmux renumbers all windows # automatically from whatever value is assigned to the 'base-index' option. #}}} set -g base-index 1 # │ # â”” must be applied globally to all sessions # # same thing for the panes set -gw pane-base-index 1 # ││ # │└ window option # â”” must be applied globally to all windows # make tmux capture the mouse and allow mouse events to be bound as key bindings set -g mouse on # use `M-space` as a prefix set -g prefix M-space # renumber windows, when:{{{ # # - we destroy one of them # - we move the first window (index 1) at the end (index 99), by running `movew -t :99` # # to prevent holes in the sequence of indexes #}}} set -g renumber-windows on # time for repeating of a hotkey bound using the -r flag without having to type the prefix again; default: 500 set -g repeat-time 1000 # Some consoles don't like attempts to set the window title. # This might cause tmux to freeze the terminal when you attach to a session. # https://github.com/tmux/tmux/wiki/FAQ#tmux-freezes-my-terminal-when-i-attach-to-a-session-i-have-to-kill--9-the-shell-it-was-started-from-to-recover set -g set-titles off # update the status line every 5 seconds (instead of 15s by default) set -g status-interval 5 # emacs key bindings in tmux command prompt (prefix + :) are better than vi keys, # even for vim users. set -g status-keys emacs # color of status line set -g -F status-style "bg=#{?#{==:$DISPLAY,},blue,colour138}" # Center the position of the window list component of the status line set -g status-justify centre # cache the number of cpu cores in `~/.ncore`, which a shell command in 'status-right' is going to need if '[ ! -s "${HOME}/.ncore" ]' \ { run "lscpu | awk '/^CPU\\(s\\):\\s*[0-9]/ { print $2 }' >\"${HOME}/.ncore\"" } # set the contents of the status line # What's `#[...]`?{{{ # # It lets you embed some styles. # If you want to apply the same style all over the left part or the right part # of the status line, you can also use `status-left-style` or `status-right-style`: # # set -g status-left '#[fg=colour15,bold] #S' # ⇔ # set -g status-left ' #S' # set -g status-left-style '#[fg=colour15,bold]' # # However, I prefer embedding the styles inside the value of `status-left` and # `status-right`, because: # # - it's more concise # - it's more powerful: you can set the style of an arbitrary *portion* of the status line # # --- # # Note that you can use this syntax only in the value of an option which sets # the *contents* of sth, not its style. # So, this is *not* a valid syntax: # # # ✘ # set -g status-left-style '#[fg=colour15,bold]' # # Here, you must get rid of `#[...]`: # # # ✔ # set -g status-left-style 'fg=colour15,bold' #}}} # What's `#{?...}`?{{{ # # A conditional: # # #{?test,val1,val2} # # For example: # # {?client_prefix,#[bold],} # # This will be evaluated into the style `bold` if the prefix has been pressed, # or nothing otherwise. #}}} # Why do you use `nobold`?{{{ # # We set the style `bold` for some part of the status line. # But a style applies to *all* the remaining text in the status line. # We need `nobold` to reset the style. #}}} # How can I include the time of the day or the hour in the status line?{{{ # # Use `date(1)` and `%` items: # # %a = day of week # %d = day of month # %b = month # %R = hour # # See `man date`. #}}} set -g status-left ' #[fg=colour7]#{?client_prefix,#[fg=colour0],}#S#[fg=colour7]' # Which alternative could I use to get the cpu load?{{{ # # $ uptime|awk '{split(substr($0, index($0, "load")), a, ":"); print a[2]}' # # https://github.com/tmux/tmux/wiki/FAQ#what-is-the-best-way-to-display-the-load-average-why-no-l #}}} # Why don't you write the code in a script and invoke it with `#(my-script.sh)`?{{{ # # A script needs another shell to be interpreted. # The latter adds overhead, which would almost double the time the code needs to # be run. # # Check this: # # $ cat <<'EOF' >/tmp/sh.sh # awk 'BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }' # EOF # $ chmod +x /tmp/sh.sh # # $ time zsh -c 'repeat 100 /tmp/sh.sh' # ... 0,420 totalËœ # $ time zsh -c 'repeat 100 awk '\''BEGIN { getline load <"/proc/loadavg"; getline ncore <(ENVIRON["HOME"]"/.ncore"); printf("%d", 100 * load / ncore) }'\''' # ... 0,193 totalËœ #}}} # Why do you double the percent sign in `printf("%%d", ...)`?{{{ # # The code is going to be expanded inside the value of 'status-right'. # And the latter is always passed to `strftime(3)` for which the percent sign # has a special meaning. # As a result, if you don't double the percent sign – to make it literal – `%d` # will be replaced with the current day of the month (01, 02, ..., 31). # # From `man tmux /status-right`: # # > As with status-left, string will be passed to strftime(3) and character # > pairs are replaced. #}}} cpu='awk '\''BEGIN { \ getline load <"/proc/loadavg"; \ getline ncore <(ENVIRON["HOME"]"/.ncore"); \ printf("%%d", 100 * load / ncore) }'\''' mem='free | awk '\''/Mem/ { total = $2; used = $3 }; \ END { printf("%%d", 100 * used / total) }'\''' # TODO: Maybe we could get rid of `free(1)`, by inspecting `/proc/meminfo`. # However, I can find the second field ("total") of `free(1)` in this file # (first line: "MemTotal"), but not the third one ("used"). # # Update: From `man free`: # # used Used memory (calculated as total - free - buffers - cache) # # You would have to read 4 files to compute the `used` field. # Make some tests (with `time(1)`) to see whether the resulting `awk(1)` command # is slower than what we currently run. # TODO: Color the numbers in red if they exceed some threshold. # Try to capture the output of the 2 shell commands in tmux variables, so that # we can test them in a conditional. set -g status-right '#[fg=colour235]M #[fg=colour15,bold]' set -ga status-right "#($mem) " set -ga status-right '#[fg=colour235,nobold]C #[fg=colour15,bold]' set -ga status-right "#($cpu) " setenv -gu cpu setenv -gu mem # Why do you want `COLORTERM` to be automatically updated?{{{ # # It can be useful to detect a terminal which lies about its identity. # E.g., xfce4-terminal advertises itself as `xterm-256color`, but the value of # its `COLORTERM` variable is 'xfce4-terminal'. # # So, the more reliable `COLORTERM` is, the better we can detect that we're in # xfce4-terminal, and react appropriately. # This can be useful, for example, to prevent vim-term from sending `CSI 2 SPC q` # when we're leaving Vim from xfce4-terminal on Ubuntu 16.04. # The latter doesn't understand this sequence, and once sent to the terminal, # tmux will regularly reprint the sequence wherever our cursor is. #}}} # Why could I be tempted to run the same command for `LESSOPEN`, `LESSCLOSE`, `LS_COLORS`?{{{ # # setenv -gu LESSOPEN # setenv -gu LESSCLOSE # setenv -gu LS_COLORS # # These environment variables are set in `~/.zshenv`, but only on the condition # they've not been set yet. # The purpose of the condition is to make the shell quicker to start. # Indeed, setting these variables adds around 8ms to the shell's startup time. # However, if they are set in the tmux global environment, then they'll never be # reset when we start a new shell, because the condition will never be # satisfied. # # This means that we can't change the value of these variables by simply editing # `~/.zshenv`, which can be an issue. #}}} # Why don't you do it?{{{ # # Because it would add around 8ms to the startup time of every shell we ask tmux # to open. #}}} #  Isn't this an issue?{{{ # # No. # If you want to modify one of these variables, and if you want the change to be # applied immediately without restarting the tmux server, do it in the context # of the tmux global environment: # # $ tmux setenv -g LESSOPEN new_value #}}} if '[ "$TERM" != "#{default-terminal}" ]' { set -ga update-environment COLORTERM } # TODO: We have several similar `if` conditions. Maybe we should write only one, # and put inside all the commands we want to run. # This would make tmux start a little faster. # display a message when activity is detected in a window # Why?{{{ # # We haven't customized the status line to include an indicator when some # activity is detected in a window, because by default we don't monitor activity # ('monitor-activity' is off). # Indeed, generally, most windows will produce some output and have some activity. # Seeing a lot of indicators in the status line, all the time, is meaningless. # # But we do have a key binding to temporarily toggle 'monitor-activity' in the # current window; so, we need a way to be notified when some activity is later # detected in it. #}}} set -g visual-activity on # Window {{{2 # Use vi key bindings in copy mode. set -gw mode-keys vi # colors of *borders* of focused and non-focused panes set -gw pane-active-border-style 'fg=colour138,bg=#cacaca' set -gw pane-border-style 'fg=colour138,bg=#cacaca' # How to insert the index of a window?{{{ # # Use the alias `#I`. #}}} # How to insert its flags, like `Z` for a zoomed window?{{{ # # Use the alias `#F`. #}}} # Set what to display for the current window (then for the other ones), and how, # in the status line window list. # See: https://github.com/tmux/tmux/issues/74#issuecomment-129130023 set -gw window-status-current-format '#[fg=colour232,bg=colour253]#W#F#{?window_zoomed_flag, #[fg=blue]#P/#{window_panes},}' set -gw window-status-format '#[fg=colour232#,bg=colour248]#W#F#[bg=default]' # Pane {{{2 # colors of focused and non-focused panes # Because we use `-gw`, the colors will affect any pane.{{{ # # But, at runtime, you could use `-p` to set the color of a given pane when it's # (un)focused differently. #}}} set -gw window-active-style 'bg=#dbd6d1' set -gw window-style 'bg=#cacaca' # }}}1 # Key Bindings {{{1 # root {{{2 # `F1`, ..., `F10` are used in `htop(1)`. # `F11` and `F12` are used in WeeChat to scroll in the nicklist bar. bind -T root S-F8 run -b 'tmux_log' # Why do you rebind `command-prompt` to `pfx + ;`? It's already bound to `pfx + :`...{{{ # # We often press the prefix key by accident, then press `:` to open Vim's # command-line. As a result, we open tmux command-line; it's distracting. #}}} bind ';' command-prompt unbind : # Do *not* bind `command-prompt` to `M-:`; we press it by accident too frequently. # focus next/previous window # I'm frequently pressing these key bindings in Vim's insert mode. It's distracting!{{{ # # Idea1: Use `pfx w` to focus an arbitrary window, and supercharge `pfx h/l` to # focus the next/previous pane or window. # # bind -r h if -F '#{pane_at_left}' 'prev' 'selectp -L' # bind -r l if -F '#{pane_at_right}' 'next' 'selectp -R' # # Idea2: Use `M-h/l` twice when the current pane is running Vim. # # set -g @foo 0 # bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; next } { set -g @foo 1 } } { next } # bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' { if -F '#{@foo}' { set -g @foo 0 ; prev } { set -g @foo 1 } } { prev } # # Idea3: Make tmux send `M-h` or `M-l` when Vim is running in the current pane, # and let Vim decide what to do, based on whether we're in insert mode or normal # mode. # # noremap! m_hl('h') # noremap! m_hl('l') # nno m_hl('h') # nno m_hl('l') # xno m_hl('h') # xno m_hl('l') # # fu s:m_hl(seq) abort # if mode() =~# '^[ic]$' # return '' # endif # call system('tmux '..(a:seq is# 'l' ? 'next' : 'prev')) # return '' # endfu # bind -T root M-l if -F '#{m:*vim,#{pane_current_command}}' 'send M-l' 'next' # bind -T root M-h if -F '#{m:*vim,#{pane_current_command}}' 'send M-h' 'prev' # # Problem: When we're running Vim without config (`-Nu NONE`), we can't focus another tmux window. # This is because those custom mappings are not installed then. #}}} # Why do you inspect these window flags?{{{ # # To prevent the commands from wrapping around the edges. # They do that by default, and that bothers me at the moment. # # For example, I often want to focus the next window, and press `¹` by accident # instead of `²`. It still works, because we only have 2 windows, but that # prevents me from learning to press the correct keys. # # Once you're confident that those keys are well-chosen, and they've passed into # your muscle memory, you could just install: # # bind -T root ¹ prev # bind -T root ² next #}}} bind -T root ¹ if -F '#{window_start_flag}' {} { prev } bind -T root ² if -F '#{window_end_flag}' {} { next } # TODO: Sometimes, we press these keys by accident. Find better ones?{{{ # # In particular, when we press `[ox` (where `x` is some character) to toggle # some Vim setting, we need to press `AltGr` to produce `[`; but sometimes, we # don't release the key before pressing `o`. # As a result, we press `AltGr + o` which produce `²`; tmux intercepts the # keypress, and tries to visit the previous window, while in reality, we wanted # to toggle some Vim setting. #}}} # `M-s` to enter copy mode # Do *not* bind `M-s` to anything while in copy mode!{{{ # # This would make you lose an interesting feature of the `copy-mode` command # while you're already in copy mode, reading the output of some command such as # `list-keys`. # # The default behavior makes tmux show you the contents of the original window: # # # you're reading a file # :list-keys # :copy-mode # # the window shows again the file you were reading # # press `q`, and you get back the output of `:list-keys` # # I don't know where this is documented. # And I don't know why the `copy-mode` command is invoked when we press `M-s` # while in copy mode. # We only have one key binding using `M-s` as its lhs, and it's in the root # table, not in the copy-mode table. # # Note that this feature is not specific to our `M-s` key binding. # I can reproduce with no config (and `C-b [` instead of `M-s`). #}}} bind -T root M-s copy-mode bind -T root M-z resize-pane -Z bind -T root M-Z lastp \; resize-pane -Z # Do *not* exit copy mode when when we reach the bottom of the scrollback buffer.{{{ # # We remove the `-e` flag which is passed to `copy-mode` by default, so that if # we enter copy mode by scrolling the mouse wheel upward, and we press a key # which reaches the bottom of the scrollback buffer, we don't quit copy mode. # # If you leave `-e` in the default key binding, here's what could happen: # you scroll the wheel upward to enter copy mode; at one point, you keep # pressing `C-d` to scroll toward the bottom; once you reach the bottom, you'll # quit copy mode, and `C-d` will close the shell if the command-line is empty. #}}} # Where did you find the code?{{{ # # $ tmux -Lx -f/dev/null start \; lsk | grep 'root.*WheelUpPane' #}}} bind -T root WheelUpPane \ if -F -t= '#{mouse_any_flag}' \ { send -M } \ { if -Ft= '#{pane_in_mode}' 'send -M' 'copy-mode -t=' } # But *do* exit copy mode if we scroll downward with the mouse wheel and reach the bottom of the buffer. bind -T copy-mode-vi WheelDownPane \ selectp \; \ send -X -N 5 scroll-down \; \ if -F '#{scroll_position}' '' 'send -X cancel' # copy-mode-vi {{{2 bind -T copy-mode-vi C-Space send -X set-mark # actually, it should be named "exchange-point-and-mark"... bind -T copy-mode-vi C-x send -X jump-to-mark # jump Back to the Beginning of the previous shell command{{{ # # Look for the previous shell prompt, to get to the beginning of the last # command output. After pressing the key binding, you can visit all the other # prompts by pressing `n` or `N`. # # Inspiration: https://www.youtube.com/watch?v=uglorjY0Ntg #}}} bind -T copy-mode-vi ! send -X start-of-line \; send -X search-backward 'Ùª' # Make tmux use the prefix 'buf_' instead of 'buffer' when naming the buffer # storing the copied selection. bind -T copy-mode-vi Enter send -X copy-selection-and-cancel 'buf_' bind -T copy-mode-vi g switchc -T g-prefix bind -T g-prefix g send -X history-top # Open a visually selected text (filepath or url) by pressing `gf` or `gx`. # TODO: The key binding will break if the file name contains double quotes.{{{ # # Find a way to escape special characters. # # I tried `$ tmux display -p '#{q:#(tmux pasteb)}'`, but it doesn't work. # You probably need `q:`, but a modifier needs to be followed by the name of a # replacement variable and `#(tmux pasteb)` is not one. # # Update: # I don't think you should use `q:`. # Maybe you should try to find a shell utility which quotes special characters # in a given text. # Does such a tool exist? # If it does, maybe you could try: `#(magic_tool $(tmux pasteb))`. # # Update: # `#{}` can be used for a user option (`@foo`). # You could temporarily set a user option with the the filepath/url, and quote # it with `q:`. # # Make some tests on that: # # http://example.org/foo/bar"baz.html # http://example.org/foo/bar'baz.html # https://www.reddit.com/r/Fantasy/ # # bind -T copy-mode-vi x send -X copy-selection-and-cancel \; \ # run 'tmux set @copied_url "$(tmux showb)"' \; \ # run 'xdg-open "#{q:@copied_url}"' # # For some reason, we need 2 `run-shell`, otherwise, it seems that the key # binding doesn't update the url, when we try a new one. # # For some reason, when we try to open this: # # http://example.org/a'b.html # # tmux opens this instead: # # http://example.org/a/'b.html # ^ # ✘ # # For some reason, when we try to open this: # # http://example.org/a"b.html # # tmux doesn't escape the double quote. # # Text ended before matching quote was found for ".Ëœ # (The text was '/usr/bin/firefox "http://example.org/foo/bar"baz.html"')Ëœ # # $ tmux set @foo "a'b" \; display -p '#{q:@foo}' # a\'bËœ # # $ tmux set @foo "a'b" \; run 'echo #{q:@foo}' # a'bËœ # # Why doesn't `run-shell` expand the `#{q:}` format? # # Update: you need to quote the format: # # $ tmux set @foo "a'b" \; run 'echo "#{q:@foo}"' # a\'bËœ # # Update: # I don't think it's possible. # Try to open the urls via `urlscan(1)`. # The latter fails for the first urls. # If a specialized tool fails, I doubt we can do better. #}}} # FIXME: `gf` fails to open a file path starting with `~/`. bind -T g-prefix f send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" bind -T g-prefix x send -X pipe "xargs -I {} tmux run 'xdg-open \"{}\"'" bind -T copy-mode-vi v if -F '#{selection_present}' { send -X clear-selection } { send -X begin-selection } bind -T copy-mode-vi V if -F '#{selection_present}' { send -X clear-selection } { send -X select-line } bind -T copy-mode-vi C-v if -F '#{selection_present}' \ { if -F '#{rectangle_toggle}' \ { send -X rectangle-toggle ; send -X clear-selection } \ { send -X rectangle-toggle } \ } { send -X begin-selection ; send -X rectangle-toggle } # set -s copy-command 'xsel -i' # if there's a selection, make `y` yank it # without selection, make `yy` yank the current line and `yiw` the current word bind -T copy-mode-vi y if -F '#{selection_present}' \ { send -X copy-pipe-and-cancel 'xsel -i -b' 'buf_' } \ { switchc -T operator-pending-and-cancel } # y**y** bind -T operator-pending-and-cancel y send -X copy-line 'buf_' # y**iw** bind -T operator-pending-and-cancel i switchc -T text-object-and-cancel # Do *not* use `select-word`: https://github.com/tmux/tmux/issues/2126 bind -T text-object-and-cancel w \ { send -X cursor-right send -X previous-word send -X begin-selection send -X next-word-end send -X copy-selection-and-cancel 'buf_' } bind -T copy-mode-vi . run "zsh -c \"tmux source =(sed -n '/^# #{@dot_command}/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" # **"A**yiw **"A**yy v_**"A**y bind -T copy-mode-vi '"' switchc -T specify-register bind -T specify-register A switchc -T operate-on-register # Why the `if 'tmux showb'`?{{{ # # `append-selection` doesn't accept the optional prefix buffer name argument. # If there's no buffer, `append-selection` will create a buffer with the prefix # name `buffer`; we want the prefix `buf_`. #}}} # "A**y**iw "A**y**y v_"A**y** bind -T operate-on-register y if -F '#{selection_present}' \ { if 'tmux showb' { send -X append-selection } { send -X copy-selection 'buf_' }} \ { switchc -T operator-pending } # "Ay**i**w bind -T operator-pending i switchc -T text-object # "Ay**y** bind -T operator-pending y { run "zsh -c \"tmux source =(sed -n '/^# yy/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } # v**i**w bind -T copy-mode-vi i switchc -T text-object # Why pass a second command to the first `if`?{{{ # # To support `"Ayiw`. #}}} # vi**w** "Ayi**w** bind -T text-object w if -F '#{selection_present}' \ { send -X stop-selection send -X cursor-right send -X previous-word send -X begin-selection send -X next-word-end } \ { run "zsh -c \"tmux source =(sed -n '/^# yiw/,/^$/p' $HOME/.config/tmux/repeat.conf)\"" } bind -T copy-mode-vi Y send -X copy-end-of-line 'buf_' # Why don't you pass `-b` to run?{{{ # # There's no need to. # `pipe-and-cancel` doesn't block. # The shell command passed as an argument is forked. #}}} bind -T copy-mode-vi S send -X pipe-and-cancel \ "xargs -I {} tmux run 'xdg-open \"https://www.startpage.com/do/dsearch?query={}\"'" # Why? {{{ # # `search-backward-incremental` is better than `search-forward`, because it # highlights all the matches as you type (like in Vim when 'hlsearch' and # 'incsearch' are both set); you need to pass `-i` to `command-prompt` for it to work. # # Also, these key bindings make the prompt less noisy (`/` is shorter than `search down`). # # Inspired from the default emacs key bindings in copy mode. # }}} bind -T copy-mode-vi / command-prompt -ip '/' { send -X search-forward-incremental '%%' } bind -T copy-mode-vi ? command-prompt -ip '?' { send -X search-backward-incremental '%%' } bind -T copy-mode-vi % send -X next-matching-bracket bind -T copy-mode-vi _ send -X start-of-line # move current window position forward/backward # Why not using `h` and `l`, or `j` and `k`?{{{ # # `M-C-[jk]` could be more useful for something else (WeeChat?), and doesn't # match a horizontal motion. # `M-C-[hl]` conflicts with our window manager (move virtual desktop). # `M-C-[HL]` is hard to press. #}}} bind -r > if -F '#{window_end_flag}' { movew -t :0 } { swapw -t :+ ; selectw -n } bind -r < if -F '#{window_start_flag}' { movew -t :99 } { swapw -t :- ; selectw -p } # If you don't like `:99`, you could write this instead:{{{ # # bind -r < if -F '#{window_start_flag}' \ # { run 'tmux movew -t:$((#{W:#{?window_end_flag,#I,}}+1))' } \ # { swapw -t :- ; selectw -p } #}}} # prefix {{{2 # NOTE: `C-\` is free. # cycle through predefined layouts # Do *not* pass `-r` to `bind`!{{{ # # We use Space in Vim as a prefix key. # If you use `-r` here, when you're in Vim there's a risk that when you press # Space as a prefix key, it's consumed by tmux to run `nextl` instead. #}}} bind Space nextl # focus last pane, without breaking the zoomed state of the window bind M-Space lastp -Z # display short description for the next keypress (inspired from a default key binding) bind M-h command-prompt -k -p key { lsk -1N '%%' } # ├┘ ├─┘{{{ # │ â”” -N instead show keys and attached notes in the root and prefix key tables; # │ with -1 only the first matching key and note is shown # â”” -k is like -1 but the key press is translated to a key name #}}} # What is the side effect of `-1`?{{{ # # Not only does it limit the output of the command to the first matching key and # note, but it also redirects where it's displayed. # Without `-1`, it's displayed on the terminal in copy-mode. # With `-1`, it's displayed as a message on the tmux status line. #}}} # display short description for the next 2 keypresses bind M-l command-prompt -1p 'key1,key2' \ { run "tmux lsk | awk '/-T prefix\s+%1\s+/ { print \$NF }' | xargs -I {} tmux lsk -1N -P 'M-Space %1 ' -T {} '%2'" } # repeat last shell command in last active pane # Warning: This overrides a default key binding:{{{ # # bind-key -T prefix . command-prompt -T target "move-window -t '%%'" #}}} bind . if -F -t '{last}' '#{m:*sh,#{pane_current_command}}' { send -t '{last}' Up Enter } # copy clipboard selection into tmux buffer # Why `run`?{{{ # # To make the shell evaluate the command substitution. #}}} # `--`?{{{ # # The evaluation of the substitution command could start with a hyphen. # And if that happens, tmux could parse the text as an option passed to # `set-buffer` (i.e. `-a`, `-b`, or `-n`). #}}} # `-o`?{{{ # # To make `xsel(1x)` output something. #}}} bind b switchc -T buffer bind -T buffer -N 'copy clipboard into tmux buffer' > run 'tmux setb -- "$(xsel -ob)"' \; display 'clipboard copied into tmux buffer' bind -T buffer -N 'copy tmux buffer into clipboard' < choose-buffer -F '#{buffer_sample}' \ { run 'tmux showb -b "%%" | xsel -ib' ; display 'tmux buffer copied into clipboard' } # We use `*` instead of `q` because it's more consistent with `#`. # They both show information. Besides, if I hit `pfx q` by accident (which # happens often), I won't be distracted by the panes numbers. bind * displayp bind ! show-messages # make these default key bindings repeatable # Do *not* pass `-r` to `bind`!{{{ # # Suppose you're in the 'study' session, Vim is running, and the Vim window is # horizontally split in 2 viewports. # You press `pfx + )` to switch to the 'fun' session, then you press `)` to go # back to the 'study' session. # Finally you press C-j to focus the Vim split below; it won't work because of this: # # bind -r C-j resizep -D 5 #}}} bind ( switchc -p bind ) switchc -n # Problem: tmux-fingers doesn't let us search outside the current screen. # Solution: Install a key binding which lets us search through the scrollback buffer, in a Vim buffer. # What does `-J` do for `capture-pane`?{{{ # # It joins wrapped lines. # # Suppose that we have a long line in a file, which doesn't fit on a single line # of the terminal, but on two. # If you run `$ cat file`, this too-long line will be displayed on two # consecutive lines of the terminal. # Without `-J`, tmux would copy – in one of its buffers – the two consecutive # lines on two different lines. # # But that's not what we want. # We want the buffer to join back these two lines, as they were originally in the file. #}}} # `-S -`? {{{ # # `-S` specifies the starting line number, from where to copy. # The special value `-` refers to the start of the history. # Without this, `capture-pane` would capture only from the first visible line in # the pane; we want the *whole* scrollback buffer. #}}} # Why don't you use `split-window` instead of `popup`?{{{ # # With `split-window`, you would also need to run `resize-pane -Z`. # But what if there's already a zoomed pane in the current window? # After quitting Vim, the latter would be unzoomed. # So, we use `popup` to preserve a possible zoomed pane in the current window. #}}} bind -T root M-c if -F \ '#{||:#{m:*vim,#{pane_current_command}},#{==:#{pane_current_command},man}}' \ '' \ { capture-pane -b scrollback -J -S - popup -E -xC -yC -w75% -h75% -d '#{pane_current_path}' \ "tmux showb -b scrollback | vim --not-a-term +'call tmux#formatCapture#main()' - ; \ tmux deleteb -b scrollback" } # split window vertically / horizontally # When the window is zoomed, we often forget that it's already split, and wrongly press `pfx _`.{{{ # # Make `pfx _` smarter; i.e. if the window is already split and zoomed, don't # split it again, instead focus the previous pane. #}}} bind _ if -F '#{window_zoomed_flag}' 'lastp' 'split-window -v -c "#{pane_current_path}"' bind | if -F '#{window_zoomed_flag}' 'lastp' 'split-window -h -c "#{pane_current_path}"' bind - if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fv -c "#{pane_current_path}"' bind '\' if -F '#{window_zoomed_flag}' 'lastp' 'split-window -fh -c "#{pane_current_path}"' # ├───────────────────────┘ # â”” keep current working directory bind -N 'bring arbitrary pane in current window' [ command-prompt -p 'join pane from:' { join-pane -s '%%' } bind -N 'send current pane in arbitrary window' ] command-prompt -p 'send pane to:' { join-pane -t '%%' } bind T breakp # Why a space before every shell command (` cmus`, ` weechat`, ...)?{{{ # # It's useful to prevent zsh from saving the command in the history when we # cancel the search with `C-c` or `C-d` (`setopt HIST_IGNORE_SPACE`). #}}} # What's the `-n` option passed to `neww`?{{{ # # It sets the name of the window. #}}} # What about the `-c` option?{{{ # # It sets the cwd of the shell. #}}} bind M-1 rename -t 0 fun \; \ renamew -t 1 music \; \ send ' cmus' 'Enter' '2' 'Enter' 'Enter' \; \ neww -n irc -c $HOME \; \ send ' weechat' 'Enter' \; \ new -s study \; \ send ' nv' 'Enter' # you need the display command to force tmux to clear the log (`set message-limit 0` is not enough) bind C set -F @message-limit-save '#{message-limit}' \; \ set message-limit 0 \; display 'message log cleared' \; \ set -F message-limit '#{@message-limit-save}' \; set -u @message-limit-save # Note that `clear-history` doesn't clear *all* the history.{{{ # # The last lines of the scrollback buffer which fits in one screen are preserved. # So, if you enter copy mode, you'll still be able to scroll back *some* lines, # but not more than a few dozens. #}}} # We can't use `C-l` for the lhs, because we already use it in another key binding:{{{ # # bind -r C-l resizep -R 5 #}}} bind C-c send C-l \; clear-history # │ │ # │ â”” clear tmux scrollback buffer # â”” clear terminal screen # Do *not* use `q` in the prefix table; pressed by accident too frequently. bind Q switchc -T Q-prefix bind -T Q-prefix q confirm 'kill-pane' bind -T Q-prefix Q confirm 'kill-window' # Why `TERM="#{client_termname}"` in the 'sourced files' entry?{{{ # # When `$ tmux -v -Ldm` is started, it inherits the TERM of the current tmux # server, which is set by 'default-terminal'. # As a result, the condition `[ if '[ "$TERM" != #{default-terminal} ]'` is # true, and several files which we expect to be sourced, won't be sourced. # # We want to see all files which would be sourced if we were to start tmux from # a regular shell; so we need to reset TERM. #}}} # Why don't you pass `-v` to `show-options`?{{{ # # If you do, it would considerably simplify our commands; we wouldn't need `sed(1)` at all. # Unfortunately, it would also fuck up the output if one of the alias is defined # on several lines. # # By avoiding `-v`, we make sure that each alias is output on a single line. #}}} bind i display-menu -x 0 -y 0 \ 'server information' i info \ 'key bindings' K lsk \ 'aliases' a { run 'tmux show -s command-alias | column -t -s= | sed "s/^command-alias\[[0-9]*]\s*//; s/^\"\|\"$//g"' } \ 'sourced files' f { run 'cd "$(mktemp -d /tmp/.tmux.XXXXXXXXXX)" \ ; TERM="#{client_termname}" tmux -v -Ldm start \ ; grep loading tmux-server*.log | grep -v grep | sed "s/.*loading \(.*\)/\1/"' } \ '' \ 'options' o { display-menu -y 0 \ 'server options' C-s { show -s } \ 'global session options' s { show -g } \ 'local session options' S { show } \ '' \ 'global window options' w { show -gw } \ 'local window options' W { show -w } \ 'local pane options' p { show -p } \ } \ '' \ 'global hooks' h { show-hooks -g } \ 'local hooks' H { show-hooks } \ '' \ 'global environment' e { showenv -g } \ 'local environment' E { showenv } \ '' \ 'outer terminfo description' t { run 'infocmp -1x "#{client_termname}" | sort' } \ 'inner terminfo description' T { run 'infocmp -1x "#{default-terminal}" | sort' } \ '' \ 'default settings' d { display-menu -y 0 \ 'key bindings' K { run 'tmux -Ldm -f/dev/null start \; lsk' } \ 'aliases' a { run 'tmux -Ldm -f/dev/null start \; show -sv command-alias | column -t -s= | sed "s/^command-alias\\[[0-9]]\\s*//; s/^\"\|\"$//g"' } \ '' \ 'server options' C-s { run 'tmux -Ldm -f/dev/null start \; show -s' } \ 'window options' w { run 'tmux -Ldm -f/dev/null start \; show -gw' } \ 'session options' s { run 'tmux -Ldm -f/dev/null start \; show -g' } \ } # By default, `detach-session` is bound to `d`. # I find that too easy to press, so we move it to `@`. # Why `@`? # I didn't find anything better, and it seems hard to press by accident... bind @ detach # resize pane bind -r C-h resizep -L 5 bind -r C-j resizep -D 5 bind -r C-k resizep -U 5 bind -r C-l resizep -R 5 # focus neighboring panes # Do *not* make them repeatable. It leads to a confusing user experience.{{{ # # Example: Press `pfx k` to focus the pane above which is running Vim, then press `j`. # Expected: Vim scrolls downward. # Actual Result: tmux focuses back the pane below. # # If you have many panes, and you need to focus one, try `display-panes` # (currently bound to `pfx *`), then press the index of the desired pane. #}}} bind h selectp -L bind l selectp -R bind j selectp -D bind k selectp -U # move pane to the far right/left/bottom/top bind H split-window -fhb \; swap-pane -t ! \; kill-pane -t ! bind L split-window -fh \; swap-pane -t ! \; kill-pane -t ! bind J split-window -fv \; swap-pane -t ! \; kill-pane -t ! bind K split-window -fvb \; swap-pane -t ! \; kill-pane -t ! # Toggle mouse. # Temporarily preventing tmux from handling the mouse can be useful in some # terminals to copy text in the clipboard. # Why `M` for the lhs?{{{ # # It provides a good mnemonic for “mouseâ€. # I don't use `C-m` nor `M-m` because, atm, they're used to display some default menus. # # However, note that `pfx M` is used by default to clear the marked pane (`select-pane -M`). # It's not a big deal to lose it, because we can get the same result by focusing # the marked pane and then pressing `pfx m` (`select-pane -m`). # The latter marks the pane if it's not already, or clears the mark otherwise. #}}} bind M set -g mouse \; display 'mouse: #{?#{mouse},ON,OFF}' # toggle 'monitor-activity' in current window bind C-a set -w monitor-activity \; display 'monitor-activity: #{?#{monitor-activity},ON,OFF}' # kill all panes except the current one (similar to `:only` or `C-w o` in Vim) bind o kill-pane -a # paste last tmux buffer # Do *not* choose a key too easy to type.{{{ # # It's dangerous. # In Vim, the contents of the buffer will be typed, which will have unexpected # results, unless you're in insert mode. #}}} bind C-p paste-buffer -p # choose and paste arbitrary tmux buffer # What's `-Z`?{{{ # # It makes tmux zoom the pane so that it takes the whole window. #}}} # `-F`?{{{ # # It specifies the format with which each buffer should be displayed. # In it, you can use these replacement variables: # # ┌────────────────┬─────────────────────────────┠# │ buffer_created │ creation date of the buffer │ # ├────────────────┼─────────────────────────────┤ # │ buffer_name │ name of the buffer │ # ├────────────────┼─────────────────────────────┤ # │ buffer_sample │ starting text of the buffer │ # ├────────────────┼─────────────────────────────┤ # │ buffer_size │ size of the buffer │ # └────────────────┴─────────────────────────────┘ # # Note that even with an empty format, tmux will still display the name of a # buffer followed by a colon. # So, `buffer_name` is not very useful (unless you want to print the name of a # buffer twice). #}}} # the `-p` argument passed to `paste-buffer`?{{{ # # It prevents the shell from automatically running a pasted text which contains # a newline. # # See `man tmux /^\s*paste-buffer` #}}} bind p choose-buffer -Z -F '#{buffer_sample}' "paste-buffer -p -b '%%'" # similar to `C-w r` and `C-w R` in Vim bind -r r rotate-window -D \; selectp -t :.+ bind -r R rotate-window -U \; selectp -t :.- # reload tmux config bind C-r source "$HOME/.config/tmux/tmux.conf" \; display 'Configuration reloaded' # You need to install the `urlscan(1)` utility for this key binding to work. # Why do you include `deleteb` inside the shell cmd run by `split-window`?{{{ # # If you move it outside: # # bind u capture-pane \; split-window -l 10 'urlscan =(tmux showb)' \; deleteb # # `urlscan(1)` can't find any link. # # This is because: # # 1. deleteb is run before `$ tmux showb` # 2. thus `$ tmux showb` outputs nothing # 3. urlscan finds no url # # You can get the same effect by running: # # $ tmux split-window 'urlscan =(echo "")' #}}} # We name the tmux buffer so that we can remove it reliably at the end.{{{ # # Indeed, you might copy some text while the urlscan pane is opened (not # necessarily in the latter; in any session, window, pane), creating a new # buffer at the top of the stack. # If you just run `$ tmux deleteb`, it would remove that buffer instead of the # buffer created by `capture-pane`. #}}} # Why `head -c -1`?{{{ # # If there is no url in the tmux buffer, we want tmux to automatically close the # pane. That's why we use `ifne(1)` later; it runs the second `urlscan(1)` on # the condition that the output of the previous one is empty. The problem is # that even when the first `urlscan(1)` fails to find any url, it still outputs # a single newline. We need to remove it, so that `ifne(1)` works as expected. #}}} bind u capture-pane -b urlscan \; \ split-window -l 10 " tmux showb -b urlscan | \ urlscan --no-browser | \ head -c -1 | \ ifne urlscan --compact \ --dedupe \ --nohelp \ --regex \"(http|ftp)s?://[^ '\\\">)}\\\\]]+\" \ ; tmux deleteb -b urlscan " # focus next pane bind -r C-w selectp -t :.+ # similar to `C-w x` in Vim bind x swap-pane -U bind X swap-pane -D # }}}1 # Hooks {{{1 # Don't use this hook: `set-hook -g after-split-window 'selectl even-vertical'`{{{ # # You wouldn't be able to split vertically anymore. # Splitting vertically would result in an horizontal split no matter what. # # The hook is given as an example in `man tmux`; its purpose is to resize # equally all the panes whenever you split a pane horizontally. #}}} set-hook -g pane-focus-out '' # Plugins {{{1 # Why the guard?{{{ # # To prevent the plugins from re-installing their key bindings every time we # resource `tmux.conf`. # Indeed, we only unbind the key bindings from the copy-mode table once. # # Besides, it's probably a bad idea to resource plugins. #}}} if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/plugins/run" } # Rebind {{{1 # Purpose:{{{ # # The tmux-yank plugin installs this key binding: # # bind-key -T copy-mode-vi Y send-keys -X copy-pipe-and-cancel "tmux paste-buffer" # # It copies the selection, quit copy mode, then paste the buffer. # # However, it doesn't support the bracketed paste mode. # So we redefine the key binding, and pass `-p` to `paste-buffer` to surround # the text with the sequences `Esc [ 200 ~` and `Esc [ 201 ~`. # This way, if we select a text containing a newline, then press `p`, it's not # automatically run by the shell. # # From `man tmux /^\s*paste-buffer`: # # > If -p is specified, paste bracket control codes are inserted around the # > buffer if the application has requested bracketed paste mode. # # Note that this requires that the shell supports the bracketed paste mode. # I.e. if you're using zsh, you need zsh version 5.1 or greater, and if you're # using bash, you need bash 4.4 or greater. # # --- # # The `-p` option was added to tmux in the commit `f4fdddc`. # According to the changelog, this was somewhere between tmux 1.6 and 1.7. # # --- # # Note that the original key binding used `copy-pipe-and-cancel` which – while # working – doesn't make sense; you can't pipe anything to `$ tmux paste-buffer`, # since it doesn't read its input. #}}} bind -T copy-mode-vi p send -X copy-selection-and-cancel \; paste-buffer -p \; deleteb # ^^ # Unbind {{{1 # How to find the default key bindings installed with no config?{{{ # # $ tmux -Lx -f/dev/null new # C-b ? # VG$ # Enter # $ vim # i # C-b ] # # Make sure to release `Ctrl` before pressing `]`. #}}} # How to unbind `#`, `~`, `'`, `"`?{{{ # # Quote the key (with single or double quotes). # # From `man tmux /^KEY BINDINGS`: # # > Note that to bind the ‘"’ or ‘'’ keys, quotation marks are necessary. #}}} # How to unbind `;`?{{{ # # Escape it. # # From `man tmux /^COMMANDS`: # # > A literal semicolon may be included by escaping it with a backslash (for # > example, when specifying a command sequence to bind-key). #}}} # TODO: # Remove all default key bindings which you're not interested in. # Some of them could be hit by accident. # Keep only the ones you really use. # Besides, it will give us a smaller table of key bindings, which will be easier # to read when we have an issue with one of our key bindings. # Have a look at `~/Desktop/tmux.md`. # prefix {{{2 # send-prefix unbind C-b # rotate-window unbind C-o # show-messages unbind '~' # split-window unbind '"' # choose-buffer -Z unbind = # detach-client unbind d # next-window unbind n # swap-pane -[UD] unbind '{' unbind '}' # select-pane -[UDLR] unbind Up unbind Down unbind Left unbind Right # tmux clear-history (tmux-logging) unbind M-c # rotate-window -D unbind M-o # resize-pane -U 5 unbind M-up # resize-pane -D 5 unbind M-down # resize-pane -L 5 unbind M-left # resize-pane -R 5 unbind M-right # resize-pane -[UDLR] unbind C-Up unbind C-Down unbind C-Left unbind C-Right # run ~/.config/tmux/plugins/tmux-logging/scripts/screen_capture.sh (tmux-logging) unbind M-p # run ~/.config/tmux/plugins/tmux-logging/scripts/save_complete_history.sh (tmux-logging) unbind M-P # resize-pane -Z unbind z # command-prompt -i -p / { send-keys -X search-forward-incremental "%%" } unbind / # copy-mode-vi {{{2 # By default, it's bound to `send-keys -X copy-pipe-and-cancel`.{{{ # # I don't like that, because I often select some text with the mouse by accident # (or when I'm bored); when that happens, obviously, tmux creates a buffer. # # This pollutes our list of buffers, and makes the interesting ones harder to # find. Besides, if when I want to copy some text, I will certainly not do it # with the mouse (not accurate enough). #}}} unbind -T copy-mode-vi MouseDragEnd1Pane # send-keys -X begin-selection unbind -T copy-mode-vi Space # send-keys -X copy-pipe-and-cancel unbind -T copy-mode-vi C-j # send -X copy-pipe-and-cancel 'xsel -i --clipboard; tmux paste-buffer' (tmux-yank) unbind -T copy-mode-vi M-y # (tmux-yank) unbind -T copy-mode-vi Y # copy-mode {{{2 # We don't need the key bindings from the copy-mode table; we use the copy-mode-*vi* table. # Why the guard?{{{ # # Once the table is empty, it's removed. # So, if you later try to unbind a key binding from it, an error will be raised: # # Table copy-mode doesn't exist # # Which can be repeated for every key binding you try to remove: # # Table copy-mode doesn't exist # Table copy-mode doesn't exist # ... # # Run `show-messages` to see them. # # This is annoying when you reload `tmux.conf`. #}}} if '[ "$TERM" != "#{default-terminal}" ]' { source "$HOME/.config/tmux/unbind-copy-mode.conf" } # }}}1 tmux-tmux-f222026/regress/conf/21867280ff7e99631046f9cc669b80d2.conf000066400000000000000000000002661511153563100233330ustar00rootroot00000000000000%if #{l:1} set -g status-style fg=cyan,bg='#001040' %elif #{l:1} set -g status-style fg=white,bg='#400040' %else set -g status-style fg=white,bg='#800000' %endif bind ^X last-window tmux-tmux-f222026/regress/conf/29813ff35544434e2e64dc879a8dd274.conf000066400000000000000000000033731511153563100234110ustar00rootroot00000000000000set -g prefix C-g # needed for e.g. mutt bind C-g send-prefix set -g set-titles on set -g status-position top set -g status-keys vi set -g mode-keys vi set -g base-index 1 set -g pane-base-index 1 set -g focus-events on set history-file ~/.tmux_SSH_history set focus-events on set -g history-limit 100000 set -s set-clipboard on set -g display-time 3000 set -g display-panes-time 3000 set -g pane-border-status top setw -g window-status-current-style bg=colour240,fg=colour250 setw -g window-status-separator "|" set -g status-bg colour235 set -g status-fg colour245 set -g window-status-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" set -g window-status-current-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" set -g pane-border-format " #P: #{s/ //:pane_title} " set -g renumber-windows on set -g status-right-length 0 ############################################################## # I prefer not to have a status for my tabbed term set -g status-right "" set -g status-right-length 0 set -g status-left-length 0 set -g status-left "" # some settings for "navigation" bind -n C-PageUp copy-mode -u unbind -n C-Left unbind -n C-Right bind -n C-Left select-window -t :- bind -n C-Right select-window -t :+ # I prefer a tiled layout and easy joining of current active pane via windows' # index bind F1 join-pane -s 1.\; select-layout tiled bind F2 join-pane -s 2.\; select-layout tiled bind F3 join-pane -s 3.\; select-layout tiled bind F4 join-pane -s 4.\; select-layout tiled bind F5 join-pane -s 5.\; select-layout tiled bind F6 join-pane -s 6.\; select-layout tiled bind F7 join-pane -s 7.\; select-layout tiled bind F8 join-pane -s 8.\; select-layout tiled bind F9 join-pane -s 9.\; select-layout tiled tmux-tmux-f222026/regress/conf/2eae5d47049c1f6d0bef3db4e171aed7.conf000066400000000000000000000040411511153563100240670ustar00rootroot00000000000000# 256 colors for vim set -g default-terminal "screen-256color" # Set default shell to zsh set-option -g default-shell /bin/zsh # Start window numbering at 1 set-option -g base-index 1 set-window-option -g pane-base-index 1 # Cycle panes with C-b C-b unbind ^B bind ^B select-pane -t :.+ # Reload config with a key bind-key r source-file ~/.tmux.conf \; display "Config reloaded!" # Mouse works as expected # set -g mode-mouse on # set -g mouse-select-pane on # set -g mouse-resize-pane on # set -g mouse-select-window on # Scrolling works as expected set -g terminal-overrides 'xterm*:smcup@:rmcup@' # Use the system clipboard # set-option -g default-command "reattach-to-user-namespace -l zsh" # Clear the pane and its history bind -n C-k send-keys C-l \; clear-history # smart pane switching with awareness of vim splits bind -n C-h run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-h) || tmux select-pane -L" bind -n C-j run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-j) || tmux select-pane -D" bind -n C-k run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-k) || tmux select-pane -U" bind -n C-l run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-l) || tmux select-pane -R" bind -n C-\ run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys 'C-\\') || tmux select-pane -l" # C-l is taken oer by vim style pane navigation bind C-l send-keys 'C-l' # Use vim keybindings in copy mode setw -g mode-keys vi # Setup 'v' to begin selection as in Vim # bind-key -t vi-copy v begin-selection # bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy" # Update default binding of `Enter` to also use copy-pipe # unbind -t vi-copy Enter # bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy" # Powerline run-shell "powerline-daemon -q" source "/Users/adamcooper/Library/Python/3.7/lib/python/site-packages/powerline/bindings/tmux/powerline.conf"tmux-tmux-f222026/regress/conf/327af72ad372255817b585a74da06eda.conf000066400000000000000000000015421511153563100235150ustar00rootroot00000000000000set -sg escape-time 10 set -g default-terminal tmux-256color set -g prefix ^X set -g history-limit 10000 setw -g mode-keys vi setw -g xterm-keys off # black, red, green, yellow, blue, magenta, cyan, white, default. setw -g message-command-style fg=yellow,bg=black setw -g message-style fg=black,bg=yellow %if #{m:*mydomain*,#{host}} set -g status-style fg=cyan,bg='#001040' setw -g window-status-current-style fg='#f0f0f0',bg='#001040' %elif #{||:#{m:*somedomain*,#{host}},#{m:*otherdomain*,#{host}}} set -g status-style fg=white,bg='#400040' setw -g window-status-current-style fg=yellow,bg='#400040',bright %else set -g status-style fg=white,bg='#800000' setw -g window-status-current-style fg=brightwhite,bg='#800000' %endif unbind ^B bind ^X last-window bind x send-prefix bind ^C new-window bind ^D detach-client bind ^N next-window bind ^P previous-window tmux-tmux-f222026/regress/conf/58304907c117cab9898ea0b070bccde3.conf000066400000000000000000000065231511153563100235760ustar00rootroot00000000000000# # Tureba's tmux.conf # # To use it, either: # a) link ~/.tmux.conf to it; or # b) create a ~/.tmux.conf that sources it. # # who: Arthur Nascimento # where: github.com/tureba/myconfigfiles # # defaults set -g default-shell /bin/zsh set -g default-command zsh # tmux sets screen/screen-256, but has no codes for italics set -g default-terminal tmux-256color # linux terminal doesn't need this, but xterm does set -g terminal-overrides 'xterm*:smcup@:rmcup@,*256col*:colors=256,xterm*:XT' # xterm-style function key sequences setw -g xterm-keys on # 1, 2 and 3 are closer together than 0, 1 and 2 set -g base-index 1 set -g pane-base-index 1 # easier to type than C-b set -g prefix C-a set -g prefix2 C-b unbind C-b bind C-a send-prefix # for repeatable keys set -g repeat-time 170 # status bar set -g status-style fg=green,bg=colour234 set -g status-right-style bg=colour236 set -g status-right "#[bold,fg=blue][#[fg=default]#T#[fg=blue]]#[nobold,fg=default] | #[fg=yellow]%F %R" set -g status-right-length 120 set -g status-left-style bg=colour236,bright set -g status-left "#[fg=blue][#[fg=default]#h#[fg=cyan]:#[fg=default]#S#[fg=blue]]" set -g status-left-length 30 setw -g window-status-style fg=green setw -g window-status-format " #I#[nobold]:#W " setw -g window-status-current-style fg=green,bright setw -g window-status-current-format "#[fg=red][#[fg=default]#I:#W#[fg=red]]" setw -g window-status-separator "|" setw -g window-status-activity-style blink setw -g window-status-bell-style blink setw -g window-status-last-style bright # enable wm window titles set -g set-titles on # auto window rename setw -g automatic-rename on # auto window resize setw -g aggressive-resize on # mouse settings set -g mouse on # var|bind \ cmd | vim | less | copy | zsh # pane_in_mode | 0 | 0 | 1 | 0 # mouse_any_flag | 1 | 0 | 0 | 0 # alternate_on | 1 | 1 | 0 | 0 # WheelUpPane | send -M | send Up | * | send Up (** or copy-mode -e) # WheelDownPane | send -M | send Down | * | send Down # * panes in copy mode have scroll handled by different bindings # ** cycle over shell history #bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Up' # ** enter copy mode bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'if -Ft= "#{alternate_on}" "send -t= Up" "copy-mode -et="' bind -T root WheelDownPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Down' # sensible v/h splits unbind % unbind '"' bind | split-window -h bind - split-window -v # hjkl pane traversal bind -r h select-pane -L bind -r j select-pane -D bind -r k select-pane -U bind -r l select-pane -R # window navigation unbind p bind -r [ previous-window unbind n bind -r ] next-window # Vi copypaste mode setw -g mode-keys vi bind C-c copy-mode bind p paste-buffer bind -T copy-mode-vi v send-keys -X begin-selection bind -T copy-mode-vi y send-keys -X copy-selection bind -T copy-mode-vi V send-keys -X rectangle-toggle # toggle window activity monitoring bind m setw monitor-activity # reload the configuration bind r source-file ~/.tmux.conf # toggle synchronize-panes bind S setw synchronize-panes # create a new window with exactly this command bind C command-prompt "new-window 'exec %%'" # (toggle) mark this pane for easier joins and swaps bind . select-pane -m tmux-tmux-f222026/regress/conf/872441a98b06444acc5ce08eb24aabde.conf000066400000000000000000000016571511153563100237340ustar00rootroot00000000000000bind -r Up if -F '#{pane_at_top}' '' 'selectp -U' bind -r Down if -F '#{pane_at_bottom}' '' 'selectp -D' bind -r Left if -F '#{pane_at_left}' '' 'selectp -L' bind -r Right if -F '#{pane_at_right}' '' 'selectp -R' bind -n WheelUpPane if -Ft= "#{mouse_any_flag}" "send -M" "send Up" bind -n WheelDownPane if -Ft= "#{mouse_any_flag}" "send -M" "send Down" bind w run 'tmux choose-tree -Nwf"##{==:##{session_name},#{session_name}}"' bind C { splitw -f -l30% '' set-hook -p pane-mode-changed 'if -F "#{!=:#{pane_mode},copy-mode}" "kill-pane"' copy-mode -s'{last}' } set -g word-separators "" bind -n C-DoubleClick1Pane if -F '#{m/r:^[^:]*:[0-9]+:,#{mouse_word}}' { run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' emacs `echo #{mouse_word}|awk -F: "{print \"+\"\\$2,\\$1}"` ' } } { run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' emacs "#{mouse_word}" ' } } tmux-tmux-f222026/regress/conf/91378fd400b0444eb8cac471e30642b3.conf000066400000000000000000000034021511153563100234170ustar00rootroot00000000000000### if-shell " \ tmux -V \ | awk '{print $2}' \ | awk -F - '{print $1}' \ | awk '{ \ if ($1 ~ /^[[:digit:].]+$/) { \ exit !($1 >= 2.6) \ } else { \ exit !($1 == \"master\" || $1 == \"next\") \ } \ }'" \ "source-file ~/.tmux/v2rc" \ "source-file ~/.tmux/v1rc" \ ; ### set-option -qg status-left \ "[#[fg=yellow]#{session_name}#[default]] #[fg=colour060]#{host_short}#[default]:#[fg=colour151]#{b:pane_current_path} #[fg=colour099]#(git -C #{pane_current_path} symbolic-ref --short HEAD) #[fg=green]#(git -C #{pane_current_path} status --porcelain --untracked-files=no | cut -b 1-1 | sort | uniq | awk '/^[^[:space:]]/ {printf\(\"%%s\", $0\)}')#[fg=red]#(git -C #{pane_current_path} status --porcelain --untracked-files=no | cut -b 2-2 | sort | uniq | awk '/^[^[:space:]]/ {printf\(\"%%s\", $0\)}')#[fg=colour113]#(git -C #{pane_current_path} stash list 2>/dev/null | wc -l | tr -d '\n' | sed s,^0\$,,) #[default]" set-option -qg status-right \ "#[default] ┊ #[fg=colour065]#(grep ^MemFree /proc/meminfo | awk '{print rshift\($2, 10\)}')#[fg=colour071]m #[default]┊ #[fg=colour101]#(echo \"\(`awk '{print \$1}' /proc/loadavg` / `grep ^processor /proc/cpuinfo | wc -l`\) \* 100\" | bc -ql | sed 's,\\..*,,' | awk '{printf\(\"%%2u\", $0\)}')#[fg=colour102]%% " set-option -qwg window-status-current-format \ "#[fg=colour208]»#[fg=colour190]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" set-option -qwg window-status-format \ "#[default]»#[fg=colour066]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" tmux-tmux-f222026/regress/conf/99749670b62bcb99a9b2e3d59708e357.conf000066400000000000000000000057151511153563100234260ustar00rootroot00000000000000# ----------------------------------------------------------------------------- # This config is targeted for tmux 2.1+ and should be placed in $HOME. # # Read the "Plugin Manager" section (bottom) before trying to use this config! # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # Global options # ----------------------------------------------------------------------------- # Set a new prefix / leader key. set -g prefix ` bind ` send-prefix # Allow opening multiple terminals to view the same session at different sizes. setw -g aggressive-resize on # Remove delay when switching between Vim modes. set -s escape-time 0 # Allow Vim's FocusGained to work when your terminal gains focus. # Requires Vim plugin: https://github.com/tmux-plugins/vim-tmux-focus-events set -g focus-events on # Add a bit more scroll history in the buffer. set -g history-limit 50000 # Enable color support inside of tmux. set -g default-terminal "screen-256color" # Ensure window titles get renamed automatically. setw -g automatic-rename # Start windows and panes index at 1, not 0. set -g base-index 1 setw -g pane-base-index 1 # Enable full mouse support. set -g mouse on # Status bar optimized for Gruvbox. set -g status-fg colour244 set -g status-bg default set -g status-left '' set -g status-right-length 0 #set -g status-right-length 20 #set -g status-right '%a %Y-%m-%d %H:%M' set -g pane-border-fg default set -g pane-border-bg default set -g pane-active-border-fg colour250 set -g pane-active-border-bg default set-window-option -g window-status-current-attr bold set-window-option -g window-status-current-fg colour223 # ----------------------------------------------------------------------------- # Key bindings # ----------------------------------------------------------------------------- # Unbind default keys unbind C-b unbind '"' unbind % # Reload the tmux config. bind-key r source-file ~/.tmux.conf # Split panes. bind-key h split-window -v bind-key v split-window -h # Move around panes with ALT + arrow keys. bind-key -n M-Up select-pane -U bind-key -n M-Left select-pane -L bind-key -n M-Down select-pane -D bind-key -n M-Right select-pane -R # ----------------------------------------------------------------------------- # Plugin Manager - https://github.com/tmux-plugins/tpm # In order to use the plugins below you need to install TPM and the plugins. # Step 1) git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm # Step 2) Reload tmux if it's already started with `r # Step 3) Launch tmux and hit `I (capital i) to fetch any plugins # ----------------------------------------------------------------------------- # List of plugins. set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'tmux-plugins/tmux-resurrect' set -g @plugin 'tmux-plugins/tmux-yank' # Initialize TPM (keep this line at the very bottom of your tmux.conf). run -b '~/.tmux/plugins/tpm/tpm' tmux-tmux-f222026/regress/conf/a46e6e84cd1071105aa807256dbc158d.conf000066400000000000000000000437521511153563100235160ustar00rootroot00000000000000# Dynamic configuration file generated by ~/Makefile from /home/sunny/.tmux.conf.erb # # DO NOT EDIT THIS FILE BY HAND -- # YOUR CHANGES WILL BE OVERWRITTEN # bind-key R source ~/.tmux.conf \; display-message 'config reloaded!' #----------------------------------------------------------------------------- # terminal #----------------------------------------------------------------------------- # enable mouse support for general selection and control set-option -g mouse on # auto-set terminal title to current window pane's title set-option -g set-titles on # enable 256-color support for pretty colorschemes in Vim set-option -g default-terminal 'screen-256color' # allow Vim to receive focus events from terminal window set-option -g focus-events on # allow Vim to receive modifier keys: Shift, Control, Alt set-window-option -g xterm-keys on # prevent tmux from catching modifier keys meant for Vim set-option -s escape-time 0 # enable 24-bit true color RGB escape sequences under st # https://sunaku.github.io/tmux-24bit-color.html set-option -ga terminal-overrides ',st-256color:Tc' set-option -ga terminal-overrides ',xterm-256color:Tc' # hterm (ChromeOS) # allow set-titles to change the window title under XTerm # http://opennomad.com/content/goodbye-screen-hello-tmux set-option -ga terminal-overrides ',xterm*:XT' # allow set-titles to change the window title under XTerm # http://opennomad.com/content/goodbye-screen-hello-tmux # http://stackoverflow.com/questions/15195624 set-option -ga terminal-overrides ',st-256color:smkx=\E=' # yank to system clipboard rather than primary selection # http://invisible-island.net/xterm/terminfo-contents.html#tic-xterm_tmux set-option -ga terminal-overrides ',xterm*:Ms=\E]52;c;%p2%s\007' # KiTTY always appends to clipboard; must clear it first # https://sw.kovidgoyal.net/kitty/protocol-extensions.html#pasting-to-clipboard set-option -ga terminal-overrides ',xterm-kitty:Ms=\E]52;c;!\007\E]52;c;%p2%s\007' # prevent standout from appearing as italics under URxvt # http://comments.gmane.org/gmane.comp.terminal-emulators.tmux.user/1927 set-option -ga terminal-overrides ',rxvt-unicode*:sitm@' #----------------------------------------------------------------------------- # appearance #----------------------------------------------------------------------------- # Colors from the "lucius" and "gruvbox" themes in the vim-airline plugin: # https://github.com/bling/vim-airline/blob/master/autoload/airline/themes/lucius.vim # https://github.com/morhetz/gruvbox/blob/master/autoload/airline/themes/gruvbox.vim set-option -g status-style fg=colour246,bg=colour237 set-window-option -g window-status-current-style fg=colour214,bg=colour239 set-option -g pane-border-style fg=colour239 set-option -g pane-active-border-style fg=colour208 set-option -g message-style fg=colour214,bg=colour239 set-window-option -g mode-style fg=colour214,bg=colour239,bold,reverse # Common UI interaction cues from Blueprint CSS: # http://blueprintcss.org/tests/parts/forms.html set-window-option -g window-status-bell-style 'bg=#205791,fg=#d5edf8' # info (blue) set-window-option -g window-status-activity-style 'bg=#8a1f11,fg=#fbe3e4' # error (red) #----------------------------------------------------------------------------- # status bar #----------------------------------------------------------------------------- # toggle status bar visibility bind-key -n M-` set-option -g status # toggle status bar position bind-key -n M-~ \ if-shell 'tmux show-option -g status-position | grep -q top$' \ 'set-option -g status-position bottom' \ 'set-option -g status-position top' # put status bar at the top of the screen set-option -g status-position top # list windows on left side of status bar set-option -g status-left-length 0 # make window list easier to scan set-window-option -g window-status-format ' #[bold]#I#F#[nobold]#W ' set-window-option -g window-status-current-format ' #[bold]#I#F#[nobold]#W ' set-window-option -g window-status-separator '' # show pane title, pane identifier, and hostname on right side of status bar set-option -g status-right-length 64 set-option -g status-right '#{=32:pane_title} \ #[fg=colour214,bg=colour239] #S:#I.#P \ #(test -n "$SSH_TTY" && echo "#[fg=colour214,bg=colour239,bold,reverse] #H ")' #----------------------------------------------------------------------------- # windows #----------------------------------------------------------------------------- # create window bind-key -n M-e new-window # rename window bind-key -n M-E command-prompt -I '#W' 'rename-window "%%%"' set-window-option -g automatic-rename off # break off pane to a new window bind-key -n M-x \ command-prompt -p 'break-pane:' -I '#W' \ 'break-pane ; rename-window "%%%"' bind-key -n M-X break-pane # focus window bind-key -n M-, previous-window bind-key -n M-. next-window bind-key -n M-o last-window # focus by number set-option -g base-index 1 set-window-option -g pane-base-index 1 set-option -g renumber-windows on bind-key -n M-0 choose-window bind-key -n M-1 select-window -t :1 bind-key -n M-2 select-window -t :2 bind-key -n M-3 select-window -t :3 bind-key -n M-4 select-window -t :4 bind-key -n M-5 select-window -t :5 bind-key -n M-6 select-window -t :6 bind-key -n M-7 select-window -t :7 bind-key -n M-8 select-window -t :8 bind-key -n M-9 select-window -t :1 \; select-window -t :-1 # swap window bind-key -n M-< swap-window -t :-1 bind-key -n M-> swap-window -t :+1 # monitor window set-option -g visual-activity on set-option -g visual-silence on bind-key -n M-k \ set-window-option monitor-activity \;\ display-message 'monitor-activity #{?monitor-activity,on,off}' bind-key -n M-K \ if-shell 'tmux show-window-option -g monitor-activity | grep -q off$' \ 'set-window-option -g monitor-activity on' \ 'set-window-option -g monitor-activity off' \;\ display-message 'monitor-activity #{?monitor-activity,on,off} (global)' bind-key -n M-j \ command-prompt -p 'monitor-silence (seconds):' -I '#{monitor-silence}' \ 'set-window-option monitor-silence %% ;\ display-message "monitor-silence #{?monitor-silence,on,off}"' #----------------------------------------------------------------------------- # panes #----------------------------------------------------------------------------- # send input to all panes in window (toggle) bind-key C-a \ set-option synchronize-panes \;\ display-message 'synchronize-panes #{?synchronize-panes,on,off}' # clear the screen in all panes in window bind-key C-l \ set-option synchronize-panes on \;\ send-keys C-l \;\ set-option synchronize-panes off # create pane (below, above, left, right) bind-key -n M-c split-window -c '#{pane_current_path}' bind-key -n M-C split-window -c '#{pane_current_path}' -b bind-key -n M-R split-window -c '#{pane_current_path}' -b -h bind-key -n M-r split-window -c '#{pane_current_path}' -h # join pane (above, left, below, right) bind-key -n M-g move-pane -t .-1 -s . # join pane at bottom of prev pane bind-key -n M-l move-pane -t .-1 -s . -h # join pane at right of prev pane bind-key -n M-G move-pane -d -s .+1 -t . # join next pane at bottom bind-key -n M-L move-pane -d -s .+1 -t . -h # join next pane at right # Intelligently navigate tmux panes and Vim splits using the same keys. # See https://sunaku.github.io/tmux-select-pane.html for documentation. # # +-------------+------------+-----------------------------+ # | inside Vim? | is Zoomed? | Action taken by key binding | # +-------------+------------+-----------------------------+ # | No | No | Focus directional tmux pane | # | No | Yes | Nothing: ignore key binding | # | Yes | No | Seamlessly focus Vim / tmux | # | Yes | Yes | Focus directional Vim split | # +-------------+------------+-----------------------------+ # vim_navigation_timeout=0.05 # number of seconds we give Vim to navigate navigate=' \ pane_is_zoomed() { \ test #{window_zoomed_flag} -eq 1; \ }; \ pane_title_changed() { \ test "#{pane_title}" != "$(tmux display -p "##{pane_title}")"; \ }; \ command_is_vim() { \ case "${1%% *}" in \ (vi|?vi|vim*|?vim*|view|?view|vi??*) true ;; \ (*) false ;; \ esac; \ }; \ pane_contains_vim() { \ case "#{=3:pane_current_command}" in \ (git|ssh|sh) command_is_vim "#{=5:pane_title}" ;; \ (*) command_is_vim "#{=5:pane_current_command}" ;; \ esac; \ }; \ pane_contains_neovim_terminal() { \ test "#{=12:pane_title}" = "nvim term://"; \ }; \ navigate() { \ tmux_navigation_command=$1; \ vim_navigation_command=$2; \ vim_navigation_only_if=${3:-true}; \ if pane_contains_vim && eval "$vim_navigation_only_if"; then \ if pane_contains_neovim_terminal; then \ tmux send-keys C-\\ C-n; \ fi; \ eval "$vim_navigation_command"; \ if ! pane_is_zoomed; then \ sleep $vim_navigation_timeout; : wait for Vim to change title; \ if ! pane_title_changed; then \ eval "$tmux_navigation_command"; \ fi; \ fi; \ elif ! pane_is_zoomed; then \ eval "$tmux_navigation_command"; \ fi; \ }; \ navigate ' navigate_left=" $navigate 'tmux select-pane -L' 'tmux send-keys C-w h'" navigate_down=" $navigate 'tmux select-pane -D' 'tmux send-keys C-w j'" navigate_up=" $navigate 'tmux select-pane -U' 'tmux send-keys C-w k'" navigate_right="$navigate 'tmux select-pane -R' 'tmux send-keys C-w l'" navigate_back=" $navigate 'tmux select-pane -l || tmux select-pane -t1'\ 'tmux send-keys C-w p' \ 'pane_is_zoomed' " ## QWERTY keys - comment these out if you don't use QWERTY layout! #bind-key -n M-h run-shell -b "$navigate_left" #bind-key -n M-j run-shell -b "$navigate_down" #bind-key -n M-k run-shell -b "$navigate_up" #bind-key -n M-l run-shell -b "$navigate_right" #bind-key -n M-\ run-shell -b "$navigate_back" # Dvorak keys - comment these out if you don't use Dvorak layout! bind-key -n M-d run-shell -b "$navigate_back" bind-key -n M-h run-shell -b "$navigate_left" bind-key -n M-t run-shell -b "$navigate_up" bind-key -n M-n run-shell -b "$navigate_down" bind-key -n M-s run-shell -b "$navigate_right" # resize pane bind-key -r H resize-pane -L 5 bind-key -r T resize-pane -U 5 bind-key -r N resize-pane -D 5 bind-key -r S resize-pane -R 5 # zoom pane bind-key -n M-m resize-pane -Z # swap pane bind-key -n M-- swap-pane -D bind-key -n M-_ swap-pane -U bind-key -n M-D run-shell 'tmux select-pane -l \; swap-pane -d -s #D' bind-key -n M-H run-shell 'tmux select-pane -L \; swap-pane -d -s #D' bind-key -n M-T run-shell 'tmux select-pane -U \; swap-pane -d -s #D' bind-key -n M-N run-shell 'tmux select-pane -D \; swap-pane -d -s #D' bind-key -n M-S run-shell 'tmux select-pane -R \; swap-pane -d -s #D' # attach by number bind-key -n 'M-!' join-pane -t :1 bind-key -n 'M-@' join-pane -t :2 bind-key -n 'M-#' join-pane -t :3 bind-key -n 'M-$' join-pane -t :4 bind-key -n 'M-%' join-pane -t :5 bind-key -n 'M-^' join-pane -t :6 bind-key -n 'M-&' join-pane -t :7 bind-key -n 'M-*' join-pane -t :8 bind-key -n 'M-(' run-shell 'tmux select-window -t :1 \;\ select-window -t :-1 \;\ join-pane -s "#{pane_id}"' bind-key -n 'M-)' choose-window 'join-pane -t "%%%"' # promote pane (toggle) bind-key -n M-Enter \ if-shell 'test #P -ne 1' \ 'select-pane -t 1' \ 'last-pane; swap-pane -s 1' # rotate panes bind-key -n M-a rotate-window -D bind-key -n M-A rotate-window -U #----------------------------------------------------------------------------- # layouts #----------------------------------------------------------------------------- bind-key M-w select-layout main-horizontal bind-key M-W select-layout even-vertical bind-key M-v select-layout main-vertical bind-key M-V select-layout even-horizontal bind-key M-z select-layout tiled # half-screen tiling layouts (horizontal, vertical) # https://sunaku.github.io/tmux-half-screen-tiling-layouts.html bind-key -n M-w select-layout main-horizontal \;\ run-shell 'tmux resize-pane -t 1 -y $(( #{window_height} / 2 ))' bind-key -n M-v select-layout main-vertical \;\ run-shell 'tmux resize-pane -t 1 -x $(( #{window_width} / 2 ))' # binary space partitioned layouts (dwindle, spiral) # https://sunaku.github.io/tmux-layout-dwindle.html bind-key -n M-w run-shell 'tmux-layout-dwindle brhc && tmux-redraw-vim' bind-key -n M-W run-shell 'tmux-layout-dwindle trhc && tmux-redraw-vim' bind-key -n M-v run-shell 'tmux-layout-dwindle brvc && tmux-redraw-vim' bind-key -n M-V run-shell 'tmux-layout-dwindle blvc && tmux-redraw-vim' bind-key -n M-z select-layout tiled #----------------------------------------------------------------------------- # scrollback buffer #----------------------------------------------------------------------------- # buffer length set-option -g history-limit 32767 # search buffer using copy mode bind-key -n M-/ copy-mode \;\ command-prompt -p 'search-backward (press up):' \ -i 'send-keys -X search-backward-incremental "%%%"' # search buffer using Vim or less bind-key -n M-| \ capture-pane -J -S - \; \ new-window -n '#S:#I.#P' -a ' \ tmux save-buffer - \; delete-buffer | { \ if command -v vim; \ then vim -R -c "set nofen is hls ic" -; \ else less; \ fi; \ }; \ ' \; \ run-shell 'tmux send-keys G \?' # search colored buffer using less bind-key -n M-? \ capture-pane -e -J -S - \; \ new-window -n '#S:#I.#P' -a ' \ tmux save-buffer - \; delete-buffer | \ less -R \ ' \; \ run-shell 'tmux send-keys G \?' # scroll buffer # NOTE: set "URxvt.saveLines: 0" in ~/.Xdefaults to make Shift+PageUp bindable # NOTE: see http://aperiodic.net/screen/interface for doing the same in XTerm bind-key -n S-PPage copy-mode -u # copy text from buffer bind-key -n M-u copy-mode set-window-option -g mode-keys vi bind-key -T copy-mode-vi v send-keys -X begin-selection bind-key -T copy-mode-vi y send-keys -X copy-selection bind-key -T copy-mode-vi - send-keys -X jump-again bind-key -T copy-mode-vi _ send-keys -X jump-reverse bind-key -T copy-mode-vi ? command-prompt -p 'search-backward:' -I '#{pane_search_string}' -i 'send-keys -X search-backward-incremental "%%%"' bind-key -T copy-mode-vi / command-prompt -p 'search-forward:' -I '#{pane_search_string}' -i 'send-keys -X search-forward-incremental "%%%"' # transfer copied text to attached terminal with yank: # https://github.com/sunaku/home/blob/master/bin/yank bind-key -T copy-mode-vi Y send-keys -X copy-pipe 'yank > #{pane_tty}' # open the visual selection with xdg-open(1) bind-key -T copy-mode-vi O send-keys -X copy-pipe 'xargs -r xdg-open' # paste most-recently copied text bind-key -n M-i paste-buffer # paste previously copied text (chosen from a menu) bind-key -n M-I choose-buffer # transfer most-recently copied text to attached terminal with yank: # https://github.com/sunaku/home/blob/master/bin/yank bind-key -n M-y run-shell 'tmux save-buffer - | yank > #{pane_tty}' # transfer previously copied text (chosen from a menu) to attached terminal: # https://github.com/sunaku/home/blob/master/bin/yank bind-key -n M-Y choose-buffer 'run-shell "tmux save-buffer -b \"%%%\" - | yank > #{pane_tty}"' #----------------------------------------------------------------------------- # TMUX plugin manager https://github.com/tmux-plugins/tpm #----------------------------------------------------------------------------- set -g @plugin 'tmux-plugins/tmux-resurrect' set -g @resurrect-capture-pane-contents on set -g @plugin 'Morantron/tmux-fingers' set -g @fingers-key '-n M-U' set -g @fingers-compact-hints 1 set -g @fingers-hint-format '#[fg=yellow,bold,reverse]%s' set -g @fingers-hint-labels ' \ a o e u i d h t n s \ p y f g c r l \ q j k x b m w v z \ A O E U I D H T N S \ P Y F G C R L \ Q J K X B M W V Z \ ' run-shell ~/.tmux/plugins/tpm/tpm tmux-tmux-f222026/regress/conf/a4789a6782859c66aa8c9614ee6fabfa.conf000066400000000000000000000047531511153563100237210ustar00rootroot00000000000000set -g default-command "if [ \"$(uname)\" = 'Darwin' ]; then exec reattach-to-user-namespace $SHELL; else exec $SHELL; fi" set -g history-limit 32000 set -g update-environment "DISPLAY WINDOWID SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION SSH_CLIENT SSH_TTY KRB5CCNAME Apple_PubSub_Socket_Render Apple_Ubiquity_Message" # Reset SHLVL (otherwise it is 2 inside tmux) setenv -g SHLVL 0 # Send esc faster so that neovim won't get so laggy # https://github.com/neovim/neovim/issues/2093 set -g escape-time 100 # Disable paste detection set -g assume-paste-time 0 # Titles and window names set -g set-titles on set -g set-titles-string "#T" # Make it not so annoying/sticky to switch windows set -g repeat-time 170 # Don't deattach me when a session ends set -g detach-on-destroy off # Make shift+keys work setw -g xterm-keys on # Prefix set -g prefix ^A unbind ^B bind ^A send-prefix bind a send-prefix # Last window bind ^a last # Next & prev bind ' ' next bind '^ ' next bind ^p prev # Status set -g status off # Need more (cow)bells! set -g bell-action any set -g bell-on-alert on # Detach bind ^d detach # Control the a tmux in a tmux bind A send-prefix \; send-prefix bind C send-prefix \; send-keys c bind n send-prefix \; send-keys ' ' bind bspace send-prefix \; send-keys p bind '#' send-prefix \; send-keys '"' # Other key bindings. bind ^r command-prompt "find-window '%%'" bind '"' choose-tree -w bind w split-window bind W split-window -c "#{pane_current_path}" bind ^w split-window bind I list-windows bind i list-windows bind D neww 'if who | grep -q "$USER.* via mosh"; then tmux lsc -F "#{client_activity} #{client_tty}" | sort | head -n -1 | awk "{print \$2}" | xargs -n1 tmux detach -t; else for i in $(tmux lsc | cut -d: -f1 | grep -v "^$SSH_TTY$"); do tmux detach -t $i; done; fi' bind S neww -t 999 'window=`tmux display -p "#{pane_title}"`; i=0; tmux list-windows | cut -d: -f1 | while read j; do if [ $j != $i ]; then tmux move-window -s $j -t $i; fi; i=$(($i+1)); done' # ; tmux find-window -T "$window" bind ^s command-prompt "rename-session '%%'" # Make the default HOME always ~ bind c neww -c ~ bind ^c new -c ~ bind escape copy-mode # Copy to the OS clipboard bind -T copy-mode-vi y send -X copy-pipe-and-cancel "if [ \"$(uname)\" = 'Darwin' ]; then reattach-to-user-namespace pbcopy; else xclip; fi" bind j command-prompt "join-pane -s '%%'" bind ! break-pane -d bind - command-prompt "move-pane -t '%%'" # Makes `tmux a` work even when there isn't a session going on new-session -A -c ~ tmux-tmux-f222026/regress/conf/ad0537c4e83d7a25d5dc4f3a3c571349.conf000066400000000000000000000055751511153563100236100ustar00rootroot00000000000000set-option -g allow-rename on set-option -g automatic-rename off set-option -g base-index 1 set-option -g default-command "$SHELL" set-option -g default-terminal "tmux-256color" set-option -g history-limit 25000 set-option -g mode-keys vi set-option -g prefix C-f set-option -g renumber-windows yes set-option -g set-titles on set-option -g set-titles-string "#T" set-option -g xterm-keys on set-option -g status-interval 1 set-option -g status-left "#(tmux-status-left)" set-option -g status-left-length 40 set-option -g status-right "" set-option -g window-status-current-attr bold set-option -g window-status-current-format "[#I#F#{?window_zoomed_flag, ,}#{=40:pane_title}]" set-option -g window-status-format "#I#{?window_zoomed_flag, ,}#F#{?window_flags,, }#{?window_zoomed_flag, ,}#{=20:pane_title}" set-option -g pane-active-border-fg colour247 set-option -g pane-border-fg colour235 set-option -g status-bg colour7 set-option -g status-fg colour16 set-option -g status-left-bg colour4 set-option -g status-left-fg colour15 set-option -g window-status-current-bg colour15 set-option -g window-status-current-fg colour16 set-option -g update-environment "DBUS_SESSION_BUS_ADDRESS DISPLAY KRB5CCNAME \ SESSION_MANAGER SSH_AGENT_PID SSH_ASKPASS SSH_AUTH_SOCK SSH_CONNECTION \ WINDOWID XAUTHORITY SSH_TTY" bind-key w break-pane -d bind-key l clear-history \; display "Pane history cleared." bind-key C-f if-shell "test #{window_panes} -eq 1" last-window last-pane bind-key N new-session bind-key t new-window bind-key z resize-pane -Z bind-key C-r rotate-window -D bind-key -n C-t run-shell "metamux new-shell-in-pane #{window_panes}" bind-key n run-shell "metamux rotate-pane next" bind-key p run-shell "metamux rotate-pane prev" bind-key q run-shell "metamux pane-buster" bind-key S run-shell "metamux join-hidden-pane -v" bind-key u run-shell "metamux open-last-url-printed" bind-key | run-shell "metamux join-hidden-pane -h" bind-key f send-prefix bind-key r source "$HOME/.tmux.conf" \; display "Configuration reloaded." # When the current window is split, Ctrl+Tab and Ctrl+Shift+Tab should rotate # between the split windows. If there is only one pane in the current window, # Ctrl+Tab and Ctrl+Shift+Tab will cycle between windows as though they were # tabs in modern desktop UIs. bind-key -n C-Tab if-shell "test #{window_panes} -eq 1" next-window "select-pane -t :.+" bind-key -n C-S-Tab if-shell "test #{window_panes} -eq 1" previous-window "select-pane -t :.-" # Binding to mark and swap panes; if no pane is marked, the shortcut will mark # the active pane, but if a pane is already marked, active pane will be swapped # with the marked pane. bind-key m if-shell 'test -z "$PANE_IS_MARKED"' \ "select-pane -m; set-env PANE_IS_MARKED 1" \ "swap-pane; select-pane -M; set-env -u PANE_IS_MARKED" tmux-tmux-f222026/regress/conf/ad21dbb0893240563ddfdd954b9903a1.conf000066400000000000000000000627271511153563100236040ustar00rootroot00000000000000# Time-stamp: <2018-05-31 17:10:05 kmodi> # https://github.com/tmux/tmux # Hi-lock: (("\\(^\\s< \\**\\)\\(\\* *.*\\)" (1 'org-hide prepend) (2 '(:inherit org-level-1 :height 1.3 :weight bold :overline t :underline t) prepend))) # Hi-Lock: end # Running tmux built from master branch on tcsh in uxterm # tmux version 2.5-RC+ dev # Contents: # # PREFIX # Source config # Pane Management # Window <-join/split-> Pane # Select Panes # Resize Panes # Dynamic Split # Window Management # Window Navigation # Swap Windows # Split Window # Layout # Session Management # Mouse # Drag pane border to resize # Left click on pane # Middle click on pane # Right click on pane # Wheel scroll in pane # Wheel scroll in pane WHILE in copy-mode # Left click on status # Middle click on status # Other mouse settings # Window Title # Status Bar # Left Status # Right Status # Pane Status # Colors # Status Bar Colors # Message Colors # Window Status Colors # Pane Colors # Mode Info Colors # Activity # Command Prompt # Audible and Visual Bells # Copy & Paste # Synchronize commands to panes/windows/sessions # Terminal Setting # Other Options # Server Options # Session Options # Window Options # Notes # * PREFIX set -g prefix C-z unbind C-b # unbind the default binding to send prefix key to the application # Often you'll run a tmux inside another tmux and need a command sequence to # send things to the inner session. With below binding that can be accomplished # using "PREFIX Z " bind Z send-prefix # * Source config unbind r # unbind default binding to force redraw of attached client bind r source-file ~/.tmux.conf \; display "Finished sourcing ~/.tmux.conf ." # * Pane Management set -g pane-base-index 1 # start pane indices at 1 set -g main-pane-width 100 # used by selectl main-vertical bind z resize-pane -Z # zoom/unzoom the current pane # If the window has >1 panes kill them without confirming. But confirm before kill # the last pane (along with its window) in a window bind x if "tmux display -p \"#{window_panes}\" | grep ^1\$" \ "confirm-before -p \"Kill the only pane in window? It will kill this window too! (y/n)\" kill-pane" \ "kill-pane" bind C clear-history \; display "Cleared history of the current pane." unbind C-p bind C-p run -b "tmux display -p -F '#{pane_current_path}' | xclip -i -sel pri" \; display "Copied current path '#{pane_current_path}' to the primary selection." # Hooks need tmux 2.3+ # set-hook -g -u after-kill-pane # Remove after hook for kill-pane set-hook -g after-kill-pane "selectl main-vertical" # If -g options is used when setting the hook, it has to be used when # removing (-u option) the hook too. # ** Window <-join/split-> Pane # Join a pane *from* a different window (of same or different session) into the CURRENT window # Binding mnemonic: F for (F)etch/pull (as in git) from a different window bind F command-prompt -p "Join pane from [sess:]win#[.pane#] (ex: kmodi:3.1) into current window:" "join-pane -s '%%'" # Join CURRENT pane *to* a different window # Binding mnemonic: P for (P)ush (as in git) to a different window bind P command-prompt -p "Send CURRENT pane to [sess:]win# (ex: kmodi:3):" "join-pane -t '%%'" # PREFIX ! : break-pane, convert the current pane to a window # ** Select Panes bind o select-pane -t :.+ # cycle to the next pane number bind O select-pane -t :.- # cycle to the previous pane number # PREFIX ; : last-pane or select-pane -l, switch to the last active pane # PREFIX ↠: select-pane -L, switch to the pane on the left # PREFIX → : select-pane -R, switch to the pane on the right # PREFIX ↑ : select-pane -U, switch to the pane on the top # PREFIX ↓ : select-pane -D, switch to the pane on the bottom # PREFIX { : swap-pane -U, swap current pane with the pane above (not literally above) # PREFIX } : swap-pane -D, swap current pane with the pane below (not literally below) # ** Resize Panes bind -r h resize-pane -L 2 bind -r C-h resize-pane -L 2 bind -r j resize-pane -D 2 bind -r C-j resize-pane -D 2 bind -r k resize-pane -U 2 bind -r C-k resize-pane -U 2 unbind l # unbind default binding for `last-window` bind -r l resize-pane -R 2 bind -r C-l resize-pane -R 2 # ** Dynamic Split # Key-chaining example, analogous to prefix maps in emacs bind / switch-client -Tlauncher # Run below -Tlauncher commands using "PREFIX / " # Open calendar in a split window "PREFIX / c" # FIXME: Below does not work; cal pane quits as soon as it launches (before "&& sleep .." # was added). To make better of the situation, I now auto-close that pane after 3 seconds. # bind -Tlauncher c split-window -h 'cal && sleep 3' bind -Tlauncher c run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'cal && sleep 3'" # Start emacsclient in terminal mode in a split window "PREFIX / e" # Use the emacs binding "C-x 5 0" to quit from that pane gracefully. bind -Tlauncher e run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'emacsclient -a \"\" -t'" # Open man page "PREFIX / m" # PREFIX / m will bring up the tmux command prompt. Enter the command for which # you want to see the man page, example: ls. That man page will open in a split # pane. When you are done reviewing the man page, hit q and the split pane # closes by itself. Beautiful! bind -Tlauncher m command-prompt -p "man" "run \"/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'man %1'\"" # Open python interpreter in a split window for quick calculations "PREFIX / p" # Ctrl-D in python quits python and thus closes the split window too. bind -Tlauncher p run "/home/kmodi/scripts/tcsh/tmux/dynamic_split.csh 'ipython --profile=default --no-confirm-exit'" # PREFIX Up, Down, Right, Left : Move cursor from one pane to another # PREFIX Space : Cycle through different pane layouts # PREFIX C-o : rotate-window, rotate panes in the current window # * Window Management set -g base-index 1 # start window indices at 1 # automatically renumber the windows # http://unix.stackexchange.com/questions/21742/renumbering-windows-in-tmux set -g renumber-windows on bind C-f command-prompt -p "New window:" "new-window -c '#{pane_current_path}' -n %1" bind C-r command-prompt -p "New name for this window:" "rename-window '%%'" unbind L # unbind default binding for `switch-client -l` bind L list-windows -F '#{window_index}:#{window_name}: #{?pane_dead, (dead), (not dead)}' unbind & # unbind default binding for `kill-window` bind C-c confirm-before -p "Kill this window? (y/n)" kill-window # Move the current window to another window index in the same or any other session bind m command-prompt -p "Move window to sess or sess:win# or win# (ex: kmodi or kmodi:3 or 2(of current session)):" "move-window -t '%%'" # Move or bring a window from a different session to the current one bind M command-prompt -p "Move the window from sess:win# (ex: kmodi:3):" "move-window -s '%%'" # ** Window Navigation bind C-z last-window # switch to last active window # Allow repeats for next/previous-window bind -r p previous-window bind -r n next-window # switch to another window by name bind W split-window "tmux lsw | peco --initial-index `tmux lsw | awk '/active.$/ {print NR-1}'` | cut -d':' -f 1 | xargs tmux select-window -t" # PREFIX : switches to window with index=N # ** Swap Windows bind N move-window -r # renumber the windows unbind , # unbind default binding for `rename-window` bind -r , swap-window -t -1 # move window one position to the left bind -r < swap-window -t -1 # move window one position to the left unbind . # unbind default binding to move window to user provided index bind -r . swap-window -t +1 # move window one position to the right bind -r > swap-window -t +1 # move window one position to the right unbind t # unbind default binding to show time bind t swap-window -t 1 # swap the current window's position with window # 1, move it to the top # ** Split Window unbind & # unbind default binding for `split-window -h` bind - split-window -v -c '#{pane_current_path}' # vertical split bind _ split-window -v -c '#{pane_current_path}' -f # full vertical split (v2.3+) bind \ split-window -h -c '#{pane_current_path}' # horizontal split bind | split-window -h -c '#{pane_current_path}' -f # full horizontal split (v2.3+) # https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/cw5wy00 bind w switch-client -Tsplit_wind bind -Tsplit_wind v split-window -v -c '#{pane_current_path}' bind -Tsplit_wind V split-window -v -c '#{pane_current_path}'\; swap-pane -U bind -Tsplit_wind h split-window -h -c '#{pane_current_path}' bind -Tsplit_wind H split-window -h -c '#{pane_current_path}'\; swap-pane -U # ** Layout bind Space next-layout bind C-Space select-layout -o # undo only the last layout change #v2.1 # * Session Management bind C-t command-prompt -p "New name for this session:" "rename-session '%%'" bind b switch-client -l # switch to previously selected session # switch to another session by name bind S split-window "tmux ls | peco --initial-index `tmux ls | awk '/attached.$/ {print NR-1}'` | cut -d':' -f 1 | xargs tmux switch-client -t" # switch to ANY window in ANY session by name bind s split-window "tmux ls | cut -d: -f1 | xargs -I SESSION tmux lsw -F 'SESSION:#{window_name}' -t SESSION | peco --initial-index `tmux ls | cut -d: -f1 | xargs -I SESSION tmux lsw -F '___#{session_attached}#{window_active}___' -t SESSION | awk '/___11___/ {print NR-1}'` | xargs tmux switch-client -t" # tmux kill-session -t NAME/SESSIONNUMBER # Kill session # * Mouse # setw -g mode-mouse on # incompatible in tmux 2.1+ set -g mouse on # ** Drag pane border to resize # set -g mouse-resize-pane off # incompatible in tmux 2.1+ bind -T root MouseDrag1Border resize-pane -M # default # unbind -T root MouseDrag1Border # disable drag pane border to resize bind -T root MouseDrag1Pane if -Ft= '#{mouse_any_flag}' 'if -Ft= "#{pane_in_mode}" "copy-mode -M" "send-keys -M"' 'copy-mode -M' # default # ** Left click on pane # set -g mouse-select-pane on # incompatible in tmux 2.1+ # Left click on a pane selects it # bind -T root MouseDown1Pane select-pane -t=\; send-keys -M # default bind -T root MouseDown1Pane select-pane -t= # Sun Feb 19 11:31:34 EST 2017 - kmodi # Below break in tmux 2.4 # # Fri Aug 26 18:35:21 EDT 2016 - kmodi # # FIXME Need to remember why I unbound the below 2 bindings # unbind -temacs-copy MouseDown1Pane # unbind -temacs-copy MouseUp1Pane # # # https://groups.google.com/forum/#!topic/tmux-users/mHhdx7Au0Ds # Fri Aug 26 18:30:15 EDT 2016 - kmodi # Do not do the below!! That will update the primary selection with the top-most # tmux buffer each time you left click on a pane. # bind -T root MouseUp1Pane run -b "tmux show-buffer | xclip -i -sel pri" # # Left click in the pane *followed after a region selection* copies that to the # secondary selection bind -T root MouseUp1Pane run -b "tmux show-buffer | xclip -i -sel sec" # Fri Aug 26 19:03:57 EDT 2016 - kmodi # FIXME: As of today it needs to be figured out how to best paste the content # from secondary selection # ** Middle click on pane # Middle click in a pane to paste from the primary selection bind -T root MouseDown2Pane run -b "xclip -o -sel pri | tmux load-buffer - && tmux paste-buffer -s ' '" # ** Right click on pane # Right click on a pane selects and marks it *if not in copy-mode*; else # passes on the mode keys # bind -T root MouseDown3Pane select-pane -t= -m # default bind -T root MouseDown3Pane if -Ft= '#{pane_in_mode}' 'send-keys -M' 'select-pane -t= -m' # Sun Feb 19 11:32:00 EST 2017 - kmodi # Below breaks in tmux 2.4 # # Right click *release* on a pane *in copy-mode* quits copy-mode # bind -temacs-copy MouseUp3Pane cancel # ** Wheel scroll in pane unbind -T root WheelUpPane unbind -T root WheelDownPane # Do mouse wheel-up to enter copy mode and do page-up # https://groups.google.com/d/msg/tmux-users/XTrSVUR15Zk/3iyJLMyQ7PwJ # Below binding did not work # bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'if -Ft= "#{pane_in_mode}" "copy-mode -u" "send-keys -M"' 'copy-mode -u' # Below works and allows the WheelUpPane binding in emacs-copy table to be effective bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send-keys -M' 'if -Ft= "#{pane_in_mode}" "send-keys -M" "copy-mode -u"' # |---------------------+-----------------------------------------+--------------------------------| # | using mouse? AND .. | #{pane_in_mode} (already in copy-mode?) | action | # |---------------------+-----------------------------------------+--------------------------------| # | Yes | Don't care | Send the mode keys | # | No | Yes | Send the mode keys | # | No | No | Enable copy-mode and do PageUp | # |---------------------+-----------------------------------------+--------------------------------| # *** Wheel scroll in pane WHILE in copy-mode # Sun Feb 19 11:32:16 EST 2017 - kmodi # Below breaks in tmux 2.4 # # Once in copy-mode, mouse wheel scrolls scrolls by half pages # bind -temacs-copy WheelUpPane halfpage-up # bind -temacs-copy WheelDownPane halfpage-down # ** Left click on status # set -g mouse-select-window on # incompatible in tmux 2.1+ # Left click on a window name in status bar to select it bind -T root MouseDown1Status select-window -t= # default # ** Middle click on status # Middle click on a window name in status bar to kill it bind -T root MouseDown2Status kill-window # ** Other mouse settings # The special token ‘{mouse}’ or ‘=’ may be used as target-window or target-pane in # commands bound to mouse key bindings. Example: -t = # * Window Title set -g set-titles on set -g set-titles-string '#h :: #S:W#I(#W).P#P' # * Status Bar set -g status-interval 5 # default = 15 seconds set -g status-justify centre # ** Left Status set -g status-left-length 20 # Change the left status when prefix is pressed. # https://www.reddit.com/r/tmux/comments/5cm2ca/post_you_favourite_tmux_tricks_here/d9ziuy9/ set -g status-left "#{?client_prefix,#[fg=yellow]prefix pressed ..,[#S]}" # ** Right Status set -g status-right-length 20 set -g status-right "%l:%M %b %d %a " # ** Pane Status setw -g pane-border-status "bottom" setw -g pane-border-format " #P #T " # # tmux-powerline # # https://github.com/erikw/tmux-powerline # set -g status-left-length 30 # set -g status-right-length 30 # set -g status-left "#(~/usr_local/scripts/tmux-powerline/powerline.sh left)" # set -g status-right "#(~/usr_local/scripts/tmux-powerline/powerline.sh right)" # * Colors # ** Status Bar Colors set -g status-style fg=colour246,bg=colour233 # default for whole status line set -g status-left-style fg=white,bold,bg=colour233 set -g status-right-style fg=colour75,none,bg=colour233 # ** Message Colors set -g message-style fg=colour2,bold,bg=default # ** Window Status Colors setw -g window-status-style default # default for all window statuses setw -g window-status-last-style fg=default,bg=colour235 setw -g window-status-current-style fg=white,bold,bg=colour63 setw -g window-status-bell-style default setw -g window-status-activity-style fg=white,none,bg=colour196 # setw -g window-status-content-style fg=black,none,bg=green # incompatible with tmux 2.0+ # ** Pane Colors setw -g pane-active-border-style fg=colour63,bg=default setw -g pane-border-style fg=colour235,bg=default setw -g window-active-style 'bg=#330000' # bg color of active pane setw -g window-style 'bg=black' # bg color of inactive pane(s) # ** Mode Info Colors # Color of display shown on top-right in copy-mode, highlighting setw -g mode-style fg=black,bg=colour244 # * Activity # Notify when a window has activity # This quick snippet will have tmux notify you in the status area when a # window has activity: setw -g monitor-activity on set -g visual-activity off # Display message telling that an activity happened (on/off) # It lets me know that there is activity in a non-active window # To try this, enter `sleep 10 && echo “Hiâ€` in a window and switch to # another window. # # Notify when a window has a content alert # setw -g monitor-content "--[A-Za-z][A-Za-z]sim Done--" # This string appears when a sim finishes, alert then # incompatible with tmux 2.0+ # # setw -g monitor-content "" # Disable monitor-content # set -g visual-content on # Display message telling that a content alert was triggered (on/off) # incompatible with tmux 2.0+ # * Command Prompt # Move focus to command prompt. tmux commands can be entered there directly # without using the `tmux` prefix and it also supports auto-complete. bind C-x command-prompt # default command-prompt binding "PREFIX :" also works # * Audible and Visual Bells set -g bell-action any set -g bell-on-alert off set -g visual-bell on # * Copy & Paste set -g set-clipboard off # default is on # Copy tmux buffer to primary and clipboard selections # run -b runs a shell command in background # http://grota.github.io/blog/2012/05/08/tmux-clipboard-integration/ bind C-w run -b "tmux show-buffer | xclip -i -sel pri && tmux show-buffer | xclip -i -sel cli" # Fri Aug 26 18:41:30 EDT 2016 - kmodi # Below binding was suggested by Nicholas Marriott # But the my older binding works fine so I am commenting out below for now. # bind C-w run "tmux saveb - | xclip -i -sel pri; tmux saveb - | xclip -i -sel cli" # Paste into tmux; also replace LF characters with # space as separator characters (-s) when pasting. # Yank from primary bind C-y run -b "xclip -o -sel pri | tmux load-buffer - && tmux paste-buffer -s ' '" # Yank from clipboard bind M-y run -b "xclip -o -sel cli | tmux load-buffer - && tmux paste-buffer -s ' '" # Open the file/dir path that was copied by selection in existing emacs client # Usage: Highlight a file name in ls output and press "PREFIX e" bind e run -b "tmux show-buffer | xclip -i -sel pri; (emacsclient -a '' `tmux display -p '#{pane_current_path}'`/`xclip -o -sel pri `&)" # * Synchronize commands to panes/windows/sessions # Send the same command to all panes in the same window bind C-a command-prompt -p "Command to all panes in this window:" \ "run \"tmux list-panes -F '##{pane_index}' | xargs -I PANE \ tmux send-keys -t PANE '%1' Enter\"" # Alternative to using the above "C-a" binding is to enable pane synchronization, # type the command you want to execute in all panes in the same window and disable # pane synchronization # Also turn the pane borders red while pane synchronization is enabled. # - https://www.reddit.com/r/tmux/comments/5cm2ca/post_you_favourite_tmux_tricks_here/d9y6jzu/ bind C-s if -F '#{pane_synchronized}' \ 'setw synchronize-panes off; \ setw pane-active-border-style fg=colour63,bg=default; \ setw pane-border-format " #P #T "' \ 'setw synchronize-panes on; \ setw pane-active-border-style fg=red; \ setw pane-border-format " #P - Pane Synchronization ON "' # So it would be: C-s C-s # https://scripter.co/command-to-every-pane-window-session-in-tmux/ # Send the same command to all panes/windows in the current session bind C-e command-prompt -p "Command:" \ "run \"tmux list-panes -s -F '##{session_name}:##{window_index}.##{pane_index}' \ | xargs -I PANE tmux send-keys -t PANE '%1' Enter\"" # Send the same command to all panes/windows/sessions bind E command-prompt -p "Command:" \ "run \"tmux list-panes -a -F '##{session_name}:##{window_index}.##{pane_index}' \ | xargs -I PANE tmux send-keys -t PANE '%1' Enter\"" # * Terminal Setting # From `man tmux', about `default-terminal' # Set the default terminal for new windows created in this session - the default # value of the TERM environment variable. For tmux to work correctly, this must # be set to ‘screen’, ‘tmux’ or a derivative of them. # set -g default-terminal "screen" set -g default-terminal "screen-256color" # Mon May 22 11:43:56 EDT 2017 - kmodi # Blinking text (useful to show broken symlinks in ls) does not work when using tmux-24bits. # set -g default-terminal "tmux-24bits" # tmux-24bits is a custom terminfo profile created using the steps explained # on https://github.com/ThomasAdam/tmux/blob/master/FAQ to support italics and # 256 colors. # Enable 24-bit color # https://sunaku.github.io/tmux-24bit-color.html set -ga terminal-overrides ",screen-256color:Tc" # set -ga terminal-overrides ",tmux-24bits:Tc" # Thu May 31 17:10:04 EDT 2018 - kmodi # TODO: Try the 24-bit emacs+tmux config for ST # https://www.reddit.com/r/emacs/comments/8ndm2x/gnu_emacs_261_24bit_colors_suckless_st_terminal/dzwh4vv/ # set -g default-terminal "tmux-256color" # set -ga terminal-overrides ",*256col*:Tc" # setw -g xterm-keys on # Uncomment below when using st (by suckless.org) # set -g default-terminal "st-256color" # # https://sunaku.github.io/tmux-24bit-color.html # # st supports 24-bit color, so enable support for that in tmux # set -ga terminal-overrides ",st-256color:Tc" # setw -g xterm-keys off bind R refresh-client # bind R refresh-client \; display "Refreshed the client." # * Other Options # ** Server Options set -s escape-time 0 # Allows for faster key repetition # ** Session Options # Set the default shell to /bin/sh. If the default is tcsh, doing a split-window takes a long # time as my tcsh init is loaded first (which takes really long). set -g default-shell /bin/sh # If I am doing a new-window or split-window without a specified command, start the tcsh # shell by default. set -g default-command tcsh set -g history-limit 100000 set -g display-time 1000 # Duration of tmux display messages in milliseconds # ** Window Options # When a smaller terminal connects to a tmux client, it shrinks to fit it. The # clients attached with bigger displays see this constrained view. # aggressive-resize makes it such that the window is only resized if the smaller # client is actively looking at it. setw -g aggressive-resize on setw -g mode-keys emacs # Use emacs keybindings in copy mode setw -g status-keys emacs # * Notes # |-------------------+------------| # | tmux command | short form | # |-------------------+------------| # | set-option | set | # | set-window-option | setw | # | bind-key | bind | # | unbind-key | unbind | # | display-message | display | # | run-shell | run | # | if-shell | if | # |-------------------+------------| # Colo'u'r table # http://guns.github.io/xterm-color-table.vim/images/xterm-color-table.png # CHARACTER PAIR REPLACED WITH # #(command) First line of command’s output # #[attributes] Colour or attribute change # #H Hostname of local host # #I Current window index # #P Current pane index # #S Session name # #T Current window title # #W Current window name # ## A literal ‘#’ # Variables used in time format # Source: http://docs.splunk.com/Documentation/Splunk/5.0.2/SearchReference/Commontimeformatvariables # %y = year in numbers (2-digit) # %Y = year in numbers (4-digit) # %m = month in number (eg: 12) # %B = full month name (eg: December)sho # %b = short month name (eg: Dec) # %d = day in numbers, with leading zeros (eg: 08) # %e = day in numbers, no leading zeros (eg: 8) # %A = full weekday name (eg: Sunday) # %a = short weekday name (eg: Sun) # %H = hours in 24-clock, with leading zeros # %k = hours in 24-clock, no leading zeros # %l = hours in 12-clock, with leading zeros # %p = am/pm # %T = time in 24-hour notation (%H:%M:%S) # PREFIX ? : list-keys, display key bindings # In command-prompt: show-options -g shows the global options # In command-prompt: show-window-options -g shows the global windows options # How do I know which tmux version I am running? # tmux -V # How to set bindings that don't need the prefix? # bind -n .. or # bind -T root .. # Changelog: https://github.com/tmux/tmux/blob/master/CHANGES # style colors: default, black, red, green, yellow, blue, magenta, cyan, white, # colour0-colour255, hexadecimal RGB string '#ffffff' # Use $SCRIPTS/bash/256-colors.sh to figure out the color number you want # style attributes: none, bold/bright, dim, underscore, blink, reverse, hidden, # or italics # https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/cw552qd # tmux buffers # PREFIX # : List all paste buffers # PREFIX - : Delete the most recently copied buffer of text # PREFIX = : Choose which buffer to paste interactively from a list # PREFIX ] : Paste the most recently copied buffer of text # How to start a temporary tmux server in addition to an existing running one? # > tmux -L temp # In a shell environment in a terminal in tmux, the env var $TMUX will be # defined to something like "/tmp/tmux-23273/default,31101,0". Outside tmux, # $TMUX will be undefined. # Notation to address a specific pane # SESSION_NAME:WINDOW_INDEX.PANE_NUMBER (Example: foo:2.1 i.e. Pane 1 in Window 2 of Session foo) # To print a message containing tmux variable values to stdout use '-p' option in display-message # tmux display-message -p '#{session_name}:#{window_name}.#{pane_index}', or # tmux display -p '#{session_name}:#{window_name}.#{pane_index}' tmux-tmux-f222026/regress/conf/b9f0ce1976ec62ec60dc5da7dd92c160.conf000066400000000000000000000047241511153563100240270ustar00rootroot00000000000000# none of these attempts worked, to bind keys, except sometimes during the session. Oh well. # I thought maybe that was because F1 is handled differently in a console than in X, but # even just C-1 didn't work. Using just "a" or "x" as the key did, but not yet sure why not "C-". #bind-key -T root C-1 attach-session -t$0 #But this one works now, only picks the wrong one? Mbe need2understand what "$1" or $0 mean, better, #but with the stub maybe this doesn't matter: bind-key "`" switch-client -t$1 new-session #$0, stub, for keystroke convenience #$1 for root new-session #a stub I guess, where keyboard convenience is concerned new-window send-keys -l pgup send-keys Enter send-keys -l "less /root/.tmux.conf &" send-keys -l "echo; echo; echo Put something here for ssa or just run manly?" send-keys Enter new-window new-window new-window sul #for lcall, like man pages send-keys -l "man tmux&" send-keys Enter select-window -t :=1 #$2 for om, so, can do C-b C-s 2 to get to the session, then C-b <#s> to get ~"tabs" new-session sula ; send-keys -l q #0 send-keys Enter Escape Escape new-window sula ; send-keys -l q #1 send-keys Enter Escape Escape new-window sula ; send-keys -l q #2 send-keys Enter Escape Enter Enter new-window sula ; send-keys q #3, to start: send-keys Enter Escape # %%need a sleep here & .. ? send-keys Enter Enter Enter Enter new-window sula ; send-keys -l q #4 send-keys Enter Escape Escape new-window sula new-window sula new-window sula new-window sula new-window sula select-window -t :=2 select-window -t :=3 #$3 for email (mutt) new-session sula new-window sula ; send-keys mutt Enter #nah, probably better not?: #send-keys -l z #send-keys -l "thepassifdecide" #send-keys Enter new-window sula ; send-keys mutt Enter send-keys -l "c!=sent" send-keys Enter new-window sula ; send-keys -l "cd mail/config; less mailsig.txt&" send-keys Enter send-keys "less macros&" send-keys Enter select-window -t :=1 #$4 for lacall-net: links etc new-session suln new-window suln #send-keys -l "lkslfx" #; et; links ksl.com" #send-keys asdafdfadfadfadfadf #%%does opening links break subsequent cmds? With this Enter, the switch-client etc dont work: #send-keys Enter #send-keys Space Space Space new-window suln new-window suln select-window -t :=1 #send-keys Space Space Space #$5 for lacall-secnet, links?: #new-session sulsn # then, where to start: #%%need a sleep here, or ck a debug thing? switch-client -t"$0" send-keys -l "sleep 2" send-keys Enter switch-client -t$2 tmux-tmux-f222026/regress/conf/d0040b2e097f1e3d31d78eed6ce8d461.conf000066400000000000000000000077221511153563100236630ustar00rootroot00000000000000# Put the status bar on top #set -g status-position "top" # Basic colours, safer for dumb terminals. #set -g status-style "bg=white,fg=black" #set -g status-right-style "bg=green,fg=black" #set -g window-status-current-style "bg=yellow,fg=black" #set -g message-style "bg=white,fg=black" #set -g window-status-activity-style "fg=blue" #set -g window-status-bell-style "fg=red" ## Moar colours! Not recommended if attaching from dumber terminals with 8 or 16 colours. #set -g default-terminal "tmux-256color" # A more compatible XTERM var. set -g default-terminal "screen-256color" set -g message-style "bg=#485548 fg=#ffffff" set -g pane-border-style "fg=#424954" set -g pane-active-border-style "fg=#ffffff" set -g status-style "bg=#424954 fg=#ffffff" set -g status-right-style "bg=#303338 fg=colour87" set -g window-status-current-style "bg=#303338" set -g window-status-last-style "bg=#364146" set -g window-status-format ' #I:#W#[fg=colour201]#F ' set -g window-status-current-format ' #[fg=colour226]#I#[fg=#ffffff]:#[fg=colour119]#W#[fg=colour202]#F ' set -g window-status-separator "" # Uncomment and reload settings for sanity in a console with 8 colours. #set -g status-style "bg=white,fg=black" #set -g window-status-last-style "bg=white" # Might help when graphical characters used for vertical and horizontal lines are drawn as x and q. #set-option -ga terminal-overrides ',*:enacs@:smacs@:rmacs@:acsc@' # Count panes starting from 1. set -g base-index 1 # With this you set the window name in the status line. # Beware of outrageous prompts, such as the default one in RHEL 7. set -g set-titles on # Let status right consists of only the pane title (removes date and time). # Usually shows current path. set -g status-right ' #T ' # Increase the default length of 40. set -g status-right-length 80 # Scroll up with the mouse. set -g mouse # Clipboard integration, use this in tandem with the recommended xterm settings. set -g set-clipboard on # Pass through modifier keys, xterm style. You'll want this in vim. set -g xterm-keys on # Reduce time to wait for Escape key. You'll want this for neovim. set-option escape-time 40 # Leave ESC alone... #set-option -s escape-time 0 # New-style mouse scroll (>2.1) bind -n WheelUpPane select-pane -t= \; copy-mode -e \; send-keys -M bind -n WheelDownPane select-pane -t= \; send-keys -M # This is for scrolling up with the terminal using keys, but has issues... #set -ga terminal-overrides ',xterm*:smcup@:rmcup@' # 10x more history. set -g history-limit 20000 # Swap the default Control-b with Control-s which usually stops the output in a shell. unbind C-b set-option -g terminal-overrides "xterm-rightclick:krightclick=^[[29~" set -g prefix C-s bind C-s send-prefix # For renumbering windows when you get gaps in numbering. bind R \ move-window -r\; \ display-message "Windows reordered..." # My shortcuts. #bind-key -n C-S-t new-window # Doesn't work :-/ bind-key -n C-t new-window bind-key -n C-PgUp prev bind-key -n C-PgDn next #bind-key -n C-S-PgUp swap-window -t -1 # Doesn't work :-/ #bind-key -n C-S-PgDn swap-window -t +1 # Doesn't work :-/ bind-key -n C-S-Left swap-window -t -1 bind-key -n C-S-Right swap-window -t +1 bind-key -n M-` select-window -t 0 bind-key -n M-1 select-window -t 1 bind-key -n M-2 select-window -t 2 bind-key -n M-3 select-window -t 3 bind-key -n M-4 select-window -t 4 bind-key -n M-5 select-window -t 5 bind-key -n M-6 select-window -t 6 bind-key -n M-7 select-window -t 7 bind-key -n M-8 select-window -t 8 bind-key -n M-9 select-window -t 9 bind-key -n M-0 select-window -t 10 # switch panes without prefix using Alt-arrow bind -n M-Left select-pane -L bind -n M-Right select-pane -R bind -n M-Up select-pane -U bind -n M-Down select-pane -D # join pane from inputted window (horizontally or vertically) #bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -h" bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -v" tmux-tmux-f222026/regress/conf/d2e576f947e108eb9903679b65c81fbc.conf000066400000000000000000000235651511153563100235630ustar00rootroot00000000000000### GENERAL set-option -g prefix C-a # Set prefix to bind a send-prefix # Send with a bind R source-file ~/.tmux.conf \; display "~/.tmux.conf reloaded" bind -n M-R source-file ~/.tmux.conf \; display "~/.tmux.conf reloaded" set -g history-limit 10000 # lines to keep in hisoty set-option -g display-panes-time 3000 # Timeout for pane-numbering in ms bind -n M-q display-panes set-option -sg escape-time 0 # speed up commands set -g mouse on # enable mouse (tmux 2.1+) set -g base-index 1 # start window numbering at 1 set -g pane-base-index 1 # start pane numbering at 1 set -g renumber-windows on # renumber windows automatically setw -g automatic-rename on # rename window after process # Clear window name before renaming bind , rename-window "" \; command-prompt "rename-window '%%'" #### APPEARANCE set -g default-terminal "screen-256color" # use 256 colors setw -g aggressive-resize on # resize window to smallest client set -g pane-border-style fg=colour238 # border color for inactive panes set -g pane-active-border-style fg=colour247 # border color for active panes # Status bar colors and format setw -g window-status-format ' #[fg=#999999]#I #[fg=$666666]#W ' setw -g window-status-current-format '#[fg=#ffffff] #I #W#[fg=#ffffff] ' setw -g window-status-separator '#[fg=#292929]|#[fg=default]' set -g status-bg default # background color for status bar set -g status-position bottom # put status bar on top or bottom set -g status-interval 2 # interval in s to update status set -g status-justify left # horizontal alignment set -g message-style fg=white,bg=black # appearance of status messages set -g message-command-style fg=white # appearance of status message cmds # Left section of status bar set -g status-left "" # Status bar visibility set -g status off bind -r -n M-t set status on bind -r -n M-T set status off bind t set status on bind T set status off # Right section of status bar if-shell 'uname | grep -qi Darwin' "set -g status-right \"#[fg=#81a2be]#(/usr/local/bin/mpc | head -n 1 | sed 's/volume.*$//') #[fg=cyan]#(~/bin/battery-osx) #(~/bin/mailstatus.sh) #[fg=yellow]#(uptime|sed 's/.* //') #[fg=#666666]%F #[fg=#bababa]%R\"" if-shell 'uname | grep -qi Linux' "set -g status-right \"#[fg=cyan]#(~/bin/battery-linux) #(~/bin/mailstatus.sh) #[fg=yellow]#(cat /proc/loadavg|awk '{print $1;}') #[fg=#666666]%F #[fg=#bababa]%R\"" # Scaling of status-bar sections set -g status-right-length 40 #### NAVIGATION # With C-a prefix bind h select-pane -L # navigate left with h bind j select-pane -D # navigate down with j bind k select-pane -U # navigate up with k bind l select-pane -R # navigate right with l bind -r H resize-pane -L 5 # resize pane left with H bind -r J resize-pane -D 5 # resize pane down with J bind -r K resize-pane -U 5 # resize pane up with K bind -r L resize-pane -R 5 # resize pane right with L # Navigate panes with Meta (alt) modifier + hjkl bind -r -n M-h select-pane -L # navigate left with M-h bind -r -n M-j select-pane -D # navigate down with M-j bind -r -n M-k select-pane -U # navigate up with M-k bind -r -n M-l select-pane -R # navigate right with M-l bind -r -n M-H resize-pane -L 5 # resize pane left with M-H bind -r -n M-J resize-pane -D 5 # resize pane down with M-J bind -r -n M-K resize-pane -U 5 # resize pane up with M-K bind -r -n M-L resize-pane -R 5 # resize pane right with M-L # Navigate windows with Meta (alt) modifier + number keys bind -n M-1 select-window -t :=1 bind -n M-2 select-window -t :=2 bind -n M-3 select-window -t :=3 bind -n M-4 select-window -t :=4 bind -n M-5 select-window -t :=5 bind -n M-6 select-window -t :=6 bind -n M-7 select-window -t :=7 bind -n M-8 select-window -t :=8 bind -n M-9 select-window -t :=9 bind -n M-0 select-window -t :=10 bind C-s last-window # go to last window with bind C-a last-pane # go to last pane with bind -n M-s last-window # go to last pane with M-s bind -n M-a last-pane # go to last pane with M-a bind -r n next-window # next window with n bind -r b previous-window # next window with p bind -n -r M-n next-window # next window with bind -n -r M-b previous-window # previous window with #bind -n M-, run-shell "tmux list-panes -as -F \"##I.##P ##{pane_current_command} . #{pane_current_path} (#W)#F\" | fzf-tmux | cut -d \" \" -f 1 | xargs tmux select-pane -t" bind -n M-, run-shell "tmux list-windows -F \"##I:##W\" | fzf-tmux | cut -d \":\" -f 1 | xargs tmux select-window -t" bind -n M-. run-shell "tmux list-sessions -F \"##S\" | fzf-tmux | xargs tmux switch -t" #### LAYOUT CHANGING BINDINGS # create panes in same directory bind '"' split-window -c "#{pane_current_path}" bind '%' split-window -h -c "#{pane_current_path}" bind -r z resize-pane -Z # toggle pane zoom with z bind -r y next-layout # cycle to next pane layout with y bind -r Y previous-layout # cycle to previous pane layout with Y bind -r r rotate-window # rotate panes with r bind -n M-z resize-pane -Z # toggle pane zoom with bind -n -r M-y next-layout # cycle to next pane layout with bind -n -r M-Y previous-layout # cycle to previous pane layout with bind -n -r M-r rotate-window # rotate panes with bind -r Left swap-window -t -1 # Swap window left bind -r Right swap-window -t +1 # Swap window right bind -r B swap-window -t -1 # Swap window left bind -r N swap-window -t +1 # Swap window right bind -n -r M-B swap-window -t -1 # Swap window left bind -n -r M-N swap-window -t +1 # Swap window right #### CLIPBOARD # enable reattach-to-user-namespace which fixes pasteboard access and launchctl bind Space copy-mode # enter copy mode with bind -n M-u copy-mode # enter copy mode with M-u bind -T copy-mode-vi M-u send -X halfpage-up # scroll up with M-u bind -T copy-mode-vi M-d send -X halfpage-down # scroll down with M-d bind -T copy-mode-vi v send -X begin-selection # start "visual" with v # Copy (yank) with y if-shell 'uname | grep -qi Linux && which xclip > /dev/null' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "DISPLAY=:0 xclip -i -sel clipboard"' if-shell 'uname | grep -qi Darwin' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"' if-shell 'uname | grep -qi Cygwin' 'bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "cat > /dev/clipboard"' # Paste with C-a p or M-p if-shell 'uname | grep -qi Linux && which xclip > /dev/null' 'bind p run "DISPLAY=:0 xclip -o | tmux load-buffer - ; tmux paste-buffer"' if-shell 'uname | grep -qi Darwin && which reattach-to-user-namespace > /dev/null' 'bind p run "pbpaste | tmux load-buffer - ; tmux paste-buffer"' if-shell 'uname | grep -qi Darwin' 'bind -n M-p run "pbpaste | tmux load-buffer - ; tmux paste-buffer"' if-shell 'uname | grep -qi Cygwin' 'bind p run "cat /dev/clipboard | tmux load-buffer - ; tmux paste-buffer"' if-shell 'uname | grep -qi Cygwin' 'bind -n M-p run "cat /dev/clipboard | tmux load-buffer - ; tmux paste-buffer"' #### LAUNCH PROCESSES # use urlview to follow URLs in current pane bind u capture-pane -J \; \ save-buffer "/tmp/active_tmux_buffer" \; \ delete-buffer \; \ split-window -l 10 "urlview '/tmp/active_tmux_buffer' && rm /tmp/active_tmux_buffer" # Launch offlineimap in inactive splits bind o split-window -p 25 '$SHELL -c "offlineimap -qf INBOX"' \; select-pane -l bind O split-window -p 25 '$SHELL -c "offlineimap"' \; select-pane -l # Use nested bindings (l) for grouping process launch bindings bind -n C-M-v new-window -n vim "/usr/local/bin/vim" bind -n C-M-w new-window -n weather \ "curl 'wttr.in/?m'; echo -e '\nPress to quit'; read -n 1 -s" # Open new window and resize status accordingly (should be a hook instead) bind Enter new-window -c "#{pane_current_path}" "$SHELL" bind -n M-Enter new-window \ "tmux set status-right-length `echo $(tput cols)/2|bc|tr -d '\n'`; zsh" # Use nested bindings (m) for grouping music-control bindings bind m switchc -Tmpd bind -n M-m switchc -Tmpd bind -Tmpd v new-window -n vimpc "vimpc" bind -Tmpd p display "#(mpc toggle | tr '\n' ' ')" bind -Tmpd s display "#(mpc stop | tr '\n' ' ')" bind -Tmpd n display "#(mpc next | tr '\n' ' ')" bind -Tmpd b display "#(mpc prev | tr '\n' ' ')" bind -Tmpd r display "#(mpc clear && mpc ls | mpc add && mpc random on && mpc play | tr '\n' ' ')" #bind -Tmpd r new-window -n mpc "mpc clear && mpc ls | mpc add && mpc shuffle && mpc play" bind -n C-M-p display "#(mpc toggle | tr '\n' ' ')" bind -n C-M-s display "#(mpc stop | tr '\n' ' ')" bind -n C-M-n display "#(mpc next | tr '\n' ' ')" bind -n C-M-b display "#(mpc prev | tr '\n' ' ')" #bind -n C-M-r display "#(mpc clear && mpc ls | mpc add && mpc random on && mpc play | tr '\n' ' ')" bind -n C-M-a split-window -p 50 "source ~/code/fzf-mpd/fzf-mpd.zsh && fm" # fzf-locate from entire file system and insert result in current pane (Alt-`) bind -n 'M-`' run "tmux split-window -p 40 'tmux send-keys -t #{pane_id} \"$(locate / | fzf -m | paste -sd\\ -)\"'" # Change to the previous pane, repeat the last command, change back bind -n M-! last-pane \; send-keys C-p C-m \; last-pane tmux-tmux-f222026/regress/conf/d41d8cd98f00b204e9800998ecf8427e.conf000066400000000000000000000103151511153563100235400ustar00rootroot00000000000000set-option -g prefix C-a unbind-key C-b bind-key C-a send-prefix set-option -s set-clipboard on set -sg escape-time 0 set -g bell-action other set -g lock-after-time 1800 set -g lock-command 'tput civis && read -s -n1' set -g history-limit 10000 set -g default-terminal "screen-256color" set -g pane-border-style fg=white,bg=default set -g pane-active-border-style fg=red,bg=default set -g repeat-time 100 set -g terminal-overrides "xterm*:kLFT5=\eOD:kRIT5=\eOC:kUP5=\eOA:kDN5=\eOB:smkx@:rmkx@:Tc" #set -g terminal-overrides '*88col*:colors=88,*256col*:colors=256,rxvt-uni*:Tc:XT:Ms=\E]52;%p1%s;%p2%s\007:Cc=\E]12;%p1%s\007:Cr=\E]12;green\007:Cs=\E]777;Cs;%p1%d\007' set -g mouse on set -g status-style bg=blue,fg=cyan set-hook -g alert-bell 'run -b "notify-send \"Bell in session #{session_name}:#{window_index}:#{window_name}\""' unbind-key / unbind-key c unbind-key d unbind-key f unbind-key i unbind-key l unbind-key n unbind-key o unbind-key p unbind-key r unbind-key s unbind-key t unbind-key w unbind-key x unbind-key | unbind-key - unbind-key A unbind-key S unbind-key . unbind-key "'" unbind-key '#' unbind-key ' ' unbind-key z unbind-key ^z bind a send-prefix bind c new-window -a -c '#{pane_current_path}' bind d detach-client bind "/" command-prompt "find-window '%%'" bind i display-message bind a last-window bind n next-window bind o select-pane -D bind p previous-window bind r respawn-window bind s choose-tree -Z bind t clock-mode bind w choose-window bind k confirm-before kill-pane bind x set lock-command '/usr/bin/vlock' \; lock-client \; set lock-command 'tput civis && read -s -n1' bind "|" split-window -v -c '#{pane_current_path}' bind "-" split-window -h -c '#{pane_current_path}' bind l command-prompt "rename-window '%%'" bind S command-prompt "rename-session '%%'" bind . display-panes bind "'" command-prompt -p "SSH: " "new-window -n %1 'ssh %1'" bind ' ' choose-window bind z resize-pane -Z bind ^a last-window bind ^c new-window -a -c '#{pane_current_path}' bind ^d detach-client bind ^i display-message bind a last-window bind ^n next-window bind ^o select-pane -D bind ^p previous-window bind ^r respawn-window bind ^s choose-session bind ^t clock-mode bind ^w choose-window bind ^k confirm-before kill-pane bind ^x lock-client bind ^S command-prompt "rename-session '%%'" bind ^z resize-pane -Z bind -n C-Left previous-window bind -n C-Right next-window bind -n C-s set status bind -r C-Left swapw -t:- bind -r C-Right swapw -t:+ # Status stuff. set -g status-left-style "fg=white, bg=magenta" set -g status-left-length 30 set -g status-left "#S " set -g status-right-length 30 set -g status-right-fg white set -g status-right-bg blue set -g status-right "#{?client_prefix,#[reverse][^a]#[noreverse],}[%a %d/%m %H:%M]" set -g display-panes-time 4000 set -g window-status-bell-style reverse #setw -g window-status-current-fg white #setw -g window-status-current-bg colour34 setw -g mode-keys vi setw -g window-status-separator "| " #setw -g window-status-format "#[bg=blue]#I:#W:#{window_flags}#[bg=default]" #setw -g window-status-current-format "#[fg=black,bg=green]#I:#W:#{window_flags}" setw -g window-status-format "#[bg=blue]#I:#W:#{?window_linked,+#{window_flags},#{window_flags} }#[bg=default]" setw -g window-status-current-format "#[fg=black,bg=green]#I:#W:#{?window_linked,+#{window_flags},#{window_flags}}" set-window-option -g clock-mode-colour green # Sessions new -d -sspecial new -d -swork -d -nmutt 'exec neomutt' neww -d neww -d neww -d neww -d # FIXME -- the entire block below is required for taskwarrior. #new -d -stask -ntask -x237 -y 79 #selectl -ttask tiled #set -ttask status off #splitw -ttask:task #splitw -ttask:task #splitw -ttask:task #splitw -ttask:task #splitw -ttask:task #selectl -ttask:task 4ada,237x79,0,0[237x67,0,0{156x67,0,0,5,80x67,157,0[80x27,157,0,19,80x22,157,28,20,80x16,157,51,21]},237x11,0,68,22] #send -ttask:task.0 'cyclenext list' 'C-m' #send -ttask:task.1 'clear ; tasksh' 'C-m' #send -ttask:task.2 'cyclenext summary' 'C-m' #send -ttask:task.3 'cyclenext burndown.daily' 'C-m' #send -ttask:task.4 'cyclenext ghistory.monthly' 'C-m' #selectp -ttask:task.1 #linkw -stask:task -twork #set -t task:task remain-on-exit on set -t work:irc remain-on-exit on set -t work:mutt remain-on-exit on tmux-tmux-f222026/regress/conf/dfd579a114a8366b5a665c264e29c084.conf000066400000000000000000000015471511153563100234550ustar00rootroot00000000000000set -as terminal-overrides '\e\r\n\t\u12ab\U000012ab' set -as terminal-overrides "\e\r\n\t\u12ab\U000012ab" # format #{abc #{def}} # abc set -g status-left \ "\u007c \ abc" %if #{TMUX} set -g status-bg red %endif X=1 Y=2 set -g status-bg blue; Z=3 set -g status-bg magenta set -g status-left "~/abcdef"$HOME # abcdef %if #{l:1} set -g status-bg red %endif %if #{l:0} X=1 %elif #{l:1} Y=1 %if #{l:0} Y=2 %else Y=3 %endif %endif bind x display-message \"hello\" bind c neww -c ~ bind ';' lsk set -g status-left "a""b" set -g status-left ~ set -g status-left 'a $HOME b ~ c \e\e\e' set -g status-left "a $HOME b ~ c \e\e\e" set -s command-alias[99] "foo=lsk;neww" bind-key -n C-s if-shell 'true' 'display-message hello' set -g status-left-style \ bg=red set -g status-left \\\ abc set -g status-left 'xyz' ; %if #{l:1} set -g status-bg red %endif ; bind x lsk tmux-tmux-f222026/regress/conf/e2661d67d0d45a8647fb95de76ec8174.conf000066400000000000000000000107701511153563100235520ustar00rootroot00000000000000# Scott Rochford's tmux configuration # # change the prefix to the GNU screen default (avoids clash with page up in vi) set -g prefix C-a unbind-key C-b bind-key C-a send-prefix # toggle sending input to all panes bind-key b set-window-option synchronize-panes # alternative to ',' which doesn't pre-fill the prompt with the existing name bind-key < command-prompt "rename-window '%%'" # Disabled all of these in favour of changing 'default-command' below. #bind-key C-p pipe-pane -o 'cat >>~/tmux_logs/output.$(echo #I-#P-#W-#T | sed "s/[^[:alnum:].-]/_/g")' \; display-message 'Toggled logging' # From http://unix.stackexchange.com/questions/5832/is-there-an-equivalent-of-gnu-screens-log-command-in-tmux # bind-key H pipe-pane -o "exec cat >>$HOME/'#W-tmux.log'" \; display-message 'Toggled logging to $HOME/#W-tmux.log' #bind-key H pipe-pane "exec cat >>$HOME/'#W-tmux.log'" \; display-message 'Started logging to $HOME/#W-tmux.log' #bind-key h pipe-pane \; display-message 'Ended logging to $HOME/#W-tmux.log' #set -g utf8 on set-option -g history-limit 32768 # no longer available in 2.2 #set-option -g mouse-select-pane on #set-option -g mouse-select-window on set-option -g mouse on # increase the amount of time status bar messages are displayed for (default 1000 I think) set-option -g display-time 1500 # unfortunately this seems to have no effect in putty :-( set-option -g set-clipboard on set-option -g default-command 'tmux pipe-pane -o "cat >>~/tmux_logs/output-`date +%Y%m%d-%H%M%S-$$`" ; /bin/ksh -l' # # allow yank into system clipboard # from http://stackoverflow.com/questions/17255031/how-to-copy-from-tmux-running-in-putty-to-windows-clipbard # # for some reason this is wrapping at 80 cols, using save- instead of show- helps # -b for background is needed because xclip continues to run to service the clipboard paste requests until the # clipboard buffer is replaced with some new contents #bind C-y run-shell -b "tmux save-buffer - | DISPLAY=$(<~/.xdisplay) xclip -selection clipboard -in && tmux display-message 'xclipped successfully'" bind C-y save-buffer ~/etc/clipboard.pipe # # this was just for testing, but interestingly for some reason tmux-show-buffer >/tmp/t never terminates, writing to a pipe works fine?? #bind C-z run-shell "tmux show-buffer | cat >/tmp/t" # move x clipboard into tmux paste buffer #bind C-p run-shell -b "xclip -o -selection clipboard | tmux load-buffer - ; tmux paste-buffer" bind C-p run-shell "DISPLAY=$(<~/.xdisplay) xclip -o -selection clipboard | tmux load-buffer - ; tmux paste-buffer" # switch to last-but-one window (like prefix-l but last, last) # only works on tmux-2.4 + with Nicholas Marriott's patch from my feature request, unless it reached mainline.... #bind k run-shell "tmux select-window -t $(tmux list-windows -F '#{session_stack}' | awk -F, '{print $3;exit}END{print $1}')" bind k run-shell "tmux select-window -t $(echo #{session_stack} | awk -F, '{w=$1}NF>=3{w=$3;exit}END{print w}')" # switch to oldest window (for clean-up), not sure why brackets are required around (NF) here... bind K run-shell "tmux select-window -t $(echo #{session_stack} | awk -F, '{print $(NF)}')" # prompt for hosts to connect to, open a new synchronized window with horizontally split panes for each host, supports brace expansion bind N command-prompt -p hosts: 'run-shell -b "bash -c \"~/lbin/nw %% >/dev/null\""' # seems to cause unexpected resizes when focussing on putty :-( #set-option mouse-resize-pane on #05:59 < Celti> annihilannic: I believe the #{pane_in_mode} format does what you want #05:59 < Celti> put it in your statusline #05:59 < Celti> annihilannic: No, my mistake, I should have read farther down, you want #{pane_synchronized} # only works in tmux 2.0?, higher than 1.6.3 anyway set-option -g window-status-format ' #I:#W#F#{?pane_synchronized,S,}' #set-option -g window-status-current-format ' #I:#W#{?pane_synchronized,[sync],}#F' # to highlight in red when sync is on... not sure why I did this with set-window-option instead of set-option, perhaps # both work? set-window-option -g window-status-current-format "#{?pane_synchronized,#[bg=red],}#{?window_zoomed_flag,#[bg=yellow],} #I:#W#F#{?pane_synchronized,S,}" # # also only in 2.0? if I use this, don't need #F in window-status-*-format? - actually, nah, # still useful for showing [Z]oomed, or - last active, etc. set-option -g window-status-current-style bg=blue # Toggle input on a pane (from Thomas Sattler) bind-key R if -F '#{pane_input_off}' "select-pane -e; select-pane -P fg=default" "select-pane -d; select-pane -P fg=yellow" tmux-tmux-f222026/regress/conf/ed08995f38b5a3079262a88d2563abe4.conf000066400000000000000000000276261511153563100234710ustar00rootroot00000000000000#---------------------------------------------------------------------------# # .tmux.conf # Helmut K. C. Tessarek, Last update 2018-10-16 #---------------------------------------------------------------------------# #---------------------------------------------------------------------------# # set prefix key to ctrl+a / ctrl-b is used in vi for going back one page #---------------------------------------------------------------------------# unbind C-b set -g prefix C-a #---------------------------------------------------------------------------# # send the prefix to client inside window (nested sessions) #---------------------------------------------------------------------------# bind-key a send-prefix #---------------------------------------------------------------------------# # toggle last window like screen #---------------------------------------------------------------------------# bind-key C-a last-window #---------------------------------------------------------------------------# # start window indexing at one instead of zero #---------------------------------------------------------------------------# #set -g base-index 1 #---------------------------------------------------------------------------# # default terminal - we want 256 colors !!! #---------------------------------------------------------------------------# set -g default-terminal "screen-256color" #---------------------------------------------------------------------------# # on-screen time for status messages in ms #---------------------------------------------------------------------------# set -g display-time 2000 #---------------------------------------------------------------------------# # on-screen time for display-panes in ms #---------------------------------------------------------------------------# set -g display-panes-time 2000 #---------------------------------------------------------------------------# # color for display pane indicator #---------------------------------------------------------------------------# set -g display-panes-colour "cyan" #set -g display-panes-active-colour "#0087ff" #set -g display-panes-active-colour "red" #---------------------------------------------------------------------------# # open a man page in new window #---------------------------------------------------------------------------# unbind m bind m command-prompt "split-window 'exec man %%'" #---------------------------------------------------------------------------# # quick view of processes #---------------------------------------------------------------------------# #bind '~' split-window "exec htop" #---------------------------------------------------------------------------# # scrollback buffer n lines #---------------------------------------------------------------------------# set -g history-limit 5000 #---------------------------------------------------------------------------# # toggle status bar #---------------------------------------------------------------------------# unbind b bind-key b set-option status #---------------------------------------------------------------------------# # resize panes like vim # feel free to change the "1" to however many lines you want to resize by, # only one at a time can be slow #---------------------------------------------------------------------------# unbind < unbind > unbind - unbind + bind -r < resize-pane -L 1 bind -r > resize-pane -R 1 bind -r - resize-pane -D 1 bind -r + resize-pane -U 1 #---------------------------------------------------------------------------# # toggle mouse helpers #---------------------------------------------------------------------------# unbind Enter unbind C-m bind C-m set-option mouse \; display-message 'mouse -> #{?mouse,on,off}' #---------------------------------------------------------------------------# # Reload config file #---------------------------------------------------------------------------# unbind R bind-key R source-file ~/.tmux.conf \; display-message "Reloading configuration done" #---------------------------------------------------------------------------# # start ssh session in new window #---------------------------------------------------------------------------# unbind S bind-key S command-prompt "new-window -n %1 'ssh %1'" #---------------------------------------------------------------------------# # start new session #---------------------------------------------------------------------------# unbind C bind-key C command-prompt "new-session -s %1" #---------------------------------------------------------------------------# # Keys to switch session #---------------------------------------------------------------------------# bind Q switchc -t0 bind W switchc -t compile bind E switchc -t config #---------------------------------------------------------------------------# # break pane in background #---------------------------------------------------------------------------# unbind '@' bind '@' break-pane -d #---------------------------------------------------------------------------# # join pane with target window #---------------------------------------------------------------------------# unbind ^ bind ^ command-prompt "join-pane -t %1" #---------------------------------------------------------------------------# # move around panes with hjkl, as one would in vim after pressing ctrl-w #---------------------------------------------------------------------------# #bind h select-pane -L #bind j select-pane -D #bind k select-pane -U #bind l select-pane -R #---------------------------------------------------------------------------# # bind : to command-prompt like vim # this is the default in tmux already #---------------------------------------------------------------------------# bind : command-prompt #---------------------------------------------------------------------------# # Remain on exit #---------------------------------------------------------------------------# #setw -g remain-on-exit on #---------------------------------------------------------------------------# # vi-style controls for copy mode #---------------------------------------------------------------------------# setw -g mode-keys vi #---------------------------------------------------------------------------# # Make mouse useful in copy mode #---------------------------------------------------------------------------# #setw -g mode-mouse on #---------------------------------------------------------------------------# # More straight forward key bindings for splitting #---------------------------------------------------------------------------# unbind % unbind v #bind | split-window -h bind v split-window -h unbind '"' unbind h #bind - split-window -v bind h split-window -v #---------------------------------------------------------------------------# # Synchronize panes #---------------------------------------------------------------------------# unbind y bind y set-window-option synchronize-panes \; display-message 'synchronize-panes -> #{?synchronize-panes,on,off}' #---------------------------------------------------------------------------# # Other key codes: Tab, BTab, Escape #---------------------------------------------------------------------------# #---------------------------------------------------------------------------# # Clock #---------------------------------------------------------------------------# setw -g clock-mode-colour green setw -g clock-mode-style 24 #---------------------------------------------------------------------------# # Terminal emulator window title #---------------------------------------------------------------------------# set -g set-titles on set -g set-titles-string '#S:#I.#P #W' #---------------------------------------------------------------------------# # Status Bar #---------------------------------------------------------------------------# set -g status-bg black set -g status-fg white set -g status-interval 1 set -g status-left-length 30 set -g status-left '#[fg=green]#h#[default] ' #set -g status-right '#[fg=yellow]#(cut -d " " -f 1-4 /proc/loadavg)#[default] #[fg=cyan,bold]%Y-%m-%d %H:%M:%S#[default]' #set -g status-right '#[fg=yellow,bold]%Y-%m-%d %H:%M#[default]' set -g status-right '#[fg=yellow]%Y-%m-%d %H:%M %Z#[default]' #set -g status-justify center #set -g status-keys vi set -g allow-rename off setw -g automatic-rename on #---------------------------------------------------------------------------# # Highlighting the active window in status bar #---------------------------------------------------------------------------# #setw -g window-status-current-bg red set-option -g window-status-format "#I:#W#F#{?pane_synchronized,S,}" set-window-option -g window-status-current-format "#{?pane_synchronized,#[bg=red],}#{?window_zoomed_flag,#[bg=colour130],}#I:#W#F#{?pane_synchronized,S,}" set-option -g window-status-current-style bg=blue #---------------------------------------------------------------------------# # global update environment #---------------------------------------------------------------------------# set -g update-environment "DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY TZ" #---------------------------------------------------------------------------# # settings for AIX # terminal overrides to enable colors # set default terminal to vt100 or xterm (screen does not exist on AIX) #---------------------------------------------------------------------------# if-shell "uname|grep AIX" 'set -g terminal-overrides "xterm*:XT,xterm*:setab=\\E[4%p1%dm,xterm*:setaf=\\E[3%p1%dm"' #if-shell "uname|grep AIX" "set -g default-terminal vt100" if-shell "uname|grep AIX" "set -g default-terminal xterm" #---------------------------------------------------------------------------# # settings for macOS #---------------------------------------------------------------------------# if-shell "uname|grep Darwin" 'set -g default-command "/bin/bash -l"' #---------------------------------------------------------------------------# # Pane coloring # set inactive/active window styles #---------------------------------------------------------------------------# set -g window-style "fg=colour247,bg=colour234" set -g window-active-style "fg=colour250,bg=black" set -g @TPCS "1" #---------------------------------------------------------------------------# # pane border - different style / use cyan #---------------------------------------------------------------------------# #set -g pane-border-bg colour235 #set -g pane-border-fg colour238 #set -g pane-active-border-bg colour234 #set -g pane-active-border-fg colour51 #---------------------------------------------------------------------------# # toggle pane coloring on/off #---------------------------------------------------------------------------# unbind C-b bind C-b if -F '#{@TPCS}' \ 'set -g window-style "fg=default,bg=default" ; set -g window-active-style "fg=default,bg=default" ; set -g @TPCS "0"; display-message "Pane coloring -> off"' \ 'set -g window-style "fg=colour247,bg=colour234" ; set -g window-active-style "fg=colour250,bg=black" ; set -g @TPCS "1"; display-message "Pane coloring -> on"' #---------------------------------------------------------------------------# # List of plugins #---------------------------------------------------------------------------# set -g @plugin 'tmux-plugins/tpm' #set -g @plugin 'tmux-plugins/tmux-sensible' set -g @plugin 'tmux-plugins/tmux-resurrect' set -g @plugin 'tmux-plugins/tmux-logging' set -g @resurrect-capture-pane-contents 'on' set -g @resurrect-save-bash-history 'on' set -g @logging-path $HOME set -g @screen-capture-path $HOME set -g @save-complete-history-path $HOME # Other examples: # set -g @plugin 'github_username/plugin_name' # set -g @plugin 'git@github.com/user/plugin' # set -g @plugin 'git@bitbucket.com/user/plugin' #---------------------------------------------------------------------------# # Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) #---------------------------------------------------------------------------# run '~/.tmux/plugins/tpm/tpm' tmux-tmux-f222026/regress/conf/f4f1cdb9d518c2f7808a4915299f2524.conf000066400000000000000000000032421511153563100234600ustar00rootroot00000000000000bind m-4 run -C '#{@layout-vertical-two}' set -g @layout-vertical-two { selectl main-vertical if -F '#{==:#{@vertical-two-active},true}' { set -wu @vertical-two-active } { if -F '#{&&:#{==:#{N/s:layout_overflow},0},#{e|>=:#{n:#{P:x}},3}}' { run -C '#{@layout-vertical-two-init}' } } } set -g @layout-vertical-two-init { set -gF @total_panes '#{n:#{P:x}}' set -gF @cur_window '#S:#I' new -ds layout_overflow run -C '\ swapw -t layout_overflow: -s . ;\ splitw -fh -l 40% -t #{@cur_window} ;\ splitw -h -t #{@cur_window}.2 ;\ swapp -s #{@cur_window}.1 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ swapp -s #{@cur_window}.2 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ swapp -s #{@cur_window}.3 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ #{@layout-vertical-two-loop}' } set -g @layout-vertical-two-cleanup { set -gu @cur_window set -gu @total_panes '#{n:#{P:x}}' set -w @vertical-two-active true selectp -t .1 } # (x - 1) % 2 == 0 ? (x - 1) / 2 + 1 : x # #{?#{==:#{e|%:#{e|-:#{cur_panes},1},2},0} <-- TODO: inserting horizontally shuffles windows. # ,#{e|+:#{e|/:#{e|-:#{cur_panes},1},2},1} <-- end of first column # ,#{cur_panes}} <-- end of second column set -g @layout-vertical-two-loop { # count(panes) < count(original.panes) if -F '#{e|<:#{n:#{P:x}},#{@total_panes}}' { run -C "joinp -s layout_overflow:1.1 -vt '#{@cur_window}.#{?#{==:#{e|%:#{e|-:#{#{n:#{P:x}}},1},2},0},#{e|+:#{e|/:#{e|-:#{#{n:#{P:x}}},1},2},1},#{#{n:#{P:x}}}}' ;\ selectl -E ; #{@layout-vertical-two-loop}" } { run -C '#{@layout-vertical-two-cleanup}' } } tmux-tmux-f222026/regress/control-client-sanity.sh000066400000000000000000000014571511153563100222260ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x200 -y200 || exit 1 $TMUX -f/dev/null splitw || exit 1 sleep 1 cat <$TMP refresh-client -C 200x200 selectp -t%0 splitw neww splitw selectp -t%0 killp -t%1 swapp -t%2 -s%3 neww splitw splitw selectl tiled killw EOF sleep 1 $TMUX has || exit 1 $TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1 cat </dev/null exit 0 tmux-tmux-f222026/regress/control-client-size.sh000066400000000000000000000022661511153563100216700ustar00rootroot00000000000000#!/bin/sh # 947 # size in control mode should change after refresh-client -C, and -x and -y # should work without -d for control clients PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) OUT=$(mktemp) trap "rm -f $TMP $OUT" 0 1 15 $TMUX -f/dev/null new -d || exit 1 sleep 1 cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 100,50 EOF grep ^: $TMP >$OUT $TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":80 24\n:100 50\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null $TMUX -f/dev/null new -d || exit 1 sleep 1 cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 80,24 EOF grep ^: $TMP >$OUT $TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":80 24\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null cat <$TMP ls -F':#{window_width} #{window_height}' refresh -C 80,24 EOF grep ^: $TMP >$OUT $TMUX ls -F':#{window_width} #{window_height}' >>$OUT printf ":100 50\n:80 24\n"|cmp -s $OUT - || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/copy-mode-test-emacs.sh000066400000000000000000000075361511153563100217300ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -f/dev/null -Ltest" $TMUX kill-server 2>/dev/null $TMUX new -d -x40 -y10 \ "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 # Enter copy mode and go to the first column of the first row. $TMUX set-window-option -g mode-keys emacs $TMUX set-window-option -g word-separators "" $TMUX copy-mode $TMUX send-keys -X history-top $TMUX send-keys -X start-of-line # Test that `previous-word` and `previous-space` # do not go past the start of text. $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X previous-space $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer 2>/dev/null)" = "" ] || exit 1 # Test that `next-word-end` does not skip single-letter words. $TMUX send-keys -X next-word-end $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "A" ] || exit 1 # Test that `next-word-end` wraps around indented line breaks. $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "words\n\tIndented")" ] || exit 1 # Test that `next-word` wraps around un-indented line breaks. $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "line\n")" ] || exit 1 # Test that `next-word-end` treats periods as letters. $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "line..." ] || exit 1 # Test that `previous-word` and `next-word` treat periods as letters. $TMUX send-keys -X previous-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "line...\n")" ] || exit 1 # Test that `previous-space` and `next-space` treat periods as letters. $TMUX send-keys -X previous-space $TMUX send-keys -X begin-selection $TMUX send-keys -X next-space $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "line...\n")" ] || exit 1 # Test that `next-word` and `next-word-end` treat other symbols as letters. $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "... @nd then \$ym_bols[]{}" ] || exit 1 # Test that `previous-word` treats other symbols as letters # and `next-word` wraps around for indented symbols $TMUX send-keys -X previous-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "\$ym_bols[]{}\n ")" ] || exit 1 # Test that `next-word-end` treats digits as letters $TMUX send-keys -X next-word-end $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = " 500xyz" ] || exit 1 # Test that `previous-word` treats digits as letters $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 # Test that `next-word` and `next-word-end` stop at the end of text. $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word $TMUX send-keys -X next-space $TMUX send-keys -X next-space-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/copy-mode-test-vi.sh000066400000000000000000000074161511153563100212530ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -f/dev/null -Ltest" $TMUX kill-server 2>/dev/null $TMUX new -d -x40 -y10 \ "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 # Enter copy mode and go to the first column of the first row. $TMUX set-window-option -g mode-keys vi $TMUX copy-mode $TMUX send-keys -X history-top $TMUX send-keys -X start-of-line # Test that `previous-word` and `previous-space` # do not go past the start of text. $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X previous-space $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "A" ] || exit 1 # Test that `next-word-end` skips single-letter words # and `previous-word` does not skip multi-letter words. $TMUX send-keys -X next-word-end $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "line" ] || exit 1 # Test that `next-word-end` wraps around indented line breaks. $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "words\n\tIndented")" ] || exit 1 # Test that `next-word` wraps around un-indented line breaks. $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "line\nA")" ] || exit 1 # Test that `next-word-end` does not treat periods as letters. $TMUX send-keys -X next-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "line" ] || exit 1 # Test that `next-space-end` treats periods as letters. $TMUX send-keys -X previous-word $TMUX send-keys -X begin-selection $TMUX send-keys -X next-space-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "line..." ] || exit 1 # Test that `previous-space` and `next-space` treat periods as letters. $TMUX send-keys -X previous-space $TMUX send-keys -X begin-selection $TMUX send-keys -X next-space $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "line...\n.")" ] || exit 1 # Test that `next-word` and `next-word-end` do not treat other symbols as letters. $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X next-word $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "... @nd then" ] || exit 1 # Test that `next-space` wraps around for indented symbols $TMUX send-keys -X next-space $TMUX send-keys -X begin-selection $TMUX send-keys -X next-space $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "$(printf "\$ym_bols[]{}\n ?")" ] || exit 1 # Test that `next-word-end` treats digits as letters $TMUX send-keys -X next-word-end $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "? 500xyz" ] || exit 1 # Test that `previous-word` treats digits as letters $TMUX send-keys -X begin-selection $TMUX send-keys -X previous-word $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 # Test that `next-word`, `next-word-end`, # `next-space`, and `next-space-end` stop at the end of text. $TMUX send-keys -X begin-selection $TMUX send-keys -X next-word $TMUX send-keys -X next-word-end $TMUX send-keys -X next-word $TMUX send-keys -X next-space $TMUX send-keys -X next-space-end $TMUX send-keys -X copy-selection [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/copy-mode-test.txt000066400000000000000000000001241511153563100210310ustar00rootroot00000000000000A line of words Indented line Another line... ... @nd then $ym_bols[]{} ?? 500xyz tmux-tmux-f222026/regress/cursor-test.txt000066400000000000000000000006761511153563100204660ustar00rootroot00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. tmux-tmux-f222026/regress/cursor-test1.result000066400000000000000000000015031511153563100212340ustar00rootroot0000000000000014 8 t 0 ud exercitation ullamco laboris nisi ut 1 aliquip ex ea 2 commodo consequat. Duis aute 3 irure dolor in reprehenderit in voluptat 4 e velit esse cillum dolore eu fugiat 5 nulla pariatur. Excepteur sint occaecat 6 cupidatat non proident, sunt in culpa qu 7 i officia 8 deserunt mollit anim id est laborum. 9 4 6 t 0 cupidatat 1 non proide 2 nt, sunt i 3 n culpa qu 4 i officia 5 deserunt m 6 ollit anim 7 id est la 8 borum. 9 14 8 t 0 incididunt ut labore et dolore magna aliqua. Ut en 1 im ad minim veniam, quis nostrud exercitation ulla 2 mco laboris nisi ut aliquip ex ea 3 commodo consequat. Duis aute 4 irure dolor in reprehenderit in voluptate velit es 5 se cillum dolore eu fugiat 6 nulla pariatur. Excepteur sint occaecat cupidatat 7 non proident, sunt in culpa qui officia 8 deserunt mollit anim id est laborum. 9 tmux-tmux-f222026/regress/cursor-test1.sh000066400000000000000000000015121511153563100203300ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -f/dev/null -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x40 -y10 \ "cat cursor-test.txt; printf '\e[9;15H'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x10 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x50 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP cmp -s $TMP cursor-test1.result || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/cursor-test2.result000066400000000000000000000011221511153563100212320ustar00rootroot000000000000009 7 a 0 cupidatat 1 non proide 2 nt, sunt i 3 n culpa qu 4 i officia 5 deserunt m 6 ollit anim 7 id est la 8 borum. 9 4 6 a 0 icia 1 deser 2 unt m 3 ollit 4 anim 5 id e 6 st la 7 borum 8 . 9 29 8 a 0 incididunt ut labore et dolore magna aliqua. Ut en 1 im ad minim veniam, quis nostrud exercitation ulla 2 mco laboris nisi ut aliquip ex ea 3 commodo consequat. Duis aute 4 irure dolor in reprehenderit in voluptate velit es 5 se cillum dolore eu fugiat 6 nulla pariatur. Excepteur sint occaecat cupidatat 7 non proident, sunt in culpa qui officia 8 deserunt mollit anim id est laborum. 9 tmux-tmux-f222026/regress/cursor-test2.sh000066400000000000000000000014751511153563100203410ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x10 -y10 \ "cat cursor-test.txt; printf '\e[8;10H'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x5 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x50 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP cmp -s $TMP cursor-test2.result || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/cursor-test3.result000066400000000000000000000001111511153563100212300ustar00rootroot000000000000006 1 b 0 abcdefa 1 bcdefab 3 1 b 0 fabcd 1 efab 6 1 b 0 abcdefa 1 bcdefab tmux-tmux-f222026/regress/cursor-test3.sh000066400000000000000000000014751511153563100203420ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x7 -y2 \ "printf 'abcdefabcdefab'; printf '\e[2;7H'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x5 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x7 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP cmp -s $TMP cursor-test3.result || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/cursor-test4.result000066400000000000000000000001211511153563100212320ustar00rootroot000000000000000 1 0 abcdef 1 2 0 1 0 abcdef 1 2 0 1 0 def 1 2 0 1 0 abcdef 1 2 tmux-tmux-f222026/regress/cursor-test4.sh000066400000000000000000000016621511153563100203410ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d -x10 -y3 "printf 'abcdef\n'; cat" || exit 1 $TMUX set -g window-size manual || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x20 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x3 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP $TMUX resizew -x10 || exit 1 $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP cmp -s $TMP cursor-test4.result || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/format-strings.sh000066400000000000000000000205351511153563100207420ustar00rootroot00000000000000#!/bin/sh # Tests of formats as described in tmux(1) FORMATS PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" # test_format $format $expected_result test_format() { fmt="$1" exp="$2" out=$($TMUX display-message -p "$fmt") if [ "$out" != "$exp" ]; then echo "Format test failed for '$fmt'." echo "Expected: '$exp'" echo "But got '$out'" exit 1 fi } # test_conditional_with_pane_in_mode $format $exp1 $exp2 # # Tests the format string $format to yield $exp1 if #{pane_in_mode} is true and # $exp2 when #{pane_in_mode} is false. test_conditional_with_pane_in_mode() { fmt="$1" exp_true="$2" exp_false="$3" $TMUX copy-mode # enter copy mode test_format "$fmt" "$exp_true" $TMUX send-keys -X cancel # leave copy mode test_format "$fmt" "$exp_false" } # test_conditional_with_session_name #format $exp_summer $exp_winter # # Tests the format string $format to yield $exp_summer if the session name is # 'Summer' and $exp_winter if the session name is 'Winter'. test_conditional_with_session_name() { fmt="$1" exp_summer="$2" exp_winter="$3" $TMUX rename-session "Summer" test_format "$fmt" "$exp_summer" $TMUX rename-session "Winter" test_format "$fmt" "$exp_winter" $TMUX rename-session "Summer" # restore default } $TMUX kill-server 2>/dev/null $TMUX -f/dev/null new-session -d || exit 1 # used later in conditionals $TMUX rename-session "Summer" || exit 1 $TMUX set @true 1 || exit 1 $TMUX set @false 0 || exit 1 $TMUX set @warm Summer || exit 1 $TMUX set @cold Winter || exit 1 # Plain string without substitutions et al test_format "abc xyz" "abc xyz" # Test basic escapes for "#", "{", "#{" "}", "#}", "," test_format "##" "#" test_format "#," "," test_format "{" "{" test_format "##{" "#{" test_format "#}" "}" test_format "###}" "#}" # not a "basic" one but interesting nevertheless # Simple expansion test_format "#{pane_in_mode}" "0" # Simple conditionals test_format "#{?}" "" test_format "#{?abc}" "abc" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc}" "abc" "" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,xyz}" "abc" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,@true,xyz}" "abc" "xyz" test_format "#{?@false,abc,@false,xyz}" "" test_format "#{?@false,abc,@false,xyz,default}" "default" # Expansion in conditionals test_format "#{?#{@warm}}" "Summer" test_conditional_with_pane_in_mode "#{?#{pane_in_mode},#{@warm}}" "Summer" "" test_conditional_with_pane_in_mode "#{?#{pane_in_mode},#{@warm},#{@cold}}" "Summer" "Winter" # Basic escapes in conditionals # Value of an (else-)if-condition test_conditional_with_pane_in_mode "#{?pane_in_mode,##,xyz}" "#" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,#,,xyz}" "," "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,{,xyz}" "{" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,##{,xyz}" "#{" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,#},xyz}" "}" "xyz" # not a "basic" one but interesting nevertheless test_conditional_with_pane_in_mode "#{?pane_in_mode,###},xyz}" "#}" "xyz" # Default value if no condition matches test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,##}" "abc" "#" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#,}" "abc" "," test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,{}" "abc" "{" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,##{}" "abc" "#{" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#}}" "abc" "}" # not a "basic" one but interesting nevertheless test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,###}}" "abc" "#}" # mixed test_conditional_with_pane_in_mode "#{?pane_in_mode,{,#}}" "{" "}" test_conditional_with_pane_in_mode "#{?pane_in_mode,#},{}" "}" "{" test_conditional_with_pane_in_mode "#{?pane_in_mode,##{,###}}" "#{" "#}" test_conditional_with_pane_in_mode "#{?pane_in_mode,###},##{}" "#}" "#{" # Curly brackets {...} do not capture a comma inside of conditionals as the # conditional ends on the first '}' test_conditional_with_pane_in_mode "#{?pane_in_mode,{abc,xyz},bonus}" "{abc,bonus}" "xyz,bonus}" # Substitutions '#{...}' capture the comma # invalid format: #{abc,xyz} is not a known variable name. #test_conditional_with_pane_in_mode "#{?pane_in_mode,#{abc,xyz},bonus}" "" "bonus" # Parenthesis (...) do not capture a comma, and "xyz)" is a false condition test_conditional_with_pane_in_mode "#{?pane_in_mode,(abc,xyz),bonus}" "(abc" "" test_conditional_with_pane_in_mode "#{?pane_in_mode,(abc#,xyz),bonus}" "(abc,xyz)" "bonus" # Brackets [...] do not capture a comma, and "xyz]" is a false condition test_conditional_with_pane_in_mode "#{?pane_in_mode,[abc,xyz],bonus}" "[abc" "" test_conditional_with_pane_in_mode "#{?pane_in_mode,[abc#,xyz],bonus}" "[abc,xyz]" "bonus" # Escape comma inside of #(...) Note: #() commands are run asynchronous and are # substituted with result of the *previous* run, an empty string if the command # is new, or a placeholder after a few seconds. The format is updated as soon # as the command finishes. As we are printing the message only once it never # gets updated and the displayed message is empty. test_format "#{?pane_in_mode,#(echo #,),xyz}" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "" "xyz" # This caching does not work :-( #$TMUX display-message -p "#(echo #,)" > /dev/null #test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "," "xyz" #test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo #,),xyz}" "," "xyz" # invalid format: '#(' is not closed in the first argument of #{?,,}. test_conditional_with_pane_in_mode "#{?pane_in_mode,#(echo ,)xyz}" "" ")xyz" # Escape comma inside of #[...] test_conditional_with_pane_in_mode "#{?pane_in_mode,#[fg=default#,bg=default]abc,xyz}" "#[fg=default,bg=default]abc" "xyz" # invalid style: '#[' is not closed in the first argument of #{?,,} test_conditional_with_pane_in_mode "#{?pane_in_mode,#[fg=default,bg=default]abc}" "#[fg=default" "bg=default]abc" # Conditionals with comparison test_conditional_with_session_name "#{?#{==:#{session_name},Summer},abc,xyz}" "abc" "xyz" # Conditionals with comparison and escaped commas $TMUX rename-session "," test_format "#{?#{==:#,,#{session_name}},abc,xyz}" "abc" $TMUX rename-session "Summer" # reset to default # Conditional in conditional test_conditional_with_pane_in_mode "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" "ABC" "xyz" test_conditional_with_session_name "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" "xyz" "xyz" test_conditional_with_pane_in_mode "#{?pane_in_mode,abc,#{?#{==:#{session_name},Summer},ABC,XYZ}}" "abc" "ABC" test_conditional_with_session_name "#{?pane_in_mode,abc,#{?#{==:#{session_name},Summer},ABC,XYZ}}" "ABC" "XYZ" # Some fancy stackings test_conditional_with_pane_in_mode "#{?#{==:#{?pane_in_mode,#{session_name},#(echo Spring)},Summer},abc,xyz}" "abc" "xyz" # Tests for boolean expressions # "0" and the empty string are false, everything else is true. test_format "#{!!:0}" "0" test_format "#{!!:}" "0" test_format "#{!!:1}" "1" test_format "#{!!:2}" "1" test_format "#{!!:non-empty string}" "1" test_format "#{!!:-0}" "1" test_format "#{!!:0.0}" "1" # Logical operators test_format "#{!:0}" "1" test_format "#{!:1}" "0" test_format "#{&&:0}" "0" test_format "#{&&:1}" "1" test_format "#{&&:0,0}" "0" test_format "#{&&:0,1}" "0" test_format "#{&&:1,0}" "0" test_format "#{&&:1,1}" "1" test_format "#{&&:0,0,0}" "0" test_format "#{&&:0,1,1}" "0" test_format "#{&&:1,0,1}" "0" test_format "#{&&:1,1,0}" "0" test_format "#{&&:1,1,1}" "1" test_format "#{||:0}" "0" test_format "#{||:1}" "1" test_format "#{||:0,0}" "0" test_format "#{||:0,1}" "1" test_format "#{||:1,0}" "1" test_format "#{||:1,1}" "1" test_format "#{||:0,0,0}" "0" test_format "#{||:1,0,0}" "1" test_format "#{||:0,1,0}" "1" test_format "#{||:0,0,1}" "1" test_format "#{||:1,1,1}" "1" # Format test for the literal option # Note: The behavior for #{l:...} with escapes is sometimes weird as #{l:...} # respects the escapes. test_format "#{l:#{}}" "#{}" test_format "#{l:#{pane_in_mode}}" "#{pane_in_mode}" test_format "#{l:#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}}" "#{?pane_in_mode,#{?#{==:#{session_name},Summer},ABC,XYZ},xyz}" # With escapes (which escape but are returned literally) test_format "#{l:##{}" "#{" test_format "#{l:#{#}}}" "#{#}}" # Invalid formats: #test_format "#{l:#{}" "" #test_format "#{l:#{#}}" "" exit 0 tmux-tmux-f222026/regress/has-session-return.sh000066400000000000000000000010011511153563100215170ustar00rootroot00000000000000#!/bin/sh # 971 # has-session should return 1 on error PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null $TMUX -f/dev/null has -tfoo /dev/null && exit 1 $TMUX -f/dev/null start\; has -tfoo /dev/null && exit 1 $TMUX -f/dev/null new -d\; has -tfoo /dev/null && exit 1 $TMUX -f/dev/null new -dsfoo\; has -tfoo /dev/null || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/if-shell-TERM.sh000066400000000000000000000012471511153563100202320ustar00rootroot00000000000000#!/bin/sh # 882 # TERM should come from outside tmux for if-shell from the config file PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP if '[ "\$TERM" = "xterm" ]' \ 'set -g default-terminal "vt220"' \ 'set -g default-terminal "ansi"' EOF TERM=xterm $TMUX -f$TMP new -d "echo \"#\$TERM\" >>$TMP" || exit 1 sleep 1 && [ "$(tail -1 $TMP)" = "#vt220" ] || exit 1 TERM=screen $TMUX -f$TMP new -d "echo \"#\$TERM\" >>$TMP" || exit 1 sleep 1 && [ "$(tail -1 $TMP)" = "#ansi" ] || exit 1 $TMUX has 2>/dev/null && exit 1 exit 0 tmux-tmux-f222026/regress/if-shell-error.sh000066400000000000000000000010601511153563100206050ustar00rootroot00000000000000#!/bin/sh # 883 # if-shell with an error should not core :-) PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) OUT=$(mktemp) trap "rm -f $TMP $OUT" 0 1 15 cat <$TMP if 'true' 'wibble wobble' EOF $TMUX -f$TMP -C new <$OUT EOF grep -q "^%config-error $TMP:1: $TMP:1: unknown command: wibble$" $OUT cat <$TMP wibble wobble EOF echo "source $TMP" | $TMUX -C new >$OUT grep -q "^%config-error $TMP:1: unknown command: wibble$" $OUT tmux-tmux-f222026/regress/if-shell-nested.sh000066400000000000000000000007251511153563100207450ustar00rootroot00000000000000#!/bin/sh # 882 # tmux inside if-shell itself should work PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP if '$TMUX run "true"' 'set -s @done yes' EOF TERM=xterm $TMUX -f$TMP new -d "$TMUX show -vs @done >>$TMP" || exit 1 sleep 1 && [ "$(tail -1 $TMP)" = "yes" ] || exit 1 $TMUX has 2>/dev/null && exit 1 exit 0 tmux-tmux-f222026/regress/input-keys.sh000066400000000000000000000220351511153563100200700ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null sleep 1 $TMUX -f/dev/null new -x20 -y2 -d || exit 1 sleep 1 $TMUX set -g escape-time 0 exit_status=0 assert_key () { key=$1 expected_code=$2 W=$($TMUX new-window -P -- sh -c 'stty raw -echo && cat -tv') $TMUX send-keys -t$W "$key" 'EOL' || exit 1 sleep 0.2 actual_code=$($TMUX capturep -pt$W | \ head -1 | \ sed -e 's/EOL.*$//') $TMUX kill-window -t$W || exit 1 if [ "$actual_code" = "$expected_code" ]; then if [ -n "$VERBOSE" ]; then echo "[PASS] $key -> $actual_code" fi else echo "[FAIL] $key -> $expected_code (Got: $actual_code)" exit_status=1 fi shift shift if [ "$1" = "--" ]; then shift assert_key "$@" fi } assert_key 'C-Space' '^@' assert_key 'C-a' '^A' -- 'M-C-a' '^[^A' assert_key 'C-b' '^B' -- 'M-C-b' '^[^B' assert_key 'C-c' '^C' -- 'M-C-c' '^[^C' assert_key 'C-d' '^D' -- 'M-C-d' '^[^D' assert_key 'C-e' '^E' -- 'M-C-e' '^[^E' assert_key 'C-f' '^F' -- 'M-C-f' '^[^F' assert_key 'C-g' '^G' -- 'M-C-g' '^[^G' assert_key 'C-h' '^H' -- 'M-C-h' '^[^H' assert_key 'C-i' '^I' -- 'M-C-i' '^[^I' assert_key 'C-j' '' -- 'M-C-j' '^[' # NL assert_key 'C-k' '^K' -- 'M-C-k' '^[^K' assert_key 'C-l' '^L' -- 'M-C-l' '^[^L' assert_key 'C-m' '^M' -- 'M-C-m' '^[^M' assert_key 'C-n' '^N' -- 'M-C-n' '^[^N' assert_key 'C-o' '^O' -- 'M-C-o' '^[^O' assert_key 'C-p' '^P' -- 'M-C-p' '^[^P' assert_key 'C-q' '^Q' -- 'M-C-q' '^[^Q' assert_key 'C-r' '^R' -- 'M-C-r' '^[^R' assert_key 'C-s' '^S' -- 'M-C-s' '^[^S' assert_key 'C-t' '^T' -- 'M-C-t' '^[^T' assert_key 'C-u' '^U' -- 'M-C-u' '^[^U' assert_key 'C-v' '^V' -- 'M-C-v' '^[^V' assert_key 'C-w' '^W' -- 'M-C-w' '^[^W' assert_key 'C-x' '^X' -- 'M-C-x' '^[^X' assert_key 'C-y' '^Y' -- 'M-C-y' '^[^Y' assert_key 'C-z' '^Z' -- 'M-C-z' '^[^Z' assert_key 'Escape' '^[' -- 'M-Escape' '^[^[' assert_key "C-\\" "^\\" -- "M-C-\\" "^[^\\" assert_key 'C-]' '^]' -- 'M-C-]' '^[^]' assert_key 'C-^' '^^' -- 'M-C-^' '^[^^' assert_key 'C-_' '^_' -- 'M-C-_' '^[^_' assert_key 'Space' ' ' -- 'M-Space' '^[ ' assert_key '!' '!' -- 'M-!' '^[!' assert_key '"' '"' -- 'M-"' '^["' assert_key '#' '#' -- 'M-#' '^[#' assert_key '$' '$' -- 'M-$' '^[$' assert_key '%' '%' -- 'M-%' '^[%' assert_key '&' '&' -- 'M-&' '^[&' assert_key "'" "'" -- "M-'" "^['" assert_key '(' '(' -- 'M-(' '^[(' assert_key ')' ')' -- 'M-)' '^[)' assert_key '*' '*' -- 'M-*' '^[*' assert_key '+' '+' -- 'M-+' '^[+' assert_key ',' ',' -- 'M-,' '^[,' assert_key '-' '-' -- 'M--' '^[-' assert_key '.' '.' -- 'M-.' '^[.' assert_key '/' '/' -- 'M-/' '^[/' assert_key '0' '0' -- 'M-0' '^[0' assert_key '1' '1' -- 'M-1' '^[1' assert_key '2' '2' -- 'M-2' '^[2' assert_key '3' '3' -- 'M-3' '^[3' assert_key '4' '4' -- 'M-4' '^[4' assert_key '5' '5' -- 'M-5' '^[5' assert_key '6' '6' -- 'M-6' '^[6' assert_key '7' '7' -- 'M-7' '^[7' assert_key '8' '8' -- 'M-8' '^[8' assert_key '9' '9' -- 'M-9' '^[9' assert_key ':' ':' -- 'M-:' '^[:' assert_key '\;' ';' -- 'M-\;' '^[;' assert_key '<' '<' -- 'M-<' '^[<' assert_key '=' '=' -- 'M-=' '^[=' assert_key '>' '>' -- 'M->' '^[>' assert_key '?' '?' -- 'M-?' '^[?' assert_key '@' '@' -- 'M-@' '^[@' assert_key 'A' 'A' -- 'M-A' '^[A' assert_key 'B' 'B' -- 'M-B' '^[B' assert_key 'C' 'C' -- 'M-C' '^[C' assert_key 'D' 'D' -- 'M-D' '^[D' assert_key 'E' 'E' -- 'M-E' '^[E' assert_key 'F' 'F' -- 'M-F' '^[F' assert_key 'G' 'G' -- 'M-G' '^[G' assert_key 'H' 'H' -- 'M-H' '^[H' assert_key 'I' 'I' -- 'M-I' '^[I' assert_key 'J' 'J' -- 'M-J' '^[J' assert_key 'K' 'K' -- 'M-K' '^[K' assert_key 'L' 'L' -- 'M-L' '^[L' assert_key 'M' 'M' -- 'M-M' '^[M' assert_key 'N' 'N' -- 'M-N' '^[N' assert_key 'O' 'O' -- 'M-O' '^[O' assert_key 'P' 'P' -- 'M-P' '^[P' assert_key 'Q' 'Q' -- 'M-Q' '^[Q' assert_key 'R' 'R' -- 'M-R' '^[R' assert_key 'S' 'S' -- 'M-S' '^[S' assert_key 'T' 'T' -- 'M-T' '^[T' assert_key 'U' 'U' -- 'M-U' '^[U' assert_key 'V' 'V' -- 'M-V' '^[V' assert_key 'W' 'W' -- 'M-W' '^[W' assert_key 'X' 'X' -- 'M-X' '^[X' assert_key 'Y' 'Y' -- 'M-Y' '^[Y' assert_key 'Z' 'Z' -- 'M-Z' '^[Z' assert_key '[' '[' -- 'M-[' '^[[' assert_key "\\" "\\" -- "M-\\" "^[\\" assert_key ']' ']' -- 'M-]' '^[]' assert_key '^' '^' -- 'M-^' '^[^' assert_key '_' '_' -- 'M-_' '^[_' assert_key '`' '`' -- 'M-`' '^[`' assert_key 'a' 'a' -- 'M-a' '^[a' assert_key 'b' 'b' -- 'M-b' '^[b' assert_key 'c' 'c' -- 'M-c' '^[c' assert_key 'd' 'd' -- 'M-d' '^[d' assert_key 'e' 'e' -- 'M-e' '^[e' assert_key 'f' 'f' -- 'M-f' '^[f' assert_key 'g' 'g' -- 'M-g' '^[g' assert_key 'h' 'h' -- 'M-h' '^[h' assert_key 'i' 'i' -- 'M-i' '^[i' assert_key 'j' 'j' -- 'M-j' '^[j' assert_key 'k' 'k' -- 'M-k' '^[k' assert_key 'l' 'l' -- 'M-l' '^[l' assert_key 'm' 'm' -- 'M-m' '^[m' assert_key 'n' 'n' -- 'M-n' '^[n' assert_key 'o' 'o' -- 'M-o' '^[o' assert_key 'p' 'p' -- 'M-p' '^[p' assert_key 'q' 'q' -- 'M-q' '^[q' assert_key 'r' 'r' -- 'M-r' '^[r' assert_key 's' 's' -- 'M-s' '^[s' assert_key 't' 't' -- 'M-t' '^[t' assert_key 'u' 'u' -- 'M-u' '^[u' assert_key 'v' 'v' -- 'M-v' '^[v' assert_key 'w' 'w' -- 'M-w' '^[w' assert_key 'x' 'x' -- 'M-x' '^[x' assert_key 'y' 'y' -- 'M-y' '^[y' assert_key 'z' 'z' -- 'M-z' '^[z' assert_key '{' '{' -- 'M-{' '^[{' assert_key '|' '|' -- 'M-|' '^[|' assert_key '}' '}' -- 'M-}' '^[}' assert_key '~' '~' -- 'M-~' '^[~' assert_key 'Tab' '^I' -- 'M-Tab' '^[^I' assert_key 'BSpace' '^?' -- 'M-BSpace' '^[^?' ## These cannot be sent, is that intentional? ## assert_key 'PasteStart' "^[[200~" ## assert_key 'PasteEnd' "^[[201~" assert_key 'F1' "^[OP" assert_key 'F2' "^[OQ" assert_key 'F3' "^[OR" assert_key 'F4' "^[OS" assert_key 'F5' "^[[15~" assert_key 'F6' "^[[17~" assert_key 'F8' "^[[19~" assert_key 'F9' "^[[20~" assert_key 'F10' "^[[21~" assert_key 'F11' "^[[23~" assert_key 'F12' "^[[24~" assert_key 'IC' '^[[2~' assert_key 'Insert' '^[[2~' assert_key 'DC' '^[[3~' assert_key 'Delete' '^[[3~' ## Why do these differ from tty-keys? assert_key 'Home' '^[[1~' assert_key 'End' '^[[4~' assert_key 'NPage' '^[[6~' assert_key 'PageDown' '^[[6~' assert_key 'PgDn' '^[[6~' assert_key 'PPage' '^[[5~' assert_key 'PageUp' '^[[5~' assert_key 'PgUp' '^[[5~' assert_key 'BTab' '^[[Z' assert_key 'C-S-Tab' '^I' assert_key 'Up' '^[[A' assert_key 'Down' '^[[B' assert_key 'Right' '^[[C' assert_key 'Left' '^[[D' # assert_key 'KPEnter' assert_key 'KP*' '*' -- 'M-KP*' '^[*' assert_key 'KP+' '+' -- 'M-KP+' '^[+' assert_key 'KP-' '-' -- 'M-KP-' '^[-' assert_key 'KP.' '.' -- 'M-KP.' '^[.' assert_key 'KP/' '/' -- 'M-KP/' '^[/' assert_key 'KP0' '0' -- 'M-KP0' '^[0' assert_key 'KP1' '1' -- 'M-KP1' '^[1' assert_key 'KP2' '2' -- 'M-KP2' '^[2' assert_key 'KP3' '3' -- 'M-KP3' '^[3' assert_key 'KP4' '4' -- 'M-KP4' '^[4' assert_key 'KP5' '5' -- 'M-KP5' '^[5' assert_key 'KP6' '6' -- 'M-KP6' '^[6' assert_key 'KP7' '7' -- 'M-KP7' '^[7' assert_key 'KP8' '8' -- 'M-KP8' '^[8' assert_key 'KP9' '9' -- 'M-KP9' '^[9' # Extended keys $TMUX set -g extended-keys always assert_extended_key () { extended_key=$1 expected_code_pattern=$2 expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;2/') assert_key "S-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;3/') assert_key "M-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;4/') assert_key "S-M-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;5/') assert_key "C-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;6/') assert_key "S-C-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;7/') assert_key "C-M-$extended_key" "$expected_code" expected_code=$(printf '%s' "$expected_code_pattern" | sed -e 's/;_/;8/') assert_key "S-C-M-$extended_key" "$expected_code" } ## Many of these pass without extended keys enabled -- are they extended keys? assert_extended_key 'F1' '^[[1;_P' assert_extended_key 'F2' "^[[1;_Q" assert_extended_key 'F3' "^[[1;_R" assert_extended_key 'F4' "^[[1;_S" assert_extended_key 'F5' "^[[15;_~" assert_extended_key 'F6' "^[[17;_~" assert_extended_key 'F8' "^[[19;_~" assert_extended_key 'F9' "^[[20;_~" assert_extended_key 'F10' "^[[21;_~" assert_extended_key 'F11' "^[[23;_~" assert_extended_key 'F12' "^[[24;_~" assert_extended_key 'Up' '^[[1;_A' assert_extended_key 'Down' '^[[1;_B' assert_extended_key 'Right' '^[[1;_C' assert_extended_key 'Left' '^[[1;_D' assert_extended_key 'Home' '^[[1;_H' assert_extended_key 'End' '^[[1;_F' assert_extended_key 'PPage' '^[[5;_~' assert_extended_key 'PageUp' '^[[5;_~' assert_extended_key 'PgUp' '^[[5;_~' assert_extended_key 'NPage' '^[[6;_~' assert_extended_key 'PageDown' '^[[6;_~' assert_extended_key 'PgDn' '^[[6;_~' assert_extended_key 'IC' '^[[2;_~' assert_extended_key 'Insert' '^[[2;_~' assert_extended_key 'DC' '^[[3;_~' assert_extended_key 'Delete' '^[[3;_~' assert_key 'C-Tab' "^[[27;5;9~" assert_key 'C-S-Tab' "^[[27;6;9~" $TMUX kill-server 2>/dev/null exit $exit_status tmux-tmux-f222026/regress/kill-session-process-exit.sh000066400000000000000000000007231511153563100230170ustar00rootroot00000000000000#!/bin/sh # when we kill a session, processes running in it should be killed PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null sleep 1 $TMUX -f/dev/null new -d 'sleep 1000' || exit 1 P=$($TMUX display -pt0:0.0 '#{pane_pid}') $TMUX -f/dev/null new -d || exit 1 sleep 1 $TMUX kill-session -t0: sleep 3 kill -0 $P 2>/dev/null && exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/new-session-base-index.sh000066400000000000000000000006621511153563100222510ustar00rootroot00000000000000#!/bin/sh # new session base-index PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP set -g base-index 100 new set base-index 200 neww EOF $TMUX -f$TMP start echo $($TMUX lsw -F'#{window_index}') >$TMP (echo "100 200"|cmp -s - $TMP) || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/new-session-command.sh000066400000000000000000000005751511153563100216530ustar00rootroot00000000000000#!/bin/sh # new session command PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP new sleep 101 new -- sleep 102 new "sleep 103" EOF $TMUX -f$TMP start [ $($TMUX ls|wc -l) -eq 3 ] || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/new-session-environment.sh000066400000000000000000000021221511153563100225670ustar00rootroot00000000000000#!/bin/sh # new session environment PATH=/bin:/usr/bin [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TERM=$($TMUX start \; show -gv default-terminal) TMP=$(mktemp) OUT=$(mktemp) SCRIPT=$(mktemp) #trap "rm -f $TMP $OUT $SCRIPT" 0 1 15 cat <$SCRIPT ( echo TERM=\$TERM echo PWD=\$(pwd) echo PATH=\$PATH echo SHELL=\$SHELL echo TEST=\$TEST ) >$OUT EOF cat <$TMP new -- /bin/sh $SCRIPT EOF (cd /; env -i TERM=ansi TEST=test1 PATH=1 SHELL=/bin/sh \ $TMUX -f$TMP start) || exit 1 sleep 1 (cat </dev/null exit 0 tmux-tmux-f222026/regress/new-session-no-client.sh000066400000000000000000000006531511153563100221220ustar00rootroot00000000000000#!/bin/sh # 869 # new with no client (that is, from the config file) should imply -d and # not attach PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP new -stest EOF $TMUX -f$TMP start || exit 1 sleep 1 && $TMUX has -t=test: || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/new-session-size.sh000066400000000000000000000011661511153563100212040ustar00rootroot00000000000000#!/bin/sh # new-session without clients should be the right size PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d $TMP printf "80 24\n"|cmp -s $TMP - || exit 1 $TMUX kill-server 2>/dev/null $TMUX -f/dev/null new -d -x 100 -y 50 $TMP printf "100 50\n"|cmp -s $TMP - || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/new-window-command.sh000066400000000000000000000006051511153563100214710ustar00rootroot00000000000000#!/bin/sh # new session command PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 cat <$TMP new neww sleep 101 neww -- sleep 102 neww "sleep 103" EOF $TMUX -f$TMP start [ $($TMUX lsw|wc -l) -eq 4 ] || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/osc-11colours.sh000066400000000000000000000255061511153563100204000ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null $TMUX new -d $TMUX set -g remain-on-exit on do_test() { $TMUX splitw "printf '$1'" sleep 0.25 c="$($TMUX display -p '#{pane_bg}')" $TMUX kill-pane [ "$c" != "$2" ] && return 1 return 0 } do_test '\033]11;rgb:ff/ff/ff\007' '#ffffff' || exit 1 do_test '\033]11;rgb:ff/ff/ff\007\033]111\007' 'default' || exit 1 do_test '\033]11;cmy:0.9373/0.6941/0.4549\007' '#0f4e8b' || exit 1 do_test '\033]11;cmyk:0.88/0.44/0.00/0.45\007' '#104e8c' || exit 1 do_test '\033]11;16,78,139\007' '#104e8b' || exit 1 do_test '\033]11;#104E8B\007' '#104e8b' || exit 1 do_test '\033]11;#10004E008B00\007' '#104e8b' || exit 1 do_test '\033]11;DodgerBlue4\007' '#104e8b' || exit 1 do_test '\033]11;DodgerBlue4 \007' '#104e8b' || exit 1 do_test '\033]11; DodgerBlue4\007' '#104e8b' || exit 1 do_test '\033]11;rgb:10/4E/8B\007' '#104e8b' || exit 1 do_test '\033]11;rgb:1000/4E00/8B00\007' '#104e8b' || exit 1 do_test '\033]11;grey\007' '#bebebe' || exit 1 do_test '\033]11;grey0\007' '#000000' || exit 1 do_test '\033]11;grey1\007' '#030303' || exit 1 do_test '\033]11;grey2\007' '#050505' || exit 1 do_test '\033]11;grey3\007' '#080808' || exit 1 do_test '\033]11;grey4\007' '#0a0a0a' || exit 1 do_test '\033]11;grey5\007' '#0d0d0d' || exit 1 do_test '\033]11;grey6\007' '#0f0f0f' || exit 1 do_test '\033]11;grey7\007' '#121212' || exit 1 do_test '\033]11;grey8\007' '#141414' || exit 1 do_test '\033]11;grey9\007' '#171717' || exit 1 do_test '\033]11;grey10\007' '#1a1a1a' || exit 1 do_test '\033]11;grey11\007' '#1c1c1c' || exit 1 do_test '\033]11;grey12\007' '#1f1f1f' || exit 1 do_test '\033]11;grey13\007' '#212121' || exit 1 do_test '\033]11;grey14\007' '#242424' || exit 1 do_test '\033]11;grey15\007' '#262626' || exit 1 do_test '\033]11;grey16\007' '#292929' || exit 1 do_test '\033]11;grey17\007' '#2b2b2b' || exit 1 do_test '\033]11;grey18\007' '#2e2e2e' || exit 1 do_test '\033]11;grey19\007' '#303030' || exit 1 do_test '\033]11;grey20\007' '#333333' || exit 1 do_test '\033]11;grey21\007' '#363636' || exit 1 do_test '\033]11;grey22\007' '#383838' || exit 1 do_test '\033]11;grey23\007' '#3b3b3b' || exit 1 do_test '\033]11;grey24\007' '#3d3d3d' || exit 1 do_test '\033]11;grey25\007' '#404040' || exit 1 do_test '\033]11;grey26\007' '#424242' || exit 1 do_test '\033]11;grey27\007' '#454545' || exit 1 do_test '\033]11;grey28\007' '#474747' || exit 1 do_test '\033]11;grey29\007' '#4a4a4a' || exit 1 do_test '\033]11;grey30\007' '#4d4d4d' || exit 1 do_test '\033]11;grey31\007' '#4f4f4f' || exit 1 do_test '\033]11;grey32\007' '#525252' || exit 1 do_test '\033]11;grey33\007' '#545454' || exit 1 do_test '\033]11;grey34\007' '#575757' || exit 1 do_test '\033]11;grey35\007' '#595959' || exit 1 do_test '\033]11;grey36\007' '#5c5c5c' || exit 1 do_test '\033]11;grey37\007' '#5e5e5e' || exit 1 do_test '\033]11;grey38\007' '#616161' || exit 1 do_test '\033]11;grey39\007' '#636363' || exit 1 do_test '\033]11;grey40\007' '#666666' || exit 1 do_test '\033]11;grey41\007' '#696969' || exit 1 do_test '\033]11;grey42\007' '#6b6b6b' || exit 1 do_test '\033]11;grey43\007' '#6e6e6e' || exit 1 do_test '\033]11;grey44\007' '#707070' || exit 1 do_test '\033]11;grey45\007' '#737373' || exit 1 do_test '\033]11;grey46\007' '#757575' || exit 1 do_test '\033]11;grey47\007' '#787878' || exit 1 do_test '\033]11;grey48\007' '#7a7a7a' || exit 1 do_test '\033]11;grey49\007' '#7d7d7d' || exit 1 do_test '\033]11;grey50\007' '#7f7f7f' || exit 1 do_test '\033]11;grey51\007' '#828282' || exit 1 do_test '\033]11;grey52\007' '#858585' || exit 1 do_test '\033]11;grey53\007' '#878787' || exit 1 do_test '\033]11;grey54\007' '#8a8a8a' || exit 1 do_test '\033]11;grey55\007' '#8c8c8c' || exit 1 do_test '\033]11;grey56\007' '#8f8f8f' || exit 1 do_test '\033]11;grey57\007' '#919191' || exit 1 do_test '\033]11;grey58\007' '#949494' || exit 1 do_test '\033]11;grey59\007' '#969696' || exit 1 do_test '\033]11;grey60\007' '#999999' || exit 1 do_test '\033]11;grey61\007' '#9c9c9c' || exit 1 do_test '\033]11;grey62\007' '#9e9e9e' || exit 1 do_test '\033]11;grey63\007' '#a1a1a1' || exit 1 do_test '\033]11;grey64\007' '#a3a3a3' || exit 1 do_test '\033]11;grey65\007' '#a6a6a6' || exit 1 do_test '\033]11;grey66\007' '#a8a8a8' || exit 1 do_test '\033]11;grey67\007' '#ababab' || exit 1 do_test '\033]11;grey68\007' '#adadad' || exit 1 do_test '\033]11;grey69\007' '#b0b0b0' || exit 1 do_test '\033]11;grey70\007' '#b3b3b3' || exit 1 do_test '\033]11;grey71\007' '#b5b5b5' || exit 1 do_test '\033]11;grey72\007' '#b8b8b8' || exit 1 do_test '\033]11;grey73\007' '#bababa' || exit 1 do_test '\033]11;grey74\007' '#bdbdbd' || exit 1 do_test '\033]11;grey75\007' '#bfbfbf' || exit 1 do_test '\033]11;grey76\007' '#c2c2c2' || exit 1 do_test '\033]11;grey77\007' '#c4c4c4' || exit 1 do_test '\033]11;grey78\007' '#c7c7c7' || exit 1 do_test '\033]11;grey79\007' '#c9c9c9' || exit 1 do_test '\033]11;grey80\007' '#cccccc' || exit 1 do_test '\033]11;grey81\007' '#cfcfcf' || exit 1 do_test '\033]11;grey82\007' '#d1d1d1' || exit 1 do_test '\033]11;grey83\007' '#d4d4d4' || exit 1 do_test '\033]11;grey84\007' '#d6d6d6' || exit 1 do_test '\033]11;grey85\007' '#d9d9d9' || exit 1 do_test '\033]11;grey86\007' '#dbdbdb' || exit 1 do_test '\033]11;grey87\007' '#dedede' || exit 1 do_test '\033]11;grey88\007' '#e0e0e0' || exit 1 do_test '\033]11;grey89\007' '#e3e3e3' || exit 1 do_test '\033]11;grey90\007' '#e5e5e5' || exit 1 do_test '\033]11;grey91\007' '#e8e8e8' || exit 1 do_test '\033]11;grey92\007' '#ebebeb' || exit 1 do_test '\033]11;grey93\007' '#ededed' || exit 1 do_test '\033]11;grey94\007' '#f0f0f0' || exit 1 do_test '\033]11;grey95\007' '#f2f2f2' || exit 1 do_test '\033]11;grey96\007' '#f5f5f5' || exit 1 do_test '\033]11;grey97\007' '#f7f7f7' || exit 1 do_test '\033]11;grey98\007' '#fafafa' || exit 1 do_test '\033]11;grey99\007' '#fcfcfc' || exit 1 do_test '\033]11;grey100\007' '#ffffff' || exit 1 do_test '\033]11;gray\007' '#bebebe' || exit 1 do_test '\033]11;gray0\007' '#000000' || exit 1 do_test '\033]11;gray1\007' '#030303' || exit 1 do_test '\033]11;gray2\007' '#050505' || exit 1 do_test '\033]11;gray3\007' '#080808' || exit 1 do_test '\033]11;gray4\007' '#0a0a0a' || exit 1 do_test '\033]11;gray5\007' '#0d0d0d' || exit 1 do_test '\033]11;gray6\007' '#0f0f0f' || exit 1 do_test '\033]11;gray7\007' '#121212' || exit 1 do_test '\033]11;gray8\007' '#141414' || exit 1 do_test '\033]11;gray9\007' '#171717' || exit 1 do_test '\033]11;gray10\007' '#1a1a1a' || exit 1 do_test '\033]11;gray11\007' '#1c1c1c' || exit 1 do_test '\033]11;gray12\007' '#1f1f1f' || exit 1 do_test '\033]11;gray13\007' '#212121' || exit 1 do_test '\033]11;gray14\007' '#242424' || exit 1 do_test '\033]11;gray15\007' '#262626' || exit 1 do_test '\033]11;gray16\007' '#292929' || exit 1 do_test '\033]11;gray17\007' '#2b2b2b' || exit 1 do_test '\033]11;gray18\007' '#2e2e2e' || exit 1 do_test '\033]11;gray19\007' '#303030' || exit 1 do_test '\033]11;gray20\007' '#333333' || exit 1 do_test '\033]11;gray21\007' '#363636' || exit 1 do_test '\033]11;gray22\007' '#383838' || exit 1 do_test '\033]11;gray23\007' '#3b3b3b' || exit 1 do_test '\033]11;gray24\007' '#3d3d3d' || exit 1 do_test '\033]11;gray25\007' '#404040' || exit 1 do_test '\033]11;gray26\007' '#424242' || exit 1 do_test '\033]11;gray27\007' '#454545' || exit 1 do_test '\033]11;gray28\007' '#474747' || exit 1 do_test '\033]11;gray29\007' '#4a4a4a' || exit 1 do_test '\033]11;gray30\007' '#4d4d4d' || exit 1 do_test '\033]11;gray31\007' '#4f4f4f' || exit 1 do_test '\033]11;gray32\007' '#525252' || exit 1 do_test '\033]11;gray33\007' '#545454' || exit 1 do_test '\033]11;gray34\007' '#575757' || exit 1 do_test '\033]11;gray35\007' '#595959' || exit 1 do_test '\033]11;gray36\007' '#5c5c5c' || exit 1 do_test '\033]11;gray37\007' '#5e5e5e' || exit 1 do_test '\033]11;gray38\007' '#616161' || exit 1 do_test '\033]11;gray39\007' '#636363' || exit 1 do_test '\033]11;gray40\007' '#666666' || exit 1 do_test '\033]11;gray41\007' '#696969' || exit 1 do_test '\033]11;gray42\007' '#6b6b6b' || exit 1 do_test '\033]11;gray43\007' '#6e6e6e' || exit 1 do_test '\033]11;gray44\007' '#707070' || exit 1 do_test '\033]11;gray45\007' '#737373' || exit 1 do_test '\033]11;gray46\007' '#757575' || exit 1 do_test '\033]11;gray47\007' '#787878' || exit 1 do_test '\033]11;gray48\007' '#7a7a7a' || exit 1 do_test '\033]11;gray49\007' '#7d7d7d' || exit 1 do_test '\033]11;gray50\007' '#7f7f7f' || exit 1 do_test '\033]11;gray51\007' '#828282' || exit 1 do_test '\033]11;gray52\007' '#858585' || exit 1 do_test '\033]11;gray53\007' '#878787' || exit 1 do_test '\033]11;gray54\007' '#8a8a8a' || exit 1 do_test '\033]11;gray55\007' '#8c8c8c' || exit 1 do_test '\033]11;gray56\007' '#8f8f8f' || exit 1 do_test '\033]11;gray57\007' '#919191' || exit 1 do_test '\033]11;gray58\007' '#949494' || exit 1 do_test '\033]11;gray59\007' '#969696' || exit 1 do_test '\033]11;gray60\007' '#999999' || exit 1 do_test '\033]11;gray61\007' '#9c9c9c' || exit 1 do_test '\033]11;gray62\007' '#9e9e9e' || exit 1 do_test '\033]11;gray63\007' '#a1a1a1' || exit 1 do_test '\033]11;gray64\007' '#a3a3a3' || exit 1 do_test '\033]11;gray65\007' '#a6a6a6' || exit 1 do_test '\033]11;gray66\007' '#a8a8a8' || exit 1 do_test '\033]11;gray67\007' '#ababab' || exit 1 do_test '\033]11;gray68\007' '#adadad' || exit 1 do_test '\033]11;gray69\007' '#b0b0b0' || exit 1 do_test '\033]11;gray70\007' '#b3b3b3' || exit 1 do_test '\033]11;gray71\007' '#b5b5b5' || exit 1 do_test '\033]11;gray72\007' '#b8b8b8' || exit 1 do_test '\033]11;gray73\007' '#bababa' || exit 1 do_test '\033]11;gray74\007' '#bdbdbd' || exit 1 do_test '\033]11;gray75\007' '#bfbfbf' || exit 1 do_test '\033]11;gray76\007' '#c2c2c2' || exit 1 do_test '\033]11;gray77\007' '#c4c4c4' || exit 1 do_test '\033]11;gray78\007' '#c7c7c7' || exit 1 do_test '\033]11;gray79\007' '#c9c9c9' || exit 1 do_test '\033]11;gray80\007' '#cccccc' || exit 1 do_test '\033]11;gray81\007' '#cfcfcf' || exit 1 do_test '\033]11;gray82\007' '#d1d1d1' || exit 1 do_test '\033]11;gray83\007' '#d4d4d4' || exit 1 do_test '\033]11;gray84\007' '#d6d6d6' || exit 1 do_test '\033]11;gray85\007' '#d9d9d9' || exit 1 do_test '\033]11;gray86\007' '#dbdbdb' || exit 1 do_test '\033]11;gray87\007' '#dedede' || exit 1 do_test '\033]11;gray88\007' '#e0e0e0' || exit 1 do_test '\033]11;gray89\007' '#e3e3e3' || exit 1 do_test '\033]11;gray90\007' '#e5e5e5' || exit 1 do_test '\033]11;gray91\007' '#e8e8e8' || exit 1 do_test '\033]11;gray92\007' '#ebebeb' || exit 1 do_test '\033]11;gray93\007' '#ededed' || exit 1 do_test '\033]11;gray94\007' '#f0f0f0' || exit 1 do_test '\033]11;gray95\007' '#f2f2f2' || exit 1 do_test '\033]11;gray96\007' '#f5f5f5' || exit 1 do_test '\033]11;gray97\007' '#f7f7f7' || exit 1 do_test '\033]11;gray98\007' '#fafafa' || exit 1 do_test '\033]11;gray99\007' '#fcfcfc' || exit 1 do_test '\033]11;gray100\007' '#ffffff' || exit 1 $TMUX -f/dev/null kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/run-shell-output.sh000066400000000000000000000011351511153563100212250ustar00rootroot00000000000000#!/bin/sh # 4476 # run-shell should go to stdout if present without -t PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX -f/dev/null new -d "$TMUX run 'echo foo' >$TMP; sleep 10" || exit 1 sleep 1 && [ "$(cat $TMP)" = "foo" ] || exit 1 $TMUX -f/dev/null new -d "$TMUX run -t: 'echo foo' >$TMP; sleep 10" || exit 1 sleep 1 && [ "$(cat $TMP)" = "" ] || exit 1 [ "$($TMUX display -p '#{pane_mode}')" = "view-mode" ] || exit 1 $TMUX kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/style-trim.sh000066400000000000000000000057531511153563100201010ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen shell= if command -v bash >/dev/null 2>&1; then # If Bash is available, we start a plain Bash session (without any user # configuration files) for testing. # # Note: We disable the command history by passing "+o history". If an # interactive Bash session is started without any configuration files, # the user's command history may be truncated to the default maximum # size of 500. To avoid breaking the user's command history, we disable # the command history. shell='bash --noprofile --norc +o history' fi [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMUX2="$TEST_TMUX -Ltest2" $TMUX2 kill-server 2>/dev/null $TMUX2 -f/dev/null new -d "$TMUX -f/dev/null new -- $shell" sleep 2 $TMUX set -g status-style fg=default,bg=default check() { v=$($TMUX display -p "$1") $TMUX set -g status-format[0] "$1" sleep 1 r=$($TMUX2 capturep -Cep|tail -1|sed 's|\\033\[||g') if [ "$v" != "$2" -o "$r" != "$3" ]; then printf "$1 = [$v = $2] [$r = $3]" printf " \033[31mbad\033[0m\n" exit 1 fi } # drawn as #0 $TMUX setenv -g V '#0' check '#{V} #{w:V}' '#0 2' '#0 2' check '#{=3:V}' '#0' '#0' check '#{=-3:V}' '#0' '#0' # drawn as #0 $TMUX setenv -g V '###[bg=yellow]0' check '#{V} #{w:V}' '###[bg=yellow]0 2' '#43m0 249m' check '#{=3:V}' '###[bg=yellow]0' '#43m049m' check '#{=-3:V}' '###[bg=yellow]0' '#43m049m' # drawn as #0123456 $TMUX setenv -g V '#0123456' check '#{V} #{w:V}' '#0123456 8' '#0123456 8' check '#{=3:V}' '#01' '#01' check '#{=-3:V}' '456' '456' # drawn as #0123456 $TMUX setenv -g V '##0123456' check '#{V} #{w:V}' '##0123456 8' '#0123456 8' check '#{=3:V}' '##01' '#01' check '#{=-3:V}' '456' '456' # drawn as ##0123456 $TMUX setenv -g V '###0123456' check '#{V} #{w:V}' '###0123456 9' '##0123456 9' check '#{=3:V}' '####0' '##0' check '#{=-3:V}' '456' '456' # drawn as 0123456 $TMUX setenv -g V '#[bg=yellow]0123456' check '#{V} #{w:V}' '#[bg=yellow]0123456 7' '43m0123456 749m' check '#{=3:V}' '#[bg=yellow]012' '43m01249m' check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' # drawn as #[bg=yellow]0123456 $TMUX setenv -g V '##[bg=yellow]0123456' check '#{V} #{w:V}' '##[bg=yellow]0123456 19' '#[bg=yellow]0123456 19' check '#{=3:V}' '##[b' '#[b' check '#{=-3:V}' '456' '456' # drawn as #0123456 $TMUX setenv -g V '###[bg=yellow]0123456' check '#{V} #{w:V}' '###[bg=yellow]0123456 8' '#43m0123456 849m' check '#{=3:V}' '###[bg=yellow]01' '#43m0149m' check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' # drawn as ##[bg=yellow]0123456 $TMUX setenv -g V '####[bg=yellow]0123456' check '#{V} #{w:V}' '####[bg=yellow]0123456 20' '##[bg=yellow]0123456 20' check '#{=3:V}' '####[' '##[' check '#{=-3:V}' '456' '456' # drawn as ###0123456 $TMUX setenv -g V '#####[bg=yellow]0123456' check '#{V} #{w:V}' '#####[bg=yellow]0123456 9' '##43m0123456 949m' check '#{=3:V}' '#####[bg=yellow]0' '##43m049m' check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null exit 0 tmux-tmux-f222026/regress/tty-keys.sh000066400000000000000000000301241511153563100175470ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" $TMUX kill-server 2>/dev/null TMUX2="$TEST_TMUX -Ltest2" $TMUX2 kill-server 2>/dev/null TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX2 -f/dev/null new -d || exit 1 $TMUX -f/dev/null new -d "$TMUX2 attach" || exit 1 sleep 1 exit_status=0 format_string () { case $1 in *\') printf '"%%%%"' ;; *) printf "'%%%%'" ;; esac } assert_key () { keys=$1 expected_name=$2 format_string=$(format_string "$expected_name") $TMUX2 command-prompt -k 'display-message -pl '"$format_string" > "$TMP" & sleep 0.05 $TMUX send-keys $keys wait keys=$(printf '%s' "$keys" | sed -e 's/Escape/\\\\033/g' | tr -d '[:space:]') actual_name=$(tr -d '[:space:]' < "$TMP") if [ "$actual_name" = "$expected_name" ]; then if [ -n "$VERBOSE" ]; then echo "[PASS] $keys -> $actual_name" fi else echo "[FAIL] $keys -> $expected_name (Got: '$actual_name')" exit_status=1 fi if [ "$3" = "--" ]; then shift; shift; shift assert_key "$@" fi } assert_key 0x00 'C-Space' # -- 'Escape 0x00' 'C-M-Space' assert_key 0x01 'C-a' -- 'Escape 0x01' 'C-M-a' assert_key 0x02 'C-b' -- 'Escape 0x02' 'C-M-b' assert_key 0x03 'C-c' -- 'Escape 0x03' 'C-M-c' assert_key 0x04 'C-d' -- 'Escape 0x04' 'C-M-d' assert_key 0x05 'C-e' -- 'Escape 0x05' 'C-M-e' assert_key 0x06 'C-f' -- 'Escape 0x06' 'C-M-f' assert_key 0x07 'C-g' -- 'Escape 0x07' 'C-M-g' assert_key 0x08 'C-h' -- 'Escape 0x08' 'C-M-h' assert_key 0x09 'Tab' -- 'Escape 0x09' 'M-Tab' assert_key 0x0A 'C-j' -- 'Escape 0x0A' 'C-M-j' assert_key 0x0B 'C-k' -- 'Escape 0x0B' 'C-M-k' assert_key 0x0C 'C-l' -- 'Escape 0x0C' 'C-M-l' assert_key 0x0D 'Enter' -- 'Escape 0x0D' 'M-Enter' assert_key 0x0E 'C-n' -- 'Escape 0x0E' 'C-M-n' assert_key 0x0F 'C-o' -- 'Escape 0x0F' 'C-M-o' assert_key 0x10 'C-p' -- 'Escape 0x10' 'C-M-p' assert_key 0x11 'C-q' -- 'Escape 0x11' 'C-M-q' assert_key 0x12 'C-r' -- 'Escape 0x12' 'C-M-r' assert_key 0x13 'C-s' -- 'Escape 0x13' 'C-M-s' assert_key 0x14 'C-t' -- 'Escape 0x14' 'C-M-t' assert_key 0x15 'C-u' -- 'Escape 0x15' 'C-M-u' assert_key 0x16 'C-v' -- 'Escape 0x16' 'C-M-v' assert_key 0x17 'C-w' -- 'Escape 0x17' 'C-M-w' assert_key 0x18 'C-x' -- 'Escape 0x18' 'C-M-x' assert_key 0x19 'C-y' -- 'Escape 0x19' 'C-M-y' assert_key 0x1A 'C-z' -- 'Escape 0x1A' 'C-M-z' assert_key 0x1B 'Escape' -- 'Escape 0x1B' 'M-Escape' assert_key 0x1C "C-\\" -- 'Escape 0x1C' "C-M-\\" assert_key 0x1D 'C-]' -- 'Escape 0x1D' 'C-M-]' assert_key 0x1E 'C-^' -- 'Escape 0x1E' 'C-M-^' assert_key 0x1F 'C-_' -- 'Escape 0x1F' 'C-M-_' assert_key 0x20 'Space' -- 'Escape 0x20' 'M-Space' assert_key 0x21 '!' -- 'Escape 0x21' 'M-!' assert_key 0x22 '"' -- 'Escape 0x22' 'M-"' assert_key 0x23 '#' -- 'Escape 0x23'= 'M-#' assert_key 0x24 '$' -- 'Escape 0x24'= 'M-$' assert_key 0x25 '%' -- 'Escape 0x25'= 'M-%' assert_key 0x26 '&' -- 'Escape 0x26'= 'M-&' assert_key 0x27 "'" -- 'Escape 0x27' "M-'" assert_key 0x28 '(' -- 'Escape 0x28' 'M-(' assert_key 0x29 ')' -- 'Escape 0x29' 'M-)' assert_key 0x2A '*' -- 'Escape 0x2A' 'M-*' assert_key 0x2B '+' -- 'Escape 0x2B' 'M-+' assert_key 0x2C ',' -- 'Escape 0x2C' 'M-,' assert_key 0x2D '-' -- 'Escape 0x2D' 'M--' assert_key 0x2E '.' -- 'Escape 0x2E' 'M-.' assert_key 0x2F '/' -- 'Escape 0x2F' 'M-/' assert_key 0x30 '0' -- 'Escape 0x30' 'M-0' assert_key 0x31 '1' -- 'Escape 0x31' 'M-1' assert_key 0x32 '2' -- 'Escape 0x32' 'M-2' assert_key 0x33 '3' -- 'Escape 0x33' 'M-3' assert_key 0x34 '4' -- 'Escape 0x34' 'M-4' assert_key 0x35 '5' -- 'Escape 0x35' 'M-5' assert_key 0x36 '6' -- 'Escape 0x36' 'M-6' assert_key 0x37 '7' -- 'Escape 0x37' 'M-7' assert_key 0x38 '8' -- 'Escape 0x38' 'M-8' assert_key 0x39 '9' -- 'Escape 0x39' 'M-9' assert_key 0x3A ':' -- 'Escape 0x3A' 'M-:' assert_key 0x3B ';' -- 'Escape 0x3B' 'M-;' assert_key 0x3C '<' -- 'Escape 0x3C' 'M-<' assert_key 0x3D '=' -- 'Escape 0x3D' 'M-=' assert_key 0x3E '>' -- 'Escape 0x3E' 'M->' assert_key 0x3F '?' -- 'Escape 0x3F' 'M-?' assert_key 0x40 '@' -- 'Escape 0x40' 'M-@' assert_key 0x41 'A' -- 'Escape 0x41' 'M-A' assert_key 0x42 'B' -- 'Escape 0x42' 'M-B' assert_key 0x43 'C' -- 'Escape 0x43' 'M-C' assert_key 0x44 'D' -- 'Escape 0x44' 'M-D' assert_key 0x45 'E' -- 'Escape 0x45' 'M-E' assert_key 0x46 'F' -- 'Escape 0x46' 'M-F' assert_key 0x47 'G' -- 'Escape 0x47' 'M-G' assert_key 0x48 'H' -- 'Escape 0x48' 'M-H' assert_key 0x49 'I' -- 'Escape 0x49' 'M-I' assert_key 0x4A 'J' -- 'Escape 0x4A' 'M-J' assert_key 0x4B 'K' -- 'Escape 0x4B' 'M-K' assert_key 0x4C 'L' -- 'Escape 0x4C' 'M-L' assert_key 0x4D 'M' -- 'Escape 0x4D' 'M-M' assert_key 0x4E 'N' -- 'Escape 0x4E' 'M-N' assert_key 0x4F 'O' -- 'Escape 0x4F' 'M-O' assert_key 0x50 'P' -- 'Escape 0x50' 'M-P' assert_key 0x51 'Q' -- 'Escape 0x51' 'M-Q' assert_key 0x52 'R' -- 'Escape 0x52' 'M-R' assert_key 0x53 'S' -- 'Escape 0x53' 'M-S' assert_key 0x54 'T' -- 'Escape 0x54' 'M-T' assert_key 0x55 'U' -- 'Escape 0x55' 'M-U' assert_key 0x56 'V' -- 'Escape 0x56' 'M-V' assert_key 0x57 'W' -- 'Escape 0x57' 'M-W' assert_key 0x58 'X' -- 'Escape 0x58' 'M-X' assert_key 0x59 'Y' -- 'Escape 0x59' 'M-Y' assert_key 0x5A 'Z' -- 'Escape 0x5A' 'M-Z' assert_key 0x5B '[' -- 'Escape 0x5B' 'M-[' assert_key 0x5C "\\" -- 'Escape 0x5C' "M-\\" assert_key 0x5D ']' -- 'Escape 0x5D' 'M-]' assert_key 0x5E '^' -- 'Escape 0x5E' 'M-^' assert_key 0x5F '_' -- 'Escape 0x5F' 'M-_' assert_key 0x60 '`' -- 'Escape 0x60' 'M-`' assert_key 0x61 'a' -- 'Escape 0x61' 'M-a' assert_key 0x62 'b' -- 'Escape 0x62' 'M-b' assert_key 0x63 'c' -- 'Escape 0x63' 'M-c' assert_key 0x64 'd' -- 'Escape 0x64' 'M-d' assert_key 0x65 'e' -- 'Escape 0x65' 'M-e' assert_key 0x66 'f' -- 'Escape 0x66' 'M-f' assert_key 0x67 'g' -- 'Escape 0x67' 'M-g' assert_key 0x68 'h' -- 'Escape 0x68' 'M-h' assert_key 0x69 'i' -- 'Escape 0x69' 'M-i' assert_key 0x6A 'j' -- 'Escape 0x6A' 'M-j' assert_key 0x6B 'k' -- 'Escape 0x6B' 'M-k' assert_key 0x6C 'l' -- 'Escape 0x6C' 'M-l' assert_key 0x6D 'm' -- 'Escape 0x6D' 'M-m' assert_key 0x6E 'n' -- 'Escape 0x6E' 'M-n' assert_key 0x6F 'o' -- 'Escape 0x6F' 'M-o' assert_key 0x70 'p' -- 'Escape 0x70' 'M-p' assert_key 0x71 'q' -- 'Escape 0x71' 'M-q' assert_key 0x72 'r' -- 'Escape 0x72' 'M-r' assert_key 0x73 's' -- 'Escape 0x73' 'M-s' assert_key 0x74 't' -- 'Escape 0x74' 'M-t' assert_key 0x75 'u' -- 'Escape 0x75' 'M-u' assert_key 0x76 'v' -- 'Escape 0x76' 'M-v' assert_key 0x77 'w' -- 'Escape 0x77' 'M-w' assert_key 0x78 'x' -- 'Escape 0x78' 'M-x' assert_key 0x79 'y' -- 'Escape 0x79' 'M-y' assert_key 0x7A 'z' -- 'Escape 0x7A' 'M-z' assert_key 0x7B '{' -- 'Escape 0x7B' 'M-{' assert_key 0x7C '|' -- 'Escape 0x7C' 'M-|' assert_key 0x7D '}' -- 'Escape 0x7D' 'M-}' assert_key 0x7E '~' -- 'Escape 0x7E' 'M-~' assert_key 0x7F 'BSpace' -- 'Escape 0x7F' 'M-BSpace' # Numeric keypad assert_key 'Escape OM' 'KPEnter' -- 'Escape Escape OM' 'M-KPEnter' assert_key 'Escape Oj' 'KP*' -- 'Escape Escape Oj' 'M-KP*' assert_key 'Escape Ok' 'KP+' -- 'Escape Escape Ok' 'M-KP+' assert_key 'Escape Om' 'KP-' -- 'Escape Escape Om' 'M-KP-' assert_key 'Escape On' 'KP.' -- 'Escape Escape On' 'M-KP.' assert_key 'Escape Oo' 'KP/' -- 'Escape Escape Oo' 'M-KP/' assert_key 'Escape Op' 'KP0' -- 'Escape Escape Op' 'M-KP0' assert_key 'Escape Oq' 'KP1' -- 'Escape Escape Oq' 'M-KP1' assert_key 'Escape Or' 'KP2' -- 'Escape Escape Or' 'M-KP2' assert_key 'Escape Os' 'KP3' -- 'Escape Escape Os' 'M-KP3' assert_key 'Escape Ot' 'KP4' -- 'Escape Escape Ot' 'M-KP4' assert_key 'Escape Ou' 'KP5' -- 'Escape Escape Ou' 'M-KP5' assert_key 'Escape Ov' 'KP6' -- 'Escape Escape Ov' 'M-KP6' assert_key 'Escape Ow' 'KP7' -- 'Escape Escape Ow' 'M-KP7' assert_key 'Escape Ox' 'KP8' -- 'Escape Escape Ox' 'M-KP8' assert_key 'Escape Oy' 'KP9' -- 'Escape Escape Oy' 'M-KP9' # Arrow keys assert_key 'Escape OA' 'Up' -- 'Escape Escape OA' 'M-Up' assert_key 'Escape OB' 'Down' -- 'Escape Escape OB' 'M-Down' assert_key 'Escape OC' 'Right' -- 'Escape Escape OC' 'M-Right' assert_key 'Escape OD' 'Left' -- 'Escape Escape OD' 'M-Left' assert_key 'Escape [A' 'Up' -- 'Escape Escape [A' 'M-Up' assert_key 'Escape [B' 'Down' -- 'Escape Escape [B' 'M-Down' assert_key 'Escape [C' 'Right' -- 'Escape Escape [C' 'M-Right' assert_key 'Escape [D' 'Left' -- 'Escape Escape [D' 'M-Left' # Other xterm keys assert_key 'Escape OH' 'Home' -- 'Escape Escape OH' 'M-Home' assert_key 'Escape OF' 'End' -- 'Escape Escape OF' 'M-End' assert_key 'Escape [H' 'Home' -- 'Escape Escape [H' 'M-Home' assert_key 'Escape [F' 'End' -- 'Escape Escape [F' 'M-End' # rxvt arrow keys assert_key 'Escape Oa' 'C-Up' assert_key 'Escape Ob' 'C-Down' assert_key 'Escape Oc' 'C-Right' assert_key 'Escape Od' 'C-Left' assert_key 'Escape [a' 'S-Up' assert_key 'Escape [b' 'S-Down' assert_key 'Escape [c' 'S-Right' assert_key 'Escape [d' 'S-Left' # rxvt function keys assert_key 'Escape [11~' 'F1' assert_key 'Escape [12~' 'F2' assert_key 'Escape [13~' 'F3' assert_key 'Escape [14~' 'F4' assert_key 'Escape [15~' 'F5' assert_key 'Escape [17~' 'F6' assert_key 'Escape [18~' 'F7' assert_key 'Escape [19~' 'F8' assert_key 'Escape [20~' 'F9' assert_key 'Escape [21~' 'F10' assert_key 'Escape [23~' 'F11' assert_key 'Escape [24~' 'F12' # With TERM=screen, these will be seen as F11 and F12 # assert_key 'Escape [23~' 'S-F1' # assert_key 'Escape [24~' 'S-F2' assert_key 'Escape [25~' 'S-F3' assert_key 'Escape [26~' 'S-F4' assert_key 'Escape [28~' 'S-F5' assert_key 'Escape [29~' 'S-F6' assert_key 'Escape [31~' 'S-F7' assert_key 'Escape [32~' 'S-F8' assert_key 'Escape [33~' 'S-F9' assert_key 'Escape [34~' 'S-F10' assert_key 'Escape [23$' 'S-F11' assert_key 'Escape [24$' 'S-F12' assert_key 'Escape [11^' 'C-F1' assert_key 'Escape [12^' 'C-F2' assert_key 'Escape [13^' 'C-F3' assert_key 'Escape [14^' 'C-F4' assert_key 'Escape [15^' 'C-F5' assert_key 'Escape [17^' 'C-F6' assert_key 'Escape [18^' 'C-F7' assert_key 'Escape [19^' 'C-F8' assert_key 'Escape [20^' 'C-F9' assert_key 'Escape [21^' 'C-F10' assert_key 'Escape [23^' 'C-F11' assert_key 'Escape [24^' 'C-F12' assert_key 'Escape [11@' 'C-S-F1' assert_key 'Escape [12@' 'C-S-F2' assert_key 'Escape [13@' 'C-S-F3' assert_key 'Escape [14@' 'C-S-F4' assert_key 'Escape [15@' 'C-S-F5' assert_key 'Escape [17@' 'C-S-F6' assert_key 'Escape [18@' 'C-S-F7' assert_key 'Escape [19@' 'C-S-F8' assert_key 'Escape [20@' 'C-S-F9' assert_key 'Escape [21@' 'C-S-F10' assert_key 'Escape [23@' 'C-S-F11' assert_key 'Escape [24@' 'C-S-F12' # Focus tracking assert_key 'Escape [I' 'FocusIn' assert_key 'Escape [O' 'FocusOut' # Paste keys assert_key 'Escape [200~' 'PasteStart' assert_key 'Escape [201~' 'PasteEnd' assert_key 'Escape [Z' 'BTab' assert_extended_key () { code=$1 key_name=$2 assert_key "Escape [${code};5u" "C-$key_name" assert_key "Escape [${code};7u" "C-M-$key_name" } # Extended keys # assert_extended_key 65 'A' # assert_extended_key 66 'B' # assert_extended_key 67 'C' # assert_extended_key 68 'D' # assert_extended_key 69 'E' # assert_extended_key 70 'F' # assert_extended_key 71 'G' # assert_extended_key 72 'H' # assert_extended_key 73 'I' # assert_extended_key 74 'J' # assert_extended_key 75 'K' # assert_extended_key 76 'L' # assert_extended_key 77 'M' # assert_extended_key 78 'N' # assert_extended_key 79 'O' # assert_extended_key 80 'P' # assert_extended_key 81 'Q' # assert_extended_key 82 'R' # assert_extended_key 83 'S' # assert_extended_key 84 'T' # assert_extended_key 85 'U' # assert_extended_key 86 'V' # assert_extended_key 87 'W' # assert_extended_key 88 'X' # assert_extended_key 89 'Y' # assert_extended_key 90 'Z' # assert_extended_key 123 '{' # assert_extended_key 124 '|' # assert_extended_key 125 '}' # assert_key 'Escape [105;5u' 'C-i' # assert_key 'Escape [73;5u' 'C-I' # assert_key 'Escape [109;5u' 'C-m' # assert_key 'Escape [77;5u' 'C-M' # assert_key 'Escape [91;5u' 'C-[' assert_key 'Escape [123;5u' 'C-{' # assert_key 'Escape [64;5u' 'C-@' assert_key 'Escape [32;2u' 'S-Space' # assert_key 'Escape [32;6u' 'C-S-Space' assert_key 'Escape [9;5u' 'C-Tab' assert_key 'Escape [1;5Z' 'C-S-Tab' $TMUX kill-server 2>/dev/null $TMUX2 kill-server 2>/dev/null exit $exit_status tmux-tmux-f222026/regress/utf8-test.result000066400000000000000000000554371511153563100205430ustar00rootroot00000000000000UTF-8 decoder capability and stress test ---------------------------------------- Markus Kuhn - 2015-08-28 - CC BY 4.0 This test file can help you examine, how your UTF-8 decoder handles various types of correct, malformed, or otherwise interesting UTF-8 sequences. This file is not meant to be a conformance test. It does not prescribe any particular outcome. Therefore, there is no way to "pass" or "fail" this test file, even though the text does suggest a preferable decoder behaviour at some places. Its aim is, instead, to help you think about, and test, the behaviour of your UTF-8 decoder on a systematic collection of unusual inputs. Experience so far suggests that most first-time authors of UTF-8 decoders find at least one serious problem in their decoder using this file. The test lines below cover boundary conditions, malformed UTF-8 sequences, as well as correctly encoded UTF-8 sequences of Unicode code points that should never occur in a correct UTF-8 file. According to ISO 10646-1:2000, sections D.7 and 2.3c, a device receiving UTF-8 shall interpret a "malformed sequence in the same way that it interprets a character that is outside the adopted subset" and "characters that are not within the adopted subset shall be indicated to the user" by a receiving device. One commonly used approach in UTF-8 decoders is to replace any malformed UTF-8 sequence by a replacement character (U+FFFD), which looks a bit like an inverted question mark, or a similar symbol. It might be a good idea to visually distinguish a malformed UTF-8 sequence from a correctly encoded Unicode character that is just not available in the current font but otherwise fully legal, even though ISO 10646-1 doesn't mandate this. In any case, just ignoring malformed sequences or unavailable characters does not conform to ISO 10646, will make debugging more difficult, and can lead to user confusion. Please check, whether a malformed UTF-8 sequence is (1) represented at all, (2) represented by exactly one single replacement character (or equivalent signal), and (3) the following quotation mark after an illegal UTF-8 sequence is correctly displayed, i.e. proper resynchronization takes place immediately after any malformed sequence. This file says "THE END" in the last line, so if you don't see that, your decoder crashed somehow before, which should always be cause for concern. All lines in this file are exactly 79 characters long (plus the line feed). In addition, all lines end with "|", except for the two test lines 2.1.1 and 2.2.1, which contain non-printable ASCII controls U+0000 and U+007F. If you display this file with a fixed-width font, these "|" characters should all line up in column 79 (right margin). This allows you to test quickly, whether your UTF-8 decoder finds the correct number of characters in every line, that is whether each malformed sequences is replaced by a single replacement character. Note that, as an alternative to the notion of malformed sequence used here, it is also a perfectly acceptable (and in some situations even preferable) solution to represent each individual byte of a malformed sequence with a replacement character. If you follow this strategy in your decoder, then please ignore the "|" column. Here come the tests: | | 1 Some correct UTF-8 text | | You should see the Greek word 'kosme': "κόσμε" | | 2 Boundary condition test cases | | 2.1 First possible sequence of a certain length | | 2.1.1 1 byte (U-00000000): "" 2.1.2 2 bytes (U-00000080): "€" | 2.1.3 3 bytes (U-00000800): "à €" | 2.1.4 4 bytes (U-00010000): "ð€€" | 2.1.5 5 bytes (U-00200000): "�����" | 2.1.6 6 bytes (U-04000000): "������" | | 2.2 Last possible sequence of a certain length | | 2.2.1 1 byte (U-0000007F): "" 2.2.2 2 bytes (U-000007FF): "ß¿" | 2.2.3 3 bytes (U-0000FFFF): "ï¿¿" | 2.2.4 4 bytes (U-001FFFFF): "����" | 2.2.5 5 bytes (U-03FFFFFF): "�����" | 2.2.6 6 bytes (U-7FFFFFFF): "������" | | 2.3 Other boundary conditions | | 2.3.1 U-0000D7FF = ed 9f bf = "퟿" | 2.3.2 U-0000E000 = ee 80 80 = "" | 2.3.3 U-0000FFFD = ef bf bd = "�" | 2.3.4 U-0010FFFF = f4 8f bf bf = "ô¿¿" | 2.3.5 U-00110000 = f4 90 80 80 = "�" | | 3 Malformed sequences | | 3.1 Unexpected continuation bytes | | Each unexpected continuation byte should be separately signalled as a | malformed sequence of its own. | | 3.1.1 First continuation byte 0x80: "�" | 3.1.2 Last continuation byte 0xbf: "�" | | 3.1.3 2 continuation bytes: "��" | 3.1.4 3 continuation bytes: "���" | 3.1.5 4 continuation bytes: "����" | 3.1.6 5 continuation bytes: "�����" | 3.1.7 6 continuation bytes: "������" | 3.1.8 7 continuation bytes: "�������" | | 3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf): | | "���������������� | ���������������� | ���������������� | ����������������" | | 3.2 Lonely start characters | | 3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf), | each followed by a space character: | | "� � � � � � � � � � � � � � � � | � � � � � � � � � � � � � � � � " | | 3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef), | each followed by a space character: | | "� � � � � � � � � � � � � � � � " | | 3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7), | each followed by a space character: | | "� � � � � � � � " | | 3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb), | each followed by a space character: | | "� � � � " | | 3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd), | each followed by a space character: | | "� � " | | 3.3 Sequences with last continuation byte missing | | All bytes of an incomplete sequence should be signalled as a single | malformed sequence, i.e., you should see only a single replacement | character in each of the next 10 tests. (Characters as in section 2) | | 3.3.1 2-byte sequence with last byte missing (U+0000): "�" | 3.3.2 3-byte sequence with last byte missing (U+0000): "�" | 3.3.3 4-byte sequence with last byte missing (U+0000): "�" | 3.3.4 5-byte sequence with last byte missing (U+0000): "����" | 3.3.5 6-byte sequence with last byte missing (U+0000): "�����" | 3.3.6 2-byte sequence with last byte missing (U-000007FF): "�" | 3.3.7 3-byte sequence with last byte missing (U-0000FFFF): "�" | 3.3.8 4-byte sequence with last byte missing (U-001FFFFF): "���" | 3.3.9 5-byte sequence with last byte missing (U-03FFFFFF): "����" | 3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF): "�����" | | 3.4 Concatenation of incomplete sequences | | All the 10 sequences of 3.3 concatenated, you should see 10 malformed | sequences being signalled: | | "���������������������������" | | 3.5 Impossible bytes | | The following two bytes cannot appear in a correct UTF-8 string | | 3.5.1 fe = "�" | 3.5.2 ff = "�" | 3.5.3 fe fe ff ff = "����" | | 4 Overlong sequences | | The following sequences are not malformed according to the letter of | the Unicode 2.0 standard. However, they are longer then necessary and | a correct UTF-8 encoder is not allowed to produce them. A "safe UTF-8 | decoder" should reject them just like malformed sequences for two | reasons: (1) It helps to debug applications if overlong sequences are | not treated as valid representations of characters, because this helps | to spot problems more quickly. (2) Overlong sequences provide | alternative representations of characters, that could maliciously be | used to bypass filters that check only for ASCII characters. For | instance, a 2-byte encoded line feed (LF) would not be caught by a | line counter that counts only 0x0a bytes, but it would still be | processed as a line feed by an unsafe UTF-8 decoder later in the | pipeline. From a security point of view, ASCII compatibility of UTF-8 | sequences means also, that ASCII characters are *only* allowed to be | represented by ASCII bytes in the range 0x00-0x7f. To ensure this | aspect of ASCII compatibility, use only "safe UTF-8 decoders" that | reject overlong UTF-8 sequences for which a shorter encoding exists. | | 4.1 Examples of an overlong ASCII character | | With a safe UTF-8 decoder, all of the following five overlong | representations of the ASCII character slash ("/") should be rejected | like a malformed UTF-8 sequence, for instance by substituting it with | a replacement character. If you see a slash below, you do not have a | safe UTF-8 decoder! | | 4.1.1 U+002F = c0 af = "��" | 4.1.2 U+002F = e0 80 af = "�" | 4.1.3 U+002F = f0 80 80 af = "�" | 4.1.4 U+002F = f8 80 80 80 af = "�����" | 4.1.5 U+002F = fc 80 80 80 80 af = "������" | | 4.2 Maximum overlong sequences | | Below you see the highest Unicode value that is still resulting in an | overlong sequence if represented with the given number of bytes. This | is a boundary test for safe UTF-8 decoders. All five characters should | be rejected like malformed UTF-8 sequences. | | 4.2.1 U-0000007F = c1 bf = "��" | 4.2.2 U-000007FF = e0 9f bf = "�" | 4.2.3 U-0000FFFF = f0 8f bf bf = "�" | 4.2.4 U-001FFFFF = f8 87 bf bf bf = "�����" | 4.2.5 U-03FFFFFF = fc 83 bf bf bf bf = "������" | | 4.3 Overlong representation of the NUL character | | The following five sequences should also be rejected like malformed | UTF-8 sequences and should not be treated like the ASCII NUL | character. | | 4.3.1 U+0000 = c0 80 = "��" | 4.3.2 U+0000 = e0 80 80 = "�" | 4.3.3 U+0000 = f0 80 80 80 = "�" | 4.3.4 U+0000 = f8 80 80 80 80 = "�����" | 4.3.5 U+0000 = fc 80 80 80 80 80 = "������" | | 5 Illegal code positions | | The following UTF-8 sequences should be rejected like malformed | sequences, because they never represent valid ISO 10646 characters and | a UTF-8 decoder that accepts them might introduce security problems | comparable to overlong UTF-8 sequences. | | 5.1 Single UTF-16 surrogates | | 5.1.1 U+D800 = ed a0 80 = "�" | 5.1.2 U+DB7F = ed ad bf = "�" | 5.1.3 U+DB80 = ed ae 80 = "�" | 5.1.4 U+DBFF = ed af bf = "�" | 5.1.5 U+DC00 = ed b0 80 = "�" | 5.1.6 U+DF80 = ed be 80 = "�" | 5.1.7 U+DFFF = ed bf bf = "�" | | 5.2 Paired UTF-16 surrogates | | 5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80 = "��" | 5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf = "��" | 5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80 = "��" | 5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf = "��" | 5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80 = "��" | 5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf = "��" | 5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80 = "��" | 5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf = "��" | | 5.3 Noncharacter code positions | | The following "noncharacters" are "reserved for internal use" by | applications, and according to older versions of the Unicode Standard | "should never be interchanged". Unicode Corrigendum #9 dropped the | latter restriction. Nevertheless, their presence in incoming UTF-8 data | can remain a potential security risk, depending on what use is made of | these codes subsequently. Examples of such internal use: | | - Some file APIs with 16-bit characters may use the integer value -1 | = U+FFFF to signal an end-of-file (EOF) or error condition. | | - In some UTF-16 receivers, code point U+FFFE might trigger a | byte-swap operation (to convert between UTF-16LE and UTF-16BE). | | With such internal use of noncharacters, it may be desirable and safer | to block those code points in UTF-8 decoders, as they should never | occur legitimately in incoming UTF-8 data, and could trigger unsafe | behaviour in subsequent processing. | | Particularly problematic noncharacters in 16-bit applications: | | 5.3.1 U+FFFE = ef bf be = "￾" | 5.3.2 U+FFFF = ef bf bf = "ï¿¿" | | Other noncharacters: | | 5.3.3 U+FDD0 .. U+FDEF = "ï·ï·‘﷒﷓﷔﷕﷖﷗﷘﷙﷚﷛﷜ï·ï·žï·Ÿï· ï·¡ï·¢ï·£ï·¤ï·¥ï·¦ï·§ï·¨ï·©ï·ªï·«ï·¬ï·­ï·®ï·¯"| | 5.3.4 U+nFFFE U+nFFFF (for n = 1..10) | | "🿾🿿𯿾𯿿𿿾𿿿ñ¿¾ñ¿¿ñŸ¿¾ñŸ¿¿ñ¯¿¾ñ¯¿¿ñ¿¿¾ñ¿¿¿ò¿¾ò¿¿ | òŸ¿¾òŸ¿¿ò¯¿¾ò¯¿¿ò¿¿¾ò¿¿¿ó¿¾ó¿¿óŸ¿¾óŸ¿¿ó¯¿¾ó¯¿¿ó¿¿¾ó¿¿¿ô¿¾ô¿¿" | | THE END | tmux-tmux-f222026/regress/utf8-test.sh000066400000000000000000000006501511153563100176220ustar00rootroot00000000000000#!/bin/sh PATH=/bin:/usr/bin TERM=screen [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) TMUX="$TEST_TMUX -Ltest" TMP=$(mktemp) trap "rm -f $TMP" 0 1 15 $TMUX kill-server 2>/dev/null $TMUX -f/dev/null \ set -g remain-on-exit on \; \ set -g remain-on-exit-format '' \; \ new -d -- cat UTF-8-test.txt sleep 1 $TMUX capturep -pCeJS- >$TMP $TMUX kill-server cmp -s $TMP utf8-test.result || exit 1 exit 0 tmux-tmux-f222026/regsub.c000066400000000000000000000056031511153563100154070ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" static void regsub_copy(char **buf, ssize_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, ssize_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); } tmux-tmux-f222026/resize.c000066400000000000000000000274411511153563100154250ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "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, 1); /* 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, __unused int type, __unused int current, struct session *s, struct window *w) { 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); } tmux-tmux-f222026/screen-redraw.c000066400000000000000000000715761511153563100166750ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" 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 *); static void screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *); static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *, struct window_pane *, int, int, int, u_int, u_int, u_int); static void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *, struct window_pane *); #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; case PANE_LINES_SPACES: gc->attr &= ~GRID_ATTR_CHARSET; utf8_set(&gc->data, ' '); 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 screen_redraw_ctx *ctx, struct window_pane *wp, u_int px, u_int py) { struct options *oo = wp->window->options; u_int ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy; int hsplit = 0, vsplit = 0, pane_status = ctx->pane_status; int pane_scrollbars = ctx->pane_scrollbars, sb_w = 0; int sb_pos; if (pane_scrollbars != 0) sb_pos = ctx->pane_scrollbars_pos; else sb_pos = 0; /* 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: hsplit = screen_redraw_two_panes(wp->window, 0); vsplit = screen_redraw_two_panes(wp->window, 1); break; } /* Are scrollbars enabled? */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; /* * Left/right borders. The wp->sy / 2 test is to colour only half the * active window's border when there are two panes. */ if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) { if (sb_pos == PANE_SCROLLBARS_LEFT) { if (wp->xoff - sb_w == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff - sb_w != 0) { if (px == wp->xoff - sb_w - 1 && (!hsplit || (hsplit && py > wp->sy / 2))) return (SCREEN_REDRAW_BORDER_LEFT); if (px == wp->xoff + wp->sx + sb_w - 1) return (SCREEN_REDRAW_BORDER_RIGHT); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/ if (wp->xoff == 0 && px == wp->sx + sb_w) if (!hsplit || (hsplit && py <= wp->sy / 2)) return (SCREEN_REDRAW_BORDER_RIGHT); if (wp->xoff != 0) { if (px == wp->xoff - 1 && (!hsplit || (hsplit && py > wp->sy / 2))) return (SCREEN_REDRAW_BORDER_LEFT); if (px == wp->xoff + wp->sx + sb_w) return (SCREEN_REDRAW_BORDER_RIGHT); } } } /* Top/bottom borders. */ if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) { 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 (sb_pos == PANE_SCROLLBARS_LEFT) { if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { if (wp->yoff != 0 && py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); if (py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || px >= wp->xoff) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { if (wp->yoff != 0 && py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); 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 screen_redraw_ctx *ctx, u_int px, u_int py) { struct client *c = ctx->c; struct window *w = c->session->curw->window; struct window_pane *wp; u_int sy = w->sy; if (ctx->pane_status == PANE_STATUS_BOTTOM) sy--; /* Outside the window? */ if (px > w->sx || py > sy) return (0); /* On the window border? */ if (px == w->sx || py == sy) return (1); /* Check all the panes. */ TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; switch (screen_redraw_pane_border(ctx, wp, px, py)) { 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 screen_redraw_ctx *ctx, u_int px, u_int py) { struct client *c = ctx->c; int pane_status = ctx->pane_status; struct window *w = c->session->curw->window; u_int sx = w->sx, sy = w->sy; int borders = 0; if (pane_status == PANE_STATUS_BOTTOM) sy--; /* Is this outside the window? */ if (px > sx || py > sy) return (CELL_OUTSIDE); /* * Construct a bitmask of whether the cells to the left (bit 8), right, * top, and bottom (bit 1) of this cell are borders. * * bits 8 4 2 1: 2 * 8 + 4 * 1 */ if (px == 0 || screen_redraw_cell_border(ctx, px - 1, py)) borders |= 8; if (px <= sx && screen_redraw_cell_border(ctx, px + 1, py)) borders |= 4; if (pane_status == PANE_STATUS_TOP) { if (py != 0 && screen_redraw_cell_border(ctx, px, py - 1)) borders |= 2; if (screen_redraw_cell_border(ctx, px, py + 1)) borders |= 1; } else if (pane_status == PANE_STATUS_BOTTOM) { if (py == 0 || screen_redraw_cell_border(ctx, px, py - 1)) borders |= 2; if (py != sy && screen_redraw_cell_border(ctx, px, py + 1)) borders |= 1; } else { if (py == 0 || screen_redraw_cell_border(ctx, px, py - 1)) borders |= 2; if (screen_redraw_cell_border(ctx, px, py + 1)) 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 screen_redraw_ctx *ctx, u_int px, u_int py, struct window_pane **wpp) { struct client *c = ctx->c; struct window *w = c->session->curw->window; struct window_pane *wp, *active; int pane_status = ctx->pane_status; u_int sx = w->sx, sy = w->sy; int border, pane_scrollbars = ctx->pane_scrollbars; u_int right, line; int sb_pos = ctx->pane_scrollbars_pos; int sb_w; *wpp = NULL; if (px > sx || py > sy) return (CELL_OUTSIDE); if (px == sx || py == sy) /* window border */ return (screen_redraw_type_of_cell(ctx, px, py)); 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 + 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; /* Check if CELL_SCROLLBAR */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) { if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else line = wp->yoff + wp->sy; /* * Check if py could lie within a scrollbar. If the * pane is at the top then py == 0 to sy; if the pane * is not at the top, then yoff to yoff + sy. */ sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; if ((pane_status && py != line) || (wp->yoff == 0 && py < wp->sy) || (py >= wp->yoff && py < wp->yoff + wp->sy)) { /* Check if px lies within a scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && (px >= wp->xoff + wp->sx && px < wp->xoff + wp->sx + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && (px >= wp->xoff - sb_w && px < wp->xoff))) return (CELL_SCROLLBAR); } } /* * If definitely inside, return. If not on border, skip. * Otherwise work out the cell. */ border = screen_redraw_pane_border(ctx, wp, px, py); if (border == SCREEN_REDRAW_INSIDE) return (CELL_INSIDE); if (border == SCREEN_REDRAW_OUTSIDE) goto next2; return (screen_redraw_type_of_cell(ctx, px, py)); 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(struct screen_redraw_ctx *ctx, u_int px, u_int py, struct window_pane *wp) { enum screen_redraw_border_type border; border = screen_redraw_pane_border(ctx, wp, px, py); 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, sb_w = 0; int pane_scrollbars = rctx->pane_scrollbars; u_int width, i, cell_type, px, py; struct screen_write_ctx ctx; struct screen old; if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; 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 + sb_w - 2; 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 (pane_status == PANE_STATUS_TOP) py = wp->yoff - 1; else py = wp->yoff + wp->sy; cell_type = screen_redraw_type_of_cell(rctx, px, py); 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 uint64_t screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags) { struct client *c = ctx->c; struct window *w = c->session->curw->window; struct window_pane *wp; int redraw; enum pane_lines lines; 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 (ctx->pane_status != PANE_STATUS_OFF) { lines = ctx->pane_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"); ctx->pane_scrollbars = options_get_number(wo, "pane-scrollbars"); ctx->pane_scrollbars_pos = options_get_number(wo, "pane-scrollbars-position"); 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; uint64_t flags; if (c->flags & CLIENT_SUSPENDED) return; screen_redraw_set_context(c, &ctx); flags = screen_redraw_update(&ctx, c->flags); if ((flags & CLIENT_ALLREDRAWFLAGS) == 0) return; 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); screen_redraw_draw_borders(&ctx); if (ctx.pane_status != PANE_STATUS_OFF) screen_redraw_draw_pane_status(&ctx); screen_redraw_draw_pane_scrollbars(&ctx); } if (flags & CLIENT_REDRAWWINDOW) { log_debug("%s: redrawing panes", c->name); screen_redraw_draw_panes(&ctx); screen_redraw_draw_pane_scrollbars(&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 and its scrollbar. */ void screen_redraw_pane(struct client *c, struct window_pane *wp, int redraw_scrollbar_only) { 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); if (!redraw_scrollbar_only) screen_redraw_draw_pane(&ctx, wp); if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars)) screen_redraw_draw_pane_scrollbar(&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(ctx, x, y, 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, 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(ctx, x, y, &wp); if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR) 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(ctx, x, y, 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(ctx, active, x, y); 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(ctx, x, y, active)) { gc.attr |= GRID_ATTR_CHARSET; utf8_set(&gc.data, BORDER_MARKERS[border]); } } tty_cell(tty, &gc, &grid_default_cell, NULL, NULL); if (isolates) tty_puts(tty, START_ISOLATE); } /* 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); } #ifdef ENABLE_SIXEL tty_draw_images(c, wp, s); #endif } /* Draw the panes scrollbars */ static void screen_redraw_draw_pane_scrollbars(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_show_scrollbar(wp, ctx->pane_scrollbars) && window_pane_visible(wp)) screen_redraw_draw_pane_scrollbar(ctx, wp); } } /* Draw pane scrollbar. */ void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx, struct window_pane *wp) { struct screen *s = wp->screen; double percent_view; u_int sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy; u_int sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y; int sb_w = wp->scrollbar_style.width; int sb_pad = wp->scrollbar_style.pad; int cm_y, cm_size, xoff = wp->xoff, ox = ctx->ox; int sb_x, sb_y = (int)(wp->yoff - ctx->oy); /* sb top */ if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) { if (sb == PANE_SCROLLBARS_MODAL) return; /* Show slider at the bottom of the scrollbar. */ total_height = screen_size_y(s) + screen_hsize(s); percent_view = (double)sb_h / total_height; slider_h = (double)sb_h * percent_view; slider_y = sb_h - slider_h; } else { if (TAILQ_FIRST(&wp->modes) == NULL) return; if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0) return; total_height = cm_size + sb_h; percent_view = (double)sb_h / (cm_size + sb_h); slider_h = (double)sb_h * percent_view; slider_y = (sb_h + 1) * ((double)cm_y / total_height); } if (sb_pos == PANE_SCROLLBARS_LEFT) sb_x = xoff - sb_w - sb_pad - ox; else sb_x = xoff + wp->sx - ox; if (slider_h < 1) slider_h = 1; if (slider_y >= sb_h) slider_y = sb_h - 1; screen_redraw_draw_scrollbar(ctx, wp, sb_pos, sb_x, sb_y, sb_h, slider_h, slider_y); /* Store current position and height of the slider */ wp->sb_slider_y = slider_y; /* top of slider y pos in scrollbar */ wp->sb_slider_h = slider_h; /* height of slider */ } static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx, struct window_pane *wp, int sb_pos, int sb_x, int sb_y, u_int sb_h, u_int slider_h, u_int slider_y) { struct client *c = ctx->c; struct tty *tty = &c->tty; struct grid_cell gc, slgc, *gcp; struct style *sb_style = &wp->scrollbar_style; u_int i, j, imax, jmax; u_int sb_w = sb_style->width, sb_pad = sb_style->pad; int px, py, ox = ctx->ox, oy = ctx->oy; int sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff; int yoff = wp->yoff; /* Set up style for slider. */ gc = sb_style->gc; memcpy(&slgc, &gc, sizeof slgc); slgc.fg = gc.bg; slgc.bg = gc.fg; imax = sb_w + sb_pad; if ((int)imax + sb_x > sx) imax = sx - sb_x; jmax = sb_h; if ((int)jmax + sb_y > sy) jmax = sy - sb_y; for (j = 0; j < jmax; j++) { py = sb_y + j; for (i = 0; i < imax; i++) { px = sb_x + i; if (px < xoff - ox - (int)sb_w - (int)sb_pad || px >= sx || px < 0 || py < yoff - oy - 1 || py >= sy || py < 0) continue; tty_cursor(tty, px, py); if ((sb_pos == PANE_SCROLLBARS_LEFT && i >= sb_w && i < sb_w + sb_pad) || (sb_pos == PANE_SCROLLBARS_RIGHT && i < sb_pad)) { tty_cell(tty, &grid_default_cell, &grid_default_cell, NULL, NULL); } else { if (j >= slider_y && j < slider_y + slider_h) gcp = &slgc; else gcp = &gc; tty_cell(tty, gcp, &grid_default_cell, NULL, NULL); } } } } tmux-tmux-f222026/screen-write.c000066400000000000000000001631711511153563100165340ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" 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 int screen_write_combine(struct screen_write_ctx *, const struct grid_cell *); 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 (ttyctx->allow_invisible_panes) { if (session_has(c->session, wp->window)) return (1); return (0); } if (c->session->curw->window != wp->window) return (0); if (wp->layout_cell == NULL) 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|PANE_REDRAWSCROLLBAR); 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; if (options_get_number(global_options, "extended-keys") == 2) s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; 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 == '\t' || (*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 == '\t' || (*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 window_pane *wp = ctx->wp; struct tty_ctx ttyctx; struct grid *gd = src->grid; struct grid_cell gc; u_int xx, yy, cx = s->cx, cy = s->cy; if (nx == 0 || ny == 0) return; for (yy = py; yy < py + ny; yy++) { if (yy >= gd->hsize + gd->sy) break; s->cx = cx; if (wp != NULL) screen_write_initctx(ctx, &ttyctx, 0); for (xx = px; xx < px + nx; xx++) { if (xx >= grid_get_line(gd, yy)->cellsize && s->cx >= grid_get_line(ctx->s->grid, s->cy)->cellsize) break; grid_get_cell(gd, xx, yy, &gc); if (xx + gc.data.width > px + nx) break; grid_view_set_cell(ctx->s->grid, s->cx, s->cy, &gc); if (wp != NULL) { ttyctx.cell = &gc; tty_write(tty_cmd_cell, &ttyctx); ttyctx.ocx++; } s->cx++; } s->cy++; } s->cx = cx; s->cy = cy; } /* Select character set for drawing border lines. */ static void screen_write_box_border_set(enum box_lines lines, int cell_type, struct grid_cell *gc) { switch (lines) { case BOX_LINES_NONE: break; case BOX_LINES_DOUBLE: gc->attr &= ~GRID_ATTR_CHARSET; utf8_copy(&gc->data, tty_acs_double_borders(cell_type)); break; case BOX_LINES_HEAVY: gc->attr &= ~GRID_ATTR_CHARSET; utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type)); break; case BOX_LINES_ROUNDED: gc->attr &= ~GRID_ATTR_CHARSET; utf8_copy(&gc->data, tty_acs_rounded_borders(cell_type)); break; case BOX_LINES_SIMPLE: gc->attr &= ~GRID_ATTR_CHARSET; utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]); break; case BOX_LINES_PADDED: gc->attr &= ~GRID_ATTR_CHARSET; utf8_set(&gc->data, PADDED_BORDERS[cell_type]); break; case BOX_LINES_SINGLE: case BOX_LINES_DEFAULT: gc->attr |= GRID_ATTR_CHARSET; utf8_set(&gc->data, CELL_BORDERS[cell_type]); break; } } /* Draw a horizontal line on screen. */ void screen_write_hline(struct screen_write_ctx *ctx, u_int nx, int left, int right, enum box_lines lines, const struct grid_cell *border_gc) { struct screen *s = ctx->s; struct grid_cell gc; u_int cx, cy, i; cx = s->cx; cy = s->cy; if (border_gc != NULL) memcpy(&gc, border_gc, sizeof gc); else memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= GRID_ATTR_CHARSET; if (left) screen_write_box_border_set(lines, CELL_LEFTJOIN, &gc); else screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); screen_write_cell(ctx, &gc); screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); for (i = 1; i < nx - 1; i++) screen_write_cell(ctx, &gc); if (right) screen_write_box_border_set(lines, CELL_RIGHTJOIN, &gc); else screen_write_box_border_set(lines, CELL_LEFTRIGHT, &gc); screen_write_cell(ctx, &gc); screen_write_set_cursor(ctx, cx, cy); } /* 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, enum box_lines lines, const struct grid_cell *menu_gc, const struct grid_cell *border_gc, const struct grid_cell *choice_gc) { struct screen *s = ctx->s; struct grid_cell default_gc; const struct grid_cell *gc = &default_gc; u_int cx, cy, i, j, width = menu->width; const char *name; cx = s->cx; cy = s->cy; memcpy(&default_gc, menu_gc, sizeof default_gc); screen_write_box(ctx, menu->width + 4, menu->count + 2, lines, border_gc, menu->title); for (i = 0; i < menu->count; i++) { name = menu->items[i].name; if (name == NULL) { screen_write_cursormove(ctx, cx, cy + 1 + i, 0); screen_write_hline(ctx, width + 4, 1, 1, lines, border_gc); continue; } if (choice >= 0 && i == (u_int)choice && *name != '-') gc = choice_gc; screen_write_cursormove(ctx, cx + 1, cy + 1 + i, 0); for (j = 0; j < width + 2; j++) screen_write_putc(ctx, gc, ' '); screen_write_cursormove(ctx, cx + 2, cy + 1 + i, 0); if (*name == '-') { default_gc.attr |= GRID_ATTR_DIM; format_draw(ctx, gc, width, name + 1, NULL, 0); default_gc.attr &= ~GRID_ATTR_DIM; continue; } format_draw(ctx, gc, width, name, NULL, 0); gc = &default_gc; } screen_write_set_cursor(ctx, cx, cy); } /* Draw a box on screen. */ 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'); #ifdef ENABLE_SIXEL if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif for (yy = 0; yy < screen_size_y(s); yy++) { for (xx = 0; xx < screen_size_x(s); xx++) grid_view_set_cell(s->grid, xx, yy, &gc); } 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; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; 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; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; 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; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 0); ttyctx.bg = bg; 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; #ifdef ENABLE_SIXEL u_int sy = screen_size_y(s); #endif if (ny == 0) ny = 1; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif if (s->cy < s->rupper || s->cy > s->rlower) { if (ny > screen_size_y(s) - s->cy) ny = screen_size_y(s) - s->cy; 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; u_int sy = screen_size_y(s); if (ny == 0) ny = 1; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif if (s->cy < s->rupper || s->cy > s->rlower) { if (ny > sy - s->cy) ny = sy - 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; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); screen_write_collect_clear(ctx, s->cy, 1); 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; #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); before = screen_write_collect_trim(ctx, s->cy, s->cx, sx - s->cx, NULL); 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; } #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif if (s->cx > sx - 1) grid_view_clear(s->grid, 0, s->cy, sx, 1, bg); else 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) { #ifdef ENABLE_SIXEL if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); screen_write_collect_flush(ctx, 0, __func__); 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; #ifdef ENABLE_SIXEL int redraw = 0; #endif u_int rupper = s->rupper, rlower = s->rlower; gl = grid_get_line(gd, gd->hsize + s->cy); if (wrapped) gl->flags |= GRID_LINE_WRAPPED; log_debug("%s: at %u,%u (region %u-%u)", __func__, s->cx, s->cy, rupper, rlower); if (bg != ctx->bg) { screen_write_collect_flush(ctx, 1, __func__); ctx->bg = bg; } if (s->cy == s->rlower) { #ifdef ENABLE_SIXEL if (rlower == screen_size_y(s) - 1) redraw = image_scroll_up(s, 1); else redraw = image_check_line(s, rupper, rlower - rupper); if (redraw && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); screen_write_collect_scroll(ctx, bg); ctx->scrolled++; } 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; } #ifdef ENABLE_SIXEL if (image_scroll_up(s, lines) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif for (i = 0; i < lines; i++) { grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); screen_write_collect_scroll(ctx, bg); } 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; #ifdef ENABLE_SIXEL if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif for (i = 0; i < lines; i++) grid_view_scroll_region_down(gd, s->rupper, s->rlower, bg); 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); #ifdef ENABLE_SIXEL if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; /* 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); #ifdef ENABLE_SIXEL if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; 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); #ifdef ENABLE_SIXEL if (image_free_all(s) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif screen_write_initctx(ctx, &ttyctx, 1); ttyctx.bg = bg; /* 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); if (ttyctx.redraw_cb != NULL) 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); if (ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAWSCROLLBAR; } 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 (xx != s->cx) { if (xx == 0) grid_view_get_cell(s->grid, 0, s->cy, &gc); if (gc.data.width > 1) { grid_view_set_cell(s->grid, xx, s->cy, &grid_default_cell); } } } #ifdef ENABLE_SIXEL if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; #endif grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, ci->used); screen_write_set_cursor(ctx, s->cx + ci->used, -1); 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->flags & GRID_FLAG_TAB) 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; 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 = ud->width, xx, not_wrap; int selected, skip = 1; /* Ignore padding cells. */ if (gc->flags & GRID_FLAG_PADDING) return; /* Get the previous cell to check for combining. */ if (screen_write_combine(ctx, gc) != 0) 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, 0, __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. */ not_wrap = !(s->mode & MODE_WRAP); if (s->cx <= sx - not_wrap - width) screen_write_set_cursor(ctx, s->cx + width, -1); else screen_write_set_cursor(ctx, sx - not_wrap, -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 if necessary. */ static int screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc) { struct screen *s = ctx->s; struct grid *gd = s->grid; const struct utf8_data *ud = &gc->data; u_int n, cx = s->cx, cy = s->cy; struct grid_cell last; struct tty_ctx ttyctx; int force_wide = 0, zero_width = 0; /* Ignore U+3164 HANGUL_FILLER entirely. */ if (utf8_is_hangul_filler(ud)) return (1); /* * Is this character which makes no sense without being combined? If * this is true then flag it here and discard the character (return 1) * if we cannot combine it. */ if (utf8_is_zwj(ud)) zero_width = 1; else if (utf8_is_vs(ud)) { zero_width = 1; if (options_get_number(global_options, "variation-selector-always-wide")) force_wide = 1; } else if (ud->width == 0) zero_width = 1; /* Cannot combine empty character or at left. */ if (ud->size < 2 || cx == 0) return (zero_width); log_debug("%s: character %.*s at %u,%u (width %u)", __func__, (int)ud->size, ud->data, cx, cy, ud->width); /* Find the cell to combine with. */ n = 1; grid_view_get_cell(gd, cx - n, cy, &last); if (cx != 1 && (last.flags & GRID_FLAG_PADDING)) { n = 2; grid_view_get_cell(gd, cx - n, cy, &last); } if (n != last.data.width || (last.flags & GRID_FLAG_PADDING)) return (zero_width); /* * Check if we need to combine characters. This could be a Korean * Hangul Jamo character, zero width (set above), a modifier character * (with an existing Unicode character) or a previous ZWJ. */ if (!zero_width) { switch (hanguljamo_check_state(&last.data, ud)) { case HANGULJAMO_STATE_NOT_COMPOSABLE: return (1); case HANGULJAMO_STATE_CHOSEONG: return (0); case HANGULJAMO_STATE_COMPOSABLE: break; case HANGULJAMO_STATE_NOT_HANGULJAMO: if (utf8_should_combine(&last.data, ud)) force_wide = 1; else if (!utf8_has_zwj(&last.data)) return (0); break; } } /* Check if this combined character would be too long. */ if (last.data.size + ud->size > sizeof last.data.data) return (0); /* Combining; flush any pending output. */ screen_write_collect_flush(ctx, 0, __func__); log_debug("%s: %.*s -> %.*s at %u,%u (offset %u, width %u)", __func__, (int)ud->size, ud->data, (int)last.data.size, last.data.data, cx - n, cy, n, last.data.width); /* Append the data. */ memcpy(last.data.data + last.data.size, ud->data, ud->size); last.data.size += ud->size; /* Force the width to 2 for modifiers and variation selector. */ if (last.data.width == 1 && force_wide) { last.data.width = 2; n = 2; cx++; } else force_wide = 0; /* Set the new cell. */ grid_view_set_cell(gd, cx - n, cy, &last); if (force_wide) grid_view_set_padding(gd, cx - 1, cy); /* * Redraw the combined cell. If forcing the cell to width 2, reset the * cached cursor position in the tty, since we don't really know * whether the terminal thought the character was width 1 or width 2 * and what it is going to do now. */ screen_write_set_cursor(ctx, cx - n, cy); screen_write_initctx(ctx, &ttyctx, 0); ttyctx.cell = &last; ttyctx.num = force_wide; /* reset cached cursor position */ tty_write(tty_cmd_cell, &ttyctx); screen_write_set_cursor(ctx, cx, cy); return (1); } /* * 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); if (gc->flags & GRID_FLAG_TAB) { memcpy(&tmp_gc, gc, sizeof tmp_gc); memset(tmp_gc.data.data, 0, sizeof tmp_gc.data.data); *tmp_gc.data.data = ' '; tmp_gc.data.width = tmp_gc.data.size = tmp_gc.data.have = 1; grid_view_set_cell(gd, xx, s->cy, &tmp_gc); } else 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, const char *flags, u_char *str, u_int len) { struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; ttyctx.ptr2 = (void *)flags; ttyctx.num = len; tty_write(tty_cmd_setselection, &ttyctx); } /* Write unmodified string. */ void screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len, int allow_invisible_panes) { struct tty_ctx ttyctx; screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = str; ttyctx.num = len; ttyctx.allow_invisible_panes = allow_invisible_panes; tty_write(tty_cmd_rawstring, &ttyctx); } #ifdef ENABLE_SIXEL /* Write a SIXEL image. */ void screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg) { struct screen *s = ctx->s; struct grid *gd = s->grid; struct tty_ctx ttyctx; u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; struct sixel_image *new; sixel_size_in_cells(si, &x, &y); if (x > screen_size_x(s) || y > screen_size_y(s)) { if (x > screen_size_x(s) - cx) sx = screen_size_x(s) - cx; else sx = x; if (y > screen_size_y(s) - 1) sy = screen_size_y(s) - 1; else sy = y; new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); sixel_free(si); si = new; /* Bail out if the image cannot be scaled. */ if (si == NULL) return; sixel_size_in_cells(si, &x, &y); } sy = screen_size_y(s) - cy; if (sy < y) { lines = y - sy + 1; if (image_scroll_up(s, lines) && ctx->wp != NULL) ctx->wp->flags |= PANE_REDRAW; for (i = 0; i < lines; i++) { grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1, bg); screen_write_collect_scroll(ctx, bg); } ctx->scrolled += lines; if (lines > cy) screen_write_cursormove(ctx, -1, 0, 0); else screen_write_cursormove(ctx, -1, cy - lines, 0); } screen_write_collect_flush(ctx, 0, __func__); screen_write_initctx(ctx, &ttyctx, 0); ttyctx.ptr = image_store(s, si); tty_write(tty_cmd_sixelimage, &ttyctx); screen_write_cursormove(ctx, 0, cy + y, 0); } #endif /* Turn alternate screen on. */ void screen_write_alternateon(struct screen_write_ctx *ctx, struct grid_cell *gc, 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); if (wp != NULL) layout_fix_panes(wp->window, NULL); screen_write_initctx(ctx, &ttyctx, 1); if (ttyctx.redraw_cb != NULL) 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); if (wp != NULL) layout_fix_panes(wp->window, NULL); screen_write_initctx(ctx, &ttyctx, 1); if (ttyctx.redraw_cb != NULL) ttyctx.redraw_cb(&ttyctx); } tmux-tmux-f222026/screen.c000066400000000000000000000415141511153563100154000ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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->mode = MODE_CURSOR; s->default_mode = 0; s->ccolour = -1; s->default_ccolour = -1; s->tabs = NULL; s->sel = NULL; #ifdef ENABLE_SIXEL TAILQ_INIT(&s->images); TAILQ_INIT(&s->saved_images); #endif s->write_list = NULL; s->hyperlinks = 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 = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; if (SCREEN_IS_ALTERNATE(s)) 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); #ifdef ENABLE_SIXEL image_free_all(s); #endif screen_reset_hyperlinks(s); } /* Reset hyperlinks of a screen. */ void screen_reset_hyperlinks(struct screen *s) { if (s->hyperlinks == NULL) s->hyperlinks = hyperlinks_init(); else hyperlinks_reset(s->hyperlinks); } /* Destroy a screen. */ 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 (SCREEN_IS_ALTERNATE(s)) grid_destroy(s->saved_grid); grid_destroy(s->grid); if (s->hyperlinks != NULL) hyperlinks_free(s->hyperlinks); screen_free_titles(s); #ifdef ENABLE_SIXEL image_free_all(s); #endif } /* Reset tabs to default, eight spaces apart. */ 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 default cursor style and colour from options. */ void screen_set_default_cursor(struct screen *s, struct options *oo) { int c; c = options_get_number(oo, "cursor-colour"); s->default_ccolour = c; c = options_get_number(oo, "cursor-style"); s->default_mode = 0; screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode); } /* 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); #ifdef ENABLE_SIXEL image_free_all(s); #endif 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 * it 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); if (COLOUR_DEFAULT(dst->fg)) dst->fg = src->fg; if (COLOUR_DEFAULT(dst->bg)) dst->bg = src->bg; utf8_copy(&dst->data, &src->data); dst->attr = src->attr; 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 (SCREEN_IS_ALTERNATE(s)) 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); #ifdef ENABLE_SIXEL TAILQ_CONCAT(&s->saved_images, &s->images, entry); #endif 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 (SCREEN_IS_ALTERNATE(s)) screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 0); /* * 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 (!SCREEN_IS_ALTERNATE(s)) { 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; #ifdef ENABLE_SIXEL image_free_all(s); TAILQ_CONCAT(&s->images, &s->saved_images, entry); #endif 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, "MOUSE_UTF8,", sizeof tmp); if (mode & MODE_MOUSE_SGR) strlcat(tmp, "MOUSE_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_KEYS_EXTENDED) strlcat(tmp, "KEYS_EXTENDED,", sizeof tmp); if (mode & MODE_KEYS_EXTENDED_2) strlcat(tmp, "KEYS_EXTENDED_2,", sizeof tmp); tmp[strlen(tmp) - 1] = '\0'; return (tmp); } tmux-tmux-f222026/server-acl.c000066400000000000000000000101701511153563100161560ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2021 Holland Schutte, Jayson Morberg * Copyright (c) 2021 Dallas Lyons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include "tmux.h" struct server_acl_user { uid_t uid; int flags; #define SERVER_ACL_READONLY 0x1 RB_ENTRY(server_acl_user) entry; }; static int server_acl_cmp(struct server_acl_user *user1, struct server_acl_user *user2) { if (user1->uid < user2->uid) return (-1); return (user1->uid > user2->uid); } RB_HEAD(server_acl_entries, server_acl_user) server_acl_entries; RB_GENERATE_STATIC(server_acl_entries, server_acl_user, entry, server_acl_cmp); /* Initialize server_acl tree. */ void server_acl_init(void) { RB_INIT(&server_acl_entries); if (getuid() != 0) server_acl_user_allow(0); server_acl_user_allow(getuid()); } /* Find user entry. */ struct server_acl_user* server_acl_user_find(uid_t uid) { struct server_acl_user find = { .uid = uid }; return (RB_FIND(server_acl_entries, &server_acl_entries, &find)); } /* Display the tree. */ void server_acl_display(struct cmdq_item *item) { struct server_acl_user *loop; struct passwd *pw; const char *name; RB_FOREACH(loop, server_acl_entries, &server_acl_entries) { if (loop->uid == 0) continue; if ((pw = getpwuid(loop->uid)) != NULL) name = pw->pw_name; else name = "unknown"; if (loop->flags == SERVER_ACL_READONLY) cmdq_print(item, "%s (R)", name); else cmdq_print(item, "%s (W)", name); } } /* Allow a user. */ void server_acl_user_allow(uid_t uid) { struct server_acl_user *user; user = server_acl_user_find(uid); if (user == NULL) { user = xcalloc(1, sizeof *user); user->uid = uid; RB_INSERT(server_acl_entries, &server_acl_entries, user); } } /* Deny a user (remove from the tree). */ void server_acl_user_deny(uid_t uid) { struct server_acl_user *user; user = server_acl_user_find(uid); if (user != NULL) { RB_REMOVE(server_acl_entries, &server_acl_entries, user); free(user); } } /* Allow this user write access. */ void server_acl_user_allow_write(uid_t uid) { struct server_acl_user *user; struct client *c; user = server_acl_user_find(uid); if (user == NULL) return; user->flags &= ~SERVER_ACL_READONLY; TAILQ_FOREACH(c, &clients, entry) { uid = proc_get_peer_uid(c->peer); if (uid != (uid_t)-1 && uid == user->uid) c->flags &= ~CLIENT_READONLY; } } /* Deny this user write access. */ void server_acl_user_deny_write(uid_t uid) { struct server_acl_user *user; struct client *c; user = server_acl_user_find(uid); if (user == NULL) return; user->flags |= SERVER_ACL_READONLY; TAILQ_FOREACH(c, &clients, entry) { uid = proc_get_peer_uid(c->peer); if (uid != (uid_t)-1 && uid == user->uid) c->flags |= CLIENT_READONLY; } } /* * Check if the client's UID exists in the ACL list and if so, set as read only * if needed. Return false if the user does not exist. */ int server_acl_join(struct client *c) { struct server_acl_user *user; uid_t uid; uid = proc_get_peer_uid(c->peer); if (uid == (uid_t)-1) return (0); user = server_acl_user_find(uid); if (user == NULL) return (0); if (user->flags & SERVER_ACL_READONLY) c->flags |= CLIENT_READONLY; return (1); } /* Get UID for user entry. */ uid_t server_acl_get_uid(struct server_acl_user *user) { return (user->uid); } tmux-tmux-f222026/server-client.c000066400000000000000000003246461511153563100167150ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "tmux.h" enum mouse_where { NOWHERE, PANE, STATUS, STATUS_LEFT, STATUS_RIGHT, STATUS_DEFAULT, BORDER, SCROLLBAR_UP, SCROLLBAR_SLIDER, SCROLLBAR_DOWN }; 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 void server_client_update_latest(struct client *); static void server_client_dispatch(struct imsg *, void *); static int server_client_dispatch_command(struct client *, struct imsg *); static int server_client_dispatch_identify(struct client *, struct imsg *); static int server_client_dispatch_shell(struct client *); static void server_client_report_theme(struct client *, enum client_theme); /* 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; window_update_focus(c->session->curw->window); 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_resize = NULL; c->overlay_data = NULL; c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); if (c->session != NULL) window_update_focus(c->session->curw->window); 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++; if (gettimeofday(&c->keytable->activity_time, NULL) != 0) fatal("gettimeofday failed"); } static uint64_t server_client_key_table_activity_diff(struct client *c) { struct timeval diff; timersub(&c->activity_time, &c->keytable->activity_time, &diff); return ((diff.tv_sec * 1000ULL) + (diff.tv_usec / 1000ULL)); } /* 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; c->theme = THEME_UNKNOWN; 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_INIT(&c->input_requests); 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); session_theme_changed(s); 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); input_cancel_requests(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); } static enum mouse_where server_client_check_mouse_in_pane(struct window_pane *wp, u_int px, u_int py, u_int *sl_mpos) { struct window *w = wp->window; struct options *wo = w->options; struct window_pane *fwp; int pane_status, sb, sb_pos, sb_w, sb_pad; u_int line, sl_top, sl_bottom; sb = options_get_number(wo, "pane-scrollbars"); sb_pos = options_get_number(wo, "pane-scrollbars-position"); pane_status = options_get_number(wo, "pane-border-status"); if (window_pane_show_scrollbar(wp, sb)) { sb_w = wp->scrollbar_style.width; sb_pad = wp->scrollbar_style.pad; } else { sb_w = 0; sb_pad = 0; } if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else if (pane_status == PANE_STATUS_BOTTOM) line = wp->yoff + wp->sy; /* Check if point is within the pane or scrollbar. */ if (((pane_status != PANE_STATUS_OFF && py != line) || (wp->yoff == 0 && py < wp->sy) || (py >= wp->yoff && py < wp->yoff + wp->sy)) && ((sb_pos == PANE_SCROLLBARS_RIGHT && px < wp->xoff + wp->sx + sb_pad + sb_w) || (sb_pos == PANE_SCROLLBARS_LEFT && px < wp->xoff + wp->sx - sb_pad - sb_w))) { /* Check if in the scrollbar. */ if ((sb_pos == PANE_SCROLLBARS_RIGHT && (px >= wp->xoff + wp->sx + sb_pad && px < wp->xoff + wp->sx + sb_pad + sb_w)) || (sb_pos == PANE_SCROLLBARS_LEFT && (px >= wp->xoff - sb_pad - sb_w && px < wp->xoff - sb_pad))) { /* Check where inside the scrollbar. */ sl_top = wp->yoff + wp->sb_slider_y; sl_bottom = (wp->yoff + wp->sb_slider_y + wp->sb_slider_h - 1); if (py < sl_top) return (SCROLLBAR_UP); else if (py >= sl_top && py <= sl_bottom) { *sl_mpos = (py - wp->sb_slider_y - wp->yoff); return (SCROLLBAR_SLIDER); } else /* py > sl_bottom */ return (SCROLLBAR_DOWN); } else { /* Must be inside the pane. */ return (PANE); } } else if (~w->flags & WINDOW_ZOOMED) { /* Try the pane borders if not zoomed. */ TAILQ_FOREACH(fwp, &w->panes, entry) { if ((((sb_pos == PANE_SCROLLBARS_RIGHT && fwp->xoff + fwp->sx + sb_pad + sb_w == px) || (sb_pos == PANE_SCROLLBARS_LEFT && fwp->xoff + fwp->sx == px)) && fwp->yoff <= 1 + py && fwp->yoff + fwp->sy >= py) || (fwp->yoff + fwp->sy == py && fwp->xoff <= 1 + px && fwp->xoff + fwp->sx >= px)) break; } if (fwp != NULL) return (BORDER); } return (NOWHERE); } /* 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, *fs; struct window *w = s->curw->window; struct winlink *fwl; struct window_pane *wp, *fwp; u_int x, y, b, sx, sy, px, py, sl_mpos = 0; 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 mouse_where 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; if (m->sgr_type == 'm') b = m->sgr_b; 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; } } /* DOWN is the only remaining event type. */ if (type == NOTYPE) { 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->wp = -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: log_debug("mouse range: left"); where = STATUS_LEFT; break; case STYLE_RANGE_RIGHT: log_debug("mouse range: right"); where = STATUS_RIGHT; break; case STYLE_RANGE_PANE: fwp = window_pane_find_by_id(sr->argument); if (fwp == NULL) return (KEYC_UNKNOWN); m->wp = sr->argument; log_debug("mouse range: pane %%%u", m->wp); where = STATUS; break; case STYLE_RANGE_WINDOW: fwl = winlink_find_by_index(&s->windows, sr->argument); if (fwl == NULL) return (KEYC_UNKNOWN); m->w = fwl->window->id; log_debug("mouse range: window @%u", m->w); where = STATUS; break; case STYLE_RANGE_SESSION: fs = session_find_by_id(sr->argument); if (fs == NULL) return (KEYC_UNKNOWN); m->s = sr->argument; log_debug("mouse range: session $%u", m->s); where = STATUS; break; case STYLE_RANGE_USER: where = STATUS; break; } } } /* * Not on status line. Adjust position and check for border, pane, or * scrollbar. */ if (where == NOWHERE) { if (c->tty.mouse_scrolling_flag) where = SCROLLBAR_SLIDER; else { 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)", w->id, m->ox, m->oy, sx, sy); if (px > sx || py > sy) return (KEYC_UNKNOWN); px = px + m->ox; py = py + m->oy; /* Try inside the pane. */ wp = window_get_active_at(w, px, py); if (wp == NULL) return (KEYC_UNKNOWN); where = server_client_check_mouse_in_pane(wp, px, py, &sl_mpos); 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); else if (where == SCROLLBAR_UP || where == SCROLLBAR_SLIDER || where == SCROLLBAR_DOWN) { log_debug("mouse on pane %%%u scrollbar", wp->id); } m->wp = wp->id; m->w = wp->window->id; } } /* Stop dragging if needed. */ if (type != DRAG && type != WHEEL && type != DOUBLE && type != TRIPLE && 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; c->tty.mouse_scrolling_flag = 0; /* * 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND1_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND2_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND3_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND6_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND7_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND8_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND9_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND10_SCROLLBAR_SLIDER; 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 == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAGEND11_SCROLLBAR_SLIDER; if (where == BORDER) key = KEYC_MOUSEDRAGEND11_BORDER; break; default: key = KEYC_MOUSE; break; } c->tty.mouse_drag_flag = 0; c->tty.mouse_slider_mpos = -1; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG9_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG10_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDRAG11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDRAG11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDRAG11_SCROLLBAR_DOWN; 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. If starting to drag * the scrollbar, store the relative position in the slider * where the user grabbed. */ c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; if (c->tty.mouse_scrolling_flag == 0 && where == SCROLLBAR_SLIDER) { c->tty.mouse_scrolling_flag = 1; c->tty.mouse_slider_mpos = sl_mpos; } 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP9_SCROLLBAR_DOWN; 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_MOUSEUP10_STATUS_DEFAULT; if (where == SCROLLBAR_UP) key = KEYC_MOUSEUP10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEUP11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEUP11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEUP11_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN9_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN10_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_MOUSEDOWN11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_MOUSEDOWN11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_MOUSEDOWN11_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK9_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK10_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_SECONDCLICK11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_SECONDCLICK11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_SECONDCLICK11_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK9_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK10_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_DOUBLECLICK11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_DOUBLECLICK11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_DOUBLECLICK11_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK1_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK1_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK1_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK2_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK2_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK2_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK3_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK3_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK3_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK6_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK6_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK6_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK7_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK7_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK7_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK8_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK8_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK8_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK9_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK9_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK9_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK10_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK10_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK10_SCROLLBAR_DOWN; 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 == SCROLLBAR_UP) key = KEYC_TRIPLECLICK11_SCROLLBAR_UP; if (where == SCROLLBAR_SLIDER) key = KEYC_TRIPLECLICK11_SCROLLBAR_SLIDER; if (where == SCROLLBAR_DOWN) key = KEYC_TRIPLECLICK11_SCROLLBAR_DOWN; 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 a bracket paste key? */ static int server_client_is_bracket_paste(struct client *c, key_code key) { if ((key & KEYC_MASK_KEY) == KEYC_PASTE_START) { c->flags |= CLIENT_BRACKETPASTING; log_debug("%s: bracket paste on", c->name); return (0); } if ((key & KEYC_MASK_KEY) == KEYC_PASTE_END) { c->flags &= ~CLIENT_BRACKETPASTING; log_debug("%s: bracket paste off", c->name); return (0); } return !!(c->flags & CLIENT_BRACKETPASTING); } /* Is this fast enough to probably be a paste? */ static int server_client_is_assume_paste(struct client *c) { struct session *s = c->session; struct timeval tv; int t; if (c->flags & CLIENT_BRACKETPASTING) return (0); if ((t = options_get_number(s->options, "assume-paste-time")) == 0) return (0); timersub(&c->activity_time, &c->last_activity_time, &tv); if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) { if (c->flags & CLIENT_ASSUMEPASTING) return (1); c->flags |= CLIENT_ASSUMEPASTING; log_debug("%s: assume paste on", c->name); return (0); } if (c->flags & CLIENT_ASSUMEPASTING) { c->flags &= ~CLIENT_ASSUMEPASTING; log_debug("%s: assume paste off", c->name); } 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); } /* Get repeat time. */ static u_int server_client_repeat_time(struct client *c, struct key_binding *bd) { struct session *s = c->session; u_int repeat, initial; if (~bd->flags & KEY_BINDING_REPEAT) return (0); repeat = options_get_number(s->options, "repeat-time"); if (repeat == 0) return (0); if ((~c->flags & CLIENT_REPEAT) || bd->key != c->last_key) { initial = options_get_number(s->options, "initial-repeat-time"); if (initial != 0) repeat = initial; } return (repeat); } /* * 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; u_int repeat; uint64_t flags, prefix_delay; struct cmd_find_state fs; key_code key0, prefix, prefix2; /* Check the client is good to accept input. */ if (s == NULL || (c->flags & CLIENT_UNATTACHEDFLAGS)) goto out; wl = s->curw; /* Update the activity timer. */ memcpy(&c->last_activity_time, &c->activity_time, sizeof c->last_activity_time); 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; } /* Handle theme reporting keys. */ if (key == KEYC_REPORT_LIGHT_THEME) { server_client_report_theme(c, THEME_LIGHT); goto out; } if (key == KEYC_REPORT_DARK_THEME) { server_client_report_theme(c, THEME_DARK); goto out; } /* 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; /* Forward if bracket pasting. */ if (server_client_is_bracket_paste (c, key)) goto paste_key; /* Treat everything as a regular key when pasting is detected. */ if (!KEYC_IS_MOUSE(key) && key != KEYC_FOCUS_IN && key != KEYC_FOCUS_OUT && (~key & KEYC_SENT) && server_client_is_assume_paste(c)) goto paste_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. */ prefix = (key_code)options_get_number(s->options, "prefix"); prefix2 = (key_code)options_get_number(s->options, "prefix2"); key0 = (key & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)); if ((key0 == (prefix & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS)) || key0 == (prefix2 & (KEYC_MASK_KEY|KEYC_MASK_MODIFIERS))) && strcmp(table->name, "prefix") != 0) { server_client_set_key_table(c, "prefix"); server_status_client(c); 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"); bd = key_bindings_get(table, key0); /* * If prefix-timeout is enabled and we're in the prefix table, see if * the timeout has been exceeded. Revert to the root table if so. */ prefix_delay = options_get_number(global_options, "prefix-timeout"); if (prefix_delay > 0 && strcmp(table->name, "prefix") == 0 && server_client_key_table_activity_diff(c) > prefix_delay) { /* * If repeating is active and this is a repeating binding, * ignore the timeout. */ if (bd != NULL && (c->flags & CLIENT_REPEAT) && (bd->flags & KEY_BINDING_REPEAT)) { log_debug("prefix timeout ignored, repeat is active"); } else { log_debug("prefix timeout exceeded"); server_client_set_key_table(c, NULL); first = table = c->keytable; server_status_client(c); goto table_changed; } } /* Try to see if there is a key binding in the current table. */ 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. */ repeat = server_client_repeat_time(c, bd); if (repeat != 0) { c->flags |= CLIENT_REPEAT; c->last_key = bd->key; tv.tv_sec = repeat / 1000; tv.tv_usec = (repeat % 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; } /* * Binding movement keys is useless since we only turn them on when the * application requests, so don't let them exit the prefix table. */ if (key == KEYC_MOUSEMOVE_PANE || key == KEYC_MOUSEMOVE_STATUS || key == KEYC_MOUSEMOVE_STATUS_LEFT || key == KEYC_MOUSEMOVE_STATUS_RIGHT || key == KEYC_MOUSEMOVE_STATUS_DEFAULT || key == KEYC_MOUSEMOVE_BORDER) goto forward_key; /* * 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); goto out; paste_key: if (c->flags & CLIENT_READONLY) goto out; if (event->buf != NULL) window_pane_paste(wp, key, event->buf, event->len); key = KEYC_NONE; goto out; out: if (s != NULL && key != KEYC_FOCUS_OUT) server_client_update_latest(c); free(event->buf); 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|PANE_REDRAWSCROLLBAR); } check_window_name(w); } /* Send theme updates. */ RB_FOREACH(w, windows, &windows) { TAILQ_FOREACH(wp, &w->panes, entry) window_pane_send_theme_update(wp); } } /* 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) { if (!flag) 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 if (c->prompt_string == NULL) s = wp->screen; else s = c->status.active; 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(oo, "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; } 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 = xcalloc(1, sizeof *event); event->key = KEYC_DOUBLECLICK; memcpy(&event->m, &c->click_event, sizeof event->m); if (!server_client_handle_key(c, event)) { free(event->buf); 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, tty_flags, mode = tty->mode; uint64_t client_flags = 0; int redraw_pane, redraw_scrollbar_only; 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%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" : "", (c->flags & CLIENT_REDRAWSCROLLBARS) ? " scrollbars" : ""); } /* * 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; client_flags |= CLIENT_REDRAWPANES; break; } if (wp->flags & PANE_REDRAWSCROLLBAR) { needed = 1; client_flags |= CLIENT_REDRAWSCROLLBARS; /* no break - later panes may need redraw */ } } } 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); } else if (wp->flags & PANE_REDRAWSCROLLBAR) { log_debug("%s: pane %%%u scrollbar " "needs redraw", c->name, wp->id); c->redraw_scrollbars |= (1 << bit); } if (++bit == 64) { /* * If more that 64 panes, give up and * just redraw the window. */ client_flags &= ~(CLIENT_REDRAWPANES| CLIENT_REDRAWSCROLLBARS); client_flags |= CLIENT_REDRAWWINDOW; break; } } if (c->redraw_panes != 0) c->flags |= CLIENT_REDRAWPANES; if (c->redraw_scrollbars != 0) c->flags |= CLIENT_REDRAWSCROLLBARS; } c->flags |= client_flags; return; } else if (needed) log_debug("%s: redraw needed", c->name); tty_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_pane = 0; redraw_scrollbar_only = 0; if (wp->flags & PANE_REDRAW) redraw_pane = 1; else if (c->flags & CLIENT_REDRAWPANES) { if (c->redraw_panes & (1 << bit)) redraw_pane = 1; } else if (c->flags & CLIENT_REDRAWSCROLLBARS) { if (c->redraw_scrollbars & (1 << bit)) redraw_scrollbar_only = 1; } bit++; if (!redraw_pane && !redraw_scrollbar_only) continue; if (redraw_scrollbar_only) { log_debug("%s: redrawing (scrollbar only) pane " "%%%u", __func__, wp->id); } else { log_debug("%s: redrawing pane %%%u", __func__, wp->id); } screen_redraw_pane(c, wp, redraw_scrollbar_only); } c->redraw_panes = 0; c->redraw_scrollbars = 0; c->flags &= ~(CLIENT_REDRAWPANES|CLIENT_REDRAWSCROLLBARS); } 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)|(tty_flags & TTY_NOCURSOR); tty_update_mode(tty, mode, NULL); tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))| tty_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: if (server_client_dispatch_identify(c, imsg) != 0) goto bad; break; case MSG_COMMAND: if (server_client_dispatch_command(c, imsg) != 0) goto bad; break; case MSG_RESIZE: if (datalen != 0) goto bad; if (c->flags & CLIENT_CONTROL) break; server_client_update_latest(c); tty_resize(&c->tty); tty_repeat_requests(&c->tty, 0); 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) goto bad; 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) goto bad; 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) goto bad; if (server_client_dispatch_shell(c) != 0) goto bad; 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; } return; bad: log_debug("client %p invalid message type %d", c, imsg->hdr.type); proc_kill_peer(c->peer); } /* 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) { if (c->flags & CLIENT_CONTROL) control_ready(c); tty_send_requests(&c->tty); } return (CMD_RETURN_NORMAL); } /* Handle command message. */ static int server_client_dispatch_command(struct client *c, struct imsg *imsg) { struct msg_command data; char *buf; size_t len; int argc = 0; char **argv, *cause; struct cmd_parse_result *pr; struct args_value *values; struct cmdq_item *new_item; struct cmd_list *cmdlist; if (c->flags & CLIENT_EXIT) return (0); if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data) return (-1); 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') return (-1); if (cmd_unpack_argv(buf, len, data.argc, &argv) != 0) { cause = xstrdup("command too long"); goto error; } argc = data.argc; if (argc == 0) { cmdlist = cmd_list_copy(options_get_command(global_options, "default-client-command"), 0, NULL); } else { 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); cmdlist = pr->cmdlist; } if ((c->flags & CLIENT_READONLY) && !cmd_list_all_have(cmdlist, CMD_READONLY)) new_item = cmdq_get_callback(server_client_read_only, NULL); else new_item = cmdq_get_command(cmdlist, NULL); cmdq_append(c, new_item); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); cmd_list_free(cmdlist); return (0); error: cmd_free_argv(argc, argv); cmdq_append(c, cmdq_get_error(cause)); free(cause); c->flags |= CLIENT_EXIT; return (0); } /* Handle identify message. */ static int 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) return (-1); data = imsg->data; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; switch (imsg->hdr.type) { case MSG_IDENTIFY_FEATURES: if (datalen != sizeof feat) return (-1); 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) return (-1); 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) return (-1); 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') return (-1); 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') return (-1); 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') return (-1); 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') return (-1); 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) return (-1); c->fd = imsg_get_fd(imsg); log_debug("client %p IDENTIFY_STDIN %d", c, c->fd); break; case MSG_IDENTIFY_STDOUT: if (datalen != 0) return (-1); c->out_fd = imsg_get_fd(imsg); log_debug("client %p IDENTIFY_STDOUT %d", c, c->out_fd); break; case MSG_IDENTIFY_ENVIRON: if (datalen == 0 || data[datalen - 1] != '\0') return (-1); 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) return (-1); 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 (0); c->flags |= CLIENT_IDENTIFIED; if (c->term_name == NULL || *c->term_name == '\0') { free(c->term_name); c->term_name = xstrdup("unknown"); } if (c->ttyname == NULL || *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); c->out_fd = dup(c->fd); #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; } if (c->out_fd != -1) 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(); return (0); } /* Handle shell message. */ static int 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); return (0); } /* 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; else if (strcmp(next, "no-detach-on-destroy") == 0) flag = CLIENT_NO_DETACH_ON_DESTROY; 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_NO_DETACH_ON_DESTROY) strlcat(s, "no-detach-on-destroy,", 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); } } } /* Print to a client. */ void server_client_print(struct client *c, int parse, struct evbuffer *evb) { void *data = EVBUFFER_DATA(evb); size_t size = EVBUFFER_LENGTH(evb); struct window_pane *wp; struct window_mode_entry *wme; char *sanitized, *msg, *line, empty = '\0'; if (!parse) { utf8_stravisx(&msg, data, size, VIS_OCTAL|VIS_CSTYLE|VIS_NOSLASH); } else { if (size == 0) msg = ∅ else { msg = EVBUFFER_DATA(evb); if (msg[size - 1] != '\0') evbuffer_add(evb, "", 1); } } log_debug("%s: %s", __func__, msg); if (c == NULL) goto out; if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { if (~c->flags & CLIENT_UTF8) { sanitized = utf8_sanitize(msg); if (c->flags & CLIENT_CONTROL) control_write(c, "%s", sanitized); else file_print(c, "%s\n", sanitized); free(sanitized); } else { if (c->flags & CLIENT_CONTROL) control_write(c, "%s", msg); else file_print(c, "%s\n", msg); } goto out; } wp = server_client_get_pane(c); wme = TAILQ_FIRST(&wp->modes); if (wme == NULL || wme->mode != &window_view_mode) window_pane_set_mode(wp, NULL, &window_view_mode, NULL, NULL); if (parse) { do { line = evbuffer_readln(evb, NULL, EVBUFFER_EOL_LF); if (line != NULL) { window_copy_add(wp, 1, "%s", line); free(line); } } while (line != NULL); size = EVBUFFER_LENGTH(evb); if (size != 0) { line = EVBUFFER_DATA(evb); window_copy_add(wp, 1, "%.*s", (int)size, line); } } else window_copy_add(wp, 0, "%s", msg); out: if (!parse) free(msg); } static void server_client_report_theme(struct client *c, enum client_theme theme) { if (theme == THEME_LIGHT) { c->theme = THEME_LIGHT; notify_client("client-light-theme", c); } else { c->theme = THEME_DARK; notify_client("client-dark-theme", c); } /* * Request foreground and background colour again. Don't forward 2031 to * panes until a response is received. */ tty_repeat_requests(&c->tty, 1); } tmux-tmux-f222026/server-fn.c000066400000000000000000000257641511153563100160410ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" 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; } 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 (marked_pane.wl == srcwl) marked_pane.wl = dstwl; 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); kill(getpid(), SIGCHLD); #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); session_destroy(s, 1, __func__); } else { TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { server_destroy_session(s); session_destroy(s, 1, __func__); } } } static struct session * server_find_session(struct session *s, int (*f)(struct session *, struct session *)) { struct session *s_loop, *s_out = NULL; RB_FOREACH(s_loop, sessions, &sessions) { if (s_loop != s && f(s_loop, s_out)) s_out = s_loop; } return (s_out); } static int server_newer_session(struct session *s_loop, struct session *s_out) { if (s_out == NULL) return (1); return (timercmp(&s_loop->activity_time, &s_out->activity_time, >)); } static int server_newer_detached_session(struct session *s_loop, struct session *s_out) { if (s_loop->attached) return (0); return (server_newer_session(s_loop, s_out)); } void server_destroy_session(struct session *s) { struct client *c; struct session *s_new = NULL, *cs_new = NULL, *use_s; int detach_on_destroy; detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); if (detach_on_destroy == 0) s_new = server_find_session(s, server_newer_session); else if (detach_on_destroy == 2) s_new = server_find_session(s, server_newer_detached_session); else if (detach_on_destroy == 3) s_new = session_previous_session(s); else if (detach_on_destroy == 4) s_new = session_next_session(s); /* * If no suitable new session was found above, then look for any * session as an alternative in case a client needs it. */ if (s_new == NULL && (detach_on_destroy == 1 || detach_on_destroy == 2)) cs_new = server_find_session(s, server_newer_session); TAILQ_FOREACH(c, &clients, entry) { if (c->session != s) continue; use_s = s_new; if (use_s == NULL && (c->flags & CLIENT_NO_DETACH_ON_DESTROY)) use_s = cs_new; c->session = NULL; c->last_session = NULL; server_client_set_session(c, use_s); if (use_s == NULL) c->flags |= CLIENT_EXIT; } recalculate_sizes(); } void server_check_unattached(void) { struct session *s; struct session_group *sg; /* * If any sessions are no longer attached and have destroy-unattached * set, collect them. */ RB_FOREACH(s, sessions, &sessions) { if (s->attached != 0) continue; switch (options_get_number(s->options, "destroy-unattached")) { case 0: /* off */ continue; case 1: /* on */ break; case 2: /* keep-last */ sg = session_group_contains(s); if (sg == NULL || session_group_count(sg) <= 1) continue; break; case 3: /* keep-group */ sg = session_group_contains(s); if (sg != NULL && session_group_count(sg) == 1) continue; break; } session_destroy(s, 1, __func__); } } void server_unzoom_window(struct window *w) { if (window_unzoom(w, 1) == 0) server_redraw_window(w); } tmux-tmux-f222026/server.c000066400000000000000000000270601511153563100154270ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; time_t current_time; 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; if (wl != NULL) 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(uint64_t 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, uint64_t 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(); utf8_update_width_cache(); 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; current_time = time(NULL); 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); } } tmux-tmux-f222026/session.c000066400000000000000000000422001511153563100155750ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); int session_group_cmp(struct session_group *s1, struct session_group *s2) { return (strcmp(s1->name, s2->name)); } RB_GENERATE(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 tv; 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", s->id, s->name, (long long)s->activity_time.tv_sec, (int)s->activity_time.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)) 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); wl2->flags |= WINLINK_VISITED; } } /* 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, marked_idx = -1; /* 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 == marked_pane.wl) marked_idx = wl_new->idx; 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->flags &= ~WINLINK_VISITED; wl_new = winlink_find_by_window(&s->windows, wl->window); if (wl_new != NULL) { TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry); wl_new->flags |= WINLINK_VISITED; } } /* Set the current window. */ if (marked_idx != -1) { marked_pane.wl = winlink_find_by_index(&s->windows, marked_idx); if (marked_pane.wl == NULL) server_clear_marked(); } s->curw = winlink_find_by_index(&s->windows, new_curw_idx); /* Free the old winlinks (reducing window references too). */ RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1) winlink_remove(&old_wins, wl); } /* Set the PANE_THEMECHANGED flag for every pane in this session. */ void session_theme_changed(struct session *s) { struct window_pane *wp; struct winlink *wl; if (s != NULL) { RB_FOREACH(wl, winlinks, &s->windows) { TAILQ_FOREACH(wp, &wl->window->panes, entry) wp->flags |= PANE_THEMECHANGED; } } } tmux-tmux-f222026/spawn.c000066400000000000000000000327511511153563100152540ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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); w->active = NULL; 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%s", server_client_get_cwd(c, target->s), *cwd != '\0' ? "/" : "", 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; } #if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS) /* * Move the child process into a new cgroup for systemd-oomd isolation. */ if (systemd_move_to_new_cgroup(cause) < 0) { log_debug("%s: moving pane to new cgroup failed: %s", __func__, *cause); free (*cause); } #endif /* * 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. */ proc_clear_signals(server_proc, 1); closefrom(STDERR_FILENO + 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); } tmux-tmux-f222026/status.c000066400000000000000000001440151511153563100154440ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #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 the prompt line number for client's session. 1 means at the bottom. */ static u_int status_prompt_line_at(struct client *c) { struct session *s = c->session; if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) return (1); return (options_get_number(s->options, "message-line")); } /* Get window at window list position. */ struct style_range * status_get_range(struct client *c, u_int x, u_int y) { 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, int no_freeze, const char *fmt, ...) { struct timeval tv; va_list ap; char *s; va_start(ap, fmt); xvasprintf(&s, fmt, ap); va_end(ap); log_debug("%s: %s", __func__, s); if (c == NULL) { server_add_message("message: %s", s); free(s); return; } status_message_clear(c); status_push_screen(c); c->message_string = s; server_add_message("%s message: %s", c->name, s); /* * With delay -1, the display-time option is used; zero means wait for * 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; if (!no_freeze) c->tty.flags |= TTY_FREEZE; c->tty.flags |= TTY_NOCURSOR; 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, messageline; 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); messageline = status_prompt_line_at(c); if (messageline > lines - 1) messageline = lines - 1; len = screen_write_strlen("%s", c->message_string); if (len > c->tty.sx) len = c->tty.sx; 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); screen_write_cursormove(&ctx, 0, messageline, 0); for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, messageline, 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); } /* Accept prompt immediately. */ static enum cmd_retval status_prompt_accept(__unused struct cmdq_item *item, void *data) { struct client *c = data; if (c->prompt_string != NULL) { c->prompt_inputcb(c, c->prompt_data, "y", 1); status_prompt_clear(c); } return (CMD_RETURN_NORMAL); } /* 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; server_client_clear_overlay(c); 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 = ""; status_message_clear(c); status_prompt_clear(c); status_push_screen(c); c->prompt_formats = ft; c->prompt_string = xstrdup (msg); if (flags & PROMPT_NOFORMAT) tmp = xstrdup(input); else tmp = format_expand_time(ft, input); 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); free(tmp); 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_FREEZE; c->flags |= CLIENT_REDRAWSTATUS; if (flags & PROMPT_INCREMENTAL) c->prompt_inputcb(c, c->prompt_data, "=", 0); if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); } /* 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; format_free(c->prompt_formats); c->prompt_formats = 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) { char *tmp; free(c->prompt_string); c->prompt_string = xstrdup(msg); free(c->prompt_buffer); tmp = format_expand_time(c->prompt_formats, input); c->prompt_buffer = utf8_fromcstr(tmp); c->prompt_index = utf8_strlen(c->prompt_buffer); free(tmp); memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); c->flags |= CLIENT_REDRAWSTATUS; } /* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ static int status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, struct grid_cell *gc, const struct utf8_data *ud) { u_char ch; if (*width < offset) { *width += ud->width; return (1); } if (*width >= offset + pwidth) return (0); *width += ud->width; if (*width > offset + pwidth) return (0); ch = *ud->data; if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { gc->data.data[0] = '^'; gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; gc->data.size = gc->data.have = 2; gc->data.width = 2; } else utf8_copy(&gc->data, ud); screen_write_cell(ctx, gc); return (1); } /* * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, * 0 otherwise. */ static int status_prompt_redraw_quote(const struct client *c, u_int pcursor, struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, struct grid_cell *gc) { struct utf8_data ud; if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { utf8_set(&ud, '^'); return (status_prompt_redraw_character(ctx, offset, pwidth, width, gc, &ud)); } return (1); } /* 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, n; u_int pcursor, pwidth, promptline; struct grid_cell gc; struct format_tree *ft = c->prompt_formats; char *prompt, *tmp; 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); n = options_get_number(s->options, "prompt-cursor-colour"); sl->active->default_ccolour = n; n = options_get_number(s->options, "prompt-cursor-style"); screen_set_cursor_style(n, &sl->active->default_cstyle, &sl->active->default_mode); promptline = status_prompt_line_at(c); if (promptline > lines - 1) promptline = lines - 1; if (c->prompt_mode == PROMPT_COMMAND) style_apply(&gc, s->options, "message-command-style", ft); else style_apply(&gc, s->options, "message-style", ft); tmp = utf8_tocstr(c->prompt_buffer); format_add(c->prompt_formats, "prompt-input", "%s", tmp); prompt = format_expand_time(c->prompt_formats, c->prompt_string); free (tmp); start = format_width(prompt); 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); screen_write_cursormove(&ctx, 0, promptline, 0); for (offset = 0; offset < c->tty.sx; offset++) screen_write_putc(&ctx, &gc, ' '); screen_write_cursormove(&ctx, 0, promptline, 0); format_draw(&ctx, &gc, start, prompt, NULL, 0); screen_write_cursormove(&ctx, start, promptline, 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 (c->prompt_flags & PROMPT_QUOTENEXT) pwidth++; 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 + pcursor - offset; width = 0; for (i = 0; c->prompt_buffer[i].size != 0; i++) { if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width, &gc)) break; if (!status_prompt_redraw_character(&ctx, offset, pwidth, &width, &gc, &c->prompt_buffer[i])) break; } status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width, &gc); 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 'a'|KEYC_CTRL: case 'c'|KEYC_CTRL: case 'e'|KEYC_CTRL: case 'g'|KEYC_CTRL: case 'h'|KEYC_CTRL: case '\011': /* Tab */ case 'k'|KEYC_CTRL: case 'n'|KEYC_CTRL: case 'p'|KEYC_CTRL: case 't'|KEYC_CTRL: case 'u'|KEYC_CTRL: case 'v'|KEYC_CTRL: case 'w'|KEYC_CTRL: case 'y'|KEYC_CTRL: 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 = 'u'|KEYC_CTRL; 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 = 'k'|KEYC_CTRL; 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 = 'u'|KEYC_CTRL; 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 = 'y'|KEYC_CTRL; return (1); case 'q': *new_key = 'c'|KEYC_CTRL; 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 'h'|KEYC_CTRL: case 'c'|KEYC_CTRL: 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 = udp = xreallocarray(NULL, bufsize + 1, sizeof *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) { 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; if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) key = 0x7f; else if ((key & KEYC_MASK_KEY) > 0x7f) { if (!KEYC_IS_UNICODE(key)) return (0); key &= KEYC_MASK_KEY; } else key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; c->prompt_flags &= ~PROMPT_QUOTENEXT; goto append_key; } 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 'b'|KEYC_CTRL: if (c->prompt_index > 0) { c->prompt_index--; break; } break; case KEYC_RIGHT: case 'f'|KEYC_CTRL: if (c->prompt_index < size) { c->prompt_index++; break; } break; case KEYC_HOME: case 'a'|KEYC_CTRL: if (c->prompt_index != 0) { c->prompt_index = 0; break; } break; case KEYC_END: case 'e'|KEYC_CTRL: 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 'h'|KEYC_CTRL: 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 'd'|KEYC_CTRL: 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 'u'|KEYC_CTRL: c->prompt_buffer[0].size = 0; c->prompt_index = 0; goto changed; case 'k'|KEYC_CTRL: if (c->prompt_index < size) { c->prompt_buffer[c->prompt_index].size = 0; goto changed; } break; case 'w'|KEYC_CTRL: 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 'p'|KEYC_CTRL: 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 'n'|KEYC_CTRL: 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 'y'|KEYC_CTRL: if (status_prompt_paste(c)) goto changed; break; case 't'|KEYC_CTRL: 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 'c'|KEYC_CTRL: case 'g'|KEYC_CTRL: if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) status_prompt_clear(c); break; case 'r'|KEYC_CTRL: 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 's'|KEYC_CTRL: 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 'v'|KEYC_CTRL: c->prompt_flags |= PROMPT_QUOTENEXT; break; default: goto append_key; } c->flags |= CLIENT_REDRAWSTATUS; return (0); append_key: if (key <= 0x7f) { utf8_set(&tmp, key); if (key <= 0x1f || key == 0x7f) tmp.width = 2; } 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-horizontal-mirrored", "main-vertical", "main-vertical-mirrored", "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, 0, NULL, offset, py, c, BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); return (0); } 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); free(spm); 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); free(spm); 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, 0, NULL, offset, py, c, BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, status_prompt_menu_callback, spm) != 0) { menu_free(menu); free(spm); return (NULL); } 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]); } tmux-tmux-f222026/style.c000066400000000000000000000301531511153563100152560ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * Copyright (c) 2014 Tiago Cunha * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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 }, 0, 8, STYLE_ALIGN_DEFAULT, STYLE_LIST_OFF, STYLE_RANGE_NONE, 0, "", STYLE_WIDTH_DEFAULT, STYLE_PAD_DEFAULT, STYLE_DEFAULT_BASE }; /* Set range string. */ static void style_set_range_string(struct style *sy, const char *s) { strlcpy(sy->range_string, s, sizeof sy->range_string); } /* * Parse an embedded style of the form "fg=colour,bg=colour,bright,...". Note * that this adds onto the given style, so it must have been initialized * already. */ int style_parse(struct style *sy, const struct grid_cell *base, const char *in) { struct style saved; const char delimiters[] = " ,\n", *errstr; char tmp[256], *found; int value; size_t end; u_int n; 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.us = base->us; 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, "set-default") == 0) sy->default_type = STYLE_DEFAULT_SET; 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; strlcpy(sy->range_string, style_default.range_string, sizeof sy->range_string); } else if (end > 6 && strncasecmp(tmp, "range=", 6) == 0) { found = strchr(tmp + 6, '|'); if (found != NULL) { *found++ = '\0'; if (*found == '\0') goto error; } if (strcasecmp(tmp + 6, "left") == 0) { if (found != NULL) goto error; sy->range_type = STYLE_RANGE_LEFT; sy->range_argument = 0; style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "right") == 0) { if (found != NULL) goto error; sy->range_type = STYLE_RANGE_RIGHT; sy->range_argument = 0; style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "pane") == 0) { if (found == NULL) goto error; if (*found != '%' || found[1] == '\0') goto error; n = strtonum(found + 1, 0, UINT_MAX, &errstr); if (errstr != NULL) goto error; sy->range_type = STYLE_RANGE_PANE; sy->range_argument = n; style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "window") == 0) { if (found == NULL) goto error; n = strtonum(found, 0, UINT_MAX, &errstr); if (errstr != NULL) goto error; sy->range_type = STYLE_RANGE_WINDOW; sy->range_argument = n; style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "session") == 0) { if (found == NULL) goto error; if (*found != '$' || found[1] == '\0') goto error; n = strtonum(found + 1, 0, UINT_MAX, &errstr); if (errstr != NULL) goto error; sy->range_type = STYLE_RANGE_SESSION; sy->range_argument = n; style_set_range_string(sy, ""); } else if (strcasecmp(tmp + 6, "user") == 0) { if (found == NULL) goto error; sy->range_type = STYLE_RANGE_USER; sy->range_argument = 0; style_set_range_string(sy, found); } } else if (strcasecmp(tmp, "noalign") == 0) sy->align = style_default.align; 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 (end > 3 && strncasecmp(tmp, "us=", 3) == 0) { if ((value = colour_fromstring(tmp + 3)) == -1) goto error; if (value != 8) sy->gc.us = value; else sy->gc.us = base->us; } else if (strcasecmp(tmp, "none") == 0) sy->gc.attr = 0; else if (end > 2 && strncasecmp(tmp, "no", 2) == 0) { if (strcmp(tmp + 2, "attr") == 0) value = 0xffff & ~GRID_ATTR_CHARSET; else if ((value = attributes_fromstring(tmp + 2)) == -1) goto error; sy->gc.attr &= ~value; } else if (end > 6 && strncasecmp(tmp, "width=", 6) == 0) { n = strtonum(tmp + 6, 0, UINT_MAX, &errstr); if (errstr != NULL) goto error; sy->width = (int)n; } else if (end > 4 && strncasecmp(tmp, "pad=", 4) == 0) { n = strtonum(tmp + 4, 0, UINT_MAX, &errstr); if (errstr != NULL) goto error; sy->pad = (int)n; } 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[21]; *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_PANE) { snprintf(b, sizeof b, "pane|%%%u", sy->range_argument); tmp = b; } else if (sy->range_type == STYLE_RANGE_WINDOW) { snprintf(b, sizeof b, "window|%u", sy->range_argument); tmp = b; } else if (sy->range_type == STYLE_RANGE_SESSION) { snprintf(b, sizeof b, "session|$%u", sy->range_argument); tmp = b; } else if (sy->range_type == STYLE_RANGE_USER) { snprintf(b, sizeof b, "user|%s", sy->range_string); tmp = b; } off += xsnprintf(s + off, sizeof s - off, "%srange=%s", comma, tmp); 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"; else if (sy->default_type == STYLE_DEFAULT_SET) tmp = "set-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->us != 8) { off += xsnprintf(s + off, sizeof s - off, "%sus=%s", comma, colour_tostring(gc->us)); comma = ","; } if (gc->attr != 0) { xsnprintf(s + off, sizeof s - off, "%s%s", comma, attributes_tostring(gc->attr)); comma = ","; } if (sy->width >= 0) { xsnprintf(s + off, sizeof s - off, "%swidth=%u", comma, sy->width); comma = ","; } if (sy->pad >= 0) { xsnprintf(s + off, sizeof s - off, "%spad=%u", comma, sy->pad); 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; if (sy->gc.us != 8) gc->us = sy->gc.us; 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); } void style_set_scrollbar_style_from_option(struct style *sb_style, struct options *oo) { struct style *sy; sy = options_string_to_style(oo, "pane-scrollbars-style", NULL); if (sy == NULL) { style_set(sb_style, &grid_default_cell); sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; sb_style->pad = PANE_SCROLLBARS_DEFAULT_PADDING; utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); } else { style_copy(sb_style, sy); if (sb_style->width < 1) sb_style->width = PANE_SCROLLBARS_DEFAULT_WIDTH; if (sb_style->pad < 0) sb_style->pad = PANE_SCROLLBARS_DEFAULT_PADDING; utf8_set(&sb_style->gc.data, PANE_SCROLLBARS_CHARACTER); } } tmux-tmux-f222026/tmux-protocol.h000066400000000000000000000045021511153563100167560ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2021 Nicholas Marriott * * 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, MSG_READ_CANCEL }; /* * 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_read_cancel { int stream; }; 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 */ tmux-tmux-f222026/tmux.1000066400000000000000000005660371511153563100150500ustar00rootroot00000000000000.\" $OpenBSD$ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" .\" 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 2CDhlNuVv .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 \[ti]/.tmux.conf or .Pa $XDG_CONFIG_HOME/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 h Print usage information and exit. .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 T Ar features Set terminal features for the client. This is a comma-separated list of features. See the .Ic terminal-features option. .It Fl u Write UTF-8 output to the terminal even if the first environment variable of .Ev LC_ALL , .Ev LC_CTYPE , or .Ev LANG that is set does not contain .Qq UTF-8 or .Qq UTF8 . .It Fl V Report the .Nm version. .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 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 command in .Ic default-client-command is assumed, which defaults to .Ic new-session . .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 \[aq] 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 \[ti] 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-7 Arrange panes in one of the seven preset layouts: even-horizontal, even-vertical, main-horizontal, main-horizontal-mirrored, main-vertical, main-vertical-mirrored, 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 \[ti]/.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 \[ti]/.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 \[aq];\[aq] 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; splitw .Ed .Pp Or: .Bd -literal -offset indent $ tmux \[aq]neww;\[aq] 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 \[aq];\[aq] splitw ) or escaped (such as .Ql neww \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 \[aq]foo\e;\[aq] bar $ tmux neww foo\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 \[aq]foo-;-bar\[aq] $ tmux neww foo-\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 (\[aq]) quotes or double quotes (\[dq]), or as command lists surrounded by 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 \[ti] or \[ti]user is expanded to the home directory of the current or specified user. .It \euXXXX or \euXXXXXXXX is replaced by the Unicode codepoint corresponding to 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 \[aq]brace-dollar-foo: }$foo\[aq] } if-shell true "display -p \[aq]brace-dollar-foo: }\e$foo\[aq]" .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 A .Xr glob 7 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 a .Xr glob 7 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 \[ti] ) 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 \[aq]vi \[ti]/.tmux.conf\[aq] .Ed .Pp Will run: .Bd -literal -offset indent /bin/sh -c \[aq]vi \[ti]/.tmux.conf\[aq] .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 \[ti]/.tmux.conf .Ed .Pp Will run .Xr vi 1 directly without invoking the shell. .Pp .Ar command .Op Ar argument ... 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 \[ti]/.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 \[aq]vi \[ti]/.tmux.conf\[aq] \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 , attach to .Ar target-session in the current terminal. .Ar target-session must already exist - to create a new session, see the .Ic new-session command (with .Fl A to create or attach). If used from inside, switch the currently attached session to .Ar target-session . 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-detach-on-destroy do not detach the client when the session it is attached to is destroyed if there are any other sessions .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 f Ar filter .Op Fl t Ar target-session .Xc .D1 Pq alias: Ic lsc List all clients attached to the server. .Fl F specifies the format of each line and .Fl f a filter. Only clients for which the filter is true are shown. See the .Sx FORMATS section. If .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 Op Ar argument ... .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; if .Fl A is given, .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 r Ar pane:report .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 . .Fl r allows a control mode client to provide information about a pane via a report (such as the response to OSC 10). The argument is a pane ID (with a leading .Ql % ) , a colon, then a report escape sequence. .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 .Op Fl t Ar target-pane .Ar path ... .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 \[ti]/.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 -tag -width Ds .It Xo .Ic append-selection .Xc Append the selection to the top paste buffer. .It Xo .Ic append-selection-and-cancel (vi: A) .Xc Append the selection to the top paste buffer and exit copy mode. .It Xo .Ic back-to-indentation (vi: ^) (emacs: M-m) .Xc Move the cursor back to the indentation. .It Xo .Ic begin-selection (vi: Space) (emacs: C-Space) .Xc Begin selection. .It Xo .Ic bottom-line (vi: L) .Xc Move to the bottom line. .It Xo .Ic cancel (vi: q) (emacs: Escape) .Xc Exit copy mode. .It Xo .Ic clear-selection (vi: Escape) (emacs: C-g) .Xc Clear the current selection. .It Xo .Ic copy-end-of-line .Op Fl CP .Op Ar prefix .Xc Copy from the cursor position to the end of the line. .Ar prefix is used to name the new paste buffer. .It Xo .Ic copy-end-of-line-and-cancel .Op Fl CP .Op Ar prefix .Xc Copy from the cursor position and exit copy mode. .It Xo .Ic copy-pipe-end-of-line .Op Fl CP .Op Ar command .Op Ar prefix .Xc Copy from the cursor position to the end of the line and pipe the text to .Ar command . .Ar prefix is used to name the new paste buffer. .It Xo .Ic copy-pipe-end-of-line-and-cancel .Op Fl CP .Op Ar command .Op Ar prefix .Xc Same as .Ic copy-pipe-end-of-line but also exit copy mode. .It Xo .Ic copy-line .Op Fl CP .Op Ar prefix .Xc Copy the entire line. .It Xo .Ic copy-line-and-cancel .Op Fl CP .Op Ar prefix .Xc Copy the entire line and exit copy mode. .It Xo .Ic copy-pipe-line .Op Fl CP .Op Ar command .Op Ar prefix .Xc Copy the entire line and pipe the text to .Ar command . .Ar prefix is used to name the new paste buffer. .It Xo .Ic copy-pipe-line-and-cancel .Op Fl CP .Op Ar command .Op Ar prefix .Xc Same as .Ic copy-pipe-line but also exit copy mode. .It Xo .Ic copy-pipe .Op Fl CP .Op Ar command .Op Ar prefix .Xc Copy the selection, clear it and pipe its text to .Ar command . .Ar prefix is used to name the new paste buffer. .It Xo .Ic copy-pipe-no-clear .Op Fl CP .Op Ar command .Op Ar prefix .Xc Same as .Ic copy-pipe but do not clear the selection. .It Xo .Ic copy-pipe-and-cancel .Op Fl CP .Op Ar command .Op Ar prefix .Xc Same as .Ic copy-pipe but also exit copy mode. .It Xo .Ic copy-selection .Op Fl CP .Op Ar prefix .Xc Copies the current selection. .It Xo .Ic copy-selection-no-clear .Op Fl CP .Op Ar prefix .Xc Same as .Ic copy-selection but do not clear the selection. .It Xo .Ic copy-selection-and-cancel .Op Fl CP .Op Ar prefix (vi: Enter) (emacs: M-w) .Xc Copy the current selection and exit copy mode. .It Xo .Ic cursor-down (vi: j) (emacs: Down) .Xc Move the cursor down. .It Xo .Ic cursor-down-and-cancel .Xc Same as .Ic cursor-down but also exit copy mode if reaching the bottom. .It Xo .Ic cursor-left (vi: h) (emacs: Left) .Xc Move the cursor left. .It Xo .Ic cursor-right (vi: l) (emacs: Right) .Xc Move the cursor right. .It Xo .Ic cursor-up (vi: k) (emacs: Up) .Xc Move the cursor up. .It Xo .Ic cursor-centre-vertical (emacs: C-l) .Xc Moves the cursor to the vertical centre of the pane. .It Xo .Ic cursor-centre-horizontal (emacs: M-l) .Xc Moves the cursor to the horizontal centre of the pane. .It Xo .Ic end-of-line (vi: $) (emacs: C-e) .Xc Move the cursor to the end of the line. .It Xo .Ic goto-line .Ar line (vi: :) (emacs: g) .Xc Move the cursor to a specific line. .It Xo .Ic halfpage-down (vi: C-d) (emacs: M-Down) .Xc Scroll down by half a page. .It Xo .Ic halfpage-down-and-cancel .Xc Same as .Ic halfpage-down but also exit copy mode if reaching the bottom. .It Xo .Ic halfpage-up (vi: C-u) (emacs: M-Up) .Xc Scroll up by half a page. .It Xo .Ic history-bottom (vi: G) (emacs: M->) .Xc Scroll to the bottom of the history. .It Xo .Ic history-top (vi: g) (emacs: M-<) .Xc Scroll to the top of the history. .It Xo .Ic jump-again (vi: ;) (emacs: ;) .Xc Repeat the last jump. .It Xo .Ic jump-backward .Ar to (vi: F) (emacs: F) .Xc Jump backwards to the specified text. .It Xo .Ic jump-forward .Ar to (vi: f) (emacs: f) .Xc Jump forward to the specified text. .It Xo .Ic jump-reverse (vi: ,) (emacs: ,) .Xc Repeat the last jump in the reverse direction (forward becomes backward and backward becomes forward). .It Xo .Ic jump-to-backward .Ar to (vi: T) .Xc Jump backwards, but one character less, placing the cursor on the character after the target. .It Xo .Ic jump-to-forward .Ar to (vi: t) .Xc Jump forward, but one character less, placing the cursor on the character before the target. .It Xo .Ic jump-to-mark (vi: M-x) (emacs: M-x) .Xc Jump to the last mark. .It Xo .Ic middle-line (vi: M) (emacs: M-r) .Xc Move to the middle line. .It Xo .Ic next-matching-bracket (vi: %) (emacs: M-C-f) .Xc Move to the next matching bracket. .It Xo .Ic next-paragraph (vi: }) (emacs: M-}) .Xc Move to the next paragraph. .It Xo .Ic next-prompt .Op Fl o .Xc Move to the next prompt. .It Xo .Ic next-word (vi: w) .Xc Move to the next word. .It Xo .Ic next-word-end (vi: e) (emacs: M-f) .Xc Move to the end of the next word. .It Xo .Ic next-space (vi: W) .Xc Same as .Ic next-word but use a space alone as the word separator. .It Xo .Ic next-space-end (vi: E) .Xc Same as .Ic next-word-end but use a space alone as the word separator. .It Xo .Ic other-end (vi: o) .Xc Switch at which end of the selection the cursor sits. .It Xo .Ic page-down (vi: C-f) (emacs: PageDown) .Xc Scroll down by one page. .It Xo .Ic page-down-and-cancel .Xc Same as .Ic page-down but also exit copy mode if reaching the bottom. .It Xo .Ic page-up (vi: C-b) (emacs: PageUp) .Xc Scroll up by one page. .It Xo .Ic pipe .Op Ar command .Xc Pipe the selected text to .Ar command and clear the selection. .It Xo .Ic pipe-no-clear .Op Ar command .Xc Same as .Ic pipe but do not clear the selection. .It Xo .Ic pipe-and-cancel .Op Ar command .Op Ar prefix .Xc Same as .Ic pipe but also exit copy mode. .It Xo .Ic previous-matching-bracket (emacs: M-C-b) .Xc Move to the previous matching bracket. .It Xo .Ic previous-paragraph (vi: {) (emacs: M-{) .Xc Move to the previous paragraph. .It Xo .Ic previous-prompt .Op Fl o .Xc Move to the previous prompt. .It Xo .Ic previous-word (vi: b) (emacs: M-b) .Xc Move to the previous word. .It Xo .Ic previous-space (vi: B) .Xc Same as .Ic previous-word but use a space alone as the word separator. .It Xo .Ic rectangle-on .Xc Turn on rectangle selection mode. .It Xo .Ic rectangle-off .Xc Turn off rectangle selection mode. .It Xo .Ic rectangle-toggle (vi: v) (emacs: R) .Xc Toggle rectangle selection mode. .It Xo .Ic refresh-from-pane (vi: r) (emacs: r) .Xc Refresh the content from the pane. .It Xo .Ic scroll-bottom .Xc Scroll up until the current line is at the bottom while keeping the cursor on that line. .It Xo .Ic scroll-down (vi: C-e) (emacs: C-Down) .Xc Scroll down. .It Xo .Ic scroll-down-and-cancel .Xc Same as .Ic scroll-down but also exit copy mode if the cursor reaches the bottom. .It Xo .Ic scroll-middle (vi: z) .Xc Scroll so that the current line becomes the middle one while keeping the cursor on that line. .It Xo .Ic scroll-top .Xc Scroll down until the current line is at the top while keeping the cursor on that line. .It Xo .Ic scroll-up (vi: C-y) (emacs: C-Up) .Xc Scroll up. .It Xo .Ic search-again (vi: n) (emacs: n) .Xc Repeat the last search. .It Xo .Ic search-backward .Ar text (vi: ?) .Xc Search backwards for the specified text. .It Xo .Ic search-backward-incremental .Ar text (emacs: C-r) .Xc Search backwards incrementally for the specified text. Is expected to be used with the .Fl i flag to the .Ic command-prompt command. .It Xo .Ic search-backward-text .Ar text .Xc Search backwards for the specified plain text. .It Xo .Ic search-forward .Ar text (vi: /) .Xc Search forward for the specified text. .It Xo .Ic search-forward-incremental .Ar text (emacs: C-s) .Xc Search forward incrementally for the specified text. Is expected to be used with the .Fl i flag to the .Ic command-prompt command. .It Xo .Ic search-forward-text .Ar text .Xc Search forward for the specified plain text. .It Xo .Ic search-reverse (vi: N) (emacs: N) .Xc Repeat the last search in the reverse direction (forward becomes backward and backward becomes forward). .It Xo .Ic select-line (vi: V) .Xc Select the current line. .It Xo .Ic select-word .Xc Select the current word. .It Xo .Ic selection-mode .Op Ic char | word | line .Xc Change the selection mode. .It Xo .Ic set-mark (vi: X) (emacs: X) .Xc Mark the current line. .It Xo .Ic start-of-line (vi: 0) (emacs: C-a) .Xc Move the cursor to the start of the line. .It Xo .Ic stop-selection .Xc Stop selecting without clearing the current selection. .It Xo .Ic toggle-position (vi: P) (emacs: P) .Xc Toggle the visibility of the position indicator in the top right. .It Xo .Ic top-line (vi: H) (emacs: M-R) .Xc Move to the top line. .El .Pp The search commands come in several varieties: .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 The default incremental search key bindings, .Ql C-r and .Ql C-s , are designed to emulate .Xr emacs 1 . When first pressed they allow a new search term to be entered; if pressed with an empty search term they repeat the previously used search term. .Pp The .Ql next-prompt and .Ql previous-prompt move between shell prompts, but require the shell to emit an escape sequence (\e033]133;A\e033\e\e) to tell .Nm where the prompts are located; if the shell does not do this, these commands will do nothing. The .Fl o flag jumps to the beginning of the command output instead of the shell prompt. Finding the beginning of command output requires the shell to emit an escape sequence (\e033]133;C\e033\e\e) to tell tmux where the output begins. If the shell does not send these escape sequences, these commands do nothing. .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. All the copy commands can take the .Fl C and .Fl P flags. The .Fl C flag suppresses setting the terminal clipboard when copying, while the .Fl P flag suppresses adding a paste buffer with the text. .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 deHMqSu .Op Fl s Ar src-pane .Op Fl t Ar target-pane .Xc Enter copy mode. .Pp .Fl u enters copy mode and scrolls one page up and .Fl d one page down. .Fl H hides the position indicator in the top right. .Fl q cancels copy mode and any other modes. .Pp .Fl M begins a mouse drag (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . .Fl S scrolls when bound to a mouse drag event; for example, .Ic copy-mode -Se is bound to .Ar MouseDrag1ScrollbarSlider by default. .Pp .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 bind PageDown copy-mode -ed .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-horizontal-mirrored The same as .Ic main-horizontal but mirrored so the main pane is at the bottom of the window. .It Ic main-vertical A large (main) pane is shown on the left of the window and the remaining panes are spread from top to bottom in the leftover space on the right. Use the .Em main-pane-width window option to specify the width of the left pane. .It Ic main-vertical-mirrored The same as .Ic main-vertical but mirrored so the main pane is on the right of the window. .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 \[aq]bb62,159x48,0,0{79x48,0,0,79x48,80,0}\[aq] .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 aepPqCJMN .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. Similarly, if the pane is in a mode, .Fl M uses the screen for the mode. 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 T ignores trailing positions that do not contain a character. .Fl N preserves trailing spaces at each line's end and .Fl J preserves trailing spaces and joins any wrapped lines; .Fl J implies .Fl T . .Fl P captures only any output that the pane has received that is the beginning of an as-yet incomplete escape sequence. .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 NryZ .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. .Fl y disables any confirmation prompts. 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 forwards" .It Li "N" Ta "Repeat last search backwards" .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 \[aq]%%\[aq]" is used. .Pp .Fl O specifies the initial sort field: one of .Ql name , .Ql size , .Ql creation (time), or .Ql activity (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 list and .Fl K a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview or if given twice with the larger preview. This command works only if at least one client is attached. .It Xo .Ic choose-tree .Op Fl GNrswyZ .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. .Fl y disables any confirmation prompts. 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 "S-Up" Ta "Swap the current window with the previous one" .It Li "S-Down" Ta "Swap the current window with the next one" .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 forwards" .It Li "N" Ta "Repeat last search backwards" .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 \[aq]%%\[aq]" is used. .Pp .Fl O specifies the initial sort field: one of .Ql index , .Ql name , or .Ql time (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 tree and .Fl K a format for each shortcut key; both are evaluated once for each line. .Fl N starts without the preview or if given twice with the larger 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 forwards" .It Li "N" Ta "Repeat last search backwards" .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 \[aq]%%\[aq]". 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 glob 7 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 Op Ar argument ... .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 \[aq]cat >>\[ti]/output.#I-#P\[aq] .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 Op Ar argument ... .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 Op Ar argument ... .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 F Ar format .Op Fl l Ar size .Op Fl t Ar target-pane .Op Ar shell-command Op Ar argument ... .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 (\[aq]\[aq]) will create a pane with no command running in it. Output can be sent to such a pane with the .Ic display-message command. 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 \[aq] keys, quotation marks are necessary, for example: .Bd -literal -offset indent bind-key \[aq]"\[aq] split-window bind-key "\[aq]" 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 .Op Ar command Op Ar argument ... .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 initial-repeat-time and .Ic repeat-time options. .Fl N attaches a note to the key (shown with .Ic list-keys .Fl N ) , which can be cleared by passing an empty string. The .Fl r and .Fl N flags can be used without .Ar command to alter an existing binding. .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 .Op 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 FHKlMRX .Op Fl c Ar target-client .Op Fl N Ar repeat-count .Op Fl t Ar target-pane .Op Ar key ... .Xc .D1 Pq alias: Ic send Send a key or keys to a window or client. 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. If .Fl K is given, keys are sent to .Ar target-client , so they are looked up in the client's key table, rather than to .Ar target-pane . All arguments are sent sequentially from first to last. If no keys are given and the command is bound to a key, then that key is used. .Pp 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. Each option belongs to one or multiple scopes .Po .Em server , .Em session , .Em window , and .Em pane .Pc and has a type .Po .Em string , .Em number , .Em key , .Em colour , .Em flag , .Em choice , or .Em command .Pc . Values of .Em flag Ns -type options may be one of: .Ic 1 , .Ic on , .Ic yes , .Ic 0 , .Ic off , or .Ic no ; for possible .Em choice values, see the respective option; for .Em key options, the .Sx KEY BINDINGS section; and for .Em colour options, the .Sx STYLES section. .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 Options are managed with these commands: .Bl -tag -width Ds .Tg set .It Xo Ic set-option .Op Fl aFgopqsuUw .Op Fl t Ar target-pane .Ar option .Op 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 scope 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 its type and can be omitted for flag and choice options to toggle its value (choice options toggle between the first two choices). .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 scope 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=\[aq]resize-pane -Z\[aq] .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 codepoint-widths[] Ar string An array option allowing widths of Unicode codepoints to be overridden. Note the new width applies to all clients. Each entry is of the form .Em codepoint=width , where codepoint may be a UTF-8 character or an identifier of the form .Ql U+number where the number is a hexadecimal number. .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 default-client-command Ar command Set the default command to run when tmux is called without a command. The default is .Ic new-session . .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 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. .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 Controls how modified keys (keys pressed together with Control, Meta, or Shift) are reported. This is the equivalent of the .Ic modifyOtherKeys .Xr xterm 1 resource. .Pp When set to .Ic on , the program inside the pane can request one of two modes: mode 1 which changes the sequence for only keys which lack an existing well-known representation; or mode 2 which changes the sequence for all keys. When set to .Ic always , modes 1 and 2 can still be requested by applications, but mode 1 will be forced instead of the standard mode. When set to .Ic off , this feature is disabled and only standard keys are reported. .Pp .Nm will always request extended keys itself if the terminal supports them. See also the .Ic extkeys feature for the .Ic terminal-features option, the .Ic extended-keys-format option and the .Ic pane_key_mode variable. .It Xo Ic extended-keys-format .Op Ic csi-u | xterm .Xc Selects one of the two possible formats for reporting modified keys to applications. This is the equivalent of the .Ic formatOtherKeys .Xr xterm 1 resource. For example, C-S-a will be reported as .Ql ^[[27;6;65~ when set to .Ic xterm , and as .Ql ^[[65;6u when set to .Ic csi-u . .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 input-buffer-size Ar bytes Maximum of bytes allowed to read in escape and control sequences. Once reached, the sequence will be discarded. .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 glob 7 patterns) 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 hyperlinks Supports OSC 8 hyperlinks. .It ignorefkeys Ignore function keys from .Xr terminfo 5 and use the .Nm internal set only. .It margins Supports DECSLRM margins. .It mouse Supports .Xr xterm 1 mouse sequences. .It osc7 Supports the OSC 7 working directory extension. .It overline Supports the overline SGR attribute. .It rectfill Supports the DECFRA rectangle fill escape sequence. .It RGB Supports RGB colour with the SGR escape sequences. .It sixel Supports SIXEL graphics. .It strikethrough Supports the strikethrough SGR escape sequence. .It sync 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 glob 7 patterns) 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\[ti]" bind User0 resize-pane -L 3 .Ed .It Xo Ic variation-selector-always-wide .Op Ic on | off .Xc Always treat Unicode variation selector 16 as marking a wide character. This is a feature of some terminals as part of their Unicode 14 support. .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 off | on | keep-last | keep-group .Xc If .Ic on , destroy the session after the last client has detached. If .Ic off (the default), leave the session orphaned. If .Ic keep-last , destroy the session only if it is in a group and has other sessions in that group. If .Ic keep-group , destroy the session unless it is in a group and is the only session in that group. .It Xo Ic detach-on-destroy .Op Ic off | on | no-detached | previous | next .Xc If .Ic on (the default), the client is detached when the session it is attached to is destroyed. If .Ic off , the client is switched to the most recently active of the remaining sessions. If .Ic no-detached , the client is detached only if there are no detached sessions; if detached sessions exist, the client is switched to the most recently active. If .Ic previous or .Ic next , the client is switched to the previous or next session in alphabetical order. .It Ic display-panes-active-colour Ar colour Set the colour used by the .Ic display-panes 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 initial-repeat-time Ar time Set the time in milliseconds for the initial repeat when a key is bound with the .Fl r flag. This allows multiple commands to be entered without pressing the prefix key again. See also the .Ic repeat-time option. If .Ic initial-repeat-time is zero, .Ic repeat-time is used for the first key press. .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 menu-style Ar style Set the menu style. See the .Sx STYLES section on how to specify .Ar style . .It Ic menu-selected-style Ar style Set the selected menu item style. See the .Sx STYLES section on how to specify .Ar style . .It Ic menu-border-style Ar style Set the menu border style. See the .Sx STYLES section on how to specify .Ar style . .It Ic menu-border-lines Ar type Set the type of characters used for drawing menu borders. See .Ic popup-border-lines for possible values for .Ar border-lines . .It Ic message-command-style Ar style Set status line message command style. This is used for the command prompt with .Xr vi 1 keys when in command mode. For how to specify .Ar style , see the .Sx STYLES section. .It Xo Ic message-line .Op Ic 0 | 1 | 2 | 3 | 4 .Xc Set line on which status line messages and the command prompt are shown. .It Ic message-style Ar style Set status line message style. This is used for messages and for the command prompt. 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 Ic prefix-timeout Ar time Set the time in milliseconds for which .Nm waits after .Ic prefix is input before dismissing it. Can be set to zero to disable any timeout. .It Ic prompt-cursor-colour Ar colour Set the colour of the cursor in the command prompt. .It Ic prompt-cursor-style Ar style Set the style of the cursor in the command prompt. See the .Ic cursor-style options for available styles. .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. See also the .Ic initial-repeat-time option. .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 | 12-with-seconds | 24-with-seconds .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 , .Ic main-horizontal-mirrored , .Ic main-vertical , or .Ic main-vertical-mirrored 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 Ic copy-mode-position-format Ar format Format of the position indicator in copy mode. .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 copy-mode-position-style Ar style Set the style of the position indicator in copy mode. For how to specify .Ar style , see the .Sx STYLES section. .Pp .It Ic copy-mode-selection-style Ar style Set the style of the selection in copy mode. For how to specify .Ar style , see the .Sx STYLES section. .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 and .Ic main-horizontal-mirrored layouts. 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 and .Ic main-vertical-mirrored layouts. .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 .It spaces space characters .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. See the .Sx STYLES section on how to specify .Ar style . Attributes are ignored. .Pp .It Ic popup-border-style Ar style Set the popup border style. See the .Sx STYLES section on how to specify .Ar style . 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 Xo Ic pane-scrollbars .Op Ic off | modal | on .Xc When enabled, a character based scrollbar appears on the left or right of each pane. A filled section of the scrollbar, known as the .Ql slider , represents the position and size of the visible part of the pane content. .Pp If set to .Ic on the scrollbar is visible all the time. If set to .Ic modal the scrollbar only appears when the pane is in copy mode or view mode. When the scrollbar is visible, the pane is narrowed by the width of the scrollbar and the text in the pane is reflowed. If set to .Ic modal , the pane is narrowed only when the scrollbar is visible. .Pp See also .Ic pane-scrollbars-style . .Pp .It Ic pane-scrollbars-style Ar style Set the scrollbars style. For how to specify .Ar style , see the .Sx STYLES section. The foreground colour is used for the slider, the background for the rest of the scrollbar. The .Ar width attribute sets the width of the scrollbar and the .Ar pad attribute the padding between the scrollbar and the pane. Other attributes are ignored. .Pp .It Xo Ic pane-scrollbars-position .Op Ic left | right .Xc Sets which side of the pane to display pane scrollbars on. .Pp .It Ic pane-status-current-style Ar style Set status line style for the currently active pane. For how to specify .Ar style , see the .Sx STYLES section. .Pp .It Ic pane-status-style Ar style Set status line style for a single pane. For how to specify .Ar style , see the .Sx STYLES section. .Pp .It Ic session-status-current-style Ar style Set status line style for the currently active session. For how to specify .Ar style , see the .Sx STYLES section. .Pp .It Ic session-status-style Ar style Set status line style for a single session. For how to specify .Ar style , see the .Sx STYLES section. .Pp .It Ic tiled-layout-max-columns Ar number Set the maximum number of columns in the .Ic tiled layout. A value of 0 (the default) means no limit. When a limit is set, panes are arranged to not exceed this number of columns, with additional panes stacked in extra rows. .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 | all .Xc Allow programs in the pane to bypass .Nm using a terminal escape sequence (\eePtmux;...\ee\e\e). If set to .Ic on , passthrough sequences will be allowed only if the pane is visible. If set to .Ic all , they will be allowed even if the pane is invisible. .Pp .It Xo Ic allow-rename .Op Ic on | off .Xc Allow programs in the pane to change the window name using a terminal escape sequence (\eek...\ee\e\e). .Pp .It Xo Ic allow-set-title .Op Ic on | off .Xc Allow programs in the pane to change the title using the terminal escape sequences (\ee]2;...\ee\e\e or \ee]0;...\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 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 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 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] \[aq]set -g status-left-style bg=red\[aq] set-option -g pane-mode-changed[42] \[aq]set -g status-left-style bg=red\[aq] .Ed .Pp Setting a hook without specifying an array index clears the hook and sets the 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 If a command fails, the .Ql command-error hook will be fired. For example, this could be used to write to a log file: .Bd -literal -offset indent set-hook -g command-error "run-shell \\"echo 'a tmux command failed' >>/tmp/log\\"" .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 client-light-theme Run when a client switches to a light theme. .It client-dark-theme Run when a client switches to a dark theme. .It command-error Run when a command fails. .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-layout-changed Run when a window layout is changed. .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 .Op 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 .Op Ar hook .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" .It Li "ScrollbarSlider" Ta "the scrollbar slider" .It Li "ScrollbarUp" Ta "above the scrollbar slider" .It Li "ScrollbarDown" Ta "below the scrollbar slider" .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, or the name of an environment variable. 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 \&? . For each pair of two arguments, if the variable in the first of the pair exists and is not zero, the second of the pair is chosen, otherwise it continues. If no condition from paired arguments matches, the default value is chosen. If there's an unpaired final argument, that is the default. If not, the default is the empty string. 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. .Ql #{?#{n:window_name},#{window_name} - } will include the window name with a dash separator if there is a window name, or the empty string if the window name is empty. .Ql #{?session_format,format1,window_format,format2,format3} will include format1 for a session format, format2 for a window format, or format3 for neither a session nor a window format. 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 any or all of the comma-separated alternatives are true, for example .Ql #{||:#{pane_in_mode},#{alternate_on}} . .Ql \&! evaluates to true if the value is false and vice versa, for example .Ql #{!:#{pane_in_mode}} . .Ql !! converts a value to a canonical boolean form, 1 for true and 0 for false, for example .Ql #{!!:non-empty string} evaluates to 1. .Pp An .Ql m specifies a .Xr glob 7 pattern 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 glob 7 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 a .Xr glob 7 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} . .Ql R repeats the first argument by a number of times given by the second argument, so .Ql #{R:a,3} will result in .Ql aaa . .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:\& , .Ql P:\& or .Ql L:\& will loop over each session, window, pane or client and insert the format once for each. .Ql L:\& , .Ql S:\& and .Ql W:\& can take an optional sort argument .Ql /i\& , .Ql /n\& , .Ql /t\& to sort by index, name, or last activity time; additionally .Ql /r\& to sort in reverse order. .Ql /r\& can also be used with .Ql P:\& to reverse the sort order by pane index. For example, .Ql S/nr:\& to sort sessions by name in reverse order. For each, two comma-separated formats may be given: the second is used for the current window, active pane, or active session. 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 . A different delimiter character may also be used, to avoid collisions with literal slashes in the pattern. For example, .Ql s|foo/|bar/|:\& will substitute .Ql foo/ with .Ql bar/ throughout. .Pp Multiple modifiers may be separated with a semicolon (;) as in .Ql #{T;=10:status-left} , which limits the resulting .Xr strftime 3 -expanded string to at most 10 characters. .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_full" Ta "" Ta "Full buffer content" .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 "cursor_blinking" Ta "" Ta "1 if the cursor is blinking" .It Li "copy_cursor_hyperlink" Ta "" Ta "Hyperlink under cursor in copy mode" .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_colour" Ta "" Ta "Cursor colour in pane" .It Li "cursor_flag" Ta "" Ta "Pane cursor flag" .It Li "cursor_shape" Ta "" Ta "Cursor shape in pane" .It Li "cursor_very_visible" Ta "" Ta "1 if the cursor is in very visible mode" .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_session_index" Ta "" Ta "Index of last session" .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 "loop_last_flag" Ta "" Ta "1 if last window, pane, session, client in the W:, P:, S:, or L: loop" .It Li "mouse_all_flag" Ta "" Ta "Pane mouse all flag" .It Li "mouse_any_flag" Ta "" Ta "Pane mouse any flag" .It Li "mouse_button_flag" Ta "" Ta "Pane mouse button flag" .It Li "mouse_hyperlink" Ta "" Ta "Hyperlink under mouse, if any" .It Li "mouse_line" Ta "" Ta "Line under mouse, if any" .It Li "mouse_sgr_flag" Ta "" Ta "Pane mouse SGR flag" .It Li "mouse_standard_flag" Ta "" Ta "Pane mouse standard flag" .It Li "mouse_status_line" Ta "" Ta "Status line on which mouse event took place" .It Li "mouse_status_range" Ta "" Ta "Range type or argument of mouse event on status line" .It Li "mouse_utf8_flag" Ta "" Ta "Pane mouse UTF-8 flag" .It Li "mouse_word" Ta "" Ta "Word under mouse, if any" .It Li "mouse_x" Ta "" Ta "Mouse X position, if any" .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 "Number of modes pane is in" .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_key_mode" Ta "" Ta "Extended key reporting mode in this pane" .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_unseen_changes" Ta "" Ta "1 if there were changes in pane while in mode" .It Li "pane_width" Ta "" Ta "Width of pane" .It Li "pid" Ta "" Ta "Server PID" .It Li "rectangle_toggle" Ta "" Ta "1 if rectangle selection is activated" .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_count" Ta "" Ta "Count of search results" .It Li "search_count_partial" Ta "" Ta "1 if search count is partial count" .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 "server_sessions" Ta "" Ta "Number of sessions" .It Li "session_active" Ta "" Ta "1 if session active" .It Li "session_activity" Ta "" Ta "Time of session last activity" .It Li "session_activity_flag" Ta "" Ta "1 if any window in session has 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_bell_flag" Ta "" Ta "1 if any window in session has bell" .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_index" Ta "" Ta "Index of session" .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_silence_flag" Ta "" Ta "1 if any window in session has silence alert" .It Li "session_stack" Ta "" Ta "Window indexes in most recent order" .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "sixel_support" Ta "" Ta "1 if server has support for SIXEL" .It Li "start_time" Ta "" Ta "Server start time" .It Li "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 brightblack , .Ic brightred , .Eo ...; .Ec .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 us=colour Set the underscore 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 Ic noattr Do not copy attributes from the default style. .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=session|X , .Ic range=window|X , .Ic range=pane|X , .Ic range=user|X , .Ic norange .Xc Mark a range for mouse events in the .Ic status-format option. When a mouse event occurs in the .Ic range=left or .Ic range=right range, the .Ql StatusLeft and .Ql StatusRight key bindings are triggered. .Pp .Ic range=session|X , .Ic range=window|X and .Ic range=pane|X are ranges for a session, window or pane. These trigger the .Ql Status mouse key with the target session, window or pane given by the .Ql X argument. .Ql X is a session ID, window index in the current session or a pane ID. For these, the .Ic mouse_status_range format variable will be set to .Ql session , .Ql window or .Ql pane . .Pp .Ic range=user|X is a user-defined range; it triggers the .Ql Status mouse key. The argument .Ql X will be available in the .Ic mouse_status_range format variable. .Ql X must be at most 15 bytes in length. .It Ic set-default Set the current colours and attributes as the default, overwriting any previous default. The previous default cannot be restored. .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 \[aq]\e033kWINDOW_NAME\e033\e\e\[aq] .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 \[aq]\e033]2;My Title\e033\e\e\[aq] .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 variable 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 "\[ti]" Ta "The window has been silent for the monitor-silence interval." .It Li "M" Ta "The window contains the marked pane." .It Li "Z" Ta "The window's active pane is zoomed." .El .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 1bFiklN .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 .Fl I is present, .Ar inputs 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. .Fl l disables splitting of .Ar inputs and .Ar prompts at commas and treats them literally. .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 by .Op Fl c Ar confirm-key .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. .Fl y changes the default behaviour (if Enter alone is pressed) of the prompt to run the command. .Fl c changes the confirmation key to .Ar confirm-key ; the default is .Ql y . .Tg menu .It Xo Ic display-menu .Op Fl OM .Op Fl b Ar border-lines .Op Fl c Ar target-client .Op Fl C Ar starting-choice .Op Fl H Ar selected-style .Op Fl s Ar style .Op Fl S Ar border-style .Op Fl t Ar target-pane .Op Fl T Ar title .Op Fl x Ar position .Op Fl y Ar position .Ar name .Ar key .Ar command .Op Ar name key command ... .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 b sets the type of characters used for drawing menu borders. See .Ic popup-border-lines for possible values for .Ar border-lines . .Pp .Fl H sets the style for the selected menu item (see .Sx STYLES ) . .Pp .Fl s sets the style for the menu and .Fl S sets the style for the menu border (see .Sx STYLES ) . .Pp .Fl T is a format for the menu title (see .Sx FORMATS ) . .Pp .Fl C sets the menu item selected by default, if the menu is not bound to a mouse key binding. .Pp .Fl x and .Fl y 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 .Fl M tells .Nm the menu should handle mouse events; by default only menus opened from mouse key bindings do so. .Pp The following keys are available in menus: .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 aCIlNpv .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. If .Fl C is given, the pane will continue to be updated while the message is displayed. If .Fl l is given, .Ar message is printed unchanged. Otherwise, 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 BCEkN .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 Op Ar argument ... .Xc .D1 Pq alias: Ic popup Display a popup running .Ar shell-command (or .Ar default-command when omitted) 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. If the command is run inside an existing popup, that popup is modified. Only the .Fl b , .Fl B , .Fl C , .Fl E , .Fl EE , .Fl K , .Fl N , .Fl s , and .Fl S options are accepted in this case; all others are ignored. .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. .Fl k allows any key to dismiss the popup instead of only .Ql Escape or .Ql C-c . .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 characters used for drawing popup borders. 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 (see .Sx STYLES ) . .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. .Pp .Fl N disables any previously specified -E, -EE, or -k option. .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 NryZ .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. .Fl y disables any confirmation prompts. 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 forwards" .It Li "N" Ta "Repeat last search backwards" .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 -p -b \[aq]%%\[aq]" is used. .Pp .Fl O specifies the initial sort field: one of .Ql time (creation), .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 Xo Ic clear-history .Op Fl H .Op Fl t Ar target-pane .Xc .D1 Pq alias: Ic clearhist Remove and free the history for the specified pane. .Fl H also removes all hyperlinks. .Tg deleteb .It Ic delete-buffer Op Fl b Ar buffer-name .D1 Pq alias: Ic deleteb 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. If .Ar path is .Ql - , the contents are read from stdin. .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. If .Ar path is .Ql - , the contents are written to stdout. .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 bCE .Op Fl c Ar start-directory .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. .Fl E redirects the command's stderr to stdout instead of ignoring it. If .Fl c is given, the current working directory is set to .Ar start-directory . If .Fl C is not given, any output to stdout is displayed in view mode (in the pane specified by .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 \[aq]\e033]12;red\e033\e\e\[aq] .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 \&Hls Set or clear a hyperlink annotation. .It Em \&Nobr Tell .Nm that the terminal does not use bright colors for bold display. .It Em \&Rect Tell .Nm 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 , \&Setulc1, \&ol Set the underscore colour or reset to the default. .Em Setulc is for RGB colours and .Em Setulc1 for ANSI or 256 colours. The .Em Setulc argument is (red * 65536) + (green * 256) + blue where each is between 0 and 255. .It Em \&Ss , Se Set or reset the cursor style. If set, a sequence such as this may be used to change the cursor to an underline: .Bd -literal -offset indent $ printf \[aq]\e033[4 q\[aq] .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 \&Sxl Indicates that the terminal supports SIXEL. .It Em \&Sync Start (parameter is 1) or end (parameter is 2) a synchronized update. .It Em \&Tc 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 %config-error Ar error An error has happened in a configuration file. .It Ic %continue Ar pane-id The pane has been continued after being paused (if the .Ar pause-after 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 Xo Ic %layout-change .Ar window-id .Ar window-layout .Ar window-visible-layout .Ar window-flags .Xc The layout of a window with ID .Ar window-id changed. 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 %message Ar message A message sent with the .Ic display-message command. .It Ic %output Ar pane-id Ar value A window pane produced output. .Ar value 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 %paste-buffer-changed Ar name Paste buffer .Ar name has been changed. .It Ic %paste-buffer-deleted Ar name Paste buffer .Ar name has been deleted. .It Ic %pause Ar pane-id The pane has been paused (if the .Ar pause-after 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 \[ti]/.tmux.conf .It Pa $XDG_CONFIG_HOME/tmux/tmux.conf .It Pa \[ti]/.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 \[ti]/.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 \[aq]exec man %%\[aq]" bind-key S command-prompt "new-window -n %1 \[aq]ssh %1\[aq]" .Ed .Sh SEE ALSO .Xr pty 4 .Sh AUTHORS .An Nicholas Marriott Aq Mt nicholas.marriott@gmail.com tmux-tmux-f222026/tmux.c000066400000000000000000000276141511153563100151230ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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(int); static char *make_label(const char *, char **); static int areshell(const char *); static const char *getshell(void); static __dead void usage(int status) { fprintf(status ? stderr : stdout, "usage: %s [-2CDhlNuVv] [-c shell-command] [-f file] [-L socket-name]\n" " [-S socket-path] [-T features] [command [flags]]\n", getprogname()); exit(status); } 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 no_realpath) { 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 (no_realpath) path = expanded; else { if (realpath(expanded, resolved) == NULL) { log_debug("%s: realpath(\"%s\") failed: %s", __func__, expanded, strerror(errno)); free(expanded); continue; } 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, 0); 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 & TMUX_SOCK_PERM) != 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); } char * shell_argv0(const char *shell, int is_login) { const char *slash, *name; char *argv0; slash = strrchr(shell, '/'); if (slash != NULL && slash[1] != '\0') name = slash + 1; else name = shell; if (is_login) xasprintf(&argv0, "-%s", name); else xasprintf(&argv0, "%s", name); return (argv0); } 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:hlL: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 'h': usage(0); case 'V': printf("tmux %s\n", 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(1); } } argc -= optind; argv += optind; if (shell_command != NULL && argc != 0) usage(1); if ((flags & CLIENT_NOFORK) && argc != 0) usage(1); 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)); } tmux-tmux-f222026/tmux.h000066400000000000000000003351061511153563100151260ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * 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 #include #include #include #include #include #include #ifdef HAVE_UTEMPTER #include #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 hyperlinks_uri; struct hyperlinks; struct input_ctx; struct input_request; struct input_requests; 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; #ifdef ENABLE_SIXEL struct sixel_image; #endif 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_SOCK_PERM #define TMUX_SOCK_PERM (7 /* o+rwx */) #endif #ifndef TMUX_TERM #define TMUX_TERM "screen" #endif #ifndef TMUX_LOCK_CMD #define TMUX_LOCK_CMD "lock -np" #endif /* Minimum layout cell size, NOT including border lines. */ #define PANE_MINIMUM 1 /* 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 #define KEYC_USER_END (KEYC_USER + KEYC_NUSER) /* 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 #define KEYC_SENT 0x40000000000000ULL /* 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) && \ (((key) & KEYC_MASK_KEY) < KEYC_USER || \ ((key) & KEYC_MASK_KEY) >= KEYC_USER_END)) /* Is this a paste key? */ #define KEYC_IS_PASTE(key) \ (((key) & KEYC_MASK_KEY) == KEYC_PASTE_START || \ ((key) & KEYC_MASK_KEY) == KEYC_PASTE_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 ## _SCROLLBAR_UP, \ KEYC_ ## name ## _SCROLLBAR_SLIDER, \ KEYC_ ## name ## _SCROLLBAR_DOWN, \ 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 "ScrollbarUp", KEYC_ ## name ## _SCROLLBAR_UP }, \ { #s "ScrollbarSlider", KEYC_ ## name ## _SCROLLBAR_SLIDER }, \ { #s "ScrollbarDown", KEYC_ ## name ## _SCROLLBAR_DOWN }, \ { #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; /* C0 control characters */ enum { C0_NUL, C0_SOH, C0_STX, C0_ETX, C0_EOT, C0_ENQ, C0_ASC, C0_BEL, C0_BS, C0_HT, C0_LF, C0_VT, C0_FF, C0_CR, C0_SO, C0_SI, C0_DLE, C0_DC1, C0_DC2, C0_DC3, C0_DC4, C0_NAK, C0_SYN, C0_ETB, C0_CAN, C0_EM, C0_SUB, C0_ESC, C0_FS, C0_GS, C0_RS, C0_US }; /* 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, /* Theme reporting. */ KEYC_REPORT_DARK_THEME, KEYC_REPORT_LIGHT_THEME, /* 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_HLS, 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_NOBR, 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_SETULC1, TTYC_SGR0, TTYC_SITM, TTYC_SMACS, TTYC_SMCUP, TTYC_SMKX, TTYC_SMOL, TTYC_SMSO, TTYC_SMUL, TTYC_SMULX, TTYC_SMXX, TTYC_SXL, TTYC_SS, TTYC_SWD, TTYC_SYNC, TTYC_TC, TTYC_TSL, TTYC_U8, TTYC_VPA, TTYC_XT }; /* Character classes. */ #define WHITESPACE "\t " /* 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_KEYS_EXTENDED 0x8000 #define MODE_CURSOR_VERY_VISIBLE 0x10000 #define MODE_CURSOR_BLINKING_SET 0x20000 #define MODE_KEYS_EXTENDED_2 0x40000 #define MODE_THEME_UPDATES 0x80000 #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) #define EXTENDED_KEY_MODES (MODE_KEYS_EXTENDED|MODE_KEYS_EXTENDED_2) /* 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 32 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 }; /* State for processing of Korean characters. */ enum hanguljamo_state { HANGULJAMO_STATE_NOT_HANGULJAMO, HANGULJAMO_STATE_CHOSEONG, HANGULJAMO_STATE_COMPOSABLE, HANGULJAMO_STATE_NOT_COMPOSABLE }; /* 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 #define GRID_FLAG_TAB 0x80 /* Grid line flags. */ #define GRID_LINE_WRAPPED 0x1 #define GRID_LINE_EXTENDED 0x2 #define GRID_LINE_DEAD 0x4 #define GRID_LINE_START_PROMPT 0x8 #define GRID_LINE_START_OUTPUT 0x10 /* Grid string flags. */ #define GRID_STRING_WITH_SEQUENCES 0x1 #define GRID_STRING_ESCAPE_SEQUENCES 0x2 #define GRID_STRING_TRIM_SPACES 0x4 #define GRID_STRING_USED_ONLY 0x8 #define GRID_STRING_EMPTY_CELLS 0x10 /* Cell positions. */ #define CELL_INSIDE 0 #define CELL_TOPBOTTOM 1 #define CELL_LEFTRIGHT 2 #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_SCROLLBAR 13 /* Cell borders. */ #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; u_int link; }; /* Grid extended cell entry. */ struct grid_extd_entry { utf8_char data; u_short attr; u_char flags; int fg; int bg; int us; u_int link; } __packed; /* Grid cell entry. */ struct grid_cell_entry { union { u_int offset; struct { u_char attr; u_char fg; u_char bg; u_char data; } data; }; u_char flags; } __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; time_t time; }; /* 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_PANE, STYLE_RANGE_WINDOW, STYLE_RANGE_SESSION, STYLE_RANGE_USER }; struct style_range { enum style_range_type type; u_int argument; char string[16]; u_int start; u_int end; /* not included */ TAILQ_ENTRY(style_range) entry; }; TAILQ_HEAD(style_ranges, style_range); /* Default style width and pad. */ #define STYLE_WIDTH_DEFAULT -1 #define STYLE_PAD_DEFAULT -1 /* Style default. */ enum style_default_type { STYLE_DEFAULT_BASE, STYLE_DEFAULT_PUSH, STYLE_DEFAULT_POP, STYLE_DEFAULT_SET }; /* 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; char range_string[16]; int width; int pad; enum style_default_type default_type; }; #ifdef ENABLE_SIXEL /* Image. */ struct image { struct screen *s; struct sixel_image *data; char *fallback; u_int px; u_int py; u_int sx; u_int sy; TAILQ_ENTRY (image) all_entry; TAILQ_ENTRY (image) entry; }; TAILQ_HEAD(images, image); #endif /* Cursor style. */ enum screen_cursor_style { SCREEN_CURSOR_DEFAULT, 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; #ifdef ENABLE_SIXEL struct images images; struct images saved_images; #endif struct screen_write_cline *write_list; struct hyperlinks *hyperlinks; }; /* 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 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_LINES_SPACES }; /* Pane border indicator option. */ #define PANE_BORDER_OFF 0 #define PANE_BORDER_COLOUR 1 #define PANE_BORDER_ARROWS 2 #define PANE_BORDER_BOTH 3 /* Mode returned by window_pane_mode function. */ #define WINDOW_PANE_NO_MODE 0 #define WINDOW_PANE_COPY_MODE 1 #define WINDOW_PANE_VIEW_MODE 2 /* Screen redraw context. */ struct screen_redraw_ctx { struct client *c; u_int statuslines; int statustop; int pane_status; enum pane_lines pane_lines; int pane_scrollbars; int pane_scrollbars_pos; 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 *); struct screen *(*get_screen)(struct window_mode_entry *); }; /* 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; }; /* Type of request to client. */ enum input_request_type { INPUT_REQUEST_PALETTE, INPUT_REQUEST_QUEUE }; /* Palette request reply data. */ struct input_request_palette_data { int idx; int c; }; /* Request sent to client on behalf of pane. */ TAILQ_HEAD(input_requests, input_request); /* 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 #define PANE_VISITED 0x8 /* 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 #define PANE_THEMECHANGED 0x2000 #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 u_int sb_slider_y; u_int sb_slider_h; 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; int control_bg; int control_fg; struct style scrollbar_style; TAILQ_ENTRY(window_pane) entry; /* link in list of all panes */ TAILQ_ENTRY(window_pane) sentry; /* link in list of last visited */ RB_ENTRY(window_pane) tree_entry; }; TAILQ_HEAD(window_panes, window_pane); 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_panes last_panes; 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) #define WINLINK_VISITED 0x8 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 /* Pane scrollbars option. */ #define PANE_SCROLLBARS_OFF 0 #define PANE_SCROLLBARS_MODAL 1 #define PANE_SCROLLBARS_ALWAYS 2 /* Pane scrollbars position option. */ #define PANE_SCROLLBARS_RIGHT 0 #define PANE_SCROLLBARS_LEFT 1 /* Pane scrollbars width, padding and fill character. */ #define PANE_SCROLLBARS_DEFAULT_PADDING 0 #define PANE_SCROLLBARS_DEFAULT_WIDTH 1 #define PANE_SCROLLBARS_CHARACTER ' ' /* True if screen in alternate screen. */ #define SCREEN_IS_ALTERNATE(s) ((s)->saved_grid != NULL) /* 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_ALERTED 0x1 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; char *buf; size_t len; }; /* 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 #define TERM_SIXEL 0x40 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; time_t last_requests; u_int sx; u_int sy; /* Cell size in pixels. */ u_int xpixel; u_int ypixel; u_int cx; u_int cy; enum screen_cursor_style cstyle; int ccolour; /* Properties of the area being drawn on. */ /* When true, the drawing area is bigger than the terminal. */ int oflag; u_int oox; u_int ooy; u_int osx; u_int osy; int mode; int fg; int bg; 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 #define TTY_HAVEDA2 0x800 #define TTY_WINSIZEQUERY 0x1000 #define TTY_WAITFG 0x2000 #define TTY_WAITBG 0x4000 #define TTY_ALL_REQUEST_FLAGS \ (TTY_HAVEDA|TTY_HAVEDA2|TTY_HAVEXDA) 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; int mouse_scrolling_flag; int mouse_slider_mpos; 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; void *ptr2; /* * Whether this command should be sent even when the pane is not * visible (used for a passthrough sequence when allow-passthrough is * "all"). */ int allow_invisible_panes; /* * Cursor and region position before the screen was updated - this is * where the command should be applied; the values in the screen have * already been updated. */ u_int ocx; u_int ocy; u_int 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 theme, this is worked out from the background colour if not reported * by terminal. */ enum client_theme { THEME_UNKNOWN, THEME_LIGHT, THEME_DARK }; /* 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 timeval last_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; enum client_theme theme; struct input_requests input_requests; #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_BRACKETPASTING 0x1000000000ULL #define CLIENT_ASSUMEPASTING 0x2000000000ULL #define CLIENT_REDRAWSCROLLBARS 0x4000000000ULL #define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ CLIENT_REDRAWOVERLAY| \ CLIENT_REDRAWPANES| \ CLIENT_REDRAWSCROLLBARS) #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; key_code last_key; uint64_t redraw_panes; uint64_t redraw_scrollbars; int message_ignore_keys; int message_ignore_styles; char *message_string; struct event message_timer; char *prompt_string; struct format_tree *prompt_formats; 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 #define PROMPT_ACCEPT 0x20 #define PROMPT_QUOTENEXT 0x40 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 source_file_depth; 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 timeval activity_time; 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); char *shell_argv0(const char *, 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 *, struct cmd_find_state *, int, struct cmdq_item **); int load_cfg_from_buffer(const void *, size_t, const char *, struct client *, struct cmdq_item *, struct cmd_find_state *, int, struct cmdq_item **); void printflike(1, 2) cfg_add_cause(const char *, ...); void cfg_print_causes(struct cmdq_item *); void cfg_show_causes(struct session *); /* 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 *); int paste_is_empty(void); struct paste_buffer *paste_get_top(const char **); struct paste_buffer *paste_get_name(const char *); void paste_free(struct paste_buffer *); 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_LAST 0x10 #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_pretty_time(time_t, int); char *format_expand_time(struct format_tree *, const char *); char *format_expand(struct format_tree *, const char *); char *format_single(struct cmdq_item *, const char *, 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_hyperlink(struct grid *, u_int, u_int, struct screen *); char *format_grid_line(struct grid *, u_int); /* format-draw.c */ void format_draw(struct screen_write_ctx *, const struct grid_cell *, u_int, const char *, struct style_ranges *, 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 *); void notify_paste_buffer(const char *, int); /* 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 *); const struct cmd_list *options_get_command(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); struct options_entry *options_set_command(struct options *, const char *, struct cmd_list *); 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 #define JOB_DEFAULTSHELL 0x8 #define JOB_SHOWSTDERR 0x10 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 *, struct hyperlinks *); void tty_reset(struct tty *); void tty_region_off(struct tty *); void tty_margin_off(struct tty *); void tty_cursor(struct tty *, u_int, u_int); void tty_clipboard_query(struct tty *); void tty_putcode(struct tty *, enum tty_code_code); void tty_putcode_i(struct tty *, enum tty_code_code, int); void tty_putcode_ii(struct tty *, enum tty_code_code, int, int); void tty_putcode_iii(struct tty *, enum tty_code_code, int, int, int); void tty_putcode_s(struct tty *, enum tty_code_code, const char *); void tty_putcode_ss(struct tty *, enum tty_code_code, const char *, const char *); void tty_puts(struct tty *, const char *); void tty_putc(struct tty *, u_char); void tty_putn(struct tty *, const void *, size_t, u_int); void tty_cell(struct tty *, const struct grid_cell *, const struct grid_cell *, struct colour_palette *, struct hyperlinks *); int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); void tty_invalidate(struct tty *); void tty_start_tty(struct tty *); void tty_send_requests(struct tty *); void tty_repeat_requests(struct tty *, int); void tty_stop_tty(struct tty *); void tty_set_title(struct tty *, const char *); void tty_set_path(struct tty *, const char *); void tty_update_mode(struct tty *, int, struct screen *); void tty_draw_line(struct tty *, struct screen *, u_int, u_int, u_int, u_int, u_int, const struct grid_cell *, struct colour_palette *); #ifdef ENABLE_SIXEL void tty_draw_images(struct client *, struct window_pane *, struct screen *); #endif void tty_sync_start(struct tty *); void tty_sync_end(struct tty *); int tty_open(struct tty *, char **); void tty_close(struct tty *); void tty_free(struct tty *); void tty_update_features(struct tty *); void tty_set_selection(struct tty *, const char *, 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_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 *); #ifdef ENABLE_SIXEL void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); #endif void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *); /* 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_string_i(struct tty_term *, enum tty_code_code, int); const char *tty_term_string_ii(struct tty_term *, enum tty_code_code, int, int); const char *tty_term_string_iii(struct tty_term *, enum tty_code_code, int, int, int); const char *tty_term_string_s(struct tty_term *, enum tty_code_code, const char *); const char *tty_term_string_ss(struct tty_term *, enum tty_code_code, const char *, const char *); int tty_term_number(struct tty_term *, enum tty_code_code); int tty_term_flag(struct tty_term *, enum tty_code_code); const char *tty_term_describe(struct tty_term *, enum tty_code_code); /* 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 *); int tty_keys_colours(struct tty *, const char *, size_t, size_t *, int *, int *); /* arguments.c */ void args_set(struct args *, u_char, struct args_value *, int); struct args *args_create(void); struct args *args_parse(const struct args_parse *, struct args_value *, u_int, char **); 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_strtonum_and_expand(struct args *, u_char, long long, long long, struct cmdq_item *, char **); long long args_percentage(struct args *, u_char, long long, long long, long long, char **); long long args_string_percentage(const char *, long long, long long, long long, char **); long long args_percentage_and_expand(struct args *, u_char, long long, long long, long long, struct cmdq_item *, char **); long long args_string_percentage_and_expand(const char *, long long, long long, long long, struct cmdq_item *, char **); /* cmd-find.c */ int cmd_find_target(struct cmd_find_state *, struct cmdq_item *, 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[]; const struct cmd_entry *cmd_find(const char *, char **); 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 *); int cmd_get_parse_flags(struct cmd *); struct cmd *cmd_parse(struct args_value *, u_int, const char *, u_int, 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(const 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(const 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 */ 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 *, struct cmd_find_state *); void cmdq_free_state(struct cmdq_state *); void printflike(3, 4) cmdq_add_format(struct cmdq_state *, const char *, const char *, ...); 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 cmdq_print_data(struct cmdq_item *, struct evbuffer *); 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 *); struct client_file *file_read(struct client *, const char *, client_file_cb, void *); void file_cancel(struct client_file *); void file_push(struct client_file *); int file_write_left(struct client_files *); void file_write_open(struct client_files *, struct tmuxpeer *, 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 *); void file_read_cancel(struct client_files *, struct imsg *); /* server.c */ extern struct tmuxproc *server_proc; extern struct clients clients; extern struct cmd_find_state marked_pane; extern struct message_list message_log; extern time_t current_time; void server_set_marked(struct session *, struct winlink *, struct window_pane *); void server_clear_marked(void); int server_is_marked(struct session *, struct winlink *, struct window_pane *); int server_check_marked(void); int server_start(struct tmuxproc *, uint64_t, 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(uint64_t, 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); 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 *); void server_client_print(struct client *, int, struct evbuffer *); /* 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(6, 7) status_message_set(struct client *, int, 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 */ #define INPUT_BUF_DEFAULT_SIZE 1048576 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 *); void input_set_buffer_size(size_t); void input_request_reply(struct client *, enum input_request_type, void *); void input_cancel_requests(struct client *); /* 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); enum client_theme colour_totheme(int); int colour_fromstring(const char *); int colour_256toRGB(int); int colour_256to16(int); int colour_byname(const char *); int colour_parseX11(const char *); void colour_palette_init(struct colour_palette *); void colour_palette_clear(struct colour_palette *); void colour_palette_free(struct colour_palette *); 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); void grid_set_tab(struct grid_cell *, 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, struct screen *); 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); int grid_in_set(struct grid *, u_int, u_int, const char *); /* 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, enum box_lines, const struct grid_cell *); void screen_write_vline(struct screen_write_ctx *, u_int, int, int); void screen_write_menu(struct screen_write_ctx *, struct menu *, int, enum box_lines, const struct grid_cell *, const struct grid_cell *, const struct grid_cell *); void screen_write_box(struct screen_write_ctx *, u_int, u_int, enum box_lines, const struct grid_cell *, const char *); void screen_write_preview(struct screen_write_ctx *, struct screen *, u_int, u_int); void screen_write_backspace(struct screen_write_ctx *); 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 *, const char *, u_char *, u_int); void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int, int); #ifdef ENABLE_SIXEL void screen_write_sixelimage(struct screen_write_ctx *, struct sixel_image *, u_int); #endif void screen_write_alternateon(struct screen_write_ctx *, struct grid_cell *, int); void screen_write_alternateoff(struct screen_write_ctx *, struct grid_cell *, int); /* screen-redraw.c */ void screen_redraw_screen(struct client *); void screen_redraw_pane(struct client *, struct window_pane *, int); /* 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_reset_hyperlinks(struct screen *); void screen_set_default_cursor(struct screen *, struct options *); 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); 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 *); void window_pane_paste(struct window_pane *, key_code, char *, size_t); int window_pane_visible(struct window_pane *); int window_pane_exited(struct window_pane *); u_int window_pane_search(struct window_pane *, const char *, int, int); const char *window_printable_flags(struct winlink *, int); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); struct window_pane *window_pane_find_right(struct window_pane *); void window_pane_stack_push(struct window_panes *, struct window_pane *); void window_pane_stack_remove(struct window_panes *, struct window_pane *); void window_set_name(struct window *, const char *); void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); 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 *); void window_pane_default_cursor(struct window_pane *); int window_pane_mode(struct window_pane *); int window_pane_show_scrollbar(struct window_pane *, int); int window_pane_get_bg(struct window_pane *); int window_pane_get_fg(struct window_pane *); int window_pane_get_fg_control_client(struct window_pane *); int window_pane_get_bg_control_client(struct window_pane *); int window_get_bg_client(struct window_pane *); enum client_theme window_pane_get_theme(struct window_pane *); void window_pane_send_theme_update(struct window_pane *); /* 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 *, int); 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 int (*mode_tree_swap_cb)(void *, void *); 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); int 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, mode_tree_swap_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_align(struct mode_tree_item *, int); 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_scroll(struct window_pane *, int, u_int, int); void window_copy_pageup(struct window_pane *, int); void window_copy_pagedown(struct window_pane *, int, 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); int window_copy_get_current_offset(struct window_pane *, u_int *, u_int *); char *window_copy_get_hyperlink(struct window_pane *, u_int, 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_ready(struct client *); void control_stop(struct client *); void control_set_pane_on(struct client *, struct window_pane *); void control_set_pane_off(struct client *, struct window_pane *); 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_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 *); void control_notify_paste_buffer_changed(const char *); void control_notify_paste_buffer_deleted(const char *); /* session.c */ extern struct sessions sessions; extern struct session_groups session_groups; extern u_int next_session_id; int session_cmp(struct session *, struct session *); RB_PROTOTYPE(sessions, session, entry, session_cmp); int session_group_cmp(struct session_group *, struct session_group *s2); RB_PROTOTYPE(session_groups, session_group, entry, session_group_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_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 *); void session_theme_changed(struct session *); /* utf8.c */ enum utf8_state utf8_towc (const struct utf8_data *, wchar_t *); enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *); void utf8_update_width_cache(void); 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); /* utf8-combined.c */ int utf8_has_zwj(const struct utf8_data *); int utf8_is_zwj(const struct utf8_data *); int utf8_is_vs(const struct utf8_data *); int utf8_is_hangul_filler(const struct utf8_data *); int utf8_should_combine(const struct utf8_data *, const struct utf8_data *); enum hanguljamo_state hanguljamo_check_state(const struct utf8_data *, const struct utf8_data *); /* 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, int, struct cmdq_item *, u_int, u_int, struct client *, enum box_lines, const char *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); int menu_display(struct menu *, int, int, struct cmdq_item *, u_int, u_int, struct client *, enum box_lines, const char *, const char *, const char *, struct cmd_find_state *, menu_choice_cb, void *); struct screen *menu_mode_cb(struct client *, void *, u_int *, u_int *); void menu_check_cb(struct client *, void *, u_int, u_int, u_int, 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 #define POPUP_CLOSEANYKEY 0x8 typedef void (*popup_close_cb)(int, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *); int popup_display(int, enum box_lines, struct cmdq_item *, u_int, u_int, u_int, u_int, struct environ *, const char *, int, char **, const char *, const char *, struct client *, struct session *, const char *, const char *, popup_close_cb, void *); int popup_editor(struct client *, const char *, size_t, popup_finish_edit_cb, void *); int popup_present(struct client *); int popup_modify(struct client *, const char *, const char *, const char *, enum box_lines, int); /* 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 *); void style_set_scrollbar_style_from_option(struct style *, struct options *); /* 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); #ifdef ENABLE_SIXEL /* image.c */ int image_free_all(struct screen *); struct image *image_store(struct screen *, struct sixel_image *); int image_check_line(struct screen *, u_int, u_int); int image_check_area(struct screen *, u_int, u_int, u_int, u_int); int image_scroll_up(struct screen *, u_int); /* image-sixel.c */ #define SIXEL_COLOUR_REGISTERS 1024 struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int); void sixel_free(struct sixel_image *); void sixel_log(struct sixel_image *); void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *); struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int, u_int, u_int, u_int, int); char *sixel_print(struct sixel_image *, struct sixel_image *, size_t *); struct screen *sixel_to_screen(struct sixel_image *); #endif /* server-acl.c */ void server_acl_init(void); struct server_acl_user *server_acl_user_find(uid_t); void server_acl_display(struct cmdq_item *); void server_acl_user_allow(uid_t); void server_acl_user_deny(uid_t); void server_acl_user_allow_write(uid_t); void server_acl_user_deny_write(uid_t); int server_acl_join(struct client *); uid_t server_acl_get_uid(struct server_acl_user *); /* hyperlink.c */ u_int hyperlinks_put(struct hyperlinks *, const char *, const char *); int hyperlinks_get(struct hyperlinks *, u_int, const char **, const char **, const char **); struct hyperlinks *hyperlinks_init(void); struct hyperlinks *hyperlinks_copy(struct hyperlinks *); void hyperlinks_reset(struct hyperlinks *); void hyperlinks_free(struct hyperlinks *); #endif /* TMUX_H */ tmux-tmux-f222026/tools/000077500000000000000000000000001511153563100151105ustar00rootroot00000000000000tmux-tmux-f222026/tools/24-bit-color.sh000066400000000000000000000046251511153563100175700ustar00rootroot00000000000000#!/bin/bash # # This file echoes four gradients with 24-bit color codes # to the terminal to demonstrate their functionality. # The foreground escape sequence is ^[38;2;;;m # The background escape sequence is ^[48;2;;;m # range from 0 to 255 inclusive. # The escape sequence ^[0m returns output to default # # From # https://github.com/gnachman/iTerm2/blob/master/tests/24-bit-color.sh # and presumably covered by # https://github.com/gnachman/iTerm2/blob/master/LICENSE # SEQ1= if which gseq >/dev/null 2>&1; then SEQ1=gseq elif seq --version|grep -q GNU; then SEQ1=seq fi if [ -n "$SEQ1" ]; then # GNU seq requires a -ve increment if going backwards seq1() { if [ $1 -gt $2 ]; then $SEQ1 $1 -1 $2 else $SEQ1 $1 $2 fi } SEQ=seq1 else SEQ=seq fi SEPARATOR=':' setBackgroundColor() { echo -en "\033[48${SEPARATOR}2${SEPARATOR}$1${SEPARATOR}$2${SEPARATOR}$3""m" } resetOutput() { echo -en "\033[0m\n" } # Gives a color $1/255 % along HSV # Who knows what happens when $1 is outside 0-255 # Echoes "$red $green $blue" where # $red $green and $blue are integers # ranging between 0 and 255 inclusive rainbowColor() { let h=$1/43 let f=$1-43*$h let t=$f*255/43 let q=255-t if [ $h -eq 0 ] then echo "255 $t 0" elif [ $h -eq 1 ] then echo "$q 255 0" elif [ $h -eq 2 ] then echo "0 255 $t" elif [ $h -eq 3 ] then echo "0 $q 255" elif [ $h -eq 4 ] then echo "$t 0 255" elif [ $h -eq 5 ] then echo "255 0 $q" else # execution should never reach here echo "0 0 0" fi } for i in `$SEQ 0 127`; do setBackgroundColor $i 0 0 echo -en " " done resetOutput for i in `$SEQ 255 128`; do setBackgroundColor $i 0 0 echo -en " " done resetOutput for i in `$SEQ 0 127`; do setBackgroundColor 0 $i 0 echo -n " " done resetOutput for i in `$SEQ 255 128`; do setBackgroundColor 0 $i 0 echo -n " " done resetOutput for i in `$SEQ 0 127`; do setBackgroundColor 0 0 $i echo -n " " done resetOutput for i in `$SEQ 255 128`; do setBackgroundColor 0 0 $i echo -n " " done resetOutput for i in `$SEQ 0 127`; do setBackgroundColor `rainbowColor $i` echo -n " " done resetOutput for i in `$SEQ 255 128`; do setBackgroundColor `rainbowColor $i` echo -n " " done resetOutput tmux-tmux-f222026/tools/256colors.pl000066400000000000000000000031741511153563100172100ustar00rootroot00000000000000#!/usr/bin/perl # Author: Todd Larason # $XFree86: xc/programs/xterm/vttests/256colors2.pl,v 1.2 2002/03/26 01:46:43 dickey Exp $ # use the resources for colors 0-15 - usually more-or-less a # reproduction of the standard ANSI colors, but possibly more # pleasing shades # colors 16-231 are a 6x6x6 color cube for ($red = 0; $red < 6; $red++) { for ($green = 0; $green < 6; $green++) { for ($blue = 0; $blue < 6; $blue++) { printf("\x1b]4;%d;rgb:%2.2x/%2.2x/%2.2x\x1b\\", 16 + ($red * 36) + ($green * 6) + $blue, ($red ? ($red * 40 + 55) : 0), ($green ? ($green * 40 + 55) : 0), ($blue ? ($blue * 40 + 55) : 0)); } } } # colors 232-255 are a grayscale ramp, intentionally leaving out # black and white for ($gray = 0; $gray < 24; $gray++) { $level = ($gray * 10) + 8; printf("\x1b]4;%d;rgb:%2.2x/%2.2x/%2.2x\x1b\\", 232 + $gray, $level, $level, $level); } # display the colors # first the system ones: print "System colors:\n"; for ($color = 0; $color < 8; $color++) { print "\x1b[48;5;${color}m "; } print "\x1b[0m\n"; for ($color = 8; $color < 16; $color++) { print "\x1b[48;5;${color}m "; } print "\x1b[0m\n\n"; # now the color cube print "Color cube, 6x6x6:\n"; for ($green = 0; $green < 6; $green++) { for ($red = 0; $red < 6; $red++) { for ($blue = 0; $blue < 6; $blue++) { $color = 16 + ($red * 36) + ($green * 6) + $blue; print "\x1b[48;5;${color}m "; } print "\x1b[0m "; } print "\n"; } # now the grayscale ramp print "Grayscale ramp:\n"; for ($color = 232; $color < 256; $color++) { print "\x1b[48;5;${color}m "; } print "\x1b[0m\n"; tmux-tmux-f222026/tools/UTF-8-demo.txt000066400000000000000000000333521511153563100174040ustar00rootroot00000000000000 UTF-8 encoded sample plain-text file ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ Markus Kuhn [ˈmaʳkÊŠs kuËn] — 2002-07-25 CC BY The ASCII compatible UTF-8 encoding used in this plain-text file is defined in Unicode, ISO 10646-1, and RFC 2279. Using Unicode/UTF-8, you can write in emails and source code things such as Mathematics and sciences: ∮ Eâ‹…da = Q, n → ∞, ∑ f(i) = ∠g(i), ⎧⎡⎛┌─────â”⎞⎤⎫ ⎪⎢⎜│a²+b³ ⎟⎥⎪ ∀x∈â„: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), ⎪⎢⎜│───── ⎟⎥⎪ ⎪⎢⎜⎷ c₈ ⎟⎥⎪ â„• ⊆ â„•â‚€ ⊂ ℤ ⊂ ℚ ⊂ ℠⊂ â„‚, ⎨⎢⎜ ⎟⎥⎬ ⎪⎢⎜ ∞ ⎟⎥⎪ ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫), ⎪⎢⎜ ⎲ ⎟⎥⎪ ⎪⎢⎜ ⎳aâ±-bâ±âŽŸâŽ¥âŽª 2Hâ‚‚ + Oâ‚‚ ⇌ 2Hâ‚‚O, R = 4.7 kΩ, ⌀ 200 mm ⎩⎣âŽi=1 ⎠⎦⎭ Linguistics and dictionaries: ði ıntəˈnæʃənÉ™l fəˈnÉ›tık É™soÊŠsiˈeıʃn Y [ˈÊpsilÉ”n], Yen [jÉ›n], Yoga [ˈjoËgÉ‘] APL: ((Vâ³V)=â³â´V)/Vâ†,V ⌷â†â³â†’â´âˆ†âˆ‡âŠƒâ€¾âŽâ•⌈ Nicer typography in plain text files: â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•— â•‘ â•‘ â•‘ • ‘single’ and “double†quotes â•‘ â•‘ â•‘ â•‘ • Curly apostrophes: “We’ve been here†║ â•‘ â•‘ â•‘ • Latin-1 apostrophe and accents: '´` â•‘ â•‘ â•‘ â•‘ • ‚deutsche‘ „Anführungszeichen“ â•‘ â•‘ â•‘ â•‘ • †, ‡, ‰, •, 3–4, —, −5/+5, â„¢, … â•‘ â•‘ â•‘ â•‘ • ASCII safety test: 1lI|, 0OD, 8B â•‘ â•‘ ╭─────────╮ â•‘ â•‘ • the euro symbol: │ 14.95 € │ â•‘ â•‘ ╰─────────╯ â•‘ ╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• Combining characters: STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑ Greek (in Polytonic): The Greek anthem: Σὲ γνωÏίζω ἀπὸ τὴν κόψη τοῦ σπαθιοῦ τὴν Ï„ÏομεÏá½µ, σὲ γνωÏίζω ἀπὸ τὴν ὄψη ποὺ μὲ βία μετÏάει τὴ γῆ. ᾿Απ᾿ τὰ κόκκαλα βγαλμένη τῶν ῾Ελλήνων τὰ ἱεÏá½± καὶ σὰν Ï€Ïῶτα ἀνδÏειωμένη χαῖÏε, ὦ χαῖÏε, ᾿ΕλευθεÏιά! From a speech of Demosthenes in the 4th century BC: Οá½Ï‡á½¶ ταá½Ï„á½° παÏίσταταί μοι γιγνώσκειν, ὦ ἄνδÏες ᾿Αθηναῖοι, ὅταν τ᾿ εἰς τὰ Ï€Ïάγματα ἀποβλέψω καὶ ὅταν Ï€Ïὸς τοὺς λόγους οὓς ἀκούω· τοὺς μὲν Î³á½°Ï Î»á½¹Î³Î¿Ï…Ï‚ πεÏá½¶ τοῦ τιμωÏήσασθαι Φίλιππον á½Ïá¿¶ γιγνομένους, τὰ δὲ Ï€Ïάγματ᾿ εἰς τοῦτο Ï€Ïοήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αá½Ï„οὶ Ï€ÏότεÏον κακῶς σκέψασθαι δέον. οá½Î´á½³Î½ οὖν ἄλλο μοι δοκοῦσιν οἱ τὰ τοιαῦτα λέγοντες á¼¢ τὴν ὑπόθεσιν, πεÏá½¶ á¼§Ï‚ βουλεύεσθαι, οá½Ï‡á½¶ τὴν οὖσαν παÏιστάντες ὑμῖν á¼Î¼Î±Ïτάνειν. á¼Î³á½¼ δέ, ὅτι μέν ποτ᾿ á¼Î¾á¿†Î½ τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον τιμωÏήσασθαι, καὶ μάλ᾿ ἀκÏιβῶς οἶδα· á¼Ï€á¾¿ á¼Î¼Î¿á¿¦ γάÏ, οὠπάλαι γέγονεν ταῦτ᾿ ἀμφότεÏα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν Ï€Ïολαβεῖν ἡμῖν εἶναι τὴν Ï€Ïώτην, ὅπως τοὺς συμμάχους σώσομεν. á¼á½°Î½ Î³á½°Ï Ï„Î¿á¿¦Ï„Î¿ βεβαίως ὑπάÏξῃ, τότε καὶ πεÏá½¶ τοῦ τίνα τιμωÏήσεταί τις καὶ ὃν Ï„Ïόπον á¼Î¾á½³ÏƒÏ„αι σκοπεῖν· Ï€Ïὶν δὲ τὴν á¼€Ïχὴν á½€Ïθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι πεÏá½¶ τῆς τελευτῆς á½Î½Ï„ινοῦν ποιεῖσθαι λόγον. Δημοσθένους, Γ´ ᾿Ολυνθιακὸς Georgian: From a Unicode conference invitation: გთხáƒáƒ•თ áƒáƒ®áƒšáƒáƒ•ე გáƒáƒ˜áƒáƒ áƒáƒ— რეგისტრáƒáƒªáƒ˜áƒ Unicode-ის მეáƒáƒ—ე სáƒáƒ”რთáƒáƒ¨áƒáƒ áƒ˜áƒ¡áƒ კáƒáƒœáƒ¤áƒ”რენციáƒáƒ–ე დáƒáƒ¡áƒáƒ¡áƒ¬áƒ áƒ”ბáƒáƒ“, რáƒáƒ›áƒ”ლიც გáƒáƒ˜áƒ›áƒáƒ áƒ—ებრ10-12 მáƒáƒ áƒ¢áƒ¡, ქ. მáƒáƒ˜áƒœáƒªáƒ¨áƒ˜, გერმáƒáƒœáƒ˜áƒáƒ¨áƒ˜. კáƒáƒœáƒ¤áƒ”რენცირშეჰკრებს ერთáƒáƒ“ მსáƒáƒ¤áƒšáƒ˜áƒáƒ¡ ექსპერტებს ისეთ დáƒáƒ áƒ’ებში რáƒáƒ’áƒáƒ áƒ˜áƒªáƒáƒ ინტერნეტი დრUnicode-ი, ინტერნáƒáƒªáƒ˜áƒáƒœáƒáƒšáƒ˜áƒ–áƒáƒªáƒ˜áƒ დრლáƒáƒ™áƒáƒšáƒ˜áƒ–áƒáƒªáƒ˜áƒ, Unicode-ის გáƒáƒ›áƒáƒ§áƒ”ნებრáƒáƒžáƒ”რáƒáƒªáƒ˜áƒ£áƒš სისტემებსáƒ, დრგáƒáƒ›áƒáƒ§áƒ”ნებით პრáƒáƒ’რáƒáƒ›áƒ”ბში, შრიფტებში, ტექსტების დáƒáƒ›áƒ£áƒ¨áƒáƒ•ებáƒáƒ¡áƒ დრმრáƒáƒ•áƒáƒšáƒ”ნáƒáƒ•áƒáƒœ კáƒáƒ›áƒžáƒ˜áƒ£áƒ¢áƒ”რულ სისტემებში. Russian: From a Unicode conference invitation: ЗарегиÑтрируйтеÑÑŒ ÑÐµÐ¹Ñ‡Ð°Ñ Ð½Ð° ДеÑÑтую Международную Конференцию по Unicode, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑоÑтоитÑÑ 10-12 марта 1997 года в Майнце в Германии. ÐšÐ¾Ð½Ñ„ÐµÑ€ÐµÐ½Ñ†Ð¸Ñ Ñоберет широкий круг ÑкÑпертов по вопроÑам глобального Интернета и Unicode, локализации и интернационализации, воплощению и применению Unicode в различных операционных ÑиÑтемах и программных приложениÑÑ…, шрифтах, верÑтке и многоÑзычных компьютерных ÑиÑтемах. Thai (UCS Level 2): Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese classic 'San Gua'): [----------------------------|------------------------] ๠à¹à¸œà¹ˆà¸™à¸”ินฮั่นเสื่อมโทรมà¹à¸ªà¸™à¸ªà¸±à¸‡à¹€à¸§à¸Š พระปà¸à¹€à¸à¸¨à¸à¸­à¸‡à¸šà¸¹à¹Šà¸à¸¹à¹‰à¸‚ึ้นใหม่ สิบสองà¸à¸©à¸±à¸•ริย์à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¹à¸¥à¸–ัดไป สององค์ไซร้โง่เขลาเบาปัà¸à¸à¸² ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนัà¸à¸«à¸™à¸² โฮจิ๋นเรียà¸à¸—ัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัภเหมือนขับไสไล่เสือจาà¸à¹€à¸„หา รับหมาป่าเข้ามาเลยอาสัภà¸à¹ˆà¸²à¸¢à¸­à¹‰à¸­à¸‡à¸­à¸¸à¹‰à¸™à¸¢à¸¸à¹à¸¢à¸à¹ƒà¸«à¹‰à¹à¸•à¸à¸à¸±à¸™ ใช้สาวนั้นเป็นชนวนชื่นชวนใจ พลันลิฉุยà¸à¸¸à¸¢à¸à¸µà¸à¸¥à¸±à¸šà¸à¹ˆà¸­à¹€à¸«à¸•ุ ช่างอาเพศจริงหนาฟ้าร้องไห้ ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูà¸à¸¹à¹‰à¸šà¸£à¸£à¸¥à¸±à¸‡à¸à¹Œ ฯ (The above is a two-column text. If combining characters are handled correctly, the lines of the second column should be aligned with the | character above.) Ethiopian: Proverbs in the Amharic language: ሰማይ አይታረስ ንጉሥ አይከሰስᢠብላ ካለአእንደአባቴ በቆመጠáŠá¢ ጌጥ ያለቤቱ á‰áˆáŒ¥áŠ“ áŠá‹á¢ ደሀ በሕáˆáˆ™ ቅቤ ባይጠጣ ንጣት በገደለá‹á¢ የአá ወለáˆá‰³ በቅቤ አይታሽáˆá¢ አይጥ በበላ ዳዋ ተመታᢠሲተረጉሙ ይደረáŒáˆ™á¢ ቀስ በቀስᥠዕንá‰áˆ‹áˆ በእáŒáˆ© ይሄዳáˆá¢ ድር ቢያብር አንበሳ ያስርᢠሰዠእንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርáˆá¢ እáŒá‹œáˆ­ የከáˆá‰°á‹áŠ• ጉሮሮ ሳይዘጋዠአይድርáˆá¢ የጎረቤት ሌባᥠቢያዩት ይስቅ ባያዩት ያጠáˆá‰…ᢠሥራ ከመáታት áˆáŒ„ን ላá‹á‰³á‰µá¢ ዓባይ ማደሪያ የለá‹á¥ áŒáŠ•á‹µ ይዞ ይዞራáˆá¢ የእስላሠአገሩ መካ የአሞራ አገሩ ዋርካᢠተንጋሎ ቢተበተመáˆáˆ¶ ባá‰á¢ ወዳጅህ ማር ቢሆን ጨርስህ አትላሰá‹á¢ እáŒáˆ­áˆ…ን በáራሽህ áˆáŠ­ ዘርጋᢠRunes: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛠᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ áš¹á›áš¦ ᚦᚪ ᚹᛖᛥᚫ (Old English, which transcribed into Latin reads 'He cwaeth that he bude thaem lande northweardum with tha Westsae.' and means 'He said that he lived in the northern land near the Western Sea.') Braille: ⡌â â §â ‘ â ¼â â ’ â¡â œâ ‡â ‘⠹⠰⠎ ⡣⠕⠌ â¡â œâ ‡â ‘â ¹ â ºâ â Ž ⠙⠑â â ™â ’ â žâ • ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ â Šâ Ž â â • ⠙⠳⠃⠞ â ±â â žâ ‘â §â » â â ƒâ ³â ž â ¹â â žâ ² ⡹⠑ ⠗⠑⠛⠊⠌⠻ â •â ‹ ⠙⠊⠎ ⠃⠥⠗⠊â â ‡ â ºâ â Ž â Žâ Šâ ›â â « ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹â â â â ‚ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ â ¥â â ™â »â žâ â …⠻⠂ â â â ™ ⠹⠑ â ¡â Šâ ‘â ‹ â â ³â —â â »â ² ⡎⠊⠗⠕⠕⠛⠑ â Žâ Šâ ›â â « â Šâ žâ ² â¡â â ™ ⡎⠊⠗⠕⠕⠛⠑⠰⠎ â â â â ‘ â ºâ â Ž ⠛⠕⠕⠙ â ¥â â •â  â °â¡¡â â â ›â ‘â ‚ â ‹â •â — â â â ¹â ¹â ”â › ⠙⠑ â ¡â •â Žâ ‘ â žâ • â â ¥â ž ⠙⠊⠎ â ™â â â ™ â žâ •â ² ⡕⠇⠙ â¡â œâ ‡â ‘â ¹ â ºâ â Ž â â Ž ⠙⠑â â ™ â â Ž â  â ™â •â •â —â ¤â â â Šâ ‡â ² â¡â ”⠙⠖ ⡊ ⠙⠕â â °â ž â â ‘â â  â žâ • â Žâ â ¹ â ¹â â ž ⡊ â …â â ªâ ‚ â •â ‹ â â ¹ â ªâ  â …â â ªâ ‡â «â ›â ‘â ‚ â ±â â ž ⠹⠻⠑ â Šâ Ž â â œâ žâ Šâ Šâ ¥â ‡â œâ ‡â ¹ ⠙⠑â â ™ â â ƒâ ³â ž â  â ™â •â •â —â ¤â â â Šâ ‡â ² ⡊ â â Šâ £â ž â ™â â §â ‘ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ â â ¹â Žâ ‘⠇⠋⠂ â žâ • ⠗⠑⠛⠜⠙ â  â Šâ •â ‹â ‹â ”â ¤â â â Šâ ‡ â â Ž ⠹⠑ ⠙⠑â â ™â ‘â Œ â â Šâ ‘â Šâ ‘ â •â ‹ â Šâ —â •â â â •â â ›â »â ¹ â ” ⠹⠑ â žâ —â â ™â ‘â ² ⡃⠥⠞ ⠹⠑ â ºâ Šâ Žâ ™â •â  â •â ‹ ⠳⠗ â â â Šâ ‘⠌⠕⠗⠎ â Šâ Ž â ” ⠹⠑ â Žâ Šâ â Šâ ‡â ‘â † â â â ™ â â ¹ â ¥â â ™â â ‡â ‡â ªâ « â ™â â â ™â Ž â ©â â ‡â ‡ â â •â ž ⠙⠊⠌⠥⠗⠃ â Šâ žâ ‚ â •â — ⠹⠑ ⡊⠳â â žâ —⠹⠰⠎ ⠙⠕â â ‘ â ‹â •â —â ² ⡹⠳ ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ â â »â â Šâ ž â â ‘ â žâ • â —â ‘â â ‘â â žâ ‚ â ‘â â â ™â â žâ Šâ Šâ â ‡â ‡â ¹â ‚ â ¹â â ž â¡â œâ ‡â ‘â ¹ â ºâ â Ž â â Ž ⠙⠑â â ™ â â Ž â  â ™â •â •â —â ¤â â â Šâ ‡â ² (The first couple of paragraphs of "A Christmas Carol" by Dickens) Compact font selection example text: ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789 abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ –—‘“â€â€žâ€ â€¢â€¦â€°â„¢Å“ŠŸž€ ΑΒΓΔΩαβγδω ÐБВГДабвгд ∀∂∈â„∧∪≡∞ ↑↗↨↻⇣ â”┼╔╘░►☺♀ ï¬ï¿½â‘€â‚‚ἠḂӥẄÉËâŽ×ԱრGreetings in various languages: Hello world, ΚαλημέÏα κόσμε, コンニãƒãƒ Box drawing alignment tests: â–ˆ â–‰ â•”â•â•╦â•â•â•— ┌──┬──┠╭──┬──╮ ╭──┬──╮ â”â”â”┳â”â”┓ ┎┒â”┑ â•· â•» â”┯┓ ┌┰┠▊ ╱╲╱╲╳╳╳ ║┌─╨─â”â•‘ │╔â•â•§â•╗│ │╒â•╪â•╕│ │╓─â•─╖│ ┃┌─╂─â”┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ â”╋┥ â–‹ ╲╱╲╱╳╳╳ ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ â•¿ │┃ â”╅╆┓ ╵ ╹ â”—â”·â”› └┸┘ â–Œ ╱╲╱╲╳╳╳ â• â•¡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┠╎ â”┅┅┓ ┋ ■╲╱╲╱╳╳╳ ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╠┇ ┋ â–Ž ║└─╥─┘║ │╚â•╤â•â•│ │╘â•╪â•╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╠┇ ┋ ■╚â•â•â•©â•â•╠└──┴──┘ ╰──┴──╯ ╰──┴──╯ â”—â”â”â”»â”â”â”› ▗▄▖▛▀▜ └╌╌┘ ╎ â”—â•â•â”› ┋ â–▂▃▄▅▆▇█ â–▀▘▙▄▟ tmux-tmux-f222026/tools/ansicode.txt000066400000000000000000001211001511153563100174310ustar00rootroot00000000000000Summary of ANSI standards for ASCII terminals Joe Smith, 18-May-84 Contents: 1. Overview and Definitions 2. General rules for interpreting an ESCape Sequence 3. General rules for interpreting a Control Sequence 4. C0 and C1 control codes in numeric order 5. Two and three-character ESCape Sequences in numeric order 6. Control Sequences in numeric order 7. VT100 emulation requirements The VT100 USER GUIDE and ANSI standard X3.64-1979 both list the ANSI ESCape sequences in alphabetic order by mnemonic, but do not have a have a cross reference in order by ASCII code. This paper lists the combination of all definitions from the three ANSI standards in numeric order. For a description of the advantages of using these standards, see the article "Toward Standardized Video Terminals" in the April-1984 issue of BYTE magazine. ANSI X3.4-1977 defines the 7-bit ASCII character set (C0 and G0). It was written in 1968, revised in 1977, and explains the decisions made in laying out the ASCII code. In particular, it explains why ANSI chose to make ASCII incompatible with EBCDIC in order to make it self-consistent. ANSI X3.41-1974 introduces the idea of an 8-bit ASCII character set (C1 and G1 in addition to the existing C0 and G0). It describes how to use the 8-bit features in a 7-bit environment. X3.41 defines the format of all ESCape sequences, but defines only the 3-character ones with a parameter character in the middle. These instruct the terminal how to interpret the C0, G0, C1, and G1 characters (such as by selecting different character-set ROMs). Note: NAPLPS does videotex graphics by redefining the C1 set and selecting alternate G0, G1, G2, and G3 sets. See the February 1983 issue of BYTE magazine for details. ANSI X3.64-1979 defines the remaining ESCape sequences. It defines all the C1 control characters, and specifies that certain two-character ESCape sequences in the 7-bit environment are to act exactly like the 8-bit C1 control set. X3.64 introduces the idea of a Control-Sequence, which starts with CSI character, has an indefinite length, and is terminated by an alphabetic character. The VT100 was one of the first terminals to implement this standard. Definitions: Control Character - A single character with an ASCII code with the range of 000 to 037 and 200 to 237 octal, 00 to 1F and 80 to 9F hex. Escape Sequence - A two or three character string staring with ESCape. (Four or more character strings are allowed but not defined.) Control Sequence - A string starting with CSI (233 octal, 9B hex) or with ESCape Left-Bracket, and terminated by an alphabetic character. Any number of parameter characters (digits 0 to 9, semicolon, and question mark) may appear within the Control Sequence. The terminating character may be preceded by an intermediate character (such as space). Character classifications: C0 Control 000-037 octal, 00-1F hex (G0 is 041-176 octal, 21-7E hex) SPACE 040+240 octal, 20+A0 hex Always and everywhere a blank space Intermediate 040-057 octal, 20-2F hex !"#$%&'()*+,-./ Parameters 060-077 octal, 30-3F hex 0123456789:;<=>? Uppercase 100-137 octal, 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ Lowercase 140-176 octal, 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~ Alphabetic 100-176 octal, 40-7E hex (all of upper and lower case) Delete 177 octal, 7F hex Always and everywhere ignored C1 Control 200-237 octal, 80-9F hex 32 additional control characters G1 Displayable 241-376 octal, A1-FE hex 94 additional displayable characters Special 240+377 octal, A0+FF hex Same as SPACE and DELETE Note that in this paper, the terms uppercase, lowercase, and alphabetics include more characters than just A to Z. ------------------------------------------------------------------------------ General rules for interpreting an ESCape Sequence: An ESCape Sequence starts with the ESC character (033 octal, 1B hex). The length of the ESCape Sequence depends on the character that immediately follows the ESCape. If the next character is C0 control: Interpret it first, then resume processing ESCape sequence. Example: CR, LF, XON, and XOFF work as normal within an ESCape sequence. Intermediate: Expect zero or more intermediates, a parameter terminates a private function, an alphabetic terminates a standard sequence. Example: ESC ( A defines standard character set, ESC ( 0 a DEC set. Parameter: End of a private 2-character escape sequence. Example: ESC = sets special keypad mode, ESC > clears it. Uppercase: Translate it into a C1 control character and act on it. Example: ESC D does indexes down, ESC M indexes up. (CSI is special) Lowercase: End of a standard 2-character escape sequence. Example: ESC c resets the terminal. Delete: Ignore it, and continue interpreting the ESCape sequence C1 and G1: Treat the same as their 7-bit counterparts Note that CSI is the two-character sequence ESCape left-bracket or the 8-bit C1 code of 233 octal, 9B hex. CSI introduces a Control Sequence, which continues until an alphabetic character is received. General rules for interpreting a Control Sequence: 1) It starts with CSI, the Control Sequence Introducer. 2) It contains any number of parameter characters (0123456789:;<=>?). 3) It terminates with an alphabetic character. 4) Intermediate characters (if any) immediately precede the terminator. If the first character after CSI is one of "<=>?" (074-077 octal, 3C-3F hex), then Control Sequence is to be interpreted according to private standards (such as setting and resetting modes not defined by ANSI). The terminal should expect any number of numeric parameters, separated by semicolons (073 octal, 3B hex). Only after the terminating alphabetic character is received should the terminal act on the Control Sequence. ============================================================================= C0 set of 7-bit control characters (from ANSI X3.4-1977). Oct Hex Name * (* marks function used in DEC VT series or LA series terminals) --- -- - --- - -------------------------------------------------------------- 000 00 @ NUL * Null filler, terminal should ignore this character 001 01 A SOH Start of Header 002 02 B STX Start of Text, implied end of header 003 03 C ETX End of Text, causes some terminal to respond with ACK or NAK 004 04 D EOT End of Transmission 005 05 E ENQ * Enquiry, causes terminal to send ANSWER-BACK ID 006 06 F ACK Acknowledge, usually sent by terminal in response to ETX 007 07 G BEL * Bell, triggers the bell, buzzer, or beeper on the terminal 010 08 H BS * Backspace, can be used to define overstruck characters 011 09 I HT * Horizontal Tabulation, move to next predetermined position 012 0A J LF * Linefeed, move to same position on next line (see also NL) 013 0B K VT * Vertical Tabulation, move to next predetermined line 014 0C L FF * Form Feed, move to next form or page 015 0D M CR * Carriage Return, move to first character of current line 016 0E N SO * Shift Out, switch to G1 (other half of character set) 017 0F O SI * Shift In, switch to G0 (normal half of character set) 020 10 P DLE Data Link Escape, interpret next control character specially 021 11 Q XON * (DC1) Terminal is allowed to resume transmitting 022 12 R DC2 Device Control 2, causes ASR-33 to activate paper-tape reader 023 13 S XOFF* (DC2) Terminal must pause and refrain from transmitting 024 14 T DC4 Device Control 4, causes ASR-33 to deactivate paper-tape reader 025 15 U NAK Negative Acknowledge, used sometimes with ETX and ACK 026 16 V SYN Synchronous Idle, used to maintain timing in Sync communication 027 17 W ETB End of Transmission block 030 18 X CAN * Cancel (makes VT100 abort current escape sequence if any) 031 19 Y EM End of Medium 032 1A Z SUB * Substitute (VT100 uses this to display parity errors) 033 1B [ ESC * Prefix to an ESCape sequence 034 1C \ FS File Separator 035 1D ] GS Group Separator 036 1E ^ RS * Record Separator (sent by VT132 in block-transfer mode) 037 1F _ US Unit Separator 040 20 SP * Space (should never be defined to be otherwise) 177 7F DEL * Delete, should be ignored by terminal ============================================================================== C1 set of 8-bit control characters (from ANSI X3.64-1979) Oct Hex Name * (* marks function used in DEC VT series or LA series terminals) --- -- - --- - -------------------------------------------------------------- 200 80 @ Reserved for future standardization 201 81 A Reserved 202 82 B Reserved 203 83 C Reserved 204 84 D IND * Index, moves down one line same column regardless of NL 205 85 E NEL * NEw Line, moves done one line and to first column (CR+LF) 206 86 F SSA Start of Selected Area to be sent to auxiliary output device 207 87 G ESA End of Selected Area to be sent to auxiliary output device 210 88 H HTS * Horizontal Tabulation Set at current position 211 89 I HTJ Hor Tab Justify, moves string to next tab position 212 8A J VTS Vertical Tabulation Set at current line 213 8B K PLD Partial Line Down (subscript) 214 8C L PLU Partial Line Up (superscript) 215 8D M RI * Reverse Index, go up one line, reverse scroll if necessary 216 8E N SS2 * Single Shift to G2 217 8F O SS3 * Single Shift to G3 (VT100 uses this for sending PF keys) 220 90 P DCS * Device Control String, terminated by ST (VT125 enters graphics) 221 91 Q PU1 Private Use 1 222 92 R PU2 Private Use 2 223 93 S STS Set Transmit State 224 94 T CCH Cancel CHaracter, ignore previous character 225 95 U MW Message Waiting, turns on an indicator on the terminal 226 96 V SPA Start of Protected Area 227 97 W EPA End of Protected Area 230 98 X Reserved for for future standard 231 99 Y Reserved 232 9A Z * Reserved, but causes DEC terminals to respond with DA codes 233 9B [ CSI * Control Sequence Introducer (described in a separate table) 234 9C \ ST * String Terminator (VT125 exits graphics) 235 9D ] OSC Operating System Command (reprograms intelligent terminal) 236 9E ^ PM Privacy Message (password verification), terminated by ST 237 9F _ APC Application Program Command (to word processor), term by ST ============================================================================== Character set selection sequences (from ANSI X3.41-1974) All are 3 characters long (including the ESCape). Alphabetic characters as 3rd character are defined by ANSI, parameter characters as 3rd character may be interpreted differently by each terminal manufacturer. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- -- - ------------------------------------------------------------------ 040 20 ANNOUNCER - Determines whether to use 7-bit or 8-bit ASCII A G0 only will be used. Ignore SI, SO, and G1. B G0 and G1 used internally. SI and SO affect G0, G1 is ignored. C G0 and G1 in an 8-bit only environment. SI and SO are ignored. D G0 and G1 are used, SI and SO affect G0. E F * 7-bit transmission, VT240/PRO350 sends CSI as two characters ESC [ G * 8-bit transmission, VT240/PRO350 sends CSI as single 8-bit character 041 21 ! Select C0 control set (choice of 63 standard, 16 private) 042 22 " Select C1 control set (choice of 63 standard, 16 private) 043 23 # Translate next character to a special single character #3 * DECDHL1 - Double height line, top half #4 * DECDHL2 - Double height line, bottom half #5 * DECSWL - Single width line #6 * DECDWL - Double width line #7 * DECHCP - Make a hardcopy of the graphics screen (GIGI,VT125,VT241) #8 * DECALN - Alignment display, fill screen with "E" to adjust focus 044 24 $ MULTIBYTE CHARACTERS - Displayable characters require 2-bytes each 045 25 % SPECIAL INTERPRETATION - Such as 9-bit data 046 26 & Reserved for future standardization 047 27 ' Reserved for future standardization 050 28 ( * SCS - Select G0 character set (choice of 63 standard, 16 private) (0 * DEC VT100 line drawing set (affects lowercase characters) (1 * DEC Alternate character ROM set (RAM set on GIGI and VT220) (2 * DEC Alternate character ROM set with line drawing (5 * DEC Finnish on LA100 (6 * DEC Norwegian/Danish on LA100 (7 * DEC Swedish on LA100 (9 * DEC French Canadian (< * DEC supplemental graphics (everything not in USASCII) (A * UKASCII (British pound sign) (B * USASCII (American pound sign) (C * ISO Finnish on LA120 (E * ISO Norwegian/Danish on LA120 (H * ISO Swedish on LA120 (K * ISO German on LA100,LA120 (R * ISO French on LA100,LA120 (Y * ISO Italian on LA100 (Z * ISO Spanish on LA100 051 29 ) * SCS - Select G1 character set (choice of 63 standard, 16 private) * (same character sets as listed under G0) 052 2A * * SCS - Select G2 character set * (same character sets as listed under G0) 053 2B + * SCS - Select G3 character set * (same character sets as listed under G0) 054 2C , SCS - Select G0 character set (additional 63+16 sets) 055 2D - SCS - Select G1 character set (additional 63+16 sets) 056 2E . SCS - Select G2 character set 057 2F / SCS - Select G3 character set ============================================================================== Private two-character escape sequences (allowed by ANSI X3.41-1974) These can be defined differently by each terminal manufacturer. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- - - ------------------------------------------------------------------ 060 30 0 061 31 1 DECGON graphics on for VT105, DECHTS horiz tab set for LA34/LA120 062 32 2 DECGOFF graphics off VT105, DECCAHT clear all horz tabs LA34/LA120 063 33 3 DECVTS - set vertical tab for LA34/LA120 064 34 4 DECCAVT - clear all vertical tabs for LA34/LA120 065 35 5 * DECXMT - Host requests that VT132 transmit as if ENTER were pressed 066 36 6 067 37 7 * DECSC - Save cursor position and character attributes 070 38 8 * DECRC - Restore cursor and attributes to previously saved position 071 39 9 072 3A : 073 3B ; 074 3C < * DECANSI - Switch from VT52 mode to VT100 mode 075 3D = * DECKPAM - Set keypad to applications mode (ESCape instead of digits) 076 3E > * DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq) 077 3F ? DCS Device Control Strings used by DEC terminals (ends with ST) Pp = Start ReGIS graphics (VT125, GIGI, VT240, PRO350) Pq = Start SIXEL graphics (screen dump to LA34, LA100, screen load to VT125) Pr = SET-UP data for GIGI, $PrVC0$\ disables both visible cursors. Ps = Reprogram keys on the GIGI, $P0sDIR$\ makes keypad 0 send "DIR" 0-9=digits on keypad, 10=ENTER, 11=minus, 12=comma, 13=period, 14-17=PF1-PF4, 18-21=cursor keys. Enabled by $[?23h (PK1). Pt = Start VT105 graphics on a VT125 ============================================================================== Standard two-character escape sequences (defined by ANSI X3.64-1979) 100 40 @ See description of C1 control characters An ESCape followed by one of these uppercase characters is translated to an 8-bit C1 control character before being interpreted. 220 90 P DCS - Device Control String, terminated by ST - see table above. 133 5B [ CSI - Control Sequence Introducer - see table below. 137 5F _ See description of C1 control characters ============================================================================== Independent control functions (from Appendix E of X3.64-1977). These four controls have the same meaning regardless of the current definition of the C0 and C1 control sets. Each control is a two-character ESCape sequence, the 2nd character is lowercase. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- - - -------------------------------------------------------------------- 140 60 ` DMI - Disable Manual Input 141 61 a INT - INTerrupt the terminal and do special action 142 62 b EMI - Enable Manual Input 143 63 c * RIS - Reset to Initial State (VT100 does a power-on reset) ... The remaining lowercase characters are reserved by ANSI. 153 6B k NAPLPS lock-shift G1 to GR 154 6C l NAPLPS lock-shift G2 to GR 155 6D m NAPLPS lock-shift G3 to GR 156 6E n * LS2 - Shift G2 to GL (extension of SI) VT240,NAPLPS 157 6F o * LS3 - Shift G3 to GL (extension of SO) VT240,NAPLPS ... The remaining lowercase characters are reserved by ANSI. 174 7C | * LS3R - VT240 lock-shift G3 to GR 175 7D } * LS2R - VT240 lock-shift G2 to GR 176 7E ~ * LS1R - VT240 lock-shift G1 to GR ============================================================================== Control Sequences (defined by ANSI X3.64-1979) Control Sequences are started by either ESC [ or CSI and are terminated by an "alphabetic" character (100 to 176 octal, 40 to 7E hex). Intermediate characters are space through slash (40 to 57 octal, 20 to 2F hex) and parameter characters are zero through question mark (60 to 77 octal, 30 to 3F hex, including digits and semicolon). Parameters consist of zero or more decimal numbers separated by semicolons. Leading zeros are optional, leading blanks are not allowed. If no digits precede the final character, the default parameter is used. Many functions treat a parameter of 0 as if it were 1. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- - - -------------------------------------------------------------------- 100 40 @ ICH - Insert CHaracter [10@ = Make room for 10 characters at current position 101 41 A * CUU - CUrsor Up * [A = Move up one line, stop at top of screen, [9A = move up 9 102 42 B * CUD - CUrsor Down * [B = Move down one line, stop at bottom of screen 103 43 C * CUF - CUrsor Forward * [C = Move forward one position, stop at right edge of screen 104 44 D * CUB - CUrsor Backward * [D = Same as BackSpace, stop at left edge of screen 105 45 E CNL - Cursor to Next Line [5E = Move to first position of 5th line down 106 46 F CPL - Cursor to Previous Line [5F = Move to first position of 5th line previous 107 47 G CHA - Cursor Horizontal position Absolute [40G = Move to column 40 of current line 110 48 H * CUP - CUrsor Position * [H = Home, [24;80H = Row 24, Column 80 111 49 I CHT - Cursor Horizontal Tabulation [I = Same as HT (Control-I), [3I = Go forward 3 tabs 112 4A J * ED - Erase in Display (cursor does not move) * [J = [0J = Erase from current position to end (inclusive) * [1J = Erase from beginning to current position (inclusive) * [2J = Erase entire display * [?0J = Selective erase in display ([?1J, [?2J similar) 113 4B K * EL - Erase in Line (cursor does not move) * [K = [0K = Erase from current position to end (inclusive) * [1K = Erase from beginning to current position * [2K = Erase entire current line * [?0K = Selective erase to end of line ([?1K, [?2K similar) 114 4C L * IL - Insert Line, current line moves down (VT102 series) [3L = Insert 3 lines if currently in scrolling region 115 4D M * DL - Delete Line, lines below current move up (VT102 series) [2M = Delete 2 lines if currently in scrolling region 116 4E N EF - Erase in Field (as bounded by protected fields) [0N, [1N, [2N act like [L but within currend field 117 4F O EA - Erase in qualified Area (defined by DAQ) [0O, [1O, [2O act like [J but within current area 120 50 P * DCH - Delete Character, from current position to end of field [4P = Delete 4 characters, VT102 series 121 51 Q SEM - Set Editing extent Mode (limits ICH and DCH) [0Q = [Q = Insert/delete character affects rest of display [1Q = ICH/DCH affect the current line only [2Q = ICH/DCH affect current field (between tab stops) only [3Q = ICH/DCH affect qualified area (between protected fields) 122 52 R * CPR - Cursor Position Report (from terminal to host) * [24;80R = Cursor is positioned at line 24 column 80 123 53 S SU - Scroll up, entire display is moved up, new lines at bottom [3S = Move everything up 3 lines, bring in 3 new lines 124 54 T SD - Scroll down, new lines inserted at top of screen [4T = Scroll down 4, bring previous lines back into view 125 55 U NP - Next Page (if terminal has more than 1 page of memory) [2U = Scroll forward 2 pages 126 56 V PP - Previous Page (if terminal remembers lines scrolled off top) [1V = Scroll backward 1 page 127 57 W CTC - Cursor Tabulation Control [0W = Set horizontal tab for current line at current position [1W = Set vertical tab stop for current line of current page [2W = Clear horiz tab stop at current position of current line [3W = Clear vert tab stop at current line of current page [4W = Clear all horiz tab stops on current line only [5W = Clear all horiz tab stops for the entire terminal [6W = Clear all vert tabs stops for the entire terminal 130 58 X ECH - Erase CHaracter [4X = Change next 4 characters to "erased" state 131 59 Y CVT - Cursor Vertical Tab [2Y = Move forward to 2nd following vertical tab stop 132 5A Z CBT - Cursor Back Tab [3Z = Move backwards to 3rd previous horizontal tab stop 133 5B [ Reserved for future standardization left bracket 134 5C \ Reserved reverse slant 135 5D ] Reserved right bracket 136 5E ^ Reserved circumflex 137 5F _ Reserved underscore 140 60 ` * HPA - Horizontal Position Absolute (depends on PUM) [720` = Move to 720 decipoints (1 inch) from left margin * [80` = Move to column 80 on LA120 141 61 a * HPR - Horizontal Position Relative (depends on PUM) [360a = Move 360 decipoints (1/2 inch) from current position * [40a = Move 40 columns to right of current position on LA120 142 62 b REP - REPeat previous displayable character [80b = Repeat character 80 times 143 63 c * DA - Device Attributes * [c = Terminal will identify itself * [?1;2c = Terminal is saying it is a VT100 with AVO * [>0c = Secondary DA request (distinguishes VT240 from VT220) 144 64 d * VPA - Vertical Position Absolute (depends on PUM) [90d = Move to 90 decipoints (1/8 inch) from top margin * [10d = Move to line 10 if before that else line 10 next page 145 65 e * VPR - Vertical Position Relative (depends on PUM) [720e = Move 720 decipoints (1 inch) down from current position * [6e = Advance 6 lines forward on LA120 146 66 f * HVP - Horizontal and Vertical Position (depends on PUM) [720,1440f = Move to 1 inch down and 2 inches over (decipoints) * [24;80f = Move to row 24 column 80 if PUM is set to character 147 67 g * TBC - Tabulation Clear * [0g = Clear horizontal tab stop at current position * [1g = Clear vertical tab stop at current line (LA120) * [2g = Clear all horizontal tab stops on current line only LA120 * [3g = Clear all horizontal tab stops in the terminal 150 68 h * SM - Set Mode (. means permanently set on VT100) [0h = Error, this command is ignored * [1h = GATM - Guarded Area Transmit Mode, send all (VT132) [2h = KAM - Keyboard Action Mode, disable keyboard input [3h = CRM - Control Representation Mode, show all control chars * [4h = IRM - Insertion/Replacement Mode, set insert mode (VT102) [5h = SRTM - Status Report Transfer Mode, report after DCS * [6h = ERM - ERasure Mode, erase protected and unprotected [7h = VEM - Vertical Editing Mode, IL/DL affect previous lines [8h, [9h are reserved [10h = HEM - Horizontal Editing mode, ICH/DCH/IRM go backwards [11h = PUM - Positioning Unit Mode, use decipoints for HVP/etc . [12h = SRM - Send Receive Mode, transmit without local echo [13h = FEAM - Format Effector Action Mode, FE's are stored [14h = FETM - Format Effector Transfer Mode, send only if stored [15h = MATM - Multiple Area Transfer Mode, send all areas * [16h = TTM - Transmit Termination Mode, send scrolling region [17h = SATM - Send Area Transmit Mode, send entire buffer [18h = TSM - Tabulation Stop Mode, lines are independent [19h = EBM - Editing Boundary Mode, all of memory affected * [20h = LNM - Linefeed Newline Mode, LF interpreted as CR LF * [?1h = DECCKM - Cursor Keys Mode, send ESC O A for cursor up * [?2h = DECANM - ANSI Mode, use ESC < to switch VT52 to ANSI * [?3h = DECCOLM - COLumn mode, 132 characters per line * [?4h = DECSCLM - SCrolL Mode, smooth scrolling * [?5h = DECSCNM - SCreeN Mode, black on white background * [?6h = DECOM - Origin Mode, line 1 is relative to scroll region * [?7h = DECAWM - AutoWrap Mode, start newline after column 80 * [?8h = DECARM - Auto Repeat Mode, key will autorepeat * [?9h = DECINLM - INterLace Mode, interlaced for taking photos * [?10h = DECEDM - EDit Mode, VT132 is in EDIT mode * [?11h = DECLTM - Line Transmit Mode, ignore TTM, send line [?12h = ? * [?13h = DECSCFDM - Space Compression/Field Delimiting on, * [?14h = DECTEM - Transmit Execution Mode, transmit on ENTER [?15h = ? * [?16h = DECEKEM - Edit Key Execution Mode, EDIT key is local [?17h = ? * [?18h = DECPFF - Print FormFeed mode, send FF after printscreen * [?19h = DECPEXT - Print Extent mode, print entire screen * [?20h = OV1 - Overstrike, overlay characters on GIGI * [?21h = BA1 - Local BASIC, GIGI to keyboard and screen * [?22h = BA2 - Host BASIC, GIGI to host computer * [?23h = PK1 - GIGI numeric keypad sends reprogrammable sequences * [?24h = AH1 - Autohardcopy before erasing or rolling GIGI screen * [?29h = - Use only the proper pitch for the LA100 font * [?38h = DECTEK - TEKtronix mode graphics 151 69 i * MC - Media Copy (printer port on VT102) * [0i = Send contents of text screen to printer [1i = Fill screen from auxiliary input (printer's keyboard) [2i = Send screen to secondary output device [3i = Fill screen from secondary input device * [4i = Turn on copying received data to primary output (VT125) * [4i = Received data goes to VT102 screen, not to its printer * [5i = Turn off copying received data to primary output (VT125) * [5i = Received data goes to VT102's printer, not its screen * [6i = Turn off copying received data to secondary output (VT125) * [7i = Turn on copying received data to secondary output (VT125) * [?0i = Graphics screen dump goes to graphics printer VT125,VT240 * [?1i = Print cursor line, terminated by CR LF * [?2i = Graphics screen dump goes to host computer VT125,VT240 * [?4i = Disable auto print * [?5i = Auto print, send a line at a time when linefeed received 152 6A j Reserved for future standardization 153 6B k Reserved for future standardization 154 6C l * RM - Reset Mode (. means permanently reset on VT100) * [1l = GATM - Transmit only unprotected characters (VT132) . [2l = KAM - Enable input from keyboard . [3l = CRM - Control characters are not displayable characters * [4l = IRM - Reset to replacement mode (VT102) . [5l = SRTM - Report only on command (DSR) * [6l = ERM - Erase only unprotected fields . [7l = VEM - IL/DL affect lines after current line [8l, [9l are reserved . [10l = HEM - ICH and IRM shove characters forward, DCH pulls . [11l = PUM - Use character positions for HPA/HPR/VPA/VPR/HVP [12l = SRM - Local echo - input from keyboard sent to screen . [13l = FEAM - HPA/VPA/SGR/etc are acted upon when received . [14l = FETM - Format Effectors are sent to the printer [15l = MATM - Send only current area if SATM is reset * [16l = TTM - Transmit partial page, up to cursor position [17l = SATM - Transmit areas bounded by SSA/ESA/DAQ . [18l = TSM - Setting a tab stop on one line affects all lines . [19l = EBM - Insert does not overflow to next page * [20l = LNM - Linefeed does not change horizontal position * [?1l = DECCKM - Cursor keys send ANSI cursor position commands * [?2l = DECANM - Use VT52 emulation instead of ANSI mode * [?3l = DECCOLM - 80 characters per line (erases screen) * [?4l = DECSCLM - Jump scrolling * [?5l = DECSCNM - Normal screen (white on black background) * [?6l = DECOM - Line numbers are independent of scrolling region * [?7l = DECAWM - Cursor remains at end of line after column 80 * [?8l = DECARM - Keys do not repeat when held down * [?9l = DECINLM - Display is not interlaced to avoid flicker * [?10l = DECEDM - VT132 transmits all key presses * [?11l = DECLTM - Send page or partial page depending on TTM [?12l = ? * [?13l = DECSCFDM - Don't suppress trailing spaces on transmit * [?14l = DECTEM - ENTER sends ESC S (STS) a request to send [?15l = ? * [?16l = DECEKEM - EDIT key transmits either $[10h or $[10l [?17l = ? * [?18l = DECPFF - Don't send a formfeed after printing screen * [?19l = DECPEXT - Print only the lines within the scroll region * [?20l = OV0 - Space is destructive, replace not overstrike, GIGI * [?21l = BA0 - No BASIC, GIGI is On-Line or Local * [?22l = BA0 - No BASIC, GIGI is On-Line or Local * [?23l = PK0 - Ignore reprogramming on GIGI keypad and cursors * [?24l = AH0 - No auto-hardcopy when GIGI screen erased * [?29l = Allow all character pitches on the LA100 * [?38l = DECTEK - Ignore TEKtronix graphics commands 155 6D m * SGR - Set Graphics Rendition (affects character attributes) * [0m = Clear all special attributes * [1m = Bold or increased intensity * [2m = Dim or secondary color on GIGI (superscript on XXXXXX) [3m = Italic (subscript on XXXXXX) * [4m = Underscore, [0;4m = Clear, then set underline only * [5m = Slow blink [6m = Fast blink (overscore on XXXXXX) * [7m = Negative image, [0;1;7m = Bold + Inverse [8m = Concealed (do not display character echoed locally) [9m = Reserved for future standardization * [10m = Select primary font (LA100) * [11m - [19m = Selete alternate font (LA100 has 11 thru 14) [20m = FRAKTUR (whatever that means) * [22m = Cancel bold or dim attribute only (VT220) * [24m = Cancel underline attribute only (VT220) * [25m = Cancel fast or slow blink attribute only (VT220) * [27m = Cancel negative image attribute only (VT220) * [30m = Write with black, [40m = Set background to black (GIGI) * [31m = Write with red, [41m = Set background to red * [32m = Write with green, [42m = Set background to green * [33m = Write with yellow, [43m = Set background to yellow * [34m = Write with blue, [44m = Set background to blue * [35m = Write with magenta, [45m = Set background to magenta * [36m = Write with cyan, [46m = Set background to cyan * [37m = Write with white, [47m = Set background to white [38m, [39m, [48m, [49m are reserved 156 6E n * DSR - Device Status Report * [0n = Terminal is ready, no malfunctions detected [1n = Terminal is busy, retry later [2n = Terminal is busy, it will send DSR when ready * [3n = Malfunction, please try again [4n = Malfunction, terminal will send DSR when ready * [5n = Command to terminal to report its status * [6n = Command to terminal requesting cursor position (CPR) * [?15n = Command to terminal requesting printer status, returns [?10n = OK, [?11n = not OK, [?13n = no printer. * [?25n = "Are User Defined Keys Locked?" (VT220) 157 6F o DAQ - Define Area Qualification starting at current position [0o = Accept all input, transmit on request [1o = Protected and guarded, accept no input, do not transmit [2o = Accept any printing character in this field [3o = Numeric only field [4o = Alphabetic (A-Z and a-z) only [5o = Right justify in area [3;6o = Zero fill in area [7o = Set horizontal tab stop, this is the start of the field [8o = Protected and unguarded, accept no input, do transmit [9o = Space fill in area ============================================================================== Private Control Sequences (allowed by ANSI X3.41-1974). These take parameter strings and terminate with the last half of lowercase. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- - - -------------------------------------------------------------------- 160 70 p * DECSTR - Soft Terminal Reset [!p = Soft Terminal Reset 161 71 q * DECLL - Load LEDs [0q = Turn off all, [?1;4q turns on L1 and L4, etc [154;155;157q = VT100 goes bonkers [2;23!q = Partial screen dump from GIGI to graphics printer [0"q = DECSCA Select Character Attributes off [1"q = DECSCA - designate set as non-erasable [2"q = DECSCA - designate set as erasable 162 72 r * DECSTBM - Set top and bottom margins (scroll region on VT100) [4;20r = Set top margin at line 4 and bottom at line 20 163 73 s * DECSTRM - Set left and right margins on LA100,LA120 [5;130s = Set left margin at column 5 and right at column 130 164 74 t * DECSLPP - Set physical lines per page [66t = Paper has 66 lines (11 inches at 6 per inch) 165 75 u * DECSHTS - Set many horizontal tab stops at once on LA100 [9;17;25;33;41;49;57;65;73;81u = Set standard tab stops 166 76 v * DECSVTS - Set many vertical tab stops at once on LA100 [1;16;31;45v = Set vert tabs every 15 lines 167 77 w * DECSHORP - Set horizontal pitch on LAxxx printers [1w = 10 characters per inch, [2w = 12 characters per inch [0w=10, [3w=13.2, [4w=16.5, [5w=5, [6w=6, [7w=6.6, [8w=8.25 170 78 x * DECREQTPARM - Request terminal parameters [3;5;2;64;64;1;0x = Report, 7 bit Even, 1200 baud, 1200 baud 171 79 y * DECTST - Invoke confidence test [2;1y = Power-up test on VT100 series (and VT100 part of VT125) [3;1y = Power-up test on GIGI (VK100) [4;1y = Power-up test on graphics portion of VT125 172 7A z * DECVERP - Set vertical pitch on LA100 [1z = 6 lines per inch, [2z = 8 lines per inch [0z=6, [3z=12, [4z=3, [5z=3, [6z=4 173 7B { Private 174 7C | * DECTTC - Transmit Termination Character [0| = No extra characters, [1| = terminate with FF 175 7D } * DECPRO - Define protected field on VT132 [0} = No protection, [1;4;5;7} = Any attribute is protected [254} = Characters with no attributes are protected 176 7E ~ * DECKEYS - Sent by special function keys [1~=FIND, [2~=INSERT, [3~=REMOVE, [4~=SELECT, [5~=PREV, [6~=NEXT [17~=F6...[34~=F20 ([23~=ESC,[24~=BS,[25~=LF,[28~=HELP,[29~=DO) 177 7F DELETE is always ignored ============================================================================== Control Sequences with intermediate characters (from ANSI X3.64-1979). Note that there is a SPACE character before the terminating alphabetic. Oct Hex * (* marks function used in DEC VT series or LA series terminals) --- -- - - -------------------------------------------------------------------- 100 40 @ SL - Scroll Left [4 @ = Move everything over 4 columns, 4 new columns at right 101 41 A SR - Scroll Right [2 A = Move everything over 2 columns, 2 new columns at left 102 42 B GSM - Graphic Size Modification [110;50 B = Make 110% high, 50% wide 103 43 C GSS - Graphic Size Selection [120 C = Make characters 120 decipoints (1/6 inch) high 104 44 D FNT - FoNT selection (used by SGR, [10m thru [19m) [0;23 D = Make primary font be registered font #23 105 45 E TSS - Thin Space Specification [36 E = Define a thin space to be 36 decipoints (1/20 inch) 106 46 F JFY - JustiFY, done by the terminal/printer [0 E = No justification [1 E = Fill, bringing words up from next line if necessary [2 E = Interword spacing, adjust spaces between words [3 E = Letter spacing, adjust width of each letter [4 E = Use hyphenation [5 E = Flush left margin [6 E = Center following text between margins (until [0 E) [7 E = Flush right margin [8 E = Italian form (underscore instead of hyphen) 107 47 G SPI - SPacing Increment (in decipoints) [120;72 G = 6 per inch vertical, 10 per inch horizontal 110 48 H QUAD- Do quadding on current line of text (typography) [0 H = Flush left, [1 H = Flush left and fill with leader [2 H = Center, [3 H = Center and fill with leader [4 H = Flush right, [5 H = Flush right and fill with leader 111 49 I Reserved for future standardization 157 67 o Reserved for future standardization 160 70 p Private use ... May be defined by the printer manufacturer 176 7E ~ Private use 177 7F DELETE is always ignored ============================================================================== Minimum requirements for VT100 emulation: 1) To act as a passive display, implement the 4 cursor commands, the 2 erase commands, direct cursor addressing, and at least inverse characters. The software should be capable of handling strings with 16 numeric parameters with values in the range of 0 to 255. [A Move cursor up one row, stop if a top of screen [B Move cursor down one row, stop if at bottom of screen [C Move cursor forward one column, stop if at right edge of screen [D Move cursor backward one column, stop if at left edge of screen [H Home to row 1 column 1 (also [1;1H) [J Clear from current position to bottom of screen [K Clear from current position to end of line [24;80H Position to line 24 column 80 (any line 1 to 24, any column 1 to 132) [0m Clear attributes to normal characters [7m Add the inverse video attribute to succeeding characters [0;7m Set character attributes to inverse video only 2) To enter data in VT100 mode, implement the 4 cursor keys and the 4 PF keys. It must be possible to enter ESC, TAB, BS, DEL, and LF from the keyboard. [A Sent by the up-cursor key (alternately ESC O A) [B Sent by the down-cursor key (alternately ESC O B) [C Sent by the right-cursor key (alternately ESC O C) [D Sent by the left-cursor key (alternately ESC O D) OP PF1 key sends ESC O P OQ PF2 key sends ESC O Q OR PF3 key sends ESC O R OS PF3 key sends ESC O S [c Request for the terminal to identify itself [?1;0c VT100 with memory for 24 by 80, inverse video character attribute [?1;2c VT100 capable of 132 column mode, with bold+blink+underline+inverse 3) When doing full-screen editing on a VT100, implement directed erase, the numeric keypad in applications mode, and the limited scrolling region. The latter is needed to do insert/delete line functions without rewriting the screen. [0J Erase from current position to bottom of screen inclusive [1J Erase from top of screen to current position inclusive [2J Erase entire screen (without moving the cursor) [0K Erase from current position to end of line inclusive [1K Erase from beginning of line to current position inclusive [2K Erase entire line (without moving cursor) [12;24r Set scrolling region to lines 12 thru 24. If a linefeed or an INDex is received while on line 24, the former line 12 is deleted and rows 13-24 move up. If a RI (reverse Index) is received while on line 12, a blank line is inserted there as rows 12-13 move down. All VT100 compatible terminals (except GIGI) have this feature. ESC = Set numeric keypad to applications mode ESC > Set numeric keypad to numbers mode OA Up-cursor key sends ESC O A after ESC = ESC [ ? 1 h OB Down-cursor key sends ESC O B " " " OC Right-cursor key sends ESC O B " " " OB Left-cursor key sends ESC O B " " " OM ENTER key sends ESC O M after ESC = Ol COMMA on keypad sends ESC O l " " (that's lowercase L) Om MINUS on keypad sends ESC O m " " Op ZERO on keypad sends ESC O p " " Oq ONE on keypad sends ESC O q " " Or TWO on keypad sends ESC O r " " Os THREE on keypad sends ESC O s " " Ot FOUR on keypad sends ESC O t " " Ou FIVE on keypad sends ESC O u " " Ov SIX on keypad sends ESC O v " " Ow SEVEN on keypad sends ESC O w " " Ox EIGHT on keypad sends ESC O x " " Oy NINE on keypad sends ESC O y " " 4) If the hardware is capable of double width/double height: #3 Top half of a double-width double-height line #4 Bottom half of a double-width double-height line #5 Make line single-width (lines are set this way when cleared by ESC [ J) #6 Make line double-width normal height (40 or 66 characters) 5) If the terminal emulator is capable of insert/delete characters, insert/delete lines, insert/replace mode, and can do a full-screen dump to the printer (in text mode), then it should identify itself as a VT102 [c Request for the terminal to identify itself [?6c VT102 (printer port, 132 column mode, and ins/del standard) [1@ Insert a blank character position (shift line to the right) [1P Delete a character position (shift line to the left) [1L Insert blank line at current row (shift screen down) [1M Delete the current line (shift screen up) [4h Set insert mode, new characters shove existing ones to the right [4l Reset insert mode, new characters replace existing ones [0i Print screen (all 24 lines) to the printer [4i All received data goes to the printer (nothing to the screen) [5i All received data goes to the screen (nothing to the printer) [End of ANSICODE.TXT] tmux-tmux-f222026/tools/cmp-cvs.sh000066400000000000000000000003421511153563100170130ustar00rootroot00000000000000#!/bin/ksh rm diff.out touch diff.out for i in *.[ch]; do diff -u -I'\$OpenBSD' $i /usr/src/usr.bin/tmux/$i >diff.tmp set -- `wc -l diff.tmp` [ $1 -eq 8 ] && continue echo $i cat diff.tmp >>diff.out done tmux-tmux-f222026/tools/image.sixel000066400000000000000000007723031511153563100172540ustar00rootroot00000000000000P0;0;0q"1;1;440;323#0;2;11;12;11#1;2;14;14;11#2;2;16;17;15#3;2;18;19;17#4;2;20;20;17#5;2;22;22;18#6;2;18;20;19#7;2;21;22;20#8;2;23;24;20#9;2;23;24;23#10;2;25;24;18#11;2;25;24;20#12;2;25;25;18#13;2;24;25;22#14;2;26;26;21#15;2;26;27;23#16;2;28;28;23#17;2;29;30;23#18;2;29;29;24#19;2;32;31;24#20;2;33;32;24#21;2;38;35;25#22;2;22;24;26#23;2;24;26;28#24;2;24;28;33#25;2;24;31;41#26;2;26;27;26#27;2;27;29;26#28;2;29;30;27#29;2;27;29;29#30;2;30;30;29#31;2;32;31;26#32;2;31;32;27#33;2;31;32;29#34;2;33;33;27#35;2;33;33;29#36;2;35;35;28#37;2;36;36;30#38;2;26;30;33#39;2;28;31;33#40;2;29;33;34#41;2;33;33;33#42;2;33;35;33#43;2;36;36;33#44;2;33;35;36#45;2;36;36;36#46;2;38;37;30#47;2;38;37;33#48;2;37;38;30#49;2;37;38;35#50;2;39;38;30#51;2;41;40;30#52;2;39;39;33#53;2;42;42;33#54;2;39;39;36#55;2;41;40;35#56;2;42;42;36#57;2;44;43;36#58;2;43;44;36#59;2;45;44;36#60;2;28;34;39#61;2;30;36;40#62;2;30;36;41#63;2;33;37;40#64;2;27;36;45#65;2;36;38;39#66;2;33;38;42#67;2;35;39;42#68;2;33;40;45#69;2;34;40;45#70;2;36;42;46#71;2;36;42;47#72;2;39;40;39#73;2;40;41;40#74;2;42;42;39#75;2;39;40;41#76;2;40;42;42#77;2;42;43;42#78;2;44;44;39#79;2;43;44;40#80;2;43;44;42#81;2;45;45;39#82;2;48;47;40#83;2;45;45;42#84;2;48;48;42#85;2;38;43;46#86;2;38;43;47#87;2;42;45;46#88;2;39;45;49#89;2;42;46;49#90;2;45;46;45#91;2;48;49;45#92;2;46;47;47#93;2;49;49;48#94;2;51;49;42#95;2;51;50;45#96;2;50;50;43#97;2;49;50;48#98;2;52;51;45#99;2;54;53;45#100;2;51;51;49#101;2;54;54;48#102;2;24;39;56#103;2;24;40;57#104;2;26;40;56#105;2;26;40;57#106;2;27;42;58#107;2;29;42;58#108;2;29;43;60#109;2;33;44;58#110;2;30;44;59#111;2;30;45;61#112;2;32;44;59#113;2;33;45;61#114;2;35;46;61#115;2;34;47;62#116;2;36;47;62#117;2;40;46;52#118;2;42;47;52#119;2;42;48;55#120;2;45;49;51#121;2;45;49;54#122;2;41;49;59#123;2;44;50;57#124;2;31;46;63#125;2;33;47;63#126;2;33;47;63#127;2;36;49;64#128;2;38;49;64#129;2;39;50;66#130;2;49;51;53#131;2;45;51;55#132;2;48;52;55#133;2;43;51;59#134;2;47;52;57#135;2;37;51;65#136;2;39;51;65#137;2;40;52;66#138;2;41;53;66#139;2;41;53;67#140;2;42;54;68#141;2;46;55;64#142;2;44;55;68#143;2;46;57;68#144;2;40;53;69#145;2;43;55;69#146;2;44;56;70#147;2;44;57;71#148;2;45;57;70#149;2;48;58;71#150;2;49;60;71#151;2;47;59;73#152;2;49;61;73#153;2;49;63;74#154;2;52;52;51#155;2;55;55;52#156;2;53;55;54#157;2;56;56;53#158;2;59;59;54#159;2;58;58;55#160;2;52;55;58#161;2;55;58;59#162;2;58;59;58#163;2;61;61;58#164;2;61;62;61#165;2;63;62;57#166;2;62;63;60#167;2;65;64;59#168;2;64;64;61#169;2;67;67;61#170;2;69;68;61#171;2;56;61;64#172;2;51;61;71#173;2;53;62;71#174;2;51;62;73#175;2;56;63;68#176;2;61;64;65#177;2;52;64;74#178;2;55;65;73#179;2;55;64;74#180;2;58;65;73#181;2;58;67;74#182;2;60;68;74#183;2;59;69;75#184;2;62;69;74#185;2;65;66;65#186;2;67;67;64#187;2;69;68;65#188;2;68;69;67#189;2;71;70;64#190;2;73;72;65#191;2;70;71;67#192;2;74;73;67#193;2;64;71;74#194;2;67;71;73#195;2;71;71;70#196;2;71;73;71#197;2;73;74;71#198;2;74;75;73#199;2;76;73;65#200;2;76;74;67#201;2;76;75;71#202;2;75;75;72#203;2;75;76;73#204;2;78;76;68#205;2;77;76;71#206;2;79;77;70#207;2;76;77;74#208;2;77;79;75#209;2;80;78;72#210;2;80;79;74#211;2;82;80;73#212;2;84;82;74#213;2;51;62;75#214;2;55;66;76#215;2;58;68;76#216;2;60;68;76#217;2;59;69;76#218;2;61;70;76#219;2;64;71;76#220;2;66;73;76#221;2;70;75;76#222;2;74;76;77#223;2;77;78;76#224;2;78;79;76#225;2;77;79;78#226;2;80;80;77#227;2;80;81;79#228;2;82;81;77#229;2;81;82;78#230;2;83;82;77#231;2;83;83;80#232;2;85;84;79#233;2;86;85;80#234;2;79;82;82#235;2;84;85;82#236;2;86;86;83#237;2;88;87;80#238;2;88;87;83#239;2;87;88;84#240;2;89;88;81#241;2;89;88;84#242;2;89;89;85#243;2;91;91;87#244;2;87;89;88#245;2;92;92;89#246;2;95;95;91#147~~KJH~o{!8?AGOia_o#129!14?A??@???@AJ?OFKB!9?_!4?A?B?OEWbcPiS]gHFf?BF^FsW@g?OO?_??O_XEyITvIco@_???@GBW?kPGTEhUpcyPgiWjSWdCAcIeHCOk@iOY_?A_WOW!4?C??O!5?O??O#107_@#108!6?EAGAC?@cDaW_CGDDwcOG?OQ?LE@@IG@CJMEg?w?cI?SWHaapC??@CHW]zL_sPXhEPwKD?mADCK_?CHJNoA??ADc?s_O#104!7?GC???G@C@Q???KAELEHO@@?ZS\vi~zU~H~j?|IOyCzbAe?osODnUU[fr|}Z~rxw|wypxyxl{^SCRfrv|Wadk_AO__oXD[XspO[CG?A???K#103O??oo?___!4?wowwO[K_oOqoWvSwg{xpWwwO~~~f~~~{gwvx~~~|$#146??b?o?@?AA??{wRO!4?O?I?Kz{rU{ftwja{IWy!6?D?@???C#137BK]Hp}IBINN~fZl[??_?`KBM?A?@CwWoOo__G_s?DIMCD?G!4?C#114!11?AD?DGAiPIxUhEZDgRTdSifIZXZdXUznR}Tnd^Ew\fnfuxwkwmljxXEsWioig??IoG?dCGGa|jgC@i\`WHo?w!4?OchE?@?A#109!11?AsI_@?PC?GG_?OcC@#105??E@!6?O??@OG_@G?A???@?G@@ABEHECOBJWCUhVQBITPKBSk@dTPho_OQJwmY?@!7?_?OfAsMDBCG\WmLIJWOg__!8?C#107!7?O#103???O#105!9?C?_?O?_o!4?K?!4o??S?_rRBMN]]I_P?n???@__b??A!17?W$#145??O!5?C?GB@!8?CC!17?Q\BIE??IKgBG#139??_???_s!5?_!5?o??O_Cg_S!8?@???_???O!4?C??C#122!14?O#125OCC!10?A?@!16?xDA???HEFQAPQ?AawJB?ITDV{`MTmYaP@C???_A??ACa?YACWA_I@?_O!8?O???C@O?C??_G?G?O?_!9?_!6?_?_?W#113_!4?O#106!44?A#109!4?_?A!7?O?C#103?@#106???__#104!70?@!5?AB$#144???CE?E@OXTc?BGehviTCZ@yrCBGhBWIFS\BtdD~ka{twwssbOoo{r@uI@TGtOo??CO`siDGEOG?O???_#128??A#135!6?G???_??GEG?_O#113!58?@@??CCC??cD!12?oO??EYWO!4?E??@@C@_??WAGC?caI?C@HOW?wG??GCC?@C?EGE??@#109!21?G??O!9?G?@!7?H?SICAog?O?_??HAHCC#102!17?@@!6?@A@?A?@HB?KA@C?KEBAFDMEDEAB_jjkWKGAf\YRZ|N^NNEIbeJMbbJFNL~~jrNKkK@o@@T^m~OFMFENBO\KlLNfGjFVBEMfFFn!7?BVFGE???A$#151???o??GAhcaWACcHS?D?H??D???C#135!28?C!7?G??AGDI#136!5?@@@AA??LgG?GA??A?_?O?ao#109!69?@???D?AGAS@aP?X_C@?OO!7?@!4?@EDA??_#112OQC@DSZ___De#114???@O?_??O?Q!4?O?_CQH???C_$#127!64?O!4?O?@!8?O???C??O?FATYD@JIT@f^Ah@@IGDBAS?O??C!6?A!7?EC#107!56?@!4?CBO?_?AAgO?GC???G!7?O?@BE@?`BAACGA``VWjAa??_OImEQXmFby^OLqBQV~puc_M|v}{wWxAPjm{sfzhU__{`_aoGA@mYIaS?O___EOdci_GT?CH?S?CW??_?g?O_?O??C!5?W???_$#128!95?_!8?o_?oWLi^n~~wq{af@?__???G!9?O_c?O?_#105!62?@#124?_#111_wOO__O??A#105!8?O-#144OGS?Oc_UJCTQS]HGOnHECIhUwC?C?dGAIwH@FKNRvdTjZv^b_sCB@PASneI?ABsIB@@??GECgA@@???o@G#127@!5?@@??C?O??A???A@A!6?A??@#139?O#114!4?@???A??ADEDaEE@IAV?YPCD?oFcQPfhviUdlzErm`NONRX`ei]tGlQeMz?l\|TO^BmDbGCoWtAF@aSg__OG?_?o_#112s?@pWA_RWGAKS@?C#114BD_[AA@?SSO?C?_WA?@@?_??O??OeCG#112?O___!4?c??cOO#111WG#109!10?A!4?@QGC?E???_G?__?@AEA_!5?GSXAx??A__?gW?!4_?_??_#102!9?AB!8?@WPCA???@?@A@AFE@C!6?A?B@?@BB??@??@?@!5?B!9?A$#151kui{mW[hsZili_eSfOeGIcUgCO???W!6?G!6?C#140!4?SG_CsGS_?WScCO!7?_#136!4?GG!6?AAA!5?B@?AIPG?ACCCK!9?G#127!12?@??OOO???OO[?C?Gww#125C??G!8?A?G???oNOGcYHO?GEAdHpC|Q_Ail_gPo?OILcA{OSOJPUO@@ACB?@!6?Q???@??AA?g?@?W?cg?A?c!4?G??G?@??@C???A#105!14?A???@C!5?@XAK@DH@?@BGH@LGPWEfQ?HIG!8?_?__CrFDWJ??AIPLBE?FRNOC!5?Gco{G!9?A?A!10?wx}z~\}{|~|~{}~}wo|{w{AW~}KE{zrw???EnSMb??A!5?{{[[GOKG?O??_??n_qqyww{{$#147B@@B?BB!9?G??@?@?@???W#145!20?B?IW??_#129!9?Si[cOS`R?@AAE@_MCdo?lQHUWcCYAHDeesTzRG?\~~|}xoLa?__?c{kkYEWoPaP?ihiT@@E?`?g`c@?INwRlmWUGThYOCpKPY?__cA?OD_?oO#127GO#109??A?A!9?O?AG?GG???G!4?@??Q#111@GCID@@gcCW_?GS#105??G#109?B_h?P@???_D@c?OOC??O??Qg?W??cI@?G?_!4?@??A?A#111!11?O#104!4?C#106!4?O#104!6?OO?@WoFdcPnBTjEDAG_oBO~EdtMOSWmOcoNq~fm~~bRE@OZZzzN~~HCCmXxvzz}^y\}\!5?A@BA#103!7?CKABEB|a??rxBCKCBJfP?Bp[^|tnv~f~BBbbffrrnFvzZ~\O^LLDFFBB$#146!4?@!4?_???@?B???o!4?Bj~b~Av|tFu}orokGYiOcG_[!5?_#137G??@??`?B`???A?A??FCoO!4?IA?K?@?@?Q_?h__??@G?g`A#122!7?_???W!4?O??C?_#125!6?G#122!8?A#113!19?C!5?C??@A@#108!10?A?C??CA??@?@??@??@G??LQ?B!9?A@?@??@IO_E_DOQA_@F@Z_[ADEEotSmJA_Sc?W?G`?C?QN]YY??G_??@wgOA??s?_GO!10?_#106!10?@@?ABM!19?@@!4?@??@??CGG?_c?C!4?ow??_C??C?_#109!9?_#108!17?C#106!8?sCG?Og??_$#145!12?@?O_??O?pO#142!26?G??_#146!12?g??OC??_#135!11?@#140O??_#128!13?OO_??A@ENO\v]FnZBRBcxbNK[m|!4?wwgd?gRC?AAp#113!40?O?IW``A???__K_C?FkuoG{KKIvyCa{KC@qcQjogp?OO!5?A??Q?YA@_gg?GG?s!5?_???GHO_?_#103!82?C?C!5?EA`!8?C#104!35?GO?G$#129!49?A@#145!14?S_Gg?O#107!117?C!6?A??A??A!19?C??CAG??CGCGggG!4?O@?AAA??kHBJ@?rRCUQFLO@d@~}RLlfsERn|~~J}Et`myu}}gsruwAvmFpWLNess_KWWW_O{A??W?CWGCC?P???AG?OGG??G?WO??O?@???_?Co!8?GC!4?_?_!33?_#108O!7?GOG?W!5?OG?COgGCC?a$#147!49?@#139O?IE@JO?`ZWkGS??A@B@GGOwCcx}^@oOKo?kuGeGWcOS?GPG_???_#105!143?@!6?@C#111!148?_-#144G?_?gB?U\?iBAWaX@??g_HGdO_W__@A@CHEBA@AJANIDBHJDCA!4?AOF?@CBCDJ`?_??CeCeg?_ocCAAD?C?_!4?C#136!5?@OCG#128???A!4?G?CC!5?@?P?@UNF[?[??AbiHNAdpV~c~ca#125!6?C??A!8?EAE?E?@_?C@??A@O??JLGCcAQ?hCSC}a_CagyIcCEXhMOGoCcH?A_?C@!6?AGKCLALAK??A!4?AcKaGTC!8?@#112JNj_@D}cmFaIC@A???E??PA?KOO?G#105???A#106???@OO#113???O#105?O?@A?O!4?_?O@?_CGA?HC?C?@?L?CC???CE!5?K?O!4?@!9?C!5?_OW[~NYmM~^ZKLNJNNN@\~jZ\_ubo?o?FBNN#124?A!6?G?O_G#105???NNNFoOO_O@@OIE?`BB?GN@BB$#151u|^qR{^ha~T{lf\eQw_?XudY_W_AAO_AHC??D?@CD?@G??CO#140?W?MNACK?E?GGGAC?PPWJGGO??_??B???oC??CGA#142_OO#122!14?C?_!4?C!7?E!7?_#125@#114G??A!6?@???E?w@HaPA\_IeUHRgN??H?J?HCYjHi[dTELf]cQdzQ|H~EGjz?T^z[P@cIw???oBBKAQ__OO!9?gBA!5?@??_??G?_@!5?A???O?Q#110!5?Ooo???_!8?__??O!7?_#104!7?G??G?@!4?@BA??@?@E?CD?@?CCOA?DCO?J@_JJTQ@SX|@ArTFH|TTW|VDVW@voKXWhpb|PAbB?OC@@???BA?_???G#108a?O__X??AIGAG?O??g`G?cdO?I?C?_R!4OgG@hQ_g?hS?c?c?OvOO_$#145@A?L#213C#147?_!9?KFNF!4?@@??GCGG?oO?O??o?OO#142!7?@??G!12?G_#137_!14?OO?o??@!4?BA?B#141!12?_#136??@AKKC_?_GgowaK???_OO?__!7?O#128!10?OO#127!4?O_oowcoEq@O??B???_O?O_!4?_??o#109??@G!5?@!4?C?C#112!7?A???B??@#107??D!9?E!6?W?H?GRL@PPL?`?AC_?BAG@IP?HcHKtNJM@F`Kk]rAb}U}^~\d^^qbfn`BNmj~il}k{x^y^}g}zYzuTh?@|wWiDq_QIcsaGwgeAk|?IguAiieAga_?WGGo__SCSAA@???_?O!7?C???E!9?O#124!43?_$#146!12?O???_?O?A???AEF[TA@oaAh{g]{?g?CaKeoi?CC??_??_Wq?_???GACB#133!58?A#127???@?@??@??_?W?JG#126!16?_!4?O#128?o???oO_??o??_??O#113?@!4?B!6?@EC?@Bx?O@gsBxHU^lL~zu[z^m~~?o??oOO??khW^~vq??OCO?__yEmAWK_S_OSK???O!4?_AGo_@?GG!5?G?_#108!17?OO!4?_#106!9?_OO???_O??O?O???_?B??A!11?W?de?EAAFAIG?KkC_??_?_?__oOoOo??_!9?_?oO_!5?OB???@?@$#145!19?OC?Q?K??@?gSCO!4?_??O__OoO??z`yo_CpaW?KQCqwOS_!7?O#135!87?O#116!26?O#129!10?O#127O#112??E#107A#111!12?G?C!6?O_?_#110?OOO#112O#111!28?@?WOPO!12?@#103!70?GA??@#102@#109!12?@!4?C#102?_#110???_$#139!51?@?@G@?@?@?@??A???SrPjXF^^MWz{{AzhnXAoJGIJ{j{wucwVxYItlgy\dOBGgcCoO?WAW!5?_?O#124!74?_#115!14?_?_O!4?_O??___#109!35?A???S???A!5?C?@???_Q_?KK?O]s_OC?C???@E?C??@!5?GAai?BA?i?Cg@O?G@!4?O??_#103!29?O!8?OoO??CCAEH[LDFL_[_o~T]f{ZYnutmZv^kn___OFmELNU}E`xZ]W{n?_M[{$#129!52?O!4?_?_O??_?KAC!8?@??@@G?A?ADLSe`sBS?DGGB_EdtGQRDAQnWrRYxBbyFk@u@??@PbN}SKCsO[YMG?A?ODx~F}u\i|a\dHhukV?NHC@?H?GCCAC?YgGAG@??A?G?C#124!37?O#109C@GADAG?@??_??GGAa?eaGO?G??e?BgO$#147!53?O#108!173?B?G?DAA?D???DT?@O@???C_?_@?[!5?KA!4?C??o???oWOSa@`?k?@?@???G?_-#151~|n~~}~~f}y}z|VhUyxNlqWygPviHX_O_PGO?C?O???_!8?@A#144G??@???@?@?KE???WOo?KD?G!6?G@KO#137!6?_#141?A#133!4?C#136???CA!7?C?SADJ??HKP?A@MADEHOASbI?ZJMSG#114_?A??AC??G??aCI@!9?@?EA@Ed`?`U?I?A???CC`ACRSXAE{JjOcOAPdY@CD?R@D@A!6?W@?C!4?CCG??@?SoAWAo?oBCG#107G?_?@!4?I???_!5?@?R@??AA?TO_?CF??F@@cOKOSekOfgTRLD[bBZLwNkYAJ^n~~jnpZFTKrUm|JEMWst[tujbrpwh\J}|{y|ykQgx\Uz^CMSk[owWweyG?E_QqOO??SM!6?_??_?k??o??OWG?Cc??O#124??@AO?O?A_!4?C!4?GO??_??G!4?_$#144?AO??@!7?A?A#145@@??A@B@???O__A?A!4?`!6?OOOC??`GSo`{?KJF@Qvqv`_lbo?kLuO?_???@???O?_g#140!18?O???G#128?G#141A#137_o#142??G#122?@#137!8?_gGC_C_C??_#122!9?C#127!10?ivVzs__SXGO?Ko??EDE`TtaGaH|zYOog?GcC!4?k@gsgG?_?q_???_oO#124!7?C#110!4?A#116_!8?O#108??J?@??_#112?@EK?KS??CCACWh{lQV[Gc[k]G?AAB]wGwWng_OXC?e?O?eO@?__?_!10?_O!5?c#110oG_G_??_@O???_#106!8?_O??A???OGUC?_??a_aBbJ@C@G?qm?FDK?byvJ`~GL!8?BMnBVDNCC[WI?o_O#105!20?GCB@G_S?k$#146!8?W@D@C?gSgCEoOKcCVmGCUESKPcrJnY~j~~~^FnNyx}[v_@?BnqCw{cGG#141!15?G#146O??o_???GEGS#144!16?_#128!22?`???@!4?C?BB#135!16?CGGC???_#125???G!5?Q!4?CDTe??_E@ACbAwh@?S@IF?EA@]ZGAc!6?___??GW??g??@!5?O??`@??hA_@Q?A?o??_#110!4?D@BA@!8?@C?@?C?BB?_O?Q_!5?@@@#104!26?A@!7?_?A@I@C??MCQAC?A@DADB@@AAHC_@@HO?CE_C???@!4?AC@#103!4?Ba#104!12?_#103A?@?@@?DE@AJq\dSaF@QLqHUOAXSDQhBo?[AEGbB@Qn$#144!27?@??@#129!16?@!4?ACA?O!6?C!17?SoC?o??dhTG!5?A?CA?A??H?AATPDBH@F@GoCcMO`EDWipDAOoLhIWOwCoOgs^~|~~xz~~v~~\zt}O?_???WG??G?_?@YO_#122??G#116!5?A?@GK@!8?A??G??c?_?[?oo#122G#111?@???C#109!24?OI@A!5?B??@!6?O!8?O?gG!5?A???CAa@?A?AG?SaG?o@Cg?_COADHs!4?S?M?G_QCHO?Sw_AIGA?GO[K?BC?_!7?c??_???GO!5?A!9?G#110!22?__?_$#147!30?GbKICcO??C!4?g?_!5?GGS#139!5?A!9?AAAHbIRFVUE@AADKBF???a^j^^||^z|z|~~ql|\ieyssYGitAoYpeQgydS?WO??AO??c???_#126!18?@#128???J]FBeufpPMw?GGWGa?XoGO!5?O#129g??@OAs#113??O?@!6?@GA?A@AEUDB@EEbZMUCKYrV^^MnjNKe|CUKKoCt{XB}r?v~ozoy?OAALgAUG_?__pS?g??_?C?OC??G_GgG??G?GC??AG?cO?_#106!17?@A??@D@#105!12?@#110!13?O#105!6?@APDDOHGG@d?C!4?C?mF`MEtN@?POC#111!4?@???GG?CcG?I??g$#142!48?E?A!7?O!7?G??@@@!4?K?_`giK!5?A?_@!6?_#122!33?A#127!4?@C??@#116!23?@#126!39?O!8?OK!7?O#111!30?GG???G?CC??_?_`?AR?@!5?C!13?O#108!77?_O_!4?G_??oOPw]PxIO}O_?GGw?AqAaPq@MhOC_?jLO{LqLuhj|ejydEsJ[atPb[O}LO$#140!49?@!8?_??G??GQPQ[Mc!4?o?oG???@HAAO?OOA?_S?_#135!42?B#115!73?KIC?kxHYW}`_?_p@zp?G___o#108!38?K???@?O?@??GI?@Q@@!4?A??QI?OS?AB?P_s!6?O#110!53?O!4?G???O$#114!276?_-#151nzVzt~z^~n!4~z~~}~ut}~tzT~HYfigg}@GsPGcgPGH_W_?l_W__???OGW#142!5?_??O???XG@O!5?b???C?GGGo_??GO??_??C?GIA_?@?WcC_??G??_???OCU_C???_??O?O#138__og#128A#144???GO?o#127??CQ#128GC!6?BDF#127@BKQFAEIAcF@@c[V~`BdY@V^|n}~~?_GaNIemym}kWS]kxUyX_~ykj?_WP[O???_C__`?W_??S!8?_#108!5?@???@#127??O#107!16?G!8?A?@!4?@J?@!9?RDB?DD@D?DSI@BHA@@A?@@CA?BB?B?FbVJ`F@i^SETFFT\~XRRL^vRoeHzSxTPBn~pIG??EA@?G??_o??C??qD`D@@QCBRBA@???A?G#135!7?G#105!5?c$#213OCgCI?C_?O#146!4?C??@?HI@?ICi?u_OOS??A?GksGFmFuNDO`?NfNBwC?Jv_gw{~|!4?C_?_q[G_oO?wGy}JQvCF!4?_???W??GZ?@?O?C!4?O!7?AO??_?GOg#137?@!7?WC?OoO?TmEHHhGl?O!5?o?_??W_coO???G_??O!6?_!4?O#114???@?A??O?@!4?CG??E_?AY?D@OFJ?A?@A?_?OETCW_BOaIhU?O[c[S?OwUOkW[_o}`_ICogY??eCOo?A_???_?@?A!8?KA??_@?A!5?_?A#111!9?G?C#109???cSE??A?GD@!4?H?C?OE?a??@@A?OIa?e?_A#106?GCD@_CJAAECO?EOsPSG?EXPPb@LC??O???MG_IH@!5?BEED#110?__$#147!28?CGDBU@{uBABR!5?AL[!4?GCr{_??O!5?YGG?@G!7?C#139!8?OE?EDBBDA@A?B!4?KC??BfBB]v~EOsO_c{_???@B~kYleijL\@A@gAdx!4?a?o?__O?O_O???O#135??@@G??D\AGAo?_!7?_#136_#126!13?@#116@???A?@??@C???A??O__?_!4?G@#128!5?_#112!17?@?C!4?A??@@F@@?AN?T_@F@RMto[_?@pPIEPHkyS]ID]{}|yU{[??oowIminoB`U{uXGwOQ_?OOeOogwo?G?_GGCP__OGgG#109!21?G#108@@ahp{weai[MAzzyjn@AOQ]scy{cgdECHHGNV^AIg`__??aasoG{}q@co_miBAA?A?tryn$#144!28?@???@#145??@!4?O!5?AAQO??SBGB??FFFB?AVCFFy]pEC_dYMLbFO??_G?___IGGG??_?CW??O__?G?cC!6?o!4?@?B#136!6?AD!5?AAGSCLIE!8?O??_!4?wWgQC???hh__#126??c!7?O#122!20?@A#113!10?O!7?@A@?A?AA@D?MD_C@NF@B@h@NF_NReA\H?]\dxNFc}wWynLo|I^}wUkoGKaNukKeOha_QC??Sg_@??CH?_GwCH?oOOO?gOg???_?g?W?_gO??C!4?GC??G??A!4?_#105!23?C??@!8?C@C#111OKW?_!5?GC?O__oo_!6?SYPz$#129!42?O#144?O_!5?O!4?C#140!7?G@O_@?E!5?@!9?O?O@@qOogCC?f_?_OC?PoGo!7?J?HS?AG#141!9?G#135!14?A?EB#128!25?AA!6?}??A!4?oSoOO_?O?O@O_!4?G?_#126!4?CW??G_GS{OW@WG?c#109!46?A@@?HQ?GcO?S@??__O@A@A@?@@_AGE#110!12?A?DlC}B?GkKQCAwO_?OoOC??G_O_!4?kKo_?gIWU?_Cggw???cAGA!4?CC?O#112!7?_#103!8?OGgWO??O??HTSEIDeCDWHIB@?DSJET?TWdlYDyICDO$#213!42?_#148!22?__#144!5?@AAD??O!9?@CO?CCAXA@#141!6?@#144!11?_G#125!59?@??CG!7?@??MH@C_DX?C??A@``Q?@C?D!5?CF?BA?@A?_??OAA[@W@QgogaWaA]_?H_??_AA??A???O#111!19?O#108!20?A?C???A!4?A#106!17?@#108?C!9?G_#124!54?O!8?s_BW@???WDADOA@GiOHIP?cWOdwD?G$#129!77?aG?CD@S`GB!9?_W?S_NEP_AJQG?Wg@G?@CBEHI@CZhViS?O?QPDSa?C@A@???a@xCSO??FJn^n^nNCA?k_QG?COO?POWMH?g?YsID?G!7?ACH#115!21?_??C?Cg?KF???G-#151Ev^HEvvVVTVNnxnn~JzBnxx~nvfJvjJM~n~g}rxf@IHi@[Xa`gBC_R@?aC???G!6?_#140A??AC#129!7?@#142???C??_?O!5?_?O???oGyAK?@W_GOAaO`GHC_g??BDKA_P@Q[pbT??gSGCgC!6?_#136??@!5?A@HPP?CI?QXFTCK?GK?C?K[C??aO_#126???A#125?@?@!7?A@@!4?@?@A?B!7?C??G??_C?B@??B@AAIBHIE?HAC@@EGGg@_!4?@#126??G#112!10?A_GO@@@!7?KOCHAKCD`OAfJvL?BH@ABLNQJqBAIEECSCH^tvoZuPBoB{cWw\Z[{ckGjQ[CaoKosCA?I__wG?_?EC!6?W??o?C?CO#109G#124!6?_!7?O??SwsGsW?C?ggg?o?oqk?OC?cAAGY?gB_CaDRl?s?I$#213@??Ah!4?A?_?A?O#148?O!4?C??G!4?Oo!9?S!5?G[OkoKcOoO_soDE{sw_Y{O`???WsG?CC#140!6?O??CpCHCIEDO?kgwXKO??Bg_`!4?KK[P??A@?AK?O??C!6?IM!5?@#144CPO@?g#138!7?o?GO?GCc_!4?G?G#128!12?@C!5?AA@A??@@?M!6?OqG#126G!4?HFIAgAcR@DO??K?AC?_???_W?WS?_G?OOG??OO#109!27?A!5?@?@?@?_@A?WgG?c??sSSuScOoC_CKO_@OO??O??GD?@egLG?@!4?@AA?O???A?G?I!4G?W?C?G!7?OO#106???@!9?G?OO?GG?_?GRCC?g???G_[G!5?G#125!11?_$#174wG_sO?Ggg_gOO#153!4?_#150?O#146?AA!7?C@?O?S??A??@A@ABA?AB?BB??A?BBA??BB!4?B?B?@ABB}zzrnuVItiyg?A???@??BA??Be?c@?_OOAAb__???_OgKQqgO_ACG_!5?g__O#127!28?@!4?@??@?@@?@@QS@?_@dOU`KGGSg]uM~@zTUmf^NLUvM|^CuGs{TwYgqwb}w@gWo{QhoCS?O?_O?E_??O?g??AOO?a??_#107!23?@#135??_!7?o?Co?OG?Gg_G?W_!4?O#107!5?A#110?A?A!9?AFFa_a@OOECK?W[CQDBrTAPBWAvuUBHB?leMhFfewBMwYHH_D{sWCg[ScWK{S?_oo!7?A$#152!5?G!7?CO??CC_OC!5?_?O#153!10?_??O#152O!9?_#142!9?@@@???OG?@#144!13?EA#139?A?B#129!4?@!5?@?@??A?@??G!4?COOCHT_Y@h??A?`MWASOCgCGSG@!6?_?GH!8?A?A!6?A?`CA?A??aHAiKJEIC?K`?UgF!5?C_g???_#115!5?_G!4?AD@?K???AA?@G?GCGG?__C?_?oO?___O!5?CCG_!4?_!8?__!7?_#110!4?G!10?C!8?_#116!22?O#107!12?@??B@?A@!4?P?CB@??G[O?NA@@EO?GCK@!4?RQ??@?A@AA???@#103!4?AEJs?C!4?A?q@?AK??@!5?A@?B???DAKAA$#149!9?G!9?K!4?O?WS??_#145!9?A#150?_#149!8?G#145??A!10?A?B???Fo?Gs@???OGgtI@DPH?HqWsxyK{BTC?@A?C?C??GSGC#141!4?_#144?@#172?C#145???_O??G#141???C#137?!4@?@!6?@@A?@??AqN}tmuegXPp}KeOgOr]QpagRq?_hSO??OgHOOc_??_Go?O!4?G#116!11?@@!6?C?@OBC??COC_G!5?O??_!4?_?C!4?cCO_??G!9?O??_#133!9?C#106!73?@#111@??w??O_???@A???_A??_A?cgC?`OcQ?_kGGK???BB@_oO@?@@EB@$#147!28?GC!5?B@KCW[_SCk_cT?CO?OGKLLWGLyp?GE[c?K[goK_!5?K!8?OK_C#148_#144!6?A??A??_O?K#135!59?@??A!5?[Ok!7?W?B_??O??O!4?_?G??O_??_?o??o?o#128!7?A!5?_!6?@#113?C!9?C!5?FBH@R?ADd@BCEaDTbLV@DvAU}CAJHC~MRq?Y?@?@GCB?W??OB!7?@?@?cCGgJhJC?G?I_???AoBW_??C??HB_O`__@@_???_cc?C!4?_??_!4?g!8?_#135!18?@@$#143!70?C#139!33?CW?B?CS?FPPbAEAB!8?VIslA!5?AArqBqymn}}U\^}uu|L?@!5?AGC?_?_?_??_#114!21?@#136!7?O#114!21?@C?O??A@A?Q?D@_ADH@ELJEY@FfPmGwOQGM\yQU{zx\yi[qGKY?Lg?w|Suy?o??n_u[qwAA?@???a??A?H?A?gSG_HPo@_?o__???CGGC?C#108!31?_?OOG!4?O??oBDoECG@BAB@A?@B@BA?BBB!5?AB@nUTUK?{K@R~mz~Z||Tc~S{^zWw_O|J~t-#151HhA?@ADEfCDh@CJnB?Ca@JZL?xbTlYzJCE`\L\~mXopoHoO_CZwXCm@IQAe@IGCWoC@??C??G??_??D!6?_O#140??@!6?C@??B?@B?@?@!7?G!6?CggW?AA!7?MCCC?A?J@q_@GL??o_O?O?_???C!6?O?_#135!9?@CG???_?G??EG?CA_BMOBC??_?@CqYKmDLicQ?ES_Si_O?o???_!5?O!4?__#115??LCO?O??o]?@@?A!4?`Wo_??`u_}[C??GK?G#126A?O#122__#112?@?@?E?OKc?B?_H?GCDqIl`DKFQ??PF@c?_@GP_oBbLMk?dobCEJK@A~NMBGB?AFBNLOeR?@G??@GO_!6?!4_@@???A#107@#106?C@A@!6?C!5?AcDO??H??A?AC#125!7?o???@AGGaWa___?c_O?@$#152_?__OOO_?O??Wo_?svz\}scqWA[gO_??q_?___#153???AG?C?CG!6?O_?GO!9?O#146!5?AG?GIC?gGAzZgD^}aEy}]~!5?GKKmk|{{ooO_O{se]emHBOCAA@?g_?_oKGA`_?X?C?O?G@C?A_Q#138?O_o_?O_tYqo[|obG_H???_O?Po???yoO!6?__#126!14?A!8?A@!4?@???O!6?A@FAFCA!4?AH?xKP?Y?K?S???@!9?A#112!7?@#125??_!4?GA_#109g[cW_?G?BHGE@???Oi?CQ?Oa??@A?gGQ?AO??A??OO@@??A?H`O_?O??_sS??OOG???@?@??@??O!5?O#115!8?_!7?_?_??_O?___?_#108!8?HK?IC_?A@eYoHAdwSI|tE[fX\WEUWTfLo$#213QAT??gG?G?WQ_??O!8?_#174!4?D?o#148@?IAA??PcDAIOA@?wCFchPGD_oGitBr`EWaEE?n_B|{VwO_z|Uu{CCCy_?\wC!5?_?O?_#144???A#139?@??@@HA!7?B???_CDEHA@@A!5?A@@?O?O_sO??IFNMF~M[GC?K!4?`DoH}t^i!9?wG#127???@???@AG@?@A_?eITWsR@dQ?_OSXkVxj^iT^nnN}ny\z|}wTgZ{tT?Gscq?_gk@`???oWC_AcDGO??OCC?@G??_!4?C!4?OG#135?S_WdO_cOGaSH!4?IOLO?[aO???@??U!8?OK??A?O??AW_QC??OOG?_O?gO#114???O!4?E?O#111???BIA!6?@A?SsaG]^QG@?EHJM@A?K!4?O??H@@?EB{pxZR\}#105!22?A$#174CSG^mDaXOjaCEJS#153?GG#150!6?EC?A!6?C?O!6?C_?A!5?O!8?_!8?O#145!10?@@@??B!4?!4@?@O?@?b??@!4?@?AA#129???H?@PE!4?D??O?_OGOCT?PQaWgS?E@GI@?Yd#141!4?G#137?@BA@HB`AFKUI?U!4?|mM~~m@?H?qSq`Q@GSOwCG_?`?@?sIcG_??_OO_???_#128!9?@?@?C???G?_??GE#125A@???@?A_GA?@E_g?wAOS???C?G#133!22?@G???C???_#117??_!9?A!12?_#110!7?O???@_C!4?ACAD!7?CA!5?A@__lfqPY^NC?YG!5?@QWWXqCPatXR]j^j@HA_]Q?O#103O!9?G!4?@$#149!24?@???A??CGXO#147??A??AGC@ALgVB_?AA?eOLD@S?SGEHbWohY?FCA@?FF??A#149!13?_#142?AAZekCO_?A?AAMNK[e@BO_W?o{gRT_Mx?XELEaPGOIH?DOicgCA??_DGDG?@#144!5?_??A?GO#136??E?@I?DA?@??O?FeEDH?Sk]Obm@oS[oGKwW!4?G??@!6?G#129!10?O#125CA#116!4?_??@IaOOGQ?A?C??Q@@g??@!4?_EfKIqo??T@`Oy}ToG???G#117!8?O#115A#110_!8?@??@???@!4?C#103!19?O#107???O!6?O__#102?_#107!8?G#108A?A???C#135O??_#108!6?@B?@#135!26?_$#172!30?C#149!35?C@???W_!4?_O#143!17?GcCW!4?OO!5?_#172!5?@#145!8?O#172???O!4?_??_?_#145!5?COA#129!36?@?ALA??@???@A??COA@@?B#128??K#116??@IA#115@#114!26?HC!4?AA@C???AGEQ[DXIBG?B@H?G!5?C?_?QvG}FETZ??AA???@??_oQU~v???_!4?ol_o_O?G|Wgv??H??a??IA?C??C#125!21?O#124!20?C??gcCe?Co?KGA?@S?SkQwE_ku_?@C??k_?XdFuLYFis?Ap@?CAFXhBIGoM$#143!67?G?`??O#147!23?oG???OAO#141!30?_#113!108?@#122!8?C!9?_#113!6?@UGA?AJ@?AB@?t@_@?CAB??GF@AOO#116??K#107!8?A#113G???[KA?_@ADE?mLEK??OOtGLG!4?IH??@?@?LG?CoqnGky{v_|mdl][P?GLMD?OY{cAG\o__$#135!254?_#133!57?G???G!8?O!4?_!5?_!4?_?_??_G-#152CWOFABLScCWbQWOKVv|\MKLL{tSQ`Xd[|O\gORGO?A?A!4?O_G@#149!8?A??G?_O_?O_???__?C@_f??@A?yL!5?B?ck!7?__E#151??g?O#174cG#149!4?_#141@#145eCG!4?A#129??@AC??X?@???@@O!6?@#138???D?@A@C@??BaLePoAJA??A?dNn{EuBzwmo???O???@#127??E@!6?CA??A@@!4?DO@@C`CP?cEqIBTlCf@G!4?@rGE\PfUMxm~\}IZf}\{pGLyGC[[@???aWnuOI[o!4?A?GW#113A???_O???A??@!4?@@DK???@@!7?G!7?B#103!6?C_#113!7?G!6?@@@JxjNK@Z~gzMP}???__?[S~iW@i@pC#124EAHDOEY?@CC?AGA@nR]QY??Y?C??@UyWz}~~K?@BvlmVOWC?CKE]ckwr$#213hEMw{[Q@PhdWGfGO_G???AOA?A?C?A#172?A#150AMA@#153??OCK_oKAg_C?Ga?S?O_?`C_?Go@!4?A#143?@#150??_#146!7?KAA@A@a{TZudK~R?CC!5?^Rxz~U[NW?r^N^^}?Aagb{YgtG?lx\fe@EN?HK?_??_c`?A?O!4?_#141A#144!5?@??aB_S_ca?GWoO?o?_??O?gG??__??G??C#129?DE@A!17?CO!6?cA#126???C@?G??A!6?@?A#122@???_#114??@??C?A!5?O#126!4?@??C!9?C#133?H!4?_?C#117???@#122!5?O_???G?C?_Gc#110??@!7?o?G__@?O?A??e@B?W???@???E#114Ooa?AS_`???P?O#135??h?QG@W@!9?o?O?__?_??W`CO_G!8?ApAhG??C#108C@??aDYC?Q?G@CIdbQG@J@AG$#174A@@?@??AGQACd?e`G??_oo_o??__W#147!10?A@SC?@CCA???O?S?B_G@@@c?Q!4?@B??C@??g!5?C#143???C!5?A???AG?O?X???K!4?A#129_?cG?_#148???GoOAC?_#172???A???W#149!7?_#140?PHYGE]SkN]I^MK{j}~~{CQXKG[?\AW[o#135!5?@#136!6?F?!4@AB??G!9?@#139!12?O#136_C_#116!11?@?A??C@PA???@???A!8?AO?A_A@C]y}XEO?J`BF}]^Z|^rF!8?s???ocSosk[woosgW?G??oC?_#107!16?@@!8?A??@#115!7?OOwc???_a!7?__?OAwOoKo?CA?I?CIGR???AC??g_cCtWD!5?_#125!6?PycwG?P_mbpYW`p_OQDC$#214O__!4?G#151A!5?@A??A?@@A?BGJHEcY`?`_UnkfhqHHOsRZxnVSKIBNK]QG[CB?CEPaOwK?CWOWOOg_GOO??OW??@?_#147!5?@?@?A?AE#172!6?_??OC_O#140!4?G@??@#141!7?_!6?C#144!4?A?A#137!20?C@???C@EA??BHG[CF@NVonM]M}{svwa~yh}{}wxvy\ugG?S_w`AA_GGG??oG@WgA#115!7?e#136O?__?cO?o?O#115??@@!9?@@@A?@@!4?@!4?@@!6?oo!8?P?COG?@#133???A??@!7?A?_O!5?`?G?_@G???a?a?O?C?A??@?@#111!8?C!6?@!7?G?CcCDMAH#133!8?_#111!15?{G{QuHDb$#153!5?___!11?A#148!22?A`G!5?@A??_O@CqAW?N_XMDJC_]zbMFNFR]VGbx{kd?O?A??G_??Oqym{e{o???C#147!14?C#145!24?PW?G???o!4?O!4?W_!4?_#139?X@_@#140!13?O#139_?O???G?O??O#129!42?_GG_!8?O#125??@?@?@#122?G!9?G?O!5?_???_#110?@!5?G#125!16?__C_#117!4?C!4?O#71!11?GG#106!10?A#110!13?CC@??ESKEOC???@@A???AHxcyDX`TUgb]xdXuOC@H`If_#103@A@C$#150!51?_`g!6?_O#152??_?GC#172!20?AgCHOO?G#142?@?@???G!5?@@?BB???_??P@?TWADTIv{OAA??}wo~qq]NmsD??`_R_`?_oP#135!39?@!5?@@BCGC_GUv~j^AM{{ZABa^ZHDs??OzWyu}v|XkGUG?A???C!4?C!13?i_C@!9?__C?_???I@uSCIq?Oec?Aa@A??A?GA!7?@?GA?GWdPGS_OCCWfOhSCXGWAc?T_WaKY_A?S#106!14?@$#145!100?@?@@#128!143?G!4?_?OcG?aAIs_CsQ__!4?C_??_C#125_#114G!8?CC{?JJ??Jl?QIG?KG!4?@?SA]RVZMw{ACerAA?A??AB$#109!290?A@??C!6?@!6?@?@!7?A???G?c?cDGSCWWe?nOAO?SckAra\_\?cWc??O!7?A!4?OA$#112!291?G??p@??GG??@A?AA??E?A!5?@??@@@C@GI?A?@?A!9?@@G??C?o??GMGcC??AA!5?K??g`PMbAB$#115!320?OO-#177[K?gKDB_uISK???COGo_?_!6?Go_#153gx?WoBIE_s_!4?@?ok_O_GE_?I#152!23?__#172!4?_#143!5?@!5?AJ?O?@??O?COGI?OO!8?_#149!4?C?GC?G??O_!7?G#147!6?@G!5?_?GO??o#144??@?@A?_HC@?A@H?@g?gJAOWGg??EE_G_?OC???_O_?G?c?_aO??D?O!8?_!5?_#128???D@CC@???AHA!6?G?KKDEHpB?O!5?h]Kxyqq??C?GIA!9?_O?SgGoP?o_?CACD?W_??_#115??_#133??@??W@#117o!4?W?W#113@?G?C??OCA??__G_?C?@?AAACA??H!9?IIDB?IABUA#124GG?C???Wmk?OG?WoU!4?OoC!4?O?@?K@@HBo@_BcBB^Egso???LX@$#213@QTBRgcNHSAReDOAIsGYaOcgo_C#172??@#150!6?K???@QOP!4?G?CgBT@??DC??@di__bBY?[O]ic??B_o_!7?__??_C_G!7?O#147!4?A@#142@!4?A?A??G???I?@A@@?B@JABQ_@BC??XW?AG?bXU[KqA~yGSVY`AGgM??G#138A?@!5?DcUABS@^Ecp?_oUz{WwDv[xgpdSq?G\u_yGu?G@wag!9?_!5?OO??_#129?CGOG!4?A#116??C?@CE!9?B?AD?A??@??@?@C@ID??TluHDDbQShX!4B??K!5?CAQ!6?Gg^h{MWK?C#128H??@#77_#118CG#63_#25O#68O#115?O#116!5?SCi#135G?AC@?cC@cDO_T?eaHCHYcQHgIOd!8?u__WidgE@OUGPicA!4?Q_?B??K?Ad?B#113!7?@#129!4?_#108!6?D$#214a_A?_??O???_O#151!8?A#152?@@B`ps?TVE~fN_so^GLnm~[{g?@@@??OOo_r[usGSOGK??_#147!25?@#146!16?C???@GBG@??C?@?L}YLsyct{@?]{QV^aF|SqQOa_!7?A!8?_??G#137!16?CDDB@CB@@A?@CFIOiLZD#140??A#137!7?OOi?]t{o?}LQG??_?c??@A!5?O?o#133!5?O!7?O#129!23?C#114!15?O?_??A@A?@G@`JBAW!4?BOB@FIEBB?@B???DBnzzlnzhrTUP?WOo?o_?OK??COOoO_#117!6?@#116?I???@#126o_#114O#110@VW_TYV_O?@F?@?C#126_?a!4?_?_O_???Sow!4?O?KJK[?_@A$#174?@?C!5?@@?@_fxdBFD\LZUM[IMBMI!5?O#148@@#149?A#213!4?_#147??C!5?_#152!40?A#140!26?@#147?@?G#172??_O??O#140???I!7?A!5?HB_Dh?DAhGdUduF@~^Q{cS^Nuz}i@?s{IU?POGi?C???_?O?A#136???I@?CA!13?c@A@E@??G??SI??MI??WyAjAlGATCWzWHKHxsWQBGh?Gw{CCYE_OO!6?IA!4?_QGoAg?C?O#126!5?@!6?_#113@!4?O!4?A#135???_?A#175_#176_#120?_?GG?O#64?g#104G#112!10?@E?ACGPISXG_R?jG?E??`A_qUscO#125?O#111C#106!12?@!4?A#115???GVCZgLKOxN@B_W{???COGL$#179??gO?QW??_g??YG#214!11?O#151!11?G!6?BAVBOIEC_GNFO?_@?A@?SOo`Iam_OPGW_AAO@?@P_?GO!6?_O_O!7?C!8?C#145!14?A!5?C???@!5?@DDC!8?T!4?G@!4?c@Qg_O???OWg??_?_#139!4?A!9?A#127!41?@?SA@@??D?@A!4?D@?A?EAC?`G?G[mA_BCCLC?cpYF!7?[GBAAcsgS_N?PNP[@w__C??kD?AO#134!4?_??O#123?G?AA#90_#109@EC_!11?G@@?@G?A??@?_O@C?GECGCC@@#97!19?@#111g_cSBH@?X?DABGAOA[L??G?E?C$#173!12?G#148!36?AO?OA!4?GAGIO??@???D@?@C?f@CLDFA@Q?F@AC\nQ[WTSJUl|SdNmmdbn|iAfpfnhudsvu~}zS}uo!8?gD??gg_C_?_?gGC?_Q?S??_#143!6?O#135!31?G!9?_AHTDRH^TmFLATJ_GAH}@qDV~jT~J@t}\a?O?_?uKgo_Cacqq?B?_o_OoC??Ap?P@?C?_!4?_W?_oo??@EOA?__CC#125!7?C?G!5?OA??OAT@#141!5?_!4?C#162O#121C#75?_#125?A#133!13?C?gAA?G?_??G!4?A#103!26?A#125!7?g?c!6?_!5?B?iasAM]oOo_?XUHN~~yqe}~$#149!52?G???G??@!4?NA?KCO?@?@IOeWOGG[]kmW}TJAOlBaI?cHA?_Y_@OYW??OkO?W??GOG#129!11?_#139!111?_#122!15?a!8?G?__?@??OA??GP!4?@G???gO?@G??GG??E?Ai?CaW?W?iCCOgD_C?U??C!4?C!4?A@?As??CQ???G???O#110?A!7?G!5?@??PH#113!37?C#103!4?AA#127_??O???_$#136!325?O#44???O#93O_O#119?C#125!15?_#115!15?_O!8?okzt{KHl??FB$#160!330?_C$#154!330?G-#213p?T`D@imPQ?a?CeW}AKm^Op{dCcAO?CG!5?O??O?G??G!7?O#147@#149!16?_??_GKBBA@B?A@G?Ma[?UF@dNJBVBA@A@ZF?@g??LL]{]xeK?lpEC?A#174??CIg?O??o#149_cSGOgW@?_??_!4?_#147?A_?C_?GC?AA!6?O_!7?O!5?_#137!10?@???@???@@!13?A@BJ\Dk?E@DBB@A!4?AH@X??Q_!6?_#133???_!7?G?G?G!9?G?C?@c_AO??GO??_#128?M]MBCFFLUoO[MACA@A??@A#67G#133AA@@#75CC!9?O#61@#64@@P?A?_#109_!7?G#110?C#127???O!6?_!6?o?OOoCoo?c_Bwc_o__#111??G@#103??_#106??_#126JcOIGzro{XZX_cblwoSk~qRZN^}U{o?o$#214Mw_MgmTOG_x?A_Oc!4?_!4?aAG?cO!8?O???O#148!4?@#151???@!7?A?A??@BG?ED@oI?A_G_!5?Os!6?OwGOO?gG?A??_??GOO??__!4?_!5?_GSG?COO?_??G#140!12?@!10?@??A@?B@OAGT`?ED?Aq?JJ@Jm[xS][]}L{yu?XOK}gwkww{E?_oo!4?a?Pg??C!7?_?_#136!7?@!4?B@?FFABAAA?BBOOgrzZ`B@BA@@Ah}{]x{z[__r`??G?gnwojc`G}?_Ocow_!6?_G!9?@#120G?G!5?@???D?G#62AC#60c#68@#112@?C?@!6?BOO??A??@?A@!4?B?@E?@A@#126?AA?O?G?CG?C#124!5?C@!4?A_???@!5?_CO?C???GO?C?C!4?BGNBgKgCeWOK$#174?C!8?A@???@???@?AGAIW?oMYgS!4?Phw?kKCK?O_?o?gOG_?W???_!7?oO_?G#148?C!5?C???C??I!6?CA?_??CHGdQCwL_FjLqQ?@`CWR~AA@x^sA@!8?@BABED?GyHlwJQ{iuHFo@OaDO_GW_G??_oOG??G_#144!8?@!4?A!4?D?E?GQ@U?B@CAGH?G?eHo?GQg??W?oCa?AO?E?C?S?G?i??O?_#139@#128!21?A#127?@!4?@A??@?@?A#115???@?@#116?O?D?OE???@!4?W?@!5?A!8?@#121_!6?A!4?A?C#70A#118C#25AI}kk#116@??@@!8?{gk[G[KkSWUy!4?KKkCAo?G???O??C?CS[o#127!9?@`oo!4?CC??GWOE@@??G__#129_??G!5?_??X_gO$#177?BIOO??@eLC[tZH?@@@O?@A@??O??@@`o_g__A@CA_@?g!4?O!5?_W#147!43?_#153_#143!4?A!6?@??A@???C?A?@@A!8?AO_O?OeuD?AC!5?SO?S???A@B!4?A!8?G?@#138!6?B?@?@@@AB?HHEE@??E?EB?_U^FLWuN[SkEF^_iGo?wOhgOwOgGcpOOQC_w?OG?G_?OgccGwW_GdOCC_G_OO_??o#122!4?CA!5?A!4?@?@??A?E@_?`WA??_GG``?H_OICH?C?A#70CO#168_??O#154C!5?A?G?O#159G#39O#24??OO#104O#122?O??DG?Q#135Ce?g?O?AOBP?A??CcvIxBA@I!8?AAB@QAaIrisZTyv[O!6?G?_?_AO!4?_???K!6?E?G$#179!4?AO!6?G??A?{q??kC?O?G#153!5?MX?VG??G@A?AA??II?B@ABA???_?G?o!9?G_?o?cO!5?_#152!22?C?C_#146!9?OGW???gsQOCAG^NDSGGc@?@??OOBSKAT???C?B!6?OCo{GCB?A???_gGo!4?O_#135!15?@!9?A@??B@@?O_COC??A@EPGAGSaXEnDlw!4EskVWwlSXXTFc[FIFG?CU[ckS}uKU?@`A??aS\G[}YV{E?EACHWP?O???G!7?O#114??C?@EAA@#125@#60G#66C#160O?A#164G???A??_S#93?G_#69?g#114???@A^ie}yqZc??J@A???a??A?B@@GGC!9?C?E#110?@@?A!4@COIcIDG@#116???@?C#125!6?K???@M?@?@??O_@`?@_?RRRZ?FFb$#152!25?@@D`?AA@EVGECE`?Po`Tf^tCkSksK{FFnNNtvLm{{kFkWoSA?G?A??@C_a_K?oO?_xGGAO_#172!26?_!5?`_ADD?_I#151!13?_#145!11?A?@?@??B@?ACNpQ??CFCC}s??EI`_!6?o?__!7?O#136!13?@#140!22?g#116!26?@!6?C#128?B#127!16?@???@??A?FKA`SP@???@#118?G#131???AA#161@?@#87A!7?_?O#107!4?AG#127?C#128W???_H#115_??A@C?`C__OGCG!6?o??LHLFJB@K?O!4G?C!8?CYMCE?KFBA?A?B?A??AA#144!10?OCC?C_$#150!39?A??A!6?B?A!4?_OOO?G?PBAO?B@IADPVDPOKwy[\XABD`\BE`_???C{?oSSWk??oQ??Q???A#129!13?G@@g_?O#147!68?O!4?_!6?_#129!88?O_!6?GSG?C#123?C#88???C#156K?`?@???A#77@?_A?_#113!11?@C??@_C??B!5?@#122?_!8?_!6?O#111!29?@$#142!132?A!5?@?gA???E!4?@?HAgHIkXYkSocKoK?CGc_GO?GVT?Go_!8?_?o!4?_#125!109?A#109!4?_?O?GG#195!5?OO!6?_#90?@O#63@#125!13?C#133??OG?O?@#126!6?_#109???O?o#125!9?_G!4?O$#117!313?__O??OC#188???_#159_#85A??@#92@!4?A?C#128!44?O?_$#69!315?_?O??_#223!5?G!6?G$#62!316?__???G#225!4?OWG?GG$#63!316?O?_oO#185!5?_??_A#95@?_$#44!326?A#201_??O?C$#198!327?C_??C$#231!328?SG#191_#236O$#235!329?CC?W$#227!329?O-#177B!9?CIAE???A@?F?L@B@B@?C??w[{wPqBAMKG?rsaxW}oWQOx_O_W?[iSAw?_WA?_?O?O_!8?__#172?O_??O#151?E?C???C??CC?@F??O?Gc??CCH?c!4?DS?G!6?AC?@#142@#148@?A??@K!7?CCHJ?@ICIIIsG?JCE?HGCGCoG??a?c?CA#139!13?@#144@#138!7?E@??BHGI?EHUBG?FE@BRF@JvzKn~{N}~^{~w}i~}~q}|~py|ty|iq{Viylu_eOjcysxcoogg_wGY_E_aWSioOw???o_??OO#69_??_???O!6?@#195G?GC!5?@??@#65oG_???AK?O___???_???_#135C?H!6?@CCA???HCHADk?gO?W__OO???O?`{MgALNCNFI^H?Ki@[J?OA_Y?CAokogSDEIGGE_ooO__oo?_OoO$#214{yr^]|uZRyyT|hsI[`cO?A???ok??I`G@_?@_GcGO?aG?G#213!7?_C#214??G#150C???A?C?A?@AD_ihAVbLHESCMQ]PhBVVngXpTjf{KrqJZgSQooNmhQWNGGA?M@K#146_?Oi?hU?CG?CGCI@?CG???OOO??bG`DHa@???o?_oOO@oToBH[UroOoJrRA??O?P?oO_?_O!4?CCI?CC#143!6?g?_?_#142!7?C#145!4?_#137!7?@??@?@!4?E@??@?G#133!10?G!5?G??C!6?GA?O???CQ??C?_@G_@GO#122?@?A?G?PDOO?C?A@#70C#154_???O_OA???K?C#197O#227AAA#77GA@#75CA?@?E?C?GG???G#122EA!6?UH???a_???A?O#112C??@#126!5?@!6?_#112?G!6?G#109?G#110D#126?AD_P[a_[`l[dKqpAPCShw``rOoO$#179?DG__?GcCC!6?AOADwGOccE?QO_O#173_#152A!4?DGD???EK@D?E?H_@N?Zg?@?bThC@zS@?CO??OG!4?__o?_!7?BE?aOO?o??o_OaKGA_?E_A???_O#129???O!6?C@@C?@?@#143!4?C@@GC@@E??C?A!4?EM@H@DDGCGCG#147???CAA!8?C??G!5?G?O??O???_O!5?@?C??E??G#143!20?_#147??_!4?O#137!39?_#140!13?_#141_#128B??OGI@A?@#85_#60?KA#162_!4?OB???@??G#96_#66_?_!8?O!9?O#116AEEFB@EUbCGIKFEL??_?_ONF@QbW@a@Q?A?_O?oO#136??O#116!7?A#124C?@?@??@?O@!6?AG?CA!4?_OWG@q?c@_Q$#174??C#213?@A@?g@@_?OJt`KWi?taYWGOklO?UCAA?C??_?_#179O#172!16?G#153???XAC!4?G\DEDGKAuPJIoL?IUgGgOC_GG?GA?GG??A?_#172??O!4?__?O?_O???_OjQ@?GE?I#151!15?O?O!9?O#147C#172__??_?OO__??_#149???_#143W?W!5?_?_?_?KC_#148???_#144!12?C?D??@???@G?@I???G!6?A!4?A?@#136?D???C???K?AG??PDB_O?Q@^??CIDI?AEKUE^Fr`HX^XfJSFNEvn~M^\^CFM#129IFDE@A#75g??O?OO?@#93_!5?o!8?B@!4?G#63GCO?_!7?_#119O#80_#136K!8?GG?__??O???__!5?_#133!4?C#113??CLA?B#137!11?_#144!4?_!8?O??_!4?GG?CA?U??A_$#174!28?A@M@?@@EI?OO_R@o?A???@AA_?ACFVav???_??He{x!4?_?Oo#149?G!4?@??C!9?@B!5?H??L??OC?OVbGcOGBLMN!4?!4o_opo}}yuz{}fia}xKvIysXycsw?o!5?@#129??__#145???@CA!7?G???@@@K@D`?C??C??AG_OO?_???O??O#135!26?@!7?@@A?AD?ADAC??GDD???XjOP?@EX!4@??C?C#134!16?_#109_!5?GC#156!4?_???S!5?_!7?@!5?@#67@??G?O???_#71O???O#127!5?H?O@?@?P?O?Oo??oO?LCC[LIH?|H^!4?oo?_ooo_ooP?_?SAMOA?BGKL?HBA??C$#153!40?@?C@??WE`?CDK#172!17?AA#148!9?@@??C!14?@C!7?@?@@???@A@A?A@#173!9?O#142!22?A?@?CA???AAA???B??G?HCCKd\fHpI{EM]]QGqO_Ax?_KOWE@AcgU#139!94?G#64?_#117_?G??CM`?O#90?g_!6?B!8?E#72??@!7?_#76OO??_!4?_#126!4?_???@#113!4?AG?D#115I???G!5?C!5?AB@B!8?CI??A#116!11?A#125A!6?DHDFLFDDAKDHAK@L$#147!100?C??@#174!11?O!4?o?o???C_JA?J@EG#141!20?O#140!18?@!7?AA_@@???@E?AJXAjPQi@`]lJVHO}ZnWoatvwuhwuvwwS{kwusGCqO??O#62!69?_!5?B@A#164?G?Cc!4?AGC?G#58O#176A#49O?O?_??O!6?_#114@A@DEH!4?G???A?O_??O??A#122!11?_???C#129!35?O???IGA?AA@?G?HAK$#104!307?O#25_!5?C@@#121A??@#201W!7?D#92G#44??Gg{Ww??_#64?@BBACC#128@??@!6?O?O_??Oo__!8?E??AA??C~?A!7?_O$#44!308?O#63w?OY?GKE#92C#160A#198G??O!5?@#132O#76???O#45??EC#120A!5?CG#154G#85O??_#70?O!4?_#125!4?O#109!6?@$#114!309?@#120!9?@#159?_?H?G?G#199C#40!9?o_??O#104??@AAC#109GG#44?_???_$#161!319?A#185??nO??O???C#87!8?G!7?G#86O#68?O#133??G?W!4?C!7?CA?G?A$#95!323?A!7?CC#117!7?C??C?C@!4?G!4?O?_$#191!323?_??A!4?@#60!8?@B#25A#115!10?@!6?GG$#157!323?C#155O#100C#168@#207_#202o#62!11?A$#186!326?_#223A-#214F^dZNvtNnu!4~\~r}z{phzasy\mqhk?cWYOEQYouG_@??_?_g_CC?A#178?O!5?g!9?_???O#174!11?_??O?O??rMM}Cck{`iE?k?[?ggC__?__w\K{OS_C??O_??O?_??__#146?C???A?@?@G???O?@A@_QlZ]@eJg?HBZgQVQC???O__?I@AZSbn!9?`@o?So#143??{?OG??__???_!8?O#144!7?C?CG#135???A#143?C#142!4?C_O?_C!4?__!9?_#135?@#136!4?@!5?AG@@??A@@#67?O!9?@#44C#185OG??GgG?E!4?@??o?@A#189C#97?a_!7?_!7?G#91O_#73_#63??C@??@?aC?MI?C#114@?A#127BAA@???B!9?J@C!4?YAD@EDE@???GGcEb#124!4?@!4?A@#125??@#137_#124!6?GC@C@@?H?_@$#215w_Yco?GoOG!4?_#177!17?ZfcNwg_C?R]{^{^V^BRro{|~N}~~fEAUzgYtpYlQB@dCB?ILGcUIqUocHUA_AeGQ#151!4?GG#213O#177!4?_??_#151??CgOUDBW?A@AA?A@Y`EOAwM!5?G?W?GC_??ga#172!9?SdQc@mHCEHUCcQ`_#143@G_?`AIQQSY_?hCO_A#145!8?C?@???hAB@@asQGkqQGS?C!4?CG?W#136!30?@??A#133!4?A?@!4?@?G@??G?OA?GD@??CA?A?@#85GO???G#90O???O#159A???C?M!4?CA?G!4?C??_#42C#40E!4?@#87O??@???A#45!4?A!4?_??_O#75OGG#24CO#107C#116C!4?@?CD?@OA@B!5?A?A#137??O!6?CI_?_W?G@?A$#179!5?GA!9?K?CAKQ?[@??PG?Of??@!9?_#152!7?GA#179!6?G#174@@??@??E#172!5?AQ!7?C!9?GCC??O?O???C!7?C?OGHYWE@?A@hAHQ#148!32?JG#151??_O!5?G??GG#149goSwSgO?__cWc?W#140?A???@!4?A?@?A?DS@__ATHHRBHDVbEQ^^~^zezepT~y[w~X!8?wo?@?GWOpgW?oW?OOo_O?W__???__#119!8?_#117O!7?C!5?@???C#75CG#195G!9?OO?QG!4?C#203o#156?G???_!8?_#223___#130OC#40!5?{C?C@#66@#117@??@?_#128?@@???O!7?G!4?B??Go!8?A#129??G??_C!9?A??@???A@C!6?_$#213!9?@!4?A??@?@ACC@IDa?DUBW!5?@?@G_?A!8?@@#173!9?@#152?O@G?cQ@?A???@@A!4?Gg??C_`G??@??O_?`O?BGSgERI@gPRAC?_C#146?A!4?@#174!40?O_PO__??C#142???G?IC!5?D?A???cePcYmPgCYCY?KQA?GMcGA??O?g?Gxh#138!5?@C@CI?D@B?abn~hllZ~DN~y^ffNIVf~NF]njLZnnf\^]n}V^}^u]~~v~D@AEAADEA?@@@#87G#119@#70_!7?A#164C???C_O?G#223?G?O?G#163OA??E#79@#65@M??OO??@?NIHGGAB?G[sx??aA?A??G_#156_#64@?ACg__#129G#135A`SeOalQ?x]wmooSBLJ\^@DimwWpursXRRBxS}utyuo_w}ssrx[Ww?@afrvJabi{~sO[k$#178!35?_#174@?@?@C@#150!24?AG!6?@?A??QRGOC@EPQH?ES@QgK`@@A@@?Q@PX?tAVA??B#129!4?C?O!44?O?e?O?C#147!19?@@G???a???A?@@???S?O??CC?C!5?__?_!4?I_??a???SO?UOQ_?A#141!15?C?C?O!4?O?G??_?_#139!4?G#70_#66__!5?O?O!5?@C#25@#198_#161O#60@#93_G?AB?O??@#228?O#197C!5?@G#87?O#76S_??!4_?O!4?@G?G@B#85!4?@!8?C#38A#109?A?G#136GCKWIWGWOkf???PKCio?o__cgOO@_GG#126?@E!6?H?D?ILE@GGCC?E@Ti\O???OO!4?EA$#153!37?CCG!5?B?g?SKGA???_@??OwSgCEc??@?k[{Wgk{sodG``@?HI??[@h?_@!5?AA#149!11?@!4?@!4?CgSg?]HJXF@|Znv^VjbZRzT^}DCt~~Vn~}|}#148!13?@??CBBF?@DLL@!6?\WWeYdPKVW_GcgAg#137!78?C#68O??G_Ow_G???AC??G#223??_#123C#92?_#154c??R@G@AAB#226_?C#196@?C#186_#209@#73??I#44@?AHAGC!6?C@!6?XAGcY[[?C#62??A#69@#25?GO#122?DP_E?@??A#115!4?C!6?C#144!13?C!6?G?GDO@??AGA_?AiS?GK?OKGSA?Ah@Q$#152!39?A#173!81?_!6?d?_Cc?_ACOG??S?cc?G_?QWA??g#122!132?G#88O!6?_#86CG???_O#176__???C#130A#162OCO#191_#160?@#231_!5?O#188AA#168_??_#49??OG??GSASG!5?C!4?GC_?OG#41_#72??_??C#87O#104!5?O_#133??_?_!4?C_$#213!122?_#129!164?C#64?W?O_#89???G!5?_#121!7?@@#225???_#187O??CG#95A@#166?C???@#77???O!7?_?O!5?S#61!6?O???A#60A!4?w_?O$#61!289?_O#71GG??C???CGL#168!9?OC#235!4?__??_G#202?_WO?W#63???@G?DBGGA#75?_?C#191O#188O?_#90!11?O#77O???O$#62!290?_???G?_?_?AAAB!4?@#232!10?_#90?@#201_C#191@??A#66!4?E!4?P#67A??D??A??@??BA!7?G#68@@#161?_$#135!290?C???@A#156?C!9?_??G_??I??CL#208!7?G#48!6?@#80!5?_!8?A!6?@$#63!297?_Oo!4?AGAA?@#93!23?O#61!5?C#72?OC#164O#222??_$#69!297?O!4?K??A@#92!35?AA??C$#171!297?A#160E#120??O?OO???O-#214EQRG?ZBuXFr}NR^|V~\!4~v~~zzmxw}~y~P[ACIO{vksHyao\U_I{dGUSkJcW_G???Og??Q?O#172!6?@??C@AD???G??CW#173?_?A?eWh?GccG@?A?_c??CaI??_O?OGO_gpGS`W`u_JeO_DiSiS`WSiOe_|?cQG`#146?aGETQT_D@AGTKCA@C???@???E@OcAYRS#150???_#146!6?@G???@#145@!4?B@@?BAQ@A??CQ??CC???C#138!6?_B?@?A?K[?FCPV?@AP?O?FGDOVGE@I?DFGPKK??A?A?@#121A!5?CC!6?CA#227?_?I!6?G???G???P??@#100O!7?A#77@GGM#40__?__?_??_#42_#74O#54G#72A!6?_?C??B#227_??S#185ACO_#104@@#109C?O_?_#135?@?AABA!9?CAIA??A!5?OFRb@?@@?G?QlDHBRPZZdMbjEuLn^RM~`tI$#215xLkv~c{HewK@ok_Ag?a!4?G#179??CCOEB@?@?EbL!4?G?J??KG_g[C??A?@Oo?_??g?O?Asl?@?a?waGo#150!5?G!8?B#213?O_??O?O#172!9?@@GA!4?A?G??@@O#174!30?O!6?G#148??_?R??@?K?WAC???@!8?I\D@qBpzjlODu@A[EkxwOwo_!5?_!6?_#141!26?A!5?_A???_@A!4?B???CD@A?B#89C?G???O#154_!9?A!5?C!7?A?_!9?O_!4?E#80_!5?A@!8?O!6?__#45??GC?A#161A??A?Go#68@I??_#138??scgwgWS?_?k_O?c?o_?Oc_??_csgwk??GS?__??OWSo_???OPKCo?q??__?I?c$#216?_#213!26?@?C#177??C?g??HD?A?@!5?A@Ao??O_g??B?BBDnJM??OkmmHm??A@GZ^?ktaZ}}s~}yc^#179?[_G?_?_??O???W_?O_O#151??@!8?ET?OA?cQH]?@IOiTJP_[biScWWATOcC???P_??_IOKp??@?K?_C#143??C?R??A??_CG?`AqMD{MCSAe_HS|?WOC?a?CG_cpcsGbai_?@?__O?_Y__??E?_G??Ge?aS??a?P?_GO?_?OG_?D?_???WCI!4?C#162G???o!9?_!4?C!6?@!8?A!9?O?A#90sCgA_!5?C#67G#85A#155?G!4?A#79O?_!6?G#63??S?A#195C???_#69@!5?G#139!9?_OGc???_!6?GGk_#144!14?C!6?_cI_OOHH?O_KP?SIP$#153!37?_Q_l??Q?EDPF???@BYdHABCWFSsAO_`TJA@?@SPF\tMc??P??W?@#152@??@#149!28?@?@A!5?@GEA??@?ODJ???CJAC@@J@F?iJHrM~#172\eXilATiQKciAI@A!10?a!4?_#142!11?A?A?A?B?B@C?UU?CHG@CG??goYWUm{Y_GW_owZWaoT!5?_oKgk{DA_jNGDaNGQoCDty_#133__oOG#131W!4?A#85C?b#62M?A???@???A#188G???A!4?A#236C!6?G#230G#197@#186?C!8?@A#93??@#44?_O?_??_?__!8?@HGCB!4?A#201G??W#134@??CGo_#136@BNN]IRDCC`JNVIRNNRBB?P\nPTR^^ZJF??OmBG_?CD#140_!5?_$#183!37?O??A#215@#213???o!5?@#215!10?G#178?O?C!5?O!7?Qc_iQ??c!7?_#149!76?!4oZb|zy~k}x{LX[`cJS_GOG!4?OGO?g?__#147?A?K?I?HWG??SWOOGSI??H???DOAG???CPA_??O???B!13?_?G#97!8?_?_??O?G#123@#117@???G?a?oK@!4?@#156A#223_??_???G!5?OC!4?C#91_!4?O#83G!4?D#75@#72M#97?A!4?B???GC#77GG#159?_#61A#130_#67??O?G#93?_#40@@#87@#231G#130@#176AGO_#71A#62A#133O?C?O`?GO@OC?_?P#137?O_KWwKC_?A!9?KOsa]^RyHAaaKKMC$#217!38?_O#174!50?B??@??NB\vHFE^vZJv}~d^]IVlz\s~|]f}lumHAMe`W@K??sG_CO?_#142!43?A@#140!22?C!8?A??D@DBC@B?@B@?FDVN@C?CKItXb@jWG??A?WcNCOO_O!4?WoI??OCBBBA?@@#120O??A???_?G???CGG???@#238C!6?OO#156?_!4?_!8?G???OG#132!5?@#49GOOO!4?_??@@?KAOGC?@_#75?G#120C?@#221C#194G#119@??CG?_#60O#127!19?@$#88!277?o#161?w#164_?_O#90?o!5?O#104A#75??A#235_???_OG?A@??AE_???O???G#225@#187B?@??G?@#65?_!4?O?SOO???OO!6?U]?SFRGBER#155?O#244O#242G#235_#222_#198o#164C#171GO#64??CK?O$#156!280?O#130G??G#92C#66G!5?@???@#198O??C??_!7?B#155OC#228!5?G#157?O?_#188@#155?_C??@c#157!6?A!8?@#75@#66A#164??_#246!9?_#245o#132!4?A#88A#128??@#116A$#172!280?C#134C#132C!7?G#176!4?O#87C!5?@#195@?QO!4?C@!4?CA??GC??DA#95G#73?O??O???IG???K!5?_CCG@O!4?OoK#162!8?C$#166!281?_#87G#195_#67??O!7?A#222_#161O#68@@#234G?C#228O?K#164C???@@?CA@?O???_?A#200G#196A#76!4?GC??Q?@@C?CKB??OO?O__!4?_$#70!286?C#63p\?A@{E@#85??CA#242_OG#201?Oo?_OG??G?@?C@OCaSA??KA@#185??@#162!8?@#96@#161??C#168GGG#166C$#69!288?_!4?G??A#246_#245oOG#231??C@??_!9?G?OG#93_#169G???C#195!16?A#208DC@$#64!288?AC#160O???OG#70???@#185!5?G!6?A?_@??_#202!24?D#226AB#187A$#60!289?@@#168!15?G!4?o??C?A!6?Bo$#93!306?A#159@!4?G!4?A!7?_??A$#207!307?G#243__#226A#163??G?_!7?O!7?O$#221!307?C#176@#237?C#191??o!9?C$#225!308?O-#214G??C!5?WCoGF??@AD?AHDtRTJg~ZjJH^}eNwC~|~V~^z~~H^APDBRXToROgC?DCScsScgXwIDqHP?i???Gc??gO!4?_!7?_!8?_!6?_#177!6?_?_S#213OCg??O_?_OCO#149!15?A??@A!8?@?ACBWjC~@by\fRz@BBABB??M^[wMha{sWk[x{zcWuwSoOoO#147C?@_KAc?@A???C!6?GS@!4?I?CAD@!7?@??@B#122!4?__#138?_O#70_#133GA#196_!7?A#87O??H#62C@#244g?_S?A?G?A#201_WCiJPGO@_C???G_G!7?_O?BB#45O#65AGACA?YA!5?A???CCDC??O#154G??@#93O??G#239W!9?G?_#134B!5?_#71_#132_#235O!4?_#183O??_#143O?_??_#140??OO??_!9?g?@#147?_!7?_#140!5?___?AOCG$#217s{{w{{|{{cwKsw{{!4?_???_g?O!9?A!6?_!5?_?_O??_!4?O??A#181?O??OC#177A?cA@OCG?aR?A@NBA@DDBF@F@#173AG?I???PE??GSCOOB?CPICXIIEigGE]gKwS\}g[UQhxgO_`cgF`JHEBO]cIDPIeW#172xfQwmCRWy@H@CaCQ?SWDaGg?eSoc_sYc!9?_!5?O#150!8?_OW?_??G!6?_#145!8?@#140?C??A???A??@??A??@!4?A@?P@A@#88_#114O#69_#161_G!4?@!7?G??C#89@#154A#227_?@_?oG!7?_WA!4?G#166?G???O???@#100_#76_???_?@?O__c?AECC??A???@QG#188A?O#159_#91_?G#100?_#243Ck{k{o??C_?O#227O_!8?_O#139B#149G#142G#141AG!6?iC!4?_?C??GOG#137??B@#136?A#144!4?K?@oa?_F??Hb?HYZcD@?B$#215A!5BA!6B?BB}|y~\uyIKAsF?cSsu_@WoCw???g??C???_G!4?_??gC?I`Q#183???@A@???O!6?O!7?_#215!4?G#180??__!5?_#177???C#151!22?@!5?@???A??@B?A?AH?F@JDGItG_?GCE???B?ACAWCO???C???C?O??GK#143???`?@E@UG?GDOaC@??fGEhEl?bXke]A|YQEXvmUGSsJ}}vVjyM}oFs|gGyo^|gkq_X!6?HGBAMXGDBA#132O???@#162@!4?G?E#75A???A#93A#235_#195A!8?CC?O?A#168?g#92W!6?_#90]C!6?O!6?@A!8?A!4?_??_G!5?C#164?C#245AABBBK{sw#185@?AC?G#225_!7?_#224O#234_#229_#203O?_#147C#175?_#139!7?CC[W#135?@!6?G??AA?@?B??SQA?@B??@A@?BG?@$#216@#177!34?@#153?@@?A#179!7?a?TeQ?cCIF?jT_KgWhJIgGQc@@WKaGTT?K{sOOoSCow{oSWW\V|t|Z}Mw|yphxMk{eicoY_ts_SPOg??_??_???_#146!26?H??@#152!7?@?@?i#148!5?CGGKO???@!6?D@BAB@?A?A?@@AHANKAB?@P?@dwcG@GAAGs??G???o?Go??O_??_!4?G#141?AC?_?A?ACC?_QGC@@#97@?@#238s??o???_!8?OOO??C?@!7?B@!4?O#83??O#91@A#168?OGOC???@#92@!4?@#93??W???O!4?O#83_??_A#185C#74_#155_!5?A#227@#246@???A?G?Wo_#161@??G#176O?_??O#193G#180G#226_#140??G#231_#150O???_#144!13?@#142O__$#183!38?A!9?O??G?CG#178!7?O?_!7?E???C???L?A@I?K@IIA?GA?E#213??A?A?@???C#129!57?O??C?O#146!15?_?@OGdY#150O_a@o?OA#142!4?A?C#141G#149!19?O_oG!5?_?C!7?O!8?_!4?G_??G#117!6?O#120G#68A#223O???C??CC!7?C!9?A?__???C!6?O#186???O#157?E!5?C#74C#75O#73O??C#163!4?G!4?G#67@@?A#73AQ??_!5?@@#236?_#242O?O#235?@#223@!7?_#117BEG#136@!8?@!9?@?@??@$#177!48?C???G_?A?G??A#215!13?_??__??_#181???_!8?__#174!5?C??@A@AAA@B?XPID@E?@X@EFP@BBBAA@FBHKEAFl^]YSw[sso{g_Oooc?ODEO_@@yk_Ckow_??g!5?O#146!25?@#142!20?@@@B?@@!5?@BG@?BD?K?AURLTEcyBMs\csoGOCCAGC#123C#121C#185K!6?O??O#63CB?B#117@?@@#231_?O?G?A@!5?O!5?O#161!9?G#195_#187A#192C#49O??FDA??@@B??@??@??BHGGL!8?O#244!7?A#197@#222A!4?O??_!8?OO#202???_$#153!53?G!4?C??@A?@A??@A@!6?AA???@#172!173?G?_???O#195!10?O#227_C!4?C#197@#90?GG#234O#68?A#120C#160!4?@#228_?_??C!4?_#185G!4?@??CC!4?@G#196???@#72?_gk?OG?g?K!4?A???C!5?O!6?_a#231!8?A#196@A#171@@AC!7?G??G$#146!256?O?[O#131!14?B#231G!4?_#130??O#245__ooOwWGKKKDA@#198O??@OG#100A#155C??O?A!4?e?_@#197!5?K#77?G!4?OG?C??C!4?CC???O!4?O!5?C#229!10?C#203C???O#109?@@A#138@BFFBCFFCFNZ]N^^Tz]~}jJab^}vmf^^{u~T|{~]r{}NHl\W}{u[}SCCWomys$#235!275?O?K??W#168??C#159@#86??C#246!6?AA@#236?A#191_gO??@!9?_!9?O#87!5?_??C#44???@@?@@#226_?_!9?G#167O#80_A#188!12?A#194C?G?O?_#64KGO$#176!275?A???@#207A??@#221???G#232!8?G?C#188?O#162??ACA!5?@!7?A???A#63!6?G#91_!7?G#97?G!8?C!5?D??O#224!11?G#202G#160?C??O#119O#133AC??C!7?@$#242!275?_#228GO#201G??g#242!14?C?@#164!4?C?@???A?gC!8?o#80!10?O??O!4?G??A#95G#156_!4?@!5?A#56OG#123!15?ACG#69A#68?O#114G$#222!276?A#225_?G??aA#226!12?O#235!8?_#154C??CoC??@!6?BA#79!9?_#164!4?_??_#207_?O!8?FA#230C#196A#163G#118!17?@#104??C#122?C$#229!277?A#198O?@#163!25?G!8?O#66!21?A#85C#191_!4?O!9?A#198G$#202!278?A#191C??@#157!26?_#95C??A?@E??C??C#195!14?O#159G#166O???G#75?GO#92?_!5?@!4?@$#232!278?_#189A#207!29?@#93G!5?H#203!21?O#168??O#198O#231_#235!7?@#224?CO#203C$#159!311?A@!5?Gg?C@??G#223!12?_#186!14?@$#188!311?O!6?_!4?G#187!29?G$#156!312?A@A!5?`!6?A$#176!312?_-#217^N!4~N^N^^~~~nnmwo~}}]J~y^{~h]|!5w{owlyMGliwA|a?soP?_IGAi?Co?c?O?_???_??_?G??C#177@#180??O!9?o?HFGFKTFPgFWCE?H?sgKS_k_Cki__w_i_io?X?Ga?sOo?__?o_!4?_#213!4?CB??C#172?ENBAJMBE?EE?D@G?IMi][[aYUmltqeb[pkXoCoO_Yau?Kq_I??_#148!5?@!7?@?AAK?A?A!6?B!4?CA@DCG#141???C???OC?COAC@#70AA?@#235O?G_Opo?_?wC??AYAGOKAOSC??A@???_o?A?@?OIW#168@??O?G#197_??_#191_#97?O!7?@#83??_#91??C!6?OC?e??O??_?b???G#92G#203@?C???_!8?_!9?O!5?@?O#176CCKG?gg#161?_#142CAA?@!8?q!5?o$#218_o!4?o_o_!4?OOO!5?_o??_??O_#216!7?G?O?Oo#183???_??cGC?aOOCgO?O???@#179!7?I?@@?E@AGEJA@C@@@??A?B???@?@???A?B??BAFBEBJFBNjB?^BFMT]DEt_{s[w?mIKIICKCyugpKwIo_o??O!4_???__!4?__#152!4?@?@??@#146?G??AD?CB#143!5?CA?CC@?@??C?@@QE@g?YTId]FPD?E@DRSHeXfMrZf]gCVfVRleYbU^RHjtZ#129HA#146HG@H@G#195_!6?CC#228_!9?G??_???@`o??OG??_??@#242oG#223??A??G??C?OA!5?@#80G@!4?m??gGOeW!7?O_?G?@@@???_JKAtg???_#246DA@@!5?C#224@!8?@!8?_#183H?QCC#138@!4?DLBEQFUDMj^a?TL@@@COS^^TFMFyh]_Bx?Pd$#234!10?_#215!5?@CN?@@@C!8?!4FEBFF??_COS?X?WGBGi[L_qT?qgIMZwK{IOyGLCWIoDoC__wSkQG_MC?ghp?_?G?G?G?g?G#173?@!5?@!7?@?C???@?HIEBA?FI@D@T?ZAXCHPAQCoK^J{|mXVOOswo?Ooq__?O??_?_O?_#150?K??OO??OO_?ACGIHLN!8?pqQhw}OscitY`oey_?CW?J_H_Op?cG`Sy_O_?OW_!5?O?_#142???B#173??_#134O#120O!5?A#196?@#238CAGoKtCOCAO!4?ptkG#223??_#201_?E?GB!4?WT@?@E?GA__?a??GF#95GA#74A!5?O#163!6?U??@!8?_??A#49??O?_???G_#245A@???_??@ZMI#229O#195O?C!4?@_#194C!6?@?O_#141@@?A?A?G?O?Gg?Go!6?O#144???@!9?E?A?C?GG$#216!17?B#214!7?D?B?E@A!4?@???AD@BA@FCADO?B!4?@??HB@??A@BCB@D?@C??A@!5?@C@A?A?DC???E?E?qAWED_cI@[SWGOO?W??OOS?W?O??O#213!4?@@?@??q?P_#151!26?@??A???@?C!5?@??G#153!33?G#141G!4?_#153!9?O#172!6?_!6?_!4?ELAC?E#171?_#119G#87G??M#242___W?G?@OA@@???@#241@@#229C#244?G#237!6?A!5?G#243?C#233!4?C#209O#159G??C@@S?C?_?@#75G??AB#96?A#164!6?_!6?A#100A?S!4?OE#84?@#72C??A#54C#239@!4?O?GG?C?@OC#156_!5?@#73GG#226@#234C???@?_#188EAO__#150A!5?O_#147!5?I_??@G???C??_j_?IW@gDO`W_?O_O$#179!50?@???@?D??@C#181?C_??_?PKCaQ_bSMOGyO??_??_OOw~Q?MGY!4o??_!4?ow___??__?OO#177!6?@#174!15?C?@A@?EK@BDB???A@AGH?GDCPKHLWX\G}uXsO?_B_Oc_?A?GHG#131!73?C#154O?C#201O???A#243GCAEA??AA#227GC???A??A??_O!5?GEA???A?@#236_#162?D!7?P??C#79_!6?G?S#157!4?H#195AG!8?_???C#74!4?Q#76_!4?_#168O#241GGO?C#231!4?@?C#222@!7?A#235@A???aG#193@#143?@??@??CC?G?{oDw`?OS_[!5?_!5?__!6?O?_$#177!50?A!8?C#216???O!5?_?O?O???_???O???_O?_???O!8?_??OO#149!65?A??@E??@??B?@!8?MPaFpA_O`XG~qL^pMKKC@?FJ!8?]xw?__SOCG?K!4?@!4G???W`_Ku?ICo_ooy_[B#133A#71@#244_?_?O!4?@#236G?G?goo_???@#168!8?_?O#225O#222C#198@#238oKBE#188!5?_!5?@A?C#77??OCwK#49?G#207!9?c?G@!8?_#202S!9?A#242A!4?_??C??_#235_A#223@#196G!6?A#236_#239oL?_{[#202AG_#185G#175?AOC?GOO#167_$#178!53?C?A!7?@?CA#183!13?HE@??GAG!6?C#148!90?@#85!79?C#162O???C#223??@#237???@#231???GC??CU_A??A@FHKC!4?GC@??_gsA#166?O#195OG?CC?_???A#65?o#76COho@???E`#208???G!5?@#159@!4?OA#79!6?@??_#243@DMADEUrzycO#233?G#227I#225EA#164A!5?G#229I#245okO#208??C#189??A#168?O#166G??O$#178!87?C!7?D#227!169?_#64@#245_???O???C???A?@@@#232_W?G!4?IWE@!4?O#245???o#185!7?A?AA!4?AG#72???BBCC?_?@#198!5?@#192C??G#168C!8?G#96!9?G#166O#155?_#240?G#92!9?_#154G!4?a#130C#243???A?A#233?O#169!5?_#171OO_?_$#92!266?C#185O???A#225?A#230!10?C_#191!10?O#164OG!7?G!4?A!7?A@#73!5?_O???{A@M#202???O#185@A#231o?@#83GG?@!4?@wK???C#230!4?G#208?O#97!11?O#49_OOO#63G#79O#155O#244???@J#140!10?B@BB!8?@???Dia}y]IN??_??oO???DKANEA$#160!266?G#156G?G#86@#226!26?@!7?_?C??_#207?C?@#198?O#154G#90G!4?C!5?OCA?O?P`!7?_?O?JK???@?C!4?A#65!17?Og#42G?E#246!5?O#242C#172!10?C?G$#88!267?A#97?C?@#185!25?GCC#156!12?@#163`??G!4?O#191!17?qC#230GA#93G?C!4?A#186_#221!26?@#90C#67_#72F?C$#109!267?@#67?B#194G#156!26?_#187AB!12?_#157?G#92???O???_!8?@???GA#226???_?_!8?G#188!27?@#77E#76_O$#195!298?`#192!15?O#155???CG#203O#205!19?O#210C#197O#154_???AO#73A!4?OGo@O?OOO#120!17?@#44G#80_$#186!318?@#224!22?A#229D#209A#74?O#77H#166_??C$#97!348?C!6?A!5?C$#167!350?G-#217MZb?^TC[@C??BBBF?@??@A?@B@?!4@B@DB[BBFFNCNMFFECKrAA@I?FEC??JPB?BABA??BA???H?AAA#180!18?GAKAgIAOOHMoAL@_Po?XMgPA?HADVMVTdm^g~h~]Q|WkQ|{j[h|yycctXsgo`W_wG_?oGoO???_??O#178_?_?s_g?cOo??_G#180O?C??C#188_#171G!9?_!4?C??O?W??_??O?_??_?GC_??_!5?_#175!5?_!4?G?C??A?@#242C!5?@?OFL#232?G?CC?C??@!8?@#164GgK?@!8?A!7?A?G#156A??C?A!6?E#79!6?KO#163g!4?Ok!4?G#157_#202?@#96?S??O??G!4?GA??C#77G#92G#241A??@!9?O#236PWG#235WgmjB#208?E@oww_O#194CG?o?_#153@#134C?W?G?O#220CC???G#147@??C?@!4?@?JGC?G@D$#234@??@?i#219!10?G??g!4?__??__???O#216!7?B?@!5?CCC??G#219!4?O?_#215?@!8?@@??@!4?@@?@?DAA?A?BE@G#214!8?@?@??A!6?@??C?@@!4?@#179?IQ@?@???@C?BBKAB?BUADDRZGeARM]aQ@uNgNPIGsyMT]gGIOI#177O?O#175!8?_GC#196__??G#160o#142_#143!6?A!6?@???@!4?A!6?B!5?C@??@B??B@C@T?P?B@E?A#185__??O??O??O#228?_!6?W???G!9?OA??@!9?A#201PWA???@A!7?@#80_M??G?C?oxMBcA?iA!7?E!4?OWg???_`g@C_a?A@CEAe!5?A#162@!4?K#161A#224A!9?aPwK#202@?Mgw_#169CA???_#143@??@??BAA?ACC@EAEGFKMLILSAJMU]YT$#133_#220CWg??W?S?gOg?GoO??O_#183!25?HJB?_@?@B?PBFBCMC}?@?@!9?@!6?_?OOGGG#215!19?@AA!7?A???@A#213!9?A#214??@A?O???C_!4?G!8?G#174A!6?A!8?@@AA!8?A?C!9?E#150!6?@hHF^IF^N}B~~^|^l~^~~[~vx^~zWz{}GvHwuaIG@CD?E@D@#235?O!6?C??ACC?OGDDA@EBEB?@!4?L!6?_OCIol@E_!4?c\O#186AG#154CA?_?G!9?OC#230!8?OO?AA#189A!6?C#162???c#101O#76?C?_Y?L?COWG_???_!5?GC?@#203C#156@#243CcC#233??OO#229Cs\gC#203@EE#185@#176@@@B?CWo???A#97O_#203C?GG???_o___??_??__#202_#175!4?_$#153O#138_#218CU_?bbizVnS{sGf}~F]|~}[]~}]]}{}i{b{{wwowoowwoooGWg{oSwgwwko?w?w___?oo__Oq?gg?S__??S??_??_#182!30?G_O_?_#173!15?@#215??O#173!7?A?HC@?DDC@OV?eDdJDpI`VfDmSL@FU}YkKcwSO!6?C??_o_o_O_#177!4?_#175??G!8?_#149A#141!7?A!4?C@???C??G#193???_!4?G#227_??O??GG???C?W???OOG?G#191_!8?g#227?__!4?O???@!5?_?g??C#226C#188C!7?A#97C??K???@H??G#207!5?_??_#192G!6?G#155!5?C#79?W???C?a#87?C#163??@#195@#100C#243@?A#44GG?C#63@#159G#79A#239OG[II@scE@#231OG#222??AA#193?@?A??G#166@?EG!6?G??O#222GG???OOOo_?__#148?O$#215!50?@#184O#181AC_#182!9?CO??O???Wc?oCOO??SoG?OG?OO?oW??CcOOS?WO?_??__#175!67?O??_#172GG@@@BBZFHE!5BABF]^G]L^m\~]UuW_oo_#153_?c!17?A?A#149?o!4?@??EI?K!4?B@@#151@#223G_O!4?G???_?_!4?O??C??C???G??@B#225_#159A#237!4?O!9?G#159??G?AG?@aG#76?_?_PB@???@C?Kq#226??Gc#208G?C@!4?_@!4?A#166A#74?A#65?A!4?_??O??o??O__oY?A@#222???@#244!4?@#221!9?O#220C?O#141??A@CAAcD??@!7?C!4?G!6?SOO$#216!65?KK[nNKWEIKCOCc?\GMuAKAKC_@H_wwQWgegqcEsSamSOKwooW_?wcoO!7?_g_GO_U?S?_g?c?_#150!47?@#182_#195_???_#175!39?C#172???CGQCGOsaW?I?G?A#234G?C!4?@#238C?AA?BI?AFAA@A@!4?CC!4?A@!5?_goKA{HT@!5?@o#165?_#157@@???G!9?A#188!8?A#229I?T!8?B#191!4?@#49!5?G!4?__?O???gOOI@E@#160G#189!20?A?o#163?@??@#162O!8?_#193_!8?O!5?_$#184!66?O#181!4?D??@AA?HHAA?@GB?@`FSEC@E`BF!6@GL?A@@C?KFMNEA@Bks]e[wGO#193!64?G#221G??GO#152!52?O#244_?O??O?CGA?CBB?@??@#225!4?O?OO#164_O#92_#176O???_O#244?D!7?OCA???A#191_!8?@#91C??@#90A!4?L?E?A??E?_O@?CD!9?J!4?O@?_???C???GP?@!4?_!9?A#183!20?A?CG!6?A??G?C!4?G!7?O!4?___$#184!79?_!5?__#198!105?O#225OWG#149@??@?@@A?@#221!44?_#216O!4?C#173A#198__??_?G?Go!4?_?__!4?G#161_O??G#185??O???C#198?G#236!5?A?G!5?A#230A?O#93?OOO#83C???O!6?_??o?@#191!4?@#209@#210SG#101_#168O!5?A#85!11?O!6?G#167?A#154G#242A!6?_??o__?_?A#188!13?C?Go!7?C#130_#142??@B?AAA#153?G!6?O!7?__$#216!194?CC#245!56?__OOGG?CCAA??@!4?W#201!5?_!4?G!4?C_?KKA#242!9?o#223?C???@#84!5?__#155CG?OO#96?@!9?OA#196!4?C#202?@#231?@#97?`!6?C@I??L?OO_?O??G?@#229??@#203C#245@!8?_!4?AB@#158!18?A#171GGA?C#138@??@!6?@@?E!7?@?@@??I$#223!195?OO#222!55?O!6?A#191_!5?_#236??__!8?@??AB#162G!7?O?CA@!8?A!8?C@#92A???G!5?w!5?G#224!6?_#100???G?_O!6?OG#67!7?C#155!8?G!5?O#166?C#175!27?O#132C?G?W?oo_#180C!4?G#150?G#218???O$#187!195?_#239!58?_#246???G#187O#225?O?G#176_#231C?C!9?GG?CAG?@??A?A!5?G!9?_CI??G_#95?G#168??O#77C!4?aP?O#166?G!8?O#232??A#91???A??C!4?A_#156!9?O#130!8?O#246@!6?_#168!28?_#150@???A#194???OO#202OO$#221!261?@#164?_Oo#130!13?_???_#160_O!5?O#168!11?GC#169@#207O#232O__#163!7?O?C@#79O#73OA?g!7?o@@!9?G!7?G???F?@??@?_??O??O#226C#168C!5?O#207O#131!30?O_#178A#140!5?@!5?@??B@ACA$#195!265?G#159!14?O#156?_#154OO!6?_#185!12?G?@!5?O???__#196@#100_#49!4?_#65C#93!17?C#83@O???__C???A!4?B!4?A!7?C!7?@#133!28?_#175!7?C#145@#219G!5?O$#188!283?G#229A#187C?O??_C!5?@!5?O#154!39?O#74OC#198_#159A??A??@#202!19?A#40CC#75_#185_#184!41?_$#207!285?AA#195???O??Q@CA!8?C!5?@#77!32?G@#156?C?G#212!24?@$#226!285?@@#72!57?_#56A#195?OC$#186!347?@#169O-#131_??_??OO#130_#203O?_#217!10?@??KW?POCK??ABH??@??@D?O?Q#219HIgwOo?o{g}WM_}{MwpmyczkmWm{uWz[GO_Cw_?__Y_e??_?_#183???@!5?ECEA@A#219???OO??O#180!6?@BQ@?F@EB?@AI?FBVLEUVhNTTkZVV^X^\ut{p[aoa|{[wqgowyeCwgGg_OcG#172?_?C#160_!7?O?A!6?_#149!7?_`A?@?HC@#175He@GaGrC!6?O?GGC#231G!8?G#228O#164o!4?MOC!4?@!6?@@@#132?A??B??C#231_O!5?G?@?OG!9?XAAL_G#188G#95@OD??M#80?EA?wK`_C_kOeGOB!7?CD?_O???C@??A@??A!4?G#72AOJ#42A#49OO__#97?C???_#188_#208_#240?O#246?o}~w#236?@C#229`~|fE~?AA#202?_wk]KZECGG?G#195G#161A???G?OAC#196@???A?_wW#221O#225?@@@BG?E$#133WkMC_o__#234!19?W???O??@#183!13?E@??_!7?@C!5?@#182!12?@??G?P@???B???@??A@A??@@B@@A@?C@BBAGaSULNCBENB[K??Aoe_w{[{swoO_o_??O___O_?__!7?_??O#175!10?A?SQ?G???g!4G#162_?GW??_@#141@??G??_#174GO#153!16?AG?_?O#171??A???G#195_#225?_??OO?EA!5?e?B??_K!8?_W?@#130?G???C??E#134_#194_O?A#232_?OO!7?_!6?O#195A!7?@#191O???@#77OW?@k?A???PA??BHG#195_??@#207A#101_??_#91A??CAA?GA!4?G?AG??A!4?C#41G#163@#47C#83?g_#245CA@@@???G@?A_#231?@!4?G#203??@?f^EB#193@R?GO??A?_#131?@B?ACCGO#120O#198BB@?AOCei#208??_#224C_s?o$#97@B?I#134KGG#220DCK_?gwGv}smytuY{|Pcae?GamwOkugdywyiwhcY`OcU?I?SGA!4?O@#193?_??O?O??O!7?OkO_?GoOO_???_???__O_O_???G?_O?o_?O???_#195!11?_#181!19?AA?GG#179!4?@#178IBEADNDA@BFLVNFDXXFBdVViOPAECA#161?A#164RC_O??G#121W#172@@?G@A_?i}@b?FNB!8?c?qU[CGo@FNBENNNBEBB@#117_!4oO#195O?_?G!4?KB??_OS???A#118?O??O#123??C#70O#89?G#238_!4?_?_o__w[GDoNz?@!5?OG#199??C#168?I??G#163_?_#72G??C!4?E#97???_!8?wOW_O?wGG#79A!5?@#76@@?@???@??C!4?O#93?H_#233G?KK[E!7?IG??O#208???_PO#220?@#194O_?C??A?OCOGO___???@??KeWcM#223!7?C#234@Q@$#135A?@#141@!4?O_#216!55?@???B???C?B@eC@!8?@DC@??A?B@!7?A!6?W?@ECaHa?AK??[AOl}\GWXCBa@@FGkGAXhgEOIG?C_??e__#183???G@W???A#173!14?DJCTPR@@#176K#185caC_?@#119__W_#173FF]\VNP@}[~wo[[|~}nqz}OO???_??{WO[?O??C#141@#197_#161_?G#162C!9?@??oB?A?@@!4?O?@???@!4?@!9?A#241???B#237?@!9?@#233C?O#90???`!4?BPCR@?MRB??L@???D!6?I?C?g?@!5?CCG???A?AC???@@???G#74?O#185H#239CKQQbH^B???@}O#234C???O??C#188!6?__ogc?k??o_O!6?A??OCG#227!10?C#229G$#71C#88O#117oOO#218A!5?O@F@?@JPD?HCBAaBDGnr@PFkO?VYCFDSAUJdK_O@FDNjF@V@foJ?BPEK?CG?R?AO!5?EBHQDEEHG???XZ?lGSQ@OGO_C!4?C!7?G??O#214!30?@!6?AG#181!6?G??_!7?_#179!7?a#123??_#171OUPGO!5?E?A!6?C!7?A???OC#150???@K@@BCJ#219?_#222__#180O!7?A?@#160?C?_#198A??GSa?q!6?_g@#231@A#119!9?G#236???o??O?_O??WCBC!4?G!7?O?@#92???A!6?O#73?A!4?G?_OCEC!8?A#74@#185C!6?O#196_!5?_#75?A???G??G???C#157?A#242A#79_#243A!4?_C??CW#235?_Q??G#182!12?@??C!8?_#132??G#195?C?G_O$#171!4?A#219@BAJB^NU?uG!4?I?_#184!43?A?@?C?@`@?G@?a??E?A?GEE?WWeC[QSGKkMfM]w{owWgyKC{k_G???_wowo___#134!61?_#195???@BAJ#201OC#123?_C?O_#198!31?_?_#194O#196Oo!6?A#235@@@O?C#168@#238??@#154?__?@!6?_??CAC??B??A#188?G?CA#227@!8?O!9?E!4?`A#159?C?@?@#76??G??@??W?@#155???_?AC???W#100?_!5?C?G#77A???A??CD?C?@!4?A@?@_O?O#158O#226O#241???_#242!4?@E#224!4?A?`?~WkG#176!7?@@???CC?G???@`?CG#203??@?@@?C}GAO$#184!4?@#161C#182C#175G#220!72?_#183??@#181!23?@?@!5?@@@???@?@#191!71?G?A#221A#122?CGO_#146!32?@#221???_?G!4?@#234@#118G#176A#188K#244?C#187?A#160!5?G?_Q!7?E??AA@!5?A#235O???@???CA_?AM!7?o?__q?O#87???A???C!9?O#157!4?OG???G?C!6?_?_O??_?O???_?_#164O#95G#45_C_??G?B@#198C#222?@#185!30?AOa?q??G?OO?_#222!7?@?A@@@u[iWAh$#198!195?CcK#117???O_#184!40?C#223G??A???_?@?_[C?@!7?G#235A#159_?A#185!12?C!7?@!7?_??CC#225I!5?AO#162??_CA??_#97_#96??OO#83K???A?O#188?_#202O??K#226@#158C@#162@?G#223A#154C#93A?C???C!5?G??O#185?O#63C!5?G?A#175!35?@??A???G#119C#160GAS?g$#187!195?@#196O#227!47?C!7?@A???Z!9?O#201?K#195!14?@A#233_??G?O#243!6?o?D#228A??O@_C@?A?C#154??WA?_#88!5?_#79??_???G#191!4?G?A!6?@A!4?_??O!8?_#54?A#44_#66?_#156A#184!39?O#171@???C!6?_??o$#156!244?O??C??C#245??G#132!7?O#117O?G!9?__ooo_WOG#223?G@#207@?E#228C#201E!9?OCO?`!7?_C#186??O#155S#163!18?@!7?O!4?_!7?_#166O#100??G!5?G#134!43?@@@A?D@AG???_$#87!244?_#120OGGG?G#121!11?_G?A!5?W?G[G?C?@C?E#225??G???A#196@C#245!7?CA#186O??G#226??G#157!9?G#159!18?A??O!5?G#231@#98?O!4?@#188?_???O#159?_C?_#141!50?C#118A!6?O$#174!245?@#154???C#120!12?GEC!6?__o??GGG??C#191!4?C#226G?@!5?_#100!5?_#164a!7?C??_G?_#198!19?_#168@??A!6?@?OO?GGGW_#65A???@@?C@OO?cSQ#164!46?O??A$#185!249?A!12?@!5?_OK@#229!14?G#198E!4?A#209!8?G#191D#223_?C!4?G#209!25?O#166A???A#83!7?@@C@A@?C???C#130O#154G!7?A#162!47?@$#123!262?CO#156?C!5?C?A!8?@@!7?@#210!10?G#159B#187!33?C?C#155!12?C!4?OG?O$#161!265?G#176A#228EC#232@#230!18?G?G#187!12?G#197!33?_#186O_@#207!11?_#201O#73??A#87C#186_$#208!337?W#189_-#133FGDA@@@??Oi_o#219C?C?A?O@#221OO_#222?_!4?O#219O#217?@@!5?@#219!7?WhCbFE@@D~HLKLp^rMLBFvEMLAYD@VNQZMPlAhhL_BcVGMFD@@?CA!5?S?A??`WC_WD_OK?_ggO??g???Gc_??CG??W??C???G#184??O!9?_?G#178??@#181???KC#175!6?A??C_SAA@#123A@oCO!4?_#89?C#118@#122A@?@!4?DEUAC??O#149?O!6?G#175SEG?A?A#201_???G?G#191??@??A#160D#117@@?@#156@!6?CG#196__!4?OoC!5?g!6?A#237?_G#233AF?V@@_??_#245???A#209_?A#159BA@_#157O???G?C!5?O?A???_?@_#73G??G!5?o?w!4?o!8?A!8?C?_??o??{!4?_?_?A@#97@A#233B!5?_OW_M#232O#224??lh]e_oOowWOww[WO#196?@?AA?KCWW_G?C[@O#208?c!4?@#229_#227??_$#117O_A@??MC#97RH?S?O#161O#203O_#218!12?@!4?A???@@?CGB?D?@@!7?O!4?qG!8?@#194?W???_O??OiO_CSO?_???_?G#203!8?O?_C_CG#194?_?G_!4?_?O?_O??W_?_?OcWO_GS??c??g_??_??O?O!4?G#176?C!4?C!4?C??_!6?O#141_#171GCA!4?@#188A!4?@#109!5?G_P??_#123@@{y[wgo_???__OO#178I??E#244_W???@#196G??cg#176??CFC???A!8?A?O?aIGC???O?O#171CC???A#231_!6?g#230W#226O#241??Y\^??GGC#231?O?H#168G??@#191@CA#93O!6?`???c!9?_#226???A?C#224?A#186C?A#155@!8?A??C?@???O?@_A#65OGC?A?C?@O#77?@#84@#90??O#157?C#239K?_OWCC@#234AC?A#222!4?@??C??C?GCA?_c?_!4?__!7?A_MOQAAC_D??E$#123G?WCk?OO#68C?C#220@BBFJ^|~n}nn^~^~~~}nn~{[~~~m}}zv{~y~}eUz[gx{gA?uqb?A_Koo{wGxooc_wwG_kc`C?[O?_GWOGOOO_?S?__?O#217?_#183??_C?SC#216?A?AG??AASCOGg?Oa]jSBIGf_OPg?@??CCP?_QA??C???@@#183???C?@??C#185!10?O#183?gK#131?_OgcC#119J!8?GM!6?CKGGG#141@?_#172CCC!8?@@??@#150A@#227OG?A#198CFBFA???o?OGC???@A??A?@!8?AB???o??_G#161A#242_??GA!9?E??C?GCK#201OO_S??Y???A?A#77oO???_OG??@!6?CSG#195@#231A?A#101??G??@!7?@#166?O#185G??@_?G_#76G!7?@??W?C??C#159???O#243_???C#231O?A?A#203!7?@B?CC??AA?AEI}[{[WW???_?_O__sMpB@@@B??@$#70_#131E??A!5?O?G__#234!18?A#221_???O#193!13?O?AU#203g???O#222?C#184??@#193A!5?A@DAE??@???A@AAATCJ?E?gQ{i~X[}k{MLHAOr_AFWA@q^jo[G?DDC{E!9?B??Aw?Og???WCc!4?G??__#133!23?OG?Og#132?@!7?@#171!13?@???G?_?_O?G#164_#222_?O#239o???A#185__!6?}xGGG???A??A?@?w_?BAWo?J??oG@??G?CC#194CC#235O!4?O#228!7?_!8?C!9?@#80og!6?G??AA?GW???@?O_O??_!5?G_??A!4?C!8?A!8?_?e?A?A@S_[#168g#242O_OG#236?g#229GgDXpl~~QU_O??_#188??@@!9?@??@???A?@#223!4?Gc{??A$#71?@!7?_?G#134?G#184!74?A!4?@??A??A@@AB@qAXHGBW?@@???C?@BBAABB@#195?@SA??A#180!19?@@@?GB@@@BCAA@IAB@BtDJ_lNFBBJD@#134C???a#162c?CO??E#173!15?@BBBF^f^FNFF?GF@#246O#238_W#202@#207O#195OOowOO{??o??GC??A@???@?@A!4?O_NJ@?E__?OGG??C#243O_!7?OCA!8?B#165?C#227G!4?CC???@#155C??O!4?_???_#239A!7?CA!5?G#97O?CK[WHG?OG!9?D#96OGC?Wg??o?C?K?GgKA#202@#245?UKC?A#244B#208!10?G[GGG?Cc?C_#221!5?_#198?_OW??O!5?@#225??G?G??GI??Q$#121?O#119_g?U_H#135gC#150@?C#185!78?_#218@!9?@#220!10?_#182SC???@!8?D??@WDP?JCeBdEZjRRMvFHH~nzmu]}US]yq{y{xytpwy{IYs^O_wO#117???_O??C!7?oqo|}~u^mzrQo_#160??A!8?O#156_#234??_??G#177@#243?C#245A#223GCA!8?o_o???MC#187@#231O#244C#201C???O??_?_??C!5?O?OG#134A?@#236??A@?B!6?Oa@#162???__!4?G?O!8?I#120G#83C!9?G??@#49??_#79O?O!6?O#130A??C???@#232_???C#98_#93_G#49?A??o?C??BB??O#87?G#246!6?HBBB@#194!13?@??@!5?@@@?@@?CBAdACSkQ@G#224!5?swWQSNXl$#122???OOg?_#102?A#183?A??G_#171!76?_#185!63?_#88!29?GO#121?WG!4?OG#176!19?G#180?W?G!7?C#219C#228???C#187_#225@???@!7?GCS???A_#120?G#188_!5?O??G_#191_#156@#130@#123A?A#160A#223_#225O#227GC#232???_O_gG!4?_#223!6?O?_!5?G???A#75O#87G#154I???A?cG?G#97@!4?C_A#187??@#207@??A#196A#233_C#83_?G??_?_#76G#208?_#168@A#236G???@#154AO???B#72B?@#67A#54G!4?A??@#235!6?__?OC_#202!10?ABBAA??@@???AA?_!8?GA#226!8?C?_o$#88!7?A#172!178?@#122A#185??A??BI#198C#218!29?O#235_???cGDA@#164!6?A#162?@??A#227oogOG??O@#132O#154O#164CL?@!6?OEA?C#221_O!5?@#187!20?CG??_#100_!6?@A!6?_O!4?_?A#67!4?_#230!4?O#166A#157A@A!4?@#77???A#159@!7?OG#42_??@#68O#66?O#79G?g?oSq??_#234!24?_#220?@!8?C#195B??ADI#193@A$#164!191?d?COG#193!31?G#161!16?A#121A#118?@#234G#228?_??_#168???C#160G???C#197??C#157???G#168G#120@#118B#117!5@#238_Wu\DC?C_m_??XNTqztJ#154???@#164aA???G!7?@A#225??A#91C??K???O?O_C#154??G#235!4?G#208O#165C#96C?oBDQ??D#229?O#226C_#201A?@A#47O#162??@#164A#63?G#61_C#58??C??G!4?A#185!42?@?@$#160!191?G_?_#134!50?C#235!5?o[?I#162!5?C?A!5?G#187?O#244!7?GC#235!21?O#185?C_??B@???B!4?@??O!4?C!5?@#236!7?C#159??A#87??_#156?_#158C!6?_#188O???_G#202C#171C#45??A#83!5?B@$#161!191?OW#176G@#232!57?_#238K#161!6?@C@#70!15?@#156!24?W!7?C!4?C!6?O#203A!6?A???CC?C#235!14?G#231CS?A#95?C#191???@$#195!192?B??B#132!82?A#197!25?E#226@#192@#91_#90_oG??_!5?O?CC?O???P??_O??G!8?@???OsI#238??O#198A#241G#207GC#223?@$#156!193?_#201C#95!113?OG!4?O?_W#232?@#202@#166O???@#162@!5?G!5?_!4?@!6?_#242??_#243O#196?@#186O$#188!308?A#163C???C!7?G!4?C!9?@?@@?G!6?A!8?GC$#207!308?C#231?@#168???GC@???A!6?C!9?@#227!18?A$#159!314?C???@!5?C???A$#92!315?_!5?@!4?O!9?G?O$#130!323?A#186??_#188A!4?CG?@!4?_$#167!323?_#222??A#96@#72G?G-#117BB@??_?GADB???@#188_!5?O!7?O#202OG#221HHGD??@?Oo!4O!4?O#176?_#218!7?CGS#234??_?O??C?AE!9?C?AA???AA!5?C#183@!6?C@?HG?C#216@?@!6?@O?D??eAD@IADAGA@C!5?GA!9?@@#184???A?K!4?@??@@#175??_OG?AA@#160oO?OOERoCCBOC#119wg?C??GCKC?C???GAC#141A#134@#160_??O?B@_#223A@!4?O!6?O!4?oGC@BBAAA??WCoo!5?o_!8?!4_K??O?cC!4?GG#159_?_#231O_c?o???C?GA?CC?C??OO_#198G???O??_?_#186G???_??G#73?_#77_#54@#76AA???CK?@#85@#157?A@??C???_#83?A?@#235_?_?AAAC#234_#224A?A!7?O!4?C!4?CK???_OA@`??@?@G?G??KL}nso?__o?_Ow_owog_sW{V`gFMkR?BD$#119{[uwO[{vw?co#160O!8?___#134_#161__#219?G??A!4?G#234G!9?@O#185_?_#217!9?A#183@#221??OKIMCAA!7?@DC?A?@@@???@??B#193?@??@??@@B@??APBB??K?D@NBCMIC!4?_o?!4o_gc@em~Gxo`r\oA_oBq_?A_?o@???K#180!4?O!9?@#134AGN?AA?C?@??_G#117?Unz~ZFBpZRjy~^QwG#75!4?o#164GC???__!5?_I?AAE#198G???CCCA!8?__?Gkwgow?YK\q??_gs@A[K@#238W}FBA@??CEFBF@A???SFB@???A!8?_#154A???A??@CGO#168O???A#164O!7?_#79C!6?A#188??_??A#202A#189@#91O#159G??@#49O#156A??@#233__?SOEC#230o??o#212?_???_#157?@#235A??@@@#203!9?!4CGAAAEA???A@?@AC??NFACFLFFB@FAC@?AA??@?@$#122?_GFmAB?DyOF#109A#203G?C???@!9?KG!7?_O#227_#196?_#225??_???O#203I?I??@G_?A?cIO#235??_#244_?G?_#225_!9?K???G?H@D!4?G#203OACG!4?C?G@C?K#182_??OO#220OQOC!6?_???_@C#182G!4?I@??eA@CKAJs^H[HU{g^vMk~rWR~~w^m}U~|NND@#131GGKC?@?@??@G#121GK?rF@#122O!5?A__?C??@C!4?C#121?_#195O?WK\[wSo?_S!4?oB?BF??A@#235?OOG??O@@#201W?@O?SG?ICqAC!4?GSSBQA!5?G?G?G#154_#227C#210?G!6?G!4?@#164@?@!6?C#90A?@@AA#157C???@O?AOC!7?O#67?C#96O!5?E!7?GO#176O!4?C#239_?][WG#96??@@!4?@#92@#90@#87@#210G#239???@#230A!6?@#221!9?C#202CCC#222@@?@!4?I?BCS??L??QG?KUG@@A?S@#229_?O_Gow$#123!4?@@!4?G#133GD@A#235WWO#220BAABBEF@FHF@D@UUFIFFINNFMFNFNKBFDL@^MEeML|!4?JKBD@B!4@???@??AAA@@#161!4_!9?_A??GO#123_O#160O?_!7?_#176_#221!10?@#184!7?O?_O??O??O?_#185!4?_??A!7?A???C???g??O??C#171C!5?CC#164_???`_#109!7?C!4?KO@?_C??O#185!5?__?P#196??E#201_?O!8?BC#176??B@#231??___G?g#195???KC@@@AA!4?GN{^B!9?G#187O!6?O#236?A?A??BG#191_!6?G!6?C!4?GG!6?`B!6?G#154O#166G#65?@@#223???@#185@!8?G!4?I???@@#243O#229??_?IWwIc}K{[[s{CCeoa_uQeYQrirPPbpO?poooO_oO__?_#221!5?A#225??_!9?G??G#234??G_?GaC$#132!12?_#195O!5?_#162_!7?_#218A#193?O??O#194OO_#217!9?A#219C??AC?@G@@Q?b??J?@!9?@?A@??@#156_#162OO!5?_!9?O???O#89_#117_#85_#195O!9?_#219!35?@#188?O#183!6?@???A!8?A?@#133C!4?@#86!24?_?O_!4?A#225GKCA#203??@#224@?E#227G?@!5?OWG_??o{G??sgFw?@#185?AA#196EADD@@@?@o??S??@#231??_?@G#188O!9?O#228G@!7?A!6?CGA!5?OO#155C??@C!4?T?A__??AOO#158!8?_#156??G#162G?G?_?_#197B#79?O#72M#65O#203O?A#231???__??C!9?_!6?O@@#234!14?OG?WG#223!10?OOg#226!6?O?_?_GOO?ACCGA$#134!12?G#97A#234G???C???GG#171??O_?_!5?_#208?OO#194!9?_??OO_o?O!7?SA#245??O?_SC#195o!7?O!4?O!4?OO?__#202AOAC?A#220AA#218??C#194???oOITAC?G??hO``aT]HSIYCHALCDQOGO??CEG??CH?E?CG?C?G@O??__???_#161!6?_!7?G?G?GKaA#120!27?A#198OA??A#197?IA#191_!7?C???@#209?O#221!18?C??_!6?GG?@#227OB#228?_#191?G?G#155_???_#163?_#186?_!4?O#168C!9?@??A#233_#80@#83A???@#185G!7?_#83?C!9?G???@??G#195?A??@I#192C#166C?O#80_#93@#73G#225G#236C???C#208!5?O!6?A?YWGWWGkGcg?SKmMKKiYIJAHg@?cFUrO?O?LwXJ#207?G#198?B#208!8?A???C?P??O$#171!13?c#227O!4?O#223O#176?!4O?OO?_?_?_?_#193!13?_!4?_!5?O#246!6?_oWG#231O#185?A!5?G?O!5?OO?_#196g@#188??_?O?G#134{o_#180!9?_#161_#132_#176!47?C!5?@???A???A#89?O#130?A!4?G#129!31?@#162?_#186!5?G??G#156SE?C#225?_G??WG_GA?CC!5?A#188!13?@#197??BA#237!4?_!5?A?A@#157O#207!4?GH#224@#72???O!6?O#226A??C!6?!4_#97A@!7?B??B@EG??O?oqao[w_O?A!6?_!6?!6@??@?@@!4?@@#236???@$#185!14?_#131B#222CC?CDCC@GMGE??A!7?C_?G@g_GooGG!5?O?O_?W@_c_?O!6?G!7?G??AG!7?A???GG#176OC#185??G!5?_??_#123!63?o_o_o?__?_O??OOO!7?_ow!8?@bL}^zND#188!8?C#207G#235CA#154_?O#185_H!4?A#191!26?A#235!8?C??c???E#230@!8?A#54???_??O#45_#33_#225A!4?_#232O#93@@C#77@#100A??C?GQ@???G!4?C!5?__!9?C#168g?_!7?_A#246W#245K#226!8?G!5?G!4?DC$#193!14?C#150?A#244!5G#184!9?C#166?_#242!38?G#132?G!8?__#187O?O#157?_#191O#201G?g#229??K#219@???@?C?A???A??SI?JIC?wQ_CYWi@OG?C!4?R?G??@?@#141!34?G???@?C#154_!7?@#226!33?@#229@#231B#238@#162?@`O#232??_!8?G!4?A#243!28?@#164_?O#95_O#233!6?OOg?C#162GA#223@!4?A?A?O??G#162?C#188C!9?O#158_!5?O#187G!14?O#164C!6?C#95C#130_#77C#101!12?@#203AA?aA?A$#194!16?@#196_#198_#185!12?_#160!42?sA???GO_#223?G!7?C??WC#164?_#130?_#184@?C?GGAM?C?G#119!65?O#132!5?A??@?A???M#87!38?O#120G#93G#196??C#228_o!5?C?P@T?CE?B#167!26?O_#156?O#100_#205!6?C#199!4?A#156C!4?@!5?@!5?C!5?A#208CC#169C#101?G!5?C!5?_#196!8?_#203O#163C??_??@!4?A#222!13?A$#201!16?_#202@#231O#193!55?@!4?E#198!4?G!5?G?CSCO#171!4?A!6?O#156!75?O#162G??_#92!43?G#161C#168@#233!46?A#232@@!4?G?@??G#41!4?Oo_?O??_#195@O!4?G#230_#203G#91?@?A!9?GP#92_???_???AG#154!9?@?O#231@#207AC#155?G$#219!17?A#117!57?ws__#120O_#235???CC?AAA#132!10?_#130!130?_#226!49?C?G!4?CSCA#235!5?@!4?C#227?GGAGGG?OO_#166??G!4?A#209A#192_#95???O!7?O#226!12?_O#208C#237A#232@#201G$#85!75?CGG#75O#86_#231!6?CCC#207G#185!191?O!5?O#95!9?G??O#242C#7__#157O#159__#163@#197!9?O???G???@#90??A?_??AKGG?AG!4?UD?@#229O$#194!76?@?A???@!9?@??B#35!201?_#92G#201H?G?G@???E!6?O#163??O!4?G??@??O$#176!76?A?C#227!8?G?C??A#245!206?AC!7?_#210!9?_#159?K!4?CG???C!7?@$#70!77?O#89G#246!220?C#29??O#197??O#196!14?_#80!7?C???@C??BDC?OM??@$#134!77?C#84!250?G#93B-#123__o?O!7?@M!6?A?A#141O?C?@???@#71_#133pW?{#176@AC!6?_a?_JCP?B@@A#236??_!4?O#185_???GCA#120O!7?B@#119?A#132OBB@CDE?CI?IC!5?O???C?BA_C?C#70?BA#219?@??@?@?CGA??AC!4?@?CCG@C?S??C#188!5?O!4?_#216???W???C!4?@#180@#119?@#164O#132A!5?C?G#185???@!8?_!4?CG!6?AA@???A!8?_O??A!6?G#209??@!7?O???O??W#207G!4?C?S!6?NB#228_???_ok_a??CC??R#165Q???A#54O??@!5?_?G?AC#238@A?FGA?CG?O_#224???G@G!7?G!7?W?O_#197G??@?AA#233K!4SOOO?W?G?A?W?C??C??@#210G??P?@#224A!5?O??_?BQ___?@_C@?_@?ANKDFCCBB`?KaHKKKMHKMKLK!4?CAq_k$#119DO?o_p!4OB_qO#161H?@??C?oO_?@B???@!9?G?OO_!6?o???CC#246_OOGGC#243G!4?A#161o!4?C#117EC?@FBo#130A!6?@!4?C???@#120?C???@#63_#89KG???O#85?W?c_!4?__#220AG?@AhCGC?oIC?G_?o?_gg?g#185!16?_???O???A#121G!5?[E?FA_?OWoKKA_#117o[^LN@!6?@F?FB#154GCC??GO!8?@???cC???O_#227@@??@?C?GPMG_B@??_KI!5?O#224?O#197?MC#227!5?oK@???KOK#187@#156@!7?A#35_#30o???O??W@?IK?@#83_CO??G#195G#236@?A!5?__#208??C!9?oow_o?_?_!9?_#163@#239A?@#212???C#230!9?@A_G#203!9?@??_C#234!10?A?D??C?C@!5?A!8?A??@!6?@?AA??@@$#122Q@IHGIGh`Gc[K_#171C!4?AG#160G_G#131?wGCCCA???em?{_??__#234@?@!7?_!4?G?C?A!8?@#121O!8?KWFTLKo?_AwGgOCCOK!5?wB??G?_???B!5?G??O#171O?_#193???@@?B@?ACB?@AC@?B@JRR@_IRm~EwHV@!7?B#171G?C_?_O#89??@!7?_?O_?YA@#134??_!5?A#161A?G#156_!7?_#39??O??EB#120O#95?A!7?__#201O?O?PES?_!7?a??@?A?@EH@?FGo??`eB#231??O]H?O??PWW!4?a#201C`#93_?C#47G?C??_CA@_?G?Wo??_?_#241C!5?_#210!6?OG?_?G?__???O?oO_?_#185@!7?@#203A#230??G#208!16?_?_??_CDgCwT`G?HgKGgsCHG[wgYyKx]Ws|?OG??B??C#226A??O!7?@A?E@@@A#208?CG$#117GIDEFCfEIfW@#121?@_!6?B@#132???C!8?@@?GW#223A@#185G??OO@a#182A!4?O#245__O__O?GCEFFB@@#176OG?F#71_!5?O#123?cwgaoK_?o?_O?G#119??O!8?@#75_!6?@??G#66_#194@B?@EMADSZuwzKsj|q[xIDAQOGUsgc}Ysg??w@SGYg|CuOC?C#134O#175??A#162!7?_?O#154@?@CA???@#119Ka?A#171?_#195?O??C!4?O!8?GC@!8?_???AEBJAgoAW#228?AO?M@FI!5?O#226??_goO_o!8?oK#210A#232_sE!4?C!6?_#83??O#91G?C#72_!6?O?O?CG#78G_?o#243?ACKOo_#228E?HDWA__???c?CCC#201A!9?G#231??G!5?GG?AA`Bz@YOT\FF!8?AG#212!5?C#222!18?O??_G?o??G@G???c?OCP?_?o!4?AG?G?_??O$#109?C!6?C??A#164??A!4?@#134CCKF~A?ABB??@?@O?AO__??_#244@!9?_?O??G?C?A@!5?@#156_#86__!6?C#131!7?]Q!7?IAB??@#188_#123??B#67O#162CG#154C??C#69OOKGs?_#123G???_#184!13?A!4?@!6?D??@???_#195!6?GA#160_?___??Ggg?__OOGGA?@D@A@!7?O!5?C?G?GCA!4?G#89??G!7?C#156I#87?G#168??@???A???_#231?NF?OO#195!4?@B??C!8?@?B#235!7?A_?A???C?O#191A!7?@A#162A#41KKLACw??CA@#77@#154AC#245@???O__#226@B@FEIFTYFu}S?C?GAKG??KGK?OGOowo!4_#203!57?_oowo{{WG_C__oOo?oooO??wowW[GC$#132!14?O_#194GC_#154?@#117!5?oOWg?C[C#166!4?C#202B#121O#164G??O#203C!9?O!4?A#231_!7?C#164_OC#75?O#70_BEA?g#134!8?GG#156@E!4?@#70??O?G#93!6?O#185@?@#171B#104??O#63O#180??A#86OO_#119_#216@#195!15?oW?C_#176!20?_!6?_#156!7?_#120??_!5?C#141!8?GD#227g??OOO#164G!5?_O??G!7?@???BCCGG???Ac#223!4?O!4?k}@?K?C@O?GaIHg`!4?@{??@!8?A???A?G#95??A#197@?@#226@#27_#29_!6?A#43???_#201A!4?_#230@?A??G?O!9?A#159@!5?A!6?A#195?@#232G??G!5?G!4?C!5?C`!5?O#225!40?A?A???@???@?A#202_O#194_$#176!15?@a@?g_#97!6?G???AAI??A#193?@#195?C#225C@C#196G?@#171?@@oBMIKG?CC#239??OO!5?CA#196C#198A#201@#87?G#69WwGww#118!12?O???O__G??CK#195!7?@A!9?CC?O?o#182!16?A!4?G??@C??@EA?cVABHfXJGFF@#225!27?_G#198O???_#109E#89_#86O?GO???`O!6?CO#198!8?C?S!7?_!6?[{!4?AA@?CA?A?z[]W#237!6?GL?@!7?O#100???C#45?O??A!6?O?CA#197?@#95G??O#210O#232G??CC#207!8?@@@O?_?O?A#97@@@???B???A#164?C?@#223!66?@@@#227???A#188!6?_#196O$#185!15?OC?OO#175O#119!6?__?{w#221!8?@#227AC#188G??GO?O#175?G?@?AA?A#222??B@@#242GG?C#162O???G?A#85@?@#64C#162!13?@#160A!7?C_!7?W!8?AC#221?A???W??_!5?O#123!32?_?OWSKFEF|AXNGdQ\eAGaOwC@?_oO]NFAB@@A???CB@H?G#121!7?_#77??_OG???_#187???C??@?_#238!5?_#232_#187!4?A#191!4?C#196C?C!6?_#238!8?O?@!4?ogWC#243C#157???H#42?_???A#33A@_C@#6@#82!5?O#56O#235A??@#246OG#237?G?O#223!8?GGoGO#156@@#229CCK!4?G!7?OOO__h_cMk?e`fa_w_zY}|ZV~Mtv^iyUxAi]VzeVrVVGhUVBDU?@aA@AA??BA!6?OOGA?A@?EA@??@?ECC@`?AR$#195!15?A?AB#88!10?O#156!11?O#231A#235AA#194GEKSKC?_!5?@F#229??_#233o#241o#228_O#160_!5?G@#66??_#164!15?@?@#85!4?g#176A!7?C?A!9?C#182?G#161!44?O?G??QO#228!25?_??O#122??@#63?__O!4?A??_?GC#225!13?G!4?Ax??_??T!5?ap_?O#233!17?@A!4?_G#163B#164@@@#92!5?C#43GW#159@#26O??O#74A??_o!7?_#231??A??G?O?__!5?AA#164@???@#188A#168A???A!5?C#227O#235GG!6?@!6?G$#196!15?G??C#224!25?C#162_#198??G#180!4?O??G#220G??@#238!5?G#191O#132_#188G#226A#109!6?O#161!16?@?@???B!6?AA!9?@???G#235!77?O#176C#223wg_#45!4?O???C!4?Oc??A#191!13?C?G?G#221!15?@#230!21?AA??C#242?_WG#73!6?_#28???O_G#22?CA@#94!5?G#191C#227!4?@#242?O#227!10?A???A#154!6?@#155@!4?CC?A#93@#236CC???A$#198!15?C#203O#188_G#222!26?A#141!7?C#193???@#235!9?G#223G#117!25?_O_??S!4?E[o_P_?Gw_gI?cA[O#75!89?_?s?EC@_?WpWdbCK???OO_#186!5?C#155!44?@!5?K\?A#168!17?C#233@#191!18?@#185@#209O#158!7?A#169A#202C?CA@?A!4?A?@$!17?W#86!77?_???_?W#134A#90!8?C#120!5?@#25!93?_#162@O@?C@A@!6?P@B?@H???_#185!46?A#236??_#203!53?C#157?@?A??@!4?@$#154!95?A#88!5?_OB#90!108?G???_?CA??G?G!4?_#186!112?C#91@!5?A$#171!101?@#93!111?A??A!6?W?aACW?GOO#187!111?C#191!4?C$#228!349?O-#119H!7?qOoGdBXH!7?C??Aa??_C!5?G#121?I#132???D#161?FH#176@!9?_?GC!8?@#71_?G#119?AA!5?AG[AP?O[??AAA#69WME!7?_SLHbo?g_#89?O???@@@!4?@#132@#162@??O_#197?O#176?OO_??_#223!8?__?!4_#203_#198_#222_#164?_?_!6?o?C??C_O__B???_?_???O?C???wWW!4?A?@!4?o!4?WO?_??_!5?@??GW!5?@??Cs?@!4?c#154o#238?o?[W__#230AO#226POP!7?WyWXoO??C??_@#185_#236C??@#9O_#47O?OI`HB?__?GcDAq??C?A~?D@_a#236@!9?AC?g#208?@@@?@?E??KHrw?AA_FAAEKf{YmosSo}}{{yWW{wwgSlykYILECKNMIjC_ONImNBSFVFNVNFTN|LOPC?__?a?oO?GG??_O!5?_??o?W??_#225O#234?A$#122_@?UqDaC?eIpA{CO#131`?__!4?EB!4?G_???]I@??N?@??O#171e?G#244_??O#246EFBBB#191G!5?A?_#121_G#117CW???DCA?AMNXO_!5?Og??XFOO?GGC!5?_?CDGAG\dA!5?@#198oG!4?_???WC!8?__#188!9?A!5?H#195Y?@?@#132?I#121DGC#176??L|aAG#161CG?@#156C#121IB?O??G#195__?AGKC@AAA?@?@#120G!6?O#123@#93_?COO?oO???_]?K?Bc\!4?ACG#162?@!6?O#209@#206???_#229???_#195?__!8?@@#231?A@?W@???G?@?A???@#42G@A!5?@E??O#55??@??@???E??G#191@#36_#231A???G!4?@AACCG?O?__#223@#207@?@CCA]KsSws[oO#203W?_?GAAG#230@??A???@A?CAQDA#226?o???@OoO#212??@!4?_#221!9?A?A??C@#203@!5?@?_cec{Ni}YzmMEKDLlCfFEC?O$#117Q}~hLqHzCHDAW#134???Q???C?CA@#122!5?@@#123!4?CAqT??_???O#162G!8?_!6?G??G#85?_?A@???G!4?O#123CBBcm\IB@A?C!4?O#161?_O??G#198@#66GAeO#104?O#75O?_#160?Wc?A?AA!4?EEC#130C#196@?A??E??O?O?O?QoXKW?O#193??@!4?@E#160?_??G?`_??F!4?@@A@CE__!6?CCA#223_!5?@#75O_!4?EJ?CD?@_A?B?DAM@?_?@?__???_HABDC_o#159?A#201G?SO???A_!7?_???LeC@i}?A!7?A?CBOGg??C#7_#30GAA@!5?G?@!9?G!6?G#245@!6?C#237OO?_#210@AA??CACGIYAo!4?@??H_G@?@!4?G#202!6?__#224???O#203_!7?G???CCHKM?C??C#194!11?A???A??@#234GA#210??O#194!7?C#223?OoGO#198?A#226!4?@@@D$#109C!5?C?H??C#161!4?CO?CGJA#117_GsLW~^EAJ}G??C#185!9?E?G!7?O!6?C@#89??O#69GC}owgowfxo_?_#118!6?_??OC!4?EFI!4?@#62O#64O#63GAb?O#164???WO?C!9?A?O!4?_??_#171!21?OWCA@!4?GJ??X??W#154??_!5?O__GG#225OSEAA?@#86?_!4?C!5?@!6?GC#85?GC#120C#185!4?@#162F#44???_?GG?O#223A???GGK???C!5?@??BAD??@JYO!7?AAA!4?A#187O#74?_!5?O??G?G???K??oGCC?_?@???A???C#241@?@A??OAC?WO#239?_#230?G?O#209!5?A?C_oGG!8?O#204??_#210!24?_OO#224?@?@@??A#202!11?@???@BA@@!8?C??C!6?AA$#123!5?G!8?ae#171G???AC#71_O!5?_??O@#134??o_?__~]y~g#194?_A@#235OM!6?A!4?@#223?_#70???_??FC??C#64W?@#121??C?X?ad_A?@@!4?_???E!9?@?A?_A#185gwwG?CCC!5?GC??O_??_#182!22?JA#123G?{SYVJ!7?@?wOZTWRKDYBAB@@@#161@#171@#198A???@#154C???@??_?O?EO__G?O??O?AW?A?GC@A#45OGC??G#185CBG?A@!4?G#207!10?AEQ??_??y#210_?__#235?@???G??CC@C#33_!8?O!5?@!7?@O!8?G#233AA!7?C?GOO__#229!19?A@??H?B?@B@CFFADFBH??PdDqxrq_@@?aQ_ooOOwhwgwogowgo?og_gGOKCC???EQPA#226!4?@#196!4?AA$#114!6?O#133!10?_!9?D??O???B`@O@?O#160!6?O#188@!9?o??E???O#86???O???B#68O#88@#66@?C??_#86!7?GOO??_???@#164?__@#67_!5?G#86!6?@!7?@#228o_O#194@HA@@ADH???@KA?HHDAPFNNN^[!4^]oDC#185Ca!6?ooqA?WE!7?_!5?OG?oo?CCC?AA@!7?G?GG#40!4?@#45?C#77AO?A??Oo!4?@#39OOO#195@@??T?A???A#197!12?_?O#203_#225???C!4?G??C!4?G?O?C#26OG!7?O#28@#95C#35??O!8?C!7?O?_#90G#243@BMaCg#228@#226@???EEIKWOsc_??@?@?@??@@_#211??C#212@A?@C!4?@#189!40?AA#188A#192?A#222?_???_G@?X?O?@_???@_W?O@GG?i$#164!17?@??@#86_#70O#88GOGO!4?O_?C#231!13?_!8?C#161?O?O???M??AA#88!19?C#89C??_???G#171???O#154O?C!7?@#123!4?@?A??!4A#235O#161?A#156?c#221AC@A??G?W??G?_G___O?o#161!11?O_#156???_#195!6?C#162_?U!7?_!9?__GCC??A???OOG?GOD?G#132??_#121???A#95!9?A#41_??G#191A#228???_C???C??A?BB@A@?CGGG!9?CCEkc_`_@!5?A#29_#232B#45C???DO!4?P?AA?O#52?@!9?C!4?O#232C???@#242?G#229?A@CG?O??_#220!68?C#224GOsGOWO{MN@???B?@???@@O?_??_OoW?}g$#176!17?AGA#118_O??_?_!4?GC?o???K#164!9?O#198O@#236_?_!5?@#130_!9?@#134!19?@C#132G!5?oO#130??_#87O#222A#171!12?CC!6?C#203??G#220!7?DA@A?X@QCA#216!17?@#134CA#132!14?G#89??CCBI@C@#228?_#227G#90!6?G#40OG??O??@#90??_???@?C?G??HH|C?O??_!9?o#187?A#232_?Ag@!4?_?C?WG#198!4?_?OCD?@??@!6?EA#234C#154_!5?C#22O#77_???AA??A#78!7?WQ!4?@GCSO#41C#45_?G_#54_#246GO?_#224!8?@?B??@O?G?@??A!4?A!4?D#235!52?C#207!19?@$#188!17?GC#130O#132O#89?G#196!25?C#223EG#222@#245CP?CC?@#132_O?_???C@#85!23?__!4?C??@???W!7?C?A@!6?@@#225GGGgO#219!9?C!7?CA#162!16?O#120!19?G??C#93O!9?o#201@#95G#63_wG__CABAA???A?BA??A#118!11?C#63O__#117_#225!5?O__!4?@@#196!11?G???@#237!7?@#230W???O??A#168G!4?@!6?C#84!8?_#81@!4?_#43A???O?_O#207C#72?_#240@GG?O??o?_$#194!17?CB#185@#141??@#238!27?_O_#233?OG#226G!7?@#160!26?G#120???__!4?H???A!6?A?GC#134?A#195??owG!6?O?G?GG?ITICBcEC#39!57?O?_#60?O#156_???C???C!6?_???`KA?OA!8?G!7?G#229!28?GOO#238!4?@@A#155?A#43?GKO?_#27_OO?__!7?a#28_#56?W?_?G#100A#15G#42O#238@??S?@??_$#160!18?OG???@#225!26?C#243?AGG#203_!5?@AA#71!31?@?A#162!4?G#225D#176!16?CC#201?__!4?_??_?_#92!67?C#77CA#85G#89O#160_G??E!4?_#72!16?C?A#227!8?@?A@?S???a?A@?C!9?CCA!5?O@_??A#159?O#41OG??CC!5?_?GQ?@A_#83??_#65G#11C#94???o#57W#79A#93???C#185C?O#163?_$#227!50?A#239?@#230?_#207O??C#224A#160G?O?G??C@#70!27?G@!7?_!4?CC#223!11?OO!8?_#25!69?O#67???C#44???_!7?@#231!25?OG??GA???C?OG#233!20?G#72!5?G!6?_???G!4?G?C#73???A#26W#92!5?@#230???A$#242!52?G#186???O#195G?C??CC_?O#68!29?@#176!5?A#231A#132!103?G#235!37?Co???CC??WCC#91!32?_#54_!8?C?G@GAKO$#208!60?@?@#156O??B?C#185!33?C#245!142?@!5?W#83!40?C?C#90A$#202!60?A#131_!5?A#242!177?A!4?G#73!42?C$#164!61?GG#171_O??W-#122u_GHGdeEAAA??A#121??O#118??C?O?O??A??A!5?C#171?_!5?C#208GO???G#230?G?@@#160?CGE??A_#92_#117__O_!4?O?G__?G?E?C@A?o!4?@_[M???@!5?`@!7?A@#123gA?o#195FB#209G?C?_A???O?C#164_WB!9?A!5?CC?C??o_!4?PSE???G??IGCA??G?B??A???@??G?A?@#121CA???_??GC??G#185w~g?AMaafGC#93@GG#86@!5?I#89O#195???A!4?_???@???GO!9?CS?K_?G@AA?Og!4?G#75O?G#41C!8?G?O???@C?LE#95O!5?AA#26?O!6?I??O??O?KC?OoW#233B@!4?@?_!4?G?O#207@B@BJ@IzSSGoWwoC?s?o_??KKGGWsO_O_#195AC#224G??A!4?A#230!12?O#224!5?C?Bb?G{OKK?SCK!13?@?@#202?O$#117H[TEDQXHKW#131GO??wsCZTOg_g?o?__?_o?__O?CFC?C#202o??_?_#231A???@?A#161_?AA???_!4?@#70A@??G!9?O??S!7?@@@!8?A!7?@#121??OBK[G#201?OC?@???A?__@?A!9?@!4?@?!4A#154_??G#176?@!9?@B??@@#201_??_???_!5?O??@#156C!4?@!4?O!4?A#187!4?SoCC?S#90_??a?X@@B@D??K???_LFFPCK#228?@AG?C??@??@Q@!6?G#159?O!8?_#45__#26O?O???A?BO!4?W???O@#44?@#154_GG#55G??O?C??_#43___W???@?@!4?A#13GG#100C#54G#245CCG!8?_#208@BFECKKsS_CJAUFBEMOP?FEUnHAp?__GGHhXwpuqq_su{dscu_ss_?oT_YOkQeofdanCSjVBDboshqomy}}}_w?g_s__?d_t`io$#119?@aoQG??@@@BB@BBjCaB?GCk??@???@???GGA#161?O#196_??__#160A#224G?Cw??_?G!8?GC#87O#86O?G?G!7?_??_!4?G!9?@!6?GPO_?G?g_??{_#154?@??_#198KB@!5?@@?O#185_#187O#162_C@@@#121?O??K??W#185C?CC!8?___???G?IOOCF@?G@_?@?C?@@?GIE@???@#86_?oWo?@?C??A@#162B???@]@??O???C#75OSee}MK{ontoHwgO?G#95?AO!4?_#168?@!4?A!7?A#191@?@#93_#207@??@??AA#187O???C#9GC#23_!6?@#54A#35G#43A???@#75C??G?_#78_?@?g@??A#82@B#36K#42??O?A???__??_#238AA??GCC??O?GW#205!10?gO?_?_#223G#224C??!5G?O?O#192!5?A?A#187E#185C#226G@HGGA#222!29?A???A!7?G?_???H?_?G??O@$#109?A#123??_??o!9?_#117???CQ?AU?QJPA@HH@B#185??_#175O#194o?G??A#229C???_OHC#196G@!6?A#191?C#69G_SAf^`nyC[NMvNPn_Cg#118A!6?Ga#160?C??CAR#89cO#75G?GO!5?C#119?MSo_#185B?_#223OMqIC@s?YAKG#168?C#160_m?aAACg_!7?wgw?w??QOBPQEJ??@GA??C!6?@!5?A!5?G?A!7?W#40@#120??A#164?@F?U?g@WHW_GAE#156@G!9?A#162C!9?_??O?@??C???A_?I@??_??O?c#226?A@!4?BA?@#47A!8?_E@?@!4?G???Cn!6?D?G?@@#45GA!5?@#162@#207@#243@@#241@#201C#90_#228_!9?A#203!9?C??_!8?_!4?@AFOC?dC???A#229?C@@?G@WHZH^JJ^~Ni^dnBlXNWYXOwGS_?gO?G?G?OC#221!15?C$#133!8?ocSG{gCG??GgS???C_C@o??O???OWGAC???A#184C#227?A???A#197AA#162_!6?_!4?A#71K?{O?A??o!4?o??RA?o#123EHe]?]A?O?s???GG_#67?A?B#63?O??C#156!5?ACO#228?_OGsZ{GKCK#195_OK@#161W#156O??@?@AAA?C?G!6?C#171O#121C?C??C#227?_#195OOoA?WWOO?C@`?G?@ACG?@??G?A#117_#45GG#77G#63_C?GA??@@#195!12?BB#95G#160_#45!4?oO???IBo?O#93@A?G?I?KG_#238D???_???CH!5?O#197?@!5?A#210@A?@?A#22?_??o[s[U{iG???_??K#94!5?_?O?C@#84@W#16G#33C!4?C?A??Ba!5?C#73C#163??G#232AA?A???C??C#202!12?@@??@@@AB?@@?A???FBB?A#198C#230!7?@@AA#203!28?BB?@B!5@VF^V^JU^]YUIIDM$#110!10?_#88C!8?B??@HHO??KKG?CC#141!5?G?C#222G?C?G#226CSW?S?A#176?GO!4?@#201?A#120C#85A@!9?O#121!6?@@@uP@|_C?_WGC!5?_#164!13?@?G#211??_#225?@??@!5?@#188A#123??oO_G_OO_??_?O_?O?W?G?G?WE???@@#161?@#223?_!6?G???QOG??__??C#89_#123??_!4?oG??C?_!5?@#159!6?O#39!7?_#117!6?@#185C??o!5?A!6?A!4?O@??I@EAO!4?K#211??@#228?HDC@#30o_??A!4?O??_C?ao??A??A!7?E@!5?o_??c?CFOQ???__#240A#237@@A#210C!8?C?GGQO?A@#209??G??_??_c?O???C??O!4?O$#97!11?_?S#71!7?A@A??GKC??ECAA#176!6?G???@#225@#236@??@#185?_??}o_o_GC#195OG#221@#68!5?_??@@!5?G?Gw#88?C#119G??_??O#85???@B!4?AC!4?K#207!19?a#191?@#227AB#154!4?G?O@OC#90?C!4?_#225@?@??@?@!5?_!7?__?O?A?O_?g?O?POOKF@#187C#39!5?G???A#44A#121!31?@#160A??O#164_#155_A#235??@?B???OG?A?G!4?_O#166??A#123_#201?@?SCsg#224A#95O#227??B#155A#7G!5?CB!4?_K#27AAA??B@!7?_!8?__OCB???[???O#195?O#197O?O??C#243_?wW?__#204!15?A#226!4?G?O$#70!21?@#134!9?_??___OHH@D?@@#235???A#228_#191???O#166O#164?@?@#188OO#198O???@#109!5?O??A?O#132!11?G!6?a?_#164Wg#156C@#69C?KUkvbV^yz@#210!14?O#132!8?EKKcI#176@#86?GO#89_?o!9?C!4?G#175??A#228!6?_!6?O??_o_#93???__???A!5?O??O?CcC#198!26?A#77G#159!4?@#191@#201??K??GKGG?C?C#198CC_!8?@A??GGC#225C#163!5?@#77@!6?O#42???A!8?@#56??O_?_#28@#9O!5?C!5?G#169!4?A#165!5?_#236@@!4?CC!4?_#210!21?__?_!7?@???CSG$#123!32?OO??@??AAAB#244!5?@#233A#134!7?C?KC@#222C#67!8?C#64?C???@#134!12?A#87!5?A?A??A#120@!41?Og#191A!9?A#168A#86?G#117?G#90G#235!11?_O[#222A#156AC#198?CK!4?GA#75?O?WSO?C?y?C?CA?WG#232!38?AC!6?_#100??A#156X!7?G#160?G#72!13?@@!5?H@!7?O??CA?g#81GS!4?M#37OQ#90??@#41OC??_!8?O#242??G???OO???_$#68!32?A#203!9?OOO_Wo??CC??C!8?B#66!11?B#162!21?W??_#196!43?@#93C?W!8?C???C#227!14?G?O?Og???@#90C!5?O??C??A!5?_w?O#165!40?_#227oE#164C@!4?G!5?CEG?OO_#6!12?G_g!7?G???_#93???O#35A!7?A!4?a???G!4?A__#192!7?O??C#209A#241?_??OO$#238!49?@#168!7?@#156G!4?O#161!34?_#154@?O#197!43?@#195@BA#162GW??C??_!5?@!5?CC???EA??C!6?A???_!4?@?@#25??CE#67@#85@#225!45?A#231A#223P!9?C?GO!7?o?O???C#33!6?@#29?@?C#9???CO?_Oo#157???O#83??AC#11???G!6?C#83@#15?G??G?G#157!8?_#226GG_?!4@A$#132!59?B#130A???G#171!34?S#187!48?C#198A!5?A??@???_??_kC__#93!8?C#123A#154!4?C?C??O!5?A@?@??_g!4?o???_??@!4?o@?O!4?A!4?C??Co??K?oqSOO_!4?A???_O!6?_CCk!8?GG???A#45!5?_?C!5?GG?_CC??O#23!5?_#53G??@#154???G#29??O?O#83!11?_#229A!6?C?G??__$#131!60?@#223G!87?@#227?@#226?@@?@#132??A#209!30?A#244!59?__G#130!4?A#157@#231O#11!26?C!4?O#79!17?O#57?C#23!7?G#166!14?_#230@@A?A??G??O$#207!154?A#242!96?O_O!4?__#74!27?_O??@!9?@!4?A???O!5?C#191!17?C#231GG?A!5?O_$#187!252?@?_#28!32?A#235!46?O#202A$#245!252?OG-#117@O@o_???@#176C??A!4?_??G?O?_??O_??O???AG?K???OG#132?!4oOG?CC!6?_#86?CCOAA?GCO!7?GG??GO]?O#120C#123AC_??OEM\hqGC???@@!9?R?GHh#164A???O??GGKCA#123G???I@?AD?A@AX?JEWo?Bw?uC?O#225_??@#187C#223?C?@!5?KG!8?@?@#160O!7?@BAQ??G??GG#45???KS??O!7?AAO???@?A??B@#228?kO?O?A#187@???_??A#242oG?WGCC?A#164[??@?___!4?@#41_??E???A!7?O?CBC_B?`_?Ac__???S!7?A#27?B???`@?o!5?A@???g#23GO#49@G!4?O#226C!5?@??A??G??__!8?@@#210A!8?C!5?G#211C#212?_#229O_GOOhoO``P_Op?G__#203AA?E??GG???aH?_#189O#235?__#222!13?_?A$#119MGG@A??B#175C!8?C#161C??G?O!8?G#194_??OO@@?@#166GOO#168G#189C#163G#156GG?C@@??C???@C#89IA@#70c?A!4?_O??C?OC???__#119C!5?A#89!4?_??O?C#93@#69okW?WYDNUQ#89?O#119GJEAC#156OW_o???__?O#161?A?@#141!5?O!6?_#93@??CO#164!4?@AQKC#238o_OO_O#209!4?O#225_!5?C!4?@#86G??o?_g`!4?GCA#156?G???g!9?GO#77A@C#44?_??C??A#195_???Co!4?C!9?@!6?@G#93?cCEADD@APG??A@#7A#6!6?WWCC#7@#31_!4?A!9?O??AA#52aC?CGC??g??S#45A!5?A!6?O!4?O#54_?O?O??G#236C@??W!5?_#205@?C?@GA?S?`??O?O#197_#195!4?_#205!4?A#224!14?O?_#222!7?@!4?O?O?O_?__#233???O#221!16?C$#118O???O#134W!4?A_@??B???C?CG?OowG@FCAEFA@#202A!4?aA?FBBF#226@@#88_???__#171A!5?_#117?CXWwq?HZBD?@B__oGGa??p@?O_^?Bb!5?_???@?A!4?_??CMCC?C#120A#187@C???G??AA@#121?O#134?OaoCGK?GGK#154@???G_??c#161!4?A#185_@AACA#228??CEAAqwkR#164E??C@``@#90?C???O?BA!5?g?A!4?CE?@A!6?G??PE@@!6?OO??CA?@?C!7?G@#245_?CA@!6?@#160G!6?_#123A#30_!4?C@?AED!4?ooO?G??@C!5?`@?G???G??G!7?A!4?A???GOG@AWCW??_CC!7?O#239C?G?_#235G#209?@#207@??C@?SRh_@XCb[GG?GG?G?IGGAA@#202!28?CE??AA??OGG_$#88_?O#131MK_???A@@?@@?@A???B@NFEBEA?@@??@#198?___#223_#203_#161??_??G!4?A??D?W??A#154O#92?@`#85_?@??_#88??O#64??A#121!4?oo???GEBLI?pWGxpAO???B#109!6?C#121!6?__@#209???@A@@#162o?OO!4?@#121!12?@?O?GHG#171!5?C#162C#195@GA?A@!8?_W??WA?GAA??@#63?G?W??o#39_#41_#93SCG?B??@???`!8?CC!4?B_!5?O???A?BBD__!4?FMB#244K!7?A#156_??A!4?G!5?O#9C???G_G_`#39?_#72???_?AO?O!5?C!4?@!4?@#56?O@!8?G#31?G#15OO#41@?_?OC#196@#159A#231@#13O#22G#32G?C#56_SG#243@B@A!7?_#202?@?@A@?C??I_O??_o??_oooOooo_#221!29?A???G?O!4?G$#133?CC??CC?A@!6?A@??D?A?GHC@??A?@#175???@#162G!6?_#207???A#130_??A???_???AZ#160OO#87A#71?cD?Oa_?___oW?@DB?K@?_O#67_#63O#132!7?CKC#86gG}KO_}f`wOhdz`#132?O???__!9?O_{CG??O@!8?_#86F#89DQ#176!6?@#201?O???@?@!7?@???C?KC@#123?O?_GA?@EAHG?@!4?A?AO?G?_#63?OO!5?_OoC??GIBK?C?C#185o!6?O#235C!6?G!7?I?@?@#92_!7?@C#29_G!7?@??@??A!9?@@?A#186?A#165?G#43C?SacGCAF?O?_i?C?G??D???WG?_!7?C#241@?A!7?OOO#203!4?A?GI?CP_EIC_???OO!7?O_?G@#188!27?A#97C#132C!5?O$#122?BA?@?A#194G?G?CGK???O??Oo#182?_!7?_#195_??cCA?Q???@#164!5?C#97OO#71o_O#162C?@B`?_#69!6?DJCCKI^KGFFAACDB???_#154!9?A#156@#161A#75O??AAC@??A??G#160!4?OoO??O???o??ogkCAiTDyfoivtq{e}S???_?F~Hz{GG#232_!4?GWg__@#156!4?@!9?_?D#69?O#45!4?O#164??_?_?__o??O?@!4?CC#160C!8?_#164_?_?_!5?CG??GCA?o#243??OoGD?@#228AAO??A#162OO?_#85G#121GADC#75O#45GS??@?A!9?C?poC!6?A!4?GA#55?_?@?O?__!5?CG!8?_!9?G#205@#167A#208A!9?@@AAEEGWU__O!4?OAC?FBFFFNDFBLK[~vuz~^n^vnnUNnM]M^nM~v^^{|~xxhp`pdBPAB@D|KNK[WI?_?_w_o_~NnH$#71?__#123??B@#132_?o???_#202GC??OO#166!7?_#141G??G#193O!7?O#201!8?A#157C#131GG??O#185?WCO#163G#68!14?O#118!6?O??AGG?@?KcC#164!5?@#160B#67O#154!15?_??K???_#195CCE?@#117!20?A#227!10?O?HG???@D!4?O#162AEB???_!4?A#117??_#162!8?oWoPO?_!4?@@?C???A!9?_!4?G!6?@!6?O#191C#238?A?}eDG?C#225C#186?A#120?GCQA?O?C#26_??q??DD@?E?A??C?@!4?BC#77H!6?CO#81???@#34G#32O!9?@??_#11?_#9_!9?_#47@#80?@O?G#245@#169C!4?_#192?_#210@??CC?GO#198!5?M!7?_#193!41?C#166A#224?G!9?B@F?MFv\F^N\?OOo$#185!6?O!6?O?Oo?_#123A#130!8?C#171GG??GC#196???A?DC@A#186!6?A#85_#117?O#120G!8?GG#85!36?_#228!19?A#201CCEAB?@#235!32?_?O__??[WKFB#93??_@#187GAO??C#77?C???C#121!12?C???@_#86???A?_!9?_???O#154???O!6?aA?GcaKO??@!9?_?GO@??CGG?@A#201@#94!14?G?HGG!4?AG!4?B?O#82!7?O#47tC_#33@!4?DFH??_!5?_???_???_???O#242@!5?__?_#209!12?G???G??G!6?@#229!35?KK??EAABOoa_tpWGA???A$#171!6?G!4?A?A#143A#168_???_#184!8?O#185?O???WW?C?G?G??GC#224!4?@#191@#118?G#176A??C_CC#223!61?@A?@?@#198!33?@G?CAA#159!8?C#75_!5?_wG??_@KCPO??O?@@@??C??EEKQickGWo?_GGI?XG@?W?HHNI??@#209O#165?_!4?@G#240!6?o#237??o#201@_O???@#77_???O?_#23?O#22GCGwwwoWoWEdYJIH#95A!8?C???B?_C_#83!7?G#37??WO?o?OO!7?_#77A!8?I?C#37__#230C???OAC??G?O#224!13?AD?C#234!46?CC$#160!6?_#161C#162O!5?_#220G#198G#221G#119A?A?C#132!8?C#134!21?A#93?G#159?A#196G#195G#168!62?G???G#154!49?O__???W??SKA!4?KEEK???CC?J?@O?O???B???OG#39?_?wCCSOCAC#156??_???A#232???G!6?_!7?O#185@_O#90!4?G?O???A#89A#185!16?C#164A!5?A!8?o#26!16?_!4?o?O!5?OCC#74?O#83_#91???G#52O#246A#163G??_#229?AEC?G?O#176!63?O$#164!7?O#130_?_#188OS?O?C???_?_!6?o_??O_??@OCIOcC!4?C!6?A@#166_#202O#185!64?G!4?C?A@@!43?GO??QQ??AA#41!24?G#185?@BBB#159@#123!6?A#41@#89!5?@#201?_??gOG?@#157_?O#212!10?_#209?G#117!5?O#154!24?C!7?G!4?@#28!21?A#155?C#188A!4?A?A#168A#157???@#82!4?G#73?_#97O#231@B!7?__$#203!8?G#193?C???C!4?G#194!35?@#191!68?G#198!54?G#121!4?_#187!27?A#89_?_#223!16?OK#155!5?O?GC#33!45?_#47o?!4O??GC#164!24?A#154C#72E#42?C??C??A!8?_#195?_#233MCCG?GOO$#195!10?GG#159_#121!5?@@#225!212?G#159!7?O#100O#95@B#75!46?G!9?C#156!23?@#93@#36??_#229?@#207?@#203!11?G#168O$#166!10?O#196!7?G#190!221?A#35!50?_???oO!5?@?GO?@!4?BCK@?QE??CG?G?C?@G?C??AAA??_#240!4?A$#74!291?G???CG!9?AaAW?G$#42!292?_#159I#54???OW???G@!4?`!7?G$#162!292?C#223C-#71JSeo__W?W[G#161!5?B??AA!4?C?A@O_#196?GO!5?C#132_O??@!8?_#85@@???o__#160@#117??@??@A???O??P@D?@!4?C??lWkznNd]OG]@#123???O!6?CC!6?__#119_#121@?@!8?W?A_#164?[!5?@!5?A?A#232GCCCO???zjYR@?@H@@?@#44?W#90`?DB#78@#160??OCGGoC!4?O?C!9?O?PE!6?_???G?C#201?_?OK?W#228[DuLNE}~SOO_!8?_OOW#191O@!9?C#30OCQQGB`@OSCFM]AAi`?A@oqHICA?C_?AG?C@#201SomC#94@#56CGU?G!5?_?A!9?G?C?A!4?DE#83A#42G#37K#29??A#93A#73G#191@?CP?_#54_#246@AA#236A!4?_#207Z~CMP#197??OYX__#192?_#190?_#208CEF@BMDNN^N^^NnnFVNMMMLX~Wztwp~hJijREBNjfmLYrtzyaaivGDjF`E_a?@@$#86CgWCCGCK#133C??[!6?O#130GOGO?G!4?_#202@FCGO!5?E@#97OEUBg?_?_O!4?Iq?E#70WO??K[_s?WR@@?@!4?GCA@?B@?_#63O#75_?C??G#69_??__jDN@ABCD?@?_?c!4?CGG??CG?Gw#93?@@#120G#92?@#123C?g#162_@#201OoWOK??__!4?__#162?@#212!5?O?G?O#223?_!4?@#86_#63G#156?Gc?@oM???GG??_o#162_GGEC@?C??@WigK!4?@#121O!4?G#195ooWCKkea_@?_#235?_OG??_?_O#187O?_!9?C#154}GGHAGGCHGGC#29WC???O??Bg#94!4?_!5?G#43???O??_???A??A#187_C#195@#159@#95A#78G!4?O??A#49?@#48@#33O`?@!4?AO???A???_???_!4?DC#92O#231E_#198_G??O#52G#226C???@@G?_#198???CCs#196???D?_O_#210!4?AC@A#194!11?O#229@@@AE?F?ICC!5?gwoo?O!11?A#202!7?OwK$#117o@???C_O#134@@!4?QqOO#185@@??AA!5?@M!5?O!4?A#131_w_??@O??_G?GG@#130?@?A#92A#69!4?B@#118A!5?GA?C???_oWWWG??@#120A!4?@!6?_!6?C!7?_@#75AA?O_CGO?CGE#132!4?]O#185?CEAB@???AAA?A?A???@#238!8?@#211?CA#75_O?cASMA!5?@@!5?AAA#185_O?GCIGww!6?OOo?A??_oo?G??AA@???_!7?A???K#94@#198G!8?_#195G!9?@#54_#41_??@!4?__BG??{GOMb?YBKCoGG@?@@?@_x#154C?@#228O#199A#155C#81`??o!4?@#91?C#95C#32G?@OG?O#47??@!7?K#74E???C#97!4?@#157C#235gK#80@#186A!5?C#162C?G#210AC#205G??G??W!7?A#224!5?K#176!18?_#97_#234?_#224C?BI?ECDCC?K?OGOo_???!4@?@A#220!8?_$#88?A@IY??_#68__O#166!6?@!7?A?C?A!8?O?G#134_?@!8?oOOOo??@#162@#164@#120!11?_?_#69??GC???B#123???CA?@#67??_O#86?mC?]SiOMxoHqfarX~XdbuQWO??E?Q#90A?A!6?O#134@F#195?WgKCMAEAC!6?A#162!14?_!4?@?_??_OL#123?AcDAOS_WG@C?@@#156!8?O!6?@A#39OA??B@@#123?@#164@#223?O[?@#227WGQ_???G#162G!4?CA!8?_???CC!5?@A#26?gCosKYLGO_`_@O@??@???aC!4?ICO#45C???@#163G#209?O#165G#82O?_?OG#37_G???_`?CA???cgCAA#49@!5?@#15O#32_O_#185!5?_#223O#207AACG#243@@A#245@#34_#233@A#231CG#203_???A@_!4?_?O??C??HGowoW??_O!5?OG?!4O!8?O_OO!7?ADKICC[[SGswSw]x^\nEQ$#131???@!4?A?AB{{G?_??o_o___#175@#141@#164?C!6?_#160_??_#162OCA#109?G??_??C#86??@A!9?bACAk`CEC!6?B?@?_CA#75!20?A#117???GA!5?@???q_O_c_?iUgfFo#176!4?A?@???@#225G?WG??C???A?@#201!10?OGC#164CA!4?O[MA#89??A!6?D#164?O?GAADBFF}fDFawggGq_#63OK?AEC?A@#225!5?CAA@??@#206@#164?@???_#168_#95A#201@@!5?_?A#90?_??_??G!5?@#9_#23C#22G?CA??OO@?@#31??CC??@O@@CE??_#9G#28O#33G#185?G?A#211?G#168_#84A#54_?@!5?GGG#27?O_g?OH!6?OG!5?O??_?A?G!9?_#241@#202?C!6?poIbJndEYL]KY^zoo???_oo?___oOOg!5_!10?O#222???@??C?@$#121!4?@#120@?A#97?Ac_??@??__#188?@@??@!6?w???O???G#161?G#130K#71??KS?K^JBFEC??CGG?Gk]O_WGO?gwYNA@?woowKE???@P#109!25?A#63!7?G!6?O_#67O#191!7?_#171@!5?@#235!5?G!5?OQO??@#154!6?O!5?O??A_?`wOoDbg???G#63A#195_oOwoo#77!7?@#75@A?CGC@H@#40??A#198_wOOG@BA#232!5?o???_!5?CA??_O?GC@#156?A?A??A??O??@#45!13?CCO#95O?_#93C#27???A??GOQD#100???_#164A#232??_#170O#55?O?K?`G?SC#98?A#34?C!5?Q#55??_!7?GAG#84C#54A#203!5?B#159?@G#230_??A#163C#229???@ACO#209O#195!11?A@@@$#118!5?AA#160!10?A#156A!9?G!8?__#88!6?_??A!4?G?E??s?CCA@!5?C??_???_!4?O?C_?qG#154!34?@!5?@#223!12?_?OO?G#123!4?@?@#77!16?__#159!4?_#185G#86!6?A??@BD?C@@#201?_#93!11?A@B??_#89?OG??C#187!7?_#154!9?C???G@#238oo{]^FC?@#92??O#164F??CC??CA@#54!17?G#47G!6?PO!5?VA#157O#223G#74!5?@@??C#43?O!5?A???A???_?@_?C??@?G???A??G_!8?OG??O#48_#74_#208BFC$#70!5?O#132@@??@?BBcLKKKCKCGWSwwxq#195???rB@#225A#198GG@A#133!5?O@O?_O#161???__?@#89!16?C!7?G!6?C!7?P@!9?WO!6?@??@@??A??A!4?O_Mg#228!7?!4_??OwwOOWww__gcCCcc}m}QKEB#45OWCA@#187O#93!10?AG#121!4?A#123!13?C??G#154?@!7?A#93!15?A???Q#223O#242GK@???@#159!4?C#93oOH?@?C#198A#72O#45Ga#154!17?O#75C#72!4?_#82_#35GR??__#57!9?E#53G#52AC?S??b??C???A!5?D?G!8?G#201!8?O#156O#242?oO#77C#49!4?GO#167O$#162!21?A#163C#176CA!4?KO???_!4?O??@#68???AM???K!4?CG!4?O_#121!10?_??A!7?_C??A!6?@A!5?_!5?G#160!14?@@?@!6?@??B!8?!5@?@?@#93!15?G#72G#90!16?S!19?CC?C#45GA#44CC@#90!19?@FD#72@#245!4?A#237_?GGEA#185??@#120?_?@_?O#159!21?_#78!7?_#11?CG#47!14?AABFgOO#28??G??O#15@#54!5?g#26?__CJ?_!6?@#100!7?E#166A#155G#42!4?_$#191!22?@#194@#159??C#203!6?CIH?@??@#89!7?@!9?A#131!15?OG#85!4?A!9?O?OOA??_???O??CKoG!6?Y[GKACSCGG?OCC#198!16?GC?!4C???A#209!16?A#156!65?GG?C#243!4?@#240??A@#95!6?O!4?_#83!46?_!5?A#42???A???_?CA#72?C#41?L?O_#31?O#16_#81@@#239!11?@#168G$#165!26?A#186!6?_#224C?A#168O#197K#119!36?SM?A!8?A#156!38?@#209!18?OO?_??_#227C???MMDJ#45!78?A#77A#209!8?_#160!8?A??A#36!47?@#35??_???O???CDcEGPG?OQ?@??O!4?PPNg!8?C???WW$#226!35?C#223@??@#77!217?_?o#39_!5?GB?A_#16!47?O#30?_G?@??O!4?_O!7?oSoO!8?_O?_$#207!36?CAA#85!218?OO#87@#26!58?G#45!11?O@?A$#205!37?C#121!221?A#7!71?G#9C$#80!331?A-#88G`CA@D@??@`!4?@#97E?hGBg@cXiSJaGSg!6?W?KG?HEQAH!8?O?A??_CI#118CKGG??MKAB@@@AA#85?_G??O!4?O_??_!7?_!5?@!4?ZYD!5?G_BW?_#75aq?AO?I#123_OOO#162@C?WWGW?_W#223?A?A#199G?_?O#211O?_?ACAC#75ooKwkvVCA?C???AAC#89C?O#160GB?_HC#185?{!7?BA???CC@FAK@?C!5?A#227OCGGC?A!6?A#154_OO???_!9?oICB#117_C?@#39?A#9A!4?O?@A???W!8?_?M#74???C!5?GC?C!7?G!4?@#35cO!4?G?K?UO?OAOAc?B??_@__OoHC?GgC!8?@@!5?CA?w#226@AK?C???_#196??CA?A@AO?C#188W#194?GCC!6?AD?A#169A#163A#96@#132@#236@#235@#206!4?_#229!5?B@#221???_???A!9?G?_#222?G@?B$#117sCg?A??@???@@#118@@#64?H}AcSS?G#131!4?Sv??_!5?CC??@!7?A@??_??O???CGCG!6?A@???A??A#123E!5?C#75?@GOG??E@#119G#123O???@O??O?SW_!6?O#63!7?C[C!4?A#90?O?C#156?A#185A???C!4?OO!4?O#232!5?O?GO#77_!7?B!4?A#154?@??@@@DCR]#162?O}#164B!4?_??O\N@CA@??@BC@???AAA#232???_OwgOo}GGC!4?E!8?@#206A?@#159C??@#85O#45?_???pOOA@!9?A!5?G!6?Ga#54GA@?C???IA#201WGoD?@#56OG??@#27?GC??c?A???_A?K!4?sWWG??U??C?__??O#56C!8?O!6?_#195@!7?O!9?KC[_cS#208??_???B???G?KGCCEaA@@OO?OOX^^K]^vJ?bPJ@J@FBBF@VfffFNEmNW$#118B#71YR|wy]uzuAq[qIc#96O??@??G#135@???c#132??BASGwW\E!4B!8?G??G?@?GW#71@B@A?RbBAFDO?G?KGsTP_?O_#67O#86?o__!8?vlgN_?__B@@?AFNM_@A#118GGACgB#89!4?@#86??rTHttN?GK#187?@!7?C??_O_o#235!7?oo#186?O#159GC#93A!8?A!4?AAG?__#134?@#195!4?B??_Xnz__OOOGicg[_WKGFFD@@@B??@@#235SCC?AC!4?C?A@!5?@#186_??_#162G??A#95_#23??_#41G??cGGCQA!5?@@?EC_?@!4?@@G!7?A???@#207__CA@#101_F#43o?POS??A???_g???@?_p_O!4?_?O???@!4?@#29GG#162C!5?@A#43_QA_O__OO#227@C!4?G#202BDFCHG?W[Y@a?AAbrqZ~^u{}~LAN@???o??Goc_E!4?__!4?OKKoKs}wwko{gWOO?_#224???C$#86!4?C#69?_G?GC??CcW#106???O?@?AcO#169!5?@#130Go???o#96_???C#64C??K#107O#119O~@#161G?@?__???o#96A#70___?!5_O?W?C!4G#121?B!4?AEC!9?OO?CJK?G?A?O!5?O?gwS#93!14?A?_C??_#186C!9?O#223!12?A#45GoAO??GC!8?G#132CK#201!8?s_H]AO???_???O!5?O__#228?Ow_wgwO???`G@ppX~[LB??AA@??FCA@@#155??KD#77_!4?A@???O#31G#95_#22?caD??G?O!7?Gg#56!4?G?C#28_?@#81C#83G#100C#159O#210_!4?g#169A#82_#81D#37_??gA@!4?@O!7?C?G!5?_!4?AO?A#157??A_#198O?C#185G??O#77A#42`!5?G??O#207@ABA??GG?OOO???_#191G?W?G#221!4?_G#188!7?@#166@#183A#222!18?@#220!7?COGA#203???GWOpOo_$#70!8?C??KAG?A#133!6?_?A@JOH?gS!7?G?SAA`??C#118n#134?C!6?BC?C#141G#85OO?O#89C!8?A!4?@C@ECD??A#63?A@I#62@#89!6?^G??KE!4?O#69@!9?W{c???GKgeG#121??lA?g#89__#228A@@!4?@@?DBAFFFN^^nLBLH#164???@!5?B??@#121??@!6?_#225!5?GGA#199?C#187?CK??Ggo?YO?O_!9?@@#238_??GA?CA!5?@@!6?A#187O??[#185?@#93OGKA@!7?_#6??OK#47!4?A@???_C!7?OO@GY^_OAB@!9?CA?A@??@@@#73O#41C#42!7?@!6?G!6?A?C?_#90@#235AIC??O#210O#91G#97C#37GC?_?G#27CA#163A!4?O#205@#203C??GCAG___??B@@??@???@?@?!6owG[{uMJNXnne_?O__GsMOaCo$#104!10?O!5?_??A??C#160!9?A#161EEBA#71??O___?O??_#120??O#185C!4?C#166?_#68???@@#117!5?W!5?O???C?_C@HGXPwfLqpIWm???_!8?C??oCcOEC?A@sF?AzAXD#154!7?`BOWO?!4_O#72!21?CA#41?_#39OWG??C??CG?o#156AOWK?uj@#198??B?@#162!6?CA!7?AB#225OWG?G?C!4?A#231?@#212???_#164?_?_!8?O!4?q#44!6?O#26_?G?A???G@?WFKSCG?B?A?_ROPoeEA!6?@#155_#158G#223_#84C#195C#197CA#226_#167AO#94?A#55AGA!4?A#31A??A??G???C?_#34O!8?G#48?O#49G??EB!9?G??D#83A!5?G!4?_#166_#197A?BA@DF??O?_#224!16?C#222G$#109!10?G#68?_?O??@S?gAQO?C_#166!5?@#162@@#134_?G#70?_#68O?G?GdoA#87???OOO#164CE?C?W#69!11?_o_o_?_??W?Wg_!7?CS_O?AF#132??A?Oo_sG!8?_O#201!21?@??AA??AAKGG??GG__!7?CB@#90?@GG?@C???A#123??@?A?A#223!8?CC#154!7?A#156@@!5?A#198?O!4?C??AAA?@#237!5?A#201?@A?_!7?GCCEA@#30!7?OwC?Cc?K_A?___a__GGJKaSK???W`D@#72A!4?_#162?O#157?G#90?@#206GG#165C#190C#52??HcG#30?OaG??G??O[?GX??G???C_O#18??G#32W?GC??O#93??_#231@D@@E#226_#245C#167_#48?CG#81?C@#47A#168@#26C#159?_!4?O#224?O??_#209?_#192?@C!6?CG$#157!35?C?@#88!4?Oo!6?_?_!6?A!7?CW??@CCA???C@#120??A?A??G#104???C??@#120!6?O?@#154??A__#119G#87_???G@!7?@#206!15?A!5?@???F??@??G?_!5?A@#24!6?__ow{w{{woo#41?_#227!9?O#228o#191!7?_#199!5?_??_#223?G__COC??CCA#187!9?O#206G#155G?G#195@A??G??wO#29!12?@@???C???WR#33??C@#154O#31C@#42G!6?O#31??__#46C#95?G?O???A#154A#166@#228O#232O#200G#48??O#54?C!6?_!9?A?@!5?G#17!6?C#87!4?G#155_#242A??C#224?C#54??_O@!4?C#233A#229G!5?_#208Oo?_#205!5?@$#156!36?_#135!8?G#106@#70!4?_#130G?G!6?O#68!16?_O#119!5?A#93!16?C#77A#134??G#75?@#120!5?_?BD@A#164!19?CC??CCC_g_O!4?O#162!19?A??@#90!58?gOGC?@O!7?_O?LA@#72???G?@??@#7GO#15!6?O#201_#83O#28O#35O#78@!8?C???O#164??_#191?O#209?O#78!8?_#34??GC#33C!4?B?@??@G??A!5?C@?dA_@Z`O?O#164O#201?_#228A??_#55???OI??P#41?g#90??_#201@G?C$#175!51?A#162@C@?G_??G#93!54?_#159!23?G!4?WG#185!28?@@#77!60?O?OG#157@!8?A#35!10?_#94?A#162!10?_#43!4?A!6?_?O#35O?_A?@???@#28!13?O!6?A???A!5?_???A?FA#229!10?O#188O#191O?A#36???@#59?G#52W#95!5?O#33_#210C??G$#176!52?AAA!4?c#209!80?@A?@@?@!4?CC#159!83?C#75__OsC?_!9?OWMCE!4?O@#11!15?_@A#55!6?@#16!22?_!5?_@??_!5?G?C?C#238!14?G#195G#207@?G#78!6?C#236!5?C#154O#162O_$#160!52?C??G@!85?O#195?AA?C??_?G?_!7?_#162!74?C#185C#45?G?_#191?G#190?_#6!33?CC#36!29?@!4?@!8?@#52?A???O#223!15?_#236_J#94!7?A#91!7?_#198@A$#86!53?_!4?@!7?OP?A@#94!169?A#95y#199!4?G#7!33?A#26!31?w!7?_!8?B#7B@#231!34?G$#89!54?O#92O#188A#9!256?W#11CC!5?A#55???G?@#9??CA$#15!314?A???_?CC#47???C$#90!314?O-#88__??GgSi#97AGgO_GoG?@_?@EOAQ?_D@@@EO?_???d!8?@!5?G?HC#85G!6?A?C???_O!8?G@?_?CS@???AB@?AB??EG@@H#87?@#130???G!4?@#123G?O!7?G?CAcRMASLQ@?___#132O#195OG??X???GC!4?@#95_#75_?w_oL@`H`B#24W{~V~}zv}mn^^V~{}{{_#162@@?G?o@@!4?G?O#206!6?_#228_{G~~^_`o@???_@@P@#156_!4?Qo#92O#90??D#41A#39A#40@#195?_P#168w@#162GG#90@AB#22G?F!9?@#28?G!5?o?O??A???G?A?A@!4?O#78G?O!7?O#72G!9?_#31@??OA?G#13?K#7A@#83C??G#43wbG?C??CS!4?A??A!6?C??@@#198QG?A#157_??@!4?A#100_!4?O#96_#187__#202`?AADE???c?@?A???_??@A?IQ__C??@@?@@EEE{sJ|]n|FNVY~{V?~UNWv}V$#131GC_OoO_S#109C!5?G#64?A???C__C?A?A#117SGC#130?C?@?O@#70??pqs[?__{{_c@???_o?wg{qpxkwwBBBQ???C!4?G!8?ABA#89!7?O!6?OO@#160?_OG#69!6?C?O???A#154_GGcIGOHAO@!8?@#228KA!4?gG?SGGA@??@#154@O_CC#39?geB!9?_???A@AA[wW_#187@!7?CE?G?gG?@?@#209??s??_#232RYJ]eENFE[ACB@#75gIoKGDCQGpOAC!9?@#41@OkIwOG`@_@?G_?OMD???G??OC??C_GE?OC?@?Bo?_#185O???G#55AOA?@?A@#27O?ABAA!4?wO!5?@!6?GO??@??O?_G???SAW!6?O#163G@!5?G#47_!9?_#229CG?O!4?_#221@#206!5?C#222!19?O#194!7?GS?_O???Gc??gi?g__G@_$#117VJO_#70?@@@#107???g???_#96?U?D_?CG@?A#109O#88?S?G#131GOOw_s#88!4?G?OO!7?CO#160C#132A#96C?C???C#69O??wWo??BR??MM\uT]z][wiQk_CwG?@O_FE???A#118!15?A#75_@c???@#130?_#86@@!6?[VUMUNA@#187_?_?@@??@???O#90?O??WA??OAA#63S#25???g??CG@PO??g#29?@#41?@#90@A?CG!4?_oOo_#199?@??Q?@?A#235!7?CCC?@@#212???A#159_???@?A?@#72!6?c#191?B#164_?uvo#121A#26_OoO?@??GF??Wy?FBW___CKw_Oc?BBY@D@!5?G#73K#165C!8?@#81_???E@#49C#43@!5?A_#35__CCC!6?@!4?o?_???a??CS?[C??_??@??O#36G#166C#230G#207@??A#162_@!4?@#73_O#224A?AA?C?C#197?@#192@?A#221!34?@?A???G!4?B???@#189?C$#119?O#71CIDEA?pUBBR_E@!8?Gk?gg_wo?_!4?G~MK@BnL^??KZ][o?OG_???KAA@D@?CKLN{_???@?@_#63_?@!6?O?_SW`!9?C#85!13?R?A@@#160O!5?c?SHA?_!5?G#201_SO?`!4?C??Q?G#72?w#93C???GG#22!8?@#40!6?_#156!6?@AA!9?G#227!16?G#238??_WWOWw?K#95O???@?_??G@??@?_#225G#199A#185A#165G#156?@G#30O??A_A??A??O??GOS@F?WwA???GD?KcqIC??A#27@#11?_#155?GC#187C???CC#84CG???GG#58?g#32_#30E??@??B?A?i??_G!7?A?_!6?_?_L?G?_!6?___!8?G!7?o#208@@QA?Ec{W?ozs}W}wmwwOSWoc_sXS@ES??xcggQ!8?@!5?@#192???S??O???G$#68??@C!5?@C?CF@U|?YyYO?ocO\?A???_!5?Q??@A??A?A?O?__?_??OCO??K!4?C_!4?KzzpOa#117?A???A!4?ShDSiaKSOgOc?GS[ijkI!9?xGcXE??AB@#162???_!9?_?C!4?K!6?_??G#77??CKAC??C#63!21?C#44_#164CEA?C???C??G_O#211!17?__??_#185?_!4?@#201@!9?SK#186C#159??C#155O#77H#29gC_#9G!8?Cu#94!5?A#156@!6?O#77O#29@#42?O!9?A#159A???_#163?A#82_!5?_#36?G#15H!6?@H??@_O@#163G??A#52_??O??CC?GG!4?__!7?@_GA#232?C#100_#41__!4?A@#33oc?G?_#207@?A@@A???C#212O#188!35?_$#133??G???G?G_OCG!9?A!6?A?@!5?G#69!5?_#117!4?B#118B???AG#161?@#130A??@#76@#86???A!5?_#66???C#64?_#67!4?C#75?_??_PG!4?C?Gg?G@?C?a?CAC#70!10?CA#93?W!4?O?O!5?C?_#69G#198???_O#223G??A_#232_oO?W?CCC?B#120???_#45AAoW?@#185!21?B?@A???C???_?_#199!23?G#72WSA#154O!5?K!4?G!6?c#92C#7??K#47?O?@?_O#95!10?A??C#47G!8?oO?O_!4?o!6?CK#52GF???A!6?g#47[!9?_#101_#84_#54O?O!4?G#55O#31_#47?C!9?O?_?C#37@#223???A#91B#26O!4?_U?G#233@@#230@A#72G#43O#74_#231?O#203@?A??GGCJ@B@APDFnjFNZ]He`kXJz~EYEVk}xxxBA!4?Aoo_!4?@???B$#69??A@A#135!8?O#102???gC??GG#106??@!4?A#132?BNMFNA#86!14?B#89@#131CA#164?@#156AA#87A#88!5?A#86!17?@B@K!10?@@AA?A@#63!15?G#67??A@#89!6?@???GA!6?A#185?@?C!5?_#159??_#75!34?OO#155o!8?O#237!25?A#164C#45G?cK?EIr_oKkW!9?CA???_E??EEC!6?@!5?C?G_#74!4?o#43G_?g#33!4?@#90G#223CG??_#206O#95O@#56@?A!9?O#33?OC!8?B!5?CGGA?B??A???AO_#159@!9?_!5?O#83G!6?K#226@??G?K?OG#196?@$#104!18?@#110??@#134@@#92!30?A#166???@#176@#77!39?_!7?O#120!16?CCOA#164!4?_?__!8?A_?W?ME???_@?O?C?A??O#45!27?_#165C?O#195I???A@EBJDbgE\OB#78!18?_C#187!14?@#160??A#31!7?@!4?g!5?eGO!6?A!8?@?AGSA#157??@#228@A@#191G?_#83?A#99_???O#37?A#13Ow#6_K#41O#73C#16?A!4?G#45??O?C#167O#168C@#37K?A!6?A#9GG!5?C#54C!6?C???C#185D?@#154_A#35C?@!4?W#188C#163G#210???GGO?_$#134!54?@#118!45?O#121G!6?pD@@@`???N!4?_CGO_?W#176!4?_#75???G@PH#90?D#227??A#206C??O!4?A?A@C#159!34?G#154KA?GGG#168!30?A#93O!4?_??A???R#43!13?S?_#54O??@#168!9?@#90B@!4?_#168!14?G#210@A#167OG#54???C#96o??G#48?C#23_#26C??@!6?AF@sA#100GG#186C#185@#56C#32?D?@@?A?@@#11O#15O#83@!7?P#56G?P#48A#164!6?G#155SC#95C!6?O#91C#235?C$#154!101?O!8?A#168!37?Q?@@?@@#223!39?@??@?@#35!59?@?G?A??_!7?C??@!7?@Kk?VAp_#209??A#77_#201C@Q@#157!4?_O#9!4?@C?K!5?@?OA#41?_#44A#197O#191G#242A#166A#74?_!4?G#41??_!6?A#8G#186@#90G!9?G?OA!8?_$#120!101?_O_!5?A#165!41?A#209_!7?A#93!34?_O???Oo#15!57?C#198!15?A#10_#9O_#72G#230!16?C#154_#197A@#158!5?O_#7!5?Oo#28???K???o#156???C#78O#36!6?@???_?OG???O#34?_!6?G#49A#225!7?C#80G#168A#45O!4?O#76C$#92!101?C#132_!6?Oku}]}v~_!5?w_?C#235!25?O?G???A?@#168!34?G#225C#228CA#154!77?@#199!20?O#79!7?C#74@CO#8???G#11!6?@#42!14?AA!9?@#77@#155@?A#191A!8?S#226??@#11???O#27gGO#9G#196A$#191!152?A#199A#235!43?A#201@A@??CC?SUw_M[BB#162!62?A#200!20?G#16!37?O#49o??C??A#26?A???O#55?_AQOGC#77!10?@#15?C?C$#211!153?C#89!44?_#28!137?@!4?D#73@#16!4?G#157??C#74c!5?Q??OG#13!6?A#42A$#210!350?@#82G#78C#53_$#81!353?A-#71DAEDBAM[Os?c`cmZ???Oo_??B@???_@ZNE?G?CgPScA?Op?O@SXdgWQS?_O???_CK__G???sA?OGGG??O#67G??CA?E?O#121G?O??Oo?_?WgCCC?G?KC?EAE?B@@B!5?@??@#162A???G!7?O???O#93_!6?AAW@#44O???_??K?CA!4?_!5?__?O_#41!10?DA#77@!5?_#154O?P??A#228G!5?oGy||G?_?OD?@?A@#156??_#185O#41_o?K?C??_!4?G?g?Gg?WE@?O@???L???@?OK_!8?_??@!7?C#95!4?G!4?AEC#101c{PA#58GIG#32C!5?O!5?OK!4?_???_???AB!6?_#48A?@#84C#58G#79G#53?@@#30CC!6?_???_OP@#162A#207C?C!8?C#30_#208BF?DFNVFX^^urv|{|ww{owqxtSYJ?o?_O?_?wOo?_!4?__O_?_??_$#97O!4?@!4?@A?A??UgC_?U?PC??D!6?@??@!5?O!7?O???_#88_#69!7?`A^S^NG??CLBEFL?lvFHBTB`p???@#132O!5?O?OWGG??K?AE@@?B#75C?C??AAA?A?AkwWWOo!4O?__!6?_o_GWGKG?aieow[C??_oGB!5?_!6?G??O#90!9?@??OP??YC#187O!4?_@ACC#211_!5?_#235F?`??A#75GO??A??CQ?`?_HY?LF#30G???G?k_OiA@_?_???G!5?@?_??@A@???B?P?CC??B_!5?a#80?C#90A#158O!4?A??K@#84O#43_?OO??C?D@??E!4?CB?K@??G?AWOCG?aGC??gA?_??A??@!9?CKQ!7?B!6?O?O#229COCOOogO__#224!12?CEAB@#220!5?@#222!4?_#221??O!6?G!4?O$#133aC??G!4?@#64A!5?H??L?@F?G?GI#70CEK?oo{oYy?CGGKEMKFC?!4A@HH[Xj^~vTRQ[?`??VHKZAC???O!4?O?O#85OG!4?C?C?LG?@A???B`_?O#86O#66_!9?G?G#90!4?C#154C???GG!4?O??___O?G??C???@#45??HFBB?@oX@#24??G?FB?O??@JFB???C@@Z?NGC#44??C#75_XBMaA#164??AGu?SAo__#206OP@O!5?O?G#154O!5?@??G?A!5?G!6?C#162A#47?O!4?S@JqML?A@#9F???_???GCCVC???G#43_g?CA??@??C??E#156A#155_#201G?B#52_!8?C?_OO??AY?_#28?G???G!7?o!5?_C?OO!8?O?A#41G!6?G??@G?C#185G!5?_#201O!4?A#210@???BA???G#203A??H?GA@AFFBMEG?GGCCFJ\^k{]}EMG~N|o!4O??_?_??_$#131G?GG#109???@#103???@#110?@#107??_#135O_???_#85!8?C!8?@#69@??@!8?C#88!14?___?@#117?_??O!8?GGCIDUE@MHF?AL?@??@!5?GGG?C!4?A?@?_?COA#160@#164@!7?ENLLOOO??G??C??@#89_AK#40?O#39G??oY?CA?wVD_G\HMIGO?kaCN`iucdOvY~yWU#86_K#45_#156G!4?A#168G?@??O#209??G??@#232A?VGLUD?@#78S#93C???_O_!6?_O??A?C#155C#9O#29??A@?@!8?O#11O?C!5?@o?_?GW??C???_!5?@@!5?A#167_!4?AAA#55_!8?A??AW!7?@@!4?I??g?CA!5?@o?I?VH_#157__!4?_??@#52_!4?A#155A???A#159O!4?W@#33G#42O#48?_#96_#202_!9?C!7?@@@??__owCa?AB!4@F??ANmjLNcX^\TjWYa$#68?@?AC?@ANI[W]WPc?BQ?F?Oeo}VoxXq_?@AEc?ViaQph_Awj}hcGDacABEC_?GIg?@?A?O!6?@??F??O_#62?_#75??AC??_A?A?ADAA#118?A!4?@#69_!4?G??C?C???O??AC#223??A?@?A#185?C?@?AA@???G#63??O??o!7?C?Bc?AGSC?_GO?E?_c??OCJoAO!8?_GE#89_???C#185?@?@_?CA#225!4?G#238!6?AGAA#45___oc{F@??HCQSQC_QW!6?O???_?A!4?A!5?C#39O#77I#54?G#31?A#22?W??cO#29@?A#56O???G#36@!4?O#188??O#187C#79_#45A#189_#99O??O?_C#57?O#35_??G?WG???@?_?OA@_S?E@?O?@???a!4?LC!7?C[#33W!8?G#35Ga#27@#26GG#91_#231G#198A!9?@#232??G#233?O#235_#231_#212??_#211C???G??A#194!27?@CA?@E?AG?D@[$#117?o??_Oo__?_#106!6?C#104@!4?G#88!9?G#133?@#131@#107!15?O#86!18?A?_??O?O_!4?GG!4?O??_#123O#77?G#87O!5?AA_A?O#63__?OoGG?WGssckS{wGO?__??!5_#123O??_#206AC?CC?A#69__?O#156@#41!7?G?B??`#40?_???_??OO#45!4?O#93!16?@W@b#165_??G?O#201GGXjAAfC?A?o#191??_#77_??G???O!7?_?A#22??o#26Ggo?O?CAPC!6?_??G?JA??BBcCg??_Bk]AG!9?A@???@#162C#72@#231C#165D#56G!8?G???A@_?W#27??ED??O???G#13_g!6?@#34@!6?C#49A#46!5?A#11?A@#77@???@C#72A#55_#36O#42?_#44_#156C#236?_??G???G#72_?_#192!49?A???ACAC@$#88?G???G#102!12?G??G#85!50?o??O#66??AG??_O_!5?__#120GG__?_?_CC@@?SOA!9?A!4?@??@#195!4?@???C!6?C?@G???C?A@#25!17?I?CA?PDACW?@??GCG?Y_?`#121!7?S@?G#162K_#155?S#223_#199@#187!15?G#72O??GW?GMB!4?B?@!8?C!9?C#35OOB?E!6?CW?@!9?@OO_??j??cC#195?_#226O#191?C#236O#170G#94@@G#98@#82?O_#59@#34@!4?_cG???GO???_??O#26?CO#74CC#52C???O!6?C???O_C#26???@#45C!4?_!5?@#176???O#233GG#169C#166O!7?GO$#70??@??C#96!13?AG?G???_?A#118!46?!4_#63?A??C??c??`A@??@#130!5?_!4?GC#123?A!4?@#93??A!6?@??@!4?G#209C#225@@#156GGG#90??_!5?O!9?@#69!12?OO#86???_#123!24?C#195???C??G?CDA?CC#95!10?G??C!7?A@!6?@!6?@#31CC#54?GGMC#55G#33??O#15_#6_GO__!8?P#72???_#42O!5?A#33_#83?_O#227!4?G#208?O#243G#49?O#51_!5?C#36A_!9?O???G??_#9??O#29C#154@#77@#83_#47C!4?@OG!6?_??I!8?G?C??CC!5?@!5?C??G$#119??o?O_#64!76?GA#89!7?G!6?O!5?_??O#154@#92G??C#64???G???GO#187!8?C!5?A!4?G??@#121???G!5?c#160!44?_#159!5?A#186!18?C#165A#164B!7?C#39W!6?_!4?_#78!6?o#28?_??_?_!6?@??A?OA!6?@!8?A?A@@@?@#232G#50??_#53O!4?C#46?O#13@A#30C??O!5?@??@#15?O??oG!9?GA#11@#42??O#83?W#97!7?O#201G?@#168E!9?O#195?G#164@#230_!9?C?!4G!5?_$#118???o#131!98?_oO#160!4?@@#70O#62_?ooo?o#67G!6?_#228???AAA#168???C#191!5?G#228AAABB@B@#86C_S?OC#186!48?@#195!21?@#159A#94!4?@#90??_???D??CC!4?A#43!6?O??@!9?O#78O#15!4?_!7?_#78G???E!5?O!7?G#157@#96??GB#27??A#33G!8?@?gB???G???A#155A#54??A#56?O!9?c?F#191!5?_#9A#29A#163O?AA!8?s#223@#235A#224O!9?B$#60!117?_#89?@#85@??A#201!7?C?CAB@!4?ADG?@?C#67?O#159?A#162!81?C#40???G#6!6?OO#163A#46!14?C#2??o_#23O#74A#155C#7!6?GA#27!4?CA#74O!7?_??_#23!16?D#74_?@#47??C@E#16??A???G???A@!9?C??W#236!11?O#93C#95G?@CA@#79???A#241!4?_#192?_#73@!6?_$#86!131?_!5?O#209!6?C#168!88?G#7!10?_#164@@B@@#154!16?G#164G#31!15?A?_??O#159?O#7!21?@#37_!6?_??_oAC?A!5?W@D_???_?AA?G?P??O#243!4?_#198g#154AX#90G??O!6?@#243?O#55??A?@#226O???A$#227!132?@#156!111?A#159A#186!21?C#79!15?G#47@KC??OGG?WG#9!17?A#29@#81???_#53_#49!5?C??A#100!7?_#81?G!6?@#207!12?O#100C#54?O???A#245!7?C#101??Cg#74AA?o$#187!244?@#45!39?A#37@?O#42!25?A#54!4?C#36!20?C!6?@#188!11?_#78?_???O#190!10?G#56A?@#37A#35K$#46!286?O?GC??G#45!20?G#31!25?W#30O#27@a_!7?_#75!8?C#74O??@#98!11?CC#83_?AC$#55!287?G#49!25?C#37!48?O#57!13?@#93_#100C#54@$#73!287?C#228!89?G$#238!377?O-#118GO[cOGGOWoS_ggc[#71`SG_CQ?OK[cG?Aab?A??CKg{HHaWHcOGPj_AC?Cf???ACC_C!4?_#63?_!5?C?aI??A???@?A??@AArOy_??__??ApB@???OpHAgX{RlbzT]lZlrrixson~XglAwo_???OwWgSG?`!7?_??@!4?@?A_???_B??@O_!6?__Ba?QC#93@A!5?C???_oO#195?GA!9?@#90@?O???C@??G?cCCK#29wO??P?C#11?G#78?O??G]X@_#43@#22_??GO__O_?P!7?@GA#223_#155O#159_!9?A??A#165C!4?G?_#59b?CPWG!8?OO!9?W#84E#47C!4?O?gACC#15OG!7?o!5?_?G!4?K#49CG#155?_??@!4?O?S!6?@#48C@?A??_#232@??O#210??G#236__#212?A?C#220!6?@??@#229!8?C_#222?O?C!7?G$#119ok?GleOKCK_?CCW#68@?A??P?ECAAZTGPXS??AN??O?aESAEBDb??@@@!5?AC??C@#86@@!5?OP??CA?@#62oOWGN~b[}S~IsHS?_!9?AogFX~fMSO@AB?A[C??O_O#67C#75C??@!4?P?@FGWw_?g???A`sSNYMFDN@?gWMnHA??C?KA??B??@#40?KWW!7?KK#75?GHO?B_@@Aw#168@???G!6?O#232CG???@#78!4?W?O??A??_#39???_?CCB_#30o??O#95?G?O#60!5?_#30?A??A???G?gAcE?_??O??A?@B@A??O??oW#190C?G!4?O#158O!4?@#37?@??C?OGc@!4?@CK~XGuB#98_#94W#31G#26@G!4?O#55@??@??_?I@#41???OO??_!5?@#26K#95A!8?O?_???O??C#47g#74`#81O#96o?D??_#242CG#229@@DEAE]OSWoog___#202???BFC@??O!9?@BADG!7?F$#117AA_RAPfBA?JZOO#109@???O???_#70B???AvkC?~|TozRFB#97SOH?_Gi?gCESO`qG??G??GOI#120YCGG?GIGAIAA#87G#76_#66?Ch@O_?W_?@?sGsgKG?O#69_!5?K#68?G#60Coa???acE???O#69??_?AC?GGDEIEO?CE?_!8?_?_S?A#45OC???o??O?OOO?G??@??G!8?C#44!6?@??ac#45Q?C?@]AC#154BoWk??c#199?CO?OG???AC#75CAAGg?gGo???C??@@??A!9?_!6?O!5?C#161A?C#93A#43C?@#47oSA!6?GGC??_?AAA#83@!4?GWH#169G?E#99oCKLA#82??B!7?G_#46?CC!6?C??o#28oO?@#187A@#74AOG#16??S!5?Oo#31?A!5?G#27O???@#33O???C#54@C!5?A!6?@O#49?I#58_#169_?G#233@@?CQGO#226???C#203!13?AA?@?_wXpgD@A_I_CGDABA@iTIg$#131D?A!4?__@?C#97?AA??`_T_HOgp#107@#67!8?_#88??_#109???_??O???E?GG?O#70??F!6@!6?_!7?A#68??AADO??A???@A#67?@?D!8?_C#61?QGC#66?G#64??@Oc?k???G_!5?O??G??_?Q#90??@?C???C!6?_@?G!9?G!5?G#86CA#25??@!4?_!5?@#90!6?A_#41GC?ww#164?E?AAC?b@#206gA?C?@??A#93!9?C???C?GG?@!8?A#31?@?@???C#11??C??@??_!9?BCA@??A??@@#28@?C#15?C#95@???@#94_!7?A?_CC#52A?_?A@!4?I??O?__!7?G!6?AA?_???C@!5?o?G???G!4?p#100??O#207O???A#79?_O???@!5?CG#32C#208A!4?_?@?@@BBDNJV^^^~~}{ww{~}n^FeMRY}l^o[woq|{|uTitO$#133?@!7?A??A??AO?E?Ic#131???_#133!17?_?O?O?O?_AAGO!6?G_#118cYuDZV@ECC#156GG?O#64!7?C#61??g#70!6?ACG???A__?GC#25!7?G!7?A#117@??A#86!5?@??A??[!5?o???A@?GG??@#77?a!4?_#162???C#24O??_?_O?O[{G!4?@^~}m#86!8?O#121_#72??C#77@#162?GC?O??K#191?@??A#228B???@#154!9?A!9?@#22WKACGC#94???W?O_#6!5?wEs_GW#90C!8?C#164??_#191_O_#78O_#35AGCO?@_???_@#82OAC#56_#155@#189@#79@A#16?_#55_??_?GCK???_??@GO#49A!6?@#11?K#30C?_?O??@??AW#37O_??CB!6?O?A??C!5?P#231_#55@#91A?_?G#77G@#72G??A?A#61!4?A#202P#231O!4?C?GCO#194!31?@#221A$#88??@!5?@???@#64@#120?_#96K?@???@!4?_???G??G#64!8?@#135???C#88??O!5?gK_g?_!4?@???O@!6?G#86!17?@??OCO?G#121!36?C!4?C@A#67OGAB#93???OOW!5?_???AC#89@#154??C#156A#160!22?@#201!7?@@!5?CkH@GOND#77G???_?_?_I??@!4?Q?A#26_???GBBDG!9?O!6?G?_?K!4?@O?DGDE!8?KG#41_#157G!5?O#167_#58??@#21??G#56???W???@!4?Coo_!8?A!6?@!7?A?G#7???_#83G!4?A!5?@A!5?C!5?GA???A??_#163??O#236_#239@?AQG???o$#135!16?AG?G??G#107!20?C#131!8?GK@?OoOOwO?O#117?_?o!5?@D??@#85!19?AO_Q?AAP#93!35?A@A#89OG??C#85C#160!6?_#39???{N@A??Ao`owWOO_wK_Au}bFB}_?@P]]oP\@_kGSY_#187!4?A?H@@?q??e#170_O#212E#72_???C?OFEO??G_!4?Q?P!9?O_??C#9!7?@??@???P!7?CA#72!4?C#54O_#43CGC??@??o?_#84_#101@???_KWO#50?O#51???C#36A??CI!5?A???CE??@!4?_!6?G!5?O???@!6?O?O#81@!8?A#166G#93A!7?@#27o???C#132!4?C#203OO?_?_$#103!19?A#117!32?_#85!4?AC?A?A#119!4?AC#121?C!4?@@#85C#120!19?@EH@?H@#123!37?E?C?@#156A#119?@#41!10?A!5?c??G?C???C#185!29?_#155OC?G!7?_#45?OOwGo@?@FC@OW{?AB__?C?@!7?B!4?C?@??@#2_A#24G#154A?AA!6?O??_?O#55???GC#34O#27A__OO#80A#186?A??A#78C#57_!6?O?Ca?_?O!6?A!8?_C#99_#16?A#27A!8?g#18?C#49C?C???C#74G#45C??G#159A#231@#162@#93@#72_#56???_A#228???_#197_#159C??C#31C#73_!8?@Y#222??_#245GG#240CAG#230A_H??Gg_$#67!61?A#69!9?_!6?S@Cc_!4?@#76!9?C#87GA#89GKSS#154!38?@JFA?@!6?_?_#44???OEC???@!4?B@#165!34?AAO!5?_?O??A#95!9?@???O#23!4?G#6_oKB#9ow#77!4?G!5?G!6?C??G#29A#41@?GA?aGGO??@#74??G!7?C?@#230C!4?C#47!10?@???O]#74@#84A#48???G@#33?A#50OG?A#13???@#35A!4?C?O?_B@!4?C???g???CcGAAq?G??GK#245?O#242G?@@#187@#44C#45_??GG#78C#243!7?E#97?_#241O!4?_$#132!72?_O#185_#186O#161_#160G#121!20?@#117C#118?@#40!60?@#164!4?C#160AA???@@#211!37?O#41!5?__?oKE???B?_CAr@!6?A!5?adCEBA@?cIAK#0?W#7@?O@@@#162C#31?_??_@#45O_??CG#36???O#81_#37OG#9G#14G#77???@#72O#54O???@#206G#43!11?@!9?B???@@???@???A__?G?`?@?AT!4?G?AO??C_??Co?_??ZA!8?A?_???O?G$#92!72?G#162_?_O#223!135?@#185!5?G#156!14?OG??A#47!12?C_?AQ?CB#39???C#23???O#28??O??@#187G#42!12?A#31D#29_#191!4?A#81???A???A!7?@o?@???AKg#51!8?G#41!4?_#34@!5?A?C!6?GG!9?A!6?C#201!4?@?G#30???@@#80C#162C#35_?C!4?G$#195!74?O#164!158?G#54!16?_??_GO#35!13?Q@#95GCG#197!18?G#53???O!8?I?O!7?O#101@#53!9?O#42!5?S#93C?A#90C#155_#78_#42!4?G#48G??A#90??C#13_#168A#30G!4?C!6?_#74!4?_#163A?A#41???Gw?O?G$#36!269?G#201!22?C#91!4?A#208A#83!11?G!4?C?@#79!15?O#77G#45G#83??O#77!6?@!6?C?C!8?@#42C#191!5?O#235DC#168!4?A#55??@$#168!297?C#35!12?A#31_#15_#54?_!6?_#163!11?G#156C#97C#32!9?_O#73!4?@#191@#97C#47O???_!8?AG#243??G#90OO!8?AA$#78!311?A#34O#164!21?@#28!11?__?A@!9?G#48??O#154!8?_!4?CA$#54!346?A#235!6?@#195@#224@#78???O$#239!353?A#226A-#121o_!5?RwGFeoo{{#97Ok]?QhPcS?eXE?RGGA?EC?C@]AEIBICKc@GI?MFDCADA?B@!9?@#65A#44A#77@#75@#69!4?@!7?O!6?_!6?@?CC@!4?_!9?@!5?OG???KE_?CE???@#120?AOCGG#164GC!6?C??G#77??_@#63A??II_???_??@eG?D@?_??USAC@!7?@#195o??G#187G?C??@?G??A@?O#77_?osC#95?A?@!4?O#30__gAI???CG?O?GE?___#159A#75O#27???O!8?@OW?_#7AC#72??@?_#201A@!4?O???O#90C#82OOC!7?A!5?_#58?CA#37?A??@E?GGsC#51?C!4?C?C#20@#155_?A#238AO?_#41C#91C!6?_#185G?G#30_G`?G!6?_???_!5?O??C?O#33O?C!4?G??O#32G#12O#84C#41@#236A?A?A#224G#72A#159@#42__#233@?A?A?CGGO#203!6?@@??BF??OWGeYgciWCZbC@}$#118E@ZZOpi??@??CCA#133?cP??kA_H`a?CGG?ED???GAGC??G?G?A?I?BC@???@??@A#63???CQC]!9?A?AG??Y?_COO?OOG?c?XqXB??_OC?oSO_oCE__]DKXL|bwUzh_eLv||~aH^[GGF@A???@@#119A#85@#165O#95_???O??_#185K???@@#39?@\N@@?oGrPAC??FogM~RdyhAOoo[^~nUUK#93AG?A???A#209_#159!4?C!9?__!5?_#164o?C#29?OWO@#9Sk_O???F#78?O!5?pOG#33G_!5?O??g?@#28A#90!5?@#41GO???A!6?A@#74@?C!5?@#99G!4?oK#78??C#52?K?CY?G?C?j#78?_#56?_#34_!4?G#74?C!4?@??i!9?G#28OG?OoOO#52_???C?C?_!4?C#8O???_#159A#28C#97@??A#52O#79C@??A#18G#194_#225_#45G#101C!6?A#48G?_#246???O_#212A!5?C#224??_???___#210?@??@#202!7?Ggw$#119HMCCKG@K@E_#89??@@#71?B?@G@OMA?P?A@DK@ACA?@DBI#135@G@??C?A?G??G#66?O!6?K??GGW?O??_QO?A__O??A@?OWG!4?BDcQDA??G?@@a!4?C??o!4?A#64BC???C_?OG?a?AA?P!4?@#121?OO?o{G?C#201??OO???@#45?@?@???KGo?O??P?C#69??_??G#40?J!4?OC#45?gG@!5?_#90@_?E!5?@!5?_!4?GO_C!4?C??G???Y@@#26CCw??A??HN?@#95??K?S!6?_#2B#24C#26CC?ACA?g?_??{oW#95Q!7?G#73_!4?A#81_?OA???c!5?@?__@!8?@!6?O#94EBAA#30O#159?G??CS#165?_#55@#94C#43@@!5?A?O!7?@??I???O!4?A!9?C?__!5?C@!9?C#23O#245CK?Gw_O__#221!27?O$#120?O_?@CO?A_WOGG#135??G??C#109!6?H!7?C#70?A#189?_#143?_#64@?@C#104@#96?@?_O#71?A@??A??C@#69???A#118@@#86@#70???@#130?@#92@#86!14?_#70_#61_??G#67??@#117!6?_!7?GGC#75!19?_??pOGEDA@@??`_@A@@!4?A??BU{Ou?_?CcGG@??@Q_?O!9?C!6?@?QK?A@#170C!5?_??I#41_?O?C@B@???C???CG?_?O?@@C???EwC??oLC?OGC?@`C#22K??AA@!7?G?_A#80??C#45__?Q!4?K#84O!4?OO?@#36G!5?E!8?@!4?COO!7?O?C#43_#225??O???_#78??G#37??A?AB#33@#155O#154_#9@@!4?B#36G!4?O?Ww#55@#74A!7?A??G?AA#54_!7?A!9?C#27O#158C#32_#161O#235???C#208A?@?B@P@^FYQ~~KW^~mfvW@FZTfzcSBE@$#88???_!8?@??A!8?I???O#134_??O??G??O??O!4?G#161_#132?O#61___o_oOOO_woCo!4?GHGCC_?KI@#60!14?_!7?GC#75??G!8?O#60O_?__??G#86!12?O?bA?OG!5?_??E@#159_#162a@?C@G??O?A!4?_#160O??C#24_KM[!5?Oo?KI@!5?__#41??Gg??@@#165G?C?G???_COE#45o_K@CG???J???KbVL@@GC!8?_!9?GC!5?_!4?_#30D!6?@ME!6?G?@A#33@#52A???A?cO?OO#53??C!6?AOA!8?@???`H@H#35_?O?HB!8?A_??CA!6?`_K???Y_?_#56B?AC???o!7?_#80g#49O?CC???C?_A??_!4?OY#35G#96?O#240!5?G!4?_$#117!4?aAC_CO?HAA?@#68?A?O?C?O!9?@@@!5?C?C??@?@ECPO?G?G?G?C!5?A?@??A!8?@!5?@?_??G!4?C!9?@#71?G#61A@B#25@#40A#89!24?___g???AA?C#235?GG#206OA!5?_#44!5?K#25A#121??O?@#29O#44???G??_CA!6?@`IMB??O#120??@#162O???A#228O!8?@@#47G!8?_??O#22!7?__?J@O#54?C!7?@???GC!5?_#87G#11C!8?J#164???@#54GG!4?CC#18@#186_#207_#97_#163_#101_#56@??GA??O!8?Og@#46?A??@?C?G?@B?A!4?GEA#201_??_#83A?P#46???OG#26_!5?CA?A???GC#32C#48TC?C?C#91@A#157_G#83@#34B?G?G!5?O#157@A!7?AO!4?O#91K#203???G$#131!18?__!5?KO__O?O?GG??G?O#167???_#166O#194??O#70!4?C??G?G#64_???aA!8?G??O#65!16?A??G#86!8?OwAA#67???G?CG!4?O#93!22?GG?c??O??A?_?GE?AA???A!8?A#85???@YO#198!20?_#154?K!5?@???_?GC?p_!5?OWAA!4?G?A#39???A#7_?@#47?@??_?ABR!5?O???_O#72O#156_#75G#15CO??OC@#159!6?C?@#43gCA??OK!5?A!4?A#96??_???A#54!6?C??G#57!9?E#27_#31OG?_#191??O?G#95GO#31!5?OG!7?Q!9?O!9?K#18C?O#77GCG??@!9?G#155c#154G!5?_$#70!19?A#107!9?A#161__#185_??o!9?O#107!9?A#62_??O??WKoc__eEFW_Kw_S[n|u{}dnFqnNNNCOZ@q?Kesvy]KBBGgb\M??@B@wOAaASAHCEVX#90!14?o???WO?_!4?_O!5?h#86!8?A!6?C#25@#199!22?_???G???OOA#72?OGC???C??QC?oo!7?A!7?A!5?W#155?G???_!7?O#83_#23G#13G?_?G?@#223!5?@#47O}?P!4?A@?A??@?C???A?G!8?__oA???__??U?M!4?_??S!4?@MIqP{KBC?Oa!8?C#37@?_?@H_?G!6?@!8?GGG_O#229A!9?G!6?@P@@B@DEN[]my_wCk??O!7?cO$#96!19?@#171!13?O!9?O!4?O#60!8?_?G!9?oo???C#85!37?O#123!29?CE!4?A#225??C#228KKE#154_w???c!7?_?C#164A#89!4?_#201!24?O_o_O_[q?F#155!4?A#75A?GOGHH???Ga??AA?C#6!5?Q#11G`#31?A??G_!4?GEAA#7_QCC#74!4?_#37??E#34C#35@#155!8?A!5?O#168???O#187K#158G#57?G!6?@O!5?A#30!4?@@#82!11?G#28?G#33O!4?@#163OB#11!7?_O??@?A#6A#16??G???CW#42@#84!4?A!5?C!7?OO#55gE??O#185A!5?S#162@#235A??O#188?_$!33?_???_?_!6?o#131!10?C!5?@#117@#187!88?O#195HA???C#206!42?O??_??KMG@@#30!4?A#39A#93?AA??@G!5?C#154!12?OO!6?@#28???B#9@!6?OA???OSi#75!6?C#78?@??@!7?G?B???C#101???O?QK#43!7?gc_q#99!11?@@#154???_#156H#52!8?C#36@#27g!5?cCO@?AA#96!6?@#154?G#163O?A!6?C#168@#100@#91?@#48?O!4?G#35C#93@#202???@#239Ga_!4?B?A?cC??OO$#202!34?_?_!5?_?__#109!11?@A#155!98?O#41@#209G#185!42?C?@C#162!19?@#78C@O#156!15?_#94!5?A@#93A#29!5?G??GAB@??@!7?_#31!4?C??A#15A#197???G#190?C#98?_!6?C@#190_#34A@#48!8?O#227!19?C#207C#7!10?O??@??@#15?CC?@#49_??I?A#159???O#155_??G??@#90@???A!6?GG!6?O#231C??C!7?A$#176!34?O?OO???_O#168!115?_#211O#164!43?@A@OA@#185!41?C#199_#165_#6!5?B?@@!9?OS#55!8?G@?_!8?G??`_!9?@?[OG??O@AAAOGWOC#235!8?G#223_#54!11?G#207C#196O#90C#8!4?A#34?_?A#188!8?GC#92O#47?g#15_??_#201B#191!6?@#83A#27_O#186A!9?_@$#190!158?O#168!46?A!5?O#63!46?O?G#43!7?A#83!12?_#35C!4?C@!7?E#59G?o!5?A\G!8?@???_??O#79!21?C#197?C#157_#167!17?C#162OC#32??A#11O#16G??_#96!6?@#224C#13_?_?O#163???_??A!6?_$#120!259?G#92!22?O!4?G#37!6?_CG#31@#18!4?@#158G#224!43?G#186O#185!17?@#186@@#26???_#35_C#166!10?@#208@@!8?@$#130!259?O#191!22?_#154G#94!11?@??G?A@???P#166!61?G#95_#45???A#5O#72A#188!10?C#15_O?G#226???O!5?@$#157!283?_#83!11?_!5?G#155C#167c#192!63?A#73!6?@#198!18?@#243O!6?A?KO?G???__$#54!295?O#199!6?_#242!91?KC$#201!302?G#192!91?@#230@$#204!302?O-#169KWGCC??_GGGSgOGsgOOO_OO???O???C??@#80A!7?A#68?OQP!5?Fo???e__#66!6?C?HoAA??Ay@CE@C???O??I?Cq_sEFB!6?@?A@#93_#68?A#67O!9?@!5?A#93???_?_#119O#90OO???A??@Gq_WGAE@?A@GB??xoWSWO?_??@!6?A!4?_#77??G#29!7?@#123_#121_?_#160_#45OC!8?__GGW_NB!8?Ca???@??A#23_!4?O#33?_O#164_??C!6?_??C#11?_@#43A?A_G??O_!9?OGGOC#168O!8?O#206_?G!9?@#158G_#78??CD#47Gc!9?Eaa[GIA?G!4?OAA!4?@!4?_?@__W#33@A#48C?X?G#50C#46?A??@#90G?_#166{#157@!4?O#55_G??@???C!5?_!8?G#48O?C?A@!7?_O#91G#96AA?OG!4?__#233O#212?_#210_?C#207?cSC{$#161@_???_A?A??A!4?A#171A!9?@@???A#67S??A!6?A!7?G#25O#64C!9?O?O#63A@#70?GSO!4?O??_o!4?w#85G?@A!4?_!8?@@??C!7?O!6?O#45G#77!12?C!7?W!7?_OK!5?G@CP??@#44OO!9?@?AOA!8?O#90?_???GA!5?O#94???_#72C?B?CF??O?C!4?@@A?GGG!9?O??_G#94O!4?GODA@#35?K?Cc?_?CKOB?C!5?OCoO?G??AA!5?C#232OO#170A#52@!4?_O!9?P!7?FAJ#81OO#46?_#26@#43?C@Eg!4?GC#155G?Q#32_C#35@!9?CCGO!5?_??O@#47_!6?B???C#31O??_Q#15_??@#9C#77G!9?A#33A#58G#32@@_#188O?O!4?E#72C#84_#141A#156CC#54A#185G#35_#157_#224_?@?OA#222!7?@$#166O!6?GcC!4?S#185?CK?G?AAA???A?@?A!9?@#166@#132?@#96CAD!4?G?G#62?B?A@A???FFpauB_?KGK@??@ABFVuAFF?K??[A#97??_#75GC!6?C!6?O??W???O?G?G?G??GGA?C?FB?A!4?o_!4?@??__[C??QC!4?AIPWE???oG?E?AC?OO_?CB#164!12?_?C?C#159A???oO#77??A???O???@?A???@???A#30??O?N@???`G?C?@??D!8?_AAG?OG@C!6?@??_WC#47a??@@@?C@A#83@GO!6?@#55?@??C!5?C???_B???G??gS???S#27!4?_#83?A?DG??_E#154@#95@#34O#28G?@@!5?C#55_o???CA#28???@OA#41__!6?@#34C!4?G!4?A?A#91O??G??A#239OOBB!9?_?@?C!7?C???gO$#157_?_?_#155O#121@??@@?@@?@#132@@#131@???!4@#96_!5O#157K??@!7?@#60!4?B!8?@!4?olGGGO#67?C!8?_#60??GG#63@d??@??G??@??@B!4@???CC?CG?GFCAHDFLKANFFDDEEC@?AD!7?_#93???C_aC?APC???__?A?A?G#185_#39!4?e^FEBOoo?MEN@@ps]jNdjeU@VDFYN#201__G?C?I??@#93??@???__?@??@#201G!8?@#29_O?wGO_#42?O#162??Gc#95@!5?GAC_?C#9?_!4?A!7?_FBR#22@#159??A!6?O!4?_???O#82A???AA!7?O#43???WO?JOK#82???_?G#163!8?A!6?_C#59C#7_OOWK#6C??A#27@#72C#36?OA???o@[OA#9CC?@#159Q!5?G#43A!5?G???A???_C#159G!6?C#37O?A#96G_#243?G??_?H#242@!8?A#234??C$#185A??A#164A!5?A#123@??@#186???CC?_#194C_?C?K#25_??_#63!4_KO#130?@#85?A#65!11?AO#86!15?G#68crvpCe`?K?o_GG???O#67_??GwW[#92_O_OO_gO#62?ABB@FMF??@???A#64?C???A??@@???A#85???_#123@!4?A#154??C?__??H!6?D?@#195?_#63!6?_G@?H??oo??O???@#156!12?O#75@!9?CodO_GO#164_?A_GS?@!5?A#93?@#39G?A@#9GAMC@@#165O#45A??GC?CO?GAGG!4?@???O!9?_?@#90@!6?G#195G#169O???_?A?G#81O?W!7?C?`P#36???a??_?_!6?AA?OO#168??O!7?G#94@#15O_?_#30@O!4?A!7?_@?G#15G#26?A#163O?B??_#54A?@#16@!8?O#13G?@#157O#97A?c???_G??@??GO?P?A?_??I?O?@!9?_$#176?AQ??CcS???_Sk_?O_IaA#170G#192?COG#68?_#61?_!4?OOokW{{kksmgkysz~t_HF}{W\]|~NAo_?K??@@!8?G!6?C?WCB@#120!4?_?gk?SgwoOo_?_?_O___?_!4?__??_!5?_??G?S[KC??B#45?@!6?_o[G!7?AcweH!5?C??@#85_??I#199!16?O???P?A?A?@#78?A???G???G?_?aSAA#41@?oo?F???_???gGE??C?`W?A@??OO??@!4?_!6?C!4?a??_?O#100g#101_!8?@?@_G@!8?GG#31!5?C#35}_?A!8?c_`M@P#90c#93_#73_#52G!4?_G#31?A!9?A#37H`G??AY#19?G#27cOI#44C#191GG?A#91@A??_#52Q??B?o#28A?O#7_o??@#90@A#229Gg??G?@!9?D!4?@!5?@?A!5?@$#183?C?G!4?O?S!4?G!4?O#202???C?G?ACA@@#163?@!5?@#69!30?G!4?GA#97O#86!8?OA@!6?O?C!9?G???OG??OO!5?OOo??_?AWGGCd@A?_O??G?@#156C??C#72???A???A!5?C#86!7?_W?@?@!4?C!8?_#185!7?G@#155A!4?G!9?G??@!5?GO#75?_#26!4?_CU??O?CA@???A?@!7?@#16O#27oO?C!5?gQG#7O#31???C#93??C#95A??A!5?_#56AC!9?C!7?EAQC!8?gOG#78DG!9?C#100@?@#167_#158A#5???CC#26A?HWG??@#32?_#16O#34@!4?aC#45?_#83?O#185CC#226?o?G#97c#155_#37C??H_Q??H@#17?@#26G?C?C#42A!9?AA#185?C#208CG!7?@!4?@?@@AFNKFFMeIIWiX?t`OE$#134?@#120@@?@#163O#160A!4?!4A#168??_#134@@@!4?@#97??GGG?A??@@@!6?@#89!31?O!8?___!8?G?CAS!4?G?O_?_!7?O?O!4?oO?C{?_?gQO{`?AGOD#162??O#159G#78@???A@?A!5?E@#89!8?O???@A#228!22?ogGCC???B#47!4?O!7?@#100???O#156??K#38!4?O#22?_F@??A#168?_#232O#154O@!5?C???A#73???@#33G!5?G???A#54!7?CC!4?C@#158_!4?C#166C#167??C!8?@#41!7?G#48@???GO?C#57@#91!9?G#185G#77@#55__#81O???A#13?G#11A?Q?cCGA#54!4?C#52_KD???@@#72??A#87@#201?K#228_??O#57?CA#27O!8?EAA#130O#185_?C#49A!5?KD??Ca!5?_?O!5?_O$#189??C#158OO?G#118@#119@#167o_G#188!8?CC??aAA??A@#76?OG#49C???CA#88!37?GGO!6?O#69???@!5?E?A#70A#117?A?A?G?G#69??O?G?CEIG?A!5?GH?A?@@#199!15?O#168G#164OC!8?_#168_#67!11?C#24_!4?G!5?_OoYSWGMGYWC#162???E#206O_G?C@#162!9?@?GC??G!4?W#7!9?K?B#235???G#159A!5?C?A#75G#15!7?AC!8?G#72!6?@#78A?C?O?C?O!8?O#99_!6?AA#54!6?@#37?O@OR?@!4?@OSG?O!5?OG#201@#207C#9!4?gC???AC#41W#56!4?_#74A#53_#207!12?A?O#59??GC#56AC#36KCX#166!4?_#93G#235??O?O#41C#203G!6?_!4?@?CAO!8?CP??CO??OG@HptB?aB?Mjx$#130???_#171GA#162???A#189!10?G?gWGOC?C#60?_#62???GG#65?AA#118!59?W!4?C#61!6?@#121??_???_?!4_!5?OW!5?G??GB@AD??A#187??O#95_AO???@@?G#187A?_#40!12?G?G???K_#167!18?@#170@?O??G#168!8?O#185A!4?C!21?G#47o_Wep`O?O??K!5?O#13A!4?`?GOO#155!6?_??G???C!7?C#94_G#58A@#37W#57A!8?G#49!5?A#33@!9?@??_#157?@#227O#98O#57C#56C#209?O#233O#75!6?_#2A#8Ao#43@??G??A?O#230!12?G#74@??G!8?GC#188??_#225??@#233O???C#202C!4?O?G??A???A!4?G?C???AAgG??GOOO!6?IOC$#92!4?@#156G#166!19?_??G??C#66???C??_?AOOG!4?GC?A??_@#87!45?GA#40!10?@#117!6?@#201!28?GG#41!5?C!7?KB?G!4?_??K!7?G?C???@!5?@?B!9?OO??K#195!4?G??O#90A@O??_#78!18?A#31?A!8?O?Cg#42?@??@?A??A#36!9?G#74A!6?CC!9?G?_!8?_A???C!8?@!6?C?AAA!6?A#44!5?@#22?O#58!7?@#231!12?C#203C#224G#35???O_???CC?HC#234?O#155???@#72@#54@#162@!4?__#130?O#155??_#163C!6?C???_!9?_$#73!35?A#60!77?A#119!6?O#159!38?C#211!41?O#187a@@AC!8?AC?A??A#54!26?I!4?`!4?A???G??@#32O#26CO?_CKAH#154!4?_??_#201_?G!4?G#187_#79!4?C#59C@@!5?B?_#79!6?C#42_C#94???_#72!13?@#235??G#232!31?O#209O#30!4?@!5?_#169!10?G#163C#167O?A#79C#80?O#169!4?A#245?OWO!6?W!4?WO$#199!159?@#154!43?_???G!7?C_?O??_G??CC?DA#186!21?`#37!8?O???A?C#23?_#157!12?@!4?@!7?AA!4?Q#200C?_#46!71?_#48??_@#243!12?_#191A#73?o#35O!4?C@#224?G#246?G??G!6?G#236A$#165!204?GC?C!8?O#159_???O?G?G???C#29!32?@!5?@??g#73!9?C!4?A#84GG???_O?_!9?_GG#33!71?A#226!13?_#83??g#186_#189!9?_!4?A?@#186_@$#95!204?_???A#209!8?C#95?_!4?c?_O??C#52!31?Og?C#77!15?A#79G#238?o#55A#163?_??A@#43!5?O?G#54C#191GO#195!94?@#158!9?A#235C!7?O??KC$#28!263?A???G!7?G#191!5?O#92@#165!4?@#96@!8?_#95??G#205AC#37!107?_#166?G#159C#226_$#49!264?@#91!23?O#98A!5?C!7?CPO#90!104?O#230?@#233_#231O$#55!264?_#93!23?A#186@??C#168!7?_#189O#165GO#176!106?G$#72!289?G#237?G#190!8?@#204@#207_#241!107?@$#231!301?_#211A-#202_???OOWOO_O_?Ogo_o_oO#97OGCA#61GgGfn~}~n{~NmrNNGb{~nMN^M]BngO}rju{xzf~vkWsW_!4?^XEuMOwwO__#87A??A@?WA!6?gSgiO#89{oW?cO__?_gUwAC?EG?EKCI@CJHGGHNG#75GA??_CE???e{_UH^h?@!4?_!4?[NCO??@?C?OWnACCU_??CG???@#159O!6?_?G#75O?CA_???OlGbOO?s??AA???Q???O?_#26__yGCB@!5?_???__WoII???B!4?@!4?I???A#29@#83???_?G!4?a??C#101O!4?_?A@!8?_C#47O???GYA?O??_???___?K?G?CC?ED!4?GH?_o?C???O_?@??_H#59CC#48??O#203_!4?C#192C!6?_#56@??CA#159O?G!7?@!5?@#188@G#42O!7?A#41A#34??@!4?O#97@#40?@#202??@!7?@!7?E??_$#97C???@#188CAA???AAC??C?CABI@#65o#63oOOo!6?A#66!6?EC!6?@`??EJ???@!7?_?AAp`?O#60?A_!5?_#44O#131?G?D!7?@#95?_#118G???O_!5?C?G#86???_!9?B@!8?E?@@#41?E#77I@@aO@?@?q!7?_!5?_#63??C@?G??E!6?_??C??A@#186O?GC#228_?GCKKCA#155C?@?GC#26_#30G!4?A#78???@A?KC??G#159A#30_o?GO???oB!7?O!8?_GoC?@GOAG!8?OG??B#47C!4?E?G#168G??_#210C???G!9?_#200_A#81?C@?A#46C?K#31CAE#82???O!4?C#18@#27E#8AA#49_O#34@!5?__#18_#28C!5?O???K?GG!9?@#230W!5?_WG#158@#95@#98_???W#18_#34B#32B?@#239O??OO_??UO??A#13__!5?@!4?_!9?A!6?w#203?Y???@??BB@AB#192O$#193O??G#169C???!5@BC?A?A@#157_#76_#66_?G_EEOO#68?@#44!9?@#64!4?@#96_?O?C??_#63??O!7?O#70!6?B!6?AD!8?A#93??_?AG!8?@!8?AC?@???@?@#195?O!9?O#93_!6?ooC!8?o!5?CG??@??G???_g!7?A??__???C?A@!8?O!4?OB!7?@@???@#54_O#47GC??O!9?o?AMKGML[WI@B!6?GG_???__U#55@!6?_??_??O_?_B???G!6?_#84@#95C!9?g#82A#55_?Op!7?o@?[OC???_?G_G?AR@B?@#20O#31I!4?_??IK?G#34?CG!4?K@AA#232_!6?O_SQ#167O#58O_?_#37@!4?A#231CG!7?D#96_?O??A!5?@??_!8?@???A?@A??C#239CA??A?C?G#224GO#191G#222C$#96@EA#192o_g_gGGG!6?G?C?CA#193A#171@#60C!7?O??oOKoooWB?OoO__!6?KCG?EC#64!10?O??c#63?@Pg??G??___??A@#92C?@AE@A@!5?I@EBGKC#154!6?o#164O!5?OO!6?O?O#45O???W!7?O?C_S#159G!4?C?A#29_#39W!4?bmhAAApN??gww?@D?BA@#187o!4?O#211Oo!4?@#45?OOC??W_G?OS??_GWGC@_OG@_@A#65?A#29GN@@@#31O!5?o_??A`C?C?o!4?O#35C@@@C?GGW???_c?EKC#41G#73@@!4?@#205_#232A!4?_!6?C_??W?A#165O#99?@#52GG?A!4?O??@A??KBo??OO??OG?C?_@#78C!9?_#54O??@_#46?A`#57A#91??_#30C#231_!7?@#209@#155@?G!7?C#49G???A???A#202A_?O!5?@#27AAAC#222?@#162@#58??G#55GC??AG???C#134?@#42_OG#65E#159?_#243wwK{_O#235C?O_#198C$#88A#189oO!4?C_Ca[KG?K?CGGK?C@#67??@#49@#62G!5?@??@#49!13?_?O#62!5?B??G?GBDJdW?[gJ_?XG_C#71A??A#118??G#76OO#85SW_???_#117!4?_!4?C#121??O???C!5?@??@!5?G??C#162?O#211_#85?@C#24_?]#72?@???_??G#47G#41??AB!6?_WcM??_!4?C!5?C#85A??S!6?@#165o?O#201?GCI?OH#77__??H!9?GC#156??_#39!7?G?_#38?C#22OSCE#27G#28C!5?O#11!4?G[_C!4?@#54_!7?Q!4?G!5?AO??OG?C#167?K!4?D?O@#205?O???@#236G#238CUG#170G#56??UA@!4?_!4?G?A@?G!9?_M?E#46OC?O#27K???gG?A#52PAa@P_?W@#33??@#49@!6?C#201???G#190C#59GGsG#35_???C#97GA!4?G???_A!6?@#49@!5?@AA!4?C?A?@???G?@?g#162@#242CA??C#225A#229_O#196?COG$#175G#166G???B?@#183A#190O??o???O?O#96???OGCB#25!25?G#68O?C@!6?O???A??DMACc!8?!4@#120??G???Ws{CH]pUJUDLA@exGBRRjXRHE|GNGEMHA??EJCADFA#39??_O{_@#78?GA??WAC!8?_?O?C#223@#45OBoR?O??@_?_CO#77@?@?G#156??_#185_?GGK_??A#168@#170__???A@#41G??_?SC?_G?A!9?_@?KNtB!4?_?K\!4?ABDC??BA?OpHoCA!4?Q#9??_#155_!9?A??A!6?G!5?C!7?@???A#74??C???_#28?@??K!7?A?@?A#35HA@!8?@???O?C!5?ACC??O??G#9C#226O!6?AC#228?C#81?AC?A#46C#50@#42_#28_O#156O??@!7?@#48__??S?__?OYG_ALCdH?_?c[KK??QSKE?A#241??CA???G#208C?@?B$#133?@#194_?G?C?C?C!5?G#185@#65!33?O#67?@!25?@?@?GOOO?__dE@???_#201!22?_??_?_?_?_??_??_#69?B#63CA@#95!4?@H?@?`!8?W#228@@#156!6?G#40?O!7?O??@A#154???O#195o???C_?CA@#206PA??@#72g!4?@?B???Gc??ECGOHcR?WC@#9!4?G#33???G#35A?_!6?O#9O??GCC?C#28??G??OO!4?@!4?C#52!4?_??_#95@#37G#191??O?C?A#231G!6?_CG?A?O#190C#53???@#43G#36?C!4?@OE!6?oHC?O?_?G???O??@???G?C!5?O?A???KqE#42O#12G#15A#210__???OG#237??_#94?C#47A#55@??C#84C#43O#27a!4?C#90@C#203O!7?A@#32wGOo__oWSO_Ooo!4?Q_gskA??@#233???@#245O?GGO#97__#195G#226_$#161??CA#171A#170!4?A#199???_O#168?@#97!64?A?CC?E!6?A#90?O??C??@?A!5?_??OC!4?A!5?@?C!4?A!9?o?KWE!7?C???O?GC??_#44!5?EC??G?B!4?G#162!5?G?_??@@#232_#199?O???C#158?A#154_oC?A!7?A@?O!6?CA#23!5?A#43!4?_?Oo!4?_#95!4?@#187@#94@#7EAA#27?OOGKA@??C!4?G#79!5?G#198A!6?_#200A#186@!6?_#158G?A#235!5?@#206@#37!6?@???GOBG???A?@?O?_??COO#81?OG?AA#16_#26A?@!4?@#50?_#37CCO???_a@G#100?O#26C#166C@#202gwO#186@!4?A#51?@#82GO#197?G#91O#33?@?@#242o_!9?G?@#35g???G!4?G?O??K???_!9?P#246???_?O_$#131??@#176C??@!7?@@??@#88!62?CA?G?@#121?@#86!4?@??O#185!23?OO?O#123?G!7?C#25???G#94!10?A#191!5?O???@#72?O#89!9?O!7?@???OOW?C#160?C#164?AIM@!4?O#95!5?@G???AF?@?W???O!4?C#42!12?O#37_#72@?A??_??A#43!10?_a_?C`C?O!4?O_pW@?_?KOO#242??@#226_#201?@G#241OG!8?__G#35!9?PZ#34G@M#53??C#54_!5?_?G?A#32?_#48_#59?_#57OG#19??O#11K??_??O#55???O???O?@#154!5?G#45@#208KA!4?A!9?G!8?O#163C!7?C#33?A??@?CCC#54???A!4?A#44!7?@#30_#232!5?@#188?@@$#167??G#132@#186!10?AA?A???@#89!60?C?AC?ICG#130???G#206!26?__?_#223?_?_??_#228?_#195!21?G?A#168G#24!14?@!9?AB?@#209!10?A#223@#90_?GCA?A?CB_!6?A!4?AB#78!17?A#54C#75@#36!15?A???G?_#37A#15CG#74O!6?O??O??cO#165!4?G#170C#169Aa?A!4?@#27!15?C#81!5?H@#43?W!4?C!5?G#53?C#15!5?@#30gP???C@#56?_!4?I#77!7?A#198A!6?A#53???O#57A#52@?A#54???C#241g???_?_#229G@!4?@#24??C#17CK#29C#37G!9?G#155@!4?@#33!4?O#230!7?A$#187!122?O#160!4?O??OO#185!22?CA#164@@???A#120!10?O#86??_!6?GG?C?AA#212!9?A#78!6?O#94???O??_???_?_???_#155!16?@#165@#34!16?CC#52!5?@#7?EDB#23@#185!5?C#197C!5?B#166???O#190@#159?@!8?@#57!19?C#11!7?@#26@#74!12?A#41O???_#51!8?O#205!8?O#185@#168A??C#185!13?_#30?@??C#80A@@!6?G#61??O#15??A#73!7?A!7?A$#132!128?O#154O!24?A#162@_??A??A!8?O#121G#39!39?C#187!4?@#13!48?@#42G???W#230!5?A!6?@!8?_gWICQ?@C#59!16?A#45!22?C#168A?A#91@#206!18?O#80@#157HG!16?A#245O_!7?C#246CE#90!15?A#17C???_OOG??_$#198!129?_#187!24?_#201sKC???@#90!104?O#6A#33O?A@A#100!4?C!4?C?O#228?a#237O#233?S!7?qC#156!42?A#43?C!4?A#96!17?C#212_??_#235!13?C#100C#41A#83I#166?G#233SG?C#130O#40?O#168!15?@#36O_O$#199!155?G??A#100!107?_#49_#45C#36!8?G#207@???O??A!4?@#99GCA#78A!9?G?o??_?_?__??_O???A#33!20?G#185@#155A#162!20?A#97A#223!17?G#72@#230???_#243?GGG#79!18?A#50G$#190!156?O#206A#159!119?@#238!9?_#211??E#81OW#94?@!9?_#73!38?@#204!22?_#93!19?G#168!5?A#224?A#226!18?@$#192!294?C!8?@#162!39?A$#212!294?O?O$#57!295?@!7?O???c-#202H@?@c_QapHgXd\]LD?@#66c_AOPA#44?G#25!12?@#66!8?C??B!5?@DD?A?AGDAD???_U?oW__`?OG??@_@P?P}Q#89?A!8?A@??OBGAC?@AB#206?_?GgWH~LW[Z]KAAAB@#162@!6?G#95@@??GKA#41?_?_O???K!8?__O???@?A?A???A??A#154H!5?_??KAOK??A@@#39_?K!5?@???A!8?B?A?A#159G#187O!7?_??O#164G#72C#11gG?_G?C#41??B?SOO!9?O#37a!5?O?C#100?Q???_!4?A#47_OK#230oO???A#241?ABB#200C#101A#82A@GC!9?C@#30_!9?G!7?G!5?O#91A#53O???C??OO#163C!5?A#210OOsB??I!9?_#36C@??O!6?G?C?C#32cF\POKX^zssO]PA_]sJGGGsg|NsbYRYMA#49CGGG#239_GJO@!5?A$#204__?s?O?WG!8?@#85OW#68GC!8?C#48!17?G??C#96?C#25D@#76C!7?C#85A#69!6?_A#60?@BO!4?@#69?_#67??AA?@L~wW#120BAm]zMG`!4?OFDBBE@?@#132A#187A#191?@#199@!7?O??C#72G!9?_OG_P!5?S???C!7?GG#89??C?@#63??_Co!6?A#77@#187_c?SKT???A??@#75OO???S?_?_XGA!5?_?aGC?D???CC#227O#232_#47?OwaSEOCI#185O#31B!4?@@@???Ha{GG?G#55O!6?_???_???@?@O!5?GOO#210@!7?o#237?_GEO{#206?w#158K#99[_#53O?GW!4?_?C#34O??O!8?C!6?@#41?O???g#57_???G??A#52P??@G@#28_#30_#155A#226O@#230a@?_!7?C#48OKYH!9?_!6?A?AI@RC_CJJN@MLP??_SCEBP?_!5?OWG?AC#96A!5?@#245C#162@?C$#208OWgGWKKC??A#203?A#211A?A#166O?A@#60?O!4?C?G_??kee?r?`Av~yO@@!4?_ooo_!4?OxC#70!11?_!7?O#88__#65!5?C#87???@C?@?`CpD?C#161_???O#164O__???C#223W#204???Ca?___!4?C#45_OGA@!7?_?@??_{Qo???__a?P_[@?_p]I??CA!4?@!4?`?G#93E#201O!4?K@!7?_#41?_?kAGGA?Oa__A!8?E!4?C??AE?I?@??AC#26B@o?s{[PyZUK?o#54AA!5?H???O@?O!4?AA?O#98_??@!4?s#228@!5?EKC!4?O#169?@#84@#59G?_!8?G#11_#31OO#37@?@!4?OC??AT???_#26?G#47I?a??WCC!4?GC???KGO#159_#228_g#229KA#212oRqO?XI@#101_G#49G!4?A!7?OO#17_??_OWG__g__!5?_?oC@@??@?G?A?HS?G#42?`??E!9?@#33_#80G#37G$#192EAFABB_@#206ACDC!5?A#73_#62??G!8?G!4?G?GG#68!10?AD!4?_AoaA??A?A?@!4?@?D@!4?_??GO#70!9?C_#75OO!7?EC#121A??GG!4?A#170!11?_?OG#75O!6?oA_???cMaOTA@@JIK]Y@@_O_}vFE?CGIA@???B_!5?_#85O#162O#165GK!4?wo?XKA#93A??MC!9?GB!7?_?_O#30@@?@EA`@!6?@??_GCAAA??__P?C!6?O??B???C#48GC#79G!9?W?_#77G#59_#207@!5?C@!7?A#170O#165_#94UE#55@O??gGo@o?G!6?_G@H?@???@?O!8?_G?B!6?O_#43ACA@#100@#197A#233@#211GG?K?E#202?__G?A#56_#79@???A#100A???G!6?@#37A#12?_!9?_#13?_!5?A_o?C???GCc_#35?cA?C#73A!9?_#56O$#190?C!7?O#167???_#97___#67_??A@B#63A#67!32?G!6?C!11?_#63???O!5?@a?@a?_#92??BC!9?@@G!8?@#165!11?_#159_??C#39_wMToE#90KAASE!6?C!5?@#63O#24CA#77???G#44!5?O!7?_??@#90?@#120_#195?A@??A@#228AB#158_cA#90O_??_?G!8?B?C?A?@?_G??G#92C#40?B#235_#238O#43???C?@AQ?K#83G#168C#209C#158A#9O???eD#47!4?B?D?DcC?AC#98_G#83A!5?G!9?@A?G!4?O#37A#211_GrB@!4?@#199_#78??_?@?HAA??@B#94@#15_#16?__#74A??_CC#49??N??A??O#31??CB!4?@A#59C!5?AG#84@#35_Q#83C#185G#192C#224C#209??CC?C?_#170??O#241A!4?OG!8?@@#42C!9?A#27!8?A#40_!8?O!4?@#33?@#97_?_#155O!8?G#93O$#211??O#189???@???O#168!4?O#165G#96WCAT#65_#61kk|~r~v^r~RXXvKvU{G?Dn}}~~r|YGNJY]p??gChkP@G{apUrIGGCC[LSKADENIMK_C#85!6?ggO!5?A#162?__!5?G!4?@#223!11?@#78_??C!8?A?P??_!5?@A!9?O#90!4?C#40!4?G!4?G#185!5?@#199QA??A#186C#155K??@!4?O?@#30O?@B#94?@?C!5?_COI@#77??CGG#168O#35!5?@@??@@#237_#210_#198G#74??A#95!10?__#100_!8?@#34??@#49Gc?CcO?C#159C?a#186C??A#78O#158I#95_C??C#232G??W?h??g#57!4?@??Be@#54@#46C!4?GGC#43G_?W@???A!7?O#83?_!4?C#94_O?C#81A#37KAA???O@@#186C#205G#208!4?G?@#232_A@EA!7?G#45O??O#242B@#239C??@#73A#56?@#23?O#96?CC#35!10?G#23?AC!9?_?C??@#188O!9?G?A$#209!8?C!7?A#130C#76G#70!38?MO#64??O#85!18?_?O#93!13?C#117@???qO?XYKC???C#198O?G#154!15?OG!8?W?G!7?G???@#159@#40??G#24!17?GKF?_?G#168???_`#159_?O??O#77??_G#45KC!4?PoOcol?OO@@@??FO!4?A???@DK!4?_G!4?O#94!12?@#35GFA@GG?B???cGAB?A??G#97??@#163@#155O#157_#84O??C?CA#43_#90_#57@#159A#233???cO_?C#56!5?WACOCO??A!8?CSQAb_?G??@#18C#16AA#188?O#95_OC#80A#74?A#82g?A#36??C#50@?_#191!4?O#203!6?@#231_#237GCCG@@#97C!8?@#243E?AB#83G#27O?WOg#58!4?A#36!12?GO_Q@?A#27??A?@#58!4?O#28@#168O!8?AA?_$#210!9?A?A??@#62!43?G?A?Pgoo?WMhKS@OIA?AIBKA??SOOKwG#118!12?KW#168???_?_??_#185G??C?@#41!13?_OC#24oiN@#93OCT!9?C#29?_#39o???AKNB!4?@Pb`w{}^pKSPwvCESC#191???GAG#211???@#162???@?O#78?O?@#29?EC#154!5?G?C?@???Og???_G??_#78??W?OG?O#232?O#43!17?GY@CC???GAP??B@A?CI#196?G#189_#198G#187A#166?@#96C#165A#94?OO?A#192G#238!4?O?L#81!7?A#47c_?OcISKO?CFG@?_#97A#81?G_??o#32@?_AG?D#156??G#54K#11O@@#51??_???__#98G?A#240!12?O?OOC#53_#37_OCC@#74_!9?C#15G?K#20!20?@#224!14?_#80@#203@!6?A#224?C#166C$#199!9?_?_W#44!67?@#89_#130!21?_#199!4?_#195?O??O#209_??W?O!4?@@?@@?@@#168?A#63!4?G#77_@_?GA?CAG@#44?A!6?o#29!18?A??WW#164!6?@#95_!5?_?CB??GA!7?G!5?GO??C?P!4?G!8?_???_A@@#90!13?_#74?a@!8?OO??@??G??@?E!9?@#51!19?@#52A!8?O?A_??O?c_??@#226!4?O#90?AG#78???O@@#157???C#101A???G#206!9?@#81!4?O#239@#84A#168_O#163g???A#208C!5?A#13o_#167!38?_#202@C???K#41__$#201!111?O??Ow?cuEaS?QEAc?AK#40!9?@#94!5?O?@!5?C#170!34?O#185!9?_#72G???A!4?E?O!5?OG_@AAG#223???_#54!6?_g??C#27!21?O!6?G#42?C#52??C!4?_!5?G#224@#99??G?G@#36!22?G!5?A#28O#159G#98@#155@#59!6?O#48A?O#42_#164!7?C#46!4?Owo??_?O#187!19?@#34?AA#159CO?G?G#8_#169G#187G#188C#167?A#202@#233!39?O???A#163@?OO$#156!111?G#123?C#160C#156!36?G!47?@?A!9?CC!8?O#55!13?C#72!24?A#157O#196G#163CA#36_!7?G?@#197??A#242C#101!7?GF#35!25?_!4?EO!5?W???GG??G?@@?@@#56!5?G?@?G??C#226!23?O#35__!4?_?_???A?@#76!36?C#229@???A???@@$#190!113?_#164!99?_?GC!6?_oO???G#188!30?_#231_#226_#187O#56@C???_?oW?__B!5?C_#170???@#99!27?A#95A#185???C#157G#36!9?O???_#34!10?_!4?@#43!26?@??O_#209C#233C#240?A#187!42?G#241oO?{#185_#100O$#185!213?G_?A#236!45?O#228G#77O#81!7?_#232!5?O#186!42?E#17!9?C#28CAC?e?C!5?@#236!33?C#231C?A#229@#169!47?A#236_#237A#226?O#197G$#163!213?O#201W#159O#166!48?C#241!13?C#15!53?_#32!46?@#41@??OO#242!46?C#238G#207??C$#191!215?C#243!62?G#33!53?G#185!46?G#47__#240!50?d$#191!380?C#91G-#97Oc_!9?aLEBC#49G#66C?D?E@#60!4?n~rx|hHnBONj}{}~foGF@?NJ?@BAO!6?@???G#88_?o?s_@?Y[kmm@xhD@?g#120cCSCAA??@??@#207EA!5?C#228CC#210AE#199!5?_!7?@_?C#75C!7?JT?@#87A#164C?C#157O#73@#44GE@?STO?GA?@HA?O!8?C???P???_#121C#159GK??@!5?A!5?C!8?CA???_?A!6?O!6?A#165@!9?A#101@#28_!9?C???_#162A?A#230GC?@#35__GA@g?AA_!4?_!9?G`#30@#165G?_!9?G#55_?D!5?C?@@OSC??oEZIA!5?IC!8?G?A!5?O?G#15??AA@@#27C#168S!9?O!5?O#41AAG!6?C#49@C#34_!5?G?C#12_!8?@???WO??DIECc!4?C#97?A!5?O?G?G#43G?_?C#196_$#158C?K_???E??A?O#96qHg_F#63???C#40!8?G#48???A#44!9?O#65!7?O#68O??`CSug??g_?@?GGC@#97?Y_EC?O??aCQIC{A#67@@#93_#162_!5?A???@!7?@#158!8?_!4?C#170A?@#165B@@#63A!5?C#77OGgA@!4?G@#42?_?GG?O?GA?O#75K?B!7?@A!6?A?G@#156O!9?_#94OC@#47O#228A#41wgQP?Og__??sS???_W???_??o?@CC??cc!6?OG?@W??_???O!4?OO!8?G#18C#54OOA!8?A@#191@@#83O#91A!7?@#228@@G!7?O#82_???O??G??G#53???_#31?BA#49?@!9?@#30C!4?G?C!4?C#16E#50???GC#28???C#30AA#49A#210@HKG#232WiIA@A#74_??G#26GC???C?O#13G???P???@BNNAA?OWO@?CGAD@C??_??CE#38O#24?G!4?A#96O?[G!8?A#28c?_#16G#41C#243oW$#166_?OC??C!5?@#62?_!4?@?_?O#63!20?G#62!9?_G#25?O??@#97_#66??HQ?A??A#85o???O`A@!6?O???A@?@#206o??C?WWG?O??G!6?@c}__SVNEFaB@G?G#45_?C!5?OcAB!7?A!9?C??O~kLFJ#29?OO?O!4?A#45@AO??O#168_?A?@?A#165AB\@#95GY#45_?O?C@GEHUFBO?A_@??O?C_??_?GA?A???@?G!6?C#31?CE#9?CCGC?K#95?_CAG?G!4?G#74G#78A#28??_#34_#36C!4?G#81_@!5?A???P#47O???__#159O#238_OKr[UgWG#170CA#56cG!4?C_C??_?_!5?PGC???_@!7?o??CK#11O!9?C#54_#83_#186_#74O#158G#211A??A?S!9?_#77G#44C#27A!4?@#48_?WGEG!4?pe@EK?K`??o___DFAE!6?@?A_?O_@#49GO!7?A#30_O#45?@#91A$#208@!8?G?G#143C#68?O?@??O?Q#64!33?_?_@@?OOA???O#86?O#71?KID]H???OOO??_GAO#118G#90_?A#195G!6?A#190?C#185@!7?@#94!8?_#95O??GC???G#44o?@!4?a#90??CO?AA?C?_#40??@??@?OO@Ue!7?AEGA?C?`_!4?A#93_?C!9?C??H#235?@#39OGcO_???__?B#155?o#94?I@?@!8?G?_GA_!5?G#30_gW@CG??G?AVJ?@??_#195C??A#55O!8?OO??R?O@G?C!8?A#233C??O!5?A#206?E@#78GQ#81og_g!7?G??O??_?@#99goM{#37O_???@@!4?@A!6?G@A@!4?G#90@#203A#212OQ@?S@@#101G#186G???O???@#54O#35?@!5?AA_@?C!9?A#44??_!6?@#23g??AC@??C??D@#229?C???CA#185_???O?A???A$#192A?A???`#190G#211OOG#101A#61???O?ozmyHxm!4~O?CEAUsO{noS@B@??Nvw}~osnm[TMJG?ANNSELKDFF@#76!6?_#89A@@K?C!4?A!9?@#191???C?C#192@!5?@#164!7?O#154_!4?EA_#72O?@!7?_O!5?AA#66???A#63?_#41_!6?`??ow_GL??C!6?_`Oo#162CA!5?@#154@#170o_#155OA@#75Q_?C?_?GC@??@[??GD?CCHAOA@#156C@@#228??@!6?O_O#100O#11_!5?@B??D#22@#154?O#168W!9?A#33???C#52??Qh!4?Sm???_#95_#98KG#96AA@#11O#26O#54_#197A#230C???_!4?A#189_#101O#59O#57G!8?G_G#78??O#43_E!8?@?GGG!9?_?O?C!7?G#208?_#229_#230c`O?@#167_#95_O??@#83A!6?@#28A?O???G#73?@!9?@#36@???_?G???O#96?C#61!8?@???_A#166?C_!4?G#169C#207O!4?_?@$#96GG#169?G!5?C#133!5?C#60O#96!41?aGS?_!4?_??G?@#117!9?A?OA??O?I???@??@#196!4?A@#168A#169A?A#159@#190!11?G#195_C#228?O?G#186O#200A#42G#39_CBBD}#65G!5?@???CCWCcAAMFFD#33?_#63!4?A!9?W#86O??@#90???A@#195_C??A#190G_C#158?M@#72_??g?A???A!7?E?_?AA#120?G#65C#29?_#26?OQO#191?@!7?A#195@#187C#26???_zoG??G?G#78?_#228K?A??G??A#83C#42!6?C?@#46OO#74???C???C!9?O#192C#210@???@#241?O#190?@#201A#99C#158@#47A!4?@@PsW??A?C#83?_?_#34C!8?OC?@!5?OG?@B??_?W?@???G#233???_!4?C???O#37C!8?_?o???A#20?O#52!6?A#49??C#24!5?G#42?O!13?O!5?G!6?C#47?O#32G#83?G#162@$#202?@@?@h?OG_sS#73!4?G#63!47?O?O#87??_#133!15?_?C#87?W#154?G!8?@#186??A??B#163@#187A#78!12?O#90G#168?G#187?C#155??A#38O_#23_#60??@@#83???_o??__#91_#67???O#39???___w!7?Sv?HFG@JBAGqWKN@#209??O?CG#191@#199C#90!4?_#164?K??A#162@!8?@#40G#44G#154???@??G??GI!4?_#185C!6?C#232C??@#7!4?A#72?_o!4?A???C?@#192O#56_!8?@?_?@?a@W???G!4?C#27_#28G#72G#231?A??C#211!8?@#34??A??A#84!9?GG!8?G#28O??_???_?B#53_!8?O#18_?c?@#209!6?A!5?O#201@?_#47A@#36@!5?_#15@A#23CA?C#53!12?G#186!26?A???@#208G??@#90?A#74C#201?O$#204?A?@???_#165E#132A#141!6?A#67!48?_#70_?O??A???_?@#68!8?@#66@#86???@?@#211_OOwC_O??OO??www{owY?OO?G!4?__o?C#40??GA#93!8?C#154K#95w_#49@@??`YG@??G#38???G#23!8?_?_#67!4?G#164!8?B#201@?AKoW!7?@#30??CA@#95??O!6?AG??_SBOAOC??@Q?O???O#238_WGCA#6!7?FB#43O?_??_?G!8?C@GGP_?CB_!4?QgW!8?OO#187GG#207A#232@PKA?C@#37!5?@??@#27!9?@#79?G#36?C!7?O!5?S?GO!6?B`??C?_#205!7?CC#159??O__#187A#163G!6?@#16?C??K#32?G__?W_Wo_xCX_?_qpYV\?UYNiW?@@Wg_oXYQd@gI@#29?_#202@@???_O#72_?C?@$#141?O#167?A??A#206?_#65!57?A#157!24?O#156C#201GKK!5?WGG@!4?A!5?@?@??@?@O#212??O#24!4?W[{y#155!5?G#92??O???O#22!20?_??O_???G#185!6?G#228ww#206Oo!8?C#26!5?_#90??G#77O??@?O?O!4?C?C?A#31KcG#223?G!4?_#163G#74A@#199G#237@#29!9?A#159!4?O#31_!7?_?Oa@#49???C???CG#34??_!8?C#33A#211???A#209C#237IA?_h@c#43!5?C#52G?C!7?Gc??OC??A#165@#59?@#74A!9?O#46?_??A?__??PA@#237!9?_??CA@#228C#191O?_?O#30GC?o?@!6?O#34!38?O#83C#203g?G#239DE@#163G???A$#188???OC#200!87?o#187C#132A#212_??_?__?__o_#159!14?G#156O#47!20?@#76A!6?_#24!17?O_gOc!4C#187!7?A???CG#185!5?A#187!9?G#93G!4?GC??O??H?@!6?E#47AGVM@@?_??AA!4?O?__!6?O??@??O[BO?E#77!10?C#48C#84A??E?K???C#235!4?_#35!12?C!7?B?@??@#98???O?A??C#91C#35AO?G???G??@??o?OO!6?GCC#206!5?@??G#190?C#231G#158a#56C!5?_#8?O#7C#4A#17OG!5?A_?OC??G?A!5?A!5?_?y`?P?a?gWKA??o#79@#187A#73A#188O??O_?@!5?C$#168!4?A!7?G#204!81?O#92@#209G!9?G!4?GE@@MNI#163!25?G#101O#159G#162O#211!39?c#195!20?C#185A!4?@#30?_??_!4?_G_#155?_#162G#72O???C#167?_#235A#90!16?@#155C?_?_???@#73!15?A#189?@#101oO#57_#46_#31_?C#74!18?O!9?OG#101!5?_OKp#94A#54??_!8?A#52O?C???G?E#169!19?C#157G!4?O#92G#18!4?@A#33S!4?C#168!38?C#54@!8?SA$#189!4?gUW!4@`#163!83?A#230_!5?_???O#187!36?C#186G#201!82?O#54??P??A#223!19?A#201@??C?G#37_!7?G?GKEG!4?O!7?A#28!19?A!8?@#82!8?@#15!6?B?_]i?@???_#57@@#155!25?@#55@#31E!5?_#42??_#5?@#195!43?O#157A#224_!9?G$#194!4?O#164!91?A?A#199!4?C#205G#189C#197C#35!125?@!5?OS!4?O!6?@#225?C#187?@#92O?A#100O#200C#157!20?@#82@??A#78@#31!19?@#46@??AaAECA#48!15?_#13@#18A???A#83_C#78G?A#226!28?__#9C!4?IC#189!47?G#233CA@#235A#226??@#164@$#210!97?C?C#91!156?A#211_#226C#166@#232@#99!21?_#79@#58?G#36!21?E?A#41!22?A#16O#26_#27O@!6?G#154!27?O#11AA???G#230!49?_#158O$#163!256?@#231O#94!25?[#91!23?O#94OGW#42!21?C#9??C#31A!7?GC!4?_YW#80!19?G#100@#185_$#188!256?G#95!51?_o#33!27?C#47???@??_G?E!5?OOO-#97@?@!9?F?A@@!5?_#62C???G?__O#48?@#70??O_wGW?_?KK???G?C_?oo!8?`!6?BG??B#131__!4?@#185O??G#205O#118@@#206OOGsog?O#201??C!7?O??O!4?O?S?_O_#45?_?KE!8?_aB!4?OKGE#40?B@yO_OOO?BBH???@!8?oo?_#63_#29?C#75{@#159G!4?kCOO???A#30_??A?W!4?@A@!8?PAq[ji@#54O???_?_S#77A#11_?W?CG??G?A!8?O#31A?@#83@??_!4?A?gAC__?O?_??G?C#90A#54@!8?GAA?_#210@!5?A#81?G?A_A?@O!7?@?w!5?O??O??_G???_#99O#47@O__?_H#31OOQ!4?C#201G?W?G!9?__GGg?G#206C#167OG#35_SG?_!4?A??A?A#13??E#53!5?G@#52A#49A#165O#13A??@?G#29!6?G!5?IO@#208o!9?G#91A?G#4G$#158__!9?_#131_#68Q!6?O_Ow??K!8?GC??_C?CC!9?qHBrIWHiOBD!8?C?GCACC#96A#176_#195_!6?G#200___e!4?G!5?_!5?OO!4?A#162_??@???_#94O#39O__gkDOCWC#65W#72I@kHdY???BOW!9?_#45___?A#23ookC_K!5?@@#45??_#95?O_!9?_GC!9?G!6?K?@#43?_!8?O#46o#94?@???G??A#9WG?@_?@@#35oCA???_OC!9?A@!9?@?A?B?A@!6?O?C???GG#83O#232Ag??C#57_??_?OOB!8?@#7G#79?C!5?_#52O_!4?@!5?_??OgC??B#16C`Q??@#238OO!6?@#47O???@#6C#164G#235OO#91C!4?O#55O#36G?o?CO?C?G!6?h?EORYAOg??C?AW!6?O_?_??o#13?CCC#49A#233oJ#226A#243_!4?_o#159?_@#6C$#188C!9?A#163C#67?G#66G_GO?W!5?o_C#40?@#64?_OO_O!5?C#62?A#88?OoOO_O#63A#97O???C_c?Si?W!9?PCoG?OSRG??@A?@#160C#207O?G#211@JMUW_xK?E_A@@GUFINH?@?OG???KID@@#41_#44OGAA!5?B?C!7?C?E?C@A?__!4?Q#22!7?A`ADIADACK#78???_?_!9?_#45o^BOGL_pmC??S??A??K?PP#26_{C??POA?B!7?o_cOSYIC?CB@!7?GC#187_?@?@#41Q!4?G#31@#79K!4?Gg#27??A#78?K???G?A??_!7?_!6?_?H@???O!7?A#15C#77?@#96__?_!5?GGW#83_??C?E#95C@#34??A??GC`#15CL???A!9?O#28C?A@AA!8?A?B#37O???A#12!15?_??C???A?C@!5?C?CA???GA#159@#236?O!8?C#154@#7A#8A$#189IG_syJC?C?O#166@#63??O???o#64?G#25?C#67?cC#97!7?_#85??_???_??G??KkcOkGK??C#64?O?C??__#88YnxxYYl_AAA!5?GAIF??A#201?_#154?B#170B#212??@@FFErzxNLQ]VHGC#206?EEEB@???C??IG?@#78A@#75@!6?_@#87G#42?AA??_!4?C?C!7?O#41CPD@!8?_!6?O#93?CO#206A?C!7?A#75G???O?EC?A?C!5?A??A#9???G#15_O#28?G!8?_?G???B?@#6oC#27w#54?_???AP#72@@!8?G#82O!5?_#58C#53G#37CACA#30???A!9?@#41@??_#91A#95CC!6?A#53O?O!6?A#47GA@B??_#91A#84H!6?@#46@#36@@!6?@??G??@??g!6?@#95C!4?G?G!5?O#227O#236_#77@#191@???@#52_@#18_A?G#48bbA?CeMCC_???PwK?@TMDd@?Bs?__?_GG!5?G?@#188??OGE??@#35A?_@!5?@$#202O?WA@_Rfr\D#65???_??A@#70!5?G#85G#67!10?G[#63C#71O?oOoo!6?o?K?G#102?CA_#106?@G#89?CO??@@#71OOpgpGSW?HCC??A@#120C?AA#190?C#232!10?O#230OK#189_#170__???_#209G!6?G#212?C?CA#72G#155@#40OC@a???G#66C#76O???G??G!4?O#24A??KGMF!6?_?AQzLQ|YTLIKIQK#90??I#165CGOOG?iGGC#162?C@#41?[mDa?G??QA_OG!6?AM?@!4?C??@#45A@A?@G#72C#30C?a?c!4?KA!8?H#223Oo#201Cs#77C#33K?@#74GOA!8?G_CC#99O???A#57?_E#43o?OG_#74G#163?@#201A?o#238V??A#167C#59?A?cB_g?f#37g?CK!4?G!9?AA?AAG!7?F?AO#28??G??BA#30A#231_#228_!5?C#81OG#16_#26_???DC#159?AO#205_Q#101_#74@#73A#47@#32_CD?G@p`OpHWDc~?I@Agc_?O?I@?@cPOoAFBl]OW`tJypB?C!4?K#168@C??C??O#11_$#96?@!9?OWd@CA???_WG!9?_?O_!5?GG??_!6?A?E?G@@Q@?SA#85??AA??AC!5?@B#109??@!5?A#209O#199!15?_???___?O?C?@SGOP_O#95??C#38??O???O#67_#77_??OOO?C?C#47?_@#75?GO!6?_#168!24?@!7?_#72??O#39??_?_O???pD?HMD?@!6?@?@#31CCA@B!7?O??A!4?@#4A#95???GG??A#19_#28GcA#162C#228CQA#156O#84?O???_?_!7?O#101??_?C[#82@cG#47@J??oE?@o#233@#166A#207G#237?CH?@#58!5?@#36???O?o?A?c?A#74?C#98OC!7?_!5?C?G#46_S?C?_c#18??G?C#11G???A!8?GC?A??E#166O#186_???A#79G#17?S!4?G?G??o`WY??C?`C!4?G!5?I@MK?sA@AB??CC#203??_?H#235CG#97I@Q#43O#78C#30B@@A?O$#132?A!9?A#71??C#61O_kMfFB@@BBRr{S[GG?GEBB@?A?A@BB!6@?@#104!7?C#87!4?CCc#86c?G#133?C!4?G#162???O??G???C#228!19?@??@!6?AA@?A#24!5?OWNJFB#90!4?C??@!5?_#39??CQ???CKK?My[IC@!4?_???oO@rJB#164?A#154O!8?O#47!4?@#90??@#40AO??O#154?_OCC#72?A?GG#35!6?CG_?W[#55_#78?G#155C#159@??@#41?_?CA???GC@#56A??A!8?@!4?@!4?AA???C__!8?C!5?C??G!6?O_?G??O?GB!7?O!8?A?C?C?O!5?CA???A@#14??A#43?O???@#158_O!5?C#31G#27O!4?@#228??I??A#231C#154C#31?@#20G!5?O???A???O_!7?O#192_#202G#96_!5?Oo!5?G!4?_#63G#241??_#239t#176C#163o?@#83A#187G??C#26c$#169?OC@!7?G#60???G?@!4?AA!4?BIBFFMF@??AB@B@#87!5?G#76C#67A#65?@#67!23?@A#132__?O??G??C#95!24?__#186O?C!8?A#63!5?_??O?C!5?O?OOG??G!6?OO#170!24?@!5?eC_C#29!9?@?_???A@!9?A#56??__#47KKe?]\bO!4?@!7?O@AX@?CA@_!4?G???CH#28C!7?A#16!4?@#26@!7?@#55_??D??O!9?O!7?@@!5?@#95?A#59C??_?K?_!8?_#78O#53G??OG#55CA#26??O#27?G@#159_#33?A#84@#78__!5?C?@#7K#30G#226!4?D?_#230G#42!5?A?CO#12G#233!19?_#97C#65G#23?CA?@!4?@?A#202!10?O#58?G#54G#92G#242O?w#238W$#168?C#194A#190G?C!4?_#133!4?A#49S!4?C#76??O#66!14?C!6?!4A#117!32?CG??@#155!26?OG#187g???_???OC#60!7?_#73!6?_??C!4?_#38???g!4?A#201!25?C#155__!5?OG#22!10?G#77?G!4?AP?C_C#186!16?C#201A#223@#7!4?O?W#74???_OC#94?G#37G#15_O#45OA#198A#211_#192G#163A#43?@!4?A?@!5?C#95!4?G?AO@#59C#52O!5?oG#228???C#240?A?@#50!10?C#34g??G#27O?OC#99??JOCOK#48?OG#42?A#55@C#35E!7?G!7?_?CG???A!5?_#192A#96A#9G#161O#197!6?O#207H#237C#13!6?W#34?C??@!4?@#242!12?O#185O#20!10?_#7C#40O#73!12?_#196A#100O#74C#41A$#192!4?COG#204O?_#203G!71?__#188O??G??C#191!22?G#159_?A#154_!6?G#74!15?O@I?A#65?_@?_!5?_#199!27?GG?A?@?A@#26!12?_#164??_???oO#209!18?A#238@#22!7?_#91???O#78c?{C!9?_?_oC#95O#32C#52@?@CG#36C???@#165??OO#94_cQP#34_#36?A?A#73@??C#230??@#241?HC#82_??@E?KGC_!8?A??O!5?A!5?O!5?AA@#19?@#51O#163!6?_#83_?C???O#232A?@#46_#101??@#43A#223_#208!6?B#33!10?_#188!21?G#76!25?C#155?_$#199!6?_GGA#202!74?_?o???G#165!24?C?A_#33!22?_#41_?_#49!6?@@#29E@!8?CG??O#228!15?A@B@#157O#187?B?A@#93!15?G???_C#81!32?_#199!6?G#210G#231G#226@#54??CCA#81_???OAOO_OO??O!5?G#53?G#15?GG#37?O?O#158!5?_#94_OOKC???C?M#18??O#52A?C#30G#26G#101!5?JYN!4?oO#32??@#30C?@#158G#73!13?O#186O#225?G#90C#185C#230C!4?A@#100?O#210!7?C$#89!84?CC#164!31?H#93@#83!24?A@#30!8?@?GH!7?@#209!18?DA#190C!4?@#162!17?oG_#159@#94A#90!46?_G#101???O#96O???_#48@#167!6?_#158g#46!6?C#27C#212!9?O#101O?G#46!10?C#35?o?_O_#58!4?_???_#20G#51C#50?C#93??_#91O?A#191!19?@#189A#212@@#82g#240A#162???_$#204!85?_?_!4?O???_#195!21?A#91!24?@#101!41?o#158O???G#165!18?O#155??G#27!46?A#91!6?A!6?GW#31!11?A#243!10?A#169G?@#51!11?@#31o#43C#165!7?@#94?B@#21C#15!6?B@#163O#236!19?G#211@#54O#233AA?@!7?_$#197!85?O???G#204!97?@#170!21?_#55!56?GW@@??@@#43!59?GA#189G#55!21?_#210C?C$#169!333?_#155_#187!22?G#56OO$#165!334?G#237!22?C#94_$#197!334?O-#97W!4?__??AKOIFS_?_O_!5?O?aSg`@AGEGA?@!6?@???S?WO?GCIEDCEC?@?A???@@#130@#169!4?@#201???O!8?@!6?G!4?G!6?O?GGo!6?OG@#39_{Kqz~g[G??C!5?o_sqB!5?A??@GG!4A?@!4?_?GO!4?@?@A#154O?oOB@!7?@#24_o_#164!8?__G???C#54C!8?SgG?@?A??@!6?G??C!4?A#82SG#95F@!6?G??C??@!9?_!6?O???O#18A#47OC?BHF?AHK??Ymc!4?__O??G#28@?G?K#30C??@#82C?O?A!6?@?@_?O#54O#52C???HASA#18__#28_???AA#155C!8?O#16A#236GS??A#230G!5?C#101e#55gcGM?O??O???_#35???_#20???@#21A#16_?o#58O!6?O???_#53??O#27!5?O#35O#189_??@#233@#163o??A#73A#33O#91O???A#6g?_$#202A??A@C?@H??@#85C!9?K@@CG#64O_?W_???C#117!5?_#70@???A@??FDA@#64C??@#102A?@#199__??o_?O??Ck?O??Cc__O_oo@???OO!5?CE?F@AQOIE?@!5?P?__Oh#209?CA#41OA#24oCC?O?C!9?[I@!5?_odyi?oO__?OOjepGFB@#30_#75Cf@?A?GCG?I?G!5?_G!7?_O_B#90??O!4?O#47O!6?_OcG?oEyTLf@?@?_!5?G?G?iES???GI???C?__!9?C#84C@DCA_?B!7?G!7?O#53G!7?A!4?B_G?@#34?@@??GAA#99_OKCC@#81G??GID!7?@!7?G#43G???_??@!5?A?_G#242?OG??P#233?C@??GO#155G#56C?C!5?A#28C#17A?A??_JEbAI_UO?NA!9?G!6?F???BE!6?GG#35A#168C?G#4_OO_C$#158C!8?CQ#96C???C!5?_??a!5?C??@O???_?A!6?_!6?A#176O#194O!4?G#131C???A#189_?O?_OG?oWGIKCC!4?A#169A#206g__JJBP@@#170?E?A?K_?GDAG@???_?P?_?A#154_#45_G?@#40@??D???E!7?A?KK?E?_E!4?EE?DGGA!6?Oa!6?O#72???HCCOO???O???A?@!5?O!8?_!4?@#6OGC#31C??@!5?GG??H#74?O!9?W?@!8?A#185@!9?G#56@???GK?O???A_???@O???GS?@??@PO!4?QCQ??G?G!8?G!5?o??@@C!4?@??GA??@C??_??A#210???_!4?A#95AA??B#31C#11?A#8@#207A!9?@#37O?@?C?`#47O#91@#57O#13@D#189!13?_!4?AC#49C#36OKC!4?QGo_SC!9?O#96@#42@#93@#54O#7_O_?DB$#169@O?Wo??O?_#185`#67G??A???GC#70@?_??G?@???A#104@#85!10?@@???B@#161_!8?G!7?A#171A!4?@#200!7?_O!5?DE@???GK?G??cs_AC#95O?G???@#206A#237G#186@#232E#200O#191@!4?@#44?A!5?OC#159o_#41C???_!8?O!9?DC@!4?o???K!4?og?@E!4?__!4?C??AA@??FF?CsA!7?GA?A?_Ag!4?O?_!5?OQ???@???C?D@H!7?CAC??_W#77G#191C???@#74C?@!4?C???G@!4?C???@!8?@#159@#36O#35O!9?O??__`AC@A#48@#55@!4?C_!9?_?@@??C?KCC@#11???C#241o__!8?o__K_#240_OGG#232B`C@#167O#79A#48@qOg_!4?GOPz[N!7?AE???@???aBBXDF^_KvMOGgC#23G?@#240G?C#243A@!5?B#100C#8G?A#2G$#176_#167@!6?A@#164?A#161O#159G#64@#68A_O_?OOO_O`p?JTA[[ehAC_?@COO__A?OC?GADM@JD??B?B@A#166C!9?@#204!9?EwwwO?MsOW#212??ooWwG!9?O?GEMGMMC#168?@#74O#95C@#25?G#63??A?A?HA!9?O#44BH@??@???@@@??@!6?WKC@???@#90CA???O@@!5?_#94OAA#22OWG[[#187!8?O???K#186@#9_o??_O#45P@W?A@#35_C???AAA#33_#9?E???GA?@@#55O???_#34_O#16_O??O#43_O???@!6?A!6?_OO!9?G!4?A?A_#99???@#51A!4?A#46??_OC#18?GCDA#32o#52_CCA?_??A#167O!8?@C#99A#34GE#36A??@??OOCS_#168?O#74D#238G#83A??OG#82_??@#15?@#237?o??_oU??o#205A#98?@#52G??O?C?g#159_#53O#12A#96!14?G!4?aG!4?w?K_?@#157???_#49O#12@#239_S]B@?@???GC@#27C#11C#3WO$#163?g???OO_#186O#168O#162???O#131g#61P?C@#71O_??O?AA!5?_O?`x^]}HNMMM_KIWIO#153_#135G!4?G!4?A#71?A?@?@#176A!5?@#192!8?G#205!4?G#211??_ceqA@@?O!8?__OC!5?A#157??G#162A#67!6?B?H#75?@G?OO!5?_oo#67G#76@#29O?A?C!5?oGA#22OPEF#45?_OAwW]uG?a_@E??_!6?O!7?WGNwBdM#154G#93CGEB#201A#26O?kGE#28!4?@!5?O#27O?_O!6?O#52_?O!6?O#28_?AC#37A#30_!5?C#230_O#57@!5?_G#100A#79?G@C@#52@!9?O!5?oOcgc??K#57HO?P[C?@#36A!5?@@#98_OgA!5?O???EB#78_??_!6?__O?A#225!5?G#240O#72@#187G@#84@_#37@?G#197_#45???@#229C!7?g#43??A???KGK#96??_#58_#243!14?_??w#208_#44@#20?_!5?B@??GA@#166??O#236O_#188?G#53G#15_#13!5_#26O??A$#189?E]dEBMK#62!7?GA#66ACA#87G@BM#89C#133!4?A#131!18?_#134_!4?O!8?C!8?@#209!19?C#232!5?_#189??@?C!4?@#101A#158@#190C!7?GAC#60!7?_#65_??G!4?G@#30?@#38!7?KW!4?KGO?c#77!9?A?_???OO_??A#165?CIICB?@#30C!5?A!4?GW?C!4?_?C?s@B??BA!7?OC??G_A!4?A?A#78?__i@??@#27?_!7?A#154@#212G???_#98G?K!8?GD#94C?@!8?_#82_#59!7?@!6?AA#11_q???O#37?W#58O!4?_#57C?@???G#228O#169C!4?@#35?OO!8?@?D??A!9?C#202???@?@B#212_KQ#206A#49???O??A#18@???A#186!16?O#97AA??W#34??o#51?O?_#230!9?_#97A#231A_#185?C#34O#48??C#169I#205@#230?A#226@#41@$#166??_!5?_??_`_#63??LHA#88GCE??G?CK!7?O!4?o?_OO[pC#169!4?_#202_??_ooo_OWwwGW[KKKYAemEERPRHBHF@A@@#167!14?@?@!9?o#163???O#69!11?@#66A#156?O#77@P_?A!9?@#23??DOo?_O???kCGG#63!5?@??G#93?CCG?_??C#155G@OGGK#39_GC!4?|!6?@AA#156A#168O??@#43?A!4?A!7?_!8?OO_?O_#94G!4?EG#83C#31CL?@H#26G#201A?F?_!4?O#101B_???_!6?O?@#33O??G#8A#37_!6?C!5?G#81??@???A?C#9??O#15AA#83!4?G??@#101B?_!7?O??G#46??_?O_GG@@??GQA#199???C#101O#94O_#159C!4?GC#231??W#163CE#209??_?@#32!5?_!5?@??C?bOSH[\CLHlH?D@!4?@K?aA!7?`AJCG!4?oGCSG???G$#188??@!6?G#60!6?O#65??@#80A#76G#133!31?O#203_#208_#132??G#150?G#88???@@#70?@#167_#165_#190O??O??__??AGAG?CCCA#187!14?G??A!9?@??`O#93!10?_#72??AA?_#33?G!8?G#23!25?@#95!5?_??O_o!5?@!5?_!7?@??G?@@#11?G?G#78??_?C@#55?G#26??c?kED@@??D@@#29A#79O#81!4?O#101?A#18O_#35A??O#187C?G!5?A#199C#165?O#78AA???O??_#72A#99_??@#45O???A#78AC?GCO!6?@??IH??@C#31_?KCOO#96!6?OG!7?A??A#190GG#210A#27??G#17C#37M!7?_#230!5?W#165_#191GC#56@W#52O?A??A#239?A#191G#211???OC#36!6?@??O_CGCGGC???o?_oO#229!5?C#240{CO#194!18?G#55!4?C#245!4?E$#170!4?GG??C#204!51?O#188!4?CC???A#165!32?@???C???A#117!19?O#154??o#45C??O!7?o#47!33?G#164?A?@#159BCA?@#26???A@#162!9?O?@#157??A#22?O??C#94!4?SA#37G?A#11???W!7?A#15!13?Ow#45?@#232?A!4?_GG?O#82?W?aOO?A#27_!5?g#11_??C#28@#35?_#94!18?@#16???A#27?G#59!6?_?W#53G!4?@#209__#165_?A#229C#226@#32???G#51?A???A#201!9?@#98_#158G#81O#54C#53@C#210!11?G#34!7?@#35A#97A#166A#230!19?G#202?@@!18?C#208!4?A$#199!6?@#190A#170!62?_!4?G!6?C#83!21?__!4?A#230_#90!23?G#73BAD!7?CC#186!35?CC#169??@#29?ogCC?a!8?@@!5?G#56!20?G!7?_#11!9?G#228!4?@???O?C#211O_#94??_#81OO@@!5?CAIA!5?G??OG#26!19?_#163!9?A#84??P_C!9?C#53!5?O???@#211!8?A#228C#55???i#36O?@#192!20?@#233!19?O#203?A#239C$#163!104?O?_#159OC?C?@#94!22?_#80CL#49?A@???C??I#185!56?_#159C#155_#15!25?_#72@???G#19!11?@#159!4?G???A#209C??A#48!7?@?G#54A???O!4?_O!5?@?AG#50!28?C??A#155_O!4?O#31!8?@???OA@W#237!4?C#78!4?C#236!43?_#241??G$#169!105?@??A#155C#97!26?G#42??C!4?G?A#165!58?o#195G#81!25?O#95GA#210!19?C???G#163A#155A@#49!8?A#55G!6?C!6?Oa??C??AE???oc_cK??O#158!17?G!4?G??O#230A$#91!106?O#223_#228_W!9?C#154!112?C#32_#45C?S??C#42!14?O#54O#186@!5?G#83!8?C??S!4?K?_??C??_#46!34?AA#47OO!8?o?__O?A???G?K?H$#157!106?G#201!125?G#159C#42_??_#75!18?_#168O#233??OC#35!11?C#155_#158?G#31_!6?_#159!38?G#94C!6?_$#168!233?A#157!22?A#237???_#206_#42!11?G#59!5?A#34A#26C#30G#31!41?_#201???O#204K#205C$#76!280?_#51@#36@#232!45?G-#132_!4?P?W!5?C#85?WG!4?@#65C#63A#166O??__g??O?CC!4?A!5?@#190?PaG???G?IIY?a?a?G?CC??@!6?@??O#201?G_#212?OC!5_O~~~|^LEE!4?_?O___og@?BA#45_G?@???_?g!8?_A`?g!4?@G_#29?@@?@?A?@#199_?_#77G!8?__#24!6?w?_WgO#95??@A!5?_??o@???o??@#201O#47CC!4?oS_SRccuaQ@A@@??_??A!6?G!6?@!6?OGo#72_G#77G#201O!4?B#84GO!7?c?@@#43_??O?C!7?C#53A???AK?C?@?HA?@??_?GO??a_GC?B_WC#18!4_#41A#56o?QC!5?A#190O#168G#31_OW???A!9?_C#8Go#5A#27K#169A???A?_?O#55_??W?@?@?@#37?@@#155@!4?_#32`??OGGC?@!8?CKLIA?K?GG?A!9?ACA#16CG!5?OOO#27C$#163G!5?@?K??A#159?A!4?_#61C!4?@?@#25A#141G#163??W#192___OW??GCGGGg?C[??CcMKSS#167!8?_OWGG???OG??C#199?@?cyRH#211??qKD^Y\m!4?_Q?G???_!4?@@???G#159_#154_#95O?@#39}]Ux]F??@!8?AABB@?WW??JYw{OSGGCA??@A?@?@?EeB???_?oWC!9?_CTXB^F#159G!6?O???@A#11O??A#54???G??@?GD_!4?_!8?@B??CO?_!9?O#33?C#157_!9?_#55C?@?A??oOK!4?G???E??@?G???J!5?GK?H#51C?A!9?_?F??_O#84?@???A#54C!7?@#238G#206C#35OG?wOO#37CDKC???o_G!8?__#82_!4?O_#58W?_!7?O??A??GEG#17__C@?A!8?WGQ?CGI??cQE#5O??cG#15_!6?OO!6?_??o$#169C_DaW!4?A#66_!7?C?EC#175O#168_#169_!5?_?GC??C!5_#170O#194A#160@#199???KO_G?oA?_?CQW`XUADA#162?_#158O?CGAA??CA#200?@@cS#206!4?I?DA@#230???A??G#190_!5?CG!4?A??O#40??_o!5?G#75OCCo_???_@??o!4?@_`!6?_??G!4?KWCK?m!4?k!6?@?@???EE!9?A_??_#78?c??AA??@???_??cY#94W@@_GwWE#74@!5?O!6?G???A_OC#16@???A#9A???A#90C#230_??G#82_?_?@!8?G#28_!6?O_#46G!7?@!4?@???o?O?@??_?CA!5?gCK??A???_?O!5?@???_!5?G!9?OIA#15cC#16W#166@#35OGO#84G!6?G_!6?A#158??C!5?O!7?_?C#56@#240A?B#166C?@#35Q#34@?@???C???@#4__?CK#100A??_#73_#18AC#17I#8??PQO??_?G$#158O!6?A#76_???oo#131?A???@#202___!8?_??O??GGC?CCCEEAAaDRRpB@J@d`h@YCHDA@QEA?@@???`??H#191IC#204?A~nHRO#199!9?_??A!6?G!4?qO?OC@#72A!8?_!4?_?O?O!4?CA#30A#60???_#24EBABCFA!4?@@#45_??OX???BD?CA!8?_wgC@?_!4?@SaEGD@HAC???_@_??C??@#55?GC???A#9O!4?_O#168B?_?A#37G???_!7?O#30s@???G#168O#93O#156@#237G]F@?@#81GCG!7?A#31?E!7?OO#83A#168G#82@!4?O!4?gC#36???GG???C?A#94_O#59G?UH#34?W???OCG#15@#74@#228@???_???G#94C#84??@#167@!8?@#241@@@!8?@??@#209G??G?C#53O??O?GWE?c_!8?C???_??C#54A!5?C#13!5?CC!4?gGA#35@!5?_?@O#13@`f[KKMq#11@?c$#189AZWT@#96Kk_!7?CO??O!4?CAC!5?@?A!7?@#170!11?__!7?_O#169g?_OD?A?_gEO?GA#170!19?_?GE!9?C!4?G#30O#41K?@!5?o#95??AO?GKA#40?CCC_aG?CAEcC??C!4?@@@E???@???G?C??Dg?C#22?wWcUi#93???@@!5?GCC!4?G!8?A#7G#56!9?G#31C#35@??A!7?C???E#32B@!6?A#28A???GA#42?O#187??A#232E@#101_O???O#74A!7?C#9?O?GCA#37??G!7?_C???A!7?_?_?G??C!8?A!5?O#43C#189@#78CI@?@!4?@??@!8?_???@#4?_O#28@?O#41C#163AA#155C#230@???C@#170C#229A#59_?oo?wKG#56?A#100C#242_??O#48@GBRCqOWW!7?Ga_?oo@P`@@#27C?C#26C#163A!5?A#52@#91C#27_G#7???a?_@K@#2A@$#202@?A#97?_AQ??O??G?F@b`@?@?GGGWGCSDA@?A#71@@!4?@#117@?@#97!25?_wW#165AC@?GG#201!21?O?`__???A???GOCAGLB#155C#24??_gE@#63O?I?C!9?OK!6?O!5?O!7?A#95OO#72O!5?OA!6?@!4?H?O!6?OO!4?@?A_G???_!4?GW_G!5?O?C#11_OGO?C#43@??G??SOc!8?W!5?N@#197??G#228@#210_#204O#206A!8?o#54@!9?A#52?E?A@OO?W@Q?G#84O#94OWC#34!5?OG#35E??A@@#56G#82OT#99@#50_???@#36_?GCEK#163???O!6?C#159?C#19__#8A#36_??A??S???WC!5?a#74?_#233@??G!5?@#96CA#52G?A??B#190??_#54C#96D@@E??K!8?_#84O#55OW#100A#20!5?O?AAA@#12O?H#9G?OOo_#157@#54A#97W#28H#12_?Ob#5?`#4@#6GAA?A$#188?C_G#167C!5?A?C#80G?_#133?A!9?G#171???C#183C!8?A#168!27?G#157C#101_C!5?C#200!20?@??@!5?A?IA??CC#187??A#25???@#77??_???G!4?OK#41??@???O???@#25!4?G#38G#23@#65O???C#185O#94?_??G#41@_OCB?wQOKbBA??@?NO??WyaE??_A?G[wUA?EC?W?C!4?B@???A?O?AC?G!5?C??A!9?O!5?G??_???A#207C#240_#165??G!7?G#187G#167_#35@!4?__GO?_??@!5?C#57??_?G???_??A!9?CG?Q?_??G!5?G#232??_?GW[?O#227??A#195A#43A!5?O@_C#81C!4?@!4?_#83?CO#226O??_#210G#157G#167??A#50G?@!4?E!7?O#36O?H@?_??@!5?OC?_A?__OwO?@#7_O?O#11_#33A#30G#83O#207O#223O#9!9?C?C#3@$#170!4?A!4?@#186@!8?_#130O#161O#185?O#60A#132_???OS!6?A#161AA!5?@#166!26?_?C#170PAa#228!22?@!4?A@?@#237O??@#230?@#44!11?F@YB!6?{G??O[`c?SW!4?_!4?CAA???A!5?WW???B@#23??CC#29A?Co!7?G#40??@#77G!6?O#164_#26?_WG?_O@#33!13?_O!7?@#56o!4?AKGGG_SCK#95?_!5?_!7?A!6?C#73A#72@#36_O!5?G??__??_?cG#59!6?QA??C#18??_O#52OO?G??@!5?@CBGA!4?I#59?@#155G!6?A#30??C#11D#16C@???_#54GG#212A#232A!9?@???CG???B#202?@#81C!6?W_W#101A#243og#167C#27??_#51?A#13?E@#49C!4?_?_??@#16!8?_#72???C???C#195A#242K#226K#34!11?G#28G$#68!5?_!8?g??OAA!5?@A@BA@A?@#131?A#88!4@#186!60?O#155O??O#168C?C#189?A#101C#154!19?O?G?A#65???G???A#160!10?_#164_?G#187_??_#93CG#67A#90A#30??@!4?G!5?A??@!6?G?S#154!9?G??`??AGC#26!15?C?IIJg#159G??C#157C#52_!4?oS???G#14@???@#45C#241!5?O#169??C#99WC#199@#78A??AC@??G?A@!5?B@???A?O???_??_??OOC!9?O#96??A#48!4?A??C#19O#55@???_!6?@?B#28!5?@cG?_#52GA?_!5?@!4?S#48G#205A#158C#101_??_??C#97?@#48CE#98_!4?G?G#208G#163AA#3!7?A#12@#37A!6?_#6!14?O#8G#56A#208@#205@#197G$#166!7?@BCC@@#70!4?GO??G#104???C#158_???G#110?A#189WGg_OSOWOaoOww_?G!6?SO?CCC!4?_@@?A??POg?Oos#209!21?C#95WWGG#206O??G???G#162!16?G@#165C@#157@#162!20?O!4?O#154??_#94!21?CA!6?_??@@#185!4?O#9??_???C#5!14?G#28@?_#15O#154?_#83COAG???GC#78A@AA#34@!6?@#94!9?_#98Oc!7?G#79A#15??G?C?_#34??O??_#192C#81IA!4?O#16!10?O#31GGA@#81_?_!9?@!6?`?C@!5?O#15!4?AA#18Pg!9?GA#9GC#237!6?BEB#49!6?A#51?C#46@#57oO#191??A#159@#211?C#205G#233!9?_o?D?CA#239@#154!15?A#233@#230??@$#134!7?C#156O#162G???@#62!5?G#162!6?OO#156!8?A#193C!9?@#158!55?A!5?C?C??_#90!15?@#159G?E#201!21?_O#191OO#200_#90!30?_!4?A!7?G#155?@#187@#30OG@M?AB!8?_!4?D#162??C#90G#100?_#49??@?A!5?A#159_#163_#27A@!4?A?@#158!5?C#170A#83?G?aG@CAOC#18???E???O#56???K!8?Ca?_#50!7?C#11@@@#16!15?OO#95!5?A!4?_#9!8?_#34O?O!6?O!5?G#239!4?O#211OCB#204!14?O#240?O#239_#165!9?G#237GS?B#52_#243@#202!16?@#236???C$#49!9?_#79O#67_!4?C?G?GABD#157!79?C!4?C#155!21?C#74_o#94O??@#85!20?C#156G!5?c#186!42?A#22O#7!16?E#27DO!4?@#163O#36??_O_#81G??CC#79G#15??@A#11K!4?C#56!10?`D#90O#47A!7?@!4?C?@?O??_?QgC@@DA@?BAo@E!4?s?GF!4?O?O!9?G!7?_?GC??G???BA!5?_C#14O#11?A#30@#240!7?@#212O?A#169!26?O#202?A#190O#205G#101G$#157!10?G#73O??O??C#165!85?@?@G#170!24?C#168B!24?G#191!49?C#91!23?_#185?G#55!4?O@@?@#84_#101O#35??_#26?OC???@#96!10?O#49O#93C#190O?O#14!5?G#30@??`G!4?_#93C#98@#95@!8?_???_#28???E#49!16?A#31G#201!5?OC#158A#157A#90A#210G#56!13?OWG#57CA@#33???_#212!39?_#236G#241C$#168!11?CA#169!91?EO#232_`O?O@#185!19?A#93!99?O#83!18?_#52!15?G#168_#207_#186?G#26!5?O#27O_#41C#100!5?G#237!45?O??O#163!14?A#93?C#158A#232!45?G$#101!11?G#90!92?@#210?A#95!124?@#11!45?A#7@@#101!5?C#230!45?_C#199_#165_#159!17?@$#155!231?A#6!46?A#100!52?A#209_#187?C-#96CC?Q@OO??CB?HGG?GG!9?_??O!8?__?_?_#171??_#170!5?A??oOGg??AFNC@!4?C?CM?@@BA#200CCAA!8?_!5?OCc??IA#237A?A#209_??G#191C#154G!9?G#90G!7?M???C!5?CO!5?C#199_G??W{e@A!6?_oO!4?O?O_O??__#22@!8?GCI?_?OCA#11O?G?A@#47G?ki?w_O_{UKDE@__WOk@?_?_YW?O???@???A?O@??JXE`O?CA!6?_#55G??@!6?P_!7?G!4?CA??CC?S??AC???G???GA#51?_!5?A?@?@!5?@!6?G#18__oG???BC@@!7?G!9?WG#32_#7OO#11C#100@#81Ga#59EA@@@#50o???G#51@@#55O!4?O?CAA??C!5?A#35_?O?O#17o???CEC@#3oGOCDCGQmggAAO???oO#8@??H?EG#27_$#97a`E`_!6?O?@?G!5?@@@?__?oO#101?G???_#141!4?O#97_?_!6?_#168??_#190C@!4?B???__O_o_O?[z@^KK??B#211?KG@@B@??_?OG?A@?_?H]E?C?c_A?OP#159?C!8?O_#164_#72??oWSGG@@_`AA!6?O?O#95O!9?G?o!4?A???_XO_#44@?@#170??_?GO#95?_!6?C??@#29??_#26?G[CS?K?A!7?_!7?OCA???GCEC#83@A??_??_G#81C??C?CEC?_!7?O?CO@C#28_#189A?G???@#99oP#78G!4?O?A!8?_?IO@_?A!5?A#52G??C?@#50?_#46??oxDTCAA@!6?OggCo?WGCDD#14OQ#52_!4?O??CQCE@!7?_???@@_C_!7?G?AC?G?_O??I??@?A?O???_#189A#51?I@?C#58CC#4?O!4?_!6?@_P@@??_??_??WYB#15A_??C$#70W!6?G#130G!9?G#186O???_#161??O!5?C#158CCCoo?O!5?o_?__?_#200???A#199LEA@!6?_!4?o??o_qAKD?B??__#158!7?_!8?_o!6?C???D#41_?@!8?A??B!8?OC?@!4?C#195?_???A#201@?B???@#155A!4?@#191?G!7?O_#162??A??G???C#165G?O#159A#9g!4?GA?IG?A#94??o???G??MFA?OawEB??G#43OO!6?_???@CA?@!6?C#156?@#90C#83A?G??A???A#35?O#159@???O#167O#170AE#94A!7?O!8?G?O?_!4?@???O!6?C?_GA#20?O!5?@C!9?O?O???A#37G?O!4?O!4?P!9?C@@#14G#4G#95??A#58K@@#96A?OHC!9?A!5?_#53A@!6?D???AA#32??G!4?C#2O?GGWO??CCc_#9A#28B@#14@!7?GdO$#163@#76G!4?G#202_o_???_?a_!7?A#188??@??@#183?_#202!10?G???@??@!5?A@@A!8?C?g#130@#204_!4?ooWOO??AO_!7?C#165CA@???_??_OG?C???A#157@#75o!5?O_?CD@??_GCA__?O?C???C?ENH???A@@!8?o??A!6?@!4?@@???AC??G???_!4?@#39???O#31??_??_#78??GOOCF^?W?_!9?G!8?D???_OCC@??Go???A?@??__???O#163??C?A@#210E#200C#199C#59?K!7?_#53_#52O?@G??O#47Dc?@E?C[SOGZA?ap#59O?cGQOGAC#36?__?C?AA!4?G??OO?_!4?GL#17@#56?__!4?wG???G!7?A?A#50A#9?C#169??@#79O#83G??_#48_??KKQ!6?G?EA???C!6?O?EHJGHBFC@#7_!4?_?AbC@??O@D?OOG??E`S#16?G?A@$#85?O#158@!9?O!7?`#157_!5?O!4?G?_???o!5?O#163!22?@#97@?@#167?@#212!6?___oo[M[}~~^^NFB@???O??H@@DB@@?@#45!4?WEA?G!5?YsK??B@!4?WH?_G!4?UJ#187??oOC!5?C!8?G!9?AAwO?O?O?O#206GC?A#7?O??A#54!11?_??@!8?@#30O?G??AA??OG#95?@#55W?O?CA?_?_?GI#155@!5?C?O!9?_G#192C?G#211B#204G#43??AO?ag_!5?_@o?d#46?@!4?__??O#43_#84_???__#58G#34!7?G?G???[#37G#81_O?@!6?C!9?__#30CC@#55_C@!7?C?@@C#84!6?@#190_?C#202CG#205C#37O???}A??_O?C!5?@!6?_#12?_??_??_ooWGG!4?O!6?G??g?O#11!6?O_#28?A$#101?A#65G!4?A?A#189g?_!4?_O?C???G???C@?`@@@!4?AABNFDLLEC?GL?WQO?GCCCapoOZK[?WCIA#201!7?GG#206@D!7?_#201?O??_??G?@!9?Co#94??E#39ww^r?A@???@B#77?_Os??RE!7?@!4?G#162G#156A?@#200_???A@#45G?[I?G?@@???@!6?@!5?FC?@@G_A#6s}[]E#95!9?P!9?_O#11???A@!4?B#74_??CC!4?G???@!7?G??S@?C!7?O#101A??___G_!6?G#42@#57O#48C#34QE#49CE#54AG???A#53GGY?@!8?A?_@?P?H??A?@!5?C!6?G!4?A#15!4?A?C#27AG!9?@!8?C#210!5?OG#243OG!9?FB@???_o#81C!8?@#20?WEO??G??A?A#15?@#74@#1G#27B#5??A!8?gCKC_?_?@@$#68??OKOK??E#168O#165O!8?@!9?G#192!14?IAA?BFE?[CGK!5?@#166!6?AA#170!24?G!9?GG!4?_A#72!4?@#40C??CCAA!4?A!8?__!9?@#206??_sMF#169?O???@#74@#41CCC#100G#186?C#156A!6?A!5?G#29@#72@!5?_O@#45!14?CA@???@#55???B#45!6?C!6?C!8?A#94?GG?_OG!5?_?GO??QAC#169???O_!4?@#47?CG?@?A#56_?E!9?_G?A!9?G?@@A#18!7?GOO?O?A#82O?A?B#57A!6?A#34O??c[!6?C@!9?OGO!5?A#99!4?C?A#167A#207A#17_o#36o??C_!4?_?@?@!7?_!7?CGB?@@A#33A#157A#97@#23!9?G#30C#32CA#18A#9!7?OO$#131??_#67?GAED#49@#188G#161CG#176ACA??P??A!4?C!4?_#96!38?@#95!24?o??G#155@!9?G!4?Q?@!6?_#162O#95!6?_?OU#159G!7?W_??_?O#204??OGO#190_?@#154OC!6?A??O?G#169G#77?AA??@!4?CA??A#155O#191C#72!20?Q!4?@@?G?G#31???a!6?_#101?A#54_?C!7?A!4?E???GA@@#190@!8?O??G#37!4?A_!7?G???_???@#82_!6?A?G_???OOS#19!8?_GC??@!7?_#157@#165@#59AC#35!7?G?G#9@#83_#53?O#36_??C!6?CC?_C?_@#238!4?_#240G?C#101?@@#57@#20_?_#165@#56O!8?C?_#202C???@#5???_???O#6!4?_O?_?O??_K?QAOO!5?g?CG$#73!4?E#61@@??@#169?_??oO???E?C?I?GDK@EECAQQ@HGA?DK!6?OWOQ??`_?_O?w[KG??AAGCA#190!24?_?O!9?O#206O#95C???_G#29???_#63C!6?G?C!7?@#65?@???B#170!13?G#72?G?GA@??C!4?_#40??@#195c#39!4?@?AA#154K?G??G@#35!19?_#9!13?@!4?@#29@#207??@#82C!6?G??CO#95_B??oOG?_oG?Q#158@!7?_#36!6?D??O!4?_CGA@???A#57??O???GGCC!8?GNEt?E??A#16_?_??@??_!8?G?__??@E???B!6?A!8?OO#245!7?O#208_#191O#168??A#82A#53?@#46?O#169I#190G#98G#100C!9?A#97_??O#13!4?_??_??G?OCGCC__!8?H?C?BB???sE??G$#133!5?_#159_#134O!6?@#167?A??_W???CAA?G!9?DL!7?W!4?B@!4?_O#94!35?G?A#101A!8?G!6?A#44!5?_?C@AC??@!7?_OMZUA!6?C@#93!8?_!9?CC#201CCg?GGK!5?O!5?A#33!35?@??O#8G#56!4?GAWG???oOQ#57_#28???O#101??G#41@???A#82KKGh?@#165??_!4?O#35!4?B?C!4?B@OO?P?A!9?_?@?CAB#31!13?OC!4?_?G!8?_O???G??CA!7?@@!9?A#189!17?C#210?C#18_#34_!6?G!7?O#50???@#163!10?A$#185!11?C!6?_G??C#132!6?_GO#154!63?O#90CC#157C#199@A??C?_?C?_?_#38!8?B#24@#93G?O_!8?C!8?G?_#94!12?OC?_!7?_#198???G#24!6?@#93??_#41?@@!7?@?`_@@?_@XoFA@!7?G???_KPc??ca??O#91???A?_???G#46??_#35!4?_#30???_#91A!9?EG@!7?_#31?SCB!5?_?_#32GG#83??O#34!6?_#31_#83??C#36CO#54C#37C#11!13?_!6?O#169???@#101??@#49!8?O#47O??G!7?A?@?G!7?OA#54!17?G#232@!8?GC#205OG#226C$#132!11?A???@#141@#159?E#162??A#168OO??G?A??A#83!64?G#210_#159G#232_O!8?@#85!10?G#92G#157!11?G#78?G#39!4?@!6?NA#168!10?A??__@??_???A!5?[C??_!5?_O#35!37?@!4?_#158?@#37G???@#32@#72!14?C#209??@!8?C#28!7?GW#81?G??G!7?OG???@@@?A@#157?O#95@#185@#49?G#14!13?G#15G#55_!6?@@GCC??@?A#28!5?A???A#57?G#50i?A#238O?Gg#74@?@!7?WS#211!16?A#43S!7?A@#230WG#168OG$#66!11?@#175C#166Q?CS!4?OICoP?A!4?O_GYEFG?GO?O??O?G#163!47?O#169??A!6?O#167??G??G#156!8?O#154!18?_???_#47!15?C#162?O#157@!7?O#207?_#160!6?C#30!4?A!4?C!4?@?@__BVoCK???C#27!17?O??A#52!5?BA?C#57!18?_?C_#166???C#205?Q@#82!9?C?C#72@#56!40?O#78G?GC#33!16?G#43O???A@?_!7?_#15G??G?A#167!21?@#79@!4?_?_?_??G$#163!14?C??E@??G_?@!8?G!8?O!9?_#189!49?O!6?AA?@#185!28?_!8?G#90!12?G#185_OC!5?CC?C!8?O!5?@#28!35?C#7G#84!8?_OO!4?@#56!14?_?GY@#212???G#33!10?@#95SG#84!41?_#8A#94GCA!7?B#11!9?@#95_#51!4?O#163?_??A#230A#95O#54@!8?_#84!20?G?C!9?C$#99!104?O#228!4?OG#87!30?G#164_!4?_??CA!5?_!5?O??AA?AA?A!5?ACG???_??A#72!48?O???A#30!37?@!43?C#32@#47O?cSEAAA#97!19?O#237_oo#232O#165G#155_#35O!4?_?G#191!21?O??GO??O#210?C#212C$#155!141?O#159!22?O???G#223!4?O#228!11?__#73!91?A#43!44?A#84!28?C#225C#242C#241C#187C#159A#93A#49?_#197!26?_#233O!6?AA#237@$#65!168?@#195!17?O#50!136?_#207!28?G#243G#191??A#53???A#163!27?G#192G!5?@#241@#240A$#210!356?_#203!33?_#31@#74O#82G$#242!390?O#155G-#167OO???G?A?o?O?CA?_???CO??C#162CC?_#87O?O#88_#161?@???H#176_!4?@#202?__#163CC_?GG#101?A!6?OO#204?C?c@O__@DxRBhbgPOBBHGKE#190G#158_!4?_?OG#167??@?@!6?C?C#95A!9?@?A?A?IAI!8?@??]#158GO#170A!8?_sGEG!6?A!8?ACAA#78??@_#11@#75?G?O?G!7?@#168C#28??GC#54G???o_SIAOc!4?cOCA@@@!7?C?A??_#28__W!4?W???G!9?_#30_?C@#53?A!5?A!5?OEAA#31o!7?B!4?WkWXH?AC???K!9?OCC?C!5?@!5?AkCKG??_!9?_#34G@#91A#240Cow}]#170C#84A#48A?R??@!5?_KG?@??_?GG!4?C!4?GG?KOP??@#3g?Oe?OOGW?oXWo?O_!5?K|^KMsaOKCG!8?_$#189CC!6?CGFCB@@!4?_??_#185!5?A#155C?C#99C#141?O#167@!8?_!6?C???@??AA??C???GGG?CC#209GGC#211?_AO@?G?CC???@!5?GA??@???G??{|]r#190@C#47o#41G?A@!5?OA#72KC???Oo??GCA???@?WG#186?@#165_#199KeM\A??]z^G@!8?@@#31_!5?O#47G???_!4?_#162?A!6?C???A#78!4?_?_I@!7?KC!7?KC!7?W?@?_B@BA!4?@!6?@???`DCGG??C!6?A??A@#91A#48_!7?A???_#56__?A!9?_A@!5?@#19??_???AD@?_O?C!5?@??@OO@#35_#53C!8?OA@#35C!8?O??@#53oOdQD#20?G???C??AOWQO_#35AG???@A#32O??G!6?@#2SK???G??OG_#17!7?C#4??`A?@??KI??C?A?Q#8?A#11A@#26@$#96___??__!5?_#165?G??@#157D!6?OOo!6?G#189?G!4?_!5?O?wG#170!5?AB@?BBG??_?O?C!8?C??_??OO_!9?GS[IG@_!7?@#92G#42O?C!6?CK#83_oG?C?G#78?G!8?__#167??@#206o???o{K#190???BA#95oA?_@??__?AIW???G_??_G#29!8?_??A!9?C#31A!7?@!5?G!9?_?G#52?O!4?G??G???CS#27?_!4?C#91C!8?C#43RG?A!5?_!5?@!6?B?@W#54G#83O#94WGKg#51A#21?O#34@#26_#11o?G#16A#55?@!4?@!7?GG??A?C@!4?A#51__#18!4?B#82??W?G#55F@@#81G?A#36??A#237_wwMF@`#200G#94O#82_!5?O!4?@#95A#17_!5?C_??KO#52G??_o!4?OK??C#8_C#5a_HL#13A@Ea@A???EMM~~rN{!5?@???AO??ACIog[K$#171@???@#170???A#208!5?_#132G!5?C!4?A#92??Go#80_#194!4?O#132OA??C??G?OC??C#199!8?@???@B@BAQ@A@_A?GK??EaB#212C?ABB@!7?C?B???OS[^@#191???G#83_#63?_!6?OS_#76_O#77G??A@CpqOB???Og?C#187@??I@@?A@???C!4?C!6?C!8?@?@?@!6?@#9??@@G!8?o#33?G!5?C#42?OO#73?O#94?AA@@??_?Qo#74@!6?_#56oG?OG???@???A#31O#11_???@!8?O#15G#35A!8?o_#79_#81C?CC#36_O?GO!5?@!5?C!9?C?A!6?OO?B??A???AA!4?@EcO!4?A#50A#59C!7?@@#74_#201OC#212B@#205???A#167_#18G#36[!7?GCG?C!5?_CbG@C!5?_?_OB_?B?C#1OG#12@??dEOCICAEFX_P??GoAA!7?@_?J???T??_#28A$#168A?A#158S!4?_!6?A?_?OW?A_?A?H??AABBA?`D!5?d!6?@A???_!8?OO#165O#206?__?OO??cOOGOGCGG?C??AB?_???G!4?C?A??A?_???@#72C#45A@!4?_hAO!5?_???@gKcs#94??A???O#190C#201?GO???A`#165???oG?[???AG?A!5?C?C??@#22!11?QC??@!4?_#83???OWC#43?K!7?g?_!8?A@QWE@!9?G??W!4?OA#84GG#81?C?Q#56OAAB!8?cW??K?_O#46_?GGA!9?_OOaA?AA@???_W?_OOP_??GgG_@??p?GC@???hYGO?o??WC@!6?kskWS!8?C??G#37AIC???_OO_OA#31A#16M_?A!5?@#97_S#37ACG??__@??@#15O#14A#4?AOQG!5?C#2!12?_Qo?O_oW#32_??WG#18??C#27A$#199G#169A!4?@?O?WGCaO@??g??_W!5?@A!4?_KAG???Oo?Ab!5?G?_??@???CC??_#202AC@?A@#200??o!6?C?O!4?GCCa??AS?C_A`AC!4?A?Ka?A#169@#40WwC??wC?G#93?@#168@#90OoOC@CCA?@??_O#168?@?@E#200??O_?C#167!6?C#168@#159__?A?@C??@!7?_???@!4?DC#39!4?@#164?G??C#199G#74!7?_k!5?_G#37??O#56C#45A#26AB!6?O#83!5?CO??C#34O?_?_#41???_#30O#74@A#73O#101?_o??G!7?O?gCD#52??K?O???GG?C@_?@??@C#78?O?@@@!6?_@#51?_??C_?G?@DD#20B!5?O#34_!8?@BA_cEX#37?@??A#52A!7?@!8?@??C?A@#57I?AA#207@#186@#18g{?@B@@???@?@@???O!6?O#19G#6!4?_?__?c??_G?@#1!9?_#6@A!4?@!6?@$#202?G!4?IXHF!6?O!4?A@!6?@#96G?W[???_sB@@@OOO?H?A?OOEE?GG???__#192AC!5?G#159!11?_!5?O???C#199c??_??C_g?@_??@??I#44??c!5?GA#75@?A???@???@?OoO?KE{?S__#195???@#204_GBp#186!6?O!5?R!8?C#154?G??A!7?A???_O???@?A#56!6?OO#27???_#35KG??O!9?cO_C_?O!7?O???_???G#83?@!5?G!8?GG!8?G#50???OC#32_!6?CA#72?C!9?O#54O#94???oO_KkU}IA???o_wK#53I#50A#16o??C!7?B#56!4?AB#51?G!5?C!9?G!5?G??O#79!5?@#5?OG#12C#55!6?C??OG#56A!5?C#43C#16_!8?@#7??@?@?_!6?@O!5?@C?@A?@?__$#166?@D@MB#204!9?_#166@??C_G?\???E!7?A?A??O?K?C?B?G?O?_!6?C?O?_#205!6?OG#95!11?_!4?_OOKG@#209??@#232!4?A#230?A#155!6?O#77??@#39?ABQAA#91!5?cGG#54!4?_#41??AG@#159??A#155A#154!16?@?C?C#90C!4?O!9?C!8?@#24!4?@#156_#47_!6?OEC??BQjC@AAr@cWWGgT??GYD?`CHmAB!7?AB@B?KE{?_q!4?CAGC!4?C!7?A???A@!4?Gs!5?CB@?k?@???A@HY[A?GG!4?C??CO?G!6?_oFSCOG!4?_???C?C???G#14???A#230!6?O#197@#31C#50A?O!5?S_?O!8?C#79???_#101G#81_!5?@#17?A!5?@!6?@#5!18?G?@A??C?@@?@??O$#97??O??CC_???_???CEEQA@??AB@@?W??G?_C!4?[MIAG?KKAA??`FPX[ss{WwG#189ACGG!8?A!8?O!6?C!8?O???_#38!11?w{kC#29@#163!5?A#74@!5?C#44??@?A#45!21?BOG?O!6?WC!9?OOC?G?G???B!5?@C??@#36!8?_#11!10?C!4?G#84!5?G?A#95D#36??G!4?_#33!4?CG!6?_#36O??C@#84G!5?G!4?OO?A#34?_O?A??C#84??G#19!10?G#28C#48!4?A#21C!5?O??_#18??A#81??C#35?G??@#56G#78!11?_#94_wOK?A#95!4?G#233!6?_#79?@#47_C#55G?A!7?A#8??_#14S#13G#164!7?O#163G#82G#158@#54C#58C?A#59??G#50G#83A#34!34?O#15C???KO$#190??G!8?B???O#168GG??A#159?C?og_?C?C#168@#171!11?A#157!4?@@#161A#131!8?_#190??@G??_?CG?AE!4?C#94!6?_#165_!5?A@?@O#228A#24!20?@@#164!6?@#154C#73_#65!7?G#39B#41!23?@o_!7?d!9?_?gYO_CS_??K!5?_?FA!6?@???@@!5?CO?G!4?C?A#55!4?C?G?AC@??GC?A@A?@!4?C??@!5?@C!4?C!4?C!7?G??_O???QC#75!6?_#95_#84!4?@#50@#59A???@#57??A!6?@@#11?wK!7?G#165!5?G#99O#57?_osO?A#243!9?@#56???_C!5?B?C#100!16?C#186?@#84A#53C#49C??A??A!33?_#35G#17C$#101???_???C!6?C!4?@#156!9?_#188@!4?oC???_!7?_!5?_#167!31?O??G#201@@?OA@??_?O!8?O#72!50?G!5?O?@@???_O???O!7?G?C!6?_OO@!5?@A!9?@A?_!9?@#82!4?_???C!7?@!7?G#94Q!8?_??R??@@#37[!8?CD?KaE_#34!19?@#98A#82A#95!8?O#28!6?AA#78O#167!11?O#84!4?A?@#58!15?_?_#59GK??@#168!20?@#96A#37!41?O#83_$#188???G??O???_#192!37?O#101!38?_??G!4?_#75!64?A#164CA??O#26_!5?o!6?EIEE!5?AK!9?GO?@@#95!17?o_A#46?G!8?C?O!5?A?C#54???_#95??ONr`?G!4?O???C#35!17?o?@A!8?eCCS??C!9?_#33!12?O#30G#82_#98!16?@#101@#32!17?@#34??__!5?O?@!5?VoaQAA???_O???_oG#97!35?_$#185???A#163OO!6?WW???O?G?@??G?G#169!60?O!5?G#39!68?O#94g???W?CAG#189A#191A?A#164C#228C#89!14?A#160A#95O???A???_#36!23?A#81!7?_#101A#37???CsO#157!9?A!9?__#98?_P_?@!4?aG#18!9?G#155!105?@$#132!4?_#163!86?O#154A#47!69?G#201??G!6?@@??GG!8?@!4?__S?G#235C#91!37?O#49!6?G#155!9?C#37!5?_O#92?_#74?A!9?@#28!11?O$#191!92?O#163!69?@#155???C!4?BG??O?O!6?A#209!7?O#228G#92@#77O_G#94!36?@#57!23?@!9?A!8?@@@$#210!92?_#93!73?GCC#77A#22?_#195???@_#185!17?K#93A#158!66?O#99?O?GI$#30!168?__?O!7?wSH?@_O_AO_O!7?W?`??A???G???C!6?@?GA!7?_?@#100!32?@#82A?oO??@??@G$#211!176?O#195!17?A-#96BGC?C@@?OG!9?G_??OAG!6?HC?ADN#169KA!4?G??A!5?OKWAC??o???OO??I???@!7?_#157@!9?WO#95CFCKA?@B??@#200CYA#167_#47_?@#63@???C#80_!7?@A#73D#49A#45@_??O?F@??EB#204_?__!8?@#47g_oGIoo_!5?_!7?@?og]IHFG!9?C!8?OgIiYLycC{O?il`rMB??G??O?P!9?_wA??oK???A??@!4?kG@D!9?G???@???o!4?ECBCHEB!4?C@?aCC??o_???i@!5?_?C@???OO@?C#91AC#240OO#82@!8?OC@#19???KA#34_?GG?O#48_#52_CA!5?KB@???G#37_#14?G#21??A!5?O#37C?@?GA?O@H???@!4?_#12A??_?CoPOsG?_GO@OA??A!6?E??_@O??WG???@#5G#1G$#169O?A!5?@?[!5?_!4?@C#162C#90G!6?G#61O?GKA#170_!5?W!5?C??@#165@#132!4?A#162??@C#208_#165MEGCCC!8?`O???K?A#204?G#201???__??OOOGG???C!4?@#44WOY?G#65G!4?@!7?@#41_???_#154?O???G??GC!4?G?o!5?C!7?@???_g???W#72W!5?OE?A#22O#29`??A#154A???A#83A!6?_!8?G??WC!4?_@!8?_OD?G!5?OG!4?C??A#57_!5?A??G!8?C!6?`!4?G?_!5?O#51@?PWOI???O???A_@O??O!6?G??@??C!9?F?T!7?Wu_#20O#18[Nc_!5?G#81O?_O#58GC#48??K???C?_dD?__q?_@?oo?o?CC_?AD@@#15C?A?C???_?O#2??E??_???@O??_??EC???@?O#27GO???C#159@#18_??O#15?O#2u$#132C!8?C!9?O#157?K!4?G#84_#91O#80M@!5?O#162O#202o_!4?A@@C???@!9?_!8?_#206??OOOGG??CC???CC!6?_!4?_??g??G?`H#45?_??@#49O#39@??@#74O!8?_#54?O#168A!5?_?O???O!4?S#78?GWIG??GB@A#77C#29A#30@WOCU??@?^IBAC!4?@?g!4?GO???GwGOA?H#94_GO@#42?_#35O!6?A???A!4?O!5?C?A!5?__O?@A?@A?A?@!6?OO?A?@!8?C??@#74AB#56O?C??oA?A??C??_#94AkeHC?WO_OWA!4?OQH@?@???o{^MND_!5?C?_z?G?_!6?@#16!5?OOPW#159@#201@#190@#56_?C#84G???A#34??G!6?OO!8?GY@??aB?o_!7?PO#4B@???@#6?K?@m?Oa_!6?G_c?AP``??_#16GG??G??_$#202_??@@!5?_I?_?_???@#83!4?C!5?C#65_???o#60G#132?A#189CB`???Ca???@OW_???G!5?@?@_???G?CA?A!5?_!7?g!6?@@#165@?C#168C!6?C#54C#40G?uFC?A#90OG!4?@?COOC@@G???K?K#190_#159GG??A!4?A???A!5?@@#93C#162G#74C!4?O#155O!6?I#164H#159C!5?C#187!4?CA#7_!8?@#54@?O?@?A@??@#27C#77C!4?@#82?K#26_!7?@!6?O#37?@#84?O#94O??_??_oCCo^M??@!4?C??Q!4?O???C#81C!5?C?_!5?E#31!4?_?@???_?KOA???_?AgYI@A!6?GB#197GC#36@!8?G?B!6?__?@C???O??G!6?O??W_aCC?_M?GG???@A??G_???o??OC!4?@#3???A???E?BBE@H`???_!4?@DBBO?G?@!9?Ae@$#130G#97ToiaA_OIAB@!6?W?C?Wg?B@?K??A??A@??@!9?OH??AEADP`_z@COO#187??O!8?_#158_!9?@???C#94?G#210?O!5?G#158?_?_#169_#204GA#91?A#41eC#42G#66_#75O?CA#159_!4?O#77A??C!5?AG#167_#93C#195_#199?_O???`q[B!5?@A#90???K#94?O#185C#164A#26K@?DA???D??G!7?O_?OA@???_??GO?CB#155OC#55_#56???@!4?A@!5?O??G?G!4?GC_??_CA???C?C!8?o?_???O___#167A??@#158@#36O_O!8?@?B??@G??_!6?_#35!4?A?A#54@#53@?_#21?@CAOS?`@?@??o_#57??Q!7?COO!6?o??@#5!4?A#11A#37A?C??OC#55I?_O!8?A#32!7?@!5?_?D!8?A!5?_#9?C#7A?B#23!10?C#7O???G?G?k?GE#30?G#17C??_???Q#7A$#166?_??WO?A?O#208?C?B@#192AK#158g???a#93??_#87C#159A@A@#75_#160C#63_g#48C#101?_#192??G?AA?O??@E#96OG?C!5?@#192???_?_?@!7?GG?C?G??C#211?_??CQB@@??!4_?oOO???@OC#33???_#38??AB@#60?@#95G??@!5?GE?@?_?OOA@?A??@!4?KA???C!5?G?A`!6?G#44?O#54_#42_#33O#93C?A!5?A???K#165G#195C@#162C#191@#95@!5?_G?@#80!5?G#81O!8?O#95?D?_!6?C?A#33?_#30!6?@??A#95o?O?B?E#99?@@!4?CCGC{KG???__#78C?_?C#55A!4?G@_G??_?@A@??CCGG?CC??OA#19?_Ce?OCCC?A_??_#190?_#201O_?G#56B#157A!6?_#47CKI#56?C#32!8?__#200???@@#169@#157@??A@#20??S!4?O???WC!5?G@#15C#35C!7?_W?@#13KG??C?GCcw?g_GOOUEKMjL^gtO?OOOc_?SEO?OOc??HLG@$#134?A#163@!4?d#165C!8?A?C??@#154?O#155?cG#130_#76OOP#134A#49O#66O#199!4?O??_??G?O#158__?A!6?C?A?AI?G#170?G?I?GAC??C@O_A!4?GA?@?gA?O!8?[#199WRCCOA#85!5?_#87OGc?A#72@!5?A?OGooGG!6?@BB!6?CECc!8?_#9??A@A@#94!4?C#37??_#168A#78@?C??C#90!6?H#206CA#72??@?O!8?_#31???@?G@#55?O!4?O!7?O!5?OGCC?GC?K#54O!6?G!6?G#73OA#74O#165_@#98??@D#46A!7?a??K!5?A!4?s??AOz?b?_?GOBCkCG?GHA?Ow!4?OIJC!9?bAIEB??@?C?^A@!6?K!6?_!5?A??@!4?C?CA#28@!7?_#52@?M?O#55K#159_#163O#14C??A??G#5?@B#15!14?E#5G!8?A#33?G!6?C#34_$#76??G#73C#189?GC!4?OOOG?B@@A@#166!5?OC#188@#92_#77I#158??@#67_#176!4?@#190C?@!5?G#208A#194A#163C??OAG??C?A?C??C#200O!4?o?@@!7?AW??Q?CA#187???G#155HA!4?AA!5?G#76!6?_??A#191_C#167_O#158_!6?CC#75@T?AJ@??CC#165GOO???P!5?CB!4?G#43!4?_#56!9?O#228C#31??_?O#9_??_o__oOO!7?A#90A#101E#30!10?A@#101!6?AB!8?A#82`Q!5?__!6?G??AG#31?o???C!8?O?A#84G?GC!7?G#43?O?_#74?W#78K#26!9?@#57G!5?@#26!4?_#35C#16??A!9?@#28@#243o_#81@#101C#53?GCg!4?CIW_G?@!7?A!7?G?C_B@!5?A!6?Ke!4?OG!4?A#231??_#79G#27_!7?G#16!14?@#4?@!4?G!4?_!8?C$#157???O#188?C???@#204??E?A@O#101CC?G#100!6?O#194!6?@#204!5?wGOEDAG#101_!6?_#168!7?@#190!4?`??AC?@A?@?CCG???C#212___cO!8?O?_#84?@#67!12?G#92OG???@#165_#93A#83GHg?A#78?G#169A#162??_??O#187?O!8?_#45?_o!4?CC?A@???o??G#187!7?@#45!6?@?C@C?O!4?S?_GO#11C#46!22?O#94C@#31_!9?O!4?@#52A#43A?C?B!7?_#55?eO#26A#81O#100_#170BA#15??_#21A#51G!7?G#34?_#53?C?O???A@?WG#43!8?C@#11??K#36G#26!18?CA#191G#73A#237G#200c#158Q#21?_#31??D?OO@!7?OG@C?G#82???_oOG#98C#51OO_A_?O??@A???O?oWo!8?G?C#58GAA#154?_#42O#8O??@C!4?G#26!12?O#8?_o?G#23!6?A?_#163@#14??o#20_$#101!5?_O#167G_???@!4?OA_Q?AB?O!7?A#211!7?O?_?_#167C???s__G@_cQW?KO??@_AA??C???C?AA???G??B@G#83!10?CE??@!6?O#154!9?C#98O#155A??C!8?E#94!6?C#200?O??GGB#167???@@?A#75G!8?A!9?C#223!16?A#228@#77??E@??O#157!22?C#99A#57a!7?C?A???C#34!4?@#16@#33@#101O!8?@!4?GOwA?C!4?OoG#37??@?H_A???C@#28!13?@#74!23?G#223O#226O#241??G#167G#35!4?O@G#95_#59_?_A#47!8?BA?MA#155??A#167C#205A#50__GO??HOWA!6?KE?G!6?GC#191!6?O#11?_#17G!4?O#1!18?AA#32!8?@C??S@??O$#170!6?G!4?_gGC#163???_??O_?@_!6?C#200!8?C?@#166!4?@??G!9?GG#199!5?@@?BGG???O!7?O??G?O?@#82!6?A#166!19?C#199_G#170G#157G#91CO#97?G#206!13?_??EC#170__?@!4?@#75!39?_#78C!5?GAD??O??_!5?[??GA??O[?C??O?O?@G@?A!5?_G?cCGW?@??C#155!4?G#157@#53!5?G#82@_???_W#18!5?@#19@#35??G#41!14?A#228!23?_#238?_#11!9?O#186_#81???A#35!9?C_?O#192???C#232@#243@#230@#17!4?CA@?GOG`@A!5?@A#90???@#56O#28!10?_#37!33?C???A#35E$#171!6?A#190??_#200???C#199OK#191!11?A#206!14?C!4?G#205!20?_O!8?G???AO#169!36?G#185AA#84?O#100_#201!17?K?@#41!6?OOO?oo???O?_GGWCEv__?@a??O`?o??HYM@?@?G??_?GE??C!6?_C@!4?O!7?_!5?C?@#53_#81WC@?B?A#77!5?O#163G#155G#59!7?A!9?_A#43?_??A#54A#95O#59!7?_#233!76?A#19!6?_#18C!7?@@!7?A!9?G?@EOI?OO#49!29?A??A@$#206!14?_O#209!26?G?_OO#201!21?O!5?_?O#100_??OO#209__??O#189!33?O#190OC#163?_#74!71?@!5?C!5?O??__???K_C!8?_G?G???C?@???g!6?D#37!5?G#52!13?O!5?IOGE!5?OOO_#59!90?A!6?O#48!45?G#97AA$#204!69?_Po?_#168?C!6?O#200!38?C#43!74?__!5?OCC??AA!4?A!4?_!5?a#37A#79??A#50!37?@#16B#28@!6?OO#98?O#16!99?C??A!9?A?@?CG_?G#55!29?@$#163!78?AA???@!9?G???G!5?A!5?@#33!87?A#29G#165!23?@#54?F#36@!9?GG#45!31?C#91K$#101!79?@@?G!4?@???CEB?A???C???_#73!120?G#30OGB#91?G$#202!81?A#159@#52!142?G_-#169@!9?@!4?G!4?A#73_???G!5?G??@#199GE??CO??A#163O???C??G?D?A!8?OSA!7?G???C#211@???A??@@!6?C`GM`?_BOGGA!9?_?G#189O#170_o~MD???_#45JAA@#93@#169@#190B?C@#206o{]NF#47O?G?C??`Z@s~L^na@???@!8?CwS?GB#26g?MDCDG??GGG?@?@E#47GC?WAPHbOKOANTGcOL?OLbRg_???D??CKAOGOOA@][AOAZOO??oG?@#36_#30?GAA!8?O#34@!8?_A@!6?A#74_#82gCAc#53_#94Q_[AHO]KJG??OAaEAG?C_WKxqXJ??GO!7?A???_?_?o#31_#34O!5?S?@??A@?C!9?G#21_#19???_#52C@#169O#202O#82_#21?@GQ#34?W!5?@!6?A!5?BA#3?K??gO??C??ai_C?C?_GW[_GDCC??AD@!8?@#34g#6@$#189AO#97@BFRIc_SWmG?o__OMH?@ejC??K?E??G??@!8?_AbqBWOA??D@@B!5?e#101G!4?_?O!5?G??G???C!4?AO!9?G!5?A#45gO@?_#83_#168G!4?@#210@#165??oG@#83E!5?A#187E#204??_?OG?@??F#41o?GGM?YCC@?q??[Q@_?I@@?CAB@W??@@_?AI@!4?_!4?Ga?E??@_??AA??OK?OGS??a@!4?C#81?O_???G??G#57?C??_#36???@#53G#28G??G!7?_??@#158_wO?AC@#55C???GG!6?KC_C?A#5G#51@!8?@@??E!6?A?O!5?C_??LEOB!8?G???`?@!8?BC#19_B#47G!4?_?Q#53OA?AB!9?UGC@G??CC_??E?_#54C??G???G#4_?C!6?OQ?_B@B#0!9?WW#4?A??A#1?A???A?A#35?A#17\`_`g_GoOG$#190C#158@__!9?GG???@?CG#87??AC!4?O#204_!5?O@???@#169W?@G!4?OC?OG???CCCK?@@G#204GGSA#84_?OA!8?_!6?G?G#190_G!4?O??G#209?CC#186@!6?O#190GC?A?KF#169?@#100OC#81GG#44@#162O#75@#200_?owwEO#94!4?_O#167G#78G?C#95A#72?@??gG!4?@???O!4?A???_???GE#31kC`#9?A@?OG??A@@#43?O!4?@!6?_@!7?__O#94!5?OOA#54A@!5?G?O!7?A???G#95BE!5?_OC#98Aa@!8?A@!7?O!6?CG#96GO#101A#19?G!8?OO!6?_#35?@#36!5?_?K!6?_!4?W@!7?G?_PAgB???C?S?C!4?B@@AO?dM!6?@!4?G!8?O_?O#27OG?G#16EDD#2!4?WG???BAEDD@Hg[?@XoEAWRGG?EK#48GO!5?A!5?O$#199w#202K!5?@@@???@?C???A#65_!9?@C@#165_?O!6?O!7?A?A#189OG?OGK?`!5?@@??G#158C#91CgC@!9?O!5?C#199@?AAA!4?C???G!8?O??_gOG??A!6?GoGECXiMF@_OG?BB@#170@#54???_O???_O!7?AG???o?_??A#93!5?O#75G???OO???S??_#74O!8?@?G?A??A??@??K?G!5?A?B_a#79_#73a#35C???_O?sC_!9?D!6?_!5?E#47F??_o??O?_A!5?G??B???lE_g_`_@SRG?D?@oc??BG@@!5?O?@!5?O??OCC#199C???AC#82@#84CA#52C!5?_!4?@A??C#51G!8?O??AEBMWiA??@#58_??_#37oG?@O@@@!7?@#12??`??C??_G?W??A?@C#5?@#8@#23!4?__O?O#42G#20???C???S#4A?@#7A$#167?_#163A!5?A!8?G??G#80???X@@??O#186_!4?A#158G!5?_??CCGG@?D@!6?@#206?_!7?G!9?A?BB@B@!4?@@???W?@?_?O#77???_!6?G#76A@#200GCO#91!6?O@#72A?CC@#157C#46!10?__#191C#30___ow???A!5?G!8?C!4?@_?OOPSoG?w!4?O?C?`WGA!5?C???A!6?OG!5?C#157?GA#72G#37@#43?G!5?@@?@B!5?C!7?O???@!4?O?G_OWOO!7?G??_C??_#79G#95A#84O??P#55?OA#46DQ?@??C@JG{KHXBIWOCaE?_?{`Q??_??O_wwGAA!6?_O?O?G!4?__KA!7?AG?@??@`#55?O#59G??O#17!5?AG!5?@!8?WGAC#6!4?USA??@_g?SSPa?_???@DC!5?C#130_#14!4?GGOB???C$#170?A#166O#130O?_!9?O!4?O!4?O??G!5?A#206?_?GGGE??@#96_?@o_K??GA???@#190G??A@??CeAA?A?@!6?C#189?_!9?AA@???G???C#72???_QA#40_NF@#87C#206_OO?E#97!5?_#80O#49@A#209_#95G#87C#31!14?OOO?EC?A!6?_A#155OG!6?GC#78?@??C#22!6?A?fUGCCEA#42??_#35O???_KC??_??@!4?_!4?O_B?A#158C#56_???B??K!4?s???C#94??G?rnAcWM[???O??C#101WG!9?C@#81??@_??I!5?SO?TG#155!6?G#21?O__C!6?sT_?AO!5?A#31@#57_??_???C?@#54?_#158A?O#78_#167G#205?A#202A#20G??T?C!8?GW_o?scsaFA??@!8?GUC?G#96C!6?A#43A#82G#11CGC#35@#13OO?cA_???GK{OO!5?@aAE!4?_oRJ`_??q{AW@!4?K#58?_$#186??K#101KgC@!5?ACC??E#166?C!8?_#67?GA#202oWGc?B?P!5?G#132?C!5?A#194?C#202!5?B?W!4?C?C@#90oC#80_#155OG??O!6?OQ!4?CG#201_!5?O??AO#54?W#47CC#42AO#41G#156O#92?A@#201_??A!9?OOG!4?@_?A#159!5?C#165A?@#26!10?C#95O?_O??@O?C?D!9?_#185O???_#159_#72_!8?C#33??G#83O?C!7?G???A!4?C???_!7?_#31K???__#74???@!7?G#11O[#45C#165G???CB#52O???_???O!6?K?@!4?@@#59__#99A#95!8?A#11??_#55??@!6?A!8?_?O#95O???C!8?O?J!9?C#16@???G#55_C??C#37!5?G#58!4?A#241__#210_#97!8?C#169A#49O#28G!8?O#18O?A???@#1!4?__??@A#32!15?O!4?G?_A??C?_$#161!4?O!6?@#167@?A?L@#131?O#91!4?_#90aM@?GC#72@#58C#61@#101_!5?_??cC#203!6?_#192_??__oooW?O???O?@@@!5?C?O#170C!5?A!6?_!5?O?C?@??@#84C#90@#65@!4?@#163C#162A#155@@!6?G#90_#99_O#211?_#45!30?C??CC?oG??G??A??@#164!5?_#29A#6?@EA#45?O?GI?@CAA#56!4?O?A!5?O#82C#31?S#46G#98!9?O#82???O?A#41?A!7?_!4?C#57O??@D#9_#31@!6?_!4?@!6?_!9?C!9?OC??_???c_#53!13?C!4?_!6?Ce#159?G#55?_???OG_C#18?_???ABA??G#31?G#48o_O?GGO??O?oK???G??COpD`_PpXHF#15_!4?_???A#7?_???_?C???O?G?O??A!5?C__!5?G@@#16!5?OO!4?C$#132!5?G!6?_!7?@?HC!9?C#190O!7?C#168?G?O#208!6?__!7?`#100?C#165G!5?GA?@!8?C??A??@AA??O!9?`#44???g[??E#209???GC#101!6?A#59?S#51G#159!32?G@??g???O?AA#42WCA#28o#7!10?@@@#154_O#54!4?@?O?O?_C@???_??A#84@#101!12?G#52???C#33???G#45!9?A#101??@#37!5?A#26_#77G#155CA#59@!4?A@!8?K#48!4?@#78O#15OCC#35O#36!16?A#78!18?C#237C???G#101G#170?A!5?O!4?C#37?O!8?OA#59_#81??@#50CG?C?A@??W?AG@??C?b#167!6?A#79?A#32?OO#78GG#46O#74C#33?O#8a_???G#12!29?O??C@!6?C?A$#96!6?OWS_EO?_???_o_?UOO!9?A#170???KE?@p#166G!6?E_!7?AA#170?O#157??A#209__#211OO#83?_!4?_!8?G??G?KC#100?OO#200?C@!6?cA#63!5?_O#191!4?A#53!10?C#35!32?_???A#94_A_@_#77!20?_?C!7?_#95!5?o!7?A!5?GALO#77!18?_#99!13?GC?GA@!6?AA#16!8?GOO#200!37?G??Q#165O???@!6?G#191C#50???_GA#159??_#74C#165O#56C#17@!8?o??GC#101!5?_#98O#157!14?A#5_!5?_??_?_oWK?A@#97!25?_#36!5?C?@GO$#157!6?C!5?SO??O#162!9?o#189_!6?C?P???A#99!24?A#96!6?G#98A??_?_?O?G#92O#94?C??A#230_#192_#212!5?__EBB_aO#164!57?A#168D??G#74C!5?A?G#94!20?_OGF`#42!6?C#28?_!4?A?_???@@#157!35?_#81@#82O?C!6?@??CA#8!6?_#27_#9G#199!37?A#155G!9?O!7?@!6?_#232G#78O#57G??_@#58?B@#163!27?C#187A#31_#52@??E@#100@#154@#14??G???@#37!33?AC#50A#8?@A$#70!6?_?G#68G#76_#170??A?A#189A#159!10?O?_?O#68A#209!4?_o#167a?G??AO!4?@?W??OMC??AG??_???_?O!4?CO!4?CC!5?@!4?D#204??SO?C#158?G?@!9?_!9?_#201!37?C#186A#185_#209O#154O??g#77O#78!26?g!5?GI?_!4?H???C!5?_@?_Cs_?@?@@??G??CA@?C?KO???OA!4?A@!4?G#16O?A#74?_#56d???G?G??O?E???@A!4?G#238!31?A?C!8?GME@#228@#59???@#53?G#169??O#207G#167?G#35?O@#231!33?C#229A#190A#47_$#155!7?A#159?A#204!4?@#199@#92!11?A?@#75A#211!9?C?A@#205!28?@#97?O#95A!5?G?G!4?G??W#157!7?G!5?O#228!55?G#157???C#100?@#55!41?B!6?K?OO!6?Oo?@OC??A_?G@??@!5?`_?A#36!9?_!9?_!4?@#50???B#32_#201!38?CG!9?O#169@#231@#236A#211!10?O#236!39?C#210C#55G$#93!28?A#166!46?G#78@!7?O???C#26!120?_!9?A#27!41?_?G#57??A??G??EOO??G!8?@C#191!31?@#232@?@?A!7?GC#185!52?C$#100!28?@#157!47?A?GG!4?_#73G#72G#168_?O?_#5!170?G#11C#46???O??OO?@A???@?A#169!39?C#190@#240A??@$#155!28?O#197!48?O!8?_#47C#162O#15!173?C#73_#37???_#94GCB@!4?_#186!43?A#245G#243CB@$#156!28?C#169!48?C???O#186O#191??_#74C#20!175?A#84!4?K#35!54?_#211C$#187!77?A???_$#159!77?`#200@#79_#209A!7?_$#201!78?A#154?_$#210!80?@-#76O!5?@??@_???@?@!7?@#206O!9?VE#211@#96?NBaCOHAA?C???oo!6?OoGG?_?@??A#200O#93A#186?CC#166CG#77?@?O#212A?AC?A?@???_?Q@!4?@#189@#191_!7?C#170_OOW_[}RuVGA?@__!5?_???C#47OO!4?[Y??OBC`?Os|^HEI??GGK?AAC?FA@GM}N[EA#45@???@_?O!5?@#77_#35C?@B!4?@?__??G!5?AohW?A!7?A!4?OG?B?@#31?A#57??C#46_???A@#45_#22C#101_?kQG!9?O#56AA?O?AO?_#36AO#15OA#95_O??A?K??C#36_?___g!5?_o#78!4?C#95?A#99??C#36?GC!6?XGG!8?O!4?OdV??O!6?G?P_cGO??A?EKEeAS?G??@?s???C?B#15?@???W@??C#11G!5?@#0!4?_?__#5G?CC?G?g_?O#23O!9?CA#17?@@G??MHM?O#51@$#96GO[MOG_?OO?A?@C_#92_!4?GGG#202_???AA@?CAEG?A@#97?G@QnA@L`@B!4?_!7?o???OA?E?A?AA???A#72A#74___#211A?BD???@??O^@G#169C!4?G#166G#74W@#47G@#44@@#206_O#212CAA#165!4?_?GF@@#212W??@#200???OOOG@#35_???@?O#41bcj|fOOQOGA?_AXd`_!6?GG?C??_??AW!7?W?W?QA@???CE???AS!4?A?C!4?CG#73???A#74CG!6?g?G?A!8?A#26???_???OO#75G#158OPQK#52_???_O#95_?O__#37??G_#35@!5?CG#26@#41@#101_o#99O#57?o!8?A??G?A???COwsQO`@a?C!4?_!4?_?G???}O_@!4?K???_???I`?@@@#19?G?O??GD_G?oO!6?O#32?C!7?A#13?A?w_OC??s!9?A???@FGoWG!5?_?ppoOGnk_[G?G!4?A@#20E__?s$#166C!6?GG#79_O#65?A!5?@A?CC#163_?A#189O_?G_D!6?_#163?OO!5?E#192G!5?G@@???G?C??@??O??G??G#209OG!7?H_A???_!5?O#159O??C#167A#72???QB??A?@#59@#204C@B!7?oOEMBABN~LNJBEA#49GGoCE#40@#30?@?A?GGCg!5?_!7?W{??GOc@!4?@@???@!4?cOC#47G???I@??OMgb[PO?ww?CoAL???CC?G!4?U!6?ogAQ!4?wW?oGOGC@!9?ACOAJE!4?GEAO@?G?@???A??@C!4?P?GQ??A!4?@??G#59!10?_!9?_!5?AC?@!8?A!5?A#16?C??O???CG#51???@??O#189@!7?@#84C#8?__!6?AAAB`AC???@???@!9?O#35!6?_!8?_O#32O?EqP??_O#21?C?A$#66_!4?C#63A!6?AA#155O?O??G!9?C#165O?G#199X??s#87??CC#80_??K#157?O#132A#204G???CB?Gc?@#158???C!7?_?C!8?G???G!9?C??A!4?@#77_O#63CA?@#199_YhKc^B@K@_oKm_P[{{o?_!5?A#43C?__#31???S?GC?G@!4?O!4?_!8?_O_@??@#54_C!7?A??_!5?G?Cg`O??GH!5?A?A!7?O!4?AA???C??@!6?C?G#81?E#94@!5?C??_?@?O!4?_!8?C!7?GGgxM@KMINB?B!4?GO_???_?GGMCE@J#21?D!5?@#18C#94???_k]M!8?O!5?_??E#56O#37!4?a#20@?_@Oo\AhAgH_!4?CA??__G#4?OGGC??A??__???B_HkORVA?A??G_??`CUWUG!8?O#26A#96@A#48_`owK!5?OJnG$#170A#97ca_`_GP??@tdC_GG_COUOPOC#192GAACO#101AAOP!6?_!5?o#202??O!5?G#190OA`???@@AG!7?GO!8?G#204_G!9?g?@#190CC@?OE#54?G#45C#75GKC#230OG#99??_??_#158??G#211!11?A#206?CC?@@#84A@#42W?K#45!5?__??_GA?C#94?O#78QG??B@???A#72?@#36?O#46O#33@#43O#37_#155?_#185A#201WC?Q?@!9?__#159o#57A#55O???_A!6?[!6?P@??A`??G??CG?B_???C???@@D#11?_O?K@#95O#165GE@#157@@#55_???gC?D?C@O_H?G?CA?G!5?A!6?OSHCCCACO?UA??C??@`?O!6?F!7?C!7?A??A!7?Oc???_?_#18?GGC??A!6?@_#5_oO!7?O!6?C!6?b#2GI??G??_o?_OOO!4?@f???A@??Ie???W#176@#49A#55!4?A#16@@#36?_?O$#199@#101A@#167@!5?C!9?G!5?_??G??_H?_??GAO!8?_??_!7?__???O??_??_#187!6?C???@!7?O???G!4?G#155_???O#73?C#80_#42A#163O#210O#65A#84A#95!22?_#57_?G#54__#37C?W_#54!6?A#26?ED@??_!6?_???O#190!11?G#164@???@#78C!6?aC@???D???_ECE!4?K!7?Coe!5?@??A@!9?_{AA?CB#7g?A@#41@#99_??A#82C!5?_??GS!7?OO!4?A!4?O#46___??COG?kba@??NPBB@???gGOB?HE?CKgxaOOR@?O!5?oO??_?GQWHDOOGkQADC!4?_??@??@???G???O#37_?K#187A#3???oogcC!7?WsTOOckg[KXJG???BAb???@^@?AK??o#27?@#61A#185?@#15!5?A$#71?G#70?O??C#157C?A?G?WO???_!7?_#190@?_???C??G#158!4?G??__??O#208_CB#206D!4?WCG?@!7?O#205O?G??G!7?@#199?O!6?@A??A#78_A#94@#79GC#83!5?O#228_#211_#92C#101!24?O#46o#34O#81C#190@#48G??A#72!6?@#9??A#158!6?C#95@?@!5?@!9?O!8?@??_WGB???G#56??C#36G!6?o_!6?A#57???@#81A???@??d_A#57G#41??_G!4?@#82!4?A#6_O?KA#77A#167G#98??CGO#57_!8?_??C?B#46A#34?C#31@C!9?@??_??OPP#53?C!4?A!5?G??_G?OO?P!8?_??lAK???o!4?K?Cg???O?o#50???AO!4?C!4?KGC!9?B#16?C!5?@?GO#28O#7!4?AA!8?E?BG!5?O_!4?@G!7?A$#190?@#49??EA?A!9?DA@@#83_???@#205K#204K_@!6?O#155!5?@#70?C#166O#211!4?gG?B!4?GEBAAA#100_A!4?CC!4?@#84@c?GGO!4?G???G#91C!5?_!5?_#192!5?G#165!25?G#33???AA@#162!17?G#37O#35OO?__!5?AE??_#191???S#227A??C#156G#72_C!4?EO#154?_#37A!4?@!4?GC!8?A!7?K?G!7?CC?W?a#36@#28!7?C#29_#159?_#93!4?O#74A?@!4?A@#43!4?C?O!6?KC#83@#90C#59???AP#54!5?G#78@#35???@#11!15?A#31A???A??C?@#81?Q#82?@!8?_!4?C???G@#17!7?AAB??_O!9?c!4?O?C#6???G_#30@#9??@?A#6!4?c?A!4?CSD!8?@A?GGCAC@A?O$#69!4?G#87@#159O!5?O#101_#44?AA#100?O#90_#80_@A#73E#168O?@#158O#169OC!8?K!8?G?C?O#199G?CSC@A#169!4?O!6?G??_!9?O??_#228@?_!7?G#82O??G#191!59?C#159A!5?A#195!11?_#228coWG#199C#43?_???C#94_C#155??O#187O#43!5?A!4?EBG@?O__L?B!5?AGo?@!5?OGCeWL#83!17?OG!6?A#53!5?a#52?GPD#51_#16A#7@#54O#74G#53!5?_#51AO!8?O?kG\?_GCA_?O?S?oO_HwIpO!6?@_@?XqgEAD@GA#19_#31@!7?C?G#14!4?C???O!6?O?_??A!4?G!8?K?C#34G#1!13?A!6?_!4?C#0E$#132!5?O#80?_#73A#165G#161A???G??G#186G#166C#89?A#87_#156@#77A#191C#170??@?WGA??__?O_#162??G#130?O#189!5?@??GOAA!4?G#163?_!8?O???O???C#189C#206CCO???A??@_@#93!6?O#201!60?A#83C??A@#155@#27_?o#159!11?A#162A#163_?A#42GA#29_G#22@#31!14?O???P???@???_#82???C!4?_O#84Y#83@#30!5?O???C!5?OG?A#81!8?_G!5?G?_?O??c?_?G!8?@#47!30?_#19???EA?FE#50!8?CHCG?A#170???_#47??@?_???Q?C#21!10?O#74?A#55A!8?E#7!5?P#18!5?G#15!17?o$#85!8?`#163?K#156?G#67??D#61CA#154!6?G#99!7?__#209??@#212!13?OCA!5?O?CC#98??C?AC#101G???_??_#80???_#208?A#210@?C???_#95G?CG!6?W?OA?C!4?G#187!52?@#74A!5?C!9?G!5?O!8?WO?G#94!8?@#45@#27@!4?_#30I#18C#94!6?GW#52?`?CC!4?C?@??aO#78!18?C?KCOG?SBGD@?G!8?OCBC#34!51?@!8?A!4?G!4?@@g#198!10?@#52E?@???OO$#162!8?C#158!7?O#170!33?A!4?__??O#168!4?G?C#82?@#83@!6?_oXWP!8?C#165AO?C!4?@??_?@???_#28!55?_OWC??o?GG#168!9?@#212_#226?A#100?@#33?A@#83O#33!18?A???O??O!8?C#27!9?_#28@#36!20?@#43A#31@#30@#84?OG!8?_?G#28?_#9A#98_#20!55?g???W??@#81!4?C#101!20?@!5?A$#209!64?@#155C?O!4?CO???A??A??_??O?G#90?K!9?_#55!62?C#43?C#170??@#209!13?G#223C#26!5?K#56!22?@??O???g??@DO??O??OoD!9?A?OH#35!14?A#54??@#30!14?_#52!58?AC!4?A!9?A#48!15?EAGWA?G@?B?@$#159!65?O???G#90!9?C#167!7?_?AO#157A!4?_!5?C?A#90!84?@#52!22?_#28??@#95!6?__#37!114?@#95!8?A#34!21?GG?@G$#157!66?@!8?AA#101!10?OC#207??_#200O??C!6?_#99!119?O#83!146?@#12_!5?_?G?CQ?Z?C_O??@!9?@!5?C!6?C???G???D??C??KD??c{{O?@$#91!66?_GE?!6@#170!11?C#230@!5?_#201!5?A#100!272?@?@$#165!66?G??_!4?_#192!13?O#99???A!4?A$#191!68?_-#76O!6?C???_GGC#132A#161_#100@?CG??A#101APG???CK??GH@K!4?g???@@!6?GA@!5?C!4?O?@#200d_#190C#100C#79?AA?@#81A#84O#191_#206O!4?G{IU@DA!4?__O??_!5?H#165?CA?ACC@!8?_G??A#45C???G!4?o??_??O#26AOG[c[siomJW?KQZGBABFEOsK??_!8?__?CA#33C#78_??A@!8?@!9?GG!5?`@_G@#91O!5?@#46O#36?AK#78C?C???B!9?O???AGCrI?I!7?A?@??oG??E??CC?c!7?A@@#54@#11??O#31?EA!9?K??E?@#59??OO?@@H??CC?gg#50O!4?A#18C#47_@???@@?G?C@#11G!8?G???O!5?_??WG#6_sS?S?Q@?C?@OWW???@??_O?_?_CD?c!7?@#12C!5?C?ICQ?G?CA??PDaAc?AI$#49G#97HB@JU??q?TO?AA`F_RbB_WC???G!8?__?_?@OQuEEAok\JCao?gO!5?G!9?AG#74?CKC??@#169_!5?@!7?AG!8?@#167!4?A#99?B?A@AB#212gGW??C#201?G#30_!4?O#43A@A?B!4?@Q?_GG#9_O_O??O?OO_OO??@#15!6?G#37???@#36@#47OO@?O!4?@??OA!7?g_VocAZGE@@OG?A?C??O??KA?@A@?A!4?GB?A!5?@?B@#3C#9A#26C?@#83GOK?G?K??CC???O#100_#94A??CC??C!8?_?C@!5?MA?MAWwW?aow~}N#16??O#20O#55?O@!7?C#94?CE?A?BA#36_!4?_??RO?A!5?GUO??I??AA_#15OC!7?O!6?C#9CO#7O?A?AG#0!4?_!5?O!7?O?@#3@?@AdG?A#17OG!5?O!5?@G??C!8?AA?@$#67F#96A!4?A??O?CQD`?O?_?O@???__Oo??AZG??W?OG@??K?W?_BAA?BD??C`ROG??u#83GC?_@!5?Bk`??O?E@#211OOPG???@!9?__w??OSO_#158!5?CG?C?@#211?CSK?C#95?O#21gG#41_?g[wOr!4?O??WIBPMA?@???A???@?@???W!7?G???k?a_??OCA?@#83?GCC#211_oGO#159O#54?O??A??C???_!9?O!5?A?G#81Ge!7?A#49@!9?K#56O!7?@A?O?O??C??@!5?AC???O???o#51??C!8?_?A@BPG???Ol[L`@_!4?_??@?AGOOO??@WE@?c!4?@!7?A#26??O#94@#35?A#19WG?B!4?@C?A?A#30C??O#31G#12???A?G@?G??EDA#9@#1_E!4?W??C??@@PM?g?GGCA#35C!4?GA__G#28G#23A@?_!9?GK??O#36`$#91_#166oG??_??@#79_??C#155?O??CG#83O#188C#165C!6?G?A!5?C!5?C#87?G?O#85O#83G#155O!5?G!9?G#168G!5?G?O!5?_OG#190C??OP?C?_O?Gc!4?A@@#201?G???A#206!14?_#200WGCC#19O#47OCoA_?C?gSEKG??@!4?@@???@#11G#22?_?__#28G_!5?@E!4?O#46?@#95??@IO_!7?OG??@?_A#55G#35GAH??O?_aC@!6?C???_??C#94_#55G?o?E?@??`!4?A!9?_!6?E@??`!5?E??@!7?_?A???W???O?A@C?OS?@EC#53?@#47!9?C_O#11C#34?@A#21??@#99?__?C#101B#34!7?G?C??Q!7?G@K??OCC??_#8O!5?_??A???@???CG#2?_o_??_ww[]gee{m_w}y^N]MmOyO{OQAD@@!7?@@@!4?oo_Oc{CK??g??G$#77?C#163C!5?G!7?G!5?@#167O_???CE?O!6?CE?o!7?@!7?KGG?C?@#90@?B!9?O__?@G#155C@#204@!6?G!7?O???O!5?O!5?__OwSs_gO?O??B?@#74G!7?H#49AA?@#30CCcc@??AbACC?CDIBcCUCdCGGhAaI?MGC?G!5?W_YGB#44@#74A!6?SC#56_#31@?C_@@O!6?@???@??W@#45?G#42?A#52C??g?ecs!4?A#35@??G#6ww#27G#45O?A#91C???_O?_!5?`#101?O#58C#47@???@!5?a???mW!5?G?O?Hp_?_C#53!15?@!4?o?Gk@?_???G@G?S#16??C#37?C#31GG@!4?_??_A?O?B#14ooWC?@C???CG??@??A?AB#13!4?@!4?A@#7!12?_!6?A!5?A!6?@???C#3C!4?G??A?A!4?C$#189??o_#80C?C???G??_G!5?_O!4?O_!6?__??_!6?__!4?O#49_#165?A???_!7?O??O#209?G_O#77??GOOGM#154?A#167_C?A!6?O#83O???A@!4?@#232CA#94!22?COA#158@#56@!4?@#78_#33??WK#7!5?_#29?G#35?@?@!7?__?wO???@OV?OgBaS!5?C#43_?G!9?A?G??CA?A?HEA???AM_!4?C??CD@???G?OH?@@?@#82GAG#73C!6?A#84_?a?k!7?E?G_!5?G_?BC#46@!5?G!4?FE???G!5?GK?D??_?_AA]JQayHWoN]A@!4?OO_?@?BJRBWGC_???_cC?`E`A_d#20_?C?@??@?B@#4O!7?OG_?AA?E@!5?A???@??EE@@#22??_#12!4?A#5?@!7?C#33?@A#73?C#34??_!5?@#1g!6?OO?C$#157???G??O?C??@???O?GC???_?@#190?@?A!5?A??A#163?@_?@#73???GK#76??_??O#159CC#189???A??Q!8?@A#192_#207!9?G#170I??_???O?C!7?E?C!7?hL[X?I??A@???@@@?C#46_#54G?C???G?A#48???A#164!37?@?O#94G@!4?O??@!5?@#41?C?_?_!5?@!5?A#15G#27@?C#28A#82!5?@#101!7?O_ow#94O#79G!6?_#77C?@#81_?O???O!4?OCA?gAO?_eO_!8?B??@g#19?@??_!6?_!9?K!6?A!9?__!8?a__#52@#82C?G#57GWW!4?OC#37??@@#17???AOA??@@#157_#35GC@#52_#13!37?O???TUGA??A?_GIoYd!5?@?_aP@#20@@$#156???A!7?A!5?O#92???GC#202_WEC??GW??CCA?@@?C#186GA#44!8?_O#141?@#169@??C?O?g#91?Q??BA!6?O???_#165!4?_?_?A???G#199G!4?OGK?C?O_gkCanOo__POgC?ABBBaaQB?B?@#35??E!6?`_#159!37?C#72_!6?_#77C#82GB#101C#155A#209Ci#168G#30!4?OO??GCW!9?eE#79O???_#163!11?_#84G??C#58cC#7_??DB@#74g#43O???C#47A#46@#57?G!5?G!8?A?GO???_???aYOO_??O!4?A!7?@?QB!6?@!4?__?QGA?KK?AOU#17???_#168!4?G?C#53O!6?G#18!4?G?IHG_!4?EK???@CC@#4!34?@!6?@#37?GCO#48!6?C@?@#6??a???C$#155???C#167O?_#159g!7?G???G#176?A#186A#168G#189CG!4?@!5?A!4?E#100!9?C#65G#157??O#202???c??@#95?_??C_#158g???H!9?A?e!6?_?A??A?A??A@#223C#80@#31!29?_!8?A@???CA??G??@?AC!4?W??_o?@Od_?eA?SG#209?B?@#45A?@o#73??@#189_#169G#200K#199A#201@C@#26!4?G???G#33?A??_!6?G#57A#98!16?OGC#99_#96O#42_!4?G#29A?@#98G!4?_!5?C?A???G?OG?GC#52?_???G#53!5?g???@#35_#41?C#56?G!28?K!5?_#158!9?O#238CA@#55?AC???_#5!10?_O!8?OG??`C@???O!9?@@@#29!14?_#32G??__O_SOGACoO@??ACAA@!7?_Oo$#187???O#169_#130@???@#66_#73G???C#90?A#192!5?@#206??AB#204@#158o?@!8?W??@?@!9?_QA#170?@?G#92??C#80@!8?C@?BID#201!5?G#200DC?@_!6?oG??G??_G??GCO#36!24?@#72CCGO?C#228!39?C@#165C!7?O#210O#212?O#228CB#28!8?o?C#83?_?G!6?_??O???O?A?@A!4?GC#54A!9?O#95??A??@?OO!6?@@@??A?A#53O#37???@C#28J#83_CO#36!7?_k?O???C?_!9?_?_??K?oCK#20!7?_??O!5?CW@#81!4?__#165A@#16?o!9?C?asIO?C?_@???E?B#48!36?_?c???O#38!10?G#0?O?W?W!4?GG?C$#154!5?GG#87A!4?@O#166!13?C#199?@#169__?@#73?O#157?OG!4?_@#206!15?A!9?Y#204OO#101!14?A!7?O#192_??C#187!5?A#42!31?O??@?C?_O#169!35?G#201GA#27?G#9?C#91??_#232???_#74!14?O??C!8?GSOG??CW!6?A#13!6?OC?_#48_#101???C?@#41A#51@#82??_G?C??_?W?o`?S??G!6?@A???A@#31?W#155!51?O#78OCA#10!16?CGG???@#37A#32CG#83!38?O#49G#42G!5?@#22!14?@#40?O$#158!6?@??AA#88?_#130!19?_#79OO!4?O!8?C!4?G#190!6?C#192@#186???O?G#159G!5?_#212!14?OGCA@!7?O?G???BP#84!29?_#232!43?A#29??O#56!24?CO?O!8?a??_??@?C???O#11!9?B#33O#100!5?A#36C#52!6?@O#54@#59!4?OC?_???O?A!7?@??E#201!53?I#207@#3!19?_!9?_o??@?GG?flW!4?@@?A#52!18?_#15?AG#36_?gC??O!5?@A$#162!7?@#101?G#159!22?C#170A@C??A?A#205!19?@#208q#84???Ac#163?CA#211?E#205@#209!16?_?A#84?_#159_#95A!7?AA#237!77?C#84!27?g#52@@?K#48@#26?O#158!33?@#90G#37!6?_#74I?G#96!5?G#99G??@@!7?@#2!80?OG#168??O#74G#49A#96!40?O#27??@!5?_A$#168!7?O#186?C#76!23?_#211!26?_#154!4?_#94??O#187C#189!24?D#98@G#155@@?@#159!109?O#55CA_P#49O#96!36?O#93!8?_#35!14?GOG#79@#6!82?_#223???_#238o#56G#8!44?A$#157!95?G#82@#91C#163C#37!113?OA__H!9?o??Oo?G!8?O?A#15!32?O#31C#73@O#50!82?A#169!5?O$#168!96?C#18!115?_#185!155?_$#191!96?O-#97C!7?c?_@???A?P!4?_?A?IOQ!8?P@??E?O@??FC???@???jQ?ggG?F!5?_#190C!9?B??A??G???@!6?A!4?C#201O#212CC???C??A??@I@???A#165_#41_??e?__ORQ]r@???C?O!5?COG!7?_QW?AHO?__??SC!4?_O?`@??@??C#81_#74GA!7?C@@?O!7?G!9?@??O#84OC!7?C!5?IB!6?G?O??@S?CCD???_?gE_C?C?@?G??o?AaG#55A??C?@?@?C!4?CB#19???@?GAG???GY#52?G??K#47AC?@#99!6?@#36O??C!5?C|__Y_CC???QOWI!4?G???C!6?KG#21GG??_G#4_BBN#3FJ???CC!9?KC?@!7?@@@WH???A#20B#48_!7?OG???@AK[GG[OWGC?B@@#192C$#76@??_?_o_!6?Wg?G!8?C#92C#199???C#158?@O?G!5?@!9?_Wg_#70??G#90O?_??O#100C???O?O!9?Co!6?_!7?W#95O?E#165H@??O?O#206__[K??O!6?_#155?O#47oWOe?Jf?@@?E@??AJE@?@C#33A#185O#29??AC!7?@#47???G[#28?GC?_??_!9?CE#33CG#82O#155o#78@!6?O??E@#36G#37G#33?@!8?@???C#58?__?_#82GO#78!5?_O_#99_#167O!4?G#82@#35A#33A?Q#48G!4?G#95O?O#78_??A#58?_?_#101A_?AA!9?O#43@??O#57?@!4?A@P???_???_!6?__!5?k??GWkWOC?HCAA!4?_#50?gCG!4?OG@#16?WC!7?WO!5?G??A#31_a_!5?C#11H#2oWowS||viOk[sQjsDNLBBE#27O?A?@??G#28O!5?@#34???G#49O#239_?Q?G#16__#13AFIO!4?AA@?_B?@???A$#65A???_OGC#49?@#156GA!6?@!4?C#130?A#167O?c!4?O??C_G!5?CG#163?G!4?A#168!6?A#167A!7?C?W!9?_!5?C!8?g!4?@?O!9?_#101!11?G#45_GG@???G??__?O!4?@#195O?G#188G#6??_??O#45_#23C#39A#31O?_IWs_?A?A?A?@H?@?@?AEEG#42Oo???E#95C!6?C#56A!7?_!9?C?_!4?ACA??E@GCAB@?@!4?A@!7?@??@!5?OWOO!7?_?AQ??G???_B_#31?@!6?C??B#46gOG?oO?_O???@?PA!4?B@AB?O???{?bcw{?@SOO?A???@yEA?_@??@E?B_G#8???G?AA#28C??@#46SSCc?B#6?C_??_A?G@@ABBL??W??O#26O#13GG?@B??@?AsgA??CgW#35?O#96_#229O??C#79_@#15@??OG#20O!4?_#49?G??GC#96C#246_oo$#91_O?A#73OEAAB!5?_OW#162_#169?K!6?_??Q??@!6?C???CG???G#76@#49@#65@#208!7?@#211@#83?O!5?o?_!4?CC?o??O??O_??GW?A#204GGJ???@!7?CA?G?PL^@]DMI]VHFD#200@#95C#78@!9?O#164@!5?_?G!9?_#157O#54!12?G#9!5?G#36__#55?O#54O???_#83_???@#200A@??C#90_#55H??C?P?o?_!6?Aaa??cCGM??@?C?wOA!5?_??_#155c#49?@@#43_?A#36??_#55O!7?_#37E#35@#99C?G?@C??AC??K#52?C??_???o!4?O_#51_!4?OAOGMgO?AOOG_???_@os?c??@ioCw?K?G??CKAGB!5?O?@!6?A!7?@A!7?__WY[#5???C#0!4?A!5?G?CIA#8_#9_?_#18O!6?A!4?_#8??AC#55!4?A#97@???G#28A#27C??C#29?@!4?AA#33C#37O#79__#159O#229GG#230G$#167O#80_gOC??XO!4?P?CEC??_o?A???B!8?O???A!8?A!8?o!5?__OO!4?w#79S@B__!5?__?_?O!9?_#74_#155g#94AA#209C#158!21?A#30?@???kC?G??_!8?@???CBA?O?CMcAC@?C?@V@N@W}?LaQ?H?G??G?H#45@#206??OG?A_O#157O#165A#43_!4?C?MGG!4?_!5?G@?@??@?_?_#41???_#158O???C#91G_!8?O???C#82_???YS@?G?O?SWGJX?__??L#59A#74G#157O#47ACCCGg?OQ_W?@?A#21!6?@?C?CG#37C?C?C??A#21!11?_O!8?A#14K!6?_G!6?O???_o_c_LdG#34C#15@#55OA???A#1!10?OgO_?_O@??A??@#32?I??ADC?G!8?OD?@!5?C?w?@M?AACC??BCA??A?@$#198G#157G!7?O?OCA??_?_O#202EA@OW!5?A?UAM#101@?G??@?o???C??C??CCVAC?A#92???_#84o#91C??G!8?OO@O??G#98?@B#169A#199A?@???C?@!6?G?OG_dNR`_O?m_ooo__OWIE@#72?OO#31O#43G#11?_#26_!5?OO!6?BCC@?CbBA@@@?P_!5?W??b?Eo[CICC???AH#72AO#211??_o[C?G#190G#170@#35?GG???@?A?OA!8?@?G#81!4?gB???`WG?A???c?C!6?O?a!6?g?hM??G??G_!4?@?c??@??O!6?@?C#28???A@#36!7?_O???OO?I???@@#81!10?@#82G#20A@@?A!4?@R`FA!6?_S?_???C?KA?P#10?OO!8?_UG#7!8?A@!5?_OO?G?E!6?@?A?@OH#163!6?C#235A??A#73O#43G#3??@???@@#2@@#35?_#12??@#63A#195_#166C#61A$#100?AC!6?G#185O#96c@?C???C_O?C?@???@!4?__O_!5?G_??@?@!4?_?OOlCCSUNG?O!7?A#90??@#163G!7?O?G!4?A#98???O??A#154?O#54!26?EC!8?GGC?EAE#42AC#7!4?o#35@!5?GC?CABb[?_o???OA?@??G?HE??O?C#168?G#158A#230_?CC#11???O!9?W?_#82@#73O#37GOOK!9?W!4?@#165G#197C#163C???O#169OOo#93C#54G?O???G@??C#100?A!7?@#94A?_o_?Q?GOA_!8?kE?G??_ooOCHbF?EFHdjF!4?oO???o?O?g@@qB#34???O?A#17@??__??GC!9?_oG???bN?WO?o#18G?I#94O#37A#47@AA#163@#82@#5!21?_#11?__#17CG?C??C?G!6?`KAE!7?`???_!6?@#163??O#202G#235G#226O#167C$#93?@#159A!6?_???C@!7?O!9?_?@?AA?A#168??A#165G!7?CO#93!11?B??@!8?A@#168C#157_?@?O#206???C@!6?A@#91_?CO#166C#83!26?G!7?AGC???G#95G!4?G!6?Gg#11!19?C#47OO_?`!4?J@!6?_?O?_ifa{?@Un@???@??GB???C!6?O#205!5?O#57_#100C!5?C#157O#72C#9@#7@#74_B?E??AAO!4?@#52A#157A@#155O#96??@?E??O?_#35!4?@O!9?G#50!15?A!4?A#18!15?@#19A!7?O!5?w?`!4?GOK_FOOB??A??_!8?O#4!24?@!6?_!4?O??C#209???G#245SG#82@#84@#154C#5???C#34A!5?O???_#203?O$#166?C#83OC??@!6?G???A!7?@?G#190!5?K?K#156@#187CC#157@CH?@A!6?A?B?K#98!8?G@D#72_#158AG?C!6?G?S??G??@???GD?`o!8?B__O#35!27?C#159O??B!8?_#11!5?GC!5?_#15!16?A#56??_#43O_#73??A#201???C#101@#212W?@A#41??_?O??A#54?O!7?_@@_?G?O_!4?A?A#212!4?G#79A#189G@!4?G#159G#77A#27G#26A#79CKL?EA?GGO_#53!12?O?@@#78@??D???_JAO?G_??A?CG#18!13?A@#56?@#59C!7?C?E!8?O?@@#10???C#55??_#34??C?@?@???AC#52!5?@#5C??@@#50?C#100?@#53_#16!27?O!5?_#5??ACAE_A#233!5?_#242C?C#156A#18???_#14_#58?o_#50?O#42??C$#163??@!7?E!7?O!6?C?_#58!7?_#202!9?_!4?G!7?@#95!6?AAE!9?C!5?@?C#80?O_#99CA!4?O!8?@@#155!29?G!8?G#43@#168!9?O#237!35?_?A#81???C#28O!8?C!8?A#79?A#209!14?G#74_#101W@!4?_#41?C#30_#29@#73??O#47_#96GG#98_B@!5?@C?@?O??CG!4?O_!4?O#37?E#83A#36_???C#17!16?C#34_C?@#37!18?A!8?G!6?gC#43@#11??_#41!11?O#158!4?@#81!27?_#15CC!4?C???_#246!8?G#166@#196A#208O#53!7?C#51O$#90???GA!7?_#166?A!4?@???@!5?@!8?A???CA??G!5?G#186!12?G#74_!4?_??a?G??G!7?_#211A???O?CCA@!5?GG?A???AA!6?D@GC#191!14?G!9?_#232!45?W?@#84??A#30!6?C@??O#83G?GC!8?C_?C!9?A!7?o!7?_?@!6?C#57!8?G?C#95?A???KC!5?O!5?GGC#82!17?G#58!21?A#47!9?@!6?@@#91!45?O???_#12A??O?C_WO#231!6?_#203_#65!8?G$#158???@@???G#130A#168?G#154G!5?G#187A#165@?A!5?G#155!16?C!9?@#163!10?G#155?G??G!7?@?A?CKG#189??CA?G#192CA??C#157CA#191??_#154!34?C#201A!8?O#228!45?B#94_?G#26!11?_#95F?QC#36OG!5?A#95!14?O#170A#98Q#94@!8?_#27!31?G#11G!9?A@#18!51?Q#52GA#57A?C#226!47?_#101G#159G#36A!9?@?_?H!7?O!6?__?_$#79!4?G!8?_#188?@!8?CG#90@#87!18?AA?A?A#82!17?@#165@???@#154???AA#187A#84G_!8?C?@!6?W#75!37?O#22_??_!6?BB!4?G@#7!52?_#90F?A#52?C???A??@HA?O_?CE?C??@#59???C#191?G#42!4?C#72!33?@#53!6?G@!5?G?CCO!7?@??_??Q?G?A?cA?G!4?C???Go!13?C#56C#205!51?_#197O#48G#34GO#6??C#243!12?W$#67!5?GC???@#87?Q#101???@?A???G#189G_o???C|ZG!6?WwO??O???OG?G!9?@#204!7?A@A#101A!8?G@?A?O!9?g!9?@#187!30?C#9?__!7?_oWGwC#72!55?O#101G#78_#48???O?OO?O!4?@G??@#59!55?C?A?O!4?_#210!101?O#201_#56O#37O$#155!5?@#195???C#158!10?GC#186?_#199!21?OO???o#206!18?CA@#81C#73G[G#191???C#170??E!4?@!4?_!6?@???_A?GGO!4?_#74!25?@#72?G?C#186?_#154!64?C#49!5?O!7?G?@#192!171?_$#170!21?@!7?g?_???A??o_?_??_?!4_?o#209!20?@#77_?o#159!14?O#200o?O!8?_??A#39!35?@#90_#165O_#199O$#58!21?G#83!23?@#192?O#190?O#159C#188!39?_#96!7?K#93!43?O-#97Wg??EBcMB???GgEOGAN!7?OC!4?@CD?A!5?IBC!7?SAEO?CdlC?@!9?O#54?_!7?C#76?G#81@#91??BA#93A#101@@??@#47_???CC!9?A!7?OG?@B#11C#7!4?CC!8?@#75??p[GGWwoO#29?B#35_!9?Ou!9?@?@#83K!7?AO!6?GW!4?O#52_#46??_#37?C!8?A#59A!7?A!9?O#32_#77?G!6?C#28?A#81?A???`!5?_??S?O?G_??W@G???O???C???A#51cC!4?@!4?_Wo??CD?w@CEHPIuiA?@AECOOWieFnD?cO??@!4?O???B??G?W#18@!9?GO#97_#79O#51??GKCABA@#4?_???_!6?@?O__SOhKG@B?@_?O_?O?O#6_#48@O!9?G_#159@!5?C#47C#5_?_#1O_C#36???AQC#43_#246A^^~$#166_!4?G#162G#85@#92G#67_???C!8?_?G!5?_??G_G#159O#165A#190?A@!5?G#206_#204_#165?AA?O_#90?AA??A??@?C!9?_?A!4?@!4?oO?CC!5?_#155A??G???B_!5?@!6?GC#30OIC???E?@?`?CA?O_??_@??O?C@??__GWOOAmiGc@C!5?eALe?A#33E?@#101C??__!5?A#165A??A#55ACG?_O?O?@@EHD@!4?@?@O?_??JE?CCKU?P??QA_?C#91_!4?@@#79G!8?_!7?B#47@__#98??Eo`!6?C_AO??@#57CW?_!4?G???O_??YOWbC??@OC@?@K@!5?H?C???AD???@#20C@!5?wC?@???_A?GNFCB@C!4?CK#94???A???O#2??oMFOLs?DCSuuaBAB#3@??CM!7?O#230AA!6?_O?G#96A@#239A!7?G#42O#14?OG#4E???@_O#48?`?G#157W#239_#243_$#91@#96CW?P?@o?A!6?C_???A?@?Ac?G_GggOWbK???OODKIA!6?ACPNK?AQ?^C#65kO`X??G_?AA#44O#45O#101?C#100CC?@#168!7?@???@#163_!4?@#154A?C!5?C#204@!5?oGGKF@#45A#26o??AEGE???KHT@ACA!6?C_!6?__gpO?OHE@!9?K#74O?GOG?CAGM?@C!6?_!6?@?OA_!7?A!7?C!6?@??G???Go!7?A???C???CA???@!4?_!9?@O#53A!7?O??I#165?EC#170!4?O#199?C#46?K!6?g??A??G!6?_??_?G_Oo??B[CA??O?A@BbcgkoOE!8?G#5BA#12G#101O#57!8?@#5???G?O#6AABbaBJG@D?D#15G!4?O?G!9?O?@?C#226O!4?A#97G#202A@#20?UC???A!4?@!6?C#34G#52O#162_$#134C#80@B?_???o?eJB?w!4?ZT???E?G#130O_!7?@!5?O#168O#176O#132C!8?G???G#73?O?GQ_??DA?I??s?GGG??OG_S_O???__?O#158OG!7?@!6?A@??@#211_#46!4?_#41okC?A!4@?`??A_??O!7?O???GG?FBMA#43!8?_??G!8?AA?@?B#84O?c!7?@#43_?G??C#81???C#27???_?GO#43I!4?_#81CHA!9?O?_?CB@#34A#30G???I??Cc???G#37A#59?O!5?_!6?O_???@??@#101?A@_!7?AGGG?`OGUI#53?AK?GAOAC??CA??C@???_#36!8?G?O!4?UucOgO???AO?@@?GC!6?K!5?O#1!10?_gGO?S?G#9??GG???CC#28???O??O??G#35G#41G#155@???_#12BA#83G???_#30_#208@#28O#34GCG!4?`B#3?_Oe#2EOA#37!4?A#83@#245D$#159A#90A!6?CC@!7?O#65!4?g?G#169@B!9?g`!8?O!4?_#80???@???_!6?Q`A?GGG?E???A!4?@CNG#92?G#169@#159?_??C?E?a!9?_???C#22!9?Ooowooo?IOo?KHBGY]K??__?CEFC#31?OC?CK@TfQwi@!4?@T?@#45o!4?O#91C??O_??_#47A!6?BO?FAJENE?HaqA!7?_O#58A!5?@!5?_#43@!7?@!5?g?c??O[#78?@!7?_CGC#35!5?C!9?G#79O#47_?@#59O#167??O!5?G#59??@Oc_!5?G_#99!5?_?o#19@#59!9?A???A#53oG?GB@!7?G#55?K#10_???WGAB?@!5?c??OG??C[FP?@#11_!4?_???GW#8_?A@p?CC???_???O#159G???O#37G!9?Q#49@#15@!4?OG!8?O#53?@#97C$#132?O???_#165O#83??G!5?CA#162C#101!6?@???CG?@!7?o?K!4?BH???C?_!4?O#87?G#67_??C#77?_!7?E??_W!6?A!5?o!5?G??G#190P???C#165O!5?WGE???O??@#9?gGK??GII?b?GAC?@?@!5?AA#6@?B#74!4?@#37!8?@@#41AfGGA??G#78?O!9?HS!5?C??A!4?oGG!9?{??K??C#94_#35?__???WC!7?C@A!7?O??_#82???O??A??@?WBA?MP?E!4?GG_!5?A???D?@A#50!8?@@#52A#21???O!4?C!5?@??__#11!6?O??_#34w#37G!5?G#17CG!6?_??O!4?O???G`Bj#3!12?C#0?GGWO#13!7?G#5O_?_GB??_??_??_#79H#27_!9?_#93C#50O#57?_#51G#82O#12A!8?@GOGG#55?O#163A$#73??_C!8?CB?G!5?wQS!6?OO???C#166_#211??G#163_@#157???G#99!5?O#157!4?ow#92??@#76?o?GGE_COCA#72C?@@P#98?A#95G#58_#84C!6?G?GA#98A#95A??A??O!4?A??O_#56G#100G#101@C#72!11?@#29!5?O!4?O???c#63??C#154@A@#54!19?G!8?G_??I#98_???O???C!9?_#93_#30!7?W?o#31?@#82O?GC??s#79@#34!4?_O#48GCA??G#47@??G!6?C#54o???OO#31@#84???_R??@??G!9?UE?OC??@G#37OG?O#158!4?A#36!15?A#101!9?_#47!11?g?_@#50??_GAgCa!9?E#48@#11!7?O#15C#7!21?_!4?S??a?QA#14?_#16__?O!8?CG?C@!7?G???@???C!9?G#56_$#76??C`G?A??O?C??@???_CiCL?_???OO???_?O#158!5?A???_??GC?GHH#83???@#91!4?A#79?@??GW`Pd!5?O@?G??bSG!4?O???C#54_O?S???A!5?O#93??C#39!19?O!6?s??O!6?G??[#9!17?_#11_O#28BC#52??__O???@#77A#82O!7?O?A@#42G#36G#9!9?M?A#36O#83C#52B_?@W!5?W??@?_C!6?@!9?J?g?_#96??Kg{??AA!6?G!4?O???WWK#77A#36cC#78K@#19!43?G#31K#56A#16???@#52@C#48?C#19?OWS!6?O??BCs!8?A`oG?GGWA#34!22?O#18G!5?_!4?A??AC!5?_!8?_A#8O#9G#7G#6GGC$#65???I#158?C#87???@#79G!8?_?@?A?C?G!5?@#49!30?AQ#74???C!4?G?A@_o!4?A!7?GG???@???A?A!6?C#6!20?C#45???__G???aI_!5?_O#22!19?OG#27!5?@@#81O???_?A#100_#158@???C#35??_oO!4?_OO??C??Gc#95G#57?SG#84??Ao#46!4?O#49A_???CC#82OGC#83?O#33_???S!7?A#93???A#58C#99?o?C!4?@???M??__??@@CO_!7?p_s[Q`E??@#14!45?@!5?__G?o?_??BFA_C#26!32?C#43@!8?G#13GC?@??@!8?B!5?E???__McC$#66???O#169?O#77!4?O#159O?O#157?B?O!9?_!4?C#162AA!9?_#83!26?O_???c!5?A??_oFcS?GCCAA@?`#65G#185O??C#170C!5?Ow?OGEA_#90!25?A#72C#47A@!7?@!8?G]u|OO!4?_pE#55??_G#56CC?D??@!7?C!9?OG!5?_???A@?GOx^?H@?A?H_?cBOA!6?A!5?F??GK??B!5?A?Oo!4?O?G@!6?@a?G?@#34!56?A???O@!8?C#36!37?O#55GG#192A?C#228@#11_#231AA#32O!5?@#17A!7?A??B??@@??@?O?@@$#163!11?_#185O!4?G#54!6?O#49O#158A#170?B!9?Q@???_??_CAGX#55!21?@@#159!5?@#157A#49oWG#201!9?_??C???`?OGG#187?@???_#168!33?@#79!34?A#95O???G?G#230_??C#99_#73??@#54C???G#26!7?`?c#53!4?@!8?A#36_??G#79?O?G#26!4?S?gs!4?H#83?@???A!6?@#95K#55??C?C!4?G???C_!5?@?_A?G??@!8?A?@!4?I#56!37?A!4?A!8?_#52!36?A#185CC#31G#168C!4?C??_#233_!5?C?@#80A#165??_#228_#32C!5?@!4?@A_$#187!12?_#100??_#168p@#92!7?_#189???D@?A!4?OC?K_!4?C??`#163!30?D#165A#207!12?OG#166G#45O#72GC!4?@??_#94GSA?AA!4?_#72!61?H#93??G#157???G#211@_O#155O#41!5?C#11!9?O#6F@#27!16?_?A?@!7?AA???AO#55?G???O#98?@#101??KK#57!5?GA@#52???A??I!5?@#43A#57!62?CA#16??O??o_???o!4?O#224!34?A#197@#95C#201@?C#74G#81@#73?C#14??@#36?A!6?C??OC#101G#81C#168G#0!5?OA$#166!25?@#199???AA!7?GC!8?PO#186!44?@#165O#189!5?_#199g?owMFA???__@HtVB?A#167!66?O#232GQH#169G#72!5?@#37!31?A#15!6?A#22cO#36@!8?C@#94!6?OWo!4?@??@!6?A!9?_O???aCM``@E?_??_K@?po?go@Oo]]KWZNEFPPW???B#31!15?@#21_!8?G!5?G??PQHdsseb@#233!25?A#210@#207A@#235?C#236C#156?A#203??O#210?_OOK#74O#241!7?O#163_$#202!30?CA!8?A?_??O#78!57?@#169???G#206@!5?O#238!73?OG#201_#206@#7!45?O#41@!4?@#97!12?A#83!11?_#58??c_#158??A#59!66?C#32!11?_#2!41?O#232A#158!9?G?C#167O#188G#242!7?_#203O$#167!31?C!5?D!6?@@OACAc`???_#200!47?o#192!84?C#237CA#11!46?G#9CG#45_G?O#35!164?A?@?O!9?G$#68!32?O#233!155?_#212@#65!51?O#90@???_#55!161?C!6?_??G$#80!241?_#209!166?_???C$#205!408?G#186_-#96EG@A_D@D_?_?OQ@??FpK@???O?O?@???@AA!4?OO_o@?_??___??S__oE]BD?O??_GEBI_!8?_#74??A@@???@!4?A?@?@@O???_?O??_O#72O??G!5?C#22__?|ZLFz\w{]^??skIL`Cu}eB@B#30?G}UBFqQPe???@!4?K?@C?ECE#35@B?@!9?_???_O??B!4?_??W??W_C!5?_!8?Ca?OC??O#81D#77_!6?c#78A!6?_?A?C!7?C#55F??@#170O!4?G#94O??GOOHAC!4?@?OWKAB@?OOoTeDSBC?oI@??B#21O???G??F!7?G#53?WG??I@??GG#21GO?Q?O#17@??EC?GGK#18??K_o#4O?C#46?W?C!4?C#54_#4}@O_?@Y@B[GC[j]NoOO?GCF@{C!8?O#42@#210A??@#55W?@#14_#17aAC#12OG!6?K#32_#18oP?@?_O!5?A??O$#73g!8?A?O_!6?`WtOsCA!8?@#190Co?O??@#91GEHG#81_#101C!4?G?BA@!6?_O#87@O#73?_?QE@TLCk?XK?G@!5?AA!4?G#93A#47W?O?__!7?_?O_???G?A#6???_??A#7!9?A#24Poo!4?X#41CA__A@_wgGKCH@E@???@???G???A@E#56?gCA!5?@!7?G@???@?A???__A!5?AO??G_O??G?M!5?@?H?B#7O#95@?Q??O?@@??G!5?@C!4?A#96_???C?C??E!4?A#57_?@??O!9?C??O?@@?H???`??@oG?GA?O#20??gGGKqroJC@???`??_o_??_FK?KRLIA@???E#11?O?Oo?KMK@@?@!6?W?GgWwW@???CO@!6?B!6?A#73O!9?G#163C#13_??G???WK#4_?Co_G#14G@AA?_O_!5?C#35_??CG$#65O!8?@!8?A?CGFBA#97G??OIMBA_?@!4?cG??O!6?oog[SM@`G??kMmUC#65?GP@!4?o?SE?A@#90G??@!8?A?@#84C!9?@#159O#167CC!4?C#29_!4?O!4?GC?@@?_c_H!5?@!4?C?C?@#47?O??G?}WI??K?A@_O?_?GG_g!9?__?o!4?_!4?COG?_AW[b??_PG_??@???_O??_?D_?GO?o?@#168C!5?C!6?C??O#81_!7?A??O?@O!6?_???O#46???G#53_OO!5?C_H@?CG??A?A!5?HCdWC??E#46O!5?oZA_WB?OQ?KCA???AE@#14__?wC?IO?v@NL?IB#34!4?a#21sW]NNFB@#2?o@BEe?S[@p__C_oH_?_ogW[?_#30G!5?_??_#100G#197C#208A#191A!7?A#3O?G?G#36@!5?A??CC!6?G#56G#74C$#101@#76o?_AA??O[Lg!9?A!4?MH#202!4_G??_?I_A!6?O!5?B#169A#132?G#80?_!4?B?@GB_O??_w__@!4?o_SO?AA#27O???_#52C#77C??A!9?_!4?G#41O??O?KL?B@??__#6!10?ADA]wG#45??G_?[p#31?GC?C?AO?@#94S#74_!7?oWo_?OOO?@!4?A?O!7?C?G??A!9?_#30@#78A?BK!7?W!5?O#83eA#57A#84A#42??_#29O#163G!4?A#83GCo??B??A!8?@#37?@#77@#169o!5?o#165C#78??A???__ECA?G#59?_?O??IeaII!4?c?@FoAcE_?___@?@#19_OKCH???A!4?@!8?w_KAoCA_oM@?___!5?O{D?@@O?G?A@#1AEC??_g#0__#5!7?HKKC??_@?@!7?G#83G#207@#232A#231@#32G???GKL#5_??W??C_qOG!5?@__?G#13???A#186@#157A$#97?@?D?_go!5?@#130A#202u[#159G#49C!4?G#90G??AAC#170O?O??G!4?A!6?@C???C#165??@#70?G!9?@#61C#67C#58?@??O!4?@#77??_#83ICC@!8?C#165?G??O#169O#55_#170?CB??GAB??A#45_??__RG#9?W?C#39O!8?O!5?@?@?_KGA#78!10?__#43!4ALA@!4?@??CCA#101G?oBF_?@M!4?C#30_#73?_#83?_#91O#82_#53?C??C!6?_O#81K???A_oF#31???G?A@#72??_!6?_#155O?G?o!9?G#84__#59??O?O!6?KD!5?@#95C?CAKA!4?A#98?@#167C#51?G?O?K_?aOWHWWgG??RoOG?BMuOC!6?_KCccOC???IteVO!6?@#10o?@`O?A???@?_OiA??_!4?GC?C!5?A!4?AO@?EEA#3O???A???_!5?W#33C!8?@!4?C#7_O??O#46!4?I!6?A???_#97@#230?@$#132?CA#189G!8?@?G?A#169_#66GA#67_?g?@C#80@?K#162@#157@C#169cGO??@?C#90G?Ag#98A?AA#211?AA#71!6?O#68?C#76AB!5?W?_KOAG???`?O!7?A#33O!6?_?_#157A?C!8?G#42_#44_#165CCC?A#93?OG#40!24?O#90O#75O#56!13?GW#33@???A#11A?@#78?O#55_#31??B#84G?A!8?G!4?A?o???@#34OA#28A#54?A!6?_#34?_@!5?_???@#41_!4?_!4?_?AO?@!4?A#101G!5?OINN!8?G??OEA?C!4?C!7?@CA#20?Og#21???C#98!7?A?CG#58!4?DGP??A#17?A@GC???P??A#57GK?A@?@#36!7?C#16?GX!5?OABO?@@#36???A!5?O#6!5?@!4?AA#15!7?@!6?@?C?@?A?D`!7?O??O#6?_!4?O#10A?A@D?@!4?O#52@??@$#161?A#80_?D??IE?A?C#158GO#186@#176@#63??OA#89???_#83@#156_#165_?O#189?O??gOE#204?A!8?OX@CF#162!7?O#155G#85C?@#79??_!4?GC?A?@!4?O??I?C@#45G??GG#56G#43@G#186??C#158C???OG?@!4?C#154?_#26?_???_?GFA??O?aEA`?ZE!5?A!6?@!5?@`_#55!4?C#72C!5?_#45GCG#95!5?o???oOOSE@#55O??_G?WG??A?GA@??D@!7?@?A@!4?D?A???G#82?C?G#195??A#159F#154D#45_!6?C#158O?O???O?O#94???@#57@#58_?O!9?O#74?@#47@!4?O?PA!4?_#19_#50O!11?O#96!7?A???@#36_O_!5?O?B?CB#14_#48O?O???__#5!10?_!8?a#20???GA#94_??O#189_#7!7?C!4?J#1!6?_??O#9_??A?B!4?C#164??C#229C#79??C#48_a#187!5?@#233@#236@#8_??CC_C??O_!6?gG?C$#130??O#165O#163W!6?@#167A!4?O#163!7?o#79?S#158???G#101?OC#164A#199GsL!9?A[H??@#176!5?_#168_#158O#156?O#49!9?A?AQI_AKE???GGK#95?CG!8?G!5?GA_B!4?O??C@#81!42?O#15@#84O#54K!9?G#99???o?@C#82G?G#163??G#209C#43_!4?ECJ!4?OO!6?A!4?I!6?APO!8?GG!9?_?C???@#98C?G?_oGkC!5?A??G???AC?C#56_???@o?_?W#14!31?@#16??AC#50?_#10!4?_#59?C??G#50O??@@#8!15?C#158!9?_#51O#201_#16!22?A!5?O???@!4?O_!5?CO!7?AA?@?OG_?KOG???C?CC$#166??G#157??G!8?_#204G#206_#68!16?@#167??@??_!6?@???OG#185!8?O#159_#54!13?G!4?@???o???C#72@@!5?CQ#187G#168@#200@??D!8?@???@#157!44?_#201_#187_#155_O#77O!5?O#91!4?C#83@???@#78AG???GG?C!5?O#31??C!5?O#83W#91C#73C#36??O#28O#84C??@?@#74???G???O@???EGG#90?G!5?@#47J?A#170G#82_?@!6?OI?__GG??A@???g?O?I?PG?A??ccI_???C#37!38?@#199!34?_#14!23?@#8B???Wc#168G???O#18B!4?_O???_?P_#41!4?A#2K#84@#20!5?C!9?A$#186??C#168??O#85A?@_#68O#155A#83G#70_#165C#166!18?C#99!5?G#209@#84CC???C_#166!13?G#55!21?_#35?O???O??Oo_O_?_#190A#199AJ^IB!4?@?BA@B#90G#30OO??OE!4?_!7?H#188!28?O#186O#80_#26CC@A@#94!7?C?G_#81C??O!7?O?O#98_#95@#36??gH#9!4?CA#95G#42!5?_#58G?K?C#53???O#54G!7?_#30???D#26fB#190O!7?G#169C#187G#53A!8?O!5?W#43_#167GC#55!4?A!4?g?C@#7!109?A!6?CG_#49!6?C#27G#226!8?@#186@#17!7?G!7?A$#88!6?C#79?G??C#90?C#206!25?@#100?A??CA#189?G#84!35?C#26?_O?_#28_??_#101@#189!7?C#54??__GE#204!7?A#29!50?G#22?A#98!9?WI#90???_#201??@#237A#37O!9?_!6?C!8?O!6?_?C#26O#11A@!4?O?O#33???G#191?g#167C!6?O#206O#73??A#99DO?f!7?_???@@j@??G???@!5?A?@B@!8?a_??C??C#35!91?_!5?A???G???AA??B#19!12?C#31AC??C!5?_$#169!6?O#89!34?O#155@#87O#92_#157@G#11!39?__#154?G#30_???O#82!9?O#155C?OC#206!6?@#155!62?_#57C??G#232???E#238@#190C#52O???@?CCGC@??@?_???OGD?AC??G???_?@!5?G??C#165!8?G???_#52@???_?@!6?OG!4?_!8?_?_#54???@#31G#18C#226!113?G__#155G!9?@!7?A#11!11?B???oO?O??__$#93!45?C#207!44?C?@#78!13?C???G?G?W??A??C#211!67?A#189CA#155@#33!9?C#11!4?A@#48!7?O#82C#79AGA!6?_#59??C#100!11?E!6?_#56!9?G?C#80A#79E#84?B!6?W#35!9?G#13!113?B#28AA@?A??OO_O???C!5?C!11?G#33A!8?A$#168!45?O#31!44?O#163CA?C!5?G#75!5?O#91G#230!80?@#205A#45O!5?G#15!9?@#98!8?C#96@#57?O#27!6?CA#35!14?_G?@#74C#46?@#210G#48!11?A#157_#34???_#158?o#45!127?C#101C#230O!6?@#84??A#37S#34!20?O??C#37A@???O$#159!90?A#41O!7?_#206!89?@#199@#158A#27!14?G#101!9?A#30!8?G#36!15?O#27O#189??_#54?A!12?G#159!134?O#223G#54C!4?A#47!27?A@!4?OO$#162!91?@#91!150?A#200??O#166?C#224!148?O#198G#195_!8?@!7?A#15!14?GG!4?G$#201!91?A#26!307?O#27!30?@#55A?@??@$#9!432?G?O$#26!433?_-#73A!7?_w?A`?W??O?o_??`#83_!7?G!7?C#155C#166C???A!7?@@!6?@@@?@???A#80@?@!6?O#74@@@#27CA??I??oDaIG???@#74!6?@@#47@?@@#77C#29???DA???C_@OWS?A@AAF?O?G!5?cIAC!6?C#11AC@#35G_!7?O_#42O?G#28?A#45D#94??GO?C?@B@#30O??C???_!9?O!7?C#57@#36??_O???A#30???O#11A!5?O!6?_#27O_!4?_#73?O#78A#56O?A@CC?_!6?C?CF#35C!6?_?g???CC@#16C!5?C#21?@???_?OO?_!8?__??o@?C#19?CEP???KA?@?_?OC?G?w?weA??O?gaZwGCA???C!5?@!8?C?QAOA??GG??O#3!4?C#15G???CC?AO!9?A@!6?A!4?C?O#37?_?C!8?C!7?W$#76lv!6?BCg@E?c?W_o??AB#87A#158O!9?A??C#239_??GOw?k[_[KcOomeIaoO#202?@@!7?E#96C?@?@#65A?BG#35GG???_?O!7?CcA??A???A#39!4?A#22CG_?_?@?oKwJB?_???HL!5?GG?FED@oGC?BB#42C#47O!9?`?OA???GKGIf_pOfP??_?_??GAB!4?@!8?G??A!5?@?G@@!6?@?A@??D@AAAa?_??L??OaO?@#13O#91?G#158@#52GO???A??A???gGO#36_G!8?GO?@???GAOA_?_?A#98!5?C#99A?BB#50!14?A!8?@!9?A#48A?B#18???_???G?C!8?B!7?G#8O#1?OW#14_wO!9?OG!5?_?cW__#7???B!6?@???A!9?@A??G#14?A!5?GCC#53OOo!5?O#56G#7@$#80O?B!4?cKAO_?@AG??G?O#101?G!6?_OOc??G#235_#245_OO#231G#242O#236_???_G???GGO??WGG#229???Y]MWY{go?_#201O?O#198o___#43C?O!4?GO_o!7?A#41CC???OEP???AC?GC?_G!4?G?I?@???_W??E??_???_!7?FhGD!5?PA!6?_?__OQC#95???ELM?BA#35?O?GdGA?A@Q!6?@HBK???A??G!5?@!4?@Og@?Q?C#41cGCC?_??@??@C???G#79???G_???G?O#82E??C??A!8?@@#52E!8?_#53G?@???o[?O!6?A!4?A?OAGC?@#20?BQHA@??A!7?O?H??GCHBH[A_?_#51???C#31SGCCE???O!4?G?C!6?AC[G??A??C!5?_#9?CG!5?H#2C#35C??o!4?A@!7?G#6A???A#4A@@#8@@#34A?@?C?@@E@?WGoSA__$#67?G#96_@??CO!8?C?AH@?oC!9?BC?O#49?CC!4A?@#79?@#96!4@#235C!6?S???_#230Oc_#162@!8?G#191O??_#45A#33OSA_cK??OE???A??A!5?@??A#45?@AC?A!7?oC?@?_???_?WOC_!4?CC??G#65G#72G#92G#54G!9?S!5?_??A!5?G_!8?C??_#84O!8?@#37oAA??O??_!4?OOP@!4?A??C?C???G#45?O#95G??S#30_G!7?E#48!4?WG??_!6?O!4?@#47O!4?_??A??AA???C???A?O#17!5?_#20_#36!18?@?hGAA@AA@!5?@A!6?O?C!9?_?OA#2??_?_O!4?OG!9?`@#8?O!6?GBa?a@B@C#83??G!4?_#156O#97C???OO#16A???@!7?G?A!4?@@#43O#94_#50_#55G#52O!5?CC$#77??K!5?O?C#156O#67?O!4?@??`#85C#165?G!6?G!4?G#157?G!6?A#208???A#233GE??WsC?cik_c#232?_#163@!6?A#159GC#83C#197O??_#77@#54C#41?A?O#11@?@!9?W???C??_???G?O#78@#39!9?ACA!4?C?C???C?gG??H?pT#83O#90O#162O#74_#30ETAB`w[C#56?G!5?W#33?O!5?A#101???A@@#52O?o!6?AO???C???A!4?M??@???E?A?_??E??_#16??O??_#26_O!7?AhC#74G_!4?@?__???@#50A!6?_?G#18_?O!9?C!4?w!5?C#34!30?O?@C??CC#3OC#21!4?@???A!6?O!8?G#54???G#26C??O#78C!7?@#21G??CGG#7??@#18?_GOC!8?Woo#52?OO#186G???C#245!5_#17C!5?G#48O???__OO!9?G#11??@@#47C?G$#92??O#97E??g!4?CW??A?CC??S?W@_!6?O!4?GA@!4?A#234!5?A#132?@#163@#171??@#231EA?Q]???A#208C?CG_#168AC#223__#188G#63C#47C#42C??GMGW??_??_#32G?D#76_#15@#9???O!4?_???K?_?o?E???oFC?@#90!4?_!6?@#24OR#63O!4?A#87?_#154_#95_#164_#33?O#27_#28G_O#9A#43_oG#74?OiGC_?@@D@???_OK!4?G!8?G!6?_C@!4?@D#49O?sS?O?gAG???GCC_??G#72?A!4?_???A!7?@#58!5?C???CCGw?W?A#158?@!7?C#54OC#43GC#34A??_!5?_?G_#18!29?_?G#14w!5?@?C?_?O!7?A?@@???@@!5?B#0?_#41G#47A!4?A?A#43A#17CG#35C??A#20C?KC#47A#34??C!8?OO!4?_#163@!6?O#33G#159O#18@???BA!9?G?@??AQ?IE??Cc_#28G#4G?@$#157???G??A#90@!7?C#202@!8?G?`@GM@!5?O@!9?A#176!8?@#169@@#222!7?A#203A?G#186G#161G#187C#73A@A#91G#49AAE?__@#30@AA?G!5?Ooc???wo_?KCc_!4?GX]qD@!4?OKE?O?WCW?@#93A#6???@A#26??O???CB??AoWK@A@#77??_#72C!7?C??GG#82???_#56?O???C!4?_c?G??_G?G!6?A#54G???o!5?G???OO#28??Gb_#46OG#78@@#155?O?O@#9?OE@#77C#101@#28o?C#49!6?s?W#53@P??_??C_#34?Ow#30G!8?O??C#46OO?AGE?GoPF???_???__?_!7?OOokGwkguO?KC!4?B!6?KCC?CC!4?_!5?_oWW?K!7?@???A!9?C??C#36??_!9?G#196!5?G!4?_#30@#13C?GKACC???CM?C@T?EC#36GGBmK?_!4?G???__$#168???O@#155CO!7?@#190@#167A!9?_!5?@#80W_O!5?@?@#240G??O?OO?A#157!15?@#156@#97@C?A#195G#79?@#66?@#76@#155?_#29???C!8?O#75!16?A?@!8?W!5?_!6?B??ooGA#31!14?AE#81?C#165@#55C!4?A#30?O?K#53!7?_#43?o?_G?A?C@??cdW??O?C??@__?G@?@?MC!7?AG!8?@!9?!4ApAgBB#81?A@#96???@?@?B@#32!4?_#31CC!4?O???@?G@?_???O?G#16!30?__?OO!4?Gg?OO?_!5?WC??C@?@!7?G!7?G!6?@!8?_O?k__!7?a???A#198A?C#43o???@#164O#8?A@#73O!7?G#49G??_#20?oOGO_???G$#169???_A#166G#159@#85G?@A#162G#79?e?o_#163@???G??A!7?A??C#196???G???C#241oO?C__?_#210!16?O#224?OO#226_#196O#185!4?O#100O#48!5?G#9?@#13CC??OO#159!18?A#72!15?O??_?G#40!5?G#90!19?G#95?O?LB#81!13?G#55?GG?@!8?AAs?_K!6?F??A?_!7?pG?@A!6?C#93?A#159O?A#42???G?GG_!4?C#55???A??Ic!5?GC#9???@!9?c#19??_#56??P?C#10!34?O!9?c?__??o???@@JEA!6?__`??@BA!8?O!5?_oOOo!6?@?OOO#27C!7?_#19_#91G#74G#79O?G#42G!9?O?G#96_#33O#28!4?C#46_s!8?_??BBCO$#189!4?GQ#83?A#58??@!6?G#132?E#130A#164???C#189UA!8?BFA!9?A#44!40?O#28B??A?@??@@?@A?@??A#23!23?@!5?C???A#94!18?A#159?_O#168A#83CA!9?`???CC?A!4?G!7?E#36??OC?_#33???C!7?E!9?O!4?_!6?C!4?C#35G??_C!4?G_#99???@A@#37O??O??A!7?_???_#59!5?PAA!5?GK?GGG?H@@AO??BA?DB#17!11?K???A??BI???O!4?@_???OW?AAA#9!7?O#94C#95CA??A#201??@#162?@#41A!7?A#28!12?G??B?_#205?C#47O#201A#90A#54GI#72A#28!4?G@#58O??O???O#79_#19!8?O!8?O$#163!4?C#167_#65!7?G#159???A??G#169!4?@!8?@#154_#243???_o_G#56?@?@#26!43?K@!4?HW_gwCHKGAwWOoOG?O_??GA?o???__JE?A?@#95_#44__!5?O?@#154!16?@#155?@#78GO?C!4?@???AO?O???C!6?O!5?@!8?W#48?OG_??_?K?C?wC?GC#9?C#31???O??G#163@#201GF#54_O???O!5?C??C@???O#59???O_??A?A!7?A#53A#81@#55@??@@??@#51??O???gMBABPT@?[QOop?FKK@??GRECO!8?@#31G???O#53!4?@A#11!6?WC???c???@B@OwsE?P`_O??C!5?o``@!4?_HF?AZ]R#13!4?C??G#229!4?@#203@#95@#11A#169?O#239!4?_#32GHG#23C#229_#5@?A#17!10?@?G#81_$#190!4?_#186@#168!14?C#199!5?@MEV@E!5?@#35?A#76??@#229?C#31!45?B!5?C???E@A?O#73!57?A#99!15?A#37!4?_?O#31B#41@?G#45OG#73?@#57A#81PA#34??OC???_!9?A#74@@!7?GC@#191!4?@#187G#90G#7???A#100??@#37C???O#32!4?O#57!5?G#84C??@!9?C#11???_?G???G#94??@!6?@?I?EC??KCKMwpo?@A#11!12?_???C?C#58!5?@#90!22?G#28?G#30G!7?A#12_#37?G#26???A???A?A#231!16?E#191?C#26@#235!7?_#231___#203?_#3@#171_#35!11?_!4?A??A!4?A$#202!4?O#190!21?[?G#211??_#90?_W!5?@#7!51?_#23G#98!68?@#42!21?o!4?_#33C#26@G#82??G#18???_#31_O!6?A#56!5?oE!9?G#199!5?E#29!9?O#14G#36!7?_#98!5?C#101A!4?@!4?`EA#15!4?W???A#57!4?GC@???_s!6?C?I?`???kK#4!14?_O???B#45!31?C#6_#4___?C!7?@??@???@@!5?@@A!4?G@!8?CC#14???A#12C@C??C@?GE#31!8?O??@?B?C??@A$#204!27?OO#92!4?C#16!58?G#163!69?_#49!21?C?O#28??CA#16!9?G#41!5?A#32G#79!7?G??_#15???_#155!42?_???_#28!5?AO??C#1!42?_O#28_#27O#169!33?A#5O?Gc_K!8?_!8?CG@A!5?C#100!6?C#52!9?OO???O#30!12?A!5?A$#170!28?_?_#27!154?G!6?O!5?_#58!16?o#26!49?II#83G??G#4!4?O#5!47?G?I#199!34?A?@@?@#36!44?G#202??_$#8!266?@!8?G#2!47?_G#37_#83!34?C#200@$#95!266?O#74C#98GO#101!90?@$#170!266?_#91A#73O#99@$#93!267?O#159_#157G$#165!268?@-#80JI!5?A]S__oWHWCG??s???__!7?@@???_#241G?A??A?@??AB#74_!9?C??C!4?G#226A?LD??CCG???oo!4_#72@?C??@???_!8?GA#45??O??A!8?BG!4?E???S?AEU?O???@A???G_!7?_O?GO!5?Ag??C#56?A!6?C@!8?O!4?A@#16_A??@!9?_#58?C?@#30?__???H??@?o??O???@?A@!4?OC#101G??__#48A?A!6?A#8O!6?O!8?@!9?C???o#18cO?@@#52_g#82!4?B@#19!4?OGGAA?C?UD?_#50_#52O!8?G#17C?AA@C_`AE#11?o!6?E@A??@!5?@!8?_@!8?G?C!4?OO?OO#19GG#17?A!6?A!4?K!4?O??O!6?G??C!7?OA?O#54_#13@#46_OOo$#87C?C???A??_???_!8?__#67?OOG#97@@CA@_!6?O#239C???@?AA??@BBA@@@#32G???O#188A#189A#191A#197A!8?O???@!9?G#33A#47C??@#76C#26G@BZMj~}q?_pdF?CB?o[wop??O_B#95G!4?AG?A_@!4?C!5?D?A#43F!5?_@?C{o!6?@?C!9?G?A??c???GC??@#96C#18_#28o??O?@G@c?D#54_O??GG?A!4?A!6?O!5?@??C!5?C???@?BCC???C#42@#53?C#52A?GC!4?D?A?A!8?A???G#2_MI#26O#56G!6?OK!5?A?C?A#20??GD??@@?@@#14o?AAA!9?@B@Pqw?A??_C@@@#16?@#0!7?GKNc_#3G?@?O@?_#8???_!6?G!9?_?AOO??_G@??C??O#202@?@?A!4?C!5?A#52O?O??O!7?@!6?AA$#97o?G@@?_#77?@?@#73G@A?@??F@??GA?ECC#66_O#163@#88G#159A!4?_!9?C?C!4?CC#65_#231A!6?@@?@?@#229@K#157O!8?@#49A@@@?B!6?O#154_#11@#22G?C@O?@?@!4?C!8?`O#164A!4?A!4?A!8?_#168O?oO#201o_#78O#30WDCK?GC?E#54???G_!5?C@O??@@_G?Ebg?BWHgW?OO!5?C#27_??oG?O?Q#49_W_C#79AE??G?_#18?OG#26O!4?I!9?A@!7?G#155_#79I??@O???COA#50G#46C#34O???B@?G!5?_!6?@?@!6?@!4?@C#53OAA_OID?GKW#16???O!4?AS??E@@!6?o!4?COC#4G!9?CKM?_CdCAAB!5?WWXJG?AC[OO?OpsVCC!5?k???G?@!7?CW?_??_#49A#235?@?A!4?C?O#156_#35O!8?A!7?_?A?_$#76?@??EAC??JMRMDEE@FgMGyO[MGIOK#169??@#74o#83S!6?_#158G!9?C#28O!5?O!8?GG_#80O#100O#210A?A#207@!5?G#74@#42CA???A@@@#45GG!4?o#29??C???_#47CG!4?oC#31G#75!5?C!4?@??@???g^{XHnjbC??@#100G#77A#186O#41??a_O?BPo@G??_!7?W#42@#90??G#52???EO!4?@!4?@???AOG?A#30C??CAG?C#18A#42EA#55A@?DA_?OO??@?@#72_!8?_!4?G??G@#37@#33C!8?@#36@!4?_???A?A#43?@!7?C??C@#55o!7?O!5?_g_??G?@#94??O@#36!4?_A@??G#21A#34_??G!5?EG??C#36CG#19?C???QSXPG!6?@@#6!9?@A#7@#5?ECCN?AB_?O???_YAEKYC?O?g@O`_@@??SCA?CGO?_?_???_#97CC???O_??C#55A_?_??GG#56?C#53SA#32??O@$#156?O???O#79@@!8?A???BC??@@?A!4?GG!5?_??_O#58O#73O#163C???C?CC#45_??_!8?C#26__#43O!4?_!8?A!8?A?G#95?_#75!9?A#11???@!9?_#93@#154??CC!5?C!9?CK?A?@@#35?OW?WsGI?B#74??A??BA??A?_?@?A??@!8?C?c#35?D!6?A@?AC!5?K?O_O!5?GC!4?A!5?AAA@!9?a#27A!6?@#82O#81G?_#15???_#56A???Wg!9?C#59_o??_!9?@!6?C?_owsa?@#11!4?_?GM???G#15?CC??G!4?G!4?_#21???GG?G#2??Gooo?GWwOwocOOORYoac??OoSo?KCELMHG!8?_#30?C#12@!7?BK!4?o???_?G?_GG__#233G#188?O#36_?G?@?_!5?__a?L???O@$#83?CAE#96GDGG!8?O_?_??A?O?_?QeyS!9?oO?GG#54G#48O?G?G!4?G#96CCC#208A!9?GB#223?A@EA@CKG???O??O_O?_#164O#100O#9ABAEG?O???H???O?A@???_??GW?_#47??O?OG#201??C#39O!9?@#187_?_#28!4?G#26B#27@_#47???O_BFC!5?CPaMHE???C!4?O?_?O#81?A!6?B#15?G!6?O#52???@??O?C???CA#15O?O??C#42O!5?GO!4?C???_?@#13@#56?@#95O#98GO#58GO??AL@@#7??_W!6?oO#101D?O?_#15BA@!8?A@#19OGC??A#48??@#10!13?O_O?_#28!6?GO?KG#37DI?G??G#1!12?G?oo??G?KWcb_GCCCA___?GG??G#15_!6?_O!8?G?AA?@???A!5?GOO#76A?C#243@??@B@??A#37_c??GA??@D!4?O_COOGIGG$#166?_O#154G#186O_#70!4?O!5?_#68??O??@!4?_???_#100C#91AA#243[K^FBF@BA@AA@@@#30OO???O???OOO???wG???__#41?_#185O???_!4?G#30C!4?A??@A??C?C?_???CG@?IG?G?@CAEJEAGGSca__?wO!6?_#117OCO#165O?O??GM_#31???a#33F?ACG#94???@O_???G!7?W#49?G???G?E?CA??AGC#82O#13???O#26GgB#31??CAG!7?_#81O?K#13???GCG??C#74?EB!9?CO#55?O!4?C_@??GG???woXD@B???OE#9_CG#11_#78O!9?C#11_??O@??o??G#43O#5!17?O?oO!7?h!6?A!5?C@!6?OM#10!20?A??@?ABHA?A?`??A?BEBF?_#43CC#31GC?C#34G#3?O?o?_#48@#37?AA?A#28_O#19O#231A#236A#242@???@#203H#82G!9?G#59w#57?@#42???A$#90??@???Oo_#67??C#92??O_#58!7?@#90??@#101@#197!6?G@#185_!8?C#42??o_G!7?__#79C??C#230??@!6?G#228K!5?_#83AC#91?G?C#159C#93C#92C#162G#35A?C#185_#6!16?A#90!8?AC???G???C`_@#72!6?G!7?C#168!13?AI#95O??_???_oS{Fo#97O#55@CB!6?w!4?_B#36??C#47@#9C#11K???G#56!4?@C@???PA_?@#28C#9GC?G?A#78G#41GEg??AA!4?@#52??S#100!4?G#32A#96G_?_Og#28!5?G?C#47_!9?@!9?@#5@!6?W#20A#31!18?K???GG!6?O??_!5?_#12!34?@#14!5?_!4?@O@@?G??B!6?bC#2?_o?_#35@?B?CC#14?O#239@@?@?A?EME#166@#49G@!5?_!6?G!4?C$#190??_#191O#192_#85G?C!8?GOO??@C!6?G#167!4?C#235A#245O?GC#208G#101?GG#155!4?C#33O???OO?OO??g?GGO!7?_#162O#227?GGWO#200A#196@!4?O#195OG#79???G#207_#29!27?@???@#72OK#159A???@#93!8?I?AGC#165!16?G#201C?o_#93O#101!5?_#100???_#79_!8?CA_??C#43!10?@!8?O@!5?_@?O@@_o?OO!7?O_O?_G!5?A#16?A!9?C?C#82_#30?_???O#187K???G#155G#28A!4?O???_C!5?G#18!18?C??_?AHO?D!6?@#10@@???G?A@?c_OGe?AB?E?A?B@#16!24?_@??G@???I???__O?G???A?CG#156@#196?@#42??C#157???C?G#226???C#54O#34?O???CC?A!6?G??A?_@$#204???_#161!10?_#226!19?_#236_#246B#202?O!5?C#233!6?A???AA?A!5@!4?@#83C#93!5?_#201@?A_!8?O_#191?O#162!33?A@#185@#225??A#44!9?@#132A#94?C#159@#186!16?@#155@?C#78E!4?OG???B!9?_#73@#33??A!9?OoA#48!5?WG??H???HC#33??A!4?C#95??@!4?C#163G#159O!7?_S#99??O#84!4?_?A#41!5?_#35gGC!4?C???_@???C!4?A#4?@#50???@!7?O?G!6?_?A#4???_#17?@???o?G#51_#8A!7?A#46_!5?C#9!40?O#32?_!7?A#36???C#13!5?@?A???OG?K?GG?OG?_#155???@#48?B?A!6?AWG???Mc?CCC$#231!34?O#157!4?O#212??C#49___ogG?_?__Gg#130C#240?@#203A!8?AAC#187!5?__#198@?C!7?G?o??_#162!47?G#187!19?C#77O#162_?G#48!11?GO!6?@#83???_g?GC@#34!13?_#73?A#53??_#49!5?_#90_!7?@@!4?C??C?A#77I#37!19?PO?A#14O#13G#74A?G??O??C#81G#36O?A!7?AC#51A?@???W@U?C???_C]f@#7!13?C#35_??O?C???O#34!48?_#18O_??C???C??GoB!7?G!4?_#234???A#20!9?A??@!6?CCA???@?@$#224!43?C#35!8?G!4?G!6?G#52??C#49C#235!7?O#225A?O#224Oo#203G#41??C!9?WOO?_!5?UOC?ogoKwB@@C?C??GO???s?hOG???_#167!5?_#100!21?C#57@#72@!4?A!8?O???O#32A!5?@?_O??O!4?_?_#82!9?K#27!6?A!4?@???_#155C#157_??_#91O!7?A#18!21?C#165@!5?O#98_#18??GE#57OgD!8?@!4?C!5?G?Q?@#2!16?B#55_O??O?_#11A#35!50?_!6?A#7!8?G?OG#16!20?G!6?@!6?@$#242!43?@#41!9?G#236@#24!4?G#27?G?O_?oOO#163!11?A#168C#54???A!8?C#156!68?G#209G#200O#37!14?CC!6?H?A!8?_???@W@?_!7?A@?A!8?_#83CCG!6?A@!5?o???@_??O#91!12?@#4G#84@A!5?O#51?_#37GG!7?K???_??A???C#6!23?C#1@#47_A?_??O#17!51?O#1?W#32!13?@??CC!6?O__O!7?C#14C??A#18@??A$#29!61?_?_#198???A#44!17?G!6?C#41!87?__?A#84!6?O#97G#84!21?A#45!6?O?O#154!8?C#189__#28A???_#6!28?_#95?@@A#26A#170O#16!4?@??C??GA!5?CA#4!29?A#9@#20?@A!4?G_!6?FB#79!79?_?G#58GO???G$#195!61?A#54C??C#48!20?@#42!94?G??@!9?G#16!26?C?_#165!7?O#31@#191O#164G#29@#49_!8?C??CCCA???CA#27!12?_#167CG#163G#31!5?C!6?__#14?G?C#74!31?O#56??C#95?_#81O#51!92?@!6?@C@$#23!62?_#73!23?A#30!94?O#34!39?A#47_?G?CG!8?G?C#157!30?A#166A#159C#46!7?A!7?A?A!6?@B!5?_?WcAD!9?O#101!8?O#96!93?O???OO$#168!62?A#80!23?G#168!144?G#162G#84??_#45A!5?O#169!27?G#82!8?OA#3O?C#15!137?C?_#4A#99__$#169!232?O#78!4?GAG#7!41?Go#6_#50!138?@!8?_???G$#84!422?O#28??C-#229AI??A!7?O???_?_??G#90@!9?A#229OO??OOO__?o!6?_!8?_#186@#162@#130@#227_?A!8?K??OOG!5?AGG?ICC?O??C!4?g_o#26@??@BA@AAA??@@?@AB#9@#72?C??GA#75O@?@!4?FD#168?@G#54_???_?@#37C#35?@EA@#42?A#54??GOoG!4?CD@!4?FC?kG_?_uK?W?___#48S!7?Go!4?@!9?A#54?_?C??@???O!9?A??O?C#169W?P!5?_#74CS!9?_??A??_#13C#33C#31_!8?O???CD?AA#18@!8?_#14?O!6?A#31O?C!8?G?A_?GE??C@#20O!4?FA#5!4?_#4ggGWUB!9?GGKIIAA???`_!5?K?AeoOqGC@@DO!5?O_EC_!7?@#9AA#14?@#32C@_AgOCB!6?C#50?@!9?_#32A#11AB?C$#231CC?AG???W??OGo???C#96@!5?@AA??@@@#83@#84@#166A!6?@???@@#202@!8?_??@#80???@#77@@@#72@#45@!9?@#163A#228_?SAG??DQSG_O???GoWo_OW??o__?_??_?o?o?_??oo?o_#90G#47E?N#45?E?AB???GO!8?_!5?O?G`_Ei!5?AA?A!4?O?_?C#49F!4?O???OWa???G??_o@??gCA#9?OG#42?O!9?_!4?O!4?O!5?C?@A#77K#98O!7?@?_!9?O??B#34C!4?`A!7?G!6?G?CA!7?_!9?_!5?@A!5?@?A?C@?H#10???C?_o_c??@_MV@??__HC!5?O!4?_!6?A?O?A?A?O@???C?@_??@GJ#0?O#15_!7?G?A?C#48G???__!4?G!5?C?C?COO?Chqve@@#56@#82_#31G?W@$#227G?K???OO???__#155@?@!6?A#87A!4?A#159??C???@@#233__???A?O??COS!9?Oo?G?W?OG?O__??_#33@@@#230_O?_?__??_!5?_#195??@?A??CC??G??G??G!5?G!5?G???O?_#93?_!7?K#31_#169BO#159@?_?A#43G_I_?WK?@AB#94?CG???oH_G?A_GM#43?G??_W_??__!9?E!4?AA???Oo@???G@???_C?G??G??__?O??W__??_#73A#96???_!8?K@?A??O#43AS??@!5?G?wWO!8?_??C??_#47oG#50?G?DIG???A?A#11_w?G_SOGDAA@!8?@!8?ABA!8?P!7?G!6?GoooO#14o___}WGO_P[O?O?GW@?G!5?MF?A??A#18B#6_A???@#36_oG!4?_W!4?A!7?C?A_??AAA??O??ALS#35O_#4Q$#223_??C!5?g#83@#76@@???@#234G?CO!6?GG#131??A#195C#101A#162@#243C??A#167?@#231Oo#203?__#208!4_@#226@@!8?__?A?A#207A#44?@?@#185AAA!4?@#226Y]jWF\TAH?AAkK]~#164?@?A!7?G!4?G!5?G???G!7?_!9?G#189C#199G???C#47_??C!4?Oo[Oo@?o!4?@A???_?gA!4?A@G@#79A@_W??@#27_?G?W?@???L?_??C_HC#56O?H@LEA@BPA!7?G!6?H?GcB!5?@!6?OC!5?Aa!5?O#6@#35?A#36?O!8?_!7?opO_@o_?C!5?A??@!8?CH??EC?D_`?Io?C#14?GcEMOW}VIB#2??EVfH[UaM@@XwC?DEA@?@@?@!8?Ae!7?Go!4?g_??OWWWO#27o#34o??_?O?_G!8?C@#96G?_!5?A#51G???G@#46C!4?G#10DG$#168@#194@#198_!5?AA#242???C#245CC!6?GG??__?oo#208!4?A#240G!4?C!4?G???C#242?G?_#238C!9?O#196???A#188AA#41@?@#157A#100A#91A#223CC#210C#224@?@O!8?B#209???C?CG#39@???A#187G#90C??C#29@C#185G!6?GG???G#209?_#78G??@#26Og__OO_#94?O#170?F?_#211[#157G#56C#31!4?@#78!8?@?O!8?OO?@#73@!4?E?A?_#83OE#84A@#37G!8?Ct#55_O?GC!5?o?A?G?CO?G#30_O???G?C_!4?W#79?O#97???G#192?O!4?_#168O#84@OB??Og??_wGI#59C#37W!6?CB!6?O@!8?C??I#20O???WO?C!4?@!9?AOPV@#15?_#8O#37?O#15!7?@#17X@G?@gO#3???O_#6???_P@!4?G#9O!7?C#32!4?A#36G#12?C#11C???@GC??_O!6?A#17?@!8?G?__?O??IwOS@@@!8?@!8?G?O#19?@C#3A#28_$#196O#197O#195O_#203C?AA_?_??A?O?OOGG#130@#71??A#70@@#246OO#164!8?@!6?@#196???@@!8?_#224!4?A#40!6?@#30??@#225???C#80A#229!4?C??aOc_oW??_#198??A#95@??A#30@@?@BA?@???@B?A?@AC???BFD???WGg?CCG?O_!7?O?@?O#164!9?C???C#158!4?_#101?@#74_???O??GG!4?A?C#82A#33?Y?o?D?GA!9?_#23A#11@#81GA?_@!4?C#31O#41@!5?@!4?O#91A!6?A???C#82@!5?@XG!5?C#101?B#28_#30W!5?@#46_!5?_!7?SG_??_O!6?A?EE@!5?O??_CPGCEG!4?C?OcE#21!8?@#1!13?_??E@A?A@@?@#18O!5?A@_?_o#15_#8?O?CA!5?_??_C?DG!5?A??@@@?@#31G#49!7?_??C!8?G#20???@#21?G#14oWC#155?_#47_$#201?_#208@#226H?Q!5?C??O??_?O#97@!6?CCCE#236!7?KG???G!8?A?IGIAGGAMOO?___o_#225!19?@!6?oGcG???G???Oo!4?_?_??O!7?_#54?C#29??O!4?_#167???E#201??[MB#212B#191@#159!13?A#187A?G#163A#41!9?_O#56A@??O??CEH?GC#42?C??o??O#30@!5?_???OG#34C#37?OO?OGO??O?_???_!4?G!5?O??A#190?_E?G#209GS#187G#157A#81A?C?c!4?C?T#18??_#26G?A#50!4?A!4?OC#28I???_y#19??@!4?_!7?G?CDEA@!6?cO?_?OGG?O!4?rI_oW!6?_O#0!9?O}}_E@F#5???CCK???eS???@B@@HC?A??CA??I?OC@DI!5?_??@?C#35O?G?CC#58!6?O??_?A!4?KCOoCC#18???CGO$#233??A#191O#185_?_!4?A??@#80??@!5?@#164C#156C#244O?_#168!9?@@#230O#241?G?C???G?O?_?O?C#231WS???A?_???CG?KW[O[{gG_@!4?_#223!4?@?@?@?A???S???w?O??OooO??G?o!7?O#95!7?@!4?AB?O?_??O#154!15?C#95KO?oOos\^E@#31?O#52E?O?O#72?@#81?@#77??@#91@#36??@#15C!9?@???GA??A#50?_#79_???A#47_#73G@#27?A_C!7?O#189@!8?G!4?A_#79?G?GA!4?O#47@!7?O???GO#53O!4?_#11CBGgA#56???OG#52E??C@GO#81@#17K{g#10O?W#3OG?GA#35GGCA!8?C#43??A#15!30?_#30_#16!6?GI???@?A_GG!5?g#34@!5?_#13!5?_???A@?AA???C!4?@AH?OEA!8?A#99?@#5!7?_$#207!4?O!5?KG#243??G!7?O?_#242_#73?@#67@#163!13?@@#235!9?A?A!6?CKCKKSGC[Oc_?_#207!15?@#210A#45!6?@??A???C#93?C#198G#201G???G?G!7?G?O?_#9!4?O?W#39@#190!6?A@#162!17?@#155A!5?G#30!6?G#55@??C??OG?C#26!5?q!9?A???_?C#78?_#82?C#36!4?G??_#15?G!5?_#90@???C#80??G#186!5?B#170g!4?AO#55?G?_B?C_!7?O!4?G#57?_??A@G#35??O!4?BD#48???G!4?g#59?@#5!4?_!7?@!9?O#19!41?O!4?C#43G#54C#1!5?_!4?OGEE???OO!5?@#20!4?O!4?o?O?O??_#37??G??A???Gg!10?A$#210!4?@#239G!7?G?G??C!4_oWWG!4?ww_kgeM?EMICEI]UYEII}S[COOCAEKO#41!38?@??AA!7?@C?B?AAC?CAA??A@@?E?EC?AEG???_!5?GUYpMwdanC?K@#72???__?CG#48!10?A#35?@#13!11?GD??vA#28???A#52?O?I???_?A?KO?@_!8?@???C??_CO?AG#101!5?C?A@#229C#48??_???B!6?_C#11?A#8A#41C#81!5?G??_@A#26?G??G#53!6?C??AD#57A??@#6!4?_#2O#16_?@!6?__???o??G!5?oL!8?CK#8!22?C#17O???G#159C#17!9?@#3?aO?GO!4?GO?G??DK@#16?G!5?C@A#79!6?_?_#81C!5?G$#224!5?CCC!8?O!5?C#76!4?A#225!25?@@#207@#203@@@!9?A#154!31?A???C!4?C!5?C!7?C#22!10?W??_#232!6?O#167!18?@#190A#165G#16!26?@#9A#35@A??CGGOCA??A!9?O!7?H@QgO!4?G!4?@#158!5?A#206O_#212G#54!4?_???G!5?G?@??G#82!5?AC?Q?@#8?C?@#16O#55!6?B#58??CA@A#21!6?A#4?GC#47__?gGA!9?B?_O#3!37?@?@#19!14?@!5?_#12!4?CC!4?GCCCA!7?CEHC#53??_??A?G!5?dHOG$#176!5?_#236GG!4?C#208?AA??G?CC#243!31?_#230?_#195?_#162!45?C#75?C!8?C#227O???oo??o#24!11?@#195!28?@#170@#29!29?_#7G#23C#32!5?@???@#77!11?A#7??CW???C#83A!7?C??o#159!4?@#78A#201C#83!8?C??G#73!6?_#78_#94!6?COoCC#7???@?@#51!10?OC???@@@#7!5?CA@#53?OOG#18@??__??G??G@?G#9!65?@#7_?AC#37???O#11AAC@A#55!8?G??@??P?O__$#202!5?@#162@#188_??A?A???A???A??C#22!86?A!9?@??@#200!40?C#54!38?G#74C@!5?C!7?C?E!7?C??A???_C#165!6?G#205_#230O#27!9?@#52G!4?O!8?CL??@#84G#98A#5???O#6O#55!26?O?C#51g??A???A#42!82?G#83!12?O???@#59O#54?@$#159!7?@#225CC#166!7?A?A#202?A#72!89?C#209?O#77??C!5?C#16!84?OA@#9!13?F#26A#49G?@???C!4?@_???@#204??o#91!11?O#15A!7?@C#59!6?_j#99K#9!5?_#27A#57!26?_O#50!5?G#82!99?WOG!5?A$#156!8?@#235OO???__K!4?O!7?GG??K?OO??_#45!77?CC#156G!8?O#7!78?C#32!17?C!6?C#167A!7?_C#95!13?_#4@#7@!6?C@?@#58!4?@#30!7?A#18!134?A#28@#56O??_$#93!9?@#132!8?A!7?C#34!198?A#28C!4?O#163@!8?C#9!16?A!9?A#41!12?C#84!136?_?`$#85!19?@!9?A#33!197?A!4?G#35!25?C#98!159?C#52WAAO!9?A$#72!229?GA#78A$#157!229?A#45_???_!5?@$#155!230?@???@$#95!231?@-#231AO??CfK?OOOAg?___??AA?GO!5?AA@!5?A?WG@??F?E??HOo@?cAYDC?@??CCC?C??s???C??A?@I?AA@@??@@d_??aAO?GG@?cC@KGSOG?COO??_??___??Gg?_?_o#223AAEC?G???_#41AMFk@_@A@DKFRoagxDC#74_OgOC!4?oC?_O??QO_?C??o?_?A@!8?OG!8?O!5?oOGG!6?C!9?G???OG`#52A#98A!6?_???W??@#83H!5?_??A#78C!6?O#46OGCDA#16C!5?_?AC???__??F?O!5?B!5?GA@??B??@?@ao???_CO?@?@AG#6!4?A??C?C?WK#0?MBB@#9@!7?C#4GGc`C!4?E??@!4?C#7?C#17??a?H!6?_G!5?_???C???@#55?A?AG#99?o_?@WwGGCE#52?G!8?G#157_??G$#235@@@D@O_G??@?C_E??C_?D@@GRC?_?K?_?Oq?rhdeAG@R?CO???CA!4?_Q@?GOO?G?@!4?W???@_P??COC??A!5?K!4?CG??Gg??w?O??Oo_???_#210?@A@#201!8?@??@???C??O#30A@L!4?@_CeaoO#54???C#72??_?G?A#95_g`g#98@C#36_#55CC@??@?G??@!4?Eh!5?OO!4?C!9?__?AICO??_???CG#49KA!4?@S`_OWQA_C#72@#189G!4?K@?C!4?_#79A#57C#55c_G!7?AA!5?G?A!7?O!6?A#19CC@??_!8?_?g#8???S#7O#10@??A!5?Og!6?AG!5?{ht?WkA?@#3O#30??G#47_?C?A#34CG#10WWKA!8?C#20G???GO!7?Ku#48_E_?A!6?CA??_KXYY~E?@Da@?G??SO???@!6?_#18@??_#13O???_#27_$#227_A??A?BcCDEO?G?C?_?H!5?O???O??_A?@???@_OS??B@!5?AG?@@#246__#244O#198?A?A#222_#229ACA!4?CE???GGG?_!5?@AIG?@?@@#228BADCCEGbEBCiDADGEAQD!4CBK!6?{?HwWW_o___#162G#31A?@?AC!4?G#94!8?O#43J@#73O?G!7?A#43?BGKA?_oG?A!4?OK@__??sO?AAGC#22O#72_#16??C#84_O?K!5?OO#97C#42@!4?A???@@CAC!6?A#84_#158C!7?O???G#56@CAGW!4?G??G?C??C!6?@???_w#43c!4?_#46?O#4???_!4?CC#46_??OCC??@!8?_??A!7?O#4?O!7?AX@B?B?b#33??_#63C#52_#37_O#19__CD#8?AOT!5?_OG?C??B!5?G@A!8?_#4A#18_qG_#21???AQC#96_C?wCq?OGC?BP@A@DUUi_Z#55?G!5?G#54CO#196@?G#30@$#238CK!4?OO?G?KAC@!9?CBD?C_l?O??o!4?CEA?Go_M#242UC!4?GG???@!7?@?I!4?O_#230A???@B!4?B??OABAA#223O#238_GOO#195_#201!5?_#226???C?@@???BA@AC?CCA@?B#225?@E?C?GGOO?_#72G#90?O#47?@???@??@?K@[AEI@??D@!6?O#49?@EKu!5?EC???@?c#52A#30sOGB!8?_?@K@A#73A!5?G!6?G#52?A#48@C@!4?GY!4?@#155??W#206O?c??@#167_??KG#81@?CG_?FD!4?C!7?G@#6???_?_#7W!7?W#5!8?@EaI??@!7?CAbQ??KGC#35[#55K#21GA#17C?CgG#94_#78_#7?_!8?A???_#44!4?O#168O#75C#28A#31@G?AAA#17@!8?A?@?G#32oC!9?@A?@!5?O?_P!4?@#58!5?Q?W??AEG?Gg!5?GC_D#32G???S?_A#231O@#97O#43O$#234O??A#225WG???A??O??B???_G?A???_?G!9?G?P??C!6?g#195GCC!6?CD??@???_#243@#233?C??@?C?B#225_!5?C#246_#234O#207C!4?O#244??__#229!14?G???G?G?GBBAC?e#187!5?@#154@!7?O#35!18?C#56_!8?G!8?OG_G?a?O#37?GO#27?G???@!6?A?oOI@#79GCB???@?@GA#96C#101G#33CP??GG@O#41G#79O??C???GDOC#211_G?A#205C_G#200A#82_?a??A?O??A#31C!5?_!8?__??A!6?G@_OI??IK!7?O#26A#37GCW??A!8?_#9G#36_#51?C#19GAJRAf^?K???A?C#2???__OGGe?YO#223?G#163O#79O#20_OOO#14__??A@???HRH@#48_OO?_#47CC#11GB@@G#13?@!5?WEB?C@@#20GWWS\???_?@#59G??GO_?CG!4?_?O?_@A#37c???GO#56@@#236GG#155@#168A$#245G#226__O!7?_#243@#245@!4?C!8?@???KGG?G!8?_??O!4?_#203A#230@O!8?O#224P#226Q!5?G!7?_?`!6?C???@#232???C!5?G?O???_?O#238__#223@A#45!18?@!8?O?_!5?AG?M@P?oI?AA!8?o!5?GC#79?A???G#72A???@#26?D@!7?_G?G?_#42?C#82_G!6?AC#157G#165G#28?B_?o?CO#72_o#96_!4?_#55?_#101???@#230_O!9?O#100G#74??O!7?_?G@#36G!5?O!9?C?G??@??@#8!5?C?G#2@#30C#51O#34A?gC??@!5?_OO?O#20?D@#31!9?E?_#1!8?C??D@C?A#164G#35KC@#16_???_!5?o??A?G?G???C_?O#21??O#12?O??__W?O!5?_#53??__!5?G?C??O?A?C_A@??P?QOB#15OGG#3@#49A???_#226E#191C$#236??KG#198???B!8?RB?OoO?_?gG!5?@#228!5?A#226???_!4?@#229@?@@?@#234A!4?C#207??_!6?O#232!6?@??E#198!4?O??K???O!8?oOO#230!10?GCWOwoOO?gXOUOW#195!6?A?C?G?O?_#77!17?O#49C#84?_O@?OO?A!6?_!7?C#15!5?AC#33C??A!7?AA??@#58?O!6?@?A#43?OA@??_#90_!6?C#169!7?@??O???BO#163OC??_#43?B?o_`O?A?O#57C???o???B!6?O#74??A#11?_?K!4?_OoQ`P`PE!6?_owgKgSA???A@!8?MpE|p|^zBSGCA?pO??__!7?@?D#12o???O#15?_#18?K!5?C?`oA!4?G#37???@!8?A?@#51!9?_C_?_O_??a?E??_@??C#21_#34O??__#79CA_#74A#208O$#228??O#223__???A?_???W?C?J??I??_?OG???AA_!8?GG!7?C???_?G?Ga_??A??O#245@!5?_!7?GC?_?_#224G???_#208_#225???@?O???O??_#209!6?@#236_#29!20?@@??C#83!23?G?EWD_!9?O???oKC#9!7?A!8?_KF#54!7?o???@!4?c?G??@!4?G!5?C#170!5?A!5?O#165?A???O#47!5?@!8?BC?A???O!8?@!6?@#7!7?O#6A#35?G#55_#36_@??C#14!4?C!8?_Oc?P#232!24?G#36?A@G#5!4?H?UJ]ZE!4?@!5?_???CC!9?@!4?_#34!12?@#81C?@#50?C#16!12?C?O#28??G#33C#185C#227A$#229??A#201!5?__???O?GGG???__!4?OO!4?@@!6?_#236?_???_?_A?O_OCC?QaOGGW?G??A??@?@?CO?O?OK?Oa???O_???KKK#159!39?A#95A?C#90!24?C#78B?E#54A???JGGo!4?B!4?@@GOA!5?_??G#78_O#56AB!6?CR?A@@AE#81C#80_#13!7?O??C#34??@#73?Oa?GC#199!4?C!4?O#91??@#36@#101a#84@?O#35!4?OO!9?A#82CG#9???O#15C?C@!5?O!6?OA??G???G??@#56AA#57@#27!6?_O#46!39?A#13!5?_#2_!9?@!6?OA#52!5?G!9?A#79!15?@#12!15?G#7CC#238!5?C$#222!8?@#242?G??A#207?O#185?O#187O#232??CC@G???A!9?O#246!6?G#239??gQ???O#191!4?G!6?___#203O_#227!7?__!4?`???A!7?O??aBA@B???B@i@IA?CG!4?G?OO_`?HCA]?C__O#22??A!9?O#100!17?O!4?@#77?O_??_??C#80@#47O@!9?O?G?_FC_@!5?_G!4?O#83?_??AOO#26!5?_#32A??A#157?_#83?B#56G!4?G#232??OG#192O_O?@!4?_#37!6?A!8?C#18@???_??G??AC!6?@A???OALGK?G!4?C!4?OC!5?@@?@!5?OG#34!42?c!4?A!5?___O!6?C!8?@#17!29?A#5A#2AA@$#233!8?G#244??@!7?C???C???C`??O???A?O!6?O?G#185!5?G!9?@#188@#239??GGKCJarwIYGC!4?O!9?K#9!44?@!5?G??G#101!21?I@#42!5?@!6?A#73?OW_C!5?_#41A?C!6?OG#49!6?G?C#78?_!4?O#30!5?OG#23G#58!5?@?`?Q#190!4?B_GAAKG#186_#157C#99?E#30!6?G?@C@#52@??o!4?_!5?@???CG!5?O#28!10?_#47?O#50?G#20?OOGA#3!4?G#31_O!4?@#37!47?o#52O#1A!7?K?A!9?OG#36!35?@#35o#14@$#228!22?OA#243??A???O!5?K#225!25?C_#238!14?CO???GC#195OG#241?_#196?O#205?O#156!44?C!5?_#158!24?CA#35!7?CB!4?@!5?_I???DA???@!4?C@O!5?G!5?@_@_?o!6?A!4?F#204!8?@??C#27!13?IG#157O?O#84@#48o#50G#51OG!6?A#2_O#27_!5?_#14?A#74!14?_#53???@A@#4!6?K?EA#53!52?_#36_?OIH!4?O_?GC!8?KC?CEJ??A@@$#242!27?A?@!4?KC?C#201!24?A!21?G?S#198!50?G?O?_#81!27?G!9?G#45!13?G!4?@#83G#80C#11O#52!9?C#37c!7?A#27!6?EAC#212!14?K@#11!15?C#9C#16@#159_#95CA@#59?__G`?BC#3??O#8G#28?A@?@??C#9!85?A!4?O#3WA??@!7?_#2GCA$#246!32?CC#191!51?G#26!52?B@C???OYUMwWO?__#41!23?@#42!17?G#36G!5?@#59!8?@#100!4?_#210!28?A#14!16?A#26A#168G#158G#34!4?A@!4?G???@O?@!5?@?`Y#28!80?C#43???A#46@#79!8?O?A#35@#7?C?@$#29!196?@???O#186!62?_#190O#53!5?O?O???@#5?G#12??G#37CAAWB???_K#53!95?_#55A#15@!6?O$#81!197?_#91O#94!74?EC#48!6?_#35A???S???C#159!97?O#27O?@$#49!282?G#167!105?C#74G$#169!388?G#165C-#227@???A??@@!5?@@S@CA!6?H?A@__Q???S?AA?GCO??G?CO_!5?Q??_D?GA??O??o?O?_G@?CC?CO!7?CG!5?_??B??C!4?C?_?g_?CC??O!4?AOP!5?_@??CG?G?GB?aMCGWO?G!9?_#83IQ?COOH???G?C??@!4?O#55?CA??A#42?@_#36??A#83?_!9?G!6?C?@#155A#49A!7?KAFw??O_@DG??S#210_GOG?OQ@!9?C#155A#165_#99O_#52??O_?B???O#34_owO!6?A@!4?_?O??@!9?Ww?G#17GCC#2??_O#15dOgKA@??_!4?G!4?G#8@#7@??C!9?__!4?O#17@?@@!4?C???[ECI!4?_??A??A??B!6?CC?oYHOLC??@#96_hwWVYVR[SS?@!4?AACFEEe#239G??A#37_?C!6?CCA$#235CCDACCCA?DGKCTOOAA?O?GGgw_?P`AHQDBBKA@?@d?_?_O?P?A??A?A??O?Z_OC?OO?W_!5?A_Q?B???G!5?CG?_OCw??W[WHWJrOQ_aOGE?D_a?`_@CA?@??_w_@ECCo_CAOOo??G??o_O?G?ooO??OO?__#74B?HGA???G??CC???G!4?GYA@??K!6?_GS!9?@???I?A@#165C#166C#96@!7?__?A?O???g?P#55?@!9?O!4?_!4?F?@??GAA?@#53?CgKCA#35_O!5?@???_?A@!4?_?W!5?A??G#14_O?OKC!9?A??_@A#197_#31ABCo_#6??C_!9?C??A#3O#16?AA!5?C?G?_!4?@#20O?C??O?C!7?B??A!7?_?OG#8?@#52O#51COBb_?gK?I`K_cG??@@?G?@#165G#93_#166O!4?@#49A?A!5?C$#198WG_g??G?_!6?G!5?@D@C@??G#232_?C#207??O!5?A!9?OC_#243CC#245GGGA???_??C?CCEAB?@@!6?_??OGG!9?C#244@???@#187??G#236?OO!6?@!5?_A!5?@#198C!5?W!8?@???_!9?_#91??GC?B??@Q!7?CC???O#73@C#78?_?_!9?A@!9?_?SOK?A!5?G#15A#54CC???_C???WO???_I#157CA!5?O#82G!6?A!4?HE?I???_CA?@B@#51C#31GC#8A!6?CA?@!9?@!9?_??_???O#27@#10!5?C?@?O!8?o??@NN^B~bPW??@?HA_???_???CA#34?G!4?O!6?`?@@??_??C#53?C??_!8?OO!5?C?C??@`?_??_OA?O??@#83_!6?A?@#48GC#12?A#16A#20G#35?@$#236AAA!4?C!5?AAA#195???@@!4?!5C#236A@GCG???O[G?@`@?O???GHCA??CC[?O_Oc?AA?O??C?QOO_??_?C!4?C?_?B?EQ@I#228O??__???@?C?@#242C!5?A#224A#207?A#245_#228S?C!7?OPGW__FK?NF?U??Oc!6?O?_#39C#95GKC?O??OCGC??OOGG!7?_#56!5?_!9?A@!9?G?C#43@@#187G???_?O#167_#27@#35AA@#42@#58G!9?@?@!8?_E???O#48G#208_A#49!6?_#78?c#57O?OG#50?O#48G#36?C#18I!9?C??_?_?CB?C!8?o?C!8?O??A???O?G?B#73_#27G#78@#30CC#2??WUQ??_??OgE`B#34!4?@#20@#14aaAaDBB@??CG??A!8?C?A#35O???G!9?C#32CN??A#55O#54_#21@!7?_??P??C#17_?_#32_!8?G?W???G??SoOG$#226_#225_???@!6?O!5?GCAAA?A??I??C?_O!4?_!5?G?C!9?@!4?A??_@H???O?C?_?C??C?A???C@#230@??C???C#243?@#201???C??G!7?O?I?G!5?__!4?A??GG???G?A?O!5?A??_#156C#75C!6?C?G#166O???__#101A#77?OOG!5?O@_??C???_!9?O#73@#46G#72_???C?_#94_?WC?_?G!8?_?o#79OW??_??CM?@?K!8?_oKWS??GC???O#59!8?A_bE?A#46?B#26@#11@@??@#16GG??@?@???_?C@??@!7?O?GI@AE!5?G?AG@?PaL#223O#155O#95O#162O#4G#43@#1??_???O???C?_A#8OWC!5?WOO??GS#12?O???@#32?C#50aAg??K#8_?_#7O?AC#52GO#50?A?`!9?_#81?_#99!5?G!7?MBO#57I#58@GC?O?G@#188O#80_#8_!7?G#28O$#231?@?C!8?G_CC@CA!6?G__!9?OH_?`??OG_?Zg@???@??B?CA??GA???G@??O??A#234@O???GC???O#232?G!9?a?O!7?_@?@???_??C#192!5?C#195E#244??G!4?EA??_#195@_O?_!5?C#26???@@@C#47?A#92??C#56@#90C!5?C#80G???A!5?O#47C??G?OK?mG?_WGW?gc???Ox_O???_!7?C?C#32@#154?W#28@#43@#74?C#56O!6?AA#82c#80?_#100A@#230CC@??A#96C???C!8?GGC!4?G#58?O#47!5?_#3C??A#27M??G#4C#55G?K?@#47@???O!9?E#10??O#4_?_OG!6?O#36?C?K#74?_???A#185A#47A#0!5?o_#4???GE@?C?__WG?O?G??_#9_#2_O??@#37_#48GG?CgCW?Q!9?EhPYDGG!7?CKKYEC??`?_!8?C??W#13_!6?O!6?A??@$#195?OO??_?_#238C?CAA!4?OO?O??O?O!5?G?GC@!9?@#244@#234!4?A#195O???_!6?@#238C??A!9?b_???O_go?G???A!4?IA??AAO?__??EAGC???@??_@@?A?_??Wo?A?C@?@_!5?G!6?G#77???G#72GG?@?@#195?_#164_#162_??_#82?@#55A??@#170O#72@??_!5?_#30!6?K???A???GH_???C#90O#95{F???O?CA@!4?G?w#100!4?@#163@`!8?G?A!9?@I#91?@#167C#202O_#94!9?O#7!8?o??C#45_#14C#43o?G???CCA#19G!6?@?A!5?BB@A??A!4?CO@@???C??@!4?_!7?_#5!4?W??OE_?_KCCKA???j#13B?A#53!4?O??_F#5?O#1OWG??A#43C#58_w?_?O??G!6?_#59!10?A?AA??A??C?B?GO?@#98@#74O?C#34_??O!5?B$#223??G?GI@??G!4?G??G@GK??E?AA!8?O!6?AIA??A?C?_!6?_??@???_?@???GgG?G??GIA?A@?@A!5?G!6?C?C??CGHH!8?GG?O?W?O?GEC!9?@A??O@!6?_???_o?_O#79!5?S#100G?C?__#54C?A@??_A??I???CF?O!5?@?C??A??GA??C??O#77_!7?O#26A??A#83!6?O!4?OOG#187??O#212_G??_C#158G?@!8?O?_#6!16?Ga#30_?O#49O#9C??A#48_#50O#36O_KB!7?O?WCC?C#28!6?C!7?_!6?G!4?W#19!21?@#18??oG???CiSA???@!7?A@#16C?O!9?_#15A@#34?_#14?A#20!13?G!4?C#2_#55?G??O#190?O#207C#202A#99@#27_!7?@?A$#196???O#201o??GG???_!8?C??@!4?G???_???c#230C??C!6?_#239???H@!4?@#226?GC!4?__??G!7?O!5?@??O?a?O#239_??D#225!5?_?C?D!4?o???O!7?Wc#232!5?@#242?A??@#225O??C!6?G?C?O??_#29?C#84!9?@???A#41?A!6?GC!5?O!4?c?KC!4?OSo???EGA#91A#81?@#57G#55@A#201_?_G???_#169!8?AA@#158A#81??_?O#167??C#95@#207@A???@#97A#56_?_?A!4?GC!4?C@C?@!4?@!7?_?OO?G#52?IA#31OCAO_?K???K@#37_??@#7!6?_G??@#35!4?O#20AA#55?O#186??_#163_#191@#15!26?GOO???O!4?@#51?OOFG#19?G#4g???C#55?O#51???CC#42A#18A!6?A@#50!15?O!4?O#12?_#56?_!8?A#4__?O$#234???@#228@?A!6?G#242_!7?_#188!11?_#242A@#228A!9?G#191!5?O#246G#242C#221!5?G#230A#244O#196?G#242C?A?@?@!9?_!4?C!9?C#226!10?G??AA!4?OG??C#245!10?A#164!7?_#187_!4?A#41@?@?EC??@H#155!8?G?_!4?O!6?__#81A#33!8?O!9?O#42c?G#84!4?A#79@#189?_#186O#209_!4?_#84!9?C#155GG#168@#231!7?O#202?oKC#189?C#169A#84?@?A?C!7?C???W?_#2!9?[N@#37???@`OS#46??G#11??GGAAogMF@!8?OH@@?MFqpkKO?_???CCCG?OEMBHg???[!5?GEH?DSK#3!10?@?@#46!5?@#3!5?C#28_??G#81_#21???G#56??O#37oOG!5?GA#98!14?W#101@@!8?A#235G#169G#163C#36O??C??@__?G$#188!5?O!4?O!5?G#185!18?_#196_#226G!6?CC#241!7?G#224!14?@#229G#201__?C?C!4?O??C#231??__@@?D?HAo?_@???_?A?A???@??C?OSC?IG@?HH?@?Aw`!10?C?[#185@!6?G!6?OO#73!5?A???@#168_!4?G#93A#52CO!8?@@!6?A#26???CN???A@#82???A#210???O#206O#190_#93G#33A?A@???A#97???G#170C#205A!9?_???@!9?A@#22!21?O#15G??AA??A!4?O???_A??G#78_???@#31!8?A_#84!6?G#34G#36!41?_?o__G@A???B!5?_??@O??G_?CC???_???A!16?O#246!8?C#236C#170@#35G#33C??A#14OCC$#185!6?oO?O#168__#246???__#234!19?G#224???O??C???C??A!4?P#243!14?C#239?A#191???G!8?A#195??A#208Oo#229?@#241_#237!15?O#191??O!4?A!6?A#93!20?@#45@?A!7?A?BA#78!5?@???C#30@#45B!6?G!6?GO?OO?O?s!9?P#207!10?G#199O#170O#41C??C??C#73!6?_?_?CAA#232!4?A#211_?G#81!4?@??@!5?A?@_CO?G#33!12?O#78???_#28!8?_?G??C#7O#56_#55O#43C#46@??A!16?C#2!55?C#15GO#12!9?@?@!5?@#82!18?G#243!9?A#167?A#191@#17GW!7?_$#244!8?A?@@@!5?__?OO???O?OOO#198!10?A!4?A???C??_?_?_!4?@?H!9?C!4?O??@?C#224???O#198!23?G?G#154!32?A!4?G#159!18?_#197_#207_#79E!7?`@#27!15?A!4?@#200!11?_#45G?C???G#192!6?C#208!11?O#192@_!9?@#206G#41!22?_#14!12?O?C#5@?O!9?_??OG!7?_?A#12!57?@#27G!8?@!4?A#224!34?@#18?_?@@??G??O$#187!8?O#245AA!6?_??__#195!19?O#197O??A!8?o#195!22?G#207?@G!6?A???G#229!21?@???@#30!30?@AB@AEAA#157!18?G#49@?O!8?B#11!14?@#52!15?@#168O?O#190!22?A#74!5?A???O!7?OAG#26!32?A#30_#50??_#53A#57A#74!75?_#32@@#13!8?AA?_O#15!36?O!4?G@?_$#191!9?_#197?O#203!30?G!8?O#244!28?_??G#195!29?O#201!60?O#56A#43??BG@??O!5?AAB`?@@#29!5?A#30!16?@#209!24?G#49!5?_#101G???G?K??O#74!42?@#5!87?@#52!41?C#5__$#229!45?_?_?@!5?A#188!24?@#187@#185?AA#211!89?_#158_#35???B!4?G!5?@?A?C#72!23?G#73!30?G#154O#98@!4?@???_?@!4?K$#246!83?O#191!91?O#33???C#83!71?_!8?A@?@$#100!251?O?_!6?O$#170!251?C??O#211_-#235A[CB?GHJIiIA?Y?_?CO??gHHh_G_??Dc?CC?sWGC?oWO??ISKSOO??_?d?_OO_G?@@hAaGCS???`?_??A??A!4?CG?GD[Ec?AWWKG??QBBK?YaeOW@`xcHO?CP`GGO?GHCCkCKCGMrZ!5?KKCebO?MKKiKC??p]?__@TpA?ogg??gog__o?G???_O_?__O?O#56A!6?G??C#94D?AP??_?_?_??wIOxJ#79A@@???O_@???G!8?ABAC_[#157O#240_???A#81_G???O?G#16?gG??G??C#54_!4?_G#56A#8?_#5O_#16`S???@DBA!6?@!8?AAG@?@ADOA!9?GG?D@#4?G!5?@EA@A!9?A??@!6?C#79c?A#101_?GG#83O#16K!5?_A??G#32O!6?C??A?A#12_?C#46?O__??_#14_O??EO#4g!4?@#7?G#49??@#16?`?_O!5?C#15A#2??O$#231GB@!5?_??[G?OG!9?G???@!5?G?_???A??OOP??AK??@@@!4?c!6?@?_W@G#207@#227@#241O?O?_?C??C#243!5?C#231O?a_?W??!4@P???A??G?GB?A?@o_`oIIO?C?@O?G?O?G?_G?G!5?_???_A!7?o!5?SGGC_!4?G?G?OO??cC?_!5?O?_#83@#161?G#42A??C@#75O#83??B?G?G#201A@@B?MC?B#169B#41CA?_#37G!6?C#36C#90@G#100_!8?A#97C!4?_#208_#167?@!4?@#52c??D@_G?A#36ABAA??@!4?C?_??@C?A!6?_?_???@#9_#7OEA!4?A@???_!9?_#72CC#54A#55O#2!6?_??o???@!8?_!7?@#19I!4?@#1A#15A!6?O!6?C#17A?oKE?E??_Gg??A???G?B???A!4?C#5w_@G!5?O#35!4?O?@#14_!7?@@@$#238o??GIA!5C?A@EA?_aoO@__??_?O?O?G!8?C???A!7?O#227???E?@#197A#210G#238???A??@@?_??O!5?@!6?A!9?O@???GKK{c_K_C?C?sK?AALG??O???GCC???G???@??O??AWo???SG{O?O??_CG!6?A!4?O!9?W!5?G?_#47A?@!4?@I!4?MB??@#167?E!4?C#100_#228G@C#45?oC?O#28_#49]OI@uAAOG_#82A#55@#158@!7?G!6?C!4?GA#37GCC#57_#59C?@@@#18[K!7?G!5?OG?G?GHCAi_K!4?C#5C?MG@#28??_???@!8?O?C#37G???O?O#0!6?^~#21?@#26C!6?O#18?_AGM!5?_??@O!9?@@?@??C!4?`#48C!4?CjE@?@@???@@AKEHB?F?GAA#13!4?_!4?@@??B???IP?@@?oSi?A$#232C#227_Q?@@??@OO?___!5?G??C?@??@__?AA??I?A???_#229@#241_C!4?@???C!7?A?C#228??CC#233A???_!8?_!5?_AG?G??O#227?C!4?AA!7?G`!8?A??_?@`a?AAB?__?OCC!4?A??_@?B!5?OP!4?O?OA??_UG??H?O#92@#93@#101@#157@#91@#83@#74@@???@A!4?CED#195O#33CC?A#39!4?O#72G??__O#154O#199CHO#211AA#47???G#72CC#32O#74_!6?AC#84CO#155O#202agCF??CW!8?A#230A`#163_#101O#99C@#28G!7?_!6?CA!5?_??G#14OG?CB??K#17?K#52KO#43C#8GG#13G#2_?_o[EE#31?C!8?_??IE!4?A`AC!7?A#6??G#9O!4?H#16Cp@?@?C!5?A#3?_#32_???G#168?_?_#11?_oO?O?O#43_#58?C#51?@#49???A??A#53@?o!5?o!6?G#10_O???O#6?I?C???G#55???IC#17I!7?@O$#237@#223?_?__?OO@!6?GOKC?AA?EA??e?A?@@AA!5?@#230@!5?A#245???!4A#225??A#229G@!7?G??G!9?A?_!5?@`@#228_???C?ACQ?O!4?@A??P??A??G??A?_??S???AH?ObOAD!5?A?@!8?A?O?GO!5?@_!6?@!9?OG#78@??A#54@A!6?F?@??KF@!7?_?_!7?@A???E?C?@G__@#169?_#101G!6?@#52@?O#49AH#58B@!8?A???@CA#32C#9?o#7O?G???@#32??A!5?BA@#46@!6?_O??A#12AE@@#6???G#27_#26?o#15CCA!4?C!4?_C#26?WGG#1!7?_#36??G#8!4?C?C!9?A?E!7?@!9?GG?GA#56!9?O!7?O#58C#96A??GE?G#2???_O??ERPOwoYC_O?G#79CA#18O#4?_Oo?_???OG$#242??G#201_??_!9?O!8?C#199C!4?A#236Cg?_?CsG[GAaMGC_?h?KG?O[IhH??C?Hq_EoOSbAsW_?O?_??@PP`ILHBsOAA?WG_!5?C!5?_!6?O??C?CG!4?GC#209?_#198?O???O!4?A!4?@@!4?@#195@@@!7?AA!6?A!5?C!7?O#95B!5?C!5?G???O_!4?C!5?O???_o?@_#55??C#65?G?G?G#42?G#80?O#208??CCA#206GA?G#170C_!6?G#232g_OC#189C#35?O#27_!6?o!4?_!4?CC#52GAPC#3?_#15O??C!6?A@?_O#4???O?G?P?@#18??@!4?A??_?_@!8?_#7!11?a?A#5BO???_CE?@!8?A?@!9?O#36A??@_Q@AO?@?C??CO!5?_#57?_??O#3!5?_?A?cC_AIc_O_AC!5?hCk???C__$#244???C?C#236A#207_#190??@#195@#232O!6?I#198?CC#195!4?C!6?@@@???@#224???@#226@??@!6?_??C!8?G#222!4?@#245A#232??_#243?GO#230??A!9?O#241??A#232?g???C??`#243?O#245OO#226?@#237?A_???O#223???@C?@`!4?_???A?O???@@?@??A@#243!4?_#225?A???G??@?O?G_?@!9?G??c?__!4?O!5?_#188??_#159O!8?C!5?O!4?C#82!4?@#73?_O_#83@_O??ACA!9?GC???M#237?OGM@#210@#15??O!5?O!7?C#4@#55!4?SG#31!6?o?WCOO???_O#3!4?_O???@#14!4?KC?CGG?@#36O??A#91?@#166@#75@#13!15?G#15KGCcHGE?A#52O??G!9?O!4?A?A?C??@??G!6?_!4?_#16C#15C#31_#51!5?O?g_GFD@@#1!5?A???AEE_O!6?G#7AI!4?C$#198???O#245C!9?@D!8?O?O???G?o?O!5?_#243!4?_#228?@#195!11?A#224G#244!23?A#224!6?_#223!6?_!4?_!4?@@O#232!5?A???O!7?O??_!9?G!5?A!7?C_#223!5?C@!4?G?CC!6?E!5?G???W?G!4?O#26???G!6?_#170???C?A?A#162?O#165OG#58!8?`!7?H#166?O#212A??C#207OA#165?O#82O?G#73A!8?_#98?@#34O???o!4?C?B???WO!4?OBE?C_?o?G??O?A?G#10!16?AG!8?_!4?Oo?OkV??N?BA?D#3?@@#14GA???P!6?A??_?G#74G#159O#98G#49_??@!5?@#12G!8?A#14!4?o#55A??wWHA#94!4?O?O#12!4?G?L???D@C@??@!5?CMEA?CGG?I$#228!4?O?O???_!4?O?G??A!6?A#239!6?_!4?o!8?o?a_dkG?O!4?Rsu?OO!5?ACKCJBBA?oCGWS?oW?A??@#225!4?CA?Q#233!14?C??A!7?O#242!11?_??KG??O!7?_!6?__#188G!7?A#245OoO#155@#230?C#168???A#163AA#154A#232GO#227_??O#45AA!8?A??c??Op#74?O#200A???@#209@#212C#48!9?CAGC@?O#189!4?OO#230_O#211f#55!4?_?C??@!5?O#43??_!4?C!8?G??A@??@#11!6?A!4?@@???_oO??KFb?W{GwqoZKpo?[?I@??O?!4CHWkR!4?sw{ogOo???O?O?G@@?@??C?@#13?O#84_#207G#155C#48COJA#56CC#37?E??@??W!9?H???G??Eg#21!5?C@OOA#0!7?G!5?G[_#32?_?@!6?_$#225!5?O!5?_@C!6?C??A??B@?E?@???C???A#222A#232!6?I#207!11?C#201!39?__?_!20?C??A!7?@?a???C?@??A!9?@@!5?@CA!4?H?@?A@??A?G!6?C!4?G!4?_#29??A!6?O#187???@#157CGG#232???G#96!10?O!4?A??C!9?c?o???A!6?A#53?@!6?@#2?_W#8C#35A??c_G!4?G!4?A#19?_o#20???O#55?C?@#5!15?O!4?I!9?_#27!15?__?A#28C!8?C#185!5?O#53??@??C#14???G!4?G#5!12?O#35???C#28!29?O#20C#5!4?OP???C$#210!12?C#233?G#242?@B@?_OO!6?O???O?O!6?CC??_!4?B!5?OO???@?GK!8?AICKSL{GGC?`qC???C??@@B@!8?G??C???CG#225!9?C?_A??O@#245!8?_?__!4?O!4?o!4?AAE#196?G#197?C!7?A!6?A#198???OCC!7?G!5?__#78!5?AAG?G#55G#155C#231!25?@#209_?_G`!8?O???OA#56???AIaO#20!5?@@#23O#37@G_??O?_?AC#56!13?GA#8!16?_!5?C#34O#47O?G?__#30!17?O#36!6?_?GC?GGPC?G#166!6?O#190_O#9???_#50C!5?GA!4?@!4?A?@??OO#48!26?G#27!7?A$#243!16?E??@!8?G??G#233??G!8?G!8?O#223!6?_#185!41?_#207!22?A#195A!9?@!5?E#191!22?G!8?C??A#233O#209!7?O#72@!4?@CA#207G#30!4?D@???O#210!34?@O??O#54???@?O#212???CC#206?G#17!4?G#82?WCA#6!5?_???A#30!4?@#48!16?A#79@#19!19?b@?_!8?@AAA#20!19?O!7?O#241!9?O#28!4?@#4__O!7?@#82!9?@#74!29?@#34!7?@$#244!16?_???@??O?O?W?G?O#196!5?@#198@#197@#244!101?oOSc??WGGC!5?AA`DG#226!6?C!4?C!7?G#77!5?A#164?C!9?O??_#167!37?G#204?@??_#56??G#48@#211!5?@#84!9?A#14!6?C#48?O?O???O??G#49!14?@#4!22?C#9@#35??@A_??A#50!24?_?O_???G??B#81!11?A#84A#20?A!5?_gSwW!4?G!6?L?@??C???K?@$#234!37?A#236!129?A#209O#185?@!8?CE!6?C!7?G?o#154??O#232!40?G#233!12?O#49!18?_O???`??C#78!42?G@#155A#31!27?G#55_??O!9?OH???i!8?S$#186!174?CC#242!10?G#41??@@!9?G??oGg?_#13!65?G#33A??B#83!46?O#46!30?O#51_!5?CE!6?C#3!8?CC$#192!174?A#176A#162!13?C#156C#197?G#55@#233_#37!161?G???_#1!18?_$#244!174?_#199!14?G#229??_#236O#35!163?A#34?O?_I@C!4?C!4?@!5?@?@???@g???O??C$#47!357?C#17?_???O???_?A$#56!357?O-#227UCD@!5?@??A?pgW!7?@@???OC!6?g!4?C??A??G??G??O#222O??O#197A#202C#241??A@#208C#227??OO?G??K#197@#195@@#208??C!4?A!9?A#227ACP??G@_!4?S?O_kv!4?FIi@P!5?IG`wwae??O!9?_B?@??_??_??A?A?WO??ESRWOA??_???A??_!5?@_!4?GG_?@?OS?_cAKE???_W?G_oG#72?D?@A#93G#49@??G?MK#80DC#52@#167A!9?C!7?A??CA?C#37_?C??@!8?D!5?K@#16A??_AOc?GA@A?AEC?@!7?O_C#3_!4?_???O#16@!6?QA#36G!4?G#4@??C???@@#16SA?ca]HG#36?o??_g___!7?Gc?C??@!6?CAC!4?G!4?C!5?C_GCACA#17c???OOA#7?_!6?oAA?CC!4?O?_#14???G#27??O$#238GgO?C??C?A??s?A@DeK@GEKc?o?C??AG!8?b?A#201G?C#203_!9?OO???G__??_???C#198CA@??AA@#241??G?G_#232O!7?@!7?_??G?O?@C#233!4?gOC#197?_#198OC#209C#225?K@@?A?@#242!10?_?O!6?CC!5?A?@???@@??C!5?@!8?O??_!5?A#230?A#245@#198?A!7?@???GAO???_?K#78@#156C#47??@#41C?C#23C#65@!7?A#205CwG!9?A!5?_C#170G#186@#163?@#100O#36O!4?GWA!8?o?O#31_#18_C!5?G?`OPOoE?GG!8?OGAA#1???_O#15???B??B!4?_#34G#20G#1!7?F?A#9?_??W#18?OvAC?O?KC@qS!4?A!8?Oo_!7?G??G!5?C!8?C??cWGgG#51@#5K?K?@WGCA?Oo_?GG!6?O???Co?O??CA$#244@??SG??AA??_???A??AE?G@GG?GB?@#223?C??C???_?CC!9?OG!5?A!6?_?_?AO!4?A#233!8?@??@#203???@#198C??_#201c??@???C!8?P!4?_O??O!4?@@#225!13?C???KC?_#232?OE!5?O?`?_A!4?O!7?G#195G#244?G#245??G#236?C??c???C??A!5?_#201A#185@??@!9?O!7?oO_#162O?O#156O#97O@??C#210OCO?AWu?@???C???A??_#199???C#159A#56H?OA@!8?O?C??C#81A#4?_G??E#32__#34@???CC?@O?WG!9?G#19!9?@!7?@??W??O#2??gO#26_!5?@#31_G?@!6?C#53@?K!6?C?O#16?G??O?A!4?OO_O!8?OA!6?@C?_#21??@#10?AGBNKCDA?O#0?@!6?B#15_??@!7?D#36??_$#231_@I?@??G!5?W#206?O#191!4?_#237?A#236?C!7?@@@O?A???@?B??!4A??_??_?@???AO?Q@?KG?_?CC?OsCOGH?EOC?_s`C??G?G???G#195?@@#222?G#228C!4?O?@!5?A?@?G?@G!8?G?EEGVCKGA_?@G@?O???G!8?GC!5?CAGWw#234!9?_#243G#228?O!4?G_??C???O?GO#238GW?O?_!5?C#94@?A?AA?@@#27G#33C#83@C??OG??@#233A???A!7?OO?OC#192?OO_OG#43?A???G?C#26O?CA#2E#22A#84_!4?A#98@#28??@!4?EA!9?_O!7?_@#10_O???I?OcCAC!5?@?C_!5?K#0?N@#6??O#15?_gK?WG_EQ??A#20@?C!7?_?_O?G#14?GgG???_?CA??A!4?G!7?O!5?o?oWOOO?A?A???_!7?_#34O#35EAG#17A!4?C???_G$#235?O???aQ@CC???eCCa@@G??_??Ga_vgxrIGG@_O?A?a`C!5?g!4?_!6?CCG??P@?c??GGO?G?AB??@?W?O?G@??O??GX@?OyOqPpVHCA`GKAGUk?K!5?_AK_?s_ADA?GPHE?Q??_ESo@GC@?@OQghW?wCPG??KP?h`___?FFEaFPD_CKGO|Br_@_?LK`@CHwKk_mA?GaO?_??C!4?o_#30???C?A#43G#74GA???A#77@#168G#230g??gGC!6?G!5?G#52!7?oA??A!8?o!4?C#8??C#15A??P?WY@_??O!8?O??G#30@#19C#6!12?O#26?O#45G#18_?C!5?@#8!4?O???C#12C#13?@#34?@!4?oGOG?OA??CE?B!4?R?G?A!5?G??D?G@?_!5?A@CGG??AO?C#12!7?_#3@O???MMBh!8?_?CGB!5?@@$#223?A???@??GG@B#231!13?AC!5?_A_e???_???_???CCCy?AAA?@?@?P??CGO?Q`G?OO??G???C?@!4?F??aa??OPP?OAa?C!7?G_!5?Q@G?@aaAC?A??QO?@C@?O!4?AG?i?_???_?I!4?_?O#207A#225?C???@!5?_!6?_!9?G??O?AO??AA!9?C?C_!6?_#48!5?A#79E?E@AO#90G#208O#187?@#202EA!6?_!8?G_?C#55???M@LCC!8?GGAG#27_#14!4?@#3L@#33G#11??CECa@?G?aB_hOo?A?@C?PHHCHDMMB??AC@@d?A_OUf~}mvqO??}^B?AA!6?@A@C#50??@??A?_!5?C?_#5??CSuy??@#32?H!9?@#52?_?O!6?A#1!11?WG!6?@???B#36OOC#8???G?W?_$#242??_AaCC!4?O!8?F@O?_?OOG!6?GE??@!8?@@@??@???_?__#243??@#224_?C???G@#207@?@!6?C!8?@A?eaC#223?_!4?K?A!6?O!4?_O?cO?K!5?@?G??@!4?GG??C!4?C?_???C!6?CCO??_??A?C?C?_?C???A??B@???AOWO!4?P??@?A!6?SAS!4?O??GO#191!5?_#163O#54@H???A#229_#212??_???@_G??W?G!4?@#74!7?@#49_!9?_??@#79GC??@#31!9?_GGG`O?PC?C!5?cA#46O#28!14?_#47O#31OO#46?@#14!19?O??C?A??GA??@#55g??O@#49@#74???@@#17?O#2_??A#3@!9?_!9?O#11GG#20A!5?!4_#11!4?__#13!5?OWc!5?P_??K`_OG?C$#228???G!6?_G@@G!4?O???@?C@#243G?A#226??OOA!9?__K???C!4?@???@!6?A???@??A!7?A!9?C??C#238??GCKCA???gCb?ACA??@AG!6?O?ACW?CCACO?G_`_?R`?W@?OOWAIW`@TU?G?Gg??ABMQ?G??@???@@_CGW_O?CAGK???C#195!4?A???@?@!5?G?IG?O_?G??G_o#222!4?_#203??_#188_?_#191???@#232OC`!5?_??_WgQ#81!9?G#34_??A!9?@#43OGG#14!14?GC#35???AA!5?A?G#30!16?C#35C??@#28!19?@!4?G#56???O???G?CA!4?A#8!4?H?W?__!9?_CC!4?C#46??@@?@BCA@#12!14?__!6?_JC?B?G?vW$#245???_OWG!5?G!4?OO!4?OO#234!9?O#239?@?@!5?@??@!4?@_@!6?@AO?GG?C??__!4?_??___AA_??UW[W?G#209???A#225_!4?A???WG#237!7?@#244!8?OW!7?O!4?_?O@@!5?G!4?A?@?C??P!4?g#231???@???WO??aOD??OA???SC?_?oS_GsC!6?c!4?_!7?O!8?__#166??_#101O#206!4?C@???E?BC#169@!6?@C??O#48!4?O#35o_?GA@!4?AO?`?O?A!4?C#19!8?_`#47??C!5?O#56!19?G#52G#19!23?_???_?A!5?@!9?C#4???CCAAA!9?G!7?_!9?C!5?@@@???O?C!9?bO$#201!6?@?O?G!8?_#245!16?W!8?@#224?G#230???A#195CCC?C#245?_???@#191!7?A#185A#242?_???GgoOOW_???A#197!4?A#242!13?A?_?@!4?@!8?o_#232?c!7?_???@#195!5?AAA#243???_#201C!9?_!6?G?A!5?G???@#232!6?GA?@#207!5?A#244!5?C?O?_#154@???A#228G#155?A#192!21?@#211_?GU?C!5?C#240?G#53!11?A#27O#30_?D??_???_!4?O!6?@#8!12?O?G!9?A!9?aC#17!27?_!6?@@?@#84OG!9?@#9??@?C#28???_!8?_!5?@@#53???@#6!20?CA!7?O!6?A$#209!6?_#195__o#225SC!8?O_?A!8?Cc???CA!5?C???_???O!6?O#230!13?O#245__?OC!4?C#230?KGG#224!4?@#244!13?_???A#245!45?CBA#191!4?_#195_#198!6?C!4?OO???AB#244!7?@A#232!11?O#209A#168@#164@!7?A!5?GG?_O#226!13?O#209??HOB?A!5?_O#28!12?@@#9g???P!8?G???O#7!15?KAA@!8?C?@@!4?_?I!8?@!4?_?@?_A#37!16?AG??__??@@???B#15!4?_G??O!9?G!5?_!6?O#16!19?G??_!7?AC$#198!7?O#232@?A!6?G_??O??A!4?C#241!7?@!8?@#243!4?@#234?O!7?O!13?C#246!81?C#162!49?@#187?G!6?C#189!25?_!9?C?B??A#11!8?_#6OWWHK!9?_oQ#26!16?@???A#58!53?G?O!4?AI#7!9?@??@#12?G?CA???O?A#49??G#37@R?G#33!25?O?G@#32@!7?BA$#196!38?G!6?O?_???C???C?CC#93!150?@#95@!5?A@F#158!24?O!6?@!6?_G#54!7?C#32?A#13@??B!9?C?G#27!15?_??C#2@!5?__O_?_??_??W??B#59!33?C???O?GOC#13!10?OC!6?A#20@@#2_C!8?O!8?A???__?SM@?[EF?@@#37O??C?C$#207!38?OO!5?G!9?A?A#45!153?!4@!5?CA?A#190!19?GO???C!4?A?Wg#42!11?_#7C??C!7?OOG_#9!16?AC#4@!7?E???O?WwKw#51!38?OC#32??A#52G???A?CA#48!11?A@GGAAB@?_WSA#31C#19A?_O!7?@#42!16?G#48?G#20_?G$#195!38?CCO#191G??O#185!9?GG??GGG#159!150?A?A!8?G#101!20?_!5?A!5?A#41!13?_#3O#33?G#48?A!4?A#5!21?@G!6?EOXA!7?GC?A???_!9?G??Kw!9?@#46!7?O!5?_?_#50!14?_??O!7?A#83_#55_#50!31?G#41A$#188!39?G???O!4?O!4?G??GG?C#201!151?O!8?O#165!23?G#204@#237o_G_C@#83!23?CA#74@#43!24?_G#36_#35!57?C#82?OC#37!16?P#96__#35C!5?W??G???A$#185!40?G#187O#186O!4?O#244!8?_#242!156?C#84!33?@@!7?@?`C#6!42?C#54O#74C#81!57?@#96??G#56!17?O#17C@?O?A?@@#79_$#197!42?G???O#159!200?A#98A#79@#55!53?_#78G#55!79?O#59_#58GC#9?O!7?A$#229!44?A!6?_!8?A_G?CA??A@!4?A!5?A??@?@!5?__#208!154?C#51!138?O$#198!44?G!4?O!6?A#53!329?C-#235EFDR}UmKIa?A?A???w_c??@OOOP@AA?@A?@?@@_?AQABAA?C!8?G?AAO?GGBE~BAGAEAA??@??GWO?@DWCDD??C???W?g???O??P???A??G?@@??OoPO@BKGOk@B@??_QCElKk?Iow@vjQO_o??wppUU{AJ[g`KU?XaJ`WKS{ccCv__U{?O@gOHG?SWOSB@?G@AG@CKKCAPCC\O_?@B???M??CC???CAAC@JG@!9?O!6?O#205C!4?_#55KC#43OEUGGO??o!6?@!5?O?G#14???@?A!5?A??@!8?@#2?@N@BB?B?AA#18O???@#31??AA_O#46G??w#4?@#16oG??_?A@!7?O!4?GG#4?oI?KA!9?@!4?A#81CC#34@???CCW?C?O!5?A@??A?G???@!4?CCGG#8?G??C???G!7?@aC#15C@@$#238@!4?_OOCScPEC??k?C?A??B@B??@?B?@!5?@_?`?OOo#229?G!9?G???CC#236?ox?CLDC@@?@@!9?`@!7?O_#222@???O#237?C#228_??_?__???A!8?GO_??C#245GSSSCG#243G#228!4?O_!4?C__!6?G@G@Gc@?AA?_!5?OA?GG@!8?C_#232O!6?G???A???G???@#195!5?GG??O_o_@???@@@#201@???Oo#230C?@??O?AA??@?G?K_K???O#155G!4?_!8?@#55o?IC#32?A?_!6?@Q?g?G???OC#13C_O#26_OK!4?_O!5?_?O?GGO#27G???_#36!5?G?C#15!4?AsWg!4?_#17@CGH?_?G!6?D@!5?_?O_?G?G#32CCA#18@??@!4?g#17OCGO??OOK_??_p!4?C??SC#21@?C#11@?B`!4?WzF?O?CB??@H#17??W??_w]$#227OWo!7?O???AA??@?_x[_cCCO?!4O?_!4?Q??GCC#226???G#245??AN[__ao_!8?G#227O??g?GC?EO?P@g@?IA?A?@B!5?OA__``@@__?_?@?CNKIH??@GG?_#242!4?O#246?!4G#227__???O_O!5?@BA?o_?G?G?ACOAA?@!6?_??@OOIG?@?A?AC?A_??AA@_OIGE??CC??@`gC??_`A@W?_C?OIGA??WCaL?G??OEgO?_C#208O??B!6?A???G#168O#101KF#56@A#52G@#35@E@?_?C!7?O???__!4?c#31?A#15?`!5?GLK_o!5?_#3!5?C??CAC@#16??O_ABC#28!10?C!4?OC#37G?G!8?o?A!4?_!6?C@#16G??C#56G#15?@#84G???C#20_OW?__!5?CAA@??M?_!5?GA?C!5?G??O???G!7?_#32!4?K??@$#231G?A??G#195?@@#242GGg???_???A?AA???A#198?GKcK??!4CK?C#207G#239??_@BoovG?__@@TBPPBqooG!8?@#234?C#226??O?C??W!8?@#196A!5?OG#242???A@POCW?O?OO?_#225@!4?O#201!8?@#198@#231!15?@??O!6?o?_@C?_@???M@_!4?O?DC@@CCg@??o_?CCBC?@@KCAG@@???a?AA??A?G?HI?_???cc_???@??AG?cc???_??_SOoOW#209@@?G#190@@#165A#95G#57A#74GA!8?_#84I?O#7??CC?CA?C@#52A#17!6?O#19???A#8?B!5?G#6__G???I?MGCBGD@?BA#9C#14O#8!13?@??FHG#34O?DC!6?O?@g!7?o??O#59G!8?OO?GOo?C!8?@@#50?@!4?_!7?@#51A?A#0?_#3!4?BA!6?@#1o#34_!7?AIA$#198_#225_G_??@#244?O??CG??OOEA!5?A??A?@?A!7?@_?__???A!5?@A!5?_#226?@#225!5?_??OG???oCA__#224?O#230?O#245?_wo_oo_#198?AEC#245!7?CCAMG???O__#201EC#187G#232???G??O_!8?@@OO#242!12?CCA#225???@!7?@#223!5?AB#201@?@!7?@!9?A??o?_!4?a?A???G!4?O#242C!8?W!7?C#232???ABP!4?A??@?@?@#211?EA#192AAA??_#53@#54C#37W?@!9?_tO!8?E!6?o?_G!8?@#5C?CA?_#28???_?_!7?O#30!12?G#9?BO?C#18G?AQOS???G???O??O?_#2G?C#50W?C#48BB?o#49O!4?C#98?G!4?A@#36?@??ADKC???A???A?G?A#2OG??_GO???A?o?@#6A#28???@!6?AA#37!4?C$#228???G@@?a_?B?@!4?@GG???C?GG#196??_??C#236@?@!6?P?H?_?E?@#243SO?[]#208?C#234G??@?DC#198!4?_!5?_?_?A#207A??_#243??O??O#225?B??@??SG@?A#243???A?@???_???_#231!5?O!8?@??@?_#232!22?@??CG??O_?C?C?_?A#225A!9?A!8?_CE!4?A!6?OO?KC???@A?@???@???GoO???__O???_#229_!9?o#197?G#169C?CO#158O#91O#82O#81@#80_!6?@#100W#73@@#48CAA!7?G?G_#52!8?O#12?CA#35?B?A#31A?@@!8?_#19O!5?_???@?_OAso|F~[!7?_!9?@!5?G!7?O#12_GEA@?AA#5??@@#82GAB@#14_O??_G!4?OSs_Q!5?@@???C???B???C?_??_OC???G?A?GW_a_??_$#232???C#201!5?@!5?@!9?_#195___?G?GGG?G??C#242@!5?O@@?UH?A!5?_#207!9?O#223o?_??O?A???EgC!9?@C@A?EK?K_!6?A@@??C?HC?AAAC_#244!40?G??@#198!7?@!7?@#223??@#244C#237!4?_#198???O?_??_Oo???G?_#185!7?O???@#198@?A??O#234???G#233!5?A??K!4?AD?_!4?O#200C#203_#167@#188??_#47??_???O#11C#49A!4?W?_!7?C_!7?_#9!4?_!9?oc#8!7?_!5?CC#7!16?E#5@!5?_!8?O?A!4?@#46_O#11?_#35?O??G!4?CCA#54A#46?O#19_#99A!7?A#57A@#5??WK!4?_o!7?G???aA?C!5?C??EB!5?@$#245!12?Ow{K#223@!5?_!5?C!4?O???O?G??C#203!15?C?G#230!9?G#201?O?O!8?A!8?A!4?@PAE#246!4?GK#195???A!5?AAICCC#236!54?_??A!6?C?@K??GG#195??_??OO#244!4?G??@!5?_C!5?_o#236?O!7?C@#210!5?A???CC!4?C?G?@#207_#228?_#41!5?GO#33C!9?G!7?O??@#27!14?@C#55@#15!15?_?G?@GO#31!16?__A#48G???C!6?@#53@!9?GC!8?__?_?GC#55G#52CA??A#32@O#81A#4?_#18!6?@?C!4?A!4?A???S??G!4?A@???C?CO??OO?@$#246!12?_#206@#198@#231?A???D#191!12?C#234A_?a#221??G#224!18?GC#241!9?@#195??_!8?E#241???G#238?GGG?Ow_?g!5?GM!4?@Cg!5?O_?_?_?BDNQac?a_A?QWAbBNDND}GOKKSFGGDCE__!4?OOo?ME[oO??GA!4?Q?_?wg?OGAEBH_!7?@!6?S??@???cA_C?@?SSG_G!5?AA#211!7?@@#239[?G!8?O#196_#27!9?_OCAO!8?GS`_!8?o#11!12?G?C?Q!5?_OO??OOG_oJI?ky|\NdBJA??a?Bc?OA!4?_#20??HJFD!5?G!5?Ab@#13??C@DCA@@?A#94oO#57_#37!5?AAC!4?G!6?OO?C#1?M!4?O?oO#9!6?@#36!4?_??!4O!6?C$#232!18?OP?C#231!13?Aa?A?O??G?KK??G__!9?GAA???o?A??CCIIGHLC?@??_CA?AEC??C!8?QO!6?C#198A!6?C#226!59?GGG?A!8?@#223!5?OO??O?Ao!6?AP!8?A!4?W??CO_!6?GO!6?_#238C#30!16?A_CG#158?C#91C#54E?@#28??@!9?PO@!4?C?_???G#7?__OO?IKW?_O??C??_?CD#13!21?@???A@#12?OO_#55_?G#15?_?_!4?G#58?G!5?_?OOWG#28!9?G#12@???_??_??C#52??KG#10_!5?_O_h??O?@!5?O?_?aC!4?_C$#225!20?W??GG??KO??__aOO??O#222!20?C#196!13?O_#197???_#242!12?G#244G?G!9?GO??O?O?_??O!4?`???A?@GOa?AOC!8?A!5?GGBB?A#233!25?O!4?Q!4?O_#228!8?G!5?Q??aG!5?OI?C!4?A!5?__!4?CGGB#209G#212?@!7?A#7!14?_#26A!9?G!4?O??@#4!17?OOGGC??BSO??GG???C#36!24?S??G??EC?A??C?@!4?_?CCA?G#27A#51??O!9?G??@#16??O_g!4?G?@!4?O?G?@_O!9?_???GB??G???K?C??G?O$#185!35?G#201O!4?C#234!50?C#188!106?_#187_#196!24?A#226A??H!4?O???C???__A??G!4?@#15!12?@??G#166A#79?W#81@#34???_!6?O???C??CKO?`O#30!5?@#32!42?O???A#51!4?CEA#28?_???OO#21!9?_#96___#8!12?@???_???G!5?AO#3?_e#19?GG@??GCS!4?_??c?AO??G$#188!37?G#242!162?@#202!41?@!9?_#36!12?G#9@B!8?A?A@#2A@#36__OKG#14!58?_@Ao_!6?CUDACBB?@#29!7?A#48!17?A@G!8?C#13@@#4??O!7?_??@[??C???G?G@!4?@B$#240!242?C!5?B#45!17?G#31_#82???_#96_#16!4?A!8?A?K_?@@??GO?O?A?C?A?@#46!46?w#8!6?O#3C#27!39?@#43?A#57!6?A#21!14?O#46???__$#242!242?OO#236GG#237AA#18!29?@!9?A?TIAMJ?@??AC???A#50!49?A$#245!242?G#224??_!5?_#23!25?G#6WGG#30GA$#3!279?CDA#45C$#13!279?O-#235BCDA!4?DF?CCA?@AB@@G@FHGAIMCUAAG!7?B???DC?G?C?C!6?GG!4?KOABHBRYIEWO?AG@DDDHJCO#243!4?HGKGCGWK#235?BA?P@GA??@C???GGQBL?J@!5?O?OIHXP@?SO??OSI?CIABF@AFDAAQAC@C?@EBD?JXXXOAO?H?P?@#228!5?A@?A@?C??A#234??C#188??@#195BA#242!4?C??A!4?ABC#230O!8?G?A#245?GOGC#225?G???O???CC!6?G?CO!4?AC!9?O#26A#32@!6?C???KAS@CO!5?B@G#17C#13O#8@!8?C!4?WG?DICC???O?O#36?PO?C!4?QOO#16I!4?@???C#55GS?C!9?@!9?K#51@???O???B#34A!4?G?C?O#4BBD#37???@!7?G#5@@A?O!5?C#16@?DA!9?CG!4?O??A$#227WP??C??C!6?AY!7?ABC??A??@B!5?B#207G!7?@#246???GGWLB#244?C??@#241?C#238G#234OG!8?C#224?C#238??A!8?@@B?A!4?@@A???AAA?!4A!8?@?AAA??P@BDDAEEAG@E?C?B@@X@@!5?A@@@?X???A??OG!9?@???@?@@@??O!4?O!5?O!7?E?CA!4?@??B!5?QF!5?KEKCW?@D!4C!4?O[?OCC#222??@#224@#207@??@??B?C#95@@#77@#35@!9?A??G@G?G?O???C#79O#48@BO#29?CG#14?A!8?G!4?CC!5?D?Y??C#17?G#31BEA@G???A?O!8?G#46O#82G#36OXMWDW?@?A??AO!5?K??O!5?[D!4?OG???G!4?GYSE?EO?G#34G?A#18A#10?@@@A???@@!5?CA?@C!4?A#34C#17@?@@??C$#236C#238AA@?@!4?AA??@!4?G@#222???C#207O#196@!6?C#188C?C?O#210CC#239O?GGGW?G?W@B!4?W??A@BBB@?OOO??SO??OWSWWWOO?O?OOOWCC?OO??O[W[WKKCC??COO#243?@!8?WGG?G?C#234!5?O#232?I#245?E@#227???A!5?GHG!7?A@!4?@?CC???GG?AA?AASQCIOOHO?R!4?AC?A!5?A@O?O@??BG??G??A!8?@P!6?AW?O???C!5?G?O?DG??O#195W!4?@!4?A!9?O#156?OO#48C#56A#18?C?C@?B!8?G!9?O?O??A!4?O#13G#19?A!4?AAC@@??]A@#15@B@U?SSCO??A#34??@??E??@??CC??GO???C#37OCO!7?A#20C?QQKO??O!6?@I?A@@#14?A?O@?A!9?C!4?O!8?@???ACGCO$#225?G??A?C#198GG???O#226?O!8?O!9?@#198GA#197A#228@#208C?W#238BB@#243?O???O??CBAO?@B#229!6?!4CGC#227?@?A@A#233??C??GCO!4?CC!9?C#227@???@P#236CC?GGWWOO??OWS!7?GGOO?GKC?GWWK?OW?SCC!8?W?C?CC?G?SO!4?S?OOO?@??@!9?O?O???O?W???WWKG??CC??CC?C?KKG[KKGK!4?Q?A??OC??G!8?@#242!7?G#226A???HG??C?A#224?C!7?O#33@?G??OO#28?A!9?A#157O#33O#9?OCA?@A?A#31??OACB#9!5?A@!4?C#50!5?O#46AB@[#28?K???A??O#4AQ??A!9?O!6?O#56!4?W?CG#50O??O???@#19?@#32?C?CGC???G#50!4?@#19???@#8A#9A#32CGC!9?GC#18GC#31@#4G?@EA#5@!4?@!4?OA#4@$#223??G??E!4?GG?W??O!7?O@???@?O??@@?A#202G#230A#233?G#236CEAA???@BA!6?CO[WOS?AP!6?G!4?A?A!5?@@AA?OO#241O#244??@AA@#231??E?OO?@@W@??AAA?C!4?@!5?O!6?R!5?GCC??G??C!4?OGCAA@A???AACA??CGA@!5?GIKA!7?I?@@?@AGCGGO??A?A?GG!4?G?G!7?@B?C?@??@@A?APBBG???AN@ECGHC!4AC??S?O???A?GG???G#27A!4?A@C!5?GA??A!7?C#26?G?C!8?@!4?AO#34???@!6?GWG!4?K#18?G?@K@!6?C!8?A???C?CC?GDA!4?O!7?O??@?O#16O!6?E!8?@@#4???CEA#2@???G!7?CSO@#50KC???OO#18C[G#13B#2O$#207??O#231C@!5?C??CCCG?OO??O??GCO??CC??OO???@?C???@!4A!16?GGA?GD???IG!4?AAA?JMMKG#242?A???@O#246C#226!4?@!5?G??CC??CC#233O?C#242?CCCS]CK!9?@#228!6?A!9?CC#244@!5?@#230C#223!4?A!4?AA?G?WC???O?GC?A??CK?G?G@?A???@??@???GGO!6?A!7?O#243??O#234@!5?G#233??@!7?A#201!7?@AA#197??C#191C!7?C#162C#13A@!4?@A#15A!7?@?@!5?BABGAKAW?B@O?A!5?CO?GO#2???A@#53!6?C#55C#21?@#27???CG!6?G#35O!4?O#50@?@!4?A#53A#32O!5?@A#58?AG@#53H?GK@#12O!8?B@BC!7?G?CO???OOW??W#0W^[#3A!7?O#51???OP#20CO???B#15??C#8G$#188???O!4?OO#242@!6?CEA?A#234!4?O#187@#195@!8?S?O#242??G!6?G!6?W??B?EG#226!6?C!5?C@C@#229!5?C#245!7?@BFIE#222!9?G#203W#224O!4?C#245@@!7?O??A#226!19?OGOO?OOK#225G#242???GG?OGOG#225!4?CA!4?G?Q?O!5?A?@?C?G?GC?@?A???A??@???O!8?@?A#226???OOO#229G#201!12?O??GO#198!12?@#236C!6?KSO!4?O#185G#49AA?GG???C@!4?C!4?OGC#7!4?@?@CSOOE@#27C#28C??@#10!10?A@#56!5?O#5!8?O???C!4?A#56I#79A#57@!9?@#46@#19?@@#83!4?@#59AE?LFB#46A???@#15!4?A!6?@#51???C#1!10?O??F?BO!7?GIG#53??G@#12?CA???A?@G$#195???GGG?O#232A!7?@#225O??O??C!6?@??I??@!9?@#226C@#225!24?@@??@#244!33?@@???@!7?A??A?A?C!6?@AB#198!6?O#230G?C?G#245!5?OOW?O#195!6?A#232H@@#198C?C#221?C#232!6?@!6?A#226!14?O?O?C#196O#244??@??@!4?@!9?O?C#229!6?@?A!5?@?@!6?G@A?OQ!5?G?O#239?O#163G???O#43?@?@#34??GA!8?G#30!4?O#5@???C???@??O!7?OW?H#59!7?G#30!8?@#9G#14BA@L??@@!6?O!4?GCA???G#96!6?A#81O#14!4?WG???@#48???O???OOC??G??O!4?C#7??G?C!4?G#19A?A?AA???G???I???G$#185!4?OOO#201??G??G#229?G!6?O!8?O#224?SO??O#245!5?OOO??OO??OOCOKFAOC#232!51?AA#229?C?GG#227A!5?@#232!24?A#224O??O#241!7?G#226!10?C!9?G???A#196GC#198G!5?G!5?@@#229!4?G???C???O#224O#233?OC???O??C#239!14?AA!6?AA#203!7?O#196???A#159@#233?OG!8?O#37E!4?I??O@?W?WO?A#159K?O#11!7?A@??@??@O??BB?@@AG?C?OI@!5?O!4?C???A!4?O#91???C#37???QAE?C#12WG#98!13?C#99A#3!13?A??C#5A#8!21?C#11A?B!4?@#59!4?A#21A#48???G$#228!6?BB#187??O#245@@@!5?CC#239!8?G#201!4?A#185CGG#229!8?C?C?C#228!68?A!5?@#201!48?C?CGC???OCCC!7?C!4?@#228!8?@??B#239???C!4?O?O#185!19?O?O#203@#93!23?A???C#36?@??C#16!10?AC?B!5?@?C?OW!6?GGGC!7?AC@?G?[C#12!14?G!4?@#48!6?GC#20G!7?@#13!25?C!8?OC?CS???G??K!5?O#27O#34G?GW$#197!6?G#196!4?O#244A!7?A#194!14?G#241!85?W#225?@#222!60?G???O#233?@#235@AOPQ?C?SSKCCO?GA?@??BYO??A???B??AKBAK?@?AAHO?A?GAC!4?AW@O?A??KS?C?GO?GGY??CO!5?O#54@!5?O?A!4?G#74?G#91C#97O#96?C#6!9?G?C?A#35??A#10G#13!30?G#8G?E#17!10?K!8?A??@!9?G?CKKAAFH???WG???@WO?IJ??KO!12?OO#36O!8?G?OGEO$#236!16?CGG??KG!5?W?GG#195!155?C#185G#203!21?O#185!8?@#201@#207!20?G#176O#223@G!6?OWO??G#34!11?@#52@?@???C!8?@#167!6?G#165C#3!12?G!8?OO#10!30?@#13!10?O!9?A#27!22?G#58?O#46!27?@C@???OA?O@$#191!242?G#74!26?AA??C#159?G#42@?C#226!13?G#4!13?GG!7?GGC!5?C#15!34?A!5?GC??W$#224!242?@#223!26?OG#227G#91!4?G#11!81?@!7?C$#188!269?C#55@???C#16!83?G@?OG?AI$#52!360?@!9?B$#8!360?O!5?OO-\tmux-tmux-f222026/tty-acs.c000066400000000000000000000177701511153563100155140ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" /* 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\234", 0, 3, 1 }, /* U+2524 */ { "\342\224\244", 0, 3, 1 }, /* U+251C */ { "\342\225\213", 0, 3, 1 }, /* U+254B */ { "\302\267", 0, 2, 1 } /* U+00B7 */ }; /* 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); } tmux-tmux-f222026/tty-features.c000066400000000000000000000267011511153563100165560ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2020 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #if defined(HAVE_CURSES_H) #include #elif defined(HAVE_NCURSES_H) #include #endif #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 *const *capabilities; int flags; }; /* Terminal has xterm(1) title setting. */ static const char *const 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 *const 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 *const 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 *const 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 OSC 8 hyperlinks. */ static const char *tty_feature_hyperlinks_capabilities[] = { #if defined (__OpenBSD__) || (defined(NCURSES_VERSION_MAJOR) && \ (NCURSES_VERSION_MAJOR > 5 || \ (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 8))) "*:Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\", #endif NULL }; static const struct tty_feature tty_feature_hyperlinks = { "hyperlinks", tty_feature_hyperlinks_capabilities, 0 }; /* * Terminal supports RGB colour. This replaces setab and setaf also since * terminals with RGB have versions that do not allow setting colours from the * 256 palette. */ static const char *const tty_feature_rgb_capabilities[] = { "AX", "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm", "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm", "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 *const tty_feature_256_capabilities[] = { "AX", "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", NULL }; static const struct tty_feature tty_feature_256 = { "256", tty_feature_256_capabilities, TERM_256COLOURS }; /* Terminal supports overline. */ static const char *const 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 *const tty_feature_usstyle_capabilities[] = { "Smulx=\\E[4::%p1%dm", "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m", "Setulc1=\\E[58::5::%p1%dm", "ol=\\E[59m", NULL }; static const struct tty_feature tty_feature_usstyle = { "usstyle", tty_feature_usstyle_capabilities, 0 }; /* Terminal supports bracketed paste. */ static const char *const 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 *const 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 *const 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 *const 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 *const 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 *const tty_feature_sync_capabilities[] = { "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;", NULL }; static const struct tty_feature tty_feature_sync = { "sync", tty_feature_sync_capabilities, 0 }; /* Terminal supports extended keys. */ static const char *const tty_feature_extkeys_capabilities[] = { "Eneks=\\E[>4;2m", "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 *const 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 *const tty_feature_rectfill_capabilities[] = { "Rect", NULL }; static const struct tty_feature tty_feature_rectfill = { "rectfill", tty_feature_rectfill_capabilities, TERM_DECFRA }; /* Use builtin function keys only. */ static const char *const tty_feature_ignorefkeys_capabilities[] = { "kf0@", "kf1@", "kf2@", "kf3@", "kf4@", "kf5@", "kf6@", "kf7@", "kf8@", "kf9@", "kf10@", "kf11@", "kf12@", "kf13@", "kf14@", "kf15@", "kf16@", "kf17@", "kf18@", "kf19@", "kf20@", "kf21@", "kf22@", "kf23@", "kf24@", "kf25@", "kf26@", "kf27@", "kf28@", "kf29@", "kf30@", "kf31@", "kf32@", "kf33@", "kf34@", "kf35@", "kf36@", "kf37@", "kf38@", "kf39@", "kf40@", "kf41@", "kf42@", "kf43@", "kf44@", "kf45@", "kf46@", "kf47@", "kf48@", "kf49@", "kf50@", "kf51@", "kf52@", "kf53@", "kf54@", "kf55@", "kf56@", "kf57@", "kf58@", "kf59@", "kf60@", "kf61@", "kf62@", "kf63@", NULL }; static const struct tty_feature tty_feature_ignorefkeys = { "ignorefkeys", tty_feature_ignorefkeys_capabilities, 0 }; /* Terminal has sixel capability. */ static const char *const tty_feature_sixel_capabilities[] = { "Sxl", NULL }; static const struct tty_feature tty_feature_sixel = { "sixel", tty_feature_sixel_capabilities, TERM_SIXEL }; /* Available terminal features. */ static const struct tty_feature *const tty_features[] = { &tty_feature_256, &tty_feature_bpaste, &tty_feature_ccolour, &tty_feature_clipboard, &tty_feature_hyperlinks, &tty_feature_cstyle, &tty_feature_extkeys, &tty_feature_focus, &tty_feature_ignorefkeys, &tty_feature_margins, &tty_feature_mouse, &tty_feature_osc7, &tty_feature_overline, &tty_feature_rectfill, &tty_feature_rgb, &tty_feature_sixel, &tty_feature_strikethrough, &tty_feature_sync, &tty_feature_title, &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 *const *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 const 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,hyperlinks" }, { .name = "rxvt-unicode", .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" }, { .name = "iTerm2", .features = TTY_FEATURES_BASE_MODERN_XTERM ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" }, { .name = "foot", .features = TTY_FEATURES_BASE_MODERN_XTERM ",cstyle,extkeys" }, { .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, ","); } } tmux-tmux-f222026/tty-keys.c000066400000000000000000001320731511153563100157130ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #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_device_attributes2(struct tty *, const char *, size_t, size_t *); static int tty_keys_extended_device_attributes(struct tty *, const char *, size_t, size_t *); static int tty_keys_palette(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 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 arrow 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 function keys. */ { "\033[11~", KEYC_F1 }, { "\033[12~", KEYC_F2 }, { "\033[13~", KEYC_F3 }, { "\033[14~", KEYC_F4 }, { "\033[15~", KEYC_F5 }, { "\033[17~", KEYC_F6 }, { "\033[18~", KEYC_F7 }, { "\033[19~", KEYC_F8 }, { "\033[20~", KEYC_F9 }, { "\033[21~", KEYC_F10 }, { "\033[23~", KEYC_F1|KEYC_SHIFT }, { "\033[24~", KEYC_F2|KEYC_SHIFT }, { "\033[25~", KEYC_F3|KEYC_SHIFT }, { "\033[26~", KEYC_F4|KEYC_SHIFT }, { "\033[28~", KEYC_F5|KEYC_SHIFT }, { "\033[29~", KEYC_F6|KEYC_SHIFT }, { "\033[31~", KEYC_F7|KEYC_SHIFT }, { "\033[32~", KEYC_F8|KEYC_SHIFT }, { "\033[33~", KEYC_F9|KEYC_SHIFT }, { "\033[34~", KEYC_F10|KEYC_SHIFT }, { "\033[23$", KEYC_F11|KEYC_SHIFT }, { "\033[24$", KEYC_F12|KEYC_SHIFT }, { "\033[11^", KEYC_F1|KEYC_CTRL }, { "\033[12^", KEYC_F2|KEYC_CTRL }, { "\033[13^", KEYC_F3|KEYC_CTRL }, { "\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[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 }, /* Focus tracking. */ { "\033[I", KEYC_FOCUS_IN }, { "\033[O", KEYC_FOCUS_OUT }, /* Paste keys. */ { "\033[200~", KEYC_PASTE_START|KEYC_IMPLIED_META }, { "\033[201~", KEYC_PASTE_END|KEYC_IMPLIED_META }, /* Extended keys. */ { "\033[1;5Z", '\011'|KEYC_CTRL|KEYC_SHIFT }, /* Theme reporting. */ { "\033[?997;1n", KEYC_REPORT_DARK_THEME }, { "\033[?997;2n", KEYC_REPORT_LIGHT_THEME }, }; /* 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 window size change escape sequences. */ static int tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; size_t end; char tmp[64]; u_int sx, sy, xpixel, ypixel, char_x, char_y; *size = 0; /* If we did not request this, ignore it. */ if (!(tty->flags & TTY_WINSIZEQUERY)) return (-1); /* 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); /* * Stop at either 't' or anything that isn't a * number or ';'. */ for (end = 2; end < len && end != sizeof tmp; end++) { if (buf[end] == 't') break; if (!isdigit((u_char)buf[end]) && buf[end] != ';') break; } if (end == len) return (1); if (end == sizeof tmp || buf[end] != 't') return (-1); /* Copy to the buffer. */ memcpy(tmp, buf + 2, end - 2); tmp[end - 2] = '\0'; /* Try to parse the window size sequence. */ if (sscanf(tmp, "8;%u;%u", &sy, &sx) == 2) { /* Window size in characters. */ tty_set_size(tty, sx, sy, tty->xpixel, tty->ypixel); *size = end + 1; return (0); } else if (sscanf(tmp, "4;%u;%u", &ypixel, &xpixel) == 2) { /* Window size in pixels. */ char_x = (xpixel && tty->sx) ? xpixel / tty->sx : 0; char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0; tty_set_size(tty, tty->sx, tty->sy, char_x, char_y); tty_invalidate(tty); tty->flags &= ~TTY_WINSIZEQUERY; *size = end + 1; return (0); } log_debug("%s: unrecognized window size sequence: %s", c->name, tmp); 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, onlykey; 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 primary 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 a secondary device attributes response? */ switch (tty_keys_device_attributes2(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; goto complete_key; case -1: /* no, or not valid */ break; case 1: /* partial */ goto partial_key; } /* Is this an extended device attributes response? */ switch (tty_keys_extended_device_attributes(tty, buf, len, &size)) { case 0: /* yes */ key = KEYC_UNKNOWN; goto complete_key; case -1: /* no, or not valid */ break; case 1: /* partial */ goto partial_key; } /* Is this a colours response? */ switch (tty_keys_colours(tty, buf, len, &size, &tty->fg, &tty->bg)) { case 0: /* yes */ key = KEYC_UNKNOWN; session_theme_changed(c->session); goto complete_key; case -1: /* no, or not valid */ break; case 1: /* partial */ session_theme_changed(c->session); goto partial_key; } /* Is this a palette response? */ switch (tty_keys_palette(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; } /* Check for window size query */ switch (tty_keys_winsz(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; } 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; } /* C-Space is special. */ if ((key & KEYC_MASK_KEY) == C0_NUL) key = ' ' | KEYC_CTRL | (key & KEYC_META); /* * 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 == bspace) { log_debug("%s: key %#llx is backspace", c->name, key); key = KEYC_BSPACE; } /* * Fix up all C0 control codes that don't have a dedicated key into * corresponding Ctrl keys. Convert characters in the A-Z range into * lowercase, so ^A becomes a|CTRL. */ onlykey = key & KEYC_MASK_KEY; if (onlykey < 0x20 && onlykey != C0_HT && onlykey != C0_CR && onlykey != C0_ESC) { onlykey |= 0x40; if (onlykey >= 'A' && onlykey <= 'Z') onlykey |= 0x20; key = onlykey | KEYC_CTRL | (key & KEYC_META); } 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"); if (delay == 0) delay = 1; if ((tty->flags & (TTY_WAITFG|TTY_WAITBG) || (tty->flags & TTY_ALL_REQUEST_FLAGS) != TTY_ALL_REQUEST_FLAGS)) { log_debug("%s: increasing delay for active query", c->name); if (delay < 500) delay = 500; } 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); /* 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 = xcalloc(1, sizeof *event); event->key = key; memcpy(&event->m, &m, sizeof event->m); event->buf = xmalloc(size); event->len = size; memcpy (event->buf, buf, event->len); if (!server_client_handle_key(c, event)) { free(event->buf); free(event); } } /* Remove data from buffer. */ evbuffer_drain(tty->in, size); 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, onlykey; struct utf8_data ud; utf8_char uc; *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 - 2); tmp[end - 2] = '\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; /* Convert UTF-32 codepoint into internal representation. */ if (nkey != KEYC_BSPACE && nkey & ~0x7f) { if (utf8_fromwc(nkey, &ud) == UTF8_DONE && utf8_from_data(&ud, &uc) == UTF8_DONE) nkey = uc; else return (-1); } /* Update the modifiers. */ if (modifiers > 0) { modifiers--; if (modifiers & 1) nkey |= KEYC_SHIFT; if (modifiers & 2) nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Alt */ if (modifiers & 4) nkey |= KEYC_CTRL; if (modifiers & 8) nkey |= (KEYC_META|KEYC_IMPLIED_META); /* Meta */ } /* Convert S-Tab into Backtab. */ if ((nkey & KEYC_MASK_KEY) == '\011' && (nkey & KEYC_SHIFT)) nkey = KEYC_BTAB | (nkey & ~KEYC_MASK_KEY & ~KEYC_SHIFT); /* * Deal with the Shift modifier when present alone. The problem is that * in mode 2 some terminals would report shifted keys, like S-a, as * just A, and some as S-A. * * Because we need an unambiguous internal representation, and because * restoring the Shift modifier when it's missing would require knowing * the keyboard layout, and because S-A would cause a lot of issues * downstream, we choose to lose the Shift for all printable * characters. * * That still leaves some ambiguity, such as C-S-A vs. C-A, but that's * OK, and applications can handle that. */ onlykey = nkey & KEYC_MASK_KEY; if (((onlykey > 0x20 && onlykey < 0x7f) || KEYC_IS_UNICODE(nkey)) && (nkey & KEYC_MASK_MODIFIERS) == KEYC_SHIFT) nkey &= ~KEYC_SHIFT; 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), -2 if an invalid mouse * sequence. */ 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 (-2); 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 (-2); 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 = 0, 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 + 1; /* 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 primary 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; int *features = &c->term_features; u_int i, n = 0; char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; *size = 0; if (tty->flags & TTY_HAVEDA) return (-1); /* First three bytes are always \033[?. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); if (buf[1] != '[') return (-1); if (len == 2) return (1); if (buf[2] != '?') return (-1); if (len == 3) return (1); /* Copy the rest up to a c. */ for (i = 0; i < sizeof tmp; i++) { if (3 + i == len) return (1); if (buf[3 + i] >= 'a' && buf[3 + i] <= 'z') break; tmp[i] = buf[3 + i]; } if (i == sizeof tmp) return (-1); if (buf[3 + i] != 'c') return (-1); tmp[i] = '\0'; *size = 4 + i; /* Convert all arguments to numbers. */ cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); if (*endptr != '\0') p[n] = 0; if (++n == nitems(p)) break; } /* Add terminal features. */ switch (p[0]) { case 61: /* level 1 */ case 62: /* level 2 */ case 63: /* level 3 */ case 64: /* level 4 */ case 65: /* level 5 */ for (i = 1; i < n; i++) { log_debug("%s: DA feature: %d", c->name, p[i]); if (p[i] == 4) tty_add_features(features, "sixel", ","); if (p[i] == 21) tty_add_features(features, "margins", ","); if (p[i] == 28) tty_add_features(features, "rectfill", ","); if (p[i] == 52) tty_add_features(features, "clipboard", ","); } break; } log_debug("%s: received primary DA %.*s", c->name, (int)*size, buf); tty_update_features(tty); tty->flags |= TTY_HAVEDA; return (0); } /* * Handle secondary device attributes input. Returns 0 for success, -1 for * failure, 1 for partial. */ static int tty_keys_device_attributes2(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; int *features = &c->term_features; u_int i, n = 0; char tmp[128], *endptr, p[32] = { 0 }, *cp, *next; *size = 0; if (tty->flags & TTY_HAVEDA2) return (-1); /* First three bytes are always \033[>. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); if (buf[1] != '[') return (-1); if (len == 2) return (1); if (buf[2] != '>') return (-1); if (len == 3) return (1); /* Copy the rest up to a c. */ for (i = 0; i < sizeof tmp; i++) { if (3 + i == len) return (1); if (buf[3 + i] >= 'a' && buf[3 + i] <= 'z') break; tmp[i] = buf[3 + i]; } if (i == sizeof tmp) return (-1); if (buf[3 + i] != 'c') return (-1); tmp[i] = '\0'; *size = 4 + i; /* Convert all arguments to numbers. */ cp = tmp; while ((next = strsep(&cp, ";")) != NULL) { p[n] = strtoul(next, &endptr, 10); if (*endptr != '\0') p[n] = 0; if (++n == nitems(p)) break; } /* * Add terminal features. We add DECSLRM and DECFRA for some * identification codes here, notably 64 will catch VT520, even though * we can't use level 5 from DA because of VTE. */ switch (p[0]) { case 'M': /* mintty */ tty_default_features(features, "mintty", 0); break; case 'T': /* tmux */ tty_default_features(features, "tmux", 0); break; case 'U': /* rxvt-unicode */ tty_default_features(features, "rxvt-unicode", 0); break; } log_debug("%s: received secondary DA %.*s", c->name, (int)*size, buf); tty_update_features(tty); tty->flags |= TTY_HAVEDA2; 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; int *features = &c->term_features; 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 \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(features, "iTerm2", 0); else if (strncmp(tmp, "tmux ", 5) == 0) tty_default_features(features, "tmux", 0); else if (strncmp(tmp, "XTerm(", 6) == 0) tty_default_features(features, "XTerm", 0); else if (strncmp(tmp, "mintty ", 7) == 0) tty_default_features(features, "mintty", 0); else if (strncmp(tmp, "foot(", 5) == 0) tty_default_features(features, "foot", 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); } /* * Handle foreground or background input. Returns 0 for success, -1 for * failure, 1 for partial. */ int tty_keys_colours(struct tty *tty, const char *buf, size_t len, size_t *size, int *fg, int *bg) { struct client *c = tty->client; u_int i; char tmp[128]; int n; *size = 0; /* First four bytes are always \033]1 and 0 or 1 and ;. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); if (buf[1] != ']') return (-1); if (len == 2) return (1); if (buf[2] != '1') return (-1); if (len == 3) return (1); if (buf[3] != '0' && buf[3] != '1') return (-1); if (len == 4) return (1); if (buf[4] != ';') return (-1); if (len == 5) return (1); /* Copy the rest up to \033\ or \007. */ for (i = 0; i < (sizeof tmp) - 1; i++) { if (5 + i == len) return (1); if (buf[5 + i - 1] == '\033' && buf[5 + i] == '\\') break; if (buf[5 + i] == '\007') break; tmp[i] = buf[5 + i]; } if (i == (sizeof tmp) - 1) return (-1); if (tmp[i - 1] == '\033') tmp[i - 1] = '\0'; else tmp[i] = '\0'; *size = 6 + i; n = colour_parseX11(tmp); if (n != -1 && buf[3] == '0') { if (c != NULL) log_debug("%s fg is %s", c->name, colour_tostring(n)); else log_debug("fg is %s", colour_tostring(n)); *fg = n; tty->flags &= ~TTY_WAITFG; } else if (n != -1) { if (c != NULL) log_debug("%s bg is %s", c->name, colour_tostring(n)); else log_debug("bg is %s", colour_tostring(n)); *bg = n; tty->flags &= ~TTY_WAITBG; } return (0); } /* Handle OSC 4 palette colour responses. */ static int tty_keys_palette(struct tty *tty, const char *buf, size_t len, size_t *size) { struct client *c = tty->client; u_int i, start; char tmp[128], *endptr; int idx; struct input_request_palette_data pd; *size = 0; /* First three bytes are always \033]4. */ if (buf[0] != '\033') return (-1); if (len == 1) return (1); if (buf[1] != ']') return (-1); if (len == 2) return (1); if (buf[2] != '4') return (-1); if (len == 3) return (1); if (buf[3] != ';') return (-1); if (len == 4) return (1); /* Parse index. */ idx = strtol(buf + 4, &endptr, 10); if (endptr == buf + 4 || *endptr != ';') return (-1); if (idx < 0 || idx > 255) return (-1); /* Copy the rest up to \033\ or \007. */ start = (endptr - buf) + 1; for (i = start; i < len && i - start < sizeof tmp; i++) { if (buf[i - 1] == '\033' && buf[i] == '\\') break; if (buf[i] == '\007') break; tmp[i - start] = buf[i]; } if (i - start == sizeof tmp) return (-1); if (i > 0 && buf[i - 1] == '\033') tmp[i - start - 1] = '\0'; else tmp[i - start] = '\0'; *size = i + 1; /* Work out the colour. */ pd.c = colour_parseX11(tmp); if (pd.c == -1) return (0); pd.idx = idx; input_request_reply(c, INPUT_REQUEST_PALETTE, &pd); return (0); } tmux-tmux-f222026/tty-term.c000066400000000000000000000646571511153563100157230ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * 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 #if defined(HAVE_CURSES_H) #include #elif defined(HAVE_NCURSES_H) #include #endif #include #include #include #include #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_HLS] = { TTYCODE_STRING, "Hls" }, [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_NOBR] = { TTYCODE_STRING, "Nobr" }, [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_SETULC1] = { TTYCODE_STRING, "Setulc1" }, [TTYC_SE] = { TTYCODE_STRING, "Se" }, [TTYC_SXL] = { TTYCODE_FLAG, "Sxl" }, [TTYC_SGR0] = { TTYCODE_STRING, "sgr0" }, [TTYC_SITM] = { TTYCODE_STRING, "sitm" }, [TTYC_SMACS] = { TTYCODE_STRING, "smacs" }, [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); } /* Log the SIXEL flag. */ log_debug("SIXEL flag is %d", !!(term->flags & TERM_SIXEL)); /* Update the RGB flag if the terminal has RGB colours. */ if (tty_term_has(term, TTYC_SETRGBF) && tty_term_has(term, TTYC_SETRGBB)) 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~."; 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, *errstr; size_t offset, namelen; char *first; int n; struct environ_entry *envent; 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: n = strtonum(value, 0, INT_MAX, &errstr); if (errstr != NULL) log_debug("%s: %s", ent->name, errstr); else { code->type = TTYCODE_NUMBER; code->value.number = n; } 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 /* Check for COLORTERM. */ envent = environ_find(tty->client->environ, "COLORTERM"); if (envent != NULL) { log_debug("%s COLORTERM=%s", tty->client->name, envent->value); if (strcasecmp(envent->value, "truecolor") == 0 || strcasecmp(envent->value, "24bit") == 0) tty_add_features(feat, "RGB", ","); else if (strstr(envent->value, "256") != NULL) tty_add_features(feat, "256", ","); } /* 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; default: fatalx("unknown capability type"); } *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_string_i(struct tty_term *term, enum tty_code_code code, int a) { const char *x = tty_term_string(term, code), *s; #if defined(HAVE_TIPARM_S) s = tiparm_s(1, 0, x, a); #elif defined(HAVE_TIPARM) s = tiparm(x, a); #else s = tparm((char *)x, a, 0, 0, 0, 0, 0, 0, 0, 0); #endif if (s == NULL) { log_debug("could not expand %s", tty_term_codes[code].name); return (""); } return (s); } const char * tty_term_string_ii(struct tty_term *term, enum tty_code_code code, int a, int b) { const char *x = tty_term_string(term, code), *s; #if defined(HAVE_TIPARM_S) s = tiparm_s(2, 0, x, a, b); #elif defined(HAVE_TIPARM) s = tiparm(x, a, b); #else s = tparm((char *)x, a, b, 0, 0, 0, 0, 0, 0, 0); #endif if (s == NULL) { log_debug("could not expand %s", tty_term_codes[code].name); return (""); } return (s); } const char * tty_term_string_iii(struct tty_term *term, enum tty_code_code code, int a, int b, int c) { const char *x = tty_term_string(term, code), *s; #if defined(HAVE_TIPARM_S) s = tiparm_s(3, 0, x, a, b, c); #elif defined(HAVE_TIPARM) s = tiparm(x, a, b, c); #else s = tparm((char *)x, a, b, c, 0, 0, 0, 0, 0, 0); #endif if (s == NULL) { log_debug("could not expand %s", tty_term_codes[code].name); return (""); } return (s); } const char * tty_term_string_s(struct tty_term *term, enum tty_code_code code, const char *a) { const char *x = tty_term_string(term, code), *s; #if defined(HAVE_TIPARM_S) s = tiparm_s(1, 1, x, a); #elif defined(HAVE_TIPARM) s = tiparm(x, a); #else s = tparm((char *)x, (long)a, 0, 0, 0, 0, 0, 0, 0, 0); #endif if (s == NULL) { log_debug("could not expand %s", tty_term_codes[code].name); return (""); } return (s); } const char * tty_term_string_ss(struct tty_term *term, enum tty_code_code code, const char *a, const char *b) { const char *x = tty_term_string(term, code), *s; #if defined(HAVE_TIPARM_S) s = tiparm_s(2, 3, x, a, b); #elif defined(HAVE_TIPARM) s = tiparm(x, a, b); #else s = tparm((char *)x, (long)a, (long)b, 0, 0, 0, 0, 0, 0, 0); #endif if (s == NULL) { log_debug("could not expand %s", tty_term_codes[code].name); return (""); } return (s); } int 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); } tmux-tmux-f222026/tty.c000066400000000000000000002346741511153563100147540ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "tmux.h" static int tty_log_fd = -1; 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_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, struct hyperlinks *); static int tty_check_overlay(struct tty *, u_int, u_int); static void tty_check_overlay_range(struct tty *, u_int, u_int, u_int, struct overlay_ranges *); #ifdef ENABLE_SIXEL static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), struct client *, struct tty_ctx *); #endif #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) #define tty_full_width(tty, ctx) \ ((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 #define TTY_REQUEST_LIMIT 30 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; tty->fg = tty->bg = -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; if ((xpixel == 0 || ypixel == 0) && tty->out != NULL && !(tty->flags & TTY_WINSIZEQUERY) && (tty->term->flags & TERM_VT100LIKE)) { tty_puts(tty, "\033[18t\033[14t"); tty->flags |= TTY_WINSIZEQUERY; } } 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_HAVEDA2|TTY_HAVEXDA)) == 0) tty_update_features(tty); tty->flags |= TTY_ALL_REQUEST_FLAGS; tty->flags &= ~(TTY_WAITBG|TTY_WAITFG); } static void tty_start_start_timer(struct tty *tty) { struct client *c = tty->client; struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT }; log_debug("%s: start timer started", c->name); evtimer_del(&tty->start_timer); evtimer_set(&tty->start_timer, tty_start_timer_callback, tty); evtimer_add(&tty->start_timer, &tv); } void tty_start_tty(struct tty *tty) { struct client *c = tty->client; struct termios tio; 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"); } if (tty_term_has(tty->term, TTYC_ENBP)) tty_putcode(tty, TTYC_ENBP); if (tty->term->flags & TERM_VT100LIKE) { /* Subscribe to theme changes and request theme now. */ tty_puts(tty, "\033[?2031h\033[?996n"); } tty_start_start_timer(tty); 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_HAVEDA2) tty_puts(tty, "\033[>c"); if (~tty->flags & TTY_HAVEXDA) tty_puts(tty, "\033[>q"); tty_puts(tty, "\033]10;?\033\\\033]11;?\033\\"); tty->flags |= (TTY_WAITBG|TTY_WAITFG); } else tty->flags |= TTY_ALL_REQUEST_FLAGS; tty->last_requests = time(NULL); } void tty_repeat_requests(struct tty *tty, int force) { struct client *c = tty->client; time_t t = time(NULL); u_int n = t - tty->last_requests; if (~tty->flags & TTY_STARTED) return; if (!force && n <= TTY_REQUEST_LIMIT) { log_debug("%s: not repeating requests (%u seconds)", c->name, n); return; } log_debug("%s: %srepeating requests (%u seconds)", c->name, force ? "(force) " : "" , n); tty->last_requests = t; if (tty->term->flags & TERM_VT100LIKE) { tty_puts(tty, "\033]10;?\033\\\033]11;?\033\\"); tty->flags |= (TTY_WAITBG|TTY_WAITFG); } tty_start_start_timer(tty); } 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_string_ii(tty->term, TTYC_CSR, 0, ws.ws_row - 1)); if (tty_acs_needed(tty)) tty_raw(tty, tty_term_string(tty->term, TTYC_RMACS)); tty_raw(tty, tty_term_string(tty->term, TTYC_SGR0)); 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_string_i(tty->term, TTYC_SS, 0)); } if (tty->ccolour != -1) tty_raw(tty, tty_term_string(tty->term, TTYC_CR)); tty_raw(tty, tty_term_string(tty->term, TTYC_CNORM)); 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_has(tty->term, TTYC_DSBP)) tty_raw(tty, tty_term_string(tty->term, TTYC_DSBP)); if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, "\033[?7727l"); 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)); if (tty->term->flags & TERM_VT100LIKE) tty_raw(tty, "\033[?2031l"); 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"); /* * Features might have changed since the first draw during attach. For * example, this happens when DA responses are received. */ server_redraw_client(c); tty_invalidate(tty); } void 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_putcode_i(struct tty *tty, enum tty_code_code code, int a) { if (a < 0) return; tty_puts(tty, tty_term_string_i(tty->term, code, a)); } void tty_putcode_ii(struct tty *tty, enum tty_code_code code, int a, int b) { if (a < 0 || b < 0) return; tty_puts(tty, tty_term_string_ii(tty->term, code, a, b)); } void tty_putcode_iii(struct tty *tty, enum tty_code_code code, int a, int b, int c) { if (a < 0 || b < 0 || c < 0) return; tty_puts(tty, tty_term_string_iii(tty->term, code, a, b, c)); } void tty_putcode_s(struct tty *tty, enum tty_code_code code, const char *a) { if (a != NULL) tty_puts(tty, tty_term_string_s(tty->term, code, a)); } void tty_putcode_ss(struct tty *tty, enum tty_code_code code, const char *a, const char *b) { if (a != NULL && b != NULL) tty_puts(tty, tty_term_string_ss(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_putcode_ii(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_s(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_putcode_i(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_putcode_i(tty, TTYC_SS, 1); else tty_putcode_i(tty, TTYC_SS, 2); } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_UNDERLINE: if (tty_term_has(tty->term, TTYC_SS)) { if (cmode & MODE_CURSOR_BLINKING) tty_putcode_i(tty, TTYC_SS, 3); else tty_putcode_i(tty, TTYC_SS, 4); } else if (cmode & MODE_CURSOR_BLINKING) tty_putcode(tty, TTYC_CVVIS); break; case SCREEN_CURSOR_BAR: if (tty_term_has(tty->term, TTYC_SS)) { if (cmode & MODE_CURSOR_BLINKING) tty_putcode_i(tty, TTYC_SS, 5); else tty_putcode_i(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"); } 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_putcode_i(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_putcode_i(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_putcode_i(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_putcode_i(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); if (gc->flags & GRID_FLAG_TAB) 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, s->hyperlinks); 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 || gcp->link != last.link || ux + width + gcp->data.width > nx || (sizeof buf) - len < gcp->data.size)) { tty_attributes(tty, &last, defaults, palette, s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, 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, s->hyperlinks); for (j = 0; j < OVERLAY_MAX_RANGES; j++) { if (r.nx[j] == 0) continue; /* Effective width drawn so far. */ eux = r.px[j] - atx; if (eux < nx) { tty_cursor(tty, r.px[j], aty); nxx = nx - eux; if (r.nx[j] > nxx) r.nx[j] = nxx; tty_repeat_space(tty, r.nx[j]); ux = eux + r.nx[j]; } } } } else if (gcp->attr & GRID_ATTR_CHARSET) { tty_attributes(tty, &last, defaults, palette, s->hyperlinks); tty_cursor(tty, atx + ux, aty); for (j = 0; j < gcp->data.size; j++) tty_putc(tty, gcp->data.data[j]); 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, s->hyperlinks); if (last.flags & GRID_FLAG_CLEARED) { log_debug("%s: %zu cleared (end)", __func__, len); tty_clear_line(tty, defaults, aty, atx + ux, width, 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, s->hyperlinks); 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); } #ifdef ENABLE_SIXEL /* Update context for client. */ static int tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) { struct window_pane *wp = ttyctx->arg; if (c->session->curw->window != wp->window) return (0); if (wp->layout_cell == NULL) return (0); /* Set the properties relevant to the current client. */ ttyctx->bigger = tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, &ttyctx->wsy); ttyctx->yoff = ttyctx->ryoff = wp->yoff; if (status_at_line(c) == 0) ttyctx->yoff += status_line_size(c); return (1); } void tty_draw_images(struct client *c, struct window_pane *wp, struct screen *s) { struct image *im; struct tty_ctx ttyctx; TAILQ_FOREACH(im, &s->images, entry) { memset(&ttyctx, 0, sizeof ttyctx); /* Set the client independent properties. */ ttyctx.ocx = im->px; ttyctx.ocy = im->py; ttyctx.orlower = s->rlower; ttyctx.orupper = s->rupper; ttyctx.xoff = ttyctx.rxoff = wp->xoff; ttyctx.sx = wp->sx; ttyctx.sy = wp->sy; ttyctx.ptr = im; ttyctx.arg = wp; ttyctx.set_client_cb = tty_set_client_cb; ttyctx.allow_invisible_panes = 1; tty_write_one(tty_cmd_sixelimage, c, &ttyctx); } } #endif void tty_sync_start(struct tty *tty) { 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_putcode_i(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_putcode_i(tty, TTYC_SYNC, 2); } } static int tty_client_ready(const struct tty_ctx *ctx, struct client *c) { if (c->session == NULL || c->tty.term == NULL) return (0); if (c->flags & CLIENT_SUSPENDED) return (0); /* * If invisible panes are allowed (used for passthrough), don't care if * redrawing or frozen. */ if (ctx->allow_invisible_panes) return (1); if (c->flags & CLIENT_REDRAWWINDOW) return (0); if (c->tty.flags & TTY_FREEZE) return (0); 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(ctx, c)) { state = ctx->set_client_cb(ctx, c); if (state == -1) break; if (state == 0) continue; cmdfn(&c->tty, ctx); } } } #ifdef ENABLE_SIXEL /* Only write to the incoming tty instead of every client. */ static void tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *), struct client *c, struct tty_ctx *ctx) { if (ctx->set_client_cb == NULL) return; if ((ctx->set_client_cb(ctx, c)) == 1) cmdfn(&c->tty, ctx); } #endif void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); if (tty_term_has(tty->term, TTYC_RI)) tty_putcode(tty, TTYC_RI); else tty_putcode_i(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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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_putcode_i(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, ctx->s->hyperlinks); tty_region_pane(tty, ctx, ctx->orupper, ctx->orlower); tty_margin_pane(tty, ctx); tty_cursor_pane(tty, ctx, ctx->ocx, ctx->orupper); if (tty_term_has(tty->term, TTYC_RIN)) tty_putcode_i(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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); 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, ctx->s->hyperlinks); if (ctx->num == 1) tty_invalidate(tty); } 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, ctx->s->hyperlinks); /* 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->ptr2, ctx->ptr, ctx->num); } void tty_set_selection(struct tty *tty, const char *flags, 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_ss(tty, TTYC_MS, flags, 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); } #ifdef ENABLE_SIXEL void tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) { struct image *im = ctx->ptr; struct sixel_image *si = im->data; struct sixel_image *new; char *data; size_t size; u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy; u_int i, j, x, y, rx, ry; int fallback = 0; if ((~tty->term->flags & TERM_SIXEL) && !tty_term_has(tty->term, TTYC_SXL)) fallback = 1; if (tty->xpixel == 0 || tty->ypixel == 0) fallback = 1; sixel_size_in_cells(si, &sx, &sy); log_debug("%s: image is %ux%u", __func__, sx, sy); if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry)) return; log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry); if (fallback == 1) { data = xstrdup(im->fallback); size = strlen(data); } else { new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0); if (new == NULL) return; data = sixel_print(new, si, &size); } if (data != NULL) { log_debug("%s: %zu bytes: %s", __func__, size, data); tty_region_off(tty); tty_margin_off(tty); tty_cursor(tty, x, y); tty->flags |= TTY_NOBLOCK; tty_add(tty, data, size); tty_invalidate(tty); free(data); } if (fallback == 0) sixel_free(new); } #endif void tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) { 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, struct hyperlinks *hl) { 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, hl); /* If it is a single character, write with putc to handle ACS. */ if (gcp->data.size == 1) { tty_attributes(tty, gcp, defaults, palette, hl); 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->link != 0) tty_putcode_ss(tty, TTYC_HLS, "", ""); if ((gc->attr & GRID_ATTR_CHARSET) && tty_acs_needed(tty)) tty_putcode(tty, TTYC_RMACS); tty_putcode(tty, TTYC_SGR0); memcpy(gc, &grid_default_cell, sizeof *gc); } memcpy(&tty->last_cell, &grid_default_cell, sizeof tty->last_cell); } 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_putcode_ii(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_putcode_ii(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_putcode_ii(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_putcode_i(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_putcode_i(tty, TTYC_CUB, change); goto out; } else if (change < 0 && tty_term_has(term, TTYC_CUF) && !tty_use_margin(tty)) { tty_putcode_i(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_putcode_i(tty, TTYC_VPA, cy); goto out; } } else if (change > 0 && tty_term_has(term, TTYC_CUU)) { tty_putcode_i(tty, TTYC_CUU, change); goto out; } else if (change < 0 && tty_term_has(term, TTYC_CUD)) { tty_putcode_i(tty, TTYC_CUD, -change); goto out; } } absolute: /* Absolute movement. */ tty_putcode_ii(tty, TTYC_CUP, cy, cx); out: tty->cx = cx; tty->cy = cy; } static void tty_hyperlink(struct tty *tty, const struct grid_cell *gc, struct hyperlinks *hl) { const char *uri, *id; if (gc->link == tty->cell.link) return; tty->cell.link = gc->link; if (hl == NULL) return; if (gc->link == 0 || !hyperlinks_get(hl, gc->link, &uri, NULL, &id)) tty_putcode_ss(tty, TTYC_HLS, "", ""); else tty_putcode_ss(tty, TTYC_HLS, id, uri); } void tty_attributes(struct tty *tty, const struct grid_cell *gc, const struct grid_cell *defaults, struct colour_palette *palette, struct hyperlinks *hl) { 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 && gc2.link == tty->last_cell.link) 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_putcode(tty, TTYC_SMUL); else if (changed & GRID_ATTR_UNDERSCORE_2) tty_putcode_i(tty, TTYC_SMULX, 2); else if (changed & GRID_ATTR_UNDERSCORE_3) tty_putcode_i(tty, TTYC_SMULX, 3); else if (changed & GRID_ATTR_UNDERSCORE_4) tty_putcode_i(tty, TTYC_SMULX, 4); else if (changed & GRID_ATTR_UNDERSCORE_5) tty_putcode_i(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); /* Set hyperlink if any. */ tty_hyperlink(tty, gc, hl); 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; /* 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, send sgr0. This resets both colours to default. * Otherwise, try to set the default colour only as needed. */ if (!tty_term_flag(tty->term, TTYC_AX)) tty_reset(tty); else { if (COLOUR_DEFAULT(gc->fg) && !COLOUR_DEFAULT(tc->fg)) { tty_puts(tty, "\033[39m"); tc->fg = gc->fg; } if (COLOUR_DEFAULT(gc->bg) && !COLOUR_DEFAULT(tc->bg)) { tty_puts(tty, "\033[49m"); 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_colours_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 and Nobr is not present, use the bright entry in * the palette by changing to the aixterm colour */ if (~gc->flags & GRID_FLAG_NOPALETTE) { c = gc->fg; if (c < 8 && gc->attr & GRID_ATTR_BRIGHT && !tty_term_has(tty->term, TTYC_NOBR)) 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) return; gc->fg = colour_256to16(gc->fg); if (~gc->fg & 8) return; gc->fg &= 7; if (colours >= 16) gc->fg += 90; else { /* * Mapping to black-on-black or white-on-white is not * much use, so change the foreground. */ if (gc->fg == 0 && gc->bg == 0) gc->fg = 7; else if (gc->fg == 7 && gc->bg == 7) gc->fg = 0; } 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) return; gc->bg = colour_256to16(gc->bg); if (~gc->bg & 8) return; 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; } /* Convert underscore colour if only RGB can be supported. */ if (!tty_term_has(tty->term, TTYC_SETULC1)) { if ((c = colour_force_rgb (gc->us)) == -1) gc->us = 8; else gc->us = c; } } static void tty_colours_fg(struct tty *tty, const struct grid_cell *gc) { struct grid_cell *tc = &tty->cell; char s[32]; /* * If the current colour is an aixterm bright colour and the new is not, * reset because some terminals do not clear bright correctly. */ if (tty->cell.fg >= 90 && tty->cell.bg <= 97 && (gc->fg < 90 || gc->fg > 97)) tty_reset(tty); /* 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_putcode_i(tty, TTYC_SETAF, gc->fg - 90 + 8); goto save; } /* Otherwise set the foreground colour. */ tty_putcode_i(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_putcode_i(tty, TTYC_SETAB, gc->bg - 90 + 8); goto save; } /* Otherwise set the background colour. */ tty_putcode_i(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 (COLOUR_DEFAULT(gc->us)) { tty_putcode(tty, TTYC_OL); goto save; } /* * If this is not an RGB colour, use Setulc1 if it exists, otherwise * convert. */ if (~gc->us & COLOUR_FLAG_RGB) { c = gc->us; if ((~c & COLOUR_FLAG_256) && (c >= 90 && c <= 97)) c -= 82; tty_putcode_i(tty, TTYC_SETULC1, c & ~COLOUR_FLAG_256); return; } /* * Setulc and setal follows the ncurses(3) one argument "direct colour" * 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_putcode_i(tty, TTYC_SETULC, c); else if (tty_term_has(tty->term, TTYC_SETAL) && tty_term_has(tty->term, TTYC_RGB)) tty_putcode_i(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_putcode_i(tty, TTYC_SETAF, colour & 0xff); else if (tty_term_has(tty->term, TTYC_SETAB)) tty_putcode_i(tty, TTYC_SETAB, colour & 0xff); return (0); } if (colour & COLOUR_FLAG_RGB) { colour_split_rgb(colour & 0xffffff, &r, &g, &b); if (*type == '3' && tty_term_has(tty->term, TTYC_SETRGBF)) tty_putcode_iii(tty, TTYC_SETRGBF, r, g, b); else if (tty_term_has(tty->term, TTYC_SETRGBB)) tty_putcode_iii(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 hyperlinks *hl) { struct grid_cell gc; memcpy(&gc, &grid_default_cell, sizeof gc); gc.bg = bg; tty_attributes(tty, &gc, defaults, palette, hl); } static void tty_clipboard_query_callback(__unused int fd, __unused short events, void *data) { struct tty *tty = data; struct client *c = tty->client; c->flags &= ~CLIENT_CLIPBOARDBUFFER; free(c->clipboard_panes); c->clipboard_panes = NULL; c->clipboard_npanes = 0; tty->flags &= ~TTY_OSC52QUERY; } void tty_clipboard_query(struct tty *tty) { struct timeval tv = { .tv_sec = TTY_QUERY_TIMEOUT }; if ((~tty->flags & TTY_STARTED) || (tty->flags & TTY_OSC52QUERY)) return; tty_putcode_ss(tty, TTYC_MS, "", "?"); tty->flags |= TTY_OSC52QUERY; evtimer_set(&tty->clipboard_timer, tty_clipboard_query_callback, tty); evtimer_add(&tty->clipboard_timer, &tv); } tmux-tmux-f222026/utf8-combined.c000066400000000000000000000165271511153563100165730ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2023 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "tmux.h" enum hanguljamo_subclass { HANGULJAMO_SUBCLASS_NOT_HANGULJAMO, HANGULJAMO_SUBCLASS_CHOSEONG, // U+1100 - U+1112 HANGULJAMO_SUBCLASS_OLD_CHOSEONG, // U+1113 - U+115E HANGULJAMO_SUBCLASS_CHOSEONG_FILLER, // U+115F HANGULJAMO_SUBCLASS_JUNGSEONG_FILLER, // U+1160 HANGULJAMO_SUBCLASS_JUNGSEONG, // U+1161 - U+1175 HANGULJAMO_SUBCLASS_OLD_JUNGSEONG, // U+1176 - U+11A7 HANGULJAMO_SUBCLASS_JONGSEONG, // U+11A8 - U+11C2 HANGULJAMO_SUBCLASS_OLD_JONGSEONG, // U+11C3 - U+11FF HANGULJAMO_SUBCLASS_EXTENDED_OLD_CHOSEONG, // U+A960 - U+A97C HANGULJAMO_SUBCLASS_EXTENDED_OLD_JUNGSEONG, // U+D7B0 - U+D7C6 HANGULJAMO_SUBCLASS_EXTENDED_OLD_JONGSEONG // U+D7CB - U+D7FB }; enum hanguljamo_class { HANGULJAMO_CLASS_NOT_HANGULJAMO, HANGULJAMO_CLASS_CHOSEONG, HANGULJAMO_CLASS_JUNGSEONG, HANGULJAMO_CLASS_JONGSEONG }; /* Has this got a zero width joiner at the end? */ int utf8_has_zwj(const struct utf8_data *ud) { if (ud->size < 3) return (0); return (memcmp(ud->data + ud->size - 3, "\342\200\215", 3) == 0); } /* Is this zero width joiner U+200D? */ int utf8_is_zwj(const struct utf8_data *ud) { if (ud->size != 3) return (0); return (memcmp(ud->data, "\342\200\215", 3) == 0); } /* Is this variation selector U+FE0F? */ int utf8_is_vs(const struct utf8_data *ud) { if (ud->size != 3) return (0); return (memcmp(ud->data, "\357\270\217", 3) == 0); } /* Is this Hangul filler U+3164? */ int utf8_is_hangul_filler(const struct utf8_data *ud) { if (ud->size != 3) return (0); return (memcmp(ud->data, "\343\205\244", 3) == 0); } /* Should these two characters combine? */ int utf8_should_combine(const struct utf8_data *with, const struct utf8_data *add) { wchar_t w, a; if (utf8_towc(with, &w) != UTF8_DONE) return (0); if (utf8_towc(add, &a) != UTF8_DONE) return (0); /* Regional indicators. */ if ((a >= 0x1F1E6 && a <= 0x1F1FF) && (w >= 0x1F1E6 && w <= 0x1F1FF)) return (1); /* Emoji skin tone modifiers. */ switch (a) { case 0x1F44B: case 0x1F44C: case 0x1F44D: case 0x1F44E: case 0x1F44F: case 0x1F450: case 0x1F466: case 0x1F467: case 0x1F468: case 0x1F469: case 0x1F46E: case 0x1F470: case 0x1F471: case 0x1F472: case 0x1F473: case 0x1F474: case 0x1F475: case 0x1F476: case 0x1F477: case 0x1F478: case 0x1F47C: case 0x1F481: case 0x1F482: case 0x1F485: case 0x1F486: case 0x1F487: case 0x1F4AA: case 0x1F575: case 0x1F57A: case 0x1F590: case 0x1F595: case 0x1F596: case 0x1F645: case 0x1F646: case 0x1F647: case 0x1F64B: case 0x1F64C: case 0x1F64D: case 0x1F64E: case 0x1F64F: case 0x1F6B4: case 0x1F6B5: case 0x1F6B6: case 0x1F926: case 0x1F937: case 0x1F938: case 0x1F939: case 0x1F93D: case 0x1F93E: case 0x1F9B5: case 0x1F9B6: case 0x1F9B8: case 0x1F9B9: case 0x1F9CD: case 0x1F9CE: case 0x1F9CF: case 0x1F9D1: case 0x1F9D2: case 0x1F9D3: case 0x1F9D4: case 0x1F9D5: case 0x1F9D6: case 0x1F9D7: case 0x1F9D8: case 0x1F9D9: case 0x1F9DA: case 0x1F9DB: case 0x1F9DC: case 0x1F9DD: case 0x1F9DE: case 0x1F9DF: if (w >= 0x1F3FB && w <= 0x1F3FF) return (1); break; } return 0; } static enum hanguljamo_subclass hanguljamo_get_subclass(const u_char *s) { switch (s[0]) { case 0xE1: switch (s[1]) { case 0x84: if (s[2] >= 0x80 && s[2] <= 0x92) return (HANGULJAMO_SUBCLASS_CHOSEONG); if (s[2] >= 0x93 && s[2] <= 0xBF) return (HANGULJAMO_SUBCLASS_OLD_CHOSEONG); break; case 0x85: if (s[2] == 0x9F) return (HANGULJAMO_SUBCLASS_CHOSEONG_FILLER); if (s[2] == 0xA0) return (HANGULJAMO_SUBCLASS_JUNGSEONG_FILLER); if (s[2] >= 0x80 && s[2] <= 0x9E) return (HANGULJAMO_SUBCLASS_OLD_CHOSEONG); if (s[2] >= 0xA1 && s[2] <= 0xB5) return (HANGULJAMO_SUBCLASS_JUNGSEONG); if (s[2] >= 0xB6 && s[2] <= 0xBF) return (HANGULJAMO_SUBCLASS_OLD_JUNGSEONG); break; case 0x86: if (s[2] >= 0x80 && s[2] <= 0xA7) return (HANGULJAMO_SUBCLASS_OLD_JUNGSEONG); if (s[2] >= 0xA8 && s[2] <= 0xBF) return (HANGULJAMO_SUBCLASS_JONGSEONG); break; case 0x87: if (s[2] >= 0x80 && s[2] <= 0x82) return (HANGULJAMO_SUBCLASS_JONGSEONG); if (s[2] >= 0x83 && s[2] <= 0xBF) return (HANGULJAMO_SUBCLASS_OLD_JONGSEONG); break; } break; case 0xEA: if (s[1] == 0xA5 && s[2] >= 0xA0 && s[2] <= 0xBC) return (HANGULJAMO_SUBCLASS_EXTENDED_OLD_CHOSEONG); break; case 0xED: if (s[1] == 0x9E && s[2] >= 0xB0 && s[2] <= 0xBF) return (HANGULJAMO_SUBCLASS_EXTENDED_OLD_JUNGSEONG); if (s[1] != 0x9F) break; if (s[2] >= 0x80 && s[2] <= 0x86) return (HANGULJAMO_SUBCLASS_EXTENDED_OLD_JUNGSEONG); if (s[2] >= 0x8B && s[2] <= 0xBB) return (HANGULJAMO_SUBCLASS_EXTENDED_OLD_JONGSEONG); break; } return (HANGULJAMO_SUBCLASS_NOT_HANGULJAMO); } static enum hanguljamo_class hanguljamo_get_class(const u_char *s) { switch (hanguljamo_get_subclass(s)) { case HANGULJAMO_SUBCLASS_CHOSEONG: case HANGULJAMO_SUBCLASS_CHOSEONG_FILLER: case HANGULJAMO_SUBCLASS_OLD_CHOSEONG: case HANGULJAMO_SUBCLASS_EXTENDED_OLD_CHOSEONG: return (HANGULJAMO_CLASS_CHOSEONG); case HANGULJAMO_SUBCLASS_JUNGSEONG: case HANGULJAMO_SUBCLASS_JUNGSEONG_FILLER: case HANGULJAMO_SUBCLASS_OLD_JUNGSEONG: case HANGULJAMO_SUBCLASS_EXTENDED_OLD_JUNGSEONG: return (HANGULJAMO_CLASS_JUNGSEONG); case HANGULJAMO_SUBCLASS_JONGSEONG: case HANGULJAMO_SUBCLASS_OLD_JONGSEONG: case HANGULJAMO_SUBCLASS_EXTENDED_OLD_JONGSEONG: return (HANGULJAMO_CLASS_JONGSEONG); case HANGULJAMO_SUBCLASS_NOT_HANGULJAMO: return (HANGULJAMO_CLASS_NOT_HANGULJAMO); } return (HANGULJAMO_CLASS_NOT_HANGULJAMO); } enum hanguljamo_state hanguljamo_check_state(const struct utf8_data *p_ud, const struct utf8_data *ud) { const u_char *s; if (ud->size != 3) return (HANGULJAMO_STATE_NOT_HANGULJAMO); switch (hanguljamo_get_class(ud->data)) { case HANGULJAMO_CLASS_CHOSEONG: return (HANGULJAMO_STATE_CHOSEONG); case HANGULJAMO_CLASS_JUNGSEONG: if (p_ud->size < 3) return (HANGULJAMO_STATE_NOT_COMPOSABLE); s = p_ud->data + p_ud->size - 3; if (hanguljamo_get_class(s) == HANGULJAMO_CLASS_CHOSEONG) return (HANGULJAMO_STATE_COMPOSABLE); return (HANGULJAMO_STATE_NOT_COMPOSABLE); case HANGULJAMO_CLASS_JONGSEONG: if (p_ud->size < 3) return (HANGULJAMO_STATE_NOT_COMPOSABLE); s = p_ud->data + p_ud->size - 3; if (hanguljamo_get_class(s) == HANGULJAMO_CLASS_JUNGSEONG) return (HANGULJAMO_STATE_COMPOSABLE); return (HANGULJAMO_STATE_NOT_COMPOSABLE); case HANGULJAMO_CLASS_NOT_HANGULJAMO: return (HANGULJAMO_STATE_NOT_HANGULJAMO); } return (HANGULJAMO_STATE_NOT_HANGULJAMO); } tmux-tmux-f222026/utf8.c000066400000000000000000000537121511153563100150120ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2008 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "compat.h" #include "tmux.h" struct utf8_width_item { wchar_t wc; u_int width; int allocated; RB_ENTRY(utf8_width_item) entry; }; static int utf8_width_cache_cmp(struct utf8_width_item *uw1, struct utf8_width_item *uw2) { if (uw1->wc < uw2->wc) return (-1); if (uw1->wc > uw2->wc) return (1); return (0); } RB_HEAD(utf8_width_cache, utf8_width_item); RB_GENERATE_STATIC(utf8_width_cache, utf8_width_item, entry, utf8_width_cache_cmp); static struct utf8_width_cache utf8_width_cache = RB_INITIALIZER(utf8_width_cache); static struct utf8_width_item utf8_default_width_cache[] = { { .wc = 0x0261D, .width = 2 }, { .wc = 0x026F9, .width = 2 }, { .wc = 0x0270A, .width = 2 }, { .wc = 0x0270B, .width = 2 }, { .wc = 0x0270C, .width = 2 }, { .wc = 0x0270D, .width = 2 }, { .wc = 0x1F1E6, .width = 1 }, { .wc = 0x1F1E7, .width = 1 }, { .wc = 0x1F1E8, .width = 1 }, { .wc = 0x1F1E9, .width = 1 }, { .wc = 0x1F1EA, .width = 1 }, { .wc = 0x1F1EB, .width = 1 }, { .wc = 0x1F1EC, .width = 1 }, { .wc = 0x1F1ED, .width = 1 }, { .wc = 0x1F1EE, .width = 1 }, { .wc = 0x1F1EF, .width = 1 }, { .wc = 0x1F1F0, .width = 1 }, { .wc = 0x1F1F1, .width = 1 }, { .wc = 0x1F1F2, .width = 1 }, { .wc = 0x1F1F3, .width = 1 }, { .wc = 0x1F1F4, .width = 1 }, { .wc = 0x1F1F5, .width = 1 }, { .wc = 0x1F1F6, .width = 1 }, { .wc = 0x1F1F7, .width = 1 }, { .wc = 0x1F1F8, .width = 1 }, { .wc = 0x1F1F9, .width = 1 }, { .wc = 0x1F1FA, .width = 1 }, { .wc = 0x1F1FB, .width = 1 }, { .wc = 0x1F1FC, .width = 1 }, { .wc = 0x1F1FD, .width = 1 }, { .wc = 0x1F1FE, .width = 1 }, { .wc = 0x1F1FF, .width = 1 }, { .wc = 0x1F385, .width = 2 }, { .wc = 0x1F3C2, .width = 2 }, { .wc = 0x1F3C3, .width = 2 }, { .wc = 0x1F3C4, .width = 2 }, { .wc = 0x1F3C7, .width = 2 }, { .wc = 0x1F3CA, .width = 2 }, { .wc = 0x1F3CB, .width = 2 }, { .wc = 0x1F3CC, .width = 2 }, { .wc = 0x1F3FB, .width = 2 }, { .wc = 0x1F3FC, .width = 2 }, { .wc = 0x1F3FD, .width = 2 }, { .wc = 0x1F3FE, .width = 2 }, { .wc = 0x1F3FF, .width = 2 }, { .wc = 0x1F442, .width = 2 }, { .wc = 0x1F443, .width = 2 }, { .wc = 0x1F446, .width = 2 }, { .wc = 0x1F447, .width = 2 }, { .wc = 0x1F448, .width = 2 }, { .wc = 0x1F449, .width = 2 }, { .wc = 0x1F44A, .width = 2 }, { .wc = 0x1F44B, .width = 2 }, { .wc = 0x1F44C, .width = 2 }, { .wc = 0x1F44D, .width = 2 }, { .wc = 0x1F44E, .width = 2 }, { .wc = 0x1F44F, .width = 2 }, { .wc = 0x1F450, .width = 2 }, { .wc = 0x1F466, .width = 2 }, { .wc = 0x1F467, .width = 2 }, { .wc = 0x1F468, .width = 2 }, { .wc = 0x1F469, .width = 2 }, { .wc = 0x1F46B, .width = 2 }, { .wc = 0x1F46C, .width = 2 }, { .wc = 0x1F46D, .width = 2 }, { .wc = 0x1F46E, .width = 2 }, { .wc = 0x1F470, .width = 2 }, { .wc = 0x1F471, .width = 2 }, { .wc = 0x1F472, .width = 2 }, { .wc = 0x1F473, .width = 2 }, { .wc = 0x1F474, .width = 2 }, { .wc = 0x1F475, .width = 2 }, { .wc = 0x1F476, .width = 2 }, { .wc = 0x1F477, .width = 2 }, { .wc = 0x1F478, .width = 2 }, { .wc = 0x1F47C, .width = 2 }, { .wc = 0x1F481, .width = 2 }, { .wc = 0x1F482, .width = 2 }, { .wc = 0x1F483, .width = 2 }, { .wc = 0x1F485, .width = 2 }, { .wc = 0x1F486, .width = 2 }, { .wc = 0x1F487, .width = 2 }, { .wc = 0x1F48F, .width = 2 }, { .wc = 0x1F491, .width = 2 }, { .wc = 0x1F4AA, .width = 2 }, { .wc = 0x1F574, .width = 2 }, { .wc = 0x1F575, .width = 2 }, { .wc = 0x1F57A, .width = 2 }, { .wc = 0x1F590, .width = 2 }, { .wc = 0x1F595, .width = 2 }, { .wc = 0x1F596, .width = 2 }, { .wc = 0x1F645, .width = 2 }, { .wc = 0x1F646, .width = 2 }, { .wc = 0x1F647, .width = 2 }, { .wc = 0x1F64B, .width = 2 }, { .wc = 0x1F64C, .width = 2 }, { .wc = 0x1F64D, .width = 2 }, { .wc = 0x1F64E, .width = 2 }, { .wc = 0x1F64F, .width = 2 }, { .wc = 0x1F6A3, .width = 2 }, { .wc = 0x1F6B4, .width = 2 }, { .wc = 0x1F6B5, .width = 2 }, { .wc = 0x1F6B6, .width = 2 }, { .wc = 0x1F6C0, .width = 2 }, { .wc = 0x1F6CC, .width = 2 }, { .wc = 0x1F90C, .width = 2 }, { .wc = 0x1F90F, .width = 2 }, { .wc = 0x1F918, .width = 2 }, { .wc = 0x1F919, .width = 2 }, { .wc = 0x1F91A, .width = 2 }, { .wc = 0x1F91B, .width = 2 }, { .wc = 0x1F91C, .width = 2 }, { .wc = 0x1F91D, .width = 2 }, { .wc = 0x1F91E, .width = 2 }, { .wc = 0x1F91F, .width = 2 }, { .wc = 0x1F926, .width = 2 }, { .wc = 0x1F930, .width = 2 }, { .wc = 0x1F931, .width = 2 }, { .wc = 0x1F932, .width = 2 }, { .wc = 0x1F933, .width = 2 }, { .wc = 0x1F934, .width = 2 }, { .wc = 0x1F935, .width = 2 }, { .wc = 0x1F936, .width = 2 }, { .wc = 0x1F937, .width = 2 }, { .wc = 0x1F938, .width = 2 }, { .wc = 0x1F939, .width = 2 }, { .wc = 0x1F93D, .width = 2 }, { .wc = 0x1F93E, .width = 2 }, { .wc = 0x1F977, .width = 2 }, { .wc = 0x1F9B5, .width = 2 }, { .wc = 0x1F9B6, .width = 2 }, { .wc = 0x1F9B8, .width = 2 }, { .wc = 0x1F9B9, .width = 2 }, { .wc = 0x1F9BB, .width = 2 }, { .wc = 0x1F9CD, .width = 2 }, { .wc = 0x1F9CE, .width = 2 }, { .wc = 0x1F9CF, .width = 2 }, { .wc = 0x1F9D1, .width = 2 }, { .wc = 0x1F9D2, .width = 2 }, { .wc = 0x1F9D3, .width = 2 }, { .wc = 0x1F9D4, .width = 2 }, { .wc = 0x1F9D5, .width = 2 }, { .wc = 0x1F9D6, .width = 2 }, { .wc = 0x1F9D7, .width = 2 }, { .wc = 0x1F9D8, .width = 2 }, { .wc = 0x1F9D9, .width = 2 }, { .wc = 0x1F9DA, .width = 2 }, { .wc = 0x1F9DB, .width = 2 }, { .wc = 0x1F9DC, .width = 2 }, { .wc = 0x1F9DD, .width = 2 }, { .wc = 0x1FAC3, .width = 2 }, { .wc = 0x1FAC4, .width = 2 }, { .wc = 0x1FAC5, .width = 2 }, { .wc = 0x1FAF0, .width = 2 }, { .wc = 0x1FAF1, .width = 2 }, { .wc = 0x1FAF2, .width = 2 }, { .wc = 0x1FAF3, .width = 2 }, { .wc = 0x1FAF4, .width = 2 }, { .wc = 0x1FAF5, .width = 2 }, { .wc = 0x1FAF6, .width = 2 }, { .wc = 0x1FAF7, .width = 2 }, { .wc = 0x1FAF8, .width = 2 } }; 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 int utf8_no_width; 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 u_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)); } /* Find a codepoint in the cache. */ static struct utf8_width_item * utf8_find_in_width_cache(wchar_t wc) { struct utf8_width_item uw; uw.wc = wc; return RB_FIND(utf8_width_cache, &utf8_width_cache, &uw); } /* Parse a single codepoint option. */ static void utf8_add_to_width_cache(const char *s) { struct utf8_width_item *uw, *old; char *copy, *cp, *endptr; u_int width; const char *errstr; struct utf8_data *ud; wchar_t wc; unsigned long long n; copy = xstrdup(s); if ((cp = strchr(copy, '=')) == NULL) { free(copy); return; } *cp++ = '\0'; width = strtonum(cp, 0, 2, &errstr); if (errstr != NULL) { free(copy); return; } if (strncmp(copy, "U+", 2) == 0) { errno = 0; n = strtoull(copy + 2, &endptr, 16); if (copy[2] == '\0' || *endptr != '\0' || n == 0 || n > WCHAR_MAX || (errno == ERANGE && n == ULLONG_MAX)) { free(copy); return; } wc = n; } else { utf8_no_width = 1; ud = utf8_fromcstr(copy); utf8_no_width = 0; if (ud[0].size == 0 || ud[1].size != 0) { free(ud); free(copy); return; } #ifdef HAVE_UTF8PROC if (utf8proc_mbtowc(&wc, ud[0].data, ud[0].size) <= 0) { #else if (mbtowc(&wc, ud[0].data, ud[0].size) <= 0) { #endif free(ud); free(copy); return; } free(ud); } log_debug("Unicode width cache: %08X=%u", (u_int)wc, width); uw = xcalloc(1, sizeof *uw); uw->wc = wc; uw->width = width; uw->allocated = 1; old = RB_INSERT(utf8_width_cache, &utf8_width_cache, uw); if (old != NULL) { RB_REMOVE(utf8_width_cache, &utf8_width_cache, old); if (old->allocated) free(old); RB_INSERT(utf8_width_cache, &utf8_width_cache, uw); } free(copy); } /* Rebuild cache of widths. */ void utf8_update_width_cache(void) { struct utf8_width_item *uw, *uw1; struct options_entry *o; struct options_array_item *a; u_int i; RB_FOREACH_SAFE (uw, utf8_width_cache, &utf8_width_cache, uw1) { RB_REMOVE(utf8_width_cache, &utf8_width_cache, uw); if (uw->allocated) free(uw); } for (i = 0; i < nitems(utf8_default_width_cache); i++) { RB_INSERT(utf8_width_cache, &utf8_width_cache, &utf8_default_width_cache[i]); } o = options_get(global_options, "codepoint-widths"); a = options_array_first(o); while (a != NULL) { utf8_add_to_width_cache(options_array_item_value(a)->string); a = options_array_next(a); } } /* Add a UTF-8 item. */ static int utf8_put_item(const u_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) { struct utf8_width_item *uw; wchar_t wc; if (utf8_towc(ud, &wc) != UTF8_DONE) return (UTF8_ERROR); uw = utf8_find_in_width_cache(wc); if (uw != NULL) { *width = uw->width; log_debug("cached width for %08X is %d", (u_int)wc, *width); return (UTF8_DONE); } #ifdef HAVE_UTF8PROC *width = utf8proc_wcwidth(wc); log_debug("utf8proc_wcwidth(%05X) returned %d", (u_int)wc, *width); #else *width = wcwidth(wc); log_debug("wcwidth(%05X) returned %d", (u_int)wc, *width); if (*width < 0) { /* * C1 control characters are nonprintable, so they are always * zero width. */ *width = (wc >= 0x80 && wc <= 0x9f) ? 0 : 1; } #endif if (*width >= 0 && *width <= 0xff) return (UTF8_DONE); return (UTF8_ERROR); } /* Convert UTF-8 character to wide character. */ enum utf8_state utf8_towc(const struct utf8_data *ud, wchar_t *wc) { #ifdef HAVE_UTF8PROC switch (utf8proc_mbtowc(wc, ud->data, ud->size)) { #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); } log_debug("UTF-8 %.*s is %05X", (int)ud->size, ud->data, (u_int)*wc); return (UTF8_DONE); } /* Convert wide character to UTF-8 character. */ enum utf8_state utf8_fromwc(wchar_t wc, struct utf8_data *ud) { int size, width; #ifdef HAVE_UTF8PROC size = utf8proc_wctomb(ud->data, wc); #else size = wctomb(ud->data, wc); #endif if (size < 0) { log_debug("UTF-8 %d, wctomb() %d", wc, errno); wctomb(NULL, 0); return (UTF8_ERROR); } if (size == 0) return (UTF8_ERROR); ud->size = ud->have = size; if (utf8_width(ud, &width) == UTF8_DONE) { ud->width = width; return (UTF8_DONE); } 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 (!utf8_no_width) { 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 ((flag & VIS_DQ) && 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); } tmux-tmux-f222026/window-buffer.c000066400000000000000000000347661511153563100167120ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "tmux.h" 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 -p -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 = NULL; 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; 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_find(const void *data, size_t datalen, const void *find, size_t findlen, int icase) { const u_char *udata = data, *ufind = find; size_t i, j; if (findlen == 0 || datalen < findlen) return (0); for (i = 0; i + findlen <= datalen; i++) { for (j = 0; j < findlen; j++) { if (!icase && udata[i + j] != ufind[j]) break; if (icase && tolower(udata[i + j]) != tolower(ufind[j])) break; } if (j == findlen) return (1); } return (0); } static int window_buffer_search(__unused void *modedata, void *itemdata, const char *ss, int icase) { 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 (icase) { if (strcasestr(item->name, ss) != NULL) return (1); bufdata = paste_buffer_data(pb, &bufsize); return (window_buffer_find(bufdata, bufsize, ss, strlen(ss), icase)); } else { if (strstr(item->name, ss) != NULL) return (1); bufdata = paste_buffer_data(pb, &bufsize); return (window_buffer_find(bufdata, bufsize, ss, strlen(ss), icase)); } } 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, NULL, 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 we were unable to select the item further down we are at * the end of the list. Move one element up instead, to make * sure that we preserve a valid selection or we risk having * the tree build logic reset it to the first item. */ mode_tree_up(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; if (paste_is_empty()) { finished = 1; goto out; } 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; } out: if (finished || paste_is_empty()) window_pane_reset_mode(wp); else { mode_tree_draw(mtd); wp->flags |= PANE_REDRAW; } } tmux-tmux-f222026/window-client.c000066400000000000000000000254341511153563100167070ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "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, BOX_LINES_DEFAULT, NULL); 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, NULL, 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; } } tmux-tmux-f222026/window-clock.c000066400000000000000000000161511511153563100165200ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2009 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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_sec == then.tm_sec) 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; const char *timeformat; 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 || style == 2) { if (style == 2) timeformat = "%l:%M:%S "; else timeformat = "%l:%M "; strftime(tim, sizeof tim, timeformat, localtime(&t)); if (tm->tm_hour >= 12) strlcat(tim, "PM", sizeof tim); else strlcat(tim, "AM", sizeof tim); } else { if (style == 3) timeformat = "%H:%M:%S"; else timeformat = "%H:%M"; strftime(tim, sizeof tim, timeformat, 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); } tmux-tmux-f222026/window-copy.c000066400000000000000000004656511511153563100164140ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "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 struct screen *window_copy_get_screen(struct window_mode_entry *); static void window_copy_scroll1(struct window_mode_entry *, struct window_pane *wp, int, u_int, int); static void window_copy_pageup1(struct window_mode_entry *, int); static int window_copy_pagedown1(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, int, int); 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 *, int, int); static void window_copy_copy_selection(struct window_mode_entry *, const char *, int, int); 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_cursor_prompt(struct window_mode_entry *, int, 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); static u_int window_copy_clip_width(u_int, u_int, u_int, u_int); static u_int window_copy_search_mark_match(struct window_copy_mode_data *, 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, .get_screen = window_copy_get_screen }; 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, .get_screen = window_copy_get_screen }; 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 args *wargs; 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 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 #define WINDOW_COPY_SEARCH_MAX_LINE 2000 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); screen_set_default_cursor(&data->screen, global_w_options); 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'); if (base->hyperlinks != NULL) data->screen.hyperlinks = hyperlinks_copy(base->hyperlinks); 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->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->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_write_ctx backing_ctx, ctx; struct grid_cell gc; u_int old_hsize, old_cy; char *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) { vasprintf(&text, fmt, ap); input_parse_screen(data->ictx, backing, window_copy_init_ctx_cb, data, text, strlen(text)); free(text); } 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_scroll(struct window_pane *wp, int sl_mpos, u_int my, int scroll_exit) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); if (wme != NULL) { window_set_active_pane(wp->window, wp, 0); window_copy_scroll1(wme, wp, sl_mpos, my, scroll_exit); } } static void window_copy_scroll1(struct window_mode_entry *wme, struct window_pane *wp, int sl_mpos, u_int my, int scroll_exit) { struct window_copy_mode_data *data = wme->data; u_int ox, oy, px, py, n, offset, size; u_int new_offset; u_int slider_height = wp->sb_slider_h; u_int sb_height = wp->sy, sb_top = wp->yoff; u_int sy = screen_size_y(data->backing); int new_slider_y, delta; /* * sl_mpos is where in the slider the user is dragging, mouse is * dragging this y point relative to top of slider. */ if (my <= sb_top + sl_mpos) { /* Slider banged into top. */ new_slider_y = sb_top - wp->yoff; } else if (my - sl_mpos > sb_top + sb_height - slider_height) { /* Slider banged into bottom. */ new_slider_y = sb_top - wp->yoff + (sb_height - slider_height); } else { /* Slider is somewhere in the middle. */ new_slider_y = my - wp->yoff - sl_mpos + 1; } if (TAILQ_FIRST(&wp->modes) == NULL || window_copy_get_current_offset(wp, &offset, &size) == 0) return; /* * See screen_redraw_draw_pane_scrollbar - this is the inverse of the * formula used there. */ new_offset = new_slider_y * ((float)(size + sb_height) / sb_height); delta = (int)offset - new_offset; /* * Move pane view around based on delta relative to the cursor, * maintaining the selection. */ 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; if (delta >= 0) { n = (u_int)delta; 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; } else { n = (u_int)-delta; if (data->oy < n) { data->oy = 0; if (data->cy + (n - data->oy) >= sy) data->cy = sy - 1; else data->cy += n - data->oy; } else data->oy -= n; } /* Don't also drag tail when dragging a scrollbar, it looks weird. */ data->cursordrag = CURSORDRAG_NONE; 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) { window_pane_reset_mode(wp); return; } 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); } 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); } void window_copy_pagedown(struct window_pane *wp, int half_page, int scroll_exit) { if (window_copy_pagedown1(TAILQ_FIRST(&wp->modes), half_page, scroll_exit)) { window_pane_reset_mode(wp); return; } } static int window_copy_pagedown1(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->backing->grid; return (format_grid_word(gd, x, gd->hsize + y - data->oy)); } 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)); } char * window_copy_get_hyperlink(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_hyperlink(gd, x, gd->hsize + y, wp->screen)); } static void * window_copy_cursor_hyperlink_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; struct grid *gd = data->screen.grid; return (format_grid_hyperlink(gd, data->cx, gd->hsize + data->cy, &data->screen)); } 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; u_int hsize = screen_hsize(data->backing); struct grid_line *gl; gl = grid_get_line(data->backing->grid, hsize - data->oy); format_add(ft, "top_line_time", "%llu", (unsigned long long)gl->time); 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); 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); if (data->cursordrag != CURSORDRAG_NONE) format_add(ft, "selection_active", "1"); else format_add(ft, "selection_active", "0"); if (data->endselx != data->selx || data->endsely != data->sely) format_add(ft, "selection_present", "1"); else format_add(ft, "selection_present", "0"); } else { format_add(ft, "selection_active", "0"); format_add(ft, "selection_present", "0"); } format_add(ft, "search_present", "%d", data->searchmark != NULL); format_add(ft, "search_timed_out", "%d", data->timeout); if (data->searchcount != -1) { format_add(ft, "search_count", "%d", data->searchcount); format_add(ft, "search_count_partial", "%d", data->searchmore); } 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); format_add_cb(ft, "copy_cursor_hyperlink", window_copy_cursor_hyperlink_cb); } static struct screen * window_copy_get_screen(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; return (data->backing); } 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->wargs, 0); 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->wargs); u_int np = wme->prefix, ocx, ocy, ooy; struct window_copy_mode_data *data = wme->data; char *prefix = NULL, *command = NULL; const char *arg0 = args_string(cs->wargs, 0); const char *arg1 = args_string(cs->wargs, 1); int set_paste = !args_has(cs->wargs, 'P'); int set_clip = !args_has(cs->wargs, 'C'); if (pipe) { if (count == 2) prefix = format_single(NULL, arg1, c, s, wl, wp); if (s != NULL && count > 0 && *arg0 != '\0') command = format_single(NULL, arg0, c, s, wl, wp); } else { if (count == 1) prefix = format_single(NULL, arg0, 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, set_paste, set_clip); else window_copy_copy_selection(wme, prefix, set_paste, set_clip); 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->wargs); u_int np = wme->prefix, ocx, ocy, ooy; char *prefix = NULL, *command = NULL; const char *arg0 = args_string(cs->wargs, 0); const char *arg1 = args_string(cs->wargs, 1); int set_paste = !args_has(cs->wargs, 'P'); int set_clip = !args_has(cs->wargs, 'C'); if (pipe) { if (count == 2) prefix = format_single(NULL, arg1, c, s, wl, wp); if (s != NULL && count > 0 && *arg0 != '\0') command = format_single(NULL, arg0, c, s, wl, wp); } else { if (count == 1) prefix = format_single(NULL, arg0, 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, set_paste, set_clip); else window_copy_copy_selection(wme, prefix, set_paste, set_clip); 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 *arg0 = args_string(cs->wargs, 0); int set_paste = !args_has(cs->wargs, 'P'); int set_clip = !args_has(cs->wargs, 'C'); if (arg0 != NULL) prefix = format_single(NULL, arg0, c, s, wl, wp); if (s != NULL) window_copy_copy_selection(wme, prefix, set_paste, set_clip); 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); } /* Scroll line containing the cursor to the given position. */ static enum window_copy_cmd_action window_copy_cmd_scroll_to(struct window_copy_cmd_state *cs, u_int to) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; u_int oy, delta; int scroll_up; /* >0 up, <0 down */ scroll_up = data->cy - to; delta = abs(scroll_up); oy = screen_hsize(data->backing) - data->oy; /* * oy is the maximum scroll down amount, while data->oy is the maximum * scroll up amount. */ if (scroll_up > 0 && data->oy >= delta) { window_copy_scroll_up(wme, delta); data->cy -= delta; } else if (scroll_up < 0 && oy >= delta) { window_copy_scroll_down(wme, delta); data->cy += delta; } window_copy_update_selection(wme, 0, 0); return (WINDOW_COPY_CMD_REDRAW); } /* Scroll line containing the cursor to the bottom. */ static enum window_copy_cmd_action window_copy_cmd_scroll_bottom(struct window_copy_cmd_state *cs) { struct window_copy_mode_data *data = cs->wme->data; u_int bottom; bottom = screen_size_y(&data->screen) - 1; return (window_copy_cmd_scroll_to(cs, bottom)); } /* Scroll line containing the cursor to the middle. */ static enum window_copy_cmd_action window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) { struct window_copy_mode_data *data = cs->wme->data; u_int mid_value; mid_value = (screen_size_y(&data->screen) - 1) / 2; return (window_copy_cmd_scroll_to(cs, mid_value)); } /* Scroll line containing the cursor to the top. */ static enum window_copy_cmd_action window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs) { return (window_copy_cmd_scroll_to(cs, 0)); } static enum window_copy_cmd_action window_copy_cmd_cursor_up(struct window_copy_cmd_state *cs) { 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_centre_vertical(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; window_copy_update_cursor(wme, data->cx, wme->wp->sy / 2); window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } static enum window_copy_cmd_action window_copy_cmd_centre_horizontal(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; window_copy_update_cursor(wme, wme->wp->sx / 2, data->cy); window_copy_update_selection(wme, 1, 0); return (WINDOW_COPY_CMD_REDRAW); } 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_pagedown1(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_pagedown1(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_selection_mode(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct options *so = cs->s->options; struct window_copy_mode_data *data = wme->data; const char *s = args_string(cs->wargs, 0); if (s == NULL || strcasecmp(s, "char") == 0 || strcasecmp(s, "c") == 0) data->selflag = SEL_CHAR; else if (strcasecmp(s, "word") == 0 || strcasecmp(s, "w") == 0) { data->separators = options_get_string(so, "word-separators"); data->selflag = SEL_WORD; } else if (strcasecmp(s, "line") == 0 || strcasecmp(s, "l") == 0) data->selflag = SEL_LINE; 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_pagedown1(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_pagedown1(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 *so = 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(so, "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 *arg0 = args_string(cs->wargs, 0); const char *arg1 = args_string(cs->wargs, 1); int set_paste = !args_has(cs->wargs, 'P'); int set_clip = !args_has(cs->wargs, 'C'); if (arg1 != NULL) prefix = format_single(NULL, arg1, c, s, wl, wp); if (s != NULL && arg0 != NULL && *arg0 != '\0') command = format_single(NULL, arg0, c, s, wl, wp); window_copy_copy_pipe(wme, s, prefix, command, set_paste, set_clip); 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 *arg0 = args_string(cs->wargs, 0); if (s != NULL && arg0 != NULL && *arg0 != '\0') command = format_single(NULL, arg0, 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 *arg0 = args_string(cs->wargs, 0); if (*arg0 != '\0') window_copy_goto_line(wme, arg0); 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 *arg0 = args_string(cs->wargs, 0); if (*arg0 != '\0') { data->jumptype = WINDOW_COPY_JUMPBACKWARD; free(data->jumpchar); data->jumpchar = utf8_fromcstr(arg0); 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 *arg0 = args_string(cs->wargs, 0); if (*arg0 != '\0') { data->jumptype = WINDOW_COPY_JUMPFORWARD; free(data->jumpchar); data->jumpchar = utf8_fromcstr(arg0); 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 *arg0 = args_string(cs->wargs, 0); if (*arg0 != '\0') { data->jumptype = WINDOW_COPY_JUMPTOBACKWARD; free(data->jumpchar); data->jumpchar = utf8_fromcstr(arg0); 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 *arg0 = args_string(cs->wargs, 0); if (*arg0 != '\0') { data->jumptype = WINDOW_COPY_JUMPTOFORWARD; free(data->jumpchar); data->jumpchar = utf8_fromcstr(arg0); 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_next_prompt(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; window_copy_cursor_prompt(wme, 1, args_has(cs->wargs, 'o')); return (WINDOW_COPY_CMD_NOTHING); } static enum window_copy_cmd_action window_copy_cmd_previous_prompt(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; window_copy_cursor_prompt(wme, 0, args_has(cs->wargs, 'o')); 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 *arg0 = args_string(cs->wargs, 0); 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__, arg0); prefix = *arg0++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; } else if (ss != NULL && strcmp(arg0, ss) != 0) { data->cx = data->searchx; data->cy = data->searchy; data->oy = data->searcho; action = WINDOW_COPY_CMD_REDRAW; } if (*arg0 == '\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(arg0); 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(arg0); 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 *arg0 = args_string(cs->wargs, 0); 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__, arg0); prefix = *arg0++; if (data->searchx == -1 || data->searchy == -1) { data->searchx = data->cx; data->searchy = data->cy; data->searcho = data->oy; } else if (ss != NULL && strcmp(arg0, ss) != 0) { data->cx = data->searchx; data->cy = data->searchy; data->oy = data->searcho; action = WINDOW_COPY_CMD_REDRAW; } if (*arg0 == '\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(arg0); 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(arg0); 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; struct args_parse args; enum window_copy_cmd_clear clear; enum window_copy_cmd_action (*f)(struct window_copy_cmd_state *); } window_copy_cmd_table[] = { { .command = "append-selection", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_append_selection }, { .command = "append-selection-and-cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_append_selection_and_cancel }, { .command = "back-to-indentation", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_back_to_indentation }, { .command = "begin-selection", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_begin_selection }, { .command = "bottom-line", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_bottom_line }, { .command = "cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_cancel }, { .command = "clear-selection", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_clear_selection }, { .command = "copy-end-of-line", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_end_of_line }, { .command = "copy-end-of-line-and-cancel", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_end_of_line_and_cancel }, { .command = "copy-pipe-end-of-line", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_end_of_line }, { .command = "copy-pipe-end-of-line-and-cancel", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_end_of_line_and_cancel }, { .command = "copy-line", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_line }, { .command = "copy-line-and-cancel", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_line_and_cancel }, { .command = "copy-pipe-line", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_line }, { .command = "copy-pipe-line-and-cancel", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_line_and_cancel }, { .command = "copy-pipe-no-clear", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_copy_pipe_no_clear }, { .command = "copy-pipe", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe }, { .command = "copy-pipe-and-cancel", .args = { "CP", 0, 2, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_and_cancel }, { .command = "copy-selection-no-clear", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_copy_selection_no_clear }, { .command = "copy-selection", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_selection }, { .command = "copy-selection-and-cancel", .args = { "CP", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_selection_and_cancel }, { .command = "cursor-down", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_down }, { .command = "cursor-down-and-cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_cursor_down_and_cancel }, { .command = "cursor-left", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_left }, { .command = "cursor-right", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_right }, { .command = "cursor-up", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_cursor_up }, { .command = "cursor-centre-vertical", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_centre_vertical, }, { .command = "cursor-centre-horizontal", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_centre_horizontal, }, { .command = "end-of-line", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_end_of_line }, { .command = "goto-line", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_goto_line }, { .command = "halfpage-down", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_halfpage_down }, { .command = "halfpage-down-and-cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_halfpage_down_and_cancel }, { .command = "halfpage-up", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_halfpage_up }, { .command = "history-bottom", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_history_bottom }, { .command = "history-top", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_history_top }, { .command = "jump-again", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_again }, { .command = "jump-backward", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_backward }, { .command = "jump-forward", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_forward }, { .command = "jump-reverse", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_reverse }, { .command = "jump-to-backward", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_to_backward }, { .command = "jump-to-forward", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_jump_to_forward }, { .command = "jump-to-mark", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_jump_to_mark }, { .command = "next-prompt", .args = { "o", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_next_prompt }, { .command = "previous-prompt", .args = { "o", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_previous_prompt }, { .command = "middle-line", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_middle_line }, { .command = "next-matching-bracket", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_next_matching_bracket }, { .command = "next-paragraph", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_paragraph }, { .command = "next-space", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_space }, { .command = "next-space-end", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_space_end }, { .command = "next-word", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_word }, { .command = "next-word-end", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_next_word_end }, { .command = "other-end", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_other_end }, { .command = "page-down", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_page_down }, { .command = "page-down-and-cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_page_down_and_cancel }, { .command = "page-up", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_page_up }, { .command = "pipe-no-clear", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_pipe_no_clear }, { .command = "pipe", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_pipe }, { .command = "pipe-and-cancel", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_pipe_and_cancel }, { .command = "previous-matching-bracket", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_previous_matching_bracket }, { .command = "previous-paragraph", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_paragraph }, { .command = "previous-space", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_space }, { .command = "previous-word", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_previous_word }, { .command = "rectangle-on", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_on }, { .command = "rectangle-off", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_off }, { .command = "rectangle-toggle", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_rectangle_toggle }, { .command = "refresh-from-pane", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_refresh_from_pane }, { .command = "scroll-bottom", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_bottom }, { .command = "scroll-down", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_scroll_down }, { .command = "scroll-down-and-cancel", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_down_and_cancel }, { .command = "scroll-middle", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_middle }, { .command = "scroll-top", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_top }, { .command = "scroll-up", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_scroll_up }, { .command = "search-again", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_again }, { .command = "search-backward", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward }, { .command = "search-backward-text", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward_text }, { .command = "search-backward-incremental", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_backward_incremental }, { .command = "search-forward", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward }, { .command = "search-forward-text", .args = { "", 0, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward_text }, { .command = "search-forward-incremental", .args = { "", 1, 1, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_forward_incremental }, { .command = "search-reverse", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_reverse }, { .command = "select-line", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_select_line }, { .command = "select-word", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_select_word }, { .command = "selection-mode", .args = { "", 0, 1, NULL }, .clear = 0, .f = window_copy_cmd_selection_mode }, { .command = "set-mark", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_set_mark }, { .command = "start-of-line", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_start_of_line }, { .command = "stop-selection", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_stop_selection }, { .command = "toggle-position", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_NEVER, .f = window_copy_cmd_toggle_position }, { .command = "top-line", .args = { "", 0, 0, NULL }, .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_pane *wp = wme->wp; 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; char *error = NULL; 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.wargs = NULL; 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) { cs.wargs = args_parse(&window_copy_cmd_table[i].args, args_values(args), count, &error); if (error != NULL) { free(error); error = NULL; } if (cs.wargs == NULL) break; clear = window_copy_cmd_table[i].clear; action = window_copy_cmd_table[i].f(&cs); args_free(cs.wargs); cs.wargs = NULL; break; } } if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) { keys = options_get_number(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(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 (*sud->data == '\t' && sud->size == 1 && gc.flags & GRID_FLAG_TAB) return (1); 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, padding; int matched; struct grid_line *gl; struct grid_cell gc; endline = gd->hsize + gd->sy - 1; for (ax = first; ax < last; ax++) { padding = 0; for (bx = 0; bx < sgd->sx; bx++) { px = ax + bx + padding; 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 - padding >= gd->sx) break; grid_get_cell(gd, px, pywrap, &gc); if (gc.flags & GRID_FLAG_TAB) padding += gc.data.width - 1; 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, padding; int matched; struct grid_line *gl; struct grid_cell gc; endline = gd->hsize + gd->sy - 1; for (ax = last; ax > first; ax--) { padding = 0; for (bx = 0; bx < sgd->sx; bx++) { px = ax - 1 + bx + padding; 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 - padding >= gd->sx) break; grid_get_cell(gd, px, pywrap, &gc); if (gc.flags & GRID_FLAG_TAB) padding += gc.data.width - 1; 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 && len < WINDOW_COPY_SEARCH_MAX_LINE) { 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, ®match, 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 && len < WINDOW_COPY_SEARCH_MAX_LINE) { 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); } if (gce->flags & GRID_FLAG_TAB) { *size = 1; *allocated = 0; return ("\t"); } 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, ®match, 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(®, 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, ®); } 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, ®); if (found) { window_copy_search_back_overlap(gd, ®, &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(®); 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, ssx; 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); if (visible_only == 0 && data->searchmark != NULL) window_copy_clear_marks(wme); free(wp->searchstr); wp->searchstr = xstrdup(str); wp->searchregex = regex; fx = data->cx; fy = screen_hsize(data->backing) - data->oy + data->cy; if ((ssx = screen_write_strlen("%s", str)) == 0) return (0); screen_init(&ss, ssx, 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 != NULL && 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 != NULL && 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 u_int window_copy_clip_width(u_int width, u_int b, u_int sx, u_int sy) { return ((b + width > sx * sy) ? (sx * sy) - b : width); } static u_int window_copy_search_mark_match(struct window_copy_mode_data *data, u_int px, u_int py, u_int width, int regex) { struct grid *gd = data->backing->grid; struct grid_cell gc; u_int i, b, w = width, sx = gd->sx, sy = gd->sy; if (window_copy_search_mark_at(data, px, py, &b) == 0) { width = window_copy_clip_width(width, b, sx, sy); w = width; for (i = b; i < b + w; i++) { if (!regex) { grid_get_cell(gd, px + (i - b), py, &gc); if (gc.flags & GRID_FLAG_TAB) w += gc.data.width - 1; w = window_copy_clip_width(w, b, sx, sy); } if (data->searchmark[i] != 0) continue; data->searchmark[i] = data->searchgen; } if (data->searchgen == UCHAR_MAX) data->searchgen = 1; else data->searchgen++; } return (w); } 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; struct grid_cell gc; int found, cis, stopped = 0; int cflags = REG_EXTENDED; u_int px, py, nfound = 0, width; u_int ssize = 1, start, end, sx = gd->sx; u_int sy = gd->sy; 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(®, 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 + sy; stop = get_timer() + WINDOW_COPY_SEARCH_ALL_TIMEOUT; } again: free(data->searchmark); data->searchmark = xcalloc(sx, 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, sx, ®); grid_get_cell(gd, px + width - 1, py, &gc); if (gc.data.width > 2) width += gc.data.width - 1; if (!found) break; } else { found = window_copy_search_lr(gd, ssp->grid, &px, py, px, sx, cis); if (!found) break; } nfound++; px += window_copy_search_mark_match(data, px, py, width, regex); } 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(®); 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); if (gc.flags & GRID_FLAG_TAB) { buf = xrealloc(buf, len + 2); buf[len] = '\t'; len++; } else if (gc.flags & GRID_FLAG_PADDING) { /* nothing to do */ } else { 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, ¤t) != 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); } } } int window_copy_get_current_offset(struct window_pane *wp, u_int *offset, u_int *size) { struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); struct window_copy_mode_data *data = wme->data; u_int hsize; if (data == NULL) return (0); hsize = screen_hsize(data->backing); *offset = hsize - data->oy; *size = hsize; return (1); } 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; u_int sx = screen_size_x(s); u_int hsize = screen_hsize(data->backing); const char *value; char *expanded; struct format_tree *ft; ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); style_apply(&gc, oo, "copy-mode-position-style", ft); gc.flags |= GRID_FLAG_NOPALETTE; style_apply(&mgc, oo, "copy-mode-match-style", ft); mgc.flags |= GRID_FLAG_NOPALETTE; style_apply(&cgc, oo, "copy-mode-current-match-style", ft); cgc.flags |= GRID_FLAG_NOPALETTE; style_apply(&mkgc, oo, "copy-mode-mark-style", ft); mkgc.flags |= GRID_FLAG_NOPALETTE; window_copy_write_one(wme, ctx, py, hsize - data->oy + py, screen_size_x(s), &mgc, &cgc, &mkgc); if (py == 0 && s->rupper < s->rlower && !data->hide_position) { value = options_get_string(oo, "copy-mode-position-format"); if (*value != '\0') { expanded = format_expand(ft, value); if (*expanded != '\0') { screen_write_cursormove(ctx, 0, 0, 0); format_draw(ctx, &gc, sx, expanded, NULL, 0); } free(expanded); } } 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, '$'); } format_free(ft); } 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); wp->flags |= PANE_REDRAWSCROLLBAR; } 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; struct format_tree *ft; 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. */ ft = format_create_defaults(NULL, NULL, NULL, NULL, wp); style_apply(&gc, oo, "copy-mode-selection-style", ft); gc.flags |= GRID_FLAG_NOPALETTE; format_free(ft); 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, int set_paste, int set_clip) { struct window_pane *wp = wme->wp; struct screen_write_ctx ctx; if (set_clip && 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); } if (set_paste) paste_add(prefix, buf, len); else free(buf); } 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) { void *buf; size_t len; buf = window_copy_pipe_run(wme, s, cmd, &len); free (buf); } static void window_copy_copy_pipe(struct window_mode_entry *wme, struct session *s, const char *prefix, const char *cmd, int set_paste, int set_clip) { 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, set_paste, set_clip); } } static void window_copy_copy_selection(struct window_mode_entry *wme, const char *prefix, int set_paste, int set_clip) { char *buf; size_t len; buf = window_copy_get_selection(wme, &len); if (buf != NULL) { window_copy_copy_buffer(wme, prefix, buf, len, set_paste, set_clip); } } 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; if (gc.flags & GRID_FLAG_TAB) utf8_set(&ud, '\t'); else 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; return (grid_in_set(data->backing->grid, px, py, set)); } 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, 0, 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_cursor_prompt(struct window_mode_entry *wme, int direction, int start_output) { struct window_copy_mode_data *data = wme->data; struct screen *s = data->backing; struct grid *gd = s->grid; u_int end_line; u_int line = gd->hsize - data->oy + data->cy; int add, line_flag; if (start_output) line_flag = GRID_LINE_START_OUTPUT; else line_flag = GRID_LINE_START_PROMPT; if (direction == 0) { /* up */ add = -1; end_line = 0; } else { /* down */ add = 1; end_line = gd->hsize + gd->sy - 1; } if (line == end_line) return; for (;;) { if (line == end_line) return; line += add; if (grid_get_line(gd, line)->flags & line_flag) break; } data->cx = 0; if (line > gd->hsize) { data->cy = line - gd->hsize; data->oy = 0; } else { data->cy = 0; data->oy = gd->hsize - line; } window_copy_update_selection(wme, 1, 0); window_copy_redraw_screen(wme); } static void window_copy_scroll_up(struct window_mode_entry *wme, u_int ny) { struct window_pane *wp = wme->wp; 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); wp->flags |= PANE_REDRAWSCROLLBAR; } 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); wp->flags |= PANE_REDRAWSCROLLBAR; } 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); } tmux-tmux-f222026/window-customize.c000066400000000000000000001170531511153563100174520ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2020 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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; int prompt_flags; 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')); if (args_has(args, 'y')) data->prompt_flags = PROMPT_ACCEPT; data->data = mode_tree_start(wp, args, window_customize_build, window_customize_draw, NULL, window_customize_menu, window_customize_height, NULL, 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, 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, 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|data->prompt_flags, 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|data->prompt_flags, 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|data->prompt_flags, 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|data->prompt_flags, 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; } } tmux-tmux-f222026/window-tree.c000066400000000000000000001036541511153563100163710ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2017 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "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; int prompt_flags; 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) { struct window_pane **a = (struct window_pane **)a0; struct window_pane **b = (struct window_pane **)b0; int result; u_int ai, bi; if (window_tree_sort->field == WINDOW_TREE_BY_TIME) result = (*a)->active_point - (*b)->active_point; else { /* * Panes don't have names, so use number order for any other * sort field. */ window_pane_index(*a, &ai); window_pane_index(*b, &bi); result = ai - bi; } 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; struct mode_tree_item *mti; char *name, *text; u_int idx; struct format_tree *ft; 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; ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, 0); format_defaults(ft, NULL, s, wl, wp); text = format_expand(ft, data->format); xasprintf(&name, "%u", idx); format_free(ft); mti = mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1); free(text); free(name); mode_tree_align(mti, 1); } 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; struct format_tree *ft; item = window_tree_add_item(data); item->type = WINDOW_TREE_WINDOW; item->session = s->id; item->winlink = wl->idx; item->pane = -1; ft = format_create(NULL, NULL, FORMAT_PANE|wl->window->active->id, 0); format_defaults(ft, NULL, s, wl, NULL); text = format_expand(ft, data->format); xasprintf(&name, "%u", wl->idx); format_free(ft); 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); mode_tree_align(mti, 1); 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; } 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 = s->curw, **l; u_int n, i, empty; int expanded; struct format_tree *ft; item = window_tree_add_item(data); item->type = WINDOW_TREE_SESSION; item->session = s->id; item->winlink = -1; item->pane = -1; ft = format_create(NULL, NULL, FORMAT_PANE|wl->window->active->id, 0); format_defaults(ft, NULL, s, NULL, NULL); text = format_expand(ft, data->format); format_free(ft); 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) { free(label); 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, pane_idx; struct grid_cell gc; int colour, active_colour, left, right; 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, int icase) { 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); if (icase) return (strcasestr(s->name, ss) != NULL); return (strstr(s->name, ss) != NULL); case WINDOW_TREE_WINDOW: if (s == NULL || wl == NULL) return (0); if (icase) return (strcasestr(wl->window->name, ss) != NULL); 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') { free(cmd); return (0); } if (icase) retval = (strcasestr(cmd, ss) != NULL); else 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 int window_tree_swap(void *cur_itemdata, void *other_itemdata) { struct window_tree_itemdata *cur = cur_itemdata; struct window_tree_itemdata *other = other_itemdata; struct session *cur_session, *other_session; struct winlink *cur_winlink, *other_winlink; struct window *cur_window, *other_window; struct window_pane *cur_pane, *other_pane; if (cur->type != other->type) return (0); if (cur->type != WINDOW_TREE_WINDOW) return (0); window_tree_pull_item(cur, &cur_session, &cur_winlink, &cur_pane); window_tree_pull_item(other, &other_session, &other_winlink, &other_pane); if (cur_session != other_session) return (0); if (window_tree_sort->field != WINDOW_TREE_BY_INDEX && window_tree_cmp_window(&cur_winlink, &other_winlink) != 0) { /* * Swapping indexes would not swap positions in the tree, so * prevent swapping to avoid confusing the user. */ return (0); } other_window = other_winlink->window; TAILQ_REMOVE(&other_window->winlinks, other_winlink, wentry); cur_window = cur_winlink->window; TAILQ_REMOVE(&cur_window->winlinks, cur_winlink, wentry); other_winlink->window = cur_window; TAILQ_INSERT_TAIL(&cur_window->winlinks, other_winlink, wentry); cur_winlink->window = other_window; TAILQ_INSERT_TAIL(&other_window->winlinks, cur_winlink, wentry); if (cur_session->curw == cur_winlink) session_set_current(cur_session, other_winlink); else if (cur_session->curw == other_winlink) session_set_current(cur_session, cur_winlink); session_group_synchronize_from(cur_session); server_redraw_session_group(cur_session); recalculate_sizes(); return (1); } 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'); if (args_has(args, 'y')) data->prompt_flags = PROMPT_ACCEPT; data->data = mode_tree_start(wp, args, window_tree_build, window_tree_draw, window_tree_search, window_tree_menu, NULL, window_tree_get_key, window_tree_swap, 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); again: 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); goto again; } 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|data->prompt_flags, 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|data->prompt_flags, 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; } } tmux-tmux-f222026/window.c000066400000000000000000001230641511153563100154310ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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; struct client_file *file; }; static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); static void window_pane_full_size_offset(struct window_pane *wp, u_int *xoff, u_int *yoff, u_int *sx, u_int *sy); 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); wl->flags |= WINLINK_VISITED; } void winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl) { if (wl != NULL && (wl->flags & WINLINK_VISITED)) { TAILQ_REMOVE(stack, wl, sentry); wl->flags &= ~WINLINK_VISITED; } } 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); TAILQ_INIT(&w->last_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); window_unzoom(w, 0); 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 && (~wp->flags & PANE_EXITED)) { 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 && c->overlay_draw == NULL) { 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) { struct window_pane *lastwp; log_debug("%s: pane %%%u", __func__, wp->id); if (wp == w->active) return (0); lastwp = w->active; window_pane_stack_remove(&w->last_panes, wp); window_pane_stack_push(&w->last_panes, lastwp); w->active = wp; w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; if (options_get_number(global_options, "focus-events")) { window_pane_update_focus(lastwp); 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; u_int xoff, yoff, sx, sy; TAILQ_FOREACH(wp, &w->panes, entry) { if (!window_pane_visible(wp)) continue; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); if (x < xoff || x > xoff + sx) continue; if (y < yoff || y > yoff + 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, int notify) { 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); if (notify) 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, 1) == 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(); window_pane_stack_remove(&w->last_panes, wp); if (wp == w->active) { w->active = TAILQ_FIRST(&w->last_panes); if (w->active == NULL) { w->active = TAILQ_PREV(wp, window_panes, entry); if (w->active == NULL) w->active = TAILQ_NEXT(wp, entry); } if (w->active != NULL) { window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; notify_window("window-pane-changed", w); window_update_focus(w); } } } 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->last_panes)) { wp = TAILQ_FIRST(&w->last_panes); window_pane_stack_remove(&w->last_panes, wp); } while (!TAILQ_EMPTY(&w->panes)) { wp = TAILQ_FIRST(&w->panes); TAILQ_REMOVE(&w->panes, wp, entry); 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|PANE_THEMECHANGED); 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; wp->control_bg = -1; wp->control_fg = -1; style_set_scrollbar_style_from_option(&wp->scrollbar_style, wp->options); colour_palette_init(&wp->palette); colour_palette_from_option(&wp->palette, wp->options); screen_init(&wp->base, sx, sy, hlimit); wp->screen = &wp->base; window_pane_default_cursor(wp); 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); kill(getpid(), SIGCHLD); #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); if (wp->event == NULL) fatalx("out of memory"); wp->ictx = input_init(wp, wp->event, &wp->palette); bufferevent_enable(wp->event, EV_READ|EV_WRITE); } 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; struct window *w = wp->window; 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_REDRAWSCROLLBAR|PANE_CHANGED); layout_fix_panes(w, NULL); 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; struct window *w = wp->window; 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) { wp->flags &= ~PANE_UNSEENCHANGES; 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_REDRAWSCROLLBAR|PANE_CHANGED); layout_fix_panes(w, NULL); 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_paste(struct window_pane *wp, char *buf, size_t len) { 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")) { log_debug("%s: %.*s", __func__, (int)len, buf); bufferevent_write(loop->event, buf, len); } } } 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); } } void window_pane_paste(struct window_pane *wp, key_code key, char *buf, size_t len) { if (!TAILQ_EMPTY(&wp->modes)) return; if (wp->fd == -1 || wp->flags & PANE_INPUTOFF) return; if (KEYC_IS_PASTE(key) && (~wp->screen->mode & MODE_BRACKETPASTE)) return; log_debug("%s: %.*s", __func__, (int)len, buf); bufferevent_write(wp->event, buf, len); if (options_get_number(wp->options, "synchronize-panes")) window_pane_copy_paste(wp, buf, len); } 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); } int window_pane_exited(struct window_pane *wp) { return (wp->fd == -1 || (wp->flags & PANE_EXITED)); } u_int window_pane_search(struct window_pane *wp, const char *term, int regex, int ignore) { 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); } /* * Get full size and offset of a window pane including the area of the * scrollbars if they were visible but not including the border(s). */ static void window_pane_full_size_offset(struct window_pane *wp, u_int *xoff, u_int *yoff, u_int *sx, u_int *sy) { struct window *w = wp->window; int pane_scrollbars; u_int sb_w, sb_pos; pane_scrollbars = options_get_number(w->options, "pane-scrollbars"); sb_pos = options_get_number(w->options, "pane-scrollbars-position"); if (window_pane_show_scrollbar(wp, pane_scrollbars)) sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad; else sb_w = 0; if (sb_pos == PANE_SCROLLBARS_LEFT) { *xoff = wp->xoff - sb_w; *sx = wp->sx + sb_w; } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ *xoff = wp->xoff; *sx = wp->sx + sb_w; } *yoff = wp->yoff; *sy = wp->sy; } /* * 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; u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); w = wp->window; status = options_get_number(w->options, "pane-border-status"); list = NULL; size = 0; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); edge = 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 = xoff; right = xoff + sx; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; if (yoff + sy + 1 != edge) continue; end = xoff + sx - 1; found = 0; if (xoff < left && end > right) found = 1; else if (xoff >= left && 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; u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); w = wp->window; status = options_get_number(w->options, "pane-border-status"); list = NULL; size = 0; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); edge = yoff + 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) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; if (yoff != edge) continue; end = xoff + sx - 1; found = 0; if (xoff < left && end > right) found = 1; else if (xoff >= left && 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; u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); w = wp->window; list = NULL; size = 0; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); edge = xoff; if (edge == 0) edge = w->sx + 1; top = yoff; bottom = yoff + sy; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; if (xoff + sx + 1 != edge) continue; end = yoff + sy - 1; found = 0; if (yoff < top && end > bottom) found = 1; else if (yoff >= top && 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; u_int xoff, yoff, sx, sy; if (wp == NULL) return (NULL); w = wp->window; list = NULL; size = 0; window_pane_full_size_offset(wp, &xoff, &yoff, &sx, &sy); edge = xoff + sx + 1; if (edge >= w->sx) edge = 0; top = wp->yoff; bottom = wp->yoff + wp->sy; TAILQ_FOREACH(next, &w->panes, entry) { window_pane_full_size_offset(next, &xoff, &yoff, &sx, &sy); if (next == wp) continue; if (xoff != edge) continue; end = yoff + sy - 1; found = 0; if (yoff < top && end > bottom) found = 1; else if (yoff >= top && 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); } void window_pane_stack_push(struct window_panes *stack, struct window_pane *wp) { if (wp != NULL) { window_pane_stack_remove(stack, wp); TAILQ_INSERT_HEAD(stack, wp, sentry); wp->flags |= PANE_VISITED; } } void window_pane_stack_remove(struct window_panes *stack, struct window_pane *wp) { if (wp != NULL && (wp->flags & PANE_VISITED)) { TAILQ_REMOVE(stack, wp, sentry); wp->flags &= ~PANE_VISITED; } } /* Clear alert flags for a winlink */ void winlink_clear_flags(struct winlink *wl) { 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 (cdata->file != NULL && (wp == NULL || c->flags & CLIENT_DEAD)) { if (wp == NULL) { c->retval = 1; c->flags |= CLIENT_EXIT; } file_cancel(cdata->file); } else if (cdata->file == NULL || closed || error != 0) { cmdq_continue(cdata->item); server_client_unref(c); free(cdata); } else 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; cdata->file = file_read(c, "-", window_pane_input_callback, cdata); c->references++; 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; else free(ud); } } void window_pane_default_cursor(struct window_pane *wp) { screen_set_default_cursor(wp->screen, wp->options); } int window_pane_mode(struct window_pane *wp) { if (TAILQ_FIRST(&wp->modes) != NULL) { if (TAILQ_FIRST(&wp->modes)->mode == &window_copy_mode) return (WINDOW_PANE_COPY_MODE); if (TAILQ_FIRST(&wp->modes)->mode == &window_view_mode) return (WINDOW_PANE_VIEW_MODE); } return (WINDOW_PANE_NO_MODE); } /* Return 1 if scrollbar is or should be displayed. */ int window_pane_show_scrollbar(struct window_pane *wp, int sb_option) { if (SCREEN_IS_ALTERNATE(wp->screen)) return (0); if (sb_option == PANE_SCROLLBARS_ALWAYS || (sb_option == PANE_SCROLLBARS_MODAL && window_pane_mode(wp) != WINDOW_PANE_NO_MODE)) return (1); return (0); } int window_pane_get_bg(struct window_pane *wp) { int c; struct grid_cell defaults; c = window_pane_get_bg_control_client(wp); if (c == -1) { tty_default_colours(&defaults, wp); if (COLOUR_DEFAULT(defaults.bg)) c = window_get_bg_client(wp); else c = defaults.bg; } return (c); } /* Get a client with a background for the pane. */ int window_get_bg_client(struct window_pane *wp) { struct window *w = wp->window; struct client *loop; TAILQ_FOREACH(loop, &clients, entry) { if (loop->flags & CLIENT_UNATTACHEDFLAGS) continue; if (loop->session == NULL || !session_has(loop->session, w)) continue; if (loop->tty.bg == -1) continue; return (loop->tty.bg); } return (-1); } /* * If any control mode client exists that has provided a bg color, return it. * Otherwise, return -1. */ int window_pane_get_bg_control_client(struct window_pane *wp) { struct client *c; if (wp->control_bg == -1) return (-1); TAILQ_FOREACH(c, &clients, entry) { if (c->flags & CLIENT_CONTROL) return (wp->control_bg); } return (-1); } /* * Get a client with a foreground for the pane. There isn't much to choose * between them so just use the first. */ int window_pane_get_fg(struct window_pane *wp) { struct window *w = wp->window; struct client *loop; TAILQ_FOREACH(loop, &clients, entry) { if (loop->flags & CLIENT_UNATTACHEDFLAGS) continue; if (loop->session == NULL || !session_has(loop->session, w)) continue; if (loop->tty.fg == -1) continue; return (loop->tty.fg); } return (-1); } /* * If any control mode client exists that has provided a fg color, return it. * Otherwise, return -1. */ int window_pane_get_fg_control_client(struct window_pane *wp) { struct client *c; if (wp->control_fg == -1) return (-1); TAILQ_FOREACH(c, &clients, entry) { if (c->flags & CLIENT_CONTROL) return (wp->control_fg); } return (-1); } enum client_theme window_pane_get_theme(struct window_pane *wp) { struct window *w; struct client *loop; enum client_theme theme; int found_light = 0, found_dark = 0; if (wp == NULL) return (THEME_UNKNOWN); w = wp->window; /* * Derive theme from pane background color, if it's not the default * colour. */ theme = colour_totheme(window_pane_get_bg(wp)); if (theme != THEME_UNKNOWN) return (theme); /* Try to find a client that has a theme. */ TAILQ_FOREACH(loop, &clients, entry) { if (loop->flags & CLIENT_UNATTACHEDFLAGS) continue; if (loop->session == NULL || !session_has(loop->session, w)) continue; switch (loop->theme) { case THEME_LIGHT: found_light = 1; break; case THEME_DARK: found_dark = 1; break; case THEME_UNKNOWN: break; } } if (found_dark && !found_light) return (THEME_DARK); if (found_light && !found_dark) return (THEME_LIGHT); return (THEME_UNKNOWN); } void window_pane_send_theme_update(struct window_pane *wp) { if (wp == NULL || window_pane_exited(wp)) return; if (~wp->flags & PANE_THEMECHANGED) return; if (~wp->screen->mode & MODE_THEME_UPDATES) return; switch (window_pane_get_theme(wp)) { case THEME_LIGHT: input_key_pane(wp, KEYC_REPORT_LIGHT_THEME, NULL); break; case THEME_DARK: input_key_pane(wp, KEYC_REPORT_DARK_THEME, NULL); break; case THEME_UNKNOWN: break; } wp->flags &= ~PANE_THEMECHANGED; } tmux-tmux-f222026/xmalloc.c000066400000000000000000000060121511153563100155520ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , 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 #include #include #include #include #include #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; } tmux-tmux-f222026/xmalloc.h000066400000000000000000000032321511153563100155600ustar00rootroot00000000000000/* $OpenBSD$ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , 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 */