Skip to content

Design: display control surface for smctl #15

Open

Description

@harryisfish

Problem

External displays are part of System Control, but macOS exposes weak hardware control for third-party monitors. Users need scriptable brightness, contrast, volume, input switching, and a reliable way to diagnose why control is unavailable.

BetterDisplay already owns the broad GUI/display-experience space. smctl should not clone BetterDisplay. The useful smctl scope is external-display hardware control plus CLI-first diagnostics.

Research PR: #14
Research doc: docs/research-display-control.md

Proposed scope

Start with a read-only surface:

  • smctl display list --json
  • smctl display status
  • smctl display edid --display <id>
  • smctl display capabilities --display <id>
  • smctl display doctor

Then add safe hardware controls:

  • smctl display brightness get/set --display <id>
  • smctl display contrast get/set --display <id>
  • smctl display volume get/set --display <id>
  • smctl display input list/get/set --display <id>

Non-goals

  • BetterDisplay clone
  • GUI-first display settings app
  • virtual displays
  • PIP/PBP/KVM
  • custom EDID or EDID override
  • HDR/XDR extra brightness
  • display mode/resolution writes in the first implementation
  • silently falling back from hardware brightness to software dimming

Design requirements

  • Enumerate displays with stable identity, not only display name.
  • Combine CoreGraphics state, IOKit/IODisplay identity, EDID hash, and DDC capability results.
  • Report a DDC capability matrix before exposing writes.
  • Distinguish unsupported, blocked, timeout, broken-read, unverified-write, and verified-write states.
  • Map common input names like hdmi1, hdmi2, dp1, dp2, and usb_c, while allowing per-display aliases.
  • Avoid default writes to an ambiguous display when multiple external monitors are connected.
  • Keep software dimming visibly separate from hardware brightness.
  • Decide whether DDC read/write belongs in the CLI process, the daemon, or a split model.

Open questions

  • Should the first implementation be Apple Silicon only, or should Intel be included if DDC transport is portable?
  • Which Swift DDC transport path is most stable on current macOS?
  • How should smctl identify Apple Studio Display, LG UltraFine, built-in panels, and other native/Apple-protocol displays?
  • What should --no-verify mean for displays that apply writes but return unreliable MCCS data?
  • Where should display aliases live: /etc/smctl/config.toml, per-user config, or both?
  • Should compatibility reporting exist, and if so how does it stay default-off and privacy-preserving?

Acceptance criteria for the design pass

  • A command contract for V0 read-only inventory and doctor output.
  • A JSON schema sketch for display identity, EDID, transport, and DDC capability state.
  • A decision on CLI vs daemon ownership for DDC probing and writes.
  • A safety model for write verification and degraded states.
  • A small hardware test matrix that covers direct USB-C/DisplayPort, HDMI, docked monitors, DisplayLink, and at least one unsupported display path.

Activity

  1. leaperone-bot commented on Jun 23, 2026

    @leaperone-bot

    🤖 Leo 自动分析

    技术评估如下:

    结论

    Issue #15 是一个新功能 / 设计阶段请求,不是 bug。目标是为 smctl 增加外接显示器硬件控制面:先做只读 inventory / diagnostics,再逐步开放 DDC/CI 硬件亮度、对比度、音量、输入源切换。

    需求对“设计 pass”来说足够清晰;但还不适合直接进入完整实现,因为 DDC transport、CLI/daemon 边界、显示器身份模型、Apple/native display 分类仍需先定案并用硬件验证。

    当前代码架构观察

    仓库目前是 Swift 6 / macOS 14+ 的 SPM 项目,主要模块:

    • Sources/SMCCore:SMC IOKit 通信、capability probing、verified write。
    • Sources/PolicyEngine:风扇 / 电池策略逻辑。
    • Sources/SMCtlProtocol:XPC DTO / 协议。
    • Sources/SMCtlDaemonCore:root daemon、配置、写入授权、安全恢复。
    • Sources/smctl/main.swift:CLI,使用 swift-argument-parser。
    • Sources/smctld/main.swift:daemon entrypoint。

    目前没有任何 display / DDC / EDID / CoreGraphics 相关实现。docs/research-display-control.md 只存在于 PR #14,尚未合入 main。

    功能范围

    建议拆成两阶段:

    V0:只读面

    实现:

    • smctl display list --json
    • smctl display status
    • smctl display edid --display <id>
    • smctl display capabilities --display <id>
    • smctl display doctor

    重点是建立事实层:

    • CoreGraphics active/online display 状态。
    • IOKit / IODisplay identity。
    • EDID 解析与 hash。
    • transport 推断。
    • DDC capability probe。
    • doctor 诊断失败原因。

    V1:安全写入

    再开放:

    • brightness get/set
    • contrast get/set
    • volume get/set
    • input list/get/set

    对应 MCCS/VCP:

    • 0x10 brightness
    • 0x12 contrast
    • 0x62 volume
    • 0x60 input source

    不应在第一版做:GUI、虚拟屏、HDR/XDR 增亮、EDID override、系统分辨率写入、PIP/PBP/KVM、软件调暗伪装成硬件亮度。

    受影响文件 / 模块

    建议新增而不是塞进 SMCCore,因为显示器控制不是 SMC:

    • Package.swift
      • 新增 DisplayCore target。
      • 链接 CoreGraphics、IOKit,必要时 ApplicationServices。
    • Sources/DisplayCore/
      • DisplayInventory.swift
      • DisplayIdentity.swift
      • EDID.swift
      • IODisplayBridge.swift
      • DDCTransport.swift
      • MCCS.swift
      • DisplayDoctor.swift
    • Sources/smctl/main.swift
      • 新增 Display command group。
    • Sources/SMCtlProtocol/SMCtlProtocol.swift
      • 如果 display 写入或 daemon 代理进入 XPC,需要新增 DTO。
    • Sources/SMCtlDaemonCore/Daemon.swift
      • 如果 daemon 管理 aliases、writes、future policies,需要扩展 config / XPC。
    • docs/design.md
      • 增补 display 子系统设计。
    • docs/research-display-control.md
    • Tests/DisplayCoreTests/
      • EDID 解析、capabilities string 解析、identity selection、DDC 状态分类、write verification mock。
    • Tests/smctlTests/
      • CLI 参数、歧义 display 选择、JSON 输出。

    建议实现路线

    1. 先合入或复用 PR docs: research display control scope #14 研究文档,作为设计输入。
    2. 新增 DisplayCore,先做纯只读 inventory:
      • CGGetOnlineDisplayList
      • CGGetActiveDisplayList
      • bounds / mode / refresh / main / mirrored / active / online
      • IODisplay EDID / vendor / product / serial
    3. 定义稳定 display identity:
      • 首选 edidHash
      • 辅助 vendorID、productID、serial
      • 运行时字段保留 cgDisplayID
      • CLI 生成短 id,但不能只依赖显示器名字。
    4. 接入 DDC 只读 probe:
      • capabilities string
      • VCP get for 0x10/0x12/0x62/0x60
      • 明确状态:supported、unsupported、blocked、timeout、broken-read、unverified
    5. 实现 display doctor:
      • 内置屏 / Apple native display
      • DisplayLink / AirPlay / Sidecar
      • 无 EDID
      • DDC capabilities 失败
      • capability 声称支持但 getvcp 失败
    6. 再进入 V1 写入:
      • 写前必须要求明确 --display
      • 多外接显示器时禁止默认写入
      • 写后回读验证
      • --no-verify 只能输出 degraded / unverified 状态,不能假装成功。

    CLI vs daemon 建议

    建议采用分层 / split model:

    • DisplayCore 放通用实现。
    • V0 只读 inventory / doctor 由 CLI 直接执行。
    • DDC 写入第一版也倾向 CLI 直接执行,但必须复用统一安全模型:明确 display、capability check、write verification、JSON 状态。
    • 不建议第一版把 CoreGraphics inventory 完全放进现有 root LaunchDaemon,因为 LaunchDaemon 可能不处于用户 WindowServer session,显示器枚举结果和用户会话不一定一致。
    • 如果未来要做持久策略、GUI 共享状态、自动输入源切换,再考虑 daemon 或单独 per-user agent。

    如果项目坚持“所有硬件写入必须经 daemon”,需要先验证:

    • root LaunchDaemon 是否稳定枚举当前用户 session displays。
    • DDC IOKit transport 在 daemon 里是否可用。
    • 多用户登录 / fast user switching 下显示器归属如何处理。

    JSON schema 草案方向

    V0 display list --json 应至少包含:

    {
      "id": "dell-u2723qe-a1b2",
      "name": "DELL U2723QE",
      "identity": {
        "vendorID": 1234,
        "productID": 5678,
        "serial": "ABC123",
        "edidHash": "sha256:...",
        "ioRegistryPath": "..."
      },
      "coreGraphics": {
        "cgDisplayID": 1,
        "online": true,
        "active": true,
        "main": false,
        "mirrored": false,
        "bounds": { "x": 0, "y": 0, "width": 3840, "height": 2160 },
        "refreshRate": 60,
        "rotation": 0
      },
      "transport": {
        "kind": "usb_c",
        "confidence": "inferred"
      },
      "ddc": {
        "state": "supported",
        "capabilitiesRead": true,
        "controls": {
          "brightness": { "vcp": "0x10", "read": "ok", "write": "available" },
          "contrast": { "vcp": "0x12", "read": "ok", "write": "available" },
          "volume": { "vcp": "0x62", "read": "unsupported", "write": "unavailable" },
          "input": { "vcp": "0x60", "read": "ok", "write": "available" }
        }
      }
    }

    复杂度评估

    • 设计 pass:中等。
    • V0 read-only inventory + doctor:中等偏大。
    • V1 安全 DDC 写入:大。

    主要难点不在 CLI 命令,而在 macOS DDC transport 稳定性、硬件差异、失败状态建模、身份稳定性、多显示器歧义处理。

    风险 / 未决问题

    1. DDC transport 稳定性:需要确定 Swift 下最稳 IOKit 路径,不能简单照搬第三方代码。
    2. Apple Silicon vs Intel:数据模型应预留 Intel;首批验证可 Apple Silicon first。
    3. Apple Studio Display / LG UltraFine / 内置屏:可能走 Apple/native protocol,不应误报为普通 DDC 显示器。
    4. DisplayLink / AirPlay / Sidecar:应显式标为 unsupported 或 non-DDC path。
    5. --no-verify 语义:只能表示 write sent but unverified,脚本输出必须可区分。
    6. aliases 存放位置:系统级 /etc/smctl/config.toml 适合共享机器策略;per-user config 更适合个人显示器命名。设计需同时预留。
    7. 隐私:兼容性上报如果做,必须默认关闭,只上传去标识化 capability/transport 结果。
    8. 硬件测试矩阵:至少覆盖 USB-C/DP 直连、HDMI 直连、dock、DisplayLink、Apple/native display、一个明确 unsupported path。

    建议下一步

    先把本 issue 收敛为设计产出,不直接实现写入:

    1. 合并/引用 PR docs: research display control scope #14 research doc。
    2. 写一份 docs/design-display-control.md。
    3. 定案 V0 command contract、JSON schema、DDC 状态机、CLI/daemon 边界。
    4. 建立硬件测试矩阵。
    5. 然后再开实现 issue:DisplayCore inventory + smctl display list/status/doctor。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions