1
votes

I wanna drag files to my window and then perform actions.

I tried to use snippets below provided in this answer to distinguish whether you're dragging a file or a window.

// In my window controller

class MyWindowController: NSWindowController {
    
    init() {
        // Some initialization steps below are omitted
        let win = NSWindow(...)
        super.init(window: win)
        
        let contentView = DropView(frame: win.frame)
        win.contentView?.addSubview(contentView)

        registerGlobalMouseEvent()
    }

    func registerGlobalMouseEvent() {
        self.window?.acceptsMouseMovedEvents = true
        
        NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
            // Codes below will cause errors
            let pasteBoard = NSPasteboard(name: .drag)
            guard let fileNames = pasteBoard.propertyList(forType: .init(rawValue: "NSFilenamesPboardType")) as? NSArray else { return }
            let changeCount = pasteBoard.changeCount

            if fileNames.count > 0 && lastChangeCount != changeCount {
                lastChangeCount = changeCount

                // My actions when dragging
            }
        })
    }
    
}

Then I ran my codes and started dragging, I got three errors:

[sandbox] Failed to get a sandbox extension

[Framework] Failed to issue sandbox extension for /Users/roy/Downloads/test.txt with error 1

[default] Failed to issue sandbox token for URL: 'file:///Users/roy/Downloads/test.txt' with error: 'Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Cannot issue a sandbox extension for file "/Users/roy/Downloads/test.txt": Operation not permitted}'

 

But when I just do

NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
    // My actions
})

, then everything went fine.

 

The first error seems harmless since it didn't prevent my app from running.

The second and the third ones are deadly and directly caused my app to crash.

I wonder if there are any problems in his code? Any useful thoughts would be great! :)


 

2

2 Answers

1
votes

You need to know about Bookmarks and Security Scoped URLs when working with sandbox . A dragged URL gives your app process permission just once to read or read/write a “user selected file” depending on how you configure entitlements.

You can save a bookmark (blob of data) to keep access over subsequent sessions as long as the file isn’t updated by another process at which point the bookmark becomes stale and you will need to encourage the user to select the file again.

Handing a URL to another process across an XPC boundary like sharing requires that you own the file so may involve a copy to your sandbox cache.

e.g:

let dragurl = url_of_dragged_file //at this point you have at-least read access
let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last!
let cachedir = URL(fileURLWithPath: cachepath)
let cacheurl = cachedir
  .appendingPathComponent(UUID().uuidString)
  .appendingPathExtension(dragurl.pathExtension)
try FileManager.default.copyItem(at: dragurl, to: cacheurl)

At this point you have a copy in your local sandbox cache that can be handed off to a share sheet.

0
votes

So I finally got a solution for this. :)

It appears that it indeed have something to do with the snippets I mentioned above, and here's the correction:

NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
    let pasteboard = NSPasteboard(name: .drag)
    let changeCount = pasteboard.changeCount
    if lastChangeCount != changeCount {
        lastChangeCount = changeCount
        if pasteboard.canReadObject(forClasses: [NSURL.self], options: [:]) {
            /// actions
        }
    }
})

In this way, I got no errors and my codes run perfectly!