loganovo
V2EX  ›  Apple

告别 finder 的乱七八糟的窗口宽度(强迫症)

  •  1
     
  •   loganovo · 1 day ago · 1024 views
    • 本来刚做系统的时候挑好了 finder 的窗口样式, 能保证每次打开都是相同的样式的窗口;
    • 但是后续觉得太宽了, 又重新调了一下
    • 问题就出现了: 直接打开的样式是我要的, 但是通过其他软件点击"在 finder 中显示"出现的窗口又会变回原来的
    • 乱七八糟的,强迫症根本忍不了
    • 该试过的办法都试了, 全盘删除 .DS_store、删除~/Library/Preferences/com.apple.finder.plist 都没有作用;
    • 直接用 hammerspoon 解决了, 每次打开 finder 强制设置, 舒服了
    -- 自定义日志包装方法, 支持传入多个参数
    function debugLog(...)
      if ENABLE_DEBUG_LOG then
        print(...)
      end
    end
    
    -- 2 、finder 窗口拦截修改宽高脚本
    -- 动态获取系统 Finder 的本地化名称 (中文系统下返回 "访达", 英文返回 "Finder")
    -- 这样写可以彻底避免硬编码字符串带来的匹配失败问题
    -- 动态获取系统中当前正在运行的 Finder 进程对象
    local finderApp = hs.application.get("com.apple.finder")
    -- 从运行的进程中提取它在当前系统语言下实际显示的名称
    -- 如果是中文系统, finderApp:name() 将 100% 准确地返回 "访达"
    local finderAppName = finderApp and finderApp:name() or "Finder"
    
    -- 初始化窗口过滤器: 传入 false 表示默认拒绝系统中的所有窗口事件, 节约性能
    FinderFilter = hs.window.filter.new(false)
    
    -- 使用 setAppFilter 专门针对 Finder 设定规则:
    -- 1. allowRoles = 'AXStandardWindow': 底层直接屏蔽偏好设置, 进度条等非标准面板.
    -- 2. 故意不写 visible 参数: 默认为 nil (忽略), 这意味着即使是刚创建还未渲染显示出来的底层窗口也能被捕获.
    -- 使用 setAppFilter 专门针对 Finder 设定规则
    FinderFilter:setAppFilter(finderAppName, {
      allowRoles = 'AXStandardWindow',
      rejectTitles = {
        -- 精准匹配设置面板
        "^" .. finderAppName .. "设置$",
        "^" .. finderAppName .. "偏好设置$",
        "^" .. finderAppName .. " Settings$",
        "^" .. finderAppName .. " Preferences$",
        -- 新增: 利用 Lua 模式匹配 (以特定字符串结尾的正则 $ 锚点)
        -- 拦截所有标题以 "简介" 或 " Info" 结尾的窗口
        "简介$",
        " Info$"
      }
    })
    
    -- 订阅窗口创建事件
    FinderFilter:subscribe(hs.window.filter.windowCreated, function(win)
      -- 此时能触发该函数的, 必定是 Finder 的标准窗口
      debugLog("检测到新建标准窗口:" .. win:title())
      --  安全与类型检查:
      -- 1. 确保 win 对象及其底层的窗口 ID 真实存在
      -- 2. isStandard() 确保这是一个标准窗口, 而非偏好设置或进度条面板
      if win and win:id() and win:isStandard() then
        debugLog("确定这是一个标准窗口")
        -- 引入 0.05 秒延迟, 等待 macOS 彻底赋予窗口宽高坐标数据
        hs.timer.doAfter(0.05, function()
          -- 安全校验: 确保句柄存在
          if win and win:id() then
            debugLog("确定句柄存在")
    
            -- 使用 Hammerspoon 的 hs.osascript 模块向 Finder 发起底层查询
            local ok, windowClass = hs.osascript.applescript('tell application "Finder" to get class of front window as string')
    
            -- 严格过滤: 如果底层类名不是 "Finder window", 则绝对不是普通的文件夹浏览窗口, 直接跳过
            if not ok or windowClass ~= "Finder window" then
              debugLog("非文件浏览窗口, 真实内部类型为:" .. tostring(windowClass) .. "-> 跳过调整")
              return
            end
    
            debugLog("确认是文件浏览窗口, 准备调整尺寸, 真实内部类型为:" .. tostring(windowClass))
    
            local currentSize = win:size()
            local targetW = 803
            local targetH = 625
            local tolerance = 5
    
            -- 模糊宽高校验
            if math.abs(currentSize.w - targetW) <= tolerance and math.abs(currentSize.h - targetH) <= tolerance then
              debugLog("窗口尺寸已符合要求, 跳过调整")
              return
            end
    
            if currentSize.w < targetW and (targetW - currentSize.w) > 300 then
              debugLog("当前窗口尺寸很小, 认为不是文件浏览窗口, 跳过调整")
              return
            end
    
            -- 记录当前动画时间并强制设为 0 (实现无缝缩放)
            local defaultAnimationDuration = hs.window.animationDuration
            hs.window.animationDuration = 0
    
            debugLog("校验完毕, 开始执行尺寸修改")
            win:setSize({ w = targetW, h = targetH })
    
            -- 恢复系统动画时间
            hs.window.animationDuration = defaultAnimationDuration
            debugLog("窗口尺寸调整完毕")
          end
        end)
      end
    end)
    
    
    4 replies    2026-07-29 18:04:49 +08:00
    iWill
        1
    iWill  
       1 day ago via iPhone
    这个好,我也有同样的观感,今晚回去试一试
    pricky777
        2
    pricky777  
       1 day ago
    感谢,实现了
    loganovo
        3
    loganovo  
    OP
       9h 55m ago
    又优化了一下, 应该是最终版了:

    1. 窗口大小判断移动到 AppleScript 执行之前, 减少开销
    2. 增强健壮性, 预防极端条件的竟态情况
    3. 设置窗口的宽高放到外部设置, 另解释: 是逻辑(非物理)像素距离,需要使用 mac 系统自带的截图工具测量距离
    4. 优化日志



    ```
    -- 统一日志打印控制开关
    -- 将其设置为 false 即可关闭此脚本的所有输出
    ENABLE_DEBUG_LOG = false

    -- 自定义日志包装方法, 支持传入多个参数
    function debugLog(...)
    if ENABLE_DEBUG_LOG then
    print(...)
    end
    end

    -- 2 、finder 窗口拦截修改宽高脚本
    -- 动态获取系统 Finder 的本地化名称 (中文系统下返回 "访达", 英文返回 "Finder")
    -- 这样写可以彻底避免硬编码字符串带来的匹配失败问题
    -- 动态获取系统中当前正在运行的 Finder 进程对象
    local finderApp = hs.application.get("com.apple.finder")
    -- 从运行的进程中提取它在当前系统语言下实际显示的名称
    -- 如果是中文系统, finderApp:name() 将 100% 准确地返回 "访达"
    FINDER_APP_NAME = finderApp and finderApp:name() or "Finder"

    -- 设置窗口的宽高: 逻辑(非物理)像素距离,需要使用 mac 系统自带的截图工具测量距离
    FINDER_APP_W = 803
    FINDER_APP_H = 625

    -- 初始化窗口过滤器: 传入 false 表示默认拒绝系统中的所有窗口事件, 节约性能
    FinderFilter = hs.window.filter.new(false)

    -- 使用 setAppFilter 专门针对 Finder 设定规则:
    -- 1. allowRoles = 'AXStandardWindow': 底层直接屏蔽偏好设置, 进度条等非标准面板.
    -- 2. 故意不写 visible 参数: 默认为 nil (忽略), 这意味着即使是刚创建还未渲染显示出来的底层窗口也能被捕获.
    -- 使用 setAppFilter 专门针对 Finder 设定规则
    FinderFilter:setAppFilter(FINDER_APP_NAME, {
    allowRoles = 'AXStandardWindow',
    rejectTitles = {
    -- 精准匹配设置面板
    "^" .. FINDER_APP_NAME .. "设置$",
    "^" .. FINDER_APP_NAME .. "偏好设置$",
    "^" .. FINDER_APP_NAME .. " Settings$",
    "^" .. FINDER_APP_NAME .. " Preferences$",
    -- 新增: 利用 Lua 模式匹配 (以特定字符串结尾的正则 $ 锚点)
    -- 拦截所有标题以 "简介" 或 " Info" 结尾的窗口
    "简介$",
    " Info$"
    }
    })

    -- 订阅窗口创建事件
    FinderFilter:subscribe(hs.window.filter.windowCreated, function(win)
    -- 此时能触发该函数的, 必定是 Finder 的标准窗口
    debugLog("检测到新建标准窗口:" .. tostring(win:title()))
    -- 安全与类型检查:
    -- 1. 确保 win 对象及其底层的窗口 ID 真实存在
    -- 2. isStandard() 确保这是一个标准窗口, 而非偏好设置或进度条面板
    if win and win:id() and win:isStandard() then
    debugLog("确定这是一个标准窗口")
    -- 引入 0.05 秒延迟, 等待 macOS 彻底赋予窗口宽高坐标数据
    hs.timer.doAfter(0.05, function()
    -- 安全校验: 确保句柄存在
    if win and win:id() then
    debugLog("确定句柄存在")

    local currentSize = win:size()
    local tolerance = 5

    -- 模糊宽高校验
    if math.abs(currentSize.w - FINDER_APP_W) <= tolerance and math.abs(currentSize.h - FINDER_APP_H) <= tolerance then
    debugLog("[拦截] 窗口尺寸已符合要求, 跳过调整")
    return
    end

    if currentSize.w < FINDER_APP_W and (FINDER_APP_W - currentSize.w) > 300 then
    debugLog("[拦截] 当前窗口尺寸很小, 认为不是文件浏览窗口, 跳过调整")
    return
    end

    -- 记录当前 Hammerspoon 捕获到的窗口标题, 用于后续校验
    local currentTitle = win:title()

    -- 编写 AppleScript: 提取当前最前方窗口的 class 和 name, 并作为列表返回
    local asCmd = [[
    tell application "Finder"
    if (count of windows) is 0 then return {"none", "none"}
    set fw to front window
    set wClass to class of fw as string
    set wName to name of fw as string
    return {wClass, wName}
    end tell
    ]]

    -- 使用 Hammerspoon 的 hs.osascript 模块向 Finder 发起底层查询
    local ok, result = hs.osascript.applescript(asCmd)

    -- 严格过滤: 确保返回的类型是表, 且包含 class 和 name 数据
    if not ok or type(result) ~= "table" or #result ~= 2 then
    debugLog("[拦截] AppleScript 执行失败或未获取到有效窗口数据 -> 跳过调整")
    return
    end

    local windowClass = result[1]
    local windowName = result[2]

    -- 双重校验: 标题必须绝对相等 (排除竞态条件), 且底层类名必须是 "Finder window"
    if windowClass ~= "Finder window" or windowName ~= currentTitle then
    debugLog("[拦截] 非浏览窗口或触发竞态条件. 顶层活跃窗口: class=" .. tostring(windowClass) .. ", title=" .. tostring(windowName) .. " -> 跳过调整")
    return
    end

    debugLog("确认是文件浏览窗口, 准备调整尺寸. 顶层活跃窗口: class=" .. tostring(windowClass) .. ", title=" .. tostring(windowName))

    -- 记录当前动画时间并强制设为 0 (实现无缝缩放)
    local defaultAnimationDuration = hs.window.animationDuration
    hs.window.animationDuration = 0

    win:setSize({ w = FINDER_APP_W, h = FINDER_APP_H })

    -- 恢复系统动画时间
    hs.window.animationDuration = defaultAnimationDuration
    debugLog("窗口尺寸调整完毕")
    end
    end)
    end
    end)

    ```
    loganovo
        4
    loganovo  
    OP
       9h 48m ago
    呃呃, 这里不太好看, 最新代码放 gist 上了


    https://gist.github.com/loganoxo/8bd043b0e63c7599bafcac6e73ddf07d
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   996 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 19:53 · PVG 03:53 · LAX 12:53 · JFK 15:53
    ♥ Do have faith in what you're doing.