A macOS CLI tool that fully disconnects the MacBook's built-in display from WindowServer compositing while the lid stays open — a true GPU disconnect, not a backlight dim. Windows on the built-in display migrate to the primary external monitor; the GPU stops rendering to it.
ELAP is free and open source. Born from a personal itch: working with a single external monitor while the laptop sits open but its screen wastes GPU cycles. No existing free tool solved this cleanly, so this one was built from scratch.
ELAP uses private SkyLight.framework APIs (CGSConfigureDisplayEnabled and fallbacks)
resolved at runtime via dlsym. SIP does not need to be disabled — these calls work from
user space. The tool will break if Apple renames or removes the symbols in a future macOS
release; see Troubleshooting.
- macOS 13 Ventura or later (Apple Silicon or Intel)
- At least one external display connected when disabling the built-in
- Xcode Command Line Tools:
xcode-select --install
brew tap LightC0de/elap
brew install elapUpgrade and uninstall:
brew upgrade elap
brew uninstall elapBuilds from source; requires Xcode Command Line Tools.
# Build release binary
make build # → .build/release/elap
# Install to /usr/local/bin
sudo make install # → /usr/local/bin/elap
# Uninstall
sudo make uninstall
# Clean build artifacts
make cleanUniversal binary (arm64 + x86_64):
swift build -c release --arch arm64 --arch x86_64
# output: .build/apple/Products/Release/elap
sudo cp .build/apple/Products/Release/elap /usr/local/bin/elap| Subcommand | Description |
|---|---|
list |
List all displays with IDs, resolutions, and state |
status |
Show whether the built-in display is enabled or disabled |
on |
Enable the built-in display |
off |
Disable the built-in display |
toggle |
Toggle: disable if enabled, enable if disabled |
watch |
Watch for display changes; auto-enable on external disconnect |
daemon |
Manage the background watch daemon |
# Disable with safety countdown (requires external display)
elap off
# Disable and auto-revert after 15 seconds if not confirmed
elap off --timeout 15
# Disable immediately, skip countdown and external-display guard
elap off --force
# Re-enable the built-in display
elap on
# Toggle
elap toggle
elap toggle --timeout 10
# List all displays with verbose info
elap list --verbose
# Install the watch daemon (auto-enables built-in when external disconnects)
elap daemon install
elap daemon status
elap daemon uninstall- External display required —
elap offrefuses if no active external display is found (exit code 2). Override with--force. - 5-second countdown — a warning countdown runs before disabling (skipped with
--force). - SIGINT / SIGTERM re-enable — pressing Ctrl+C during or after disable re-enables the built-in display immediately.
- SIGKILL cannot be caught — if the process is killed with
SIGKILL, the display stays off. Recovery:elap on # or, if elap itself is unavailable: sudo killall -KILL WindowServer - Logout / reboot —
.permanentlymode reverts on logout or reboot (macOS resets display configuration on session end).
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (API failure, symbol not found, etc.) |
| 2 | No active external display found |
A MenuBarExtra app (ELAPApp) wraps the CLI in a menu bar icon and panel — no terminal
needed. It never mutates displays itself; every toggle runs the bundled elap CLI as a
subprocess, so it stays reliable across hot-plugs and sleep/wake for as long as it runs.
make app # → dist/ELAP.app
open dist/ELAP.appPanel features:
- Toggle + status — turn the built-in display on/off; disabled with an explanation when no external display is active.
- Auto-manage — app-side equivalent of
elap watch; warns ifelap daemonis also running, since the two would otherwise fight over the display. - Launch at login — via
SMAppService; requires the app be run fromdist/ELAP.app(or/Applications), notswift run. - Global hotkey — toggle the built-in display from any app.
See .claude/app-spec.md for architecture details.
For development, swift run ELAPApp runs the app unbundled (UI iteration only — launch at
login needs the real bundle).
For CLI-only setups (no menu bar app), you can wire a system-wide hotkey directly to the CLI:
- Open Shortcuts.app → New Shortcut → Add Action → Run Shell Script
- Shell:
/bin/zsh; Script:/usr/local/bin/elap toggle --force - Assign a keyboard shortcut in the shortcut's settings
Alternatively, use Automator → Quick Action → Run Shell Script with the same command.
ELAP calls into the private SkyLight.framework (the macOS compositor layer, sitting above
CoreGraphics and below the window server). The function CGSConfigureDisplayEnabled removes
the display from WindowServer's compositing tree: the GPU stops rendering to it and any windows
on it migrate to the primary display.
The call is wrapped inside the standard public CoreGraphics display-configuration transaction:
CGBeginDisplayConfiguration → CGSConfigureDisplayEnabled → CGCompleteDisplayConfiguration(.permanently).
The symbol is resolved at runtime via dlsym from a fallback list (see below), so no SkyLight
link is required and the binary degrades gracefully when a symbol is missing.
Because a disabled display vanishes from CGGetOnlineDisplayList, ELAP persists its ID to
~/.elap-builtin-id before disabling and recovers it via hardware probe (IDs 1–32) or the
state file when re-enabling.
Tried in order; the first non-nil result wins:
CGSConfigureDisplayEnabled— macOS 13 Ventura / 14 Sonoma / 15 Sequoia (primary)CGSSetDisplayEnabled— pre-Ventura fallbackSLSConfigureDisplayEnabled— SkyLight-prefix variant, internal/debug buildsSLSSetDisplayEnabled— SkyLight-prefix variant
Symbol not found Run:
nm /System/Library/PrivateFrameworks/SkyLight.framework/SkyLight | grep -iE "display|enabled"This lists the actual symbol names on your macOS version. Open an issue with the output.
Display stuck off (elap on not working)
sudo killall -KILL WindowServerThis restarts WindowServer; macOS resets all display configuration.
GPU didn't drop the display Check:
elap status
elap list --verboseIf status shows DISABLED but the display is visually still on, the private API may have changed.
File an issue with your macOS version and the output of the nm command above.
| Version | Status |
|---|---|
| 15 Sequoia | Expected to work |
| 14 Sonoma | Expected to work |
| 13 Ventura | Primary tested target |
| 12 Monterey | Below minimum (not supported) |