Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor tweaks to ssh kitten and shell integration #4794

Merged
merged 7 commits into from Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion kittens/ssh/main.py
Expand Up @@ -73,7 +73,7 @@ def add_data_as_file(tf: tarfile.TarFile, arcname: str, data: Union[str, bytes])
def filter_from_globs(*pats: str) -> Callable[[tarfile.TarInfo], Optional[tarfile.TarInfo]]:
def filter(tarinfo: tarfile.TarInfo) -> Optional[tarfile.TarInfo]:
for junk_dir in ('.DS_Store', '__pycache__'):
for pat in (f'*/{junk_dir}', '*/{junk_dir}/*'):
for pat in (f'*/{junk_dir}', f'*/{junk_dir}/*'):
if fnmatch.fnmatch(tarinfo.name, pat):
return None
for pat in pats:
Expand Down
10 changes: 1 addition & 9 deletions kitty_tests/shell_integration.py
Expand Up @@ -213,16 +213,8 @@ def redrawn():
pty.write_to_child('i')
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM)
pty.send_cmd_to_child('_set_key default')

# pipestatus
pty.send_cmd_to_child('clear;false|true|false')
pty.send_cmd_to_child('echo $pipestatus $status')
pty.wait_till(lambda: pty.screen_contents().count(right_prompt) == 2)
self.ae(pty.last_cmd_output(), '1 0 1 1')
pty.send_cmd_to_child('_set_status_prompt')
pty.send_cmd_to_child('false|true|false')
pty.wait_till(lambda: pty.screen_contents().count(right_prompt) == 4)
self.assertTrue(str(pty.screen.line(pty.screen.cursor.y)).startswith(f'1 0 1 1 {fish_prompt}'))
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM)

pty.send_cmd_to_child('exit')

Expand Down
15 changes: 13 additions & 2 deletions shell-integration/bash/kitty.bash
Expand Up @@ -155,8 +155,19 @@ _ksi_main() {
fi

if [[ "${_ksi_prompt[title]}" == "y" ]]; then
if [[ -n "$SSH_TTY$SSH2_TTY" ]]; then
_ksi_prompt[hostname_prefix]="\h: ";
if [[ -z "$KITTY_PID" ]]; then
if [[ -n "$SSH_TTY" || -n "$SSH2_TTY$KITTY_WINDOW_ID" ]]; then
# connected to most SSH servers
# or use ssh kitten to connected to some SSH servers that do not set SSH_TTY
_ksi_prompt[hostname_prefix]="\h: ";
else
if [[ -n "$(builtin command -v who)" && "$(builtin command who -m 2> /dev/null)" =~ "\([a-fA-F.:0-9]+\)$" ]]; then
# the shell integration script is installed manually on the remote system
# the environment variables are cleared after sudo
# OpenSSH's sshd creates entries in utmp for every login so use those
_ksi_prompt[hostname_prefix]="\h: ";
fi
fi
fi
# see https://www.gnu.org/software/bash/manual/html_node/Controlling-the-Prompt.html#Controlling-the-Prompt
# we use suffix here because some distros add title setting to their bashrc files by default
Expand Down
Expand Up @@ -19,7 +19,7 @@ end

status is-interactive || exit 0
not functions -q __ksi_schedule || exit 0
# Check fish version 3.3.0+ efficiently and fallback to check the last working version 3.2.0, exit on outdated versions.
# Check fish version 3.3.0+ efficiently and fallback to check the minimum working version 3.2.0, exit on outdated versions.
# "Warning: Update fish to version 3.3.0+ to enable kitty shell integration.\n"
set -q fish_killring || set -q status_generation || string match -qnv "3.1.*" "$version"
or echo -en "\eP@kitty-print|V2FybmluZzogVXBkYXRlIGZpc2ggdG8gdmVyc2lvbiAzLjMuMCsgdG8gZW5hYmxlIGtpdHR5IHNoZWxsIGludGVncmF0aW9uLgo=\e\\" && exit 0 || exit 0
Expand Down Expand Up @@ -80,21 +80,6 @@ function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after
end
__ksi_mark_prompt_start

functions -c fish_prompt __ksi_original_fish_prompt
function fish_prompt
# fish trims one trailing newline from the output of fish_prompt, so
# we need to do the same. See https://github.com/kovidgoyal/kitty/issues/4032
# op is a list because fish splits on newlines in command substitution
set --local op (__ksi_original_fish_prompt)
# print all but last element of the list, each followed by a new line
set -q op[2]
and printf '%s\n' $op[1..-2]
# print the last component without a newline
printf '%s' $op[-1]
set --global __ksi_prompt_state prompt-end
echo -en "\e]133;B\a"
end

function __ksi_mark_output_start --on-event fish_preexec
set --global __ksi_prompt_state pre-exec
echo -en "\e]133;C\a"
Expand Down
45 changes: 22 additions & 23 deletions shell-integration/ssh/bootstrap.py
Expand Up @@ -103,21 +103,20 @@ def move(src, base_dest):


def compile_terminfo(base):
if not shutil.which('tic'):
tic = shutil.which('tic')
if not tic:
return
tname = '.terminfo'
if os.path.exists('/usr/share/misc/terminfo.cdb'):
tname += '.cdb'
os.environ['TERMINFO'] = os.path.join(HOME, tname)
tic = shutil.which('tic')
if tic:
cp = subprocess.run(
[tic, '-x', '-o', os.path.join(base, tname), os.path.join(base, '.terminfo', 'kitty.terminfo')],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if cp.returncode != 0:
sys.stderr.buffer.write(cp.stdout)
raise SystemExit('Failed to compile the terminfo database')
cp = subprocess.run(
[tic, '-x', '-o', os.path.join(base, tname), os.path.join(base, '.terminfo', 'kitty.terminfo')],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if cp.returncode != 0:
sys.stderr.buffer.write(cp.stdout)
raise SystemExit('Failed to compile the terminfo database')


def get_data():
Expand Down Expand Up @@ -160,24 +159,15 @@ def get_data():
move(tdir + '/root', '/')


def exec_bash_with_integration():
os.environ['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash')
os.environ['KITTY_BASH_INJECT'] = '1'
if not os.environ.get('HISTFILE'):
os.environ['HISTFILE'] = os.path.join(HOME, '.bash_history')
os.environ['KITTY_BASH_UNEXPORT_HISTFILE'] = '1'
os.execlp(login_shell, os.path.basename('login_shell'), '--posix')


def exec_zsh_with_integration():
zdotdir = os.environ.get('ZDOTDIR') or ''
if not zdotdir:
zdotdir = HOME
os.environ.pop('KITTY_ORIG_ZDOTDIR', None) # ensure this is not propagated
else:
os.environ['KITTY_ORIG_ZDOTDIR'] = zdotdir
# dont prevent zsh-new-user from running
for q in '.zshrc .zshenv .zprofile .zlogin'.split():
# dont prevent zsh-newuser-install from running
for q in ('.zshrc', '.zshenv', '.zprofile', '.zlogin'):
if os.path.exists(os.path.join(HOME, q)):
os.environ['ZDOTDIR'] = shell_integration_dir + '/zsh'
os.execlp(login_shell, os.path.basename(login_shell), '-l')
Expand All @@ -193,14 +183,23 @@ def exec_fish_with_integration():
os.execlp(login_shell, os.path.basename(login_shell), '-l')


def exec_bash_with_integration():
os.environ['ENV'] = os.path.join(shell_integration_dir, 'bash', 'kitty.bash')
os.environ['KITTY_BASH_INJECT'] = '1'
if not os.environ.get('HISTFILE'):
os.environ['HISTFILE'] = os.path.join(HOME, '.bash_history')
os.environ['KITTY_BASH_UNEXPORT_HISTFILE'] = '1'
os.execlp(login_shell, os.path.basename('login_shell'), '--posix')


def exec_with_shell_integration():
shell_name = os.path.basename(login_shell).lower()
if shell_name == 'bash':
exec_bash_with_integration()
if shell_name == 'zsh':
exec_zsh_with_integration()
if shell_name == 'fish':
exec_fish_with_integration()
if shell_name == 'bash':
exec_bash_with_integration()


def main():
Expand Down
99 changes: 48 additions & 51 deletions shell-integration/ssh/bootstrap.sh
Expand Up @@ -81,7 +81,7 @@ if [ -z "$HOSTNAME" ]; then
if [ -z "$hostname" ]; then
hostname=$(command hostnamectl hostname 2> /dev/null)
if [ -z "$hostname" ]; then
hostname="_";
hostname="_"
fi
fi
else
Expand All @@ -95,17 +95,17 @@ if [ -z "$USER" ]; then USER=$(command whoami 2> /dev/null); fi
# ask for the SSH data
leading_data=""

init_tty && trap 'cleanup_on_bootstrap_exit' EXIT
init_tty && trap "cleanup_on_bootstrap_exit" EXIT
[ "$tty_ok" = "y" ] && dcs_to_kitty "ssh" "id="REQUEST_ID":hostname="$hostname":pwfile="PASSWORD_FILENAME":user="$USER":pw="DATA_PASSWORD""
record_separator=$(printf "\036")

mv_files_and_dirs() {
cwd="$PWD";
cd "$1";
cwd="$PWD"
cd "$1"
command find . -type d -exec mkdir -p "$2/{}" ";"
command find . -type l -exec sh -c "tgt=\$(command readlink -n \"{}\"); command ln -sf \"\$tgt\" \"$2/{}\"; command rm -f \"{}\"" ";"
command find . -type f -exec mv "{}" "$2/{}" ";"
cd "$cwd";
cd "$cwd"
}

compile_terminfo() {
Expand All @@ -130,18 +130,18 @@ untar_and_read_env() {
# extract the tar file atomically, in the sense that any file from the
# tarfile is only put into place after it has been fully written to disk

tdir=$(command mktemp -d "$HOME/.kitty-ssh-kitten-untar-XXXXXXXXXXXX");
[ $? = 0 ] || die "Creating temp directory failed";
read_n_bytes_from_tty "$1" | command base64 -d | command tar xjf - --no-same-owner -C "$tdir";
data_file="$tdir/data.sh";
[ -f "$data_file" ] && . "$data_file";
tdir=$(command mktemp -d "$HOME/.kitty-ssh-kitten-untar-XXXXXXXXXXXX")
[ $? = 0 ] || die "Creating temp directory failed"
read_n_bytes_from_tty "$1" | command base64 -d | command tar xjf - --no-same-owner -C "$tdir"
data_file="$tdir/data.sh"
[ -f "$data_file" ] && . "$data_file"
data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR"
compile_terminfo "$tdir/home"
mv_files_and_dirs "$tdir/home" "$HOME"
[ -e "$tdir/root" ] && mv_files_and_dirs "$tdir/root" ""
command rm -rf "$tdir";
[ -z "KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty";
unset KITTY_SSH_KITTEN_DATA_DIR;
command rm -rf "$tdir"
[ -z "KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty"
unset KITTY_SSH_KITTEN_DATA_DIR
}

read_record() {
Expand Down Expand Up @@ -175,25 +175,23 @@ if [ "$tty_ok" = "y" ]; then
printf "\r\033[K" > /dev/tty
fi
shell_integration_dir="$data_dir/shell-integration"
[ -f "$HOME/.terminfo/kitty.terminfo" ] || die "Incomplete extraction of ssh data";

[ -f "$HOME/.terminfo/kitty.terminfo" ] || die "Incomplete extraction of ssh data"
fi


login_shell_is_ok() {
if [ -z "$login_shell" -o ! -x "$login_shell" ]; then return 1; fi
case "$login_shell" in
*sh) return 0;
esac
return 1;
return 1
}

detect_python() {
python=$(command -v python3)
if [ -z "$python" ]; then python=$(command -v python2); fi
if [ -z "$python" ]; then python=$(command -v python); fi
if [ -z "$python" -o ! -x "$python" ]; then return 1; fi
return 0;
return 0
}

parse_passwd_record() {
Expand All @@ -203,43 +201,43 @@ parse_passwd_record() {
using_getent() {
cmd=$(command -v getent)
if [ -n "$cmd" ]; then
output=$($cmd passwd $USER 2>/dev/null)
output=$(command $cmd passwd $USER 2>/dev/null)
if [ $? = 0 ]; then
login_shell=$(echo $output | parse_passwd_record);
login_shell=$(echo $output | parse_passwd_record)
if login_shell_is_ok; then return 0; fi
fi
fi
return 1;
return 1
}

using_id() {
cmd=$(command -v id)
if [ -n "$cmd" ]; then
output=$($cmd -P $USER 2>/dev/null)
output=$(command $cmd -P $USER 2>/dev/null)
if [ $? = 0 ]; then
login_shell=$(echo $output | parse_passwd_record);
login_shell=$(echo $output | parse_passwd_record)
if login_shell_is_ok; then return 0; fi
fi
fi
return 1;
return 1
}

using_passwd() {
if [ -f "/etc/passwd" -a -r "/etc/passwd" ]; then
output=$(command grep "^$USER:" /etc/passwd 2>/dev/null)
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=$(echo $output | parse_passwd_record);
login_shell=$output
if login_shell_is_ok; then return 0; fi
fi
fi
return 1;
return 1
}

using_python() {
if detect_python; then
output=$(command $python -c "import pwd, os; print(pwd.getpwuid(os.geteuid()).pw_shell)")
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=$output;
login_shell=$(echo $output | parse_passwd_record)
if login_shell_is_ok; then return 0; fi
fi
fi
Expand All @@ -250,14 +248,14 @@ execute_with_python() {
if detect_python; then
exec $python -c "import os; os.execlp('$login_shell', '-' '$shell_name')"
fi
return 1;
return 1
}

if [ -n "$KITTY_LOGIN_SHELL" ]; then
login_shell="$KITTY_LOGIN_SHELL"
unset KITTY_LOGIN_SHELL
else
using_getent || using_id || using_python || using_passwd || die "Could not detect login shell";
using_getent || using_id || using_python || using_passwd || die "Could not detect login shell"
fi
shell_name=$(command basename $login_shell)

Expand All @@ -272,25 +270,14 @@ if [ "$tty_ok" = "n" ]; then
fi
fi


exec_bash_with_integration() {
export ENV="$shell_integration_dir/bash/kitty.bash"
export KITTY_BASH_INJECT="1"
if [ -z "$HISTFILE" ]; then
export HISTFILE="$HOME/.bash_history"
export KITTY_BASH_UNEXPORT_HISTFILE="1"
fi
exec "$login_shell" "--posix"
}

exec_zsh_with_integration() {
zdotdir="$ZDOTDIR"
if [ -z "$zdotdir" ]; then
zdotdir=~
else
export KITTY_ORIG_ZDOTDIR="$zdotdir"
fi
# dont prevent zsh-new-user from running
# dont prevent zsh-newuser-install from running
if [ -f "$zdotdir/.zshrc" -o -f "$zdotdir/.zshenv" -o -f "$zdotdir/.zprofile" -o -f "$zdotdir/.zlogin" ]; then
export ZDOTDIR="$shell_integration_dir/zsh"
exec "$login_shell" "-l"
Expand All @@ -308,17 +295,27 @@ exec_fish_with_integration() {
exec "$login_shell" "-l"
}

exec_bash_with_integration() {
export ENV="$shell_integration_dir/bash/kitty.bash"
export KITTY_BASH_INJECT="1"
if [ -z "$HISTFILE" ]; then
export HISTFILE="$HOME/.bash_history"
export KITTY_BASH_UNEXPORT_HISTFILE="1"
fi
exec "$login_shell" "--posix"
}

exec_with_shell_integration() {
case "$shell_name" in
"zsh")
exec_zsh_with_integration
;;
"bash")
exec_bash_with_integration
;;
"fish")
exec_fish_with_integration
;;
"bash")
exec_bash_with_integration
;;
esac
}

Expand All @@ -333,7 +330,7 @@ case "$KITTY_SHELL_INTEGRATION" in
(*)
# not blank
q=$(printf "%s" "$KITTY_SHELL_INTEGRATION" | command grep '\bno-rc\b')
if [ -z "$q" ]; then
if [ -z "$q" ]; then
exec_with_shell_integration
# exec failed, unset
unset KITTY_SHELL_INTEGRATION
Expand Down