使用 Swift 在 macOS 上围绕光标绘制形状

Draw shape around cursor on macOS using Swift

我是 macOS 和 swift 开发的新手,我一直在观看一些教程来解决这个问题。我还看了一个 udemy 课程,该课程涉及 18 个 macOS 项目,但我一无所获

我只想做一个 macOS 菜单栏应用程序,它会添加一个光标突出显示,看起来应该类似于:

我可以通过以下操作将光标更改为图像

import SpriteKit

class CursorView: SKView {
    override func resetCursorRects() {
        if let targetImage = NSImage(named: "cursor") {
            let cursor = NSCursor(image: targetImage, 
                                  hotSpot: CGPoint(x: targetImage.size.width / 2, 
                                                   y: targetImage.size.height / 2))
            addCursorRect(frame, cursor: cursor)
        }
    }
}

这有三处错误:

  1. SKView 是来自 SpriteKit 的 class,我认为我不应该将其用于我的用例
  2. 这会调用 addCursorRect 将更改添加到 window 帧(无论帧如何,我都需要一直这样做)
  3. 我不能为每种样式设置 100 张图像,以便将来设置高亮颜色、大小或不透明度

所以,我在这里试图了解如何为应该在所有屏幕上都可用的菜单栏应用程序执行此操作,并实现上图中应有的突出显示

不确定这是否重要,但我正在使用 storyboard 并且不介意切换到 SwiftUI

在这方面我真的需要社区的一些帮助。谢谢

您可以通过在系统鼠标周围画一些东西来注释系统鼠标。这可以通过

来完成
  • 添加 CGEvent 点击以捕获鼠标事件
  • 使用自定义 window
  • 在光标周围绘制注释

这是一个简单的示例应用程序

import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var mouseTap: CFMachPort?
    private var window: NSWindow = {
        // Create the SwiftUI view that provides the window contents.
        let contentView = Circle()
            .stroke(lineWidth: 2)
            .foregroundColor(.blue)
            .frame(width: 30, height: 30)
            .padding(2)

        // Create the window and set the content view.
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 34, height: 34),
            styleMask: [.borderless],
            backing: .buffered,
            defer: false
        )
        window.contentView = NSHostingView(rootView: contentView)
        window.backgroundColor = .clear
        window.level = NSWindow.Level.statusBar
        window.makeKeyAndOrderFront(nil)
        return window
    }()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        if let tap = createMouseTap() {
            if CGEvent.tapIsEnabled(tap: tap) {
                let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
                CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes)
                mouseTap = tap
            } else {
                print("tap not enabled")
                mouseTap = nil
            }
        } else {
            print("tap not enabled")
        }
    }

    func createMouseTap() -> CFMachPort? {
        withUnsafeMutableBytes(of: &window) { pointer in
            CGEvent.tapCreate(
                tap: .cgSessionEventTap,
                place: .headInsertEventTap,
                options: CGEventTapOptions.listenOnly,
                eventsOfInterest: (1 << CGEventType.mouseMoved.rawValue | 1 << CGEventType.leftMouseDragged.rawValue),
                callback: mouseMoved,
                userInfo: pointer.baseAddress
            )
        }
    }
}

func mouseMoved(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, context: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
    let window = context!.assumingMemoryBound(to: NSWindow.self).pointee
    // using CGPoint+SIMD extension from https://gist.github.com/Dev1an/7973cee9d960479b35b705f88b7f38c4
    window.setFrameOrigin(event.unflippedLocation - 17)
    return nil
}

缺点

请注意,此实现确实需要用户在“系统偏好设置”的“通用访问”中允许键盘输入选项。