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

Adr*_*scu 41 macos cocoa nspasteboard

我读过Apple的" 纸板编程指南",但它没有回答我的特定问题.

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

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

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

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

谢谢!

Jos*_*zzi 40

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

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

  • 自2.5年以来的任何变化? (4认同)
  • 自3年前以来的任何变化? (3认同)
  • 我以前很怕那个.谢谢!:) (2认同)
  • @Supertecnoboff 看来是这样,可悲的是。没有允许回调的新 API。 (2认同)
  • 哈哈,2021 年我又来了……我想没有什么变化 (2认同)

Kar*_*ten 12

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

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


Dev*_*shi 10

基于Joshua提供的答案,我提出了类似的实现,但是在swift中,这里是它的要点的链接: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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:在共享代码中,我试图识别用户是否复制了文件URL,可以轻松修改提供的代码以用于其他一般目的.


bor*_*oon 8

对于那些需要在 Swift 5.7 中完成工作的简化版本代码片段的人,

它就可以工作(基于@Devarshi代码):

func watch(using closure: @escaping (_ copiedString: String) -> Void) {
    let pasteboard = NSPasteboard.general
    var changeCount = NSPasteboard.general.changeCount

    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
        guard let copiedString = pasteboard.string(forType: .string),
              pasteboard.changeCount != changeCount else { return }

        defer {
            changeCount = pasteboard.changeCount
        }
        
        closure(copiedString)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用方法如下:

watch {
    print("detected : \($0)")
}
Run Code Online (Sandbox Code Playgroud)

然后,如果您尝试复制粘贴板中的任何文本,它将监视并打印到控制台,如下所示。

detected : your copied message in pasteboard
detected : your copied message in pasteboard
Run Code Online (Sandbox Code Playgroud)

以防万一,完整的代码示例说明如何在 SwiftUI 中使用它:

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    watch {
                        print("detect : \($0)")
                    }
                }
        }
    }
    
    func watch(using closure: @escaping (_ copiedString: String) -> Void) {
        let pasteboard = NSPasteboard.general
        var changeCount = NSPasteboard.general.changeCount

        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            guard let copiedString = pasteboard.string(forType: .string),
                  pasteboard.changeCount != changeCount else { return }

            defer {
                changeCount = pasteboard.changeCount
            }
            
            closure(copiedString)
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

Swift 异步等待版本:

import SwiftUI

@main
struct Test2App: App {
    var isWatch = true
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    while true {
                        let copy = await watch()
                        
                        if let copy {
                            print("copy : \(copy)")
                        }
                    }
                }
        }
    }
    
    func watch() async -> String? {
        let pasteboard = NSPasteboard.general
        var changeCount = NSPasteboard.general.changeCount

        try? await Task.sleep(nanoseconds: 500_000_000)
        guard let copyString = pasteboard.string(forType: .string),
              pasteboard.changeCount != changeCount else { return nil }

        changeCount = pasteboard.changeCount
        return copyString
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这些只是示例。

除此之外,你可以做任何你想做的事。