Quickly switch among applications in the macOS Dock with one global hotkey.
dock-switch 现在可以通过串口监听连接的 GoKit5 / 机智云控制器,把硬件按键信息转换成屏幕控制动作。刷入匹配固件后,控制器可以向 dock-switch 发送主机按键消息,让 Codex 焦点、鼠标指针和光标反馈在内置屏、外接屏、侧边屏之间切换。
- dock-switch 启动后会自动开始串口监听。
- GoKit5 USB 串口会自动识别,也可以用
GOKIT5_SERIAL_PORT=/dev/cu.usbmodem...固定端口。 - 物理按键映射到屏幕目标:minus = 左侧边屏,voice = 外接屏,green = 右侧边屏,plus = 内置屏。
dock-switch-cli gokit5-status可以查看监听是否启用、是否运行,以及当前使用的串口。- 设置
DOCK_SWITCH_GOKIT5=0可以关闭串口监听。
- Press
F20to open the floating launcher UI. - Press the shown key for an app to focus it.
- Press an arrow key to fill the frontmost window on a physical display:
←left side display (Mi Monitor (1)on this Mac)→right side display (Mi Monitor (2)on this Mac)↑external display (DELL U3219Qon this Mac)↓internal display (Built-in Retina Displayon this Mac)
- Press
【to tile the frontmost window to the left half of its current display. - Press
】to tile the frontmost window to the right half of its current display. - Press
Shiftto focus or openCodex, then restore its remembered window bounds when available. - Press
Tabto focus or openChatGPT, then restore its remembered window bounds when available. - Press left
Commandto focus or openSystem Settings, then restore its remembered window bounds when available. - Press right
Commandfor a reserved no-op. - Press
\to enter macOS native fullscreen (same as the green window button). - App activation and arrow display moves move the pointer to the center of the target display.
- A connected GoKit5 controller flashed with open-embodied can select Codex display focus: minus = left side display, voice = external display, green = right side display, plus = internal display.
- The UI closes automatically after a selection.
This project supports per-app window placement through src/config.json.
Example:
{
"name": "Safari",
"key": "S",
"screen": "3",
"placement": "external_left_half"
}{
"name": "Google Chrome",
"key": "B",
"screen": "4",
"placement": "external_right_half"
}{
"name": "GitHub",
"key": "G",
"screen": "3",
"kind": "web_app",
"placement": "internal_fill",
"open_path": "~/Applications/Chrome Apps.localized/GitHub.app",
"app_url": "https://github.com/"
}When triggered from dock-switch, Safari lands on the left half of the external display.
Web apps with kind: "web_app" use the same placement by default.
The X web app is maximized on the internal display work area.
The 小红书 web app is maximized on the internal display work area.
The GitHub web app is maximized on the internal display work area.
Google Chrome lands on the right half of the external display.
The X, 小红书, and GitHub web app bundles can target the signed-in Chrome-family profile used by their app shims.
Xiaohongshu Web App is available on R in the current default config.
GitHub Web App is available on G in the current default config.
ChatGPT and Codex render in the HUD as symbolic shortcut labels: ⇥ for Tab / ChatGPT and ⇧ for Shift / Codex. They remain excluded from ordinary fallback numbering.
Left Command opens System Settings. Right Command is intentionally reserved as a no-op.
If no external display is available, external_right_half falls back to the right half of the internal display work area.
If no external display is available, external_left_half falls back to the left half of the internal display work area.
By default, dock-switch remembers the last known window bounds (x/y/width/height) for each app and restores them when that app is reopened from dock-switch.
- Window state is kept in memory for the current app session (no disk persistence).
- This includes maximized-like window sizes because the actual bounds are restored.
- Apps with explicit
placement(for exampleexternal_right_halforinternal_fill) keep that placement behavior. - Apps with
kind: "web_app"default toexternal_right_halfunlessplacementoverrides it. open_pathcan pin a launcher item to an exact app bundle, which is useful for Chrome web app shims stored under~/Applications/Chrome Apps.localized.app_urllets dock-switch identify a Chrome--app=...window by pid when Accessibility sees onlyGoogle Chrome.
To disable restore for a specific app, add:
{
"name": "Terminal",
"key": "T",
"screen": "4",
"remember_window_state": false
}- Download a release from GitHub Releases.
- Clone this repository.
- Install dependencies:
yarn install
- Run locally:
yarn go
- Build unsigned app bundle:
yarn dist
- Build signed app bundle (requires signing identity):
yarn dist:signed
dock-switch-cli is the canonical command-line entrypoint for window placement, display inspection, and Playwright-managed Chrome targeting.
Examples:
dock-switch-cli displays
dock-switch-cli gokit5-status
dock-switch-cli codex-display --target external
dock-switch-cli place --app "Terminal" --placement external_right_half
dock-switch-cli place --pid 12345 --placement external_right_half
dock-switch-cli move --app "Terminal" --x 0 --y 25 --w 1512 --h 875
dock-switch-cli move --pid 12345 --x 0 --y 25 --w 1512 --h 875
dock-switch-cli get-chrome-window --profile-dir /tmp/playwright_chromiumdev_profile-XXXXXX
dock-switch-cli move-chrome-window --profile-dir /tmp/playwright_chromiumdev_profile-XXXXXX --x 713 --y -1410 --w 1280 --h 1410Notes:
--pidis useful when you need to target one managed window from a multi-window app, but it is not sufficient for Playwright-managed Chrome.get-chrome-windowandmove-chrome-windowtarget the exact Chrome window for a specific--user-data-dirprofile through Chrome DevTools, which is the reliable path for Playwright-managed Chrome windows.- If the dock-switch control socket is not running, the CLI launches
/Applications/dock-switch.appand retries automatically. displaysprints JSON with Electron display bounds and work areas.gokit5-statusprints the runtime serial listener state and selected port.codex-displayfocuses an existing Codex window on the target display when available; otherwise it moves a reusable Codex window there, activates it, and centers the pointer on that display.- The GoKit5 serial listener auto-detects the Espressif USB JTAG/serial device and can be pinned with
GOKIT5_SERIAL_PORT=/dev/cu.usbmodem...; setDOCK_SWITCH_GOKIT5=0to disable it. The matching firmware lives at longbiaochen/open-embodied.
Headed Playwright Chrome should be targeted by profile, not by generic app name and not by the Playwright session pid reported in CLI output.
Typical flow:
dock-switch-cli displays
dock-switch-cli get-chrome-window --profile-dir /tmp/playwright_chromiumdev_profile-XXXXXX
dock-switch-cli move-chrome-window --profile-dir /tmp/playwright_chromiumdev_profile-XXXXXX --x 713 --y -1410 --w 1280 --h 1410This is the path used by the shared Codex Playwright wrapper.
App key/display mapping is stored in src/config.json under dock_items.
- Map a key to
F20(for example with Karabiner-Elements). - A direct hotkey can call the CLI without opening the launcher. Example:
F3 -> dock-switch-cli place --app "Terminal" --placement external_right_half. - Keep the installed app in macOS
Open at Loginso the global shortcut and control socket are available after login. - On first use, dock-switch prompts for required macOS permissions:
- Accessibility (control UI elements / Dock metadata)
- If previously denied, re-enable in Privacy & Security:
- Accessibility:
Privacy & Security > Accessibility
- Accessibility:
- macOS may warn about an unidentified developer depending on how the app is built/signed.
- Electron entry point:
src/main.js - Renderer/UI logic:
src/index.js - Dock metadata provider: native Node addon (
native/dock-query) - Canonical automation entrypoint:
bin/dock-switch-cli.js
