Summon the Obsidian window and keep it on top with a single system-wide hotkey — press the same key again to hide it.
Desktop only (Windows / macOS / Linux).
- System-wide global hotkey that fires even when the window is unfocused.
- Three-state toggle driven by the real window state, not by an internal flag: invisible → summon, visible-but-not-on-top → re-pin, visible-and-on-top → collapse.
- Topmost keeper: re-asserts the window's z-order on focus/blur/show/restore and every 1.5 s, so it survives being pushed behind another window — including a fullscreen browser. The keeper only acts while the window is unfocused: re-raising an active window can only cover popups that legitimately belong above it (IME candidate list, tooltips, menus).
- Uses the
screen-saverz-order level, which avoids Electron's Windows-only "place the window behind the taskbar" path. - Desktop only — the plugin controls the native window, so mobile is not supported.
- Close Obsidian, or make sure the target vault is open.
- Copy these three files:
manifest.json,main.js,styles.css. - Create the folder
<your-vault>/.obsidian/plugins/pin-toggle/. The folder name must match theidfield inmanifest.json(pin-toggle). - Put the three files into that folder.
- In Obsidian open
Settings → Community plugins, turn off Restricted mode, then enable Pin Toggle.
Hotkeys are bound in Obsidian's own hotkey UI — not inside the plugin settings page.
- Open
Settings → Hotkeys. - Search for Pin Toggle and find the command "唤出 / 收起窗口" (summon / collapse window).
- Click the record button and press the combination you want, for example
Ctrl+Alt+Space. - The plugin registers it as a system-wide global hotkey within a few seconds, so it works even while the window is hidden or minimized.
- If it does not take effect immediately, open the plugin settings and click 立即同步全局热键 (sync the global hotkey now).
- If the combination is already taken by another app, a notice will tell you to pick a different one.
- If you never bind a hotkey, the plugin falls back to
Ctrl+Alt+Space, so it works out of the box.
Note: the bound combination is registered system-wide, so it is captured from every application while the plugin is enabled. Prefer combinations that no other app relies on.
Alt+<letter>is a poor choice on Windows — it also drives menu mnemonics.
| Option | Description | Default |
|---|---|---|
| 置顶 (always on top) | Whether the summoned window really floats above other applications. Turn it off to merely bring it to the front. | On |
| 置顶守护 (topmost keeper) | Keep re-asserting the z-order so the window cannot be pushed behind other windows. Turn it off to save power, at the cost of only pinning at summon time. | On |
| 收起方式 | Collapse behavior: hide the window (no taskbar entry, like Alt+E) or minimize it to the taskbar. |
Hide |
Press your hotkey in any application: the window is summoned and pinned on top. Press it again to collapse and return to what you were doing. You can also use the 📌 ribbon icon in the left sidebar, or run the command from the command palette.
Two details decide whether "always on top" survives contact with other applications.
1. The z-order level matters on Windows. setAlwaysOnTop(flag) defaults to the floating level. On Windows, Electron does not use the level to pick a z-order band — it uses it to decide whether the window should be placed behind the taskbar (Shell_TrayWnd). The levels floating, torn-off-menu, modal-panel, main-menu and status all trigger that behaviour. Windows documents that repositioning a topmost window after any non-topmost window removes its topmost status, so a window pinned at the default level can silently stop being topmost. This plugin pins at screen-saver, which is not in that set.
2. A single setAlwaysOnTop call is not enough. Fullscreen windows (browser F11, video fullscreen, PDF/PPT presentation) are raised above topmost windows by the OS. The only compensation is to re-assert the z-order periodically, which the keeper does — with moveTop() only, never focus(), so the focus stays wherever the user put it.
3. Re-assertion must be conditional. moveTop() resolves to SetWindowPos(hwnd, HWND_TOP, ...). For a window that is already topmost, that means "move to the very front of the topmost band" — which also puts it in front of the IME candidate window, because that is a topmost popup in the same band. An unconditional timer therefore covers the candidate list roughly every 1.5 s while you type pinyin. The keeper now skips re-assertion in two cases:
- the window is the active window (
isFocused()) — it is already above every non-topmost window, so there is nothing to recover; - an IME composition is in progress, or ended less than 300 ms ago (
compositionstart/compositionupdate/compositionendon the document).
Only an explicit summon bypasses both checks.
- Exclusive fullscreen cannot be covered. Games and DirectX exclusive-fullscreen apps render outside the normal window stack; no user-mode always-on-top window can appear above them. This is a Windows/OS constraint, not a plugin bug — the Electron maintainers describe it as "by OS design" (electron#10078). Borderless fullscreen (browser
F11, video fullscreen, PDF/PPT presentation) can be covered. - Covering a fullscreen window requires re-assertion. A very short interval is more reliable but costs more CPU; 1.5 s is the default. Turn the keeper off if you prefer the old "pin once" behaviour.
- The keeper never calls
focus(). Stealing focus would prevent you from typing into the fullscreen page underneath. It only callsmoveTop(), which changes the z-order without changing activation. - A pinned window still covers other apps' popups. The keeper is silent while Obsidian is focused, but if Obsidian is pinned on top and you type into a different application, the 1.5 s re-assertion can still land on that application's IME candidate window. This is inherent to always-on-top; turn the keeper off if it gets in the way.
- macOS fullscreen spaces additionally need
setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }), which has no effect on Windows.
If the window still gets covered, run the read-only diagnostic probe while reproducing:
python tools/win-zorder-probe.py # one shot
python tools/win-zorder-probe.py --watch # live, once per secondIt prints every top-level window's z-order, WS_EX_TOPMOST flag, fullscreen state and the Obsidian window's position relative to the taskbar, then classifies the failure as A/B/C/D.
- If the console shows
[Pin Toggle] 无法获取 Electron 窗口, the Electron remote API is unavailable in your environment; pinning and the global hotkey are disabled. - The plugin only controls the Obsidian window. It never quits the app, so you can summon it again at any time.
npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint src/ package.json
npm run build # bundles src/main.ts into main.js
npm test # state-machine smoke test against the built main.jssrc/main.ts is the source of truth. main.js at the repository root is the esbuild bundle that Obsidian actually loads — do not edit it by hand.
一个极简 Obsidian 桌面插件:一次全局热键把窗口唤出并置顶,再按一次收起。
- 系统级全局热键(窗口无焦点时也能触发)。
- 三态切换,依据窗口真实状态而非插件内部标志:看不见 → 唤出;看得见但没置顶 → 重新置顶;看得见且已置顶 → 收起。
- 置顶守护:在 focus/blur/show/restore 以及每 1.5 秒重新断言一次 z 序,被其他窗口(含全屏浏览器)压下去后能自动抢回上层。
- 使用
screen-saver置顶层级,绕开 Electron 在 Windows 上「把窗口压到任务栏之后」的路径。 - 桌面端专用(Windows / macOS / Linux)。
- 关闭 Obsidian(或确保目标 vault 已打开)。
- 找到三个文件:
manifest.json、main.js、styles.css。 - 在 vault 内创建目录
<你的vault>/.obsidian/plugins/pin-toggle/- 文件夹名必须与
manifest.json里的id(即pin-toggle)一致。
- 文件夹名必须与
- 把上述三个文件复制进去。
- 打开 Obsidian →
设置→社区插件→ 关闭「安全模式」→ 在「已安装插件」列表里启用 Pin Toggle。
- 打开 Obsidian →
设置→快捷键。 - 搜索 Pin Toggle,找到命令 「唤出 / 收起窗口」。
- 点它右侧的录制按钮,按下你想要的组合键(例如
Ctrl+Alt+Space)绑定即可。 - 绑定后插件会在数秒内自动把该键注册为系统级全局热键(即使窗口在后台/最小化也能唤起)。
- 若未立即生效,打开本插件的设置页点一下「立即同步全局热键」。
- 若该组合键已被其他软件占用,会弹出提示要求换一个。
- 若你从不在「快捷键」页绑定,插件会默认使用
Ctrl+Alt+Space,开箱即用。
注意:绑定的组合键会被全局注册,插件启用期间会从所有软件那里截获该按键。请避免使用其他软件依赖的组合键;Windows 上
Alt+字母尤其不合适(会与菜单助记符冲突)。
在任意软件按下你设定的热键 → 窗口立即置顶唤出;用完再按 → 收起,回到原工作。左侧边栏也有一个 📌 图标可点按切换;命令面板里也能搜到该命令手动执行。
两个细节决定「置顶」能不能扛住其他软件。
一、Windows 上置顶层级很关键。 setAlwaysOnTop(flag) 缺省级别是 floating。Electron 在 Windows 上并不用这个级别决定层级高低,而是用它决定窗口要不要被压到任务栏之后——floating、torn-off-menu、modal-panel、main-menu、status 这五个都会触发该行为。而 Windows 官方文档明确:把最顶层窗口重新定位到任何非最顶层窗口之后,它就不再是最顶层窗口。所以按默认级别置顶的窗口可能悄悄失去 topmost。本插件改用 screen-saver,不命中该集合。
二、只调用一次 setAlwaysOnTop 不够。 全屏窗口(浏览器 F11、视频全屏、PDF/PPT 放映)会被操作系统抬到 topmost 窗口之上。唯一的补偿办法是周期性重新断言 z 序——由「置顶守护」完成,且只用 moveTop()、绝不用 focus(),不会打断你的输入。
- 独占全屏无法覆盖。游戏/DirectX 独占全屏应用不在普通窗口栈里渲染,任何用户态置顶窗口都盖不住它。这是 Windows 的语义限制,不是插件缺陷(Electron 维护者称之为 "by OS design",见 electron#10078)。无边框全屏(浏览器
F11、视频全屏、PDF/PPT 放映)可以覆盖。 - 覆盖全屏窗口依赖持续重新断言。间隔越短越稳、开销越大,默认 1.5 秒。关掉「置顶守护」即可回到旧的「只在唤出那一刻置顶」行为。
- 守护过程绝不调用
focus()。否则会抢走焦点,你就无法在全屏页面里打字。只用moveTop(),只改 z 序、不改激活状态。 - macOS 全屏 Space 还需
setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }),该 API 在 Windows 上无效。
若窗口仍被盖住,在复现问题时运行只读诊断探针:
python tools/win-zorder-probe.py # 打印一次
python tools/win-zorder-probe.py --watch # 每秒刷新,边操作边看它会打印所有顶层窗口的 z 序、WS_EX_TOPMOST 标志、是否全屏、以及 Obsidian 相对任务栏的位置,并给出 A/B/C/D 判定。
- 若控制台出现
[Pin Toggle] 无法获取 Electron 窗口:说明当前环境拿不到 Electron remote,置顶与全局热键会停用。 - 该插件仅控制窗口,不会退出应用,收起后可随时再次唤出。
npm install
npm run typecheck
npm run lint
npm run build
npm testsrc/main.ts 是源码;根目录的 main.js 是 esbuild 产物,不要手改。