1. 项目概述为什么我们需要一个“高亮光标”工具如果你经常做屏幕录制、线上会议演示或者像我一样有时需要向同事远程讲解一个复杂的软件操作流程那你一定遇到过这个尴尬时刻观众在屏幕那头问——“你的鼠标在哪刚才点哪里了”。尤其是在高分辨率屏幕上或者当你的桌面背景颜色和鼠标指针颜色相近时那个小小的箭头或圆点很容易就“消失”在屏幕的海洋里。传统的解决方案可能是调大鼠标指针尺寸或者换成对比度更高的颜色但这些系统自带的功能往往效果有限且不够灵活。这就是focus-cursor这类工具诞生的背景。它不是一个复杂的系统工具而是一个精准解决单一痛点的小而美应用实时、醒目地高亮你的鼠标光标。它的核心价值在于通过一个可自定义颜色、大小和样式的视觉焦点将你所有的鼠标移动轨迹和点击动作清晰地“广播”给屏幕前的每一位观众。无论是教学、演示还是日常录屏它都能极大提升沟通效率和观看体验。我最初接触这类需求是在做技术分享的时候后来发现它在很多场景下都很有用。比如产品经理给设计团队演示交互逻辑或者客服人员远程指导用户操作一个清晰可见的光标能避免大量无效的来回确认。focus-cursor这个项目正是用 SwiftUI 为 macOS 平台实现这一功能的轻量级方案。接下来我会结合自己的使用和开发经验为你深入拆解这个工具的设计思路、实现细节以及那些官方文档里不会写的实操技巧。2. 核心功能与设计思路拆解2.1 功能定位从“看得见”到“看得清”focus-cursor的核心功能非常聚焦就是光标高亮。但一个好的工具会在“聚焦”的基础上做深度优化。我们来看看它具体解决了哪些问题基础高亮在光标周围绘制一个醒目的圆形光环或十字准星这是最基本的功能。但关键在于这个高亮效果必须是实时、低延迟的。任何肉眼可察的延迟都会导致演示者说的和观众看到的不同步体验会大打折扣。自定义视觉样式不同场景需要不同的高亮样式。例如在深色背景的代码编辑器里演示可能需要一个亮黄色的光环而在一个浅色的PPT页面里一个深蓝色的十字线可能更合适。因此提供颜色、大小、甚至形状圆形、方形、十字的自定义选项是提升工具普适性的关键。点击反馈强化单纯的移动轨迹高亮还不够。当用户点击鼠标左键、右键时需要有一个更强烈的视觉反馈比如光环瞬间放大并闪烁一下或者颜色短暂变化。这能明确告知观众“此处发生了交互事件”。低干扰与常驻运行作为一个辅助工具它必须足够“安静”。它应该常驻在菜单栏或后台通过一个简单的快捷键如CmdShiftF快速开启或关闭高亮而不影响用户的其他操作。它的界面也应该极其简洁几乎不需要学习成本。2.2 技术选型为什么是 SwiftUI 和 macOS从项目关键词SwiftUI, macOS可以看出这是一个原生 macOS 应用。这个选择背后有非常实际的考量性能与原生体验光标高亮需要实时捕获全局的鼠标事件位置、点击并立即在屏幕上绘制图形。使用原生框架如 AppKit 或 SwiftUI可以直接调用底层的 Quartz 显示服务实现像素级、低延迟的屏幕绘制这是 Web 技术或跨平台框架难以媲美的。SwiftUI 的声明式 UI对于这样一个 UI 相对简单的工具SwiftUI 是绝佳选择。它的声明式语法让界面开发变得快速直观状态管理如当前高亮颜色、是否启用可以轻松绑定到 UI 控件上。开发者可以更专注于核心的业务逻辑鼠标追踪与绘制而非复杂的界面代码。macOS 的系统集成作为 macOS 专属应用它可以更好地与系统集成。例如它可以方便地实现菜单栏常驻NSStatusItem支持全局快捷键MASShortcut等第三方库或NSEvent.addGlobalMonitor以及遵循 macOS 的人机界面指南让用户感觉这就是系统的一部分。轻量与高效相比于功能庞杂的录屏或演示软件一个专精于光标高亮的独立 App 更加轻量。它启动快、内存占用小通常只有几十MB可以随时待命不会成为系统的负担。这个设计思路体现了现代工具软件的一个趋势用最合适的技术栈解决一个最明确的问题并且把体验做到极致。focus-cursor没有试图去集成录屏、标注等复杂功能它只做好“高亮光标”这一件事反而让它成为了一个不可替代的“螺丝刀”型工具。3. 核心实现原理与关键技术点3.1 全局鼠标事件监听这是整个应用的基石。在 macOS 上要实时获取鼠标位置和点击事件通常有几种方式NSEvent.addGlobalMonitorForEvents(matching:handler:)这是最常用且相对安全的方法。它可以监听全局的鼠标移动.mouseMoved、左键按下/抬起.leftMouseDown/Up、右键事件等。监听移动事件时需要注意性能因为鼠标移动事件频率极高处理器函数必须非常高效。// 示例代码监听全局鼠标移动 NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { event in let currentLocation event.locationInWindow // 注意这个坐标需要转换到屏幕坐标系 // 更新内部存储的光标位置 self.currentCursorPosition convertToScreenCoordinates(currentLocation, from: event.window) // 触发界面重绘 self.updateHighlightLayer() }注意使用全局监听需要用户在系统偏好设置 - 安全性与隐私 - 辅助功能中授权该应用控制电脑的权限。这是focus-cursor首次运行时必须引导用户完成的关键步骤否则监听会失效。CGEvent.tapCreate(tap:place:options:eventsOfInterest:callback:userInfo:)这是一个更底层的 Core Graphics 事件点击Event TapAPI。它功能更强大甚至可以修改或阻止事件。但对于单纯的光标位置读取来说有点“杀鸡用牛刀”且更容易触发系统的安全警报审核上架 Mac App Store 可能更麻烦。focus-cursor这类追求简洁和易用性的工具通常首选第一种方案。3.2 屏幕覆盖层绘制获取到光标位置后下一步就是在屏幕上绘制高亮图形。这里不能直接在现有的应用窗口上画因为光标会移动到任何地方包括其他应用之上。因此需要创建一个覆盖全屏幕的透明窗口。创建透明、无边框、置顶窗口let overlayWindow NSWindow( contentRect: NSScreen.main?.frame ?? .zero, styleMask: [.borderless], backing: .buffered, defer: false ) overlayWindow.backgroundColor .clear // 关键窗口完全透明 overlayWindow.isOpaque false overlayWindow.level .screenSaver // 或 .statusBar 确保窗口在最顶层 overlayWindow.ignoresMouseEvents true // 关键窗口不拦截任何鼠标点击鼠标事件能穿透到下层应用 overlayWindow.collectionBehavior [.stationary, .canJoinAllSpaces, .fullScreenAuxiliary]这个窗口将作为我们绘制高亮效果的画布。ignoresMouseEvents true至关重要它保证了你的所有点击仍然能正常作用于底层的 Photoshop、浏览器或任何其他应用。使用CALayer或 SwiftUICanvas进行绘制在窗口的内容视图中我们需要根据光标位置实时绘制一个图形。CALayer方案更传统性能极佳。可以创建一个自定义的NSView在其layer上添加一个CAShapeLayer用于画圆环或CATextLayer用于画十字。每次鼠标移动时更新这个ShapeLayer的position。SwiftUICanvas方案更现代与 SwiftUI 状态绑定结合得更好。在Canvas的闭包中根据State存储的光标位置使用GraphicsContext直接绘制路径。SwiftUI 会自动处理视图更新。// SwiftUI Canvas 绘制示例简化 struct OverlayView: View { StateObject var cursorTracker CursorTracker() // 负责监听鼠标的类 var body: some View { Canvas { context, size in let circlePath Path(ellipseIn: CGRect( x: cursorTracker.position.x - 20, y: cursorTracker.position.y - 20, width: 40, height: 40 )) context.stroke(circlePath, with: .color(.blue), lineWidth: 3) // 如果刚刚发生了点击再绘制一个内圈闪烁效果 if cursorTracker.didClickRecently { let innerPath Path(ellipseIn: CGRect(...)) context.fill(innerPath, with: .color(.yellow.opacity(0.7))) } } .ignoresSafeArea() // 覆盖整个屏幕 } }绘制逻辑的核心是性能。图形要尽可能简单避免复杂阴影和模糊效果重绘区域要精确只更新光标周围区域而不是整个屏幕这样才能保证在60Hz甚至更高刷新率的屏幕上流畅运行。3.3 状态管理与用户配置一个友好的工具必须允许用户自定义。focus-cursor需要管理以下状态并持久化保存启用/禁用开关一个布尔值。高亮颜色存储为NSColor或CGColor。光环大小一个CGFloat值。点击反馈效果开关。这些配置通常使用UserDefaults来存储就足够了。在 SwiftUI 中可以结合AppStorage属性包装器轻松地将界面控件如ColorPicker、Slider与持久化存储绑定起来。struct SettingsView: View { AppStorage(highlightColor) private var colorData: Data? AppStorage(ringSize) private var ringSize: Double 40.0 private var highlightColor: Color { // 从 Data 解码出 Color } var body: some View { Form { ColorPicker(高亮颜色, selection: Binding( get: { highlightColor }, set: { newColor in /* 编码并存入 UserDefaults */ } )) Slider(value: $ringSize, in: 20...100, label: { Text(光环大小: \(Int(ringSize))) }) } } }4. 从零开始的详细实现步骤假设我们现在要从零开始用 SwiftUI 实现一个focus-cursor的核心功能。我会把过程拆解得足够细即使你是 SwiftUI 新手也能跟着思路走下来。4.1 项目初始化与权限配置创建新的 macOS App 项目在 Xcode 中选择 “macOS” - “App” 模板语言选择 Swift界面选择 SwiftUI。配置应用权限在项目导航器中点击你的项目根目录选择 “Signing Capabilities” 标签页。点击 “ Capability”添加 “Hardened Runtime”。虽然不是必须但这是上架和增强安全性的好习惯。更重要的是我们需要在Info.plist文件中声明辅助功能权限。右键Info.plist- “Open As” - “Source Code”添加以下键值keyNSAppleEventsUsageDescription/key string本应用需要监听全局鼠标事件以实现光标高亮功能。/string实际上对于监听全局鼠标事件更准确的描述是辅助功能。但上述描述是用户能理解的。更严谨的做法是应用在首次尝试监听事件时如果失败会引导用户去系统设置中开启权限。我们可以在代码中实现这个引导。4.2 构建核心鼠标追踪器创建一个名为CursorTracker.swift的类它负责与系统交互获取光标位置。import SwiftUI import Cocoa class CursorTracker: ObservableObject { Published var position: CGPoint .zero Published var isClicking: Bool false private var eventMonitor: Any? func startMonitoring() { // 检查辅助功能权限简化检查实际需更严谨 let options [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary let isTrusted AXIsProcessTrustedWithOptions(options) if !isTrusted { print(请前往 系统设置 隐私与安全性 辅助功能 并授权本应用。) // 这里可以触发一个Alert提示用户去开启权限 return } // 监听全局鼠标移动 eventMonitor NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { [weak self] event in DispatchQueue.main.async { // 将窗口坐标转换为屏幕坐标 if let screen NSScreen.main { let screenPoint NSPointToCGPoint(event.locationInWindow) // 注意event.locationInWindow 在全局监听中可能不准确 // 更可靠的方式是使用 CGEvent 或 NSEvent.mouseLocation self?.position NSEvent.mouseLocation // 直接使用这个更简单 // NSEvent.mouseLocation 的Y坐标原点在屏幕左下角而SwiftUI原点在左上角需要转换 self?.position.y screen.frame.maxY - self!.position.y } } } // 监听鼠标按下事件 // 注意.leftMouseDown 是局部事件全局监听需要用 .leftMouseDownMask (在mask中) // 更简单的方式使用一个本地事件监听器来检测点击结合全局位置 let localMonitor NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .leftMouseUp]) { [weak self] event in self?.isClicking (event.type .leftMouseDown) return event // 如果不希望修改事件就原样返回 } // 需要保存这个 localMonitor 以便后续移除 } func stopMonitoring() { if let monitor eventMonitor { NSEvent.removeMonitor(monitor) eventMonitor nil } } deinit { stopMonitoring() } }实操心得NSEvent.mouseLocation是获取光标当前位置最直接的方法它返回的是屏幕坐标系下的点其中Y坐标原点在屏幕左下角。而 SwiftUI 的坐标系原点在左上角。这个坐标转换是初期最容易踩的坑之一如果不转换你绘制的高亮会上下颠倒。转换公式很简单screenHeight - mouseLocation.y。4.3 创建覆盖全屏的透明窗口我们不修改主窗口而是创建一个新的、专门用于绘制的窗口。在App文件中操作。import SwiftUI main struct FocusCursorApp: App { StateObject private var cursorTracker CursorTracker() State private var overlayWindow: NSWindow? var body: some Scene { WindowGroup { ContentView(cursorTracker: cursorTracker) .onAppear { setupOverlayWindow() cursorTracker.startMonitoring() } .onDisappear { cursorTracker.stopMonitoring() } } .windowStyle(.hiddenTitleBar) // 主窗口也可以简化样式 .commands { // 可以在这里添加快捷键命令 } } private func setupOverlayWindow() { // 确保只在主线程操作UI DispatchQueue.main.async { if overlayWindow nil { let screenFrame NSScreen.main?.frame ?? .zero let window NSWindow( contentRect: screenFrame, styleMask: [.borderless], backing: .buffered, defer: false ) window.backgroundColor .clear window.isOpaque false window.level .screenSaver // 确保在最前 window.ignoresMouseEvents true // 允许鼠标穿透 window.collectionBehavior [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary] window.isReleasedWhenClosed false let overlayView NSHostingView(rootView: OverlayView(cursorTracker: cursorTracker)) overlayView.autoresizingMask [.width, .height] window.contentView overlayView window.makeKeyAndOrderFront(nil) overlayWindow window } } } }4.4 实现高亮绘制视图现在实现OverlayView这是显示高亮效果的核心。struct OverlayView: View { ObservedObject var cursorTracker: CursorTracker AppStorage(highlightColor) private var colorData: Data Color.blue.encodeToData() // 需要扩展Color AppStorage(ringRadius) private var ringRadius: Double 25.0 AppStorage(showClickEffect) private var showClickEffect: Bool true // 计算属性从存储的Data解码出Color private var highlightColor: Color { Color.decode(from: colorData) ?? .blue } var body: some View { Canvas { context, size in // 1. 绘制外圈光环 let outerCircle Path(ellipseIn: CGRect( x: cursorTracker.position.x - ringRadius, y: cursorTracker.position.y - ringRadius, width: ringRadius * 2, height: ringRadius * 2 )) var outerStrokeStyle StrokeStyle(lineWidth: 3.0, lineCap: .round, lineJoin: .round) context.stroke(outerCircle, with: .color(highlightColor), style: outerStrokeStyle) // 2. 如果正在点击绘制一个内圈填充作为反馈 if cursorTracker.isClicking showClickEffect { let innerRadius ringRadius * 0.5 let innerCircle Path(ellipseIn: CGRect( x: cursorTracker.position.x - innerRadius, y: cursorTracker.position.y - innerRadius, width: innerRadius * 2, height: innerRadius * 2 )) // 使用半透明的白色填充制造“闪烁”感 context.fill(innerCircle, with: .color(.white.opacity(0.6))) } // 3. (可选) 绘制一个十字准星更精确地指示光标尖端 let crossHairLength: CGFloat 15.0 let horizontalLine Path { p in p.move(to: CGPoint(x: cursorTracker.position.x - crossHairLength, y: cursorTracker.position.y)) p.addLine(to: CGPoint(x: cursorTracker.position.x crossHairLength, y: cursorTracker.position.y)) } let verticalLine Path { p in p.move(to: CGPoint(x: cursorTracker.position.x, y: cursorTracker.position.y - crossHairLength)) p.addLine(to: CGPoint(x: cursorTracker.position.x, y: cursorTracker.position.y crossHairLength)) } context.stroke(horizontalLine, with: .color(highlightColor.opacity(0.8)), style: StrokeStyle(lineWidth: 1.5)) context.stroke(verticalLine, with: .color(highlightColor.opacity(0.8)), style: StrokeStyle(lineWidth: 1.5)) } .ignoresSafeArea() // 覆盖整个屏幕包括菜单栏和Dock区域 .allowsHitTesting(false) // 确保视图本身不拦截任何鼠标事件 } } // 简单的 Color 编码/解码扩展实际项目可能需要更健壮的方案 extension Color { func encodeToData() - Data { let nsColor NSColor(self) return try! NSKeyedArchiver.archivedData(withRootObject: nsColor, requiringSecureCoding: false) } static func decode(from data: Data) - Color? { guard let nsColor try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: data) else { return nil } return Color(nsColor) } }4.5 构建用户控制界面主窗口ContentView应该非常简洁只提供必要的控制项。struct ContentView: View { ObservedObject var cursorTracker: CursorTracker State private var isHighlightEnabled: Bool true var body: some View { VStack(spacing: 20) { Toggle(启用光标高亮, isOn: $isHighlightEnabled) .onChange(of: isHighlightEnabled) { newValue in if newValue { cursorTracker.startMonitoring() } else { cursorTracker.stopMonitoring() } } .toggleStyle(.switch) .controlSize(.large) Divider() Text(高亮设置) .font(.headline) ColorPicker(选择高亮颜色, selection: Binding( get: { // 这里需要从 AppStorage 读取为了简化示例我们假设有一个共享的 ViewModel // 实际项目中颜色状态应放在一个统一的 SettingsViewModel 中 return .blue }, set: { newColor in // 保存到 UserDefaults } )) .padding(.horizontal) Slider(value: .constant(25.0), in: 15...60) { Text(光环大小) } minimumValueLabel: { Image(systemName: circle) } maximumValueLabel: { Image(systemName: circle.inset.filled) } .padding(.horizontal) Toggle(点击反馈效果, isOn: .constant(true)) Spacer() Text(提示首次使用请确保在系统设置中授予辅助功能权限。) .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding() } .padding() .frame(width: 300, height: 400) } }5. 打包、分发与进阶优化思路5.1 应用打包与签名开发完成后你需要将应用打包分发给其他人使用。配置发布信息在 Xcode 项目设置中确保设置了正确的Bundle Identifier如com.yourname.focus-cursor、版本号和构建版本。代码签名对于非 App Store 分发你可以使用 “Developer ID Application” 证书进行签名。这能保证应用在非开发机器上打开时不会立即被系统拦截尽管用户可能仍需在“安全性与隐私”中手动允许。打包归档在 Xcode 菜单栏选择Product-Archive。归档成功后在 Organizer 窗口中点击Distribute App。如果只是给少数人用选择 “Copy App”它会生成一个.app文件。你可以直接压缩这个.app文件为.zip进行分发。公证Notarization为了让你的应用在 macOS Catalina 及更高版本上顺利运行避免“无法打开因为无法验证开发者”的警告强烈建议进行公证。这需要通过 Xcode 的 “Distribute App” 流程选择 “Developer ID” 并上传到 Apple 进行公证。公证成功后应用会获得一个“门票”ticket用户打开时体验会好很多。5.2 进阶功能与优化建议一个基础版本完成后可以考虑以下方向进行增强这也是区分优秀工具和普通工具的关键性能优化按需绘制目前的Canvas会在每次鼠标移动时重绘整个屏幕区域。虽然 SwiftUI 做了优化但更高效的方式是使用CALayer并只更新图层的位置和属性而不是重绘整个路径。降低监听频率对于鼠标移动事件如果不需要极其平滑的轨迹比如只用于演示可以设置一个阈值只有当光标移动超过一定像素距离时才更新位置或者使用一个低通滤波器来平滑移动并减少更新次数。功能增强多屏幕支持现在的实现假设只有一个主屏幕。需要修改为遍历NSScreen.screens为每个屏幕创建一个覆盖窗口并将光标坐标正确转换到对应的屏幕空间。高亮样式库除了颜色可以提供预设的样式如“呼吸灯效果”、“雷达扫描效果”、“点击涟漪扩散效果”等。快捷键全局切换集成HotKey库如Sauce实现按CmdShiftF快速开启/关闭高亮而无需切回应用窗口。菜单栏常驻将主窗口关闭改为一个菜单栏应用。点击菜单栏图标可以弹出设置面板。这更符合这类辅助工具的定位focus-cursor的 Releases 中提供的.app文件很可能就是这种形式。稳定性与用户体验完善的权限引导在应用启动时如果检测到没有辅助功能权限应弹出一个友好的、带图示的引导窗口直接告诉用户点击后会自动跳转到系统设置的对应页面。开机自启提供选项让应用在登录时自动启动并最小化到菜单栏。内存与电池优化确保应用在后台时高亮关闭状态几乎不消耗 CPU 和内存资源。6. 常见问题排查与实战心得在实际开发和使用过程中你肯定会遇到一些坑。以下是我总结的一些典型问题及其解决方案6.1 权限问题导致监听失效现象应用启动后光标高亮完全不工作控制台没有报错。排查首先检查应用是否出现在系统设置 隐私与安全性 辅助功能的列表中并且开关已被打开。如果没有需要在代码中主动请求权限。可以使用AXIsProcessTrusted()函数检查如果返回false则弹窗提示用户并用NSWorkspace.shared.open(URL(string: \x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility\)!)直接打开系统设置对应页面。即使权限已开启有时系统可能需要重启应用才能生效。心得权限处理是 macOS 桌面工具开发的第一道坎。一定要把用户体验做好用最清晰的方式引导用户一次成功。否则用户很可能因为“用不起来”而直接放弃。6.2 高亮图形闪烁或卡顿现象光标移动时高亮光环出现明显的闪烁、拖影或延迟。排查与解决绘制性能检查Canvas或CALayer的绘制代码是否过于复杂。避免在每一帧都创建新的Path对象可以复用。对于静态的图形样式使用displayList预编译。事件频率鼠标移动事件NSEvent.mouseMoved频率极高。在事件处理函数中不要做任何耗时操作如文件读写、网络请求。只做最简单的坐标更新和界面标记。线程问题确保所有 UI 更新都在主线程 (DispatchQueue.main.async) 中进行。NSEvent的回调可能不在主线程。Vsync 同步确保你的绘制与屏幕刷新率同步。SwiftUI 的Canvas在这方面处理得较好。如果使用CALayer可以考虑使用CADisplayLink来驱动动画更新。6.3 在多显示器环境下位置错乱现象有两个显示器高亮只在一个屏幕上显示或者位置完全不对。解决坐标系转换牢记NSEvent.mouseLocation返回的是全局屏幕坐标系原点在主屏幕的左下角。而每个NSScreen有自己的frame原点也在全局坐标系中。你需要判断当前光标位于哪个屏幕的frame内。为每个屏幕创建窗口遍历NSScreen.screens为每个屏幕创建一个全屏的透明覆盖窗口。当鼠标移动时计算它在哪个屏幕内然后只更新或显示那个屏幕对应的覆盖窗口上的高亮图层。坐标归一化在绘制时需要将全局坐标转换为当前窗口所在屏幕的局部坐标。公式类似于localX globalX - screen.frame.minX,localY globalY - screen.frame.minY注意Y轴方向。6.4 应用无法常驻后台或菜单栏现象关闭主窗口后应用就退出了。解决你需要将应用改为Agent Application或菜单栏应用。在Info.plist中将Application is agent (UIElement)设置为YES。这样应用就不会在 Dock 显示图标也不会显示菜单栏。在应用启动时 (AppDelegate或App的init)创建NSStatusItem菜单栏图标。将主要的设置界面放在一个Window中并通过点击状态栏图标来弹出/收回这个窗口。主WindowGroup可以设置为隐藏。心得对于这种“工具型”应用菜单栏是绝佳的归宿。它不占用 Dock 位置不干扰用户工作流随时可取用非常符合“随手工具”的定位。6.5 打包后在其他电脑上无法打开现象在自己电脑上运行正常打包发给别人后提示“已损坏无法打开”。解决这通常是 macOS 的 Gatekeeper 安全机制导致的。首选方案进行Developer ID 公证如上文所述。这是最正规的途径。临时测试方案让用户在终端执行命令sudo xattr -rd com.apple.quarantine /Applications/YourApp.app来移除隔离属性。或者让他们在“安全性与隐私”设置中点击“仍要打开”。注意这需要告知用户操作步骤体验不好。避免使用网上有些教程说在打包时用codesign --force --deep --sign -命令重签名这可能会带来其他问题不推荐作为最终方案。开发像focus-cursor这样一个小而精的工具整个过程就像在打磨一件顺手的手工器具。最大的成就感来自于它切实地解决了一个高频、细微但真实的痛点。从技术上看它涉及了 macOS 的事件系统、图形绘制、多窗口管理、权限模型和用户体验设计等多个层面是一个非常好的综合性练手项目。希望这篇超详细的拆解不仅能帮你理解和使用这个工具更能给你带来自己动手创造类似工具的灵感和信心。