feat: HDMI refresh boost per display, with a Saved displays screen

Rates are saved per display (EDID ID) and loaded by the hotplug script
for whichever display is connected. The item is on only while the
connected display is boosted; unticking removes just that one. The app
lists the saved displays and removes any of them (hdmi-forget).
This commit is contained in:
theupriser committed 2026-09-26 16:13:45 +02:00
1 parent 36d0a9d780
commit 4cc87aaea6
8 files changed
+293 -107

No files matched your search

+10 -7
View File
@@ -186,13 +186,16 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
<output>=<w>x<h>:<rates>`; without `--hdmi` the backend refuses it. debugfs is root-only <output>=<w>x<h>:<rates>`; without `--hdmi` the backend refuses it. debugfs is root-only
(glob it under sudo), and `edid_override` takes exactly `reset` with no (glob it under sudo), and `edid_override` takes exactly `reset` with no
newline. Build the EDID from the DDC read, not from sysfs: a live override newline. Build the EDID from the DDC read, not from sysfs: a live override
replaces the kernel's copy. The override is never on the kernel command replaces the kernel's copy. EDIDs are saved per display
line (that applied it to any display on the port): `steamify-edid-hotplug` (`steamify-<id>.bin`, id = EDID bytes 8-17, listed in
(boot unit + udev drm hotplug rule) loads it only while the display whose `/etc/steamify/hdmi-edid.conf`), never on the kernel command line (that
DDC ID is in `/etc/steamify/hdmi-edid.conf` is connected, else resets. applied to any display on the port): `steamify-edid-hotplug` (boot unit +
It records the state in `/run/steamify-edid` before its own udev drm hotplug rule) loads the connected display's file, else resets. It
`trigger_hotplug`, whose event runs it again. Pre-2.1.0 command-line records what's loaded in `/run/steamify-edid` before its own
setups are removed by `hdmi_remove_boot_param`. `trigger_hotplug`, whose event runs it again. `hdmi_status` is on only
while the connected display is boosted; disable forgets only that display
(`kpin_disable` forgets all). The app manages the list (`hdmi-forget`,
`hdmiDisplays` in status). Pre-2.1.0 setups are moved by `hdmi_migrate`.
- Steam Machine CEC driver (`cec_driver_enable`, `lib/cec.sh`): mainline - Steam Machine CEC driver (`cec_driver_enable`, `lib/cec.sh`): mainline
`cros_ec_cec` lacks Fremont, so HDMI-CEC builds Valve's copy (evlaV `cros_ec_cec` lacks Fremont, so HDMI-CEC builds Valve's copy (evlaV
`linux-integration`, pinned commit + SHA-256) with DKMS for every kernel. `linux-integration`, pinned commit + SHA-256) with DKMS for every kernel.
+10 -6
View File
@@ -7,13 +7,17 @@ one per merged pull request.
## 2.1.0 - 2026-09-26 ## 2.1.0 - 2026-09-26
- **feat: HDMI refresh boost only for the display it was set up for** - **feat: HDMI refresh boost per display, with a Saved displays screen**
- The EDID override was on the kernel command line, so any display on that - The EDID override was on the kernel command line, so any display on that
HDMI port got the first display's timings. It's now loaded at boot and HDMI port got the first display's timings. Rates are now saved per
at every hotplug only when the connected display is the one it was display and loaded at boot and at every hotplug for the display that's
tested on (its ID read over DDC); another display, or unplugging, puts connected (its ID read over DDC); a display without saved rates, or
the port back on the display's own EDID. No initramfs or boot loader unplugging, puts the port back on the display's own EDID. No initramfs
change any more. An existing setup is moved over when re-applied (`a`). or boot loader change any more. An existing setup is moved over when
re-applied (`a`).
- The item shows on only while the connected display is boosted, so a new
display can be set up by ticking it; unticking removes only that one.
- The app lists the saved displays (▶ on the item) and removes any of them.
## 2.0.3 - 2026-09-26 ## 2.0.3 - 2026-09-26
+21 -15
View File
@@ -259,24 +259,30 @@ ticked by default, run from the desktop in Konsole):
to each step, lowest first. Each one needs a "y" within 15 s to each step, lowest first. Each one needs a "y" within 15 s
(`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back (`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back
and stops. and stops.
5. Keeps only the confirmed steps, for that display only: 5. Saves the confirmed steps for that display: the EDID as
`/usr/lib/firmware/edid/steamify-<connector>.bin`, and the display's ID `/usr/lib/firmware/edid/steamify-<id>.bin`, where `<id>` is the display's
(manufacturer, model, serial, date: EDID bytes 8-17) in manufacturer, model, serial and date (EDID bytes 8-17), and a line in
`/etc/steamify/hdmi-edid.conf`. `steamify-edid.service` (at boot, before `/etc/steamify/hdmi-edid.conf` (id, name, mode, rates). Other saved
the login manager) and a udev rule (`90-steamify-edid.rules`, every drm displays are kept.
hotplug) run `/usr/local/bin/steamify-edid-hotplug`, which reads the
connected display's ID over DDC (the real display, even while an override `steamify-edid.service` (at boot, before the login manager) and a udev rule
is loaded) and loads the file through debugfs only when it matches. (`90-steamify-edid.rules`, every drm hotplug) run
Another display, or no display, resets the port to the display's own `/usr/local/bin/steamify-edid-hotplug`. Per HDMI port it reads the connected
EDID, so a different monitor never gets the boosted timings. The state display's ID over DDC (the real display, even while an override is loaded)
per port is kept in `/run/steamify-edid`, so the hotplug the script and loads that display's saved EDID through debugfs, or resets the port to
triggers itself doesn't loop. the display's own EDID when there is none, or no display. What's loaded per
port is kept in `/run/steamify-edid`, so the hotplug the script triggers
itself doesn't loop.
The item is on when the connected display runs on its saved EDID; with
another display it's off, and ticking it sets that one up. Turning it off
removes the connected display's EDID; the unit and rule go with the last
one. The app's **Saved displays** screen (▶ on the item) lists every saved
display and removes any of them. Unpinning the kernel removes them all.
Versions before 2.1.0 used `drm.edid_firmware=` on the kernel command line Versions before 2.1.0 used `drm.edid_firmware=` on the kernel command line
(and the initramfs), which applied to any display on that port; re-applying (and the initramfs), which applied to any display on that port; re-applying
moves such a setup over to the hotplug script. Re-applying with another saves such a setup per display and removes the parameter. Untick it before removing the
display connected tests that one instead. Turning it off removes the files,
the unit and the rule. Untick it before removing the
kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1. kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1.
## BIOS updates (Steam Machine) ## BIOS updates (Steam Machine)
+16 -3
View File
@@ -14,6 +14,8 @@
# hdmi-options event: every HDMI output, its mode and rates to offer. # hdmi-options event: every HDMI output, its mode and rates to offer.
# steamify.sh --backend hdmi-try <connector> <w> <h> <hz> [<rate>...] # steamify.sh --backend hdmi-try <connector> <w> <h> <hz> [<rate>...]
# steamify.sh --backend hdmi-reset <connector> <w> <h> <hz> # steamify.sh --backend hdmi-reset <connector> <w> <h> <hz>
# steamify.sh --backend hdmi-forget <display id>
# removes a saved display (hdmi-forgotten event); status lists them.
# switch to a rate live (hdmi-tried event), or back to the display's # switch to a rate live (hdmi-tried event), or back to the display's
# own EDID (hdmi-reset event); the app asks in between. # own EDID (hdmi-reset event); the app asks in between.
# steamify.sh --backend bios-prepare | bios-flash # steamify.sh --backend bios-prepare | bios-flash
@@ -68,13 +70,20 @@ backend_status() {
if bios_available; then if bios_available; then
bios="{\"current\":$(json_str "$(bios_current)"),\"newest\":$(json_str "${BIOS_NEWEST:-}"),\"selectable\":$(bios_selectable && echo true || echo false),\"dryRun\":$([[ -n "$BIOS_DRY_RUN" ]] && echo true || echo false)}" bios="{\"current\":$(json_str "$(bios_current)"),\"newest\":$(json_str "${BIOS_NEWEST:-}"),\"selectable\":$(bios_selectable && echo true || echo false),\"dryRun\":$([[ -n "$BIOS_DRY_RUN" ]] && echo true || echo false)}"
fi fi
printf '{"version":%s,"firstRun":%s,"steamMachine":%s,"kernel":%s,"pinnedKernel":%s,"cecDevices":%s,"leds":%s,"bios":%s,"items":[%s]}\n' \ # HDMI refresh boost's saved displays; "active" = connected and boosted.
local hdmi_list="" active hid hname hmode hrates
active=" $(hdmi_active_ids | tr '\n' ' ') "
while IFS=$'\t' read -r hid hname hmode hrates; do
[[ -n "$hid" ]] || continue
hdmi_list+="${hdmi_list:+,}{\"id\":$(json_str "$hid"),\"name\":$(json_str "$hname"),\"mode\":$(json_str "$hmode"),\"rates\":$(json_str "$hrates"),\"active\":$([[ "$active" == *" $hid "* ]] && echo true || echo false)}"
done < <(hdmi_saved)
printf '{"version":%s,"firstRun":%s,"steamMachine":%s,"kernel":%s,"pinnedKernel":%s,"cecDevices":%s,"leds":%s,"bios":%s,"hdmiDisplays":[%s],"items":[%s]}\n' \
"$(json_str "$VERSION")" "$first_run" \ "$(json_str "$VERSION")" "$first_run" \
"$(detect_valve_fremont && echo true || echo false)" \ "$(detect_valve_fremont && echo true || echo false)" \
"$(json_str "$(uname -r)")" "$(json_str "${PINNED_KERNEL_VER:-}")" \ "$(json_str "$(uname -r)")" "$(json_str "${PINNED_KERNEL_VER:-}")" \
"$(json_str "$cec")" \ "$(json_str "$cec")" \
"$(compgen -G '/sys/class/leds/valve-leds*' | wc -l)" \ "$(compgen -G '/sys/class/leds/valve-leds*' | wc -l)" \
"$bios" "$items" "$bios" "$hdmi_list" "$items"
} }
backend_run_component() { backend_run_component() {
@@ -177,7 +186,7 @@ backend_apply() {
} }
backend_hdmi() { backend_hdmi() {
# backend_hdmi options|try|reset [args]: the app's HDMI screen. Events # backend_hdmi options|try|reset|forget [args]: the app's HDMI screen. Events
# only (no "finished"): the app goes on from its own screen. # only (no "finished"): the app goes on from its own screen.
local what="$1" rc local what="$1" rc
shift shift
@@ -192,6 +201,9 @@ backend_hdmi() {
reset) reset)
hdmi_reset "$@" >/dev/null 2>&1 hdmi_reset "$@" >/dev/null 2>&1
backend_event hdmi-reset ;; backend_event hdmi-reset ;;
forget)
[[ "${1:-}" =~ ^[0-9a-f]{20}$ ]] && hdmi_forget "$1" >/dev/null 2>&1; rc=$?
backend_event hdmi-forgotten "\"id\":$(json_str "${1:-}"),\"ok\":$([[ $rc -eq 0 ]] && echo true || echo false)" ;;
esac esac
} }
@@ -204,6 +216,7 @@ backend_main() {
hdmi-options) backend_hdmi options ;; hdmi-options) backend_hdmi options ;;
hdmi-try) shift; backend_hdmi try "$@" ;; hdmi-try) shift; backend_hdmi try "$@" ;;
hdmi-reset) shift; backend_hdmi reset "$@" ;; hdmi-reset) shift; backend_hdmi reset "$@" ;;
hdmi-forget) shift; backend_hdmi forget "$@" ;;
*) err "Usage: steamify.sh --backend status | apply [--reapply] [--boot gamescope|desktop] <id>..."; return 2 ;; *) err "Usage: steamify.sh --backend status | apply [--reapply] [--boot gamescope|desktop] <id>..."; return 2 ;;
esac esac
} }
+128 -69
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# "HDMI refresh boost" menu item, Steam Machine (Fremont) only: finds the # "HDMI refresh boost" menu item, Steam Machine (Fremont) only: finds the
# highest refresh rate the display runs at the desktop resolution over HDMI, # highest refresh rate the display runs at the desktop resolution over HDMI,
# tests it live and keeps it with an EDID override that's only loaded while # tests it live and saves an EDID override per display, loaded only while
# that display is connected (see hdmi_install_hotplug). Two things keep displays below what they can do: # that display is connected (see hdmi_install_hotplug). Two things keep displays below what they can do:
# - Monitors put their fast modes in an extra EDID block (HDMI Forum EEODB); # - Monitors put their fast modes in an extra EDID block (HDMI Forum EEODB);
# the pinned kernel (7.1.6) only reads the first extension. Newer kernels # the pinned kernel (7.1.6) only reads the first extension. Newer kernels
@@ -16,8 +16,11 @@
HDMI_FW_DIR=/usr/lib/firmware/edid HDMI_FW_DIR=/usr/lib/firmware/edid
# Before 2.1.0 the override was on the kernel command line; only removed now. # Before 2.1.0 the override was on the kernel command line; only removed now.
HDMI_INITRAMFS_CONF=/etc/mkinitcpio.conf.d/90-steamify-edid.conf HDMI_INITRAMFS_CONF=/etc/mkinitcpio.conf.d/90-steamify-edid.conf
# "<output> <display id>" per line: which display each EDID file is for. # The saved displays, one per line: <id> TAB <name> TAB <w>x<h> TAB <rates>.
# Their EDIDs are $HDMI_FW_DIR/steamify-<id>.bin.
HDMI_MAP=/etc/steamify/hdmi-edid.conf HDMI_MAP=/etc/steamify/hdmi-edid.conf
# Per output, the ID of the display whose EDID is loaded, or "reset".
HDMI_RUN=/run/steamify-edid
HDMI_HOTPLUG=/usr/local/bin/steamify-edid-hotplug HDMI_HOTPLUG=/usr/local/bin/steamify-edid-hotplug
HDMI_UNIT_NAME=steamify-edid.service HDMI_UNIT_NAME=steamify-edid.service
HDMI_UNIT="/etc/systemd/system/$HDMI_UNIT_NAME" HDMI_UNIT="/etc/systemd/system/$HDMI_UNIT_NAME"
@@ -55,13 +58,25 @@ hdmi_cmdline_param() {
hdmi_available() { hdmi_available() {
# Stays available while on, so it can be turned off after the pin is gone. # Stays available while on, so it can be turned off after the pin is gone.
detect_valve_fremont || return 1 detect_valve_fremont || return 1
hdmi_status || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; } # Saved displays too, so they can be removed without the pin.
hdmi_status || [[ -n "$(hdmi_saved)" ]] || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; }
} }
hdmi_status() { hdmi_status() {
# The command line: set up by an older version, re-applying moves it over. # On = the connected display runs on its saved EDID. Another display on
{ [[ -f "$HDMI_UNIT" ]] || [[ -n "$(hdmi_cmdline_param)" ]]; } && # the port is off, so ticking it sets that one up. The command line: set
compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null # up by an older version, re-applying moves it over.
[[ -n "$(hdmi_active_ids)" ]] ||
{ [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; }
}
hdmi_saved() { [[ -f "$HDMI_MAP" ]] && grep -v '^#' "$HDMI_MAP"; }
hdmi_active_ids() {
# IDs of the connected displays whose saved EDID is loaded.
local f
for f in "$HDMI_RUN"/*; do [[ -f "$f" ]] && grep -vx reset "$f"; done
return 0
} }
hdmi_edid_tool() { hdmi_edid_tool() {
@@ -171,6 +186,11 @@ if cmd == 'blocks':
print(nblocks(load(args[0]))) print(nblocks(load(args[0])))
elif cmd == 'id': elif cmd == 'id':
print(load(args[0])[8:18].hex()) print(load(args[0])[8:18].hex())
elif cmd == 'name':
e = load(args[0])
for o in range(54, 126, 18):
if e[o:o + 3] == b'\0\0\0' and e[o + 3] == 0xfc:
print(e[o + 5:o + 18].split(b'\n')[0].decode('ascii', 'replace').strip())
elif cmd == 'plan': elif cmd == 'plan':
e = load(args[0]); w, h, cur = int(args[1]), int(args[2]), float(args[3]) e = load(args[0]); w, h, cur = int(args[1]), int(args[2]), float(args[3])
lim, t = tmds_khz(e), shortest(e, w, h) lim, t = tmds_khz(e), shortest(e, w, h)
@@ -351,29 +371,69 @@ hdmi_remove_boot_param() {
esac esac
} }
hdmi_install_hotplug() { hdmi_write_map() {
# The EDID file of an output is only loaded while the display it was # hdmi_write_map <lines>: the saved displays list.
# tested on is connected: a udev rule runs the script at every hotplug sudo mkdir -p "$(dirname "$HDMI_MAP")"
# (and a unit at boot, before the login manager). The script reads the { printf '# Written by steamify: HDMI refresh boost, one display per line:\n'
# display's ID over DDC, which shows the real display even while an printf '# <id>\t<name>\t<w>x<h>\t<rates>\n'
# override is loaded, and loads the file or resets to the display's own [[ -n "$1" ]] && printf '%s\n' "$1"; } | sudo tee "$HDMI_MAP" >/dev/null
# EDID. Another display on that port gets its own EDID, and unplugging }
# resets it, so the next display never starts on the wrong one.
local f out map="" hdmi_save() {
for f in "$HDMI_FW_DIR"/steamify-*.bin; do # hdmi_save <edid> <w>x<h> <rates>: keep an EDID for the display it was
# built from (block 0 is the display's own, so is its ID), replacing
# what was saved for that display before.
local id name
id="$(hdmi_edid_tool id "$1")" && [[ -n "$id" ]] || return 1
name="$(hdmi_edid_tool name "$1")"
sudo install -Dm644 "$1" "$HDMI_FW_DIR/steamify-$id.bin" || return 1
hdmi_write_map "$(hdmi_saved | awk -F'\t' -v i="$id" '$1 != i'
printf '%s\t%s\t%s\t%s' "$id" "${name:-HDMI display}" "$2" "$3")"
}
hdmi_forget() {
# hdmi_forget <id>|all: remove saved displays; the hotplug script puts
# a connected one back on its own EDID, and goes when none are left.
local rest=""
if [[ "$1" == all ]]; then
sudo rm -f "$HDMI_FW_DIR"/steamify-*.bin
else
rest="$(hdmi_saved | awk -F'\t' -v i="$1" '$1 != i')"
sudo rm -f "$HDMI_FW_DIR/steamify-$1.bin"
fi
if [[ -z "$rest" ]]; then hdmi_remove_hotplug; return; fi
hdmi_write_map "$rest"
sudo systemctl restart "$HDMI_UNIT_NAME"
}
hdmi_migrate() {
# Setups before 2.1.0: steamify-<output>.bin on the kernel command line,
# the display's ID and mode in the state file. Saved per display now.
local f out
local -a smode
for f in "$HDMI_FW_DIR"/steamify-*-*.bin; do
[[ -e "$f" ]] || continue [[ -e "$f" ]] || continue
out="${f##*/steamify-}"; out="${out%.bin}" out="${f##*/steamify-}"; out="${out%.bin}"
map+="$out $(state_get hdmi "$out")"$'\n' read -ra smode <<< "$(state_get hdmi "$out-mode")"
[[ ${#smode[@]} -ge 2 ]] && hdmi_save "$f" "${smode[0]}x${smode[1]}" "${smode[*]:2}"
sudo rm -f "$f"
done done
[[ -n "$map" ]] || return 1 state_clear hdmi
sudo mkdir -p "$(dirname "$HDMI_MAP")" hdmi_remove_boot_param
printf '# Written by steamify: HDMI refresh boost, <output> <display id>.\n%s' "$map" | }
sudo tee "$HDMI_MAP" >/dev/null || return 1
hdmi_install_hotplug() {
# A saved EDID is only loaded while its display is connected: a udev
# rule runs the script at every hotplug (and a unit at boot, before the
# login manager). The script reads the display's ID over DDC, which
# shows the real display even while an override is loaded, and loads
# that display's file or resets to its own EDID. Unplugging resets too,
# so the next display never starts on the wrong one.
sudo tee "$HDMI_HOTPLUG" > /dev/null << 'EOF' sudo tee "$HDMI_HOTPLUG" > /dev/null << 'EOF'
#!/bin/bash #!/bin/bash
# Steamify CachyOS, HDMI refresh boost: load an output's EDID override only # Steamify CachyOS, HDMI refresh boost: load a display's saved EDID only
# while the display it was made for is connected. Runs as root (debugfs). # while it is connected. Runs as root (debugfs).
MAP=/etc/steamify/hdmi-edid.conf FW=/usr/lib/firmware/edid RUN=/run/steamify-edid FW=/usr/lib/firmware/edid RUN=/run/steamify-edid
modprobe i2c-dev 2>/dev/null modprobe i2c-dev 2>/dev/null
mkdir -p "$RUN" mkdir -p "$RUN"
# Hotplug events come in bursts, and the link needs a moment before DDC works. # Hotplug events come in bursts, and the link needs a moment before DDC works.
@@ -381,9 +441,7 @@ sleep 1
for c in /sys/class/drm/card*-HDMI-A-*; do for c in /sys/class/drm/card*-HDMI-A-*; do
[ -e "$c" ] || continue [ -e "$c" ] || continue
out="${c##*/}"; out="${out#card*-}" out="${c##*/}"; out="${out#card*-}"
want="$(awk -v o="$out" '$1 == o { print $2 }' "$MAP")" new=reset id=""
[ -n "$want" ] && [ -f "$FW/steamify-$out.bin" ] || continue
new=reset
if [ "$(cat "$c/status")" = connected ]; then if [ "$(cat "$c/status")" = connected ]; then
bus="$(basename "$(readlink -f "$c/ddc")")" bus="$(basename "$(readlink -f "$c/ddc")")"
for i in 1 2 3 4 5; do for i in 1 2 3 4 5; do
@@ -392,13 +450,13 @@ for c in /sys/class/drm/card*-HDMI-A-*; do
[ ${#id} -eq 36 ] && break [ ${#id} -eq 36 ] && break
sleep 1 sleep 1
done done
[ "${id:16}" = "$want" ] && new=override [ ${#id} -eq 36 ] && [ -f "$FW/steamify-${id:16}.bin" ] && new="${id:16}"
fi fi
[ "$(cat "$RUN/$out" 2>/dev/null || echo reset)" = "$new" ] && continue [ "$(cat "$RUN/$out" 2>/dev/null || echo reset)" = "$new" ] && continue
for d in /sys/kernel/debug/dri/*/"$out"; do [ -e "$d/edid_override" ] && break; done for d in /sys/kernel/debug/dri/*/"$out"; do [ -e "$d/edid_override" ] && break; done
[ -e "$d/edid_override" ] || continue [ -e "$d/edid_override" ] || continue
if [ "$new" = override ]; then cat "$FW/steamify-$out.bin" > "$d/edid_override" if [ "$new" = reset ]; then printf reset > "$d/edid_override"
else printf reset > "$d/edid_override"; fi else cat "$FW/steamify-$new.bin" > "$d/edid_override"; fi
# Before the hotplug below, whose own event runs this again. # Before the hotplug below, whose own event runs this again.
echo "$new" > "$RUN/$out" echo "$new" > "$RUN/$out"
echo 1 > "$d/trigger_hotplug" echo 1 > "$d/trigger_hotplug"
@@ -432,6 +490,17 @@ EOF
sudo systemctl restart "$HDMI_UNIT_NAME" sudo systemctl restart "$HDMI_UNIT_NAME"
} }
hdmi_remove_hotplug() {
local conn
sudo systemctl disable "$HDMI_UNIT_NAME" >/dev/null 2>&1
sudo rm -f "$HDMI_UDEV_RULE" "$HDMI_UNIT" "$HDMI_HOTPLUG" "$HDMI_MAP"
sudo rm -rf "$HDMI_RUN"
sudo systemctl daemon-reload
sudo udevadm control --reload
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done
return 0
}
hdmi_tune() { hdmi_tune() {
# hdmi_tune <card-connector> <tmpdir>: find and confirm the highest rate # hdmi_tune <card-connector> <tmpdir>: find and confirm the highest rate
# at the desktop resolution. Sets HDMI_RESULT to "<w> <h> <hz>..." (the # at the desktop resolution. Sets HDMI_RESULT to "<w> <h> <hz>..." (the
@@ -570,6 +639,16 @@ hdmi_choose() {
hdmi_enable() { hdmi_enable() {
local -a HDMI_RESULTS=() local -a HDMI_RESULTS=()
if [[ -n "$(hdmi_cmdline_param)" ]]; then
info "Moving the EDID override off the kernel command line, so it only applies to its display..."
hdmi_migrate && hdmi_install_hotplug || { err "Moving it failed."; return 1; }
ok "HDMI refresh boost now only applies to the display it was set up for."
return 0
fi
if hdmi_status; then
ok "Already set up for the connected display. Untick and tick it again to re-test."
return 0
fi
if [[ "${BACKEND:-false}" == true && ${#HDMI_CHOICE[@]} -eq 0 ]]; then if [[ "${BACKEND:-false}" == true && ${#HDMI_CHOICE[@]} -eq 0 ]]; then
err "HDMI refresh boost needs you at the screen: pick and test the rates in the app first." err "HDMI refresh boost needs you at the screen: pick and test the rates in the app first."
return 1 return 1
@@ -578,28 +657,10 @@ hdmi_enable() {
err "Run this from the Plasma desktop: the test switches the display mode through KDE." err "Run this from the Plasma desktop: the test switches the display mode through KDE."
return 1 return 1
fi fi
if hdmi_status; then
local conn stale=false
for conn in $(hdmi_connectors); do
[[ -f "$HDMI_FW_DIR/steamify-${conn#card*-}.bin" ]] || continue
[[ "$(state_get hdmi "${conn#card*-}")" == "$(hdmi_edid_tool id "/sys/class/drm/$conn/edid")" ]] || stale=true
done
if [[ "$stale" == false && -n "$(hdmi_cmdline_param)" ]]; then
info "Moving the EDID override off the kernel command line, so it only applies to this display..."
hdmi_remove_boot_param && hdmi_install_hotplug || { err "Moving it failed."; return 1; }
ok "HDMI refresh boost now only applies to the display it was set up for."
return 0
fi
if [[ "$stale" == false ]]; then
ok "Already set up for the connected display(s). Untick and tick it again to re-test."
return 0
fi
warn "Set up for another display; setting up the one connected now."
hdmi_disable || return 1
fi
sudo pacman -S --needed --noconfirm i2c-tools python >/dev/null 2>&1 || { err "Installing i2c-tools failed."; return 1; } sudo pacman -S --needed --noconfirm i2c-tools python >/dev/null 2>&1 || { err "Installing i2c-tools failed."; return 1; }
local tmp conn out files=() rc=0 res local tmp conn out res saved=false rc=0
local -a smode
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
if [[ ${#HDMI_CHOICE[@]} -gt 0 ]]; then if [[ ${#HDMI_CHOICE[@]} -gt 0 ]]; then
# Picked and tested in the app. # Picked and tested in the app.
@@ -611,34 +672,32 @@ hdmi_enable() {
done done
fi fi
for res in "${HDMI_RESULTS[@]}"; do for res in "${HDMI_RESULTS[@]}"; do
out="${res%%|*}" out="${res%%|*}"; read -ra smode <<< "${res#*|}"
sudo install -Dm644 "$tmp/$out.final" "$HDMI_FW_DIR/steamify-$out.bin" || { rc=1; continue; } hdmi_save "$tmp/$out.final" "${smode[0]}x${smode[1]}" "${smode[*]:2}" && saved=true || rc=1
state_set hdmi "$out" "$(hdmi_edid_tool id "$tmp/$out.orig")"
state_set hdmi "$out-mode" "${res#*|}"
files+=("$HDMI_FW_DIR/steamify-$out.bin")
done done
rm -rf "$tmp" "$HDMI_CACHE" rm -rf "$tmp" "$HDMI_CACHE"
[[ ${#files[@]} -gt 0 ]] || return $rc [[ "$saved" == true ]] || return $rc
if ! hdmi_install_hotplug; then if ! hdmi_install_hotplug; then
# Nothing half-done left behind; the live EDID lasts until a restart. # Nothing half-done left behind; the live EDID lasts until a restart.
hdmi_disable >/dev/null 2>&1 hdmi_remove_hotplug
err "Making it permanent failed; the display is back on its own EDID." err "Making it permanent failed; the display is back on its own EDID."
return 1 return 1
fi fi
ok "HDMI refresh boost is set up for this display; another display gets its own settings." ok "HDMI refresh boost is saved for this display; other displays keep their own settings."
return $rc return $rc
} }
hdmi_disable() { hdmi_disable() {
local f conn # For the connected display; other saved displays stay (the app lists
sudo systemctl disable "$HDMI_UNIT_NAME" >/dev/null 2>&1 # them). Without the pinned kernel none may stay: newer kernels read the
sudo rm -f "$HDMI_UDEV_RULE" "$HDMI_UNIT" "$HDMI_HOTPLUG" "$HDMI_MAP" # EDID themselves.
sudo rm -rf /run/steamify-edid local id
sudo systemctl daemon-reload if [[ -n "$(hdmi_cmdline_param)" ]]; then
sudo udevadm control --reload hdmi_forget all; hdmi_remove_boot_param || return 1; state_clear hdmi
for f in "$HDMI_FW_DIR"/steamify-*.bin; do [[ -e "$f" ]] && sudo rm -f "$f"; done elif [[ "${WANTED[kpin]:-1}" == 0 ]] || ! pinned_kernel_installed; then
hdmi_remove_boot_param || return 1 hdmi_forget all
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done else
state_clear hdmi for id in $(hdmi_active_ids); do hdmi_forget "$id"; done
ok "HDMI refresh boost removed; the display uses its own EDID again." fi
ok "HDMI refresh boost removed for the connected display; it uses its own EDID again."
} }
+6 -1
View File
@@ -163,7 +163,12 @@ kpin_enable() {
fi fi
} }
kpin_disable() { remove_kernel_pin; } kpin_disable() {
# Saved HDMI refresh boost EDIDs are for the pinned kernel only (newer
# ones read the whole EDID), also those of displays not connected now.
[[ -n "$(hdmi_saved)" ]] && hdmi_forget all
remove_kernel_pin
}
install_kernel_headers() { install_kernel_headers() {
# DKMS can only build leds-valve against kernels whose headers are # DKMS can only build leds-valve against kernels whose headers are
+97 -6
View File
@@ -74,7 +74,7 @@ ApplicationWindow {
changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] }, changes: ["linux-cachyos from Steamify's release (signature checked)", "Kept in /var/cache/steamify/kernel", "Added to IgnorePkg"] },
hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI", hdmi: { label: "HDMI refresh boost", hint: "Higher refresh rates over HDMI",
body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.", body: "The pinned kernel keeps many HDMI displays at 60 Hz. Turning this on shows which refresh rates your display can run at the desktop resolution; you pick them, and each one is tried for 15 seconds so you can check the picture before it's installed.",
changes: ["The display's EDID with the rates you confirmed", "In the initramfs, drm.edid_firmware on the kernel command line", "Off: the display uses its own EDID again"] }, changes: ["The display's EDID with the rates you confirmed, saved for that display only", "Loaded whenever that display is connected; other displays keep their own settings", "Off: the connected display uses its own EDID again", "Saved displays: ▶ on this row lists them, to remove one"] },
bios: { label: "Update BIOS", hint: "", bios: { label: "Update BIOS", hint: "",
body: "Installs Valve's newest Steam Machine BIOS, at your own risk. It checks Valve's checksum and asks fwupd whether the file fits this machine, then warns you twice before anything is written.", body: "Installs Valve's newest Steam Machine BIOS, at your own risk. It checks Valve's checksum and asks fwupd whether the file fits this machine, then warns you twice before anything is written.",
changes: ["Valve's fremont-hw-support package (checksum checked)", "fwupd writes the BIOS during the next restart", "Keep the power on until the machine has fully started again"] } changes: ["Valve's fremont-hw-support package (checksum checked)", "fwupd writes the BIOS during the next restart", "Keep the power on until the machine has fully started again"] }
@@ -193,6 +193,31 @@ ApplicationWindow {
readonly property var status: backend.status || ({}) readonly property var status: backend.status || ({})
readonly property var items: status.items || [] readonly property var items: status.items || []
// HDMI refresh boost, saved displays: [{id, name, mode, rates, active}].
readonly property var hdmiSaved: status.hdmiDisplays || []
property int hdmiListSel: 0
property string hdmiConfirm: "" // the display to remove on a second press
function hdmiRowHint() {
var on = hdmiSaved.filter(function (d) { return d.active; });
var others = hdmiSaved.length - on.length;
if (on.length) return "On for " + on[0].name + (others ? " · " + others + " more saved" : "");
if (others) return "Not set up for this display · " + others + " saved";
return "";
}
function openHdmiList() {
if (!hdmiSaved.length) return;
hdmiListSel = 0; hdmiConfirm = ""; hdmiNote = ""; screen = "hdmilist";
}
function hdmiForget() {
var d = hdmiSaved[hdmiListSel];
if (!d) return;
// A second press removes it: nothing to undo but testing again.
if (hdmiConfirm !== d.id) { hdmiConfirm = d.id; return; }
if (!sessionPassword && askPassword("forget")) return;
hdmiForgetNow();
}
function hdmiForgetNow() { screen = "hdmilist"; whenIdle(function () { backend.hdmiForget(sessionPassword, hdmiConfirm); }); }
function hdmiListBack() { hdmiConfirm = ""; sessionPassword = ""; syncFromStatus(); screen = "menu"; }
readonly property var rows: items.filter(function (i) { return !i.parent || want[i.parent]; }) readonly property var rows: items.filter(function (i) { return !i.parent || want[i.parent]; })
function nowOn(id) { function nowOn(id) {
@@ -274,6 +299,7 @@ ApplicationWindow {
var p = pw.text; pw.text = ""; var p = pw.text; pw.text = "";
if (pending === "bios") { sessionPassword = p; biosCheck(); } if (pending === "bios") { sessionPassword = p; biosCheck(); }
else if (pending === "hdmi") { sessionPassword = p; hdmiLoad(); } else if (pending === "hdmi") { sessionPassword = p; hdmiLoad(); }
else if (pending === "forget") { sessionPassword = p; hdmiForgetNow(); }
else startApply(p); else startApply(p);
} }
// --- BIOS: check -> warning 1 -> warning 2 -> flash --- // --- BIOS: check -> warning 1 -> warning 2 -> flash ---
@@ -330,7 +356,12 @@ ApplicationWindow {
else { hdmiPhase = "failed"; hdmiReject(); } else { hdmiPhase = "failed"; hdmiReject(); }
} }
else if (ev.event === "hdmi-reset") { } else if (ev.event === "hdmi-reset") { }
else if (ev.event === "finished" && (screen === "hdmi" || screen === "hdmitest")) { else if (ev.event === "hdmi-forgotten") {
hdmiConfirm = "";
if (!ev.ok) hdmiNote = "Removing the saved display failed.";
backend.refresh();
}
else if (ev.event === "finished" && (screen === "hdmi" || screen === "hdmitest" || screen === "hdmilist")) {
// Only on an error (sudo): the HDMI commands end without one. // Only on an error (sudo): the HDMI commands end without one.
runError = ev.error === "wrong-password" ? "The password didn't work." : "sudo isn't available."; runError = ev.error === "wrong-password" ? "The password didn't work." : "sudo isn't available.";
failed = []; restartNeeded = false; sessionPassword = ""; screen = "done"; failed = []; restartNeeded = false; sessionPassword = ""; screen = "done";
@@ -368,6 +399,7 @@ ApplicationWindow {
} }
else if ((a === "left" || a === "right") && sel < rows.length && rows[sel].kind === "choice") else if ((a === "left" || a === "right") && sel < rows.length && rows[sel].kind === "choice")
boot = a === "left" ? "gamescope" : "desktop"; boot = a === "left" ? "gamescope" : "desktop";
else if (a === "right" && sel < rows.length && rows[sel].id === "hdmi") openHdmiList();
else if (a === "apply") goReview(false); else if (a === "apply") goReview(false);
else if (a === "reapply") goReview(true); else if (a === "reapply") goReview(true);
else if (a === "back") Qt.quit(); else if (a === "back") Qt.quit();
@@ -376,7 +408,7 @@ ApplicationWindow {
else if (a === "back") { reapply = false; screen = "menu"; } else if (a === "back") { reapply = false; screen = "menu"; }
} else if (screen === "password") { } else if (screen === "password") {
if (a === "accept" || a === "apply") submitPassword(); if (a === "accept" || a === "apply") submitPassword();
else if (a === "back") { pw.text = ""; if (pending === "hdmi") hdmiCancel(); else screen = "review"; } else if (a === "back") { pw.text = ""; if (pending === "hdmi") hdmiCancel(); else if (pending === "forget") { hdmiConfirm = ""; screen = "hdmilist"; } else screen = "review"; }
} else if (screen === "bios1") { } else if (screen === "bios1") {
if (a === "left") biosFocus = 0; if (a === "left") biosFocus = 0;
else if (a === "right") biosFocus = 1; else if (a === "right") biosFocus = 1;
@@ -397,6 +429,12 @@ ApplicationWindow {
// A second Back meant for the test (it takes a moment to switch // A second Back meant for the test (it takes a moment to switch
// back) would otherwise leave the whole screen. // back) would otherwise leave the whole screen.
else if (a === "back" && Date.now() - hdmiBackAt > 1500) hdmiCancel(); else if (a === "back" && Date.now() - hdmiBackAt > 1500) hdmiCancel();
} else if (screen === "hdmilist") {
if (backend.busy) return;
if (a === "up") { hdmiListSel = Math.max(0, hdmiListSel - 1); hdmiConfirm = ""; }
else if (a === "down") { hdmiListSel = Math.min(hdmiSaved.length - 1, hdmiListSel + 1); hdmiConfirm = ""; }
else if (a === "accept") hdmiForget();
else if (a === "back") { if (hdmiConfirm) hdmiConfirm = ""; else hdmiListBack(); }
} else if (screen === "hdmitest") { } else if (screen === "hdmitest") {
if (a === "accept" || a === "apply") hdmiKeep(); if (a === "accept" || a === "apply") hdmiKeep();
else if (a === "back") hdmiReject(); else if (a === "back") hdmiReject();
@@ -507,7 +545,7 @@ ApplicationWindow {
anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 14 anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 14
Image { source: iconUrl; width: 40; height: 40; sourceSize: Qt.size(80, 80); anchors.verticalCenter: parent.verticalCenter } Image { source: iconUrl; width: 40; height: 40; sourceSize: Qt.size(80, 80); anchors.verticalCenter: parent.verticalCenter }
Text { text: "Steamify"; color: t.text; font.family: t.display; font.pixelSize: 28; font.weight: Font.Bold; anchors.verticalCenter: parent.verticalCenter } Text { text: "Steamify"; color: t.text; font.family: t.display; font.pixelSize: 28; font.weight: Font.Bold; anchors.verticalCenter: parent.verticalCenter }
Text { text: screen === "menu" ? "v" + (status.version || "") : "/ " + ({review: "Review", password: "Password", applying: biosRun ? "BIOS update" : "Applying", done: "Done", bios1: "BIOS update", bios2: "BIOS update", hdmi: "HDMI refresh boost", hdmitest: "HDMI refresh boost"})[screen] Text { text: screen === "menu" ? "v" + (status.version || "") : "/ " + ({review: "Review", password: "Password", applying: biosRun ? "BIOS update" : "Applying", done: "Done", bios1: "BIOS update", bios2: "BIOS update", hdmi: "HDMI refresh boost", hdmitest: "HDMI refresh boost", hdmilist: "Saved displays"})[screen]
color: t.faint; font.family: screen === "menu" ? t.mono : t.body; font.pixelSize: screen === "menu" ? 13 : 15; anchors.verticalCenter: parent.verticalCenter } color: t.faint; font.family: screen === "menu" ? t.mono : t.body; font.pixelSize: screen === "menu" ? 13 : 15; anchors.verticalCenter: parent.verticalCenter }
} }
Rectangle { Rectangle {
@@ -584,7 +622,7 @@ ApplicationWindow {
width: controls.x - x - 16 width: controls.x - x - 16
opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1 opacity: row.modelData.kind === "action" && !(bios && bios.selectable) ? 0.6 : 1
Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight; width: parent.width } Text { text: label(row.modelData); color: t.textHi; font.family: t.body; font.pixelSize: 17; font.weight: Font.DemiBold; elide: Text.ElideRight; width: parent.width }
Text { text: row.modelData.id === "bios" ? biosHint() : ((texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width } Text { text: row.modelData.id === "bios" ? biosHint() : ((row.modelData.id === "hdmi" && hdmiRowHint()) || (texts[row.modelData.id] && texts[row.modelData.id].hint) || row.modelData.hint); color: t.mute; font.family: t.body; font.pixelSize: 13; elide: Text.ElideRight; width: parent.width }
} }
// Right: every control ends on the same edge // Right: every control ends on the same edge
Item { Item {
@@ -652,6 +690,8 @@ ApplicationWindow {
Text { text: "WHAT IT CHANGES"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8; topPadding: 4 } Text { text: "WHAT IT CHANGES"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8; topPadding: 4 }
Repeater { model: detail.tx.changes || [] Repeater { model: detail.tx.changes || []
Text { required property string modelData; text: "• " + modelData; color: t.soft; font.family: t.body; font.pixelSize: 14; wrapMode: Text.WordWrap; width: detailCol.width } } Text { required property string modelData; text: "• " + modelData; color: t.soft; font.family: t.body; font.pixelSize: 14; wrapMode: Text.WordWrap; width: detailCol.width } }
Btn { visible: !!detail.it && detail.it.id === "hdmi" && hdmiSaved.length > 0; k: "▶"; height: 40
text: "Saved displays (" + hdmiSaved.length + ")"; onClicked: openHdmiList() }
} }
} }
Column { Column {
@@ -682,7 +722,7 @@ ApplicationWindow {
Row { Row {
anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 28 anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 28
Repeater { Repeater {
model: [[g.toggle, "Toggle"], [g.choose, "Choose"], [g.reapply, "Re-apply what's on"], [g.quit, "Quit"]] model: [[g.toggle, "Toggle"], sel < rows.length && rows[sel].id === "hdmi" && hdmiSaved.length ? ["▶", "Saved displays"] : [g.choose, "Choose"], [g.reapply, "Re-apply what's on"], [g.quit, "Quit"]]
Row { required property var modelData; spacing: 8 Row { required property var modelData; spacing: 8
Glyph { k: parent.modelData[0]; anchors.verticalCenter: parent.verticalCenter } Glyph { k: parent.modelData[0]; anchors.verticalCenter: parent.verticalCenter }
Text { text: parent.modelData[1]; color: "#b8c3d1"; font.family: t.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter } } Text { text: parent.modelData[1]; color: "#b8c3d1"; font.family: t.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter } }
@@ -964,6 +1004,57 @@ ApplicationWindow {
} }
} }
// ================= HDMI: saved displays =================
Item {
visible: screen === "hdmilist"
anchors.top: header.bottom; anchors.bottom: parent.bottom; width: parent.width
Text { visible: hdmiSaved.length === 0; anchors.centerIn: parent; text: "No saved displays."; color: t.mute; font.family: t.body; font.pixelSize: 18 }
Column {
visible: hdmiSaved.length > 0
x: 40; y: 28; width: 740; spacing: 12
Text { text: "Saved displays"; color: t.text; font.family: t.display; font.pixelSize: 30; font.weight: Font.DemiBold }
Text { text: "Each display gets its own rates; they're loaded whenever it's connected."; color: t.mute; font.family: t.body; font.pixelSize: 14 }
Repeater {
model: hdmiSaved
Rectangle {
required property var modelData; required property int index
readonly property bool confirm: hdmiConfirm === modelData.id
width: 740; height: 64; radius: 12
color: hdmiListSel === index ? t.cardSel : t.card; border.width: hdmiListSel === index ? 2 : 0; border.color: confirm ? t.bad : t.accent
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: { inputType = "keyboard"; hdmiListSel = parent.index; hdmiForget(); } }
Column { anchors.left: parent.left; anchors.leftMargin: 18; anchors.verticalCenter: parent.verticalCenter; spacing: 2
Text { text: parent.parent.modelData.name; color: t.textHi; font.family: t.body; font.pixelSize: 18; font.weight: Font.DemiBold }
Text { text: parent.parent.modelData.mode.replace("x", "×") + (parent.parent.modelData.rates ? " · " + parent.parent.modelData.rates.split(" ").join(", ") + " Hz" : " · its own modes")
color: t.mute; font.family: t.mono; font.pixelSize: 13 } }
Row { anchors.right: parent.right; anchors.rightMargin: 14; anchors.verticalCenter: parent.verticalCenter; spacing: 12
Chip { anchors.verticalCenter: parent.verticalCenter; text: parent.parent.modelData.active ? "Connected" : "Not connected"
fg: parent.parent.modelData.active ? t.good : "#b8c3d1"; bgc: parent.parent.modelData.active ? t.goodBg : t.line }
Btn { height: 38; anchors.verticalCenter: parent.verticalCenter; focusRing: hdmiListSel === parent.parent.index
k: hdmiListSel === parent.parent.index ? g.ok : ""; color: parent.parent.confirm ? "#8a2c2c" : "#232c38"
text: parent.parent.confirm ? (backend.busy ? "Removing…" : "Press again to remove") : "Remove"
onClicked: { hdmiListSel = parent.parent.index; hdmiForget(); } }
}
}
}
}
Rectangle {
visible: hdmiSaved.length > 0
x: parent.width - 468; y: 28; width: 428; height: 200; radius: 16; color: t.card
Column { anchors.fill: parent; anchors.margins: 24; spacing: 12
Text { text: "REMOVING"; color: t.faint; font.family: t.body; font.pixelSize: 12; font.weight: Font.DemiBold; font.letterSpacing: 0.8 }
Text { width: parent.width; wrapMode: Text.WordWrap; color: t.soft; font.family: t.body; font.pixelSize: 15; lineHeight: 1.35
text: "A removed display uses its own EDID again (right away when it's connected). To get its rates back, set it up again from the menu." }
}
}
Rectangle {
anchors.bottom: parent.bottom; width: parent.width; height: 64; color: t.bar
Rectangle { width: parent.width; height: 1; color: t.line }
Btn { anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; k: g.back; text: "Back to the menu"; onClicked: hdmiListBack() }
Text { anchors.right: parent.right; anchors.rightMargin: 40; anchors.verticalCenter: parent.verticalCenter; visible: !!hdmiNote
text: hdmiNote; color: t.bad; font.family: t.body; font.pixelSize: 14 }
}
}
// ================= HDMI: keep this rate? ================= // ================= HDMI: keep this rate? =================
Item { Item {
visible: screen === "hdmitest" visible: screen === "hdmitest"
+5
View File
@@ -289,6 +289,11 @@ class Backend(QObject):
"""The display's own EDID and the old rate again (hdmi-reset event).""" """The display's own EDID and the old rate again (hdmi-reset event)."""
self._start(["hdmi-reset", conn, str(w), str(h), "%g" % hz], password) self._start(["hdmi-reset", conn, str(w), str(h), "%g" % hz], password)
@Slot(str, str)
def hdmiForget(self, password, display):
"""Removes a saved display (hdmi-forgotten event)."""
self._start(["hdmi-forget", display], password)
def _read(self, p): def _read(self, p):
self._buf += bytes(p.readAllStandardOutput()) self._buf += bytes(p.readAllStandardOutput())
while b"\n" in self._buf: while b"\n" in self._buf: