mirror of
https://github.com/carey314/mio-plugin-airdrop.git
synced 2026-06-11 03:54:33 +00:00
Quick-access AirDrop from the notch panel. Tap the card body, pick files via NSOpenPanel, hand off to NSSharingService(.sendViaAirDrop). No private APIs, no entitlements, no network — just a thin wrapper around the same AirDrop API Finder uses. Design constraint: drag-and-drop is intentionally not implemented. MioIsland's notch panel auto-collapses on click-outside, which breaks the Cmd-Tab-to-Finder / grab / drag-back workflow before the drop target reaches. Tap-to-choose is rock solid by comparison. Requires MioIsland host v2.2.0+ (panel size clamp floor was lowered to 120pt in that release).
56 lines
1.8 KiB
Swift
56 lines
1.8 KiB
Swift
//
|
|
// AirDropService.swift
|
|
// MioIsland AirDrop Plugin
|
|
//
|
|
// Thin wrapper around NSSharingService(.sendViaAirDrop). This is a
|
|
// 100% public Apple API — no entitlements, no permissions, no
|
|
// private frameworks. When invoked, macOS pops the native AirDrop
|
|
// chooser sheet; we don't render the device list ourselves.
|
|
//
|
|
|
|
import AppKit
|
|
import Foundation
|
|
|
|
enum AirDropError: Error, LocalizedError {
|
|
case serviceUnavailable
|
|
case cannotPerform
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .serviceUnavailable:
|
|
return L10n.errServiceUnavailable
|
|
case .cannotPerform:
|
|
return L10n.errCannotPerform
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AirDropService {
|
|
/// Invoke macOS' AirDrop chooser for the given files.
|
|
///
|
|
/// NSSharingService does NOT surface per-recipient completion,
|
|
/// so we can only distinguish "could not start" vs "started and
|
|
/// returned control to us". A returned .success(()) means the
|
|
/// chooser sheet was presented; the user may have cancelled it,
|
|
/// but from our UI perspective we treat it as handled.
|
|
static func perform(
|
|
files: [URL],
|
|
completion: @escaping (Result<Void, Error>) -> Void
|
|
) {
|
|
DispatchQueue.main.async {
|
|
guard let service = NSSharingService(named: .sendViaAirDrop) else {
|
|
completion(.failure(AirDropError.serviceUnavailable))
|
|
return
|
|
}
|
|
guard service.canPerform(withItems: files) else {
|
|
completion(.failure(AirDropError.cannotPerform))
|
|
return
|
|
}
|
|
service.perform(withItems: files)
|
|
// AirDrop chooser is modal; perform() returns right after
|
|
// presenting. Treat that as success.
|
|
completion(.success(()))
|
|
}
|
|
}
|
|
}
|