mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
More work on DnD kitten
This commit is contained in:
@@ -51,6 +51,7 @@ type drop_dest struct {
|
|||||||
human_name, path string
|
human_name, path string
|
||||||
dest io.WriteCloser
|
dest io.WriteCloser
|
||||||
mime_type string
|
mime_type string
|
||||||
|
completed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type button_region struct {
|
type button_region struct {
|
||||||
@@ -76,14 +77,14 @@ func truncate_at_space(text string, width int) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type drop_status struct {
|
type drop_status struct {
|
||||||
offered_mimes []string
|
offered_mimes []string
|
||||||
accepted_mimes []string
|
accepted_mimes []string
|
||||||
cell_x, cell_y int
|
cell_x, cell_y int
|
||||||
action int
|
action int
|
||||||
in_window bool
|
in_window bool
|
||||||
reading_data bool
|
reading_data bool
|
||||||
requesting_mime_idx_plus_one int
|
is_remote_client bool
|
||||||
is_remote_client bool
|
remote_phase_started bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func paragraph_as_lines(text string, width int) (ans []string) {
|
func paragraph_as_lines(text string, width int) (ans []string) {
|
||||||
@@ -96,7 +97,7 @@ func paragraph_as_lines(text string, width int) (ans []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[string]drag_source, uri_list_buffer *bytes.Buffer) (err error) {
|
func run_loop(opts *Options, drop_dests map[string]*drop_dest, drag_sources map[string]drag_source, uri_list_buffer *bytes.Buffer) (err error) {
|
||||||
allow_drops, allow_drags := len(drop_dests) > 0, len(drag_sources) > 0
|
allow_drops, allow_drags := len(drop_dests) > 0, len(drag_sources) > 0
|
||||||
data_has_been_dropped := false
|
data_has_been_dropped := false
|
||||||
drag_started := false
|
drag_started := false
|
||||||
@@ -235,7 +236,7 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||||||
render_screen()
|
render_screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
all_mime_data_dropped := func() {
|
all_mime_data_dropped := func() error {
|
||||||
if _, found := drop_dests["text/uri-list"]; found && drop_status.is_remote_client {
|
if _, found := drop_dests["text/uri-list"]; found && drop_status.is_remote_client {
|
||||||
// TODO: Handle remote client
|
// TODO: Handle remote client
|
||||||
} else {
|
} else {
|
||||||
@@ -243,16 +244,13 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||||||
data_has_been_dropped = true
|
data_has_been_dropped = true
|
||||||
render_screen()
|
render_screen()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
request_mime_data := func() {
|
request_mime_data := func() {
|
||||||
drop_status.requesting_mime_idx_plus_one++
|
for idx := range drop_status.accepted_mimes {
|
||||||
idx := drop_status.requesting_mime_idx_plus_one - 1
|
lp.QueueDnDData(DC{Type: 'r', X: idx + 1})
|
||||||
if idx >= len(drop_status.accepted_mimes) {
|
|
||||||
all_mime_data_dropped()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
lp.QueueDnDData(DC{Type: 'r', X: idx + 1})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
on_drop_move := func(cell_x, cell_y int, has_more bool, offered_mimes string, is_drop bool) (needs_rerender bool) {
|
on_drop_move := func(cell_x, cell_y int, has_more bool, offered_mimes string, is_drop bool) (needs_rerender bool) {
|
||||||
@@ -316,7 +314,38 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on_remote_drop_data := func(cmd DC) error {
|
||||||
|
// TODO: Implement this
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
on_drop_data := func(cmd DC) error {
|
on_drop_data := func(cmd DC) error {
|
||||||
|
if drop_status.remote_phase_started {
|
||||||
|
return on_remote_drop_data(cmd)
|
||||||
|
}
|
||||||
|
if cmd.X < 0 || cmd.X > len(drop_status.accepted_mimes) {
|
||||||
|
return fmt.Errorf("terminal sent drop data for a index outside the list of accepted MIMEs")
|
||||||
|
}
|
||||||
|
mime := drop_status.accepted_mimes[cmd.X]
|
||||||
|
dest := drop_dests[mime]
|
||||||
|
if cmd.Xp == 1 && mime == "text/uri-list" {
|
||||||
|
drop_status.is_remote_client = true
|
||||||
|
}
|
||||||
|
if !cmd.Has_more && len(cmd.Payload) == 0 {
|
||||||
|
dest.completed = true
|
||||||
|
pending := false
|
||||||
|
for _, d := range drop_dests {
|
||||||
|
if !d.completed {
|
||||||
|
pending = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pending {
|
||||||
|
return all_mime_data_dropped()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// TODO: Implement this
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
@@ -449,12 +478,12 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||||
drop_dests := make(map[string]drop_dest)
|
drop_dests := make(map[string]*drop_dest)
|
||||||
if os.Stdout != nil && !tty.IsTerminal(os.Stdout.Fd()) {
|
if os.Stdout != nil && !tty.IsTerminal(os.Stdout.Fd()) {
|
||||||
drop_dests["text/plain"] = drop_dest{human_name: "STDOUT", dest: os.Stdout, mime_type: "text/plain"}
|
drop_dests["text/plain"] = &drop_dest{human_name: "STDOUT", dest: os.Stdout, mime_type: "text/plain"}
|
||||||
}
|
}
|
||||||
uri_list_buffer := &bytes.Buffer{}
|
uri_list_buffer := &bytes.Buffer{}
|
||||||
drop_dests["text/uri-list"] = drop_dest{
|
drop_dests["text/uri-list"] = &drop_dest{
|
||||||
human_name: "Files", mime_type: "text/uri-list", dest: &bufferWriteCloser{uri_list_buffer}}
|
human_name: "Files", mime_type: "text/uri-list", dest: &bufferWriteCloser{uri_list_buffer}}
|
||||||
for _, spec := range opts.Drop {
|
for _, spec := range opts.Drop {
|
||||||
mime, dest, _ := strings.Cut(spec, ":")
|
mime, dest, _ := strings.Cut(spec, ":")
|
||||||
@@ -465,7 +494,7 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
drop_dests[mime] = drop_dest{human_name: dest, path: path, mime_type: mime}
|
drop_dests[mime] = &drop_dest{human_name: dest, path: path, mime_type: mime}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drag_sources := make(map[string]drag_source)
|
drag_sources := make(map[string]drag_source)
|
||||||
|
|||||||
10
kitty/dnd.c
10
kitty/dnd.c
@@ -2206,6 +2206,16 @@ dnd_test_probe_state(PyObject *self UNUSED, PyObject *args) {
|
|||||||
if (w->drop.accepted_mimes == NULL) return PyUnicode_FromString("");
|
if (w->drop.accepted_mimes == NULL) return PyUnicode_FromString("");
|
||||||
return PyUnicode_FromStringAndSize(w->drop.accepted_mimes, w->drop.accepted_mimes_sz);
|
return PyUnicode_FromStringAndSize(w->drop.accepted_mimes, w->drop.accepted_mimes_sz);
|
||||||
}
|
}
|
||||||
|
if (strcmp(q, "drop_data_requests") == 0) {
|
||||||
|
PyObject *ans = PyTuple_New(w->drop.num_data_requests);
|
||||||
|
for (size_t i = 0; i < w->drop.num_data_requests; i++) {
|
||||||
|
#define item w->drop.data_requests[i]
|
||||||
|
PyObject *x = Py_BuildValue("iii", item.cell_x, item.cell_y, item.pixel_y);
|
||||||
|
PyTuple_SET_ITEM(ans, i, x);
|
||||||
|
#undef item
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,10 +67,11 @@ class TestDnDKitten(BaseTest):
|
|||||||
self.pty.write_to_child(chunk)
|
self.pty.write_to_child(chunk)
|
||||||
self.pty.write_to_child(b'\x1b\\', flush=is_last and flush)
|
self.pty.write_to_child(b'\x1b\\', flush=is_last and flush)
|
||||||
|
|
||||||
def finish_setup(self, remote_client: bool = False):
|
def finish_setup(self, remote_client: bool = False, cli_args = ()):
|
||||||
cmd = [kitten_exe(), 'dnd']
|
cmd = [kitten_exe(), 'dnd']
|
||||||
if remote_client:
|
if remote_client:
|
||||||
cmd.append('--machine-id=remote-client-for-test')
|
cmd.append('--machine-id=remote-client-for-test')
|
||||||
|
cmd += list(cli_args)
|
||||||
self.pty = self.enterContext(PTY(argv=cmd, cwd=self.kitten_wd, rows=25, columns=80, window_id=self.capture.window_id))
|
self.pty = self.enterContext(PTY(argv=cmd, cwd=self.kitten_wd, rows=25, columns=80, window_id=self.capture.window_id))
|
||||||
self.capture.pty = self.pty
|
self.capture.pty = self.pty
|
||||||
self.pty.callbacks.printbuf = self
|
self.pty.callbacks.printbuf = self
|
||||||
@@ -128,14 +129,13 @@ class TestDnDKitten(BaseTest):
|
|||||||
self.pty = None
|
self.pty = None
|
||||||
|
|
||||||
def test_dnd_kitten_drop(self):
|
def test_dnd_kitten_drop(self):
|
||||||
self.finish_setup(remote_client=False)
|
|
||||||
self.dnd_kitten_drop(False)
|
self.dnd_kitten_drop(False)
|
||||||
|
|
||||||
def test_dnd_kitten_drop_remote(self):
|
def test_dnd_kitten_drop_remote(self):
|
||||||
self.finish_setup(remote_client=True)
|
|
||||||
self.dnd_kitten_drop(True)
|
self.dnd_kitten_drop(True)
|
||||||
|
|
||||||
def dnd_kitten_drop(self, remote_client):
|
def dnd_kitten_drop(self, remote_client):
|
||||||
|
self.finish_setup(remote_client=remote_client, cli_args=('--drop=image/png:images/image.png',))
|
||||||
copy, move = self.get_button_geometry()
|
copy, move = self.get_button_geometry()
|
||||||
all_mimes = 'text/uri-list a/b c/d'
|
all_mimes = 'text/uri-list a/b c/d'
|
||||||
for b, expected in ((copy, GLFW_DRAG_OPERATION_COPY), (move, GLFW_DRAG_OPERATION_MOVE)):
|
for b, expected in ((copy, GLFW_DRAG_OPERATION_COPY), (move, GLFW_DRAG_OPERATION_MOVE)):
|
||||||
@@ -153,11 +153,14 @@ class TestDnDKitten(BaseTest):
|
|||||||
self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_COPY)
|
self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_COPY)
|
||||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||||
self.wait_for_responses(large_mimes)
|
self.wait_for_responses(large_mimes)
|
||||||
|
del large_mimes
|
||||||
dnd_test_fake_drop_event(self.capture.window_id, False)
|
dnd_test_fake_drop_event(self.capture.window_id, False)
|
||||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||||
self.wait_for_responses('')
|
self.wait_for_responses('')
|
||||||
|
all_mimes += ' image/png'
|
||||||
dnd_test_fake_drop_event(self.capture.window_id, False, all_mimes.split(), copy[0] + 1, copy[1] + 1)
|
dnd_test_fake_drop_event(self.capture.window_id, False, all_mimes.split(), copy[0] + 1, copy[1] + 1)
|
||||||
self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_COPY)
|
self.wait_for_state('drop_action', GLFW_DRAG_OPERATION_COPY)
|
||||||
dnd_test_fake_drop_event(self.capture.window_id, True, all_mimes.split(), copy[0] + 1, copy[1] + 1)
|
dnd_test_fake_drop_event(self.capture.window_id, True, all_mimes.split(), copy[0] + 1, copy[1] + 1)
|
||||||
self.send_dnd_command_to_kitten('DROP_MIMES')
|
self.send_dnd_command_to_kitten('DROP_MIMES')
|
||||||
self.wait_for_responses(all_mimes)
|
self.wait_for_responses(all_mimes)
|
||||||
|
self.wait_for_state('drop_data_requests', ((1,0,0), (2,0,0)))
|
||||||
|
|||||||
Reference in New Issue
Block a user