feat: drop file into the Finder window under the cursor

Previously the guest-agent relocation script always asked Finder for the
*front* window and dropped there. That meant dropping on the bare Desktop
while a Downloads window happened to be frontmost put the file in
Downloads — the opposite of user intent.

Plumb the drop point (already captured as a normalized 0..1 top-left
coordinate by DropGeometry.normalize) through synthesizeGuestDrop and
GuestDropSynthesis.perform into the in-guest script. The script now
converts the point to guest-screen pixels using the desktop window's
bounds, walks `every Finder window` in front-to-back order, and returns
the folder of the first window whose bounds contain the point. If no
window does, dest_dir stays empty and the existing fallback drops the
file on ~/Desktop — which is correct for a drop on bare Desktop.

The AppleScript body is piped to `osascript -` via a single-quoted bash
variable (rather than a heredoc) so it survives Swift's multi-line
string indentation rules cleanly.
This commit is contained in:
Dal Rupnik 2026-05-18 13:02:16 +02:00
parent 0cc0969652
commit 9e5f65b8da
2 changed files with 72 additions and 30 deletions

View File

@ -1260,7 +1260,6 @@ private func synthesizeGuestDrop(
atNormalized normalized: CGPoint,
controlSocketURL: URL?
) async -> String {
_ = normalized
guard let controlSocketURL = controlSocketURL else { return "Shared Files" }
let filename = (hostFilePath as NSString).lastPathComponent
@ -1269,10 +1268,12 @@ private func synthesizeGuestDrop(
do {
let outcome = try await GuestDropSynthesis.perform(
controlSocketURL: controlSocketURL,
guestFilePath: guestPath
guestFilePath: guestPath,
normalizedDropPoint: normalized
)
return outcome.destinationFolderName
} catch {
NSLog("[GuestDrop] relocate failed for \(guestPath): \(error)")
return "Shared Files"
}
}

View File

@ -37,15 +37,27 @@ enum GuestDropError: Error {
/// Files"), we fall back to `~/Desktop`. The file is finally revealed with
/// `open -R` so Finder pops a window with it selected.
enum GuestDropSynthesis {
/// Shell script body executed inside the guest. The dropped file's guest
/// path is passed as `$1` via the `sh -c CMD -- $1` convention (with
/// `tartdrop` as `$0` so error messages identify us).
/// Shell script body executed inside the guest. Args:
/// $1 = guest path of the just-copied file (in the share)
/// $2 = drop X, normalized 0..1, top-left origin (optional)
/// $3 = drop Y, normalized 0..1, top-left origin (optional)
/// `$0` is set to "tartdrop" so error messages identify us.
///
/// The script picks the destination by *position*: it converts the
/// normalized drop point into guest-screen pixels and asks Finder which of
/// its open windows contains that point. If no Finder window does (i.e. the
/// user dropped on bare Desktop), the file goes to ~/Desktop. The front
/// window is intentionally *not* the default fallback dropping on the
/// Desktop while a Downloads window happens to be frontmost should land on
/// the Desktop, not in Downloads.
///
/// Exit codes: 0 on success, non-zero on failure (caller treats any
/// non-zero as "fall back to the share-folder copy that's already on disk").
private static let relocateAndRevealScript = #"""
set -e
src=$1
norm_x=${2:-}
norm_y=${3:-}
if [ -z "$src" ] || [ ! -e "$src" ]; then
echo "tartdrop: missing or nonexistent source: $src" >&2
exit 2
@ -53,27 +65,50 @@ enum GuestDropSynthesis {
name=$(basename "$src")
src_dir=$(dirname "$src")
# Ask Finder for the frontmost window's folder. Suppress stderr so a TCC
# denial or "no windows" error doesn't pollute the agent log; we detect
# those by an empty result. The agent runs in the user's GUI session, so
# osascript here goes through the user's TCC consent (AutomationFinder).
dest_dir=$(/usr/bin/osascript <<'OSA' 2>/dev/null || true
tell application "Finder"
if (count of Finder windows) is 0 then return ""
try
return POSIX path of (target of front Finder window as alias)
on error
return ""
end try
end tell
OSA
)
# AppleScript appends a trailing slash to folder POSIX paths.
dest_dir=${dest_dir%/}
# Resolve destination by where the drop landed. The agent runs in the
# user's GUI session so osascript goes through TCC (Automation Finder).
# Suppress stderr so a denied prompt or "no windows" error doesn't pollute
# the agent log; an empty result means "drop on Desktop".
# The AppleScript body is piped to `osascript -` so we don't need a shell
# heredoc (whose terminator must sit at column 0 incompatible with
# Swift's multi-line string indentation rules).
osa_script='on run argv
set nx to (item 1 of argv) as real
set ny to (item 2 of argv) as real
tell application "Finder"
set sb to bounds of window of desktop
set sw to (item 3 of sb) - (item 1 of sb)
set sh to (item 4 of sb) - (item 2 of sb)
set dx to (nx * sw) as integer
set dy to (ny * sh) as integer
try
set wins to every Finder window
repeat with i from 1 to count of wins
set w to item i of wins
set b to bounds of w
set L to (item 1 of b) as integer
set T to (item 2 of b) as integer
set R to (item 3 of b) as integer
set Bv to (item 4 of b) as integer
if (dx >= L) and (dx <= R) and (dy >= T) and (dy <= Bv) then
return POSIX path of ((target of w) as alias)
end if
end repeat
return ""
on error
return ""
end try
end tell
end run'
# Reject empty / nonexistent / non-writable destinations, and reject the
# source's own parent (which would either be a no-op rename or leave us
# putting the file right back into "Dropped Files").
dest_dir=""
if [ -n "$norm_x" ] && [ -n "$norm_y" ]; then
dest_dir=$(printf '%s' "$osa_script" \
| /usr/bin/osascript - "$norm_x" "$norm_y" 2>/dev/null || true)
dest_dir=${dest_dir%/}
fi
# Empty / nonexistent / non-writable / source's own parent bare Desktop.
if [ -z "$dest_dir" ] || [ ! -d "$dest_dir" ] || [ ! -w "$dest_dir" ] || [ "$dest_dir" = "$src_dir" ]; then
dest_dir=$HOME/Desktop
fi
@ -103,7 +138,8 @@ enum GuestDropSynthesis {
static func perform(
controlSocketURL: URL,
guestFilePath: String
guestFilePath: String,
normalizedDropPoint: CGPoint? = nil
) async throws -> GuestDropOutcome {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try? group.syncShutdownGracefully() }
@ -138,13 +174,18 @@ enum GuestDropSynthesis {
let client = AgentAsyncClient(channel: channel, defaultCallOptions: callOptions)
let execCall = client.makeExecCall()
// Pass the guest path as $1 (with "tartdrop" as $0 so error messages
// identify us). The script does the Finder-window lookup, picks a
// destination, moves the file out of "Dropped Files", and reveals it.
// Pass guest path as $1 and, if available, the normalized drop point as
// $2/$3 so the script can pick the Finder window under the cursor instead
// of the frontmost one. "tartdrop" is $0 so error messages identify us.
var scriptArgs = ["tartdrop", guestFilePath]
if let point = normalizedDropPoint {
scriptArgs.append(String(format: "%.6f", point.x))
scriptArgs.append(String(format: "%.6f", point.y))
}
let command = ExecRequest.with {
$0.type = .command(ExecRequest.Command.with {
$0.name = "/bin/sh"
$0.args = ["-c", relocateAndRevealScript, "tartdrop", guestFilePath]
$0.args = ["-c", relocateAndRevealScript] + scriptArgs
$0.interactive = false
$0.tty = false
})