【问题标题】:How to prevent other applications from listening to mouse events in macOS?如何防止其他应用程序在 macOS 中监听鼠标事件?
【发布时间】:2020-08-23 08:18:18
【问题描述】:

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

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

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

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

【问题讨论】:

  • @matt 你能帮我解决这个问题吗? ??????
  • Keynote 窗口是全屏模式还是 Keynote 正在运行演示文稿?鼠标事件是否使您的应用程序成为最前面的应用程序?请发minimal reproducible example
  • @Willeke Keynote 正在进行演示。我为 Apple 代码级支持做了一个截屏视频:youtu.be/LHQMVPHaFIA 也许这可以帮助理解问题
  • @TarunLalwani 由于我的应用程序的性质,我无法进行全屏模式,但我确实尝试通过将窗口框架设置为 NSScreen.main.frame 使我的应用程序窗口占据整个屏幕,但没有运气.

标签: swift macos cocoa swift5.2


【解决方案1】:

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


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

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

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


活动与非活动窗口

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

示例项目设置

  • 创建一个新项目(Xcode - macOS 应用程序 - Swift 和 Storyboard)
  • 打开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一起使用)
    • 请勿激活 -> 请勿停用其他人
  • 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) {
    }
}

【讨论】:

  • 如你所说,我创建了一个扩展 NSPanel 的类。在我的故事板中使用了这个类,即将 NSWindow 替换为我在上面创建的类。一切都在我的应用程序中正常工作,但当我在它上面绘制时,Keynote 仍然全屏退出。注意:这是我显示窗口的方式gist.github.com/rampatra/6698d95babaf944ecccbe37aecd7281c
  • 而且,当我不以这种方式显示窗口并按照您的方式 overlayWindowController?.showWindow(self) 时,面板甚至不会显示在 Keynote 的顶部。
  • 您的要点没有显示太多,因为您使用的是 Storyboard,而我不知道其中设置了什么。我只能问您是否确实遵循了我描述的所有步骤。你可以找到完整的项目here。自述文件中有动画 GIF,展示了它的工作原理。相同的代码,我只是添加了绘图支持。
  • 基于您的要点的一件事 - 答案说您无法激活您的应用,因为这是 强制 Keynote 隐藏正在运行的演示文稿的原因,但是您仍在使用window.makeKeyAndOrderFront(self) & NSApp.activate(ignoringOtherApps: true)
  • 答案已更新,keyDown/keyUp 支持。示例项目也是如此。检查this commit 看看我做了什么。
猜你喜欢
  • 2019-06-11
  • 1970-01-01
  • 2020-02-20
  • 2011-09-19
  • 2014-02-21
  • 2014-04-22
  • 2011-08-22
  • 1970-01-01
  • 2016-01-04
相关资源
最近更新 更多