mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5e644d71a | ||
|
|
929ecfb349 | ||
|
|
4cc87aaea6 | ||
|
|
36d0a9d780 | ||
|
|
cb6361b49b | ||
|
|
c510f9e2e5 | ||
|
|
05a7d50c3f | ||
|
|
badfd846d8 | ||
|
|
ff364ffd69 | ||
|
|
2b3f1de75a |
No files matched your search
@@ -161,9 +161,14 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
now? [Y/n]" (`quit_prompt`): `n` goes back to the menu, the next `q` asks
|
||||
again. Scripted input that runs out quits without restarting (never
|
||||
restart on EOF: `ask_yn` would take its default).
|
||||
- Steamify shortcut (`launcher`): the icon runs `curl | bash` of
|
||||
`releases/latest/download/steamify.sh` in Konsole, so it's
|
||||
always the newest release. Its icon, `assets/steam-gaming-settings.svg`
|
||||
- Steamify shortcut (`launcher`): "Steamify CachyOS" (desktop and launcher,
|
||||
id `steamify-ui.desktop`, the app's own id) runs `curl | bash` of
|
||||
`releases/latest/download/steamify-app.sh` without a terminal (in Konsole
|
||||
only while PySide6 is missing, for sudo); "Steamify Terminal" (launcher)
|
||||
runs `steamify.sh` in Konsole. Both always the newest release. The entry
|
||||
has `X-Steamify-Shortcut=true`: steamify-app.sh doesn't overwrite it, and
|
||||
disable only removes it then. A pre-2.0.1 shortcut (terminal only) is
|
||||
ticked by `launcher_repair`. Its icon, `assets/steam-gaming-settings.svg`
|
||||
(Valve's GPL-2.0 return icon with a gear), is a release asset too, and is
|
||||
downloaded from there (Steam's icon if that fails). Desktop files are
|
||||
written with their mode already set (`install_executable`): Plasma opens a
|
||||
@@ -181,8 +186,24 @@ 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
|
||||
(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
|
||||
replaces the kernel's copy. The kernel command-line value holds a path, so
|
||||
`sed` uses `|` as its delimiter.
|
||||
replaces the kernel's copy. EDIDs are saved per display
|
||||
(`steamify-<id>.bin`, id = EDID bytes 8-17, listed in
|
||||
`/etc/steamify/hdmi-edid.conf`), never on the kernel command line (that
|
||||
applied to any display on the port): `steamify-edid-hotplug` (boot unit +
|
||||
udev drm hotplug rule) loads the connected display's file, else resets. It
|
||||
records what's loaded in `/run/steamify-edid` before its own
|
||||
`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
|
||||
`cros_ec_cec` lacks Fremont, so HDMI-CEC builds Valve's copy (evlaV
|
||||
`linux-integration`, pinned commit + SHA-256) with DKMS for every kernel.
|
||||
It's patched to register its notifier without a port name when the board
|
||||
has one CEC port: amdgpu registers its HDMI notifier nameless, and the
|
||||
kernel only pairs that with a named lookup ("Port C") when the CEC driver
|
||||
registered first, which never happens since amdgpu loads from the
|
||||
initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`.
|
||||
- `Relogin=true` means a gamescope that fails to start is relaunched in a
|
||||
tight loop; keep that in mind when changing session handling.
|
||||
|
||||
|
||||
@@ -5,6 +5,50 @@ All notable changes, per version and per commit. Versions follow
|
||||
`steamify.sh`. Versions before 0.7.0 were numbered afterwards,
|
||||
one per merged pull request.
|
||||
|
||||
## 2.1.0 - 2026-09-26
|
||||
|
||||
- **feat(ui): Manage button for HDMI refresh boost**
|
||||
- The item's switch is a button: Set up… while no display is saved,
|
||||
Manage after that. Manage lists the saved displays (connected or not),
|
||||
removes one with its Remove button, and sets up the connected display
|
||||
when it has no saved rates yet.
|
||||
- **4cc87aa feat: HDMI refresh boost per display, with a Saved displays screen**
|
||||
- **36d0a9d feat: HDMI refresh boost only for the display it was set up for**
|
||||
- The EDID override was on the kernel command line, so any display on that
|
||||
HDMI port got the first display's timings. Rates are now saved per
|
||||
display and loaded at boot and at every hotplug for the display that's
|
||||
connected (its ID read over DDC); a display without saved rates, or
|
||||
unplugging, puts the port back on the display's own EDID. No initramfs
|
||||
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.
|
||||
|
||||
## 2.0.3 - 2026-09-26
|
||||
|
||||
- **fix: HDMI-CEC on the Steam Machine**
|
||||
- Mainline's `cros_ec_cec` driver doesn't know the Steam Machine, so there
|
||||
was no `/dev/cec0`. HDMI-CEC now builds Valve's version of the driver
|
||||
with DKMS (`steamify-cros-ec-cec`) for every installed kernel, with a fix
|
||||
so it finds amdgpu's HDMI port even though amdgpu loads first. An
|
||||
existing HDMI-CEC install is ticked in the menu to add it.
|
||||
|
||||
## 2.0.2 - 2026-09-26
|
||||
|
||||
- **badfd84 docs: Non-affiliation disclaimer in the README**
|
||||
- States that the project isn't affiliated with Valve or CachyOS, and that
|
||||
Steam and SteamOS are Valve's trademarks.
|
||||
|
||||
## 2.0.1 - 2026-09-25
|
||||
|
||||
- **2b3f1de feat: The Steamify shortcut opens the app**
|
||||
- "Steamify CachyOS" on the desktop and in the launcher starts the newest
|
||||
app (in Konsole the first time, if PySide6 still has to be installed).
|
||||
The terminal menu is "Steamify Terminal" in the launcher. A shortcut from
|
||||
before 2.0.1 is ticked in the menu, so a normal run replaces it.
|
||||
- steamify-app.sh leaves the shortcut's launcher entry alone (it has the
|
||||
same id, `steamify-ui.desktop`, and always starts the newest release).
|
||||
|
||||
## 2.0.0 - 2026-09-25
|
||||
|
||||
- **feat: Steamify app (v2 UI)**
|
||||
|
||||
@@ -25,8 +25,9 @@ Steam Machine.
|
||||
KDE wallet password prompts (e.g. from Brave). Typing a password with a
|
||||
controller is no fun
|
||||
([details](TECHNICAL.md#single-user-mode-sddm-no-locking)).
|
||||
5. **Steamify shortcut** - an icon on the desktop and in the launcher that
|
||||
opens the newest Steamify, so you don't need the install command again.
|
||||
5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the
|
||||
launcher opens the newest Steamify app, so you don't need the install
|
||||
command again. "Steamify Terminal" in the launcher opens the terminal menu.
|
||||
6. **HDMI-CEC** (experimental; off by default, on by default on a Steam
|
||||
Machine) - use Steam with your TV's
|
||||
remote, and the TV turns on and off with the PC. Needs a PC with CEC (like
|
||||
@@ -130,3 +131,11 @@ Contributing (or an AI agent)? Read [AGENTS.md](AGENTS.md).
|
||||
## License
|
||||
|
||||
MIT - do whatever you want with it. See [LICENSE.md](LICENSE.md).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Steamify CachyOS is an unofficial community project. It is not affiliated
|
||||
with, endorsed by, or sponsored by Valve Corporation or the CachyOS project.
|
||||
Steam, SteamOS and the Steam logo are trademarks of Valve Corporation. CachyOS
|
||||
is the name of its respective project. They are used here only to describe
|
||||
what this script works with.
|
||||
+25
-9
@@ -259,16 +259,32 @@ ticked by default, run from the desktop in Konsole):
|
||||
to each step, lowest first. Each one needs a "y" within 15 s
|
||||
(`WIZARD_HDMI_CONFIRM_SECONDS` for tests); anything else switches back
|
||||
and stops.
|
||||
5. Makes it permanent with only the confirmed steps:
|
||||
`/usr/lib/firmware/edid/steamify-<connector>.bin`, in the initramfs via
|
||||
`/etc/mkinitcpio.conf.d/90-steamify-edid.conf` (amdgpu loads from there),
|
||||
and `drm.edid_firmware=<connector>:edid/steamify-<connector>.bin` on the
|
||||
kernel command line: `/etc/default/limine` (`limine-mkinitcpio`),
|
||||
`/etc/sdboot-manage.conf` or `/etc/default/grub`.
|
||||
5. Saves the confirmed steps for that display: the EDID as
|
||||
`/usr/lib/firmware/edid/steamify-<id>.bin`, where `<id>` is the display's
|
||||
manufacturer, model, serial and date (EDID bytes 8-17), and a line in
|
||||
`/etc/steamify/hdmi-edid.conf` (id, name, mode, rates). Other saved
|
||||
displays are kept.
|
||||
|
||||
The display's manufacturer, model and serial are stored; re-applying with
|
||||
another display connected tests that one instead. Turning it off removes
|
||||
the files and the parameter and rebuilds. Untick it before removing the
|
||||
`steamify-edid.service` (at boot, before the login manager) and a udev rule
|
||||
(`90-steamify-edid.rules`, every drm hotplug) run
|
||||
`/usr/local/bin/steamify-edid-hotplug`. Per HDMI port it reads the connected
|
||||
display's ID over DDC (the real display, even while an override is loaded)
|
||||
and loads that display's saved EDID through debugfs, or resets the port to
|
||||
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. In the app the item is a **Set up…** button, and **Manage** once a
|
||||
display is saved: it lists every saved display, removes any of them, and
|
||||
sets up the connected display when it has none. Unpinning the kernel
|
||||
removes them all.
|
||||
|
||||
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
|
||||
saves such a setup per display and removes the parameter. Untick it before removing the
|
||||
kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1.
|
||||
|
||||
## BIOS updates (Steam Machine)
|
||||
|
||||
+16
-3
@@ -14,6 +14,8 @@
|
||||
# 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-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
|
||||
# own EDID (hdmi-reset event); the app asks in between.
|
||||
# steamify.sh --backend bios-prepare | bios-flash
|
||||
@@ -68,13 +70,20 @@ backend_status() {
|
||||
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)}"
|
||||
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" \
|
||||
"$(detect_valve_fremont && echo true || echo false)" \
|
||||
"$(json_str "$(uname -r)")" "$(json_str "${PINNED_KERNEL_VER:-}")" \
|
||||
"$(json_str "$cec")" \
|
||||
"$(compgen -G '/sys/class/leds/valve-leds*' | wc -l)" \
|
||||
"$bios" "$items"
|
||||
"$bios" "$hdmi_list" "$items"
|
||||
}
|
||||
|
||||
backend_run_component() {
|
||||
@@ -177,7 +186,7 @@ backend_apply() {
|
||||
}
|
||||
|
||||
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.
|
||||
local what="$1" rc
|
||||
shift
|
||||
@@ -192,6 +201,9 @@ backend_hdmi() {
|
||||
reset)
|
||||
hdmi_reset "$@" >/dev/null 2>&1
|
||||
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
|
||||
}
|
||||
|
||||
@@ -204,6 +216,7 @@ backend_main() {
|
||||
hdmi-options) backend_hdmi options ;;
|
||||
hdmi-try) shift; backend_hdmi try "$@" ;;
|
||||
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 ;;
|
||||
esac
|
||||
}
|
||||
+77
-5
@@ -3,8 +3,9 @@
|
||||
# volume control (cec-audio-control) and the units that attach USB CEC
|
||||
# adapters (Pulse-Eight, RainShadow) with inputattach. They're only in
|
||||
# Valve's SteamOS (holo) repository, not in CachyOS or the AUR. Works with
|
||||
# any /dev/cec*: a GPU with CEC (e.g. the Steam Machine) or a USB adapter.
|
||||
# On a Steam Machine, steamos-manager configures cecd from Steam's settings.
|
||||
# any /dev/cec*: a GPU with CEC or a USB adapter. On a Steam Machine it also
|
||||
# builds Valve's CEC driver (see cec_driver_enable), and steamos-manager
|
||||
# configures cecd from Steam's settings.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
CEC_PKGS=(cecd cec-audio-control inputattach-cec-units)
|
||||
@@ -16,6 +17,15 @@ CEC_ORDER_DROPIN=/etc/systemd/user/steamos-manager.service.d/10-steamify-after-c
|
||||
# file. A later EnvironmentFile= overrides it.
|
||||
CEC_STEAM_ENV=/etc/steamify/steam-cec.env
|
||||
CEC_STEAM_DROPIN=/etc/systemd/user/steam-launcher.service.d/10-steamify-cec.conf
|
||||
# The Steam Machine's CEC runs through its embedded controller (cros_ec_cec).
|
||||
# Mainline's driver doesn't list Fremont, so there's no /dev/cec0; Valve's
|
||||
# kernel does. We build Valve's copy with DKMS for every kernel, pinned to a
|
||||
# reviewed commit.
|
||||
CEC_DKMS_NAME=steamify-cros-ec-cec
|
||||
CEC_DKMS_VER=1
|
||||
CEC_DKMS_SRC="/usr/src/$CEC_DKMS_NAME-$CEC_DKMS_VER"
|
||||
CEC_DRIVER_URL="https://raw.githubusercontent.com/evlaV/linux-integration/10c8c8800ccd3ae359203b4eefb6479f613b3b8e/drivers/media/cec/platform/cros-ec/cros-ec-cec.c"
|
||||
CEC_DRIVER_SHA256=e89fd4e87fceb32d713ffc59b64794779b3c2b3c012b0e2f2e9f0c40b42f4373
|
||||
|
||||
cec_link_steamos_manager() {
|
||||
# On a Steam Machine: steamos-manager writes cecd's config from Steam's
|
||||
@@ -33,9 +43,69 @@ cec_link_steamos_manager() {
|
||||
}
|
||||
|
||||
cec_installed() { pacman -Q "${CEC_PKGS[@]}" >/dev/null 2>&1; }
|
||||
# On = installed and Steam told to show its CEC settings. An install from
|
||||
# before 1.1.3 lacks the latter; the menu then ticks it (see cec_repair).
|
||||
cec_status() { cec_installed && [[ -f "$CEC_STEAM_DROPIN" ]]; }
|
||||
cec_driver_ok() { ! detect_valve_fremont || [[ -d "$CEC_DKMS_SRC" ]]; }
|
||||
# On = installed, Steam told to show its CEC settings and, on a Steam
|
||||
# Machine, the driver built. An install from before 1.1.3 (settings) or
|
||||
# 2.0.3 (driver) lacks one; the menu then ticks it (see cec_repair).
|
||||
cec_status() { cec_installed && [[ -f "$CEC_STEAM_DROPIN" ]] && cec_driver_ok; }
|
||||
|
||||
cec_reload_driver() {
|
||||
# cecd keeps /dev/cec0 open, so the old module can't be unloaded under it.
|
||||
systemctl --user stop cecd.service 2>/dev/null
|
||||
sudo modprobe -r cros_ec_cec 2>/dev/null
|
||||
sudo modprobe cros_ec_cec 2>/dev/null
|
||||
systemctl --user start cecd.service 2>/dev/null
|
||||
}
|
||||
|
||||
cec_driver_enable() {
|
||||
detect_valve_fremont || return 0
|
||||
local tmp k
|
||||
if ! pacman -Q dkms >/dev/null 2>&1; then
|
||||
sudo pacman -S --needed --noconfirm dkms || { err "Installing dkms failed."; return 1; }
|
||||
fi
|
||||
install_kernel_headers || return 1
|
||||
tmp="$(mktemp -d)"
|
||||
info "Downloading Valve's Steam Machine CEC driver..."
|
||||
if ! curl -fsL "$CEC_DRIVER_URL" -o "$tmp/cros-ec-cec.c" ||
|
||||
! echo "$CEC_DRIVER_SHA256 $tmp/cros-ec-cec.c" | sha256sum -c --quiet -; then
|
||||
rm -rf "$tmp"
|
||||
err "Downloading Valve's CEC driver failed (or its checksum didn't match)."
|
||||
return 1
|
||||
fi
|
||||
# amdgpu registers its HDMI notifier without a port name, which only
|
||||
# matches the driver's named lookup ("Port C") if the driver loaded
|
||||
# first; amdgpu loads from the initramfs, so it never does. With a
|
||||
# single CEC port, look it up by device alone.
|
||||
sed -i 's/cec_notifier_cec_adap_register(hdmi_dev, conns\[port_num\],/cec_notifier_cec_adap_register(hdmi_dev, conns[1] ? conns[port_num] : NULL,/' \
|
||||
"$tmp/cros-ec-cec.c"
|
||||
grep -q 'conns\[1\] ? conns\[port_num\] : NULL' "$tmp/cros-ec-cec.c" ||
|
||||
{ rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; }
|
||||
echo 'obj-m += cros-ec-cec.o' >"$tmp/Makefile"
|
||||
printf '%s\n' "# Written by Steamify: Valve's cros_ec_cec, which knows the Steam Machine." \
|
||||
"PACKAGE_NAME=\"$CEC_DKMS_NAME\"" "PACKAGE_VERSION=\"$CEC_DKMS_VER\"" \
|
||||
'BUILT_MODULE_NAME[0]="cros-ec-cec"' 'DEST_MODULE_LOCATION[0]="/updates/dkms"' \
|
||||
'AUTOINSTALL="yes"' >"$tmp/dkms.conf"
|
||||
sudo dkms remove "$CEC_DKMS_NAME/$CEC_DKMS_VER" --all >/dev/null 2>&1
|
||||
sudo rm -rf "$CEC_DKMS_SRC"
|
||||
sudo install -d "$CEC_DKMS_SRC" && sudo install -m644 "$tmp"/{cros-ec-cec.c,Makefile,dkms.conf} "$CEC_DKMS_SRC/"
|
||||
rm -rf "$tmp"
|
||||
sudo dkms add "$CEC_DKMS_NAME/$CEC_DKMS_VER" >/dev/null || { err "Adding the CEC driver to DKMS failed."; return 1; }
|
||||
# Every kernel with headers; one that fails to build only lacks CEC.
|
||||
for k in /usr/lib/modules/*/build; do
|
||||
k="$(basename "$(dirname "$k")")"
|
||||
info "Building the CEC driver for $k..."
|
||||
sudo dkms install "$CEC_DKMS_NAME/$CEC_DKMS_VER" -k "$k" >/dev/null ||
|
||||
warn "Building the CEC driver for $k failed; that kernel has no HDMI-CEC."
|
||||
done
|
||||
cec_reload_driver
|
||||
}
|
||||
|
||||
cec_driver_disable() {
|
||||
[[ -d "$CEC_DKMS_SRC" ]] || return 0
|
||||
sudo dkms remove "$CEC_DKMS_NAME/$CEC_DKMS_VER" --all >/dev/null 2>&1
|
||||
sudo rm -rf "$CEC_DKMS_SRC"
|
||||
cec_reload_driver
|
||||
}
|
||||
cec_repair() { cec_installed && ! cec_status; }
|
||||
|
||||
fetch_holo_pkg() {
|
||||
@@ -79,6 +149,7 @@ cec_enable() {
|
||||
return 1
|
||||
fi
|
||||
rm -rf "$tmp"
|
||||
cec_driver_enable || return 1
|
||||
# Its udev rule gives the user /dev/cec*; cecd starts with the graphical
|
||||
# session, after steamos-manager wrote its config from Steam's settings.
|
||||
sudo udevadm control --reload
|
||||
@@ -108,6 +179,7 @@ cec_disable() {
|
||||
systemctl --user disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null
|
||||
systemctl --user disable steamos-manager-configure-cecd.service 2>/dev/null
|
||||
sudo pacman -Rns --noconfirm "${CEC_PKGS[@]}" 2>/dev/null
|
||||
cec_driver_disable
|
||||
sudo rm -f "$CEC_ORDER_DROPIN" "$CEC_STEAM_DROPIN" "$CEC_STEAM_ENV"
|
||||
sudo rmdir "$(dirname "$CEC_ORDER_DROPIN")" "$(dirname "$CEC_STEAM_DROPIN")" "$(dirname "$CEC_STEAM_ENV")" 2>/dev/null
|
||||
systemctl --user daemon-reload
|
||||
|
||||
+203
-59
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
# "HDMI refresh boost" menu item, Steam Machine (Fremont) only: finds the
|
||||
# highest refresh rate the display runs at the desktop resolution over HDMI,
|
||||
# tests it live and makes it permanent with an EDID override
|
||||
# (drm.edid_firmware). Two things keep displays below what they can do:
|
||||
# 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:
|
||||
# - 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
|
||||
# do, and do HDMI 2.1, so it's only offered with the pinned kernel.
|
||||
@@ -14,7 +14,17 @@
|
||||
# black). Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
HDMI_FW_DIR=/usr/lib/firmware/edid
|
||||
# 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
|
||||
# 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
|
||||
# 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_UNIT_NAME=steamify-edid.service
|
||||
HDMI_UNIT="/etc/systemd/system/$HDMI_UNIT_NAME"
|
||||
HDMI_UDEV_RULE=/etc/udev/rules.d/90-steamify-edid.rules
|
||||
# No answer within this time switches back; longer for scripted tests.
|
||||
HDMI_CONFIRM_SECONDS="${WIZARD_HDMI_CONFIRM_SECONDS:-15}"
|
||||
# amdgpu's HDMI 2.0 limit; the display's own limit (from its EDID) may be lower.
|
||||
@@ -48,10 +58,26 @@ hdmi_cmdline_param() {
|
||||
hdmi_available() {
|
||||
# Stays available while on, so it can be turned off after the pin is gone.
|
||||
detect_valve_fremont || return 1
|
||||
hdmi_status || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" && -n "$(hdmi_boot_file)" ]]; }
|
||||
# Saved displays too, so they can be removed without the pin.
|
||||
hdmi_status || [[ -n "$(hdmi_saved)" ]] || { pinned_kernel_installed && [[ -n "$(hdmi_connectors)" ]]; }
|
||||
}
|
||||
|
||||
hdmi_status() { [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; }
|
||||
hdmi_status() {
|
||||
# On = the connected display runs on its saved EDID. Another display on
|
||||
# the port is off, so ticking it sets that one up. The command line: set
|
||||
# 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 <command> ...: EDID parsing and building (python, as
|
||||
@@ -160,6 +186,11 @@ if cmd == 'blocks':
|
||||
print(nblocks(load(args[0])))
|
||||
elif cmd == 'id':
|
||||
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':
|
||||
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)
|
||||
@@ -322,23 +353,16 @@ hdmi_confirm() {
|
||||
[[ "$reply" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
hdmi_boot_param() {
|
||||
# hdmi_boot_param <value|"">: set or remove our drm.edid_firmware= in
|
||||
# the boot loader's command line, then rebuild the initramfs and entries.
|
||||
local f value="$1"
|
||||
f="$(hdmi_boot_file)" || return 1
|
||||
[[ -n "$f" ]] || { err "No supported boot loader (Limine, systemd-boot with sdboot-manage, GRUB) found."; return 1; }
|
||||
hdmi_remove_boot_param() {
|
||||
# Removes the drm.edid_firmware= older versions put on the kernel command
|
||||
# line (it applied to whatever display was on that port), then rebuilds
|
||||
# the initramfs and boot entries.
|
||||
local f
|
||||
[[ -n "$(hdmi_cmdline_param)" ]] || { sudo rm -f "$HDMI_INITRAMFS_CONF"; return 0; }
|
||||
f="$(hdmi_boot_file)"
|
||||
backup_file "$f"
|
||||
sudo sed -i -E 's/drm\.edid_firmware=[^ "]*steamify-[^ "]* ?//' "$f"
|
||||
if [[ -n "$value" ]]; then
|
||||
case "$f" in
|
||||
# | as delimiter: the value holds a path.
|
||||
/etc/default/limine) sudo sed -i -E "0,\|^KERNEL_CMDLINE\[default\]\+?=\"|s||&$value |" "$f" ;;
|
||||
/etc/sdboot-manage.conf) sudo sed -i -E "0,\|^LINUX_OPTIONS=\"|s||&$value |" "$f" ;;
|
||||
/etc/default/grub) sudo sed -i -E "0,\|^GRUB_CMDLINE_LINUX_DEFAULT=\"|s||&$value |" "$f" ;;
|
||||
esac
|
||||
grep -qF "$value" "$f" || { err "Couldn't add $value to $f."; return 1; }
|
||||
fi
|
||||
sudo rm -f "$HDMI_INITRAMFS_CONF"
|
||||
info "Rebuilding the initramfs and boot entries..."
|
||||
case "$f" in
|
||||
/etc/default/limine) sudo limine-mkinitcpio ;;
|
||||
@@ -347,6 +371,136 @@ hdmi_boot_param() {
|
||||
esac
|
||||
}
|
||||
|
||||
hdmi_write_map() {
|
||||
# hdmi_write_map <lines>: the saved displays list.
|
||||
sudo mkdir -p "$(dirname "$HDMI_MAP")"
|
||||
{ printf '# Written by steamify: HDMI refresh boost, one display per line:\n'
|
||||
printf '# <id>\t<name>\t<w>x<h>\t<rates>\n'
|
||||
[[ -n "$1" ]] && printf '%s\n' "$1"; } | sudo tee "$HDMI_MAP" >/dev/null
|
||||
}
|
||||
|
||||
hdmi_save() {
|
||||
# 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
|
||||
out="${f##*/steamify-}"; out="${out%.bin}"
|
||||
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
|
||||
state_clear hdmi
|
||||
hdmi_remove_boot_param
|
||||
}
|
||||
|
||||
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'
|
||||
#!/bin/bash
|
||||
# Steamify CachyOS, HDMI refresh boost: load a display's saved EDID only
|
||||
# while it is connected. Runs as root (debugfs).
|
||||
FW=/usr/lib/firmware/edid RUN=/run/steamify-edid
|
||||
modprobe i2c-dev 2>/dev/null
|
||||
mkdir -p "$RUN"
|
||||
# Hotplug events come in bursts, and the link needs a moment before DDC works.
|
||||
sleep 1
|
||||
for c in /sys/class/drm/card*-HDMI-A-*; do
|
||||
[ -e "$c" ] || continue
|
||||
out="${c##*/}"; out="${out#card*-}"
|
||||
new=reset id=""
|
||||
if [ "$(cat "$c/status")" = connected ]; then
|
||||
bus="$(basename "$(readlink -f "$c/ddc")")"
|
||||
for i in 1 2 3 4 5; do
|
||||
# Bytes 8-17 of block 0: manufacturer, product, serial, date.
|
||||
id="$(i2ctransfer -y "${bus#i2c-}" w1@0x50 0 r18@0x50 2>/dev/null | tr -d ' ' | sed 's/0x//g')"
|
||||
[ ${#id} -eq 36 ] && break
|
||||
sleep 1
|
||||
done
|
||||
[ ${#id} -eq 36 ] && [ -f "$FW/steamify-${id:16}.bin" ] && new="${id:16}"
|
||||
fi
|
||||
[ "$(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
|
||||
[ -e "$d/edid_override" ] || continue
|
||||
if [ "$new" = reset ]; then printf reset > "$d/edid_override"
|
||||
else cat "$FW/steamify-$new.bin" > "$d/edid_override"; fi
|
||||
# Before the hotplug below, whose own event runs this again.
|
||||
echo "$new" > "$RUN/$out"
|
||||
echo 1 > "$d/trigger_hotplug"
|
||||
done
|
||||
exit 0
|
||||
EOF
|
||||
sudo chmod 755 "$HDMI_HOTPLUG"
|
||||
# StartLimitIntervalSec=0: a burst of hotplugs must not get it rate-limited.
|
||||
sudo tee "$HDMI_UNIT" > /dev/null << EOF
|
||||
[Unit]
|
||||
Description=Steamify CachyOS: HDMI refresh boost EDID for the connected display
|
||||
StartLimitIntervalSec=0
|
||||
Wants=sys-kernel-debug.mount
|
||||
After=sys-kernel-debug.mount
|
||||
Before=display-manager.service sddm.service plasmalogin.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$HDMI_HOTPLUG
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
EOF
|
||||
# restart, not start: a hotplug during a run must still be looked at.
|
||||
printf '%s\n' '# Written by steamify: HDMI refresh boost, check the display at every hotplug.' \
|
||||
"ACTION==\"change\", SUBSYSTEM==\"drm\", ENV{HOTPLUG}==\"1\", RUN+=\"/usr/bin/systemctl --no-block restart $HDMI_UNIT_NAME\"" |
|
||||
sudo tee "$HDMI_UDEV_RULE" >/dev/null || return 1
|
||||
sudo systemctl daemon-reload
|
||||
sudo udevadm control --reload
|
||||
sudo systemctl enable "$HDMI_UNIT_NAME" >/dev/null 2>&1 || { err "Enabling $HDMI_UNIT_NAME failed."; return 1; }
|
||||
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 <card-connector> <tmpdir>: find and confirm the highest rate
|
||||
# at the desktop resolution. Sets HDMI_RESULT to "<w> <h> <hz>..." (the
|
||||
@@ -485,6 +639,16 @@ hdmi_choose() {
|
||||
|
||||
hdmi_enable() {
|
||||
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
|
||||
err "HDMI refresh boost needs you at the screen: pick and test the rates in the app first."
|
||||
return 1
|
||||
@@ -493,22 +657,10 @@ hdmi_enable() {
|
||||
err "Run this from the Plasma desktop: the test switches the display mode through KDE."
|
||||
return 1
|
||||
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 ]]; 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; }
|
||||
|
||||
local tmp conn out param="" files=() rc=0 res
|
||||
local tmp conn out res saved=false rc=0
|
||||
local -a smode
|
||||
tmp="$(mktemp -d)"
|
||||
if [[ ${#HDMI_CHOICE[@]} -gt 0 ]]; then
|
||||
# Picked and tested in the app.
|
||||
@@ -520,40 +672,32 @@ hdmi_enable() {
|
||||
done
|
||||
fi
|
||||
for res in "${HDMI_RESULTS[@]}"; do
|
||||
out="${res%%|*}"
|
||||
sudo install -Dm644 "$tmp/$out.final" "$HDMI_FW_DIR/steamify-$out.bin" || { rc=1; continue; }
|
||||
state_set hdmi "$out" "$(hdmi_edid_tool id "$tmp/$out.orig")"
|
||||
state_set hdmi "$out-mode" "${res#*|}"
|
||||
param+="${param:+,}$out:edid/steamify-$out.bin"
|
||||
files+=("$HDMI_FW_DIR/steamify-$out.bin")
|
||||
out="${res%%|*}"; read -ra smode <<< "${res#*|}"
|
||||
hdmi_save "$tmp/$out.final" "${smode[0]}x${smode[1]}" "${smode[*]:2}" && saved=true || rc=1
|
||||
done
|
||||
rm -rf "$tmp" "$HDMI_CACHE"
|
||||
[[ -n "$param" ]] || return $rc
|
||||
|
||||
# amdgpu loads from the initramfs (the kms hook), before the root
|
||||
# filesystem: the EDID file has to be in there too.
|
||||
sudo mkdir -p "$(dirname "$HDMI_INITRAMFS_CONF")"
|
||||
printf '# Written by steamify: EDID override for HDMI refresh boost.\nFILES+=(%s)\n' "${files[*]}" |
|
||||
sudo tee "$HDMI_INITRAMFS_CONF" >/dev/null
|
||||
if ! hdmi_boot_param "drm.edid_firmware=$param"; then
|
||||
[[ "$saved" == true ]] || return $rc
|
||||
if ! hdmi_install_hotplug; then
|
||||
# Nothing half-done left behind; the live EDID lasts until a restart.
|
||||
sudo rm -f "$HDMI_INITRAMFS_CONF" "${files[@]}"
|
||||
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done
|
||||
state_clear hdmi
|
||||
hdmi_remove_hotplug
|
||||
err "Making it permanent failed; the display is back on its own EDID."
|
||||
return 1
|
||||
fi
|
||||
ok "HDMI refresh boost is set up; it's active now and after every restart."
|
||||
ok "HDMI refresh boost is saved for this display; other displays keep their own settings."
|
||||
return $rc
|
||||
}
|
||||
|
||||
hdmi_disable() {
|
||||
local f conn
|
||||
# Drop-in and files first: the rebuild below leaves them out.
|
||||
sudo rm -f "$HDMI_INITRAMFS_CONF"
|
||||
for f in "$HDMI_FW_DIR"/steamify-*.bin; do [[ -e "$f" ]] && sudo rm -f "$f"; done
|
||||
hdmi_boot_param "" || return 1
|
||||
for conn in $(hdmi_connectors); do hdmi_override_live "$conn" reset 2>/dev/null; done
|
||||
state_clear hdmi
|
||||
ok "HDMI refresh boost removed; the display uses its own EDID again."
|
||||
# For the connected display; other saved displays stay (the app lists
|
||||
# them). Without the pinned kernel none may stay: newer kernels read the
|
||||
# EDID themselves.
|
||||
local id
|
||||
if [[ -n "$(hdmi_cmdline_param)" ]]; then
|
||||
hdmi_forget all; hdmi_remove_boot_param || return 1; state_clear hdmi
|
||||
elif [[ "${WANTED[kpin]:-1}" == 0 ]] || ! pinned_kernel_installed; then
|
||||
hdmi_forget all
|
||||
else
|
||||
for id in $(hdmi_active_ids); do hdmi_forget "$id"; done
|
||||
fi
|
||||
ok "HDMI refresh boost removed for the connected display; it uses its own EDID again."
|
||||
}
|
||||
+4
-1
@@ -24,7 +24,7 @@ declare -A LABEL=(
|
||||
[theme]="Install SteamOS theme: Vapor look (cachyos-vapor)"
|
||||
[glyphs]="Install Steam Deck/Machine icons: Deck button icons in gaming mode"
|
||||
[single]="Single user mode: no password, lock screen or log out (SDDM)"
|
||||
[launcher]="Steamify shortcut: desktop icon to run this again"
|
||||
[launcher]="Steamify shortcut: the app on the desktop, Steamify Terminal in the launcher"
|
||||
[cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)"
|
||||
[machine]="Steam Machine support: LED bar driver, hardware settings in Steam"
|
||||
[kpin]="Pin the kernel to $PINNED_KERNEL_VER (fixes rebooting after shutdown)"
|
||||
@@ -72,6 +72,9 @@ detect_components() {
|
||||
done
|
||||
# HDMI-CEC set up by an older version: tick it, so a normal run fixes it.
|
||||
component_available cec && cec_repair && WANTED[cec]=1
|
||||
# The terminal-only Steamify shortcut from before 2.0.1: tick it, so a
|
||||
# normal run replaces it with the app.
|
||||
launcher_repair && WANTED[launcher]=1
|
||||
# Shows the current and newest BIOS version.
|
||||
bios_available && { bios_lookup_newest; LABEL[bios]="$(bios_label)"; }
|
||||
# First run: preselect the full SteamOS experience (never an action).
|
||||
|
||||
@@ -163,7 +163,12 @@ kpin_enable() {
|
||||
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() {
|
||||
# DKMS can only build leds-valve against kernels whose headers are
|
||||
|
||||
+61
-14
@@ -1,19 +1,29 @@
|
||||
#!/bin/bash
|
||||
# "Steamify shortcut" menu item: a desktop icon and an app launcher entry that
|
||||
# open the newest release of Steamify CachyOS in Konsole (curl | bash, like the
|
||||
# install command in the README), so it's never out of date.
|
||||
# "Steamify shortcut" menu item: "Steamify CachyOS" on the desktop and in the
|
||||
# app launcher opens the newest release of the app (curl | bash of
|
||||
# steamify-app.sh, like the install command in the README), so it's never out
|
||||
# of date; "Steamify Terminal" in the launcher opens the terminal menu in
|
||||
# Konsole the same way.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
WIZARD_RELEASE="https://github.com/theupriser/steamify-cachyos/releases/latest/download"
|
||||
WIZARD_URL="$WIZARD_RELEASE/steamify.sh"
|
||||
WIZARD_APP_URL="$WIZARD_RELEASE/steamify-app.sh"
|
||||
# Steam logo with a gear (assets/ in the repo, published with every release).
|
||||
WIZARD_ICON="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps/cachyos-gamescope-boot-wizard.svg"
|
||||
WIZARD_LAUNCHER="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot/run-wizard"
|
||||
WIZARD_DESKTOP_NAME="cachyos-gamescope-boot-wizard.desktop"
|
||||
WIZARD_APP_ENTRY="${XDG_DATA_HOME:-$HOME/.local/share}/applications/$WIZARD_DESKTOP_NAME"
|
||||
WIZARD_APP_LAUNCHER="${XDG_DATA_HOME:-$HOME/.local/share}/cachyos-gamescope-boot/run-app"
|
||||
WIZARD_APPS="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
||||
# steamify-ui: the id the app gives itself, so its window belongs to this
|
||||
# entry. steamify-app.sh leaves an entry with X-Steamify-Shortcut alone.
|
||||
WIZARD_DESKTOP_NAME="steamify-ui.desktop"
|
||||
WIZARD_APP_ENTRY="$WIZARD_APPS/$WIZARD_DESKTOP_NAME"
|
||||
WIZARD_TERMINAL_ENTRY="$WIZARD_APPS/steamify-terminal.desktop"
|
||||
# Before 2.0.1 the shortcut opened the terminal menu under this name.
|
||||
WIZARD_OLD_NAME="cachyos-gamescope-boot-wizard.desktop"
|
||||
|
||||
wizard_desktop_file() {
|
||||
echo "$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")/$WIZARD_DESKTOP_NAME"
|
||||
wizard_desktop_dir() {
|
||||
xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop"
|
||||
}
|
||||
|
||||
install_executable() {
|
||||
@@ -29,11 +39,15 @@ install_executable() {
|
||||
}
|
||||
|
||||
launcher_status() {
|
||||
[[ -x "$WIZARD_LAUNCHER" && -f "$WIZARD_APP_ENTRY" ]]
|
||||
[[ -x "$WIZARD_APP_LAUNCHER" && -x "$WIZARD_LAUNCHER" && -f "$WIZARD_APP_ENTRY" && -f "$WIZARD_TERMINAL_ENTRY" ]]
|
||||
}
|
||||
|
||||
# A shortcut from before 2.0.1 (terminal only): the menu ticks the item, so a
|
||||
# normal run replaces it.
|
||||
launcher_repair() { [[ -f "$WIZARD_APPS/$WIZARD_OLD_NAME" ]] && ! launcher_status; }
|
||||
|
||||
launcher_enable() {
|
||||
info "Adding the Steamify shortcut (desktop icon and app launcher entry)..."
|
||||
info "Adding the Steamify shortcut (the app on the desktop and in the launcher, Steamify Terminal in the launcher)..."
|
||||
install_executable "$WIZARD_LAUNCHER" 755 << EOF
|
||||
#!/bin/bash
|
||||
# Installed by Steamify CachyOS: run its newest release.
|
||||
@@ -53,6 +67,23 @@ for (( i = 10; i > 0; i-- )); do
|
||||
read -rs -t 1 _ && break
|
||||
done
|
||||
echo
|
||||
EOF
|
||||
|
||||
# The app, without a terminal. Only the first time, when PySide6 still
|
||||
# has to be installed, it runs in Konsole: sudo asks for the password there.
|
||||
install_executable "$WIZARD_APP_LAUNCHER" 755 << EOF
|
||||
#!/bin/bash
|
||||
# Installed by Steamify CachyOS: start the newest release of its app.
|
||||
set -o pipefail
|
||||
if [[ "\${1:-}" != --in-terminal ]] && ! python3 -c 'import PySide6.QtQml' 2>/dev/null; then
|
||||
exec konsole -e "\$0" --in-terminal
|
||||
fi
|
||||
if ! curl -fsSL --max-time 30 "$WIZARD_APP_URL" | bash; then
|
||||
msg="Couldn't download or start Steamify. Are you connected to the internet?"
|
||||
if [[ "\${1:-}" == --in-terminal ]]; then echo; echo "\$msg"; read -rp "Press Enter to close this window... " _
|
||||
else kdialog --title Steamify --sorry "\$msg" 2>/dev/null; fi
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
|
||||
# The icon comes with the release; without it, Steam's own icon.
|
||||
@@ -72,18 +103,34 @@ EOF
|
||||
Type=Application
|
||||
Name=Steamify CachyOS
|
||||
Comment=Turn the SteamOS-style parts of this PC on or off
|
||||
Exec=$WIZARD_APP_LAUNCHER
|
||||
Icon=$icon
|
||||
Terminal=false
|
||||
Categories=Game;System;Settings;
|
||||
X-Steamify-Shortcut=true"
|
||||
printf '%s\n' "$entry" | install_executable "$WIZARD_APP_ENTRY" 644
|
||||
printf '%s\n' "$entry" | install_executable "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME" 755
|
||||
printf '%s\n' "[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Steamify Terminal
|
||||
Comment=Steamify CachyOS's menu in a terminal
|
||||
Exec=konsole -e $WIZARD_LAUNCHER
|
||||
Icon=$icon
|
||||
Terminal=false
|
||||
Categories=Game;System;Settings;"
|
||||
printf '%s\n' "$entry" | install_executable "$WIZARD_APP_ENTRY" 644
|
||||
printf '%s\n' "$entry" | install_executable "$(wizard_desktop_file)" 755
|
||||
Categories=Game;System;Settings;" | install_executable "$WIZARD_TERMINAL_ENTRY" 644
|
||||
# The terminal-only shortcut from before 2.0.1.
|
||||
rm -f "$(wizard_desktop_dir)/$WIZARD_OLD_NAME" "$WIZARD_APPS/$WIZARD_OLD_NAME"
|
||||
kbuildsycoca6 >/dev/null 2>&1 || true
|
||||
ok "Steamify shortcut added: \"Steamify CachyOS\" on the desktop and in the launcher."
|
||||
ok "Steamify shortcut added: \"Steamify CachyOS\" (the app) on the desktop and in the launcher, \"Steamify Terminal\" in the launcher."
|
||||
}
|
||||
|
||||
launcher_disable() {
|
||||
rm -f "$(wizard_desktop_file)" "$WIZARD_APP_ENTRY" "$WIZARD_LAUNCHER" "$WIZARD_ICON"
|
||||
# The launcher entry only if it's ours: steamify-app.sh writes its own
|
||||
# under the same name when there's no shortcut.
|
||||
grep -q '^X-Steamify-Shortcut=true' "$WIZARD_APP_ENTRY" 2>/dev/null && rm -f "$WIZARD_APP_ENTRY"
|
||||
rm -f "$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME" "$WIZARD_TERMINAL_ENTRY" \
|
||||
"$(wizard_desktop_dir)/$WIZARD_OLD_NAME" "$WIZARD_APPS/$WIZARD_OLD_NAME" \
|
||||
"$WIZARD_LAUNCHER" "$WIZARD_APP_LAUNCHER" "$WIZARD_ICON"
|
||||
rmdir "$(dirname "$WIZARD_LAUNCHER")" 2>/dev/null || true
|
||||
kbuildsycoca6 >/dev/null 2>&1 || true
|
||||
ok "Steamify shortcut removed."
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
set -uo pipefail
|
||||
|
||||
# Release version, see CHANGELOG.md.
|
||||
VERSION=2.0.0
|
||||
VERSION=2.1.0
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
|
||||
+113
-10
@@ -60,8 +60,9 @@ ApplicationWindow {
|
||||
single: { label: "Single user mode", hint: "No password, lock screen or wallet prompts",
|
||||
body: "Like SteamOS: never a login or lock screen, and no KDE wallet password prompts. Typing a password with a controller is no fun.",
|
||||
changes: ["SDDM autologin, no lock screen or user switching", "Valve's empty wallet; your own is kept aside", "Launcher shows Sleep, Restart, Shut Down only"] },
|
||||
launcher: { label: "Steamify shortcut", hint: "Icon on the desktop and in the launcher",
|
||||
body: "Opens the newest Steamify, so you never need the install command again.", changes: ["Desktop icon and launcher entry"] },
|
||||
launcher: { label: "Steamify shortcut", hint: "The app on the desktop and in the launcher",
|
||||
body: "Opens the newest Steamify app, so you never need the install command again. Steamify Terminal in the launcher opens the terminal menu.",
|
||||
changes: ["Steamify CachyOS on the desktop and in the launcher (the app)", "Steamify Terminal in the launcher"] },
|
||||
cec: { label: "HDMI-CEC", hint: "Use Steam with the TV remote, TV on/off with the PC", experimental: true,
|
||||
body: "Use Steam with your TV remote, and the TV turns on and off with the PC. Turn on CEC on the TV too (Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink).",
|
||||
changes: ["Valve's cecd and cec-audio-control", "HDMI CEC section in Steam's Display settings", "Volume buttons for the TV in the Quick Access menu"] },
|
||||
@@ -73,7 +74,7 @@ ApplicationWindow {
|
||||
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",
|
||||
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", "Manage: every saved display, remove one or set up the connected display"] },
|
||||
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.",
|
||||
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"] }
|
||||
@@ -192,6 +193,32 @@ ApplicationWindow {
|
||||
|
||||
readonly property var status: backend.status || ({})
|
||||
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 being removed
|
||||
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 "";
|
||||
}
|
||||
// Manage offers setting up the connected display when it has no saved rates.
|
||||
readonly property bool hdmiCanAdd: !hdmiSaved.some(function (d) { return d.active; })
|
||||
function openHdmiList() {
|
||||
if (!hdmiSaved.length) return;
|
||||
hdmiListSel = 0; hdmiConfirm = ""; hdmiNote = ""; screen = "hdmilist";
|
||||
}
|
||||
function hdmiForget() {
|
||||
var d = hdmiSaved[hdmiListSel];
|
||||
if (!d) return;
|
||||
hdmiConfirm = d.id;
|
||||
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]; })
|
||||
|
||||
function nowOn(id) {
|
||||
@@ -217,6 +244,7 @@ ApplicationWindow {
|
||||
if (id === "single" && w.single) w.gaming = true;
|
||||
// Turning HDMI refresh boost on goes through its own screen: pick
|
||||
// the rates and try each one first.
|
||||
if (id === "hdmi" && !hdmiChoice && hdmiSaved.length) { openHdmiList(); return; }
|
||||
if (id === "hdmi" && w.hdmi && !nowOn("hdmi") && !hdmiChoice) { startHdmi(); return; }
|
||||
if (id === "hdmi" && !w.hdmi) hdmiChoice = "";
|
||||
if (id === "machine") w.kpin = w.machine;
|
||||
@@ -273,6 +301,7 @@ ApplicationWindow {
|
||||
var p = pw.text; pw.text = "";
|
||||
if (pending === "bios") { sessionPassword = p; biosCheck(); }
|
||||
else if (pending === "hdmi") { sessionPassword = p; hdmiLoad(); }
|
||||
else if (pending === "forget") { sessionPassword = p; hdmiForgetNow(); }
|
||||
else startApply(p);
|
||||
}
|
||||
// --- BIOS: check -> warning 1 -> warning 2 -> flash ---
|
||||
@@ -329,7 +358,12 @@ ApplicationWindow {
|
||||
else { hdmiPhase = "failed"; hdmiReject(); }
|
||||
}
|
||||
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.
|
||||
runError = ev.error === "wrong-password" ? "The password didn't work." : "sudo isn't available.";
|
||||
failed = []; restartNeeded = false; sessionPassword = ""; screen = "done";
|
||||
@@ -375,7 +409,7 @@ ApplicationWindow {
|
||||
else if (a === "back") { reapply = false; screen = "menu"; }
|
||||
} else if (screen === "password") {
|
||||
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") {
|
||||
if (a === "left") biosFocus = 0;
|
||||
else if (a === "right") biosFocus = 1;
|
||||
@@ -396,6 +430,12 @@ ApplicationWindow {
|
||||
// A second Back meant for the test (it takes a moment to switch
|
||||
// back) would otherwise leave the whole screen.
|
||||
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 - (hdmiCanAdd ? 0 : 1), hdmiListSel + 1); hdmiConfirm = ""; }
|
||||
else if (a === "accept") { if (hdmiListSel === hdmiSaved.length) { hdmiConfirm = ""; startHdmi(); } else hdmiForget(); }
|
||||
else if (a === "back") hdmiListBack();
|
||||
} else if (screen === "hdmitest") {
|
||||
if (a === "accept" || a === "apply") hdmiKeep();
|
||||
else if (a === "back") hdmiReject();
|
||||
@@ -506,7 +546,7 @@ ApplicationWindow {
|
||||
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 }
|
||||
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: "HDMI refresh boost"})[screen]
|
||||
color: t.faint; font.family: screen === "menu" ? t.mono : t.body; font.pixelSize: screen === "menu" ? 13 : 15; anchors.verticalCenter: parent.verticalCenter }
|
||||
}
|
||||
Rectangle {
|
||||
@@ -559,7 +599,7 @@ ApplicationWindow {
|
||||
readonly property bool selected: shown && rowIndex === sel
|
||||
readonly property bool on: modelData.kind === "choice" ? true : !!want[modelData.id]
|
||||
// HDMI refresh boost is set up on its own screen: a button while off.
|
||||
readonly property bool setup: modelData.id === "hdmi" && !modelData.on
|
||||
readonly property bool setup: modelData.id === "hdmi"
|
||||
onSelectedChanged: if (selected) Qt.callLater(list.showSelected)
|
||||
width: list.width - 12; x: 2
|
||||
height: shown ? 62 : 0
|
||||
@@ -583,7 +623,7 @@ ApplicationWindow {
|
||||
width: controls.x - x - 16
|
||||
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: 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
|
||||
Item {
|
||||
@@ -611,8 +651,8 @@ ApplicationWindow {
|
||||
width: sut.implicitWidth + 24; height: 30; radius: 8; color: hdmiChoice && want.hdmi ? t.goodBg : "#1b2b40"
|
||||
Text { id: sut; anchors.centerIn: parent; font.family: t.body; font.pixelSize: 13; font.weight: Font.DemiBold
|
||||
color: hdmiChoice && want.hdmi ? t.good : "#7cc4ff"
|
||||
text: hdmiChoice && want.hdmi ? (hdmiChoice.split(":")[1] ? hdmiChoice.split(":")[1].split(",").join(", ") + " Hz ✓" : "Own modes ✓") : "Set up…" }
|
||||
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: { sel = row.rowIndex; startHdmi(); } } }
|
||||
text: hdmiChoice && want.hdmi ? (hdmiChoice.split(":")[1] ? hdmiChoice.split(":")[1].split(",").join(", ") + " Hz ✓" : "Own modes ✓") : (hdmiSaved.length ? "Manage" : "Set up…") }
|
||||
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: { sel = row.rowIndex; toggle("hdmi"); } } }
|
||||
// action
|
||||
Rectangle { visible: row.modelData.kind === "action"; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter
|
||||
width: at.implicitWidth + 24; height: 30; radius: 8; color: t.warnBg
|
||||
@@ -963,6 +1003,69 @@ 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: "Manage 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; } }
|
||||
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…" : "Remove"
|
||||
onClicked: { hdmiListSel = parent.parent.index; hdmiForget(); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
visible: hdmiSaved.length > 0 && hdmiCanAdd
|
||||
x: 40; y: 28 + 84 + hdmiSaved.length * 76; width: 740; height: 64; radius: 12
|
||||
color: hdmiListSel === hdmiSaved.length ? t.cardSel : t.card; border.width: hdmiListSel === hdmiSaved.length ? 2 : 0; border.color: t.accent
|
||||
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: { inputType = "keyboard"; hdmiListSel = hdmiSaved.length; } }
|
||||
Column { anchors.left: parent.left; anchors.leftMargin: 18; anchors.verticalCenter: parent.verticalCenter; spacing: 2
|
||||
Text { text: "The connected display"; color: t.textHi; font.family: t.body; font.pixelSize: 18; font.weight: Font.DemiBold }
|
||||
Text { text: "No saved rates yet"; color: t.mute; font.family: t.body; font.pixelSize: 13 } }
|
||||
Btn { anchors.right: parent.right; anchors.rightMargin: 14; anchors.verticalCenter: parent.verticalCenter; height: 38
|
||||
focusRing: hdmiListSel === hdmiSaved.length; k: hdmiListSel === hdmiSaved.length ? g.ok : ""; text: "Set up…"
|
||||
onClicked: { hdmiConfirm = ""; startHdmi(); } }
|
||||
}
|
||||
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? =================
|
||||
Item {
|
||||
visible: screen === "hdmitest"
|
||||
|
||||
+4
-1
@@ -53,8 +53,11 @@ if ! python3 -c 'import PySide6.QtQml' 2>/dev/null; then
|
||||
sudo pacman -S --needed --noconfirm pyside6 </dev/tty || die "Installing pyside6 failed."
|
||||
fi
|
||||
|
||||
# Launcher entry (its id also names the app to the desktop portal).
|
||||
# Launcher entry (its id also names the app to the desktop portal). The
|
||||
# Steamify shortcut's entry has the same id and always starts the newest
|
||||
# release: left alone.
|
||||
mkdir -p "$APPS"
|
||||
grep -q '^X-Steamify-Shortcut=true' "$APPS/steamify-ui.desktop" 2>/dev/null ||
|
||||
cat > "$APPS/steamify-ui.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
|
||||
@@ -289,6 +289,11 @@ class Backend(QObject):
|
||||
"""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)
|
||||
|
||||
@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):
|
||||
self._buf += bytes(p.readAllStandardOutput())
|
||||
while b"\n" in self._buf:
|
||||
|
||||
Reference in new issue
Block a user