mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ae9673eb2 | ||
|
|
00bcaea712 | ||
|
|
a898d23b13 | ||
|
|
e09fe1609b | ||
|
|
9dcac5cf08 | ||
|
|
f91dd1f0b1 | ||
|
|
0411429654 | ||
|
|
1aa5e955fe | ||
|
|
ce75e895c4 | ||
|
|
9aef0a986a | ||
|
|
f7aa2412df | ||
|
|
87162efaa2 | ||
|
|
c79a3bbda5 | ||
|
|
ba11f58d9e | ||
|
|
45ff036f09 | ||
|
|
de02f267f4 | ||
|
|
0f123004d0 | ||
|
|
3048b35dd8 | ||
|
|
f7837f82b9 | ||
|
|
3eb41eef5a | ||
|
|
41422e5bb4 | ||
|
|
6c2e203563 | ||
|
|
1522e5632d | ||
|
|
1d994ae359 | ||
|
|
700f3e514b | ||
|
|
d78652c862 | ||
|
|
727e6a2302 | ||
|
|
c4bd1ca39c | ||
|
|
a449a9e467 | ||
|
|
388b1286c9 | ||
|
|
508f22acaf | ||
|
|
3f8e0372d1 | ||
|
|
bd90e29e7a | ||
|
|
4895de3a46 | ||
|
|
0f503dd112 |
No files matched your search
@@ -5,8 +5,6 @@
|
||||
# release are moved to it, so both of these always get the newest version:
|
||||
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash
|
||||
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/latest/steamify.sh | bash
|
||||
# The bundle is also published as setup-gamescope-boot.sh, its name before
|
||||
# 0.9.0, so older install commands keep working.
|
||||
# Pull requests only build and check it, and warn when VERSION is already released.
|
||||
name: Single-file build
|
||||
|
||||
@@ -30,8 +28,6 @@ jobs:
|
||||
- name: Build
|
||||
run: |
|
||||
.github/tools/bundle.sh dist/steamify.sh
|
||||
# Old name (before 0.9.0), for older install commands and links.
|
||||
cp dist/steamify.sh dist/setup-gamescope-boot.sh
|
||||
|
||||
- name: Check
|
||||
run: |
|
||||
@@ -77,7 +73,7 @@ jobs:
|
||||
|
||||
## Changes in v$VERSION
|
||||
$changes"
|
||||
gh release create "v$VERSION" dist/steamify.sh dist/setup-gamescope-boot.sh assets/steam-gaming-settings.svg \
|
||||
gh release create "v$VERSION" dist/steamify.sh assets/steam-gaming-settings.svg \
|
||||
--target "$GITHUB_SHA" --title "Steamify CachyOS v$VERSION" --notes "$notes" --latest
|
||||
|
||||
# "latest" follows the newest version tag, for the older install URL
|
||||
@@ -90,4 +86,4 @@ jobs:
|
||||
else
|
||||
gh release create latest --title "Steamify CachyOS - latest (v$VERSION)" --notes "$latest_notes" --latest=false
|
||||
fi
|
||||
gh release upload latest dist/steamify.sh dist/setup-gamescope-boot.sh assets/steam-gaming-settings.svg --clobber
|
||||
gh release upload latest dist/steamify.sh assets/steam-gaming-settings.svg --clobber
|
||||
@@ -17,7 +17,7 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
logic here.
|
||||
- `lib/*.sh` - one file per responsibility, each defining functions only
|
||||
(no top-level side effects besides constants). See the table in
|
||||
`README.md`.
|
||||
`TECHNICAL.md`.
|
||||
- **Components.** Every menu item `<id>` (listed in `COMPONENTS` and `LABEL`
|
||||
in `lib/menu.sh`) provides `<id>_status` (return 0 if on, detected from
|
||||
the system, no state file needed), `<id>_enable` and `<id>_disable`, all
|
||||
@@ -132,7 +132,17 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
and `ensure-kernel-headers.service` installs missing ones at boot (a
|
||||
pacman hook can't run pacman), which triggers DKMS's install hook. The module creates
|
||||
`/sys/class/leds/valve-leds*`.
|
||||
- `bios` is an *action* (`ACTIONS` in `lib/menu.sh`), not an on/off
|
||||
- `boot` ("Boot into: [gamescope] / desktop") is a choice row, not a
|
||||
checkbox: on = desktop. It's a sub-option of the conversion: `menu_visible`
|
||||
only shows it (indented, `└`) while `gaming` is ticked, so menu numbers
|
||||
after it shift by one when the conversion is unticked. It needs `gaming` (ticking it ticks gaming,
|
||||
unticking gaming unticks it) and is never preselected (`NO_PRESELECT`), so
|
||||
the conversion boots into gamescope by default. Desktop is
|
||||
`steamify-boot-desktop.service`, ordered `Before=` the login managers:
|
||||
`steam-set-session plasma.desktop` plus the plasmalogin sync bridge when it
|
||||
exists, at every boot. CachyOS's `cachyos-gamescope-autologin` still sets
|
||||
gamescope during each desktop session; the unit corrects it at boot.
|
||||
- `bios` is an *action* (`ACTIONS` in `lib/menu.sh`) and a sub-option of `machine`, not an on/off
|
||||
component: never preselected (not even on a first run), never re-applied
|
||||
by `a`, not listed in the state overview, and `bios_status` is always off.
|
||||
Only selectable when Valve's version differs from the installed one
|
||||
@@ -161,6 +171,15 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
The start script closes its window after a 10-second countdown on success
|
||||
and waits for Enter after an error; it uses `pipefail`, or a failed
|
||||
download would run an empty script and count as success.
|
||||
- HDMI refresh boost (`hdmi`, `lib/hdmi-refresh.sh`): a sub-option of
|
||||
`machine` (like `kpin` and `bios`), only on Fremont with the pinned kernel
|
||||
(stays visible while on); unticking `kpin` unticks it. It needs someone at the
|
||||
screen: the graphical app's backend (`BACKEND=true`) must refuse it, and every step needs a "y" within 15 s
|
||||
(`WIZARD_HDMI_CONFIRM_SECONDS` for scripted tests). 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.
|
||||
- `Relogin=true` means a gamescope that fails to start is relaunched in a
|
||||
tight loop; keep that in mind when changing session handling.
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# HDMI-CEC: does my PC have it?
|
||||
|
||||
The **HDMI-CEC** item in Steamify lets you use Steam with your TV's remote,
|
||||
and turns the TV on and off with the PC. It only works when the PC has a
|
||||
CEC device (`/dev/cec*`). Many PCs don't: on desktop PCs CEC is rare.
|
||||
How it works: [TECHNICAL.md](TECHNICAL.md#hdmi-cec); problems:
|
||||
[TROUBLESHOOTING.md](TROUBLESHOOTING.md#the-tv-remote-does-nothing-hdmi-cec).
|
||||
|
||||
## Graphics cards
|
||||
|
||||
- **NVIDIA:** no. The driver doesn't expose CEC on the HDMI port, on Linux
|
||||
or Windows.
|
||||
- **AMD and Intel:** the CEC line of the HDMI port is normally not
|
||||
connected on desktop cards. The Linux drivers (`amdgpu`, `i915`) only
|
||||
support CEC over a **DisplayPort** connection: some DisplayPort-to-HDMI
|
||||
adapters and built-in converter chips pass CEC along ("DP-AUX CEC"). That
|
||||
depends on the adapter or chip, not on the graphics card, so it's worth
|
||||
trying a different adapter.
|
||||
|
||||
## Devices with CEC built in
|
||||
|
||||
- the **Valve Steam Machine** (Steamify ticks HDMI-CEC by default there);
|
||||
- some **Intel NUC** mini PCs, which have a CEC chip on the board;
|
||||
- the **Raspberry Pi**.
|
||||
|
||||
## Any PC: a USB CEC adapter
|
||||
|
||||
A USB CEC adapter, such as the **Pulse-Eight USB-CEC adapter** (or a
|
||||
RainShadow Tech one), sits between the PC and the TV on the HDMI cable and
|
||||
works on Linux. This is the way to get CEC on a custom build, e.g. with an
|
||||
NVIDIA card. Steamify installs what these adapters need
|
||||
(`inputattach-cec-units`), so plug it in and turn on HDMI-CEC.
|
||||
|
||||
## Check your PC
|
||||
|
||||
After turning on HDMI-CEC in Steamify, the menu lists the CEC devices it
|
||||
found. Or run:
|
||||
|
||||
```bash
|
||||
ls /dev/cec*
|
||||
```
|
||||
|
||||
No device (and no USB adapter): no CEC on this PC. Also turn on CEC on the
|
||||
TV itself: it has a brand name there (Sony: BRAVIA Sync, Samsung: Anynet+,
|
||||
LG: SimpLink, Philips: EasyLink, Panasonic: VIERA Link).
|
||||
|
||||
This overview is based on how the Linux drivers work; hardware differs,
|
||||
so the only sure test is `ls /dev/cec*` on your own PC.
|
||||
+125
-2
@@ -5,14 +5,137 @@ 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.
|
||||
|
||||
## 0.9.1 - 2026-09-24
|
||||
## 1.2.0 - 2026-09-25
|
||||
|
||||
- **feat: HDMI refresh boost (Steam Machine, pinned kernel)**
|
||||
- The pinned 7.1.6 kernel doesn't read the extra EDID block where monitors
|
||||
list their fast modes, and does no HDMI 2.1, so HDMI displays often stay
|
||||
at 60 Hz. The new menu item reads the display's EDID over DDC,
|
||||
calculates the highest rate that fits HDMI 2.0 at the desktop resolution
|
||||
(rounded down to ten, plus the hundred below as a safe option), tests
|
||||
each step live with a confirmation, and makes the confirmed ones
|
||||
permanent through `drm.edid_firmware` (Limine, sdboot-manage or GRUB).
|
||||
- **feat: HDMI refresh boost and Update BIOS under Steam Machine support**
|
||||
- Both are sub-options now, shown only while Steam Machine support is
|
||||
ticked. Unticking it unticks them; unticking the kernel pin unticks the
|
||||
HDMI refresh boost.
|
||||
|
||||
## 1.1.4 - 2026-09-25
|
||||
|
||||
- **fix: No lock screen in single user mode before a restart**
|
||||
- The screen locker only read the new settings at the next login, so the
|
||||
PC still locked after 5 minutes idle until then. Turning single user
|
||||
mode on or off now tells it to reload them right away.
|
||||
|
||||
## 1.1.3 - 2026-09-25
|
||||
|
||||
- **fix: HDMI-CEC settings shown in Steam**
|
||||
- CachyOS's gaming mode script sets `STEAM_ENABLE_CEC=0`, which hides
|
||||
Steam's HDMI-CEC settings (and its CEC volume buttons). HDMI-CEC now adds
|
||||
a drop-in for `steam-launcher.service` that overrides it with
|
||||
`STEAM_ENABLE_CEC=1` (`/etc/steamify/steam-cec.env`).
|
||||
- HDMI-CEC set up by 1.1.0-1.1.2 shows as off and ticked, so a normal run
|
||||
(Enter) adds this.
|
||||
|
||||
## 1.1.2 - 2026-09-25
|
||||
|
||||
- **fix: Steam's HDMI-CEC settings reach cecd on a first run**
|
||||
- HDMI-CEC runs before Steam Machine support, which installs
|
||||
steamos-manager; so steamos-manager's `configure-cecd` unit couldn't be
|
||||
enabled and cecd ran without Steam's settings. Steam Machine support now
|
||||
links them once steamos-manager is there.
|
||||
|
||||
## 1.1.1 - 2026-09-25
|
||||
|
||||
- **perf: Pinned kernel before the LED driver**
|
||||
- With the kernel pin ticked, Steam Machine support installs kernel
|
||||
7.1.6-1 first, so DKMS builds the LED driver once, for that kernel,
|
||||
instead of for the current kernel first and then again. The LED driver
|
||||
then loads after the restart, which it now says instead of warning.
|
||||
- **fix: steamos-manager starts after cecd**
|
||||
- It checks only at its start whether cecd runs; if it was first, Steam's
|
||||
HDMI-CEC settings were missing until it restarted. A drop-in
|
||||
(`/etc/systemd/user/steamos-manager.service.d/`) orders it after cecd.
|
||||
|
||||
## 1.1.0 - 2026-09-25
|
||||
|
||||
- **feat: HDMI-CEC** (experimental, on every PC; ticked by default only on a
|
||||
Steam Machine)
|
||||
- Valve's `cecd`, `cec-audio-control` and `inputattach-cec-units` from its
|
||||
SteamOS repository (SHA-256 checked): use Steam with the TV remote, and
|
||||
the TV turns on and off with the PC. On a Steam Machine, steamos-manager
|
||||
configures it from Steam's settings.
|
||||
- Works with any `/dev/cec*` (GPU or USB adapter); the menu lists them.
|
||||
- `CEC.md`: which PCs have CEC (NVIDIA doesn't, AMD/Intel only over some
|
||||
DisplayPort adapters), and USB adapters for your own build.
|
||||
- Restarts steamos-manager, which only offers Steam its HDMI-CEC settings
|
||||
when cecd was there at its start.
|
||||
- The README no longer says Steam Machine support alone gives HDMI-CEC:
|
||||
`cecd` wasn't installed, so it didn't.
|
||||
|
||||
## 1.0.0 - 2026-09-25
|
||||
|
||||
- **feat: Pin the kernel to 7.1.6-1 on a Steam Machine**
|
||||
- New sub-option under Steam Machine support, ticked along with it (opt
|
||||
out): newer CachyOS kernels make the Steam Machine reboot instead of
|
||||
shutting down. Installs `linux-cachyos` and `-headers` 7.1.6-1 and adds
|
||||
them to `IgnorePkg`; unticking removes the pin and updates the system.
|
||||
- The packages are kept in `/var/cache/steamify/kernel` and reinstalled
|
||||
from there without downloading; else from pacman's cache, the CachyOS
|
||||
archive, the CachyOS mirror, or `PINNED_KERNEL_URL`.
|
||||
- First download source: the `kernel-7.1.6-1` release of this repo, a
|
||||
one-off release that always has these files.
|
||||
- Every file is checked against its SHA-256 (in the script) and its
|
||||
CachyOS signature before it's installed; a bad file is deleted.
|
||||
- Installed kernel headers are no longer updated by the wizard, so the
|
||||
pinned kernel keeps matching headers.
|
||||
- The BIOS item moves down one (9 on a Steam Machine).
|
||||
- **docs: Shorter README, with a Steam Machine section**
|
||||
- How it works moved to `TECHNICAL.md`, problems to `TROUBLESHOOTING.md`;
|
||||
the README links to them and to the files it mentions.
|
||||
- **feat: No more KDE wallet password prompts in single user mode**
|
||||
- Uses Valve's empty, password-less wallet, like SteamOS; apps such as
|
||||
Brave no longer ask for the wallet password after autologin.
|
||||
- Your own wallet is moved to `kdewallet.kwl.bak-steamify` and comes back,
|
||||
unchanged, when single user mode is off; the single user wallet is kept
|
||||
and reused next time.
|
||||
- Moved from the SteamOS theme, where it only helped without any wallet.
|
||||
- **fix: No second sudo password prompt while installing yay**
|
||||
- `makepkg -si` installs with `sudo -k`, which forgets the cached password;
|
||||
yay is now built with makepkg and installed with `sudo pacman -U`.
|
||||
- **docs: LICENSE.md** with the MIT license the README already named.
|
||||
- **chore: No more `setup-gamescope-boot.sh` release asset**
|
||||
- Only `steamify.sh` is published; the old name from before 0.9.0 is gone.
|
||||
|
||||
## 0.10.0 - 2026-09-24
|
||||
|
||||
- `0f503dd` **feat: Boot into gamescope or the desktop**
|
||||
- New menu row under the conversion, "Boot into: [gamescope] / desktop";
|
||||
gamescope stays the default and is never changed on a first run.
|
||||
- Desktop: `steamify-boot-desktop.service` sets the autologin session to
|
||||
Plasma at every boot, before the login manager starts; Return to Gaming
|
||||
Mode and Steam's Switch to Desktop keep working. Back to gamescope removes
|
||||
it. Choosing desktop turns the conversion on; turning the conversion off
|
||||
resets it to gamescope.
|
||||
- The later menu items move down one (BIOS is now 8 on a Steam Machine).
|
||||
- `4895de3` **feat: "Boot into" is a sub-option of the conversion**
|
||||
- Shown indented under the SteamOS conversion, and only while it's ticked.
|
||||
- `bd90e29` **feat: Left/Right arrows choose on the "Boot into" row**
|
||||
- Left = gamescope, right = desktop (Space still switches); the row says
|
||||
"←/→ choose" and the key help lists Left/Right.
|
||||
- **fix: Messages say which way it boots**
|
||||
- "This will: boot into: gamescope (from the next boot)" instead of "turn
|
||||
off: Boot into the desktop instead of gaming mode", and the same in the
|
||||
progress line and the "Done" overview.
|
||||
|
||||
## 0.9.1 - 2026-09-24 (#10)
|
||||
|
||||
- `2e107e5` **fix: LED driver for every kernel on a fresh install**
|
||||
- The DKMS override was written to `/etc/dkms` before `dkms` was installed,
|
||||
when that folder doesn't exist yet, so it silently failed and the build
|
||||
for other kernels (e.g. LTS) failed as in 0.6. The folder is created first
|
||||
now, and a failed write stops with an error.
|
||||
- **feat: Restart question on quit is "Restart now? [Y/n]"**
|
||||
- `5ab83d6` **feat: Restart question on quit is "Restart now? [Y/n]"**
|
||||
- When something needs a restart, `q` asks with yes as the default; `n` goes
|
||||
back to the menu and the next `q` asks again. Ctrl+C quits without
|
||||
restarting; scripted input that runs out never restarts.
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2026 Rick Peters
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
The kernel packages in the `kernel-7.1.6-1` release are unmodified CachyOS
|
||||
packages under their own license (GPL-2.0); see that release for the source.
|
||||
@@ -5,30 +5,60 @@ into Steam's Big Picture (gamescope), and you can switch to the KDE Plasma
|
||||
desktop and back whenever you like - just like on a Steam Deck or Valve's
|
||||
Steam Machine.
|
||||
|
||||
- **One command**, then a simple checklist: pick what you want.
|
||||
- **Everything is undoable**: untick something and it's put back the way it was.
|
||||
- **Made for the Steam Machine**, and works on any CachyOS KDE desktop PC.
|
||||
|
||||
## What you get
|
||||
|
||||
The wizard is a menu: pick what you want, and it turns each part on or off.
|
||||
Everything you turn off is put back the way it was.
|
||||
|
||||
1. **SteamOS conversion** - boots straight into gaming mode, **Switch to
|
||||
Desktop** in Steam works and so does going back (the **Return to Gaming
|
||||
Mode** icon on the desktop), you're back in gaming mode after a restart,
|
||||
and Steam's on-screen keyboard (Steam + X) also works on the desktop.
|
||||
2. **SteamOS theme** - the Vapor look for the desktop (CachyOS's
|
||||
`cachyos-vapor` package).
|
||||
1. **SteamOS conversion** - boots straight into gaming mode. **Switch to
|
||||
Desktop** in Steam works, and so does going back (the **Return to Gaming
|
||||
Mode** icon on the desktop). Steam's on-screen keyboard (Steam + X) works
|
||||
on the desktop too.
|
||||
- **Boot into: gamescope / desktop** - where the PC starts. Gaming mode is
|
||||
the default.
|
||||
2. **SteamOS theme** - the Vapor look of SteamOS for the desktop, with
|
||||
**Add to Steam** in right-click menus
|
||||
([details](TECHNICAL.md#steamos-desktop-look)).
|
||||
3. **Steam Deck/Machine icons** - Steam Deck button icons in gaming mode.
|
||||
4. **Single user mode** - like SteamOS: never a login or lock screen, no
|
||||
user switching or logging out. Typing a password with a controller is no
|
||||
fun. (Needs 1.)
|
||||
5. **Steamify shortcut** - a "Steamify CachyOS" icon on the desktop and in
|
||||
the app launcher (the Steam logo with a gear) that opens the newest version
|
||||
of Steamify in Konsole, so you don't need the install command again.
|
||||
6. **Steam Machine support** - only shown on a Valve Steam Machine: the
|
||||
driver for the front LED bar, and the hardware settings in Steam (fan,
|
||||
TV control over HDMI-CEC).
|
||||
7. **Update BIOS** - only on a Steam Machine, opt-in and at your own risk:
|
||||
installs the newest Steam Machine BIOS from Valve (see
|
||||
[BIOS updates](#bios-updates-steam-machine)).
|
||||
4. **Single user mode** - like SteamOS: never a login or lock screen, and no
|
||||
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.
|
||||
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
|
||||
the Steam Machine) or a USB CEC adapter; most graphics cards, NVIDIA
|
||||
included, don't have it. See [CEC.md](CEC.md) for which PCs do, and how to
|
||||
add it to your own build ([details](TECHNICAL.md#hdmi-cec)).
|
||||
|
||||
## Steam Machine
|
||||
|
||||
What Steamify can do on a Valve Steam Machine:
|
||||
|
||||
- **HDMI-CEC** - ticked by default here, as on SteamOS: use Steam with your
|
||||
TV's remote, and the TV turns on and off with the Steam Machine
|
||||
([details](TECHNICAL.md#hdmi-cec)).
|
||||
- **Steam Machine support** - the front **LED bar** works, Steam's
|
||||
**hardware settings** (fan, performance) work, and the power
|
||||
button puts it to sleep like a console.
|
||||
- **Pin the kernel to 7.1.6-1** - on by default: with newer CachyOS kernels
|
||||
the Steam Machine reboots instead of shutting down. Untick it once CachyOS
|
||||
fixes that. The kernel comes from its own
|
||||
[release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1),
|
||||
checked against CachyOS's signature
|
||||
([details](TECHNICAL.md#kernel-pin-steam-machine)).
|
||||
- **HDMI refresh boost** - only with the pinned kernel, never ticked by
|
||||
default: gets the highest refresh rate your HDMI display runs at the
|
||||
desktop resolution (e.g. 110 Hz instead of 60 on a 3440x1440 monitor).
|
||||
Each step is shown and you confirm it; no answer within 15 s switches
|
||||
back. For more (e.g. 175 Hz), use DisplayPort
|
||||
([details](TECHNICAL.md#hdmi-refresh-boost-steam-machine)).
|
||||
- **Update BIOS** - installs Valve's newest Steam Machine BIOS. Never ticked
|
||||
by default, at your own risk, and only after two warnings
|
||||
([details](TECHNICAL.md#bios-updates-steam-machine)).
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -43,355 +73,60 @@ Open **Konsole** on your Plasma desktop and run:
|
||||
curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash
|
||||
```
|
||||
|
||||
(Before 0.9.0 the script was called `setup-gamescope-boot.sh`; that name
|
||||
still works in the download URL.) That downloads and runs the latest
|
||||
single-file version, and works in any
|
||||
shell, including fish (CachyOS's default). Prefer to keep a copy, or to look
|
||||
at the script first? Clone the repository instead:
|
||||
Prefer to look at the script first? Clone the repository and run
|
||||
[`./steamify.sh`](steamify.sh) (keep the whole folder: it needs
|
||||
[`lib/`](lib/)).
|
||||
|
||||
```bash
|
||||
git clone https://github.com/theupriser/steamify-cachyos.git
|
||||
cd steamify-cachyos
|
||||
./steamify.sh
|
||||
```
|
||||
|
||||
Run it as yourself, not as root. You'll see a checklist:
|
||||
You'll see a checklist:
|
||||
|
||||
```
|
||||
[x] SteamOS conversion: boot into gaming mode, Steam on the desktop (now: off)
|
||||
└ Boot into: [gamescope] desktop
|
||||
> [x] Install SteamOS theme: Vapor look (cachyos-vapor) (now: off)
|
||||
[x] Install Steam Deck/Machine icons: Deck button icons in gaming mode (now: off)
|
||||
[x] Single user mode: no password, lock screen or log out (SDDM) (now: off)
|
||||
[x] Steamify shortcut: desktop icon to run this again (now: off)
|
||||
|
||||
Kernels (> = running; controller = Steam controller driver, LEDs = LED bar driver built)
|
||||
6.18.52-1-cachyos-lts linux-cachyos-lts headers yes controller yes
|
||||
> 7.2.7-1-cachyos linux-cachyos headers yes controller yes
|
||||
|
||||
Up/Down move Space select Enter run a run + re-apply what's on q quit
|
||||
Up/Down move Space select Left/Right choose Enter run
|
||||
a run + re-apply what's on q quit
|
||||
```
|
||||
|
||||
Move with the **arrow keys**, tick or untick with **Space**, and press
|
||||
**Enter** to run. "now:" shows what's on at the moment. Below the list, every
|
||||
installed kernel is shown with its headers and Steam controller driver; on a
|
||||
Steam Machine also whether the LED bar driver is built for it, and whether
|
||||
it's loaded right now, so a kernel update is easy to check. The wizard then
|
||||
shows what it will change, asks your password once, and at the end offers
|
||||
to restart (needed for changes to how the PC starts).
|
||||
Tick with **Space**, run with **Enter**. It shows what it will change, asks
|
||||
your password once, and offers to restart at the end. Below the list you see
|
||||
every installed kernel, so after an update it's easy to check that
|
||||
everything is in place.
|
||||
|
||||
After each run the menu comes back with the new state, so you can change more
|
||||
in one go; quit with **q**. When something needs a restart, you choose
|
||||
between going back to the menu and restarting now. Quitting then asks
|
||||
"Restart now? [Y/n]": **n** takes you back to the menu (and the next **q**
|
||||
asks again); Ctrl+C quits without restarting.
|
||||
|
||||
Run it again whenever you like - to change your choices, to turn things off
|
||||
again, or after a CachyOS update (press `a` in the menu to re-apply
|
||||
everything that's on).
|
||||
|
||||
If you cloned the repository, keep the whole folder: the script needs the
|
||||
files in `lib/` next to it.
|
||||
Run it again whenever you like: to change your choices, or after a CachyOS
|
||||
update (press `a` to re-apply what's on).
|
||||
|
||||
## Using it
|
||||
|
||||
- **To the desktop:** in gaming mode, open the Steam menu and choose
|
||||
**Power > Switch to Desktop**.
|
||||
- **Back to gaming mode:** double-click **Return to Gaming Mode** on the
|
||||
desktop (or log out, if you're not using single user mode).
|
||||
- After a restart you always start in gaming mode.
|
||||
desktop.
|
||||
- **Remove it all:** run the wizard and untick everything. You get the
|
||||
normal CachyOS login screen back; Steam stays installed.
|
||||
|
||||
Prefer to decide yourself where your PC starts? Run one of these in Konsole:
|
||||
Prefer to start where you left off last time?
|
||||
|
||||
```bash
|
||||
steamos-session-select persistent # start where you left off last time
|
||||
steamos-session-select oneshot # always start in gaming mode (default)
|
||||
```
|
||||
|
||||
## If something goes wrong
|
||||
## Something wrong?
|
||||
|
||||
**Gaming mode shows a black screen or keeps restarting.** Press
|
||||
**Ctrl+Alt+F3**, log in with your username and password, and run:
|
||||
Stuck on a black screen in gaming mode: press **Ctrl+Alt+F3**, log in, and run
|
||||
|
||||
```bash
|
||||
sudo /usr/lib/steamos/steam-set-session plasma.desktop && sudo systemctl restart display-manager
|
||||
```
|
||||
|
||||
That takes you back to the desktop.
|
||||
|
||||
**The LED bar on the Steam Machine stays dark.** Restart the PC once more; a
|
||||
freshly installed driver often needs a reboot. Still dark? See
|
||||
[Front LED bar](#front-led-bar-steam-machine) under technical details.
|
||||
|
||||
**Undo something:** run the wizard again and untick it.
|
||||
|
||||
---
|
||||
|
||||
## Technical details
|
||||
|
||||
### How turning things on and off works
|
||||
|
||||
At startup every component checks whether it is on (`*_status` in `lib/`).
|
||||
You choose; the wizard then turns off what you unticked (in reverse order)
|
||||
and turns on what you ticked. To be able to undo:
|
||||
|
||||
- **System files** are backed up once, next to the original, with a
|
||||
`.bak-gamescope-wizard` suffix, and restored when you turn the component
|
||||
off.
|
||||
- **KDE settings** in your home directory are recorded with their previous
|
||||
value the first time the wizard changes them, in an undo journal under
|
||||
`~/.local/state/cachyos-gamescope-boot/`. Turning the component off writes
|
||||
the old values back (or removes keys that didn't exist before). Setups
|
||||
made by older versions of the script, without a journal, fall back to
|
||||
KDE's defaults.
|
||||
- **Packages** installed for the conversion (Steam, gamescope-session, ...)
|
||||
are kept when you turn it off; Steam Machine support removes its own.
|
||||
|
||||
### Single user mode: SDDM, no locking
|
||||
|
||||
Turning it on switches to **SDDM** and turns off everything that asks for a
|
||||
password or another user, per user: `action/lock_screen`, `switch_user` and
|
||||
`start_new_session` restrictions in `kdeglobals`, no automatic locking
|
||||
(`kscreenlockerrc`), Meta+L and Ctrl+Alt+Del unbound, and the launcher shows
|
||||
only Sleep / Restart / Shut Down (kickoff `primaryActions=3`), which hides
|
||||
the Session dropdown with Log Out. (Restricting `action/logout` would also
|
||||
hide Restart and Shut Down.) Turning it off restores all of that and moves
|
||||
the conversion back to plasma-login-manager.
|
||||
|
||||
**SDDM** is what SteamOS uses, and CachyOS's `steam-set-session` supports it
|
||||
directly: it writes `/etc/sddm.conf.d/zz-steamos-autologin.conf`, which SDDM
|
||||
honours. The script installs and enables `sddm` (active from the next boot),
|
||||
writes `User=`, `Session=` and `Relogin=true` to
|
||||
`/etc/sddm.conf.d/10-gamescope-autologin.conf`, and removes any
|
||||
`[Autologin]` from `/etc/sddm.conf` (read last, so it would override both).
|
||||
No sync bridge or sudoers rule is needed; the shortcut just runs
|
||||
`steamos-session-select gamescope`, which logs out, and `Relogin=true` logs
|
||||
straight back in to gamescope.
|
||||
|
||||
Without single user mode, the conversion uses **plasma-login-manager**
|
||||
(CachyOS's default since March 2026), which needs the workarounds below.
|
||||
|
||||
### Why this is needed (plasma-login-manager)
|
||||
|
||||
CachyOS's `gamescope-session-cachyos` package ships `steamos-session-select`,
|
||||
which is meant to work exactly like it does on real SteamOS. Since the March
|
||||
2026 release, CachyOS uses `plasma-login-manager` instead of SDDM, and three
|
||||
things get in the way:
|
||||
|
||||
1. **No `User=` written to the autologin config.**
|
||||
`steam-set-session` (called by `steamos-session-select`) only writes a
|
||||
`Session=` key. `plasma-login-manager` needs both `Session=` and `User=`
|
||||
to autologin at all; without `User=` it shows the greeter every boot.
|
||||
|
||||
2. **Missing `/etc/plasmalogin.conf.d` directory.**
|
||||
Without it, `steam-set-session` fails, and Steam's "Switch to Desktop"
|
||||
hangs forever. *(Fixed upstream in `gamescope-session-cachyos` 1.1.6; the
|
||||
script still creates the directory for older versions.)*
|
||||
|
||||
3. **The base `/etc/plasmalogin.conf` wins over conf.d, and CachyOS ships it
|
||||
with `Session=plasma`.**
|
||||
Every tool that changes sessions (`steamos-session-select`, Steam's power
|
||||
menu, and `cachyos-gamescope-autologin.service`, which resets you to
|
||||
gamescope after a desktop session) only writes
|
||||
`/etc/plasmalogin.conf.d/zz-steamos-autologin.conf`. The base file takes
|
||||
priority, so none of those switches stick.
|
||||
|
||||
The script sets `Session=`, `User=` and `Relogin=true` in the base config,
|
||||
and installs a small systemd path watcher that copies whatever CachyOS's
|
||||
tools write to the conf.d fragment into the base config.
|
||||
|
||||
### What each component changes
|
||||
|
||||
**SteamOS conversion** (`lib/login-manager.sh`, `lib/steam-desktop.sh`,
|
||||
`lib/desktop-shortcut.sh`):
|
||||
|
||||
- installs missing packages: `gamescope-session-cachyos`, `steam`,
|
||||
`mangohud`, `xterm`, `ttf-liberation`, `wqy-zenhei`, `plasma-keyboard`;
|
||||
- *SDDM (single user mode):* installs/enables SDDM and writes its autologin
|
||||
config;
|
||||
- *plasma-login-manager:* creates `/etc/plasmalogin.conf.d`, backs up and
|
||||
rewrites the `[Autologin]` section of `/etc/plasmalogin.conf`, removes
|
||||
stray `zzz-steamos-autologin*.conf` files from manual troubleshooting
|
||||
(never `zz-steamos-autologin.conf`, which CachyOS's tools own), and
|
||||
installs `/usr/local/bin/sync-steamos-session.sh` plus
|
||||
`sync-steamos-session.path`/`.service`;
|
||||
- `KWIN_IM_SHOW_ALWAYS=1` for the virtual keyboard and a
|
||||
`steam-desktop-autostart` systemd user service that starts Steam silently
|
||||
in Plasma only;
|
||||
- the **Return to Gaming Mode** shortcut; on plasma-login-manager with a
|
||||
narrow sudoers rule (`/etc/sudoers.d/gamescope-session-switch`) so it can
|
||||
restart the login manager without a password prompt.
|
||||
|
||||
Turning it off restores `/etc/plasmalogin.conf` from its backup, removes the
|
||||
SDDM autologin, sync bridge, shortcut, sudoers rule and Steam autostart, and
|
||||
switches back to plasma-login-manager.
|
||||
|
||||
**Steam Deck/Machine icons:** `STEAM_GAMEPADUI_ARGS="-gamepadui -steamos3"`
|
||||
in `~/.config/environment.d/` (and gamescope-session's own environment
|
||||
file), which makes Steam show Steam Deck button glyphs in gaming mode.
|
||||
|
||||
### SteamOS desktop look
|
||||
|
||||
Installs CachyOS's `cachyos-vapor` package (the SteamOS Vapor theme the
|
||||
CachyOS handheld edition uses) from the CachyOS repository and switches your
|
||||
desktop to its Vapor global theme, including its desktop and window layout
|
||||
(like ticking "Desktop and window layout" in System Settings): Vapor colors
|
||||
and Plasma style, the SteamOS panel and launcher icon, and the Steam Deck
|
||||
wallpaper. Run it from the Plasma desktop: applying the layout needs a
|
||||
running Plasma session.
|
||||
|
||||
It also adds the SteamOS desktop extras that `cachyos-vapor` doesn't ship,
|
||||
taken from the newest `steamdeck-kde-presets` on Valve's SteamOS mirror
|
||||
(checksum verified) and installed to `/usr/local`:
|
||||
|
||||
- **Add to Steam** in the right-click menu of apps, AppImages and `.exe`
|
||||
files, and in the launcher: adds them to Steam as non-Steam games;
|
||||
- **Nested Desktop**: add it to Steam from the launcher, then start it in
|
||||
gaming mode for a Plasma desktop inside gaming mode;
|
||||
- the SteamOS **Return to Gaming Mode** icon (Steam logo with a return arrow);
|
||||
- a window rule that keeps the **Steam keyboard** above other windows;
|
||||
- an empty, password-less **KWallet**, only if you don't have a wallet yet,
|
||||
so nothing asks for a wallet password after autologin.
|
||||
|
||||
Turning it off restores your previous look and panel layout, and removes `cachyos-vapor` again
|
||||
if the wizard installed it (and nothing else, like `cachyos-handheld`, needs it).
|
||||
|
||||
### Front LED bar (Steam Machine)
|
||||
|
||||
The **Steam Machine support** component, only shown on Fremont hardware
|
||||
(DMI `Valve`/`Fremont`, or `OEM`/`F7F` on early units):
|
||||
|
||||
- installs an AUR helper (`yay`) if neither `yay` nor `paru` is present;
|
||||
- installs the kernel headers for every installed kernel;
|
||||
- installs `leds-valve-dkms-git` from the AUR - Valve's own driver from the
|
||||
SteamOS kernel - builds it for every installed kernel (so the LTS kernel
|
||||
works too), and loads it at every boot (`/etc/modules-load.d/leds-valve.conf`);
|
||||
- adds a udev rule (`/etc/udev/rules.d/70-valve-leds-user.rules`) that gives
|
||||
your user the LED files, so Steam, which runs as you, can drive the bar;
|
||||
- console-like power button: it puts the machine to sleep, and it never
|
||||
suspends by itself on mains power (the launcher's Shut Down still shuts down);
|
||||
- installs and enables `steamos-manager`, the service Steam in gaming mode
|
||||
uses for hardware settings (fan, HDMI-CEC, performance); it recognises the
|
||||
Steam Machine from its DMI data.
|
||||
|
||||
Turning it off removes all of that again (the AUR helper is kept).
|
||||
|
||||
The LEDs appear as `/sys/class/leds/valve-leds*`. The driver's Makefile
|
||||
builds against the running kernel (`uname -r`) instead of the kernel DKMS
|
||||
builds for, so the wizard adds a DKMS override
|
||||
(`/etc/dkms/leds-valve-dkms.conf`) that passes DKMS's target kernel in. With
|
||||
it, DKMS rebuilds the driver whenever a kernel or its headers are installed
|
||||
or upgraded. A newly added kernel doesn't come with its headers, so
|
||||
`ensure-kernel-headers.service` checks at every boot and installs any
|
||||
missing `-headers` package, which makes DKMS build the driver for it.
|
||||
|
||||
Troubleshooting:
|
||||
|
||||
```bash
|
||||
dkms status
|
||||
ls -l /sys/class/leds/valve-leds*/
|
||||
sudo dmesg | grep -i valve
|
||||
cat /var/lib/dkms/leds-valve-dkms/0.1/build/make.log
|
||||
journalctl --user -b | grep -i led
|
||||
```
|
||||
|
||||
### BIOS updates (Steam Machine)
|
||||
|
||||
The **Update BIOS** item is only shown on a Steam Machine and is never ticked
|
||||
by default. It shows the BIOS version you have now and the newest one Valve
|
||||
ships (the `.cab` file in its `fremont-hw-support` package, looked up on
|
||||
Valve's SteamOS mirror):
|
||||
|
||||
```
|
||||
[ ] Update BIOS: now F7F0107, newest F7F0108 (own risk) (opt-in, runs once)
|
||||
```
|
||||
|
||||
It can only be ticked when Valve has a newer BIOS than yours; when you're up
|
||||
to date, when the newest version can't be looked up (offline), or when an
|
||||
update is already waiting for a restart, it's greyed out.
|
||||
|
||||
When you run it, the wizard:
|
||||
|
||||
1. downloads Valve's package and checks its **SHA-256** against Valve's
|
||||
repository, so it's exactly Valve's file;
|
||||
2. asks **fwupd** whether the firmware is for this very machine (fwupd compares
|
||||
the firmware's hardware IDs with the device) and stops if it isn't;
|
||||
3. shows a large red **warning** with the current and the new version, and asks
|
||||
whether you understand the risks (default: no);
|
||||
4. shows the warning **again** and only continues when you type `UPDATE`;
|
||||
5. hands the firmware to fwupd, which writes it during the **next restart**:
|
||||
choose "restart now" or restart later yourself.
|
||||
|
||||
**At your own risk:** a failed or interrupted BIOS update can leave the machine
|
||||
unable to start. Keep it on mains power, and never turn off the power, unplug
|
||||
it or press the power button while it updates, including during the restart;
|
||||
the screen can stay black for several minutes.
|
||||
|
||||
To walk through it without flashing anything, run the wizard with
|
||||
`WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both
|
||||
warnings, skips fwupd's device check, and only prints the install command.
|
||||
|
||||
### Manual session control
|
||||
|
||||
```bash
|
||||
steamos-session-select gamescope # switch to gamescope right now
|
||||
steamos-session-select plasma # switch to desktop right now
|
||||
steamos-session-select persistent # remember the last-used session across reboots
|
||||
steamos-session-select oneshot # always start in gamescope (default, like a Deck)
|
||||
```
|
||||
|
||||
With `Relogin=true`, a gamescope session that fails to start is restarted
|
||||
immediately, which can turn into a loop - hence the Ctrl+Alt+F3 escape above.
|
||||
|
||||
### Removing everything
|
||||
|
||||
Run the wizard and untick everything. Afterwards the PC boots to the normal
|
||||
CachyOS login screen again; packages installed for the conversion (Steam,
|
||||
gamescope-session, ...) stay installed.
|
||||
|
||||
### Project layout
|
||||
|
||||
| File | Responsibility |
|
||||
|---|---|
|
||||
| `steamify.sh` | Entry point: checks, menu, apply, summary, restart |
|
||||
| `lib/menu.sh` | The menu: detect, toggle, plan and apply changes |
|
||||
| `lib/state.sh` | Undo journal for KDE settings (`kset`/`krevert`) |
|
||||
| `lib/common.sh` | Output helpers, prompts, backups, plasmashell handling |
|
||||
| `lib/packages.sh` | Required packages, AUR helper (yay/paru) |
|
||||
| `lib/login-manager.sh` | SteamOS conversion: SDDM or plasmalogin autologin, sync bridge |
|
||||
| `lib/steam-desktop.sh` | Steam in the Plasma session; Steam Deck/Machine icons |
|
||||
| `lib/desktop-shortcut.sh` | Return to Gaming Mode shortcut and its sudoers rule |
|
||||
| `lib/vapor-theme.sh` | SteamOS theme: installs and switches to `cachyos-vapor` |
|
||||
| `lib/steamos-extras.sh` | SteamOS desktop extras from Valve's package (Add to Steam, Nested Desktop, icon, keyboard rule, KWallet) |
|
||||
| `lib/single-user.sh` | Single user mode: no lock screen, user switching or log out |
|
||||
| `lib/wizard-shortcut.sh` | Steamify shortcut: desktop icon and launcher entry that run the newest release |
|
||||
| `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd |
|
||||
| `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager |
|
||||
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) |
|
||||
| `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` |
|
||||
|
||||
The single-file version is generated: on every push to `main`, GitHub
|
||||
Actions runs `.github/tools/bundle.sh`, checks the result with `bash -n` and
|
||||
shellcheck, and publishes it as a release per version (tag `v<VERSION>`,
|
||||
never overwritten; the newest is marked latest, and the `latest` tag follows
|
||||
it). It inlines
|
||||
`lib/*.sh` and wraps the entry point in `main()`, so bash has read the whole
|
||||
file before anything runs; when stdin is a pipe (`curl | bash`) it reattaches
|
||||
the terminal for the menu (set `WIZARD_KEEP_STDIN=1` to keep piped input).
|
||||
|
||||
Notes for contributors and AI coding agents are in [`AGENTS.md`](AGENTS.md).
|
||||
|
||||
### Caveats
|
||||
|
||||
- This works around CachyOS/`plasma-login-manager` behavior as of September
|
||||
2026. If CachyOS fixes `steam-set-session` upstream, parts of this script
|
||||
become unnecessary - please open an issue or PR if you notice that.
|
||||
- Tested on a Valve Steam Machine running CachyOS Desktop edition. It should
|
||||
work on any CachyOS desktop install using `plasma-login-manager`, but
|
||||
hasn't been tested on Steam Deck/Legion Go hardware.
|
||||
|
||||
### Related upstream reports
|
||||
|
||||
- [CachyOS/gamescope-session#9](https://github.com/CachyOS/gamescope-session/issues/9) - "Switch to Desktop" hang due to missing `/etc/plasmalogin.conf.d`
|
||||
More in [TROUBLESHOOTING.md](TROUBLESHOOTING.md). How it all works:
|
||||
[TECHNICAL.md](TECHNICAL.md). Changes per version: [CHANGELOG.md](CHANGELOG.md).
|
||||
Contributing (or an AI agent)? Read [AGENTS.md](AGENTS.md).
|
||||
|
||||
## License
|
||||
|
||||
MIT - do whatever you want with it.
|
||||
MIT - do whatever you want with it. See [LICENSE.md](LICENSE.md).
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
# Technical details
|
||||
|
||||
How Steamify CachyOS works under the hood. For using it, see
|
||||
[README.md](README.md); for problems, [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||
|
||||
## How turning things on and off works
|
||||
|
||||
At startup every component checks whether it is on (`*_status` in `lib/`).
|
||||
You choose; the wizard then turns off what you unticked (in reverse order)
|
||||
and turns on what you ticked. To be able to undo:
|
||||
|
||||
- **System files** are backed up once, next to the original, with a
|
||||
`.bak-gamescope-wizard` suffix, and restored when you turn the component
|
||||
off.
|
||||
- **KDE settings** in your home directory are recorded with their previous
|
||||
value the first time the wizard changes them, in an undo journal under
|
||||
`~/.local/state/cachyos-gamescope-boot/`. Turning the component off writes
|
||||
the old values back (or removes keys that didn't exist before). Setups
|
||||
made by older versions of the script, without a journal, fall back to
|
||||
KDE's defaults.
|
||||
- **Packages** installed for the conversion (Steam, gamescope-session, ...)
|
||||
are kept when you turn it off; Steam Machine support removes its own.
|
||||
|
||||
## Single user mode: SDDM, no locking
|
||||
|
||||
Turning it on switches to **SDDM** and turns off everything that asks for a
|
||||
password or another user, per user: `action/lock_screen`, `switch_user` and
|
||||
`start_new_session` restrictions in `kdeglobals`, no automatic locking
|
||||
(`kscreenlockerrc`), Meta+L and Ctrl+Alt+Del unbound, and the launcher shows
|
||||
only Sleep / Restart / Shut Down (kickoff `primaryActions=3`), which hides
|
||||
the Session dropdown with Log Out. (Restricting `action/logout` would also
|
||||
hide Restart and Shut Down.) Turning it off restores all of that and moves
|
||||
the conversion back to plasma-login-manager.
|
||||
|
||||
**KDE wallet.** KDE normally unlocks your wallet with the password you log in
|
||||
with; with autologin nobody types it, so apps that keep passwords in it (Brave,
|
||||
for example) ask for the wallet password. Single user mode does what SteamOS
|
||||
does: it uses Valve's empty wallet without a password (from
|
||||
`steamdeck-kde-presets`, checksum verified). Your own wallet in
|
||||
`~/.local/share/kwalletd/` is moved to `kdewallet.kwl.bak-steamify` (and
|
||||
`.salt`). Turning single user mode off puts it back, unchanged; the single
|
||||
user wallet, with anything saved in it meanwhile, is kept as
|
||||
`kdewallet.kwl.steamify-single-user` and used again the next time it's on.
|
||||
Passwords aren't shared between the two wallets.
|
||||
|
||||
**SDDM** is what SteamOS uses, and CachyOS's `steam-set-session` supports it
|
||||
directly: it writes `/etc/sddm.conf.d/zz-steamos-autologin.conf`, which SDDM
|
||||
honours. The script installs and enables `sddm` (active from the next boot),
|
||||
writes `User=`, `Session=` and `Relogin=true` to
|
||||
`/etc/sddm.conf.d/10-gamescope-autologin.conf`, and removes any
|
||||
`[Autologin]` from `/etc/sddm.conf` (read last, so it would override both).
|
||||
No sync bridge or sudoers rule is needed; the shortcut just runs
|
||||
`steamos-session-select gamescope`, which logs out, and `Relogin=true` logs
|
||||
straight back in to gamescope.
|
||||
|
||||
Without single user mode, the conversion uses **plasma-login-manager**
|
||||
(CachyOS's default since March 2026), which needs the workarounds below.
|
||||
|
||||
## Why this is needed (plasma-login-manager)
|
||||
|
||||
CachyOS's `gamescope-session-cachyos` package ships `steamos-session-select`,
|
||||
which is meant to work exactly like it does on real SteamOS. Since the March
|
||||
2026 release, CachyOS uses `plasma-login-manager` instead of SDDM, and three
|
||||
things get in the way:
|
||||
|
||||
1. **No `User=` written to the autologin config.**
|
||||
`steam-set-session` (called by `steamos-session-select`) only writes a
|
||||
`Session=` key. `plasma-login-manager` needs both `Session=` and `User=`
|
||||
to autologin at all; without `User=` it shows the greeter every boot.
|
||||
|
||||
2. **Missing `/etc/plasmalogin.conf.d` directory.**
|
||||
Without it, `steam-set-session` fails, and Steam's "Switch to Desktop"
|
||||
hangs forever. *(Fixed upstream in `gamescope-session-cachyos` 1.1.6; the
|
||||
script still creates the directory for older versions.)*
|
||||
|
||||
3. **The base `/etc/plasmalogin.conf` wins over conf.d, and CachyOS ships it
|
||||
with `Session=plasma`.**
|
||||
Every tool that changes sessions (`steamos-session-select`, Steam's power
|
||||
menu, and `cachyos-gamescope-autologin.service`, which resets you to
|
||||
gamescope after a desktop session) only writes
|
||||
`/etc/plasmalogin.conf.d/zz-steamos-autologin.conf`. The base file takes
|
||||
priority, so none of those switches stick.
|
||||
|
||||
The script sets `Session=`, `User=` and `Relogin=true` in the base config,
|
||||
and installs a small systemd path watcher that copies whatever CachyOS's
|
||||
tools write to the conf.d fragment into the base config.
|
||||
|
||||
## What each component changes
|
||||
|
||||
**SteamOS conversion** (`lib/login-manager.sh`, `lib/steam-desktop.sh`,
|
||||
`lib/desktop-shortcut.sh`):
|
||||
|
||||
- installs missing packages: `gamescope-session-cachyos`, `steam`,
|
||||
`mangohud`, `xterm`, `ttf-liberation`, `wqy-zenhei`, `plasma-keyboard`;
|
||||
- *SDDM (single user mode):* installs/enables SDDM and writes its autologin
|
||||
config;
|
||||
- *plasma-login-manager:* creates `/etc/plasmalogin.conf.d`, backs up and
|
||||
rewrites the `[Autologin]` section of `/etc/plasmalogin.conf`, removes
|
||||
stray `zzz-steamos-autologin*.conf` files from manual troubleshooting
|
||||
(never `zz-steamos-autologin.conf`, which CachyOS's tools own), and
|
||||
installs `/usr/local/bin/sync-steamos-session.sh` plus
|
||||
`sync-steamos-session.path`/`.service`;
|
||||
- `KWIN_IM_SHOW_ALWAYS=1` for the virtual keyboard and a
|
||||
`steam-desktop-autostart` systemd user service that starts Steam silently
|
||||
in Plasma only;
|
||||
- the **Return to Gaming Mode** shortcut; on plasma-login-manager with a
|
||||
narrow sudoers rule (`/etc/sudoers.d/gamescope-session-switch`) so it can
|
||||
restart the login manager without a password prompt.
|
||||
|
||||
Turning it off restores `/etc/plasmalogin.conf` from its backup, removes the
|
||||
SDDM autologin, sync bridge, shortcut, sudoers rule and Steam autostart, and
|
||||
switches back to plasma-login-manager.
|
||||
|
||||
**Steam Deck/Machine icons:** `STEAM_GAMEPADUI_ARGS="-gamepadui -steamos3"`
|
||||
in `~/.config/environment.d/` (and gamescope-session's own environment
|
||||
file), which makes Steam show Steam Deck button glyphs in gaming mode.
|
||||
|
||||
## SteamOS desktop look
|
||||
|
||||
Installs CachyOS's `cachyos-vapor` package (the SteamOS Vapor theme the
|
||||
CachyOS handheld edition uses) from the CachyOS repository and switches your
|
||||
desktop to its Vapor global theme, including its desktop and window layout
|
||||
(like ticking "Desktop and window layout" in System Settings): Vapor colors
|
||||
and Plasma style, the SteamOS panel and launcher icon, and the Steam Deck
|
||||
wallpaper. Run it from the Plasma desktop: applying the layout needs a
|
||||
running Plasma session.
|
||||
|
||||
It also adds the SteamOS desktop extras that `cachyos-vapor` doesn't ship,
|
||||
taken from the newest `steamdeck-kde-presets` on Valve's SteamOS mirror
|
||||
(checksum verified) and installed to `/usr/local`:
|
||||
|
||||
- **Add to Steam** in the right-click menu of apps, AppImages and `.exe`
|
||||
files, and in the launcher: adds them to Steam as non-Steam games;
|
||||
- **Nested Desktop**: add it to Steam from the launcher, then start it in
|
||||
gaming mode for a Plasma desktop inside gaming mode;
|
||||
- the SteamOS **Return to Gaming Mode** icon (Steam logo with a return arrow);
|
||||
- a window rule that keeps the **Steam keyboard** above other windows.
|
||||
|
||||
Turning it off restores your previous look and panel layout, and removes `cachyos-vapor` again
|
||||
if the wizard installed it (and nothing else, like `cachyos-handheld`, needs it).
|
||||
|
||||
## Front LED bar (Steam Machine)
|
||||
|
||||
The **Steam Machine support** component, only shown on Fremont hardware
|
||||
(DMI `Valve`/`Fremont`, or `OEM`/`F7F` on early units):
|
||||
|
||||
- installs an AUR helper (`yay`) if neither `yay` nor `paru` is present;
|
||||
- installs the kernel headers for every installed kernel;
|
||||
- installs `leds-valve-dkms-git` from the AUR - Valve's own driver from the
|
||||
SteamOS kernel - builds it for every installed kernel (so the LTS kernel
|
||||
works too), and loads it at every boot (`/etc/modules-load.d/leds-valve.conf`);
|
||||
- adds a udev rule (`/etc/udev/rules.d/70-valve-leds-user.rules`) that gives
|
||||
your user the LED files, so Steam, which runs as you, can drive the bar;
|
||||
- console-like power button: it puts the machine to sleep, and it never
|
||||
suspends by itself on mains power (the launcher's Shut Down still shuts down);
|
||||
- installs and enables `steamos-manager`, the service Steam in gaming mode
|
||||
uses for hardware settings (fan, performance, and the HDMI-CEC settings
|
||||
when [HDMI-CEC](#hdmi-cec) is on); it recognises the Steam Machine from its
|
||||
DMI data.
|
||||
|
||||
Turning it off removes all of that again (the AUR helper is kept).
|
||||
|
||||
The LEDs appear as `/sys/class/leds/valve-leds*`. The driver's Makefile
|
||||
builds against the running kernel (`uname -r`) instead of the kernel DKMS
|
||||
builds for, so the wizard adds a DKMS override
|
||||
(`/etc/dkms/leds-valve-dkms.conf`) that passes DKMS's target kernel in. With
|
||||
it, DKMS rebuilds the driver whenever a kernel or its headers are installed
|
||||
or upgraded. A newly added kernel doesn't come with its headers, so
|
||||
`ensure-kernel-headers.service` checks at every boot and installs any
|
||||
missing `-headers` package, which makes DKMS build the driver for it.
|
||||
|
||||
## HDMI-CEC
|
||||
|
||||
The **HDMI-CEC** item (on every PC; ticked by default only on a Steam
|
||||
Machine) installs
|
||||
Valve's CEC stack from its SteamOS `holo` repository, the newest `holo-X.Y`
|
||||
on `steamdeck-packages.steamos.cloud`, each package checked against the
|
||||
SHA-256 in Valve's package index:
|
||||
|
||||
- `cecd`, Valve's CEC daemon (a user service started with the desktop and
|
||||
gaming mode): TV remote keys become normal key presses (arrows, Enter,
|
||||
Back), and it turns the TV on and off with the PC;
|
||||
- `cec-audio-control`: volume of the TV or receiver;
|
||||
- `inputattach-cec-units` (with `linuxconsole` from CachyOS for
|
||||
`inputattach`): attaches USB CEC adapters (Pulse-Eight, RainShadow).
|
||||
|
||||
It works with any `/dev/cec*`: a GPU that has CEC on its HDMI port (the
|
||||
Steam Machine; the CachyOS kernel has DisplayPort CEC built in) or a USB
|
||||
adapter ([CEC.md](CEC.md) lists which PCs have one). The menu lists the CEC
|
||||
devices found. Also turn on CEC on the TV
|
||||
(Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink). On a Steam Machine
|
||||
`steamos-manager` writes cecd's settings from Steam's (wake the TV, put it to
|
||||
sleep), in `~/.config/cecd/config.d/`, and Steam shows its HDMI-CEC
|
||||
settings; the wizard restarts steamos-manager so that happens right away.
|
||||
CachyOS's gaming mode script (`/usr/lib/steamos/gamescope-session`) sets
|
||||
`STEAM_ENABLE_CEC=0`, which hides those settings; Steam reads the script's
|
||||
variables from `$XDG_RUNTIME_DIR/gamescope-environment`. HDMI-CEC adds a
|
||||
drop-in for `steam-launcher.service`
|
||||
(`/etc/systemd/user/steam-launcher.service.d/10-steamify-cec.conf`) with a
|
||||
second `EnvironmentFile=` (`/etc/steamify/steam-cec.env`,
|
||||
`STEAM_ENABLE_CEC=1`), which overrides it from the next gaming mode start.
|
||||
|
||||
It's experimental: on SteamOS itself CEC can wake the Steam Machine right
|
||||
after it goes to sleep (Samsung TVs,
|
||||
[SteamOS#2626](https://github.com/ValveSoftware/SteamOS/issues/2626)) and
|
||||
upset CEC for the TV's other devices
|
||||
([SteamOS#2817](https://github.com/ValveSoftware/SteamOS/issues/2817)).
|
||||
Turning it off removes the packages again (`linuxconsole` only if the
|
||||
wizard installed it).
|
||||
|
||||
## Kernel pin (Steam Machine)
|
||||
|
||||
With CachyOS kernels newer than 7.1.6 a Steam Machine reboots instead of
|
||||
shutting down. The **Pin the kernel** sub-option (ticked along with Steam
|
||||
Machine support) installs `linux-cachyos` and `linux-cachyos-headers`
|
||||
7.1.6-1 and adds them to `IgnorePkg` in `/etc/pacman.conf`, so updates skip
|
||||
them. DKMS builds the LED driver for it; restart to boot it.
|
||||
|
||||
The packages (and their signatures, which pacman checks) are kept in
|
||||
`/var/cache/steamify/kernel`, so re-applying needs no download. Missing
|
||||
files are taken from pacman's cache, else downloaded from this repo's
|
||||
`kernel-7.1.6-1` release, then `archive.cachyos.org`, then
|
||||
`mirror.cachyos.org` (which only has the current kernel). Every file must
|
||||
match the SHA-256 in the script and have a valid CachyOS signature; a bad
|
||||
one is deleted, so the next run downloads it again. Set `PINNED_KERNEL_URL`
|
||||
to a directory URL with the files to try another source first, or drop them
|
||||
into the kernel directory yourself.
|
||||
|
||||
Unticking it removes the pin and runs `sudo pacman -Syu`, which brings the
|
||||
kernel back to CachyOS's current version; the files stay for next time.
|
||||
|
||||
## HDMI refresh boost (Steam Machine)
|
||||
|
||||
With the pinned kernel (7.1.6), HDMI displays often stay at 60 Hz. Two
|
||||
reasons:
|
||||
|
||||
- Monitors list their fast modes in an extra EDID block, announced by the
|
||||
HDMI Forum EEODB data block. 7.1.6 only reads the first extension block,
|
||||
so it never sees them. Newer kernels do.
|
||||
- Their fastest modes need HDMI 2.1 (FRL). 7.1.6's amdgpu only does HDMI
|
||||
2.0 (TMDS, at most 600 MHz), but a mode with the display's own shortest
|
||||
blanking at a slightly lower rate often fits.
|
||||
|
||||
The menu item (only on a Steam Machine with the pinned kernel, never
|
||||
ticked by default, run from the desktop in Konsole):
|
||||
|
||||
1. Takes the desktop resolution from KDE (`kscreen-doctor -j`) and reads the
|
||||
display's complete EDID over DDC (`i2ctransfer`, segment pointer 0x30).
|
||||
A live EDID left by an earlier test is cleared first.
|
||||
2. Calculates the highest rate that fits: the display's TMDS limit (HDMI
|
||||
Forum VSDB, capped at amdgpu's 600 MHz), its shortest blanking at that
|
||||
resolution and its maximum refresh (range limits, VRR maximum). Steps:
|
||||
that rate rounded down to ten, and the hundred below it as a safe option.
|
||||
Rates the display already lists, or that aren't faster than what works
|
||||
now, are left out.
|
||||
3. Builds the EDID: all of the display's blocks, the block count and EEODB
|
||||
fixed, plus a DisplayID block with the steps.
|
||||
4. Loads it live (debugfs `edid_override`, `trigger_hotplug`) and switches
|
||||
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`.
|
||||
|
||||
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
|
||||
kernel pin: newer kernels read the EDID themselves and can do HDMI 2.1.
|
||||
|
||||
## BIOS updates (Steam Machine)
|
||||
|
||||
The **Update BIOS** item is only shown on a Steam Machine and is never ticked
|
||||
by default. It shows the BIOS version you have now and the newest one Valve
|
||||
ships (the `.cab` file in its `fremont-hw-support` package, looked up on
|
||||
Valve's SteamOS mirror):
|
||||
|
||||
```
|
||||
[ ] Update BIOS: now F7F0107, newest F7F0108 (own risk) (opt-in, runs once)
|
||||
```
|
||||
|
||||
It can only be ticked when Valve has a newer BIOS than yours; when you're up
|
||||
to date, when the newest version can't be looked up (offline), or when an
|
||||
update is already waiting for a restart, it's greyed out.
|
||||
|
||||
When you run it, the wizard:
|
||||
|
||||
1. downloads Valve's package and checks its **SHA-256** against Valve's
|
||||
repository, so it's exactly Valve's file;
|
||||
2. asks **fwupd** whether the firmware is for this very machine (fwupd compares
|
||||
the firmware's hardware IDs with the device) and stops if it isn't;
|
||||
3. shows a large red **warning** with the current and the new version, and asks
|
||||
whether you understand the risks (default: no);
|
||||
4. shows the warning **again** and only continues when you type `UPDATE`;
|
||||
5. hands the firmware to fwupd, which writes it during the **next restart**:
|
||||
choose "restart now" or restart later yourself.
|
||||
|
||||
**At your own risk:** a failed or interrupted BIOS update can leave the machine
|
||||
unable to start. Keep it on mains power, and never turn off the power, unplug
|
||||
it or press the power button while it updates, including during the restart;
|
||||
the screen can stay black for several minutes.
|
||||
|
||||
To walk through it without flashing anything, run the wizard with
|
||||
`WIZARD_BIOS_DRY_RUN=1`: it downloads and checks the package and shows both
|
||||
warnings, skips fwupd's device check, and only prints the install command.
|
||||
|
||||
## Manual session control
|
||||
|
||||
```bash
|
||||
steamos-session-select gamescope # switch to gamescope right now
|
||||
steamos-session-select plasma # switch to desktop right now
|
||||
steamos-session-select persistent # remember the last-used session across reboots
|
||||
steamos-session-select oneshot # always start in gamescope (default, like a Deck)
|
||||
```
|
||||
|
||||
With `Relogin=true`, a gamescope session that fails to start is restarted
|
||||
immediately, which can turn into a loop - see
|
||||
[TROUBLESHOOTING.md](TROUBLESHOOTING.md) for the way out.
|
||||
|
||||
## Project layout
|
||||
|
||||
| File | Responsibility |
|
||||
|---|---|
|
||||
| `steamify.sh` | Entry point: checks, menu, apply, summary, restart |
|
||||
| `lib/menu.sh` | The menu: detect, toggle, plan and apply changes |
|
||||
| `lib/state.sh` | Undo journal for KDE settings (`kset`/`krevert`) |
|
||||
| `lib/common.sh` | Output helpers, prompts, backups, plasmashell handling |
|
||||
| `lib/packages.sh` | Required packages, AUR helper (yay/paru) |
|
||||
| `lib/boot-session.sh` | Boot into gamescope or the desktop (`steamify-boot-desktop.service`) |
|
||||
| `lib/login-manager.sh` | SteamOS conversion: SDDM or plasmalogin autologin, sync bridge |
|
||||
| `lib/steam-desktop.sh` | Steam in the Plasma session; Steam Deck/Machine icons |
|
||||
| `lib/desktop-shortcut.sh` | Return to Gaming Mode shortcut and its sudoers rule |
|
||||
| `lib/vapor-theme.sh` | SteamOS theme: installs and switches to `cachyos-vapor` |
|
||||
| `lib/steamos-extras.sh` | SteamOS desktop extras from Valve's package (Add to Steam, Nested Desktop, icon, keyboard rule, KWallet) |
|
||||
| `lib/single-user.sh` | Single user mode: no lock screen, user switching or log out |
|
||||
| `lib/wizard-shortcut.sh` | Steamify shortcut: desktop icon and launcher entry that run the newest release |
|
||||
| `lib/bios.sh` | Update BIOS (Steam Machine, opt-in): current/newest version, double confirmation, fwupd |
|
||||
| `lib/cec.sh` | HDMI-CEC: Valve's `cecd` and friends from its `holo` repository |
|
||||
| `lib/steam-machine.sh` | Steam Machine support: LED driver, LED access, steamos-manager; kernel pin |
|
||||
| `lib/hdmi-refresh.sh` | HDMI refresh boost (Steam Machine, pinned kernel): EDID over DDC, calculated steps, live test, `drm.edid_firmware` |
|
||||
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`) |
|
||||
| `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` |
|
||||
|
||||
The single-file version is generated: on every push to `main`, GitHub
|
||||
Actions runs `.github/tools/bundle.sh`, checks the result with `bash -n` and
|
||||
shellcheck, and publishes it as a release per version (tag `v<VERSION>`,
|
||||
never overwritten; the newest is marked latest, and the `latest` tag follows
|
||||
it). It inlines
|
||||
`lib/*.sh` and wraps the entry point in `main()`, so bash has read the whole
|
||||
file before anything runs; when stdin is a pipe (`curl | bash`) it reattaches
|
||||
the terminal for the menu (set `WIZARD_KEEP_STDIN=1` to keep piped input).
|
||||
|
||||
Notes for contributors and AI coding agents are in [`AGENTS.md`](AGENTS.md).
|
||||
|
||||
## Caveats
|
||||
|
||||
- This works around CachyOS/`plasma-login-manager` behavior as of September
|
||||
2026. If CachyOS fixes `steam-set-session` upstream, parts of this script
|
||||
become unnecessary - please open an issue or PR if you notice that.
|
||||
- Tested on a Valve Steam Machine running CachyOS Desktop edition. It should
|
||||
work on any CachyOS desktop install using `plasma-login-manager`, but
|
||||
hasn't been tested on Steam Deck/Legion Go hardware.
|
||||
|
||||
## Related upstream reports
|
||||
|
||||
- [CachyOS/gamescope-session#9](https://github.com/CachyOS/gamescope-session/issues/9) - "Switch to Desktop" hang due to missing `/etc/plasmalogin.conf.d`
|
||||
@@ -0,0 +1,75 @@
|
||||
# Troubleshooting
|
||||
|
||||
See [TECHNICAL.md](TECHNICAL.md) for how each part works.
|
||||
|
||||
## Gaming mode shows a black screen or keeps restarting
|
||||
|
||||
Press **Ctrl+Alt+F3**, log in with your username and password, and run:
|
||||
|
||||
```bash
|
||||
sudo /usr/lib/steamos/steam-set-session plasma.desktop && sudo systemctl restart display-manager
|
||||
```
|
||||
|
||||
That takes you back to the desktop. Gamescope sessions that fail to start are
|
||||
restarted right away (`Relogin=true`), which is why it can loop.
|
||||
|
||||
## Something is set up wrong
|
||||
|
||||
Run the wizard again and press `a` in the menu to re-apply everything that's
|
||||
on (also useful after a CachyOS update). To undo a part, untick it.
|
||||
|
||||
## The LED bar on the Steam Machine stays dark
|
||||
|
||||
Restart once more; a freshly installed driver often needs a reboot. The menu
|
||||
shows per kernel whether the LED driver is built, and whether it's loaded.
|
||||
Still dark:
|
||||
|
||||
```bash
|
||||
dkms status
|
||||
ls -l /sys/class/leds/valve-leds*/
|
||||
sudo dmesg | grep -i valve
|
||||
cat /var/lib/dkms/leds-valve-dkms/0.1/build/make.log
|
||||
journalctl --user -b | grep -i led
|
||||
```
|
||||
|
||||
## The TV remote does nothing (HDMI-CEC)
|
||||
|
||||
- Turn on CEC in the TV's settings (Sony: BRAVIA Sync, Samsung: Anynet+, LG:
|
||||
SimpLink) and, if it has one, allow the PC to control it.
|
||||
- Check that the PC has a CEC device, and that `cecd` runs:
|
||||
|
||||
```bash
|
||||
ls /dev/cec*
|
||||
systemctl --user status cecd
|
||||
journalctl --user -u cecd -b
|
||||
cec-ctl -d /dev/cec0 -S # the devices on the TV's CEC bus (v4l-utils)
|
||||
```
|
||||
|
||||
No `/dev/cec*`: your PC has no CEC; a USB CEC adapter (e.g. Pulse-Eight)
|
||||
works. See [CEC.md](CEC.md). The Steam Machine wakes up again right after going
|
||||
to sleep, or other devices lose CEC? That happens on SteamOS too; turn
|
||||
HDMI-CEC off in the wizard.
|
||||
|
||||
## The Steam Machine still reboots when I shut it down
|
||||
|
||||
Check that the pinned kernel is installed and running:
|
||||
|
||||
```bash
|
||||
uname -r # should be 7.1.6-1-cachyos
|
||||
pacman -Q linux-cachyos linux-cachyos-headers
|
||||
grep '^IgnorePkg' /etc/pacman.conf # should list both packages
|
||||
```
|
||||
|
||||
Not running it yet? Restart. Not installed? Run the wizard and make sure
|
||||
**Pin the kernel** under Steam Machine support is ticked. If you have more
|
||||
kernels installed (e.g. the LTS kernel), pick `linux-cachyos` in the boot menu.
|
||||
|
||||
## The kernel pin can't download or verify the kernel
|
||||
|
||||
The wizard tries this repo's `kernel-7.1.6-1` release, then the CachyOS
|
||||
archive and mirror. A file that fails its checksum or CachyOS signature is
|
||||
deleted, so running the wizard again downloads it once more. Offline? Put
|
||||
the four files from the
|
||||
[kernel-7.1.6-1 release](https://github.com/theupriser/steamify-cachyos/releases/tag/kernel-7.1.6-1)
|
||||
in `/var/cache/steamify/kernel` and run it again.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
# "Boot into: gamescope / desktop" menu item, on top of the SteamOS
|
||||
# conversion. Gamescope is the default (the conversion's autologin, and
|
||||
# CachyOS's cachyos-gamescope-autologin resetting to it after a desktop
|
||||
# session). Desktop: a unit that sets the autologin session to Plasma at
|
||||
# every boot, before the login manager starts. Switching between the two in
|
||||
# a running system (Return to Gaming Mode, Steam's Switch to Desktop) works
|
||||
# the same either way.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
BOOT_UNIT_NAME="steamify-boot-desktop.service"
|
||||
BOOT_UNIT="/etc/systemd/system/$BOOT_UNIT_NAME"
|
||||
SESSION_SYNC="/usr/local/bin/sync-steamos-session.sh"
|
||||
|
||||
boot_status() {
|
||||
# On = boots into the desktop.
|
||||
[[ -f "$BOOT_UNIT" ]] && systemctl is-enabled -q "$BOOT_UNIT_NAME" 2>/dev/null
|
||||
}
|
||||
|
||||
boot_enable() {
|
||||
info "Setting the PC to boot into the desktop..."
|
||||
# The sync bridge (plasma-login-manager only; not there with SDDM) copies
|
||||
# the session into the base config; run it right away rather than rely
|
||||
# on its path unit, which could still be pending when the login manager
|
||||
# starts.
|
||||
sudo tee "$BOOT_UNIT" > /dev/null << EOF
|
||||
[Unit]
|
||||
Description=Steamify CachyOS: boot into the desktop instead of gaming mode
|
||||
Before=display-manager.service sddm.service plasmalogin.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/lib/steamos/steam-set-session plasma.desktop
|
||||
ExecStart=/bin/sh -c '[ -x $SESSION_SYNC ] && exec $SESSION_SYNC || true'
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
EOF
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable "$BOOT_UNIT_NAME" || { err "Enabling $BOOT_UNIT_NAME failed."; return 1; }
|
||||
ok "Boots into the desktop from the next boot on (Return to Gaming Mode still works)."
|
||||
}
|
||||
|
||||
boot_disable() {
|
||||
info "Setting the PC to boot into gaming mode again..."
|
||||
sudo systemctl disable "$BOOT_UNIT_NAME" 2>/dev/null
|
||||
sudo rm -f "$BOOT_UNIT"
|
||||
sudo systemctl daemon-reload
|
||||
# Next boot into gamescope, as the conversion sets it up.
|
||||
sudo /usr/lib/steamos/steam-set-session gamescope-session.desktop
|
||||
[[ -x "$SESSION_SYNC" ]] && sudo "$SESSION_SYNC"
|
||||
ok "Boots into gaming mode from the next boot on."
|
||||
}
|
||||
|
||||
boot_choice() {
|
||||
# The menu row: the chosen option in brackets.
|
||||
if [[ "$1" == 1 ]]; then
|
||||
echo "Boot into: gamescope [desktop]"
|
||||
else
|
||||
echo "Boot into: [gamescope] desktop"
|
||||
fi
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
# "HDMI-CEC" menu item, on any PC: Valve's CEC daemon (cecd), TV/receiver
|
||||
# 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.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
CEC_PKGS=(cecd cec-audio-control inputattach-cec-units)
|
||||
# steamos-manager checks once, at its start, whether cecd runs; order it
|
||||
# after cecd so it never misses it at login.
|
||||
CEC_ORDER_DROPIN=/etc/systemd/user/steamos-manager.service.d/10-steamify-after-cecd.conf
|
||||
# CachyOS's gaming mode script exports STEAM_ENABLE_CEC=0, which hides
|
||||
# Steam's HDMI-CEC settings; Steam reads it from the gamescope environment
|
||||
# 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
|
||||
|
||||
cec_link_steamos_manager() {
|
||||
# On a Steam Machine: steamos-manager writes cecd's config from Steam's
|
||||
# CEC settings (its configure-cecd unit, run before cecd), and only
|
||||
# offers Steam those settings when cecd was there at its start. Also
|
||||
# called by Steam Machine support, which installs steamos-manager after
|
||||
# this item ran.
|
||||
pacman -Q steamos-manager >/dev/null 2>&1 || return 0
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable steamos-manager-configure-cecd.service 2>/dev/null
|
||||
systemctl --user start steamos-manager-configure-cecd.service 2>/dev/null
|
||||
systemctl --user restart cecd.service 2>/dev/null
|
||||
systemctl --user restart steamos-manager.service 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
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_repair() { cec_installed && ! cec_status; }
|
||||
|
||||
fetch_holo_pkg() {
|
||||
# fetch_holo_pkg <dir> <package>: download the newest <package> from
|
||||
# Valve's newest holo repository into <dir> and print its path. The
|
||||
# SHA-256 comes from Valve's package index and is verified.
|
||||
local dir="$1" name="$2" repo desc file_name sha256
|
||||
repo="$(curl -fsL "$VALVE_MIRROR/" | grep -oE 'holo-[0-9]+\.[0-9]+/' | tr -d / | sort -V | tail -n 1)"
|
||||
if [[ ! -f "$dir/holo.db" ]]; then
|
||||
[[ -n "$repo" ]] && curl -fsL "$VALVE_MIRROR/$repo/os/x86_64/$repo.db" -o "$dir/holo.db" ||
|
||||
{ err "Couldn't read Valve's SteamOS package index ($VALVE_MIRROR)."; return 1; }
|
||||
fi
|
||||
desc="$(tar -tf "$dir/holo.db" 2>/dev/null | grep -E "^$name-[0-9][^/]*/desc$" | head -n 1)"
|
||||
[[ -n "$desc" ]] || { err "No $name in Valve's $repo repository."; return 1; }
|
||||
file_name="$(tar -xOf "$dir/holo.db" "$desc" | awk '/^%FILENAME%$/ { getline; print }')"
|
||||
sha256="$(tar -xOf "$dir/holo.db" "$desc" | awk '/^%SHA256SUM%$/ { getline; print }')"
|
||||
info "Downloading $file_name ($repo)..." >&2
|
||||
curl -fsL "$VALVE_MIRROR/$repo/os/x86_64/$file_name" -o "$dir/$file_name" ||
|
||||
{ err "Downloading $file_name failed."; return 1; }
|
||||
echo "$sha256 $dir/$file_name" | sha256sum -c --quiet - >&2 ||
|
||||
{ err "Checksum mismatch for $file_name; not using it."; return 1; }
|
||||
echo "$dir/$file_name"
|
||||
}
|
||||
|
||||
cec_enable() {
|
||||
local tmp p f files=()
|
||||
# inputattach, needed by inputattach-cec-units.
|
||||
if ! pacman -Q linuxconsole >/dev/null 2>&1; then
|
||||
sudo pacman -S --needed --noconfirm linuxconsole || { err "Installing linuxconsole failed."; return 1; }
|
||||
state_set cec installed_linuxconsole 1
|
||||
fi
|
||||
tmp="$(mktemp -d)"
|
||||
for p in "${CEC_PKGS[@]}"; do
|
||||
f="$(fetch_holo_pkg "$tmp" "$p")" || { rm -rf "$tmp"; return 1; }
|
||||
files+=("$f")
|
||||
done
|
||||
info "Installing Valve's CEC daemon..."
|
||||
if ! sudo pacman -U --needed --noconfirm "${files[@]}"; then
|
||||
rm -rf "$tmp"
|
||||
err "Installing ${CEC_PKGS[*]} failed."
|
||||
return 1
|
||||
fi
|
||||
rm -rf "$tmp"
|
||||
# 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
|
||||
sudo udevadm trigger --subsystem-match=cec --action=add
|
||||
sudo mkdir -p "$(dirname "$CEC_ORDER_DROPIN")"
|
||||
printf '%s\n' "# Written by Steamify: steamos-manager only sees cecd if it runs at its start." \
|
||||
'[Unit]' 'After=cecd.service' | sudo tee "$CEC_ORDER_DROPIN" >/dev/null
|
||||
sudo mkdir -p "$(dirname "$CEC_STEAM_ENV")" "$(dirname "$CEC_STEAM_DROPIN")"
|
||||
printf '%s\n' "# Written by Steamify: Steam's HDMI-CEC settings in gaming mode." 'STEAM_ENABLE_CEC=1' |
|
||||
sudo tee "$CEC_STEAM_ENV" >/dev/null
|
||||
printf '%s\n' "# Written by Steamify: overrides STEAM_ENABLE_CEC=0 from the gaming mode script." \
|
||||
'[Service]' "EnvironmentFile=-$CEC_STEAM_ENV" | sudo tee "$CEC_STEAM_DROPIN" >/dev/null
|
||||
systemctl --user daemon-reload
|
||||
cec_link_steamos_manager
|
||||
if compgen -G "/dev/cec*" >/dev/null; then
|
||||
systemctl --user restart cecd.service 2>/dev/null
|
||||
ok "HDMI-CEC on ($(cd /dev && echo cec*)); Steam shows its settings from the next gaming mode start."
|
||||
info "Turn on CEC on your TV too (e.g. Sony: BRAVIA Sync, Samsung: Anynet+, LG: SimpLink)."
|
||||
else
|
||||
warn "No CEC device (/dev/cec*) found: your GPU may not support CEC. A USB CEC adapter"
|
||||
warn "(e.g. Pulse-Eight) works too. Check again after a restart with: ls /dev/cec*"
|
||||
fi
|
||||
}
|
||||
|
||||
cec_disable() {
|
||||
info "Removing HDMI-CEC..."
|
||||
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
|
||||
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
|
||||
if [[ -n "$(state_get cec installed_linuxconsole)" ]]; then
|
||||
sudo pacman -Rns --noconfirm linuxconsole 2>/dev/null
|
||||
state_clear cec
|
||||
fi
|
||||
sudo udevadm control --reload
|
||||
systemctl --user is-active -q steamos-manager.service 2>/dev/null &&
|
||||
systemctl --user restart steamos-manager.service
|
||||
ok "HDMI-CEC removed."
|
||||
}
|
||||
|
||||
cec_overview() {
|
||||
# For the menu's kernel overview.
|
||||
local dev="none"
|
||||
compgen -G "/dev/cec*" >/dev/null && dev="$(cd /dev && echo cec*)"
|
||||
echo "CEC devices: $dev cecd: $(systemctl --user is-active cecd 2>/dev/null)"
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
#!/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:
|
||||
# - 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.
|
||||
# - Their fastest modes need HDMI 2.1 (FRL), which amdgpu doesn't do on
|
||||
# every kernel; a mode with the display's shortest blanking at a slightly
|
||||
# lower rate often still fits HDMI 2.0's TMDS limit (600 MHz).
|
||||
# What a display claims isn't what it accepts: every step is shown and must
|
||||
# be confirmed, and no answer within 15 s switches back (the screen may be
|
||||
# black). Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
HDMI_FW_DIR=/usr/lib/firmware/edid
|
||||
HDMI_INITRAMFS_CONF=/etc/mkinitcpio.conf.d/90-steamify-edid.conf
|
||||
# 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.
|
||||
HDMI_TMDS_MAX_KHZ=600000
|
||||
|
||||
hdmi_connectors() {
|
||||
# Connected HDMI outputs, as "<card>-<connector>" (e.g. card1-HDMI-A-1).
|
||||
local d
|
||||
for d in /sys/class/drm/card*-HDMI-A-*; do
|
||||
[[ "$(cat "$d/status" 2>/dev/null)" == connected ]] && basename "$d"
|
||||
done
|
||||
}
|
||||
|
||||
hdmi_boot_file() {
|
||||
# The file holding the kernel command line, per boot loader.
|
||||
if [[ -f /etc/default/limine ]]; then echo /etc/default/limine
|
||||
elif [[ -f /etc/sdboot-manage.conf ]] && command -v sdboot-manage >/dev/null; then echo /etc/sdboot-manage.conf
|
||||
elif [[ -f /etc/default/grub ]]; then echo /etc/default/grub
|
||||
fi
|
||||
}
|
||||
|
||||
hdmi_cmdline_param() {
|
||||
# The drm.edid_firmware= value we set, or nothing.
|
||||
local f; f="$(hdmi_boot_file)"
|
||||
[[ -n "$f" ]] && grep -oE 'drm\.edid_firmware=[^ "]*steamify-[^ "]*' "$f" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
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)" ]]; }
|
||||
}
|
||||
|
||||
hdmi_status() { [[ -n "$(hdmi_cmdline_param)" ]] && compgen -G "$HDMI_FW_DIR/steamify-*.bin" >/dev/null; }
|
||||
|
||||
hdmi_edid_tool() {
|
||||
# hdmi_edid_tool <command> ...: EDID parsing and building (python, as
|
||||
# it's byte work). Commands:
|
||||
# blocks <edid> number of blocks the display says it has
|
||||
# id <edid> vendor/product/serial, to recognise it
|
||||
# plan <edid> <w> <h> <cur_hz> "<limit_hz> <best> <step>..." for w x h
|
||||
# build <in> <out> <w> <h> <hz>... full EDID plus a DisplayID block
|
||||
# with those rates at the shortest blanking
|
||||
python3 - "$HDMI_TMDS_MAX_KHZ" "$@" <<'PY'
|
||||
import sys
|
||||
|
||||
src_khz = int(sys.argv[1]); cmd = sys.argv[2]; args = sys.argv[3:]
|
||||
def load(p): return bytearray(open(p, 'rb').read())
|
||||
def le(b): return int.from_bytes(b, 'little')
|
||||
|
||||
def cta_blocks(e):
|
||||
for i in range(128, len(e), 128):
|
||||
if e[i] == 0x02: yield i
|
||||
|
||||
def data_blocks(e, i):
|
||||
# CTA data block collection: (tag, extended tag, offset, length)
|
||||
end, p = e[i + 2], i + 4
|
||||
while p < i + end and p < i + 127:
|
||||
tag, n = e[p] >> 5, e[p] & 0x1f
|
||||
yield tag, (e[p + 1] if tag == 7 and n else None), p, n
|
||||
p += n + 1
|
||||
|
||||
def eeodb(e):
|
||||
for i in cta_blocks(e):
|
||||
for tag, ext, p, n in data_blocks(e, i):
|
||||
if tag == 7 and ext == 0x78: return p + 2
|
||||
return None
|
||||
|
||||
def nblocks(e):
|
||||
p = eeodb(e)
|
||||
return 1 + max(e[126], e[p] if p and p < len(e) else 0)
|
||||
|
||||
def timings(e):
|
||||
# (hz, clk_khz, ha, hb, hf, hs, hpos, va, vb, vf, vs, vpos)
|
||||
out = []
|
||||
def dtd(b):
|
||||
clk = le(b[0:2]) * 10
|
||||
if not clk: return
|
||||
ha = b[2] | (b[4] >> 4) << 8; hb = b[3] | (b[4] & 15) << 8
|
||||
va = b[5] | (b[7] >> 4) << 8; vb = b[6] | (b[7] & 15) << 8
|
||||
hf = b[8] | (b[11] >> 6 & 3) << 8; hs = b[9] | (b[11] >> 4 & 3) << 8
|
||||
vf = (b[10] >> 4) | (b[11] >> 2 & 3) << 4; vs = (b[10] & 15) | (b[11] & 3) << 4
|
||||
if b[17] & 0x80: return # interlaced
|
||||
out.append((clk * 1000 / ((ha + hb) * (va + vb)), clk, ha, hb, hf, hs, b[17] >> 1 & 1,
|
||||
va, vb, vf, vs, b[17] >> 2 & 1))
|
||||
for o in range(54, 126, 18): dtd(e[o:o + 18])
|
||||
for i in cta_blocks(e):
|
||||
d = e[i + 2]
|
||||
if d >= 4:
|
||||
for o in range(i + d, i + 127 - 17, 18): dtd(e[o:o + 18])
|
||||
for i in range(128, len(e), 128):
|
||||
if e[i] != 0x70: continue
|
||||
p, end = i + 5, i + 5 + e[i + 2]
|
||||
while p + 3 <= end:
|
||||
tag, n = e[p], e[p + 2]
|
||||
if tag == 0 and n == 0: break
|
||||
if tag == 0x03:
|
||||
for o in range(p + 3, p + 3 + n - 19, 20):
|
||||
b = e[o:o + 20]
|
||||
clk = (le(b[0:3]) + 1) * 10
|
||||
hf, vf = le(b[8:10]), le(b[16:18])
|
||||
ha, hb, va, vb = le(b[4:6]) + 1, le(b[6:8]) + 1, le(b[12:14]) + 1, le(b[14:16]) + 1
|
||||
out.append((clk * 1000 / ((ha + hb) * (va + vb)), clk, ha, hb, (hf & 0x7fff) + 1,
|
||||
le(b[10:12]) + 1, hf >> 15, va, vb, (vf & 0x7fff) + 1, le(b[18:20]) + 1, vf >> 15))
|
||||
p += 3 + n
|
||||
return out
|
||||
|
||||
def tmds_khz(e):
|
||||
# The display's own TMDS limit: HDMI Forum VSDB, else the HDMI 1.4 VSDB.
|
||||
hf = h14 = 0
|
||||
for i in cta_blocks(e):
|
||||
for tag, ext, p, n in data_blocks(e, i):
|
||||
if tag != 3 or n < 3: continue
|
||||
oui = e[p + 3] << 16 | e[p + 2] << 8 | e[p + 1]
|
||||
if oui == 0xC45DD8 and n >= 5: hf = e[p + 5] * 5000
|
||||
if oui == 0x000C03 and n >= 7: h14 = e[p + 7] * 5000
|
||||
lim = hf or h14 or 165000
|
||||
return min(lim, src_khz)
|
||||
|
||||
def max_vrefresh(e):
|
||||
# Range limits descriptor, the HDMI Forum / AMD VRR maximum.
|
||||
best = 0
|
||||
for o in range(54, 126, 18):
|
||||
b = e[o:o + 18]
|
||||
if b[0:3] == b'\0\0\0' and b[3] == 0xfd:
|
||||
best = max(best, b[6] + (255 if b[4] & 2 else 0))
|
||||
for i in cta_blocks(e):
|
||||
for tag, ext, p, n in data_blocks(e, i):
|
||||
if tag != 3 or n < 3: continue
|
||||
oui = e[p + 3] << 16 | e[p + 2] << 8 | e[p + 1]
|
||||
if oui == 0xC45DD8 and n >= 10: best = max(best, (e[p + 9] & 0xc0) << 2 | e[p + 10])
|
||||
if oui == 0x00001A and n >= 7: best = max(best, e[p + 7])
|
||||
return best
|
||||
|
||||
def shortest(e, w, h):
|
||||
ts = [t for t in timings(e) if t[2] == w and t[7] == h]
|
||||
return min(ts, key=lambda t: (t[2] + t[3]) * (t[7] + t[8])) if ts else None
|
||||
|
||||
if cmd == 'blocks':
|
||||
print(nblocks(load(args[0])))
|
||||
elif cmd == 'id':
|
||||
print(load(args[0])[8:18].hex())
|
||||
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)
|
||||
if not t: sys.exit(1)
|
||||
listed = [x[0] for x in timings(e) if x[2] == w and x[7] == h and x[1] <= lim]
|
||||
best = max(listed) if listed else cur
|
||||
top = int(lim * 1000 // ((t[2] + t[3]) * (t[7] + t[8])))
|
||||
vmax = max_vrefresh(e) or int(max(x[0] for x in timings(e) if x[2] == w and x[7] == h))
|
||||
top = min(top, vmax)
|
||||
# Not right at the limit: the top rounded down to ten, and the hundred
|
||||
# below it as a safe option. Rates the display already lists are left
|
||||
# out (its own timing is the safer one), and so is anything not faster
|
||||
# than what already works.
|
||||
steps = sorted({top // 10 * 10, top // 100 * 100})
|
||||
steps = [s for s in steps if s > best + 1 and not any(abs(s - x) < 1 for x in listed)]
|
||||
print(lim // 1000, round(best, 2), *steps)
|
||||
elif cmd == 'build':
|
||||
e = load(args[0]); w, h = int(args[2]), int(args[3]); rates = [int(r) for r in args[4:]]
|
||||
t = shortest(e, w, h)
|
||||
n = nblocks(e)
|
||||
e = e[:n * 128]
|
||||
out = b''
|
||||
for hz in rates:
|
||||
_, _, ha, hb, hf, hs, hp, va, vb, vf, vs, vp = t
|
||||
clk = round((ha + hb) * (va + vb) * hz / 1e4)
|
||||
x = (clk - 1).to_bytes(3, 'little') + bytes([0x08])
|
||||
for v, pol in ((ha, 0), (hb, 0), (hf, hp), (hs, 0), (va, 0), (vb, 0), (vf, vp), (vs, 0)):
|
||||
x += ((v - 1) | pol << 15).to_bytes(2, 'little')
|
||||
out += x
|
||||
if out:
|
||||
blk = bytearray(128)
|
||||
blk[0:5] = bytes([0x70, 0x12, 121, 0x00, 0x00])
|
||||
blk[5:8] = bytes([0x03, 0x00, len(out)])
|
||||
blk[8:8 + len(out)] = out
|
||||
blk[126] = (-sum(blk[1:126])) % 256
|
||||
blk[127] = (-sum(blk[:127])) % 256
|
||||
e += blk
|
||||
count = len(e) // 128 - 1
|
||||
e[126] = count
|
||||
e[127] = (-sum(e[:127])) % 256
|
||||
p = eeodb(e)
|
||||
if p:
|
||||
e[p] = count
|
||||
i = p - p % 128
|
||||
e[i + 127] = (-sum(e[i:i + 127])) % 256
|
||||
open(args[1], 'wb').write(e)
|
||||
PY
|
||||
}
|
||||
|
||||
hdmi_read_edid() {
|
||||
# hdmi_read_edid <card-connector> <out>: the display's complete EDID,
|
||||
# read over DDC block by block (the kernel's copy may miss blocks).
|
||||
local conn="$1" out="$2" bus n i tmp
|
||||
bus="$(basename "$(readlink -f "/sys/class/drm/$conn/ddc")")"; bus="${bus#i2c-}"
|
||||
tmp="$(mktemp)"
|
||||
cat "/sys/class/drm/$conn/edid" > "$out"
|
||||
if [[ -n "$bus" ]] && sudo modprobe i2c-dev 2>/dev/null; then
|
||||
n="$(hdmi_edid_tool blocks "$out")"
|
||||
: > "$tmp"
|
||||
for (( i = 0; i < n; i++ )); do
|
||||
# Segment pointer (0x30) selects each pair of blocks.
|
||||
sudo i2ctransfer -y "$bus" w1@0x30 $(( i / 2 )) w1@0x50 $(( (i % 2) * 128 )) r128@0x50 2>/dev/null |
|
||||
xxd -r -p >> "$tmp" 2>/dev/null ||
|
||||
{ [[ $i -lt 2 ]] && { sudo i2ctransfer -y "$bus" w1@0x50 $(( i * 128 )) r128@0x50 | xxd -r -p >> "$tmp"; }; } || break
|
||||
done
|
||||
# Keep the DDC copy only when it's whole and the same display as the
|
||||
# kernel's (not the whole block: a live override changes it).
|
||||
if [[ "$(stat -c %s "$tmp")" == $(( n * 128 )) &&
|
||||
"$(hdmi_edid_tool id "$tmp")" == "$(hdmi_edid_tool id "$out")" ]]; then
|
||||
cat "$tmp" > "$out"
|
||||
fi
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
hdmi_debugfs() {
|
||||
# debugfs directory of a connector (one of several paths to the same
|
||||
# GPU). Only root can list debugfs, so the glob runs under sudo.
|
||||
local d
|
||||
d="$(sudo sh -c 'for d in /sys/kernel/debug/dri/*/"$1"; do [ -e "$d/edid_override" ] && { echo "$d"; break; }; done' _ "${1#card*-}")"
|
||||
[[ -n "$d" ]] && echo "$d"
|
||||
}
|
||||
|
||||
hdmi_override_live() {
|
||||
# hdmi_override_live <card-connector> <edid|reset>: swap the EDID the
|
||||
# kernel uses right now, without a restart.
|
||||
local d; d="$(hdmi_debugfs "$1")" || return 1
|
||||
# The kernel takes exactly "reset", without a newline.
|
||||
if [[ "$2" == reset ]]; then printf reset | sudo tee "$d/edid_override" >/dev/null
|
||||
else sudo tee "$d/edid_override" < "$2" >/dev/null; fi || return 1
|
||||
echo 1 | sudo tee "$d/trigger_hotplug" >/dev/null
|
||||
sleep 3
|
||||
}
|
||||
|
||||
hdmi_kscreen() {
|
||||
# hdmi_kscreen current|mode <output> [<w> <h> <hz>]: KDE's view of the
|
||||
# output: "w h hz" of the current mode, or set the mode closest to hz.
|
||||
kscreen-doctor -j 2>/dev/null | python3 -c '
|
||||
import json, sys
|
||||
what, name = sys.argv[1], sys.argv[2]
|
||||
for o in json.load(sys.stdin)["outputs"]:
|
||||
if o["name"] != name: continue
|
||||
modes = {m["id"]: m for m in o["modes"]}
|
||||
if what == "current":
|
||||
m = modes[o["currentModeId"]]
|
||||
print(m["size"]["width"], m["size"]["height"], round(m["refreshRate"], 2))
|
||||
else:
|
||||
w, h, hz = int(sys.argv[3]), int(sys.argv[4]), float(sys.argv[5])
|
||||
ms = [m for m in o["modes"] if m["size"]["width"] == w and m["size"]["height"] == h]
|
||||
if ms: print(min(ms, key=lambda m: abs(m["refreshRate"] - hz))["id"])
|
||||
' "$@"
|
||||
}
|
||||
|
||||
hdmi_set_mode() {
|
||||
# hdmi_set_mode <output> <w> <h> <hz>: returns 1 if KDE has no such mode.
|
||||
local id rate
|
||||
id="$(hdmi_kscreen mode "$@")" || return 1
|
||||
[[ -n "$id" ]] || return 1
|
||||
kscreen-doctor "output.$1.mode.$id" >/dev/null 2>&1 || return 1
|
||||
sleep 2
|
||||
rate="$(hdmi_kscreen current "$1" | cut -d' ' -f3)"
|
||||
[[ "${rate%.*}" -ge $(( $4 - 1 )) ]]
|
||||
}
|
||||
|
||||
hdmi_confirm() {
|
||||
# No answer (black screen, nobody there) is a no.
|
||||
local reply
|
||||
read -rt "$HDMI_CONFIRM_SECONDS" -p "$(echo -e "${c_bold}$1${c_reset} [y/N, ${HDMI_CONFIRM_SECONDS}s] ")" reply || { echo; return 1; }
|
||||
[[ "$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; }
|
||||
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
|
||||
info "Rebuilding the initramfs and boot entries..."
|
||||
case "$f" in
|
||||
/etc/default/limine) sudo limine-mkinitcpio ;;
|
||||
/etc/sdboot-manage.conf) sudo /usr/bin/mkinitcpio -P && sudo sdboot-manage gen ;;
|
||||
/etc/default/grub) sudo /usr/bin/mkinitcpio -P && sudo grub-mkconfig -o /boot/grub/grub.cfg ;;
|
||||
esac
|
||||
}
|
||||
|
||||
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
|
||||
# confirmed new rates) or leaves it empty.
|
||||
local conn="$1" tmp="$2" out="${1#card*-}" cur w h hz plan lim best steps_str
|
||||
local -a steps ok_rates=()
|
||||
HDMI_RESULT=""
|
||||
# From the display's own EDID: a live one left by an earlier test would
|
||||
# make its rates look like what the display already does.
|
||||
hdmi_override_live "$conn" reset 2>/dev/null
|
||||
cur="$(hdmi_kscreen current "$out")"
|
||||
[[ -n "$cur" ]] || { warn "$out: KDE doesn't show this output; skipped."; return 0; }
|
||||
read -r w h hz <<< "$cur"
|
||||
info "$out: desktop resolution ${w}x${h} at ${hz} Hz. Reading the display's EDID..."
|
||||
hdmi_read_edid "$conn" "$tmp/$out.orig" || return 1
|
||||
if ! plan="$(hdmi_edid_tool plan "$tmp/$out.orig" "$w" "$h" "$hz")"; then
|
||||
warn "$out: the display lists no timing for ${w}x${h}; nothing to calculate."
|
||||
return 0
|
||||
fi
|
||||
read -r lim best steps_str <<< "$plan"
|
||||
read -ra steps <<< "${steps_str:-}"
|
||||
info "$out: HDMI limit ${lim} MHz, fastest mode that fits now: ${best} Hz."
|
||||
if [[ ${#steps[@]} -eq 0 ]]; then
|
||||
ok "$out: already at the highest rate that fits HDMI here; nothing to change."
|
||||
return 0
|
||||
fi
|
||||
info "$out: steps to try: ${steps[*]} Hz. After each one you're asked whether the"
|
||||
info "picture is OK; no answer within ${HDMI_CONFIRM_SECONDS} s (e.g. a black screen) switches back."
|
||||
ask_yn "Start the test?" y || return 0
|
||||
|
||||
hdmi_edid_tool build "$tmp/$out.orig" "$tmp/$out.test" "$w" "$h" "${steps[@]}" &&
|
||||
hdmi_override_live "$conn" "$tmp/$out.test" ||
|
||||
{ err "$out: couldn't load the test EDID (debugfs)."; return 1; }
|
||||
local s
|
||||
for s in "${steps[@]}"; do
|
||||
info "$out: switching to ${w}x${h} at $s Hz..."
|
||||
if hdmi_set_mode "$out" "$w" "$h" "$s" && hdmi_confirm "Is the picture OK at $s Hz?"; then
|
||||
ok "$out: $s Hz works."
|
||||
ok_rates+=("$s")
|
||||
else
|
||||
warn "$out: $s Hz doesn't work; stopping here."
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ ${#ok_rates[@]} -eq 0 ]]; then
|
||||
hdmi_override_live "$conn" reset
|
||||
hdmi_set_mode "$out" "$w" "$h" "$hz" >/dev/null
|
||||
info "$out: back to ${hz} Hz; nothing changed."
|
||||
return 0
|
||||
fi
|
||||
# Without the steps that failed, so KDE and gamescope never pick them.
|
||||
hdmi_edid_tool build "$tmp/$out.orig" "$tmp/$out.final" "$w" "$h" "${ok_rates[@]}" &&
|
||||
hdmi_override_live "$conn" "$tmp/$out.final"
|
||||
hdmi_set_mode "$out" "$w" "$h" "${ok_rates[-1]}" >/dev/null
|
||||
HDMI_RESULT="$w $h ${ok_rates[*]}"
|
||||
}
|
||||
|
||||
hdmi_enable() {
|
||||
if [[ "${BACKEND:-false}" == true ]]; then
|
||||
err "HDMI refresh boost needs you at the screen: run it from the Steamify menu in Konsole."
|
||||
return 1
|
||||
fi
|
||||
if [[ "${XDG_CURRENT_DESKTOP:-}" != *KDE* ]] || ! command -v kscreen-doctor >/dev/null; then
|
||||
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; testing 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
|
||||
tmp="$(mktemp -d)"
|
||||
for conn in $(hdmi_connectors); do
|
||||
out="${conn#card*-}"
|
||||
hdmi_tune "$conn" "$tmp" || { rc=1; continue; }
|
||||
[[ -n "$HDMI_RESULT" ]] || continue
|
||||
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" "$HDMI_RESULT"
|
||||
param+="${param:+,}$out:edid/steamify-$out.bin"
|
||||
files+=("$HDMI_FW_DIR/steamify-$out.bin")
|
||||
done
|
||||
rm -rf "$tmp"
|
||||
[[ -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
|
||||
# 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
|
||||
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."
|
||||
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."
|
||||
}
|
||||
+84
-14
@@ -5,31 +5,57 @@
|
||||
|
||||
# Menu order. Components are turned on in this order and off in reverse;
|
||||
# gaming must come first (single user builds on it).
|
||||
COMPONENTS=(gaming theme glyphs single launcher machine bios)
|
||||
COMPONENTS=(gaming boot theme glyphs single launcher cec machine kpin hdmi bios)
|
||||
# One-off actions rather than on/off components: never preselected, never
|
||||
# re-applied, not listed as on or off.
|
||||
ACTIONS=(bios)
|
||||
# Sub-options, shown indented under their parent and only while it's ticked.
|
||||
declare -A PARENT=([boot]=gaming [kpin]=machine [hdmi]=machine [bios]=machine)
|
||||
# Never preselected on a first run: booting into the desktop is a choice,
|
||||
# gamescope is the default; HDMI-CEC is opt-in (it can wake the machine or
|
||||
# upset other devices on the TV, even on SteamOS), except on a Steam Machine,
|
||||
# which has CEC like on SteamOS. HDMI refresh boost needs someone at the
|
||||
# screen to confirm each step.
|
||||
NO_PRESELECT=(boot cec hdmi)
|
||||
|
||||
declare -A LABEL=(
|
||||
[gaming]="SteamOS conversion: boot into gaming mode, Steam on the desktop"
|
||||
[boot]="Boot into the desktop instead of gaming mode"
|
||||
[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"
|
||||
[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)"
|
||||
[hdmi]="HDMI refresh boost: highest refresh your HDMI display runs"
|
||||
[bios]="Update BIOS"
|
||||
)
|
||||
declare -A CURRENT WANTED
|
||||
|
||||
component_available() {
|
||||
case "$1" in
|
||||
machine) machine_available ;;
|
||||
machine|kpin) machine_available ;;
|
||||
hdmi) hdmi_available ;;
|
||||
bios) bios_available ;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_action() { [[ " ${ACTIONS[*]} " == *" $1 "* ]]; }
|
||||
|
||||
boot_mode() {
|
||||
# boot_mode 0|1: the "Boot into" choice as a word, for messages; "turn
|
||||
# off boot into the desktop" reads as the opposite of what was chosen.
|
||||
if [[ "$1" == 1 ]]; then echo desktop; else echo gamescope; fi
|
||||
}
|
||||
|
||||
menu_visible() {
|
||||
# Shown in the menu. A sub-option ("Boot into", the kernel pin) only
|
||||
# while its parent is ticked.
|
||||
component_available "$1" || return 1
|
||||
[[ -z "${PARENT[$1]:-}" || "${WANTED[${PARENT[$1]}]:-0}" == 1 ]]
|
||||
}
|
||||
|
||||
component_selectable() {
|
||||
# Greyed out and not tickable when it has nothing to do.
|
||||
case "$1" in
|
||||
@@ -44,13 +70,17 @@ detect_components() {
|
||||
if "${c}_status"; then CURRENT[$c]=1; any=true; else CURRENT[$c]=0; fi
|
||||
WANTED[$c]=${CURRENT[$c]}
|
||||
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
|
||||
# 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).
|
||||
if [[ "$any" == false ]]; then
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
component_available "$c" && ! is_action "$c" && WANTED[$c]=1
|
||||
component_available "$c" && ! is_action "$c" &&
|
||||
[[ " ${NO_PRESELECT[*]} " != *" $c "* ]] && WANTED[$c]=1
|
||||
done
|
||||
machine_available && WANTED[cec]=1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -60,7 +90,18 @@ toggle_component() {
|
||||
WANTED[$c]=$(( 1 - WANTED[$c] ))
|
||||
# Single user mode only makes sense on top of the SteamOS conversion.
|
||||
if [[ "$c" == single && "${WANTED[single]}" == 1 ]]; then WANTED[gaming]=1; fi
|
||||
if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; fi
|
||||
if [[ "$c" == gaming && "${WANTED[gaming]}" == 0 ]]; then WANTED[single]=0; WANTED[boot]=0; fi
|
||||
# Where to boot to is part of the conversion, too.
|
||||
if [[ "$c" == boot && "${WANTED[boot]}" == 1 ]]; then WANTED[gaming]=1; fi
|
||||
# The kernel pin is opt-out: ticked along with Steam Machine support.
|
||||
if [[ "$c" == machine ]]; then WANTED[kpin]=${WANTED[machine]}; fi
|
||||
if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
# HDMI refresh boost and the BIOS update sit under Steam Machine support;
|
||||
# the boost also needs the pinned kernel (newer kernels don't need it).
|
||||
if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[hdmi]=0; WANTED[bios]=0; fi
|
||||
if [[ "$c" == kpin && "${WANTED[kpin]}" == 0 ]]; then WANTED[hdmi]=0; fi
|
||||
if [[ "$c" == hdmi && "${WANTED[hdmi]}" == 1 ]]; then WANTED[machine]=1; WANTED[kpin]=1; fi
|
||||
if [[ "$c" == bios && "${WANTED[bios]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
@@ -69,7 +110,7 @@ show_menu() {
|
||||
printf " ${c_bold}%-3s %-6s %-6s %s${c_reset}\n" "#" "Now" "Want" "Component"
|
||||
MENU_ITEMS=()
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
component_available "$c" || continue
|
||||
menu_visible "$c" || continue
|
||||
i=$((i + 1)); MENU_ITEMS[$i]=$c
|
||||
# Pad the plain word, then colour it: colour codes would count as width.
|
||||
now="off"; [[ "${CURRENT[$c]}" == 1 ]] && now="on"
|
||||
@@ -77,10 +118,19 @@ show_menu() {
|
||||
now="$(printf '%-6s' "$now")"
|
||||
[[ "${CURRENT[$c]}" == 1 ]] && now="${now/on/${c_green}on${c_reset}}"
|
||||
want="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && want="[x]"
|
||||
if component_selectable "$c"; then
|
||||
printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}"
|
||||
if [[ "$c" == boot ]]; then
|
||||
# A choice rather than a checkbox: Now/Want show the mode.
|
||||
now="gaming"; [[ "${CURRENT[boot]}" == 1 ]] && now="desk"
|
||||
[[ "${CURRENT[gaming]}" == 1 ]] || now="-"
|
||||
want="gaming"; [[ "${WANTED[boot]}" == 1 ]] && want="desk"
|
||||
printf " %-3s %-6s %-6s └ %s\n" "$i" "$now" "$want" "$(boot_choice "${WANTED[boot]}")"
|
||||
elif ! component_selectable "$c"; then
|
||||
local tree=""; [[ -n "${PARENT[$c]:-}" ]] && tree=" └ "
|
||||
printf " %b%-3s %-6s %-6s %s%s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "$tree" "${LABEL[$c]}" "$c_reset"
|
||||
elif [[ -n "${PARENT[$c]:-}" ]]; then
|
||||
printf " %-3s %b %-6s └ %s\n" "$i" "$now" "$want" "${LABEL[$c]}"
|
||||
else
|
||||
printf " %b%-3s %-6s %-6s %s (not available)%b\n" "$c_dim" "$i" "$now" "$want" "${LABEL[$c]}" "$c_reset"
|
||||
printf " %-3s %b %-6s %s\n" "$i" "$now" "$want" "${LABEL[$c]}"
|
||||
fi
|
||||
done
|
||||
echo
|
||||
@@ -109,16 +159,28 @@ draw_menu_tui() {
|
||||
echo
|
||||
MENU_ITEMS=()
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
component_available "$c" || continue
|
||||
menu_visible "$c" || continue
|
||||
MENU_ITEMS[$i]=$c
|
||||
box="[ ]"; [[ "${WANTED[$c]}" == 1 ]] && box="[${c_green}x${c_reset}]"
|
||||
state=" (now: off)"; [[ "${CURRENT[$c]}" == 1 ]] && state=" (now: ${c_green}on${c_reset})"
|
||||
is_action "$c" && state=" (opt-in, runs once)"
|
||||
line="$box ${LABEL[$c]}"
|
||||
if [[ "$c" == boot ]]; then
|
||||
# A choice rather than a checkbox; Space switches it.
|
||||
# Indented under the conversion, whose sub-option it is.
|
||||
line="$(boot_choice "${WANTED[boot]}")"
|
||||
line=" └ ${line/\[/[${c_green}}"; line="${line/\]/${c_reset}]}"
|
||||
local mode=gamescope; [[ "${CURRENT[boot]}" == 1 ]] && mode=desktop
|
||||
state=" (${c_bold}←/→${c_reset} choose)"
|
||||
[[ "${CURRENT[gaming]}" == 1 ]] && state=" (now: $mode; ${c_bold}←/→${c_reset} choose)"
|
||||
elif [[ -n "${PARENT[$c]:-}" ]]; then
|
||||
line=" └ $line"
|
||||
fi
|
||||
if ! component_selectable "$c"; then
|
||||
# Greyed out: nothing to do (e.g. BIOS already up to date).
|
||||
local mark=" "; (( i == cursor )) && mark=" ${c_cyan}>${c_reset} "
|
||||
echo -e "${mark}${c_dim}[ ] ${LABEL[$c]} (not available)${c_reset}"
|
||||
local mark=" " tree=""; (( i == cursor )) && mark=" ${c_cyan}>${c_reset} "
|
||||
[[ -n "${PARENT[$c]:-}" ]] && tree=" └ "
|
||||
echo -e "${mark}${c_dim}${tree}[ ] ${LABEL[$c]} (not available)${c_reset}"
|
||||
elif (( i == cursor )); then
|
||||
# The green x's reset would end the bold too: re-enable it after.
|
||||
echo -e " ${c_cyan}>${c_reset} ${c_bold}${line//"$c_reset"/"$c_reset$c_bold"}${c_reset}${state}"
|
||||
@@ -130,7 +192,8 @@ draw_menu_tui() {
|
||||
echo
|
||||
echo -e "$KERNEL_OVERVIEW"
|
||||
echo
|
||||
echo -e " ${c_bold}Up/Down${c_reset} move ${c_bold}Space${c_reset} select ${c_bold}Enter${c_reset} run ${c_bold}a${c_reset} run + re-apply what's on ${c_bold}q${c_reset} quit"
|
||||
echo -e " ${c_bold}Up/Down${c_reset} move ${c_bold}Space${c_reset} select ${c_bold}Left/Right${c_reset} choose ${c_bold}Enter${c_reset} run"
|
||||
echo -e " ${c_bold}a${c_reset} run + re-apply what's on ${c_bold}q${c_reset} quit"
|
||||
}
|
||||
|
||||
run_menu_tui() {
|
||||
@@ -140,6 +203,8 @@ run_menu_tui() {
|
||||
while true; do
|
||||
draw_menu_tui "$cursor"
|
||||
count=${#MENU_ITEMS[@]}
|
||||
# The list shrinks when the conversion (and its sub-option) is unticked.
|
||||
if (( cursor >= count )); then cursor=$(( count - 1 )); draw_menu_tui "$cursor"; fi
|
||||
IFS= read -rsn1 key
|
||||
if [[ "$key" == $'\e' ]]; then
|
||||
IFS= read -rsn2 -t 0.05 rest
|
||||
@@ -149,6 +214,9 @@ run_menu_tui() {
|
||||
$'\e[A'|k) (( cursor = (cursor + count - 1) % count )) ;;
|
||||
$'\e[B'|j) (( cursor = (cursor + 1) % count )) ;;
|
||||
" ") toggle_component "${MENU_ITEMS[$cursor]}" ;;
|
||||
# On the "Boot into" row: left = gamescope, right = desktop.
|
||||
$'\e[D'|h) [[ "${MENU_ITEMS[$cursor]}" == boot && "${WANTED[boot]}" == 1 ]] && toggle_component boot ;;
|
||||
$'\e[C'|l) [[ "${MENU_ITEMS[$cursor]}" == boot && "${WANTED[boot]}" == 0 ]] && toggle_component boot ;;
|
||||
"") break ;;
|
||||
a|A) REAPPLY=true; break ;;
|
||||
q|Q) tput cnorm 2>/dev/null; echo; return 1 ;;
|
||||
@@ -208,11 +276,13 @@ apply_changes() {
|
||||
[[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm
|
||||
|
||||
for c in "${TO_DISABLE[@]}"; do
|
||||
echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}"
|
||||
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: gamescope${c_reset}"
|
||||
else echo; echo -e "${c_bold}Turning off: ${LABEL[$c]}${c_reset}"; fi
|
||||
"${c}_disable" || failed+=("$c")
|
||||
done
|
||||
for c in "${TO_ENABLE[@]}"; do
|
||||
if is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}"
|
||||
if [[ "$c" == boot ]]; then echo; echo -e "${c_bold}Boot into: desktop${c_reset}"
|
||||
elif is_action "$c"; then echo; echo -e "${c_bold}Running: ${LABEL[$c]%%:*}${c_reset}"
|
||||
else echo; echo -e "${c_bold}Turning on: ${LABEL[$c]}${c_reset}"; fi
|
||||
"${c}_enable" || failed+=("$c")
|
||||
done
|
||||
|
||||
+6
-1
@@ -41,8 +41,13 @@ bootstrap_yay() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Build only, then install it ourselves: makepkg -i runs `sudo -k pacman`,
|
||||
# which forgets the cached sudo password and asks for it again.
|
||||
info "Building and installing yay (this runs makepkg as your user, not root)..."
|
||||
if (cd "$tmp_dir/yay-bin" && makepkg -si --noconfirm); then
|
||||
local -a pkgs
|
||||
if (cd "$tmp_dir/yay-bin" && makepkg --noconfirm) &&
|
||||
mapfile -t pkgs < <(cd "$tmp_dir/yay-bin" && makepkg --packagelist | grep -v -- '-debug-') &&
|
||||
sudo pacman -U --noconfirm "${pkgs[@]}"; then
|
||||
ok "yay installed."
|
||||
rm -rf "$tmp_dir"
|
||||
return 0
|
||||
|
||||
@@ -9,6 +9,85 @@ single_status() {
|
||||
[[ "$(kreadconfig6 --file kdeglobals --group "KDE Action Restrictions" --key action/lock_screen)" == false ]]
|
||||
}
|
||||
|
||||
WALLET_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/kwalletd"
|
||||
# The user's own wallet while single user mode is on, and ours after.
|
||||
WALLET_BACKUP=".bak-steamify"
|
||||
WALLET_OURS=".steamify-single-user"
|
||||
|
||||
stop_kwallet() {
|
||||
# The wallet daemon keeps the file open and would write it back.
|
||||
local d
|
||||
for d in kwalletd6 ksecretd; do
|
||||
pkill -u "$USER" -x "$d" 2>/dev/null && sleep 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
single_wallet_enable() {
|
||||
# Like SteamOS: an empty, password-less wallet (Valve's own file), so
|
||||
# nothing asks for a wallet password (e.g. Brave at start): with
|
||||
# autologin nobody typed the login password that unlocks the usual one.
|
||||
# The user's wallet is moved aside and comes back when this is off. The
|
||||
# wallet from an earlier time in single user mode is reused, with
|
||||
# whatever was saved in it then.
|
||||
local tmp f
|
||||
if [[ -f "$WALLET_DIR/kdewallet.kwl$WALLET_OURS" ]]; then
|
||||
stop_kwallet
|
||||
for f in kdewallet.kwl kdewallet.salt; do
|
||||
[[ -f "$WALLET_DIR/$f" && ! -e "$WALLET_DIR/$f$WALLET_BACKUP" ]] &&
|
||||
mv "$WALLET_DIR/$f" "$WALLET_DIR/$f$WALLET_BACKUP"
|
||||
[[ -f "$WALLET_DIR/$f$WALLET_OURS" ]] && mv -f "$WALLET_DIR/$f$WALLET_OURS" "$WALLET_DIR/$f"
|
||||
done
|
||||
kset single kwalletrc Wallet "First Use" false
|
||||
ok "Single user mode's wallet (no password) is back; your own is kept as kdewallet.kwl$WALLET_BACKUP."
|
||||
return 0
|
||||
fi
|
||||
tmp="$(mktemp -d)"
|
||||
if ! fetch_valve_presets "$tmp"; then
|
||||
rm -rf "$tmp"
|
||||
warn "Couldn't get Valve's empty wallet; apps may still ask for the wallet password."
|
||||
return 0
|
||||
fi
|
||||
if [[ -f "$WALLET_DIR/kdewallet.kwl" ]] &&
|
||||
cmp -s "$WALLET_DIR/kdewallet.kwl" "$tmp/usr/share/kwalletd/kdewallet.kwl"; then
|
||||
rm -rf "$tmp"
|
||||
return 0
|
||||
fi
|
||||
stop_kwallet
|
||||
mkdir -p "$WALLET_DIR"
|
||||
for f in kdewallet.kwl kdewallet.salt; do
|
||||
# Never overwrite an earlier backup: that one is the user's.
|
||||
[[ -f "$WALLET_DIR/$f" && ! -e "$WALLET_DIR/$f$WALLET_BACKUP" ]] &&
|
||||
mv "$WALLET_DIR/$f" "$WALLET_DIR/$f$WALLET_BACKUP"
|
||||
install -m 600 "$tmp/usr/share/kwalletd/$f" "$WALLET_DIR/$f"
|
||||
done
|
||||
rm -rf "$tmp"
|
||||
kset single kwalletrc Wallet "First Use" false
|
||||
[[ -f "$WALLET_DIR/kdewallet.kwl$WALLET_BACKUP" ]] &&
|
||||
info "Your wallet is kept as $WALLET_DIR/kdewallet.kwl$WALLET_BACKUP and comes back when single user mode is off."
|
||||
ok "Empty wallet without a password: apps no longer ask for the wallet password."
|
||||
}
|
||||
|
||||
single_wallet_disable() {
|
||||
# The user's wallet back; ours (maybe with passwords saved since) is
|
||||
# kept next to it.
|
||||
[[ -f "$WALLET_DIR/kdewallet.kwl$WALLET_BACKUP" ]] || return 0
|
||||
stop_kwallet
|
||||
local f
|
||||
for f in kdewallet.kwl kdewallet.salt; do
|
||||
[[ -f "$WALLET_DIR/$f" ]] && mv -f "$WALLET_DIR/$f" "$WALLET_DIR/$f$WALLET_OURS"
|
||||
[[ -f "$WALLET_DIR/$f$WALLET_BACKUP" ]] && mv "$WALLET_DIR/$f$WALLET_BACKUP" "$WALLET_DIR/$f"
|
||||
done
|
||||
ok "Your own wallet is back (the empty one is kept as kdewallet.kwl$WALLET_OURS)."
|
||||
}
|
||||
|
||||
reload_screen_locker() {
|
||||
# The screen locker only reads kscreenlockerrc when the session starts:
|
||||
# without this it keeps locking after idling until the next login.
|
||||
qdbus6 org.freedesktop.ScreenSaver /ScreenSaver org.kde.screensaver.configure >/dev/null 2>&1 ||
|
||||
busctl --user call org.freedesktop.ScreenSaver /ScreenSaver org.kde.screensaver configure >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
single_launcher() {
|
||||
# Launcher: only Sleep / Restart / Shut Down, no Session dropdown (where
|
||||
# Log Out lives). Restricting action/logout instead would also hide
|
||||
@@ -42,6 +121,8 @@ single_enable() {
|
||||
single_launcher
|
||||
|
||||
restart_plasmashell_if_stopped
|
||||
reload_screen_locker
|
||||
single_wallet_enable
|
||||
ok "Single user: no lock screen, user switching or log out."
|
||||
}
|
||||
|
||||
@@ -70,5 +151,7 @@ single_disable() {
|
||||
done
|
||||
fi
|
||||
restart_plasmashell_if_stopped
|
||||
reload_screen_locker
|
||||
single_wallet_disable
|
||||
ok "KDE's normal lock screen and user switching are back."
|
||||
}
|
||||
+175
-1
@@ -14,16 +14,175 @@ detect_valve_fremont() {
|
||||
[[ "$vendor" == "OEM" && "$product" == "F7F" ]]
|
||||
}
|
||||
|
||||
# Kernel pinned on a Steam Machine: with newer linux-cachyos releases it
|
||||
# reboots instead of shutting down. The packages are kept in
|
||||
# PINNED_KERNEL_DIR, so re-applying (or reinstalling after an update slipped
|
||||
# through) needs no download.
|
||||
PINNED_KERNEL_VER="7.1.6-1"
|
||||
PINNED_KERNEL_KVER="7.1.6-1-cachyos"
|
||||
PINNED_KERNEL_PKGS=(linux-cachyos linux-cachyos-headers)
|
||||
PINNED_KERNEL_DIR="${PINNED_KERNEL_DIR:-/var/cache/steamify/kernel}"
|
||||
# SHA-256 of each package, so a file from any source is the one reviewed
|
||||
# here; its CachyOS signature is checked as well.
|
||||
declare -A PINNED_KERNEL_SHA256=(
|
||||
[linux-cachyos]=417fcd07102192b86e78e92ed7171d5a49378e9f57faea3fa2c7c541e9f68d08
|
||||
[linux-cachyos-headers]=d069866a11d9092746e1d5133cefd8924f027da2c9bf5af38d30b3aed6ded9bf
|
||||
)
|
||||
# Tried in order, after the kernel dir and pacman's cache. Our own release
|
||||
# always has these files; the archive keeps every release; the mirror only
|
||||
# the current one. PINNED_KERNEL_URL puts another source (a directory
|
||||
# holding the files) in front.
|
||||
PINNED_KERNEL_SOURCES=(
|
||||
${PINNED_KERNEL_URL:+"$PINNED_KERNEL_URL"}
|
||||
"https://github.com/theupriser/steamify-cachyos/releases/download/kernel-$PINNED_KERNEL_VER"
|
||||
"https://archive.cachyos.org/archive/cachyos"
|
||||
"https://mirror.cachyos.org/repo/x86_64/cachyos"
|
||||
)
|
||||
|
||||
pinned_kernel_installed() {
|
||||
local p
|
||||
for p in "${PINNED_KERNEL_PKGS[@]}"; do
|
||||
[[ "$(pacman -Q "$p" 2>/dev/null)" == "$p $PINNED_KERNEL_VER" ]] || return 1
|
||||
done
|
||||
}
|
||||
|
||||
fetch_pinned_kernel_file() {
|
||||
# $1 = file name. Local kernel dir first, then pacman's cache, then the
|
||||
# download sources. Downloads go to a .part file so a broken one is never
|
||||
# mistaken for a finished one.
|
||||
local f="$1" dest="$PINNED_KERNEL_DIR/$1" src
|
||||
[[ -s "$dest" ]] && return 0
|
||||
if [[ -s "/var/cache/pacman/pkg/$f" ]]; then
|
||||
sudo cp "/var/cache/pacman/pkg/$f" "$dest" && return 0
|
||||
fi
|
||||
for src in "${PINNED_KERNEL_SOURCES[@]}"; do
|
||||
if sudo curl -fL --retry 2 --connect-timeout 15 -o "$dest.part" "$src/$f" 2>/dev/null; then
|
||||
sudo mv "$dest.part" "$dest"
|
||||
return 0
|
||||
fi
|
||||
warn "Couldn't get $f from $src, trying the next source..."
|
||||
done
|
||||
sudo rm -f "$dest.part"
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_pinned_kernel_pkg() {
|
||||
# $1 = package name, $2 = file. Both checks must pass: the SHA-256 from
|
||||
# this script, and the CachyOS signature (pacman on its own installs a
|
||||
# local file without a .sig: LocalFileSigLevel = Optional).
|
||||
local sum
|
||||
sum="$(sha256sum "$2" | cut -d' ' -f1)"
|
||||
if [[ "$sum" != "${PINNED_KERNEL_SHA256[$1]}" ]]; then
|
||||
err "$(basename "$2") doesn't match its expected checksum."
|
||||
return 1
|
||||
fi
|
||||
if ! sudo pacman-key --verify "$2.sig" "$2" >/dev/null 2>&1; then
|
||||
err "$(basename "$2") doesn't have a valid CachyOS signature."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
pin_kernel_in_pacman_conf() {
|
||||
# Adds our packages to IgnorePkg in [options], keeping what's there.
|
||||
local p
|
||||
for p in "${PINNED_KERNEL_PKGS[@]}"; do
|
||||
grep -Eq "^IgnorePkg\s*=.*(\s|=)$p(\s|$)" /etc/pacman.conf && continue
|
||||
if grep -Eq '^IgnorePkg\s*=' /etc/pacman.conf; then
|
||||
sudo sed -i -E "0,/^IgnorePkg\s*=/s/^(IgnorePkg\s*=.*)$/\1 $p/" /etc/pacman.conf
|
||||
else
|
||||
sudo sed -i -E "0,/^\[options\]/s//[options]\nIgnorePkg = $p/" /etc/pacman.conf
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
unpin_kernel_in_pacman_conf() {
|
||||
local p
|
||||
for p in "${PINNED_KERNEL_PKGS[@]}"; do
|
||||
sudo sed -i -E "/^IgnorePkg\s*=/s/\s$p(\s|$)/\1/" /etc/pacman.conf
|
||||
done
|
||||
# Drop the line if nothing is left on it.
|
||||
sudo sed -i -E '/^IgnorePkg\s*=\s*$/d' /etc/pacman.conf
|
||||
}
|
||||
|
||||
install_pinned_kernel() {
|
||||
sudo mkdir -p "$PINNED_KERNEL_DIR"
|
||||
pin_kernel_in_pacman_conf
|
||||
if pinned_kernel_installed; then
|
||||
ok "Kernel $PINNED_KERNEL_VER already installed and pinned."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local p f files=()
|
||||
info "Getting kernel $PINNED_KERNEL_VER (kept in $PINNED_KERNEL_DIR)..."
|
||||
for p in "${PINNED_KERNEL_PKGS[@]}"; do
|
||||
f="$p-$PINNED_KERNEL_VER-x86_64.pkg.tar.zst"
|
||||
fetch_pinned_kernel_file "$f" && fetch_pinned_kernel_file "$f.sig" ||
|
||||
{ err "Couldn't download $f from any source."; return 1; }
|
||||
if ! verify_pinned_kernel_pkg "$p" "$PINNED_KERNEL_DIR/$f"; then
|
||||
# Removed, so the next run fetches it again.
|
||||
sudo rm -f "$PINNED_KERNEL_DIR/$f" "$PINNED_KERNEL_DIR/$f.sig"
|
||||
err "Removed it; run the wizard again to download it once more."
|
||||
return 1
|
||||
fi
|
||||
files+=("$PINNED_KERNEL_DIR/$f")
|
||||
done
|
||||
|
||||
# pacman checks each package against the .sig next to it.
|
||||
info "Installing kernel $PINNED_KERNEL_VER..."
|
||||
if ! sudo pacman -U --noconfirm "${files[@]}"; then
|
||||
err "Installing kernel $PINNED_KERNEL_VER failed."
|
||||
return 1
|
||||
fi
|
||||
[[ "$(uname -r)" != "$PINNED_KERNEL_KVER" ]] && RESTART_FOR_LOGIN=true
|
||||
ok "Kernel $PINNED_KERNEL_VER installed and pinned (restart to use it)."
|
||||
}
|
||||
|
||||
remove_kernel_pin() {
|
||||
# Back to CachyOS's current kernel. The packages stay in
|
||||
# PINNED_KERNEL_DIR, so turning support on again doesn't download.
|
||||
unpin_kernel_in_pacman_conf
|
||||
pinned_kernel_installed || return 0
|
||||
info "Updating the kernel back to CachyOS's current version..."
|
||||
sudo pacman -Syu --noconfirm ||
|
||||
{ warn "Updating the kernel failed; run: sudo pacman -Syu"; return 0; }
|
||||
RESTART_FOR_LOGIN=true
|
||||
}
|
||||
|
||||
# "Pin the kernel" menu item, a sub-option of Steam Machine support.
|
||||
kpin_status() {
|
||||
pinned_kernel_installed && grep -Eq '^IgnorePkg\s*=.*\slinux-cachyos(\s|$)' /etc/pacman.conf
|
||||
}
|
||||
|
||||
kpin_enable() {
|
||||
install_pinned_kernel || return 1
|
||||
# The DKMS pacman hook built leds-valve for the new headers; make sure.
|
||||
if pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 &&
|
||||
! dkms status -k "$PINNED_KERNEL_KVER" leds-valve-dkms 2>/dev/null | grep -q installed; then
|
||||
sudo dkms install leds-valve-dkms/0.1 -k "$PINNED_KERNEL_KVER" ||
|
||||
warn "Building the LED driver for $PINNED_KERNEL_KVER failed."
|
||||
fi
|
||||
}
|
||||
|
||||
kpin_disable() { remove_kernel_pin; }
|
||||
|
||||
install_kernel_headers() {
|
||||
# DKMS can only build leds-valve against kernels whose headers are
|
||||
# installed. CachyOS ships several kernels (linux-cachyos, -lts, -bore,
|
||||
# ...); install the matching -headers package for every one present.
|
||||
local -a kernels headers=()
|
||||
mapfile -t kernels < <(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u)
|
||||
local k
|
||||
mapfile -t kernels < <(pacman -Qqo /usr/lib/modules/*/pkgbase 2>/dev/null | sort -u)
|
||||
# Installed headers are left alone: -S would update them past a pinned
|
||||
# kernel (linux-cachyos-headers on a Steam Machine).
|
||||
local have=0
|
||||
for k in "${kernels[@]}"; do
|
||||
pacman -Q "${k}-headers" >/dev/null 2>&1 && { have=$((have + 1)); continue; }
|
||||
pacman -Si "${k}-headers" >/dev/null 2>&1 && headers+=("${k}-headers")
|
||||
done
|
||||
if [[ ${#headers[@]} -eq 0 && $have -gt 0 ]]; then
|
||||
ok "Kernel headers already installed."
|
||||
return 0
|
||||
fi
|
||||
if [[ ${#headers[@]} -eq 0 ]]; then
|
||||
warn "Couldn't work out which kernel headers package you need."
|
||||
warn "Install the -headers package for your kernel (e.g. linux-cachyos-headers) yourself."
|
||||
@@ -92,6 +251,13 @@ install_valve_led_driver() {
|
||||
# Load now, and on every boot.
|
||||
echo leds-valve | sudo tee /etc/modules-load.d/leds-valve.conf >/dev/null
|
||||
|
||||
# The running kernel was just replaced (the kernel pin): its modules are
|
||||
# gone, so the driver can only load after the restart.
|
||||
if [[ ! -d "/usr/lib/modules/$(uname -r)/kernel" ]]; then
|
||||
ok "The LED driver loads after the restart (into the new kernel)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "Loading the leds-valve kernel module..."
|
||||
if sudo modprobe leds-valve; then
|
||||
ok "Module loaded."
|
||||
@@ -185,6 +351,7 @@ kernel_overview() {
|
||||
fi
|
||||
echo -e " ${running}${line}"
|
||||
done
|
||||
cec_installed && { echo; echo -e " $(cec_overview)"; }
|
||||
if [[ "$leds" == true ]]; then
|
||||
local loaded="$miss" nodes
|
||||
lsmod | grep -q '^leds_valve' && loaded="$mark"
|
||||
@@ -201,6 +368,11 @@ machine_status() {
|
||||
}
|
||||
|
||||
machine_enable() {
|
||||
# The pinned kernel first when it's wanted too, so DKMS builds the LED
|
||||
# driver for it once, instead of for the current kernel and then again.
|
||||
if [[ "${WANTED[kpin]:-0}" == 1 ]]; then
|
||||
install_pinned_kernel || { err "Couldn't set up kernel $PINNED_KERNEL_VER."; return 1; }
|
||||
fi
|
||||
ensure_aur_helper || { warn "Couldn't set up an AUR helper automatically. Install yay or paru, then run the wizard again."; return 1; }
|
||||
install_valve_led_driver || { warn "LED driver setup ran into a problem - see errors above."; return 1; }
|
||||
install_headers_boot_check
|
||||
@@ -223,6 +395,8 @@ EOF
|
||||
sudo systemctl enable --now inputplumber.service
|
||||
sudo systemctl enable --now steamos-manager.service
|
||||
systemctl --user enable steamos-manager.service 2>/dev/null
|
||||
# HDMI-CEC ran before this and couldn't link cecd to steamos-manager yet.
|
||||
cec_status && cec_link_steamos_manager
|
||||
|
||||
# Console-like power handling, as SteamOS's powerdevilrc (mains-power
|
||||
# part; there is no battery): the power button sleeps instead of showing
|
||||
|
||||
+2
-12
@@ -3,7 +3,7 @@
|
||||
# newest Valve steamdeck-kde-presets package: "Add to Steam" (file
|
||||
# right-click menu and launcher), Nested Desktop (Plasma as a game inside
|
||||
# gaming mode), the "Return to Gaming Mode" icon, the Steam Keyboard window
|
||||
# rule and an empty KWallet. Part of the SteamOS theme component
|
||||
# rule. Part of the SteamOS theme component
|
||||
# (lib/vapor-theme.sh). System files go to /usr/local (nothing
|
||||
# package-owned); per-user settings go through the undo journal.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
@@ -75,15 +75,6 @@ extras_enable() {
|
||||
sudo cp -r "$src/share/applications/steam/holo-nested-desktop" "$NESTED_DIR"
|
||||
sudo sed -i "s|/usr/share/applications/steam/holo-nested-desktop|$NESTED_DIR|" "$NESTED_DIR/holo-nested-desktop.desktop"
|
||||
|
||||
# An empty, password-less wallet (Valve's), so nothing asks for a wallet
|
||||
# password: with autologin nobody typed one to unlock it. Only when the
|
||||
# user has no wallet yet; an existing one is never touched.
|
||||
local wallet="${XDG_DATA_HOME:-$HOME/.local/share}/kwalletd"
|
||||
if [[ ! -f "$wallet/kdewallet.kwl" ]]; then
|
||||
mkdir -p "$wallet"
|
||||
install -m 600 "$src/share/kwalletd/kdewallet.kwl" "$src/share/kwalletd/kdewallet.salt" "$wallet/"
|
||||
kset theme kwalletrc Wallet "First Use" false
|
||||
fi
|
||||
rm -rf "$tmp_dir"
|
||||
|
||||
sudo gtk-update-icon-cache -q -f -t /usr/local/share/icons/hicolor 2>/dev/null || true
|
||||
@@ -122,7 +113,6 @@ extras_disable() {
|
||||
for pkg in kdialog zstd curl; do
|
||||
[[ -n "$(state_get theme "installed_$pkg")" ]] && sudo pacman -R --noconfirm "$pkg" >/dev/null 2>&1
|
||||
done
|
||||
# The keyboard rule and kwalletrc are reverted by the theme's journal.
|
||||
# The wallet itself stays: secrets may have been saved in it since.
|
||||
# The keyboard rule is reverted by the theme's journal.
|
||||
return 0
|
||||
}
|
||||
+16
-7
@@ -10,16 +10,16 @@
|
||||
# them on or off to match what the user picks; turning one off restores
|
||||
# what was there before (system files from .bak-gamescope-wizard backups,
|
||||
# KDE settings from the undo journal in lib/state.sh). Components live in
|
||||
# lib/, see README.md. Safe to re-run.
|
||||
# lib/, see TECHNICAL.md. Safe to re-run.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Release version, see CHANGELOG.md.
|
||||
VERSION=0.9.1
|
||||
VERSION=1.2.0
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
for lib in common state packages login-manager single-user steam-desktop steam-machine vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu; do
|
||||
for lib in common state packages login-manager single-user steam-desktop steam-machine hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut menu; do
|
||||
# shellcheck source=/dev/null
|
||||
source "$SCRIPT_DIR/lib/$lib.sh"
|
||||
done
|
||||
@@ -118,9 +118,13 @@ while true; do
|
||||
|
||||
echo
|
||||
echo -e "${c_bold}This will:${c_reset}"
|
||||
for c in "${TO_DISABLE[@]}"; do echo " - turn off: ${LABEL[$c]}"; done
|
||||
for c in "${TO_DISABLE[@]}"; do
|
||||
if [[ "$c" == boot ]]; then echo " - boot into: gamescope (from the next boot)"
|
||||
else echo " - turn off: ${LABEL[$c]}"; fi
|
||||
done
|
||||
for c in "${TO_ENABLE[@]}"; do
|
||||
if is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)"
|
||||
if [[ "$c" == boot ]]; then echo " - boot into: desktop (from the next boot)"
|
||||
elif is_action "$c"; then echo " - run: ${LABEL[$c]%%:*} at your own risk (checks, then asks twice more)"
|
||||
elif [[ "${CURRENT[$c]}" == 1 ]]; then echo " - re-apply: ${LABEL[$c]}"; else echo " - turn on: ${LABEL[$c]}"; fi
|
||||
done
|
||||
ask_yn "Go ahead?" y || { info "Nothing changed."; continue; }
|
||||
@@ -135,7 +139,7 @@ while true; do
|
||||
|
||||
apply_changes
|
||||
# Login manager changes only take effect after a restart.
|
||||
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" gaming "* || " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " == *" single "* ]] &&
|
||||
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] &&
|
||||
RESTART_FOR_LOGIN=true
|
||||
|
||||
echo
|
||||
@@ -143,7 +147,12 @@ while true; do
|
||||
echo -e "${c_bold}Done. Current state:${c_reset}"
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
component_available "$c" && ! is_action "$c" || continue
|
||||
if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi
|
||||
if [[ "$c" == boot ]]; then
|
||||
[[ "${CURRENT[gaming]}" == 1 ]] && echo " └ boots into: $(boot_mode "${CURRENT[boot]}")"
|
||||
elif [[ -n "${PARENT[$c]:-}" ]]; then
|
||||
[[ "${CURRENT[${PARENT[$c]}]}" == 1 ]] || continue
|
||||
if [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " └ ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " └ off ${LABEL[$c]}"; fi
|
||||
elif [[ "${CURRENT[$c]}" == 1 ]]; then echo -e " ${c_green}on ${c_reset} ${LABEL[$c]}"; else echo " off ${LABEL[$c]}"; fi
|
||||
done
|
||||
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
warn "These had problems (see above): ${FAILED[*]}"
|
||||
|
||||
Reference in new issue
Block a user