mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-04 13:42:33 +02:00
Improve mouse-demo DnD: region-only drops, file sizes, remote file support
Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/df38243b-dc36-470c-bb8f-e1e8162bb1ab Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
864ef70484
commit
d7fcf30250
@@ -4,9 +4,14 @@ package mouse_demo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -33,8 +38,25 @@ func dnd_escape(metadata, payload string) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func dnd_start_accepting() string {
|
||||
return dnd_escape("t=a", dnd_accepted_mimes)
|
||||
// get_machine_id returns the machine id in the format expected by the DnD
|
||||
// protocol ("1:" followed by HMAC-SHA256 of /etc/machine-id).
|
||||
func get_machine_id() string {
|
||||
data, err := os.ReadFile("/etc/machine-id")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
data = bytes.TrimRight(data, "\n")
|
||||
mac := hmac.New(sha256.New, []byte("tty-dnd-protocol-machine-id"))
|
||||
mac.Write(data)
|
||||
return "1:" + hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func dnd_start_accepting(machine_id string) string {
|
||||
result := dnd_escape("t=a", dnd_accepted_mimes)
|
||||
if machine_id != "" {
|
||||
result += dnd_escape("t=a:x=1", machine_id)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dnd_stop_accepting() string {
|
||||
@@ -45,37 +67,124 @@ func dnd_accept_drag(mimes string) string {
|
||||
return dnd_escape("t=m:o=1", mimes)
|
||||
}
|
||||
|
||||
func dnd_request_data(mime string) string {
|
||||
return dnd_escape("t=r", mime)
|
||||
func dnd_reject_drag() string {
|
||||
return dnd_escape("t=m:o=0", "")
|
||||
}
|
||||
|
||||
// dnd_request_mime_data requests MIME type data by 1-based index.
|
||||
func dnd_request_mime_data(idx int) string {
|
||||
return dnd_escape(fmt.Sprintf("t=r:x=%d", idx), "")
|
||||
}
|
||||
|
||||
// dnd_request_file requests individual file data by MIME index and file subindex.
|
||||
func dnd_request_file(mime_idx, file_idx int) string {
|
||||
return dnd_escape(fmt.Sprintf("t=r:x=%d:y=%d", mime_idx, file_idx), "")
|
||||
}
|
||||
|
||||
// dnd_close_dir closes a directory handle by sending t=r:Y=handle.
|
||||
func dnd_close_dir(handle int) string {
|
||||
return dnd_escape(fmt.Sprintf("t=r:Y=%d", handle), "")
|
||||
}
|
||||
|
||||
func dnd_finish() string {
|
||||
return dnd_escape("t=r", "")
|
||||
}
|
||||
|
||||
// file_info holds metadata about a dropped file.
|
||||
type file_info struct {
|
||||
name string
|
||||
size int64
|
||||
is_dir bool
|
||||
is_link bool
|
||||
err_msg string
|
||||
}
|
||||
|
||||
type dnd_state struct {
|
||||
dragging bool
|
||||
drag_mimes []string
|
||||
drop_mimes []string
|
||||
collecting string // MIME type currently being collected
|
||||
collect_buf strings.Builder
|
||||
plain_text string
|
||||
uri_list []string
|
||||
dragging bool
|
||||
drag_mimes []string
|
||||
// Current drag cell position (set from t=m events).
|
||||
drag_cell_x int
|
||||
drag_cell_y int
|
||||
drag_over_box bool // true when the drag is currently over the drop region
|
||||
|
||||
// Drop handling.
|
||||
drop_mimes []string
|
||||
uri_list_mime_idx int // 1-based index of text/uri-list in drop_mimes (0 = not present)
|
||||
is_remote bool // X=1 received in URI list response (client is on different machine)
|
||||
|
||||
// Collection state: what we're currently collecting.
|
||||
// Values: "", "text/plain", "text/uri-list", "file"
|
||||
collecting string
|
||||
collect_buf strings.Builder
|
||||
|
||||
// Results of drop.
|
||||
plain_text string
|
||||
uri_list []string // parsed file:// URIs from text/uri-list
|
||||
file_infos []file_info // one entry per uri_list entry
|
||||
has_drop_data bool
|
||||
|
||||
// File reading state.
|
||||
file_read_idx int // 1-based index of file currently being read (0 = not reading files)
|
||||
file_read_size int64 // bytes accumulated so far for current file
|
||||
|
||||
// Layout: drop box position on screen (set during draw_screen).
|
||||
drop_box_start_row int
|
||||
drop_box_end_row int
|
||||
drop_box_width int
|
||||
}
|
||||
|
||||
func (d *dnd_state) reset_drag() {
|
||||
d.dragging = false
|
||||
d.drag_over_box = false
|
||||
d.drag_mimes = nil
|
||||
}
|
||||
|
||||
func (d *dnd_state) reset_drop_data() {
|
||||
d.drop_mimes = nil
|
||||
d.uri_list_mime_idx = 0
|
||||
d.is_remote = false
|
||||
d.collecting = ""
|
||||
d.collect_buf.Reset()
|
||||
d.plain_text = ""
|
||||
d.uri_list = nil
|
||||
d.file_infos = nil
|
||||
d.has_drop_data = false
|
||||
d.file_read_idx = 0
|
||||
d.file_read_size = 0
|
||||
}
|
||||
|
||||
// is_over_drop_box returns true if the given cell coordinates are within the
|
||||
// drop box region as tracked from the last draw_screen call.
|
||||
func (d *dnd_state) is_over_drop_box(cell_x, cell_y int) bool {
|
||||
// Before the first draw, drop_box_end_row equals drop_box_start_row (both zero).
|
||||
if d.drop_box_end_row <= d.drop_box_start_row {
|
||||
return false
|
||||
}
|
||||
return cell_y >= d.drop_box_start_row && cell_y <= d.drop_box_end_row &&
|
||||
cell_x >= 0 && cell_x < d.drop_box_width
|
||||
}
|
||||
|
||||
// filename_from_uri extracts the base filename from a file:// URI.
|
||||
func filename_from_uri(uri string) string {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil || u.Scheme != "file" {
|
||||
return uri
|
||||
}
|
||||
return filepath.Base(u.Path)
|
||||
}
|
||||
|
||||
// format_size formats a byte count as a human-readable size string.
|
||||
func format_size(n int64) string {
|
||||
switch {
|
||||
case n < 1024:
|
||||
return fmt.Sprintf("%d B", n)
|
||||
case n < 1024*1024:
|
||||
return fmt.Sprintf("%.1f KiB", float64(n)/1024)
|
||||
case n < 1024*1024*1024:
|
||||
return fmt.Sprintf("%.1f MiB", float64(n)/(1024*1024))
|
||||
default:
|
||||
return fmt.Sprintf("%.1f GiB", float64(n)/(1024*1024*1024))
|
||||
}
|
||||
}
|
||||
|
||||
func draw_rounded_box(lp *loop.Loop, width int, lines []string) {
|
||||
@@ -86,11 +195,13 @@ func draw_rounded_box(lp *loop.Loop, width int, lines []string) {
|
||||
// Top border
|
||||
lp.QueueWriteString("╭" + strings.Repeat("─", inner_width) + "╮\r\n")
|
||||
for _, line := range lines {
|
||||
if len(line) > inner_width {
|
||||
line = line[:inner_width]
|
||||
// Truncate to inner_width runes (not bytes) to handle multi-byte chars safely.
|
||||
runes := []rune(line)
|
||||
if len(runes) > inner_width {
|
||||
runes = runes[:inner_width]
|
||||
}
|
||||
padding := inner_width - len(line)
|
||||
lp.QueueWriteString("│" + line + strings.Repeat(" ", padding) + "│\r\n")
|
||||
padding := inner_width - len(runes)
|
||||
lp.QueueWriteString("│" + string(runes) + strings.Repeat(" ", padding) + "│\r\n")
|
||||
}
|
||||
// Bottom border
|
||||
lp.QueueWriteString("╰" + strings.Repeat("─", inner_width) + "╯\r\n")
|
||||
@@ -147,6 +258,54 @@ func Run(args []string) (rc int, err error) {
|
||||
var current_mouse_event *loop.MouseEvent
|
||||
var dnd dnd_state
|
||||
|
||||
machine_id := get_machine_id()
|
||||
|
||||
// build_box_lines computes the drop box content lines based on current state.
|
||||
build_box_lines := func() []string {
|
||||
if dnd.drag_over_box {
|
||||
lines := []string{"Drop here! MIME types:"}
|
||||
for _, m := range dnd.drag_mimes {
|
||||
lines = append(lines, " "+m)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
if dnd.dragging {
|
||||
return []string{"Drag in window — move over this box to drop"}
|
||||
}
|
||||
if dnd.has_drop_data {
|
||||
lines := []string{}
|
||||
if dnd.plain_text != "" {
|
||||
lines = append(lines, "text/plain: "+dnd.plain_text)
|
||||
}
|
||||
if len(dnd.file_infos) > 0 {
|
||||
for i, fi := range dnd.file_infos {
|
||||
if i >= len(dnd.uri_list) {
|
||||
break
|
||||
}
|
||||
name := filename_from_uri(dnd.uri_list[i])
|
||||
if fi.err_msg != "" {
|
||||
lines = append(lines, name+": error: "+fi.err_msg)
|
||||
} else if fi.is_dir {
|
||||
lines = append(lines, name+"/ [directory]")
|
||||
} else if fi.is_link {
|
||||
lines = append(lines, name+" [symlink]")
|
||||
} else {
|
||||
lines = append(lines, name+" "+format_size(fi.size))
|
||||
}
|
||||
}
|
||||
} else if len(dnd.uri_list) > 0 {
|
||||
for _, u := range dnd.uri_list {
|
||||
lines = append(lines, " "+u)
|
||||
}
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
lines = []string{"Drop received (no recognized content)"}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
return []string{"Drop files here"}
|
||||
}
|
||||
|
||||
draw_screen := func() {
|
||||
lp.StartAtomicUpdate()
|
||||
defer lp.EndAtomicUpdate()
|
||||
@@ -161,36 +320,47 @@ func Run(args []string) (rc int, err error) {
|
||||
sh = int(s.HeightCells)
|
||||
}
|
||||
|
||||
// y tracks the next row to be printed; used for both content drawing
|
||||
// and computing the drop box position.
|
||||
y := 0
|
||||
|
||||
if current_mouse_event == nil {
|
||||
lp.Println(`Move the mouse or click to see mouse events`)
|
||||
y++
|
||||
lp.Println("Hover the mouse over the names below to see the shapes")
|
||||
y++
|
||||
lp.Println()
|
||||
y++
|
||||
num_cols := max(1, sw/col_width)
|
||||
colfmt := "%-" + strconv.Itoa(col_width) + "s"
|
||||
for pos := 0; pos < len(all_pointer_shapes); {
|
||||
num_cols := max(1, sw/col_width)
|
||||
colfmt := "%-" + strconv.Itoa(col_width) + "s"
|
||||
for c := 0; c < num_cols && pos < len(all_pointer_shapes); c++ {
|
||||
lp.Printf(colfmt, all_pointer_shape_names[pos])
|
||||
pos++
|
||||
}
|
||||
lp.Println()
|
||||
y++
|
||||
}
|
||||
} else if current_mouse_event.Event_type == loop.MOUSE_LEAVE {
|
||||
lp.Println("Mouse has left the window")
|
||||
y++
|
||||
} else {
|
||||
lp.Printf("Position: %d, %d (pixels)\r\n", current_mouse_event.Pixel.X, current_mouse_event.Pixel.Y)
|
||||
y++
|
||||
lp.Printf("Cell : %d, %d\r\n", current_mouse_event.Cell.X, current_mouse_event.Cell.Y)
|
||||
y++
|
||||
lp.Printf("Type : %s\r\n", current_mouse_event.Event_type)
|
||||
y := 3
|
||||
y++
|
||||
if current_mouse_event.Buttons != loop.NO_MOUSE_BUTTON {
|
||||
lp.Println(current_mouse_event.Buttons.String())
|
||||
y += 1
|
||||
y++
|
||||
}
|
||||
if mods := current_mouse_event.Mods.String(); mods != "" {
|
||||
lp.Printf("Modifiers: %s\r\n", mods)
|
||||
y += 1
|
||||
y++
|
||||
}
|
||||
lp.Println("Hover the mouse over the names below to see the shapes")
|
||||
y += 1
|
||||
y++
|
||||
|
||||
num_cols := max(1, sw/col_width)
|
||||
pos := 0
|
||||
@@ -218,7 +388,7 @@ func Run(args []string) (rc int, err error) {
|
||||
lp.QueueWriteString("\x1b[m")
|
||||
pos++
|
||||
}
|
||||
y += 1
|
||||
y++
|
||||
lp.Println()
|
||||
}
|
||||
lp.PopPointerShape()
|
||||
@@ -227,43 +397,62 @@ func Run(args []string) (rc int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the drop area below the pointer shapes list
|
||||
// Draw the drop area below the pointer shapes list.
|
||||
// y is now the row where the blank separator will be printed.
|
||||
lp.Println()
|
||||
y++ // blank separator line
|
||||
|
||||
box_width := min(sw, 60)
|
||||
if dnd.dragging {
|
||||
lines := []string{"Dragging over window. MIME types:"}
|
||||
for _, m := range dnd.drag_mimes {
|
||||
lines = append(lines, " "+m)
|
||||
}
|
||||
draw_rounded_box(lp, box_width, lines)
|
||||
} else if dnd.has_drop_data {
|
||||
lines := []string{}
|
||||
if dnd.plain_text != "" {
|
||||
lines = append(lines, "text/plain: "+dnd.plain_text)
|
||||
}
|
||||
for _, u := range dnd.uri_list {
|
||||
lines = append(lines, "URL: "+u)
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
lines = []string{"Drop received (no recognized content)"}
|
||||
}
|
||||
draw_rounded_box(lp, box_width, lines)
|
||||
dnd.drop_box_width = box_width
|
||||
box_lines := build_box_lines()
|
||||
dnd.drop_box_start_row = y
|
||||
dnd.drop_box_end_row = y + len(box_lines) + 1 // top border + lines + bottom border
|
||||
|
||||
if dnd.drag_over_box {
|
||||
// Highlight the box in green when drag is over it.
|
||||
lp.QueueWriteString("\x1b[32m")
|
||||
draw_rounded_box(lp, box_width, box_lines)
|
||||
lp.QueueWriteString("\x1b[m")
|
||||
} else {
|
||||
draw_rounded_box(lp, box_width, []string{"Drop something onto this window"})
|
||||
draw_rounded_box(lp, box_width, box_lines)
|
||||
}
|
||||
}
|
||||
|
||||
// start_next_file_request sends a request for the next unread file URI,
|
||||
// or finishes the drop if all files have been read.
|
||||
var start_next_file_request func()
|
||||
start_next_file_request = func() {
|
||||
for dnd.file_read_idx < len(dnd.uri_list) {
|
||||
uri := dnd.uri_list[dnd.file_read_idx]
|
||||
if strings.HasPrefix(uri, "file://") {
|
||||
// Request this file via the protocol.
|
||||
dnd.file_read_size = 0
|
||||
dnd.collecting = "file"
|
||||
lp.QueueWriteString(dnd_request_file(dnd.uri_list_mime_idx, dnd.file_read_idx+1))
|
||||
return
|
||||
}
|
||||
// Non-file URI: record as-is with no size info.
|
||||
dnd.file_infos = append(dnd.file_infos, file_info{name: uri})
|
||||
dnd.file_read_idx++
|
||||
}
|
||||
// All files processed; finish the drop.
|
||||
dnd.collecting = ""
|
||||
lp.QueueWriteString(dnd_finish())
|
||||
dnd.has_drop_data = true
|
||||
draw_screen()
|
||||
}
|
||||
|
||||
handle_dnd_osc := func(raw []byte) error {
|
||||
// raw is the OSC payload after ESC ] and before ST
|
||||
// Format: 72;metadata;payload or 72;metadata
|
||||
// raw is the OSC payload after ESC ] and before ST.
|
||||
// Format: DND_CODE;metadata[;payload]
|
||||
prefix := fmt.Sprintf("%d;", kitty.DndCode)
|
||||
if !bytes.HasPrefix(raw, []byte(prefix)) {
|
||||
return nil
|
||||
}
|
||||
rest := string(raw[len(prefix):])
|
||||
// Split into metadata and optional payload
|
||||
// Split into metadata and optional payload.
|
||||
meta, payload, _ := strings.Cut(rest, ";")
|
||||
// Parse metadata key=value pairs separated by ':'
|
||||
// Parse metadata key=value pairs separated by ':'.
|
||||
meta_map := make(map[string]string)
|
||||
for kv := range strings.SplitSeq(meta, ":") {
|
||||
k, v, _ := strings.Cut(kv, "=")
|
||||
@@ -274,8 +463,8 @@ func Run(args []string) (rc int, err error) {
|
||||
t := meta_map["t"]
|
||||
switch t {
|
||||
case "m":
|
||||
// Drag move event from terminal
|
||||
// Check if drag has left the window (x=-1, y=-1)
|
||||
// Drag move event from terminal.
|
||||
// Check if drag has left the window (x=-1, y=-1).
|
||||
if meta_map["x"] == "-1" || meta_map["y"] == "-1" {
|
||||
dnd.reset_drag()
|
||||
draw_screen()
|
||||
@@ -286,78 +475,166 @@ func Run(args []string) (rc int, err error) {
|
||||
dnd.drag_mimes = mimes
|
||||
}
|
||||
dnd.dragging = true
|
||||
// Accept the drag with copy operation
|
||||
accepted_mimes := []string{}
|
||||
for _, m := range dnd.drag_mimes {
|
||||
if m == "text/plain" || m == "text/uri-list" {
|
||||
accepted_mimes = append(accepted_mimes, m)
|
||||
cx, _ := strconv.Atoi(meta_map["x"])
|
||||
cy, _ := strconv.Atoi(meta_map["y"])
|
||||
dnd.drag_cell_x = cx
|
||||
dnd.drag_cell_y = cy
|
||||
|
||||
over_box := dnd.is_over_drop_box(cx, cy)
|
||||
dnd.drag_over_box = over_box
|
||||
|
||||
if over_box {
|
||||
// Accept the drag with copy operation for supported MIME types.
|
||||
accepted_mimes := []string{}
|
||||
for _, m := range dnd.drag_mimes {
|
||||
if m == "text/plain" || m == "text/uri-list" {
|
||||
accepted_mimes = append(accepted_mimes, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(accepted_mimes) > 0 {
|
||||
lp.QueueWriteString(dnd_accept_drag(strings.Join(accepted_mimes, " ")))
|
||||
if len(accepted_mimes) > 0 {
|
||||
lp.QueueWriteString(dnd_accept_drag(strings.Join(accepted_mimes, " ")))
|
||||
}
|
||||
} else {
|
||||
// Not over drop region; reject the drag.
|
||||
lp.QueueWriteString(dnd_reject_drag())
|
||||
}
|
||||
draw_screen()
|
||||
case "M":
|
||||
// Drop event from terminal
|
||||
dnd.dragging = false
|
||||
// Drop event from terminal.
|
||||
dnd.reset_drag()
|
||||
dnd.reset_drop_data()
|
||||
mimes := strings.Fields(payload)
|
||||
dnd.drop_mimes = mimes
|
||||
// Request data for text/plain first, then text/uri-list
|
||||
for _, x := range []string{"text/plain", "text/uri-list"} {
|
||||
if slices.Contains(mimes, x) {
|
||||
dnd.collecting = x
|
||||
lp.QueueWriteString(dnd_request_data(x))
|
||||
|
||||
// Find the MIME indices we need.
|
||||
for i, m := range mimes {
|
||||
if m == "text/uri-list" {
|
||||
dnd.uri_list_mime_idx = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Request data: text/plain first, then text/uri-list.
|
||||
for idx, m := range mimes {
|
||||
if m == "text/plain" {
|
||||
dnd.collecting = "text/plain"
|
||||
lp.QueueWriteString(dnd_request_mime_data(idx + 1))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Nothing to collect, signal done
|
||||
if dnd.uri_list_mime_idx > 0 {
|
||||
dnd.collecting = "text/uri-list"
|
||||
lp.QueueWriteString(dnd_request_mime_data(dnd.uri_list_mime_idx))
|
||||
return nil
|
||||
}
|
||||
// Nothing to collect; signal done.
|
||||
lp.QueueWriteString(dnd_finish())
|
||||
dnd.has_drop_data = true
|
||||
draw_screen()
|
||||
case "r":
|
||||
// Data response from terminal
|
||||
// Data response from terminal.
|
||||
resp_y, _ := strconv.Atoi(meta_map["y"])
|
||||
resp_X, _ := strconv.Atoi(meta_map["X"])
|
||||
|
||||
is_file_response := resp_y != 0
|
||||
if is_file_response {
|
||||
// Response for an individual file request (t=r:x=idx:y=subidx).
|
||||
if payload == "" {
|
||||
// End of file data.
|
||||
fi := file_info{}
|
||||
if resp_X > 1 {
|
||||
// Directory: close the handle.
|
||||
fi.is_dir = true
|
||||
lp.QueueWriteString(dnd_close_dir(resp_X))
|
||||
} else if resp_X == 1 {
|
||||
fi.is_link = true
|
||||
fi.size = dnd.file_read_size
|
||||
} else {
|
||||
fi.size = dnd.file_read_size
|
||||
}
|
||||
dnd.file_infos = append(dnd.file_infos, fi)
|
||||
dnd.file_read_idx++
|
||||
draw_screen()
|
||||
start_next_file_request()
|
||||
} else {
|
||||
decoded, err := base64.RawStdEncoding.DecodeString(payload)
|
||||
if err == nil {
|
||||
dnd.file_read_size += int64(len(decoded))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response for a MIME type data request.
|
||||
if payload == "" {
|
||||
// End of data for current MIME type
|
||||
// End of MIME type data.
|
||||
switch dnd.collecting {
|
||||
case "text/plain":
|
||||
text := dnd.collect_buf.String()
|
||||
// Get first line
|
||||
text = strings.TrimRight(text, "\r\n")
|
||||
if before, _, ok := strings.Cut(text, "\n"); ok {
|
||||
dnd.plain_text = before
|
||||
dnd.plain_text = strings.TrimRight(before, "\r")
|
||||
} else {
|
||||
dnd.plain_text = text
|
||||
}
|
||||
dnd.collect_buf.Reset()
|
||||
// Now request text/uri-list if available
|
||||
if slices.Contains(dnd.drop_mimes, "text/uri-list") {
|
||||
// Now request text/uri-list if available.
|
||||
if dnd.uri_list_mime_idx > 0 {
|
||||
dnd.collecting = "text/uri-list"
|
||||
lp.QueueWriteString(dnd_request_data("text/uri-list"))
|
||||
lp.QueueWriteString(dnd_request_mime_data(dnd.uri_list_mime_idx))
|
||||
return nil
|
||||
}
|
||||
case "text/uri-list":
|
||||
text := dnd.collect_buf.String()
|
||||
dnd.collect_buf.Reset()
|
||||
// Parse URI list: lines starting with # are comments
|
||||
// Check if terminal indicated remote files (X=1 in URI list response).
|
||||
if resp_X == 1 {
|
||||
dnd.is_remote = true
|
||||
}
|
||||
// Parse URI list: lines starting with # are comments.
|
||||
for line := range strings.SplitSeq(text, "\n") {
|
||||
line = strings.TrimRight(line, "\r")
|
||||
if line != "" && !strings.HasPrefix(line, "#") {
|
||||
dnd.uri_list = append(dnd.uri_list, line)
|
||||
}
|
||||
}
|
||||
// Start reading individual files.
|
||||
if len(dnd.uri_list) > 0 && dnd.uri_list_mime_idx > 0 {
|
||||
dnd.file_read_idx = 0
|
||||
start_next_file_request()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
dnd.collecting = ""
|
||||
// Signal done
|
||||
lp.QueueWriteString(dnd_finish())
|
||||
dnd.has_drop_data = true
|
||||
draw_screen()
|
||||
} else {
|
||||
// Decode base64 payload and append to buffer
|
||||
decoded, err := base64.RawStdEncoding.DecodeString(payload)
|
||||
if err == nil {
|
||||
dnd.collect_buf.Write(decoded)
|
||||
// Capture X from URI list response (may be in first chunk).
|
||||
if dnd.collecting == "text/uri-list" && resp_X != 0 {
|
||||
dnd.is_remote = resp_X == 1
|
||||
}
|
||||
}
|
||||
}
|
||||
case "R":
|
||||
// Error response from terminal.
|
||||
resp_y, _ := strconv.Atoi(meta_map["y"])
|
||||
is_file_response := resp_y != 0
|
||||
if is_file_response && dnd.collecting == "file" {
|
||||
// Record the error for this file.
|
||||
dnd.file_infos = append(dnd.file_infos, file_info{err_msg: payload})
|
||||
dnd.file_read_idx++
|
||||
draw_screen()
|
||||
start_next_file_request()
|
||||
} else if !is_file_response {
|
||||
// Error getting MIME data; finish the drop with what we have.
|
||||
dnd.collecting = ""
|
||||
lp.QueueWriteString(dnd_finish())
|
||||
dnd.has_drop_data = true
|
||||
draw_screen()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -365,7 +642,7 @@ func Run(args []string) (rc int, err error) {
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.SetWindowTitle("kitty mouse features demo")
|
||||
lp.SetCursorVisible(false)
|
||||
lp.QueueWriteString(dnd_start_accepting())
|
||||
lp.QueueWriteString(dnd_start_accepting(machine_id))
|
||||
draw_screen()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user