mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-05 23:51:29 +02:00
A facility to easily have child programs print to kitty stdout
Works better than the kitty-print DCS escape code as that tends to get interleaved with other writes to the tty since the loop infrastructure writes in a separate I/O thread.
This commit is contained in:
@@ -80,10 +80,10 @@ wait_for_terminal_ready(int fd) {
|
||||
static PyObject*
|
||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *argv_p, *env_p, *handled_signals_p;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd, forward_stdio;
|
||||
const char *kitten_exe;
|
||||
char *cwd, *exe;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiiiiiO!s", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd, &PyTuple_Type, &handled_signals_p, &kitten_exe)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiiiiiO!sp", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd, &PyTuple_Type, &handled_signals_p, &kitten_exe, &forward_stdio)) return NULL;
|
||||
char name[2048] = {0};
|
||||
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
char **argv = serialize_string_tuple(argv_p);
|
||||
@@ -129,15 +129,20 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||
safe_close(tfd, __FILE__, __LINE__);
|
||||
|
||||
int min_closed_fd = 3;
|
||||
if (forward_stdio) {
|
||||
if (safe_dup2(STDOUT_FILENO, min_closed_fd++) == -1) exit_on_err("dup2() failed for forwarded fd 1");
|
||||
if (safe_dup2(STDERR_FILENO, min_closed_fd++) == -1) exit_on_err("dup2() failed for forwarded fd 2");
|
||||
}
|
||||
// Redirect stdin/stdout/stderr to the pty
|
||||
if (safe_dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (safe_dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (safe_dup2(slave, STDOUT_FILENO) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (safe_dup2(slave, STDERR_FILENO) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (stdin_read_fd > -1) {
|
||||
if (safe_dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
if (safe_dup2(stdin_read_fd, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
safe_close(stdin_read_fd, __FILE__, __LINE__);
|
||||
safe_close(stdin_write_fd, __FILE__, __LINE__);
|
||||
} else {
|
||||
if (safe_dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
if (safe_dup2(slave, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
}
|
||||
safe_close(slave, __FILE__, __LINE__);
|
||||
safe_close(master, __FILE__, __LINE__);
|
||||
@@ -148,7 +153,7 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
safe_close(ready_read_fd, __FILE__, __LINE__);
|
||||
|
||||
// Close any extra fds inherited from parent
|
||||
for (int c = 3; c < 201; c++) safe_close(c, __FILE__, __LINE__);
|
||||
for (int c = min_closed_fd; c < 201; c++) safe_close(c, __FILE__, __LINE__);
|
||||
|
||||
environ = env;
|
||||
execvp(exe, argv);
|
||||
|
||||
@@ -218,12 +218,13 @@ class Child:
|
||||
def final_env(self) -> Dict[str, str]:
|
||||
from kitty.options.utils import DELETE_ENV_VAR
|
||||
env = default_env().copy()
|
||||
opts = fast_data_types.get_options()
|
||||
boss = fast_data_types.get_boss()
|
||||
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not getattr(sys, 'kitty_run_data').get(
|
||||
'lc_ctype_before_python') and not getattr(default_env, 'lc_ctype_set_by_user', False):
|
||||
del env['LC_CTYPE']
|
||||
env.update(self.env)
|
||||
env['TERM'] = fast_data_types.get_options().term
|
||||
env['TERM'] = opts.term
|
||||
env['COLORTERM'] = 'truecolor'
|
||||
env['KITTY_PID'] = getpid()
|
||||
env['KITTY_PUBLIC_KEY'] = boss.encryption_public_key
|
||||
@@ -240,7 +241,8 @@ class Child:
|
||||
if tdir:
|
||||
env['TERMINFO'] = tdir
|
||||
env['KITTY_INSTALLATION_DIR'] = kitty_base_dir
|
||||
opts = fast_data_types.get_options()
|
||||
if opts.forward_stdio:
|
||||
env['KITTY_STDIO_FORWARDED'] = '3'
|
||||
self.unmodified_argv = list(self.argv)
|
||||
if 'disabled' not in opts.shell_integration:
|
||||
from .shell_integration import modify_shell_environ
|
||||
@@ -256,6 +258,7 @@ class Child:
|
||||
def fork(self) -> Optional[int]:
|
||||
if self.forked:
|
||||
return None
|
||||
opts = fast_data_types.get_options()
|
||||
self.forked = True
|
||||
master, slave = openpty()
|
||||
stdin, self.stdin = self.stdin, None
|
||||
@@ -291,7 +294,7 @@ class Child:
|
||||
self.final_argv0 = argv[0]
|
||||
pid = fast_data_types.spawn(
|
||||
self.final_exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
|
||||
ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe())
|
||||
ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe(), opts.forward_stdio)
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
self.child_fd = master
|
||||
|
||||
@@ -1322,6 +1322,7 @@ def spawn(
|
||||
ready_write_fd: int,
|
||||
handled_signals: Tuple[int, ...],
|
||||
kitten_exe: str,
|
||||
forward_stdio: bool,
|
||||
) -> int:
|
||||
pass
|
||||
|
||||
|
||||
@@ -3082,6 +3082,16 @@ work. Changing this option by reloading the config will only affect newly
|
||||
created windows.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('forward_stdio', 'no', option_type='to_bool', long_text='''
|
||||
Forward STDOUT and STDERR of the kitty process to child processes
|
||||
as file descriptors 3 and 4. This is useful for debugging as it
|
||||
allows child processes to print to kitty's STDOUT directly. For example,
|
||||
:code:`echo hello world >&3` in a shell will print to the parent kitty's
|
||||
STDOUT. When enabled, this also sets the :code:`KITTY_STDIO_FORWARDED=3`
|
||||
environment variable so child processes know about the forwarding.
|
||||
''')
|
||||
|
||||
egr() # }}}
|
||||
|
||||
|
||||
|
||||
3
kitty/options/parse.py
generated
3
kitty/options/parse.py
generated
@@ -990,6 +990,9 @@ class Parser:
|
||||
def foreground(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['foreground'] = to_color(val)
|
||||
|
||||
def forward_stdio(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['forward_stdio'] = to_bool(val)
|
||||
|
||||
def hide_window_decorations(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['hide_window_decorations'] = hide_window_decorations(val)
|
||||
|
||||
|
||||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
@@ -365,6 +365,7 @@ option_names = ( # {{{
|
||||
'font_size',
|
||||
'force_ltr',
|
||||
'foreground',
|
||||
'forward_stdio',
|
||||
'hide_window_decorations',
|
||||
'inactive_border_color',
|
||||
'inactive_tab_background',
|
||||
@@ -526,6 +527,7 @@ class Options:
|
||||
font_size: float = 11.0
|
||||
force_ltr: bool = False
|
||||
foreground: Color = Color(221, 221, 221)
|
||||
forward_stdio: bool = False
|
||||
hide_window_decorations: int = 0
|
||||
inactive_border_color: Color = Color(204, 204, 204)
|
||||
inactive_tab_background: Color = Color(153, 153, 153)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import weakref
|
||||
from collections import deque
|
||||
from contextlib import contextmanager, suppress
|
||||
@@ -1210,8 +1209,7 @@ class Window:
|
||||
|
||||
def handle_remote_print(self, msg: str) -> None:
|
||||
text = process_remote_print(msg)
|
||||
print(text, end='', file=sys.stderr)
|
||||
sys.stderr.flush()
|
||||
print(text, end='', flush=True)
|
||||
|
||||
def send_cmd_response(self, response: Any) -> None:
|
||||
self.screen.send_escape_code_to_child(DCS, '@kitty-cmd' + json.dumps(response))
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -340,6 +341,14 @@ func (self *Term) GetSize() (*unix.Winsize, error) {
|
||||
func Ctermid() string { return "/dev/tty" }
|
||||
|
||||
func DebugPrintln(a ...any) {
|
||||
if fds := os.Getenv(`KITTY_STDIO_FORWARDED`); fds != "" {
|
||||
if fd, err := strconv.Atoi(fds); err == nil && fd > -1 {
|
||||
if f := os.NewFile(uintptr(fd), "<kitty_stdout>"); f != nil {
|
||||
fmt.Fprintln(f, a...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
term, err := OpenControllingTerm()
|
||||
if err == nil {
|
||||
defer term.Close()
|
||||
|
||||
Reference in New Issue
Block a user