Remote control: Allow matching for self window even over sockets when run inside a kitty window

Have kitty-tool send the value of KITTY_WINDOW_ID if present.
This commit is contained in:
Kovid Goyal 2022-12-30 12:14:42 +05:30
parent c18bff7821
commit 456af90ad2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 47 additions and 22 deletions

View File

@ -15,6 +15,7 @@ Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form:
"cmd": "command name", "cmd": "command name",
"version": "<kitty version>", "version": "<kitty version>",
"no_response": "<Optional Boolean>", "no_response": "<Optional Boolean>",
"kitty_window_id": "<Optional value of the KITTY_WINDOW_ID env var>",
"payload": "<Optional JSON object>" "payload": "<Optional JSON object>"
} }

View File

@ -505,6 +505,18 @@ class Boss:
return response return response
if not pcmd: if not pcmd:
return response return response
self_window: Optional[Window] = None
if window is not None:
self_window = window
else:
try:
swid = int(pcmd.get('kitty_window_id', 0))
except Exception:
pass
else:
if swid > 0:
self_window = self.window_id_map.get(swid)
extra_data: Dict[str, Any] = {} extra_data: Dict[str, Any] = {}
try: try:
allowed_unconditionally = ( allowed_unconditionally = (
@ -513,12 +525,12 @@ class Boss:
except PermissionError: except PermissionError:
return {'ok': False, 'error': 'Remote control disallowed by window specific password'} return {'ok': False, 'error': 'Remote control disallowed by window specific password'}
if allowed_unconditionally: if allowed_unconditionally:
return self._execute_remote_command(pcmd, window, peer_id) return self._execute_remote_command(pcmd, window, peer_id, self_window)
q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data) q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data)
if q is True: if q is True:
return self._execute_remote_command(pcmd, window, peer_id) return self._execute_remote_command(pcmd, window, peer_id, self_window)
if q is None: if q is None:
if self.ask_if_remote_cmd_is_allowed(pcmd, window, peer_id): if self.ask_if_remote_cmd_is_allowed(pcmd, window, peer_id, self_window):
return AsyncResponse() return AsyncResponse()
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'} response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
if q is False and pcmd.get('password'): if q is False and pcmd.get('password'):
@ -528,7 +540,9 @@ class Boss:
return None return None
return response return response
def ask_if_remote_cmd_is_allowed(self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0) -> bool: def ask_if_remote_cmd_is_allowed(
self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None
) -> bool:
from kittens.tui.operations import styled from kittens.tui.operations import styled
in_flight = 0 in_flight = 0
for w in self.window_id_map.values(): for w in self.window_id_map.values():
@ -547,7 +561,7 @@ class Boss:
'\x1b[m' + styled(_( '\x1b[m' + styled(_(
'Note that allowing the password will allow all future actions using the same password, in this kitty instance.' 'Note that allowing the password will allow all future actions using the same password, in this kitty instance.'
), dim=True, italic=True)), ), dim=True, italic=True)),
partial(self.remote_cmd_permission_received, pcmd, wid, peer_id), partial(self.remote_cmd_permission_received, pcmd, wid, peer_id, self_window),
'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password', 'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password',
window=window, default='a', hidden_text=hidden_text window=window, default='a', hidden_text=hidden_text
) )
@ -556,7 +570,7 @@ class Boss:
overlay_window.window_custom_type = 'remote_command_permission_dialog' overlay_window.window_custom_type = 'remote_command_permission_dialog'
return True return True
def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, choice: str) -> None: def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, self_window: Optional[Window], choice: str) -> None:
from .remote_control import encode_response_for_peer, set_user_password_allowed from .remote_control import encode_response_for_peer, set_user_password_allowed
response: RCResponse = None response: RCResponse = None
window = self.window_id_map.get(window_id) window = self.window_id_map.get(window_id)
@ -570,7 +584,7 @@ class Boss:
elif choice in ('a', 'p'): elif choice in ('a', 'p'):
if choice == 'p': if choice == 'p':
set_user_password_allowed(pcmd['password'], True) set_user_password_allowed(pcmd['password'], True)
response = self._execute_remote_command(pcmd, window, peer_id) response = self._execute_remote_command(pcmd, window, peer_id, self_window)
if window is not None and response is not None and not isinstance(response, AsyncResponse): if window is not None and response is not None and not isinstance(response, AsyncResponse):
window.send_cmd_response(response) window.send_cmd_response(response)
if peer_id > 0: if peer_id > 0:
@ -579,10 +593,12 @@ class Boss:
elif not isinstance(response, AsyncResponse): elif not isinstance(response, AsyncResponse):
send_data_to_peer(peer_id, encode_response_for_peer(response)) send_data_to_peer(peer_id, encode_response_for_peer(response))
def _execute_remote_command(self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0) -> RCResponse: def _execute_remote_command(
self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0, self_window: Optional[Window] = None
) -> RCResponse:
from .remote_control import handle_cmd from .remote_control import handle_cmd
try: try:
response = handle_cmd(self, window, pcmd, peer_id) response = handle_cmd(self, window, pcmd, peer_id, self_window)
except Exception as err: except Exception as err:
import traceback import traceback
response = {'ok': False, 'error': str(err)} response = {'ok': False, 'error': str(err)}

View File

@ -169,7 +169,9 @@ def close_active_stream(stream_id: str) -> None:
active_streams.pop(stream_id, None) active_streams.pop(stream_id, None)
def handle_cmd(boss: BossType, window: Optional[WindowType], cmd: Dict[str, Any], peer_id: int) -> Union[Dict[str, Any], None, AsyncResponse]: def handle_cmd(
boss: BossType, window: Optional[WindowType], cmd: Dict[str, Any], peer_id: int, self_window: Optional[WindowType]
) -> Union[Dict[str, Any], None, AsyncResponse]:
v = cmd['version'] v = cmd['version']
no_response = cmd.get('no_response', False) no_response = cmd.get('no_response', False)
if tuple(v)[:2] > version[:2]: if tuple(v)[:2] > version[:2]:
@ -194,14 +196,14 @@ def handle_cmd(boss: BossType, window: Optional[WindowType], cmd: Dict[str, Any]
payload['async_id'] = async_id payload['async_id'] = async_id
if 'cancel_async' in cmd: if 'cancel_async' in cmd:
active_async_requests.pop(async_id, None) active_async_requests.pop(async_id, None)
c.cancel_async_request(boss, window, PayloadGetter(c, payload)) c.cancel_async_request(boss, self_window or window, PayloadGetter(c, payload))
return None return None
active_async_requests[async_id] = monotonic() active_async_requests[async_id] = monotonic()
if len(active_async_requests) > 32: if len(active_async_requests) > 32:
oldest = next(iter(active_async_requests)) oldest = next(iter(active_async_requests))
del active_async_requests[oldest] del active_async_requests[oldest]
try: try:
ans = c.response_from_kitty(boss, window, PayloadGetter(c, payload)) ans = c.response_from_kitty(boss, self_window or window, PayloadGetter(c, payload))
except Exception: except Exception:
if no_response: # don't report errors if --no-response was used if no_response: # don't report errors if --no-response was used
return None return None

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"os" "os"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time" "time"
"unicode/utf16" "unicode/utf16"
@ -268,6 +269,10 @@ func send_rc_command(io_data *rc_io_data) (err error) {
if err != nil { if err != nil {
return err return err
} }
wid, err := strconv.Atoi(os.Getenv("KITTY_WINDOW_ID"))
if err == nil && wid > 0 {
io_data.rc.KittyWindowId = uint(wid)
}
err = create_serializer(global_options.password, "", io_data) err = create_serializer(global_options.password, "", io_data)
if err != nil { if err != nil {
return return

View File

@ -12,6 +12,7 @@ type RemoteControlCmd struct {
CancelAsync bool `json:"cancel_async,omitempty"` CancelAsync bool `json:"cancel_async,omitempty"`
Stream bool `json:"stream,omitempty"` Stream bool `json:"stream,omitempty"`
StreamId string `json:"stream_id,omitempty"` StreamId string `json:"stream_id,omitempty"`
KittyWindowId uint `json:"kitty_window_id,omitempty"`
Payload any `json:"payload,omitempty"` Payload any `json:"payload,omitempty"`
} }