diff --git a/kittens/dnd/main.go b/kittens/dnd/main.go index 85b02d377..49dc80984 100644 --- a/kittens/dnd/main.go +++ b/kittens/dnd/main.go @@ -97,10 +97,10 @@ func run_loop(opts *Options, drop_dests map[string]drop_dest, drag_sources map[s lp.OnInitialize = func() (string, error) { lp.AllowLineWrapping(false) if allow_drops { - lp.StartAcceptingDrops(slices.Collect(maps.Keys(drop_dests))...) + lp.StartAcceptingDrops(opts.MachineId, slices.Collect(maps.Keys(drop_dests))...) } if allow_drags { - lp.StartOfferingDrags() + lp.StartOfferingDrags(opts.MachineId) } lp.SetWindowTitle("Drag and drop") return "", render_screen() diff --git a/kittens/dnd/main.py b/kittens/dnd/main.py index 37356a369..8e3d55313 100644 --- a/kittens/dnd/main.py +++ b/kittens/dnd/main.py @@ -34,6 +34,10 @@ choices=copy,move,either The set of allowed actions when dragging. If :code:`either` is chosen then the drop destination can pick which action it wants. If the action is a move, then the dragged files are deleted when the drag finishes and the kitten exits after the drag. + + +--machine-id +The machine id to use instead of the actual machine id. '''.format help_text = '''\ diff --git a/kitty/dnd.c b/kitty/dnd.c index cf042ab44..1f4c813ba 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -2184,9 +2184,18 @@ dnd_test_probe_state(PyObject *self UNUSED, PyObject *args) { if (!PyArg_ParseTuple(args, "Ks", &window_id, &q)) return NULL; Window *w = window_for_window_id((id_type)window_id); if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; } + if (strcmp(q, "drop_wanted") == 0) { + return Py_NewRef(w->drop.wanted ? Py_True : Py_False); + } + if (strcmp(q, "drag_can_offer") == 0) { + return Py_NewRef(w->drag_source.can_offer ? Py_True : Py_False); + } if (strcmp(q, "drop_is_remote_client") == 0) { return Py_NewRef(w->drop.is_remote_client ? Py_True : Py_False); } + if (strcmp(q, "drag_is_remote_client") == 0) { + return Py_NewRef(w->drag_source.is_remote_client ? Py_True : Py_False); + } Py_RETURN_NONE; } diff --git a/kitty_tests/dnd_kitten.py b/kitty_tests/dnd_kitten.py index 186dde7ff..2be0f4bd5 100644 --- a/kitty_tests/dnd_kitten.py +++ b/kitty_tests/dnd_kitten.py @@ -51,13 +51,18 @@ class TestDnDKitten(BaseTest): self.pty.write_to_child(chunk) self.pty.write_to_child(b'\x1b\\', flush=is_last and flush) - def finish_setup(self, cmd=None): - cmd = cmd or [kitten_exe(), 'dnd'] + def finish_setup(self, remote_client: bool = False): + cmd = [kitten_exe(), 'dnd'] + if remote_client: + cmd.append('--machine-id=remote-client-for-test') self.pty = self.enterContext(PTY(argv=cmd, rows=25, columns=80, window_id=self.capture.window_id)) self.pty.callbacks.printbuf = self self.screen = self.pty.screen self.pty.wait_till(lambda: bool(self.pty.callbacks.titlebuf)) - self.assertFalse(self.probe_state('drop_is_remote_client')) + self.assertTrue(self.probe_state('drop_wanted')) + self.assertEqual(remote_client, self.probe_state('drop_is_remote_client')) + if self.probe_state('drag_can_offer'): + self.assertEqual(remote_client, self.probe_state('drag_is_remote_client')) def append(self, text): self.messages_from_kitten += text @@ -80,4 +85,6 @@ class TestDnDKitten(BaseTest): del self.pty def test_dnd_kitten_drop(self): - self.finish_setup() + for remote_client in (False, True): + with self.subTest(remote_client=remote_client): + self.finish_setup(remote_client=remote_client) diff --git a/tools/cmd/mouse_demo/main.go b/tools/cmd/mouse_demo/main.go index 786eec251..035f23809 100644 --- a/tools/cmd/mouse_demo/main.go +++ b/tools/cmd/mouse_demo/main.go @@ -548,7 +548,7 @@ func Run(args []string) (rc int, err error) { lp.OnInitialize = func() (string, error) { lp.SetWindowTitle("kitty mouse features demo") lp.SetCursorVisible(false) - lp.StartAcceptingDrops("text/plain", "text/uri-list") + lp.StartAcceptingDrops("", "text/plain", "text/uri-list") draw_screen() return "", nil } diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index d3f7aa502..e35a9f524 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -707,13 +707,24 @@ func (self *Loop) QueueDnDData(metadata map[string]string, payload string, as_ba return ans } -func (self *Loop) StartAcceptingDrops(mime_types ...string) { - self.QueueDnDData(map[string]string{"t": "a"}, strings.Join(mime_types, " "), false) - if ans, err := machine_id.MachineId(); err == nil { +func effective_machine_id(m string) string { + if m == "" { + if ans, err := machine_id.MachineId(); err == nil { + m = ans + } + } + if m != "" { mac := hmac.New(sha256.New, []byte("tty-dnd-protocol-machine-id")) - mac.Write(utils.UnsafeStringToBytes(ans)) - ans = "1:" + hex.EncodeToString(mac.Sum(nil)) - self.QueueDnDData(map[string]string{"t": "a", "x": "1"}, ans, false) + mac.Write(utils.UnsafeStringToBytes(m)) + m = "1:" + hex.EncodeToString(mac.Sum(nil)) + } + return m +} + +func (self *Loop) StartAcceptingDrops(machine_id string, mime_types ...string) { + self.QueueDnDData(map[string]string{"t": "a"}, strings.Join(mime_types, " "), false) + if m := effective_machine_id(machine_id); m != "" { + self.QueueDnDData(map[string]string{"t": "a", "x": "1"}, m, false) } } @@ -721,12 +732,10 @@ func (self *Loop) StopAcceptingDrops() { self.QueueDnDData(map[string]string{"t": "A"}, "", false) } -func (self *Loop) StartOfferingDrags() { +func (self *Loop) StartOfferingDrags(machine_id string) { payload := "" - if ans, err := machine_id.MachineId(); err == nil { - mac := hmac.New(sha256.New, []byte("tty-dnd-protocol-machine-id")) - mac.Write(utils.UnsafeStringToBytes(ans)) - payload = "1:" + hex.EncodeToString(mac.Sum(nil)) + if m := effective_machine_id(machine_id); m != "" { + payload = m } self.QueueDnDData(map[string]string{"t": "o", "x": "1"}, payload, false) }