Fix action triggering by using kitten output instead of remote control

The RC approach sent actions targeting the overlay window itself rather
than the underlying window. Switch to the standard kitten output pattern:
the Go kitten outputs the selected action definition via
KittenOutputSerializer, and handle_result in Python calls boss.combine()
with the correct target window after the overlay closes.
This commit is contained in:
Daniel M German
2026-02-22 21:12:36 -08:00
parent 33b5b0a339
commit 6fa122af11
4 changed files with 25 additions and 40 deletions

View File

@@ -12,18 +12,17 @@ import (
"github.com/kovidgoyal/kitty/tools/cli" "github.com/kovidgoyal/kitty/tools/cli"
"github.com/kovidgoyal/kitty/tools/fzf" "github.com/kovidgoyal/kitty/tools/fzf"
"github.com/kovidgoyal/kitty/tools/tui"
"github.com/kovidgoyal/kitty/tools/tui/loop" "github.com/kovidgoyal/kitty/tools/tui/loop"
"github.com/kovidgoyal/kitty/tools/utils"
"github.com/kovidgoyal/kitty/tools/wcswidth" "github.com/kovidgoyal/kitty/tools/wcswidth"
) )
var _ = fmt.Print
// JSON data structures matching Python collect_keys_data output // JSON data structures matching Python collect_keys_data output
type Binding struct { type Binding struct {
Key string `json:"key"` Key string `json:"key"`
Action string `json:"action"` Action string `json:"action"`
ActionDisplay string `json:"action_display"` ActionDisplay string `json:"action_display"`
Definition string `json:"definition"`
Help string `json:"help"` Help string `json:"help"`
LongHelp string `json:"long_help"` LongHelp string `json:"long_help"`
Category string Category string
@@ -62,6 +61,7 @@ type Handler struct {
selected_idx int selected_idx int
scroll_offset int scroll_offset int
input_data InputData input_data InputData
result string // action definition to execute after exit
} }
func (h *Handler) initialize() (string, error) { func (h *Handler) initialize() (string, error) {
@@ -553,33 +553,12 @@ func (h *Handler) triggerSelected() {
h.lp.Beep() h.lp.Beep()
return return
} }
h.result = b.Definition
// Send RC action command via DCS escape code.
// Do not set "self" or "match_window" — the action runs globally via
// the boss, same as if the user had pressed the keyboard shortcut.
payload := map[string]any{
"action": b.ActionDisplay,
}
rc := utils.RemoteControlCmd{
Cmd: "action",
Version: [3]int{0, 26, 0},
NoResponse: true,
}
rc.Payload = payload
data, err := json.Marshal(rc)
if err != nil {
h.lp.Beep()
return
}
h.lp.QueueWriteString("\x1bP@kitty-cmd")
h.lp.QueueWriteString(string(data))
h.lp.QueueWriteString("\x1b\\")
h.lp.Quit(0) h.lp.Quit(0)
} }
func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) { func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
output := tui.KittenOutputSerializer()
lp, err := loop.New() lp, err := loop.New()
if err != nil { if err != nil {
return 1, err return 1, err
@@ -606,6 +585,12 @@ func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
return return
} }
rc = lp.ExitCode() rc = lp.ExitCode()
if handler.result != "" {
s, serr := output(map[string]string{"action": handler.result})
if serr == nil {
fmt.Println(s)
}
}
return return
} }

View File

@@ -57,6 +57,7 @@ def collect_keys_data(opts: Any) -> dict[str, Any]:
'key': key_repr, 'key': key_repr,
'action': action_name, 'action': action_name,
'action_display': action_repr, 'action_display': action_repr,
'definition': d.definition or action_name,
'help': help_text, 'help': help_text,
'long_help': long_help, 'long_help': long_help,
}) })
@@ -98,13 +99,11 @@ def main(args: list[str]) -> None:
raise SystemExit('This must be run as kitten command-palette') raise SystemExit('This must be run as kitten command-palette')
main.allow_remote_control = True # type: ignore[attr-defined]
main.remote_control_password = True # type: ignore[attr-defined]
@result_handler(has_ready_notification=True) @result_handler(has_ready_notification=True)
def handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType) -> None: def handle_result(args: list[str], data: dict[str, Any], target_window_id: int, boss: BossType) -> None:
pass if data and 'action' in data:
w = boss.window_id_map.get(target_window_id)
boss.combine(data['action'], w)
help_text = 'Browse and trigger keyboard shortcuts and actions' help_text = 'Browse and trigger keyboard shortcuts and actions'

View File

@@ -13,27 +13,27 @@ func sampleInputJSON() string {
"modes": { "modes": {
"": { "": {
"Copy/paste": [ "Copy/paste": [
{"key": "ctrl+shift+c", "action": "copy_to_clipboard", "action_display": "copy_to_clipboard", "help": "Copy the selected text from the active window to the clipboard", "long_help": ""}, {"key": "ctrl+shift+c", "action": "copy_to_clipboard", "action_display": "copy_to_clipboard", "definition": "copy_to_clipboard", "help": "Copy the selected text from the active window to the clipboard", "long_help": ""},
{"key": "ctrl+shift+v", "action": "paste_from_clipboard", "action_display": "paste_from_clipboard", "help": "Paste from the clipboard to the active window", "long_help": ""} {"key": "ctrl+shift+v", "action": "paste_from_clipboard", "action_display": "paste_from_clipboard", "definition": "paste_from_clipboard", "help": "Paste from the clipboard to the active window", "long_help": ""}
], ],
"Scrolling": [ "Scrolling": [
{"key": "ctrl+shift+up", "action": "scroll_line_up", "action_display": "scroll_line_up", "help": "Scroll up one line", "long_help": ""}, {"key": "ctrl+shift+up", "action": "scroll_line_up", "action_display": "scroll_line_up", "definition": "scroll_line_up", "help": "Scroll up one line", "long_help": ""},
{"key": "ctrl+shift+down", "action": "scroll_line_down", "action_display": "scroll_line_down", "help": "Scroll down one line", "long_help": ""} {"key": "ctrl+shift+down", "action": "scroll_line_down", "action_display": "scroll_line_down", "definition": "scroll_line_down", "help": "Scroll down one line", "long_help": ""}
], ],
"Window management": [ "Window management": [
{"key": "ctrl+shift+enter", "action": "new_window", "action_display": "new_window", "help": "Open a new window", "long_help": ""} {"key": "ctrl+shift+enter", "action": "new_window", "action_display": "new_window", "definition": "new_window", "help": "Open a new window", "long_help": ""}
] ]
}, },
"mw": { "mw": {
"Miscellaneous": [ "Miscellaneous": [
{"key": "left", "action": "neighboring_window", "action_display": "neighboring_window left", "help": "Focus neighbor window", "long_help": ""}, {"key": "left", "action": "neighboring_window", "action_display": "neighboring_window left", "definition": "neighboring_window left", "help": "Focus neighbor window", "long_help": ""},
{"key": "esc", "action": "pop_keyboard_mode", "action_display": "pop_keyboard_mode", "help": "Pop keyboard mode", "long_help": ""} {"key": "esc", "action": "pop_keyboard_mode", "action_display": "pop_keyboard_mode", "definition": "pop_keyboard_mode", "help": "Pop keyboard mode", "long_help": ""}
] ]
} }
}, },
"mouse": [ "mouse": [
{"key": "left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection normal", "help": "", "long_help": ""}, {"key": "left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection normal", "definition": "mouse_selection normal", "help": "", "long_help": ""},
{"key": "ctrl+left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection rectangle", "help": "", "long_help": ""} {"key": "ctrl+left press ungrabbed", "action": "mouse_selection", "action_display": "mouse_selection rectangle", "definition": "mouse_selection rectangle", "help": "", "long_help": ""}
], ],
"mode_order": ["", "mw"], "mode_order": ["", "mw"],
"category_order": { "category_order": {

View File

@@ -28,6 +28,7 @@ class TestCommandPalette(BaseTest):
self.assertIn('key', b) self.assertIn('key', b)
self.assertIn('action', b) self.assertIn('action', b)
self.assertIn('action_display', b) self.assertIn('action_display', b)
self.assertIn('definition', b)
self.assertIn('help', b) self.assertIn('help', b)
self.assertIn('long_help', b) self.assertIn('long_help', b)
self.assertIsInstance(b['key'], str) self.assertIsInstance(b['key'], str)