mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d93f9ea56c | ||
|
|
761f921e40 | ||
|
|
7a54a2fa59 | ||
|
|
9eab3978a9 | ||
|
|
9602d9d00b | ||
|
|
9a2965d339 | ||
|
|
c42d5454bf | ||
|
|
b3bed43854 | ||
|
|
3e79451244 | ||
|
|
971e792054 | ||
|
|
6557320c81 | ||
|
|
692e3fcbad | ||
|
|
70ecb563fd | ||
|
|
a7276cfd9a | ||
|
|
b3549d4eea | ||
|
|
a232a55bef | ||
|
|
397b3cb607 | ||
|
|
bda1d3ebbe | ||
|
|
3676c64c90 | ||
|
|
d44f29956a | ||
|
|
2858d13429 | ||
|
|
48cf3c7c7c | ||
|
|
619ef1f714 | ||
|
|
b6240539e6 | ||
|
|
7d6df21627 | ||
|
|
3cad70dcc4 | ||
|
|
b8742a5642 | ||
|
|
7f5d29c997 | ||
|
|
cfe3ce7bd0 | ||
|
|
3e4133c14a | ||
|
|
c4b8db85dc | ||
|
|
350011877b | ||
|
|
b1d79d48a6 | ||
|
|
5b42036786 | ||
|
|
4a64232fad | ||
|
|
4ec92f4d89 | ||
|
|
fe4f3f43be | ||
|
|
4bacc3b2e6 | ||
|
|
cc0ca1c35e | ||
|
|
580cc71b57 | ||
|
|
c87e6c763f | ||
|
|
fc05725a91 | ||
|
|
90bc5e4408 | ||
|
|
d99ff652dc | ||
|
|
2aa3068b60 | ||
|
|
1a6ddcfc0d | ||
|
|
11668db91c | ||
|
|
3316c1de28 | ||
|
|
d9365dac81 | ||
|
|
853a3926ee | ||
|
|
66ea63cf7e | ||
|
|
f560f332a9 | ||
|
|
9b86cd1a6c | ||
|
|
a72a4def71 | ||
|
|
ea051c77f6 | ||
|
|
4e829c6111 | ||
|
|
d73e71d4eb | ||
|
|
8f2d2be2aa | ||
|
|
53b72c8e33 | ||
|
|
3b4f3b2313 | ||
|
|
a72d848e78 | ||
|
|
3a58c72d16 | ||
|
|
27a40702ce | ||
|
|
8ab066f034 | ||
|
|
44bfe071d6 | ||
|
|
e281d5e879 | ||
|
|
435f69c151 | ||
|
|
9319cbca1e | ||
|
|
cf6bee1c86 | ||
|
|
16487ba00c | ||
|
|
07a94cd274 | ||
|
|
ce71b34b0d | ||
|
|
d30d505f7d | ||
|
|
9755cc3ac3 | ||
|
|
6f26e74229 | ||
|
|
2a01583b8f | ||
|
|
c40aa1f7d9 | ||
|
|
d204efb59d | ||
|
|
12f6cc0b3a | ||
|
|
b01e2c5221 | ||
|
|
affb5f79b0 | ||
|
|
58e43d4c4a | ||
|
|
23f265b02f | ||
|
|
ced4a45d2f | ||
|
|
6ad686a8b2 | ||
|
|
b1adacbcb3 | ||
|
|
8dd2db3a97 | ||
|
|
501c0ba425 | ||
|
|
bee18b76a8 | ||
|
|
eadbdda271 | ||
|
|
c4ec755a4a | ||
|
|
14aa7c0b54 | ||
|
|
5bf9da411d | ||
|
|
4f6e6fd0aa | ||
|
|
a79eeedbc4 | ||
|
|
b948c2020b | ||
|
|
0c96bed448 | ||
|
|
e3f7cc9773 | ||
|
|
353de489d7 | ||
|
|
d8b2e812c5 | ||
|
|
3a0692fdf2 | ||
|
|
e86ddcc4af | ||
|
|
389bc3346e | ||
|
|
c6c1150836 | ||
|
|
98e2effcff | ||
|
|
431515b60f |
No files matched your search
+112
-23
@@ -5,18 +5,16 @@
|
||||
# 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
|
||||
# Each release also has the app (v2): steamify-app.tar.gz (ui/, steamify.sh,
|
||||
# Each release also has the app: steamify-app.tar.gz (ui/, steamify.sh,
|
||||
# lib/, patches/, services/, the icon) and its launcher, run with:
|
||||
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash
|
||||
# A push to feat/v2-ui (the app, not released yet) updates the pre-release
|
||||
# v2-ui-preview instead, whose launcher gets that build:
|
||||
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/download/v2-ui-preview/steamify-app.sh | bash
|
||||
# Pushes to release/** build and check it like pull requests; only main publishes.
|
||||
# Pull requests only build and check it, and warn when VERSION is already released.
|
||||
name: Single-file build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, feat/v2-ui]
|
||||
branches: [main, 'release/**']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -42,12 +40,39 @@ jobs:
|
||||
- name: Check
|
||||
run: |
|
||||
bash -n dist/steamify.sh
|
||||
# SC2034/SC2154: variables used across the inlined modules.
|
||||
shellcheck -S warning -e SC2034,SC2154 dist/steamify.sh
|
||||
shellcheck -S warning dist/steamify-app.sh
|
||||
python3 -m py_compile ui/steamify-ui
|
||||
|
||||
# shellcheck through an action that brings its own binary: GitHub's runner has it installed,
|
||||
# Gitea's act_runner image doesn't. One file per run, as before (the exceptions are the bundle's).
|
||||
- name: shellcheck the bundle
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
env:
|
||||
# SC2034/SC2154: variables used across the inlined modules.
|
||||
SHELLCHECK_OPTS: -e SC2034 -e SC2154
|
||||
with:
|
||||
severity: warning
|
||||
scandir: dist
|
||||
ignore_names: steamify-app.sh
|
||||
- name: shellcheck the app launcher
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
with:
|
||||
severity: warning
|
||||
scandir: dist
|
||||
ignore_names: steamify.sh
|
||||
|
||||
# The build as an artifact on GitHub and on the mirror (Gitea); releases stay GitHub's.
|
||||
# Gitea's artifact server doesn't speak upload-artifact@v4's protocol, and GitHub fails a workflow
|
||||
# that merely mentions the deprecated @v3: Gitea gets christopherhx's fork of v4 for Gitea.
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: github.server_url == 'https://github.com'
|
||||
with:
|
||||
name: steamify
|
||||
path: |
|
||||
dist/steamify.sh
|
||||
dist/steamify-app.tar.gz
|
||||
dist/steamify-app.sh
|
||||
- uses: christopherhx/gitea-upload-artifact@v4
|
||||
if: github.server_url != 'https://github.com'
|
||||
with:
|
||||
name: steamify
|
||||
path: |
|
||||
@@ -57,6 +82,7 @@ jobs:
|
||||
|
||||
- name: Version
|
||||
id: version
|
||||
if: github.server_url == 'https://github.com'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
@@ -71,6 +97,7 @@ jobs:
|
||||
|
||||
- name: Publish the v${{ steps.version.outputs.version }} release
|
||||
if: >-
|
||||
github.server_url == 'https://github.com' &&
|
||||
github.event_name != 'pull_request' && github.ref == 'refs/heads/main' &&
|
||||
steps.version.outputs.released == 'false'
|
||||
env:
|
||||
@@ -107,22 +134,84 @@ jobs:
|
||||
fi
|
||||
gh release upload latest dist/steamify.sh dist/steamify-app.sh dist/steamify-app.tar.gz assets/steam-gaming-settings.svg --clobber
|
||||
|
||||
- name: Publish the v2-ui-preview pre-release
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/feat/v2-ui'
|
||||
# The mirror (Gitea) publishes its own release per version too, the same way: never overwrites one,
|
||||
# notes from the changelog. gh can't talk to Gitea: its API for the check, a Gitea release action to
|
||||
# publish. The token is the run's own (repository write); a secret RELEASE_TOKEN overrides it.
|
||||
- name: Version (mirror)
|
||||
id: mirror_version
|
||||
if: github.server_url != 'https://github.com'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||
run: |
|
||||
# Its launcher downloads this pre-release, not the newest release.
|
||||
sed -i 's/STEAMIFY_RELEASE:-latest/STEAMIFY_RELEASE:-v2-ui-preview/' dist/steamify-app.sh
|
||||
grep -q 'STEAMIFY_RELEASE:-v2-ui-preview' dist/steamify-app.sh
|
||||
git tag -f v2-ui-preview "$GITHUB_SHA" && git push -f origin v2-ui-preview
|
||||
notes="Preview of the Steamify app (v2) from feat/v2-ui ($(git describe --always), $(git log -1 --format='%cs %s')). Not a release.
|
||||
Run it with:
|
||||
|
||||
curl -fsSL https://github.com/${{ github.repository }}/releases/download/v2-ui-preview/steamify-app.sh | bash"
|
||||
if gh release view v2-ui-preview >/dev/null 2>&1; then
|
||||
gh release edit v2-ui-preview --title "Steamify app (v2) preview" --notes "$notes" --prerelease --latest=false
|
||||
version="$(grep -oP '^VERSION=\K.*' steamify.sh)"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
if curl -fsS -o /dev/null -H "Authorization: token $TOKEN" \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/tags/v$version"; then
|
||||
echo "released=true" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::v$version is already released on this server and won't be overwritten."
|
||||
else
|
||||
gh release create v2-ui-preview --target "$GITHUB_SHA" --title "Steamify app (v2) preview" --notes "$notes" --prerelease --latest=false
|
||||
echo "released=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
gh release upload v2-ui-preview dist/steamify-app.sh dist/steamify-app.tar.gz --clobber
|
||||
|
||||
- name: Release notes (mirror)
|
||||
if: >-
|
||||
github.server_url != 'https://github.com' &&
|
||||
github.event_name != 'pull_request' && github.ref == 'refs/heads/main' &&
|
||||
steps.mirror_version.outputs.released == 'false'
|
||||
env:
|
||||
VERSION: ${{ steps.mirror_version.outputs.version }}
|
||||
run: |
|
||||
changes="$(awk -v v="$VERSION" '
|
||||
$0 ~ "^## " { if (found) exit; if (index($0, "## " v " ") == 1) { found = 1; next } }
|
||||
found' CHANGELOG.md)"
|
||||
cat > dist/release-notes.md <<NOTES
|
||||
Single-file build of v$VERSION ($(git describe --always), $(git log -1 --format=%cs)).
|
||||
Run this version with:
|
||||
|
||||
curl -fsSL ${{ github.server_url }}/${{ github.repository }}/releases/download/v$VERSION/steamify.sh | bash
|
||||
|
||||
## Changes in v$VERSION
|
||||
$changes
|
||||
NOTES
|
||||
|
||||
- name: Publish the v${{ steps.mirror_version.outputs.version }} release (mirror)
|
||||
if: >-
|
||||
github.server_url != 'https://github.com' &&
|
||||
github.event_name != 'pull_request' && github.ref == 'refs/heads/main' &&
|
||||
steps.mirror_version.outputs.released == 'false'
|
||||
uses: akkuman/gitea-release-action@v1.3.7
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_TOKEN || github.token }}
|
||||
tag_name: v${{ steps.mirror_version.outputs.version }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
name: Steamify CachyOS v${{ steps.mirror_version.outputs.version }}
|
||||
body_path: dist/release-notes.md
|
||||
files: |
|
||||
dist/steamify.sh
|
||||
dist/steamify-app.sh
|
||||
dist/steamify-app.tar.gz
|
||||
assets/steam-gaming-settings.svg
|
||||
|
||||
# A new version starts the Steamify ISO's release of the ISO repo: its iso-1-github-tag.yml (always run from
|
||||
# master) names the ISO after the newest Steamify release, tags it, and the Gitea mirror builds and attaches the ISO.
|
||||
# The branch here decides the kind of build (the workflow's input `kind`):
|
||||
# main (publishes the release) -> release: a real ISO (tag v<steamify>-<date>)
|
||||
# release/** (a version not yet released) -> dev: a pre-release (tag v<steamify>-dev.<date>)
|
||||
# Needs a fine-grained token for that repo (Actions: read and write) as the secret ISO_DISPATCH_TOKEN;
|
||||
# without it, this is skipped.
|
||||
- name: Start the Steamify ISO's release
|
||||
if: >-
|
||||
github.server_url == 'https://github.com' &&
|
||||
github.event_name != 'pull_request' &&
|
||||
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) &&
|
||||
steps.version.outputs.released == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ISO_DISPATCH_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::notice::No ISO_DISPATCH_TOKEN: the Steamify ISO isn't released with this version."; exit 0
|
||||
fi
|
||||
# A release branch builds the ISO with its own code (and VERSION): steamify_ref. main builds the release it just published.
|
||||
if [ "$GITHUB_REF" = refs/heads/main ]; then kind=release; sref=""; else kind=dev; sref="$GITHUB_REF_NAME"; fi
|
||||
gh workflow run iso-1-github-tag.yml -R theupriser/steamify-cachyos-live-iso --ref master -f kind="$kind" -f steamify_ref="$sref"
|
||||
echo "Started the Steamify ISO's release (theupriser/steamify-cachyos-live-iso, master, kind $kind, Steamify ${sref:-the newest release})"
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Sync git.upriser.nl mirror
|
||||
|
||||
# Trigger the Gitea pull-mirror immediately instead of waiting for its interval,
|
||||
# so Gitea builds/tests right after any branch or tag lands on GitHub (the mirror has no sync interval of its own).
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['*']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
# Gitea also reads .github/workflows on the mirror; only run on GitHub
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger mirror-sync
|
||||
env:
|
||||
GITEA_URL: ${{ secrets.GIT_UPRISER_URL }}
|
||||
GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }}
|
||||
# Gitea "owner/repo"; defaults to the same path as on GitHub
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.repository }}
|
||||
run: |
|
||||
curl --fail-with-body -sS --retry 5 --retry-delay 10 --retry-all-errors -X POST \
|
||||
-H "Authorization: token ${GIT_UPRISER_TOKEN}" \
|
||||
"${GITEA_URL%/}/api/v1/repos/${GITEA_REPO}/mirror-sync"
|
||||
@@ -68,6 +68,13 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
the entry point's `for lib in ...; do` source loop and wraps everything
|
||||
after that loop in `main()`. Keep that loop on one line, keep all logic in
|
||||
functions, and don't rely on `SCRIPT_DIR` for anything but sourcing.
|
||||
The workflow is one file for GitHub and a Gitea mirror (`github.server_url`): build and check run on both
|
||||
(shellcheck through `ludeeus/action-shellcheck`, which brings its own binary; the artifact with
|
||||
`upload-artifact@v4` on GitHub and `christopherhx/gitea-upload-artifact@v4` elsewhere: never mention
|
||||
`upload-artifact@v3`, GitHub fails a workflow for a deprecated version even in a skipped step); the
|
||||
release is `gh` on GitHub and `akkuman/gitea-release-action` on Gitea. A new release on GitHub also starts the
|
||||
Steamify ISO's release (`gh workflow run iso-1-github-tag.yml` in steamify-cachyos-live-iso, secret
|
||||
`ISO_DISPATCH_TOKEN`; skipped without it): the ISO repo's GitHub tag names it, the Gitea mirror builds it.
|
||||
CI (`.github/workflows/bundle.yml`) publishes the bundle as release
|
||||
`v$VERSION` on pushes to `main`; an existing version is never overwritten,
|
||||
so bump `VERSION` for every release.
|
||||
@@ -98,11 +105,36 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
+ subject; a commit can't contain its own hash, so fill it in with the
|
||||
next commit). The section heading must be `## <VERSION> - <date>`: CI
|
||||
cuts the release notes out of the changelog by that heading.
|
||||
- Every PR that should be released bumps `VERSION` and adds its section.
|
||||
A push to `main` publishes release `v$VERSION` (tag + bundle + that
|
||||
- **Branches: one release branch per version.** Work for a release goes
|
||||
into `release/X.Y.Z` (e.g. `release/2.7.0`), branched from `main` when
|
||||
that version starts. That branch bumps `VERSION` to `X.Y.Z` and adds the
|
||||
`## X.Y.Z - <date>` changelog section (first commit on it).
|
||||
- Every change gets its own branch from the release branch:
|
||||
`feature/<name>` (new behaviour) or `bugfix/<name>` (a fix), and a
|
||||
pull request **into the release branch**, not into `main`. Its commits
|
||||
add their changelog lines to that version's section.
|
||||
- When the release is done and tested (the test plan in
|
||||
steamify-cachyos-dev, `TESTPLAN.md`), the release branch gets a pull
|
||||
request **into `main`**. Merging it publishes the release (below).
|
||||
- A fix for a released version: a new `release/X.Y.Z+1` from `main`,
|
||||
with the `bugfix/` branch into it; don't reopen an old release branch.
|
||||
- Up to the release branch, the agent manages it: create the
|
||||
`feature/`/`bugfix/` branches, commit, push, and merge them into the
|
||||
release branch itself once tested (with `gh`: `gh pr create --base
|
||||
release/X.Y.Z` + `gh pr merge --merge`; no review needed). Without
|
||||
`gh` (`command -v gh`), ask the user to install it (Ubuntu:
|
||||
`sudo apt install gh`, then `gh auth login`) instead of working around
|
||||
it. Only the
|
||||
release branch's pull request into `main` is the user's: never commit
|
||||
or merge to `main`, the user merges that one.
|
||||
Keep a feature/bugfix branch up to date by merging (or rebasing on)
|
||||
its release branch, the release branch by merging `main` when that
|
||||
moved.
|
||||
- A push to `main` publishes release `v$VERSION` (tag + bundle + that
|
||||
changelog section) and marks it latest. An existing version is never
|
||||
overwritten: without a bump nothing is released, and pull requests show
|
||||
a warning.
|
||||
a warning when `VERSION` is a version that's already released (so a
|
||||
release branch that forgot its bump shows it on every PR).
|
||||
- A release that adds, removes or renames a menu row also retakes the
|
||||
README screenshot (`assets/screenshot-menu.png`): the app from the
|
||||
branch on a Steam Machine, every row visible (window 1280 wide, tall
|
||||
@@ -227,6 +259,11 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
kernel only pairs that with a named lookup ("Port C") when the CEC driver
|
||||
registered first, which never happens since amdgpu loads from the
|
||||
initramfs. Without it `/dev/cec0` exists but stays at `f.f.f.f`.
|
||||
The source comes from one raw.githubusercontent.com download, which GitHub rate-limits (HTTP 429 after a
|
||||
day of test installs, per address): it's kept in `/var/cache/steamify/cros-ec-cec-<sha256:12>.c` and reused
|
||||
while its checksum matches. The name carries the checksum, so a new pin (commit + SHA-256 in `lib/cec.sh`)
|
||||
downloads its own file once and removes the old one. The repo is an unofficial mirror of Valve's kernel
|
||||
without releases: if it ever disappears, ship the file with Steamify. The test VMs share the host's cache.
|
||||
- Power-off fix (`poweroff`, `lib/fremont-poweroff.sh`, a default sub-option
|
||||
of `machine`): recent kernels (7.2 and the 6.x/7.0/7.1 updates with the
|
||||
backport) keep the firmware's S4/S5 wake bit on GPIO
|
||||
@@ -243,6 +280,15 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
`linux-cachyos` is clang-built, `-bore` GCC-built: let DKMS pick the
|
||||
compiler, never pass `LLVM=1`. Test shutdown on the real machine for every
|
||||
new major kernel.
|
||||
- Add as non-Steam game (`steamgame`, `lib/steam-game.sh`, sub-option of
|
||||
`launcher`, ticked along with it, 2.5.1): edits Steam's `shortcuts.vdf`
|
||||
with `patches/steam-shortcuts.py` only while Steam is closed (Steam
|
||||
rewrites it on exit), then starts Steam again. Never close Steam in gaming
|
||||
mode or when `SteamGameId` is set (the run is a Steam game). An existing
|
||||
entry for the start script is updated, not doubled; removal takes every
|
||||
entry for it. Controller input through Steam's desktop layout arrives as
|
||||
keys: Enter must select (A), Esc must never quit (B); the app re-execs
|
||||
without Steam's overlay preload.
|
||||
- Update notifications (`notify`, `lib/update-notifier.sh`, top-level,
|
||||
ticked by default, 2.5.0): a user timer (daily, and the service is wanted
|
||||
by `plasma-workspace.target`) runs `patches/steamify-notifier.py`, which
|
||||
@@ -260,6 +306,38 @@ gamescope and the Plasma desktop. Primary target: the Valve Steam Machine
|
||||
latter only starts with `kcgroupsrc [Foreground Booster] autostart=true`
|
||||
(set with `kset`). Disable removes only the packages it installed
|
||||
(`state_get vram installed_pkgs`).
|
||||
- Install-time mode (`steamify.sh --defaults`, 2.6.0): applies what the menu
|
||||
would preselect, no menu or prompts (needs passwordless sudo). The Steam
|
||||
Machine ISO ([steamify-cachyos-live-iso](https://github.com/theupriser/steamify-cachyos-live-iso))
|
||||
runs it in the installer for a user who has never logged in: no session
|
||||
bus, user systemd or plasmashell. So every `systemctl --user` goes through
|
||||
`user_systemctl` (`lib/common.sh`; without a session only unit files change,
|
||||
via `--root=/`), and the theme, when the layout is still `/etc/skel`'s,
|
||||
removes it so Plasma builds Vapor's layout at the first login. What needs
|
||||
that layout (single user's launcher) runs then, from a one-time autostart
|
||||
(`lib/first-login.sh`, `--first-login`) that also opens the app.
|
||||
`--options <id>,...` (exactly these on, the rest off; an item on brings its
|
||||
parent) and `--boot gamescope|desktop` (desktop only with `gaming`) (2.7.0,
|
||||
`defaults_options` in `lib/menu.sh`); the ISO's installer page passes them.
|
||||
Items this PC can't use are left off with a warning, not an error.
|
||||
`steamify.sh --boot gamescope|desktop` on its own (2.7.0, for scripts) only
|
||||
switches `boot` on an installed conversion: `WANTED` is `CURRENT` plus
|
||||
that, so no updates or removals a normal run would pick.
|
||||
- What Steam's System settings show (SteamOS conversion, 2.9.0): the OS name
|
||||
stays CachyOS's everywhere (legal clarity; Steam's OS Name is
|
||||
`lsb_release -d`, not touched). `/etc/os-release` gets `VERSION_ID=steamos-X.Y`
|
||||
(Steam's OS Version: Valve's newest jupiter repo; offline, the previous
|
||||
value), `VERSION_CODENAME=steam-machine`, `VARIANT="Steamify <version>"` and
|
||||
`VARIANT_ID=steamify-<version>` (Steam shows VARIANT_ID as OS Variant),
|
||||
refreshed by `os_version_refresh` after every run that changes something; a
|
||||
pacman hook (`zz-steamify-os-release`) runs after cachyos-hooks. KDE's About
|
||||
this System would show VERSION_ID after the name: `kcm-about-distrorc`
|
||||
`UseOSReleaseVersion=true` (kset) makes it show os-release's VERSION, which
|
||||
CachyOS doesn't have. Never `NAME` or `PRETTY_NAME`: limine-snapper-sync
|
||||
finds the boot entries by the OS name ("Target OS name ... not found in
|
||||
/boot/limine.conf"); `/etc/default/limine` gets `TARGET_OS_NAME="CachyOS"`
|
||||
when it has none, as a guard. The serial number (tmpfiles rule) is Steam
|
||||
Machine support's, so Fremont only.
|
||||
- `Relogin=true` means a gamescope that fails to start is relaunched in a
|
||||
tight loop; keep that in mind when changing session handling.
|
||||
|
||||
|
||||
@@ -5,6 +5,95 @@ All notable changes, per version and per commit. Versions follow
|
||||
`steamify.sh`. Versions before 0.7.0 were numbered afterwards,
|
||||
one per merged pull request.
|
||||
|
||||
## 2.9.6 - 2026-09-30
|
||||
|
||||
- **fix(ci): a dev ISO from a release branch is built with that branch's own Steamify (input steamify_ref of the ISO workflow), not the newest published release: release/2.9.6 kept producing 2.9.5 ISOs**
|
||||
- **docs: changelog hashes for 2.9.6**
|
||||
- `9a2965d` **fix(ci): the ISO workflow always runs from master; main asks for a release ISO, a release branch for a dev ISO (input `kind` of iso-1-github-tag.yml; feat/steamify is no longer needed)**
|
||||
- `c42d545` **fix(ci): a version published from main starts a real ISO release, a release branch a dev ISO (it always started the dev one)**
|
||||
- `b3bed43` **chore: Version 2.9.6**
|
||||
|
||||
## 2.9.5 - 2026-09-30
|
||||
|
||||
- `70ecb56` **ci: trigger a mirror sync**
|
||||
- `a7276cf` **ci: sync the git.upriser.nl mirror on every branch and tag**
|
||||
- `b3549d4` **ci: sync the mirror on release branch pushes too**
|
||||
- `a232a55` **feat(ci): sync the git.upriser.nl mirror on every push to main and every tag (git-upriser-sync.yml calls the mirror-sync API: the Gitea mirror builds at once instead of at its interval; secrets GIT_UPRISER_URL and GIT_UPRISER_TOKEN, retried, skipped on Gitea itself)**
|
||||
- `3676c64` **docs: link the renamed steamify-cachyos-live-iso repo (the release dispatches the ISO workflow iso-1-github-tag.yml there)**
|
||||
- `971e792` **chore: Version 2.9.5**
|
||||
|
||||
## 2.9.4 - 2026-09-29
|
||||
|
||||
- `619ef1f` **docs: AGENTS.md and TECHNICAL.md: the CEC driver cache, the one workflow for GitHub and a Gitea mirror, and that a new release starts the Steamify ISO's release**
|
||||
- `b624053` **chore: Version 2.9.4**
|
||||
|
||||
## 2.9.3 - 2026-09-29
|
||||
|
||||
- `7f5d29c` **feat(ci): a new Steamify release starts the Steamify ISO's release (a test build from the ISO repo's feat/steamify; secret ISO_DISPATCH_TOKEN)**
|
||||
- `cfe3ce7` **chore: Version 2.9.3**
|
||||
|
||||
## 2.9.2 - 2026-09-29
|
||||
|
||||
- **feat(ci): a Gitea mirror publishes its own release per version (akkuman/gitea-release-action; never overwrites one, notes from the changelog, links to the mirror)**
|
||||
- **fix(ci): the build also runs on a Gitea mirror (act_runner): shellcheck through ludeeus/action-shellcheck, which brings its own binary; the artifact goes up there with christopherhx/gitea-upload-artifact (GitHub refuses a workflow that mentions the deprecated upload-artifact@v3); releases are only published on GitHub**
|
||||
- `fe4f3f4` **chore: Version 2.9.2**
|
||||
|
||||
## 2.9.1 - 2026-09-29
|
||||
|
||||
- `c87e6c7` **docs: changelog for the CEC driver cache**
|
||||
- `fc05725` **fix: HDMI-CEC: an older cached CEC driver is removed when a new pin is downloaded**
|
||||
- `90bc5e4` **fix: HDMI-CEC: Valve's CEC driver is downloaded once and kept in /var/cache/steamify (GitHub rate-limits repeated downloads with HTTP 429, which left CEC off); turning CEC on again needs no network**
|
||||
- `d99ff65` **chore: Version 2.9.1**
|
||||
|
||||
## 2.9.0 - 2026-09-28
|
||||
|
||||
- `d30d505` **chore: Version 2.9.0**
|
||||
- `66ea63c` **docs: KDE Plasma only: other desktops and display managers "are not supported" (README, was "don't work yet")**
|
||||
- `9b86cd1` **docs: KDE Plasma only: other desktops don't work yet (README)**
|
||||
- `d73e71d` **docs: README, TECHNICAL and TROUBLESHOOTING for 2.9.0: Steam's System settings, serial number and Wi-Fi backend, --defaults --list, the Steamify ISO**
|
||||
- `a72d848` **feat: Steam's OS Version is the SteamOS release Steamify follows (steamos-3.9), its OS Codename steam-machine; About this System keeps "CachyOS Linux" (UseOSReleaseVersion)**
|
||||
- `27a4070` **fix: Steam's OS Variant shows Steamify's version (VARIANT_ID=steamify-2.9.0; Steam shows VARIANT_ID, not VARIANT)**
|
||||
- `44bfe07` **fix: No VERSION_ID in os-release: About this System said "CachyOS Linux 2.9.0"; the version is in VARIANT ("Steamify 2.9.0")**
|
||||
- `435f69c` **feat: Steam's System settings: the OS name stays CachyOS (no renaming, also not in About this System), Steamify <version> as the variant, the SteamOS release it follows as the codename (steamos-3.9)**
|
||||
- `ce71b34` **feat: Steam's System settings and About this System like on SteamOS: OS name "CachyOS with Steamify" (lsb-release, kept through CachyOS updates by a pacman hook), Steamify as OS variant and version (os-release VARIANT/VERSION_ID; NAME/PRETTY_NAME stay CachyOS's for limine, TARGET_OS_NAME pinned), "CachyOS Linux with Steamify" in About this System; on a Steam Machine the serial number and Steam's Wi-Fi backend setting (existing installs get it as an update)**
|
||||
- `07a94cd` **fix(ui): The progress screen's ✓ and ! stand upright: only a dot on the circle spins while a step runs (they kept the spinner's last angle)**
|
||||
|
||||
## 2.8.0 - 2026-09-28
|
||||
|
||||
- **chore: Version 2.8.0**
|
||||
- **feat: steamify.sh --defaults --list: what --defaults can set up here, as JSON, for an installer page**
|
||||
|
||||
## 2.7.0 - 2026-09-28
|
||||
|
||||
- `4f6e6fd` **feat: --defaults takes --skip <ids> and --boot gamescope|desktop, for the Steam Machine ISO's installer pages**
|
||||
- `5bf9da4` **fix: HDMI-CEC enables cec-audio-control.socket (the TV remote's volume keys); existing installs get it as an update**
|
||||
- `14aa7c0` **fix: the app's progress screen says "Boot into desktop", not "Boot into Boot into"**
|
||||
- `c4ec755` **feat: --defaults --options <ids> (exactly these on) replaces --skip; steamify.sh --boot gamescope|desktop on its own switches where an installed conversion starts**
|
||||
- `eadbdda` **docs: release branches (release/X.Y.Z, feature/ and bugfix/ branches into it, the release into main); CI builds release/** pushes**
|
||||
- `bee18b7` **docs: the agent merges feature/bugfix branches into the release branch; the release into main stays the user's**
|
||||
- `501c0ba` **docs: gh or git for merging into the release branch**
|
||||
- `b1adacb` **docs: gh is required for the release-branch pull requests; ask the user to install it**
|
||||
- `ced4a45` **chore: the v2 app preview is gone (it is the released app): no preview pre-release step in CI, no mention in steamify-app.sh**
|
||||
- **fix: turning the theme off no longer brings back single user's launcher restriction when single user was turned off in between**
|
||||
|
||||
## 2.6.0 - 2026-09-27
|
||||
|
||||
- `b948c20` **feat: --defaults applies the default setup without the menu, also from an installer (no session): the base for a Steam Machine ISO**
|
||||
|
||||
## 2.5.2 - 2026-09-27
|
||||
|
||||
- `0c96bed` **docs: README screenshot at 2.5.2: everything on, nothing to update**
|
||||
|
||||
## 2.5.1 - 2026-09-27
|
||||
|
||||
- **fix: Add as non-Steam game asks Steam to close again every 15 s (a Steam that was still starting ignored it)**
|
||||
- `3a0692f` **docs: README screenshot at 2.5.1 with Add as non-Steam game**
|
||||
- `e86ddcc` **feat: Add as non-Steam game: Steamify in the Steam library, under the shortcut; controller works when started from Steam**
|
||||
- `389bc33` **fix(ui): B never quits (Steam sends Esc for it), Ctrl+Q does; started from Steam, the hints show the Steam Controller's buttons**
|
||||
- `c6c1150` **fix(ui): Enter selects (a Steam Controller's A in Steam's desktop layout); Ctrl+Enter reviews and applies**
|
||||
- `98e2eff` **fix(ui): Started from Steam, the app runs without Steam's overlay (Qt aborted) and stays in the foreground, so Steam Input gives it the controller**
|
||||
- `431515b` **feat(ui): Review & apply greyed out while nothing would change; the Done screen shows why a run stopped**
|
||||
|
||||
## 2.5.0 - 2026-09-27
|
||||
|
||||
- **docs: README: what the VRAM booster says with NVIDIA's closed driver; shorter unsupported text in the app**
|
||||
|
||||
@@ -19,6 +19,10 @@ Steam Machine.
|
||||
on the desktop too.
|
||||
- **Boot into: gamescope / desktop** - where the PC starts. Gaming mode is
|
||||
the default.
|
||||
- Steam's **System** settings show the SteamOS release Steamify follows
|
||||
as OS Version (e.g. `steamos-3.9`), `steam-machine` as OS Codename and
|
||||
Steamify's version as OS Variant. The OS name stays CachyOS
|
||||
([details](TECHNICAL.md#what-steams-system-settings-show)).
|
||||
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)).
|
||||
@@ -30,6 +34,10 @@ Steam Machine.
|
||||
5. **Steamify shortcut** - "Steamify CachyOS" on the desktop and in the
|
||||
launcher opens the newest Steamify app, so you don't need the install
|
||||
command again. "Steamify Terminal" in the launcher opens the terminal menu.
|
||||
With **Add as non-Steam game** (on by default) it's in your Steam library
|
||||
too: started from Steam it gets your controller (a Steam Controller only
|
||||
talks to Steam), in gaming mode as well. Steam closes for a moment while
|
||||
it's added, so that's done from the desktop.
|
||||
6. **Update notifications** - on by default: a notification and a tray icon
|
||||
when there's a new Steamify, with Open Steamify and Skip this version. It
|
||||
never updates anything by itself
|
||||
@@ -58,7 +66,8 @@ What Steamify can do on a Valve 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.
|
||||
it to sleep like a console. Steam's System settings show the serial
|
||||
number, and Steam's **Wi-Fi backend** setting works.
|
||||
- **Power-off fix** - on by default: shutting down really turns the Steam
|
||||
Machine off, also with the newest CachyOS kernel
|
||||
([details](TECHNICAL.md#power-off-fix-steam-machine)).
|
||||
@@ -69,6 +78,11 @@ What Steamify can do on a Valve Steam Machine:
|
||||
## Requirements
|
||||
|
||||
- CachyOS with the KDE Plasma desktop (the default CachyOS Desktop edition)
|
||||
|
||||
> **KDE Plasma only.** Steamify works with KDE Plasma and its login managers
|
||||
> (plasma-login-manager or SDDM). Other desktops (GNOME, Hyprland, Cosmic,
|
||||
> ...) and other display managers are not supported: the switching between gaming mode
|
||||
> and the desktop, the theme and the shortcuts are all built for Plasma.
|
||||
- Your normal user account, with permission to use `sudo`
|
||||
|
||||
## Installation
|
||||
@@ -79,6 +93,11 @@ Open **Konsole** on your Plasma desktop and run:
|
||||
curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify.sh | bash
|
||||
```
|
||||
|
||||
Installing CachyOS on a Steam Machine? The
|
||||
[Steamify CachyOS ISO](https://github.com/theupriser/steamify-cachyos-live-iso)
|
||||
has Steamify built into the installer: pick the options there, and the
|
||||
first boot is already set up.
|
||||
|
||||
Prefer to look at the script first? Clone the repository and run
|
||||
[`./steamify.sh`](steamify.sh) (keep the whole folder: it needs
|
||||
[`lib/`](lib/)).
|
||||
@@ -121,6 +140,14 @@ steamos-session-select persistent # start where you left off last time
|
||||
steamos-session-select oneshot # always start in gaming mode (default)
|
||||
```
|
||||
|
||||
Where the PC starts after a restart can also be set from a script, once the
|
||||
SteamOS conversion is on (only that changes; `sudo` may ask for your password):
|
||||
|
||||
```bash
|
||||
steamify.sh --boot desktop # start in the desktop from the next boot on
|
||||
steamify.sh --boot gamescope # start in gaming mode again (default)
|
||||
```
|
||||
|
||||
## Something wrong?
|
||||
|
||||
Stuck on a black screen in gaming mode: press **Ctrl+Alt+F3**, log in, and run
|
||||
|
||||
+98
-3
@@ -111,6 +111,9 @@ 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.
|
||||
|
||||
The conversion also sets what Steam's System settings show, see
|
||||
[below](#what-steams-system-settings-show).
|
||||
|
||||
**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.
|
||||
@@ -139,6 +142,33 @@ taken from the newest `steamdeck-kde-presets` on Valve's SteamOS mirror
|
||||
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).
|
||||
|
||||
## What Steam's System settings show
|
||||
|
||||
With the SteamOS conversion on, `/etc/os-release` gets:
|
||||
|
||||
| Key | Value | Shown in Steam as |
|
||||
|---|---|---|
|
||||
| `VERSION_ID` | `steamos-3.9`: the newest SteamOS release in Valve's repo (offline: the previous value) | OS Version |
|
||||
| `VERSION_CODENAME` | `steam-machine` | OS Codename |
|
||||
| `VARIANT` / `VARIANT_ID` | `Steamify 2.9.0` / `steamify-2.9.0` | OS Variant (`VARIANT_ID`) |
|
||||
|
||||
The OS name stays CachyOS everywhere: Steam's OS Name comes from
|
||||
`lsb_release -d`, which isn't touched, and `NAME`/`PRETTY_NAME` stay
|
||||
CachyOS's. limine-snapper-sync finds the boot entries by that name, so
|
||||
changing it would break snapshot boot entries; `/etc/default/limine` gets
|
||||
`TARGET_OS_NAME="CachyOS"` as a guard when it has none.
|
||||
|
||||
KDE's About this System would show `VERSION_ID` after the name
|
||||
("CachyOS Linux steamos-3.9"); `UseOSReleaseVersion=true` in
|
||||
`~/.config/kcm-about-distrorc` makes it show `VERSION`, which CachyOS doesn't
|
||||
have, so it keeps saying "CachyOS Linux".
|
||||
|
||||
CachyOS's own hooks (`cachyos-hooks`) reset os-release on updates of
|
||||
`filesystem`; the pacman hook `zz-steamify-os-release` runs after them and
|
||||
sets the keys again (`/usr/local/libexec/steamify-os-release`). The values are
|
||||
refreshed after every Steamify run that changes something. Turning the
|
||||
conversion off removes the hook and runs CachyOS's `cachyos-branding` again.
|
||||
|
||||
## Front LED bar (Steam Machine)
|
||||
|
||||
The **Steam Machine support** component, only shown on Fremont hardware
|
||||
@@ -156,7 +186,14 @@ The **Steam Machine support** component, only shown on Fremont hardware
|
||||
- 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.
|
||||
DMI data;
|
||||
- makes the serial number readable for Steam, which shows it in its System
|
||||
settings (`/etc/tmpfiles.d/steamify-serial.conf`: `product_serial` is
|
||||
root-only by default);
|
||||
- makes Steam's Wi-Fi backend setting work: steamos-manager switches it in
|
||||
`/etc/NetworkManager/conf.d/99-valve-wifi-backend.conf`, which is created
|
||||
with `wifi.backend=wpa_supplicant` (NetworkManager's default) when no
|
||||
backend is set anywhere yet.
|
||||
|
||||
Turning it off removes all of that again (the AUR helper is kept).
|
||||
|
||||
@@ -180,7 +217,9 @@ 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;
|
||||
- `cec-audio-control`: volume of the TV or receiver (the TV remote's volume
|
||||
keys); its user socket is enabled by Steamify, since SteamOS enables it
|
||||
through a preset that pacman doesn't apply;
|
||||
- `inputattach-cec-units` (with `linuxconsole` from CachyOS for
|
||||
`inputattach`): attaches USB CEC adapters (Pulse-Eight, RainShadow).
|
||||
|
||||
@@ -236,6 +275,35 @@ Where Steam Machine support was set up before 2.2.0, the menu ticks the
|
||||
power-off fix as a new default sub-option (see feature versions below), so a
|
||||
normal run adds it.
|
||||
|
||||
## Add as non-Steam game
|
||||
|
||||
A sub-option of the Steamify shortcut (`steamgame`, `lib/steam-game.sh`, new
|
||||
in 2.5.1, ticked along with the shortcut): Steamify CachyOS in every Steam
|
||||
account's non-Steam games (`~/.local/share/Steam/userdata/<id>/config/shortcuts.vdf`,
|
||||
binary VDF, edited by `patches/steam-shortcuts.py`), starting the shortcut's
|
||||
`run-app`, with the icon rendered as PNG (`~/.local/share/steamify/steamify.png`;
|
||||
Steam shows no SVG). Why: while Steam runs, a Steam Controller only talks to
|
||||
Steam, which gives its input to the game it started (Steam Input); on the
|
||||
desktop through its desktop layout (A = Enter, B = Esc, Y = Space, X =
|
||||
Steam's keyboard), in gaming mode as a gamepad.
|
||||
|
||||
Steam reads the list only at startup and writes it back on exit, so Steam is
|
||||
closed (`steam -shutdown`) for the edit and started again (through
|
||||
`steam-desktop-autostart.service` when it's there). That's refused in gaming
|
||||
mode and when Steamify itself was started from Steam (`SteamGameId`), since
|
||||
closing Steam would end it. An entry for the start script that's already
|
||||
there (added by hand, or with the path from before 2.5.0) is updated, not
|
||||
added twice; what was done is recorded per account (state `steamgame`:
|
||||
`<account>=<appid> added|updated`) and a copy of each list is kept in the
|
||||
state directory before the first change. Turning it off (or the shortcut)
|
||||
removes every entry for the start script, since it can't start anything
|
||||
without it.
|
||||
|
||||
Started from Steam, the app re-executes itself without Steam's overlay
|
||||
(`LD_PRELOAD` of `gameoverlayrenderer.so` makes Qt abort) and
|
||||
`steamify-app.sh` keeps it in the foreground (`SteamGameId`), or Steam
|
||||
counts the game as ended right away and stops giving it the controller.
|
||||
|
||||
## Update notifications
|
||||
|
||||
The **Update notifications** option (new in 2.5.0, ticked by default)
|
||||
@@ -389,6 +457,31 @@ steamos-session-select persistent # remember the last-used session across reboo
|
||||
steamos-session-select oneshot # always start in gamescope (default, like a Deck)
|
||||
```
|
||||
|
||||
`steamify.sh --boot gamescope|desktop` sets where the PC starts after a
|
||||
restart (the menu's Boot into row) from a script: it needs the SteamOS
|
||||
conversion and changes nothing else, not even the updates a normal run would
|
||||
apply. Exit code 1 without the conversion or with another value.
|
||||
|
||||
## Install-time mode (`--defaults`)
|
||||
|
||||
For an installer (the Steam Machine ISO), without a session or prompts
|
||||
(passwordless sudo required):
|
||||
|
||||
```bash
|
||||
steamify.sh --defaults # what the menu would preselect
|
||||
steamify.sh --defaults --options gaming,theme,cec,machine --boot desktop
|
||||
steamify.sh --defaults --list # what --defaults can set up here, as JSON
|
||||
```
|
||||
|
||||
`--options` turns on exactly the listed items (the menu's ids: `gaming`,
|
||||
`theme`, `glyphs`, `single`, `launcher`, `notify`, `vram`, `cec`, `machine`,
|
||||
`poweroff`), everything else off; an item that's on brings its parent
|
||||
(single user the conversion, the power-off fix Steam Machine support). An
|
||||
item this PC can't use (e.g. `vram` without a VRAM region) is left off with
|
||||
a warning. `--boot desktop` needs `gaming`. Unknown options, ids, `boot` or
|
||||
`bios` in the list: exit code 1 before anything changes. What needs a
|
||||
Plasma session waits for the first login (`lib/first-login.sh`).
|
||||
|
||||
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.
|
||||
@@ -414,12 +507,14 @@ immediately, which can turn into a loop - see
|
||||
| `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 |
|
||||
| `lib/fremont-poweroff.sh` | Steam Machine support: the power-off fix (DKMS module from `patches/`) |
|
||||
| `lib/steam-game.sh` | Add as non-Steam game: Steamify in the Steam library (`patches/steam-shortcuts.py`) |
|
||||
| `lib/update-notifier.sh` | Update notifications: the notifier from `patches/` and its user timer |
|
||||
| `lib/first-login.sh` | `--defaults` without a session (the Steam Machine ISO's installer): the one-time first-login step that sets up single user's launcher on Plasma's new layout and opens the app |
|
||||
| `lib/vram-booster.sh` | VRAM booster (`dmemcg-booster`, `plasma-foreground-booster`) |
|
||||
| `services/` | The systemd units the scripts install (`service_file`, `@KEY@` placeholders); see its README |
|
||||
| `patches/` | Module sources and patches the scripts build or apply (`patch_file`); see its README |
|
||||
| `.github/tools/bundle.sh` | Builds the single-file version (`dist/steamify.sh`), with `patches/` and `services/` embedded |
|
||||
| `.github/workflows/bundle.yml` | Builds and checks it on every push; publishes it on `main` |
|
||||
| `.github/workflows/bundle.yml` | Builds and checks it on every push (GitHub and a Gitea mirror); publishes it on `main`, and starts the Steamify ISO's release |
|
||||
|
||||
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
|
||||
|
||||
@@ -18,6 +18,14 @@ restarted right away (`Relogin=true`), which is why it can loop.
|
||||
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.
|
||||
|
||||
## "Target OS name ... not found in /boot/limine.conf"
|
||||
|
||||
limine-snapper-sync finds the boot entries by the OS name. Steamify never
|
||||
changes that name (it only adds version keys to `/etc/os-release`), and pins
|
||||
`TARGET_OS_NAME="CachyOS"` in `/etc/default/limine`. If you see this message,
|
||||
check that `PRETTY_NAME` in `/etc/os-release` still says `CachyOS`, or set
|
||||
`TARGET_OS_NAME` in `/etc/default/limine` to the name your boot entries use.
|
||||
|
||||
## The LED bar on the Steam Machine stays dark
|
||||
|
||||
Restart once more; a freshly installed driver often needs a reboot. The menu
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 492 KiB After Width: | Height: | Size: 519 KiB |
@@ -177,6 +177,7 @@ backend_apply() {
|
||||
if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
|
||||
done
|
||||
feature_record_unticked
|
||||
os_version_refresh
|
||||
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true
|
||||
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)"
|
||||
}
|
||||
|
||||
+30
-16
@@ -26,6 +26,10 @@ CEC_DKMS_VER=1
|
||||
CEC_DKMS_SRC="/usr/src/$CEC_DKMS_NAME-$CEC_DKMS_VER"
|
||||
CEC_DRIVER_URL="https://raw.githubusercontent.com/evlaV/linux-integration/10c8c8800ccd3ae359203b4eefb6479f613b3b8e/drivers/media/cec/platform/cros-ec/cros-ec-cec.c"
|
||||
CEC_DRIVER_SHA256=e89fd4e87fceb32d713ffc59b64794779b3c2b3c012b0e2f2e9f0c40b42f4373
|
||||
# Downloaded once, then kept: raw.githubusercontent.com rate-limits (HTTP 429) repeated downloads
|
||||
# from one address, and turning CEC off and on again needs no network then. Named after its
|
||||
# checksum, so a new pin (a new commit and checksum above) downloads its file once by itself.
|
||||
CEC_DRIVER_CACHE="/var/cache/steamify/cros-ec-cec-${CEC_DRIVER_SHA256:0:12}.c"
|
||||
|
||||
cec_link_steamos_manager() {
|
||||
# On a Steam Machine: steamos-manager writes cecd's config from Steam's
|
||||
@@ -34,11 +38,11 @@ cec_link_steamos_manager() {
|
||||
# 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
|
||||
user_systemctl daemon-reload
|
||||
user_systemctl enable steamos-manager-configure-cecd.service 2>/dev/null
|
||||
user_systemctl start steamos-manager-configure-cecd.service 2>/dev/null
|
||||
user_systemctl restart cecd.service 2>/dev/null
|
||||
user_systemctl restart steamos-manager.service 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -51,10 +55,10 @@ cec_status() { cec_installed && [[ -f "$CEC_STEAM_DROPIN" ]] && cec_driver_ok; }
|
||||
|
||||
cec_reload_driver() {
|
||||
# cecd keeps /dev/cec0 open, so the old module can't be unloaded under it.
|
||||
systemctl --user stop cecd.service 2>/dev/null
|
||||
user_systemctl stop cecd.service 2>/dev/null
|
||||
sudo modprobe -r cros_ec_cec 2>/dev/null
|
||||
sudo modprobe cros_ec_cec 2>/dev/null
|
||||
systemctl --user start cecd.service 2>/dev/null
|
||||
user_systemctl start cecd.service 2>/dev/null
|
||||
}
|
||||
|
||||
cec_driver_enable() {
|
||||
@@ -65,13 +69,20 @@ cec_driver_enable() {
|
||||
fi
|
||||
install_kernel_headers || return 1
|
||||
tmp="$(mktemp -d)"
|
||||
if [[ -f "$CEC_DRIVER_CACHE" ]] && echo "$CEC_DRIVER_SHA256 $CEC_DRIVER_CACHE" | sha256sum -c --quiet - 2>/dev/null; then
|
||||
cp "$CEC_DRIVER_CACHE" "$tmp/cros-ec-cec.c"
|
||||
else
|
||||
info "Downloading Valve's Steam Machine CEC driver..."
|
||||
if ! curl -fsL "$CEC_DRIVER_URL" -o "$tmp/cros-ec-cec.c" ||
|
||||
if ! curl -fsL --retry 2 "$CEC_DRIVER_URL" -o "$tmp/cros-ec-cec.c" ||
|
||||
! echo "$CEC_DRIVER_SHA256 $tmp/cros-ec-cec.c" | sha256sum -c --quiet -; then
|
||||
rm -rf "$tmp"
|
||||
err "Downloading Valve's CEC driver failed (or its checksum didn't match)."
|
||||
return 1
|
||||
fi
|
||||
# The name carries the checksum: a newer pin gets its own file, the old one goes.
|
||||
sudo find "${CEC_DRIVER_CACHE%/*}" -maxdepth 1 -name 'cros-ec-cec-*.c' ! -name "${CEC_DRIVER_CACHE##*/}" -delete 2>/dev/null
|
||||
sudo install -Dm644 "$tmp/cros-ec-cec.c" "$CEC_DRIVER_CACHE"
|
||||
fi
|
||||
# So it finds amdgpu's HDMI port (see the patch for why).
|
||||
patch_file cros-ec-cec-single-port.patch | patch -s -d "$tmp" -p1 ||
|
||||
{ rm -rf "$tmp"; err "Patching Valve's CEC driver failed."; return 1; }
|
||||
@@ -154,10 +165,13 @@ cec_enable() {
|
||||
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
|
||||
user_systemctl daemon-reload
|
||||
# The TV remote's volume keys: SteamOS enables this socket through its
|
||||
# preset, which pacman doesn't apply, so nothing else does on CachyOS.
|
||||
user_systemctl enable --now cec-audio-control.socket 2>/dev/null
|
||||
cec_link_steamos_manager
|
||||
if compgen -G "/dev/cec*" >/dev/null; then
|
||||
systemctl --user restart cecd.service 2>/dev/null
|
||||
user_systemctl 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
|
||||
@@ -168,20 +182,20 @@ cec_enable() {
|
||||
|
||||
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
|
||||
user_systemctl disable --now cecd.service cec-audio-control.socket cec-audio-control.service 2>/dev/null
|
||||
user_systemctl disable steamos-manager-configure-cecd.service 2>/dev/null
|
||||
sudo pacman -Rns --noconfirm "${CEC_PKGS[@]}" 2>/dev/null
|
||||
cec_driver_disable
|
||||
sudo rm -f "$CEC_ORDER_DROPIN" "$CEC_STEAM_DROPIN" "$CEC_STEAM_ENV"
|
||||
sudo rmdir "$(dirname "$CEC_ORDER_DROPIN")" "$(dirname "$CEC_STEAM_DROPIN")" "$(dirname "$CEC_STEAM_ENV")" 2>/dev/null
|
||||
systemctl --user daemon-reload
|
||||
user_systemctl 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
|
||||
user_systemctl is-active -q steamos-manager.service 2>/dev/null &&
|
||||
user_systemctl restart steamos-manager.service
|
||||
ok "HDMI-CEC removed."
|
||||
}
|
||||
|
||||
@@ -189,5 +203,5 @@ 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)"
|
||||
echo "CEC devices: $dev cecd: $(user_systemctl is-active cecd 2>/dev/null)"
|
||||
}
|
||||
@@ -44,6 +44,30 @@ require_root_helper() {
|
||||
fi
|
||||
}
|
||||
|
||||
user_session() {
|
||||
# A running user systemd and session bus. Not in the installer's chroot,
|
||||
# where the ISO applies Steamify for a user who has never logged in.
|
||||
[[ -S "${XDG_RUNTIME_DIR:-/nonexistent}/bus" ]]
|
||||
}
|
||||
|
||||
user_systemctl() {
|
||||
# systemctl --user, also without a session: then only the unit files
|
||||
# change (enable/disable through --root); starting and stopping is left
|
||||
# to the first login, which starts what's enabled anyway.
|
||||
if user_session; then systemctl --user "$@"; return; fi
|
||||
local a verb="" args=()
|
||||
for a in "$@"; do
|
||||
[[ "$a" == --now ]] && continue
|
||||
[[ -z "$verb" && "$a" != -* ]] && verb="$a"
|
||||
args+=("$a")
|
||||
done
|
||||
case "$verb" in
|
||||
enable|disable|is-enabled|mask|unmask) systemctl --user --root=/ "${args[@]}" ;;
|
||||
is-active) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
plasma_applets() {
|
||||
# Print "containment:applet" for every applet of plugin $1 in the
|
||||
# user's Plasma layout, for kwriteconfig6 --group paths.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# The ISO applies Steamify in the installer, before the user ever logged
|
||||
# in (steamify.sh --defaults without a session). What needs Plasma's first
|
||||
# layout is finished at the first desktop login, by a one-time autostart
|
||||
# that then opens the app, next to CachyOS Hello.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
FIRST_LOGIN_ENTRY="${XDG_CONFIG_HOME:-$HOME/.config}/autostart/steamify-first-login.desktop"
|
||||
FIRST_LOGIN_SCRIPT="$STEAMIFY_BIN/first-login"
|
||||
|
||||
first_login_schedule() {
|
||||
install_executable "$FIRST_LOGIN_SCRIPT" 755 << EOF2
|
||||
#!/bin/bash
|
||||
# Installed by Steamify CachyOS: runs once, at the first desktop login.
|
||||
set -o pipefail
|
||||
curl -fsSL --max-time 30 "$WIZARD_URL" | bash -s -- --first-login
|
||||
rm -f "$FIRST_LOGIN_ENTRY" "\$0"
|
||||
[[ -x "$WIZARD_APP_LAUNCHER" ]] && exec "$WIZARD_APP_LAUNCHER"
|
||||
EOF2
|
||||
printf '%s\n' '[Desktop Entry]' 'Type=Application' 'Name=Steamify first login' \
|
||||
"Exec=$FIRST_LOGIN_SCRIPT" 'OnlyShowIn=KDE;' 'X-KDE-autostart-phase=2' 'NoDisplay=true' |
|
||||
install_executable "$FIRST_LOGIN_ENTRY" 644
|
||||
}
|
||||
|
||||
first_login_run() {
|
||||
# plasmashell has just laid out the desktop from the look-and-feel
|
||||
# package; wait for its config, then put single user's launcher
|
||||
# settings on the new launcher.
|
||||
local _
|
||||
for _ in $(seq 30); do
|
||||
[[ -n "$(plasma_applets org.kde.plasma.kickoff)" ]] && break
|
||||
sleep 2
|
||||
done
|
||||
if single_status; then
|
||||
stop_plasmashell_for_edit
|
||||
single_launcher
|
||||
restart_plasmashell_if_stopped
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
@@ -18,6 +18,78 @@ gaming_status() {
|
||||
esac
|
||||
}
|
||||
|
||||
# What Steam shows in its System settings besides the OS name (which stays
|
||||
# CachyOS's, for legal clarity and because limine-snapper-sync finds the boot
|
||||
# entries by it): the SteamOS release as the OS version, steam-machine as the
|
||||
# codename, Steamify with its version as the variant (os-release VERSION_ID,
|
||||
# VERSION_CODENAME, VARIANT_ID; see patches/steamify-os-release.sh).
|
||||
OS_NAME_SCRIPT=/usr/local/libexec/steamify-os-release
|
||||
OS_NAME_HOOK=/etc/pacman.d/hooks/zz-steamify-os-release.hook
|
||||
# limine's tools take the OS name from TARGET_OS_NAME, else os-release's
|
||||
# PRETTY_NAME: pinned to CachyOS, the boot entries never depend on os-release.
|
||||
LIMINE_DEFAULTS=/etc/default/limine
|
||||
|
||||
steamos_release() {
|
||||
# The SteamOS release Steamify follows: Valve's newest jupiter-X.Y repo
|
||||
# (where the SteamOS extras come from), as an os-release ID (no spaces or
|
||||
# capitals: steamos-3.9). Offline (e.g. in the installer): the one set
|
||||
# before, if any.
|
||||
local v
|
||||
v="$(curl -fsL --max-time 15 "$VALVE_MIRROR/" 2>/dev/null | grep -oE 'jupiter-[0-9]+\.[0-9]+/' | tr -d / | sort -V | tail -n 1)"
|
||||
if [[ -n "$v" ]]; then echo "steamos-${v#jupiter-}"
|
||||
else sed -n 's/^VERSION_ID=\(steamos-.*\)$/\1/p' /etc/os-release 2>/dev/null; fi
|
||||
}
|
||||
|
||||
os_name_enable() {
|
||||
# 2.9.0 pre-releases renamed the OS itself; the name stays CachyOS's.
|
||||
if grep -q '^DISTRIB_DESCRIPTION=.*with Steamify' /etc/lsb-release 2>/dev/null &&
|
||||
[[ -x /usr/share/libalpm/scripts/cachyos-branding ]]; then
|
||||
sudo /usr/share/libalpm/scripts/cachyos-branding lsb-release
|
||||
fi
|
||||
[[ -n "$(kreadconfig6 --file kcm-about-distrorc --group General --key Name 2>/dev/null)" ]] &&
|
||||
kset gaming kcm-about-distrorc General Name --delete
|
||||
# About this System: os-release's VERSION (CachyOS has none) instead of
|
||||
# VERSION_ID, which is the SteamOS release now.
|
||||
kset gaming kcm-about-distrorc General UseOSReleaseVersion true
|
||||
patch_file steamify-os-release.sh | fill VERSION="$VERSION" STEAMOS="$(steamos_release)" |
|
||||
sudo install -Dm755 /dev/stdin "$OS_NAME_SCRIPT" &&
|
||||
patch_file steamify-os-release.hook | fill SCRIPT="$OS_NAME_SCRIPT" |
|
||||
sudo install -Dm644 /dev/stdin "$OS_NAME_HOOK" &&
|
||||
sudo "$OS_NAME_SCRIPT" || { err "Setting Steamify's version for Steam failed."; return 1; }
|
||||
if [[ -f "$LIMINE_DEFAULTS" ]] && ! grep -q '^TARGET_OS_NAME=' "$LIMINE_DEFAULTS"; then
|
||||
backup_file "$LIMINE_DEFAULTS"
|
||||
printf '%s\n' 'TARGET_OS_NAME="CachyOS"' | sudo tee -a "$LIMINE_DEFAULTS" > /dev/null
|
||||
state_set gaming limine_target 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
os_name_disable() {
|
||||
sudo rm -f "$OS_NAME_HOOK" "$OS_NAME_SCRIPT"
|
||||
[[ -f /etc/os-release ]] && sudo sed -i -e '/^VARIANT=/d' -e '/^VARIANT_ID=/d' -e '/^VERSION_ID=/d' -e '/^VERSION_CODENAME=/d' /etc/os-release
|
||||
# CachyOS's own values back, the way its hooks write them.
|
||||
if [[ -x /usr/share/libalpm/scripts/cachyos-branding ]]; then
|
||||
sudo /usr/share/libalpm/scripts/cachyos-branding os-release
|
||||
# Undoes 2.9.0 pre-releases, which changed lsb-release's name too.
|
||||
sudo /usr/share/libalpm/scripts/cachyos-branding lsb-release
|
||||
fi
|
||||
if [[ -n "$(state_get gaming limine_target)" ]]; then
|
||||
sudo sed -i '/^TARGET_OS_NAME="CachyOS"$/d' "$LIMINE_DEFAULTS"
|
||||
state_set gaming limine_target ""
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
os_version_refresh() {
|
||||
# After every run that changed something (sudo is at hand then): Steam's
|
||||
# OS version follows Steamify's, not just the one that set it up. Silent:
|
||||
# the app reads the backend's stdout as JSON.
|
||||
gaming_status 2>/dev/null || return 0
|
||||
grep -qx "VARIANT_ID=steamify-$VERSION" /etc/os-release 2>/dev/null && return 0
|
||||
os_name_enable > /dev/null 2>&1
|
||||
return 0
|
||||
}
|
||||
|
||||
gaming_enable() {
|
||||
# LOGIN_MANAGER (sddm with single user, else plasmalogin) is set by the
|
||||
# menu. Re-running with the other value switches over cleanly.
|
||||
@@ -37,6 +109,7 @@ gaming_enable() {
|
||||
esac
|
||||
create_desktop_shortcut
|
||||
steam_enable
|
||||
os_name_enable || return 1
|
||||
}
|
||||
|
||||
gaming_disable() {
|
||||
@@ -49,6 +122,8 @@ gaming_disable() {
|
||||
switch_to_plasmalogin
|
||||
remove_desktop_shortcut
|
||||
steam_disable
|
||||
krevert gaming
|
||||
os_name_disable
|
||||
ok "The PC boots to the normal login screen again (from the next boot)."
|
||||
}
|
||||
|
||||
|
||||
+93
-4
@@ -5,12 +5,12 @@
|
||||
|
||||
# Menu order. Components are turned on in this order and off in reverse;
|
||||
# gaming must come first (single user builds on it).
|
||||
COMPONENTS=(gaming boot theme glyphs single launcher notify vram cec machine poweroff kpin hdmi bios)
|
||||
COMPONENTS=(gaming boot theme glyphs single launcher steamgame notify vram cec machine poweroff 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 [poweroff]=machine [kpin]=machine [hdmi]=machine [bios]=machine)
|
||||
declare -A PARENT=([boot]=gaming [steamgame]=launcher [poweroff]=machine [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,
|
||||
@@ -25,8 +25,8 @@ NO_PRESELECT=(boot cec kpin hdmi)
|
||||
# FEATURE_BASELINE.
|
||||
FEATURE_BASELINE=2.1.0
|
||||
declare -A FEATURE_VERSION=(
|
||||
[gaming]=2.1.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0
|
||||
[launcher]=2.1.0 [cec]=2.1.0 [machine]=2.2.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0
|
||||
[gaming]=2.9.0 [boot]=2.1.0 [theme]=2.1.0 [glyphs]=2.1.0 [single]=2.1.0
|
||||
[launcher]=2.1.0 [cec]=2.7.0 [machine]=2.9.0 [poweroff]=2.2.0 [vram]=2.3.0 [notify]=2.5.0 [steamgame]=2.5.1
|
||||
[kpin]=2.1.0 [hdmi]=2.1.0
|
||||
)
|
||||
|
||||
@@ -37,6 +37,7 @@ declare -A LABEL=(
|
||||
[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: the app on the desktop, Steamify Terminal in the launcher"
|
||||
[steamgame]="Add as non-Steam game: Steamify in your Steam library, for the controller and gaming mode"
|
||||
[notify]="Update notifications: a notification when there's a new Steamify, never updates by itself"
|
||||
[vram]="VRAM booster: the game in front keeps its VRAM, background apps make room"
|
||||
[cec]="HDMI-CEC: use Steam with the TV remote, TV on/off with the PC (experimental)"
|
||||
@@ -52,6 +53,7 @@ component_available() {
|
||||
case "$1" in
|
||||
machine|poweroff) machine_available ;;
|
||||
vram) vram_available ;;
|
||||
steamgame) steamgame_available ;;
|
||||
kpin) kpin_available ;;
|
||||
hdmi) hdmi_available ;;
|
||||
bios) bios_available ;;
|
||||
@@ -177,6 +179,88 @@ detect_components() {
|
||||
fi
|
||||
}
|
||||
|
||||
defaults_options() {
|
||||
# defaults_options [--options <id>[,<id>...]] [--boot gamescope|desktop]:
|
||||
# what --defaults sets up, from the Steam Machine ISO's installer page.
|
||||
# --options: exactly these items on, every other off (without it, the
|
||||
# menu's preselection). An item that's on brings its parent (single user
|
||||
# the conversion, like ticking it in the menu); one this PC can't use is
|
||||
# left off with a warning. --boot desktop needs the conversion. Returns 1
|
||||
# on an unknown option, item or value.
|
||||
local c ids list=false boot=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
# Both take a value: without one, shift 2 fails and never shifts, so
|
||||
# the loop would spin forever.
|
||||
[[ "$1" == --options || "$1" == --boot ]] && [[ $# -lt 2 ]] && { err "$1 needs a value"; return 1; }
|
||||
case "$1" in
|
||||
--options)
|
||||
list=true
|
||||
IFS=, read -ra ids <<< "$2"
|
||||
shift 2 ;;
|
||||
--boot)
|
||||
case "$2" in
|
||||
gamescope|desktop) boot="$2" ;;
|
||||
*) err "--boot takes gamescope or desktop"; return 1 ;;
|
||||
esac
|
||||
shift 2 ;;
|
||||
*) err "Unknown option: $1"; return 1 ;;
|
||||
esac
|
||||
done
|
||||
if [[ "$list" == true ]]; then
|
||||
for c in "${ids[@]}"; do
|
||||
[[ -n "$c" ]] || continue
|
||||
[[ -n "${LABEL[$c]:-}" ]] || { err "Unknown item: $c"; return 1; }
|
||||
[[ "$c" == boot ]] && { err "Where to start is --boot, not an item."; return 1; }
|
||||
is_action "$c" && { err "$c can't be part of the install."; return 1; }
|
||||
done
|
||||
for c in "${COMPONENTS[@]}"; do WANTED[$c]=0; done
|
||||
for c in "${ids[@]}"; do
|
||||
[[ -n "$c" ]] || continue
|
||||
if component_available "$c" && component_selectable "$c"; then
|
||||
WANTED[$c]=1
|
||||
else
|
||||
warn "Leaving out ${LABEL[$c]%%:*}: not available on this PC."
|
||||
fi
|
||||
done
|
||||
[[ "${WANTED[single]}" == 1 ]] && WANTED[gaming]=1
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
[[ "${WANTED[$c]}" == 1 && -n "${PARENT[$c]:-}" ]] && WANTED[${PARENT[$c]}]=1
|
||||
done
|
||||
fi
|
||||
case "$boot" in
|
||||
desktop)
|
||||
[[ "${WANTED[gaming]}" == 1 ]] || { err "--boot desktop needs the SteamOS conversion (gaming)."; return 1; }
|
||||
WANTED[boot]=1 ;;
|
||||
gamescope) WANTED[boot]=0 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
defaults_list() {
|
||||
# --defaults --list: what --defaults can set up on this PC, as one JSON
|
||||
# array, for an installer page (the Steam Machine ISO builds its Steamify
|
||||
# page from it, so new items show up without a new ISO). Per item: id,
|
||||
# label, hint, kind (toggle, or choice for boot: on = desktop), parent,
|
||||
# on (preselected like a first run) and selectable. Actions, retired
|
||||
# items and items this PC can't use (e.g. no Steam account yet) are
|
||||
# left out. Read-only: no sudo, no state.
|
||||
local c kind on sel items=""
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
component_available "$c" || continue
|
||||
is_action "$c" && continue
|
||||
[[ "$c" == kpin || "$c" == hdmi ]] && continue
|
||||
kind=toggle; [[ "$c" == boot ]] && kind=choice
|
||||
sel=false; component_selectable "$c" && sel=true
|
||||
on=false
|
||||
[[ "$sel" == true && " ${NO_PRESELECT[*]} " != *" $c "* ]] && on=true
|
||||
[[ "$c" == cec ]] && machine_available && on=true
|
||||
items+="${items:+,}{\"id\":$(json_str "$c"),\"label\":$(json_str "${LABEL[$c]%%:*}")"
|
||||
items+=",\"hint\":$(json_str "$( [[ "${LABEL[$c]}" == *:* ]] && echo "${LABEL[$c]#*: }")")"
|
||||
items+=",\"kind\":\"$kind\",\"parent\":$(json_str "${PARENT[$c]:-}"),\"on\":$on,\"selectable\":$sel}"
|
||||
done
|
||||
printf '[%s]\n' "$items"
|
||||
}
|
||||
|
||||
toggle_component() {
|
||||
local c="$1"
|
||||
component_selectable "$c" || return 1
|
||||
@@ -192,6 +276,10 @@ toggle_component() {
|
||||
if [[ "$c" == machine && "${WANTED[machine]}" == 0 ]]; then WANTED[kpin]=0; WANTED[bios]=0; fi
|
||||
if [[ "$c" == kpin && "${WANTED[kpin]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
if [[ "$c" == bios && "${WANTED[bios]}" == 1 ]]; then WANTED[machine]=1; fi
|
||||
# Steam starts Steamify through the shortcut's start script.
|
||||
# Opt-out, like the power-off fix: ticked along with the shortcut.
|
||||
if [[ "$c" == launcher ]] && component_available steamgame; then WANTED[steamgame]=${WANTED[launcher]}; fi
|
||||
if [[ "$c" == steamgame && "${WANTED[steamgame]}" == 1 ]]; then WANTED[launcher]=1; fi
|
||||
}
|
||||
|
||||
show_menu() {
|
||||
@@ -377,5 +465,6 @@ apply_changes() {
|
||||
if "${c}_enable"; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
|
||||
done
|
||||
feature_record_unticked
|
||||
os_version_refresh
|
||||
FAILED=("${failed[@]}")
|
||||
}
|
||||
@@ -101,6 +101,24 @@ single_launcher() {
|
||||
done
|
||||
}
|
||||
|
||||
single_launcher_drop() {
|
||||
# The theme's layout backup is taken when it's turned on: with single
|
||||
# user on then, it holds single's launcher keys, and restoring it after
|
||||
# single was turned off brings them back. Drop exactly single's values
|
||||
# (a launcher set up differently by the user is left alone).
|
||||
local applet
|
||||
local -a kick_args
|
||||
for applet in $(plasma_applets org.kde.plasma.kickoff); do
|
||||
kick_args=(--file plasma-org.kde.plasma.desktop-appletsrc --group Containments --group "${applet%%:*}"
|
||||
--group Applets --group "${applet#*:}" --group Configuration --group General)
|
||||
[[ "$(kreadconfig6 "${kick_args[@]}" --key primaryActions)" == 3 ]] &&
|
||||
kwriteconfig6 "${kick_args[@]}" --key primaryActions --delete
|
||||
[[ "$(kreadconfig6 "${kick_args[@]}" --key systemFavorites)" == 'suspend,reboot,shutdown' ]] &&
|
||||
kwriteconfig6 "${kick_args[@]}" --key systemFavorites --delete
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
single_enable() {
|
||||
info "Turning off the lock screen, user switching and logging out..."
|
||||
stop_plasmashell_for_edit
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ migrate_layout() {
|
||||
"$(wizard_desktop_dir)/$WIZARD_DESKTOP_NAME"; do
|
||||
[[ -f "$f" ]] && sed -i "s|$old_bin/|$STEAMIFY_BIN/|g" "$f"
|
||||
done
|
||||
systemctl --user daemon-reload 2>/dev/null
|
||||
user_systemctl daemon-reload 2>/dev/null
|
||||
fi
|
||||
if [[ -f "$old_icon" ]]; then
|
||||
if [[ -e "$WIZARD_ICON" ]]; then rm -f "$old_icon"; else mv "$old_icon" "$WIZARD_ICON"; fi
|
||||
|
||||
@@ -22,18 +22,18 @@ steam_enable() {
|
||||
rm -f "$HOME/.config/autostart/steam.desktop"
|
||||
|
||||
# Reload user systemd context and activate the background loop
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now steam-desktop-autostart.service
|
||||
user_systemctl daemon-reload
|
||||
user_systemctl enable --now steam-desktop-autostart.service
|
||||
|
||||
ok "Steam UI parameters and keyboard autostart deployed successfully."
|
||||
}
|
||||
|
||||
steam_disable() {
|
||||
info "Removing Steam desktop autostart..."
|
||||
systemctl --user disable --now steam-desktop-autostart.service 2>/dev/null
|
||||
user_systemctl disable --now steam-desktop-autostart.service 2>/dev/null
|
||||
rm -f "$HOME/.config/systemd/user/steam-desktop-autostart.service" \
|
||||
"$HOME/.config/environment.d/99-kde-virtual-keyboard.conf"
|
||||
systemctl --user daemon-reload
|
||||
user_systemctl daemon-reload
|
||||
ok "Steam desktop settings removed (takes full effect at next login)."
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# "Add as non-Steam game" (steamgame), a sub-option of the Steamify shortcut:
|
||||
# Steamify CachyOS in the Steam library, so Steam can start it: with Steam
|
||||
# Input it then gets the controller (a Steam Controller only talks to Steam
|
||||
# while Steam runs), in gaming mode too. Steam reads its non-Steam games
|
||||
# (userdata/<id>/config/shortcuts.vdf, binary; patches/steam-shortcuts.py)
|
||||
# only at startup and writes them back on exit, so Steam is closed for the
|
||||
# edit and started again. That's refused while a Steam session depends on it:
|
||||
# in gaming mode, or with this run started from Steam itself. What it did is
|
||||
# recorded per Steam account (state steamgame, "<account>": "<appid> added"
|
||||
# or "<appid> updated", for an entry that was there already): Steam rewrites
|
||||
# the file on exit, so a mark inside the entry could be lost; the start
|
||||
# script's path in it is the other sign it's ours.
|
||||
# Sourced by steamify.sh; not meant to be run on its own.
|
||||
|
||||
STEAM_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/Steam"
|
||||
STEAMGAME_NAME="Steamify CachyOS"
|
||||
# Steam shows no SVG icons: the shortcut's icon rendered as PNG.
|
||||
STEAMGAME_ICON="$STEAMIFY_DATA/steamify.png"
|
||||
|
||||
steamgame_files() {
|
||||
# Every Steam account's shortcuts.vdf (the file may not exist yet).
|
||||
local d
|
||||
for d in "$STEAM_ROOT"/userdata/*/config; do
|
||||
[[ -d "$d" && "$(basename "$(dirname "$d")")" != 0 ]] && echo "$d/shortcuts.vdf"
|
||||
done
|
||||
}
|
||||
|
||||
steamgame_tool() {
|
||||
python3 <(patch_file steam-shortcuts.py) "$@"
|
||||
}
|
||||
|
||||
steamgame_available() { [[ -n "$(steamgame_files)" ]]; }
|
||||
|
||||
steamgame_status() {
|
||||
local f
|
||||
while IFS= read -r f; do
|
||||
[[ -f "$f" ]] && steamgame_tool find "$f" "$WIZARD_APP_LAUNCHER" >/dev/null && return 0
|
||||
done < <(steamgame_files)
|
||||
return 1
|
||||
}
|
||||
|
||||
steamgame_steam_busy() {
|
||||
# Closing Steam would end what's running: gaming mode is Steam's own
|
||||
# session, and a run started from Steam is one of its games.
|
||||
if [[ -n "${SteamGameId:-}" ]]; then
|
||||
err "Steamify was started from Steam, which has to close for this. Start it from the desktop icon, or run Steamify Terminal."
|
||||
return 0
|
||||
fi
|
||||
if pgrep -x gamescope >/dev/null || pgrep -f gamescope-session >/dev/null; then
|
||||
err "Steam has to close for this, which would end gaming mode. Do it from the desktop."
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
steamgame_edit() {
|
||||
# steamgame_edit <function>: runs it with Steam closed, then starts Steam
|
||||
# again if it was running (through our autostart unit when that's there).
|
||||
local was_running=false i
|
||||
if pgrep -x steam >/dev/null; then
|
||||
steamgame_steam_busy && return 1
|
||||
was_running=true
|
||||
info "Closing Steam for a moment (it only reads its games list at startup)..."
|
||||
# A Steam that's still starting ignores the request: ask again.
|
||||
for ((i = 0; i < 90; i++)); do
|
||||
((i % 15 == 0)) && steam -shutdown >/dev/null 2>&1
|
||||
pgrep -x steam >/dev/null || break
|
||||
sleep 1
|
||||
done
|
||||
pgrep -x steam >/dev/null && { err "Steam didn't close; nothing changed."; return 1; }
|
||||
fi
|
||||
"$1"; local rc=$?
|
||||
if [[ "$was_running" == true ]]; then
|
||||
if user_systemctl is-enabled -q steam-desktop-autostart.service 2>/dev/null; then
|
||||
user_systemctl start steam-desktop-autostart.service
|
||||
else
|
||||
systemd-run --user --collect -q steam -silent >/dev/null 2>&1
|
||||
fi
|
||||
info "Steam is starting again."
|
||||
fi
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
steamgame_icon() {
|
||||
# The PNG, or the SVG when it can't be rendered (librsvg comes with KDE).
|
||||
if [[ -f "$WIZARD_ICON" ]] && command -v rsvg-convert >/dev/null &&
|
||||
mkdir -p "$STEAMIFY_DATA" && rsvg-convert -w 256 -h 256 -o "$STEAMGAME_ICON" "$WIZARD_ICON" 2>/dev/null; then
|
||||
echo "$STEAMGAME_ICON"
|
||||
else
|
||||
echo "$WIZARD_ICON"
|
||||
fi
|
||||
}
|
||||
|
||||
steamgame_add_all() {
|
||||
local f out icon
|
||||
icon="$(steamgame_icon)"
|
||||
while IFS= read -r f; do
|
||||
# A copy of each account's list before the first change.
|
||||
[[ -f "$f" && ! -f "$STATE_DIR/shortcuts-$(basename "$(dirname "$(dirname "$f")")").vdf" ]] &&
|
||||
mkdir -p "$STATE_DIR" && cp -a "$f" "$STATE_DIR/shortcuts-$(basename "$(dirname "$(dirname "$f")")").vdf"
|
||||
out="$(steamgame_tool add "$f" "$STEAMGAME_NAME" "$WIZARD_APP_LAUNCHER" "$icon" "$WIZARD_APP_ENTRY")" ||
|
||||
{ err "Couldn't add Steamify to $f."; return 1; }
|
||||
state_set steamgame "$(basename "$(dirname "$(dirname "$f")")")" "${out#* } ${out%% *}"
|
||||
info "Steam library ($(basename "$(dirname "$(dirname "$f")")")): ${out%% *} $STEAMGAME_NAME."
|
||||
done < <(steamgame_files)
|
||||
}
|
||||
|
||||
steamgame_remove_all() {
|
||||
local f id
|
||||
while IFS= read -r f; do
|
||||
[[ -f "$f" ]] || continue
|
||||
# Any entry for our start script: it can't start anything without it.
|
||||
while id="$(steamgame_tool find "$f" "$WIZARD_APP_LAUNCHER")"; do
|
||||
steamgame_tool remove "$f" "$id" || return 1
|
||||
done
|
||||
done < <(steamgame_files)
|
||||
}
|
||||
|
||||
steamgame_enable() {
|
||||
[[ -x "$WIZARD_APP_LAUNCHER" ]] || { err "The Steamify shortcut isn't set up; turn that on first."; return 1; }
|
||||
info "Adding $STEAMGAME_NAME to your Steam library..."
|
||||
steamgame_edit steamgame_add_all || return 1
|
||||
ok "Steamify is in your Steam library (non-Steam games): start it from there for the controller, in gaming mode too."
|
||||
}
|
||||
|
||||
steamgame_disable() {
|
||||
info "Removing $STEAMGAME_NAME from your Steam library..."
|
||||
steamgame_edit steamgame_remove_all || return 1
|
||||
state_clear steamgame
|
||||
rm -f "$STEAMGAME_ICON"
|
||||
ok "Steamify removed from your Steam library."
|
||||
}
|
||||
+46
-2
@@ -378,6 +378,46 @@ machine_status() {
|
||||
pacman -Qi leds-valve-dkms-git >/dev/null 2>&1 && pacman -Qi steamos-manager >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Steam (steamwebhelper) reads the serial number for its System settings
|
||||
# straight from DMI, which the kernel makes root-only; SteamOS makes it
|
||||
# readable. A tmpfiles rule does that at every boot.
|
||||
SERIAL_TMPFILES=/etc/tmpfiles.d/steamify-serial.conf
|
||||
|
||||
# Steam's Wi-Fi backend switch goes through steamos-manager, which reads and
|
||||
# writes the backend in this file (SteamOS ships it); without it, it fails
|
||||
# with "Wi-Fi backend not found in config". wpa_supplicant is what
|
||||
# NetworkManager uses on CachyOS anyway. Not written when someone set a
|
||||
# backend themselves.
|
||||
WIFI_BACKEND_CONF=/etc/NetworkManager/conf.d/99-valve-wifi-backend.conf
|
||||
|
||||
wifi_backend_enable() {
|
||||
[[ -f "$WIFI_BACKEND_CONF" ]] && return 0
|
||||
grep -rqs '^wifi.backend=' /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/conf.d && return 0
|
||||
printf '%s\n' "# Written by Steamify: Steam's Wi-Fi backend setting (steamos-manager)." \
|
||||
'[device]' 'wifi.backend=wpa_supplicant' | sudo install -Dm644 /dev/stdin "$WIFI_BACKEND_CONF" ||
|
||||
{ err "Writing the Wi-Fi backend setting failed."; return 1; }
|
||||
state_set machine wifi_backend 1
|
||||
}
|
||||
|
||||
wifi_backend_disable() {
|
||||
[[ -n "$(state_get machine wifi_backend)" ]] || return 0
|
||||
sudo rm -f "$WIFI_BACKEND_CONF"
|
||||
state_clear machine
|
||||
}
|
||||
|
||||
serial_enable() {
|
||||
printf '%s\n' "# Written by Steamify: Steam shows the serial number, like on SteamOS." \
|
||||
'z /sys/class/dmi/id/product_serial 0444 - - -' | sudo tee "$SERIAL_TMPFILES" > /dev/null &&
|
||||
sudo systemd-tmpfiles --create "$SERIAL_TMPFILES" ||
|
||||
{ err "Making the serial number readable for Steam failed."; return 1; }
|
||||
}
|
||||
|
||||
serial_disable() {
|
||||
sudo rm -f "$SERIAL_TMPFILES"
|
||||
sudo chmod 0400 /sys/class/dmi/id/product_serial 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -403,9 +443,11 @@ EOF
|
||||
# for hardware and controller settings; it knows the Steam Machine from its DMI data.
|
||||
info "Installing hardware manager packages (steamos-manager & inputplumber)..."
|
||||
sudo pacman -S --needed --noconfirm steamos-manager inputplumber || { err "Installing hardware manager packages failed."; return 1; }
|
||||
serial_enable || return 1
|
||||
wifi_backend_enable || return 1
|
||||
sudo systemctl enable --now inputplumber.service
|
||||
sudo systemctl enable --now steamos-manager.service
|
||||
systemctl --user enable steamos-manager.service 2>/dev/null
|
||||
user_systemctl 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
|
||||
|
||||
@@ -422,10 +464,12 @@ EOF
|
||||
|
||||
machine_disable() {
|
||||
info "Removing Steam Machine support..."
|
||||
systemctl --user disable --now steamos-manager.service 2>/dev/null
|
||||
user_systemctl disable --now steamos-manager.service 2>/dev/null
|
||||
sudo systemctl disable --now steamos-manager.service 2>/dev/null
|
||||
sudo systemctl disable --now inputplumber.service 2>/dev/null
|
||||
sudo pacman -Rns --noconfirm steamos-manager inputplumber 2>/dev/null
|
||||
serial_disable
|
||||
wifi_backend_disable
|
||||
sudo rm -f "$LED_UDEV_RULE" /etc/modules-load.d/leds-valve.conf
|
||||
sudo udevadm control --reload
|
||||
sudo modprobe -r leds-valve 2>/dev/null
|
||||
|
||||
@@ -12,8 +12,8 @@ NOTIFY_NAME=steamify-update-check
|
||||
|
||||
notify_status() {
|
||||
[[ -f "$NOTIFY_SCRIPT" ]] &&
|
||||
systemctl --user is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null &&
|
||||
systemctl --user is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null
|
||||
user_systemctl is-enabled -q "$NOTIFY_NAME.timer" 2>/dev/null &&
|
||||
user_systemctl is-enabled -q "$NOTIFY_NAME.service" 2>/dev/null
|
||||
}
|
||||
|
||||
notify_seen() {
|
||||
@@ -40,19 +40,19 @@ notify_enable() {
|
||||
mkdir -p "$NOTIFY_UNITS"
|
||||
service_file "$NOTIFY_NAME.service" NOTIFY_SCRIPT="$NOTIFY_SCRIPT" > "$NOTIFY_UNITS/$NOTIFY_NAME.service"
|
||||
service_file "$NOTIFY_NAME.timer" > "$NOTIFY_UNITS/$NOTIFY_NAME.timer"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable "$NOTIFY_NAME.service" >/dev/null 2>&1 &&
|
||||
systemctl --user enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 ||
|
||||
user_systemctl daemon-reload
|
||||
user_systemctl enable "$NOTIFY_NAME.service" >/dev/null 2>&1 &&
|
||||
user_systemctl enable --now "$NOTIFY_NAME.timer" >/dev/null 2>&1 ||
|
||||
{ err "Enabling the update check failed."; return 1; }
|
||||
state_set notify seen "$VERSION"
|
||||
ok "Update notifications on: you'll get a notification when there's a new Steamify."
|
||||
}
|
||||
|
||||
notify_disable() {
|
||||
systemctl --user disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1
|
||||
user_systemctl disable --now "$NOTIFY_NAME.timer" "$NOTIFY_NAME.service" >/dev/null 2>&1
|
||||
rm -f "$NOTIFY_UNITS/$NOTIFY_NAME.service" "$NOTIFY_UNITS/$NOTIFY_NAME.timer" "$NOTIFY_SCRIPT"
|
||||
rmdir "$STEAMIFY_BIN" 2>/dev/null || true
|
||||
systemctl --user daemon-reload
|
||||
user_systemctl daemon-reload
|
||||
state_clear notify
|
||||
ok "Update notifications off."
|
||||
}
|
||||
+19
-1
@@ -54,10 +54,18 @@ theme_enable() {
|
||||
# Only remove it again on disable if we were the ones installing it.
|
||||
state_set theme installed_pkg 1
|
||||
fi
|
||||
# Without a session (the ISO's installer), for a user who has never
|
||||
# logged in: the layout still CachyOS's default from /etc/skel is
|
||||
# removed (after the backup below), and Plasma builds its first layout
|
||||
# from the look-and-feel package, as --resetLayout does below.
|
||||
local offline=false layout=~/.config/plasma-org.kde.plasma.desktop-appletsrc
|
||||
if [[ -z "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]]; then
|
||||
if [[ -e "$layout" ]] && ! cmp -s "$layout" "/etc/skel/.config/${layout##*/}"; then
|
||||
err "Run this from the Plasma desktop: applying the Vapor layout needs a running Plasma session."
|
||||
return 1
|
||||
fi
|
||||
offline=true
|
||||
fi
|
||||
|
||||
# Only the first time, so a re-apply doesn't overwrite the originals.
|
||||
local backup="$STATE_DIR/theme-layout" f
|
||||
@@ -87,6 +95,12 @@ theme_enable() {
|
||||
kset theme kdeglobals General ColorScheme --delete
|
||||
plasma-apply-colorscheme Vapor >/dev/null 2>&1 || true
|
||||
|
||||
if [[ "$offline" == true ]]; then
|
||||
rm -f ~/.config/plasma-org.kde.plasma.desktop-appletsrc ~/.config/plasmashellrc
|
||||
extras_enable || warn "SteamOS desktop extras not installed (see above); the theme itself is on."
|
||||
ok "Vapor theme set; Plasma lays out the Vapor desktop at the first login."
|
||||
return 0
|
||||
fi
|
||||
info "Applying the Vapor theme with its desktop and window layout..."
|
||||
# --resetLayout is "Desktop and window layout" in System Settings: the
|
||||
# SteamOS panel, launcher icon and wallpaper. It needs plasmashell up.
|
||||
@@ -120,7 +134,11 @@ theme_disable() {
|
||||
fi
|
||||
# After the tools above, so keys that weren't set before are removed again.
|
||||
krevert theme
|
||||
single_status && single_launcher
|
||||
if single_status; then
|
||||
single_launcher
|
||||
else
|
||||
single_launcher_drop
|
||||
fi
|
||||
restart_plasmashell_if_stopped
|
||||
qdbus6 org.kde.KWin /KWin org.kde.KWin.reconfigure >/dev/null 2>&1 || true
|
||||
|
||||
|
||||
+4
-4
@@ -101,7 +101,7 @@ vram_selectable() { vram_supported || vram_status; }
|
||||
vram_status() {
|
||||
pacman -Q "${VRAM_PKGS[@]}" >/dev/null 2>&1 &&
|
||||
systemctl is-enabled -q dmemcg-booster-system.service 2>/dev/null &&
|
||||
systemctl --user is-enabled -q dmemcg-booster-user.service 2>/dev/null
|
||||
user_systemctl is-enabled -q dmemcg-booster-user.service 2>/dev/null
|
||||
}
|
||||
|
||||
vram_enable() {
|
||||
@@ -115,18 +115,18 @@ vram_enable() {
|
||||
fi
|
||||
sudo systemctl enable --now dmemcg-booster-system.service >/dev/null 2>&1 ||
|
||||
{ err "Starting dmemcg-booster failed."; return 1; }
|
||||
systemctl --user enable --now dmemcg-booster-user.service >/dev/null 2>&1 ||
|
||||
user_systemctl enable --now dmemcg-booster-user.service >/dev/null 2>&1 ||
|
||||
{ err "Starting dmemcg-booster for $USER failed."; return 1; }
|
||||
kset vram kcgroupsrc "Foreground Booster" autostart true
|
||||
# Started by the Plasma session from now on; start it in this one too.
|
||||
[[ "${XDG_CURRENT_DESKTOP:-}" == KDE ]] &&
|
||||
systemctl --user start plasma-foreground-booster.service >/dev/null 2>&1
|
||||
user_systemctl start plasma-foreground-booster.service >/dev/null 2>&1
|
||||
ok "VRAM booster on: the game in front keeps its VRAM."
|
||||
}
|
||||
|
||||
vram_disable() {
|
||||
local pkgs
|
||||
systemctl --user disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1
|
||||
user_systemctl disable --now plasma-foreground-booster.service dmemcg-booster-user.service >/dev/null 2>&1
|
||||
sudo systemctl disable --now dmemcg-booster-system.service >/dev/null 2>&1
|
||||
krevert vram
|
||||
pkgs="$(state_get vram installed_pkgs)"
|
||||
|
||||
@@ -11,5 +11,7 @@ read and reviewed on their own. `lib/*.sh` reads them with `patch_file
|
||||
| `ensure-kernel-headers.sh` | `lib/steam-machine.sh` (Steam Machine support) | Run at boot: installs missing headers for every installed kernel |
|
||||
| `leds-valve-dkms.conf` | `lib/steam-machine.sh` (Steam Machine support) | `/etc/dkms` override: build the LED driver for DKMS's target kernel; appended to someone's own override, which is backed up and restored |
|
||||
| `steamify-fremont-poweroff.dkms.conf`, `steamify-cros-ec-cec.dkms.conf` | `lib/fremont-poweroff.sh`, `lib/cec.sh` | The DKMS modules' `dkms.conf` (`fill NAME=... VERSION=...`) |
|
||||
| `steam-shortcuts.py` | `lib/steam-game.sh` (Add as non-Steam game) | Reads and writes Steam's binary `shortcuts.vdf` (find, add or update, remove) |
|
||||
| `steamify-notifier.py` | `lib/update-notifier.sh` (Update notifications) | The daily check: notification and tray icon when there's a new release |
|
||||
| `steamify-os-release.sh`, `steamify-os-release.hook` | `lib/login-manager.sh` (SteamOS conversion) | What Steam's System settings show besides the OS name (which stays CachyOS's): `VERSION_ID=steamos-X.Y`, `VERSION_CODENAME=steam-machine`, `VARIANT`/`VARIANT_ID` Steamify with its version, in `/etc/os-release`; the pacman hook runs it again after cachyos-hooks (`fill SCRIPT=... VERSION=... STEAMOS=...`) |
|
||||
| `cros-ec-cec-single-port.patch` | `lib/cec.sh` (HDMI-CEC) | Makes Valve's Steam Machine CEC driver find amdgpu's HDMI port |
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
# Steamify CachyOS: Steam's non-Steam games list (userdata/<id>/config/
|
||||
# shortcuts.vdf, binary VDF), for "Add as non-Steam game". Steam reads it only
|
||||
# at startup and writes it back on exit, so it's edited while Steam is closed.
|
||||
# steam-shortcuts.py find <vdf> <exe> -> appid of an entry for exe
|
||||
# steam-shortcuts.py add <vdf> <name> <exe> <icon> <desktop file>
|
||||
# -> "added <appid>" / "updated <appid>"
|
||||
# steam-shortcuts.py remove <vdf> <appid>
|
||||
# An entry counts as ours when its Exe is <exe>, or a Steamify start script
|
||||
# (run-app, also under the pre-2.5.0 path): that one is updated, not doubled.
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
|
||||
MAP, STR, INT, END = 0, 1, 2, 8
|
||||
|
||||
|
||||
def parse(data):
|
||||
pos = 0
|
||||
|
||||
def cstr():
|
||||
nonlocal pos
|
||||
end = data.index(b"\0", pos)
|
||||
s = data[pos:end].decode("utf-8", "surrogateescape")
|
||||
pos = end + 1
|
||||
return s
|
||||
|
||||
def block():
|
||||
nonlocal pos
|
||||
items = []
|
||||
while True:
|
||||
t = data[pos]
|
||||
pos += 1
|
||||
if t == END:
|
||||
return items
|
||||
key = cstr()
|
||||
if t == MAP:
|
||||
items.append((key, block()))
|
||||
elif t == STR:
|
||||
items.append((key, cstr()))
|
||||
elif t == INT:
|
||||
items.append((key, struct.unpack_from("<I", data, pos)[0]))
|
||||
pos += 4
|
||||
else:
|
||||
raise ValueError(f"unknown VDF type {t} at {pos}")
|
||||
|
||||
if not data:
|
||||
return [("shortcuts", [])]
|
||||
root = block()
|
||||
return root
|
||||
|
||||
|
||||
def dump(items):
|
||||
out = bytearray()
|
||||
for key, value in items:
|
||||
k = key.encode("utf-8", "surrogateescape") + b"\0"
|
||||
if isinstance(value, list):
|
||||
out += bytes([MAP]) + k + dump(value)
|
||||
elif isinstance(value, int):
|
||||
out += bytes([INT]) + k + struct.pack("<I", value & 0xFFFFFFFF)
|
||||
else:
|
||||
out += bytes([STR]) + k + value.encode("utf-8", "surrogateescape") + b"\0"
|
||||
return bytes(out) + bytes([END])
|
||||
|
||||
|
||||
def get(entry, key, default=None):
|
||||
# Steam's key names vary in case between versions (appid/AppName/Exe).
|
||||
for k, v in entry:
|
||||
if k.lower() == key.lower():
|
||||
return v
|
||||
return default
|
||||
|
||||
|
||||
def put(entry, key, value):
|
||||
for i, (k, _) in enumerate(entry):
|
||||
if k.lower() == key.lower():
|
||||
entry[i] = (k, value)
|
||||
return
|
||||
entry.append((key, value))
|
||||
|
||||
|
||||
def shortcuts(root):
|
||||
for k, v in root:
|
||||
if k.lower() == "shortcuts":
|
||||
return v
|
||||
raise ValueError("no shortcuts block")
|
||||
|
||||
|
||||
def ours(entry, exe):
|
||||
have = get(entry, "Exe", "").strip('"')
|
||||
return have == exe or (have.endswith("/run-app") and
|
||||
("/steamify/" in have or "/cachyos-gamescope-boot/" in have))
|
||||
|
||||
|
||||
def load(path):
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
return parse(f.read())
|
||||
except FileNotFoundError:
|
||||
return [("shortcuts", [])]
|
||||
|
||||
|
||||
def save(path, root):
|
||||
tmp = path + ".steamify-tmp"
|
||||
with open(tmp, "wb") as f:
|
||||
f.write(dump(root))
|
||||
os.replace(tmp, path)
|
||||
|
||||
|
||||
def main(argv):
|
||||
cmd, path = argv[1], argv[2]
|
||||
root = load(path)
|
||||
entries = shortcuts(root)
|
||||
if cmd == "find":
|
||||
for _, e in entries:
|
||||
if ours(e, argv[3]):
|
||||
print(get(e, "appid", 0))
|
||||
return 0
|
||||
return 1
|
||||
if cmd == "add":
|
||||
name, exe, icon, desktop = argv[3:7]
|
||||
for _, e in entries:
|
||||
if ours(e, exe):
|
||||
put(e, "AppName", name)
|
||||
put(e, "Exe", f'"{exe}"')
|
||||
put(e, "StartDir", f'"{os.path.dirname(exe)}/"')
|
||||
put(e, "icon", icon)
|
||||
put(e, "ShortcutPath", desktop)
|
||||
save(path, root)
|
||||
print("updated", get(e, "appid", 0))
|
||||
return 0
|
||||
# Steam's own id for a shortcut: CRC32 of exe and name, top bit set.
|
||||
appid = (zlib.crc32(f'"{exe}"{name}'.encode()) | 0x80000000) & 0xFFFFFFFF
|
||||
entry = [("appid", appid), ("AppName", name), ("Exe", f'"{exe}"'),
|
||||
("StartDir", f'"{os.path.dirname(exe)}/"'), ("icon", icon),
|
||||
("ShortcutPath", desktop), ("LaunchOptions", ""), ("IsHidden", 0),
|
||||
("AllowDesktopConfig", 1), ("AllowOverlay", 1), ("OpenVR", 0),
|
||||
("Devkit", 0), ("DevkitGameID", ""), ("DevkitOverrideAppID", 0),
|
||||
("LastPlayTime", 0), ("FlatpakAppID", ""), ("tags", [])]
|
||||
entries.append((str(len(entries)), entry))
|
||||
if os.path.dirname(path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
save(path, root)
|
||||
print("added", appid)
|
||||
return 0
|
||||
if cmd == "remove":
|
||||
keep = [e for _, e in entries if str(get(e, "appid", "")) != argv[3]]
|
||||
if len(keep) == len(entries):
|
||||
return 0
|
||||
entries[:] = [(str(i), e) for i, e in enumerate(keep)]
|
||||
save(path, root)
|
||||
return 0
|
||||
print(f"unknown command {cmd}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,14 @@
|
||||
# Installed by Steamify CachyOS (SteamOS conversion): runs after
|
||||
# cachyos-hooks' os-release/cachyos-branding hooks (hooks run in file name
|
||||
# order).
|
||||
[Trigger]
|
||||
Operation = Install
|
||||
Operation = Upgrade
|
||||
Type = Package
|
||||
Target = filesystem
|
||||
Target = cachyos-hooks
|
||||
|
||||
[Action]
|
||||
Description = Steamify: the OS variant and version Steam shows
|
||||
When = PostTransaction
|
||||
Exec = @SCRIPT@
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Installed by Steamify CachyOS (SteamOS conversion): what Steam shows in its
|
||||
# System settings besides the OS name, which stays CachyOS's: the SteamOS
|
||||
# release Steamify follows as the OS version (VERSION_ID, e.g. steamos-3.9),
|
||||
# steam-machine as the codename, and Steamify with its version as the variant
|
||||
# (VARIANT_ID, which Steam shows). KDE's About this System would show
|
||||
# VERSION_ID after the name: the conversion sets its UseOSReleaseVersion, so it
|
||||
# shows os-release's VERSION instead, which CachyOS doesn't have. NAME and
|
||||
# PRETTY_NAME are never touched: limine-snapper-sync finds the boot entries by
|
||||
# the OS name. The zz-steamify-os-release hook runs this after cachyos-hooks.
|
||||
sed -i -e '/^VARIANT=/d' -e '/^VARIANT_ID=/d' -e '/^VERSION_ID=/d' -e '/^VERSION_CODENAME=/d' /etc/os-release
|
||||
printf '%s\n' 'VARIANT="Steamify @VERSION@"' 'VARIANT_ID=steamify-@VERSION@' 'VERSION_CODENAME=steam-machine' >> /etc/os-release
|
||||
[ -n "@STEAMOS@" ] && echo 'VERSION_ID=@STEAMOS@' >> /etc/os-release
|
||||
exit 0
|
||||
+65
-3
@@ -15,11 +15,11 @@
|
||||
set -uo pipefail
|
||||
|
||||
# Release version, see CHANGELOG.md.
|
||||
VERSION=2.5.0
|
||||
VERSION=2.9.6
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut update-notifier menu backend; do
|
||||
for lib in common state packages login-manager single-user steam-desktop steam-machine fremont-poweroff vram-booster hdmi-refresh cec boot-session vapor-theme steamos-extras bios desktop-shortcut wizard-shortcut steam-game update-notifier first-login menu backend; do
|
||||
# shellcheck source=/dev/null
|
||||
source "$SCRIPT_DIR/lib/$lib.sh"
|
||||
done
|
||||
@@ -28,8 +28,21 @@ require_root_helper
|
||||
|
||||
BACKEND=false
|
||||
[[ "${1:-}" == --backend ]] && BACKEND=true
|
||||
# --defaults [--options <id>,...] [--boot gamescope|desktop]: apply what the
|
||||
# menu would preselect (or exactly the listed items), without the menu or any
|
||||
# prompt (the Steam Machine ISO's first login runs this; sudo must not ask).
|
||||
DEFAULTS=false
|
||||
[[ "${1:-}" == --defaults ]] && DEFAULTS=true
|
||||
# --defaults --list: what --defaults can set up here, as JSON (an installer
|
||||
# page is built from it); nothing else on stdout.
|
||||
LIST=false
|
||||
[[ "$DEFAULTS" == true && "${2:-}" == --list ]] && LIST=true
|
||||
# --boot gamescope|desktop: only change where an installed conversion
|
||||
# starts, everything else stays as it is (for scripts; no menu).
|
||||
BOOT_ONLY=false
|
||||
[[ "${1:-}" == --boot ]] && BOOT_ONLY=true
|
||||
|
||||
if [[ "$BACKEND" == false ]]; then
|
||||
if [[ "$BACKEND" == false && "$LIST" == false ]]; then
|
||||
echo -e "${c_bold}Steamify CachyOS${c_reset} v$VERSION"
|
||||
echo "Turn the SteamOS-style parts on or off. The menu shows what is on now;"
|
||||
echo "anything you turn off is put back the way it was."
|
||||
@@ -114,6 +127,55 @@ if [[ "$BACKEND" == true ]]; then
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" == --first-login ]]; then
|
||||
first_login_run
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$LIST" == true ]]; then
|
||||
defaults_list
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$DEFAULTS" == true ]]; then
|
||||
RESTART_FOR_LOGIN=false
|
||||
REAPPLY=false
|
||||
sudo -n true 2>/dev/null || { err "--defaults needs sudo without a password."; exit 1; }
|
||||
detect_components
|
||||
defaults_options "${@:2}" || exit 1
|
||||
plan_changes
|
||||
apply_changes
|
||||
# Run by the installer: the rest waits for the first desktop login.
|
||||
user_session || first_login_schedule
|
||||
[[ ${#FAILED[@]} -eq 0 ]] || { warn "These had problems (see above): ${FAILED[*]}"; exit 1; }
|
||||
ok "Done; the changes take effect after a restart."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$BOOT_ONLY" == true ]]; then
|
||||
RESTART_FOR_LOGIN=false
|
||||
REAPPLY=false
|
||||
case "${2:-}" in
|
||||
gamescope|desktop) ;;
|
||||
*) err "--boot takes gamescope or desktop"; exit 1 ;;
|
||||
esac
|
||||
detect_components
|
||||
[[ "${CURRENT[gaming]}" == 1 ]] || { err "--boot needs the SteamOS conversion; turn it on first."; exit 1; }
|
||||
# What is on stays on, and nothing else changes: not the updates or
|
||||
# removals a normal run would pick.
|
||||
for c in "${COMPONENTS[@]}"; do WANTED[$c]=${CURRENT[$c]:-0}; done
|
||||
WANTED[boot]=0; [[ "$2" == desktop ]] && WANTED[boot]=1
|
||||
plan_changes
|
||||
if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then
|
||||
ok "Already starting in $2."
|
||||
exit 0
|
||||
fi
|
||||
apply_changes
|
||||
[[ ${#FAILED[@]} -eq 0 ]] || { warn "These had problems (see above): ${FAILED[*]}"; exit 1; }
|
||||
ok "Starts in $2 from the next boot on."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Menu loop: after each run the menu comes back with the new state, until
|
||||
# the user quits; the restart question comes then, once, for everything.
|
||||
RESTART_FOR_LOGIN=false
|
||||
|
||||
+22
-5
@@ -18,6 +18,10 @@ Item {
|
||||
property var steps: ({}) // id -> "wait"|"run"|"ok"|"fail"
|
||||
property var failed: []
|
||||
property bool restartNeeded: false
|
||||
// Review & apply only when something would change; "Re-apply what's on"
|
||||
// is for when nothing did.
|
||||
readonly property bool canApply: computePlan().length > 0
|
||||
onCanApplyChanged: if (!canApply && sel === rows.length) sel = Math.max(0, rows.length - 1)
|
||||
property string runError: ""
|
||||
property bool wrongPassword: false
|
||||
property bool typing: false // a text field has the keys
|
||||
@@ -77,6 +81,8 @@ Item {
|
||||
if (id === "poweroff" && w.poweroff) w.machine = true;
|
||||
if (id === "machine" && !w.machine) w.kpin = false;
|
||||
if (id === "kpin" && w.kpin) w.machine = true;
|
||||
if (id === "launcher" && items.some(function (i) { return i.id === "steamgame"; })) w.steamgame = w.launcher;
|
||||
if (id === "steamgame" && w.steamgame) w.launcher = true;
|
||||
want = w;
|
||||
}
|
||||
function computePlan() {
|
||||
@@ -93,7 +99,10 @@ Item {
|
||||
if (want.gaming && boot !== bootNow) p.push({ id: "boot", action: boot === "desktop" ? "desktop" : "gaming" });
|
||||
return p;
|
||||
}
|
||||
function goReview(again) { reapply = again; plan = computePlan(); screen = "review"; }
|
||||
function goReview(again) {
|
||||
if (!again && !canApply) return;
|
||||
reapply = again; plan = computePlan(); screen = "review";
|
||||
}
|
||||
function wantedIds() {
|
||||
var ids = [];
|
||||
for (var i = 0; i < items.length; i++)
|
||||
@@ -195,7 +204,8 @@ Item {
|
||||
if (screen === "menu") {
|
||||
if (biosChecking) return;
|
||||
if (a === "up") sel = Math.max(0, sel - 1);
|
||||
else if (a === "down") sel = Math.min(rows.length, sel + 1);
|
||||
// The greyed-out Apply button can't be selected.
|
||||
else if (a === "down") sel = Math.min(canApply ? rows.length : rows.length - 1, sel + 1);
|
||||
else if (a === "accept") {
|
||||
if (sel === rows.length) { goReview(false); return; }
|
||||
var r = rows[sel];
|
||||
@@ -207,7 +217,9 @@ Item {
|
||||
boot = a === "left" ? "gamescope" : "desktop";
|
||||
else if (a === "apply") goReview(false);
|
||||
else if (a === "reapply") goReview(true);
|
||||
else if (a === "back") Qt.quit();
|
||||
// B/Esc/Back don't quit: Steam's desktop layout sends Esc for a
|
||||
// Steam Controller's B, which should only ever go back.
|
||||
else if (a === "quit") Qt.quit();
|
||||
} else if (screen === "review") {
|
||||
if (a === "accept" || a === "apply") onApplyPressed();
|
||||
else if (a === "back") { reapply = false; screen = "menu"; }
|
||||
@@ -246,16 +258,21 @@ Item {
|
||||
if (screen === "bios2" && Input.type !== "keyboard" && (e.key === Qt.Key_Return || e.key === Qt.Key_Enter)) {
|
||||
if (!e.isAutoRepeat) act("hold"); e.accepted = true; return;
|
||||
}
|
||||
if (!fromRemote) Input.type = "keyboard";
|
||||
if (!fromRemote) Input.type = Input.fromSteam ? "steam" : "keyboard";
|
||||
var k = e.key;
|
||||
if (k === Qt.Key_Up) act("up");
|
||||
else if (k === Qt.Key_Down) act("down");
|
||||
else if (k === Qt.Key_Left) act("left");
|
||||
else if (k === Qt.Key_Right) act("right");
|
||||
else if (k === Qt.Key_Space) act("accept");
|
||||
else if (k === Qt.Key_Return || k === Qt.Key_Enter) act(Input.type === "remote" || screen !== "menu" ? "accept" : "apply");
|
||||
// Enter selects, like A on a controller: Steam's desktop layout sends
|
||||
// Enter for a Steam Controller's A (and Space for Y). Ctrl+Enter
|
||||
// goes straight to Review & apply.
|
||||
else if ((k === Qt.Key_Return || k === Qt.Key_Enter) && (e.modifiers & Qt.ControlModifier)) act("apply");
|
||||
else if (k === Qt.Key_Return || k === Qt.Key_Enter) act("accept");
|
||||
else if (k === Qt.Key_Escape || k === Qt.Key_Back || k === Qt.Key_Backspace) { if (!e.isAutoRepeat) act("back"); }
|
||||
else if (k === Qt.Key_R) act("reapply");
|
||||
else if (k === Qt.Key_Q && (e.modifiers & Qt.ControlModifier)) act("quit");
|
||||
else return;
|
||||
e.accepted = true;
|
||||
}
|
||||
|
||||
@@ -35,9 +35,15 @@ Item {
|
||||
border.width: step.st === "wait" || step.st === "run" ? 2 : 0; border.color: step.st === "run" ? Theme.accent : "#343f50"
|
||||
Text { anchors.centerIn: parent; text: step.st === "ok" ? "✓" : (step.st === "fail" ? "!" : ""); color: step.st === "ok" ? Theme.good : Theme.bad
|
||||
font.pixelSize: 13; font.weight: Font.Bold }
|
||||
RotationAnimator on rotation { running: step.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } }
|
||||
// Only this dot turns while the step runs: ✓ and ! never rotate
|
||||
// (an animator leaves its item at whatever angle it stopped).
|
||||
Item { anchors.fill: parent; visible: step.st === "run"
|
||||
Rectangle { width: 6; height: 6; radius: 3; color: Theme.accent; x: parent.width / 2 - 3; y: -3 }
|
||||
RotationAnimator on rotation { running: step.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } }
|
||||
Row { width: 380; anchors.verticalCenter: parent.verticalCenter; spacing: 10
|
||||
Text { text: (Texts.actions[step.modelData.action] || [])[3] + Texts.label(step.modelData.id)
|
||||
// Boot into's label is its prefix already: "Boot into desktop", not "Boot into Boot into".
|
||||
Text { text: step.modelData.id === "boot" ? (Texts.actions[step.modelData.action] || [])[3] + Texts.actions[step.modelData.action][0].toLowerCase()
|
||||
: (Texts.actions[step.modelData.action] || [])[3] + Texts.label(step.modelData.id)
|
||||
color: step.st === "wait" ? Theme.faint : Theme.textHi; font.family: Theme.body; font.pixelSize: 16; font.weight: Font.DemiBold
|
||||
anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, parent.width - (badge.visible ? badge.width + parent.spacing : 0)) }
|
||||
|
||||
+4
-2
@@ -10,11 +10,13 @@ Rectangle {
|
||||
signal clicked
|
||||
height: 48; radius: 12; width: row.implicitWidth + 36
|
||||
color: primary ? Theme.accent : Theme.button
|
||||
border.width: focusRing ? 2 : 0; border.color: Theme.textHi
|
||||
border.width: focusRing && enabled ? 2 : 0; border.color: Theme.textHi
|
||||
// Greyed out while there's nothing to do.
|
||||
opacity: enabled ? 1 : 0.4
|
||||
Row { id: row; anchors.centerIn: parent; spacing: 10
|
||||
Glyph { k: btn.k; visible: btn.k !== ""; dark: btn.primary; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: btn.text; color: btn.primary ? Theme.ink : Theme.text; font.family: Theme.body; font.pixelSize: 15; font.weight: Font.DemiBold
|
||||
anchors.verticalCenter: parent.verticalCenter }
|
||||
}
|
||||
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() }
|
||||
MouseArea { anchors.fill: parent; enabled: btn.enabled; cursorShape: Qt.PointingHandCursor; onClicked: btn.clicked() }
|
||||
}
|
||||
+12
-2
@@ -1,5 +1,8 @@
|
||||
// The result of a run: all done, a problem, or nothing changed.
|
||||
// The result of a run: all done, a problem, or nothing changed. With a
|
||||
// problem, the run's output is shown below it (a BIOS check has no progress
|
||||
// screen, so this is the only place to read why it stopped).
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Item {
|
||||
required property AppState app
|
||||
@@ -20,7 +23,14 @@ Item {
|
||||
Text { width: parent.width; horizontalAlignment: Text.AlignHCenter; wrapMode: Text.WordWrap; color: Theme.soft; font.family: Theme.body; font.pixelSize: 17; text: message }
|
||||
Rectangle { visible: app.failed.length > 0; anchors.horizontalCenter: parent.horizontalCenter; width: ft.implicitWidth + 32; height: 44; radius: 12; color: Theme.warnBg
|
||||
Text { id: ft; anchors.centerIn: parent; color: "#f2c27a"; font.family: Theme.body; font.pixelSize: 14
|
||||
text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + ". See the details." } }
|
||||
text: "Had a problem: " + app.failed.map(function (id) { return Texts.label(id); }).join(", ") + (app.log.count ? ". Details below." : ".") } }
|
||||
Rectangle { visible: app.failed.length > 0 && app.log.count > 0; width: parent.width; height: 190; radius: 14; color: Theme.card
|
||||
ListView { id: details; anchors.fill: parent; anchors.margins: 16; clip: true; model: app.log
|
||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
// The end is where it stopped.
|
||||
onCountChanged: positionViewAtEnd()
|
||||
onVisibleChanged: if (visible) positionViewAtEnd()
|
||||
delegate: Text { required property string line; width: details.width - 12; text: line; color: "#aab5c4"; font.family: Theme.mono; font.pixelSize: 13; wrapMode: Text.WrapAnywhere } } }
|
||||
Row { anchors.horizontalCenter: parent.horizontalCenter; spacing: 14; topPadding: 8
|
||||
Btn { k: Input.g.back; text: "Back to the menu"; height: 52; onClicked: app.act("back") }
|
||||
Btn { visible: app.restartNeeded; k: Input.g.ok; text: "Restart now"; primary: true; height: 52; onClicked: app.backend.restart() } }
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ Rectangle {
|
||||
property string k
|
||||
property bool dark: false
|
||||
height: 26; width: Math.max(26, gt.implicitWidth + 14)
|
||||
radius: Input.type === "controller" && k.length === 1 ? 13 : 7
|
||||
radius: (Input.type === "controller" || Input.type === "steam") && k.length === 1 ? 13 : 7
|
||||
color: dark ? Theme.ink : Theme.key
|
||||
Text { id: gt; anchors.centerIn: parent; text: glyph.k; color: glyph.dark ? Theme.accent : Theme.textHi
|
||||
font.family: Theme.body; font.pixelSize: 12; font.weight: Font.DemiBold }
|
||||
|
||||
+8
-3
@@ -5,10 +5,15 @@ import QtQuick
|
||||
|
||||
QtObject {
|
||||
property string type: "keyboard"
|
||||
// Started from Steam: keys come from a Steam Controller through Steam's
|
||||
// desktop layout (A = Enter, B = Esc, Y = Space, X = Steam's keyboard),
|
||||
// shown as "steam" with the controller's buttons.
|
||||
property bool fromSteam: false
|
||||
readonly property var glyphs: ({
|
||||
controller: { toggle: "A", choose: "◀ ▶", reapply: "Y", quit: "B", apply: "X", ok: "A", back: "B" },
|
||||
keyboard: { toggle: "Space", choose: "← →", reapply: "R", quit: "Esc", apply: "Enter", ok: "Enter", back: "Esc" },
|
||||
remote: { toggle: "OK", choose: "◀ ▶", reapply: "Red", quit: "Back", apply: "↓ Apply", ok: "OK", back: "Back" }
|
||||
controller: { toggle: "A", choose: "◀ ▶", reapply: "Y", quit: "", apply: "X", ok: "A", back: "B" },
|
||||
keyboard: { toggle: "Enter", choose: "← →", reapply: "R", quit: "Ctrl+Q", apply: "Ctrl+Enter", ok: "Enter", back: "Esc" },
|
||||
steam: { toggle: "A", choose: "◀ ▶", reapply: "", quit: "", apply: "↓ A", ok: "A", back: "B" },
|
||||
remote: { toggle: "OK", choose: "◀ ▶", reapply: "Red", quit: "", apply: "↓ Apply", ok: "OK", back: "Back" }
|
||||
})
|
||||
readonly property var g: glyphs[type]
|
||||
}
|
||||
@@ -12,6 +12,8 @@ ApplicationWindow {
|
||||
required property var gamepad
|
||||
required property bool fullscreen
|
||||
required property string iconUrl
|
||||
required property bool fromSteam
|
||||
Component.onCompleted: Input.fromSteam = fromSteam
|
||||
|
||||
width: 1280; height: 720
|
||||
minimumWidth: 960; minimumHeight: 540
|
||||
|
||||
@@ -59,7 +59,10 @@ Item {
|
||||
Row {
|
||||
anchors.left: parent.left; anchors.leftMargin: 40; anchors.verticalCenter: parent.verticalCenter; spacing: 28
|
||||
Repeater {
|
||||
// Quit only has a key on a keyboard (Ctrl+Q), and re-apply has
|
||||
// none through Steam's layout; a controller quits through Steam.
|
||||
model: [[screen.g.toggle, "Toggle"], [screen.g.choose, "Choose"], [screen.g.reapply, "Re-apply what's on"], [screen.g.quit, "Quit"]]
|
||||
.filter(function (h) { return h[0] !== ""; })
|
||||
Row { required property var modelData; spacing: 8
|
||||
Glyph { k: parent.modelData[0]; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: parent.modelData[1]; color: Theme.label; font.family: Theme.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter } }
|
||||
@@ -70,7 +73,8 @@ Item {
|
||||
Text { readonly property int n: app.computePlan().length
|
||||
text: n === 0 ? "Everything is the way you want it" : n + (n === 1 ? " change" : " changes")
|
||||
color: Theme.faint; font.family: Theme.body; font.pixelSize: 14; anchors.verticalCenter: parent.verticalCenter }
|
||||
Btn { k: screen.g.apply; text: "Review & apply"; primary: true; focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) }
|
||||
Btn { k: screen.g.apply; text: "Review & apply"; primary: true; enabled: app.canApply
|
||||
focusRing: app.sel === app.rows.length; height: 44; onClicked: app.goReview(false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ Rectangle {
|
||||
Text { visible: app.wrongPassword; text: "That password didn't work. Try again."; color: Theme.bad; font.family: Theme.body; font.pixelSize: 14 }
|
||||
Rectangle { width: parent.width; height: 46; radius: 12; color: Theme.note
|
||||
Text { anchors.verticalCenter: parent.verticalCenter; x: 14; color: Theme.soft; font.family: Theme.body; font.pixelSize: 14
|
||||
text: ({ controller: "Press Steam + X for the on-screen keyboard.", keyboard: "Type your password and press Enter.", remote: "Select the field for the on-screen keyboard." })[Input.type] } }
|
||||
text: ({ controller: "Press Steam + X for the on-screen keyboard.", keyboard: "Type your password and press Enter.", steam: "Press X for Steam's on-screen keyboard, type your password and press A.", remote: "Select the field for the on-screen keyboard." })[Input.type] } }
|
||||
Item { width: parent.width; height: 48
|
||||
Btn { k: Input.g.back; text: "Back"; onClicked: app.act("back") }
|
||||
Btn { anchors.right: parent.right; k: Input.g.ok; text: "Apply"; primary: true; onClicked: screen.submit() } }
|
||||
|
||||
@@ -21,6 +21,9 @@ QtObject {
|
||||
launcher: { label: "Steamify shortcut", hint: "The app on the desktop and in the launcher",
|
||||
body: "Opens the newest Steamify app, so you never need the install command again. Steamify Terminal in the launcher opens the terminal menu.",
|
||||
changes: ["Steamify CachyOS on the desktop and in the launcher (the app)", "Steamify Terminal in the launcher"] },
|
||||
steamgame: { label: "Add as non-Steam game", hint: "Steamify in your Steam library",
|
||||
body: "Adds Steamify to your Steam library as a non-Steam game. Started from Steam it gets your controller (a Steam Controller only talks to Steam while Steam runs), and you can open it in gaming mode. Steam closes for a moment and starts again, since it only reads its games list at startup: so this is done from the desktop, not from gaming mode or with Steamify started from Steam.",
|
||||
changes: ["Steamify CachyOS in your Steam library, with its icon", "An entry that's already there is updated, not added twice", "Off (or the shortcut off): removed from the library again"] },
|
||||
notify: { label: "Update notifications", hint: "A notification when there's a new Steamify",
|
||||
body: "Checks once a day and when you log in to the desktop. When there's a new Steamify you get a notification and a tray icon: open the app to see what's new and choose what to update, or skip that version. It never updates anything by itself.",
|
||||
changes: ["A daily check (systemd user timer), only on the desktop", "Tray icon only while an update is waiting", "Skip this version: quiet until the next one"] },
|
||||
|
||||
+9
-4
@@ -1,12 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Steamify CachyOS - get and start the app (v2), for `curl | bash`:
|
||||
# Steamify CachyOS - get and start the app, for `curl | bash`:
|
||||
# curl -fsSL https://github.com/theupriser/steamify-cachyos/releases/latest/download/steamify-app.sh | bash
|
||||
# Downloads the newest release's app package (steamify-app.tar.gz: the app,
|
||||
# steamify.sh, lib/, patches/ and services/) to ~/.local/share/steamify/app, installs PySide6 if
|
||||
# it's missing, adds a launcher entry, and starts the app.
|
||||
# STEAMIFY_RELEASE=<tag> takes that release instead of the newest (the
|
||||
# v2-ui-preview build's copy of this script sets it). STEAMIFY_BRANCH=<branch>
|
||||
# takes that branch's source instead (for testing before a build).
|
||||
# STEAMIFY_RELEASE=<tag> takes that release instead of the newest.
|
||||
# STEAMIFY_BRANCH=<branch> takes that branch's source instead (for testing
|
||||
# before a build).
|
||||
set -euo pipefail
|
||||
|
||||
REPO="theupriser/steamify-cachyos"
|
||||
@@ -71,4 +71,9 @@ EOF
|
||||
command -v update-desktop-database >/dev/null && update-desktop-database "$APPS" 2>/dev/null || true
|
||||
|
||||
say "Starting Steamify..."
|
||||
# Started from Steam: stay in the foreground, or Steam counts the game as
|
||||
# ended right away and stops giving it the controller.
|
||||
if [[ -n "${SteamGameId:-}" ]]; then
|
||||
exec "$DEST/ui/steamify-ui" < /dev/null
|
||||
fi
|
||||
setsid "$DEST/ui/steamify-ui" >/dev/null 2>&1 < /dev/null &
|
||||
@@ -20,6 +20,14 @@ import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
# Started from Steam (a non-Steam game, so Steam Input hands it the
|
||||
# controller): Steam preloads its overlay into every game, and Qt aborts with
|
||||
# it loaded. Start again without it, before Qt is imported.
|
||||
if "gameoverlayrenderer" in os.environ.get("LD_PRELOAD", ""):
|
||||
os.environ["LD_PRELOAD"] = ":".join(p for p in os.environ["LD_PRELOAD"].split(":")
|
||||
if p and "gameoverlayrenderer" not in p)
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
try:
|
||||
@@ -365,6 +373,9 @@ def main():
|
||||
engine = QQmlApplicationEngine()
|
||||
engine.setInitialProperties({"backend": backend, "gamepad": pad,
|
||||
"fullscreen": fullscreen,
|
||||
# Started from Steam: its keys are a Steam
|
||||
# Controller's buttons (desktop layout).
|
||||
"fromSteam": bool(os.environ.get("SteamGameId")),
|
||||
"iconUrl": QUrl.fromLocalFile(os.path.realpath(icon)).toString()})
|
||||
engine.load(os.path.join(HERE, "qml", "Main.qml"))
|
||||
if not engine.rootObjects():
|
||||
|
||||
Reference in new issue
Block a user