@@ -31,6 +31,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
- A new remote control command to :program:`change the tab color <kitty @
|
||||
set-tab-color>` (:iss:`1287`)
|
||||
|
||||
- A new remote control command to :program:`visually select a window <kitty @
|
||||
select-window>` (:iss:`4165`)
|
||||
|
||||
- Add support for reporting mouse events with pixel co-ordinates using the
|
||||
``SGR_PIXEL_PROTOCOL`` introduced in xterm 359
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ class Boss:
|
||||
self.update_check_process: Optional['PopenType[bytes]'] = None
|
||||
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
|
||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||
self.visual_window_select_callback: Optional[Callable[[Tab, Window], None]] = None
|
||||
self.visual_window_select_callback: Optional[Callable[[Optional[Tab], Optional[Window]], None]] = None
|
||||
self.startup_cursor_text_color = opts.cursor_text_color
|
||||
self.pending_sequences: Optional[SubSequenceMap] = None
|
||||
self.default_pending_action: Optional[KeyAction] = None
|
||||
@@ -459,7 +459,8 @@ class Boss:
|
||||
response = self._handle_remote_command(cmd, peer_id=peer_id)
|
||||
if response is None:
|
||||
return None
|
||||
return cmd_prefix + json.dumps(response).encode('utf-8') + terminator
|
||||
from kitty.remote_control import encode_response_for_peer
|
||||
return encode_response_for_peer(response)
|
||||
|
||||
data = json.loads(msg_bytes.decode('utf-8'))
|
||||
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
|
||||
@@ -814,7 +815,7 @@ class Boss:
|
||||
if matched_action is not None:
|
||||
self.dispatch_action(matched_action)
|
||||
|
||||
def visual_window_select_action(self, tab: Tab, callback: Callable[[Tab, Window], None], choose_msg: str) -> None:
|
||||
def visual_window_select_action(self, tab: Tab, callback: Callable[[Optional[Tab], Optional[Window]], None], choose_msg: str) -> None:
|
||||
self.visual_window_select_callback = callback
|
||||
if tab.current_layout.only_active_window_visible:
|
||||
self.select_window_in_tab_using_overlay(tab, choose_msg)
|
||||
@@ -846,15 +847,20 @@ class Boss:
|
||||
def visual_window_select_action_trigger(self, tab_id: int, window_id: int = 0) -> None:
|
||||
redirect_mouse_handling(False)
|
||||
self.clear_pending_sequences()
|
||||
cb = self.visual_window_select_callback
|
||||
self.visual_window_select_callback = None
|
||||
called = False
|
||||
for tab in self.all_tabs:
|
||||
if tab.id == tab_id:
|
||||
for window in tab:
|
||||
window.screen.set_window_number()
|
||||
if window.id == window_id:
|
||||
if self.visual_window_select_callback:
|
||||
self.visual_window_select_callback(tab, window)
|
||||
if cb is not None:
|
||||
cb(tab, window)
|
||||
called = True
|
||||
break
|
||||
self.visual_window_select_callback = None
|
||||
if not called and cb is not None:
|
||||
cb(None, None)
|
||||
|
||||
def focus_visible_window_mouse_handler(self, ev: WindowSystemMouseEvent) -> None:
|
||||
tab = self.active_tab
|
||||
|
||||
@@ -181,7 +181,7 @@ wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
|
||||
|
||||
static void* io_loop(void *data);
|
||||
static void* talk_loop(void *data);
|
||||
static void send_response(id_type peer_id, const char *msg, size_t msg_sz);
|
||||
static void send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz);
|
||||
static void wakeup_talk_loop(bool);
|
||||
static bool talk_thread_started = false;
|
||||
|
||||
@@ -423,9 +423,9 @@ parse_input(ChildMonitor *self) {
|
||||
if (!resp) PyErr_Print();
|
||||
}
|
||||
if (resp) {
|
||||
if (PyBytes_Check(resp)) send_response(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||
if (PyBytes_Check(resp)) send_response_to_peer(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||
Py_CLEAR(resp);
|
||||
} else send_response(msg->peer_id, NULL, 0);
|
||||
} else send_response_to_peer(msg->peer_id, NULL, 0);
|
||||
}
|
||||
free(msgs); msgs = NULL;
|
||||
}
|
||||
@@ -1624,7 +1624,7 @@ end:
|
||||
}
|
||||
|
||||
static void
|
||||
send_response(id_type peer_id, const char *msg, size_t msg_sz) {
|
||||
send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz) {
|
||||
bool wakeup = false;
|
||||
talk_mutex(lock);
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||
@@ -1699,11 +1699,22 @@ cocoa_set_menubar_title(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
||||
send_data_to_peer(PyObject *self UNUSED, PyObject *args) {
|
||||
char * msg; Py_ssize_t sz;
|
||||
unsigned long long peer_id;
|
||||
if (!PyArg_ParseTuple(args, "Ks#", &peer_id, &msg, &sz)) return NULL;
|
||||
send_response_to_peer(peer_id, msg, sz);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(safe_pipe, METH_VARARGS),
|
||||
{"add_timer", (PyCFunction)add_python_timer, METH_VARARGS, ""},
|
||||
{"remove_timer", (PyCFunction)remove_python_timer, METH_VARARGS, ""},
|
||||
METHODB(monitor_pid, METH_VARARGS),
|
||||
METHODB(send_data_to_peer, METH_VARARGS),
|
||||
METHODB(cocoa_set_menubar_title, METH_VARARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -1317,3 +1317,7 @@ def redirect_mouse_handling(yes: bool) -> None:
|
||||
|
||||
def get_click_interval() -> float:
|
||||
pass
|
||||
|
||||
|
||||
def send_data_to_peer(peer_id: int, data: Union[str, bytes]) -> None:
|
||||
pass
|
||||
|
||||
71
kitty/rc/select_window.py
Normal file
71
kitty/rc/select_window.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Union
|
||||
|
||||
from kitty.fast_data_types import send_data_to_peer
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions,
|
||||
RemoteCommand, ResponseType, Window, no_response
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kitty.cli_stub import LaunchRCOptions as CLIOptions
|
||||
from kitty.tabs import Tab
|
||||
|
||||
|
||||
class SelectWindow(RemoteCommand):
|
||||
|
||||
'''
|
||||
match: The tab to open the new window in
|
||||
self: Boolean, if True use tab the command was run in
|
||||
'''
|
||||
|
||||
short_desc = 'Visually select a window in the specified tab'
|
||||
desc = (
|
||||
' Prints out the id of the selected window. Other commands '
|
||||
' can then be chained to make use of it.'
|
||||
)
|
||||
options_spec = MATCH_TAB_OPTION + '\n\n' + '''\
|
||||
--response-timeout
|
||||
type=float
|
||||
default=60
|
||||
The time in seconds to wait for the user to select a window.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified the tab containing the window this command is run in is used
|
||||
instead of the active tab.
|
||||
'''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
ans = {'self': opts.self, 'match': opts.match}
|
||||
return ans
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
peer_id: int = payload_get('peer_id', missing=0)
|
||||
window_id: int = getattr(window, 'id', 0)
|
||||
|
||||
def callback(tab: Optional['Tab'], window: Optional[Window]) -> None:
|
||||
if window:
|
||||
response: Dict[str, Union[bool, int, str]] = {'ok': True, 'data': window.id}
|
||||
else:
|
||||
response = {'ok': False, 'error': 'No window selected'}
|
||||
if peer_id > 0:
|
||||
from kitty.remote_control import encode_response_for_peer
|
||||
send_data_to_peer(peer_id, encode_response_for_peer(response))
|
||||
elif window_id > 0:
|
||||
w = boss.window_id_map[window_id]
|
||||
if w is not None:
|
||||
w.send_cmd_response(response)
|
||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||
if tab:
|
||||
boss.visual_window_select_action(tab, callback, 'Choose window')
|
||||
break
|
||||
return no_response
|
||||
|
||||
|
||||
select_window = SelectWindow()
|
||||
@@ -24,6 +24,11 @@ from .typing import BossType, WindowType
|
||||
from .utils import TTYIO, parse_address_spec
|
||||
|
||||
|
||||
def encode_response_for_peer(response: Any) -> bytes:
|
||||
import json
|
||||
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]]:
|
||||
cmd = json.loads(serialized_cmd)
|
||||
v = cmd['version']
|
||||
@@ -188,7 +193,11 @@ def main(args: List[str]) -> None:
|
||||
send = create_basic_command(cmd, payload=payload, no_response=no_response)
|
||||
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||
response = do_io(global_opts.to, send, no_response, response_timeout)
|
||||
import socket
|
||||
try:
|
||||
response = do_io(global_opts.to, send, no_response, response_timeout)
|
||||
except (TimeoutError, socket.timeout):
|
||||
raise SystemExit(f'Timed out after {response_timeout} seconds waiting for response form kitty')
|
||||
if no_response:
|
||||
return
|
||||
if not response.get('ok'):
|
||||
|
||||
@@ -601,15 +601,17 @@ class Tab: # {{{
|
||||
over the windows for easy selection in this mode.
|
||||
''')
|
||||
def focus_visible_window(self) -> None:
|
||||
def callback(tab: Tab, window: Window) -> None:
|
||||
tab.set_active_window(window)
|
||||
def callback(tab: Optional[Tab], window: Optional[Window]) -> None:
|
||||
if tab and window:
|
||||
tab.set_active_window(window)
|
||||
|
||||
get_boss().visual_window_select_action(self, callback, 'Choose window to switch to')
|
||||
|
||||
@ac('win', 'Swap the current window with another window in the current tab, selected visually')
|
||||
def swap_with_window(self) -> None:
|
||||
def callback(tab: Tab, window: Window) -> None:
|
||||
tab.swap_active_window_with(window.id)
|
||||
def callback(tab: Optional[Tab], window: Optional[Window]) -> None:
|
||||
if tab and window:
|
||||
tab.swap_active_window_with(window.id)
|
||||
get_boss().visual_window_select_action(self, callback, 'Choose window to swap with')
|
||||
|
||||
@ac('win', 'Move active window to the top (make it the first window)')
|
||||
|
||||
Reference in New Issue
Block a user