|
| 1 | +--- |
| 2 | +title: Dealing with dual-GPU display issues on KDE Plasma |
| 3 | +--- |
| 4 | + |
| 5 | +A few months ago I wrote about [[04-jetkvm-tailscale|Getting Tailscale to work on my JetKVM]]. I had been using it as a remote desktop solution by connecting it to the HDMI port on my NVIDIA GPU. It worked fine, but when I started working on [[07-gpu-passthrough-journey|GPU passthrough]], I realized I'd lose all graphics when passing the NVIDIA GPU to a VM. |
| 6 | + |
| 7 | +The solution was to enable the integrated graphics in my BIOS and move the JetKVM to the motherboard's HDMI port. This way I could still access the system remotely (and reach BIOS) even when the NVIDIA GPU was assigned to a VM. |
| 8 | + |
| 9 | +What I didn't anticipate was that having displays connected to both GPUs would cause some strange issues with KDE Plasma. |
| 10 | + |
| 11 | +## My setup |
| 12 | + |
| 13 | +- Fedora Kinoite (Wayland + Plasma 6) |
| 14 | +- NVIDIA RTX 4070 Ti SUPER (discrete GPU) |
| 15 | +- Intel UHD Graphics 770 (integrated GPU) |
| 16 | +- Samsung Odyssey G70NC (4K @ 144 Hz) connected to NVIDIA DisplayPort |
| 17 | +- JetKVM connected to motherboard's HDMI port (Intel iGPU) |
| 18 | + |
| 19 | +The JetKVM lets you choose from a selection of different EDID options. I'm using "DELL D2721H, 1920x1080" which has worked well for my setup. |
| 20 | + |
| 21 | +## The problems |
| 22 | + |
| 23 | +After moving the JetKVM to the Intel iGPU's HDMI port, I started running into two separate issues. |
| 24 | + |
| 25 | +### Problem 1: DisplayPort won't wake up |
| 26 | + |
| 27 | +My primary monitor started having issues after waking from sleep: |
| 28 | + |
| 29 | +- Turn on briefly, then go black and repeat this cycle indefinitely |
| 30 | +- Wake up stuck at 640×480 resolution |
| 31 | +- Lose all refresh rate options above 60 Hz |
| 32 | + |
| 33 | +This was frustrating because nothing about the monitor, cable, or GPU had changed. The first few times it happened I rebooted and everything went back to normal, but I knew something was wrong with the display detection. |
| 34 | + |
| 35 | +#### Troubleshooting |
| 36 | + |
| 37 | +The first thing I checked was whether this was a hardware issue. I looked at `/sys/class/drm/card0-HDMI-A-2/modes` to see what display modes the kernel was detecting: |
| 38 | + |
| 39 | +``` |
| 40 | +3840x2160 |
| 41 | +2560x1440 |
| 42 | +1920x1080 |
| 43 | +... |
| 44 | +640x480 |
| 45 | +``` |
| 46 | + |
| 47 | +When things were working correctly, this file would have entries like `3840x2160@144` and other high refresh rate modes. When broken, all the high refresh modes were just... gone. |
| 48 | + |
| 49 | +This told me the EDID wasn't being read correctly - the driver was falling back to some safe default mode list. |
| 50 | + |
| 51 | +I tried a bunch of things that didn't work: |
| 52 | +- Switching between different resolution/refresh rate combinations in Plasma's settings |
| 53 | +- Unplugging and replugging the monitor |
| 54 | +- Disabling and re-enabling displays in `kscreen-doctor` |
| 55 | + |
| 56 | +Sometimes switching from 4K@60 to 4K@120 would "kick" things back into working, but it was unreliable. |
| 57 | + |
| 58 | +After digging through forums and bug reports, I realized this was a DisplayPort link training issue. When waking from sleep, the DisplayPort connection wasn't renegotiating properly - the EDID read would fail and the driver would fall back to safe modes like 640×480. |
| 59 | + |
| 60 | +I found that forcing a known stable mode would reset the stuck state: |
| 61 | + |
| 62 | +```bash |
| 63 | +kscreen-doctor output.2.mode.3840x2160@60 |
| 64 | +sleep 2 |
| 65 | +kscreen-doctor output.2.mode.3840x2160@143.99 |
| 66 | +``` |
| 67 | + |
| 68 | +This worked as a workaround, but I shouldn't have to run commands manually every time the monitor wakes up. |
| 69 | + |
| 70 | +Eventually I just switched to an HDMI 2.1 cable and the wake issues disappeared entirely. HDMI 2.1 handles the link negotiation much more reliably than DisplayPort on my setup. |
| 71 | + |
| 72 | +### Problem 2: DPI scaling issues |
| 73 | + |
| 74 | +After switching to HDMI, I noticed something else: my Samsung display looked... off. Text seemed larger than it should be, and when I checked the display settings, I realized KDE was scaling everything to match the DPI across both displays. |
| 75 | + |
| 76 | +The JetKVM at 1080p has a much lower DPI than my 4K Samsung. KDE was using the lower DPI as the baseline, which made everything on my Samsung look less sharp than it should. |
| 77 | + |
| 78 | +## The solution |
| 79 | + |
| 80 | +I didn't need both displays active all the time. The JetKVM is only useful when I need remote access to the system or BIOS. The rest of the time, I want just my Samsung display running at full quality. |
| 81 | + |
| 82 | +What I needed was a way to easily toggle the JetKVM display on and off from SSH. |
| 83 | + |
| 84 | +## The script |
| 85 | + |
| 86 | +I put together a script that auto-detects the JetKVM by reading EDID data from `/sys/class/drm/` and uses `kscreen-doctor` to toggle it. |
| 87 | + |
| 88 | +The tricky part is that `kscreen-doctor` needs access to the Wayland session, which isn't normally available over SSH or a TTY. The script handles this by setting up the necessary environment variables to communicate with the running Plasma session. |
| 89 | + |
| 90 | +```bash |
| 91 | +#!/usr/bin/env bash |
| 92 | + |
| 93 | +# jetkvm-display: enable/disable the JetKVM HDMI output in a KDE Wayland session. |
| 94 | +# Usage: jetkvm-display enable |
| 95 | +# jetkvm-display disable |
| 96 | + |
| 97 | +set -euo pipefail |
| 98 | + |
| 99 | +# Unique EDID substring for the JetKVM monitor |
| 100 | +JETKVM_EDID_TAG="DELL D2721H" |
| 101 | + |
| 102 | +export XDG_RUNTIME_DIR="/run/user/$(id -u)" |
| 103 | +export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus" |
| 104 | + |
| 105 | +# Make sure sudo can prompt once if needed (for reading EDID) |
| 106 | +sudo -v >/dev/null 2>&1 || true |
| 107 | + |
| 108 | +# Run kscreen-doctor inside the active KDE Wayland session |
| 109 | +ks_doctor() { |
| 110 | + kscreen-doctor "$@" |
| 111 | +} |
| 112 | + |
| 113 | +# Find the HDMI connector whose EDID contains the JetKVM tag |
| 114 | +find_jetkvm_connector() { |
| 115 | + local f basename connector |
| 116 | + for f in /sys/class/drm/*HDMI-A-*/edid; do |
| 117 | + [ -r "$f" ] || continue |
| 118 | + if sudo strings "$f" 2>/dev/null | grep -q "${JETKVM_EDID_TAG}"; then |
| 119 | + basename="$(basename "$(dirname "$f")")" # e.g. card0-HDMI-A-1 |
| 120 | + connector="${basename#*-}" # strip "card0-" -> HDMI-A-1 |
| 121 | + echo "${connector}" |
| 122 | + return 0 |
| 123 | + fi |
| 124 | + done |
| 125 | + return 1 |
| 126 | +} |
| 127 | + |
| 128 | +# Verify that kscreen-doctor knows about this connector name |
| 129 | +detect_jetkvm_output_name() { |
| 130 | + local connector="$( |
| 131 | + find_jetkvm_connector || { |
| 132 | + echo "jetkvm-display: EDID tag '${JETKVM_EDID_TAG}' not found on any HDMI connector" >&2 |
| 133 | + return 1 |
| 134 | + } |
| 135 | + )" |
| 136 | + |
| 137 | + # Check that kscreen-doctor -o lists an Output with that name |
| 138 | + if ! ks_doctor -o | awk -v conn="$connector" ' |
| 139 | + /Output:/ { |
| 140 | + # "Output: 2 HDMI-A-1 <uuid>" |
| 141 | + name = $3 |
| 142 | + if (name == conn) { |
| 143 | + found = 1 |
| 144 | + } |
| 145 | + } |
| 146 | + END { exit (!found) } |
| 147 | + '; then |
| 148 | + echo "jetkvm-display: found connector '${connector}' for JetKVM, but no matching Output name in kscreen-doctor -o" >&2 |
| 149 | + echo "jetkvm-display: Outputs seen:" >&2 |
| 150 | + ks_doctor -o | grep 'Output:' >&2 || true |
| 151 | + return 1 |
| 152 | + fi |
| 153 | + |
| 154 | + echo "jetkvm-display: JetKVM detected on connector '${connector}'" >&2 |
| 155 | + echo "${connector}" |
| 156 | +} |
| 157 | + |
| 158 | +JET_OUTPUT_NAME="$(detect_jetkvm_output_name || true)" |
| 159 | + |
| 160 | +if [ -z "${JET_OUTPUT_NAME}" ]; then |
| 161 | + echo "jetkvm-display: could not auto-detect JetKVM" >&2 |
| 162 | + exit 1 |
| 163 | +fi |
| 164 | + |
| 165 | +do_enable() { |
| 166 | + ks_doctor "output.${JET_OUTPUT_NAME}.enable" |
| 167 | +} |
| 168 | + |
| 169 | +do_disable() { |
| 170 | + ks_doctor "output.${JET_OUTPUT_NAME}.disable" |
| 171 | +} |
| 172 | + |
| 173 | +case "${1:-}" in |
| 174 | + enable) |
| 175 | + do_enable |
| 176 | + ;; |
| 177 | + disable) |
| 178 | + do_disable |
| 179 | + ;; |
| 180 | + *) |
| 181 | + echo "Usage: $0 {enable|disable}" >&2 |
| 182 | + exit 1 |
| 183 | + ;; |
| 184 | +esac |
| 185 | +``` |
| 186 | + |
| 187 | +I saved this to `~/.local/bin/jetkvm-display` and made it executable. |
| 188 | + |
| 189 | +Now when I need remote access: |
| 190 | + |
| 191 | +```bash |
| 192 | +ssh desktop |
| 193 | +jetkvm-display enable |
| 194 | +# Access the system through JetKVM |
| 195 | +jetkvm-display disable |
| 196 | +``` |
| 197 | + |
| 198 | +The key things this script does: |
| 199 | +- Reads EDID data from `/sys/class/drm/` to find which HDMI connector has the JetKVM (searches for "DELL D2721H") |
| 200 | +- Verifies that `kscreen-doctor` knows about that connector |
| 201 | +- Sets up the necessary environment variables (`XDG_RUNTIME_DIR`, `WAYLAND_DISPLAY`, `DBUS_SESSION_BUS_ADDRESS`) to talk to the Wayland session from SSH |
| 202 | +- Uses `kscreen-doctor` to cleanly enable/disable the display |
| 203 | + |
| 204 | +## Results |
| 205 | + |
| 206 | +With the JetKVM display disabled by default: |
| 207 | +- No wake issues (after switching to HDMI 2.1) |
| 208 | +- No DPI scaling problems |
| 209 | +- Samsung runs at full 4K @ 143.99 Hz |
| 210 | +- I can still enable the JetKVM whenever I need remote access |
| 211 | + |
| 212 | +Switching from DisplayPort to HDMI 2.1 completely resolved the wake issues. Combined with keeping the JetKVM display disabled when not in use, everything has been stable since. |
0 commit comments