From bde4a41ed440a63a102383023ffe4a0c74565e0e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 4 Aug 2023 11:12:31 +0530 Subject: [PATCH] Remote control: Allow using a random TCP socket as the remote control socket and also allow using TCP sockets in kitty.conf --- docs/changelog.rst | 2 ++ kitty/boss.py | 11 +++++++---- kitty/cli.py | 4 ++-- kitty/main.py | 7 +++++-- kitty/options/definition.py | 16 ++++++++-------- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 76f538418..d4e0657c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -47,6 +47,8 @@ Detailed list of changes - kitten @ ls: Allow limiting output to matched windows/tabs (:iss:`6520`) +- Remote control: Allow using a random TCP socket as the remote control socket and also allow using TCP sockets in :opt:`listen_on` + - X11: Print an error to :file:`STDERR` instead of refusing to start when the user sets a custom window icon larger than 128x128 (:iss:`6507`) 0.29.2 [2023-07-27] diff --git a/kitty/boss.py b/kitty/boss.py index b050b8952..b95890022 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -169,14 +169,18 @@ class OSWindowDict(TypedDict): wm_name: str -def listen_on(spec: str) -> int: +def listen_on(spec: str) -> Tuple[int, str]: import socket family, address, socket_path = parse_address_spec(spec) s = socket.socket(family) atexit.register(remove_socket_file, s, socket_path) s.bind(address) s.listen() - return s.fileno() + if isinstance(address, tuple): + h, resolved_port = s.getsockname() + sfamily, host, port = spec.split(':', 2) + spec = f'{sfamily}:{host}:{resolved_port}' + return s.fileno(), spec def data_for_at(w: Optional[Window], arg: str, add_wrap_markers: bool = False) -> Optional[str]: @@ -357,8 +361,7 @@ class Boss: self.allow_remote_control = 'n' self.listening_on = '' if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'): - listen_fd = listen_on(args.listen_on) - self.listening_on = args.listen_on + listen_fd, self.listening_on = listen_on(args.listen_on) self.child_monitor = ChildMonitor( self.on_child_death, DumpCommands(args) if args.dump_commands or args.dump_bytes else None, diff --git a/kitty/cli.py b/kitty/cli.py index 33e688b88..f91c95f80 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -948,8 +948,8 @@ you can send commands to it with :italic:`{appname} @` using the :italic:`{appname} @` within a kitty window, there is no need to specify the :option:`{appname} @ --to` option as it will automatically read from the environment. Note that this will be ignored unless :opt:`allow_remote_control` -is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. For UNIX -sockets, this can also be specified in :file:`{conf_name}.conf`. +is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. This can +also be specified in :file:`kitty.conf`. --start-as diff --git a/kitty/main.py b/kitty/main.py index 08b63ac6f..cacce5fc8 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -359,7 +359,7 @@ def macos_cmdline(argv_args: List[str]) -> List[str]: def expand_listen_on(listen_on: str, from_config_file: bool) -> str: listen_on = expandvars(listen_on) - if '{kitty_pid}' not in listen_on and from_config_file: + if '{kitty_pid}' not in listen_on and from_config_file and listen_on.startswith('unix:'): listen_on += '-{kitty_pid}' listen_on = listen_on.replace('{kitty_pid}', str(os.getpid())) if listen_on.startswith('unix:'): @@ -370,6 +370,9 @@ def expand_listen_on(listen_on: str, from_config_file: bool) -> str: elif not os.path.isabs(path): import tempfile listen_on = f'unix:{os.path.join(tempfile.gettempdir(), path)}' + elif listen_on.startswith('tcp:') or listen_on.startswith('tcp6:'): + if from_config_file: # use a random port + listen_on = ':'.join(listen_on.split(':', 2)[:2]) + ':0' return listen_on @@ -432,7 +435,7 @@ def setup_manpath(env: Dict[str, str]) -> None: def setup_environment(opts: Options, cli_opts: CLIOptions) -> None: from_config_file = False - if not cli_opts.listen_on and opts.listen_on.startswith('unix:'): + if not cli_opts.listen_on: cli_opts.listen_on = opts.listen_on from_config_file = True if cli_opts.listen_on: diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 13c1b3db3..ab150cceb 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2882,18 +2882,18 @@ prevents any form of remote control. The meaning of the various values are: opt('listen_on', 'none', long_text=''' -Listen to the specified UNIX socket for remote control connections. Note that -this will apply to all kitty instances. It can be overridden by the -:option:`kitty --listen-on` command line option, which also supports listening -on a TCP socket. This option accepts only UNIX sockets, such as +Listen to the specified socket for remote control connections. Note that this +will apply to all kitty instances. It can be overridden by the :option:`kitty +--listen-on` command line option. For UNIX sockets, such as :code:`unix:${TEMP}/mykitty` or :code:`unix:@mykitty` (on Linux). Environment variables are expanded and relative paths are resolved with respect to the temporary directory. If :code:`{kitty_pid}` is present, then it is replaced by the PID of the kitty process, otherwise the PID of the kitty process is -appended to the value, with a hyphen. See the help for :option:`kitty ---listen-on` for more details. Note that this will be ignored unless -:opt:`allow_remote_control` is set to either: :code:`yes`, :code:`socket` or -:code:`socket-only`. +appended to the value, with a hyphen. For TCP sockets such as +:code:`tcp:localhost:0` a random port is always used even if a non-zero port +number is specified. See the help for :option:`kitty --listen-on` for more +details. Note that this will be ignored unless :opt:`allow_remote_control` is +set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. Changing this option by reloading the config is not supported. ''' )