Могу ли я получить обратный вызов всякий раз, когда записывается NSPasteboard?

Я прочитал Руководство по программированию на картоне, но это не отвечает на мой конкретный вопрос.

Я пытаюсь написать приложение Cocoa (для OS X, а не iOS), которое будет отслеживать все, что записывается в общий монтажный стол (поэтому всякий раз, когда любое приложение копирует и вставляет, но а не, скажем, перетаскивание, которое также использует NSPasteboard). Я мог бы (почти) добиться этого, постоянно опрашивая общий монтажный стол в фоновом потоке и проверяя changeCount. Конечно, делая это, я чувствую себя очень грязным внутри.

Мой вопрос: есть ли способ попросить сервер картона уведомить меня через какой-то обратный вызов каждый раз, когда в общий монтажный стол вносятся изменения? Я ничего не смог найти в справочнике по классу NSPasteboard, но надеюсь, что он скрывается где-то еще.

Другой способ, которым я мог бы вообразить выполнение этого, - это если бы был способ заменить общую реализацию монтажного стола подклассом NSPasteboard, который я мог бы определить самостоятельно для выполнения обратного вызова. Может быть, что-то подобное возможно?

Я бы предпочел, чтобы это было возможно с общедоступными API, легальными в App Store, но если необходимо использование частного API, я тоже соглашусь.

Спасибо!


person Adrian Petrescu    schedule 17.02.2011    source источник
comment
Не ответ, но кое-что, о чем следует знать, если вы следите за монтажным столом в целом: существует неофициальный протокол для отметки переходных и сгенерированных приложением данных на монтажном столе: nspasteboard.org   -  person Smilin Brian    schedule 29.11.2012


Ответы (6)


К сожалению, единственный доступный метод — это опрос (бу-у-у!). Уведомлений нет и нечего наблюдать за измененным содержимым картона. Ознакомьтесь с пример кода ClipboardViewer, чтобы увидеть, как они справляются с проверкой буфера обмена. Добавьте (надеюсь, не слишком усердный) таймер, чтобы постоянно проверять различия, и вы получите базовое (хотя и неуклюжее) решение, которое должно быть дружественным к App Store.

Отправьте запрос на улучшение по адресу bugreporter.apple.com, чтобы запросить уведомления или другой обратный вызов. К сожалению, это не поможет вам, по крайней мере, до следующего крупного выпуска ОС, но пока это опрос, пока мы все не попросим их дать нам что-то лучше.

person Joshua Nozzi    schedule 17.02.2011
comment
Я боялся этого. Спасибо! :) - person Adrian Petrescu; 17.02.2011
comment
какие-то изменения по сравнению с 2,5 года назад? - person tofutim; 24.08.2013
comment
какие-то изменения с 3-х летней давности? - person tofutim; 07.03.2014
comment
Я пока ничего не вижу, но, учитывая последние версии (10.10 Yosemite и iOS 8), монтажный стол может оставаться таким же простым, как и сейчас, поскольку теперь есть более современный, более осведомленный о контенте механизм, который Apple рекламирует для передачи информации. между приложениями (и устройствами). Внезапно монтажный стол кажется меньшей целью для тех приложений, которые заинтересованы в управлении временными неоднозначными пространствами для хранения... ;-) - person Joshua Nozzi; 07.07.2014
comment
@JoshuaNozzi Что это за новый механизм? - person Miscreant; 16.07.2018
comment
@Miscreant Ха ... Я пытаюсь придумать, что, по моему мнению, было бы лучше для передачи данных. Возможно, я имел в виду расширения приложений для совместного использования и применения действий из других приложений. В зависимости от того, чего вы пытаетесь достичь, это может помочь или не помочь, но этот механизм оказался не таким полезным, как мы надеялись в подобных случаях. - person Joshua Nozzi; 16.07.2018
comment
Все еще так в 2020 году? - person Supertecnoboff; 03.03.2020
comment
@Supertecnoboff К сожалению, так оно и есть. Нет нового API, позволяющего выполнять обратные вызовы. - person Joshua Nozzi; 03.03.2020

Однажды в списке рассылки был пост, где описывалось решение против API уведомлений. Я не могу найти его прямо сейчас. Суть в том, что, вероятно, слишком много приложений будут регистрироваться для этого API, хотя на самом деле им это не нужно. Если вы затем что-то скопируете, вся система будет как сумасшедшая просматривать новое содержимое буфера обмена, создавая много работы для компьютера. Так что я не думаю, что они изменят свое поведение в ближайшее время. Весь API NSPasteboard также внутренне построен на использовании changeCount. Таким образом, даже ваш пользовательский подкласс NSPasteboard все равно должен будет продолжать опрос.

Если вы действительно хотите проверить, изменился ли монтажный стол, просто продолжайте наблюдать за changeCount полсекунды. Сравнение целых чисел выполняется очень быстро, поэтому здесь нет проблем с производительностью.

person Karsten    schedule 07.04.2012
comment
Предполагая, что мгновенный даже необходим. Интересно, действительно ли необходимо отмечать каждое отдельное изменение (и, следовательно, нужно постоянно спрашивать с детской заботой о отвратительной трате ресурсов, это спрашивать каждую чертову секунду: МЫ ЕЩЕ ТАМ???!!!). ;-) Лучше всего смотреть, когда пользователь вызывает вашу службу (в противном случае вы являетесь любопытным приложением, тратящим ресурсы). Если вы должны перехватывать фоновые обновления (возможно, для ведения журнала), возможно, достаточно каждые несколько секунд. Если пользователь копирует что-то дважды за несколько секунд, первая, вероятно, была ошибкой, которую он позже исправил... - person Joshua Nozzi; 07.07.2014
comment
честно говоря, что расточительного в том, чтобы запрашивать у NSPasteboard значение changeCount и сравнивать два целых числа? Я бы согласился с тем, что такое поведение было расточительным в начале девяностых и раньше, но не сейчас. - person Karsten; 09.07.2014
comment
Энергопотребление современных ноутбуков для вас не важно? В наши дни жизненно важно разрешить перевод вашего приложения в спящий режим, и, за исключением приложения, целью которого является журнал каждой отдельной вещи, которая помещается на монтажный стол, независимо от того, насколько коротко (включая случайные и немедленно исправленные копии). ), нет нет веской причины часто опрашивать это. Опрашивайте, когда пользователю действительно понадобится информация (например, когда ваше приложение вызывается/выводится на передний план), если только вы не можете продемонстрировать очень, очень вескую причину, почему вашему приложению необходимо разряжать аккумулятор вашего ноутбука. - person Joshua Nozzi; 10.07.2014
comment
опрос каждые полсекунды не совсем разряжает батарею, особенно если он проверяет только количество изменений. Я только что создал тестовый проект, и он показывает нулевое воздействие энергии. Единственный сценарий, в котором это было бы полезно, — это приложения, которые создают историю вашего буфера обмена. - person Karsten; 12.07.2014
comment
В связанном посте (который привел меня к этому) я упомянул почти то же самое относительно единственной причины, по которой вам может потребоваться так часто проводить опросы. Но повторяющийся таймер заставит приложение периодически просыпаться для работы, никогда не позволяя ему долго вздремнуть. Это то, что подсказывает здравый смысл, но вы, похоже, доказали обратное. Не могли бы вы поделиться проектом где-нибудь? (Совершенно дружеский профессиональный интерес, а не преувеличение, обещаю. :-)) - person Joshua Nozzi; 14.07.2014
comment
Обновление: опрос определенно будет тратить заряд батареи на современных устройствах Apple, потому что он предотвращает переход системы в состояние низкого энергопотребления. - person Joshua Nozzi; 10.04.2021
comment
Вы можете установить качество жизни потока или очереди. Это будет вызывать ваш таймер время от времени, но он будет согласовывать этот таймер с другими процессами, которые также должны работать, а не быть единственной вещью в системе, которая продолжает работать. С другой стороны, если система простаивает (что вы могли бы обнаружить), то нет смысла следить за буфером обмена, потому что никто активно не работает с буфером обмена. - person Karsten; 12.04.2021
comment
Вы имеете в виду качество обслуживания, @Karsten? Не могли бы вы развить эту идею? Таймер все равно должен быть запланирован. Использование настроек допуска (которые действительно улучшают энергопотребление за счет большего согласования с системой) не сильно поможет, поскольку он по-прежнему будет регулярно опрашивать, даже если более гибко. Слишком много времени между опросом, и пользователь задается вопросом, где находится вещь, которую он только что скопировал, если он попытается использовать ее быстро. Однако мне интересна ваша идея - возможно, я неправильно понял ваше предложение объединить таймеры с GCD QoS. - person Joshua Nozzi; 22.04.2021
comment
(Обратите внимание, когда я сказал, что [опрос] предотвращает переход системы в режим пониженного энергопотребления, я не имел в виду, что он удерживает систему от спящего режима; я имел в виду режимы с низким энергопотреблением, но активные режимы, то есть перевод ядер в режим ожидания в ситуациях с низким потреблением.) - person Joshua Nozzi; 22.04.2021
comment
Вы оба говорите о системе, которая переходит в состояние низкого энергопотребления, и о системе, в которой пользователь активно копирует данные в буфер обмена. Я не думаю, что оба состояния возможны одновременно. Либо пользователь использует систему, и в этом случае ваш процесс опроса ничего не прерывает, либо пользователь отсутствует, и в этом случае вы можете не проводить опрос, потому что какие изменения вы обнаружите? - person Karsten; 25.04.2021
comment
Возможно, вы не знаете, как платформы Apple управляют состояниями простоя и питанием приложений. Абсолютно на 100% возможны моменты, когда система используется, но находится в состоянии низкого энергопотребления, и довольно часто система переходит в состояние низкого энергопотребления только между действиями пользователя (через несколько мгновений), если в ней больше ничего не происходит. Приложение с активным повторяющимся таймером (независимо от очереди, в которой обрабатываются его события) «что-то происходит». Откровенно говоря, ваше предложение не лучше, чем опрос, потому что он является опросом и потребует больше энергии. - person Joshua Nozzi; 25.04.2021
comment
Я только что наткнулся на метод tolerance в NSTimer, который позволяет вашему таймеру не срабатывать точно через заданный интервал, но позволяет системе лучше согласовывать этот таймер с другими операциями вашей системы. Я думаю, что это должно решить проблему предотвращения перехода системы в спящий режим. - person Karsten; 12.05.2021
comment
Он будет качаться только до сих пор (недостаточно далеко). Подробнее см. в документации. - person Joshua Nozzi; 13.05.2021

Основываясь на ответе, предоставленном Джошуа, я придумал аналогичную реализацию, но быстро, вот ссылка на ее суть: 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
        }
    }
}

Примечание: в общем коде я пытаюсь определить, скопировал ли пользователь URL-адрес файла или нет, предоставленный код можно легко изменить для других общих целей.

person Devarshi    schedule 20.06.2015
comment
Я только сегодня увидел это и проголосовал. Красивое, простое решение. Предложение: либо требуйте делегата при инициализации (поэтому это необязательно), либо реализуйте didSet для делегата, чтобы создать/запустить таймер, если делегат дан, или остановить/уничтожить, если его забрали. Вы также, вероятно, должны сделать делегата слабым, чтобы избежать циклов сохранения. Таким образом, вы избегаете потребления ресурсов, если делегат уходит. (Возможно, это невозможно при вашем текущем использовании, но подумайте о «повторном использовании»). - person Joshua Nozzi; 27.07.2017

Опрашивать не обязательно. Вставка обычно изменяется только в том случае, если текущее представление неактивно или не имеет фокуса. В Pasteboard есть счетчик, который увеличивается при изменении содержимого. Когда окно восстанавливает фокус (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
    }
}
person JustPixelz    schedule 31.07.2017
comment
Это отличная идея для работы с панелью поиска. Предполагается, что панели инструментов/панелей/и т. д. текстового поиска приложений (то, что вы видите, когда набираете Cmd-F) должны отслеживать глобальный монтажный стол поиска. - person MtnViewJohn; 01.09.2018

Для тех, кому нужна очень упрощенная версия в 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
person boraseoksoon    schedule 16.02.2020

У меня есть решение для более строгого случая: определить, когда ваш контент в NSPasteboard был заменен чем-то другим.

Если вы создадите класс, соответствующий NSPasteboardWriting, и передадите его -writeObjects: вместе с фактическим содержимым, NSPasteboard сохранит этот объект до тех пор, пока его содержимое не будет заменено. Если нет других сильных ссылок на этот объект, он освобождается.

Освобождение этого объекта — это момент, когда новый NSPasteboard получил новый контент.

person Tricertops    schedule 03.08.2017
comment
Это не будет работать для вещей, которые кэшируются/уникальны в системе. Подумайте о NSString константах, NSIndexPath и т. д. Они сохраняются на протяжении всего времени существования приложения. Есть также много других ситуаций, когда что-то сохраняется в другом месте сверх ожидаемого срока службы по причинам. Пожалуйста, не делайте ничего, что зависит от того, когда что-то еще освобождается. Только когда он находится в пределах Dealloc/Deinit освобожденного экземпляра. - person Joshua Nozzi; 22.04.2021