每当写入NSPasteboard时,我都可以收到回调吗?

时间:2011-02-17 18:55:10

标签: cocoa macos nspasteboard

我读过Apple的Pasteboard Programming Guide,但它没有回答我的特定问题。

我正在尝试编写一个Cocoa应用程序(适用于OS X,而不是iOS),它将跟踪写入general pasteboard的所有内容(因此,无论何时任何应用程序复制和粘贴,但不会, ,拖放,也使用NSPasteboard)。我可以(几乎)通过基本轮询后台线程上的常规粘贴板并检查changeCount来完成此操作。当然,这样做会让我觉得内心很脏。

我的问题是,有没有办法让粘贴板服务器在对普通粘贴板进行更改时通过某种回调通知我?我在NSPasteboard类引用中找不到任何内容,但我希望它潜伏在其他地方。

我可以想象实现这一点的另一种方法是,如果有一种方法可以将一般的粘贴板实现换成NSPasteboard的子类,我可以定义自己来发出回调。也许这样的事情可能吗?

如果公共的App Store合法API可以实现这一点,我会更加喜欢,但如果需要使用私有API,我也会采取这种做法。

谢谢!

6 个答案:

答案 0 :(得分:40)

不幸的是,唯一可用的方法是通过轮询(booo!)。没有通知,没有什么可以观察到更改的粘贴板内容。查看Apple的ClipboardViewer sample code,了解它们如何处理检查剪贴板。添加一个(希望不是过分热心)计时器来检查差异,你有一个基本的(如果笨重的)解决方案应该是App-Store-Friendly。

bugreporter.apple.com处提交增强请求以请求通知或其他一些回调。不幸的是,它不会帮助你,直到最早发布下一个主要操作系统,但现在轮询直到我们都要求他们给我们更好的东西。

答案 1 :(得分:12)

邮件列表上曾有一篇帖子,其中描述了针对通知api的决定。我现在找不到它。最重要的是,即使他们真的不需要,也可能有太多的应用程序会注册该API。如果你然后复制一些东西,整个系统就像疯了一样经历新的剪贴板内容,为计算机创造了大量的工作。所以我认为他们不会很快改变这种行为。整个NSPasteboard API也是使用changeCount内部构建的。因此,即使是NSPasteboard的自定义子类仍然需要继续进行轮询。

如果你真的想检查粘贴板是否改变了,只需要观察changeCount半秒钟。比较整数非常快,所以这里确实没有性能问题。

答案 2 :(得分:10)

根据约书亚提供的答案,我提出了类似的实施方案,但是很快,这里是其要点的链接:PasteboardWatcher.swift

相同的代码段:

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
        }
    }
}
  共享代码中的

注意:我试图识别用户是否已复制了   文件网址与否,提供的代码可以很容易地修改为其他一般   目的。

答案 3 :(得分:1)

没有必要进行投票。通常只有当前视图处于非活动状态或没有焦点时才会更改粘贴板。粘贴板有一个计数器,当内容改变时递增。当窗口重新获得焦点(windowDidBecomeKey)时,检查changeCount是否已更改,然后相应地进行处理。

这不会捕获每个更改,但如果粘贴板变为活动状态时粘贴板不同,则让应用程序响应。

在斯威夫特......

var pasteboardChangeCount = NSPasteboard.general().changeCount
func windowDidBecomeKey(_ notification: Notification)
{   Swift.print("windowDidBecomeKey")
    if  pasteboardChangeCount != NSPasteboard.general().changeCount
    {   viewController.checkPasteboard()
        pasteboardChangeCount  = NSPasteboard.general().changeCount
    }
}

答案 4 :(得分:0)

我有一个更严格的解决方案:检测NSPasteboard中的内容何时被其他内容替换。

如果您创建符合NSPasteboardWriting的类并将其与实际内容一起传递给-writeObjects:NSPasteboard将保留此对象,直到其内容被替换为止。如果没有对此对象的其他强引用,则会取消分配。

此对象的释放是新NSPasteboard获得新内容的时刻。

答案 5 :(得分:0)

对于需要在Swift 5中进行一些非常简化的版本的用户来说,它就可以工作(基于@Devarshi代码):

    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
                }
            }
        }
    }

使用方法如下:

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

它将打印如下。

watched : pasteboard1
watched : pasteboard2