Приложение macOS: обработка комбинаций клавиш, привязанных к глобальным сочетаниям клавиш

В некоторых приложениях имеет смысл напрямую обрабатывать сочетания клавиш, которые в противном случае привязаны к общесистемным комбинациям. Например, ⌘-Пробел (обычно Spotlight) или ⌘-Tab (обычно переключатель приложений). Это работает в различных приложениях для Mac, таких как VMWare Fusion, собственные клиенты Apple для совместного использования экрана и удаленного рабочего стола (пересылка событий на виртуальную машину или сервер, соответственно, вместо их локальной обработки), а также в некоторых аналогичных сторонних приложениях в приложении. Магазин.

Мы хотели бы реализовать такой режим в приложении, над которым работаем, но пока не можем понять, как это сделать. Я должен отметить, что рассматриваемое приложение является обычным приложением переднего плана, находится в песочнице, и любое решение должно соответствовать правилам App Store. Тот факт, что другие приложения в магазине могут это делать, подразумевает, что это должно быть возможно.

Чтобы было ясно, мы хотим:

  • Обнаружение и обработка всех нажатий клавиш, в том числе связанных с глобальными сочетаниями клавиш.
  • Не допускайте, чтобы глобальные ярлыки вызывали их глобально привязанный эффект.

Документ по архитектуре событий предполагает, что приложение переднего плана уже должно получать эти события. (Это говорит только о более ранних уровнях, обрабатывающих такие вещи, как кнопки питания и извлечения, что нормально.) /Cocoa/Conceptual/EventOverview/HandlingKeyEvents/HandlingKeyEvents.html#//apple_ref/doc/uid/10000060i-CH7-SW11" rel="noreferrer">документ о ключевых событиях также подразумевает, что метод NSApplication sendEvent: обнаруживает потенциальные ярлыки на основе флагов модификаторов, отправляя их в окна и, если это не удается, в строку меню. Не указано явно, что происходит с глобальными ярлыками.

Я попытался создать подкласс NSApplication и переопределить sendEvent:. Независимо от того, передаю ли я все события в реализацию суперкласса или говорю, фильтровать события клавиши-модификатора, когда я нажимаю ⌘-Пробел, я получаю события для нажатия и отпускания клавиши команды (⌘), но не пробела. Пользовательский интерфейс Spotlight всегда всплывает.

Я не нашел много информации о подклассе NSApplication и его ранней обработке событий от Apple или других источников. Я не могу понять, на каком уровне обнаруживаются и обрабатываются глобальные ярлыки.

Может кто-нибудь указать мне в правильном направлении?

Возможные решения, которые не работают:

Предложения, которые я видел в других сообщениях о переполнении стека, но которые не относятся к другим приложениям, которые я видел, которые делают это (и которые нарушают правила App Store):

  • Доступные API (требуется специальное разрешение)
  • Отводы/перехватчики событий (необходимо запускать от имени пользователя root)

И то, и другое в любом случае было бы излишним, поскольку они позволяют перехватывать все события в всегда раз, а не только тогда, когда ваше приложение находится на переднем плане.

Между тем, addGlobalMonitorForEventsMatchingMask:handler: NSevent не предотвращает срабатывание глобального обработчика ярлыков для этих событий, поэтому я даже не стал пытаться это делать.


person pmdj    schedule 10.06.2017    source источник
comment
Нарушает ли RegisterEventHotKey фреймворка Carbon правила AppStore? Он работает по крайней мере в 10.12 Sierra.   -  person vadian    schedule 12.06.2017
comment
Какие сторонние приложения в App Store могут это сделать?   -  person TheNextman    schedule 12.06.2017
comment
@vadian Это кажется невозможным: infincia.com/blog/hotkeys-codepoints-app- магазин   -  person TheNextman    schedule 13.06.2017
comment
@TheNextman Например, Remotix VNC & RDP может проходить через общесистемные ярлыки к управляемой системе, так же, как собственный общий доступ к экрану Apple.   -  person pmdj    schedule 20.06.2017


Ответы (3)


Итак, методы событий Cocoa и отводы событий Quartz отсутствуют, потому что они либо требуют root-доступа или специальных возможностей, либо не перехватывают события до того, как это сделает док-станция.

Carbon PushSymbolicHotKeyMode отсутствует, потому что, согласно документам, он требует доступа к специальным возможностям.

RegisterEventHotKey Carbon, вероятно, отсутствует, потому что Apple, похоже, не разрешает это (см. мою ссылку в комментарии к вопросу). Однако даже в этом случае я проверил, и вы не можете использовать его, чтобы поймать Command + Tab.

Я сделал быстрое доказательство того, как это может работать, но YMMV:

  • реализовать пример класса KeyboardWatcher из этого ответа. Вам нужно будет связать IOKit.
  • добавьте право на песочницу «Оборудование — USB» (com.apple.security.device.usb). Это необходимо, потому что KeyboardWatcher использует HID для обнаружения нажатий клавиш.
  • Handle_DeviceEventCallback даст вам нажатые клавиши. Очевидно, вы можете изменить это в соответствии с вашими потребностями.
  • Используйте SetSystemUIMode, чтобы заблокировать переключатель задач и Spotlight. Вам нужно будет связать Carbon.

SetSystemUIMode(kUIModeContentSuppressed, kUIOptionDisableProcessSwitch);

Обратите внимание, что это будет работать только тогда, когда ваше приложение находится на переднем плане (вероятно, то, что вы хотите). Я установил это в своем представлении с помощью прямоугольника отслеживания, поэтому оно вступает в силу только тогда, когда мышь находится над моим представлением (как в Remotix):

- (void)viewDidLoad {
[super viewDidLoad];

NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:[self.view bounds] options: (NSTrackingMouseEnteredAndExited | 

NSTrackingActiveAlways) owner:self userInfo:nil];
    [self.view addTrackingArea:trackingArea];
}

- (void) mouseEntered:(NSEvent*)theEvent {
    SetSystemUIMode(kUIModeContentSuppressed, kUIOptionDisableProcessSwitch);
}

- (void) mouseExited:(NSEvent*)theEvent {
    SetSystemUIMode(kUIModeNormal, 0);
}

Remotix, похоже, связывает Carbon и IOKit, но я не вижу, есть ли у них права на USB (я пробовал демоверсию, а не версию App Store). Возможно, они делают что-то подобное.


Обычный способ добиться этого — установить отвод событий Quartz. Однако, чтобы получать события, нацеленные на другие приложения, вам нужно (как вы говорите) быть либо root, либо иметь доступ к специальным возможностям для вашего приложения.

Кажется, невозможно использовать отвод событий с текущими правилами песочницы. Это подтверждается на форуме разработчиков. Ссылка только для входа, но цитата из темы:

Есть ли шанс обработать события, исходящие от медиа-ключей, чтобы предотвратить запуск iTunes. Раньше песочницу можно было создать CGEventTap, но теперь песочница запрещает использование hid-controll.

Нет, в настоящее время это невозможно в тестовой среде приложения.

Я не уверен в другом способе сделать это; и мне было бы интересно узнать, какие приложения в App Store можно?

VMWare Fusion явно не изолирована, а собственные приложения Apple освобождены от правил. Помните, что песочница применяется только к новым приложениям, добавленным после того, как она была представлена ​​в 2012 году. Для приложений, добавленных до этой даты, песочница не применяется. См. этот ответ.

person TheNextman    schedule 12.06.2017
comment
Приложение Remotix в App Store последний раз обновлялось в 2016 году, и, если я правильно понимаю вывод codesign -d --entitlements - /Applications/Remotix.app, оно действительно находится в песочнице. (<key>com.apple.security.app-sandbox</key> <true/>) - person pmdj; 20.06.2017
comment
@pmdj верно, но когда оно было добавлено в App Store? Похоже, он существует уже довольно давно (https://web.archive.org/web/20120520143615/https://www.nulana.com/remotix-mac/), и речь идет не о том, когда приложение было обновлено, а о том, когда оно было добавлен. Старые приложения устарели, и их не нужно помещать в песочницу. Или, возможно, у них есть исключение Apple для песочницы — см. ссылку в моем ответе. - person TheNextman; 20.06.2017
comment
Но если у него есть право на песочницу, то, конечно же, он ЯВЛЯЕТСЯ песочницей? Также обратите внимание, что для этого не требуется доступ администратора, и, насколько я вижу, он не связан с функциями касания событий кварца. - person pmdj; 20.06.2017
comment
@pmdj добавил потенциальное решение к ответу - person TheNextman; 21.06.2017

Я решил это много лет назад, но только что заметил, что никогда не публиковал его здесь. Ответ закончился с участием CGSSetGlobalHotKeyOperatingMode(). Это не общедоступный API, но есть несколько приложений в Mac App Store, которые используют его, скрывая имя функции и выполняя динамический поиск. Apple, кажется, не возражает. API довольно прост в использовании, и существует множество открытых примеров исходного кода.

person pmdj    schedule 19.09.2019
comment
Не могли бы вы описать решение или поделиться исходным кодом, который его использует (быстро) - person arthas; 25.05.2021
comment
@bhagyashingale Извините, я не делаю Swift, поэтому я не могу дать вам окончательный ответ, но если вы можете каким-то образом импортировать заголовок C, который объявляет функцию (есть некоторые на GitHub), этого должно быть достаточно. - person pmdj; 25.05.2021

Для тех, кто ищет решение для полноэкранного приложения или если вы хотите использовать полноэкранный режим, вы можете использовать: CGDisplayCapture

Это приведет к тому, что ваше приложение будет захватывать весь ввод с клавиатуры и предотвратит даже вызов Spotlight и переключение приложений с помощью клавиатуры.

import Quartz

// disable keyboard events for all apps, except yours
CGDisplayCapture(CGMainDisplayID())

// reenable keyboard events for other apps
CGDisplayRelease(CGMainDisplayID())

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

Ссылки:

person C. Bess    schedule 12.08.2020