Fix explicit stdin sources in the DnD kitten

This commit is contained in:
Hassan Raza 2026-08-28 13:19:25 +05:00
parent 9ee01568f9
commit 9c557f917b
No known key found for this signature in database
3 changed files with 56 additions and 10 deletions

View file

@ -350,6 +350,8 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error
}
}
drag_sources := make(map[string]*drag_source)
var stdin_data []byte
stdin_read := false
for _, spec := range opts.Drag {
mime, src, found := strings.Cut(spec, ":")
if !found {
@ -357,13 +359,14 @@ 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" {
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}
if !stdin_read {
stdin_data, err = io.ReadAll(os.Stdin)
if err != nil {
return 1, err
}
stdin_read = true
}
s.human_name, s.data = "STDIN", stdin_data
} else {
path, err := filepath.Abs(utils.Expanduser(src))
if err != nil {
@ -374,7 +377,7 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error
drag_sources[mime] = s
}
if _, has_plain := drag_sources["text/plain"]; os.Stdin != nil && !has_plain && !tty.IsTerminal(os.Stdin.Fd()) {
if _, has_plain := drag_sources["text/plain"]; os.Stdin != nil && !stdin_read && !has_plain && !tty.IsTerminal(os.Stdin.Fd()) {
data, err := io.ReadAll(os.Stdin)
if err != nil {
return 1, err

View file

@ -9,6 +9,7 @@ type=list
When starting a drag, use the specified file as the data source for the specified
MIME type. Syntax is: mime-type:path/to/file. For example image/jpeg:mypic.jpg
Can be specified multiple times to drag multiple MIME types.
Use :code:`-` or :file:`/dev/stdin` to read from STDIN. The same input can be used for multiple MIME types.
--drop
@ -82,7 +83,7 @@ If the text/uri-list MIME type is dropped onto this window, the files and direct
copied into the current working directory. When dragging from this window, if a move operation is
performed when dropping and the drop is to a remote machine, the files and directories to drag and deleted.
If data is present on STDIN it is set as text/plain when dragging, unless text/plain is specified via --drag.
If data is present on STDIN it is set as text/plain when dragging, unless STDIN is used explicitly via --drag or text/plain is specified via --drag.
Any text/plain data that is dropped onto this window is output to STDOUT, if STDOUT is connected to a file, otherwise it
is discarded.

View file

@ -123,10 +123,10 @@ 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, remote_client: bool = False, cli_args=()):
def finish_setup(self, remote_client: bool = False, cli_args=(), stdin_fd=None):
cmd = [kitten_exe(), 'dnd']
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, stdin_fd=stdin_fd))
self.capture.pty = self.pty
self.pty.callbacks.printbuf = self
self.screen = self.pty.screen
@ -393,6 +393,48 @@ class TestDnDKitten(BaseTest):
with open(a, 'rb') as fa, open(b, 'rb') as fb:
self.assertEqual(fa.read(), fb.read(), f'{a} ({os.path.getsize(a)}) != {b} ({os.path.getsize(b)})')
def test_dnd_kitten_stdin(self):
from .graphics import png_data
drag_thumbnail = os.path.join(self.test_dir, 'drag.png')
with open(drag_thumbnail, 'wb') as f:
f.write(png_data)
file_path = os.path.join(self.kitten_wd, 'text.txt')
file_data = b'file contents'
with open(file_path, 'wb') as f:
f.write(file_data)
stdin_data = b'<b>hello</b>\x00\xff'
for cli_args, expected in (
((), {'text/plain': stdin_data}),
(('--drag=text/plain:-',), {'text/plain': stdin_data}),
(('--drag=text/plain:/dev/stdin',), {'text/plain': stdin_data}),
(('--drag=text/html:-',), {'text/html': stdin_data}),
(('--drag=text/html:/dev/stdin',), {'text/html': stdin_data}),
(('--drag=text/html:-', '--drag=text/plain:/dev/stdin'), {'text/html': stdin_data, 'text/plain': stdin_data}),
((f'--drag=text/plain:{file_path}', '--drag=text/html:-'), {'text/plain': file_data, 'text/html': stdin_data}),
((f'--drag=text/html:{file_path}',), {'text/plain': stdin_data, 'text/html': file_data}),
((f'--drag=text/plain:{file_path}', '--drag=text/plain:-'), {'text/plain': stdin_data}),
(('--drag=text/plain:-', f'--drag=text/plain:{file_path}'), {'text/plain': file_data}),
):
with self.subTest(cli_args=cli_args):
read_fd, write_fd = os.pipe()
with os.fdopen(write_fd, 'wb') as f:
f.write(stdin_data)
self.finish_setup(cli_args=(f'--drag-thumbnail={drag_thumbnail}',) + cli_args, stdin_fd=read_fd)
try:
self.assertTrue(self.probe_state('can_offer'))
copy, move = self.get_button_geometry()
dnd_test_start_drag_offer(self.capture.window_id, copy[0] + 1, copy[1] + 1)
self.send_dnd_command_to_kitten('DRAG_ACTIVE')
self.wait_for_responses('DRAG_ACTIVE')
self.send_dnd_command_to_kitten('DRAG_OK')
self.wait_for_responses('DRAG_OK')
self.assertEqual(set(self.probe_state('drag_mimes')), set(expected))
for mime, payload in expected.items():
self.assertEqual(self.read_drag_data(mime), payload)
finally:
self.exit_kitten()
def test_dnd_kitten_drag(self):
from .graphics import png_data