Can I receive a callback whenever an NSPasteboard is written to?

后端 未结 6 1869
迷失自我
迷失自我 2020-12-02 09:26

I\'ve read Apple\'s Pasteboard Programming Guide, but it doesn\'t answer a particular question I have.

I\'m trying to write a Cocoa application (for OS X, not iOS) t

相关标签:
6条回答
  • 2020-12-02 09:39

    I have a solution for more strict case: detecting when your content in NSPasteboard was replaced by something else.

    If you create a class that conforms to NSPasteboardWriting and pass it to -writeObjects: along with the actual content, NSPasteboard will retain this object until its content is replaced. If there are no other strong references to this object, it get deallocated.

    Deallocation of this object is the moment when new NSPasteboard got new content.

    0 讨论(0)
  • 2020-12-02 09:43

    For those who need some very simplified version in Swift 5, it just works(base on @Devarshi code):

        func WatchPasteboard(copied: @escaping (_ copiedString:String) -> Void) {
            let pasteboard = NSPasteboard.general
            var changeCount = NSPasteboard.general.changeCount
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                if let copiedString = pasteboard.string(forType: .string) {
                    if pasteboard.changeCount != changeCount {        
                        copied(copiedString)                
                        changeCount = pasteboard.changeCount
                    }
                }
            }
        }
    

    Usage of how to use is as below:

    WatchPasteboard {
        print("copy detected : \($0)")
    }
    

    it will print out like below..

    watched : pasteboard1
    watched : pasteboard2
    
    0 讨论(0)
  • 2020-12-02 09:45

    It's not necessary to poll. Pasteboard would generally only be changed by the current view is inactive or does not have focus. Pasteboard has a counter that is incremented when contents change. When window regains focus (windowDidBecomeKey), check if changeCount has changed then process accordingly.

    This does not capture every change, but lets your application respond if the Pasteboard is different when it becomes active.

    In Swift...

    var pasteboardChangeCount = NSPasteboard.general().changeCount
    func windowDidBecomeKey(_ notification: Notification)
    {   Swift.print("windowDidBecomeKey")
        if  pasteboardChangeCount != NSPasteboard.general().changeCount
        {   viewController.checkPasteboard()
            pasteboardChangeCount  = NSPasteboard.general().changeCount
        }
    }
    
    0 讨论(0)
  • 2020-12-02 09:55

    Based on answer provided by Joshua I came up with similar implementation but in swift, here is the link to its gist: PasteboardWatcher.swift

    Code snippet from same:

    class PasteboardWatcher : NSObject {
    
        // assigning a pasteboard object
        private let pasteboard = NSPasteboard.generalPasteboard()
    
        // to keep track of count of objects currently copied
        // also helps in determining if a new object is copied
        private var changeCount : Int
    
        // used to perform polling to identify if url with desired kind is copied
        private var timer: NSTimer?
    
        // the delegate which will be notified when desired link is copied
        weak var delegate: PasteboardWatcherDelegate?
    
        // the kinds of files for which if url is copied the delegate is notified
        private let fileKinds : [String]
    
        /// initializer which should be used to initialize object of this class
        /// - Parameter fileKinds: an array containing the desired file kinds
        init(fileKinds: [String]) {
            // assigning current pasteboard changeCount so that it can be compared later to identify changes
            changeCount = pasteboard.changeCount
    
            // assigning passed desired file kinds to respective instance variable
            self.fileKinds = fileKinds
    
            super.init()
        }
        /// starts polling to identify if url with desired kind is copied
        /// - Note: uses an NSTimer for polling
        func startPolling () {
            // setup and start of timer
            timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("checkForChangesInPasteboard"), userInfo: nil, repeats: true)
        }
    
        /// method invoked continuously by timer
        /// - Note: To keep this method as private I referred this answer at stackoverflow - [Swift - NSTimer does not invoke a private func as selector](http://stackoverflow.com/a/30947182/217586)
        @objc private func checkForChangesInPasteboard() {
            // check if there is any new item copied
            // also check if kind of copied item is string
            if let copiedString = pasteboard.stringForType(NSPasteboardTypeString) where pasteboard.changeCount != changeCount {
    
                // obtain url from copied link if its path extension is one of the desired extensions
                if let fileUrl = NSURL(string: copiedString) where self.fileKinds.contains(fileUrl.pathExtension!){
    
                    // invoke appropriate method on delegate
                    self.delegate?.newlyCopiedUrlObtained(copiedUrl: fileUrl)
                }
    
                // assign new change count to instance variable for later comparison
                changeCount = pasteboard.changeCount
            }
        }
    }
    

    Note: in the shared code I am trying to identify if user has copied a file url or not, the provided code can easily be modified for other general purposes.

    0 讨论(0)
  • 2020-12-02 09:56

    Unfortunately the only available method is by polling (booo!). There are no notifications and there's nothing to observe for changed pasteboard contents. Check out Apple's ClipboardViewer sample code to see how they deal with inspecting the clipboard. Add a (hopefully not overzealous) timer to keep checking for differences and you've got a basic (if clunky) solution that should be App-Store-Friendly.

    File an enhancement request at bugreporter.apple.com to request notifications or some other callback. Unfortunately it wouldn't help you until the next major OS release at the earliest but for now it's polling until we all ask them to give us something better.

    0 讨论(0)
  • 2020-12-02 10:01

    There was once a post on a mailing list where the decision against a notification api was described. I can't find it right now though. The bottom line was that probably too many applications would register for that api even though they really wouldn't need to. If you then copy something the whole system goes through the new clipboard content like crazy, creating lots of work for the computer. So i don't think they'll change that behavior anytime soon. The whole NSPasteboard API is internally built around using the changeCount, too. So even your custom subclass of NSPasteboard would still have to keep polling.

    If you really want to check if the pasteboard changed, just keep observing the changeCount very half second. Comparing integers is really fast so there's really no performance issue here.

    0 讨论(0)
提交回复
热议问题