You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5.7实现macOS活跃应用聚焦、非活跃窗口着色方法

macOS 类HazeOver专注应用非活跃窗口暗化实现方案

问题描述

我计划实现类似HazeOver、Focus等专注类应用的核心效果:突出显示当前处于活跃状态的应用,对所有非活跃应用窗口进行暗化或其他色彩的蒙层处理,帮助用户聚焦当前操作内容。
我当前使用Swift 5.7进行macOS应用开发,暂未找到为非活跃应用添加背景色或着色蒙层的可行实现方案,以下是已完成的部分代码:

@objc func receiveFrontAppChangeNote(_ notification: Notification) {
    let application = NSWorkspace.shared.frontmostApplication!
    let currentApp = application.processIdentifier
          
    let options = CGWindowListOption(arrayLiteral: CGWindowListOption.excludeDesktopElements, CGWindowListOption.optionOnScreenOnly)
    let windowListInfo = CGWindowListCopyWindowInfo(options, CGWindowID(0))
    let windowInfoList = windowListInfo as NSArray? as? [[String: AnyObject]]
                                   
    for info in windowInfoList! {
        let windowPID = info["kCGWindowOwnerPID"]   as! UInt32
        let windowNumber = info["kCGWindowNumber"]   as! UInt32
                        
        if  windowPID == currentApp {
            // 这里是活跃应用的窗口编号
            print(windowNumber)
            
        }else{
            // 这里是非活跃应用
            let notActiveApp = NSRunningApplication(processIdentifier: pid_t(windowPID))
            
            // 需要在这里实现非活跃应用的着色/蒙层逻辑
            // ...                    
        }
    }
}

实现方案

首先明确核心逻辑:受macOS系统沙盒和权限机制限制,你无法直接修改其他应用窗口的渲染属性,HazeOver、Focus这类同类产品全部采用独立半透明蒙层窗口覆盖的方案实现暗化效果,不存在直接修改非活跃窗口着色的公开实现路径。

具体实现步骤

  • 申请必要权限
    首先需要在工程中声明并引导用户开启辅助功能(Accessibility)权限,没有该权限无法准确获取全局窗口位置、也无法让蒙层窗口正确置顶。权限状态可以通过AXIsProcessTrustedWithOptions接口检测,首次启动时弹窗引导用户到系统设置页开启即可。
  • 定义符合要求的蒙层窗口
    不要使用普通NSWindow作为蒙层,建议继承NSPanel实现蒙层窗口,核心配置如下:
    • 窗口层级设置为NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.floatingWindow)) - 1),该层级刚好位于普通应用窗口上方、前台活跃窗口和菜单栏下方,不会遮挡用户正在操作的活跃窗口
    • 设置为无边框样式,关闭窗口阴影,设置isOpaque = false,背景色使用带透明度的暗色调(默认推荐NSColor.black.withAlphaComponent(0.2~0.4),可开放给用户自定义透明度和色调)
    • 设置ignoresMouseEvents = true,让蒙层不拦截任何鼠标、键盘事件,避免影响用户对非活跃窗口的正常操作
    • 配置collectionBehavior为[.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle],适配多空间、全屏场景,且不会出现在窗口切换列表中
      蒙层窗口核心初始化代码参考:
    class OverlayPanel: NSPanel {
        override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
            super.init(contentRect: contentRect, styleMask: .borderless, backing: backingStoreType, defer: flag)
            self.backgroundColor = .black.withAlphaComponent(0.25)
            self.isOpaque = false
            self.hasShadow = false
            self.ignoresMouseEvents = true
            self.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.floatingWindow)) - 1)
            self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle]
        }
    }
    
  • 补全窗口信息遍历逻辑
    你现有代码只获取了窗口PID和编号,还需要从CGWindowListCopyWindowInfo返回的字典中读取kCGWindowBounds字段,拿到每个非活跃窗口的全局坐标、尺寸,为每个非活跃窗口创建对应frame的蒙层显示即可。遍历过程中注意过滤掉系统UI元素、最小化的窗口、透明窗口,避免出现多余蒙层。
  • 全场景监听更新蒙层状态
    不能只监听前台应用切换通知,还需要监听以下事件,触发时清空所有现有蒙层,重新遍历窗口生成新的蒙层,保证效果跟随系统状态实时更新:
    • 窗口移动、缩放、最小化、关闭事件
    • 桌面空间(Space)切换事件
    • 应用进入/退出全屏事件
    • 显示器插拔、分辨率变化事件
  • 多屏幕适配
    遍历NSScreen.screens获取所有显示器的坐标范围,蒙层需要创建在对应窗口所属的屏幕坐标空间内,避免跨屏幕蒙层位置错位。

注意:不要尝试调用私有CoreGraphics接口直接给其他窗口加色彩滤镜,这类方案在macOS 12及以上版本会被系统安全机制拦截,且提交App Store会直接被拒审,上述独立蒙层方案是目前公开合规、和商业产品实现逻辑完全一致的方案。

内容的提问来源于stack exchange,提问作者Daniel Atik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:03:15