diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index fc9e8da2a..bca63c2da 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -321,7 +321,7 @@ operation, it indicates the drag should be started by sending ``t=P:x=-1``. At this time if the user has already cancelled the drag or the terminal determines the drag operation is not allowed, it must respond with ``t=E ; EPERM``. If any other error occurs starting the drag operation, it must respond with the appropriate -POSIX error code. If it determines that the image data after conversion to +POSIX error name. If it determines that the image data after conversion to display format is too large, it must respond with ``t=E ; EFBIG``. If the drag operation is successfully started, it must respond with ``t=E ; OK``. diff --git a/kittens/dnd/drag.go b/kittens/dnd/drag.go index 2887e94c2..34de698a5 100644 --- a/kittens/dnd/drag.go +++ b/kittens/dnd/drag.go @@ -2,10 +2,57 @@ package dnd import ( "fmt" + "maps" + "slices" + "strings" + + "github.com/kovidgoyal/kitty/tools/utils" ) var _ = fmt.Print type drag_status struct { - active bool + active bool + terminal_accepted_drag bool +} + +func (dnd *dnd) on_potential_drag_start(cell_x, cell_y int) (err error) { + if !dnd.allow_drags || dnd.drag_status.active { + return + } + mimes := slices.Collect(maps.Keys(dnd.drag_sources)) + actions := 3 + if dnd.copy_button_region.has(cell_x, cell_y) { + actions = 1 + } else if dnd.move_button_region.has(cell_x, cell_y) { + actions = 2 + } + dnd.lp.QueueDnDData(DC{Type: 'o', Operation: actions, Payload: utils.UnsafeStringToBytes(strings.Join(mimes, " "))}) + total_preloaded_data_sz := 0 + for i, mt := range mimes { + s := dnd.drag_sources[mt] + if len(s.data) > 0 && len(s.data)+total_preloaded_data_sz < 64*1024*1024 { + total_preloaded_data_sz += len(s.data) + dnd.lp.QueueDnDData(DC{Type: 'p', X: i, Operation: actions, Payload: utils.UnsafeStringToBytes(strings.Join(mimes, " "))}) + } + } + // TODO: set the drag image + dnd.lp.QueueDnDData(DC{Type: 'P', X: -1}) // start drag + dnd.drag_status.active = true + + return dnd.render_screen() +} + +func (dnd *dnd) on_drag_error(cmd DC) (err error) { + payload := string(cmd.Payload) + switch payload { + case "OK": + if dnd.drag_status.active && !dnd.drag_status.terminal_accepted_drag { + dnd.drag_status.terminal_accepted_drag = true + err = dnd.render_screen() + } + default: + err = fmt.Errorf("terminal responded with drag source error: %s", payload) + } + return } diff --git a/kittens/dnd/main.go b/kittens/dnd/main.go index b68d98c7f..64d7dc2d6 100644 --- a/kittens/dnd/main.go +++ b/kittens/dnd/main.go @@ -240,6 +240,12 @@ func (dnd *dnd) run_loop() (err error) { return fmt.Errorf("error from the terminal while reading dropped data: %s", string(cmd.Payload)) case 'r': return dnd.on_drop_data(cmd) + + // Drag source + case 'o': + return dnd.on_potential_drag_start(cmd.X, cmd.Y) + case 'E': + return dnd.on_drag_error(cmd) } return nil } @@ -305,8 +311,13 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error } s := drag_source{human_name: src, mime_type: mime} if src == "-" || src == "/dev/stdin" { - s.human_name = "STDIN" - s.file = os.Stdin + data, err := io.ReadAll(os.Stdin) + if err != nil { + return 1, err + } + if len(data) > 0 { + drag_sources["text/plain"] = drag_source{human_name: "STDIN", mime_type: "text/plain", data: data} + } } else { path, err := filepath.Abs(src) if err != nil {