Post

2nd Monitor Not Waking Up Fix

This post documents the steps to fix an issue I was having where my second monitor that is attached to my Linux Mint laptop was not coming back on after the laptop screen blanked out. This is not sleep mode or suspend mode. This is something different where the monitor just turns off after a period of inactivity. This is what made it more difficult to troubleshoot this issue. Now my second monitor comes back on when the laptop monitor comes back on every time.

2nd Monitor Not Waking Up Fix

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:

1
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.target and sleep.target hooks never fire — no suspend event occurs
  • /lib/systemd/system-sleep/ scripts never run — same reason
  • Kernel parameters like i915.enable_psr=0 reduce 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

ItemValue
LaptopHP Envy 17-da0xxx
GPUIntel Arc (Meteor Lake-P, device ID 8086:7d55)
Driveri915
Kernel6.13.7-061307-generic
OSLinux Mint 22 Cinnamon
External monitorSceptre F27 27” (DP-3, USB-C/Thunderbolt TC3)
Suspend modeS0 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

1
sudo nano /usr/local/bin/dp3-wake-fix

Full script contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/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

1
nano ~/.config/systemd/user/dp3-wake-fix.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[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:

1
sudo apt install python3-xlib

Copy the script to the system bin and make it executable:

1
sudo install -m 755 ~/dp3-wake-fix/dp3-wake-fix /usr/local/bin/dp3-wake-fix

Create the user service directory if it doesn’t exist:

1
mkdir -p ~/.config/systemd/user

Copy the service file:

1
cp ~/dp3-wake-fix/dp3-wake-fix.service ~/.config/systemd/user/dp3-wake-fix.service

Enable and start the service:

1
2
systemctl --user daemon-reload
systemctl --user enable --now dp3-wake-fix.service

Verify it is running:

1
systemctl --user status dp3-wake-fix.service

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:

1
journalctl --user -u dp3-wake-fix --since "today" --no-pager

Expected output when working correctly:

1
2
3
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:

1
2
3
4
5
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:

1
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=None-1-1:d i915.enable_psr=0"

Management Commands

Check service status:

1
systemctl --user status dp3-wake-fix

Stop the service:

1
systemctl --user stop dp3-wake-fix

Restart the service:

1
systemctl --user restart dp3-wake-fix

Uninstall completely:

1
2
3
systemctl --user disable --now dp3-wake-fix
sudo rm /usr/local/bin/dp3-wake-fix
rm ~/.config/systemd/user/dp3-wake-fix.service
This post is licensed under CC BY 4.0 by the author.