Skip to content

Turn Off Displays can power off DDC/CI monitors and they sometimes cannot wake (requires hard power-cycle) #1213

@q2333gh

Description

@q2333gh

Title

Turn Off Displays can power off DDC/CI monitors and they sometimes cannot wake (requires hard power-cycle)

Summary

Using Twinkle Tray's Turn off displays action can sometimes put external DDC/CI monitors into a power state where they do not wake via mouse/keyboard input. In my testing, a simple Windows software "monitor off" signal is safe/recoverable, but Twinkle Tray's hardware (DDC/CI) power mode path appears to be able to power off monitors too deeply (VCP 0xD6), leading to an unrecoverable state.

Environment

  • Windows: 10.0.28020
  • Twinkle Tray: 1.17.2
  • Source commit examined: 4fce279da9acb6085d42cb66efd93a516c7bdede
  • Displays
    • Internal panel: BOE0A1F (brightness via WMI ✅)
    • External: T270LG / IPS2700 (via DDC/CI (HL) ✅)
    • External: LG HDR 4K / GSM7706 (via DDC/CI (HL) ✅)

What I did / Repro steps

  1. Ensure at least one external monitor is controllable via DDC/CI.
  2. In Twinkle Tray, use the Turn off displays button/hotkey.
  3. Observe that sometimes monitors go to a power state that does not wake on input.

Expected result

Turning off displays should be recoverable by normal input (mouse/keyboard), or at least should not send "deep power off" states by default.

Actual result

Monitors can enter a state where they do not wake on input and appear "dead" until a hard recovery (e.g. monitor power button / unplug-replug / other manual intervention).

Control experiment (safe behavior)

I tested the standard Windows software signal:

  • PostMessage(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2)

In Python/PowerShell, sending this signal is recoverable (wake with input).
However, sending the same message specifically to GetDesktopWindow() often has no effect (likely the desktop window doesn't handle it).

Findings from Twinkle Tray source (possible root cause)

Twinkle Tray's “Turn off displays” is implemented as sleepDisplays(settings.sleepAction, ...) and can run in two modes:

1) Software signal path (SC_MONITORPOWER)

Twinkle Tray uses PowerShell to call user32!PostMessage:

function sleepDisplays(mode = "ps", delayMS = 333) {
  // ...
  if (mode === "ps" || mode === "ps_ddcci") {
    exec(`powershell.exe -NoProfile (Add-Type '[DllImport(\\"user32.dll\\")]^public static extern int PostMessage(int hWnd, int hMsg, int wParam, int lParam);' -Name a -Pas)::PostMessage(0xFFFF,0x0112,0xF170,0x0002)`)
  }
}

This matches the safe control experiment above.

2) Hardware power path (DDC/CI VCP 0xD6)

If sleepAction is "ddcci" or "ps_ddcci", Twinkle Tray sends VCP 0xD6 (Power Mode) to each monitor.
Default settings show:

  sleepAction: "ps",
  // ...
  ddcPowerOffValue: 5,

and the DDC power-off code sends 0xD6 value 4 and/or 5 depending on ddcPowerOffValue:

async function turnOffDisplayDDC(hwid, toggle = false) {
  const offVal = parseInt(settings.ddcPowerOffValue)
  // ...
  if (offVal === 4 || offVal === 6) { /* send VCP 0xD6 value 4 */ }
  if (offVal === 5 || offVal === 6) { /* send VCP 0xD6 value 5 */ }
}

Hypothesis: Some monitors do not reliably wake from VCP 0xD6 value 5 (and possibly 4/6 depending on model), causing the “cannot wake” failure. This aligns with Twinkle Tray's own warning in localization strings that power-state changes may not behave correctly on many monitors.

Request / Suggestions

  • Consider making the software signal the recommended/default “Turn off displays” action, and make the DDC/CI power mode more explicitly opt-in.
  • Consider defaulting ddcPowerOffValue to a safer value (e.g. 4 / Standby) if hardware mode is enabled.
  • Add clearer UI warning / per-monitor capability detection for VCP 0xD6 power state.
  • Optional: provide a "test power-off value" / "restore power state" action to avoid trapping users.

Additional notes

  • I can provide my exact Twinkle Tray settings (sleepAction, ddcPowerOffValue) and affected monitor models if needed.
  • I understand from issues like #426 that the strange 0xD6 behaviour is largely down to specific monitor firmware/DDC/CI implementations rather than Twinkle Tray itself. My request is mainly about adding more safety rails (defaults, warnings, and per‑monitor switches) around that functionality, since Twinkle Tray is often the only DDC/CI tool users interact with.
  • In addition to the explicit “Turn off displays” action, I suspect some automatic DDC/CI writes (e.g. auto‑apply brightness / wake‑restore / power‑event handling) may also be able to trigger problematic power states on certain monitors. Even if these paths are working as designed, having options to fully disable “auto‑DDC on power/brightness events” per‑monitor would help advanced users avoid the worst‑case “unresponsive until power‑cycled” scenarios.

Related reports (same/similar symptoms)

  • Monitor goes black and unresponsive, has to be power cycled #462 (closed): “Monitor goes black and unresponsive, has to be power cycled” — user reports a Dell monitor becomes totally unresponsive after sleep and requires unplugging power; they say it does not happen when Twinkle Tray is closed.
    • Link: https://github.com/xanderfrangos/twinkle-tray/issues/462
  • Bug: Dell monitor not resuming from sleep #720 (open): “Bug: Dell monitor not resuming from sleep” — user suspects Twinkle Tray may be related to a Dell monitor not resuming.
    • Link: https://github.com/xanderfrangos/twinkle-tray/issues/720
  • Broken Powerstate #925 (open): “Broken Powerstate” — power-state control via DDC/CI behaving incorrectly depending on monitor-reported max; user mentions manually setting value 4 via ControlMyMonitor works.
    • Link: https://github.com/xanderfrangos/twinkle-tray/issues/925

Possible implementation / compatibility plan (happy to help with PR)

I know this is a tricky area because a lot of the behaviour is monitor‑firmware‑specific, so I tried to think in terms of backwards‑compatible toggles rather than changing behaviour for everyone. Rough sketch:

  • Option A – “Safer defaults” (minimal change)

    • Keep the existing sleepAction values (ps, ddcci, ps_ddcci), but:
  • Option B – Per‑monitor safety rails (medium change)

    • Extend the existing per‑monitor Troubleshooting / Features UI to add:
      • A “Allow DDC power‑state changes (VCP 0xD6)” toggle per monitor, defaulting to off for new monitors (or off for models not known to behave well).
      • A “Disable auto‑DDC on power/brightness events” toggle per monitor that prevents:
        • Auto‑apply brightness on wake.
        • Any wake‑restore / idle‑restore logic that writes to 0x10 / 0x13 / 0xD6 behind the scenes.
    • In sleepDisplays(...), only call turnOffDisplayDDC(...) for monitors that have this per‑monitor flag enabled.
    • This would let affected users (like me and the Dell cases in Twinkle Tray causing Dell monitor to become unresponsive #567/Bug: Dell monitor not resuming from sleep #720/Monitor goes black and unresponsive, has to be power cycled #462) effectively say: “this particular panel cannot safely handle DDC power‑state writes, but I still want TT for brightness”.
  • Option C – “Safe software‑only mode” preset (small UX feature)

    • Add a simple global toggle or preset like “Use software‑only Turn Off Displays” that:
      • Forces sleepAction = "ps".
      • Disables DDC power‑state writes for all monitors (ignores ddcPowerOffValue and 0xD6 in the Turn Off Displays path).
    • This would be a one‑click escape hatch for users who hit the worst‑case behaviour, without them needing to understand all the DDC options.

If you think any of these directions would be useful, I’d be happy to try to put together a PR draft that matches your preferred approach and coding style.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions