Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
*.orig
*.rej
*.bak
# Build artifacts
.build/
.swiftpm/
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
</p>

---
---
> **📢 Fork Notes**
>
> I'm not a developer — just a regular user on a **2017 Intel MacBook Air running macOS 12.7.6**. The original version didn't work on my machine, so I fixed it step by step with the help of Codex (an AI coding assistant). Now it works perfectly on my setup.
>
> Huge thanks to [@Attiv](https://github.com/Attiv) for open-sourcing this! If the original version doesn't work for you either, give this fork a try. I can't help with other technical issues though — I'm just a user who got lucky with AI.
>
> **Tested on:** macOS 12.7.6 · Intel MacBook Air (2017) · External display via Thunderbolt
>

ClamOpen is a tiny menu-bar app that **truly turns off your MacBook's built-in display** (backlight off, the compositor stops rendering to it) while an external monitor is connected — **with the lid open**. The result is identical to clamshell mode, except you keep the webcam, Touch ID, the keyboard, and better airflow.

Expand Down
19 changes: 18 additions & 1 deletion Sources/ClamOpen/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import CoreGraphics

final class AppDelegate: NSObject, NSApplicationDelegate {

Expand All @@ -16,6 +17,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

private var watchdog: Timer?

private var enableLock = false
/// CoreGraphics 显示重配置回调(拔插显示器时即时触发,比 NSNotification 更底层、更早)。
/// 闭包不捕获 self,AppDelegate 通过 userInfo 指针传入。
private let reconfigCallback: CGDisplayReconfigurationCallBack = { _, flags, userInfo in
Expand Down Expand Up @@ -44,6 +46,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
CGDisplayRegisterReconfigurationCallback(
reconfigCallback, Unmanaged.passUnretained(self).toOpaque())

// 监听分布式通知:恢复内置屏.app 发送的通知,触发恢复内置屏
DistributedNotificationCenter.default().addObserver(
self, selector: #selector(handleRestoreNotification),
name: NSNotification.Name("com.clamopen.restore-builtin"),
object: nil)

startWatchdog()
rebuildMenu()
updateIcon()
Expand All @@ -60,6 +68,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
if intentDisabled { controller.enableBuiltin() }
}


@objc private func handleRestoreNotification() {
print("[ClamOpen] Received restore notification")
if intentDisabled {
enable()
}
}
// MARK: - 动作

@objc private func disable() {
Expand All @@ -76,8 +91,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
}

@objc private func enable() {
controller.enableBuiltin()
enableLock = true
intentDisabled = false
controller.enableBuiltin()
enableLock = false
refresh()
}

Expand Down
241 changes: 219 additions & 22 deletions Sources/ClamOpen/DisplayController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,59 @@ import Foundation
///
/// 通过 CoreGraphics 私有符号 `CGSConfigureDisplayEnabled` 真正关闭内置面板
/// (停止渲染 + 关闭背光),效果等同合盖(clamshell),但盖子保持打开。
///
/// 注意:`CGDisplayConfigurationFlags` 是私有 typedef UInt32,SDK 不暴露。
/// 所有涉及配置标志的地方都用 UInt32 代替:
/// 0 = .forSession(默认,不触发完整重配置)
/// 1 = .forceConfiguration(强制 WindowServer 重配显示管线)

final class DisplayController {

// MARK: - 私有 API 签名

/// CGError CGSConfigureDisplayEnabled(CGDisplayConfigRef, CGDirectDisplayID, bool)
typealias ConfigureDisplayEnabledFn =
@convention(c) (CGDisplayConfigRef?, CGDirectDisplayID, Bool) -> CGError

private let configureEnabled: ConfigureDisplayEnabledFn?
/// CGError CGSGetDisplayList(UInt32, CGDirectDisplayID*, UInt32*) — 能拿到被禁用的显示器
typealias GetDisplayListFn =
@convention(c) (UInt32, UnsafeMutablePointer<CGDirectDisplayID>?, UnsafeMutablePointer<UInt32>?) -> CGError

/// CGError CGCompleteDisplayConfiguration(CGDisplayConfigRef, UInt32)
/// UInt32 == CGDisplayConfigurationFlags (0=forSession, 1=forceConfiguration)
typealias CompleteDisplayConfigurationFn =
@convention(c) (CGDisplayConfigRef?, UInt32) -> CGError

/// CGError CGDisplayRestoreDisplayConfiguration(CGDirectDisplayID, UInt32)
typealias RestoreDisplayConfigurationFn =
@convention(c) (CGDirectDisplayID, UInt32) -> CGError

private var configureEnabled: ConfigureDisplayEnabledFn? = nil
private var getDisplayList: GetDisplayListFn? = nil
private var completeDisplayConfiguration: CompleteDisplayConfigurationFn? = nil
private var restoreDisplayConfiguration: RestoreDisplayConfigurationFn? = nil

init() {
// RTLD_DEFAULT (== -2):符号随 CoreGraphics 已载入本进程,直接取即可
let rtldDefault = UnsafeMutableRawPointer(bitPattern: -2)
if let sym = dlsym(rtldDefault, "CGSConfigureDisplayEnabled") {
configureEnabled = unsafeBitCast(sym, to: ConfigureDisplayEnabledFn.self)
} else {
configureEnabled = nil
let rtldDefault: UnsafeMutableRawPointer? = UnsafeMutableRawPointer(bitPattern: -2)

if let s = rtldDefault.flatMap({ dlsym($0, "CGSConfigureDisplayEnabled") }) {
self.configureEnabled = unsafeBitCast(s, to: ConfigureDisplayEnabledFn.self)
}

if let s = rtldDefault.flatMap({ dlsym($0, "CGSGetDisplayList") }) {
self.getDisplayList = unsafeBitCast(s, to: GetDisplayListFn.self)
}

if let s = rtldDefault.flatMap({ dlsym($0, "CGCompleteDisplayConfiguration") }) {
self.completeDisplayConfiguration = unsafeBitCast(s, to: CompleteDisplayConfigurationFn.self)
}

if let s = rtldDefault.flatMap({ dlsym($0, "CGDisplayRestoreDisplayConfiguration") }) {
self.restoreDisplayConfiguration = unsafeBitCast(s, to: RestoreDisplayConfigurationFn.self)
}
}

/// 私有 API 是否可用(理论上所有现代 macOS 都可用)
/// 私有 API 是否可用
var isAPIAvailable: Bool { configureEnabled != nil }

// MARK: - 查询
Expand All @@ -37,10 +71,28 @@ final class DisplayController {
return Array(ids.prefix(Int(count)))
}

/// 获取所有显示器(包括被禁用的),用于恢复场景。
func allDisplays() -> [CGDirectDisplayID] {
guard let getDisplayList else { return onlineDisplays() }
var count: UInt32 = 0
if getDisplayList(0, nil, &count) == .success, count > 0 {
var ids = [CGDirectDisplayID](repeating: 0, count: Int(count))
if getDisplayList(count, &ids, &count) == .success {
return Array(ids.prefix(Int(count)))
}
}
return onlineDisplays()
}

func builtinDisplay() -> CGDirectDisplayID? {
onlineDisplays().first { CGDisplayIsBuiltin($0) != 0 }
}

/// 从 allDisplays 中查找内置屏(包括被禁用的)
func findBuiltinInAll() -> CGDirectDisplayID? {
allDisplays().first { CGDisplayIsBuiltin($0) != 0 }
}

/// 在线的外接显示器(非内置)
func externalDisplays() -> [CGDirectDisplayID] {
onlineDisplays().filter { CGDisplayIsBuiltin($0) == 0 }
Expand All @@ -60,7 +112,7 @@ final class DisplayController {
case ok
case apiMissing
case noBuiltin
case noExternal // 安全拦截:没有外接显示器,拒绝关闭内置(否则全黑无法操作)
case noExternal
case beginFailed(Int32)
case configureFailed(Int32)
case completeFailed(Int32)
Expand All @@ -82,39 +134,184 @@ final class DisplayController {

// MARK: - 操作

/// 关闭内置屏。**仅当存在在线外接显示器时**才会执行,否则返回 `.noExternal`。
/// 关闭内置屏。
/// 两阶段提交:先镜像到外接屏(forceConfiguration),等待生效后再禁用内置屏。
/// 使用 forceConfiguration 确保 WindowServer 完整迁移窗口,防止画中画。
/// 仅当存在在线外接显示器时才会执行,否则返回 .noExternal。
@discardableResult
func disableBuiltin() -> Result {
guard let fn = configureEnabled else { return .apiMissing }
guard let builtin = builtinDisplay() else { return .noBuiltin }
guard hasExternalDisplay() else { return .noExternal }
return apply(fn, display: builtin, enabled: false)
guard let completeFn = completeDisplayConfiguration else { return .apiMissing }

var lastError: Result = .ok

for _ in 0..<3 {
guard let builtin = builtinDisplay() ?? findBuiltinInAll() else { return .noBuiltin }
guard let external = externalDisplays().first else { return .noExternal }

// 阶段 1:镜像到外接屏(forceConfiguration)
var mirrorConfig: CGDisplayConfigRef?
let begin1 = CGBeginDisplayConfiguration(&mirrorConfig)
if begin1 != .success {
lastError = .beginFailed(begin1.rawValue)
Thread.sleep(forTimeInterval: 0.5)
continue
}
let mirrorOK = CGConfigureDisplayMirrorOfDisplay(mirrorConfig, builtin, external) == .success
if !mirrorOK {
print("[ClamOpen] Mirror failed before disable, continuing")
}
let mirrorComplete = completeFn(mirrorConfig, 1) // forceConfiguration
if mirrorComplete != .success {
print("[ClamOpen] Mirror commit failed: CGError \(mirrorComplete.rawValue)")
}
mirrorConfig = nil

// 等待镜像完全生效(macOS 12.x 上需要更长时间)
Thread.sleep(forTimeInterval: 0.5)

// 阶段 2:禁用内置屏(forceConfiguration)
var config: CGDisplayConfigRef?
let begin2 = CGBeginDisplayConfiguration(&config)
if begin2 != .success {
lastError = .beginFailed(begin2.rawValue)
Thread.sleep(forTimeInterval: 0.5)
continue
}

let e = fn(config, builtin, false)
if e != .success {
CGCancelDisplayConfiguration(config)
lastError = .configureFailed(e.rawValue)
print("[ClamOpen] disableBuiltin attempt failed: CGError \(e.rawValue)")
Thread.sleep(forTimeInterval: 0.5)
continue
}

let complete = completeFn(config, 1) // forceConfiguration
if complete == .success {
print("[ClamOpen] disableBuiltin succeeded")
return .ok
}
lastError = .completeFailed(complete.rawValue)
print("[ClamOpen] disableBuiltin complete failed: CGError \(complete.rawValue)")

Thread.sleep(forTimeInterval: 0.5)
}

print("[ClamOpen] disableBuiltin all retries exhausted: \(lastError.message)")
return lastError
}

/// 恢复内置屏

/// 恢复内置屏。带重试机制,最多重试 5 次,每次间隔 0.8 秒。
/// 始终使用 forceConfiguration(1)强制 WindowServer 重配显示管线。
/// 通过 CGSGetDisplayList 查找已被禁用的内置屏(onlineDisplays 找不到它)。
@discardableResult
func enableBuiltin() -> Result {
guard let fn = configureEnabled else { return .apiMissing }
guard let builtin = builtinDisplay() else { return .noBuiltin }
return apply(fn, display: builtin, enabled: true)
guard let completeFn = completeDisplayConfiguration else { return .apiMissing }

let maxRetries = 5

for attempt in 0..<maxRetries {
// 每次重试都重新查找内置屏(display ID 可能在热插拔或休眠唤醒后变化)
let displayId = builtinDisplay() ?? findBuiltinInAll()

if let d = displayId {
let result = applyEnable(fn, completeFn: completeFn, display: d)
if result.isSuccess {
print("[ClamOpen] enableBuiltin succeeded on attempt \(attempt + 1)")
return .ok
}
} else {
print("[ClamOpen] enableBuiltin attempt \(attempt + 1): no builtin display found")
}

if attempt < maxRetries - 1 {
Thread.sleep(forTimeInterval: 0.8)
}
}

// 所有 CGSConfigure 重试都失败后,尝试 CGDisplayRestoreDisplayConfiguration 兜底
if let d = findBuiltinInAll(), let rc = restoreDisplayConfiguration {
if rc(d, 0) == .success {
print("[ClamOpen] enableBuiltin: CGSConfigure all failed, restoreDisplayConfiguration succeeded")
return .ok
}
}

print("[ClamOpen] enableBuiltin all retries exhausted")
return .noBuiltin
}

private func apply(_ fn: ConfigureDisplayEnabledFn,
display: CGDirectDisplayID,
enabled: Bool) -> Result {
/// 单次启用操作,始终使用 forceConfiguration。
private func applyEnable(_ fn: ConfigureDisplayEnabledFn,
completeFn: CompleteDisplayConfigurationFn,
display: CGDirectDisplayID) -> Result {
var config: CGDisplayConfigRef?
let begin = CGBeginDisplayConfiguration(&config)
if begin != .success { return .beginFailed(begin.rawValue) }

let e = fn(config, display, enabled)
// 先取消镜像(如果有),再启用
CGConfigureDisplayMirrorOfDisplay(config, display, kCGNullDirectDisplay)

let e = fn(config, display, true)
if e != .success {
CGCancelDisplayConfiguration(config)
return .configureFailed(e.rawValue)
}

// .forSession:当前登录会话内持久(App 退出后仍生效),注销/重启自动恢复 —— 最安全
let complete = CGCompleteDisplayConfiguration(config, .forSession)
let complete = completeFn(config, 1) // forceConfiguration
if complete != .success { return .completeFailed(complete.rawValue) }
return .ok
}

/// 恢复所有显示器(包括被禁用的),用于唤醒后的全面恢复。
/// 返回成功恢复的数量。
@discardableResult
func enableAllDisplays() -> Int {
guard let fn = configureEnabled else { return 0 }
guard let completeFn = completeDisplayConfiguration else { return 0 }

let displays = allDisplays()
print("[ClamOpen] enableAllDisplays: found \(displays.count) displays")
var restored = 0

for d in displays {
var displayRestored = false
for _ in 0..<3 {
var config: CGDisplayConfigRef?
CGBeginDisplayConfiguration(&config)
CGConfigureDisplayMirrorOfDisplay(config, d, kCGNullDirectDisplay)
let e = fn(config, d, true)
if e == .success {
let c = completeFn(config, 1)
if c == .success {
displayRestored = true
break
}
}
if config != nil { CGCancelDisplayConfiguration(config) }
Thread.sleep(forTimeInterval: 0.5)
}

// CGSConfigure 失败时,尝试 CGDisplayRestoreDisplayConfiguration 兜底
if !displayRestored, let rc = restoreDisplayConfiguration {
if rc(d, 0) == .success {
displayRestored = true
print("[ClamOpen] enableAllDisplays: display \(d) restored via CGDisplayRestoreDisplayConfiguration")
}
}

if displayRestored {
restored += 1
} else {
print("[ClamOpen] enableAllDisplays: display \(d) failed to restore")
}
}

print("[ClamOpen] enableAllDisplays: restored \(restored)/\(displays.count) displays")
return restored
}
}
Loading