Как запретить другим приложениям прослушивать события мыши в macOS? - PullRequest
3 голосов
/ 08 мая 2020

Я делаю приложение для macOS на Swift, и окно моего приложения всегда поверх других приложений. Это нормально работает со всеми приложениями, даже если они находятся в полноэкранном режиме. Однако, когда Keynote работает в полноэкранном режиме и мое приложение находится поверх него, все события мыши, предназначенные для моего приложения, также go для ключевой заметки, и он выходит из полноэкранного режима.

Я не уверен, что происходит включено, но мне нужно предотвратить выход основного текста из полноэкранного режима. Как я могу это сделать?

Уровень окна моего приложения - NSWindow.Level.popUpMenu. И я пробовал следующее, но пока ничего не помогло:

window.orderFrontRegardless()
window.makeKeyAndOrderFront(self)
window.order(.above, relativeTo: 0)

1 Ответ

3 голосов
/ 12 мая 2020

TL; DR - Проблема здесь в активации приложения.


Это не дает точного ответа на ваш вопрос:

Как сделать запретить другим приложениям прослушивать события мыши в macOS?

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


Активное и неактивное окно

Проверьте следующие снимки экрана. Первый содержит активное окно Xcode, а другой - неактивное окно Xcode. Ваша цель - сохранить активное окно другого приложения, даже если вы щелкнете по оверлею. Неважно, запускает ли другое приложение презентацию (например, Keynote, в полноэкранном режиме) или нет.

Active Xcode window enter image description here

Пример настройки проекта

  • Создание нового проекта (Xcode - приложение macOS - Swift и раскадровка)
  • Откройте Main.storyboard и удалите окно и сцены контроллера просмотра
  • Установите LSUIElement на YES (Info.plist)
  • Добавьте пакет HotKey Swift (https://github.com/soffes/HotKey)
  • Скопируйте и вставьте код AppDelegate.swift ( ниже)
  • Запустить
  • Переключить красный оверлей с помощью Cmd + Opt + O

Я только что протестировал его с Keynote 10.0 и macOS Catalina 10.15.4 ( 19E287), и он работает должным образом - я могу щелкнуть внутри красной накладки, не прерывая текущую презентацию, я могу управлять презентацией с помощью клавиатуры, ...

Важные части

  • Использование NSPanel вместо NSWindow
  • Используйте styleMask и .nonactivatingPanel (нельзя использовать с NSWindow)
    • Не активировать -> не деактивировать e другие
  • Установить hidesOnDeactivate на false
    • Не скрывать, когда вы запускаете свое приложение, активируется, а затем вы активируете любое другое приложение
  • Установить becomesKeyOnlyIfNeeded на true
    • Избегайте использования ключевого окна при щелчках мыши
    • Найдите needsPanelToBecomeKey, если вам нужен ввод с клавиатуры
  • Установить collectionBehavior на [.canJoinAllSpaces, .fullScreenAuxiliary]
    • .canJoinAllSpaces = окно появляется во всех пробелах (как строка меню)
    • .fullScreenAuxiliary = окно с этим поведением коллекции может отображаться в том же пространстве, что и полноэкранное окно

AppDelegate.swift

import Cocoa
import HotKey

final class OverlayView: NSView {
    private var path: NSBezierPath?

    override func keyDown(with event: NSEvent) {
        print("keyDown - \(event.keyCode)")
    }

    override func keyUp(with event: NSEvent) {
        print("keyUp - \(event.keyCode)")
    }

    override func mouseDown(with event: NSEvent) {
        let point = self.convert(event.locationInWindow, from: nil)

        path = NSBezierPath()
        path?.move(to: point)
        needsDisplay = true
    }

    override func mouseUp(with event: NSEvent) {
        path = nil
        needsDisplay = true
    }

    override func mouseDragged(with event: NSEvent) {
        let point = self.convert(event.locationInWindow, from: nil)
        path?.line(to: point)
        needsDisplay = true
    }

    override func draw(_ dirtyRect: NSRect) {
        guard let ctx = NSGraphicsContext.current?.cgContext else {
            return
        }

        defer {
            ctx.restoreGState()
        }
        ctx.saveGState()

        NSColor.green.set()
        ctx.stroke(bounds, width: 8.0)

        guard let path = path else {
            return
        }

        path.lineWidth = 5.0
        NSColor.green.set()
        path.stroke()
    }

    override var acceptsFirstResponder: Bool {
        true
    }

    override var needsPanelToBecomeKey: Bool {
        true
    }
}

final class OverlayWindow: NSPanel {
    convenience init() {
        self.init(
            contentRect: NSScreen.main!.frame,
            styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel],
            backing: .buffered,
            defer: false
        )

        canHide = false
        hidesOnDeactivate = false
        contentView = OverlayView()
        isFloatingPanel = true
        becomesKeyOnlyIfNeeded = true
        acceptsMouseMovedEvents = true
        isOpaque = false
        hasShadow = false
        titleVisibility = .hidden
        level = .popUpMenu
        backgroundColor = NSColor.black.withAlphaComponent(0.001)
        collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
    }

    override var canBecomeKey: Bool {
        true
    }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    private var hotKey: HotKey!
    private var overlayWindowController: NSWindowController?

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        hotKey = HotKey(key: .o, modifiers: [.command, .option])
        hotKey.keyDownHandler = toggleOverlay
    }

    private func toggleOverlay() {
        if overlayWindowController != nil {
            overlayWindowController?.close()
            overlayWindowController = nil
        } else {
            overlayWindowController = NSWindowController(window: OverlayWindow())
            overlayWindowController?.showWindow(self)
            overlayWindowController?.window?.makeKey()
        }
    }

    func applicationWillTerminate(_ aNotification: Notification) {
    }
}
...