Repository navigation
Releases: zy0816/ScreenOff
Release list
DisplayToggle 1.8.1
DisplayToggle 1.8.1 — Shutdown cleanup / 关机清理修复
此版本重点改善 macOS 关机、重新启动和应用退出时的清理行为。它是预防性修复,并不代表已经确定 DisplayToggle 是之前 kernel panic 的原因。
Changes / 更新内容
- Respond to macOS power-off notifications and normal app termination by stopping the background display watchdog.
- Unregister display reconfiguration callbacks and release the activity token used to prevent App Nap.
- Reject new SkyLight display configuration transactions once shutdown begins.
- Stop the unplug-recovery sleep/wake sequence from sending caffeinate wake events after shutdown has been signaled.
- Make selftest --dry genuinely non-invasive; it now checks shutdown guards without switching displays or invoking pmset.
- Preserve the existing display-toggle behavior, auto-restore feature, and Keep across restarts preference.
Installation / 安装
Download DisplayToggle-1.8.1.dmg and drag DisplayToggle.app into Applications, replacing the previous version.
Apple Silicon, macOS 13+. Ad-hoc signed and not notarized. If Gatekeeper blocks the first launch, right-click the app and choose Open.
Validation / 验证
Compiled successfully on Apple Silicon and passed the non-destructive selftest --dry. Shutdown/restart stability still needs real-world regression testing, so this release does not claim to eliminate every system shutdown panic.
MIT License.
DisplayToggle 1.8
Fixes: built-in display could not be turned back on
On some machines, once the built-in display was switched off it could not be turned on again at all — right-clicking reported an error and the screen stayed dark. Submitting the configuration is evidently not enough there; the system simply doesn't act on it.
Turning it on now escalates through three routes until the display actually lights up:
CGRestorePermanentDisplayConfiguration— switching off is session-scoped, so the permanent configuration still has the display enabled. Cheap, and no flicker.- Sleep and wake the displays, forcing the topology to be rebuilt — the same mechanism that recovers from an unplug
- Submit once more afterwards, since the IDs are usually accepted again once the topology has been rebuilt
Turning the display off is unchanged — if that fails there's nothing else worth trying.
Because this can now take a dozen seconds, the toggle runs off the main thread so the menu no longer freezes. If every route fails, the message says what was tried and points at the lid.
New: turn the built-in display off automatically on connect
A new menu option, off by default. When an external display is connected, the built-in one switches off on its own.
It only fires on the transition where a display is newly present — a standing check would switch the display off again right after you turned it on by hand.
Ad-hoc signed and not notarized: right-click → Open on first launch, or xattr -dr com.apple.quarantine /Applications/DisplayToggle.app. Requires macOS 13+.
DisplayToggle 1.7
The recovery fix from 1.6 is confirmed working on a machine that previously could not recover from an unplug at all.
Functionally identical to 1.6 — this release only adds a diagnostic improvement and updated documentation.
Verified behaviour
| Machine | After unplugging the external display |
|---|---|
| macOS 27.0 / M4 Pro | Built-in display returns immediately — the display IDs are still valid at that moment |
| macOS 26.6.2 / M3 | IDs go invalid at once; recovery goes through sleep/wake and takes about 7 seconds |
On the second kind you will see the screen go dark, the displays sleep, then wake with the built-in display on. That pause is the fix doing its job.
Changed
- The diagnostic log records chip, model and OS version when logging is switched on, not only at launch — people enable it while the app is already running, so the logs coming back had no model line
If your display still doesn't return: close the lid and open it again, then attach ~/Library/Logs/DisplayToggle.log to an issue.
Ad-hoc signed and not notarized: right-click → Open on first launch, or xattr -dr com.apple.quarantine /Applications/DisplayToggle.app. Requires macOS 13+.
DisplayToggle 1.6
1.5's fix never actually ran. This makes it work. If you installed 1.5, update.
What went wrong
The log from an affected machine showed the entire recovery finishing in 0–300ms — while the sleep/wake step alone takes six seconds. The chain was returning early, convinced it had already succeeded.
Success was checked with CGDisplayIsActive(id) != 0. But these boolean_t functions return 0xFFFFFFFF for an invalid ID, not 0 or 1:
CGDisplayIsActive(99999) = 4294967295
Once the display IDs go invalid — exactly the situation recovery exists for — that error code reads as true. The first stage declared success and the step that actually works was never reached.
It also explains why the standalone diagnostic script succeeded where the app failed: the script decided from the enumeration, the app asked this function.
Fixed
- Display state is now decided from the enumeration, never by trusting these functions directly
- Where their return value is used, it tests
== 1instead of!= 0 - ID-based stages are skipped once the built-in display has vanished from the enumeration — they cannot succeed then, and only cost black-screen time
- Recovery returns immediately when the built-in display is present but asleep: that is a closed lid, not a failure
The self-test asserts an ID that doesn't exist is never treated as lit, and prints the raw return value so the trap stays visible.
Ad-hoc signed and not notarized: right-click → Open on first launch, or xattr -dr com.apple.quarantine /Applications/DisplayToggle.app. Requires macOS 13+.
DisplayToggle 1.4
Known issue: some Macs cannot auto-recover after an unplug
Reports so far are concentrated on M3 machines. On those, unplugging the external display while the built-in one is off leaves the screen black, and nothing in userspace brings it back — enabling the built-in ID alone, batch enabling, CGSDetectDisplays, disabling the virtual display and CGRestorePermanentDisplayConfiguration were all measured to fail.
If you hit it: close the lid and open it again. That is a hardware-level display re-enumeration and is the only confirmed fix. Plugging the external display back in also works.
Since recovery can't be promised, this release stops pretending otherwise:
- A one-time notice the first time you turn the built-in display off, explaining the risk and the lid workaround
- The diagnostic log now records chip, model and OS version, so the pattern can be confirmed as more reports come in
- The README documents exactly what was measured to fail
If this affects you, enable 记录诊断日志 / Diagnostic log in the menu, reproduce, and attach ~/Library/Logs/DisplayToggle.log to an issue.
Machines that already recovered correctly are unaffected — nothing changed on that path.
Ad-hoc signed and not notarized: right-click → Open on first launch, or xattr -dr com.apple.quarantine /Applications/DisplayToggle.app. Requires macOS 13+.
DisplayToggle 1.3
Fixes a regression in 1.2 — if you installed 1.2, update.
1.2 put CGSDetectDisplays plus a four second blocking wait at the head of the recovery chain. Recovery only works inside a narrow window (display IDs go invalid moments after the unplug), so blocking ahead of the first enable meant arriving far too late. Machines that had always recovered from an unplug stopped recovering.
1.0 and 1.1 issued the enable immediately, which is why they worked. The chain now does the same: enable goes out with zero delay, and hardware re-detection is only attempted afterwards, once that first attempt has demonstrably missed the window.
The self-test now asserts the recovery path returns in under 1.5s (currently 0.08s) so a blocking step cannot creep back in.
Everything else from 1.2 is kept: the watchdog runs on its own queue independent of the main runloop, the process holds an activity assertion against App Nap, and the opt-in diagnostic log is available from the menu.
Ad-hoc signed and not notarized: right-click → Open on first launch, or xattr -dr com.apple.quarantine /Applications/DisplayToggle.app. Requires macOS 13+.
DisplayToggle 1.1
Fixes a black-screen recovery failure reported on machines other than the one this was developed on. If you use 1.0, please update.
Unplugging the external display could leave you on a black screen
The emergency recovery guessed display IDs by iterating 0..<40. CGDirectDisplayID is opaque — on the development machine it happens to be 1, 2, 3, so the range worked in every local test. Many Macs report values like 69733248, where the batch never touches the built-in display and recovery silently does nothing.
Every display ID the app has seen is now persisted while enumeration still works, and recovery uses that set instead of a guessed range.
Manually turning the display back on reported CGError 1001
CGSCompleteDisplayConfiguration routinely returns kCGErrorIllegalArgument (1001) while the display does come back on. That return code was treated as fatal, which produced a bogus error dialog and aborted a recovery that may well have succeeded. The real display state is polled now; only a genuinely unchanged state counts as a failure.
Clicking "turn on" while already stranded also did nothing at all, because it went through the normal path which cannot see the built-in display in that state. The menu now offers Restore built-in display and routes to the emergency recovery.
Also
- CLI: added the missing virtual-display detection, and declared
argtypesforCGDisplayVendorNumber/CGDisplayModelNumber— large display IDs would otherwise have been passed as signed values - selftest: checks that don't require toggling a display now run on single-screen machines too
Install: download the .dmg, drag to Applications. Ad-hoc signed and not notarized, so right-click → Open on first launch, or:
xattr -dr com.apple.quarantine /Applications/DisplayToggle.app
Requires macOS 13+.
DisplayToggle 1.0
First release.
A menu bar app that turns the Mac's built-in display off while an external monitor stays on, plus a zero-dependency Python CLI.
Install: download the .dmg, drag the app to Applications.
The app is ad-hoc signed and not notarized, so Gatekeeper blocks the first launch. Right-click the app and choose Open, or run:
xattr -dr com.apple.quarantine /Applications/DisplayToggle.app
Requires macOS 13 or later. Verified on macOS 27 (Apple Silicon).
Notable
- Black-screen protection: refuses to turn off the built-in display unless another real display is lit, and restores it automatically when the external display is unplugged
- Correctly ignores the virtual display macOS conjures up when every physical display is gone — the thing that makes naive implementations leave you staring at a black screen
- Warns before turning off the display when it is the main one, and offers to move the main display first