From d53d92b89023ab4ed62ebb67c28f5f38dec8b864 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 31 Oct 2021 21:09:24 +0530 Subject: [PATCH] Proper handling of async responses to peers --- kitty/boss.py | 13 +++++++------ kitty/child-monitor.c | 2 +- kitty/rc/base.py | 3 ++- kitty/rc/select_window.py | 6 ++++-- kitty/remote_control.py | 8 +++++--- kitty/types.py | 4 ++++ 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 193f00a65..1e48ab614 100755 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -53,7 +53,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data from .tabs import ( SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager ) -from .types import _T, SingleKey, WindowSystemMouseEvent, ac +from .types import _T, AsyncResponse, SingleKey, WindowSystemMouseEvent, ac from .typing import PopenType, TypedDict from .utils import ( func_name, get_editor, get_new_os_window_size, get_primary_selection, @@ -435,7 +435,7 @@ class Boss: self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window - def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> Optional[Dict[str, Any]]: + def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> Union[Dict[str, Any], None, AsyncResponse]: from .remote_control import handle_cmd response = None window = window or None @@ -491,7 +491,7 @@ class Boss: tb = traceback.format_exc() self.show_error(_('remote_control mapping failed'), tb) - def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Optional[bytes]: + def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Union[bytes, bool, None]: cmd_prefix = b'\x1bP@kitty-cmd' terminator = b'\x1b\\' if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator): @@ -499,6 +499,8 @@ class Boss: response = self._handle_remote_command(cmd, peer_id=peer_id) if response is None: return None + if isinstance(response, AsyncResponse): + return True from kitty.remote_control import encode_response_for_peer return encode_response_for_peer(response) @@ -528,9 +530,8 @@ class Boss: def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None: response = self._handle_remote_command(cmd, window) - if response is not None: - if window is not None: - window.send_cmd_response(response) + if response is not None and not isinstance(response, AsyncResponse) and window is not None: + window.send_cmd_response(response) def _cleanup_tab_after_window_removal(self, src_tab: Tab) -> None: if len(src_tab) < 1: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 9e0b4b974..afb122c89 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -424,7 +424,7 @@ parse_input(ChildMonitor *self) { } if (resp) { if (PyBytes_Check(resp)) send_response_to_peer(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp)); - else send_response_to_peer(msg->peer_id, NULL, 0); + else if (resp == Py_None) send_response_to_peer(msg->peer_id, NULL, 0); Py_CLEAR(resp); } else send_response_to_peer(msg->peer_id, NULL, 0); } diff --git a/kitty/rc/base.py b/kitty/rc/base.py index 38166c573..7060aacd9 100644 --- a/kitty/rc/base.py +++ b/kitty/rc/base.py @@ -10,6 +10,7 @@ from typing import ( from kitty.cli import get_defaults_from_seq, parse_args, parse_option_spec from kitty.cli_stub import RCOptions as R from kitty.constants import appname, list_kitty_resources, running_in_kitty +from kitty.types import AsyncResponse if TYPE_CHECKING: from kitty.boss import Boss as B @@ -64,7 +65,7 @@ class PayloadGetter: no_response = NoResponse() payload_get = object() -ResponseType = Union[bool, str, None, NoResponse] +ResponseType = Union[bool, str, None, NoResponse, AsyncResponse] CmdReturnType = Union[Dict[str, Any], List[Any], Tuple[Any, ...], str, int, float, bool] CmdGenerator = Iterator[CmdReturnType] PayloadType = Optional[Union[CmdReturnType, CmdGenerator]] diff --git a/kitty/rc/select_window.py b/kitty/rc/select_window.py index f3edf6f87..ce1d3958c 100644 --- a/kitty/rc/select_window.py +++ b/kitty/rc/select_window.py @@ -3,9 +3,11 @@ from typing import TYPE_CHECKING, Optional +from kitty.types import AsyncResponse + from .base import ( MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, - RemoteCommand, ResponseType, Window, no_response + RemoteCommand, ResponseType, Window ) if TYPE_CHECKING: @@ -70,7 +72,7 @@ Exclude the currently active window from the list of windows to pick wids = set() boss.visual_window_select_action(tab, callback, payload_get('title') or 'Choose window', only_window_ids=wids) break - return no_response + return AsyncResponse() def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None: boss.cancel_current_visual_select() diff --git a/kitty/remote_control.py b/kitty/remote_control.py index cd4e3c81e..13d0d393f 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -6,9 +6,9 @@ import os import re import sys import types -from time import monotonic from contextlib import suppress from functools import partial +from time import monotonic from typing import ( Any, Dict, Generator, Iterable, List, Optional, Tuple, Union, cast ) @@ -21,10 +21,10 @@ from .rc.base import ( NoResponse, ParsingOfArgsFailed, PayloadGetter, all_command_names, command_for_name, parse_subcommand_cli ) +from .types import AsyncResponse from .typing import BossType, WindowType from .utils import TTYIO, parse_address_spec - active_async_requests: Dict[str, float] = {} @@ -33,7 +33,7 @@ def encode_response_for_peer(response: Any) -> bytes: return b'\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\x1b\\' -def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str, peer_id: int) -> Optional[Dict[str, Any]]: +def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str, peer_id: int) -> Union[Dict[str, Any], None, AsyncResponse]: cmd = json.loads(serialized_cmd) v = cmd['version'] no_response = cmd.get('no_response', False) @@ -63,6 +63,8 @@ def handle_cmd(boss: BossType, window: Optional[WindowType], serialized_cmd: str raise if isinstance(ans, NoResponse): return None + if isinstance(ans, AsyncResponse): + return ans response: Dict[str, Any] = {'ok': True} if ans is not None: response['data'] = ans diff --git a/kitty/types.py b/kitty/types.py index c6da783c2..83d1de6a8 100644 --- a/kitty/types.py +++ b/kitty/types.py @@ -74,6 +74,10 @@ class WindowSystemMouseEvent(NamedTuple): ConvertibleToNumbers = Union[str, bytes, int, float] +class AsyncResponse: + pass + + if TYPE_CHECKING: class RunOnce(Generic[_T]):