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

7 月 28 日
 loganovo
-- 自定义日志包装方法, 支持传入多个参数
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)

3570 次点击
所在节点    Apple
8 条回复
iWill
7 月 28 日
这个好,我也有同样的观感,今晚回去试一试
pricky777
7 月 28 日
感谢,实现了
loganovo
7 月 29 日
又优化了一下, 应该是最终版了:

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
7 月 29 日
呃呃, 这里不太好看, 最新代码放 gist 上了


https://gist.github.com/loganoxo/8bd043b0e63c7599bafcac6e73ddf07d
howard777
8 月 2 日
试用了一下,还不错。第一次用 hammerspoon 。

流程:
1. [官网链接]( https://github.com/Hammerspoon/hammerspoon/releases/tag/1.1.1) 下载并安装
2. 执行下面的脚本

```
echo "require("finder")" > ~/.hammerspoon/init.lua
curl -o ~/.hammerspoon/finder.lua "https://gist.github.com/loganoxo/8bd043b0e63c7599bafcac6e73ddf07d/raw/51ff32f816156ee527908c6ff07842decbec02d2/finder.lua"
```

3. 配置完成后,需要让 Hammerspoon 重新读取文件:

点击 Mac 顶部状态栏(菜单栏)上的 Hammerspoon 小锤子图标。

在弹出的菜单中点击 Reload Config (重载配置)。
loganovo
8 月 2 日
@howard777 我昨天刚又更新了一下, 解决 webdav 目录必须延迟 0.3 秒以上的情况, 看看是不是最新代码
howard777
8 月 2 日
@loganovo #6

我在你的 gist 下面评论加了一段我自己改了的代码。
1 、加了一段合并窗口的逻辑,这下在第三方程序,点 reveal file in finder ,导致的窗口反复打开的逻辑也解决了。
2 、窗口自动居中到最中央
loganovo
8 月 2 日
@howard777 ok 这两点看个人习惯

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1230472

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX