С новым обновлением macOS внесены новые критические изменения. Одним из важных изменений в Catalina является принцип работы наблюдения за распределенными уведомлениями.

Но сначала, что такое рассылаемые уведомления?

Многие люди будут знакомы с концепцией регулярных уведомлений. Вы знаете, это предупреждение, которое появляется где-то на экране и дает вам некоторую информацию о чем-то.

Распределенные уведомления похожи на обычные уведомления (фактически, они отправляют ту же структуру NSNotification), но вместо того, чтобы показывать их пользователю, они могут транслироваться в другие процессы, которые прослушивают это конкретное уведомление. В более общем смысле распределенные уведомления - это форма межпроцессного взаимодействия.

Для чего мы можем использовать распределенные уведомления?

Одним очень полезным уведомлением, которое выдает iTunes, является уведомление com.apple.iTunes.playerInfo. Это уведомление выдается всякий раз, когда пользователь меняет состояние текущего трека, в том числе когда он прекращает воспроизведение или выбирает новый трек. С уведомлением связан набор полезных полей об изменении, таких как название трека, исполнитель и общая длина трека.

Зарегистрировав наблюдателя для этого уведомления, я создал клиент iTunes Discord Rich Presence, который автоматически обновляет песню, которую я сейчас проигрываю (что-то вроде Rich Presence Spotify).

Основное преимущество этого метода по сравнению с опросом состояния проигрывателя iTunes с помощью AppleScript заключается в том, что он использует меньше процессорного времени и, следовательно, более энергоэффективен.

Проблема с macOS Catalina

Каталина заменила любимое приложение iTunes на Apple Music (которая, кстати, намного медленнее и медленнее, но это уже история для другого раза). Конечно, это означает, что название любых распространяемых уведомлений, связанных с iTunes, изменилось.

Исправить это просто. Просто замените старое название уведомления новым. Один из параметров регистрации наблюдателя уведомлений:

notificationName

Название уведомления, для которого нужно зарегистрировать наблюдателя; то есть, наблюдателю доставляются только уведомления с этим именем. Когда nil, центр уведомлений не использует имя уведомления, чтобы решить, доставить ли его наблюдателю.

Идеально! Все, что нам нужно сделать, это оставить имя nil, и это позволит нам прослушивать все распространяемые уведомления. Затем нам просто нужно найти тот, который испускается при смене трека.

Хорошо, давайте запустим его и подождем, пока в него поступят все данные.

Ничего не происходит?

Вот подсказка. Проблема не в моем коде. Этот метод не работает в macOS Catalina. Поведение метода addObserver изменилось так, что оставление имени nil ничего не даст, если процесс не является привилегированным. Между прочим, запуск кода под sudo тоже не работает.

Нахождение названия уведомления

Сначала рассматривал два основных варианта:

  • Отслеживание системных вызовов музыкального приложения
  • Грубая форсировка всех возможных названий уведомлений

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

  • 26 строчных и 26 прописных букв
  • Не менее десяти символов в имени
  • 52¹⁰ - это около 144 квадриллионов операций

Да, это не сработает.

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

Чтобы проверить свою идею, я сначала попробовал запустить strings на Spotify. Большая часть собственно исполняемого файла находится внутри пакета приложения по адресу: Contents/MacOS/Spotify. Мы знаем, что одно из названий уведомлений Spotify - com.spotify.client.PlaybackStateChanged, поэтому мы можем сопоставить это с выводом из strings Spotify:

Большой! Давайте попробуем сделать то же самое с Apple Music сейчас.

В Catalina приложение «Музыка» находится в /System/Applications. Несмотря на то, что мы не знаем точное название уведомления, мы можем отфильтровать множество строк, сопоставив их с com.apple.Music. Похоже, что все уведомления должны содержать идентификатор пакета приложения.

Одно из этих имен подозрительно похоже на уведомление iTunes. В частности: com.apple.Music.playerInfo. Давай попробуем!

Блестяще! Наконец-то мы распечатали некоторые данные. Оно немного отличается от уведомления iTunes (например, больше нет поля Display Line 0), но исправить это сравнительно простая задача.

В итоге

  • Распределенные уведомления - это способ для разных процессов отправлять сообщения друг другу.
  • Каталина прерывает использование nil в качестве значения параметра имени в addObserver для прослушивания всех распределенных уведомлений.
  • Инструмент strings можно использовать для поиска списка потенциальных имен уведомлений для приложения путем фильтрации по идентификатору пакета.

В идеале Apple должна обновить документацию, чтобы отразить это изменение в поведении, но кажется, что это слишком нишевая тема, чтобы ее упоминать. Я просто рад, что снова заработал Music Rich Presence.