Как обнаруживать клики по Dock в macOS Swift приложениях
Узнайте несколько способов обнаружения, когда пользователи кликают по другим приложениям в Dock в вашем macOS Swift приложении. Используйте уведомления NSWorkspace, фреймворк Accessibility и мониторинг CGWindowList для надежного обнаружения.
Как обнаружить в приложении macOS, когда пользователь кликает другое приложение в Dock?
Я разрабатыва полноэкранное приложение для macOS (плавающий уровень), которое должно автоматически скрываться, когда пользователь запускает другое приложение из Dock. Какой лучший подход для обнаружения кликов по Dock в Swift для macOS?
Для обнаружения щелчка пользователя по другому приложению в Dock можно использовать комбинацию уведомлений NSWorkspace и мониторинга фреймворка Accessibility в Swift. Наиболее надежный подход включает прослушивание событий активации приложений и отслеживание изменений состояния окна для определения, когда ваше приложение должно скрываться при запуске другого приложения.
Содержание
- Подход с использованием уведомлений NSWorkspace
- Метод с использованием фреймворка Accessibility
- Мониторинг CGWindowList
- Примеры реализации
- Лучшие практики и рекомендации
Подход с использованием уведомлений NSWorkspace
Класс NSWorkspace предоставляет уведомления, которые могут помочь отслеживать запуск и активацию приложений. Это наиболее прямой метод для обнаружения запуска другого приложения из Dock.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var workspace: NSWorkspace!
var runningApps: [String: Process] = [:]
func applicationDidFinishLaunching(_ notification: Notification) {
workspace = NSWorkspace.shared
setupWorkspaceNotifications()
}
private func setupWorkspaceNotifications() {
// Прослушиваем запуски приложений
workspace.notificationCenter.addObserver(
forName: NSWorkspace.didLaunchApplicationNotification,
object: nil,
queue: .main
) { [weak self] notification in
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return }
self?.handleAppLaunch(app)
}
// Прослушиваем активации приложений
workspace.notificationCenter.addObserver(
forName: NSWorkspace.didActivateApplicationNotification,
object: nil,
queue: .main
) { [weak self] notification in
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return }
self?.handleAppActivation(app)
}
}
private func handleAppLaunch(_ app: NSRunningApplication) {
let bundleId = app.bundleIdentifier ?? "unknown"
print("Приложение запущено: \(app.localizedName ?? bundleId)")
if bundleId != Bundle.main.bundleIdentifier {
// Запущено другое приложение, скрываем ваше приложение
hideFullscreenApp()
}
}
private func handleAppActivation(_ app: NSRunningApplication) {
let bundleId = app.bundleIdentifier ?? "unknown"
print("Приложение активировано: \(app.localizedName ?? bundleId)")
if bundleId != Bundle.main.bundleIdentifier {
// Активировано другое приложение, скрываем ваше приложение
hideFullscreenApp()
}
}
private func hideFullscreenApp() {
// Реализуйте здесь логику скрытия вашего приложения
NSApp.setActivationPolicy(.prohibited)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
NSApp.setActivationPolicy(.regular)
}
}
}
Этот подход использует API NSWorkspace для мониторинга событий запуска и активации приложений. При получении уведомления проверяется, было ли запущено другое приложение по сравнению с вашим текущим приложением, и запускается действие по скрытию.
Метод с использованием фреймворка Accessibility
Для более комплексного мониторинга можно использовать фреймворк Accessibility для отслеживания изменений состояния приложения и видимости окна.
import Cocoa
import ApplicationServices
class AppMonitor {
private var observer: AXObserver?
private var observedApp: AXUIElement?
func startMonitoring() {
guard let pid = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == Bundle.main.bundleIdentifier })?.processIdentifier else { return }
let appElement: AXUIElement = AXUIElementCreateApplication(pid)
var observer: AXObserver?
let error = AXObserverCreate(pid, appStateChanged, &observer)
if error == .success, let observer = observer {
self.observer = observer
// Наблюдаем за изменениями активации приложения
let notifications = [kAXFocusedUIElementChangedNotification, kAXApplicationActivatedNotification] as CFArray
AXObserverAddNotification(observer, appElement, notifications, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
CFRunLoopAddSource(CFRunLoopGetCurrent(), AXObserverGetRunLoopSource(observer), .commonModes)
}
}
private func appStateChanged(_ observer: AXObserver?, element: AXUIElement?, notification: CFString, userData: UnsafeMutableRawPointer?) {
guard let userData = userData else { return }
let monitor = Unmanaged<AppMonitor>.fromOpaque(userData).takeUnretainedValue()
// Проверяем, было ли активировано другое приложение
monitor.checkForAppActivation()
}
private func checkForAppActivation() {
let runningApps = NSWorkspace.shared.runningApplications
let currentAppPid = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == Bundle.main.bundleIdentifier })?.processIdentifier
let otherApps = runningApps.filter { $0.processIdentifier != currentAppPid }
if !otherApps.isEmpty {
hideFullscreenApp()
}
}
private func hideFullscreenApp() {
// Логика скрытия здесь
}
}
Фреймворк Accessibility предоставляет более детальный контроль над мониторингом изменений состояния приложения, как упоминается в обсуждении на Stack Overflow.
Мониторинг CGWindowList
Также можно использовать CGWindowList для отслеживания изменений окон между приложениями:
import Cocoa
class WindowMonitor {
private var timer: Timer?
func startMonitoring() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.checkWindowChanges()
}
}
private func checkWindowChanges() {
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
guard let windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else { return }
let currentAppPid = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == Bundle.main.bundleIdentifier })?.processIdentifier
let otherAppWindows = windowList.filter { window in
guard let windowPid = window[kCGWindowOwnerPID as String] as? pid_t,
windowPid != currentAppPid else { return false }
return true
}
if !otherAppWindows.isEmpty {
hideFullscreenApp()
}
}
private func hideFullscreenApp() {
// Логика скрытия здесь
}
}
Примеры реализации
Вот полная реализация, объединяющая несколько подходов для повышения надежности:
import Cocoa
import ApplicationServices
class DockClickMonitor {
private var workspace: NSWorkspace?
private var accessibilityObserver: AXObserver?
private var windowMonitor: WindowMonitor?
func startMonitoring() {
setupWorkspaceNotifications()
setupAccessibilityMonitoring()
setupWindowMonitoring()
}
private func setupWorkspaceNotifications() {
workspace = NSWorkspace.shared
workspace?.notificationCenter.addObserver(
forName: NSWorkspace.didLaunchApplicationNotification,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleAppLaunch(notification)
}
workspace?.notificationCenter.addObserver(
forName: NSWorkspace.didActivateApplicationNotification,
object: nil,
queue: .main
) { [weak self] notification in
self?.handleAppActivation(notification)
}
}
private func setupAccessibilityMonitoring() {
guard let pid = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == Bundle.main.bundleIdentifier })?.processIdentifier else { return }
let appElement: AXUIElement = AXUIElementCreateApplication(pid)
var observer: AXObserver?
let error = AXObserverCreate(pid, { observer, element, notification, userData in
guard let userData = userData else { return }
let monitor = Unmanaged<DockClickMonitor>.fromOpaque(userData).takeUnretainedValue()
monitor.handleAccessibilityEvent(notification)
}, &observer)
if error == .success, let observer = observer {
self.accessibilityObserver = observer
let notifications = [kAXFocusedUIElementChangedNotification, kAXApplicationActivatedNotification] as CFArray
AXObserverAddNotification(observer, appElement, notifications, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
CFRunLoopAddSource(CFRunLoopGetCurrent(), AXObserverGetRunLoopSource(observer), .commonModes)
}
}
private func setupWindowMonitoring() {
windowMonitor = WindowMonitor()
windowMonitor?.startMonitoring()
}
private func handleAppLaunch(_ notification: Notification) {
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return }
let bundleId = app.bundleIdentifier ?? "unknown"
if bundleId != Bundle.main.bundleIdentifier {
print("Обнаружен запуск приложения: \(app.localizedName ?? bundleId)")
hideFullscreenApp()
}
}
private func handleAppActivation(_ notification: Notification) {
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return }
let bundleId = app.bundleIdentifier ?? "unknown"
if bundleId != Bundle.main.bundleIdentifier {
print("Обнаружена активация приложения: \(app.localizedName ?? bundleId)")
hideFullscreenApp()
}
}
private func handleAccessibilityEvent(_ notification: CFString) {
print("Событие Accessibility: \(notification)")
checkForOtherActiveApps()
}
private func checkForOtherActiveApps() {
let runningApps = NSWorkspace.shared.runningApplications
let currentAppPid = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == Bundle.main.bundleIdentifier })?.processIdentifier
let otherApps = runningApps.filter { $0.processIdentifier != currentAppPid }
if !otherApps.isEmpty {
hideFullscreenApp()
}
}
private func hideFullscreenApp() {
// Реализуйте логику скрытия полноэкранного приложения
DispatchQueue.main.async {
NSApp.setActivationPolicy(.prohibited)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
NSApp.setActivationPolicy(.regular)
}
}
}
}
Лучшие практики и рекомендации
1. Комбинируйте несколько подходов
- Используйте как уведомления NSWorkspace, так и мониторинг Accessibility для максимальной надежности
- Мониторинг окон обеспечивает дополнительную резервную coverage
2. Оптимизация производительности
- Используйте дебаунсинг для быстрых событий, чтобы избежать избыточной обработки
- Используйте правильную очистку в методах
deallocилиdeinit
3. Требования к разрешениям
- Вашему приложению могут понадобиться разрешения Accessibility для расширенного мониторинга
- Запрашивайте разрешения у пользователей корректным образом
4. Управление состоянием приложения
- Правильно сохраняйте состояние вашего приложения перед скрытием
- Обеспечьте правильное восстановление при повторной активации вашего приложения
5. Обработка ошибок
- Корректно обрабатывайте случаи сбоя мониторинга
- Предоставляйте механизмы резервного копирования
// Пример запроса разрешения
func requestAccessibilityPermission() {
let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary
let accessEnabled = AXIsProcessTrustedWithOptions(options)
if accessEnabled {
print("Разрешение Accessibility предоставлено")
setupAccessibilityMonitoring()
} else {
print("Разрешение Accessibility отклонено")
}
}
Комбинация этих подходов обеспечивает комплексный мониторинг щелчков по Dock и активации приложений, гарантируя, что ваше полноэкранное приложение может надежно обнаруживать и реагировать на взаимодействие пользователя с другими приложениями.
Источники
- Документация NSWorkspace - Мониторинг приложений
- Stack Overflow: При щелчке по значку приложения в Dock после его открытия
- Stack Overflow: Как обнаружить запуск приложения на macOS
- Документация фреймворка Accessibility
- Справочник по CGWindowList
Заключение
Чтобы эффективно обнаруживать, когда пользователь щелкает по другому приложению в Dock в вашем приложении macOS на Swift, вы должны:
- Реализовать уведомления NSWorkspace как основной метод для обнаружения запуска и активации приложений
- Добавить мониторинг фреймворка Accessibility для более детального обнаружения событий
- Использовать мониторинг CGWindowList в качестве надежного механизма резервного копирования
- Комбинировать несколько подходов для максимальной надежности и покрытия
- Корректно обрабатывать разрешения и предоставлять надлежащую обработку ошибок
Многоуровневый подход гарантирует, что ваше приложение будет надежно обнаруживать щелчки по Dock и автоматически скрываться при запуске другого приложения, обеспечивая плавный пользовательский опыт для вашего полноэкранного плавающего приложения.