├── .envrc ├── regress ├── client-panic.txt ├── UTF-8-test.txt ├── copy-mode-test.txt ├── cursor-test3.result ├── cursor-test4.result ├── combine-test.result ├── Makefile ├── conf │ ├── 21867280ff7e99631046f9cc669b80d2.conf │ ├── 327af72ad372255817b585a74da06eda.conf │ ├── 872441a98b06444acc5ce08eb24aabde.conf │ ├── dfd579a114a8366b5a665c264e29c084.conf │ ├── 91378fd400b0444eb8cac471e30642b3.conf │ ├── f4f1cdb9d518c2f7808a4915299f2524.conf │ ├── 29813ff35544434e2e64dc879a8dd274.conf │ ├── 2eae5d47049c1f6d0bef3db4e171aed7.conf │ ├── b9f0ce1976ec62ec60dc5da7dd92c160.conf │ ├── a4789a6782859c66aa8c9614ee6fabfa.conf │ ├── ad0537c4e83d7a25d5dc4f3a3c571349.conf │ ├── 99749670b62bcb99a9b2e3d59708e357.conf │ ├── 58304907c117cab9898ea0b070bccde3.conf │ └── d0040b2e097f1e3d31d78eed6ce8d461.conf ├── conf-syntax.sh ├── cursor-test.txt ├── new-session-command.sh ├── new-window-command.sh ├── utf8-test.sh ├── new-session-no-client.sh ├── new-session-base-index.sh ├── kill-session-process-exit.sh ├── if-shell-nested.sh ├── has-session-return.sh ├── if-shell-error.sh ├── run-shell-output.sh ├── capture-pane-sgr0.sh ├── cursor-test2.result ├── new-session-size.sh ├── if-shell-TERM.sh ├── am-terminal.sh ├── capture-pane-hyperlink.sh ├── cursor-test2.sh ├── cursor-test3.sh ├── cursor-test1.sh ├── cursor-test1.result ├── control-client-sanity.sh ├── cursor-test4.sh ├── command-order.sh ├── combine-test.sh ├── new-session-environment.sh ├── control-client-size.sh ├── style-trim.sh ├── copy-mode-test-vi.sh └── copy-mode-test-emacs.sh ├── docs └── _config.yml ├── .github ├── codecov.yml └── workflows │ ├── coverage.yml │ ├── wiki-update.yml │ └── build.yml ├── .cargo └── config.toml ├── .config └── nextest.toml ├── .gitignore ├── src ├── compat │ ├── getdtablecount.rs │ ├── getpeereid.rs │ ├── systemd.rs │ ├── getprogname.rs │ ├── fdforkpty.rs │ ├── strlcpy.rs │ ├── ntohll.rs │ ├── freezero.rs │ ├── strlcat.rs │ ├── closefrom.rs │ ├── reallocarray.rs │ ├── mod.rs │ ├── strtonum.rs │ ├── setproctitle.rs │ ├── recallocarray.rs │ └── getopt.rs ├── utempter.rs ├── ncurses_.rs ├── bitstr.rs ├── cmd_ │ ├── cmd_kill_server.rs │ ├── cmd_rename_window.rs │ ├── cmd_kill_pane.rs │ ├── cmd_kill_session.rs │ ├── cmd_rename_session.rs │ ├── cmd_list_buffers.rs │ ├── cmd_lock_server.rs │ ├── cmd_list_sessions.rs │ ├── cmd_respawn_window.rs │ ├── cmd_respawn_pane.rs │ ├── cmd_swap_window.rs │ ├── cmd_bind_key.rs │ ├── cmd_list_clients.rs │ ├── cmd_unbind_key.rs │ ├── cmd_copy_mode.rs │ ├── cmd_show_messages.rs │ ├── cmd_detach_client.rs │ ├── cmd_load_buffer.rs │ ├── cmd_kill_window.rs │ ├── cmd_choose_tree.rs │ ├── cmd_list_windows.rs │ ├── cmd_save_buffer.rs │ ├── cmd_show_prompt_history.rs │ ├── cmd_paste_buffer.rs │ └── cmd_set_environment.rs ├── main.rs ├── utf8_combined.rs ├── tmux_protocol.rs └── attributes.rs ├── rustfmt.toml ├── fuzz ├── .gitignore ├── README.md ├── fuzz_targets │ └── colour_find_rgb.rs └── Cargo.toml ├── clippy.toml ├── COPYING ├── CHANGELOG.md ├── README.md ├── .mailmap ├── flake.nix ├── Cargo.toml └── flake.lock /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /regress/client-panic.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker 2 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | github_checks: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = [ 3 | # "-Zsanitizer=address", 4 | ] 5 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.ci] 2 | fail-fast = false 3 | junit.path = "junit.xml" 4 | -------------------------------------------------------------------------------- /regress/UTF-8-test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardscollin/tmux-rs/HEAD/regress/UTF-8-test.txt -------------------------------------------------------------------------------- /regress/copy-mode-test.txt: -------------------------------------------------------------------------------- 1 | A line of words 2 | Indented line 3 | Another line... 4 | ... @nd then $ym_bols[]{} 5 | ?? 500xyz 6 | -------------------------------------------------------------------------------- /regress/cursor-test3.result: -------------------------------------------------------------------------------- 1 | 6 1 b 2 | 0 abcdefa 3 | 1 bcdefab 4 | 3 1 b 5 | 0 fabcd 6 | 1 efab 7 | 6 1 b 8 | 0 abcdefa 9 | 1 bcdefab 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust build cache and outputs 2 | /target 3 | # Nix outputs 4 | /result 5 | # tmux log files 6 | tmux-*.log 7 | 8 | # github wiki in CI 9 | /wiki 10 | -------------------------------------------------------------------------------- /regress/cursor-test4.result: -------------------------------------------------------------------------------- 1 | 0 1 2 | 0 abcdef 3 | 1 4 | 2 5 | 0 1 6 | 0 abcdef 7 | 1 8 | 2 9 | 0 1 10 | 0 def 11 | 1 12 | 2 13 | 0 1 14 | 0 abcdef 15 | 1 16 | 2 17 | -------------------------------------------------------------------------------- /src/compat/getdtablecount.rs: -------------------------------------------------------------------------------- 1 | pub fn getdtablecount() -> i32 { 2 | if let Ok(read_dir) = std::fs::read_dir("/proc/self/fd") { 3 | read_dir.count() as i32 4 | } else { 5 | 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | 3 | # using nightly rustfmt options 4 | # use: cargo +nightly fmt 5 | 6 | group_imports = "StdExternalCrate" 7 | normalize_comments = true 8 | -------------------------------------------------------------------------------- /src/compat/getpeereid.rs: -------------------------------------------------------------------------------- 1 | pub unsafe fn getpeereid(_s: i32, uid: *mut libc::uid_t, gid: *mut libc::gid_t) -> i32 { 2 | unsafe { 3 | *uid = libc::geteuid(); 4 | *gid = libc::getegid(); 5 | } 6 | 0 7 | } 8 | -------------------------------------------------------------------------------- /regress/combine-test.result: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | Λ̊1 5 | 🏻2 6 | 👍🏻3 7 | 👍🏻 👍🏻4 8 | 🤷‍♂️5 9 | ♂️7 10 | 🤷‍♂️8 11 | 🤷‍♂️9 12 | 🤷‍♂️10 13 | 🇪11 14 | 🇸🇪12 15 | 🇸🇪13 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | 6 | # disable lockfile from fuzz git repo because the lockfile 7 | # can cause a stale version of the repo to be cached which 8 | # can be a bit confusing 9 | Cargo.lock 10 | -------------------------------------------------------------------------------- /regress/Makefile: -------------------------------------------------------------------------------- 1 | TESTS!= echo *.sh 2 | 3 | .PHONY: all $(TESTS) 4 | .NOTPARALLEL: all $(TESTS) 5 | 6 | all: $(TESTS) 7 | 8 | $(TESTS): 9 | # broken tests are ignored for now 10 | TEST_TMUX=../target/debug/tmux-rs sh $*.sh || true 11 | sleep 1 12 | -------------------------------------------------------------------------------- /regress/conf/21867280ff7e99631046f9cc669b80d2.conf: -------------------------------------------------------------------------------- 1 | %if #{l:1} 2 | set -g status-style fg=cyan,bg='#001040' 3 | %elif #{l:1} 4 | set -g status-style fg=white,bg='#400040' 5 | %else 6 | set -g status-style fg=white,bg='#800000' 7 | %endif 8 | bind ^X last-window 9 | -------------------------------------------------------------------------------- /src/compat/systemd.rs: -------------------------------------------------------------------------------- 1 | pub fn systemd_create_socket(flags: i32, cause: *mut *mut u8) -> i32 { 2 | unsafe extern "C" { 3 | fn systemd_create_socket(flags: i32, cause: *mut *mut u8) -> i32; 4 | } 5 | unsafe { systemd_create_socket(flags, cause) } 6 | } 7 | -------------------------------------------------------------------------------- /regress/conf-syntax.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | for i in conf/*.conf; do 11 | $TMUX -f/dev/null start \; source -n $i || exit 1 12 | done 13 | 14 | exit 0 15 | -------------------------------------------------------------------------------- /src/utempter.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_char, c_int}; 2 | unsafe extern "C" { 3 | pub unsafe fn utempter_add_record(master_fd: c_int, hostname: *const c_char) -> c_int; 4 | pub unsafe fn utempter_remove_added_record() -> c_int; 5 | pub unsafe fn utempter_remove_record(master_fd: c_int) -> c_int; 6 | pub unsafe fn utempter_set_helper(pathname: *const c_char); 7 | } 8 | -------------------------------------------------------------------------------- /src/compat/getprogname.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "linux")] 2 | pub unsafe fn getprogname() -> *const u8 { 3 | unsafe extern "C" { 4 | static mut program_invocation_short_name: *mut u8; 5 | } 6 | 7 | unsafe { program_invocation_short_name } 8 | } 9 | 10 | #[cfg(target_os = "macos")] 11 | pub unsafe fn getprogname() -> *const u8 { 12 | c"tmux".as_ptr().cast() 13 | } 14 | -------------------------------------------------------------------------------- /src/compat/fdforkpty.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_int; 2 | 3 | use libc::{pid_t, termios, winsize}; 4 | 5 | pub extern "C" fn getptmfd() -> c_int { 6 | c_int::MAX 7 | } 8 | 9 | pub unsafe fn fdforkpty( 10 | _ptmfd: c_int, 11 | master: *mut c_int, 12 | name: *mut u8, 13 | tio: *mut termios, 14 | ws: *mut winsize, 15 | ) -> pid_t { 16 | unsafe { ::libc::forkpty(master, name.cast(), tio, ws) } 17 | } 18 | -------------------------------------------------------------------------------- /src/compat/strlcpy.rs: -------------------------------------------------------------------------------- 1 | /// The strlcpy() function copies up to size - 1 characters from the NUL-terminated string src to dst, 2 | /// NUL-terminating the result. 3 | pub unsafe fn strlcpy(dst: *mut u8, src: *const u8, siz: usize) -> usize { 4 | unsafe { 5 | let len = crate::libc::strnlen(src, siz); 6 | core::ptr::copy_nonoverlapping(src, dst, len); 7 | *dst.add(len) = b'\0'; 8 | 9 | len 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing tmux-rs 2 | 3 | Commands should be run from the root of the tmux-rs repo. 4 | 5 | List available fuzz targets: 6 | 7 | cargo fuzz list 8 | 9 | Run a specific target: 10 | 11 | cargo fuzz run colour_find_rgb 12 | 13 | Run with more cores: 14 | 15 | cargo fuzz run colour_find_rgb -- -jobs=8 16 | 17 | Run for a specific duration: 18 | 19 | cargo fuzz run colour_find_rgb -- -max_total_time=60 20 | 21 | -------------------------------------------------------------------------------- /regress/cursor-test.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 2 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 3 | commodo consequat. Duis aute 4 | irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat 5 | nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 6 | deserunt mollit anim id est laborum. 7 | -------------------------------------------------------------------------------- /regress/new-session-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # new session command 4 | 5 | PATH=/bin:/usr/bin 6 | TERM=screen 7 | 8 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 9 | TMUX="$TEST_TMUX -Ltest" 10 | $TMUX kill-server 2>/dev/null 11 | 12 | TMP=$(mktemp) 13 | trap "rm -f $TMP" 0 1 15 14 | 15 | cat <$TMP 16 | new sleep 101 17 | new -- sleep 102 18 | new "sleep 103" 19 | EOF 20 | 21 | $TMUX -f$TMP start 22 | [ $($TMUX ls|wc -l) -eq 3 ] || exit 1 23 | $TMUX kill-server 2>/dev/null 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /regress/new-window-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # new session command 4 | 5 | PATH=/bin:/usr/bin 6 | TERM=screen 7 | 8 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 9 | TMUX="$TEST_TMUX -Ltest" 10 | $TMUX kill-server 2>/dev/null 11 | 12 | TMP=$(mktemp) 13 | trap "rm -f $TMP" 0 1 15 14 | 15 | cat <$TMP 16 | new 17 | neww sleep 101 18 | neww -- sleep 102 19 | neww "sleep 103" 20 | EOF 21 | 22 | $TMUX -f$TMP start 23 | [ $($TMUX lsw|wc -l) -eq 4 ] || exit 1 24 | $TMUX kill-server 2>/dev/null 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /regress/utf8-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | TMP=$(mktemp) 9 | trap "rm -f $TMP" 0 1 15 10 | $TMUX kill-server 2>/dev/null 11 | 12 | $TMUX -f/dev/null \ 13 | set -g remain-on-exit on \; \ 14 | set -g remain-on-exit-format '' \; \ 15 | new -d -- cat UTF-8-test.txt 16 | sleep 1 17 | $TMUX capturep -pCeJS- >$TMP 18 | $TMUX kill-server 19 | 20 | cmp -s $TMP utf8-test.result || exit 1 21 | exit 0 22 | -------------------------------------------------------------------------------- /regress/new-session-no-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 869 4 | # new with no client (that is, from the config file) should imply -d and 5 | # not attach 6 | 7 | PATH=/bin:/usr/bin 8 | TERM=screen 9 | 10 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 11 | TMUX="$TEST_TMUX -Ltest" 12 | $TMUX kill-server 2>/dev/null 13 | 14 | TMP=$(mktemp) 15 | trap "rm -f $TMP" 0 1 15 16 | 17 | cat <$TMP 18 | new -stest 19 | EOF 20 | 21 | $TMUX -f$TMP start || exit 1 22 | sleep 1 && $TMUX has -t=test: || exit 1 23 | $TMUX kill-server 2>/dev/null 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /regress/new-session-base-index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # new session base-index 4 | 5 | PATH=/bin:/usr/bin 6 | TERM=screen 7 | 8 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 9 | TMUX="$TEST_TMUX -Ltest" 10 | $TMUX kill-server 2>/dev/null 11 | 12 | TMP=$(mktemp) 13 | trap "rm -f $TMP" 0 1 15 14 | 15 | cat <$TMP 16 | set -g base-index 100 17 | new 18 | set base-index 200 19 | neww 20 | EOF 21 | 22 | $TMUX -f$TMP start 23 | echo $($TMUX lsw -F'#{window_index}') >$TMP 24 | (echo "100 200"|cmp -s - $TMP) || exit 1 25 | $TMUX kill-server 2>/dev/null 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /regress/kill-session-process-exit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # when we kill a session, processes running in it should be killed 4 | 5 | PATH=/bin:/usr/bin 6 | TERM=screen 7 | 8 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 9 | TMUX="$TEST_TMUX -Ltest" 10 | $TMUX kill-server 2>/dev/null 11 | sleep 1 12 | 13 | $TMUX -f/dev/null new -d 'sleep 1000' || exit 1 14 | P=$($TMUX display -pt0:0.0 '#{pane_pid}') 15 | $TMUX -f/dev/null new -d || exit 1 16 | sleep 1 17 | $TMUX kill-session -t0: 18 | sleep 3 19 | kill -0 $P 2>/dev/null && exit 1 20 | $TMUX kill-server 2>/dev/null 21 | 22 | exit 0 23 | -------------------------------------------------------------------------------- /regress/if-shell-nested.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 882 4 | # tmux inside if-shell itself should work 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | TMP=$(mktemp) 14 | trap "rm -f $TMP" 0 1 15 15 | 16 | cat <$TMP 17 | if '$TMUX run "true"' 'set -s @done yes' 18 | EOF 19 | 20 | TERM=xterm $TMUX -f$TMP new -d "$TMUX show -vs @done >>$TMP" || exit 1 21 | sleep 1 && [ "$(tail -1 $TMP)" = "yes" ] || exit 1 22 | 23 | $TMUX has 2>/dev/null && exit 1 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /regress/has-session-return.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 971 4 | # has-session should return 1 on error 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | $TMUX -f/dev/null has -tfoo /dev/null && exit 1 14 | $TMUX -f/dev/null start\; has -tfoo /dev/null && exit 1 15 | $TMUX -f/dev/null new -d\; has -tfoo /dev/null && exit 1 16 | $TMUX -f/dev/null new -dsfoo\; has -tfoo /dev/null || exit 1 17 | $TMUX kill-server 2>/dev/null 18 | 19 | exit 0 20 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/colour_find_rgb.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[derive(arbitrary::Arbitrary, Debug)] 4 | struct RgbInput { 5 | r: u8, 6 | g: u8, 7 | b: u8, 8 | } 9 | 10 | libfuzzer_sys::fuzz_target!(|input: RgbInput| { 11 | let RgbInput { r, g, b } = input; 12 | 13 | let current_result = tmux_rs_new::colour::colour_find_rgb(r, g, b); 14 | let old_result = tmux_rs_old::colour::colour_find_rgb(r, g, b); 15 | 16 | assert_eq!( 17 | current_result, old_result, 18 | "Regression detected!\nInput: r={r}, g={g}, b={b}\nCurrent: {current_result}\nOld: {old_result}", 19 | ); 20 | }); 21 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # https://doc.rust-lang.org/stable/clippy/lint_configuration.html 2 | # https://rust-lang.github.io/rust-clippy/master/index.html 3 | 4 | avoid-breaking-exported-api = false 5 | 6 | disallowed-types = [ 7 | { path = "core::ffi::c_char", replacement = "u8", reason = "c_char may be different types on different platforms" }, 8 | { path = "libc::c_char", replacement = "u8", reason = "c_char may be different types on different platforms" }, 9 | { path = "std::ffi::c_char", replacement = "u8", reason = "c_char may be different types on different platforms" }, 10 | ] 11 | 12 | disallowed-methods = [ 13 | { path = "str::as_ptr", reason = "likely an error from refactoring" }, 14 | ] 15 | -------------------------------------------------------------------------------- /regress/if-shell-error.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 883 4 | # if-shell with an error should not core :-) 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | TMP=$(mktemp) 14 | OUT=$(mktemp) 15 | trap "rm -f $TMP $OUT" 0 1 15 16 | 17 | cat <$TMP 18 | if 'true' 'wibble wobble' 19 | EOF 20 | 21 | $TMUX -f$TMP -C new <$OUT 22 | EOF 23 | grep -q "^%config-error $TMP:1: $TMP:1: unknown command: wibble$" $OUT 24 | 25 | cat <$TMP 26 | wibble wobble 27 | EOF 28 | 29 | echo "source $TMP" | $TMUX -C new >$OUT 30 | grep -q "^%config-error $TMP:1: unknown command: wibble$" $OUT 31 | -------------------------------------------------------------------------------- /regress/run-shell-output.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4476 4 | # run-shell should go to stdout if present without -t 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | TMP=$(mktemp) 14 | trap "rm -f $TMP" 0 1 15 15 | 16 | $TMUX -f/dev/null new -d "$TMUX run 'echo foo' >$TMP; sleep 10" || exit 1 17 | sleep 1 && [ "$(cat $TMP)" = "foo" ] || exit 1 18 | 19 | $TMUX -f/dev/null new -d "$TMUX run -t: 'echo foo' >$TMP; sleep 10" || exit 1 20 | sleep 1 && [ "$(cat $TMP)" = "" ] || exit 1 21 | [ "$($TMUX display -p '#{pane_mode}')" = "view-mode" ] || exit 1 22 | 23 | $TMUX kill-server 2>/dev/null 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /regress/capture-pane-sgr0.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 884 4 | # capture-pane should send colours after SGR 0 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | TMP=$(mktemp) 14 | trap "rm -f $TMP" 0 1 15 15 | 16 | $TMUX -f/dev/null new -d " 17 | printf '\033[31;42;1mabc\033[0;31mdef\n' 18 | printf '\033[m\033[100m bright bg \033[m' 19 | $TMUX capturep -peS0 -E1 >>$TMP" 20 | 21 | 22 | sleep 1 23 | 24 | ( 25 | printf '\033[1m\033[31m\033[42mabc\033[0m\033[31mdef\033[39m\n' 26 | printf '\033[100m bright bg \033[49m\n' 27 | ) | cmp - $TMP || exit 1 28 | 29 | $TMUX has 2>/dev/null && exit 1 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tmux-rs-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2024" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | arbitrary = { version = "1.4.2", features = ["derive"] } 12 | libfuzzer-sys = { version = "0.4.10" } 13 | 14 | # fuzz two different versions. these may need to be manually tweeked 15 | tmux-rs-new = { path = "..", package = "tmux-rs" } 16 | tmux-rs-old = { git = "https://github.com/richardscollin/tmux-rs", package = "tmux-rs", branch = "main" } 17 | # tmux-rs-old = { path = "../../tmux-rs-old", package = "tmux-rs-old" } 18 | 19 | [[bin]] 20 | name = "colour_find_rgb" 21 | path = "fuzz_targets/colour_find_rgb.rs" 22 | test = false 23 | doc = false 24 | bench = false 25 | -------------------------------------------------------------------------------- /regress/cursor-test2.result: -------------------------------------------------------------------------------- 1 | 9 7 a 2 | 0 cupidatat 3 | 1 non proide 4 | 2 nt, sunt i 5 | 3 n culpa qu 6 | 4 i officia 7 | 5 deserunt m 8 | 6 ollit anim 9 | 7 id est la 10 | 8 borum. 11 | 9 12 | 4 6 a 13 | 0 icia 14 | 1 deser 15 | 2 unt m 16 | 3 ollit 17 | 4 anim 18 | 5 id e 19 | 6 st la 20 | 7 borum 21 | 8 . 22 | 9 23 | 29 8 a 24 | 0 incididunt ut labore et dolore magna aliqua. Ut en 25 | 1 im ad minim veniam, quis nostrud exercitation ulla 26 | 2 mco laboris nisi ut aliquip ex ea 27 | 3 commodo consequat. Duis aute 28 | 4 irure dolor in reprehenderit in voluptate velit es 29 | 5 se cillum dolore eu fugiat 30 | 6 nulla pariatur. Excepteur sint occaecat cupidatat 31 | 7 non proident, sunt in culpa qui officia 32 | 8 deserunt mollit anim id est laborum. 33 | 9 34 | -------------------------------------------------------------------------------- /regress/new-session-size.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # new-session without clients should be the right size 4 | 5 | PATH=/bin:/usr/bin 6 | TERM=screen 7 | 8 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 9 | TMUX="$TEST_TMUX -Ltest" 10 | $TMUX kill-server 2>/dev/null 11 | 12 | TMP=$(mktemp) 13 | trap "rm -f $TMP" 0 1 15 14 | 15 | $TMUX -f/dev/null new -d $TMP 18 | printf "80 24\n"|cmp -s $TMP - || exit 1 19 | $TMUX kill-server 2>/dev/null 20 | 21 | $TMUX -f/dev/null new -d -x 100 -y 50 $TMP 24 | printf "100 50\n"|cmp -s $TMP - || exit 1 25 | $TMUX kill-server 2>/dev/null 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /regress/if-shell-TERM.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 882 4 | # TERM should come from outside tmux for if-shell from the config file 5 | 6 | PATH=/bin:/usr/bin 7 | TERM=screen 8 | 9 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 10 | TMUX="$TEST_TMUX -Ltest" 11 | $TMUX kill-server 2>/dev/null 12 | 13 | TMP=$(mktemp) 14 | trap "rm -f $TMP" 0 1 15 15 | 16 | cat <$TMP 17 | if '[ "\$TERM" = "xterm" ]' \ 18 | 'set -g default-terminal "vt220"' \ 19 | 'set -g default-terminal "ansi"' 20 | EOF 21 | 22 | TERM=xterm $TMUX -f$TMP new -d "echo \"#\$TERM\" >>$TMP" || exit 1 23 | sleep 1 && [ "$(tail -1 $TMP)" = "#vt220" ] || exit 1 24 | 25 | TERM=screen $TMUX -f$TMP new -d "echo \"#\$TERM\" >>$TMP" || exit 1 26 | sleep 1 && [ "$(tail -1 $TMP)" = "#ansi" ] || exit 1 27 | 28 | $TMUX has 2>/dev/null && exit 1 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /regress/am-terminal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | TMUX2="$TEST_TMUX -Ltest2" 10 | $TMUX2 kill-server 2>/dev/null 11 | 12 | TMP=$(mktemp) 13 | trap "rm -f $TMP" 0 1 15 14 | 15 | $TMUX2 -f/dev/null new -d || exit 1 16 | $TMUX2 set -as terminal-overrides ',*:am@' || exit 1 17 | $TMUX2 set -g status-right 'RRR' || exit 1 18 | $TMUX2 set -g status-left 'LLL' || exit 1 19 | $TMUX2 set -g window-status-current-format 'WWW' || exit 1 20 | $TMUX -f/dev/null new -x20 -y2 -d "$TMUX2 attach" || exit 1 21 | sleep 1 22 | $TMUX capturep -p|tail -1 >$TMP || exit 1 23 | $TMUX kill-server 2>/dev/null 24 | $TMUX2 kill-server 2>/dev/null 25 | cat </dev/null 14 | 15 | do_test() { 16 | $TMUX -f/dev/null new -d " 17 | printf '$1' 18 | $TMUX capturep -peS0 -E1 >$TMP" 19 | printf "$2\n" > $TMP2 20 | sleep 1 21 | cmp $TMP $TMP2 || exit 1 22 | return 0 23 | } 24 | 25 | 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 26 | 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 27 | 28 | $TMUX has 2>/dev/null && exit 1 29 | 30 | exit 0 31 | -------------------------------------------------------------------------------- /src/compat/ntohll.rs: -------------------------------------------------------------------------------- 1 | use ::libc; 2 | pub type __uint32_t = libc::c_uint; 3 | pub type __uint64_t = libc::c_ulong; 4 | pub type uint32_t = __uint32_t; 5 | pub type uint64_t = __uint64_t; 6 | #[inline] 7 | unsafe fn __bswap_32(mut __bsx: __uint32_t) -> __uint32_t { 8 | return (__bsx & 0xff000000 as libc::c_uint) >> 24 as libc::c_int 9 | | (__bsx & 0xff0000 as libc::c_uint) >> 8 as libc::c_int 10 | | (__bsx & 0xff00 as libc::c_uint) << 8 as libc::c_int 11 | | (__bsx & 0xff as libc::c_uint) << 24 as libc::c_int; 12 | } 13 | 14 | pub unsafe fn ntohll(mut v: uint64_t) -> uint64_t { 15 | let mut b: uint32_t = 0; 16 | let mut t: uint32_t = 0; 17 | b = __bswap_32((v & 0xffffffff as libc::c_uint as libc::c_ulong) as __uint32_t); 18 | t = __bswap_32((v >> 32 as libc::c_int) as __uint32_t); 19 | return (b as uint64_t) << 32 as libc::c_int | t as libc::c_ulong; 20 | } 21 | -------------------------------------------------------------------------------- /src/ncurses_.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | pub const ERR: i32 = -1; 3 | pub const OK: i32 = 0; 4 | 5 | #[expect(clippy::upper_case_acronyms)] 6 | #[repr(C)] 7 | #[derive(Copy, Clone)] 8 | pub struct TERMINAL { 9 | _opaque: [u8; 0], 10 | } 11 | 12 | // note I've modified the bindings under the assumption that 13 | // *c_char can be mixed with *u8 14 | // 15 | // ncurses 16 | unsafe extern "C" { 17 | pub fn setupterm(term: *const u8, filedes: i32, errret: *mut i32) -> i32; 18 | 19 | pub fn tiparm_s(expected: i32, mask: i32, str: *const u8, ...) -> *mut u8; 20 | pub fn tiparm(str: *const u8, ...) -> *mut u8; 21 | pub fn tparm(str: *const u8, ...) -> *mut u8; 22 | 23 | pub fn tigetflag(cap_code: *const u8) -> i32; 24 | pub fn tigetnum(cap_code: *const u8) -> i32; 25 | pub fn tigetstr(cap_code: *const u8) -> *mut u8; 26 | 27 | pub fn del_curterm(oterm: *mut TERMINAL) -> i32; 28 | pub static mut cur_term: *mut TERMINAL; 29 | } 30 | -------------------------------------------------------------------------------- /regress/cursor-test2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d -x10 -y10 \ 14 | "cat cursor-test.txt; printf '\e[8;10H'; cat" || exit 1 15 | $TMUX set -g window-size manual || exit 1 16 | 17 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 18 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 19 | $TMUX resizew -x5 || exit 1 20 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 21 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 22 | $TMUX resizew -x50 || exit 1 23 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 24 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 25 | 26 | cmp -s $TMP cursor-test2.result || exit 1 27 | 28 | $TMUX kill-server 2>/dev/null 29 | exit 0 30 | -------------------------------------------------------------------------------- /regress/cursor-test3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d -x7 -y2 \ 14 | "printf 'abcdefabcdefab'; printf '\e[2;7H'; cat" || exit 1 15 | $TMUX set -g window-size manual || exit 1 16 | 17 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 18 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 19 | $TMUX resizew -x5 || exit 1 20 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 21 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 22 | $TMUX resizew -x7 || exit 1 23 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 24 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 25 | 26 | cmp -s $TMP cursor-test3.result || exit 1 27 | 28 | $TMUX kill-server 2>/dev/null 29 | exit 0 30 | -------------------------------------------------------------------------------- /regress/cursor-test1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -f/dev/null -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d -x40 -y10 \ 14 | "cat cursor-test.txt; printf '\e[9;15H'; cat" || exit 1 15 | $TMUX set -g window-size manual || exit 1 16 | 17 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 18 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 19 | $TMUX resizew -x10 || exit 1 20 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 21 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 22 | $TMUX resizew -x50 || exit 1 23 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 24 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 25 | 26 | cmp -s $TMP cursor-test1.result || exit 1 27 | 28 | $TMUX kill-server 2>/dev/null 29 | exit 0 30 | -------------------------------------------------------------------------------- /regress/cursor-test1.result: -------------------------------------------------------------------------------- 1 | 14 8 t 2 | 0 ud exercitation ullamco laboris nisi ut 3 | 1 aliquip ex ea 4 | 2 commodo consequat. Duis aute 5 | 3 irure dolor in reprehenderit in voluptat 6 | 4 e velit esse cillum dolore eu fugiat 7 | 5 nulla pariatur. Excepteur sint occaecat 8 | 6 cupidatat non proident, sunt in culpa qu 9 | 7 i officia 10 | 8 deserunt mollit anim id est laborum. 11 | 9 12 | 4 6 t 13 | 0 cupidatat 14 | 1 non proide 15 | 2 nt, sunt i 16 | 3 n culpa qu 17 | 4 i officia 18 | 5 deserunt m 19 | 6 ollit anim 20 | 7 id est la 21 | 8 borum. 22 | 9 23 | 14 8 t 24 | 0 incididunt ut labore et dolore magna aliqua. Ut en 25 | 1 im ad minim veniam, quis nostrud exercitation ulla 26 | 2 mco laboris nisi ut aliquip ex ea 27 | 3 commodo consequat. Duis aute 28 | 4 irure dolor in reprehenderit in voluptate velit es 29 | 5 se cillum dolore eu fugiat 30 | 6 nulla pariatur. Excepteur sint occaecat cupidatat 31 | 7 non proident, sunt in culpa qui officia 32 | 8 deserunt mollit anim id est laborum. 33 | 9 34 | -------------------------------------------------------------------------------- /regress/control-client-sanity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d -x200 -y200 || exit 1 14 | $TMUX -f/dev/null splitw || exit 1 15 | sleep 1 16 | cat <$TMP 17 | refresh-client -C 200x200 18 | selectp -t%0 19 | splitw 20 | neww 21 | splitw 22 | selectp -t%0 23 | killp -t%1 24 | swapp -t%2 -s%3 25 | neww 26 | splitw 27 | splitw 28 | selectl tiled 29 | killw 30 | EOF 31 | sleep 1 32 | $TMUX has || exit 1 33 | $TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1 34 | cat </dev/null 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | THIS IS FOR INFORMATION ONLY, CODE IS UNDER THE LICENCE AT THE TOP OF ITS FILE. 2 | 3 | The README, CHANGES, FAQ and TODO files are licensed under the ISC license. All 4 | other files have a license and copyright notice at their start, typically: 5 | 6 | Copyright (c) 7 | 8 | Permission to use, copy, modify, and distribute this software for any 9 | purpose with or without fee is hereby granted, provided that the above 10 | copyright notice and this permission notice appear in all copies. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 17 | IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 18 | OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | -------------------------------------------------------------------------------- /regress/conf/327af72ad372255817b585a74da06eda.conf: -------------------------------------------------------------------------------- 1 | set -sg escape-time 10 2 | 3 | set -g default-terminal tmux-256color 4 | set -g prefix ^X 5 | set -g history-limit 10000 6 | setw -g mode-keys vi 7 | setw -g xterm-keys off 8 | 9 | # black, red, green, yellow, blue, magenta, cyan, white, default. 10 | setw -g message-command-style fg=yellow,bg=black 11 | setw -g message-style fg=black,bg=yellow 12 | 13 | %if #{m:*mydomain*,#{host}} 14 | set -g status-style fg=cyan,bg='#001040' 15 | setw -g window-status-current-style fg='#f0f0f0',bg='#001040' 16 | %elif #{||:#{m:*somedomain*,#{host}},#{m:*otherdomain*,#{host}}} 17 | set -g status-style fg=white,bg='#400040' 18 | setw -g window-status-current-style fg=yellow,bg='#400040',bright 19 | %else 20 | set -g status-style fg=white,bg='#800000' 21 | setw -g window-status-current-style fg=brightwhite,bg='#800000' 22 | %endif 23 | 24 | unbind ^B 25 | bind ^X last-window 26 | bind x send-prefix 27 | bind ^C new-window 28 | bind ^D detach-client 29 | bind ^N next-window 30 | bind ^P previous-window 31 | -------------------------------------------------------------------------------- /regress/conf/872441a98b06444acc5ce08eb24aabde.conf: -------------------------------------------------------------------------------- 1 | bind -r Up if -F '#{pane_at_top}' '' 'selectp -U' 2 | bind -r Down if -F '#{pane_at_bottom}' '' 'selectp -D' 3 | bind -r Left if -F '#{pane_at_left}' '' 'selectp -L' 4 | bind -r Right if -F '#{pane_at_right}' '' 'selectp -R' 5 | 6 | bind -n WheelUpPane if -Ft= "#{mouse_any_flag}" "send -M" "send Up" 7 | bind -n WheelDownPane if -Ft= "#{mouse_any_flag}" "send -M" "send Down" 8 | 9 | bind w run 'tmux choose-tree -Nwf"##{==:##{session_name},#{session_name}}"' 10 | 11 | bind C { 12 | splitw -f -l30% '' 13 | set-hook -p pane-mode-changed 'if -F "#{!=:#{pane_mode},copy-mode}" "kill-pane"' 14 | copy-mode -s'{last}' 15 | } 16 | 17 | set -g word-separators "" 18 | bind -n C-DoubleClick1Pane if -F '#{m/r:^[^:]*:[0-9]+:,#{mouse_word}}' { 19 | run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' 20 | emacs `echo #{mouse_word}|awk -F: "{print \"+\"\\$2,\\$1}"` 21 | ' } 22 | } { 23 | run -C { popup -w90% -h90% -E -d '#{pane_current_path}' ' 24 | emacs "#{mouse_word}" 25 | ' } 26 | } 27 | -------------------------------------------------------------------------------- /regress/cursor-test4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d -x10 -y3 "printf 'abcdef\n'; cat" || exit 1 14 | $TMUX set -g window-size manual || exit 1 15 | 16 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 17 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 18 | $TMUX resizew -x20 || exit 1 19 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 20 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 21 | $TMUX resizew -x3 || exit 1 22 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 23 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 24 | $TMUX resizew -x10 || exit 1 25 | $TMUX display -pF '#{cursor_x} #{cursor_y} #{cursor_character}' >>$TMP 26 | $TMUX capturep -p|awk '{print NR-1,$0}' >>$TMP 27 | 28 | cmp -s $TMP cursor-test4.result || exit 1 29 | 30 | $TMUX kill-server 2>/dev/null 31 | exit 0 32 | -------------------------------------------------------------------------------- /src/compat/freezero.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use core::ffi::c_void; 15 | 16 | pub unsafe fn freezero(ptr: *mut c_void, size: usize) { 17 | unsafe { 18 | if !ptr.is_null() { 19 | libc::memset(ptr, 0, size); 20 | libc::free(ptr); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /regress/command-order.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | cat <$TMP 14 | new -sfoo -nfoo0; neww -nfoo1; neww -nfoo2 15 | new -sbar -nbar0; neww -nbar1; neww -nbar2 16 | EOF 17 | $TMUX -f$TMP start $TMP || exit 1 20 | $TMUX kill-server 2>/dev/null 21 | cat <$TMP 31 | new -sfoo -nfoo0 32 | neww -nfoo1 33 | neww -nfoo2 34 | new -sbar -nbar0 35 | neww -nbar1 36 | neww -nbar2 37 | EOF 38 | $TMUX -f$TMP start $TMP || exit 1 41 | $TMUX kill-server 2>/dev/null 42 | cat <, 4 | } 5 | 6 | impl BitStr { 7 | pub fn new(nbits: u32) -> Self { 8 | Self { 9 | bits: vec![0; nbits.div_ceil(8) as usize].into_boxed_slice() 10 | } 11 | } 12 | 13 | pub fn bit_set(&mut self, i: u32) { 14 | let byte_index = i / 8; 15 | let bit_index = i % 8; 16 | self.bits[byte_index as usize] |= 1 << bit_index; 17 | } 18 | 19 | #[inline] 20 | pub fn bit_clear(&mut self, i: u32) { 21 | let byte_index = i / 8; 22 | let bit_index = i % 8; 23 | self.bits[byte_index as usize] &= !(1 << bit_index); 24 | } 25 | 26 | pub fn bit_nclear(&mut self, start: u32, stop: u32) { 27 | // TODO this is written inefficiently, assuming the compiler will optimize it. if it doesn't rewrite it 28 | for i in start..=stop { 29 | self.bit_clear(i); 30 | } 31 | } 32 | 33 | pub fn bit_test(&self, i: u32) -> bool { 34 | let byte_index = i / 8; 35 | let bit_index = i % 8; 36 | self.bits[byte_index as usize] & (1 << bit_index) != 0 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/compat/strlcat.rs: -------------------------------------------------------------------------------- 1 | use crate::libc; 2 | 3 | /// The strlcat() function appends the NUL-terminated string src to the end of dst. 4 | /// It will append at most size - strlen(dst) - 1 bytes, NUL-terminating the result. 5 | pub unsafe fn strlcat(dst: *mut u8, src: *const u8, size: usize) -> usize { 6 | unsafe { 7 | let dst_strlen = libc::strnlen(dst, size); 8 | let src_strlen = libc::strnlen(src, size.saturating_sub(dst_strlen).saturating_sub(1)); 9 | 10 | core::ptr::copy_nonoverlapping(src, dst.add(dst_strlen), src_strlen); 11 | *dst.add(dst_strlen + src_strlen) = b'\0'; 12 | 13 | dst_strlen + src_strlen 14 | } 15 | } 16 | 17 | #[expect(clippy::disallowed_methods)] 18 | pub unsafe fn strlcat_(dst: *mut u8, src: &str, size: usize) -> usize { 19 | unsafe { 20 | let dst_strlen = libc::strnlen(dst, size); 21 | let src_strlen = src 22 | .len() 23 | .min(size.saturating_sub(dst_strlen).saturating_sub(1)); 24 | 25 | core::ptr::copy_nonoverlapping(src.as_ptr(), dst.add(dst_strlen), src_strlen); 26 | *dst.add(dst_strlen + src_strlen) = b'\0'; 27 | 28 | dst_strlen + src_strlen 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/compat/closefrom.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2004-2005 Todd C. Miller 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #[cfg(target_os = "linux")] 16 | unsafe extern "C" { 17 | pub fn closefrom(__lowfd: i32); 18 | } 19 | 20 | #[cfg(target_os = "macos")] 21 | /// Closes all file descriptors >= `start_fd`. 22 | pub fn closefrom(start_fd: i32) { 23 | unsafe { 24 | let max_fd = libc::getdtablesize(); 25 | for fd in start_fd..max_fd { 26 | libc::close(fd); // ignore close errors 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## main 4 | 5 | ## 0.0.3 6 | 7 | - Add sixel support 8 | - Improved macOS installation process 9 | - Change tmux-rs socket path to /tmp/tmux-rs-1000/ 10 | - Refactoring to use more Rust idioms and reduce unsafe code 11 | - Fixed unnecessary cursor updates causing high CPU usage 12 | - Fixed panic in debug build due to subtraction overflow in `grid_view_delete_cells` 13 | - Fixed path display issues (due to incorrect strrchr implementation) 14 | - Fixed mouse mode issues (selecting window by mouse, etc.) 15 | - Fixed multiple bugs due to incorrect bitwise negation translation 16 | - Fixed memory leak due to shadowing 17 | - Fixed clear terminal on vim close (alternate screen check in `screen_write_alternateoff`) 18 | - Fixed broken display-message command 19 | - Fixed hex color parsing logic 20 | 21 | ## 0.0.2 22 | 23 | - macOS and linux aarch64 supported added 24 | - fixed numerous crashes 25 | - fixed broken command line argument parsing 26 | - Slightly improved panic log generation 27 | - XDG\_CONFIG\_HOME now included in default config path 28 | - added support to configure some variables at build time using environment variables: 29 | - TMUX\_VERSION 30 | - TMUX\_CONF 31 | - TMUX\_SOCK 32 | - TMUX\_TERM 33 | - TMUX\_LOCK\_CMD 34 | 35 | ## 0.0.1 36 | 37 | initial release 38 | 39 | -------------------------------------------------------------------------------- /regress/combine-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | TMP=$(mktemp) 11 | trap "rm -f $TMP" 0 1 15 12 | 13 | $TMUX -f/dev/null new -d " 14 | printf '\e[H\e[J' 15 | printf '\e[3;1H\316\233\e[3;1H\314\2120\n' 16 | printf '\e[4;1H\316\233\e[4;2H\314\2121\n' 17 | printf '\e[5;1H👍\e[5;1H🏻2\n' 18 | printf '\e[6;1H👍\e[6;3H🏻3\n' 19 | printf '\e[7;1H👍\e[7;10H👍\e[7;3H🏻\e[7;12H🏻4\n' 20 | printf '\e[8;1H\360\237\244\267\342\200\215\342\231\202\357\270\2175\n' 21 | printf '\e[9;1H\360\237\244\267\e[9;1H\342\200\215\342\231\202\357\270\2176\n' 22 | printf '\e[9;1H\360\237\244\267\e[9;1H\342\200\215\342\231\202\357\270\2177\n' 23 | printf '\e[10;1H\360\237\244\267\e[10;3H\342\200\215\342\231\202\357\270\2178\n' 24 | printf '\e[11;1H\360\237\244\267\e[11;3H\342\200\215\e[11;3H\342\231\202\357\270\2179\n' 25 | printf '\e[12;1H\360\237\244\267\e[12;3H\342\200\215\342\231\202\357\270\21710\n' 26 | printf '\e[13;1H\360\237\207\25211\n' 27 | printf '\e[14;1H\360\237\207\270\360\237\207\25212\n' 28 | printf '\e[15;1H\360\237\207\270 \010\010\360\237\207\25213\n' 29 | $TMUX capturep -pe >>$TMP" 30 | 31 | sleep 1 32 | 33 | cmp $TMP combine-test.result || exit 1 34 | 35 | $TMUX has 2>/dev/null && exit 1 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /regress/new-session-environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # new session environment 4 | 5 | PATH=/bin:/usr/bin 6 | 7 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 8 | TMUX="$TEST_TMUX -Ltest" 9 | $TMUX kill-server 2>/dev/null 10 | 11 | TERM=$($TMUX start \; show -gv default-terminal) 12 | TMP=$(mktemp) 13 | OUT=$(mktemp) 14 | SCRIPT=$(mktemp) 15 | #trap "rm -f $TMP $OUT $SCRIPT" 0 1 15 16 | 17 | cat <$SCRIPT 18 | ( 19 | echo TERM=\$TERM 20 | echo PWD=\$(pwd) 21 | echo PATH=\$PATH 22 | echo SHELL=\$SHELL 23 | echo TEST=\$TEST 24 | ) >$OUT 25 | EOF 26 | 27 | cat <$TMP 28 | new -- /bin/sh $SCRIPT 29 | EOF 30 | 31 | (cd /; env -i TERM=ansi TEST=test1 PATH=1 SHELL=/bin/sh \ 32 | $TMUX -f$TMP start) || exit 1 33 | sleep 1 34 | (cat </dev/null 65 | 66 | exit 0 67 | -------------------------------------------------------------------------------- /regress/control-client-size.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 947 4 | # size in control mode should change after refresh-client -C, and -x and -y 5 | # should work without -d for control clients 6 | 7 | PATH=/bin:/usr/bin 8 | TERM=screen 9 | 10 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 11 | TMUX="$TEST_TMUX -Ltest" 12 | $TMUX kill-server 2>/dev/null 13 | 14 | TMP=$(mktemp) 15 | OUT=$(mktemp) 16 | trap "rm -f $TMP $OUT" 0 1 15 17 | 18 | $TMUX -f/dev/null new -d || exit 1 19 | sleep 1 20 | cat <$TMP 21 | ls -F':#{window_width} #{window_height}' 22 | refresh -C 100,50 23 | EOF 24 | grep ^: $TMP >$OUT 25 | $TMUX ls -F':#{window_width} #{window_height}' >>$OUT 26 | printf ":80 24\n:100 50\n"|cmp -s $OUT - || exit 1 27 | $TMUX kill-server 2>/dev/null 28 | 29 | $TMUX -f/dev/null new -d || exit 1 30 | sleep 1 31 | cat <$TMP 32 | ls -F':#{window_width} #{window_height}' 33 | refresh -C 80,24 34 | EOF 35 | grep ^: $TMP >$OUT 36 | $TMUX ls -F':#{window_width} #{window_height}' >>$OUT 37 | printf ":80 24\n:80 24\n"|cmp -s $OUT - || exit 1 38 | $TMUX kill-server 2>/dev/null 39 | 40 | cat <$TMP 41 | ls -F':#{window_width} #{window_height}' 42 | refresh -C 80,24 43 | EOF 44 | grep ^: $TMP >$OUT 45 | $TMUX ls -F':#{window_width} #{window_height}' >>$OUT 46 | printf ":100 50\n:80 24\n"|cmp -s $OUT - || exit 1 47 | $TMUX kill-server 2>/dev/null 48 | 49 | exit 0 50 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Coverage 3 | 4 | on: 5 | push 6 | 7 | jobs: 8 | coverage: 9 | name: Coverage 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: dtolnay/rust-toolchain@nightly 14 | with: 15 | components: llvm-tools 16 | 17 | - name: Install system dependencies (ubuntu) 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install -y libncurses-dev libevent-dev 21 | 22 | - uses: taiki-e/install-action@v2 23 | with: 24 | tool: cargo-llvm-cov@0.6.21,cargo-nextest@0.9.114 25 | 26 | - uses: actions/checkout@v5 27 | 28 | - name: Coverage 29 | run: | 30 | cargo +nightly llvm-cov --no-report nextest --profile ci 31 | mv ./target/nextest/ci/junit.xml junit.xml 32 | 33 | source <(cargo llvm-cov show-env --export-prefix --branch) 34 | cargo +nightly build 35 | make -C regress 36 | 37 | cargo llvm-cov report --lcov --output-path lcov.info 38 | 39 | - name: Upload results to Codecov 40 | uses: codecov/codecov-action@v5 41 | with: 42 | token: ${{ secrets.CODECOV_TOKEN }} 43 | files: lcov.info 44 | 45 | - name: Upload test results to Codecov 46 | if: ${{ !cancelled() }} 47 | uses: codecov/test-results-action@v1 48 | with: 49 | token: ${{ secrets.CODECOV_TOKEN }} 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | > [!WARNING] 4 | > This project is alpha quality and has many known bugs. It's written in 5 | > almost entirely unsafe Rust. Don't use it yet unless you're willing to deal 6 | > with frequent crashes. 7 | > 8 | > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | > WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | > MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | > ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | > WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 13 | > IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 14 | > OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 |
17 | 18 | # tmux-rs 19 | 20 | A rust port of [tmux](https://github.com/tmux/tmux). 21 | 22 | ## Why? 23 | 24 | Why not? This a fun hobby project for me. It's been my gardening for the past year. 25 | 26 | Why not just use [zellij](https://zellij.dev/)? I like tmux. I want tmux, 27 | not something else. 28 | 29 | ## Installation 30 | 31 | ### Linux 32 | 33 | Like `tmux`, it requires `libevent2` and `libtinfo` (usually packaged with ncurses). 34 | 35 | ```sh 36 | sudo apt-get install libncurses-dev libevent-dev 37 | cargo install tmux-rs 38 | tmux-rs 39 | ``` 40 | 41 | ### macOS 42 | 43 | ```sh 44 | brew install libevent ncurses 45 | cargo install tmux-rs 46 | tmux-rs 47 | ``` 48 | -------------------------------------------------------------------------------- /src/compat/reallocarray.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008, 2017 Otto Moerbeek 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | #[cfg(target_os = "macos")] 16 | pub unsafe fn reallocarray( 17 | optr: *mut core::ffi::c_void, 18 | nmemb: usize, 19 | size: usize, 20 | ) -> *mut core::ffi::c_void { 21 | const MUL_NO_OVERFLOW: usize = 1usize << (size_of::() * 4); 22 | 23 | unsafe { 24 | if (nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) 25 | && nmemb > 0 26 | && usize::MAX / nmemb < size 27 | { 28 | crate::errno!() = libc::ENOMEM; 29 | return core::ptr::null_mut(); 30 | } 31 | libc::realloc(optr, size * nmemb) 32 | } 33 | } 34 | 35 | #[cfg(target_os = "linux")] 36 | pub(crate) use libc::reallocarray; 37 | -------------------------------------------------------------------------------- /src/compat/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod b64; 2 | pub mod fdforkpty; 3 | pub mod getdtablecount; 4 | pub mod getopt; 5 | pub mod getprogname; 6 | pub mod imsg; 7 | pub mod imsg_buffer; 8 | pub mod queue; 9 | pub mod reallocarray; 10 | pub mod recallocarray; 11 | pub mod systemd; 12 | pub mod tree; 13 | 14 | mod closefrom; 15 | mod freezero; 16 | mod getpeereid; 17 | mod setproctitle; 18 | mod strlcat; 19 | mod strlcpy; 20 | mod strtonum; 21 | mod unvis; 22 | mod vis; 23 | 24 | pub use closefrom::closefrom; 25 | pub use freezero::freezero; 26 | pub use getpeereid::getpeereid; 27 | pub use setproctitle::setproctitle_; 28 | pub use strlcat::{strlcat, strlcat_}; 29 | pub use strlcpy::strlcpy; 30 | pub use strtonum::{strtonum, strtonum_}; 31 | pub use unvis::strunvis; 32 | pub use vis::*; 33 | 34 | // #[rustfmt::skip] 35 | // unsafe extern "C" { 36 | // pub static mut optreset: c_int; 37 | // pub static mut optarg: *mut c_char; 38 | // pub static mut optind: c_int; 39 | // pub fn getopt(___argc: c_int, ___argv: *const *mut c_char, __shortopts: *const c_char) -> c_int; 40 | // pub fn bsd_getopt(argc: c_int, argv: *const *mut c_char, shortopts: *const c_char) -> c_int; 41 | // } 42 | 43 | pub const HOST_NAME_MAX: usize = 255; 44 | 45 | pub const WAIT_ANY: libc::pid_t = -1; 46 | 47 | pub const ACCESSPERMS: libc::mode_t = libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO; 48 | 49 | // #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 50 | // TODO move this to a better spot 51 | #[expect(non_snake_case)] 52 | #[inline] 53 | pub fn S_ISDIR(mode: libc::mode_t) -> bool { 54 | mode & libc::S_IFMT == libc::S_IFDIR 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/wiki-update.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Update Wiki with Safety Report 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - '**/*.rs' 9 | - '**/*.yml' 10 | workflow_dispatch: 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | update-wiki: 17 | runs-on: ubuntu-latest 18 | name: Update Wiki with Safety Analysis 19 | continue-on-error: true 20 | 21 | steps: 22 | - name: Install crate-report 23 | run: | 24 | mkdir -p ~/.local/bin 25 | curl -L --proto '=https' --tlsv1.2 -sSf \ 26 | https://github.com/richardscollin/crate-report/releases/download/v0.9.0/crate-report \ 27 | -o ~/.local/bin/crate-report 28 | chmod +x ~/.local/bin/crate-report 29 | echo "$HOME/.local/bin" >> $GITHUB_PATH 30 | 31 | - uses: actions/checkout@v5 32 | 33 | - name: Checkout wiki repository 34 | uses: actions/checkout@v5 35 | with: 36 | repository: ${{ github.repository }}.wiki 37 | path: wiki 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | - name: Update wiki with safety report 41 | run: | 42 | crate-report --output wiki/Safety-Analysis.md 43 | ls 44 | ls wiki 45 | cat wiki/Safety-Analysis.md 46 | 47 | cd wiki 48 | git config --local user.email "action@github.com" 49 | git config --local user.name "GitHub Action" 50 | 51 | git add Safety-Analysis.md 52 | git commit --allow-empty -m "Update safety analysis from commit ${{ github.sha }}" 53 | git push 54 | -------------------------------------------------------------------------------- /src/compat/strtonum.rs: -------------------------------------------------------------------------------- 1 | pub unsafe fn strtonum( 2 | nptr: *const u8, 3 | minval: T, 4 | maxval: T, 5 | ) -> Result 6 | where 7 | T: Into, 8 | i64: TryInto, 9 | { 10 | let minval: i64 = minval.into(); 11 | let maxval: i64 = maxval.into(); 12 | 13 | if minval > maxval { 14 | return Err(c"invalid"); 15 | } 16 | 17 | let buf = unsafe { std::slice::from_raw_parts(nptr, crate::libc::strlen(nptr)) }; 18 | let s = std::str::from_utf8(buf).map_err(|_| c"invalid")?; 19 | let n = s.trim_start().parse::().map_err(|_| c"invalid")?; 20 | 21 | if n < minval { 22 | return Err(c"too small"); 23 | } 24 | 25 | if n > maxval { 26 | return Err(c"too large"); 27 | } 28 | 29 | match n.try_into() { 30 | Ok(value) => Ok(value), 31 | Err(_) => unreachable!("range check above should prevent this case"), 32 | } 33 | } 34 | 35 | pub fn strtonum_(s: &str, minval: T, maxval: T) -> Result 36 | where 37 | T: Into, 38 | i64: TryInto, 39 | { 40 | let minval: i64 = minval.into(); 41 | let maxval: i64 = maxval.into(); 42 | 43 | if minval > maxval { 44 | return Err(c"invalid"); 45 | } 46 | 47 | let n = s.trim_start().parse::().map_err(|_| c"invalid")?; 48 | 49 | if n < minval { 50 | return Err(c"too small"); 51 | } 52 | 53 | if n > maxval { 54 | return Err(c"too large"); 55 | } 56 | 57 | match n.try_into() { 58 | Ok(value) => Ok(value), 59 | Err(_) => unreachable!("range check above should prevent this case"), 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/compat/setproctitle.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // a custom version of setproctitle which just supports our usage: 16 | // setproctitle( c!("%s (%s)"), name, socket_path); 17 | #[cfg(target_os = "linux")] 18 | pub unsafe fn setproctitle_(_fmt: *const u8, name: *const u8, socket_path: *const u8) { 19 | use crate::libc; 20 | 21 | unsafe { 22 | let mut title: [u8; 16] = [0; 16]; 23 | 24 | let used = libc::snprintf( 25 | &raw mut title as _, 26 | title.len(), 27 | c"tmux: %s (%s)".as_ptr(), 28 | name, 29 | socket_path, 30 | ); 31 | if used >= title.len() as i32 { 32 | let cp = libc::strrchr(&raw const title as *const u8, b' ' as i32); 33 | if !cp.is_null() { 34 | *cp = b'\0'; 35 | } 36 | } 37 | libc::prctl(libc::PR_SET_NAME, &raw const title as *const u8); 38 | } 39 | } 40 | 41 | #[cfg(target_os = "macos")] 42 | pub unsafe fn setproctitle_(_: *const u8, _: *const u8, _: *const u8) {} 43 | -------------------------------------------------------------------------------- /regress/conf/91378fd400b0444eb8cac471e30642b3.conf: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | if-shell " \ 4 | tmux -V \ 5 | | awk '{print $2}' \ 6 | | awk -F - '{print $1}' \ 7 | | awk '{ \ 8 | if ($1 ~ /^[[:digit:].]+$/) { \ 9 | exit !($1 >= 2.6) \ 10 | } else { \ 11 | exit !($1 == \"master\" || $1 == \"next\") \ 12 | } \ 13 | }'" \ 14 | "source-file ~/.tmux/v2rc" \ 15 | "source-file ~/.tmux/v1rc" \ 16 | ; 17 | 18 | ### 19 | 20 | set-option -qg status-left \ 21 | "[#[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]" 22 | 23 | set-option -qg status-right \ 24 | "#[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]%% " 25 | 26 | set-option -qwg window-status-current-format \ 27 | "#[fg=colour208]»#[fg=colour190]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" 28 | 29 | set-option -qwg window-status-format \ 30 | "#[default]»#[fg=colour066]#{window_name}#[fg=colour037]·#{?window_flags,#[fg=colour058]#{window_flags}#[default], #[default]}" 31 | -------------------------------------------------------------------------------- /regress/conf/f4f1cdb9d518c2f7808a4915299f2524.conf: -------------------------------------------------------------------------------- 1 | bind m-4 run -C '#{@layout-vertical-two}' 2 | 3 | set -g @layout-vertical-two { 4 | selectl main-vertical 5 | if -F '#{==:#{@vertical-two-active},true}' { 6 | set -wu @vertical-two-active 7 | } { 8 | if -F '#{&&:#{==:#{N/s:layout_overflow},0},#{e|>=:#{n:#{P:x}},3}}' { 9 | run -C '#{@layout-vertical-two-init}' 10 | } 11 | } 12 | } 13 | 14 | set -g @layout-vertical-two-init { 15 | set -gF @total_panes '#{n:#{P:x}}' 16 | set -gF @cur_window '#S:#I' 17 | new -ds layout_overflow 18 | run -C '\ 19 | swapw -t layout_overflow: -s . ;\ 20 | splitw -fh -l 40% -t #{@cur_window} ;\ 21 | splitw -h -t #{@cur_window}.2 ;\ 22 | swapp -s #{@cur_window}.1 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ 23 | swapp -s #{@cur_window}.2 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ 24 | swapp -s #{@cur_window}.3 -t layout_overflow:1.1 ; killp -t layout_overflow:1.1 ;\ 25 | #{@layout-vertical-two-loop}' 26 | } 27 | 28 | set -g @layout-vertical-two-cleanup { 29 | set -gu @cur_window 30 | set -gu @total_panes '#{n:#{P:x}}' 31 | set -w @vertical-two-active true 32 | selectp -t .1 33 | } 34 | 35 | # (x - 1) % 2 == 0 ? (x - 1) / 2 + 1 : x 36 | # #{?#{==:#{e|%:#{e|-:#{cur_panes},1},2},0} <-- TODO: inserting horizontally shuffles windows. 37 | # ,#{e|+:#{e|/:#{e|-:#{cur_panes},1},2},1} <-- end of first column 38 | # ,#{cur_panes}} <-- end of second column 39 | set -g @layout-vertical-two-loop { 40 | # count(panes) < count(original.panes) 41 | if -F '#{e|<:#{n:#{P:x}},#{@total_panes}}' { 42 | 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}}}}' ;\ 43 | selectl -E ; #{@layout-vertical-two-loop}" 44 | } { 45 | run -C '#{@layout-vertical-two-cleanup}' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /regress/conf/29813ff35544434e2e64dc879a8dd274.conf: -------------------------------------------------------------------------------- 1 | set -g prefix C-g 2 | # needed for e.g. mutt 3 | bind C-g send-prefix 4 | 5 | set -g set-titles on 6 | set -g status-position top 7 | set -g status-keys vi 8 | set -g mode-keys vi 9 | set -g base-index 1 10 | set -g pane-base-index 1 11 | set -g focus-events on 12 | 13 | set history-file ~/.tmux_SSH_history 14 | set focus-events on 15 | set -g history-limit 100000 16 | set -s set-clipboard on 17 | set -g display-time 3000 18 | set -g display-panes-time 3000 19 | 20 | set -g pane-border-status top 21 | 22 | setw -g window-status-current-style bg=colour240,fg=colour250 23 | setw -g window-status-separator "|" 24 | set -g status-bg colour235 25 | set -g status-fg colour245 26 | 27 | set -g window-status-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" 28 | set -g window-status-current-format " #I #{=+15:pane_title} #{=-2:?window_flags, #{window_flags}, }" 29 | set -g pane-border-format " #P: #{s/ //:pane_title} " 30 | 31 | set -g renumber-windows on 32 | set -g status-right-length 0 33 | ############################################################## 34 | 35 | # I prefer not to have a status for my tabbed term 36 | set -g status-right "" 37 | set -g status-right-length 0 38 | set -g status-left-length 0 39 | set -g status-left "" 40 | 41 | # some settings for "navigation" 42 | bind -n C-PageUp copy-mode -u 43 | unbind -n C-Left 44 | unbind -n C-Right 45 | bind -n C-Left select-window -t :- 46 | bind -n C-Right select-window -t :+ 47 | 48 | # I prefer a tiled layout and easy joining of current active pane via windows' 49 | # index 50 | bind F1 join-pane -s 1.\; select-layout tiled 51 | bind F2 join-pane -s 2.\; select-layout tiled 52 | bind F3 join-pane -s 3.\; select-layout tiled 53 | bind F4 join-pane -s 4.\; select-layout tiled 54 | bind F5 join-pane -s 5.\; select-layout tiled 55 | bind F6 join-pane -s 6.\; select-layout tiled 56 | bind F7 join-pane -s 7.\; select-layout tiled 57 | bind F8 join-pane -s 8.\; select-layout tiled 58 | bind F9 join-pane -s 9.\; select-layout tiled 59 | -------------------------------------------------------------------------------- /src/cmd_/cmd_kill_server.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::libc::{SIGTERM, kill, pid_t}; 15 | use crate::*; 16 | use crate::{args_parse, cmd, cmd_entry, cmd_flag, cmd_get_entry, cmd_retval, cmdq_item}; 17 | 18 | pub static CMD_KILL_SERVER_ENTRY: cmd_entry = cmd_entry { 19 | name: "kill-server", 20 | alias: None, 21 | 22 | args: args_parse::new("", 0, 0, None), 23 | usage: "", 24 | 25 | flags: cmd_flag::empty(), 26 | exec: cmd_kill_server_exec, 27 | source: cmd_entry_flag::zeroed(), 28 | target: cmd_entry_flag::zeroed(), 29 | }; 30 | 31 | pub static CMD_START_SERVER_ENTRY: cmd_entry = cmd_entry { 32 | name: "start-server", 33 | alias: Some("start"), 34 | args: args_parse::new("", 0, 0, None), 35 | usage: "", 36 | flags: cmd_flag::CMD_STARTSERVER, 37 | exec: cmd_kill_server_exec, 38 | source: cmd_entry_flag::zeroed(), 39 | target: cmd_entry_flag::zeroed(), 40 | }; 41 | 42 | unsafe fn cmd_kill_server_exec(self_: *mut cmd, _: *mut cmdq_item) -> cmd_retval { 43 | unsafe { 44 | if std::ptr::eq(cmd_get_entry(self_), &CMD_KILL_SERVER_ENTRY) { 45 | kill(std::process::id() as pid_t, SIGTERM); 46 | } 47 | } 48 | 49 | cmd_retval::CMD_RETURN_NORMAL 50 | } 51 | -------------------------------------------------------------------------------- /src/cmd_/cmd_rename_window.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_RENAME_WINDOW_ENTRY: cmd_entry = cmd_entry { 17 | name: "rename-window", 18 | alias: Some("renamew"), 19 | 20 | args: args_parse::new("t:", 1, 1, None), 21 | usage: "[-t target-window] new-name", 22 | 23 | target: cmd_entry_flag::new( 24 | b't', 25 | cmd_find_type::CMD_FIND_WINDOW, 26 | cmd_find_flags::empty(), 27 | ), 28 | source: cmd_entry_flag::zeroed(), 29 | 30 | flags: cmd_flag::CMD_AFTERHOOK, 31 | exec: cmd_rename_window_exec, 32 | }; 33 | 34 | unsafe fn cmd_rename_window_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 35 | unsafe { 36 | let args = cmd_get_args(self_); 37 | let target = cmdq_get_target(item); 38 | let wl = (*target).wl; 39 | 40 | let newname = format_single_from_target(item, args_string(args, 0)); 41 | window_set_name((*wl).window, newname); 42 | options_set_number((*(*wl).window).options, c!("automatic-rename"), 0); 43 | 44 | server_redraw_window_borders((*wl).window); 45 | server_status_window((*wl).window); 46 | free_(newname); 47 | } 48 | 49 | cmd_retval::CMD_RETURN_NORMAL 50 | } 51 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Bob Beck beck 2 | Claudio Jeker claudio 3 | Igor Sobrado sobrado 4 | Ingo Schwarze schwarze 5 | Jacek Masiulaniec jacekm 6 | Jason McIntyre jmc 7 | Joel Sing jsing 8 | Jonathan Gray jsg 9 | Kenneth R Westerback krw 10 | Marc Espie espie 11 | Matthew Dempsky matthew 12 | Matthias Kilian kili 13 | Matthieu Herrb matthieu 14 | Michael McConville mmcc 15 | Miod Vallat miod 16 | Nicholas Marriott Nicholas Marriott 17 | Nicholas Marriott nicm 18 | Nicholas Marriott no_author 19 | Okan Demirmen okan 20 | Philip Guenther guenther 21 | Pierre-Yves Ritschard pyr 22 | Ray Lai ray 23 | Ryan McBride mcbride 24 | Sebastian Benoit benno 25 | Sebastien Marie semarie 26 | Stefan Sperling stsp 27 | Stuart Henderson sthen 28 | Ted Unangst tedu 29 | Theo de Raadt Theo Deraadt 30 | Theo de Raadt deraadt 31 | Thomas Adam Thomas 32 | Thomas Adam Thomas Adam 33 | Thomas Adam n6tadam 34 | Tim van der Molen tim 35 | Tobias Stoeckmann tobias 36 | Todd C Miller millert 37 | William Yodlowsky william 38 | -------------------------------------------------------------------------------- /regress/conf/2eae5d47049c1f6d0bef3db4e171aed7.conf: -------------------------------------------------------------------------------- 1 | # 256 colors for vim 2 | set -g default-terminal "screen-256color" 3 | 4 | # Set default shell to zsh 5 | set-option -g default-shell /bin/zsh 6 | 7 | # Start window numbering at 1 8 | set-option -g base-index 1 9 | set-window-option -g pane-base-index 1 10 | 11 | # Cycle panes with C-b C-b 12 | unbind ^B 13 | bind ^B select-pane -t :.+ 14 | 15 | # Reload config with a key 16 | bind-key r source-file ~/.tmux.conf \; display "Config reloaded!" 17 | 18 | # Mouse works as expected 19 | # set -g mode-mouse on 20 | # set -g mouse-select-pane on 21 | # set -g mouse-resize-pane on 22 | # set -g mouse-select-window on 23 | 24 | # Scrolling works as expected 25 | set -g terminal-overrides 'xterm*:smcup@:rmcup@' 26 | 27 | # Use the system clipboard 28 | # set-option -g default-command "reattach-to-user-namespace -l zsh" 29 | 30 | # Clear the pane and its history 31 | bind -n C-k send-keys C-l \; clear-history 32 | 33 | # smart pane switching with awareness of vim splits 34 | bind -n C-h run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-h) || tmux select-pane -L" 35 | bind -n C-j run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-j) || tmux select-pane -D" 36 | bind -n C-k run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-k) || tmux select-pane -U" 37 | bind -n C-l run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys C-l) || tmux select-pane -R" 38 | bind -n C-\ run "(tmux display-message -p '#{pane_current_command}' | grep -iq vim && tmux send-keys 'C-\\') || tmux select-pane -l" 39 | 40 | # C-l is taken oer by vim style pane navigation 41 | bind C-l send-keys 'C-l' 42 | 43 | # Use vim keybindings in copy mode 44 | setw -g mode-keys vi 45 | 46 | # Setup 'v' to begin selection as in Vim 47 | # bind-key -t vi-copy v begin-selection 48 | # bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy" 49 | 50 | # Update default binding of `Enter` to also use copy-pipe 51 | # unbind -t vi-copy Enter 52 | # bind-key -t vi-copy Enter copy-pipe "reattach-to-user-namespace pbcopy" 53 | 54 | # Powerline 55 | run-shell "powerline-daemon -q" 56 | source "/Users/adamcooper/Library/Python/3.7/lib/python/site-packages/powerline/bindings/tmux/powerline.conf" -------------------------------------------------------------------------------- /src/cmd_/cmd_kill_pane.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::*; 16 | 17 | pub static CMD_KILL_PANE_ENTRY: cmd_entry = cmd_entry { 18 | name: "kill-pane", 19 | alias: Some("killp"), 20 | 21 | args: args_parse::new("at:", 0, 0, None), 22 | usage: "[-a] [-t target-client]", 23 | 24 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 25 | 26 | flags: cmd_flag::CMD_AFTERHOOK, 27 | exec: cmd_kill_pane_exec, 28 | source: cmd_entry_flag::zeroed(), 29 | }; 30 | 31 | unsafe fn cmd_kill_pane_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 32 | unsafe { 33 | let args = cmd_get_args(self_); 34 | let target = cmdq_get_target(item); 35 | let wl = (*target).wl; 36 | let wp = (*target).wp; 37 | 38 | if args_has(args, 'a') { 39 | server_unzoom_window((*wl).window); 40 | for loopwp in 41 | tailq_foreach::<_, discr_entry>(&raw mut (*(*wl).window).panes).map(NonNull::as_ptr) 42 | { 43 | if loopwp == wp { 44 | continue; 45 | } 46 | server_client_remove_pane(loopwp); 47 | layout_close_pane(loopwp); 48 | window_remove_pane((*wl).window, loopwp); 49 | } 50 | server_redraw_window((*wl).window); 51 | return cmd_retval::CMD_RETURN_NORMAL; 52 | } 53 | 54 | server_kill_pane(wp); 55 | cmd_retval::CMD_RETURN_NORMAL 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A Rust port of tmux"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs"; 6 | crane.url = "github:ipetkov/crane"; 7 | flake-utils.url = "github:numtide/flake-utils"; 8 | rust-overlay = { 9 | url = "github:oxalica/rust-overlay"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | }; 13 | 14 | outputs = 15 | { 16 | self, 17 | nixpkgs, 18 | crane, 19 | flake-utils, 20 | rust-overlay, 21 | ... 22 | }: 23 | flake-utils.lib.eachDefaultSystem ( 24 | system: 25 | let 26 | pkgs = import nixpkgs { 27 | inherit system; 28 | overlays = [ (import rust-overlay) ]; 29 | }; 30 | 31 | inherit (pkgs) lib; 32 | 33 | # use latest stable Rust compiler 34 | craneLib = (crane.mkLib pkgs).overrideToolchain (p: p.rust-bin.stable.latest.default); 35 | 36 | commonArgs = { 37 | # include Rust sources, and anything else required by build 38 | src = lib.fileset.toSource { 39 | root = ./.; 40 | fileset = lib.fileset.unions [ 41 | (craneLib.fileset.commonCargoSources ./.) 42 | (lib.fileset.fileFilter (file: file.hasExt "lalrpop") ./.) 43 | ./README.md 44 | ]; 45 | }; 46 | strictDeps = true; 47 | }; 48 | 49 | tmux-rs = craneLib.buildPackage ( 50 | commonArgs 51 | // { 52 | cargoArtifacts = craneLib.buildDepsOnly commonArgs; 53 | cargoExtraArgs = "--verbose"; 54 | 55 | # libraries we link with (per build.rs) 56 | buildInputs = [ 57 | pkgs.libevent 58 | pkgs.ncurses 59 | ]; 60 | } 61 | ); 62 | in 63 | { 64 | checks = { 65 | inherit tmux-rs; 66 | }; 67 | 68 | packages.default = tmux-rs; 69 | apps.default = flake-utils.lib.mkApp { 70 | drv = tmux-rs; 71 | }; 72 | devShells.default = craneLib.devShell { 73 | checks = self.checks.${system}; 74 | packages = [ 75 | pkgs.bashInteractive # full-featured bash with readline 76 | pkgs.rust-analyzer 77 | pkgs.clippy 78 | pkgs.rustfmt 79 | ]; 80 | }; 81 | } 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tmux-rs" 3 | version = "0.0.3" 4 | authors = ["Collin Richards "] 5 | categories = ["command-line-utilities"] 6 | edition = "2024" 7 | include = ["/src", "COPYING", "README.md", "build.rs"] 8 | keywords = ["tmux"] 9 | license = "ISC" 10 | repository = "https://github.com/richardscollin/tmux-rs" 11 | rust-version = "1.88" # let chains 12 | description = "A Rust port of tmux" 13 | 14 | # Note: most of these features are not supported yet 15 | # they just exist to make matching upstream's 16 | # configuration options easier. However, sixel support is implemented 17 | [features] 18 | default = ["sixel"] 19 | cgroups = [] 20 | hyperlinks = [] 21 | iutf8 = [] 22 | ncurses = [] 23 | nokerninfo = [] 24 | sixel = [] 25 | sys_signame = [] 26 | systemd = [] 27 | utempter = [] 28 | utf8proc = [] 29 | 30 | # configure linking of external libraries 31 | # defaults to static linking on macOS, and dynamic linking on all other platforms 32 | static = [] 33 | dynamic = [] 34 | 35 | [dependencies] 36 | bitflags = { version = "2.9.1" } 37 | lalrpop-util = { version = "0.22.2", default-features = false, features = ["unicode"] } 38 | libc = { version = "0.2.174" } 39 | memchr = { version = "2.7.6" } 40 | num_enum = { version = "0.7.4" } 41 | paste = { version = "1.0.15" } 42 | 43 | [build-dependencies] 44 | lalrpop = { version = "0.22.2", default-features = false } 45 | 46 | [lints.rust] 47 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 48 | 49 | # https://rust-lang.github.io/rust-clippy/master/index.html 50 | [lints.clippy] 51 | # disabled 52 | too_many_arguments = "allow" 53 | # enabled 54 | allow_attributes = "warn" 55 | large_types_passed_by_value = "warn" 56 | literal_string_with_formatting_args = "warn" 57 | manual_is_variant_and = "warn" 58 | manual_let_else = "warn" 59 | manual_string_new = "warn" 60 | match_bool = "warn" 61 | multiple_crate_versions = "warn" 62 | semicolon_if_nothing_returned = "warn" 63 | shadow_same = "warn" 64 | too_long_first_doc_paragraph = "warn" 65 | uninlined_format_args = "warn" 66 | unused_result_ok = "warn" 67 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use ::std::{ 2 | alloc::{GlobalAlloc, Layout}, 3 | ffi::CString, 4 | str::FromStr as _, 5 | }; 6 | 7 | struct MyAlloc; 8 | #[global_allocator] 9 | static ALLOCATOR: MyAlloc = MyAlloc; 10 | unsafe impl GlobalAlloc for MyAlloc { 11 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 12 | unsafe { libc::malloc(layout.size()) as *mut u8 } 13 | } 14 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 15 | unsafe { libc::free(ptr.cast()) } 16 | } 17 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 18 | let align = layout.align(); 19 | // exploit we know align must be a non-zero power of 2 to do a faster division 20 | let nmemb = (layout.size() + align - 1) >> align.trailing_zeros(); 21 | unsafe { libc::calloc(nmemb, align) as *mut u8 } 22 | } 23 | unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { 24 | unsafe { libc::realloc(ptr.cast(), new_size) as *mut u8 } 25 | } 26 | } 27 | 28 | // TODO idea: 29 | // I noticed in the tmux code base there are many places an empty string is allocated so that 30 | // there's data there which is valid and can be freed or realloced later. Since we hook into 31 | // the allocator I wonder if it would be worth it to reuse a common empty string, and coding 32 | // the allocator to allow multiple frees of that empty string. I suspect it wouldn't because 33 | // it would be adding unecessary code to free in the common case. 34 | 35 | // It could also be interesting to add in a histogram for viewing memory allocations 36 | 37 | fn main() { 38 | let args = std::env::args().collect::>(); 39 | let args = args 40 | .into_iter() 41 | .map(|s| CString::from_str(&s).unwrap()) 42 | .collect::>(); 43 | let mut args: Vec<*mut u8> = args.into_iter().map(|s| s.into_raw().cast()).collect(); 44 | 45 | // TODO 46 | // passing null_mut() as env is ok for now because setproctitle call was removed 47 | // a similar shim will need to be added when that call is re-added 48 | unsafe { 49 | tmux_rs::tmux_main( 50 | args.len() as i32, 51 | args.as_mut_slice().as_mut_ptr(), 52 | std::ptr::null_mut(), 53 | ); 54 | } 55 | 56 | drop( 57 | args.into_iter() 58 | .map(|ptr| unsafe { CString::from_raw(ptr.cast()) }), 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/cmd_/cmd_kill_session.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::tree::rb_foreach; 15 | use crate::*; 16 | 17 | pub static CMD_KILL_SESSION_ENTRY: cmd_entry = cmd_entry { 18 | name: "kill-session", 19 | alias: None, 20 | 21 | args: args_parse::new("aCt:", 0, 0, None), 22 | usage: "[-aC] [-t target-session]", 23 | 24 | target: cmd_entry_flag::new( 25 | b't', 26 | cmd_find_type::CMD_FIND_SESSION, 27 | cmd_find_flags::empty(), 28 | ), 29 | source: cmd_entry_flag::zeroed(), 30 | 31 | flags: cmd_flag::empty(), 32 | exec: cmd_kill_session_exec, 33 | }; 34 | 35 | unsafe fn cmd_kill_session_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 36 | unsafe { 37 | let args = cmd_get_args(self_); 38 | let target = cmdq_get_target(item); 39 | let s = (*target).s; 40 | 41 | if args_has(args, 'C') { 42 | for wl in rb_foreach(&raw mut (*s).windows).map(NonNull::as_ptr) { 43 | (*(*wl).window).flags &= !WINDOW_ALERTFLAGS; 44 | (*wl).flags &= !WINLINK_ALERTFLAGS; 45 | } 46 | server_redraw_session(s); 47 | } else if args_has(args, 'a') { 48 | for sloop in rb_foreach(&raw mut SESSIONS).map(NonNull::as_ptr) { 49 | if sloop != s { 50 | server_destroy_session(sloop); 51 | session_destroy(sloop, 1, c!("cmd_kill_session_exec")); 52 | } 53 | } 54 | } else { 55 | server_destroy_session(s); 56 | session_destroy(s, 1, c!("cmd_kill_session_exec")); 57 | } 58 | cmd_retval::CMD_RETURN_NORMAL 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cmd_/cmd_rename_session.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::compat::tree::{rb_insert, rb_remove}; 16 | use crate::*; 17 | 18 | pub static CMD_RENAME_SESSION_ENTRY: cmd_entry = cmd_entry { 19 | name: "rename-session", 20 | alias: Some("rename"), 21 | 22 | args: args_parse::new("t:", 1, 1, None), 23 | usage: "[-t target-session] new-name", 24 | 25 | target: cmd_entry_flag::new( 26 | b't', 27 | cmd_find_type::CMD_FIND_SESSION, 28 | cmd_find_flags::empty(), 29 | ), 30 | source: cmd_entry_flag::zeroed(), 31 | 32 | flags: cmd_flag::CMD_AFTERHOOK, 33 | exec: cmd_rename_session_exec, 34 | }; 35 | 36 | unsafe fn cmd_rename_session_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 37 | unsafe { 38 | let args = cmd_get_args(self_); 39 | let target = cmdq_get_target(item); 40 | let s = (*target).s; 41 | 42 | let tmp = format_single_from_target(item, args_string(args, 0)); 43 | let Some(newname) = session_check_name(tmp) else { 44 | cmdq_error!(item, "invalid session: {}", _s(tmp)); 45 | free_(tmp); 46 | return cmd_retval::CMD_RETURN_ERROR; 47 | }; 48 | free_(tmp); 49 | if newname == (*s).name { 50 | return cmd_retval::CMD_RETURN_NORMAL; 51 | } 52 | if !session_find(&newname).is_null() { 53 | cmdq_error!(item, "duplicate session: {}", newname); 54 | return cmd_retval::CMD_RETURN_ERROR; 55 | } 56 | 57 | rb_remove(&raw mut SESSIONS, s); 58 | (*s).name = newname.into(); 59 | rb_insert(&raw mut SESSIONS, s); 60 | 61 | server_status_session(s); 62 | notify_session(c"session-renamed", s); 63 | } 64 | 65 | cmd_retval::CMD_RETURN_NORMAL 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions 2 | on: 3 | pull_request: 4 | branches: [main] 5 | push: 6 | branches: [main] 7 | name: build 8 | 9 | jobs: 10 | asan-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install system dependencies (ubuntu) 15 | run: &ubuntu-install-cmd | 16 | sudo apt-get update 17 | sudo apt-get install -y libncurses-dev libevent-dev 18 | - uses: dtolnay/rust-toolchain@nightly 19 | - run: | 20 | RUSTFLAGS=-Zsanitizer=address cargo test --target x86_64-unknown-linux-gnu 21 | 22 | clippy: 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | os: [ubuntu-latest, macos-latest, ubuntu-24.04-arm] 28 | steps: 29 | - name: Install system dependencies (ubuntu) 30 | if: startsWith(matrix.os, 'ubuntu') 31 | run: *ubuntu-install-cmd 32 | - &mac-deps 33 | name: Install system dependencies (macOS) 34 | if: matrix.os == 'macos-latest' 35 | run: | 36 | brew update 37 | brew install ncurses # libevent is already installed in github runner 38 | - uses: actions/checkout@v4 39 | - uses: dtolnay/rust-toolchain@stable 40 | with: 41 | components: clippy 42 | - uses: dtolnay/rust-toolchain@nightly 43 | with: 44 | components: clippy 45 | - run: cargo +stable clippy -- -Dwarnings 46 | - run: cargo +nightly clippy -- -Dwarnings 47 | 48 | fmt: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Install system dependencies (ubuntu) 53 | run: *ubuntu-install-cmd 54 | - uses: dtolnay/rust-toolchain@nightly 55 | with: 56 | components: rustfmt 57 | - uses: dtolnay/rust-toolchain@stable 58 | with: 59 | components: rustfmt 60 | # should format cleanly on nightly and stable 61 | - run: cargo +stable fmt --check # this will warn about unstable features, but that's fine 62 | - run: cargo +nightly fmt --check 63 | 64 | build: 65 | runs-on: ${{ matrix.os }} 66 | strategy: 67 | matrix: 68 | os: [ubuntu-latest, macos-latest, ubuntu-24.04-arm] 69 | steps: 70 | - name: Install system dependencies (ubuntu) 71 | if: startsWith(matrix.os, 'ubuntu') 72 | run: *ubuntu-install-cmd 73 | - *mac-deps 74 | - uses: actions/checkout@v4 75 | - uses: dtolnay/rust-toolchain@stable 76 | - run: cargo build 77 | - run: cargo test 78 | -------------------------------------------------------------------------------- /src/cmd_/cmd_list_buffers.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::*; 16 | 17 | pub static CMD_LIST_BUFFERS_ENTRY: cmd_entry = cmd_entry { 18 | name: "list-buffers", 19 | alias: Some("lsb"), 20 | 21 | args: args_parse::new("F:f:", 0, 0, None), 22 | usage: "[-F format] [-f filter]", 23 | 24 | flags: cmd_flag::CMD_AFTERHOOK, 25 | exec: cmd_list_buffers_exec, 26 | source: cmd_entry_flag::zeroed(), 27 | target: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | unsafe fn cmd_list_buffers_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 31 | unsafe { 32 | let args = cmd_get_args(self_); 33 | 34 | let mut template: *const u8 = args_get(args, b'F'); 35 | if template.is_null() { 36 | template = c!("#{buffer_name}: #{buffer_size} bytes: \"#{buffer_sample}\""); 37 | } 38 | let filter = args_get(args, b'f'); 39 | 40 | let mut pb = null_mut(); 41 | while { 42 | pb = paste_walk(pb); 43 | !pb.is_null() 44 | } { 45 | let ft = format_create( 46 | cmdq_get_client(item), 47 | item, 48 | FORMAT_NONE, 49 | format_flags::empty(), 50 | ); 51 | format_defaults_paste_buffer(ft, pb); 52 | 53 | let flag; 54 | if !filter.is_null() { 55 | let expanded = format_expand(ft, filter); 56 | flag = format_true(expanded); 57 | free_(expanded); 58 | } else { 59 | flag = true; 60 | } 61 | if flag { 62 | let line = format_expand(ft, template); 63 | cmdq_print!(item, "{}", _s(line)); 64 | free_(line); 65 | } 66 | 67 | format_free(ft); 68 | } 69 | 70 | cmd_retval::CMD_RETURN_NORMAL 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utf8_combined.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use core::ffi::c_void; 15 | 16 | use libc::memcmp; 17 | 18 | use crate::{utf8_data, utf8_in_table, utf8_state, utf8_towc, wchar_t}; 19 | 20 | static UTF8_MODIFIER_TABLE: [wchar_t; 31] = [ 21 | 0x1F1E6, 0x1F1E7, 0x1F1E8, 0x1F1E9, 0x1F1EA, 0x1F1EB, 0x1F1EC, 0x1F1ED, 0x1F1EE, 0x1F1EF, 22 | 0x1F1F0, 0x1F1F1, 0x1F1F2, 0x1F1F3, 0x1F1F4, 0x1F1F5, 0x1F1F6, 0x1F1F7, 0x1F1F8, 0x1F1F9, 23 | 0x1F1FA, 0x1F1FB, 0x1F1FC, 0x1F1FD, 0x1F1FE, 0x1F1FF, 0x1F3FB, 0x1F3FC, 0x1F3FD, 0x1F3FE, 24 | 0x1F3FF, 25 | ]; 26 | 27 | pub unsafe fn utf8_has_zwj(ud: *const utf8_data) -> bool { 28 | unsafe { 29 | if (*ud).size < 3 { 30 | return false; 31 | } 32 | 33 | memcmp( 34 | &raw const (*ud).data[((*ud).size - 3) as usize] as *const c_void, 35 | b"\xe2\x80\x8d\x00" as *const u8 as *const c_void, 36 | 3, 37 | ) == 0 38 | } 39 | } 40 | 41 | pub unsafe fn utf8_is_zwj(ud: *const utf8_data) -> bool { 42 | unsafe { 43 | if (*ud).size != 3 { 44 | return false; 45 | } 46 | memcmp( 47 | &raw const (*ud).data as *const u8 as *const c_void, 48 | b"\xe2\x80\x8d\x00" as *const u8 as *const c_void, 49 | 3, 50 | ) == 0 51 | } 52 | } 53 | 54 | pub unsafe fn utf8_is_vs(ud: *const utf8_data) -> bool { 55 | unsafe { 56 | if (*ud).size != 3 { 57 | return false; 58 | } 59 | memcmp( 60 | &raw const (*ud).data as *const u8 as *const c_void, 61 | b"\xef\xbf\x8f\x00" as *const u8 as *const c_void, 62 | 3, 63 | ) == 0 64 | } 65 | } 66 | 67 | pub unsafe fn utf8_is_modifier(ud: *const utf8_data) -> bool { 68 | let mut wc: wchar_t = 0; 69 | unsafe { 70 | if utf8_towc(ud, &raw mut wc) != utf8_state::UTF8_DONE { 71 | return false; 72 | } 73 | } 74 | utf8_in_table(wc, &UTF8_MODIFIER_TABLE) 75 | } 76 | -------------------------------------------------------------------------------- /src/cmd_/cmd_lock_server.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_LOCK_SERVER_ENTRY: cmd_entry = cmd_entry { 17 | name: "lock-server", 18 | alias: Some("lock"), 19 | 20 | args: args_parse::new("", 0, 0, None), 21 | usage: "", 22 | 23 | flags: cmd_flag::CMD_AFTERHOOK, 24 | exec: cmd_lock_server_exec, 25 | source: cmd_entry_flag::zeroed(), 26 | target: cmd_entry_flag::zeroed(), 27 | }; 28 | 29 | pub static CMD_LOCK_SESSION_ENTRY: cmd_entry = cmd_entry { 30 | name: "lock-session", 31 | alias: Some("locks"), 32 | 33 | args: args_parse::new("t:", 0, 0, None), 34 | usage: "[-t target-session]", 35 | 36 | target: cmd_entry_flag::new( 37 | b't', 38 | cmd_find_type::CMD_FIND_SESSION, 39 | cmd_find_flags::empty(), 40 | ), 41 | 42 | flags: cmd_flag::CMD_AFTERHOOK, 43 | exec: cmd_lock_server_exec, 44 | source: cmd_entry_flag::zeroed(), 45 | }; 46 | 47 | pub static CMD_LOCK_CLIENT_ENTRY: cmd_entry = cmd_entry { 48 | name: "lock-client", 49 | alias: Some("lockc"), 50 | 51 | args: args_parse::new("t:", 0, 0, None), 52 | usage: "[-t target-client]", 53 | 54 | flags: cmd_flag::CMD_AFTERHOOK.union(cmd_flag::CMD_CLIENT_TFLAG), 55 | exec: cmd_lock_server_exec, 56 | source: cmd_entry_flag::zeroed(), 57 | target: cmd_entry_flag::zeroed(), 58 | }; 59 | 60 | unsafe fn cmd_lock_server_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 61 | unsafe { 62 | let target = cmdq_get_target(item); 63 | let tc = cmdq_get_target_client(item); 64 | 65 | if std::ptr::eq(cmd_get_entry(self_), &CMD_LOCK_SERVER_ENTRY) { 66 | server_lock(); 67 | } else if std::ptr::eq(cmd_get_entry(self_), &CMD_LOCK_SESSION_ENTRY) { 68 | server_lock_session((*target).s); 69 | } else { 70 | server_lock_client(tc); 71 | } 72 | recalculate_sizes(); 73 | } 74 | 75 | cmd_retval::CMD_RETURN_NORMAL 76 | } 77 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "locked": { 5 | "lastModified": 1751562746, 6 | "narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=", 7 | "owner": "ipetkov", 8 | "repo": "crane", 9 | "rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "ipetkov", 14 | "repo": "crane", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-utils": { 19 | "inputs": { 20 | "systems": "systems" 21 | }, 22 | "locked": { 23 | "lastModified": 1731533236, 24 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 25 | "owner": "numtide", 26 | "repo": "flake-utils", 27 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "numtide", 32 | "repo": "flake-utils", 33 | "type": "github" 34 | } 35 | }, 36 | "nixpkgs": { 37 | "locked": { 38 | "lastModified": 1751584797, 39 | "narHash": "sha256-1st3Qy4AE2Qycm/2Vy1XJ4IMDR7IEhpgAClwMVP1D5o=", 40 | "owner": "NixOS", 41 | "repo": "nixpkgs", 42 | "rev": "f404c776a76b6deaef99b873ee295be6c5d12e44", 43 | "type": "github" 44 | }, 45 | "original": { 46 | "owner": "NixOS", 47 | "repo": "nixpkgs", 48 | "type": "github" 49 | } 50 | }, 51 | "root": { 52 | "inputs": { 53 | "crane": "crane", 54 | "flake-utils": "flake-utils", 55 | "nixpkgs": "nixpkgs", 56 | "rust-overlay": "rust-overlay" 57 | } 58 | }, 59 | "rust-overlay": { 60 | "inputs": { 61 | "nixpkgs": [ 62 | "nixpkgs" 63 | ] 64 | }, 65 | "locked": { 66 | "lastModified": 1751510438, 67 | "narHash": "sha256-m8PjOoyyCR4nhqtHEBP1tB/jF+gJYYguSZmUmVTEAQE=", 68 | "owner": "oxalica", 69 | "repo": "rust-overlay", 70 | "rev": "7f415261f298656f8164bd636c0dc05af4e95b6b", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "oxalica", 75 | "repo": "rust-overlay", 76 | "type": "github" 77 | } 78 | }, 79 | "systems": { 80 | "locked": { 81 | "lastModified": 1681028828, 82 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 83 | "owner": "nix-systems", 84 | "repo": "default", 85 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 86 | "type": "github" 87 | }, 88 | "original": { 89 | "owner": "nix-systems", 90 | "repo": "default", 91 | "type": "github" 92 | } 93 | } 94 | }, 95 | "root": "root", 96 | "version": 7 97 | } 98 | -------------------------------------------------------------------------------- /regress/conf/b9f0ce1976ec62ec60dc5da7dd92c160.conf: -------------------------------------------------------------------------------- 1 | # none of these attempts worked, to bind keys, except sometimes during the session. Oh well. 2 | # I thought maybe that was because F1 is handled differently in a console than in X, but 3 | # even just C-1 didn't work. Using just "a" or "x" as the key did, but not yet sure why not "C-". 4 | #bind-key -T root C-1 attach-session -t$0 5 | #But this one works now, only picks the wrong one? Mbe need2understand what "$1" or $0 mean, better, 6 | #but with the stub maybe this doesn't matter: 7 | bind-key "`" switch-client -t$1 8 | 9 | new-session #$0, stub, for keystroke convenience 10 | 11 | #$1 for root 12 | new-session #a stub I guess, where keyboard convenience is concerned 13 | new-window 14 | send-keys -l pgup 15 | send-keys Enter 16 | send-keys -l "less /root/.tmux.conf &" 17 | send-keys -l "echo; echo; echo Put something here for ssa or just run manly?" 18 | send-keys Enter 19 | new-window 20 | new-window 21 | new-window sul #for lcall, like man pages 22 | send-keys -l "man tmux&" 23 | send-keys Enter 24 | select-window -t :=1 25 | 26 | #$2 for om, so, can do C-b C-s 2 to get to the session, then C-b <#s> to get ~"tabs" 27 | new-session sula ; send-keys -l q #0 28 | send-keys Enter Escape Escape 29 | new-window sula ; send-keys -l q #1 30 | send-keys Enter Escape Escape 31 | new-window sula ; send-keys -l q #2 32 | send-keys Enter Escape Enter Enter 33 | new-window sula ; send-keys q #3, to start: 34 | send-keys Enter Escape 35 | # %%need a sleep here & .. ? 36 | send-keys Enter Enter Enter Enter 37 | new-window sula ; send-keys -l q #4 38 | send-keys Enter Escape Escape 39 | new-window sula 40 | new-window sula 41 | new-window sula 42 | new-window sula 43 | new-window sula 44 | select-window -t :=2 45 | select-window -t :=3 46 | 47 | #$3 for email (mutt) 48 | new-session sula 49 | new-window sula ; send-keys mutt Enter 50 | #nah, probably better not?: 51 | #send-keys -l z 52 | #send-keys -l "thepassifdecide" 53 | #send-keys Enter 54 | new-window sula ; send-keys mutt Enter 55 | send-keys -l "c!=sent" 56 | send-keys Enter 57 | new-window sula ; send-keys -l "cd mail/config; less mailsig.txt&" 58 | send-keys Enter 59 | send-keys "less macros&" 60 | send-keys Enter 61 | select-window -t :=1 62 | 63 | #$4 for lacall-net: links etc 64 | new-session suln 65 | new-window suln 66 | #send-keys -l "lkslfx" #; et; links ksl.com" 67 | #send-keys asdafdfadfadfadfadf 68 | #%%does opening links break subsequent cmds? With this Enter, the switch-client etc dont work: 69 | #send-keys Enter 70 | #send-keys Space Space Space 71 | new-window suln 72 | new-window suln 73 | select-window -t :=1 74 | #send-keys Space Space Space 75 | 76 | #$5 for lacall-secnet, links?: 77 | #new-session sulsn 78 | 79 | # then, where to start: 80 | #%%need a sleep here, or ck a debug thing? 81 | switch-client -t"$0" 82 | send-keys -l "sleep 2" 83 | send-keys Enter 84 | switch-client -t$2 85 | -------------------------------------------------------------------------------- /regress/conf/a4789a6782859c66aa8c9614ee6fabfa.conf: -------------------------------------------------------------------------------- 1 | set -g default-command "if [ \"$(uname)\" = 'Darwin' ]; then exec reattach-to-user-namespace $SHELL; else exec $SHELL; fi" 2 | set -g history-limit 32000 3 | 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" 4 | 5 | # Reset SHLVL (otherwise it is 2 inside tmux) 6 | setenv -g SHLVL 0 7 | 8 | # Send esc faster so that neovim won't get so laggy 9 | # https://github.com/neovim/neovim/issues/2093 10 | set -g escape-time 100 11 | 12 | # Disable paste detection 13 | set -g assume-paste-time 0 14 | 15 | # Titles and window names 16 | set -g set-titles on 17 | set -g set-titles-string "#T" 18 | 19 | # Make it not so annoying/sticky to switch windows 20 | set -g repeat-time 170 21 | 22 | # Don't deattach me when a session ends 23 | set -g detach-on-destroy off 24 | 25 | # Make shift+keys work 26 | setw -g xterm-keys on 27 | 28 | # Prefix 29 | set -g prefix ^A 30 | unbind ^B 31 | bind ^A send-prefix 32 | bind a send-prefix 33 | 34 | # Last window 35 | bind ^a last 36 | 37 | # Next & prev 38 | bind ' ' next 39 | bind '^ ' next 40 | bind ^p prev 41 | 42 | # Status 43 | set -g status off 44 | # Need more (cow)bells! 45 | set -g bell-action any 46 | set -g bell-on-alert on 47 | 48 | # Detach 49 | bind ^d detach 50 | 51 | # Control the a tmux in a tmux 52 | bind A send-prefix \; send-prefix 53 | bind C send-prefix \; send-keys c 54 | bind n send-prefix \; send-keys ' ' 55 | bind bspace send-prefix \; send-keys p 56 | bind '#' send-prefix \; send-keys '"' 57 | 58 | # Other key bindings. 59 | bind ^r command-prompt "find-window '%%'" 60 | bind '"' choose-tree -w 61 | bind w split-window 62 | bind W split-window -c "#{pane_current_path}" 63 | bind ^w split-window 64 | bind I list-windows 65 | bind i list-windows 66 | 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' 67 | 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" 68 | bind ^s command-prompt "rename-session '%%'" 69 | # Make the default HOME always ~ 70 | bind c neww -c ~ 71 | bind ^c new -c ~ 72 | bind escape copy-mode 73 | # Copy to the OS clipboard 74 | bind -T copy-mode-vi y send -X copy-pipe-and-cancel "if [ \"$(uname)\" = 'Darwin' ]; then reattach-to-user-namespace pbcopy; else xclip; fi" 75 | bind j command-prompt "join-pane -s '%%'" 76 | bind ! break-pane -d 77 | bind - command-prompt "move-pane -t '%%'" 78 | 79 | # Makes `tmux a` work even when there isn't a session going on 80 | new-session -A -c ~ 81 | -------------------------------------------------------------------------------- /src/cmd_/cmd_list_sessions.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::tree::rb_foreach; 15 | use crate::*; 16 | 17 | pub static CMD_LIST_SESSIONS_ENTRY: cmd_entry = cmd_entry { 18 | name: "list-sessions", 19 | alias: Some("ls"), 20 | 21 | args: args_parse::new("F:f:", 0, 0, None), 22 | usage: "[-F format] [-f filter]", 23 | 24 | flags: cmd_flag::CMD_AFTERHOOK, 25 | exec: cmd_list_sessions_exec, 26 | source: cmd_entry_flag::zeroed(), 27 | target: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | const LIST_SESSIONS_TEMPLATE: *const u8 = c!( 31 | "#{session_name}: #{session_windows} windows (created #{t:session_created})#{?session_grouped, (group ,}#{session_group}#{?session_grouped,),}#{?session_attached, (attached),}" 32 | ); 33 | 34 | unsafe fn cmd_list_sessions_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 35 | unsafe { 36 | let args = cmd_get_args(self_); 37 | 38 | let mut template = args_get(args, b'F'); 39 | if template.is_null() { 40 | template = LIST_SESSIONS_TEMPLATE; 41 | } 42 | let filter = args_get(args, b'f'); 43 | 44 | for (n, s) in rb_foreach(&raw mut SESSIONS).enumerate() { 45 | let ft = format_create( 46 | cmdq_get_client(item), 47 | item, 48 | FORMAT_NONE, 49 | format_flags::empty(), 50 | ); 51 | format_add!(ft, "line", "{n}"); 52 | format_defaults(ft, null_mut(), Some(s), None, None); 53 | 54 | let flag; 55 | if !filter.is_null() { 56 | let expanded = format_expand(ft, filter); 57 | flag = format_true(expanded); 58 | free_(expanded); 59 | } else { 60 | flag = true; 61 | } 62 | if flag { 63 | let line = format_expand(ft, template); 64 | cmdq_print!(item, "{}", _s(line)); 65 | free_(line); 66 | } 67 | 68 | format_free(ft); 69 | } 70 | 71 | cmd_retval::CMD_RETURN_NORMAL 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/compat/recallocarray.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008, 2017 Otto Moerbeek 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use core::ffi::c_void; 15 | use core::ptr::null_mut; 16 | 17 | pub unsafe fn recallocarray( 18 | ptr: *mut c_void, 19 | oldnmemb: usize, 20 | newnmemb: usize, 21 | size: usize, 22 | ) -> *mut c_void { 23 | const MUL_NO_OVERFLOW: usize = 1usize << (size_of::() * 4); 24 | 25 | unsafe extern "C" { 26 | fn getpagesize() -> i32; 27 | } 28 | 29 | unsafe { 30 | if ptr.is_null() { 31 | return libc::calloc(newnmemb, size); 32 | } 33 | 34 | if (newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) 35 | && newnmemb > 0 36 | && usize::MAX / newnmemb < size 37 | { 38 | crate::errno!() = libc::ENOMEM; 39 | return null_mut(); 40 | } 41 | let newsize = newnmemb * size; 42 | 43 | if (oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) 44 | && oldnmemb > 0 45 | && usize::MAX / oldnmemb < size 46 | { 47 | crate::errno!() = libc::EINVAL; 48 | return null_mut(); 49 | } 50 | let oldsize = oldnmemb * size; 51 | 52 | // Don't bother too much if we're shrinking just a bit, 53 | // we do not shrink for series of small steps, oh well. 54 | if newsize <= oldsize { 55 | let d = oldsize - newsize; 56 | 57 | if d < oldsize / 2 && d < getpagesize() as usize { 58 | libc::memset((ptr as *mut u8).add(newsize).cast(), 0, d); 59 | return ptr; 60 | } 61 | } 62 | 63 | let newptr = libc::malloc(newsize); 64 | if newptr.is_null() { 65 | return null_mut(); 66 | } 67 | 68 | if newsize > oldsize { 69 | libc::memcpy(newptr, ptr, oldsize); 70 | libc::memset( 71 | (newptr as *mut u8).add(oldsize).cast(), 72 | 0, 73 | newsize - oldsize, 74 | ); 75 | } else { 76 | libc::memcpy(newptr, ptr, newsize); 77 | } 78 | 79 | libc::memset(ptr, 0, oldsize); 80 | libc::free(ptr); 81 | 82 | newptr 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /regress/conf/ad0537c4e83d7a25d5dc4f3a3c571349.conf: -------------------------------------------------------------------------------- 1 | set-option -g allow-rename on 2 | set-option -g automatic-rename off 3 | set-option -g base-index 1 4 | set-option -g default-command "$SHELL" 5 | set-option -g default-terminal "tmux-256color" 6 | set-option -g history-limit 25000 7 | set-option -g mode-keys vi 8 | set-option -g prefix C-f 9 | set-option -g renumber-windows yes 10 | set-option -g set-titles on 11 | set-option -g set-titles-string "#T" 12 | set-option -g xterm-keys on 13 | 14 | set-option -g status-interval 1 15 | set-option -g status-left "#(tmux-status-left)" 16 | set-option -g status-left-length 40 17 | set-option -g status-right "" 18 | 19 | set-option -g window-status-current-attr bold 20 | set-option -g window-status-current-format "[#I#F#{?window_zoomed_flag, ,}#{=40:pane_title}]" 21 | set-option -g window-status-format "#I#{?window_zoomed_flag, ,}#F#{?window_flags,, }#{?window_zoomed_flag, ,}#{=20:pane_title}" 22 | 23 | set-option -g pane-active-border-fg colour247 24 | set-option -g pane-border-fg colour235 25 | set-option -g status-bg colour7 26 | set-option -g status-fg colour16 27 | set-option -g status-left-bg colour4 28 | set-option -g status-left-fg colour15 29 | set-option -g window-status-current-bg colour15 30 | set-option -g window-status-current-fg colour16 31 | 32 | set-option -g update-environment "DBUS_SESSION_BUS_ADDRESS DISPLAY KRB5CCNAME \ 33 | SESSION_MANAGER SSH_AGENT_PID SSH_ASKPASS SSH_AUTH_SOCK SSH_CONNECTION \ 34 | WINDOWID XAUTHORITY SSH_TTY" 35 | 36 | bind-key w break-pane -d 37 | bind-key l clear-history \; display "Pane history cleared." 38 | bind-key C-f if-shell "test #{window_panes} -eq 1" last-window last-pane 39 | bind-key N new-session 40 | bind-key t new-window 41 | bind-key z resize-pane -Z 42 | bind-key C-r rotate-window -D 43 | bind-key -n C-t run-shell "metamux new-shell-in-pane #{window_panes}" 44 | bind-key n run-shell "metamux rotate-pane next" 45 | bind-key p run-shell "metamux rotate-pane prev" 46 | bind-key q run-shell "metamux pane-buster" 47 | bind-key S run-shell "metamux join-hidden-pane -v" 48 | bind-key u run-shell "metamux open-last-url-printed" 49 | bind-key | run-shell "metamux join-hidden-pane -h" 50 | bind-key f send-prefix 51 | bind-key r source "$HOME/.tmux.conf" \; display "Configuration reloaded." 52 | 53 | # When the current window is split, Ctrl+Tab and Ctrl+Shift+Tab should rotate 54 | # between the split windows. If there is only one pane in the current window, 55 | # Ctrl+Tab and Ctrl+Shift+Tab will cycle between windows as though they were 56 | # tabs in modern desktop UIs. 57 | bind-key -n C-Tab if-shell "test #{window_panes} -eq 1" next-window "select-pane -t :.+" 58 | bind-key -n C-S-Tab if-shell "test #{window_panes} -eq 1" previous-window "select-pane -t :.-" 59 | 60 | # Binding to mark and swap panes; if no pane is marked, the shortcut will mark 61 | # the active pane, but if a pane is already marked, active pane will be swapped 62 | # with the marked pane. 63 | bind-key m if-shell 'test -z "$PANE_IS_MARKED"' \ 64 | "select-pane -m; set-env PANE_IS_MARKED 1" \ 65 | "swap-pane; select-pane -M; set-env -u PANE_IS_MARKED" 66 | -------------------------------------------------------------------------------- /src/cmd_/cmd_respawn_window.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_RESPAWN_WINDOW_ENTRY: cmd_entry = cmd_entry { 17 | name: "respawn-window", 18 | alias: Some("respawnw"), 19 | 20 | args: args_parse::new("c:e:kt:", 0, -1, None), 21 | usage: "[-k] [-c start-directory] [-e environment] [-t target-window] [shell-command]", 22 | 23 | target: cmd_entry_flag::new( 24 | b't', 25 | cmd_find_type::CMD_FIND_WINDOW, 26 | cmd_find_flags::empty(), 27 | ), 28 | source: cmd_entry_flag::zeroed(), 29 | 30 | flags: cmd_flag::empty(), 31 | exec: cmd_respawn_window_exec, 32 | }; 33 | 34 | unsafe fn cmd_respawn_window_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 35 | unsafe { 36 | let args = cmd_get_args(self_); 37 | let target = cmdq_get_target(item); 38 | let mut sc: spawn_context = zeroed(); 39 | let tc = cmdq_get_target_client(item); 40 | let s = (*target).s; 41 | let wl = (*target).wl; 42 | let mut cause: *mut u8 = null_mut(); 43 | 44 | sc.item = item; 45 | sc.s = s; 46 | sc.wl = wl; 47 | sc.tc = tc; 48 | 49 | args_to_vector(args, &raw mut sc.argc, &raw mut sc.argv); 50 | sc.environ = environ_create().as_ptr(); 51 | 52 | let mut av = args_first_value(args, b'e'); 53 | while !av.is_null() { 54 | environ_put(sc.environ, (*av).union_.string, environ_flags::empty()); 55 | av = args_next_value(av); 56 | } 57 | 58 | sc.idx = -1; 59 | sc.cwd = args_get(args, b'c'); 60 | 61 | sc.flags = SPAWN_RESPAWN; 62 | if args_has(args, 'k') { 63 | sc.flags |= SPAWN_KILL; 64 | } 65 | 66 | if spawn_window(&raw mut sc, &raw mut cause).is_null() { 67 | cmdq_error!(item, "respawn window failed: {}", _s(cause)); 68 | free_(cause); 69 | if !sc.argv.is_null() { 70 | cmd_free_argv(sc.argc, sc.argv); 71 | } 72 | environ_free(sc.environ); 73 | return cmd_retval::CMD_RETURN_ERROR; 74 | } 75 | 76 | server_redraw_window((*wl).window); 77 | 78 | if !sc.argv.is_null() { 79 | cmd_free_argv(sc.argc, sc.argv); 80 | } 81 | environ_free(sc.environ); 82 | 83 | cmd_retval::CMD_RETURN_NORMAL 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/cmd_/cmd_respawn_pane.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2008 Nicholas Marriott 2 | // Copyright (c) 2011 Marcel P. Partap 3 | // 4 | // Permission to use, copy, modify, and distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 13 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 14 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | use crate::*; 16 | 17 | pub static CMD_RESPAWN_PANE_ENTRY: cmd_entry = cmd_entry { 18 | name: "respawn-pane", 19 | alias: Some("respawnp"), 20 | 21 | args: args_parse::new("c:e:kt:", 0, -1, None), 22 | usage: "[-k] [-c start-directory] [-e environment] [-t target-pane] [shell-command]", 23 | 24 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 25 | 26 | flags: cmd_flag::empty(), 27 | exec: cmd_respawn_pane_exec, 28 | source: cmd_entry_flag::zeroed(), 29 | }; 30 | 31 | unsafe fn cmd_respawn_pane_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 32 | unsafe { 33 | let args = cmd_get_args(self_); 34 | let target = cmdq_get_target(item); 35 | let mut sc: spawn_context = zeroed(); 36 | let s = (*target).s; 37 | let wl = (*target).wl; 38 | let wp = (*target).wp; 39 | let mut cause = null_mut(); 40 | 41 | sc.item = item; 42 | sc.s = s; 43 | sc.wl = wl; 44 | 45 | sc.wp0 = wp; 46 | 47 | args_to_vector(args, &raw mut sc.argc, &raw mut sc.argv); 48 | sc.environ = environ_create().as_ptr(); 49 | 50 | let mut av = args_first_value(args, b'e'); 51 | while !av.is_null() { 52 | environ_put(sc.environ, (*av).union_.string, environ_flags::empty()); 53 | av = args_next_value(av); 54 | } 55 | 56 | sc.idx = -1; 57 | sc.cwd = args_get(args, b'c'); 58 | 59 | sc.flags = SPAWN_RESPAWN; 60 | if args_has(args, 'k') { 61 | sc.flags |= SPAWN_KILL; 62 | } 63 | 64 | if spawn_pane(&raw mut sc, &raw mut cause).is_null() { 65 | cmdq_error!(item, "respawn pane failed: {}", _s(cause)); 66 | free_(cause); 67 | if !sc.argv.is_null() { 68 | cmd_free_argv(sc.argc, sc.argv); 69 | } 70 | environ_free(sc.environ); 71 | return cmd_retval::CMD_RETURN_ERROR; 72 | } 73 | 74 | (*wp).flags |= window_pane_flags::PANE_REDRAW; 75 | server_redraw_window_borders((*wp).window); 76 | server_status_window((*wp).window); 77 | 78 | if !sc.argv.is_null() { 79 | cmd_free_argv(sc.argc, sc.argv); 80 | } 81 | environ_free(sc.environ); 82 | cmd_retval::CMD_RETURN_NORMAL 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /regress/conf/99749670b62bcb99a9b2e3d59708e357.conf: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # This config is targeted for tmux 2.1+ and should be placed in $HOME. 3 | # 4 | # Read the "Plugin Manager" section (bottom) before trying to use this config! 5 | # ----------------------------------------------------------------------------- 6 | 7 | # ----------------------------------------------------------------------------- 8 | # Global options 9 | # ----------------------------------------------------------------------------- 10 | 11 | # Set a new prefix / leader key. 12 | set -g prefix ` 13 | bind ` send-prefix 14 | 15 | # Allow opening multiple terminals to view the same session at different sizes. 16 | setw -g aggressive-resize on 17 | 18 | # Remove delay when switching between Vim modes. 19 | set -s escape-time 0 20 | 21 | # Allow Vim's FocusGained to work when your terminal gains focus. 22 | # Requires Vim plugin: https://github.com/tmux-plugins/vim-tmux-focus-events 23 | set -g focus-events on 24 | 25 | # Add a bit more scroll history in the buffer. 26 | set -g history-limit 50000 27 | 28 | # Enable color support inside of tmux. 29 | set -g default-terminal "screen-256color" 30 | 31 | # Ensure window titles get renamed automatically. 32 | setw -g automatic-rename 33 | 34 | # Start windows and panes index at 1, not 0. 35 | set -g base-index 1 36 | setw -g pane-base-index 1 37 | 38 | # Enable full mouse support. 39 | set -g mouse on 40 | 41 | # Status bar optimized for Gruvbox. 42 | set -g status-fg colour244 43 | set -g status-bg default 44 | set -g status-left '' 45 | set -g status-right-length 0 46 | #set -g status-right-length 20 47 | #set -g status-right '%a %Y-%m-%d %H:%M' 48 | 49 | set -g pane-border-fg default 50 | set -g pane-border-bg default 51 | set -g pane-active-border-fg colour250 52 | set -g pane-active-border-bg default 53 | 54 | set-window-option -g window-status-current-attr bold 55 | set-window-option -g window-status-current-fg colour223 56 | 57 | # ----------------------------------------------------------------------------- 58 | # Key bindings 59 | # ----------------------------------------------------------------------------- 60 | 61 | # Unbind default keys 62 | unbind C-b 63 | unbind '"' 64 | unbind % 65 | 66 | # Reload the tmux config. 67 | bind-key r source-file ~/.tmux.conf 68 | 69 | # Split panes. 70 | bind-key h split-window -v 71 | bind-key v split-window -h 72 | 73 | # Move around panes with ALT + arrow keys. 74 | bind-key -n M-Up select-pane -U 75 | bind-key -n M-Left select-pane -L 76 | bind-key -n M-Down select-pane -D 77 | bind-key -n M-Right select-pane -R 78 | 79 | # ----------------------------------------------------------------------------- 80 | # Plugin Manager - https://github.com/tmux-plugins/tpm 81 | # In order to use the plugins below you need to install TPM and the plugins. 82 | # Step 1) git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm 83 | # Step 2) Reload tmux if it's already started with `r 84 | # Step 3) Launch tmux and hit `I (capital i) to fetch any plugins 85 | # ----------------------------------------------------------------------------- 86 | 87 | # List of plugins. 88 | set -g @plugin 'tmux-plugins/tpm' 89 | set -g @plugin 'tmux-plugins/tmux-resurrect' 90 | set -g @plugin 'tmux-plugins/tmux-yank' 91 | 92 | # Initialize TPM (keep this line at the very bottom of your tmux.conf). 93 | run -b '~/.tmux/plugins/tpm/tpm' 94 | -------------------------------------------------------------------------------- /src/cmd_/cmd_swap_window.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::compat::queue::{tailq_insert_tail, tailq_remove}; 16 | use crate::*; 17 | 18 | pub static CMD_SWAP_WINDOW_ENTRY: cmd_entry = cmd_entry { 19 | name: "swap-window", 20 | alias: Some("swapw"), 21 | 22 | args: args_parse::new("ds:t:", 0, 0, None), 23 | usage: "[-d] [-s src-window] [-t dst-window]", 24 | 25 | source: cmd_entry_flag::new( 26 | b's', 27 | cmd_find_type::CMD_FIND_WINDOW, 28 | cmd_find_flags::CMD_FIND_DEFAULT_MARKED, 29 | ), 30 | target: cmd_entry_flag::new( 31 | b't', 32 | cmd_find_type::CMD_FIND_WINDOW, 33 | cmd_find_flags::empty(), 34 | ), 35 | 36 | flags: cmd_flag::empty(), 37 | exec: cmd_swap_window_exec, 38 | }; 39 | 40 | unsafe fn cmd_swap_window_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 41 | unsafe { 42 | let args = cmd_get_args(self_); 43 | let source = cmdq_get_source(item); 44 | let target = cmdq_get_target(item); 45 | let src = (*source).s; 46 | let dst = (*target).s; 47 | let wl_src = (*source).wl; 48 | let wl_dst = (*target).wl; 49 | 50 | let sg_src = session_group_contains(src); 51 | let sg_dst = session_group_contains(dst); 52 | 53 | if src != dst && !sg_src.is_null() && !sg_dst.is_null() && sg_src == sg_dst { 54 | cmdq_error!(item, "can't move window, sessions are grouped"); 55 | return cmd_retval::CMD_RETURN_ERROR; 56 | } 57 | 58 | if (*wl_dst).window == (*wl_src).window { 59 | return cmd_retval::CMD_RETURN_NORMAL; 60 | } 61 | 62 | let w_dst = (*wl_dst).window; 63 | tailq_remove::<_, discr_wentry>(&raw mut (*w_dst).winlinks, wl_dst); 64 | let w_src = (*wl_src).window; 65 | tailq_remove::<_, discr_wentry>(&raw mut (*w_src).winlinks, wl_src); 66 | 67 | (*wl_dst).window = w_src; 68 | tailq_insert_tail::<_, discr_wentry>(&raw mut (*w_src).winlinks, wl_dst); 69 | (*wl_src).window = w_dst; 70 | tailq_insert_tail::<_, discr_wentry>(&raw mut (*w_dst).winlinks, wl_src); 71 | 72 | if args_has(args, 'd') { 73 | session_select(dst, (*wl_dst).idx); 74 | if src != dst { 75 | session_select(src, (*wl_src).idx); 76 | } 77 | } 78 | session_group_synchronize_from(src); 79 | server_redraw_session_group(src); 80 | if src != dst { 81 | session_group_synchronize_from(dst); 82 | server_redraw_session_group(dst); 83 | } 84 | recalculate_sizes(); 85 | 86 | cmd_retval::CMD_RETURN_NORMAL 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/cmd_/cmd_bind_key.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::*; 16 | 17 | pub static CMD_BIND_KEY_ENTRY: cmd_entry = cmd_entry { 18 | name: "bind-key", 19 | alias: Some("bind"), 20 | 21 | args: args_parse::new("nrN:T:", 1, -1, Some(cmd_bind_key_args_parse)), 22 | usage: "[-nr] [-T key-table] [-N note] key [command [arguments]]", 23 | 24 | flags: cmd_flag::CMD_AFTERHOOK, 25 | exec: cmd_bind_key_exec, 26 | source: cmd_entry_flag::zeroed(), 27 | target: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | fn cmd_bind_key_args_parse(_args: *mut args, _idx: u32, _cause: *mut *mut u8) -> args_parse_type { 31 | args_parse_type::ARGS_PARSE_COMMANDS_OR_STRING 32 | } 33 | 34 | unsafe fn cmd_bind_key_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 35 | unsafe { 36 | let args: *mut args = cmd_get_args(self_); 37 | let note = args_get(args, b'N'); 38 | 39 | let count: u32 = args_count(args); 40 | 41 | let key: key_code = key_string_lookup_string(args_string(args, 0)); 42 | if key == KEYC_NONE || key == KEYC_UNKNOWN { 43 | cmdq_error!(item, "unknown key bind: {}", _s(args_string(args, 0))); 44 | return cmd_retval::CMD_RETURN_ERROR; 45 | } 46 | 47 | let tablename: *const u8 = if args_has(args, 'T') { 48 | args_get(args, b'T') 49 | } else if args_has(args, 'n') { 50 | c!("root") 51 | } else { 52 | c!("prefix") 53 | }; 54 | let repeat = args_has(args, 'r'); 55 | 56 | if count == 1 { 57 | key_bindings_add(tablename, key, note, repeat, null_mut()); 58 | return cmd_retval::CMD_RETURN_NORMAL; 59 | } 60 | 61 | let value = args_value(args, 1); 62 | if count == 2 && (*value).type_ == args_type::ARGS_COMMANDS { 63 | key_bindings_add(tablename, key, note, repeat, (*value).union_.cmdlist); 64 | (*(*value).union_.cmdlist).references += 1; 65 | return cmd_retval::CMD_RETURN_NORMAL; 66 | } 67 | 68 | let pr = if count == 2 { 69 | cmd_parse_from_string(cstr_to_str(args_string(args, 1)), None) 70 | } else { 71 | cmd_parse_from_arguments(args_values(args).add(1), count - 1, None) 72 | }; 73 | 74 | match pr { 75 | Err(error) => { 76 | cmdq_error!(item, "{}", _s(error)); 77 | free_(error); 78 | cmd_retval::CMD_RETURN_ERROR 79 | } 80 | Ok(cmdlist) => { 81 | key_bindings_add(tablename, key, note, repeat, cmdlist); 82 | cmd_retval::CMD_RETURN_NORMAL 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/cmd_/cmd_list_clients.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::queue::tailq_foreach; 15 | use crate::*; 16 | 17 | const LIST_CLIENTS_TEMPLATE: *const u8 = c!( 18 | "#{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,),}" 19 | ); 20 | 21 | pub static CMD_LIST_CLIENTS_ENTRY: cmd_entry = cmd_entry { 22 | name: "list-clients", 23 | alias: Some("lsc"), 24 | 25 | args: args_parse::new("F:f:t:", 0, 0, None), 26 | usage: "[-F format] [-f filter] [-t target-session]", 27 | 28 | target: cmd_entry_flag::new( 29 | b't', 30 | cmd_find_type::CMD_FIND_SESSION, 31 | cmd_find_flags::empty(), 32 | ), 33 | 34 | flags: cmd_flag::CMD_READONLY.union(cmd_flag::CMD_AFTERHOOK), 35 | exec: cmd_list_clients_exec, 36 | source: cmd_entry_flag::zeroed(), 37 | }; 38 | 39 | unsafe fn cmd_list_clients_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 40 | unsafe { 41 | let args = cmd_get_args(self_); 42 | let target = cmdq_get_target(item); 43 | 44 | let s = if args_has(args, 't') { 45 | (*target).s 46 | } else { 47 | null_mut() 48 | }; 49 | 50 | let mut template = args_get(args, b'F'); 51 | if template.is_null() { 52 | template = LIST_CLIENTS_TEMPLATE; 53 | } 54 | let filter = args_get(args, b'f'); 55 | 56 | let mut idx = 0; 57 | for c in tailq_foreach(&raw mut CLIENTS).map(NonNull::as_ptr) { 58 | if (*c).session.is_null() || (!s.is_null() && s != (*c).session) { 59 | continue; 60 | } 61 | 62 | let ft = format_create( 63 | cmdq_get_client(item), 64 | item, 65 | FORMAT_NONE, 66 | format_flags::empty(), 67 | ); 68 | format_add!(ft, "line", "{idx}"); 69 | format_defaults(ft, c, None, None, None); 70 | 71 | let flag; 72 | if !filter.is_null() { 73 | let expanded = format_expand(ft, filter); 74 | flag = format_true(expanded); 75 | free_(expanded); 76 | } else { 77 | flag = true; 78 | } 79 | if flag { 80 | let line = format_expand(ft, template); 81 | cmdq_print!(item, "{}", _s(line)); 82 | free_(line); 83 | } 84 | 85 | format_free(ft); 86 | 87 | idx += 1; 88 | } 89 | 90 | cmd_retval::CMD_RETURN_NORMAL 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /regress/style-trim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | shell= 7 | if command -v bash >/dev/null 2>&1; then 8 | # If Bash is available, we start a plain Bash session (without any user 9 | # configuration files) for testing. 10 | # 11 | # Note: We disable the command history by passing "+o history". If an 12 | # interactive Bash session is started without any configuration files, 13 | # the user's command history may be truncated to the default maximum 14 | # size of 500. To avoid breaking the user's command history, we disable 15 | # the command history. 16 | shell='bash --noprofile --norc +o history' 17 | fi 18 | 19 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 20 | TMUX="$TEST_TMUX -Ltest" 21 | $TMUX kill-server 2>/dev/null 22 | TMUX2="$TEST_TMUX -Ltest2" 23 | $TMUX2 kill-server 2>/dev/null 24 | 25 | $TMUX2 -f/dev/null new -d "$TMUX -f/dev/null new -- $shell" 26 | sleep 2 27 | $TMUX set -g status-style fg=default,bg=default 28 | 29 | check() { 30 | v=$($TMUX display -p "$1") 31 | $TMUX set -g status-format[0] "$1" 32 | sleep 1 33 | r=$($TMUX2 capturep -Cep|tail -1|sed 's|\\033\[||g') 34 | 35 | if [ "$v" != "$2" -o "$r" != "$3" ]; then 36 | printf "$1 = [$v = $2] [$r = $3]" 37 | printf " \033[31mbad\033[0m\n" 38 | exit 1 39 | fi 40 | } 41 | 42 | # drawn as #0 43 | $TMUX setenv -g V '#0' 44 | check '#{V} #{w:V}' '#0 2' '#0 2' 45 | check '#{=3:V}' '#0' '#0' 46 | check '#{=-3:V}' '#0' '#0' 47 | 48 | # drawn as #0 49 | $TMUX setenv -g V '###[bg=yellow]0' 50 | check '#{V} #{w:V}' '###[bg=yellow]0 2' '#43m0 249m' 51 | check '#{=3:V}' '###[bg=yellow]0' '#43m049m' 52 | check '#{=-3:V}' '###[bg=yellow]0' '#43m049m' 53 | 54 | # drawn as #0123456 55 | $TMUX setenv -g V '#0123456' 56 | check '#{V} #{w:V}' '#0123456 8' '#0123456 8' 57 | check '#{=3:V}' '#01' '#01' 58 | check '#{=-3:V}' '456' '456' 59 | 60 | # drawn as #0123456 61 | $TMUX setenv -g V '##0123456' 62 | check '#{V} #{w:V}' '##0123456 8' '#0123456 8' 63 | check '#{=3:V}' '##01' '#01' 64 | check '#{=-3:V}' '456' '456' 65 | 66 | # drawn as ##0123456 67 | $TMUX setenv -g V '###0123456' 68 | check '#{V} #{w:V}' '###0123456 9' '##0123456 9' 69 | check '#{=3:V}' '####0' '##0' 70 | check '#{=-3:V}' '456' '456' 71 | 72 | # drawn as 0123456 73 | $TMUX setenv -g V '#[bg=yellow]0123456' 74 | check '#{V} #{w:V}' '#[bg=yellow]0123456 7' '43m0123456 749m' 75 | check '#{=3:V}' '#[bg=yellow]012' '43m01249m' 76 | check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' 77 | 78 | # drawn as #[bg=yellow]0123456 79 | $TMUX setenv -g V '##[bg=yellow]0123456' 80 | check '#{V} #{w:V}' '##[bg=yellow]0123456 19' '#[bg=yellow]0123456 19' 81 | check '#{=3:V}' '##[b' '#[b' 82 | check '#{=-3:V}' '456' '456' 83 | 84 | # drawn as #0123456 85 | $TMUX setenv -g V '###[bg=yellow]0123456' 86 | check '#{V} #{w:V}' '###[bg=yellow]0123456 8' '#43m0123456 849m' 87 | check '#{=3:V}' '###[bg=yellow]01' '#43m0149m' 88 | check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' 89 | 90 | # drawn as ##[bg=yellow]0123456 91 | $TMUX setenv -g V '####[bg=yellow]0123456' 92 | check '#{V} #{w:V}' '####[bg=yellow]0123456 20' '##[bg=yellow]0123456 20' 93 | check '#{=3:V}' '####[' '##[' 94 | check '#{=-3:V}' '456' '456' 95 | 96 | # drawn as ###0123456 97 | $TMUX setenv -g V '#####[bg=yellow]0123456' 98 | check '#{V} #{w:V}' '#####[bg=yellow]0123456 9' '##43m0123456 949m' 99 | check '#{=3:V}' '#####[bg=yellow]0' '##43m049m' 100 | check '#{=-3:V}' '#[bg=yellow]456' '43m45649m' 101 | 102 | $TMUX kill-server 2>/dev/null 103 | $TMUX2 kill-server 2>/dev/null 104 | exit 0 105 | -------------------------------------------------------------------------------- /src/cmd_/cmd_unbind_key.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::*; 16 | 17 | pub static CMD_UNBIND_KEY_ENTRY: cmd_entry = cmd_entry { 18 | name: "unbind-key", 19 | alias: Some("unbind"), 20 | 21 | args: args_parse::new("anqT:", 0, 1, None), 22 | usage: "[-anq] [-T key-table] key", 23 | 24 | flags: cmd_flag::CMD_AFTERHOOK, 25 | exec: cmd_unbind_key_exec, 26 | source: cmd_entry_flag::zeroed(), 27 | target: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | unsafe fn cmd_unbind_key_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 31 | unsafe { 32 | let args = cmd_get_args(self_); 33 | let mut tablename: *const u8; 34 | let keystr = args_string(args, 0); 35 | let quiet = args_has(args, 'q'); 36 | 37 | if args_has(args, 'a') { 38 | if !keystr.is_null() { 39 | if !quiet { 40 | cmdq_error!(item, "key given with -a"); 41 | } 42 | return cmd_retval::CMD_RETURN_ERROR; 43 | } 44 | 45 | tablename = args_get(args, b'T'); 46 | if tablename.is_null() { 47 | if args_has(args, 'n') { 48 | tablename = c!("root"); 49 | } else { 50 | tablename = c!("prefix"); 51 | } 52 | } 53 | if key_bindings_get_table(tablename, false).is_null() { 54 | if !quiet { 55 | cmdq_error!(item, "table {} doesn't exist", _s(tablename)); 56 | } 57 | return cmd_retval::CMD_RETURN_ERROR; 58 | } 59 | 60 | key_bindings_remove_table(tablename); 61 | return cmd_retval::CMD_RETURN_NORMAL; 62 | } 63 | 64 | if keystr.is_null() { 65 | if !quiet { 66 | cmdq_error!(item, "missing key"); 67 | } 68 | return cmd_retval::CMD_RETURN_ERROR; 69 | } 70 | 71 | let key = key_string_lookup_string(keystr); 72 | if key == KEYC_NONE || key == KEYC_UNKNOWN { 73 | if !quiet { 74 | cmdq_error!(item, "unknown key unbind: {}", _s(keystr)); 75 | } 76 | return cmd_retval::CMD_RETURN_ERROR; 77 | } 78 | 79 | if args_has(args, 'T') { 80 | tablename = args_get(args, b'T'); 81 | if key_bindings_get_table(tablename, false).is_null() { 82 | if !quiet { 83 | cmdq_error!(item, "table {} doesn't exist", _s(tablename)); 84 | } 85 | return cmd_retval::CMD_RETURN_ERROR; 86 | } 87 | } else if args_has(args, 'n') { 88 | tablename = c!("root"); 89 | } else { 90 | tablename = c!("prefix"); 91 | } 92 | key_bindings_remove(tablename, key); 93 | cmd_retval::CMD_RETURN_NORMAL 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/cmd_/cmd_copy_mode.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use super::*; 16 | 17 | pub static CMD_COPY_MODE_ENTRY: cmd_entry = cmd_entry { 18 | name: "copy-mode", 19 | alias: None, 20 | 21 | args: args_parse::new("deHMs:t:uq", 0, 0, None), 22 | usage: "[-deHMuq] [-s src-pane] [-t target-pane]", 23 | 24 | source: cmd_entry_flag::new(b's', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 25 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 26 | 27 | flags: cmd_flag::CMD_AFTERHOOK, 28 | exec: cmd_copy_mode_exec, 29 | }; 30 | 31 | pub static CMD_CLOCK_MODE_ENTRY: cmd_entry = cmd_entry { 32 | name: "clock-mode", 33 | alias: None, 34 | 35 | args: args_parse::new("t:", 0, 0, None), 36 | usage: "[-t target-pane]", 37 | 38 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 39 | source: cmd_entry_flag::zeroed(), 40 | 41 | flags: cmd_flag::CMD_AFTERHOOK, 42 | exec: cmd_copy_mode_exec, 43 | }; 44 | 45 | unsafe fn cmd_copy_mode_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 46 | unsafe { 47 | let args = cmd_get_args(self_); 48 | let event = cmdq_get_event(item); 49 | let source = cmdq_get_source(item); 50 | let target = cmdq_get_target(item); 51 | let c = cmdq_get_client(item); 52 | let mut s = null_mut(); 53 | let wp = (*target).wp; 54 | 55 | if args_has(args, 'q') { 56 | window_pane_reset_mode_all(wp); 57 | return cmd_retval::CMD_RETURN_NORMAL; 58 | } 59 | 60 | if args_has(args, 'M') { 61 | let wp = cmd_mouse_pane(&raw mut (*event).m, &raw mut s, null_mut()); 62 | if wp.is_none() { 63 | return cmd_retval::CMD_RETURN_NORMAL; 64 | } 65 | if c.is_null() || (*c).session != s { 66 | return cmd_retval::CMD_RETURN_NORMAL; 67 | } 68 | } 69 | 70 | if std::ptr::eq(cmd_get_entry(self_), &CMD_CLOCK_MODE_ENTRY) { 71 | window_pane_set_mode( 72 | wp, 73 | null_mut(), 74 | &raw const WINDOW_CLOCK_MODE, 75 | null_mut(), 76 | null_mut(), 77 | ); 78 | return cmd_retval::CMD_RETURN_NORMAL; 79 | } 80 | 81 | let swp = if args_has(args, 's') { 82 | (*source).wp 83 | } else { 84 | wp 85 | }; 86 | if window_pane_set_mode(wp, swp, &raw const WINDOW_COPY_MODE, null_mut(), args) == 0 87 | && args_has(args, 'M') 88 | { 89 | window_copy_start_drag(c, &raw mut (*event).m); 90 | } 91 | if args_has(args, 'u') { 92 | window_copy_pageup(wp, 0); 93 | } 94 | if args_has(args, 'd') { 95 | window_copy_pagedown(wp, 0, args_has(args, 'e')); 96 | } 97 | 98 | cmd_retval::CMD_RETURN_NORMAL 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /regress/conf/58304907c117cab9898ea0b070bccde3.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Tureba's tmux.conf 3 | # 4 | # To use it, either: 5 | # a) link ~/.tmux.conf to it; or 6 | # b) create a ~/.tmux.conf that sources it. 7 | # 8 | # who: Arthur Nascimento 9 | # where: github.com/tureba/myconfigfiles 10 | # 11 | 12 | # defaults 13 | set -g default-shell /bin/zsh 14 | set -g default-command zsh 15 | # tmux sets screen/screen-256, but has no codes for italics 16 | set -g default-terminal tmux-256color 17 | # linux terminal doesn't need this, but xterm does 18 | set -g terminal-overrides 'xterm*:smcup@:rmcup@,*256col*:colors=256,xterm*:XT' 19 | # xterm-style function key sequences 20 | setw -g xterm-keys on 21 | 22 | # 1, 2 and 3 are closer together than 0, 1 and 2 23 | set -g base-index 1 24 | set -g pane-base-index 1 25 | 26 | # easier to type than C-b 27 | set -g prefix C-a 28 | set -g prefix2 C-b 29 | unbind C-b 30 | bind C-a send-prefix 31 | 32 | # for repeatable keys 33 | set -g repeat-time 170 34 | 35 | # status bar 36 | set -g status-style fg=green,bg=colour234 37 | set -g status-right-style bg=colour236 38 | set -g status-right "#[bold,fg=blue][#[fg=default]#T#[fg=blue]]#[nobold,fg=default] | #[fg=yellow]%F %R" 39 | set -g status-right-length 120 40 | set -g status-left-style bg=colour236,bright 41 | set -g status-left "#[fg=blue][#[fg=default]#h#[fg=cyan]:#[fg=default]#S#[fg=blue]]" 42 | set -g status-left-length 30 43 | setw -g window-status-style fg=green 44 | setw -g window-status-format " #I#[nobold]:#W " 45 | setw -g window-status-current-style fg=green,bright 46 | setw -g window-status-current-format "#[fg=red][#[fg=default]#I:#W#[fg=red]]" 47 | setw -g window-status-separator "|" 48 | setw -g window-status-activity-style blink 49 | setw -g window-status-bell-style blink 50 | setw -g window-status-last-style bright 51 | 52 | # enable wm window titles 53 | set -g set-titles on 54 | 55 | # auto window rename 56 | setw -g automatic-rename on 57 | # auto window resize 58 | setw -g aggressive-resize on 59 | 60 | # mouse settings 61 | set -g mouse on 62 | 63 | # var|bind \ cmd | vim | less | copy | zsh 64 | # pane_in_mode | 0 | 0 | 1 | 0 65 | # mouse_any_flag | 1 | 0 | 0 | 0 66 | # alternate_on | 1 | 1 | 0 | 0 67 | # WheelUpPane | send -M | send Up | * | send Up (** or copy-mode -e) 68 | # WheelDownPane | send -M | send Down | * | send Down 69 | # * panes in copy mode have scroll handled by different bindings 70 | 71 | # ** cycle over shell history 72 | #bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Up' 73 | 74 | # ** enter copy mode 75 | bind -T root WheelUpPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'if -Ft= "#{alternate_on}" "send -t= Up" "copy-mode -et="' 76 | 77 | bind -T root WheelDownPane if -Ft= '#{mouse_any_flag}' 'send -Mt=' 'send -t= Down' 78 | 79 | # sensible v/h splits 80 | unbind % 81 | unbind '"' 82 | bind | split-window -h 83 | bind - split-window -v 84 | 85 | # hjkl pane traversal 86 | bind -r h select-pane -L 87 | bind -r j select-pane -D 88 | bind -r k select-pane -U 89 | bind -r l select-pane -R 90 | 91 | # window navigation 92 | unbind p 93 | bind -r [ previous-window 94 | unbind n 95 | bind -r ] next-window 96 | 97 | # Vi copypaste mode 98 | setw -g mode-keys vi 99 | bind C-c copy-mode 100 | bind p paste-buffer 101 | bind -T copy-mode-vi v send-keys -X begin-selection 102 | bind -T copy-mode-vi y send-keys -X copy-selection 103 | bind -T copy-mode-vi V send-keys -X rectangle-toggle 104 | 105 | # toggle window activity monitoring 106 | bind m setw monitor-activity 107 | 108 | # reload the configuration 109 | bind r source-file ~/.tmux.conf 110 | 111 | # toggle synchronize-panes 112 | bind S setw synchronize-panes 113 | 114 | # create a new window with exactly this command 115 | bind C command-prompt "new-window 'exec %%'" 116 | 117 | # (toggle) mark this pane for easier joins and swaps 118 | bind . select-pane -m 119 | -------------------------------------------------------------------------------- /src/cmd_/cmd_show_messages.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::queue::{list_foreach, tailq_foreach_reverse}; 15 | use crate::*; 16 | 17 | const SHOW_MESSAGES_TEMPLATE: *const u8 = c!("#{t/p:message_time}: #{message_text}"); 18 | 19 | pub static CMD_SHOW_MESSAGES_ENTRY: cmd_entry = cmd_entry { 20 | name: "show-messages", 21 | alias: Some("showmsgs"), 22 | 23 | args: args_parse::new("JTt:", 0, 0, None), 24 | usage: "[-JT] [-t target-client]", 25 | 26 | flags: cmd_flag::CMD_AFTERHOOK.union(cmd_flag::CMD_CLIENT_TFLAG), 27 | exec: cmd_show_messages_exec, 28 | source: cmd_entry_flag::zeroed(), 29 | target: cmd_entry_flag::zeroed(), 30 | }; 31 | 32 | unsafe fn cmd_show_messages_terminals( 33 | self_: *mut cmd, 34 | item: *mut cmdq_item, 35 | mut blank: i32, 36 | ) -> c_int { 37 | unsafe { 38 | let args = cmd_get_args(self_); 39 | let tc = cmdq_get_target_client(item); 40 | 41 | let mut n = 0u32; 42 | for term in list_foreach::<_, discr_entry>(&raw mut TTY_TERMS).map(NonNull::as_ptr) { 43 | if args_has(args, 't') && term != (*tc).tty.term { 44 | continue; 45 | } 46 | if blank != 0 { 47 | cmdq_print!(item, ""); 48 | blank = 0; 49 | } 50 | cmdq_print!( 51 | item, 52 | "Terminal {}: {} for {}, flags=0x{:x}:", 53 | n, 54 | _s((*term).name), 55 | _s((*(*(*term).tty).client).name), 56 | (*term).flags, 57 | ); 58 | n += 1; 59 | for i in 0..tty_term_ncodes() { 60 | cmdq_print!( 61 | item, 62 | "{}", 63 | _s(tty_term_describe(term, tty_code_code::try_from(i).unwrap())), 64 | ); 65 | } 66 | } 67 | (n != 0) as i32 68 | } 69 | } 70 | 71 | unsafe fn cmd_show_messages_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 72 | unsafe { 73 | let args = cmd_get_args(self_); 74 | 75 | let mut done = false; 76 | let mut blank = 0; 77 | if args_has(args, 'T') { 78 | blank = cmd_show_messages_terminals(self_, item, blank); 79 | done = true; 80 | } 81 | if args_has(args, 'J') { 82 | job_print_summary(item, blank); 83 | done = true; 84 | } 85 | if done { 86 | return cmd_retval::CMD_RETURN_NORMAL; 87 | } 88 | 89 | let ft = format_create_from_target(item); 90 | 91 | for msg in tailq_foreach_reverse(&raw mut crate::server::MESSAGE_LOG).map(NonNull::as_ptr) { 92 | format_add!(ft, "message_text", "{}", _s((*msg).msg)); 93 | format_add!(ft, "message_number", "{}", (*msg).msg_num,); 94 | format_add_tv(ft, c!("message_time"), &raw mut (*msg).msg_time); 95 | 96 | let s = format_expand(ft, SHOW_MESSAGES_TEMPLATE); 97 | cmdq_print!(item, "{}", _s(s)); 98 | free_(s); 99 | } 100 | format_free(ft); 101 | 102 | cmd_retval::CMD_RETURN_NORMAL 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/cmd_/cmd_detach_client.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::queue::tailq_foreach; 15 | use crate::*; 16 | 17 | pub static CMD_DETACH_CLIENT_ENTRY: cmd_entry = cmd_entry { 18 | name: "detach-client", 19 | alias: Some("detach"), 20 | 21 | args: args_parse::new("aE:s:t:P", 0, 0, None), 22 | usage: "[-aP] [-E shell-command] [-s target-session] [-t target-client]", 23 | 24 | source: cmd_entry_flag::new( 25 | b's', 26 | cmd_find_type::CMD_FIND_SESSION, 27 | cmd_find_flags::CMD_FIND_CANFAIL, 28 | ), 29 | 30 | flags: cmd_flag::CMD_READONLY.union(cmd_flag::CMD_CLIENT_TFLAG), 31 | exec: cmd_detach_client_exec, 32 | target: cmd_entry_flag::zeroed(), 33 | }; 34 | 35 | pub static CMD_SUSPEND_CLIENT_ENTRY: cmd_entry = cmd_entry { 36 | name: "suspend-client", 37 | alias: Some("suspendc"), 38 | 39 | args: args_parse::new("t:", 0, 0, None), 40 | usage: "[-t target-client]", 41 | 42 | flags: cmd_flag::CMD_CLIENT_TFLAG, 43 | exec: cmd_detach_client_exec, 44 | source: cmd_entry_flag::zeroed(), 45 | target: cmd_entry_flag::zeroed(), 46 | }; 47 | 48 | pub unsafe fn cmd_detach_client_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 49 | unsafe { 50 | let args = cmd_get_args(self_); 51 | let source = cmdq_get_source(item); 52 | let tc = cmdq_get_target_client(item); 53 | let cmd = args_get(args, b'E'); 54 | 55 | if std::ptr::eq(cmd_get_entry(self_), &CMD_SUSPEND_CLIENT_ENTRY) { 56 | server_client_suspend(tc); 57 | return cmd_retval::CMD_RETURN_NORMAL; 58 | } 59 | 60 | let msgtype = if args_has(args, 'P') { 61 | msgtype::MSG_DETACHKILL 62 | } else { 63 | msgtype::MSG_DETACH 64 | }; 65 | 66 | if args_has(args, 's') { 67 | let s = (*source).s; 68 | if s.is_null() { 69 | return cmd_retval::CMD_RETURN_NORMAL; 70 | } 71 | for loop_ in tailq_foreach(&raw mut CLIENTS).map(NonNull::as_ptr) { 72 | if (*loop_).session == s { 73 | if !cmd.is_null() { 74 | server_client_exec(loop_, cmd); 75 | } else { 76 | server_client_detach(loop_, msgtype); 77 | } 78 | } 79 | } 80 | return cmd_retval::CMD_RETURN_STOP; 81 | } 82 | 83 | if args_has(args, 'a') { 84 | for loop_ in tailq_foreach(&raw mut CLIENTS).map(NonNull::as_ptr) { 85 | if !(*loop_).session.is_null() && loop_ != tc { 86 | if !cmd.is_null() { 87 | server_client_exec(loop_, cmd); 88 | } else { 89 | server_client_detach(loop_, msgtype); 90 | } 91 | } 92 | } 93 | return cmd_retval::CMD_RETURN_NORMAL; 94 | } 95 | 96 | if !cmd.is_null() { 97 | server_client_exec(tc, cmd); 98 | } else { 99 | server_client_detach(tc, msgtype); 100 | } 101 | cmd_retval::CMD_RETURN_STOP 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/cmd_/cmd_load_buffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Tiago Cunha 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_LOAD_BUFFER_ENTRY: cmd_entry = cmd_entry { 17 | name: "load-buffer", 18 | alias: Some("loadb"), 19 | 20 | args: args_parse::new("b:t:w", 1, 1, None), 21 | usage: "[-b buffer-name] [-t target-client] path", 22 | 23 | flags: cmd_flag::CMD_AFTERHOOK 24 | .union(cmd_flag::CMD_CLIENT_TFLAG) 25 | .union(cmd_flag::CMD_CLIENT_CANFAIL), 26 | exec: cmd_load_buffer_exec, 27 | source: cmd_entry_flag::zeroed(), 28 | target: cmd_entry_flag::zeroed(), 29 | }; 30 | 31 | #[repr(C)] 32 | pub struct cmd_load_buffer_data { 33 | pub client: *mut client, 34 | pub item: *mut cmdq_item, 35 | pub name: *mut u8, 36 | } 37 | 38 | unsafe fn cmd_load_buffer_done( 39 | _c: *mut client, 40 | path: *mut u8, 41 | error: i32, 42 | closed: i32, 43 | buffer: *mut evbuffer, 44 | data: *mut c_void, 45 | ) { 46 | unsafe { 47 | let cdata = data as *mut cmd_load_buffer_data; 48 | let tc = (*cdata).client; 49 | let item = (*cdata).item; 50 | let bdata = EVBUFFER_DATA(buffer); 51 | let bsize = EVBUFFER_LENGTH(buffer); 52 | 53 | if closed == 0 { 54 | return; 55 | } 56 | 57 | if error != 0 { 58 | cmdq_error!(item, "{}: {}", _s(path), strerror(error)); 59 | } else if bsize != 0 { 60 | let copy = xmalloc(bsize).as_ptr(); 61 | memcpy_(copy, bdata as _, bsize); 62 | let mut cause = null_mut(); 63 | if paste_set( 64 | copy as _, 65 | bsize, 66 | cstr_to_str_((*cdata).name), 67 | &raw mut cause, 68 | ) != 0 69 | { 70 | cmdq_error!(item, "{}", _s(cause)); 71 | free_(cause); 72 | free_(copy); 73 | } else if !tc.is_null() 74 | && !(*tc).session.is_null() 75 | && !(*tc).flags.intersects(client_flag::DEAD) 76 | { 77 | tty_set_selection(&raw mut (*tc).tty, c!(""), copy as _, bsize); 78 | } 79 | if !tc.is_null() { 80 | server_client_unref(tc); 81 | } 82 | } 83 | cmdq_continue(item); 84 | 85 | free_((*cdata).name); 86 | free_(cdata); 87 | } 88 | } 89 | 90 | unsafe fn cmd_load_buffer_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 91 | unsafe { 92 | let args = cmd_get_args(self_); 93 | let tc = cmdq_get_target_client(item); 94 | let bufname = args_get(args, b'b'); 95 | 96 | let cdata = xcalloc_::(1).as_ptr(); 97 | (*cdata).item = item; 98 | if !bufname.is_null() { 99 | (*cdata).name = xstrdup(bufname).as_ptr(); 100 | } 101 | if args_has(args, 'w') && !tc.is_null() { 102 | (*cdata).client = tc; 103 | (*(*cdata).client).references += 1; 104 | } 105 | 106 | let path = format_single_from_target(item, args_string(args, 0)); 107 | file_read( 108 | cmdq_get_client(item), 109 | path, 110 | Some(cmd_load_buffer_done), 111 | cdata.cast(), 112 | ); 113 | free_(path); 114 | } 115 | 116 | cmd_retval::CMD_RETURN_WAIT 117 | } 118 | -------------------------------------------------------------------------------- /src/cmd_/cmd_kill_window.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::compat::tree::{rb_foreach, rb_next, rb_prev}; 16 | use crate::*; 17 | 18 | pub static CMD_KILL_WINDOW_ENTRY: cmd_entry = cmd_entry { 19 | name: "kill-window", 20 | alias: Some("killw"), 21 | 22 | args: args_parse::new("at:", 0, 0, None), 23 | usage: "[-a] [-t target-window]", 24 | 25 | target: cmd_entry_flag::new( 26 | b't', 27 | cmd_find_type::CMD_FIND_WINDOW, 28 | cmd_find_flags::empty(), 29 | ), 30 | 31 | flags: cmd_flag::empty(), 32 | exec: cmd_kill_window_exec, 33 | source: cmd_entry_flag::zeroed(), 34 | }; 35 | 36 | pub static CMD_UNLINK_WINDOW_ENTRY: cmd_entry = cmd_entry { 37 | name: "unlink-window", 38 | alias: Some("unlinkw"), 39 | 40 | args: args_parse::new("kt:", 0, 0, None), 41 | usage: "[-k] [-t target-window]", 42 | 43 | target: cmd_entry_flag::new( 44 | b't', 45 | cmd_find_type::CMD_FIND_WINDOW, 46 | cmd_find_flags::empty(), 47 | ), 48 | 49 | flags: cmd_flag::empty(), 50 | exec: cmd_kill_window_exec, 51 | source: cmd_entry_flag::zeroed(), 52 | }; 53 | 54 | unsafe fn cmd_kill_window_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 55 | unsafe { 56 | let args = cmd_get_args(self_); 57 | let target = cmdq_get_target(item); 58 | let wl = (*target).wl; 59 | //*loop; 60 | let w = (*wl).window; 61 | let s = (*target).s; 62 | let mut found; 63 | 64 | if std::ptr::eq(cmd_get_entry(self_), &CMD_UNLINK_WINDOW_ENTRY) { 65 | if !args_has(args, 'k') && !session_is_linked(s, w) { 66 | cmdq_error!(item, "window only linked to one session"); 67 | return cmd_retval::CMD_RETURN_ERROR; 68 | } 69 | server_unlink_window(s, wl); 70 | recalculate_sizes(); 71 | return cmd_retval::CMD_RETURN_NORMAL; 72 | } 73 | 74 | if args_has(args, 'a') { 75 | if rb_prev(wl).is_null() && rb_next(wl).is_null() { 76 | return cmd_retval::CMD_RETURN_NORMAL; 77 | } 78 | 79 | // Kill all windows except the current one. 80 | loop { 81 | found = 0; 82 | for loop_ in rb_foreach(&raw mut (*s).windows).map(NonNull::as_ptr) { 83 | if (*loop_).window != (*wl).window { 84 | server_kill_window((*loop_).window, 0); 85 | found += 1; 86 | break; 87 | } 88 | } 89 | 90 | if found == 0 { 91 | break; 92 | } 93 | } 94 | 95 | // If the current window appears in the session more than once, 96 | // kill it as well. 97 | found = 0; 98 | for loop_ in rb_foreach(&raw mut (*s).windows).map(NonNull::as_ptr) { 99 | if (*loop_).window == (*wl).window { 100 | found += 1; 101 | } 102 | } 103 | if found > 1 { 104 | { 105 | server_kill_window((*wl).window, 0); 106 | } 107 | } 108 | 109 | server_renumber_all(); 110 | return cmd_retval::CMD_RETURN_NORMAL; 111 | } 112 | 113 | server_kill_window((*wl).window, 1); 114 | cmd_retval::CMD_RETURN_NORMAL 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/cmd_/cmd_choose_tree.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Thomas Adam 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | use crate::*; 16 | 17 | pub static CMD_CHOOSE_TREE_ENTRY: cmd_entry = cmd_entry { 18 | name: "choose-tree", 19 | alias: None, 20 | 21 | args: args_parse::new("F:f:GK:NO:rst:wZ", 0, 1, Some(cmd_choose_tree_args_parse)), 22 | usage: "[-GNrswZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]", 23 | 24 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 25 | source: cmd_entry_flag::zeroed(), 26 | 27 | flags: cmd_flag::empty(), 28 | exec: cmd_choose_tree_exec, 29 | }; 30 | 31 | pub static CMD_CHOOSE_CLIENT_ENTRY: cmd_entry = cmd_entry { 32 | name: "choose-client", 33 | alias: None, 34 | 35 | args: args_parse::new("F:f:K:NO:rt:Z", 0, 1, Some(cmd_choose_tree_args_parse)), 36 | usage: "[-NrZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]", 37 | 38 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 39 | source: cmd_entry_flag::zeroed(), 40 | 41 | flags: cmd_flag::empty(), 42 | exec: cmd_choose_tree_exec, 43 | }; 44 | 45 | pub static CMD_CHOOSE_BUFFER_ENTRY: cmd_entry = cmd_entry { 46 | name: "choose-buffer", 47 | alias: None, 48 | 49 | args: args_parse::new("F:f:K:NO:rt:Z", 0, 1, Some(cmd_choose_tree_args_parse)), 50 | usage: "[-NrZ] [-F format] [-f filter] [-K key-format] [-O sort-order] [-t target-pane] [template]", 51 | 52 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 53 | source: cmd_entry_flag::zeroed(), 54 | 55 | flags: cmd_flag::empty(), 56 | exec: cmd_choose_tree_exec, 57 | }; 58 | 59 | pub static CMD_CUSTOMIZE_MODE_ENTRY: cmd_entry = cmd_entry { 60 | name: "customize-mode", 61 | alias: None, 62 | 63 | args: args_parse::new("F:f:Nt:Z", 0, 0, None), 64 | usage: "[-NZ] [-F format] [-f filter] [-t target-pane]", 65 | 66 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 67 | source: cmd_entry_flag::zeroed(), 68 | 69 | flags: cmd_flag::empty(), 70 | exec: cmd_choose_tree_exec, 71 | }; 72 | 73 | fn cmd_choose_tree_args_parse( 74 | _args: *mut args, 75 | _idx: u32, 76 | _cause: *mut *mut u8, 77 | ) -> args_parse_type { 78 | args_parse_type::ARGS_PARSE_COMMANDS_OR_STRING 79 | } 80 | 81 | unsafe fn cmd_choose_tree_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 82 | unsafe { 83 | let args = cmd_get_args(self_); 84 | let target = cmdq_get_target(item); 85 | let wp = (*target).wp; 86 | 87 | let mode = if std::ptr::eq(cmd_get_entry(self_), &CMD_CHOOSE_BUFFER_ENTRY) { 88 | if paste_is_empty() { 89 | return cmd_retval::CMD_RETURN_NORMAL; 90 | } 91 | &raw const WINDOW_BUFFER_MODE 92 | } else if std::ptr::eq(cmd_get_entry(self_), &CMD_CHOOSE_CLIENT_ENTRY) { 93 | if server_client_how_many() == 0 { 94 | return cmd_retval::CMD_RETURN_NORMAL; 95 | } 96 | &raw const WINDOW_CLIENT_MODE 97 | } else if std::ptr::eq(cmd_get_entry(self_), &CMD_CUSTOMIZE_MODE_ENTRY) { 98 | &raw const WINDOW_CUSTOMIZE_MODE 99 | } else { 100 | &raw const WINDOW_TREE_MODE 101 | }; 102 | 103 | window_pane_set_mode(wp, null_mut(), mode, target, args); 104 | cmd_retval::CMD_RETURN_NORMAL 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/cmd_/cmd_list_windows.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::compat::tree::rb_foreach; 15 | use crate::*; 16 | 17 | const LIST_WINDOWS_TEMPLATE: *const u8 = c!( 18 | "#{window_index}: #{window_name}#{window_raw_flags} (#{window_panes} panes) [#{window_width}x#{window_height}] [layout #{window_layout}] #{window_id}#{?window_active, (active),}" 19 | ); 20 | const LIST_WINDOWS_WITH_SESSION_TEMPLATE: *const u8 = c!( 21 | "#{session_name}:#{window_index}: #{window_name}#{window_raw_flags} (#{window_panes} panes) [#{window_width}x#{window_height}] " 22 | ); 23 | 24 | pub static CMD_LIST_WINDOWS_ENTRY: cmd_entry = cmd_entry { 25 | name: "list-windows", 26 | alias: Some("lsw"), 27 | 28 | args: args_parse::new("F:f:at:", 0, 0, None), 29 | usage: "[-a] [-F format] [-f filter] [-t target-session]", 30 | 31 | target: cmd_entry_flag::new( 32 | b't', 33 | cmd_find_type::CMD_FIND_SESSION, 34 | cmd_find_flags::empty(), 35 | ), 36 | 37 | flags: cmd_flag::CMD_AFTERHOOK, 38 | exec: cmd_list_windows_exec, 39 | source: cmd_entry_flag::zeroed(), 40 | }; 41 | 42 | unsafe fn cmd_list_windows_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 43 | unsafe { 44 | let args = cmd_get_args(self_); 45 | let target = cmdq_get_target(item); 46 | 47 | if args_has(args, 'a') { 48 | cmd_list_windows_server(self_, item); 49 | } else { 50 | cmd_list_windows_session(self_, NonNull::new_unchecked((*target).s), item, 0); 51 | } 52 | 53 | cmd_retval::CMD_RETURN_NORMAL 54 | } 55 | } 56 | 57 | unsafe fn cmd_list_windows_server(self_: *mut cmd, item: *mut cmdq_item) { 58 | unsafe { 59 | for s in rb_foreach(&raw mut SESSIONS) { 60 | cmd_list_windows_session(self_, s, item, 1); 61 | } 62 | } 63 | } 64 | 65 | unsafe fn cmd_list_windows_session( 66 | self_: *mut cmd, 67 | s: NonNull, 68 | item: *mut cmdq_item, 69 | type_: i32, 70 | ) { 71 | unsafe { 72 | let args = cmd_get_args(self_); 73 | 74 | let mut template = args_get_(args, 'F'); 75 | if template.is_null() { 76 | match type_ { 77 | 0 => { 78 | template = LIST_WINDOWS_TEMPLATE; 79 | } 80 | 1 => { 81 | template = LIST_WINDOWS_WITH_SESSION_TEMPLATE; 82 | } 83 | _ => (), 84 | } 85 | } 86 | let filter = args_get_(args, 'f'); 87 | 88 | for (n, wl) in rb_foreach(&raw mut (*s.as_ptr()).windows).enumerate() { 89 | let ft = format_create( 90 | cmdq_get_client(item), 91 | item, 92 | FORMAT_NONE, 93 | format_flags::empty(), 94 | ); 95 | format_add!(ft, "line", "{n}"); 96 | format_defaults(ft, null_mut(), Some(s), Some(wl), None); 97 | 98 | let flag; 99 | if !filter.is_null() { 100 | let expanded = format_expand(ft, filter); 101 | flag = format_true(expanded); 102 | free_(expanded); 103 | } else { 104 | flag = true; 105 | } 106 | if flag { 107 | let line = format_expand(ft, template); 108 | cmdq_print!(item, "{}", _s(line)); 109 | free_(line); 110 | } 111 | 112 | format_free(ft); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /regress/copy-mode-test-vi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -f/dev/null -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | $TMUX new -d -x40 -y10 \ 11 | "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 12 | $TMUX set -g window-size manual || exit 1 13 | 14 | # Enter copy mode and go to the first column of the first row. 15 | $TMUX set-window-option -g mode-keys vi 16 | $TMUX copy-mode 17 | $TMUX send-keys -X history-top 18 | $TMUX send-keys -X start-of-line 19 | 20 | # Test that `previous-word` and `previous-space` 21 | # do not go past the start of text. 22 | $TMUX send-keys -X begin-selection 23 | $TMUX send-keys -X previous-word 24 | $TMUX send-keys -X previous-space 25 | $TMUX send-keys -X previous-word 26 | $TMUX send-keys -X copy-selection 27 | [ "$($TMUX show-buffer)" = "A" ] || exit 1 28 | 29 | # Test that `next-word-end` skips single-letter words 30 | # and `previous-word` does not skip multi-letter words. 31 | $TMUX send-keys -X next-word-end 32 | $TMUX send-keys -X begin-selection 33 | $TMUX send-keys -X previous-word 34 | $TMUX send-keys -X copy-selection 35 | [ "$($TMUX show-buffer)" = "line" ] || exit 1 36 | 37 | # Test that `next-word-end` wraps around indented line breaks. 38 | $TMUX send-keys -X next-word 39 | $TMUX send-keys -X next-word 40 | $TMUX send-keys -X begin-selection 41 | $TMUX send-keys -X next-word-end 42 | $TMUX send-keys -X next-word-end 43 | $TMUX send-keys -X copy-selection 44 | [ "$($TMUX show-buffer)" = "$(printf "words\n\tIndented")" ] || exit 1 45 | 46 | # Test that `next-word` wraps around un-indented line breaks. 47 | $TMUX send-keys -X next-word 48 | $TMUX send-keys -X begin-selection 49 | $TMUX send-keys -X next-word 50 | $TMUX send-keys -X copy-selection 51 | [ "$($TMUX show-buffer)" = "$(printf "line\nA")" ] || exit 1 52 | 53 | # Test that `next-word-end` does not treat periods as letters. 54 | $TMUX send-keys -X next-word 55 | $TMUX send-keys -X begin-selection 56 | $TMUX send-keys -X next-word-end 57 | $TMUX send-keys -X copy-selection 58 | [ "$($TMUX show-buffer)" = "line" ] || exit 1 59 | 60 | # Test that `next-space-end` treats periods as letters. 61 | $TMUX send-keys -X previous-word 62 | $TMUX send-keys -X begin-selection 63 | $TMUX send-keys -X next-space-end 64 | $TMUX send-keys -X copy-selection 65 | [ "$($TMUX show-buffer)" = "line..." ] || exit 1 66 | 67 | # Test that `previous-space` and `next-space` treat periods as letters. 68 | $TMUX send-keys -X previous-space 69 | $TMUX send-keys -X begin-selection 70 | $TMUX send-keys -X next-space 71 | $TMUX send-keys -X copy-selection 72 | [ "$($TMUX show-buffer)" = "$(printf "line...\n.")" ] || exit 1 73 | 74 | # Test that `next-word` and `next-word-end` do not treat other symbols as letters. 75 | $TMUX send-keys -X begin-selection 76 | $TMUX send-keys -X next-word 77 | $TMUX send-keys -X next-word 78 | $TMUX send-keys -X next-word-end 79 | $TMUX send-keys -X next-word-end 80 | $TMUX send-keys -X copy-selection 81 | [ "$($TMUX show-buffer)" = "... @nd then" ] || exit 1 82 | 83 | # Test that `next-space` wraps around for indented symbols 84 | $TMUX send-keys -X next-space 85 | $TMUX send-keys -X begin-selection 86 | $TMUX send-keys -X next-space 87 | $TMUX send-keys -X copy-selection 88 | [ "$($TMUX show-buffer)" = "$(printf "\$ym_bols[]{}\n ?")" ] || exit 1 89 | 90 | # Test that `next-word-end` treats digits as letters 91 | $TMUX send-keys -X next-word-end 92 | $TMUX send-keys -X begin-selection 93 | $TMUX send-keys -X next-word-end 94 | $TMUX send-keys -X copy-selection 95 | [ "$($TMUX show-buffer)" = "? 500xyz" ] || exit 1 96 | 97 | # Test that `previous-word` treats digits as letters 98 | $TMUX send-keys -X begin-selection 99 | $TMUX send-keys -X previous-word 100 | $TMUX send-keys -X copy-selection 101 | [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 102 | 103 | # Test that `next-word`, `next-word-end`, 104 | # `next-space`, and `next-space-end` stop at the end of text. 105 | $TMUX send-keys -X begin-selection 106 | $TMUX send-keys -X next-word 107 | $TMUX send-keys -X next-word-end 108 | $TMUX send-keys -X next-word 109 | $TMUX send-keys -X next-space 110 | $TMUX send-keys -X next-space-end 111 | $TMUX send-keys -X copy-selection 112 | [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 113 | 114 | $TMUX kill-server 2>/dev/null 115 | exit 0 116 | -------------------------------------------------------------------------------- /src/cmd_/cmd_save_buffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Tiago Cunha 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::libc::{O_APPEND, O_TRUNC}; 15 | use crate::*; 16 | 17 | pub static CMD_SAVE_BUFFER_ENTRY: cmd_entry = cmd_entry { 18 | name: "save-buffer", 19 | alias: Some("saveb"), 20 | 21 | args: args_parse::new("ab:", 1, 1, None), 22 | usage: "[-a] [-b buffer-name] path", 23 | 24 | flags: cmd_flag::CMD_AFTERHOOK, 25 | exec: cmd_save_buffer_exec, 26 | source: cmd_entry_flag::zeroed(), 27 | target: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | pub static CMD_SHOW_BUFFER_ENTRY: cmd_entry = cmd_entry { 31 | name: "show-buffer", 32 | alias: Some("showb"), 33 | 34 | args: args_parse::new("b:", 0, 0, None), 35 | usage: "[-b buffer-name]", 36 | 37 | flags: cmd_flag::CMD_AFTERHOOK, 38 | exec: cmd_save_buffer_exec, 39 | source: cmd_entry_flag::zeroed(), 40 | target: cmd_entry_flag::zeroed(), 41 | }; 42 | 43 | unsafe fn cmd_save_buffer_done( 44 | _c: *mut client, 45 | path: *mut u8, 46 | error: i32, 47 | closed: i32, 48 | _buffer: *mut evbuffer, 49 | data: *mut c_void, 50 | ) { 51 | let item = data as *mut cmdq_item; 52 | 53 | if closed == 0 { 54 | return; 55 | } 56 | 57 | unsafe { 58 | if error != 0 { 59 | cmdq_error!(item, "{}: {}", _s(path), strerror(error)); 60 | } 61 | cmdq_continue(item); 62 | } 63 | } 64 | 65 | unsafe fn cmd_save_buffer_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 66 | unsafe { 67 | let args = cmd_get_args(self_); 68 | let c = cmdq_get_client(item); 69 | let bufname = cstr_to_str_(args_get_(args, 'b')); 70 | let path; 71 | let evb; 72 | 73 | let pb = if let Some(bufname) = bufname { 74 | let Some(pb) = NonNull::new(paste_get_name(Some(bufname))) else { 75 | cmdq_error!(item, "no buffer {}", bufname); 76 | return cmd_retval::CMD_RETURN_ERROR; 77 | }; 78 | pb 79 | } else { 80 | let Some(pb) = NonNull::new(paste_get_top(null_mut())) else { 81 | cmdq_error!(item, "no buffers"); 82 | return cmd_retval::CMD_RETURN_ERROR; 83 | }; 84 | pb 85 | }; 86 | let mut bufsize: usize = 0; 87 | let bufdata = paste_buffer_data_(pb, &mut bufsize); 88 | 89 | if std::ptr::eq(cmd_get_entry(self_), &CMD_SHOW_BUFFER_ENTRY) { 90 | if !(*c).session.is_null() || (*c).flags.intersects(client_flag::CONTROL) { 91 | evb = evbuffer_new(); 92 | if evb.is_null() { 93 | fatalx("out of memory"); 94 | } 95 | evbuffer_add(evb, bufdata as _, bufsize); 96 | cmdq_print_data(item, 1, evb); 97 | evbuffer_free(evb); 98 | return cmd_retval::CMD_RETURN_NORMAL; 99 | } 100 | path = xstrdup_(c"-").as_ptr(); 101 | } else { 102 | path = format_single_from_target(item, args_string(args, 0)); 103 | } 104 | let flags = if args_has(args, 'a') { 105 | O_APPEND 106 | } else { 107 | O_TRUNC 108 | }; 109 | file_write( 110 | cmdq_get_client(item), 111 | path, 112 | flags, 113 | bufdata as _, 114 | bufsize, 115 | Some(cmd_save_buffer_done), 116 | item as _, 117 | ); 118 | free_(path); 119 | 120 | cmd_retval::CMD_RETURN_WAIT 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /regress/copy-mode-test-emacs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=/bin:/usr/bin 4 | TERM=screen 5 | 6 | [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) 7 | TMUX="$TEST_TMUX -f/dev/null -Ltest" 8 | $TMUX kill-server 2>/dev/null 9 | 10 | $TMUX new -d -x40 -y10 \ 11 | "cat copy-mode-test.txt; printf '\e[9;15H'; cat" || exit 1 12 | $TMUX set -g window-size manual || exit 1 13 | 14 | # Enter copy mode and go to the first column of the first row. 15 | $TMUX set-window-option -g mode-keys emacs 16 | $TMUX set-window-option -g word-separators "" 17 | $TMUX copy-mode 18 | $TMUX send-keys -X history-top 19 | $TMUX send-keys -X start-of-line 20 | 21 | # Test that `previous-word` and `previous-space` 22 | # do not go past the start of text. 23 | $TMUX send-keys -X begin-selection 24 | $TMUX send-keys -X previous-word 25 | $TMUX send-keys -X previous-space 26 | $TMUX send-keys -X previous-word 27 | $TMUX send-keys -X copy-selection 28 | [ "$($TMUX show-buffer 2>/dev/null)" = "" ] || exit 1 29 | 30 | # Test that `next-word-end` does not skip single-letter words. 31 | $TMUX send-keys -X next-word-end 32 | $TMUX send-keys -X begin-selection 33 | $TMUX send-keys -X previous-word 34 | $TMUX send-keys -X copy-selection 35 | [ "$($TMUX show-buffer)" = "A" ] || exit 1 36 | 37 | # Test that `next-word-end` wraps around indented line breaks. 38 | $TMUX send-keys -X next-word 39 | $TMUX send-keys -X next-word 40 | $TMUX send-keys -X next-word 41 | $TMUX send-keys -X begin-selection 42 | $TMUX send-keys -X next-word-end 43 | $TMUX send-keys -X next-word-end 44 | $TMUX send-keys -X copy-selection 45 | [ "$($TMUX show-buffer)" = "$(printf "words\n\tIndented")" ] || exit 1 46 | 47 | # Test that `next-word` wraps around un-indented line breaks. 48 | $TMUX send-keys -X next-word 49 | $TMUX send-keys -X begin-selection 50 | $TMUX send-keys -X next-word 51 | $TMUX send-keys -X copy-selection 52 | [ "$($TMUX show-buffer)" = "$(printf "line\n")" ] || exit 1 53 | 54 | # Test that `next-word-end` treats periods as letters. 55 | $TMUX send-keys -X next-word 56 | $TMUX send-keys -X begin-selection 57 | $TMUX send-keys -X next-word-end 58 | $TMUX send-keys -X copy-selection 59 | [ "$($TMUX show-buffer)" = "line..." ] || exit 1 60 | 61 | # Test that `previous-word` and `next-word` treat periods as letters. 62 | $TMUX send-keys -X previous-word 63 | $TMUX send-keys -X begin-selection 64 | $TMUX send-keys -X next-word 65 | $TMUX send-keys -X copy-selection 66 | [ "$($TMUX show-buffer)" = "$(printf "line...\n")" ] || exit 1 67 | 68 | # Test that `previous-space` and `next-space` treat periods as letters. 69 | $TMUX send-keys -X previous-space 70 | $TMUX send-keys -X begin-selection 71 | $TMUX send-keys -X next-space 72 | $TMUX send-keys -X copy-selection 73 | [ "$($TMUX show-buffer)" = "$(printf "line...\n")" ] || exit 1 74 | 75 | # Test that `next-word` and `next-word-end` treat other symbols as letters. 76 | $TMUX send-keys -X begin-selection 77 | $TMUX send-keys -X next-word 78 | $TMUX send-keys -X next-word 79 | $TMUX send-keys -X next-word-end 80 | $TMUX send-keys -X next-word-end 81 | $TMUX send-keys -X copy-selection 82 | [ "$($TMUX show-buffer)" = "... @nd then \$ym_bols[]{}" ] || exit 1 83 | 84 | # Test that `previous-word` treats other symbols as letters 85 | # and `next-word` wraps around for indented symbols 86 | $TMUX send-keys -X previous-word 87 | $TMUX send-keys -X begin-selection 88 | $TMUX send-keys -X next-word 89 | $TMUX send-keys -X copy-selection 90 | [ "$($TMUX show-buffer)" = "$(printf "\$ym_bols[]{}\n ")" ] || exit 1 91 | 92 | # Test that `next-word-end` treats digits as letters 93 | $TMUX send-keys -X next-word-end 94 | $TMUX send-keys -X begin-selection 95 | $TMUX send-keys -X next-word-end 96 | $TMUX send-keys -X copy-selection 97 | [ "$($TMUX show-buffer)" = " 500xyz" ] || exit 1 98 | 99 | # Test that `previous-word` treats digits as letters 100 | $TMUX send-keys -X begin-selection 101 | $TMUX send-keys -X previous-word 102 | $TMUX send-keys -X copy-selection 103 | [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 104 | 105 | # Test that `next-word` and `next-word-end` stop at the end of text. 106 | $TMUX send-keys -X begin-selection 107 | $TMUX send-keys -X next-word 108 | $TMUX send-keys -X next-word-end 109 | $TMUX send-keys -X next-word 110 | $TMUX send-keys -X next-space 111 | $TMUX send-keys -X next-space-end 112 | $TMUX send-keys -X copy-selection 113 | [ "$($TMUX show-buffer)" = "500xyz" ] || exit 1 114 | 115 | $TMUX kill-server 2>/dev/null 116 | exit 0 117 | -------------------------------------------------------------------------------- /src/cmd_/cmd_show_prompt_history.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Anindya Mukherjee 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_SHOW_PROMPT_HISTORY_ENTRY: cmd_entry = cmd_entry { 17 | name: "show-prompt-history", 18 | alias: Some("showphist"), 19 | 20 | args: args_parse::new("T:", 0, 0, None), 21 | usage: "[-T type]", 22 | 23 | flags: cmd_flag::CMD_AFTERHOOK, 24 | exec: cmd_show_prompt_history_exec, 25 | source: cmd_entry_flag::zeroed(), 26 | target: cmd_entry_flag::zeroed(), 27 | }; 28 | 29 | pub static CMD_CLEAR_PROMPT_HISTORY_ENTRY: cmd_entry = cmd_entry { 30 | name: "clear-prompt-history", 31 | alias: Some("clearphist"), 32 | 33 | args: args_parse::new("T:", 0, 0, None), 34 | usage: "[-T type]", 35 | 36 | flags: cmd_flag::CMD_AFTERHOOK, 37 | exec: cmd_show_prompt_history_exec, 38 | source: cmd_entry_flag::zeroed(), 39 | target: cmd_entry_flag::zeroed(), 40 | }; 41 | 42 | unsafe fn cmd_show_prompt_history_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 43 | unsafe { 44 | let args = cmd_get_args(self_); 45 | let typestr = args_get(args, b'T'); 46 | let type_: prompt_type; 47 | 48 | if std::ptr::eq(cmd_get_entry(self_), &CMD_CLEAR_PROMPT_HISTORY_ENTRY) { 49 | if typestr.is_null() { 50 | for tidx in 0..PROMPT_NTYPES { 51 | free_(STATUS_PROMPT_HLIST[tidx as usize]); 52 | STATUS_PROMPT_HLIST[tidx as usize] = null_mut(); 53 | STATUS_PROMPT_HSIZE[tidx as usize] = 0; 54 | } 55 | } else { 56 | type_ = status_prompt_type(typestr); 57 | if type_ == prompt_type::PROMPT_TYPE_INVALID { 58 | cmdq_error!(item, "invalid type: {}", _s(typestr)); 59 | return cmd_retval::CMD_RETURN_ERROR; 60 | } 61 | free_(STATUS_PROMPT_HLIST[type_ as usize]); 62 | STATUS_PROMPT_HLIST[type_ as usize] = null_mut(); 63 | STATUS_PROMPT_HSIZE[type_ as usize] = 0; 64 | } 65 | 66 | return cmd_retval::CMD_RETURN_NORMAL; 67 | } 68 | 69 | if typestr.is_null() { 70 | for tidx in 0..PROMPT_NTYPES { 71 | cmdq_print!(item, "History for {}:\n", status_prompt_type_string(tidx),); 72 | for hidx in 0u32..STATUS_PROMPT_HSIZE[tidx as usize] { 73 | cmdq_print!( 74 | item, 75 | "{}: {}", 76 | hidx + 1, 77 | _s(*STATUS_PROMPT_HLIST[tidx as usize].add(hidx as usize)), 78 | ); 79 | } 80 | cmdq_print!(item, ""); 81 | } 82 | } else { 83 | type_ = status_prompt_type(typestr); 84 | if type_ == prompt_type::PROMPT_TYPE_INVALID { 85 | cmdq_error!(item, "invalid type: {}", _s(typestr)); 86 | return cmd_retval::CMD_RETURN_ERROR; 87 | } 88 | cmdq_print!( 89 | item, 90 | "History for {}:\n", 91 | status_prompt_type_string(type_ as u32), 92 | ); 93 | for hidx in 0u32..STATUS_PROMPT_HSIZE[type_ as usize] { 94 | cmdq_print!( 95 | item, 96 | "{}: {}", 97 | hidx + 1, 98 | _s(*STATUS_PROMPT_HLIST[type_ as usize].add(hidx as usize)), 99 | ); 100 | } 101 | cmdq_print!(item, ""); 102 | } 103 | 104 | cmd_retval::CMD_RETURN_NORMAL 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /regress/conf/d0040b2e097f1e3d31d78eed6ce8d461.conf: -------------------------------------------------------------------------------- 1 | # Put the status bar on top 2 | #set -g status-position "top" 3 | 4 | # Basic colours, safer for dumb terminals. 5 | #set -g status-style "bg=white,fg=black" 6 | #set -g status-right-style "bg=green,fg=black" 7 | #set -g window-status-current-style "bg=yellow,fg=black" 8 | #set -g message-style "bg=white,fg=black" 9 | #set -g window-status-activity-style "fg=blue" 10 | #set -g window-status-bell-style "fg=red" 11 | 12 | ## Moar colours! Not recommended if attaching from dumber terminals with 8 or 16 colours. 13 | #set -g default-terminal "tmux-256color" 14 | # A more compatible XTERM var. 15 | set -g default-terminal "screen-256color" 16 | set -g message-style "bg=#485548 fg=#ffffff" 17 | set -g pane-border-style "fg=#424954" 18 | set -g pane-active-border-style "fg=#ffffff" 19 | set -g status-style "bg=#424954 fg=#ffffff" 20 | set -g status-right-style "bg=#303338 fg=colour87" 21 | set -g window-status-current-style "bg=#303338" 22 | set -g window-status-last-style "bg=#364146" 23 | set -g window-status-format ' #I:#W#[fg=colour201]#F ' 24 | set -g window-status-current-format ' #[fg=colour226]#I#[fg=#ffffff]:#[fg=colour119]#W#[fg=colour202]#F ' 25 | set -g window-status-separator "" 26 | 27 | # Uncomment and reload settings for sanity in a console with 8 colours. 28 | #set -g status-style "bg=white,fg=black" 29 | #set -g window-status-last-style "bg=white" 30 | 31 | # Might help when graphical characters used for vertical and horizontal lines are drawn as x and q. 32 | #set-option -ga terminal-overrides ',*:enacs@:smacs@:rmacs@:acsc@' 33 | 34 | # Count panes starting from 1. 35 | set -g base-index 1 36 | 37 | # With this you set the window name in the status line. 38 | # Beware of outrageous prompts, such as the default one in RHEL 7. 39 | set -g set-titles on 40 | # Let status right consists of only the pane title (removes date and time). 41 | # Usually shows current path. 42 | set -g status-right ' #T ' 43 | # Increase the default length of 40. 44 | set -g status-right-length 80 45 | 46 | # Scroll up with the mouse. 47 | set -g mouse 48 | 49 | # Clipboard integration, use this in tandem with the recommended xterm settings. 50 | set -g set-clipboard on 51 | # Pass through modifier keys, xterm style. You'll want this in vim. 52 | set -g xterm-keys on 53 | # Reduce time to wait for Escape key. You'll want this for neovim. 54 | set-option escape-time 40 55 | # Leave ESC alone... 56 | #set-option -s escape-time 0 57 | 58 | # New-style mouse scroll (>2.1) 59 | bind -n WheelUpPane select-pane -t= \; copy-mode -e \; send-keys -M 60 | bind -n WheelDownPane select-pane -t= \; send-keys -M 61 | 62 | # This is for scrolling up with the terminal using keys, but has issues... 63 | #set -ga terminal-overrides ',xterm*:smcup@:rmcup@' 64 | 65 | # 10x more history. 66 | set -g history-limit 20000 67 | 68 | # Swap the default Control-b with Control-s which usually stops the output in a shell. 69 | unbind C-b 70 | set-option -g terminal-overrides "xterm-rightclick:krightclick=^[[29~" 71 | set -g prefix C-s 72 | bind C-s send-prefix 73 | 74 | # For renumbering windows when you get gaps in numbering. 75 | bind R \ 76 | move-window -r\; \ 77 | display-message "Windows reordered..." 78 | 79 | # My shortcuts. 80 | #bind-key -n C-S-t new-window # Doesn't work :-/ 81 | bind-key -n C-t new-window 82 | bind-key -n C-PgUp prev 83 | bind-key -n C-PgDn next 84 | #bind-key -n C-S-PgUp swap-window -t -1 # Doesn't work :-/ 85 | #bind-key -n C-S-PgDn swap-window -t +1 # Doesn't work :-/ 86 | bind-key -n C-S-Left swap-window -t -1 87 | bind-key -n C-S-Right swap-window -t +1 88 | bind-key -n M-` select-window -t 0 89 | bind-key -n M-1 select-window -t 1 90 | bind-key -n M-2 select-window -t 2 91 | bind-key -n M-3 select-window -t 3 92 | bind-key -n M-4 select-window -t 4 93 | bind-key -n M-5 select-window -t 5 94 | bind-key -n M-6 select-window -t 6 95 | bind-key -n M-7 select-window -t 7 96 | bind-key -n M-8 select-window -t 8 97 | bind-key -n M-9 select-window -t 9 98 | bind-key -n M-0 select-window -t 10 99 | 100 | # switch panes without prefix using Alt-arrow 101 | bind -n M-Left select-pane -L 102 | bind -n M-Right select-pane -R 103 | bind -n M-Up select-pane -U 104 | bind -n M-Down select-pane -D 105 | 106 | # join pane from inputted window (horizontally or vertically) 107 | #bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -h" 108 | bind-key @ command-prompt -p "join pane from:" "join-pane -s ':%%' -v" 109 | -------------------------------------------------------------------------------- /src/cmd_/cmd_paste_buffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2007 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_PASTE_BUFFER_ENTRY: cmd_entry = cmd_entry { 17 | name: "paste-buffer", 18 | alias: Some("pasteb"), 19 | 20 | args: args_parse::new("db:prs:t:", 0, 0, None), 21 | usage: "[-dpr] [-s separator] [-b buffer-name] [-t target-pane]", 22 | 23 | target: cmd_entry_flag::new(b't', cmd_find_type::CMD_FIND_PANE, cmd_find_flags::empty()), 24 | 25 | flags: cmd_flag::CMD_AFTERHOOK, 26 | exec: cmd_paste_buffer_exec, 27 | source: cmd_entry_flag::zeroed(), 28 | }; 29 | 30 | unsafe fn cmd_paste_buffer_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 31 | unsafe { 32 | let args = cmd_get_args(self_); 33 | let target = cmdq_get_target(item); 34 | let wp = (*target).wp; 35 | let bracket = args_has(args, 'p'); 36 | 37 | if window_pane_exited(wp) { 38 | cmdq_error!(item, "target pane has exited"); 39 | return cmd_retval::CMD_RETURN_ERROR; 40 | } 41 | 42 | let mut bufname = None; 43 | if args_has(args, 'b') { 44 | bufname = Some(cstr_to_str(args_get(args, b'b'))); 45 | } 46 | 47 | let pb; 48 | if let Some(bufname) = bufname { 49 | pb = paste_get_name(Some(bufname)); 50 | if pb.is_null() { 51 | cmdq_error!(item, "no buffer {bufname}"); 52 | return cmd_retval::CMD_RETURN_ERROR; 53 | } 54 | } else { 55 | pb = paste_get_top(null_mut()); 56 | } 57 | 58 | if let Some(pb) = NonNull::new(pb) 59 | && !(*wp).flags.intersects(window_pane_flags::PANE_INPUTOFF) 60 | { 61 | let mut sepstr = args_get(args, b's'); 62 | if sepstr.is_null() { 63 | if args_has(args, 'r') { 64 | sepstr = c!("\n"); 65 | } else { 66 | sepstr = c!("\r"); 67 | } 68 | } 69 | let seplen = strlen(sepstr); 70 | 71 | if bracket 72 | && (*(*wp).screen) 73 | .mode 74 | .intersects(mode_flag::MODE_BRACKETPASTE) 75 | { 76 | bufferevent_write((*wp).event, c!("\x1b[200~").cast(), 6); 77 | } 78 | 79 | let mut bufsize: usize = 0; 80 | let mut bufdata = paste_buffer_data_(pb, &mut bufsize); 81 | let bufend = bufdata.add(bufsize); 82 | 83 | loop { 84 | let line: *mut u8 = 85 | libc::memchr(bufdata as _, b'\n' as i32, bufend.addr() - bufdata.addr()).cast(); 86 | if line.is_null() { 87 | break; 88 | } 89 | 90 | bufferevent_write((*wp).event, bufdata.cast(), line.addr() - bufdata.addr()); 91 | bufferevent_write((*wp).event, sepstr.cast(), seplen); 92 | 93 | bufdata = line.add(1); 94 | } 95 | if bufdata != bufend { 96 | bufferevent_write((*wp).event, bufdata.cast(), bufend.addr() - bufdata.addr()); 97 | } 98 | 99 | if bracket 100 | && (*(*wp).screen) 101 | .mode 102 | .intersects(mode_flag::MODE_BRACKETPASTE) 103 | { 104 | bufferevent_write((*wp).event, c!("\x1b[201~").cast(), 6); 105 | } 106 | } 107 | 108 | if let Some(non_null_pb) = NonNull::new(pb) 109 | && args_has(args, 'd') 110 | { 111 | paste_free(non_null_pb); 112 | } 113 | 114 | cmd_retval::CMD_RETURN_NORMAL 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/tmux_protocol.rs: -------------------------------------------------------------------------------- 1 | pub const PROTOCOL_VERSION: i32 = 8; 2 | 3 | /// Message types. 4 | #[repr(i32)] 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 6 | pub enum msgtype { 7 | MSG_ZERO = 0, // TODO rust added so not ub on static init 8 | MSG_VERSION = 12, 9 | 10 | MSG_IDENTIFY_FLAGS = 100, 11 | MSG_IDENTIFY_TERM, 12 | MSG_IDENTIFY_TTYNAME, 13 | MSG_IDENTIFY_OLDCWD, // unused 14 | MSG_IDENTIFY_STDIN, 15 | MSG_IDENTIFY_ENVIRON, 16 | MSG_IDENTIFY_DONE, 17 | MSG_IDENTIFY_CLIENTPID, 18 | MSG_IDENTIFY_CWD, 19 | MSG_IDENTIFY_FEATURES, 20 | MSG_IDENTIFY_STDOUT, 21 | MSG_IDENTIFY_LONGFLAGS, 22 | MSG_IDENTIFY_TERMINFO, 23 | 24 | MSG_COMMAND = 200, 25 | MSG_DETACH, 26 | MSG_DETACHKILL, 27 | MSG_EXIT, 28 | MSG_EXITED, 29 | MSG_EXITING, 30 | MSG_LOCK, 31 | MSG_READY, 32 | MSG_RESIZE, 33 | MSG_SHELL, 34 | MSG_SHUTDOWN, 35 | MSG_OLDSTDERR, // unused 36 | MSG_OLDSTDIN, // unused 37 | MSG_OLDSTDOUT, // unused 38 | MSG_SUSPEND, 39 | MSG_UNLOCK, 40 | MSG_WAKEUP, 41 | MSG_EXEC, 42 | MSG_FLAGS, 43 | 44 | MSG_READ_OPEN = 300, 45 | MSG_READ, 46 | MSG_READ_DONE, 47 | MSG_WRITE_OPEN, 48 | MSG_WRITE, 49 | MSG_WRITE_READY, 50 | MSG_WRITE_CLOSE, 51 | MSG_READ_CANCEL, 52 | } 53 | 54 | #[derive(Debug)] 55 | pub struct InvalidEnumValue; 56 | impl TryFrom for msgtype { 57 | type Error = InvalidEnumValue; 58 | 59 | fn try_from(value: u32) -> Result { 60 | Ok(match value { 61 | 0 => msgtype::MSG_ZERO, 62 | 12 => msgtype::MSG_VERSION, 63 | 100 => msgtype::MSG_IDENTIFY_FLAGS, 64 | 101 => msgtype::MSG_IDENTIFY_TERM, 65 | 102 => msgtype::MSG_IDENTIFY_TTYNAME, 66 | 103 => msgtype::MSG_IDENTIFY_OLDCWD, 67 | 104 => msgtype::MSG_IDENTIFY_STDIN, 68 | 105 => msgtype::MSG_IDENTIFY_ENVIRON, 69 | 106 => msgtype::MSG_IDENTIFY_DONE, 70 | 107 => msgtype::MSG_IDENTIFY_CLIENTPID, 71 | 108 => msgtype::MSG_IDENTIFY_CWD, 72 | 109 => msgtype::MSG_IDENTIFY_FEATURES, 73 | 110 => msgtype::MSG_IDENTIFY_STDOUT, 74 | 111 => msgtype::MSG_IDENTIFY_LONGFLAGS, 75 | 112 => msgtype::MSG_IDENTIFY_TERMINFO, 76 | 200 => msgtype::MSG_COMMAND, 77 | 201 => msgtype::MSG_DETACH, 78 | 202 => msgtype::MSG_DETACHKILL, 79 | 203 => msgtype::MSG_EXIT, 80 | 204 => msgtype::MSG_EXITED, 81 | 205 => msgtype::MSG_EXITING, 82 | 206 => msgtype::MSG_LOCK, 83 | 207 => msgtype::MSG_READY, 84 | 208 => msgtype::MSG_RESIZE, 85 | 209 => msgtype::MSG_SHELL, 86 | 210 => msgtype::MSG_SHUTDOWN, 87 | 211 => msgtype::MSG_OLDSTDERR, 88 | 212 => msgtype::MSG_OLDSTDIN, 89 | 213 => msgtype::MSG_OLDSTDOUT, 90 | 214 => msgtype::MSG_SUSPEND, 91 | 215 => msgtype::MSG_UNLOCK, 92 | 216 => msgtype::MSG_WAKEUP, 93 | 217 => msgtype::MSG_EXEC, 94 | 218 => msgtype::MSG_FLAGS, 95 | 300 => msgtype::MSG_READ_OPEN, 96 | 301 => msgtype::MSG_READ, 97 | 302 => msgtype::MSG_READ_DONE, 98 | 303 => msgtype::MSG_WRITE_OPEN, 99 | 304 => msgtype::MSG_WRITE, 100 | 305 => msgtype::MSG_WRITE_READY, 101 | 306 => msgtype::MSG_WRITE_CLOSE, 102 | 307 => msgtype::MSG_READ_CANCEL, 103 | _ => return Err(InvalidEnumValue), 104 | }) 105 | } 106 | } 107 | 108 | #[repr(C)] 109 | pub struct msg_command { 110 | pub argc: i32, 111 | } 112 | 113 | #[repr(C)] 114 | pub struct msg_read_open { 115 | pub stream: i32, 116 | pub fd: i32, 117 | } 118 | 119 | #[repr(C)] 120 | pub struct msg_read_data { 121 | pub stream: i32, 122 | } 123 | 124 | #[repr(C)] 125 | pub struct msg_read_done { 126 | pub stream: i32, 127 | pub error: i32, 128 | } 129 | 130 | #[repr(C)] 131 | pub struct msg_read_cancel { 132 | pub stream: i32, 133 | } 134 | 135 | #[repr(C)] 136 | pub struct msg_write_open { 137 | pub stream: i32, 138 | pub fd: i32, 139 | pub flags: i32, 140 | } 141 | 142 | #[repr(C)] 143 | pub struct msg_write_data { 144 | pub stream: i32, 145 | } 146 | 147 | #[repr(C)] 148 | pub struct msg_write_ready { 149 | pub stream: i32, 150 | pub error: i32, 151 | } 152 | 153 | #[repr(C)] 154 | pub struct msg_write_close { 155 | pub stream: i32, 156 | } 157 | -------------------------------------------------------------------------------- /src/attributes.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Joshua Elsasser 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use std::borrow::Cow; 15 | 16 | use crate::grid_attr; 17 | 18 | #[rustfmt::skip] 19 | pub fn attributes_tostring(attr: grid_attr) -> Cow<'static, str> { 20 | if attr.is_empty() { 21 | return Cow::Borrowed("none"); 22 | } 23 | 24 | Cow::Owned(format!( 25 | "{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 26 | if attr.intersects(grid_attr::GRID_ATTR_CHARSET) { "acs," } else { "" }, 27 | if attr.intersects(grid_attr::GRID_ATTR_BRIGHT) { "bright," } else { "" }, 28 | if attr.intersects(grid_attr::GRID_ATTR_DIM ) { "dim," } else { "" }, 29 | if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE) { "underscore," } else { "" }, 30 | if attr.intersects(grid_attr::GRID_ATTR_BLINK) { "blink," } else { "" }, 31 | if attr.intersects(grid_attr::GRID_ATTR_REVERSE ) { "reverse," } else { "" }, 32 | if attr.intersects(grid_attr::GRID_ATTR_HIDDEN) { "hidden," } else { "" }, 33 | if attr.intersects(grid_attr::GRID_ATTR_ITALICS ) { "italics," } else { "" }, 34 | if attr.intersects(grid_attr::GRID_ATTR_STRIKETHROUGH) { "strikethrough," } else { "" }, 35 | if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_2) { "double-underscore," } else { "" }, 36 | if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_3) { "curly-underscore," } else { "" }, 37 | if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_4) { "dotted-underscore," } else { "" }, 38 | if attr.intersects(grid_attr::GRID_ATTR_UNDERSCORE_5) { "dashed-underscore," } else { "" }, 39 | if attr.intersects(grid_attr::GRID_ATTR_OVERLINE) { "overline," } else { "" }, 40 | )) 41 | } 42 | 43 | pub fn attributes_fromstring(str: &str) -> Result { 44 | struct table_entry { 45 | name: &'static str, 46 | attr: grid_attr, 47 | } 48 | 49 | #[rustfmt::skip] 50 | const TABLE: [table_entry; 15] = [ 51 | table_entry { name: "acs", attr: grid_attr::GRID_ATTR_CHARSET, }, 52 | table_entry { name: "bright", attr: grid_attr::GRID_ATTR_BRIGHT, }, 53 | table_entry { name: "bold", attr: grid_attr::GRID_ATTR_BRIGHT, }, 54 | table_entry { name: "dim", attr: grid_attr::GRID_ATTR_DIM, }, 55 | table_entry { name: "underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE, }, 56 | table_entry { name: "blink", attr: grid_attr::GRID_ATTR_BLINK, }, 57 | table_entry { name: "reverse", attr: grid_attr::GRID_ATTR_REVERSE, }, 58 | table_entry { name: "hidden", attr: grid_attr::GRID_ATTR_HIDDEN, }, 59 | table_entry { name: "italics", attr: grid_attr::GRID_ATTR_ITALICS, }, 60 | table_entry { name: "strikethrough", attr: grid_attr::GRID_ATTR_STRIKETHROUGH, }, 61 | table_entry { name: "double-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_2, }, 62 | table_entry { name: "curly-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_3, }, 63 | table_entry { name: "dotted-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_4, }, 64 | table_entry { name: "dashed-underscore", attr: grid_attr::GRID_ATTR_UNDERSCORE_5, }, 65 | table_entry { name: "overline", attr: grid_attr::GRID_ATTR_OVERLINE, }, 66 | ]; 67 | 68 | let delimiters = &[' ', ',', '|']; 69 | 70 | if str.is_empty() || str.find(delimiters) == Some(0) { 71 | return Err(()); 72 | } 73 | 74 | if matches!(str.chars().next_back().unwrap(), ' ' | ',' | '|') { 75 | return Err(()); 76 | } 77 | 78 | if str.eq_ignore_ascii_case("default") || str.eq_ignore_ascii_case("none") { 79 | return Ok(grid_attr::empty()); 80 | } 81 | 82 | let mut attr = grid_attr::empty(); 83 | for str in str.split(delimiters) { 84 | let Some(i) = TABLE.iter().position(|t| str.eq_ignore_ascii_case(t.name)) else { 85 | return Err(()); 86 | }; 87 | attr |= TABLE[i].attr; 88 | } 89 | 90 | Ok(attr) 91 | } 92 | -------------------------------------------------------------------------------- /src/cmd_/cmd_set_environment.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2009 Nicholas Marriott 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 12 | // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 13 | // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | use crate::*; 15 | 16 | pub static CMD_SET_ENVIRONMENT_ENTRY: cmd_entry = cmd_entry { 17 | name: "set-environment", 18 | alias: Some("setenv"), 19 | 20 | args: args_parse::new("Fhgrt:u", 1, 2, None), 21 | usage: "[-Fhgru] [-t target-session] name [value]", 22 | 23 | target: cmd_entry_flag::new( 24 | b't', 25 | cmd_find_type::CMD_FIND_SESSION, 26 | cmd_find_flags::CMD_FIND_CANFAIL, 27 | ), 28 | 29 | flags: cmd_flag::CMD_AFTERHOOK, 30 | exec: cmd_set_environment_exec, 31 | source: cmd_entry_flag::zeroed(), 32 | }; 33 | 34 | unsafe fn cmd_set_environment_exec(self_: *mut cmd, item: *mut cmdq_item) -> cmd_retval { 35 | unsafe { 36 | let args = cmd_get_args(self_); 37 | let target = cmdq_get_target(item); 38 | let env: *mut environ; 39 | let name = args_string(args, 0); 40 | let mut value: *const u8; 41 | let tflag; 42 | let mut expanded = null_mut(); 43 | let mut retval = cmd_retval::CMD_RETURN_NORMAL; 44 | 45 | 'out: { 46 | if *name == b'\0' { 47 | cmdq_error!(item, "empty variable name"); 48 | return cmd_retval::CMD_RETURN_ERROR; 49 | } 50 | if !strchr_(name, '=').is_null() { 51 | cmdq_error!(item, "variable name contains ="); 52 | return cmd_retval::CMD_RETURN_ERROR; 53 | } 54 | 55 | if args_count(args) < 2 { 56 | value = null_mut(); 57 | } else { 58 | value = args_string(args, 1); 59 | } 60 | if !value.is_null() && args_has(args, 'F') { 61 | expanded = format_single_from_target(item, value); 62 | value = expanded; 63 | } 64 | if args_has(args, 'g') { 65 | env = GLOBAL_ENVIRON; 66 | } else { 67 | if (*target).s.is_null() { 68 | tflag = args_get_(args, 't'); 69 | if !tflag.is_null() { 70 | cmdq_error!(item, "no such session: {}", _s(tflag)); 71 | } else { 72 | cmdq_error!(item, "no current session"); 73 | } 74 | retval = cmd_retval::CMD_RETURN_ERROR; 75 | break 'out; 76 | } 77 | env = (*(*target).s).environ; 78 | } 79 | 80 | if args_has(args, 'u') { 81 | if !value.is_null() { 82 | cmdq_error!(item, "can't specify a value with -u"); 83 | retval = cmd_retval::CMD_RETURN_ERROR; 84 | break 'out; 85 | } 86 | environ_unset(env, name); 87 | } else if args_has(args, 'r') { 88 | if !value.is_null() { 89 | cmdq_error!(item, "can't specify a value with -r"); 90 | retval = cmd_retval::CMD_RETURN_ERROR; 91 | break 'out; 92 | } 93 | environ_clear(env, name); 94 | } else { 95 | if value.is_null() { 96 | cmdq_error!(item, "no value specified"); 97 | retval = cmd_retval::CMD_RETURN_ERROR; 98 | break 'out; 99 | } 100 | 101 | if args_has(args, 'h') { 102 | environ_set!(env, name, ENVIRON_HIDDEN, "{}", _s(value)); 103 | } else { 104 | environ_set!(env, name, environ_flags::empty(), "{}", _s(value)); 105 | } 106 | } 107 | } 108 | 109 | // out: 110 | free_(expanded); 111 | retval 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/compat/getopt.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 1987, 1993, 1994 2 | // The Regents of the University of California. All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions 6 | // are met: 7 | // 1. Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // 2. Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 3. Neither the name of the University nor the names of its contributors 13 | // may be used to endorse or promote products derived from this software 14 | // without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 17 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | // ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | // SUCH DAMAGE. 27 | use crate::*; 28 | 29 | // originally generated by c2rust 30 | // refactored to remove the BSD prefix, we don't need to worry about collisions with C 31 | 32 | pub static mut OPTERR: i32 = 1; 33 | pub static mut OPTIND: i32 = 1; 34 | pub static mut OPTOPT: u8 = 0; 35 | pub static mut OPTRESET: i32 = 0; 36 | pub static mut OPTARG: *mut u8 = null_mut(); 37 | 38 | pub unsafe fn getopt(nargc: i32, nargv: *const *mut u8, ostr: *const u8) -> Option { 39 | unsafe { 40 | static mut PLACE: *const u8 = c"".as_ptr().cast(); 41 | let mut oli: *mut u8; 42 | if ostr.is_null() { 43 | return None; 44 | } 45 | if OPTRESET != 0 || *PLACE == 0 { 46 | OPTRESET = 0; 47 | if OPTIND >= nargc || { 48 | PLACE = *nargv.offset(OPTIND as isize); 49 | *PLACE != b'-' 50 | } { 51 | PLACE = c"".as_ptr().cast(); 52 | return None; 53 | } 54 | if *PLACE.add(1) != 0 && { 55 | PLACE = PLACE.add(1); 56 | *PLACE == b'-' 57 | } { 58 | if *PLACE.add(1) != 0 { 59 | return Some(b'?'); 60 | } 61 | OPTIND += 1; 62 | PLACE = c"".as_ptr().cast(); 63 | return None; 64 | } 65 | } 66 | let fresh0 = PLACE; 67 | PLACE = PLACE.offset(1); 68 | OPTOPT = *fresh0; 69 | if OPTOPT == b':' || { 70 | oli = crate::libc::strchr(ostr, OPTOPT as i32); 71 | oli.is_null() 72 | } { 73 | if OPTOPT == b'-' { 74 | return None; 75 | } 76 | if *PLACE == 0 { 77 | OPTIND += 1; 78 | } 79 | if OPTERR != 0 && *ostr != b':' { 80 | let tmp = OPTOPT; 81 | eprintln!("tmux-rs: unknown option -- {}", tmp as char); 82 | } 83 | return Some(b'?'); 84 | } 85 | oli = oli.offset(1); 86 | if *oli != b':' { 87 | OPTARG = null_mut(); 88 | if *PLACE == 0 { 89 | OPTIND += 1; 90 | } 91 | } else { 92 | if *PLACE != 0 { 93 | OPTARG = PLACE.cast_mut(); 94 | } else { 95 | OPTIND += 1; 96 | if nargc <= OPTIND { 97 | PLACE = c"".as_ptr().cast(); 98 | if *ostr == b':' { 99 | return Some(b':'); 100 | } 101 | if OPTERR != 0 { 102 | let tmp = OPTOPT; 103 | eprintln!("tmux-rs: option requires an argument -- {}", tmp as char); 104 | } 105 | return Some(b'?'); 106 | } else { 107 | OPTARG = *nargv.offset(OPTIND as isize); 108 | } 109 | } 110 | PLACE = c"".as_ptr().cast(); 111 | OPTIND += 1; 112 | } 113 | 114 | Some(OPTOPT) 115 | } 116 | } 117 | --------------------------------------------------------------------------------