2nd Monitor Not Waking Up Fix
External Monitor Wake Fix — Intel Arc / TC3 Link Training Failure¶
The Problem¶
On laptop (HP Envy 17-da0, Intel Arc Meteor Lake GPU, Linux Mint 22 Cinnamon), the external monitor (Sceptre F27, connected via USB-C/Thunderbolt on DP-3) fails to wake after the screen blanks due to inactivity. The monitor shows a flashing blue light with no picture. The only manual fix is unplugging and replugging the USB-C cable.
The kernel logs this error every time it occurs:
i915 0000:00:02.0: [drm] *ERROR* [CONNECTOR:278:DP-3][ENCODER:277:DDI TC3/PHY TC3][DPRX] Failed to enable link training
This is a known i915 driver bug on Intel Arc (Meteor Lake) hardware — the GPU fails to re-establish the DisplayPort link training handshake after the display wakes from a DPMS blank.
Why Standard Fixes Don't Work¶
The system never truly suspends on AC power. Cinnamon Power Management is set to "Suspend when inactive for: Never." What actually happens is DPMS (Display Power Management Signaling) blanks the screens after 15 minutes of inactivity — the CPU stays fully running.
This means:
systemd suspend.targetandsleep.targethooks never fire — no suspend event occurs/lib/systemd/system-sleep/scripts never run — same reason- Kernel parameters like
i915.enable_psr=0reduce frequency but do not eliminate the error
The Fix¶
A Python daemon called dp3-wake-fix listens for X11 MIT-SCREEN-SAVER extension events. When DPMS blanks the screen, X11 fires a ScreenSaverOn event. When the screen wakes, X11 fires ScreenSaverOff. The daemon catches the wake event, waits 2.5 seconds for the i915 driver to attempt (and fail) link training, then checks if DP-3 has lost its resolution. If broken, it forces xrandr to retrain the link automatically.
This is the same mechanism used by xss-lock and is the correct hook for DPMS events on X11/Cinnamon with S0 idle hardware.
System Details¶
| Item | Value |
|---|---|
| Laptop | HP Envy 17-da0xxx |
| GPU | Intel Arc (Meteor Lake-P, device ID 8086:7d55) |
| Driver | i915 |
| Kernel | 6.13.7-061307-generic |
| OS | Linux Mint 22 Cinnamon |
| External monitor | Sceptre F27 27" (DP-3, USB-C/Thunderbolt TC3) |
| Suspend mode | S0 idle (cat /sys/power/mem_sleep → [s2idle]) |
Files¶
Three files are involved:
/usr/local/bin/dp3-wake-fix— the Python daemon~/.config/systemd/user/dp3-wake-fix.service— systemd user service~/dp3-wake-fix/install.sh— installer (used once, kept for reference)
The Python Daemon¶
/usr/local/bin/dp3-wake-fix
Full script contents:
#!/usr/bin/env python3
"""
dp3-wake-fix — Fix DP-3 external monitor after DPMS wake on Intel Arc / TC3 port.
Mechanism: Listens for X11 MIT-SCREEN-SAVER ScreenSaverNotify events. When the
display wakes from a DPMS blank (state=ScreenSaverOff), it waits briefly for the
i915 driver to attempt (and fail) link training, then forces xrandr to retrain the
link by reapplying the desired mode. No polling, no systemd suspend hooks.
Requires: python3-xlib (sudo apt install python3-xlib)
Run as: user mark, inside X session (DISPLAY=:0)
"""
import logging
import os
import subprocess
import sys
import time
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
DISPLAY_ENV = ":0"
XAUTHORITY = "/home/mark/.Xauthority"
DP3_OUTPUT = "DP-3"
DP3_MODE = "3840x2160"
DP3_RATE = "100"
DP3_POS = "0x0"
WAKE_DELAY = 2.5 # seconds after wake before checking (let driver fail first)
# MIT-SCREEN-SAVER state constants (from Xss.h)
SS_OFF = 0 # ScreenSaverOff — display woke
SS_ON = 1 # ScreenSaverOn — display blanked
SS_NOTIFY_MASK = 1
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-7s %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
log = logging.getLogger("dp3-wake-fix")
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def xenv() -> dict:
env = os.environ.copy()
env["DISPLAY"] = DISPLAY_ENV
env["XAUTHORITY"] = XAUTHORITY
return env
def dp3_is_broken() -> bool:
"""
Return True when DP-3 is connected but has no active resolution
(the 'connected' line lacks a geometry like '3840x2160+0+0').
"""
try:
result = subprocess.run(
["xrandr", "--query"],
capture_output=True, text=True, env=xenv(), timeout=5,
)
for line in result.stdout.splitlines():
if line.startswith(f"{DP3_OUTPUT} connected"):
broken = DP3_MODE not in line
log.debug("xrandr line: %s → broken=%s", line.strip(), broken)
return broken
log.debug("DP-3 not found in xrandr output (may be fully disconnected)")
return False
except Exception as exc:
log.warning("xrandr query failed: %s", exc)
return False
def fix_dp3() -> None:
cmd = [
"xrandr",
"--output", DP3_OUTPUT,
"--mode", DP3_MODE,
"--rate", DP3_RATE,
"--pos", DP3_POS,
]
log.info("Applying fix: %s", " ".join(cmd))
try:
result = subprocess.run(
cmd, capture_output=True, text=True, env=xenv(), timeout=10,
)
if result.returncode == 0:
log.info("Fix applied successfully.")
else:
log.error("xrandr failed (rc=%d): %s", result.returncode, result.stderr.strip())
except Exception as exc:
log.error("xrandr exception: %s", exc)
# ---------------------------------------------------------------------------
# Main event loop
# ---------------------------------------------------------------------------
def main() -> None:
try:
from Xlib import display as xdisplay
from Xlib.ext import screensaver
except ImportError:
log.error("python3-xlib not installed. Run: sudo apt install python3-xlib")
sys.exit(1)
os.environ["DISPLAY"] = DISPLAY_ENV
os.environ["XAUTHORITY"] = XAUTHORITY
try:
d = xdisplay.Display(DISPLAY_ENV)
except Exception as exc:
log.error("Cannot connect to X display %s: %s", DISPLAY_ENV, exc)
sys.exit(1)
ext = d.query_extension("MIT-SCREEN-SAVER")
if ext is None:
log.error("MIT-SCREEN-SAVER extension not available on %s", DISPLAY_ENV)
sys.exit(1)
event_base = ext.first_event
log.info("MIT-SCREEN-SAVER available (event base %d)", event_base)
root = d.screen().root
screensaver.select_input(root, SS_NOTIFY_MASK)
d.flush()
log.info("Subscribed to DPMS/ScreenSaver events on %s. Waiting...", DISPLAY_ENV)
while True:
try:
event = d.next_event()
except KeyboardInterrupt:
log.info("Interrupted — exiting.")
break
except Exception as exc:
log.error("X event error: %s — retrying in 5s", exc)
time.sleep(5)
continue
if event.type != event_base:
continue
state = getattr(event, "state", None)
if state == SS_ON:
log.info("Display blanked (DPMS on).")
elif state == SS_OFF:
log.info("Display woke (DPMS off) — waiting %.1fs for driver...", WAKE_DELAY)
time.sleep(WAKE_DELAY)
if dp3_is_broken():
log.info("DP-3 in broken state — fixing.")
fix_dp3()
else:
log.info("DP-3 looks fine — no action needed.")
else:
log.debug("ScreenSaverNotify state=%s (ignored)", state)
if __name__ == "__main__":
main()
The systemd User Service¶
~/.config/systemd/user/dp3-wake-fix.service
[Unit]
Description=DP-3 external monitor wake fix (Intel Arc TC3 link training)
Documentation=https://www.x.org/releases/X11R7.7/doc/xextproto/dpms.html
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/local/bin/dp3-wake-fix
Restart=on-failure
RestartSec=5s
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/mark/.Xauthority
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=graphical-session.target
Installation Steps¶
If reinstalling from scratch:
Install the dependency:
Copy the script to the system bin and make it executable:
Create the user service directory if it doesn't exist:
Copy the service file:
Enable and start the service:
Verify it is running:
You should see active (running) and the line Subscribed to DPMS/ScreenSaver events on :0. Waiting...
Verifying It Works¶
After the screen blanks and wakes, check the log:
Expected output when working correctly:
Display blanked (DPMS on).
Display woke (DPMS off) — waiting 2.5s for driver...
DP-3 looks fine — no action needed.
Or if the fix was applied:
Display blanked (DPMS on).
Display woke (DPMS off) — waiting 2.5s for driver...
DP-3 in broken state — fixing.
Applying fix: xrandr --output DP-3 --mode 3840x2160 --rate 100 --pos 0x0
Fix applied successfully.
Other Changes Made During Troubleshooting¶
i915.enable_psr=0 was added to GRUB as a kernel parameter. It did not fully resolve the issue but is harmless and left in place.
Current GRUB line in /etc/default/grub:
Management Commands¶
Check service status:
Stop the service:
Restart the service:
Uninstall completely: