如何防止其他应用程序在 macOS 中监听鼠标事件?

How to prevent other applications from listening to mouse events in macOS?

我正在 Swift 中制作 macOS 应用程序,我的应用程序的 window 始终位于其他应用程序之上。这适用于所有应用程序,即使这些应用程序处于全屏模式。但是,当 Keynote 以全屏模式运行并且我的应用程序位于其顶部时,所有适用于我的应用程序的鼠标事件也会转到 keynote 并退出全屏。

我不确定发生了什么,但我需要防止 keynote 退出全屏。我该怎么做?

我的应用的 window 级别是 NSWindow.Level.popUpMenu。而且,我已经尝试了以下但到目前为止没有任何效果:

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

TL;DR - 这里的问题是应用程序激活。


这并没有准确回答您的问题:

How to prevent other applications from listening to mouse events in macOS?

这是一个演示如何在不阻止其他应用程序侦听 macOS 中的鼠标事件的情况下实现您想要的结果的答案。


活跃与不活跃 window

检查以下屏幕截图。第一个包含活动 Xcode window,另一个包含非活动 Xcode window。您的目标是保持其他应用程序 window 处于活动状态,即使您单击叠加层也是如此。其他应用程序是否是 运行 演示文稿(如 Keynote,全屏)无关紧要。

示例项目设置

  • 创建一个新项目(Xcode - macOS 应用程序 - Swift & Storyboard)
  • 打开 Main.storyboard 并删除 window 并查看控制器场景
  • LSUIElement 设置为 YES (Info.plist)
  • 添加热键 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一起使用)
    • 不要激活 -> 不要停用其他人
  • hidesOnDeactivate 设置为 false
    • 当你启动你的应用程序时不隐藏,被激活然后你激活任何其他应用程序
  • becomesKeyOnlyIfNeeded 设置为 true
    • 避免成为关键window鼠标点击
    • 如果需要键盘输入,请搜索 needsPanelToBecomeKey
  • collectionBehavior 设置为 [.canJoinAllSpaces, .fullScreenAuxiliary]
    • .canJoinAllSpaces = window 出现在所有 space 中(如菜单栏)
    • .fullScreenAuxiliary = 具有此收集行为的 window 可以在与全屏 window
    • 相同的 space 上显示

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) {
    }
}