diff --git a/kittens/ssh/main.py b/kittens/ssh/main.py index 74c31af8a..f33a74706 100644 --- a/kittens/ssh/main.py +++ b/kittens/ssh/main.py @@ -223,13 +223,17 @@ def safe_remove(x: str) -> None: os.remove(x) -def prepare_script(ans: str, replacements: Dict[str, str]) -> str: +def prepare_script(ans: str, replacements: Dict[str, str], script_type: str) -> str: for k in ('EXEC_CMD',): replacements[k] = replacements.get(k, '') def sub(m: 're.Match[str]') -> str: return replacements[m.group()] + if script_type == 'sh': + # Remove comments and indents. The dropbear SSH server has 9000 bytes limit on ssh arguments length. + # Needs to be trimmed before replacing EXEC_CMD to avoid affecting the indentation of user commands. + ans = re.sub(r'^[ \t]*#.*$|^[ \t]*', '', ans, flags=re.MULTILINE) return re.sub('|'.join(fr'\b{k}\b' for k in replacements), sub, ans) @@ -269,7 +273,7 @@ def bootstrap_script( if request_data: sd.update(sensitive_data) replacements.update(sensitive_data) - return prepare_script(ans, sd), replacements, shm + return prepare_script(ans, sd, script_type), replacements, shm def get_ssh_cli() -> Tuple[Set[str], Set[str]]: diff --git a/kitty_tests/shell_integration.py b/kitty_tests/shell_integration.py index f24020c7d..54fcb85f8 100644 --- a/kitty_tests/shell_integration.py +++ b/kitty_tests/shell_integration.py @@ -171,6 +171,8 @@ function _set_status_prompt; function fish_prompt; echo -n "$pipestatus $status pty.send_cmd_to_child('set -q XDG_DATA_DIRS; or echo ok') pty.wait_till(lambda: pty.screen_contents().count(right_prompt) == 2) self.ae(str(pty.screen.line(1)), 'ok') + + # CWD reporting self.assertTrue(pty.screen.last_reported_cwd.endswith(self.home_dir)) q = os.path.join(self.home_dir, 'testing-cwd-notification-🐱') os.mkdir(q) diff --git a/kitty_tests/ssh.py b/kitty_tests/ssh.py index e0b4d62be..c6ca16135 100644 --- a/kitty_tests/ssh.py +++ b/kitty_tests/ssh.py @@ -78,6 +78,12 @@ print(' '.join(map(str, buf)))'''), lines=13, cols=77) self.ae(parse(conf, '1').env, {'a': 'c', 'b': 'b'}) self.ae(parse(conf, '2').env, {'a': 'c', 'b': 'b'}) + def test_ssh_bootstrap_sh_cmd_limit(self): + sh_script, _, _ = bootstrap_script(SSHOptions({'interpreter': 'sh'}), script_type='sh', remote_args=[], request_id='123-123') + rcmd = wrap_bootstrap_script(sh_script, 'sh') + # dropbear has a 9000 bytes maximum command length limit + self.assertLessEqual(sum(len(x) for x in rcmd), 9000) + @property @lru_cache() def all_possible_sh(self): diff --git a/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish b/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish index 48148ee67..05a1c928e 100644 --- a/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish +++ b/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish @@ -73,7 +73,7 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after if not contains "no-prompt-mark" $_ksi and not set -q __ksi_prompt_state function __ksi_mark_prompt_start --on-event fish_prompt - contains "$__ksi_prompt_state" post-exec pre-exec "" + test "$__ksi_prompt_state" != prompt-start and echo -en "\e]133;D\a" set --global __ksi_prompt_state prompt-start echo -en "\e]133;A\a" @@ -97,12 +97,13 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after # Enable CWD reporting if not contains "no-cwd" $_ksi - # This is actually builtin to fish but stupidly gated on TERM - # https://github.com/fish-shell/fish-shell/blob/master/share/functions/__fish_config_interactive.fish#L257 - function __ksi_report_cwd --on-variable PWD --description "Report PWD changes to the terminal" - status --is-command-substitution; and return - echo -en "\e]7;kitty-shell-cwd://$hostname$PWD\a" + # This function name is from fish and will override the builtin one if fish enabled this feature by default. + # We provide this to ensure that fish 3.2.0 and above will work. + # https://github.com/fish-shell/fish-shell/blob/3.2.0/share/functions/__fish_config_interactive.fish#L275 + function __update_cwd_osc --on-variable PWD -d "Report PWD changes to kitty" + status is-command-substitution + or echo -en "\e]7;kitty-shell-cwd://$hostname$PWD\a" end - __ksi_report_cwd + __update_cwd_osc end end diff --git a/shell-integration/ssh/bootstrap.py b/shell-integration/ssh/bootstrap.py index 26f456f57..3d3248e7d 100644 --- a/shell-integration/ssh/bootstrap.py +++ b/shell-integration/ssh/bootstrap.py @@ -21,7 +21,7 @@ data_dir = shell_integration_dir = '' request_data = int('REQUEST_DATA') leading_data = b'' HOME = os.path.expanduser('~') -login_shell = pwd.getpwuid(os.geteuid()).pw_shell or 'sh' +login_shell = pwd.getpwuid(os.geteuid()).pw_shell or os.environ.get('SHELL') or 'sh' def set_echo(fd, on=False): @@ -137,7 +137,13 @@ def compile_terminfo(base): if not tic: return tname = '.terminfo' + q = os.path.join(base, tname, '78', 'xterm-kitty') + if not os.path.exists(q): + os.makedirs(os.path.dirname(q), exist_ok=True) + os.symlink('../x/xterm-kitty', q) if os.path.exists('/usr/share/misc/terminfo.cdb'): + # NetBSD requires this + os.symlink('../../.terminfo.cdb', os.path.join(base, tname, 'x', 'xterm-kitty')) tname += '.cdb' os.environ['TERMINFO'] = os.path.join(HOME, tname) p = subprocess.Popen( @@ -148,10 +154,6 @@ def compile_terminfo(base): if rc != 0: getattr(sys.stderr, 'buffer', sys.stderr).write(p.stdout) raise SystemExit('Failed to compile the terminfo database') - q = os.path.join(base, tname, '78', 'xterm-kitty') - if not os.path.exists(q): - os.makedirs(os.path.dirname(q), exist_ok=True) - os.symlink('../x/xterm-kitty', q) def iter_base64_data(f): diff --git a/shell-integration/ssh/bootstrap.sh b/shell-integration/ssh/bootstrap.sh index 9aa719a4a..0a61ddc77 100644 --- a/shell-integration/ssh/bootstrap.sh +++ b/shell-integration/ssh/bootstrap.sh @@ -88,12 +88,20 @@ mv_files_and_dirs() { } compile_terminfo() { - # export TERMINFO tname=".terminfo" + # Ensure the 78 dir is present + if [ ! -f "$1/$tname/78/xterm-kitty" ]; then + command mkdir -p "$1/$tname/78" + command ln -sf "../x/xterm-kitty" "$1/$tname/78/xterm-kitty" + fi + if [ -e "/usr/share/misc/terminfo.cdb" ]; then - # NetBSD requires this see https://github.com/kovidgoyal/kitty/issues/4622 + # NetBSD requires this file, see https://github.com/kovidgoyal/kitty/issues/4622 + command ln -sf "../../.terminfo.cdb" "$1/$tname/x/xterm-kitty" tname=".terminfo.cdb" fi + + # export TERMINFO export TERMINFO="$HOME/$tname" # compile terminfo for this system @@ -101,12 +109,6 @@ compile_terminfo() { tic_out=$(command tic -x -o "$1/$tname" "$1/.terminfo/kitty.terminfo" 2>&1) [ $? = 0 ] || die "Failed to compile terminfo with err: $tic_out" fi - - # Ensure the 78 dir is present - if [ ! -f "$1/$tname/78/xterm-kitty" ]; then - command mkdir -p "$1/$tname/78" - command ln -sf "../x/xterm-kitty" "$1/$tname/78/xterm-kitty" - fi } read_base64_from_tty() { @@ -166,70 +168,43 @@ if [ -n "$leading_data" ]; then fi [ -f "$HOME/.terminfo/kitty.terminfo" ] || die "Incomplete extraction of ssh data" -login_shell_is_ok() { - if [ -n "$login_shell" -a -x "$login_shell" ]; then return 0; fi - return 1 -} - parse_passwd_record() { printf "%s" "$(command grep -o '[^:]*$')" } -using_getent() { - cmd=$(command -v getent) - if [ -n "$cmd" ]; then - output=$(command "$cmd" passwd "$USER" 2>/dev/null) - if [ $? = 0 ]; then - login_shell=$(echo $output | parse_passwd_record) - if login_shell_is_ok; then return 0; fi - fi - fi +login_shell_is_ok() { + [ -n "$1" ] && login_shell=$(echo $1 | parse_passwd_record) + [ -n "$login_shell" -a -x "$login_shell" ] && return 0 return 1 } +using_getent() { + cmd=$(command -v getent) && [ -n "$cmd" ] && output=$(command "$cmd" passwd "$USER" 2>/dev/null) \ + && login_shell_is_ok "$output" +} + using_id() { - cmd=$(command -v id) - if [ -n "$cmd" ]; then - output=$(command "$cmd" -P "$USER" 2>/dev/null) - if [ $? = 0 ]; then - login_shell=$(echo $output | parse_passwd_record) - if login_shell_is_ok; then return 0; fi - fi - fi - return 1 + cmd=$(command -v id) && [ -n "$cmd" ] && output=$(command "$cmd" -P "$USER" 2>/dev/null) \ + && login_shell_is_ok "$output" } using_python() { - if detect_python; then - output=$(command "$python" -c "import pwd, os; print(pwd.getpwuid(os.geteuid()).pw_shell)") - if [ $? = 0 ]; then - login_shell="$output" - if login_shell_is_ok; then return 0; fi - fi - fi - return 1 + detect_python && output=$(command "$python" -c "import pwd, os; print(pwd.getpwuid(os.geteuid()).pw_shell)") \ + && login_shell="$output" && login_shell_is_ok } using_perl() { - if detect_perl; then - output=$(command "$perl" -e 'my $shell = (getpwuid($<))[8]; print $shell') - if [ $? = 0 ]; then - login_shell="$output" - if login_shell_is_ok; then return 0; fi - fi - fi - return 1 + detect_perl && output=$(command "$perl" -e 'my $shell = (getpwuid($<))[8]; print $shell') \ + && login_shell="$output" && login_shell_is_ok } using_passwd() { - if [ -f "/etc/passwd" -a -r "/etc/passwd" ]; then - output=$(command grep "^$USER:" /etc/passwd 2>/dev/null) - if [ $? = 0 ]; then - login_shell=$(echo $output | parse_passwd_record) - if login_shell_is_ok; then return 0; fi - fi - fi - return 1 + [ -f "/etc/passwd" -a -r "/etc/passwd" ] && output=$(command grep "^$USER:" /etc/passwd 2>/dev/null) \ + && login_shell_is_ok "$output" +} + +using_shell_env() { + [ -n "$SHELL" ] && login_shell="$SHELL" && login_shell_is_ok } execute_with_python() { @@ -250,7 +225,7 @@ if [ -n "$KITTY_LOGIN_SHELL" ]; then login_shell="$KITTY_LOGIN_SHELL" unset KITTY_LOGIN_SHELL else - using_getent || using_id || using_python || using_perl || using_passwd || die "Could not detect login shell" + using_getent || using_id || using_python || using_perl || using_passwd || using_shell_env || login_shell="sh" fi shell_name=$(command basename $login_shell) [ -n "$login_cwd" ] && cd "$login_cwd" @@ -270,7 +245,8 @@ exec_zsh_with_integration() { export ZDOTDIR="$shell_integration_dir/zsh" exec "$login_shell" "-l" fi - unset KITTY_ORIG_ZDOTDIR # ensure this is not propagated + # ensure this is not propagated + unset KITTY_ORIG_ZDOTDIR } exec_fish_with_integration() { @@ -309,8 +285,10 @@ exec_with_shell_integration() { } execute_sh_with_posix_env() { - [ "$shell_name" = "sh" ] || return # only for sh as that is likely to be POSIX compliant - command "$login_shell" -l -c ":" > /dev/null 2> /dev/null && return # sh supports -l so use that + # only for sh as that is likely to be POSIX compliant + [ "$shell_name" = "sh" ] || return + # sh supports -l so use that + command "$login_shell" -l -c ":" > /dev/null 2> /dev/null && return [ -z "$shell_integration_dir" ] && die "Could not read data over tty ssh kitten cannot function" sh_dir="$shell_integration_dir/sh" command mkdir -p "$sh_dir" || die "Creating $sh_dir failed"