diff --git a/kitty/boss.py b/kitty/boss.py index 4460f4c89..a43e10643 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -701,6 +701,17 @@ def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: cmd += ['-c', c] self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}) + def get_line( + self, msg: str, # can contain newlines and ANSI formatting + callback: Callable[..., None], # called with the answer or empty string when aborted + window: Optional[Window] = None, # the window associated with the confirmation + is_password: bool = False + ) -> None: + def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None: + callback(res.get('response') or '') + cmd = ['--type', 'password' if is_password else 'line', '--message', msg] + self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}) + def confirm_tab_close(self, tab: Tab) -> None: x = get_options().confirm_os_window_close num = tab.number_of_windows_with_running_programs if x < 0 else len(tab) diff --git a/kitty/parser.c b/kitty/parser.c index 772c34c8c..4bd859252 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -1084,6 +1084,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { } else IF_SIMPLE_PREFIX("kitty-print|", screen_handle_print) } else IF_SIMPLE_PREFIX("kitty-echo|", screen_handle_echo) } else IF_SIMPLE_PREFIX("kitty-ssh|", screen_handle_ssh) + } else IF_SIMPLE_PREFIX("kitty-askpass|", screen_handle_askpass) #undef IF_SIMPLE_PREFIX } else { REPORT_ERROR("Unrecognized DCS @ code: 0x%x", screen->parser_buf[1]); diff --git a/kitty/screen.c b/kitty/screen.c index b5fcbfcb8..624f32c51 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2114,6 +2114,12 @@ screen_handle_ssh(Screen *self, PyObject *msg) { CALLBACK("handle_remote_ssh", "O", msg); } +void +screen_handle_askpass(Screen *self, PyObject *msg) { + CALLBACK("handle_remote_askpass", "O", msg); +} + + void screen_request_capabilities(Screen *self, char c, PyObject *q) { static char buf[128]; diff --git a/kitty/screen.h b/kitty/screen.h index 736651c68..6f5bad355 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -210,6 +210,7 @@ void screen_report_color_stack(Screen *); void screen_handle_print(Screen *, PyObject *cmd); void screen_handle_echo(Screen *, PyObject *cmd); void screen_handle_ssh(Screen *, PyObject *cmd); +void screen_handle_askpass(Screen *, PyObject *cmd); void screen_designate_charset(Screen *, uint32_t which, uint32_t as); void screen_use_latin1(Screen *, bool); void set_title(Screen *self, PyObject*); diff --git a/kitty/window.py b/kitty/window.py index 2c0c1ecfb..ec9d48591 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -885,6 +885,35 @@ def handle_remote_ssh(self, msg: str) -> None: for line in get_ssh_data(msg, f'{os.getpid()}-{self.id}'): self.write_to_child(line) + def handle_remote_askpass(self, msg: str) -> None: + from .shm import SharedMemory + with SharedMemory(name=msg, readonly=True) as shm: + shm.seek(1) + data = json.loads(shm.read_data_with_size()) + + def callback(ans: Any) -> None: + data = json.dumps(ans) + with SharedMemory(name=msg) as shm: + shm.seek(1) + shm.write_data_with_size(data) + shm.flush() + shm.seek(0) + shm.write(b'\x01') + + prompt: str = data['prompt'] + if data['type'] == 'confirm': + get_boss().confirm( + prompt, callback, window=self, confirm_on_cancel=bool(data.get('confirm_on_cancel')), + confirm_on_accept=bool(data.get('confirm_on_accept'))) + elif data['type'] == 'choose': + get_boss().choose( + prompt, callback, *data['choices'], window=self, default=data.get('default', '')) + elif data['type'] == 'get_line': + get_boss().get_line( + prompt, callback, window=self, is_password=bool(data.get('is_password'))) + else: + log_error(f'Ignoring ask request with unknown type: {data["type"]}') + def handle_remote_print(self, msg: str) -> None: text = process_remote_print(msg) print(text, end='', file=sys.stderr) diff --git a/shell-integration/ssh/askpass.py b/shell-integration/ssh/askpass.py new file mode 100755 index 000000000..98bf13060 --- /dev/null +++ b/shell-integration/ssh/askpass.py @@ -0,0 +1,24 @@ +#!/usr/bin/env -S kitty +launch +# License: GPLv3 Copyright: 2022, Kovid Goyal + +import json +import os +import struct +import sys + +from kitty.shm import SharedMemory + +msg = sys.argv[-1] +prompt = os.environ.get('SSH_ASKPASS_PROMPT', '') +is_confirm = prompt == 'confirm' +ask_cmdline = ['-m', msg, '--type', 'yesno' if is_confirm else 'password'] +if is_confirm: + ask_cmdline += ['--default', 'y'] +data = json.dumps(ask_cmdline).encode('utf-8') +sz = struct.pack('>I', len(data)) +with SharedMemory(size=len(data) + len(sz) + 1, unlink_on_exit=True, prefix=f'askpass-{os.getpid()}-') as shm, open(os.ctermid(), 'wb') as tty: + shm.write(b'\0') + shm.write(sz) + shm.write(data) + shm.flush() + print(f'\x1bP@kitty-ask|{shm.name}\x1b\\', flush=True)