首页 > 解决方案 > 如何防止其他应用程序在 macOS 中监听鼠标事件?

问题描述

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

我不确定发生了什么,但我需要防止主题演讲退出全屏。我怎样才能做到这一点?

我的应用程序的窗口级别是NSWindow.Level.popUpMenu. 而且,我尝试了以下方法,但到目前为止没有任何效果:

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

标签: swiftmacoscocoaswift5.2

解决方案


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


这并不能准确回答您的问题:

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

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


活动与非活动窗口

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

活动 Xcode 窗口 在此处输入图像描述

示例项目设置

  • 创建一个新项目(Xcode - macOS 应用程序 - Swift & Storyboard)
  • 打开Main.storyboard并移除窗口和视图控制器场景
  • 设置LSUIElementYES( 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
    • 不要激活 -> 不要停用其他人
  • 设置hidesOnDeactivatefalse
    • 启动应用程序时不要隐藏,被激活然后你激活任何其他应用程序
  • 设置becomesKeyOnlyIfNeededtrue
    • 避免成为鼠标点击的关键窗口
    • 搜索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) {
    }
}

推荐阅读