Somewhat working file tray

This commit is contained in:
Hariharan Mudaliar 2025-12-08 13:29:25 +05:30
commit 30422c3d95
29 changed files with 4032 additions and 65 deletions

View file

@ -0,0 +1,174 @@
//
// NSItemProvider+LoadHelpers.swift
// DynamicIsland
//
// Ported shelf drop helpers from boringNotch.
//
import AppKit
import Foundation
import UniformTypeIdentifiers
extension NSItemProvider {
func extractItem() async -> URL? {
await loadFileURL(typeIdentifier: UTType.item.identifier)
}
/// Detects if this is a file dragged from the filesystem
func extractFileURL() async -> URL? {
if hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) {
return await loadFileURL(typeIdentifier: UTType.fileURL.identifier)
}
return nil
}
/// Loads raw data for the given type identifier
func loadData() async -> Data? {
guard hasItemConformingToTypeIdentifier(UTType.data.identifier) else { return nil }
return await withCheckedContinuation { (cont: CheckedContinuation<Data?, Never>) in
loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { item, error in
if let error {
print("Error loading data for type \(UTType.data.identifier): \(error.localizedDescription)")
cont.resume(returning: nil)
return
}
if let url = item as? URL, let data = try? Data(contentsOf: url) {
if !url.absoluteString.contains("com.apple.SwiftUI.filePromises") {
cont.resume(returning: nil)
return
}
self.suggestedName = self.suggestedName ?? url.lastPathComponent
let fileManager = FileManager.default
let folderURL = url.deletingLastPathComponent()
do {
try fileManager.removeItem(at: url)
let contents = try fileManager.contentsOfDirectory(atPath: folderURL.path)
if contents.isEmpty {
try fileManager.removeItem(at: folderURL)
}
} catch {
print("Error: \(error.localizedDescription)")
}
cont.resume(returning: data)
} else if let data = item as? Data {
cont.resume(returning: data)
} else {
cont.resume(returning: nil)
}
}
}
}
/// Attempts to extract a URL (web link) from the provider
func extractURL() async -> URL? {
if hasItemConformingToTypeIdentifier(UTType.url.identifier) {
if let url = await loadURL(typeIdentifier: UTType.url.identifier) {
guard url.scheme != nil else { return nil }
return url
}
}
return nil
}
func extractText() async -> String? {
let textTypes = [UTType.utf8PlainText.identifier, UTType.plainText.identifier]
for typeIdentifier in textTypes where hasItemConformingToTypeIdentifier(typeIdentifier) {
if let text = await loadText(typeIdentifier: typeIdentifier) {
return text
}
}
return nil
}
/// Loads a file URL from the provider for the given type identifier.
func loadFileURL(typeIdentifier: String) async -> URL? {
await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
if let error {
print("❌ Error loading item for type \(typeIdentifier): \(error.localizedDescription)")
cont.resume(returning: nil)
return
}
var resolvedURL: URL?
if let url = item as? URL {
resolvedURL = url
} else if let data = item as? Data {
if let string = String(data: data, encoding: .utf8) {
if let url = URL(string: string) {
resolvedURL = url
} else if string.hasPrefix("/") {
resolvedURL = URL(fileURLWithPath: string)
}
}
if resolvedURL == nil {
let bookmark = Bookmark(data: data)
resolvedURL = bookmark.resolveURL()
}
} else if let string = item as? String {
if let url = URL(string: string) {
resolvedURL = url
} else if string.hasPrefix("/") {
resolvedURL = URL(fileURLWithPath: string)
}
}
cont.resume(returning: resolvedURL)
}
}
}
/// Loads a URL from the provider for the given type identifier.
func loadURL(typeIdentifier: String) async -> URL? {
await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
if error != nil {
cont.resume(returning: nil)
return
}
if let url = item as? URL {
cont.resume(returning: url)
} else if let data = item as? Data {
if let string = String(data: data, encoding: .utf8) {
if let url = URL(string: string) {
cont.resume(returning: url)
return
} else if string.hasPrefix("/") {
cont.resume(returning: URL(fileURLWithPath: string))
return
}
}
cont.resume(returning: nil)
} else if let string = item as? String {
if let url = URL(string: string) {
cont.resume(returning: url)
} else if string.hasPrefix("/") {
cont.resume(returning: URL(fileURLWithPath: string))
} else {
cont.resume(returning: nil)
}
} else {
cont.resume(returning: nil)
}
}
}
}
/// Loads text from the provider for the given type identifier.
func loadText(typeIdentifier: String) async -> String? {
await withCheckedContinuation { (cont: CheckedContinuation<String?, Never>) in
loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in
if error != nil {
cont.resume(returning: nil)
return
}
if let string = item as? String {
cont.resume(returning: string)
} else if let data = item as? Data,
let string = String(data: data, encoding: .utf8) {
cont.resume(returning: string)
} else {
cont.resume(returning: nil)
}
}
}
}
}

View file

@ -0,0 +1,21 @@
//
// NSMenu+AssociatedObject.swift
// DynamicIsland
//
// Retains menu action targets using associated objects.
//
import AppKit
private final class MenuActionBox: NSObject {
let target: AnyObject
init(target: AnyObject) { self.target = target }
}
extension NSMenu {
private static let retainedAction = AssociatedObject<MenuActionBox>()
func retainActionTarget(_ target: AnyObject) {
NSMenu.retainedAction[self] = MenuActionBox(target: target)
}
}

View file

@ -0,0 +1,44 @@
//
// URL+SecurityScoped.swift
// DynamicIsland
//
// Ported from boringNotch shelf utilities.
//
import Foundation
import AppKit
extension URL {
func accessSecurityScopedResource<Value>(accessor: (URL) throws -> Value) rethrows -> Value {
let didStartAccessing = startAccessingSecurityScopedResource()
defer {
if didStartAccessing {
stopAccessingSecurityScopedResource()
}
}
return try accessor(self)
}
/// Async version of accessSecurityScopedResource
func accessSecurityScopedResource<Value>(accessor: (URL) async throws -> Value) async rethrows -> Value {
let didStartAccessing = startAccessingSecurityScopedResource()
defer {
if didStartAccessing {
stopAccessingSecurityScopedResource()
}
}
return try await accessor(self)
}
}
extension Array where Element == URL {
func accessSecurityScopedResources<Value>(accessor: ([URL]) async throws -> Value) async rethrows -> Value {
let didStart = map { $0.startAccessingSecurityScopedResource() }
defer {
for (url, started) in zip(self, didStart) where started {
url.stopAccessingSecurityScopedResource()
}
}
return try await accessor(self)
}
}