Show HN:Zen Mode – macOS 的全局专注模式
Show HN: Zen Mode – a global focus mode for macOS

原始链接: https://github.com/cabeen/zen-mode

**Zen Mode** 是一款轻量级的 Hammerspoon 脚本,为 macOS 上的任何应用程序提供全局的“专注模式”,消除干扰。只需一个快捷键(Option + Cmd + Z),当前窗口就会滑向屏幕中央并拉长至全高,同时菜单栏、Dock 栏、壁纸及背景窗口会被自定义的黑色遮罩层覆盖。 与特定应用程序的“禅模式”功能不同,该脚本适用于浏览器、终端和编辑器等所有程序。主要功能包括: * **同步过渡:** 自定义动画引擎可平滑处理窗口移动和屏幕变暗,带来流畅的视觉体验。 * **智能 UI 处理:** 能够动态追踪窗口位置,防止边缘漏光,并可轻松退出全屏。 * **状态还原:** 脚本会记住窗口的原始大小和位置,并在退出时完美恢复。 * **极简且透明:** 代码仅约 250 行,无需任何依赖,高度可定制,且避免了独立应用程序的额外负载。 Zen Mode 专为提升效率而设计,它能将你当前聚焦的窗口变成舞台中央的“聚光灯”,消除视觉杂乱,助你保持深度专注。

Hacker News 新帖 | 往期 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 **Show HN: Zen Mode – macOS 全局专注模式 (github.com/cabeen)** 7 点,由 cafebeen 于 1 小时前发布 | 隐藏 | 往期 | 收藏 | 1 条评论 | 帮助 **cafebeen 1 小时前** [–] 作者在此。我制作了这个名为“Zen Mode”的 macOS 工具,旨在提供一种跨应用运行的全局专注模式。市面上有很多类似功能的软件(如 HazeOver、台前调度、内置全屏模式等),但都没有达到我预想的效果。其基本理念是通过一个快捷键将当前窗口置于屏幕中央,并将其余部分像聚光灯一样调暗至黑色;当你完成任务后,窗口会回到原位,背景光也会恢复。这是一个基于约 250 行 Hammerspoon Lua 代码实现的简单方案,使用 Fable 构建,经过多次迭代才达到目前的交互体验。欢迎提出任何问题或建议。 回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

A small tool to reclaim my attention - providing a global focus mode across macOS apps.

My screen is always full of things asking for it: other windows, notification badges, the Menu Bar clock, the Dock, a scenic wallpaper. Zen Mode is one hotkey that silences all of it. The window I'm working in glides to the center of the screen and stretches to full height, and everything else — Menu Bar and Dock included — fades to black. Press the hotkey again (or Esc) and everything returns exactly where it was. It's like full-screen mode, except your window keeps its natural width — more like a spotlight than a takeover.

It's a Hammerspoon script, not an app, and it works on every Mac app: unlike the zen modes built into individual editors, the same keystroke works in your terminal, your browser, a PDF reader, or anything else with a window.

Read more about how this came together on my blog.

A busy desktop — other app windows, the wallpaper, the Menu Bar, and the Dock all competing for attention — until one keystroke centers the focused window and fades everything else to black. It works the same in a terminal, a browser, or any other app:

Zen Mode toggling on and off across a terminal and a browser window

  • Any app, no per-app setup — this is a global mode, not an app feature: it operates on whatever window is focused, so the same hotkey zens your terminal, browser, notes app, or anything else.
  • One-keystroke toggleOption + Cmd + Z enters or exits Zen Mode instantly.
  • Centered, full-height layout — the window keeps its width, grows to full screen height, and is centered horizontally.
  • Aggressive UI hiding — the darkening canvas renders at the macOS cursor window level, completely burying the Menu Bar and Dock.
  • Hermetic visual seal — a slightly inset, rounded cutout hugs the inside of the macOS window frame, so no background pixels leak through the corners.
  • Smooth transitions — entering plays a two-phase ease-in-out choreography: the window glides to center as the backdrop fades to black, then grows to full height under the darkness; exiting reverses it. Both phases are tunable (or fully off) via moveDuration and resizeDuration.
  • Live window tracking — drag or resize the window while Zen Mode is active and the cutout follows it; if the window closes, the overlay tears itself down.
  • Safe Escape hookEsc exits Zen Mode instantly, but the key is only intercepted while Zen Mode is active; the moment you exit, it's handed back to macOS so Vim, terminals, and normal OS behavior work as usual.
  • State restoration — your window's exact position and size are remembered and restored when you exit.
  1. Install Hammerspoon and launch it:

    brew install --cask hammerspoon
  2. Grant Hammerspoon Accessibility permissions (System Settings > Privacy & Security > Accessibility):

    Hammerspoon enabled in the Accessibility pane of System Settings
  3. Append the contents of init.lua from this repository to your ~/.hammerspoon/init.lua:

    mkdir -p ~/.hammerspoon
    curl -fsSL https://raw.githubusercontent.com/cabeen/zen-mode/main/init.lua >> ~/.hammerspoon/init.lua
  4. Click the Hammerspoon icon in the Menu Bar and select Reload Config:

    Reload Config in the Hammerspoon menu bar menu
Action Keys
Toggle Zen Mode on the focused window Option + Cmd + Z
Exit Zen Mode Option + Cmd + Z or Esc

Instead of resizing or hiding other apps, the script draws a single full-screen CoreGraphics canvas over everything — elevated to the cursor window level so it covers the Menu Bar and Dock — and then punches a transparent, rounded-corner hole exactly where your focused window sits. Your window stays a completely normal, interactive window; everything around it is simply blacked out.

Several existing tools cover parts of this idea:

  • Distraction dimmersHazeOver (paid) and free alternatives like FocusDim automatically dim background windows whenever focus changes. They are passive, always-on effects: they don't touch the desktop wallpaper, Menu Bar, or Dock, and they never move or resize your window.
  • Hammerspoon's built-in hs.window.highlight — its "isolate" mode covers other windows and the desktop with an opaque overlay, and is the closest relative to the technique used here. It doesn't reposition the window or hide the Menu Bar and Dock.
  • App-specific zen modes — VS Code, Obsidian, iA Writer, and others ship a centered, chrome-free writing mode, but only inside that one app.
  • Native macOS full screen — hides the Menu Bar and Dock, but stretches the window edge-to-edge rather than framing it.

What this script does differently is combine those pieces into one deliberate, reversible gesture that works on any window: a single hotkey centers the window at full height, blacks out everything else on screen — wallpaper, Menu Bar, and Dock included — in one synchronized animated transition, and puts the window back exactly where it was when you leave. It's also not an app: it's ~250 lines of dependency-free Lua you can read in a few minutes and tweak to taste.

Most of the effort in this script went into small UX details that are easy to get wrong:

  • Smooth animation. Hammerspoon's built-in window animation (hs.window.animationDuration) steps the window through a series of synchronous Accessibility calls, which looks choppy. Zen Mode disables it and runs its own 60 Hz animation clock, easing the window frame and the backdrop's alpha together from a single timeline. The animation is time-based rather than step-based: if an app is slow to apply a frame, it just renders fewer intermediate frames — the transition always completes on schedule.

  • Move, then resize. Moving a window is cheap for the target app; resizing forces a re-layout on every step (terminals reflow their whole character grid). So the transition is choreographed in two phases: the window glides to center at its original size while the screen fades to black, and only grows to full height once the backdrop is fully dark — the most forgiving moment for the less-smooth resize. Exiting mirrors this: shrink in the dark, then glide home as the darkness lifts.

  • Handling window edges. The cutout is fitted to where the window actually is, not where it was asked to be. Apps apply frame changes late and round them to their own grids (a terminal snaps to character cells), so a cutout that follows the requested frame runs ahead of the window and leaks bright background mid-animation. After every frame change the script reads the real frame back and fits the cutout to that — any residual lag then errs dark (window briefly under the overlay) instead of bright. The cutout is also inset slightly (padding = -2) so the darkness overlaps the window edge rather than leaving a bright seam.

  • Handling window corners. The cutout's corner radius must be at least the window's own corner radius, or a sliver of undimmed background peeks through at the corner diagonal. macOS 26 "Tahoe" made window corners dramatically rounder — and the radius varies from window to window, with no public API to query it. The default (cornerRadius = 24) is deliberately generous because the failure directions aren't symmetric: too large merely dims a sliver of the window's corner, while too small leaks bright background.

  • Hiding the Menu Bar and Dock. An ordinary overlay window sits below the system UI. The canvas is escalated to the cursor window level — above the Menu Bar and Dock levels — so the blackout genuinely covers everything on screen except your window.

  • Why the backdrop is dark, not blurred. A frosted-glass blur would be pretty, but macOS offers no public way for a script to blur what's behind an arbitrary window (the real backdrop-blur machinery is private API territory). The workaround — screenshot the desktop, blur it, display the result — needs Screen Recording permission and produces a frozen image that goes stale the moment anything behind it changes or the window moves. A translucent dark fill achieves the same focusing effect, stays live, and costs nothing. The backdrop also stays dark regardless of the system's light/dark appearance: darkness lowers both the brightness and the contrast of the background clutter, whereas a light scrim would itself become the brightest thing on screen — the spotlight metaphor works in either mode.

A few variables in init.lua control the aesthetics:

Variable Default Effect
topMargin 40 Distance from the top of the screen, clearing the Menu Bar and camera notch.
bottomMargin 40 Distance from the bottom of the screen.
cornerRadius 24 Corner rounding of the cutout. Should be at least the window's own corner radius: too small leaks bright background at the corners, too large just dims a sliver of the window corner. macOS 26 "Tahoe" windows are much rounder than older releases (where 10 is enough).
padding -2 Edge seal between the cutout and the window. Negative values overlap the window slightly to prevent light bleed; a positive value (e.g. 25) leaves a gap so the native macOS drop shadow shows.
fillColor {white = 0, alpha = 0.85} Backdrop color. Raise or lower alpha (0.01.0) for lighter dimming or pitch black.
moveDuration 0.3 Seconds for the glide to/from center. Set to 0 for instant snapping.
resizeDuration 0.3 Seconds for the grow/shrink to full height. Set to 0 for instant snapping.

To use a different hotkey, change the modifiers and key in the hs.hotkey.bind({"alt", "cmd"}, "z", ...) call.

  • If Accessibility access is missing — or has gone stale after a Hammerspoon update, which macOS reports as enabled but treats as revoked — the hotkey shows an alert instead of darkening the screen, so the Settings window you need stays visible. The fix for a stale grant is to remove Hammerspoon from the Accessibility list ( button) and re-add it.

  • Tested on macOS 26 "Tahoe" only. The Hammerspoon APIs used here are old and stable, so earlier releases should work, but they haven't been verified — and the cornerRadius default is tuned for Tahoe's rounder windows (drop it to ~10 on older macOS). Reports from other versions are welcome.

  • Windows that can't be resized (e.g. some utility and settings windows) will center but keep their size; the cutout adapts to whatever the window actually does.

  • Native full-screen windows don't respond: macOS puts them in their own Space and ignores the reposition, so the window simply doesn't move. Leave full screen first. Switching Spaces while Zen Mode is active is untested territory; the hotkey or Esc will restore things.

  • The script sets hs.window.animationDuration = 0 globally, which also disables the built-in move animation for any other window scripts in your Hammerspoon config (Zen Mode does its own animation).

  • While Zen Mode is active, Esc is captured globally to exit. If you rely on Esc inside an app (e.g. Vim), toggle off with Option + Cmd + Z instead, or remap the escape hook.

  • The overlay covers the screen the focused window is on; other displays are unaffected.

  • One window at a time: pressing the hotkey while Zen Mode is active always exits, even if a different window is now focused — press it again to zen the new window.

  • Graceful Cmd+Tab app cycling. Today, switching apps while Zen Mode is active leaves the overlay parked around the original window. The idea: intercept app switches and hand the zen treatment to the newly focused window — animate the previous window back to its place, glide the new one into the spotlight, and keep the backdrop up the whole time. This touches app-watching, per-window state, and transition choreography, so it's deliberately out of scope for now to keep the script small and readable.

Ryan Cabeen

MIT

联系我们 contact @ memedata.com