From 4e04e34438365e0d3abfab444f42f671e721d626 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 19 Apr 2026 22:54:44 +0530 Subject: [PATCH] Refactor dnd cmd queueing API --- docs/dnd-protocol.rst | 2 +- tools/cmd/mouse_demo/main.go | 27 ++++++++++++--------------- tools/tui/loop/api.go | 31 +++++++++++++++++++------------ 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 7e53c312c..b282e04bb 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -11,7 +11,7 @@ programs. There is one central escape code used for this protocol, which is of the form:: - OSC _dnd_code ; metadata ; base64 encoded payload ST + OSC _dnd_code ; metadata ; payload ST Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)`` and ST is ``ESC \\ (0x1b 0x5c)``. The ``metadata`` is a colon separated list of ``key=value`` pairs. diff --git a/tools/cmd/mouse_demo/main.go b/tools/cmd/mouse_demo/main.go index 035f23809..3adf024d5 100644 --- a/tools/cmd/mouse_demo/main.go +++ b/tools/cmd/mouse_demo/main.go @@ -354,11 +354,7 @@ func Run(args []string) (rc int, err error) { // Request this file via the protocol. dnd.file_read_size = 0 dnd.collecting = "file" - lp.QueueDnDData(map[string]string{ - "t": "r", - "x": strconv.Itoa(dnd.uri_list_mime_idx), - "y": strconv.Itoa(dnd.file_read_idx + 1), - }, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r', X: dnd.uri_list_mime_idx, Y: dnd.file_read_idx + 1}) return } // Non-file URI: record as-is with no size info. @@ -367,7 +363,7 @@ func Run(args []string) (rc int, err error) { } // All files processed; finish the drop. dnd.collecting = "" - lp.QueueDnDData(map[string]string{"t": "r"}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r'}) dnd.has_drop_data = true draw_screen() } @@ -402,11 +398,11 @@ func Run(args []string) (rc int, err error) { } } if len(accepted_mimes) > 0 { - lp.QueueDnDData(map[string]string{"t": "m", "o": "1"}, strings.Join(accepted_mimes, " "), false) + lp.QueueDnDData(loop.DndCommand{Type: 'm', Operation: 1, Payload: []byte(strings.Join(accepted_mimes, " "))}) } } else { // Not over drop region; reject the drag. - lp.QueueDnDData(map[string]string{"t": "m", "o": "0"}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'm'}) } draw_screen() case 'M': @@ -427,17 +423,17 @@ func Run(args []string) (rc int, err error) { for idx, m := range mimes { if m == "text/plain" { dnd.collecting = "text/plain" - lp.QueueDnDData(map[string]string{"t": "r", "x": strconv.Itoa(idx + 1)}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r', X: idx + 1}) return nil } } if dnd.uri_list_mime_idx > 0 { dnd.collecting = "text/uri-list" - lp.QueueDnDData(map[string]string{"t": "r", "x": strconv.Itoa(dnd.uri_list_mime_idx)}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r', X: dnd.uri_list_mime_idx}) return nil } // Nothing to collect; signal done. - lp.QueueDnDData(map[string]string{"t": "r"}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r'}) dnd.has_drop_data = true draw_screen() case 'r': @@ -451,7 +447,7 @@ func Run(args []string) (rc int, err error) { if cmd.Xp > 1 { // Directory: close the handle. fi.is_dir = true - lp.QueueDnDData(map[string]string{"t": "r", "Y": strconv.Itoa(cmd.Xp)}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r', Yp: cmd.Xp}) } else if cmd.Xp == 1 { fi.is_link = true fi.size = dnd.file_read_size @@ -487,7 +483,8 @@ func Run(args []string) (rc int, err error) { // Now request text/uri-list if available. if dnd.uri_list_mime_idx > 0 { dnd.collecting = "text/uri-list" - lp.QueueDnDData(map[string]string{"t": "r", "x": strconv.Itoa(dnd.uri_list_mime_idx)}, "", false) + lp.QueueDnDData( + loop.DndCommand{Type: 'r', X: dnd.uri_list_mime_idx}) return nil } case "text/uri-list": @@ -512,7 +509,7 @@ func Run(args []string) (rc int, err error) { } } dnd.collecting = "" - lp.QueueDnDData(map[string]string{"t": "r"}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r'}) dnd.has_drop_data = true draw_screen() } else { @@ -537,7 +534,7 @@ func Run(args []string) (rc int, err error) { } else if !is_file_response { // Error getting MIME data; finish the drop with what we have. dnd.collecting = "" - lp.QueueDnDData(map[string]string{"t": "r"}, "", false) + lp.QueueDnDData(loop.DndCommand{Type: 'r'}) dnd.has_drop_data = true draw_screen() } diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index e35a9f524..fcb16ce78 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -669,15 +669,22 @@ func (self *Loop) DrawSizedText(text string, spec SizedText) { self.QueueWriteString(b.String()) } -func (self *Loop) QueueDnDData(metadata map[string]string, payload string, as_base64 bool) IdType { +func (self *Loop) QueueDnDData(cmd DndCommand) IdType { b := strings.Builder{} b.Grow(64) - fmt.Fprintf(&b, "\x1b]%d;", kitty.DndCode) - sep := "" - for key, val := range metadata { - fmt.Fprintf(&b, "%s%s=%s", sep, key, val) - sep = ":" + as_base64 := cmd.Type == 'r' + fmt.Fprintf(&b, "\x1b]%d;t=%c", kitty.DndCode, cmd.Type) + add := func(key byte, val int) { + if val != 0 { + fmt.Fprintf(&b, ":%c=%d", key, val) + } } + add('o', cmd.Operation) + add('x', cmd.X) + add('y', cmd.Y) + add('X', cmd.Xp) + add('Y', cmd.Yp) + payload := utils.UnsafeBytesToString(cmd.Payload) payload_sz := len(payload) if payload_sz == 0 { b.WriteString("\x1b\\") @@ -696,7 +703,7 @@ func (self *Loop) QueueDnDData(metadata map[string]string, payload string, as_ba is_last := end >= len(payload) end = min(end, len(payload)) if i == 0 { - fmt.Fprintf(&b, "%sm=%d;", sep, utils.IfElse(is_last, 0, 1)) + fmt.Fprintf(&b, ":m=%d;", utils.IfElse(is_last, 0, 1)) self.QueueWriteString(b.String()) } else { self.QueueWriteString(fmt.Sprintf("\x1b]%d;m=%d;", kitty.DndCode, utils.IfElse(is_last, 0, 1))) @@ -722,14 +729,14 @@ func effective_machine_id(m string) string { } func (self *Loop) StartAcceptingDrops(machine_id string, mime_types ...string) { - self.QueueDnDData(map[string]string{"t": "a"}, strings.Join(mime_types, " "), false) + self.QueueDnDData(DndCommand{Type: 'a', Payload: []byte(strings.Join(mime_types, " "))}) if m := effective_machine_id(machine_id); m != "" { - self.QueueDnDData(map[string]string{"t": "a", "x": "1"}, m, false) + self.QueueDnDData(DndCommand{Type: 'a', X: 1, Payload: []byte(m)}) } } func (self *Loop) StopAcceptingDrops() { - self.QueueDnDData(map[string]string{"t": "A"}, "", false) + self.QueueDnDData(DndCommand{Type: 'A'}) } func (self *Loop) StartOfferingDrags(machine_id string) { @@ -737,9 +744,9 @@ func (self *Loop) StartOfferingDrags(machine_id string) { if m := effective_machine_id(machine_id); m != "" { payload = m } - self.QueueDnDData(map[string]string{"t": "o", "x": "1"}, payload, false) + self.QueueDnDData(DndCommand{Type: 'o', X: 1, Payload: []byte(payload)}) } func (self *Loop) StopOfferingDrags() { - self.QueueDnDData(map[string]string{"t": "o", "x": "2"}, "", false) + self.QueueDnDData(DndCommand{Type: 'o', X: 2}) }