Fix empty file not created when dragging from remote Linux to Finder

When dragging an empty file (or a directory containing an empty file)
from a remote Linux machine to macOS Finder, the empty file would not
be copied.

Root cause: in add_payload() in dnd.c, the file is only created with
O_CREAT when payload data arrives. For an empty file, no payload is
ever sent, so the file was never created on disk. When Finder then
tried to hard-link the (non-existent) temp file to the destination, it
failed silently.

Fix: in the "all data received" handler for type 0 (regular file),
check if the fd was ever opened. If not (empty file), create the empty
file explicitly before finishing.

Also add:
- A new test probe drag_remote_item_path:N to retrieve the filesystem
  path of a specific remote item by URI index.
- Two regression tests: test_remote_drag_empty_file (verifying the
  empty file is created on disk) and test_remote_drag_dir_with_empty_file
  (verifying an empty child file inside a directory is created on disk).

Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/da8b4577-3de8-4784-afc0-c1967f605dec

Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-05-17 15:31:15 +00:00
committed by GitHub
parent ec8d23258c
commit 634c078168
2 changed files with 57 additions and 2 deletions

View File

@@ -2933,6 +2933,12 @@ class TestDnDProtocol(BaseTest):
parse_bytes(screen, client_remote_file(1, '', item_type=0))
self._assert_no_output(cap)
self.assert_drag_data_complete(cap)
# Verify the empty file was actually created on disk
import os
path = dnd_test_probe_state(cap.window_id, 'drag_remote_item_path:0')
self.assertIsNotNone(path, 'empty file path should be known')
self.assertTrue(os.path.isfile(path), f'empty file must exist on disk: {path}')
self.assertEqual(os.path.getsize(path), 0, f'file must be empty: {path}')
def test_remote_drag_empty_directory(self) -> None:
"""Transfer a directory with no entries."""
@@ -2945,6 +2951,27 @@ class TestDnDProtocol(BaseTest):
parse_bytes(screen, client_remote_file(1, '', item_type=2))
self.assert_drag_data_complete(cap)
def test_remote_drag_dir_with_empty_file(self) -> None:
"""Directory containing an empty file: the empty child file must be created on disk."""
uri_list = b'file:///home/user/mydir\r\n'
dir_entries = b'empty_child.txt'
with dnd_test_window() as (screen, cap):
self._setup_remote_drag(screen, cap, uri_list)
b64 = standard_b64encode(dir_entries).decode()
parse_bytes(screen, client_remote_file(1, b64, item_type=2))
parse_bytes(screen, client_remote_file(1, '', item_type=2))
# Send end-of-data for the child with no payload (empty file)
parse_bytes(screen, client_remote_file(1, '', item_type=0, parent_handle=2, entry_num=1))
self._assert_no_output(cap)
self.assert_drag_data_complete(cap)
# Verify the empty child file was actually created on disk
import os
dir_path = dnd_test_probe_state(cap.window_id, 'drag_remote_item_path:0')
self.assertIsNotNone(dir_path, 'dir path should be known')
child_path = os.path.join(dir_path, 'empty_child.txt')
self.assertTrue(os.path.isfile(child_path), f'empty child file must exist on disk: {child_path}')
self.assertEqual(os.path.getsize(child_path), 0, f'child file must be empty: {child_path}')
def test_remote_drag_uri_list_with_comments(self) -> None:
"""URI list with comment lines (starting with #) should filter them out."""
uri_list = b'# this is a comment\r\nfile:///home/user/f.txt\r\n# another comment\r\n'