summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAAGaming <aagaming@riseup.net>2026-10-17 20:04:39 -0400
committerGitHub <noreply@github.com>2026-10-17 20:04:39 -0400
commit636b86cf3291e3934a1dc0dd57afb5eb7056a431 (patch)
tree854301c45c6ea1fbee28dd0a1399ed55fdb113f2
parent410a141cf3c5d0192e4a1a07d7d10be298ab02f4 (diff)
downloaddecky-loader-636b86cf3291e3934a1dc0dd57afb5eb7056a431.tar.gz
decky-loader-636b86cf3291e3934a1dc0dd57afb5eb7056a431.zip
feat(loader): support ARM Linux, experimental builds for every platform that exists (#990)
Co-authored-by: Gianni Spadoni <me@gio.blue> Co-authored-by: virtudude <virtudude@protonmail.com> Signed-off-by: Gianni Spadoni <me@gio.blue>
-rw-r--r--.github/workflows/build-linux-arm64.yml14
-rw-r--r--.github/workflows/build-linux-x86_64.yml14
-rw-r--r--.github/workflows/build-mac.yml14
-rw-r--r--.github/workflows/build-win-arm64.yml19
-rw-r--r--.github/workflows/build-win-x86_64.yml19
-rw-r--r--.github/workflows/build-win.yml67
-rw-r--r--.github/workflows/build.yml67
-rw-r--r--.github/workflows/edit-check.yml4
-rw-r--r--.github/workflows/lint.yml7
-rw-r--r--.github/workflows/release.yml83
-rw-r--r--.github/workflows/typecheck.yml16
-rw-r--r--backend/decky_loader/injector.py4
-rw-r--r--backend/decky_loader/localplatform/localplatform.py3
-rw-r--r--backend/decky_loader/plugin/sandboxed_plugin.py3
-rw-r--r--backend/decky_loader/updater.py106
-rw-r--r--backend/pyinstaller.spec27
-rw-r--r--frontend/src/components/settings/pages/general/index.tsx4
-rw-r--r--frontend/src/updater.ts2
18 files changed, 322 insertions, 151 deletions
diff --git a/.github/workflows/build-linux-arm64.yml b/.github/workflows/build-linux-arm64.yml
new file mode 100644
index 00000000..fdf84c7e
--- /dev/null
+++ b/.github/workflows/build-linux-arm64.yml
@@ -0,0 +1,14 @@
+name: Builder (Linux, arm64)
+
+on:
+ push:
+ pull_request:
+ workflow_call:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yml
+ with:
+ arch: arm64
+ os: Linux
+ artifact_name: PluginLoader-arm64 \ No newline at end of file
diff --git a/.github/workflows/build-linux-x86_64.yml b/.github/workflows/build-linux-x86_64.yml
new file mode 100644
index 00000000..9aa05b52
--- /dev/null
+++ b/.github/workflows/build-linux-x86_64.yml
@@ -0,0 +1,14 @@
+name: Builder (Linux, x86_64)
+
+on:
+ push:
+ pull_request:
+ workflow_call:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yml
+ with:
+ arch: x86_64
+ os: Linux
+ artifact_name: PluginLoader \ No newline at end of file
diff --git a/.github/workflows/build-mac.yml b/.github/workflows/build-mac.yml
new file mode 100644
index 00000000..3daf1132
--- /dev/null
+++ b/.github/workflows/build-mac.yml
@@ -0,0 +1,14 @@
+name: Builder (macOS)
+
+on:
+ push:
+ pull_request:
+ workflow_call:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yml
+ with:
+ arch: arm64
+ os: macOS
+ artifact_name: PluginLoader-mac \ No newline at end of file
diff --git a/.github/workflows/build-win-arm64.yml b/.github/workflows/build-win-arm64.yml
new file mode 100644
index 00000000..7274f235
--- /dev/null
+++ b/.github/workflows/build-win-arm64.yml
@@ -0,0 +1,19 @@
+name: Builder (Windows, arm64)
+
+on:
+ push:
+ pull_request:
+ workflow_call:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yml
+ strategy:
+ matrix:
+ noconsole: [false, true]
+ with:
+ os: Windows
+ arch: arm64
+ artifact_name: PluginLoader-win-arm64${{ matrix.noconsole == true && '-noconsole' || '' }}
+ binary_name: PluginLoader.exe
+ noconsole: ${{ matrix.noconsole }} \ No newline at end of file
diff --git a/.github/workflows/build-win-x86_64.yml b/.github/workflows/build-win-x86_64.yml
new file mode 100644
index 00000000..fa0afb14
--- /dev/null
+++ b/.github/workflows/build-win-x86_64.yml
@@ -0,0 +1,19 @@
+name: Builder (Windows, x86_64)
+
+on:
+ push:
+ pull_request:
+ workflow_call:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yml
+ strategy:
+ matrix:
+ noconsole: [false, true]
+ with:
+ os: Windows
+ arch: x86_64
+ artifact_name: PluginLoader-win${{ matrix.noconsole == true && '-noconsole' || '' }}
+ binary_name: PluginLoader.exe
+ noconsole: ${{ matrix.noconsole }} \ No newline at end of file
diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml
deleted file mode 100644
index 6cf504c4..00000000
--- a/.github/workflows/build-win.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Builder Win
-
-on:
- push:
- pull_request:
-
-permissions:
- contents: write
-
-jobs:
- build-win:
- name: Build PluginLoader for Win
- runs-on: windows-2022
-
- steps:
- - name: Checkout 🧰
- uses: actions/checkout@v4
-
- - name: Set up NodeJS 20 💎
- uses: actions/setup-node@v4
- with:
- node-version: 20
-
- - name: Set up Python 3.11.7 🐍
- uses: actions/setup-python@v5
- with:
- python-version: "3.11.7"
-
- - name: Install Poetry
- uses: snok/install-poetry@v1
- with:
- virtualenvs-create: false
-
- - name: Install Python dependencies ⬇️
- working-directory: ./backend
- run: |
- C:\Users\runneradmin\.local\bin\poetry self add "poetry-dynamic-versioning[plugin]"
- C:\Users\runneradmin\.local\bin\poetry install --no-interaction
-
- - name: Install JS dependencies ⬇️
- working-directory: ./frontend
- run: |
- npm i -g pnpm
- pnpm i --frozen-lockfile
-
- - name: Build JS Frontend 🛠️
- working-directory: ./frontend
- run: pnpm run build
-
- - name: Build Python Backend 🛠️
- working-directory: ./backend
- run: |
- C:\Users\runneradmin\.local\bin\poetry dynamic-versioning
- C:\Users\runneradmin\.local\bin\poetry run pyinstaller pyinstaller.spec
-
- - name: Build Python Backend (noconsole) 🛠️
- working-directory: ./backend
- run: $env:DECKY_NOCONSOLE = 1; C:\Users\runneradmin\.local\bin\poetry run pyinstaller pyinstaller.spec
-
- - name: Upload package artifact ⬆️
- uses: actions/upload-artifact@v4
- with:
- name: PluginLoader Win
- path: |
- ./backend/dist/PluginLoader.exe
- ./backend/dist/PluginLoader_noconsole.exe
-
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 46a106a1..a6a56f39 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,32 +1,59 @@
name: Builder
on:
- push:
- pull_request:
workflow_call:
- # schedule:
- # - cron: '0 13 * * *' # run at 1 PM UTC
+ inputs:
+ arch:
+ description: Architecture to build for (x86_64, arm64)
+ type: string
+ default: x86_64
+ os:
+ description: OS to build for (Linux, macOS, Windows)
+ type: string
+ default: Linux
+ artifact_name:
+ description: Name of the uploaded artifact
+ type: string
+ default: PluginLoader
+ binary_name:
+ description: Name of the PluginLoader binary
+ type: string
+ default: PluginLoader
+ noconsole:
+ description: If the windows build is no-console
+ type: boolean
+ default: false
jobs:
build:
- name: Build PluginLoader
- runs-on: ubuntu-22.04
+ name: Build ${{ inputs.artifact_name }} (${{ inputs.os }} ${{ inputs.arch }})
+ # No support for mac x86, sorry
+ runs-on: ${{
+ (inputs.os == 'Linux' && inputs.arch == 'arm64' && 'ubuntu-26.04-arm') ||
+ (inputs.os == 'Linux' && inputs.arch == 'x86_64' && 'ubuntu-26.04') ||
+ (inputs.os == 'macOS' && inputs.arch == 'arm64' && 'xcode-27') ||
+ (inputs.os == 'Windows' && inputs.arch == 'arm64' && 'windows-11-arm') ||
+ (inputs.os == 'Windows' && inputs.arch == 'x86_64' && 'windows-2025')
+ }}
+ defaults:
+ run:
+ shell: bash
steps:
- name: Checkout 🧰
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
with:
fetch-depth: 0
- - name: Set up NodeJS 20 💎
- uses: actions/setup-node@v4
+ - name: Set up NodeJS 26 💎
+ uses: actions/setup-node@v7
with:
- node-version: 20
+ node-version: 26
- - name: Set up Python 3.11.7 🐍
- uses: actions/setup-python@v5
+ - name: Set up Python 3.14.8 🐍
+ uses: actions/setup-python@v7
with:
- python-version: "3.11.7"
+ python-version: "3.14.8"
- name: Install Poetry
uses: snok/install-poetry@v1
@@ -53,17 +80,19 @@ jobs:
working-directory: ./backend
run: |
poetry dynamic-versioning
- pyinstaller pyinstaller.spec
+ DECKY_REPO="${{ github.repository }}" DECKY_BRANCH="${{ github.event.number && format('PR{0}/{1}', github.event.number, github.head_ref) || github.ref_name }}" ${{ inputs.noconsole == true && 'DECKY_NOCONSOLE=1 ' || '' }}pyinstaller pyinstaller.spec
+ echo Builds:
+ ls ./dist
- name: Upload package artifact ⬆️
if: ${{ !env.ACT }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
- name: PluginLoader
- path: ./backend/dist/PluginLoader
+ name: ${{ inputs.artifact_name }}
+ path: ./backend/dist/${{ inputs.binary_name }}
- name: Download package artifact locally
if: ${{ env.ACT }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v7
with:
- path: ./backend/dist/PluginLoader
+ path: ./backend/dist/${{ inputs.binary_name }}
diff --git a/.github/workflows/edit-check.yml b/.github/workflows/edit-check.yml
index 519d9e3e..95865a3c 100644
--- a/.github/workflows/edit-check.yml
+++ b/.github/workflows/edit-check.yml
@@ -7,14 +7,14 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
- uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445
+ uses: actions/checkout@v7
with:
ref: ${{ github.sha }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Get changed files
id: changed-files
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 3ff65531..cda19c28 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -10,7 +10,12 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v4 # Check out the repository first.
+ - uses: actions/checkout@v7 # Check out the repository first.
+
+ - name: Set up NodeJS 26 💎
+ uses: actions/setup-node@v7
+ with:
+ node-version: 26
- name: Install TypeScript dependencies
working-directory: frontend
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ac1637e4..9f69a294 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -27,13 +27,13 @@ permissions:
jobs:
create_tag:
name: Tag a new version of the package
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-26.04
outputs:
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
steps:
- name: Checkout 🧰
- uses: actions/checkout@v4
+ uses: actions/checkout@v7
- name: Install semver-tool asdf
uses: asdf-vm/actions/install@v4
@@ -42,11 +42,11 @@ jobs:
semver 3.4.0
- name: Get latest release
- uses: rez0n/actions-github-release@main
+ uses: rez0n/actions-github-release@v2.1
id: latest_release
with:
token: ${{ secrets.GITHUB_TOKEN }}
- repository: "SteamDeckHomebrew/decky-loader"
+ repository: ${{ github.repository }}
type: "nodraft"
- name: Prepare tag ⚙️
@@ -115,42 +115,89 @@ jobs:
echo tag_name=v$OUT >> $GITHUB_OUTPUT
- name: Push tag 📤
- uses: rickstaa/action-create-tag@v1.3.2
+ uses: rickstaa/action-create-tag@v1.7.2
if: ${{ steps.ready_tag.outputs.tag_name && !env.ACT }}
with:
tag: ${{ steps.ready_tag.outputs.tag_name }}
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
- build:
- name: Build tagged artifact
- uses: ./.github/workflows/build.yml
+ build_linux_x86_64:
+ name: Build tagged artifact (Linux, x86_64)
+ uses: ./.github/workflows/build-linux-x86_64.yml
+ needs: [create_tag]
+
+ build_linux_arm64:
+ name: Build tagged artifact (Linux, arm64)
+ uses: ./.github/workflows/build-linux-arm64.yml
+ needs: [create_tag]
+
+ build_win_x86_64:
+ name: Build tagged artifact (Windows, arm64)
+ uses: ./.github/workflows/build-win-arm64.yml
+ needs: [create_tag]
+
+ build_win_arm64:
+ name: Build tagged artifact (Windows, x86_64)
+ uses: ./.github/workflows/build-win-x86_64.yml
+ needs: [create_tag]
+
+ build_macos:
+ name: Build tagged artifact (macOS)
+ uses: ./.github/workflows/build-mac.yml
needs: [create_tag]
release:
- name: Release tagged artifact
- runs-on: ubuntu-22.04
- needs: [create_tag, build]
+ name: Release tagged artifacts
+ runs-on: ubuntu-26.04
+ needs: [create_tag, build_linux_x86_64, build_linux_arm64, build_win_x86_64, build_win_arm64, build_macos]
steps:
- - name: Fetch package artifact ⬇️
- uses: actions/download-artifact@v4
+ - name: Fetch package artifacts ⬇️
+ uses: actions/download-artifact@v8
with:
- name: PluginLoader
+ path: ./temp_artifacts
+
+ - name: Name release assets 🛠️
+ run: |
+ ARTIFACTS=("default" "arm64" "win" "win-noconsole" "win-arm64" "win-arm64-noconsole" "mac")
+
+ for artifact in "${ARTIFACTS[@]}"; do
+ echo "naming $artifact"
+
+ if [ "$artifact" = "default" ]; then
+ suffix=""
+ else
+ suffix="-$artifact"
+ fi
+
+ # stupid windows
+ if [[ "$artifact" == win* ]]; then
+ ext=".exe"
+ else
+ ext=""
+ fi
+
+ source="./temp_artifacts/PluginLoader$suffix"
+ dest="./PluginLoader$suffix$ext"
+ mv "$source"/PluginLoader$ext "$dest"
+ done
- name: Pre-release 📦
if: github.event.inputs.release == 'prerelease'
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v3
with:
name: Prerelease ${{ needs.create_tag.outputs.tag_name }}
tag_name: ${{ needs.create_tag.outputs.tag_name }}
- files: ./PluginLoader
+ files: |
+ ./PluginLoade*
prerelease: true
generate_release_notes: true
- name: Release 📦
if: github.event.inputs.release == 'release'
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v3
with:
name: Release ${{ needs.create_tag.outputs.tag_name }}
tag_name: ${{ needs.create_tag.outputs.tag_name }}
- files: ./PluginLoader
+ files: |
+ ./PluginLoade*
prerelease: false
generate_release_notes: true
diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml
index 86690aa0..113b3d30 100644
--- a/.github/workflows/typecheck.yml
+++ b/.github/workflows/typecheck.yml
@@ -10,17 +10,17 @@ jobs:
runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v4 # Check out the repository first.
+ - uses: actions/checkout@v7 # Check out the repository first.
- - name: Set up NodeJS 20 💎
- uses: actions/setup-node@v4
+ - name: Set up NodeJS 26 💎
+ uses: actions/setup-node@v7
with:
- node-version: 20
+ node-version: 26
- - name: Set up Python 3.11.7 🐍
- uses: actions/setup-python@v5
+ - name: Set up Python 3.14.8 🐍
+ uses: actions/setup-python@v7
with:
- python-version: "3.11.7"
+ python-version: "3.14.8"
- name: Install Poetry
uses: snok/install-poetry@v1
@@ -40,7 +40,7 @@ jobs:
- name: Run pyright (Python)
uses: jakebailey/pyright-action@v1
with:
- python-version: "3.10.6"
+ python-version: "3.14.8"
no-comments: true
working-directory: backend
diff --git a/backend/decky_loader/injector.py b/backend/decky_loader/injector.py
index 39b5b15d..ded58193 100644
--- a/backend/decky_loader/injector.py
+++ b/backend/decky_loader/injector.py
@@ -4,7 +4,7 @@ from asyncio import sleep
from logging import getLogger
from typing import Any, Callable, List, TypedDict, Dict
-from aiohttp import ClientSession
+from aiohttp import ClientSession, ClientTimeout
from aiohttp.client_exceptions import ClientConnectorError, ClientOSError
from asyncio.exceptions import TimeoutError
import uuid
@@ -374,7 +374,7 @@ async def get_tabs() -> List[Tab]:
while True:
try:
async with ClientSession() as web:
- res = await web.get(f"{BASE_ADDRESS}/json", timeout=3)
+ res = await web.get(f"{BASE_ADDRESS}/json", timeout=ClientTimeout(total=3))
except ClientConnectorError:
if not na:
logger.debug("Steam isn't available yet. Wait for a moment...")
diff --git a/backend/decky_loader/localplatform/localplatform.py b/backend/decky_loader/localplatform/localplatform.py
index 6cb08982..e7760315 100644
--- a/backend/decky_loader/localplatform/localplatform.py
+++ b/backend/decky_loader/localplatform/localplatform.py
@@ -4,6 +4,9 @@ ON_WINDOWS = platform.system() == "Windows"
ON_MAC = platform.system() == "Darwin"
ON_LINUX = not ON_WINDOWS and not ON_MAC
+# macOS reports arm64, Linux reports aarch64
+ON_ARM64 = platform.machine() == "arm64" or platform.machine() == "aarch64"
+
if ON_WINDOWS:
from .localplatformwin import *
from . import localplatformwin as localplatform
diff --git a/backend/decky_loader/plugin/sandboxed_plugin.py b/backend/decky_loader/plugin/sandboxed_plugin.py
index 675eb464..668e1d25 100644
--- a/backend/decky_loader/plugin/sandboxed_plugin.py
+++ b/backend/decky_loader/plugin/sandboxed_plugin.py
@@ -211,5 +211,4 @@ class SandboxedPlugin:
except Exception as e:
d["res"] = str(e)
d["success"] = False
- finally:
- return dumps(d, ensure_ascii=False)
+ return dumps(d, ensure_ascii=False)
diff --git a/backend/decky_loader/updater.py b/backend/decky_loader/updater.py
index 3fdfcc04..0a9356b6 100644
--- a/backend/decky_loader/updater.py
+++ b/backend/decky_loader/updater.py
@@ -1,12 +1,11 @@
from __future__ import annotations
from asyncio import sleep
from logging import getLogger
-import os
-from os import getcwd, path, remove
+from os import getcwd, getenv, path, remove, mkdir
from typing import TYPE_CHECKING, List, TypedDict
if TYPE_CHECKING:
from .main import PluginManager
-from .localplatform.localplatform import chmod, service_restart, service_stop, ON_LINUX, ON_WINDOWS, get_keep_systemd_service, get_selinux
+from .localplatform.localplatform import chmod, service_restart, service_stop, ON_LINUX, ON_WINDOWS, ON_MAC, ON_ARM64, get_keep_systemd_service, get_selinux
import shutil
from typing import List, TYPE_CHECKING, TypedDict
import zipfile
@@ -42,6 +41,8 @@ class Updater:
self.remoteVer: RemoteVer | None = None
self.allRemoteVers: List[RemoteVer] = []
self.localVer = helpers.get_loader_version()
+ self.repo = getenv('DECKY_REPO', 'SteamDeckHomebrew/decky-loader')
+ self.git_branch = getenv('DECKY_BRANCH', 'dirty')
try:
self.currentBranch = self.get_branch(self.context.settings)
@@ -50,13 +51,13 @@ class Updater:
logger.error("Current branch could not be determined, defaulting to \"Stable\"")
if context:
- context.ws.add_route("updater/get_version_info", self.get_version_info);
- context.ws.add_route("updater/check_for_updates", self.check_for_updates);
- context.ws.add_route("updater/do_restart", self.do_restart);
- context.ws.add_route("updater/do_shutdown", self.do_shutdown);
- context.ws.add_route("updater/do_update", self.do_update);
- context.ws.add_route("updater/get_testing_versions", self.get_testing_versions);
- context.ws.add_route("updater/download_testing_version", self.download_testing_version);
+ context.ws.add_route("updater/get_version_info", self.get_version_info)
+ context.ws.add_route("updater/check_for_updates", self.check_for_updates)
+ context.ws.add_route("updater/do_restart", self.do_restart)
+ context.ws.add_route("updater/do_shutdown", self.do_shutdown)
+ context.ws.add_route("updater/do_update", self.do_update)
+ context.ws.add_route("updater/get_testing_versions", self.get_testing_versions)
+ context.ws.add_route("updater/download_testing_version", self.download_testing_version)
context.loop.create_task(self.version_reloader())
def get_branch(self, manager: SettingsManager):
@@ -83,17 +84,19 @@ class Updater:
branch = self.get_branch(self.context.settings)
match branch:
case 0:
- url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-release.service"
+ url = f"https://raw.githubusercontent.com/{self.repo}/{self.git_branch}/dist/plugin_loader-release.service"
case 1 | 2:
- url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service"
+ url = f"https://raw.githubusercontent.com/{self.repo}/{self.git_branch}/dist/plugin_loader-prerelease.service"
case _:
logger.error("You have an invalid branch set... Defaulting to prerelease service, please send the logs to the devs!")
- url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service"
+ url = f"https://raw.githubusercontent.com/{self.repo}/{self.git_branch}/dist/plugin_loader-prerelease.service"
return str(url)
async def get_version_info(self):
return {
"current": self.localVer,
+ "repo": self.repo,
+ "gitBranch": self.git_branch,
"remote": self.remoteVer,
"all": self.allRemoteVers,
"updatable": self.localVer != "unknown" and self.localVer != "dev"
@@ -103,7 +106,7 @@ class Updater:
logger.debug("checking for updates")
selectedBranch = self.get_branch(self.context.settings)
async with ClientSession() as web:
- async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
+ async with web.request("GET", f"https://api.github.com/repos/{self.repo}/releases", headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
remoteVersions: List[RemoteVer] = await res.json()
if selectedBranch == 0:
logger.debug("release type: release")
@@ -138,12 +141,48 @@ class Updater:
pass
await sleep(60 * 60 * 6) # 6 hours
+ def get_remote_binary_name(self, with_extension: bool = True):
+ """
+ The binaries on GitHub contain an extra part in their name to denote their architecture
+
+ For example:
+ Windows: PluginLoader.exe
+ Linux (x86_64): PluginLoader
+ Linux (arm64): PluginLoader-arm64
+ """
+ binary_name = "PluginLoader"
+
+ if ON_ARM64 and ON_LINUX:
+ binary_name += "-arm64"
+
+ if ON_WINDOWS:
+ binary_name += "-win"
+ if ON_ARM64:
+ binary_name += "-arm64"
+ if bool(getenv("DECKY_NOCONSOLE", "0")):
+ binary_name += "-noconsole"
+ if with_extension:
+ binary_name += ".exe"
+
+ if ON_MAC:
+ binary_name += "-mac"
+
+ return binary_name
+
+ def get_local_binary_name(self):
+ binary_name = "PluginLoader"
+
+ if ON_WINDOWS:
+ binary_name += ".exe"
+
+ return binary_name
+
async def download_decky_binary(self, download_url: str, version: str, is_zip: bool = False, size_in_bytes: int | None = None):
- download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
+ download_filename = self.get_local_binary_name()
download_temp_filename = download_filename + ".new"
if size_in_bytes == None:
- size_in_bytes = 26214400 # 25MiB, a reasonable overestimate (19.6MiB as of 2024/02/25)
+ size_in_bytes = 35000000 # 35MiB, a reasonable overestimate (30.00 MiB as of 2026/10/05)
async with ClientSession() as web:
logger.debug("Downloading binary")
@@ -196,10 +235,10 @@ class Updater:
version = self.remoteVer["tag_name"]
download_url = None
size_in_bytes = None
- download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe"
+ remote_binary_name = self.get_remote_binary_name()
for x in self.remoteVer["assets"]:
- if x["name"] == download_filename:
+ if x["name"] == remote_binary_name:
download_url = x["browser_download_url"]
size_in_bytes = x["size"]
break
@@ -233,8 +272,8 @@ class Updater:
logger.debug("Saved service file")
logger.debug("Copying service file over current file.")
shutil.copy(service_file_path, "/etc/systemd/system/plugin_loader.service")
- if not os.path.exists(path.join(getcwd(), ".systemd")):
- os.mkdir(path.join(getcwd(), ".systemd"))
+ if not path.exists(path.join(getcwd(), ".systemd")):
+ mkdir(path.join(getcwd(), ".systemd"))
shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service")
await self.download_decky_binary(download_url, version, size_in_bytes=size_in_bytes)
@@ -249,7 +288,7 @@ class Updater:
async def get_testing_versions(self) -> List[TestingVersion]:
result: List[TestingVersion] = []
async with ClientSession() as web:
- async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/pulls",
+ async with web.request("GET", f"https://api.github.com/repos/{self.repo}/pulls",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'state':'open'}, ssl=helpers.get_ssl_context()) as res:
open_prs = await res.json()
for pr in open_prs:
@@ -265,27 +304,42 @@ class Updater:
down_id = ''
#Get all the associated workflow run for the given sha_id code hash
async with ClientSession() as web:
- async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs",
+ async with web.request("GET", f"https://api.github.com/repos/{self.repo}/actions/runs",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'head_sha': sha_id}, ssl=helpers.get_ssl_context()) as res:
works = await res.json()
#Iterate over the workflow_run to get the two builds if they exists
for work in works['workflow_runs']:
- if ON_WINDOWS and work['name'] == 'Builder Win':
+ if ON_WINDOWS and ON_ARM64 and work['name'] == 'Builder (Windows, arm64)':
down_id=work['id']
break
- elif ON_LINUX and work['name'] == 'Builder':
+ elif ON_WINDOWS and not ON_ARM64 and work['name'] == 'Builder (Windows, x86_64)':
down_id=work['id']
break
+ elif ON_LINUX and ON_ARM64 and work['name'] == 'Builder (Linux, arm64)':
+ down_id=work['id']
+ break
+ elif ON_LINUX and not ON_ARM64 and work['name'] == 'Builder (Linux, x86_64)':
+ down_id=work['id']
+ break
+ elif ON_MAC and work['name'] == 'Builder (macOS)':
+ down_id=work['id']
+ break
+
if down_id != '':
async with ClientSession() as web:
- async with web.request("GET", f"https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs/{down_id}/artifacts",
+ async with web.request("GET", f"https://api.github.com/repos/{self.repo}/actions/runs/{down_id}/artifacts",
headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
jresp = await res.json()
#If the request found at least one artifact to download...
if int(jresp['total_count']) != 0:
# this assumes that the artifact we want is the first one!
artifact = jresp['artifacts'][0]
- down_link = f"https://nightly.link/SteamDeckHomebrew/decky-loader/actions/artifacts/{artifact['id']}.zip"
+ target_art = self.get_remote_binary_name(False)
+ for art in jresp['artifacts']:
+ if art['name'] == target_art:
+ artifact = art
+ break
+ down_link = f"https://nightly.link/{self.repo}/actions/artifacts/{artifact['id']}.zip"
#Then fetch it and restart itself
await self.download_decky_binary(down_link, f'PR-{pr_id}', is_zip=True, size_in_bytes=artifact.get('size_in_bytes',None))
else:
diff --git a/backend/pyinstaller.spec b/backend/pyinstaller.spec
index 13ac66a0..3ef888f5 100644
--- a/backend/pyinstaller.spec
+++ b/backend/pyinstaller.spec
@@ -3,21 +3,38 @@ from PyInstaller.building.build_main import Analysis
from PyInstaller.building.api import EXE, PYZ
from PyInstaller.utils.hooks import copy_metadata
+noconsole_str = os.getenv('DECKY_NOCONSOLE', '0')
+noconsole = bool(noconsole_str)
+repo = os.getenv('DECKY_REPO', 'SteamDeckHomebrew/decky-loader')
+branch = os.getenv('DECKY_BRANCH', 'main')
+with open('build_config.py', 'w') as f:
+ f.write(f"""
+import os
+os.environ['DECKY_PACKAGED_BUILD'] = '1'
+
+# Build info
+os.environ['DECKY_REPO'] = {repr(repo)}
+os.environ['DECKY_BRANCH'] = {repr(branch)}
+
+# Windows-specific
+os.environ['DECKY_NOCONSOLE'] = {repr(noconsole_str)}
+""")
+
+name = "PluginLoader"
+# if noconsole:
+# name += "_noconsole"
+
a = Analysis(
['main.py'],
datas=[
('decky_loader/locales', 'decky_loader/locales'),
('decky_loader/static', 'decky_loader/static'),
] + copy_metadata('decky_loader'),
+ runtime_hooks=['build_config.py'],
hiddenimports=['logging.handlers', 'sqlite3', 'http.server', 'socketserver', 'configparser', 'decky_plugin', 'decky'],
)
pyz = PYZ(a.pure, a.zipped_data)
-noconsole = bool(os.getenv('DECKY_NOCONSOLE'))
-name = "PluginLoader"
-if noconsole:
- name += "_noconsole"
-
exe = EXE(
pyz,
a.scripts,
diff --git a/frontend/src/components/settings/pages/general/index.tsx b/frontend/src/components/settings/pages/general/index.tsx
index 9bd5616d..401da1d2 100644
--- a/frontend/src/components/settings/pages/general/index.tsx
+++ b/frontend/src/components/settings/pages/general/index.tsx
@@ -57,7 +57,9 @@ export default function GeneralSettings({
<DialogControlsSection>
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.about.header')}</DialogControlsSectionHeader>
<Field label={t('SettingsGeneralIndex.about.decky_version')} focusable={true}>
- <div style={{ color: 'var(--gpSystemLighterGrey)' }}>{versionInfo?.current}</div>
+ <div style={{ color: 'var(--gpSystemLighterGrey)' }}>
+ {versionInfo?.current} ({versionInfo?.repo}#{versionInfo?.gitBranch})
+ </div>
</Field>
</DialogControlsSection>
</DialogBody>
diff --git a/frontend/src/updater.ts b/frontend/src/updater.ts
index a8853b18..7f997b7a 100644
--- a/frontend/src/updater.ts
+++ b/frontend/src/updater.ts
@@ -18,6 +18,8 @@ export interface RemoteVerInfo {
export interface VerInfo {
current: string;
+ repo: string;
+ gitBranch: string;
remote: RemoteVerInfo | null;
all: RemoteVerInfo[] | null;
updatable: boolean;