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

如何为macOS 14实现全局应用窗口自定义固定按钮?

实现方案概述

要在所有macOS 14应用的窗口交通按钮(关闭/最小化/最大化)旁添加固定按钮并实现阻止关闭功能,核心依赖辅助功能(Accessibility)框架,同时需要处理窗口监听与视图注入。以下是分步实现细节:

1. 申请辅助功能权限

你的应用必须获取用户的辅助功能权限,才能访问和修改其他应用的窗口元素:

  • 在Xcode的Info.plist中添加NSAccessibilityUsageDescription键,填入权限用途说明(例如:"需要访问其他应用窗口以添加固定按钮")。
  • 首次启动时引导用户前往「系统设置>隐私与安全性>辅助功能」,开启你的应用权限。

2. 监听系统窗口创建/销毁事件

实时捕获所有打开的应用窗口,确保新窗口出现时自动添加按钮:

import Cocoa
import Accessibility

class WindowMonitor {
    private var observer: AXObserver?
    
    init() {
        let systemWideElement = AXUIElementCreateSystemWide()
        AXObserverCreate(systemWideElement, windowChangeHandler, &observer)
        
        // 监听窗口创建通知
        AXObserverAddNotification(observer, systemWideElement, kAXWindowCreatedNotification as CFString, nil)
        
        // 将观察者加入运行循环
        let runLoop = CFRunLoopGetCurrent()
        AXObserverAddToRunLoop(observer, runLoop, CFRunLoopMode.defaultMode.rawValue)
    }
    
    private func windowChangeHandler(observer: AXObserver, element: AXUIElement, notification: CFString, userData: UnsafeMutableRawPointer?) {
        // 给新创建的窗口添加固定按钮
        attachPinButton(to: element)
    }
}

3. 给目标窗口插入自定义按钮

要把按钮放到交通按钮旁,需定位窗口的标题栏视图,计算位置后插入按钮:

private func attachPinButton(to windowAXElement: AXUIElement) {
    // 匹配AXElement对应的NSWindow
    guard let targetWindow = NSApplication.shared.windows.first(where: { $0.axElement == windowAXElement }) else { return }
    
    // 定位系统标题栏视图(不同应用可能有差异,需适配)
    guard let titleBarView = targetWindow.contentView?.superview?.subviews.first(where: { $0.className == "NSTitleBarView" }) else { return }
    
    // 创建固定按钮
    let pinButton = NSButton(frame: NSRect(x: 0, y: 0, width: 18, height: 18))
    pinButton.image = NSImage(systemSymbolName: "pin.fill", accessibilityDescription: "固定窗口")
    pinButton.bezelStyle = .circular
    pinButton.target = self
    pinButton.action = #selector(toggleWindowPin(_:))
    pinButton.tag = targetWindow.hashValue // 标记所属窗口
    
    // 定位交通按钮组,计算按钮插入位置
    if let trafficButtons = titleBarView.subviews.first(where: { $0.className == "NSTrafficLightButtons" }) {
        let pinX = trafficButtons.frame.maxX + 8 // 与交通按钮保持8pt间距
        pinButton.frame.origin = CGPoint(x: pinX, y: (titleBarView.frame.height - pinButton.frame.height)/2)
        titleBarView.addSubview(pinButton)
    }
}

4. 实现固定/取消固定逻辑

方式1:禁用关闭按钮(公开API)

点击按钮时,通过Accessibility API切换目标窗口关闭按钮的启用状态:

@objc private func toggleWindowPin(_ sender: NSButton) {
    guard let targetWindow = NSApplication.shared.windows.first(where: { $0.hashValue == sender.tag }) else { return }
    let windowAXElement = targetWindow.axElement
    
    // 获取关闭按钮的AXElement
    var closeButtonAXElement: AXUIElement?
    AXUIElementCopyAttributeValue(windowAXElement, kAXCloseButtonAttribute as CFString, &closeButtonAXElement)
    
    guard let closeButton = closeButtonAXElement else { return }
    
    let isPinned = !sender.isEnabled
    // 更新按钮图标
    pinButton.image = NSImage(systemSymbolName: isPinned ? "pin.slash.fill" : "pin.fill", accessibilityDescription: nil)
    // 切换关闭按钮的可用状态
    AXUIElementSetAttributeValue(closeButton, kAXEnabledAttribute as CFString, !(isPinned as CFBoolean))
}

方式2:拦截窗口关闭事件(进阶,需注入)

如果需要彻底阻止窗口关闭(包括快捷键强制关闭),可以通过方法Swizzling替换NSWindow的windowShouldClose:方法,但此操作涉及代码注入,可能违反Mac App Store规则:

extension NSWindow {
    static func swizzleCloseHandler() {
        let originalSelector = #selector(windowShouldClose(_:))
        let swizzledSelector = #selector(swizzled_windowShouldClose(_:))
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    
    @objc private func swizzled_windowShouldClose(_ sender: Any?) -> Bool {
        // 判断当前窗口是否被固定,返回false阻止关闭
        if isWindowPinned(self) {
            return false
        }
        // 调用原方法逻辑
        return self.swizzled_windowShouldClose(sender)
    }
}

兼容性与注意事项

  • macOS 14对Accessibility API和窗口层级限制更严格,部分私有视图类名可能变更,需测试适配不同应用。
  • 自定义标题栏的应用(如部分第三方软件)可能无法通过上述方法定位交通按钮,需额外适配。
  • 应用需保持后台运行,才能持续监听窗口变化并维护按钮状态。

替代方案

若上述实现难度过高,可简化为:

  • 做一个全局菜单栏应用,提供「固定/取消固定当前窗口」的快捷功能,无需修改其他应用标题栏。
  • 设置全局快捷键,触发时通过Accessibility API切换目标窗口关闭按钮的状态。

内容的提问来源于stack exchange,提问作者Strange-Guy Person

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:53:17