Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions build_files/30-install-steam-session.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ dnf5 -y install --setopt=install_weak_deps=False \
# armada-gamescope carries ROCKNIX's --use-rotation-shader patch.
dnf5 -y install --setopt=install_weak_deps=False /packages/gamescope/gamescope-[0-9]*.aarch64.rpm

# Odin 3 HDR requires the opt-in composited client-format policy from the
# patched package. Do not publish an image that would silently fall back to
# the KMS-plane-only format set used by ordinary Gamescope sessions.
#
# This build runs inside a nested rootless image environment where executing
# Gamescope can be prohibited even though the installed AArch64 binary is
# valid. Validate the installed package and option marker without launching it.
/bin/bash /ctx/build_files/verify-gamescope-capability.sh /usr/bin/gamescope
printf '%s\n' expose-client-sampleable-formats-v1 \
>/usr/lib/armada/gamescope-hdr-capabilities
chmod 0644 /usr/lib/armada/gamescope-hdr-capabilities

# Patched InputPlumber: dpad signed-axis fix
dnf5 -y install --setopt=install_weak_deps=False /packages/inputplumber/inputplumber-*.rpm

Expand Down Expand Up @@ -51,6 +63,54 @@ sed -i \
's/read -r -t 5 response_x_display response_wl_display/read -r -t 15 response_x_display response_wl_display/' \
/usr/share/gamescope-session-plus/gamescope-session-plus

# Finalize the immutable Odin HDR policy after gamescope-session-plus has read
# both system and user environment.d files. This keeps existing configuration
# behavior on every other device while preventing a stale user override
# from bypassing the production wrapper on the real Odin 3.
python3 - /usr/share/gamescope-session-plus/gamescope-session-plus <<'PY'
from pathlib import Path
import sys

path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
environment_block = '''# Source user configuration from /etc/environment.d and ~/.config/environment.d
set -a
for i in /etc/environment.d/*.conf; do
\t[[ -f "${i}" ]] && . "${i}"
done
for i in "${HOME}"/.config/environment.d/*.conf; do
\t[[ -f "${i}" ]] && . "${i}"
done
set +a
'''
production_block = environment_block + '''
# Armada's immutable helper is silent on other hardware. On the real Odin 3
# it first emits an SDR baseline, then promotes HDR only when the exact DSI-1
# profile and all patched production artifacts are present.
if ! armada_hdr_policy=$(
\t/usr/bin/env -i PATH=/usr/bin:/usr/sbin:/bin:/sbin \\
\t\tXDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-}" \\
\t\t/bin/bash --noprofile --norc -p /usr/libexec/armada/hdr-session-finalize
); then
\tprintf 'Armada HDR: production session finalization failed; refusing Gaming Mode\\n' >&2
\texit 1
fi
if ! builtin eval -- "$armada_hdr_policy"; then
\tprintf 'Armada HDR: invalid production session policy; refusing Gaming Mode\\n' >&2
\texit 1
fi
builtin unset armada_hdr_policy
'''

if text.count(environment_block) != 1:
raise SystemExit(
"ERROR: gamescope-session-plus environment.d block changed; inspect before patching"
)
if "hdr-session-finalize" in text:
raise SystemExit("ERROR: gamescope-session-plus already contains Armada HDR finalization")
path.write_text(text.replace(environment_block, production_block), encoding="utf-8")
PY

dnf5 -y install --setopt=install_weak_deps=False \
erofs-fuse \
erofs-utils \
Expand Down
30 changes: 30 additions & 0 deletions build_files/40-vendor-system-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ set -euxo pipefail
cp -a /ctx/system_files/. /
install -Dpm 0755 /packages/extest/libextest.so /usr/lib/extest/libextest.so

# The source tree carries the qualified EDID as reviewable hexadecimal text. The
# image build materializes the exact 256-byte seed that Gamescope copies into
# the session user's runtime directory before generating its live patched EDID.
python3 - /usr/share/armada/hdr/ayn-odin-3.edid.hex \
/usr/share/armada/hdr/ayn-odin-3.edid.bin <<'PY'
from pathlib import Path
import sys

source = Path(sys.argv[1])
target = Path(sys.argv[2])
try:
payload = bytes.fromhex(source.read_text(encoding="ascii"))
except (OSError, UnicodeError, ValueError) as error:
raise SystemExit(f"ERROR: invalid Odin 3 HDR EDID source: {error}")
if len(payload) != 256:
raise SystemExit(f"ERROR: Odin 3 HDR EDID is {len(payload)} bytes, expected 256")
target.write_bytes(payload)
PY
echo 'a6ee4ff0c7f43723c093ea2575221a52668e7d610c13738274bf0cef61c96695 /usr/share/armada/hdr/ayn-odin-3.edid.bin' \
| sha256sum -c -
chmod 0644 /usr/share/armada/hdr/ayn-odin-3.edid.bin
rm -f /usr/share/armada/hdr/ayn-odin-3.edid.hex

# mkbootimg must be present for on-device /KERNEL rebuilds after OTA.
install -Dpm 0755 /ctx/build_files/vendor/mkbootimg/mkbootimg.py /usr/libexec/armada/mkbootimg.py
install -Dpm 0755 /ctx/build_files/vendor/mkbootimg/gki/generate_gki_certificate.py /usr/libexec/armada/gki/generate_gki_certificate.py
Expand All @@ -15,6 +38,13 @@ EOF
chmod 0755 /usr/libexec/armada/*
chmod 0755 /usr/libexec/os-session-select

test -x /usr/libexec/armada/hdr-session-finalize
test -x /usr/libexec/armada/gamescope-odin3-hdr
test -f /usr/share/gamescope/scripts/00-armada-session-policy.lua
test -f /usr/share/gamescope/scripts/10-armada/ayn.odin3.oled.lua
test "$(cat /usr/lib/armada/gamescope-hdr-capabilities)" = \
expose-client-sampleable-formats-v1

sed -i '/const allPanels/,$d' /usr/share/plasma/layout-templates/org.kde.plasma.desktop.defaultPanel/contents/layout.js
sed -i '$r /usr/share/plasma/shells/org.kde.plasma.desktop/contents/updates/armada-pins.js' /usr/share/plasma/layout-templates/org.kde.plasma.desktop.defaultPanel/contents/layout.js

Expand Down
26 changes: 26 additions & 0 deletions build_files/verify-gamescope-capability.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail

readonly gamescope_binary="${1:-/usr/bin/gamescope}"
readonly required_option='--expose-client-sampleable-formats'

fail() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}

[[ -f "$gamescope_binary" && -x "$gamescope_binary" ]] ||
fail "packaged Gamescope binary is missing or not executable: $gamescope_binary"

if ! package_owner=$(rpm -qf --queryformat '%{NAME}\n' "$gamescope_binary"); then
fail "cannot identify the package owning $gamescope_binary"
fi
[[ "$package_owner" == gamescope ]] ||
fail "unexpected package owns $gamescope_binary: $package_owner"

# Do not execute Gamescope while constructing the image. Nested rootless image
# builds can prohibit execution even when the installed AArch64 binary is valid.
# The production wrapper performs the runtime --help probe on the target device,
# and release validation can verify this exact binary by SHA-256.
LC_ALL=C grep -aFq -- "$required_option" "$gamescope_binary" ||
fail "packaged Gamescope lacks $required_option"
42 changes: 41 additions & 1 deletion decky/armada-control/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
from armada_control.controller import set_controller_type
from armada_control.power import save_power_config
from armada_control.steam import installed_games
from armada_control.system import set_ssh_enabled
from armada_control.system import (
get_auto_hdr_preferences,
get_hdr_runtime_state,
reconcile_auto_hdr,
set_ssh_enabled,
update_auto_hdr_preferences,
)
from armada_control.tweaks import load_compat_applied, save_compat_applied, save_tweaks


Expand All @@ -23,6 +29,40 @@ async def get_config(self):
async def get_installed_games(self):
return await asyncio.to_thread(installed_games)

async def get_hdr_runtime_state(self):
return await asyncio.to_thread(get_hdr_runtime_state)

async def get_auto_hdr_preferences(
self, active_scope="global", active_app_id=None
):
return await asyncio.to_thread(
get_auto_hdr_preferences, active_scope, active_app_id
)

async def update_auto_hdr_preferences(
self,
target_scope,
target_app_id,
active_scope,
active_app_id,
patch,
):
return await asyncio.to_thread(
update_auto_hdr_preferences,
target_scope,
target_app_id,
active_scope,
active_app_id,
patch,
)

async def reconcile_auto_hdr(
self, active_scope="global", active_app_id=None
):
return await asyncio.to_thread(
reconcile_auto_hdr, active_scope, active_app_id
)

async def save_power_config(self, data):
await asyncio.to_thread(save_power_config, data)
return await self.get_config()
Expand Down
2 changes: 2 additions & 0 deletions decky/armada-control/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"type": "module",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
"test": "node --experimental-strip-types --test test/*.test.mjs",
"typecheck": "tsc --noEmit",
"watch": "rollup -c -w"
},
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion decky/armada-control/py_modules/armada_control/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .controller import CONTROLLER_TYPES, controller_type
from .power import factory_power_defaults, parse_power
from .steam import installed_games
from .system import cpu_device_class, os_version, ssh_enabled
from .system import cpu_device_class, hdr_capable, os_version, ssh_enabled
from .tweaks import fex_profile_labels, load_fex_contract, load_tweaks


Expand All @@ -14,6 +14,7 @@ def build_config(include_games=True):
"installedGames": installed_games() if include_games else [],
"fexProfiles": fex_profile_labels(fex_contract),
"cpuDeviceClass": cpu_device_class(),
"hdrCapable": hdr_capable(),
"osVersion": os_version(),
"sshEnabled": ssh_enabled(),
"controllerType": controller_type(),
Expand Down
Loading