Как отправлять и получать NSApleEventDescriptor при использовании NSWorkspace для открытия URL-адресов между приложениями?

NSWorkspace имеет метод open(_:withAppBundleIdentifier: [...] ):

Открывает один или несколько файлов из массива URL-адресов.

func open(_ urls: [URL], 
     withAppBundleIdentifier bundleIdentifier: String?, 
     options: NSWorkspace.LaunchOptions = [], 
     additionalEventParamDescriptor descriptor: NSAppleEventDescriptor?, 
     launchIdentifiers identifiers:) -> Bool

NSApplicationDelegate приложения, которое вы хотите открыть, имеет соответствующие методы, вызываемые для открытия предоставленных вами URL-адресов:

func application(_ sender: NSApplication, openFile filename: String) -> Bool
func application(_ sender: NSApplication, openFiles filenames: [String])

Вернемся к open(_:withAppBundleIdentifier: [...]), этот метод имеет параметр NSApleEventDescriptor:

additionalEventParamDescriptor descriptor: NSAppleEventDescriptor?
Дополнительные параметры, указанные в дескрипторе в стиле AppleEvent. Например, вы можете использовать этот параметр, чтобы указать дополнительные документы, которые будут открываться при запуске приложения.


Я хотел бы отправить дополнительную информацию в приложение, которое откроет файлы.

Это будет использоваться аналогично словарю userInfo в уведомлении.

Я создал объект NSAppleEventDescriptor для представления этой информации. Я могу установить этот дескриптор события в функции NSWorkspace open( ... ).

Но как мне получить этот дескриптор события в Application Delegate целевого приложения?

Функции application(_: openFile:) не имеют параметров для дескрипторов событий или какой-либо другой дополнительной информации типа userInfo.


Код

Основываясь на ответах и ​​других вопросах, я остановился на решении ниже. Теперь я получаю инициированный обработчик для событий Apple. Но событие Apple, которое я устанавливаю в функции NSWorkspace, — это не то, что получено в обработчике! Как вместо этого получить мое мероприятие Apple?


Отправлять

let appleEvent = NSAppleEventDescriptor(eventClass:       AEEventClass(kCoreEventClass),
                                        eventID:          AEEventID(kAEOpenDocuments),
                                        targetDescriptor: nil,
                                        returnID:         AEReturnID(kAutoGenerateReturnID),
                                        transactionID:    AETransactionID(kAnyTransactionID))
appleEvent.setDescriptor(NSAppleEventDescriptor(string: "THIS IS A TEST"), forKeyword: keyDirectObject)

let didOpen = AppKit.NSWorkspace.shared.open([URL(fileURLWithPath: "/path/image.png")],
                                             withAppBundleIdentifier: bundleID,
                                             options: [.withErrorPresentation],
                                             additionalEventParamDescriptor: appleEvent,
                                             launchIdentifiers: nil)

Отправлено событие Apple:

<NSAppleEventDescriptor: 'aevt'\'odoc'{ '----':'utxt'("THIS IS A TEST") }>


Получать

class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        NSAppleEventManager.shared().setEventHandler(self,
                                                     andSelector: #selector(handle(event:replyEvent:)),
                                                     forEventClass: AEEventClass(kCoreEventClass),
                                                     andEventID: AEEventID(kAEOpenDocuments))
    }
    
    @objc func handle(event: NSAppleEventDescriptor?, replyEvent: NSAppleEventDescriptor?) {
        guard let event = event,
            event.eventClass == AEEventClass(kCoreEventClass) && event.eventID == AEEventID(kAEOpenDocuments) else {
            return
        }

        guard let additionalEventParamDescriptor = event.paramDescriptor(forKeyword: keyAEPropData) else {
            return
        }

        
        guard let directObject = additionalEventParamDescriptor.paramDescriptor(forKeyword: keyDirectObject) else {
            return
        }
        
        print(directObject)
    }
    
}

Полученное событие Apple:

<NSAppleEventDescriptor: 'aevt'\'odoc'{ '----':[ 'bmrk'(888/$626F6F6B7803000000000 [....] 00000AC01000000000000...$) ] }>


person pkamb    schedule 09.08.2019    source источник
comment
stackoverflow.com/questions/5812371/   -  person pkamb    schedule 14.08.2019
comment
barebones.com/support/develop/odbsuite.html ››› При этом сервер добавляет к событию odoc дополнительный параметр. ‹‹‹   -  person pkamb    schedule 19.08.2019
comment
Ваше приложение будет получать только события Apple, предназначенные для него, то есть события, указывающие ваше приложение в дескрипторе целевого адреса. applescriptlibrary.files.wordpress.com/2013/11/   -  person pkamb    schedule 20.08.2019
comment
Видя журнал консоли AppleEvents failed to encode extension for /Users/user/Desktop, err=Operation not permitted/1 - это одно из мест назначения, которое я отправляю в сценарии Apple...   -  person pkamb    schedule 20.08.2019
comment
Не беспокойтесь о передаче дополнительного дескриптораEventParamDescriptor. Это трудно сделать правильно, и я видел доказательства того, что это даже не передано должным образом. (Я перешел к прямому использованию Launch Services, которые правильно передали параметр.) lists.apple.com/archives/cocoa-dev/2009/Apr/msg01702.html   -  person pkamb    schedule 20.08.2019
comment
Опубликовано на форумах разработчиков Apple: forums.developer.apple.com/message/377852   -  person pkamb    schedule 20.08.2019
comment
Часть ~'prdt' выглядит как passThruParams Указатель на Дескриптор события Apple, который передается нетронутым как необязательный параметр с ключевым словом keyAEPropData ('prdt') в событии Apple, отправляемом каждому запущенному или активированному приложению.   -  person pkamb    schedule 21.08.2019
comment
обсуждение в Твиттере: twitter.com/catlan/status/1164242863066112000   -  person pkamb    schedule 22.08.2019
comment
Создан пример проекта: github.com/pkamb/Feedback_AdditionalEventParamDescriptor   -  person pkamb    schedule 27.08.2019
comment
Я открыл билет DTS для этого. Добавление ответа здесь на основе этого билета находится в моем списке TODO, пингуйте меня, если вам это нужно.   -  person pkamb    schedule 14.03.2020


Ответы (1)


Подход к событиям kAEOpenDocuments

- (void)applicationWillFinishLaunching:(NSNotification *)notification {
    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
}

- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {

}

-[NSWorkspace openURLs: ...] генерирует событие kAEOpenDocuments, содержащее URL-адреса в качестве данных закладок для сохранения в песочнице. (См. +[NSURL URLByResolvingBookmarkData: options: relativeToURL: bookmarkDataIsStale: error:]).

дополнительный дескрипторEventParamDescriptor:

При создании additionalEventParamDescriptor с kAEOpenDocuments с пользовательскими параметрами это событие, кажется, объединяется с базовым событием kAEOpenDocuments из -[NSWorkspace openURLs: ...].

NSAppleEventDescriptor *targetDescriptor = nil;
NSAppleEventDescriptor *appleEvent = nil;

targetDescriptor = [NSAppleEventDescriptor descriptorWithDescriptorType:typeApplicationBundleID
                                                                   data:targetBundleID];
appleEvent = [NSAppleEventDescriptor appleEventWithEventClass:kCoreEventClass
                                                      eventID:kAEOpenDocuments
                                             targetDescriptor:targetDescriptor
                                                     returnID:kAutoGenerateReturnID
                                                transactionID:kAnyTransactionID];
[appleEvent setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:@"You're so good looking"]
                    forKeyword:'urln'];

[[NSWorkspace sharedWorkspace] openURLs:@[ [[NSBundle mainBundle] resourceURL] ]
                withAppBundleIdentifier:bundleIdentifier
                                options:NSWorkspaceLaunchNewInstance
         additionalEventParamDescriptor:appleEvent
                      launchIdentifiers:NULL];

Пример вывода lldb:

NSAppleEventDescriptor: 'aevt'\'odoc'{ ~'prdt':'aevt'\'odoc'{ 'urln':'utxt'("Ты так хорошо выглядишь") }, ' ----':[ 'бмрк'(1432/$626F6F6B980 ...) }

Примечание. При настройке NSAppleEventManager для kAEOpenDocuments это перезаписывает встроенные функции AppKits методов application:openFile: или application:openFiles:. Пользовательский обработчик событий должен реализовать все это.

индивидуальный подход к событиям

Основываясь на моих выводах, отправка пользовательского класса события с пользовательским идентификатором события не запускает обработчик событий. ¯_(ツ)_/¯

person catlan    schedule 12.08.2019
comment
Я использую eventClass: AEEventClass(kCoreEventClass), eventID: AEEventID(kAEOpenDocuments). Я получаю обратный вызов, но, как ни странно, это совершенно другое событие в обработчике. Какой класс/идентификатор следует использовать? Или есть какая-то другая причина, по которой событие изменяется в пути? - person pkamb; 17.08.2019
comment
Из того, что я видел, вы получаете событие kAEOpenDocuments, а не дополнительное событие. Что имеет смысл. Но я также не смог заставить настраиваемый обработчик событий запускать дополнительное событие. - person catlan; 19.08.2019
comment
Я обновил свой ответ, никогда не делал того, что вы пытаетесь сделать сам. Только вспомнил, как играл с чем-то, связанным некоторое время назад, и подумал, что попробую. - person catlan; 19.08.2019
comment
Проблема в том, что мой обработчик запускается, даже если я НЕ включаю additionalEventParamDescriptor: в свой вызов функции NSWorkspace. В обоих случаях в обработчике событие отображается как событие закладки с прямым объектом: <NSAppleEventDescriptor: [ 'bmrk'(888/$626F6F6B7..... Поэтому я не знаю, как обрабатывать событие Apple MY, а не то, которое, кажется, отправляется автоматически. Даже когда я добавляю свое событие в качестве ключевого слова, это ключевое слово не может быть найдено в обработанном событии. Где вы можете справиться с вашим реальным событием? - person pkamb; 19.08.2019
comment
Надеюсь, что обновленный ответ прояснит ситуацию ... все еще потребуется работа с разбором события kAEOpenDocuments. - person catlan; 20.08.2019
comment
Я понимаю, что вы говорите, и я ожидаю, что Apple Events объединятся. Мой код почти идентичен коду в вашем ответе (и я собираюсь точно попробовать ваш код). Но я все еще НИКОГДА не вижу свое пользовательское событие Apple в обработанном событии; только встроенные события закладки :/ - person pkamb; 20.08.2019
comment
Несоответствие, кажется, в открываемых URL-адресах. Если я открою [Bundle.main.resourceURL!] (как в вашем примере), я, наконец, увижу свое пользовательское событие Apple. Если я использую URL-адреса файлов, которые я действительно хочу открыть, я не вижу свое пользовательское событие Apple в обработчике... - person pkamb; 20.08.2019
comment
Хм, ваше приложение изолировано или нет? - person catlan; 20.08.2019
comment
Да, это песочница. - person pkamb; 20.08.2019
comment
Хм. Мой тест также изолирован. Я использую URL-адрес NSOpenPanel для прохождения. макОС 10.14.6. - person catlan; 21.08.2019
comment
Я переключил ваш код на использование NSWorkspaceLaunchWithErrorPresentation вместо NSWorkspaceLaunchNewInstance для упрощения отладки/точки останова. Когда я выбираю .JPG, событие содержит additionalEventParamDescriptor. Когда я выбираю .png или .pdf, это НЕ содержит дополнительное событие. Сейчас тестирую, это соответствует моему приложению. Тот же .JPG работает! Но файл .png, который я тестировал, этого не делает. - person pkamb; 21.08.2019
comment
Хм. Только что попробовал это с png, и он содержал дополнительный параметр. NSWorkspaceLaunchNewInstance: я сам запускаю один экземпляр приложения, затем запускаю Xcode с ожиданием запуска исполняемого файла, а затем нажимаю кнопку. - person catlan; 21.08.2019
comment
Не мог бы кто-нибудь из вас добавить недостающий код, который анализирует событие apple и извлекает из него дополнительныйEventParamDescriptor? Мне это нужно, и я нигде не могу найти разумного объяснения того, как создать такой код. - person Motti Shneor; 24.05.2021
comment
@MottiShneor, этот код находится внизу моего исходного вопроса. - person pkamb; 25.05.2021