mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
Merge branch 'main' into fix/steam-client-download
# Conflicts: # tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-install # tools/tests/test_steam_install.py
This commit is contained in:
commit
2d9d4dbeb0
155 files changed
+6161
-993
No files matched your search
@@ -23,7 +23,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Build APK
|
||||
name: Validate source and build result
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
|
||||
+312
-459
@@ -16,26 +16,29 @@ on:
|
||||
- 'LICENSE'
|
||||
- '.gitignore'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rebuild_preloads:
|
||||
description: Rebuild preloads instead of reusing the approved binary snapshot
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: build-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
PRELOAD_CACHE_VERSION: v6
|
||||
|
||||
jobs:
|
||||
xbox:
|
||||
name: Xbox ARM64 backend
|
||||
source:
|
||||
name: Pin source tree
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
base-sha: ${{ steps.merge.outputs.base-sha }}
|
||||
base-sha: ${{ steps.merge.outputs.base-sha || steps.trusted.outputs.base-sha }}
|
||||
tree-sha: ${{ steps.source.outputs.tree-sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -53,9 +56,46 @@ jobs:
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@users.noreply.github.com"
|
||||
git merge --no-ff --no-edit origin/main
|
||||
- name: Record backend source tree
|
||||
- name: Resolve trusted manifest base
|
||||
id: trusted
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin main
|
||||
echo "base-sha=$(git rev-parse origin/main)" >> "$GITHUB_OUTPUT"
|
||||
- name: Record pinned source tree
|
||||
id: source
|
||||
run: echo "tree-sha=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
xbox:
|
||||
name: Xbox ARM64 backend
|
||||
needs: source
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Merge the pinned APK base
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin "$BUILD_BASE_SHA"
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@users.noreply.github.com"
|
||||
git merge --no-ff --no-edit FETCH_HEAD
|
||||
- name: Verify pinned source tree
|
||||
env:
|
||||
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
|
||||
run: test "$(git rev-parse 'HEAD^{tree}')" = "$SOURCE_TREE"
|
||||
- name: Plan component identities
|
||||
id: plan
|
||||
run: python3 tools/components/prepare.py plan
|
||||
- uses: actions/cache@v4
|
||||
id: xbox-cache
|
||||
with:
|
||||
@@ -63,14 +103,10 @@ jobs:
|
||||
app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
|
||||
app/build/xbox/xodus-droiddeck-source.tar.xz
|
||||
app/build/xbox/runtime/*-source.tar.xz
|
||||
key: xbox-arm64-${{ hashFiles('tools/xbox/**') }}
|
||||
key: xbox-v2-${{ steps.plan.outputs.xbox }}
|
||||
- name: Build Xbox backend
|
||||
if: steps.xbox-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
rustup toolchain install 1.99.0 --profile minimal
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake xz-utils
|
||||
bash tools/xbox/build.sh
|
||||
run: python3 tools/components/prepare.py xbox
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: xbox-arm64
|
||||
@@ -85,9 +121,158 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
components:
|
||||
name: Prepare verified components
|
||||
needs: source
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Merge the pinned APK base
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin "$BUILD_BASE_SHA"
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@users.noreply.github.com"
|
||||
git merge --no-ff --no-edit FETCH_HEAD
|
||||
- name: Verify pinned source tree
|
||||
env:
|
||||
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
|
||||
run: test "$(git rev-parse 'HEAD^{tree}')" = "$SOURCE_TREE"
|
||||
- name: Resolve the pinned Android NDK
|
||||
run: bash tools/ci/resolve-ndk.sh
|
||||
|
||||
- name: Plan component identities
|
||||
id: plan
|
||||
run: python3 tools/components/prepare.py plan
|
||||
- name: Cache remaining Linux runtime assets
|
||||
id: preloads
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/src/main/assets/linuxfs
|
||||
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
|
||||
!app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
|
||||
!app/src/main/assets/linuxfs/i386/libblsession.so
|
||||
!app/src/main/assets/linuxfs/i386/libfakeinput.so
|
||||
!app/src/main/assets/linuxfs/i386/libfaultreport.so
|
||||
!app/src/main/assets/linuxfs/libblaudit.so
|
||||
!app/src/main/assets/linuxfs/libblfastpath.so
|
||||
!app/src/main/assets/linuxfs/libblsession.so
|
||||
!app/src/main/assets/linuxfs/libfakeinput.so
|
||||
!app/src/main/assets/linuxfs/libssbs.so
|
||||
!app/src/main/assets/linuxfs/usr/local/bin/droiddeck-clipboard
|
||||
!app/src/main/assets/linuxfs/x86_64/libblsession.so
|
||||
!app/src/main/assets/linuxfs/x86_64/libfakeinput.so
|
||||
!app/src/main/assets/linuxfs/x86_64/libfaultreport.so
|
||||
!app/src/main/assets/linuxfs/x86_64/libvulkan-thunk.so
|
||||
!app/src/main/assets/linuxfs/x86_64/libwebkitinput.so
|
||||
key: runtime-v9-${{ steps.plan.outputs.runtime }}
|
||||
|
||||
- name: Cache PulseAudio 13.0 source
|
||||
id: pa-src
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: pa13
|
||||
key: pulseaudio-src-${{ steps.plan.outputs.pulseaudio-source }}
|
||||
- name: Cache proot
|
||||
id: proot
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/src/main/jniLibs/arm64-v8a/libproot.so
|
||||
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
||||
key: proot-v2-${{ steps.plan.outputs.proot }}
|
||||
- name: Cache GOG Galaxy companion
|
||||
id: comet
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-comet
|
||||
key: comet-v2-${{ steps.plan.outputs.comet }}
|
||||
|
||||
- name: Restore verified preload snapshots
|
||||
id: component-preloads
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
FORCE_PRELOAD_REBUILD: ${{ inputs.rebuild_preloads || 'false' }}
|
||||
COMPONENT_TRUSTED_BASE: ${{ needs.source.outputs.base-sha }}
|
||||
run: python3 tools/components/prepare.py preload-snapshot
|
||||
|
||||
- name: Prepare required Linux build tools once
|
||||
env:
|
||||
COMPONENTS_TO_BUILD: >-
|
||||
${{ steps.preloads.outputs.cache-hit != 'true' && 'runtime' || '' }}
|
||||
${{ steps.component-preloads.outputs.arm64 != 'true' && 'arm64-preloads' || '' }}
|
||||
${{ steps.comet.outputs.cache-hit != 'true' && 'comet' || '' }}
|
||||
run: python3 tools/components/prepare.py tools
|
||||
|
||||
- name: Build changed ARM64 preloads
|
||||
if: steps.component-preloads.outputs.arm64 != 'true'
|
||||
run: python3 tools/components/prepare.py arm64-preloads
|
||||
|
||||
- name: Prepare remaining Linux runtime assets
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: python3 tools/components/prepare.py runtime
|
||||
|
||||
- name: Fetch the Debian image the preloads build in
|
||||
if: steps.component-preloads.outputs.x86 != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: python3 tools/components/prepare.py debian-image
|
||||
|
||||
- name: Build the x86 preloads FEX guests load (glibc 2.31)
|
||||
if: steps.component-preloads.outputs.x86 != 'true'
|
||||
run: python3 tools/components/prepare.py x86-preloads
|
||||
|
||||
- name: Bundle the droiddeck-esync packs
|
||||
run: python3 tools/components/prepare.py esync
|
||||
|
||||
- name: Fetch PulseAudio 13.0 source
|
||||
if: steps.pa-src.outputs.cache-hit != 'true'
|
||||
run: python3 tools/components/prepare.py pulseaudio-source
|
||||
|
||||
- name: Fetch DirectAudio (tools/directaudio/release.env)
|
||||
run: python3 tools/components/prepare.py directaudio
|
||||
|
||||
- name: Build the AAudio sink into the audio bundle
|
||||
run: python3 tools/components/prepare.py audio
|
||||
|
||||
- name: Build proot
|
||||
if: steps.proot.outputs.cache-hit != 'true'
|
||||
run: python3 tools/components/prepare.py proot
|
||||
|
||||
- name: Check the preloads
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
|
||||
|
||||
- name: Build GOG Galaxy companion
|
||||
if: steps.comet.outputs.cache-hit != 'true'
|
||||
run: python3 tools/components/prepare.py comet
|
||||
|
||||
- name: Prepare graphics driver variants
|
||||
run: python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
- name: Record and pack prepared components
|
||||
run: python3 tools/components/prepare.py pack
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prepared-components
|
||||
path: app/build/prepared-components.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
retention-days: 14
|
||||
|
||||
build:
|
||||
needs: xbox
|
||||
name: Compile and test APK
|
||||
needs: [source, components, xbox]
|
||||
name: Compile APK and Android tests
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
@@ -150,7 +335,7 @@ jobs:
|
||||
- name: Merge onto main, or say what conflicts
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
BUILD_BASE_SHA: ${{ needs.xbox.outputs.base-sha }}
|
||||
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
git config user.name "ci"
|
||||
@@ -208,7 +393,7 @@ jobs:
|
||||
|
||||
- name: Verify backend and APK source match
|
||||
env:
|
||||
XBOX_SOURCE_TREE: ${{ needs.xbox.outputs.tree-sha }}
|
||||
XBOX_SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
|
||||
run: test "$(git rev-parse 'HEAD^{tree}')" = "$XBOX_SOURCE_TREE"
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
@@ -218,13 +403,11 @@ jobs:
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
cache-cleanup: on-success
|
||||
|
||||
- name: Resolve the runner's NDK
|
||||
run: |
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
VERSION=$(sed -n 's/^Pkg.Revision *= *//p' "$NDK/source.properties")
|
||||
echo "NDK_VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
- name: Resolve the pinned Android NDK
|
||||
run: bash tools/ci/resolve-ndk.sh
|
||||
|
||||
- name: Set the APK build label
|
||||
id: build-label
|
||||
@@ -250,264 +433,6 @@ jobs:
|
||||
echo "DROIDDECK_PR_NUMBER=${PR_NUMBER:-0}" >> "$GITHUB_ENV"
|
||||
echo "Build: \`$label\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Cache the preloads
|
||||
id: preloads
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/src/main/assets/linuxfs
|
||||
key: preloads-${{ env.PRELOAD_CACHE_VERSION }}-${{ hashFiles('app/src/main/cpp/fakeinput_steam.cpp', 'app/src/main/cpp/adrenotools/src/hook/kgsl.h', 'tools/quest/*', 'tools/linuxfs/preload/*.c', 'tools/linuxfs/build-x86-preloads.sh', 'tools/linuxfs/fex/*', 'tools/linuxfs/clipboard/*.c', 'tools/linuxfs/ssbs/*.c', 'tools/proot/fastpath/*.c', 'tools/linuxfs/overlay/usr/local/bin/*', 'tools/linuxfs/overlay/usr/bin/**', 'tools/linuxfs/desktop/*', 'tools/gamescope/release.env', 'tools/wlroots/release.env', 'tools/linuxfs/uruntime.env', 'tools/linuxfs/licenses/*', 'tools/mangoapp/*', 'tools/msitools/**', 'tools/comet/**', 'tools/xbox/**', 'tools/linuxfs/debian-image.env', 'tools/vr/**') }}
|
||||
|
||||
- name: Build libfakeinput.so and libblsession.so (glibc aarch64)
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get -o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources \
|
||||
-o Dir::Etc::sourceparts=- -o Acquire::Retries=3 update -qq
|
||||
sudo apt-get install -y -qq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libvulkan1 mesa-vulkan-drivers >/dev/null
|
||||
D=app/src/main/assets/linuxfs
|
||||
mkdir -p "$D"
|
||||
aarch64-linux-gnu-g++ -shared -fPIC -O2 -Wall -Wno-attributes -Wno-nonnull-compare \
|
||||
-pthread -std=c++17 -static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL \
|
||||
-o "$D/libfakeinput.so" app/src/main/cpp/fakeinput_steam.cpp -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libfakeinput.so"
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libblsession.so" tools/linuxfs/preload/*.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblsession.so"
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libblfastpath.so" tools/proot/fastpath/fastpath.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblfastpath.so"
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -nostdlib -ffreestanding -fno-stack-protector \
|
||||
-fno-tree-loop-distribute-patterns -Wl,-z,defs -o "$D/libblaudit.so" tools/proot/fastpath/audit.c
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libblaudit.so"
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -pthread \
|
||||
-o "$D/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/libssbs.so"
|
||||
mkdir -p "$D/usr/local/bin"
|
||||
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$D/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$D/usr/local/bin/droiddeck-clipboard"
|
||||
VK_INCLUDE=$(ls -d "${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1) \
|
||||
tools/vr/build.sh --native "$D/usr/local/lib/droiddeck-vr"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
|
||||
install -Dm644 "$script" "$D/usr/local/bin/$(basename "$script")"
|
||||
done
|
||||
for f in tools/linuxfs/overlay/usr/bin/* tools/linuxfs/overlay/usr/bin/steamos-polkit-helpers/*; do
|
||||
[ -f "$f" ] && install -Dm644 "$f" "$D/${f#tools/linuxfs/overlay/}"
|
||||
done
|
||||
if [ -f tools/gamescope/release.env ]; then
|
||||
. tools/gamescope/release.env
|
||||
curl -fsSL --retry 3 -o gamescope.tzst "https://github.com/${GAMESCOPE_REPO:-${GITHUB_REPOSITORY}}/releases/download/${GAMESCOPE_TAG}/gamescope.tzst"
|
||||
echo "${GAMESCOPE_SHA256} gamescope.tzst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf gamescope.tzst -C "$D"
|
||||
test -f "$D/usr/local/bin/gamescope"
|
||||
rm gamescope.tzst
|
||||
fi
|
||||
. tools/linuxfs/uruntime.env
|
||||
curl -fsSL --retry 3 -o uruntime "https://github.com/VHSgunzo/uruntime/releases/download/${URUNTIME_VERSION}/${URUNTIME_ASSET}"
|
||||
echo "${URUNTIME_SHA256} uruntime" | sha256sum -c -
|
||||
install -Dm644 uruntime "$D/usr/local/lib/droiddeck/uruntime"
|
||||
install -Dm644 tools/linuxfs/licenses/uruntime-LICENSE "$D/usr/local/share/licenses/uruntime/LICENSE"
|
||||
rm uruntime
|
||||
M="$D/usr/local/lib/mangoapp"
|
||||
mkdir -p "$M" mango-pkgs
|
||||
grep -v '^#' tools/mangoapp/packages.txt | while read -r sum url; do
|
||||
[ -n "$url" ] || continue
|
||||
curl -fsSL --retry 3 -o mango-pkgs/pkg.tar.zst "$url"
|
||||
echo "$sum mango-pkgs/pkg.tar.zst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf mango-pkgs/pkg.tar.zst -C mango-pkgs
|
||||
rm mango-pkgs/pkg.tar.zst
|
||||
done
|
||||
install -m644 mango-pkgs/usr/bin/mangoapp "$M/mangoapp"
|
||||
for so in libfmt.so.10 libspdlog.so.1.13 libglfw.so.3 libtraceevent.so.1; do
|
||||
cp -L "mango-pkgs/usr/lib/$so" "$M/$so"
|
||||
done
|
||||
aarch64-linux-gnu-gcc -shared -fPIC -O2 -Wall -Wl,-soname,libtracefs.so.1 \
|
||||
-o "$M/libtracefs.so.1" tools/mangoapp/libtracefs-shim.c
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$M/libtracefs.so.1"
|
||||
install -Dm644 tools/mangoapp/mangoapp "$D/usr/local/bin/mangoapp"
|
||||
rm -rf mango-pkgs
|
||||
ls -l "$M"
|
||||
mkdir -p pw-pkgs
|
||||
grep -v '^#' tools/pipewire/packages.txt | while read -r sum url; do
|
||||
[ -n "$url" ] || continue
|
||||
curl -fsSL --retry 3 -o pw-pkgs/pkg.tar.zst "$url"
|
||||
echo "$sum pw-pkgs/pkg.tar.zst" | sha256sum -c -
|
||||
tar --use-compress-program=unzstd -xf pw-pkgs/pkg.tar.zst -C pw-pkgs
|
||||
rm pw-pkgs/pkg.tar.zst
|
||||
done
|
||||
tools/pipewire/assemble.sh pw-pkgs "$D/usr/local/lib/droiddeck-pipewire.tzst"
|
||||
rm -rf pw-pkgs
|
||||
ls -l "$D/usr/local/lib/droiddeck-pipewire.tzst"
|
||||
T="$D/usr/local/lib/droiddeck-msitools"
|
||||
mkdir -p "$T" msi-pkgs
|
||||
grep -v '^#' tools/msitools/packages.txt | while read -r sum url; do
|
||||
[ -n "$url" ] || continue
|
||||
curl -fsSL --retry 3 -o msi-pkgs/pkg.tar.xz "$url"
|
||||
echo "$sum msi-pkgs/pkg.tar.xz" | sha256sum -c -
|
||||
tar -xJf msi-pkgs/pkg.tar.xz -C msi-pkgs
|
||||
rm msi-pkgs/pkg.tar.xz
|
||||
done
|
||||
install -m644 msi-pkgs/usr/bin/msiinfo "$T/msiinfo"
|
||||
install -m644 msi-pkgs/usr/bin/cabextract "$T/cabextract"
|
||||
install -m644 msi-pkgs/usr/lib/7zip/7z "$T/7z"
|
||||
install -m644 msi-pkgs/usr/lib/7zip/7z.so "$T/7z.so"
|
||||
for so in libmsi-1.0.so.0 libgsf-1.so.114 libgcab-1.0.so.0; do
|
||||
cp -L "msi-pkgs/usr/lib/$so" "$T/$so"
|
||||
done
|
||||
for f in NOTICE GPL-2 GPL-3 LGPL-2.1 7zip-License 7zip-unRarLicense; do
|
||||
install -Dm644 "tools/msitools/licenses/$f" "$D/usr/local/share/licenses/droiddeck-msitools/$f"
|
||||
done
|
||||
rm -rf msi-pkgs
|
||||
ls -l "$T"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop "$D/usr/local/bin/droiddeck-desktop"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-gpu "$D/usr/local/bin/droiddeck-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/droiddeck-desktop-gpu "$D/usr/local/bin/droiddeck-desktop-gpu"
|
||||
install -Dm644 tools/linuxfs/desktop/kwin_wayland_wrapper "$D/usr/local/bin/kwin_wayland_wrapper"
|
||||
install -Dm644 tools/linuxfs/desktop/firefox-droiddeck.js "$D/usr/lib/firefox/defaults/pref/droiddeck.js"
|
||||
ls -lR "$D"
|
||||
|
||||
- name: Fetch the Debian image the preloads build in
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
. tools/linuxfs/debian-image.env
|
||||
image=public.ecr.aws/docker/library/debian:bullseye
|
||||
if gh release download "$DEBIAN_IMAGE_TAG" -R "$GITHUB_REPOSITORY" -p "$DEBIAN_IMAGE_ASSET" -O /tmp/debian-image.tar.zst \
|
||||
&& { [ -z "$DEBIAN_IMAGE_SHA256" ] || echo "$DEBIAN_IMAGE_SHA256 /tmp/debian-image.tar.zst" | sha256sum -c -; }; then
|
||||
zstd -dc /tmp/debian-image.tar.zst | docker load
|
||||
rm -f /tmp/debian-image.tar.zst
|
||||
else
|
||||
echo "::warning::the hosted Debian image could not be fetched; pulling from the registry instead"
|
||||
for attempt in 1 2 3 4 5; do
|
||||
docker pull "$image" && break
|
||||
[ "$attempt" -lt 5 ] || { echo "::error::could not pull $image after $attempt attempts"; exit 1; }
|
||||
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
|
||||
sleep $((attempt * 30))
|
||||
done
|
||||
fi
|
||||
docker image ls "$image"
|
||||
|
||||
- name: Build the x86 preloads FEX guests load (glibc 2.31)
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker run --rm -v "$PWD:/src" -w /src public.ecr.aws/docker/library/debian:bullseye bash -c '
|
||||
set -euo pipefail
|
||||
install_compilers() {
|
||||
apt-get -o Acquire::Check-Valid-Until=false update -qq &&
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends gcc g++ gcc-multilib g++-multilib binutils >/dev/null
|
||||
}
|
||||
printf "deb http://deb.debian.org/debian bullseye main\ndeb http://security.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
|
||||
if ! install_compilers; then
|
||||
printf "deb http://archive.debian.org/debian bullseye main\ndeb http://archive.debian.org/debian-security bullseye-security main\n" > /etc/apt/sources.list
|
||||
install_compilers
|
||||
fi
|
||||
tools/linuxfs/build-x86-preloads.sh app/src/main/assets/linuxfs
|
||||
'
|
||||
sudo chown -R "$(id -u):$(id -g)" app/src/main/assets/linuxfs
|
||||
ls -l app/src/main/assets/linuxfs/x86_64 app/src/main/assets/linuxfs/i386
|
||||
|
||||
- name: Bundle the droiddeck-esync packs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -f tools/droiddeck-esync/release.env ]; then
|
||||
. tools/droiddeck-esync/release.env
|
||||
release_url="https://github.com/${SYNC_BUNDLE_REPO}/releases/download/${SYNC_BUNDLE_TAG}"
|
||||
curl -fsSL --retry 3 -o sync-bundle.tzst "$release_url/$SYNC_BUNDLE_ASSET"
|
||||
echo "$SYNC_BUNDLE_SHA256 sync-bundle.tzst" | sha256sum -c -
|
||||
mkdir -p app/src/main/assets/droiddeck-esync
|
||||
tar --use-compress-program=unzstd -xf sync-bundle.tzst -C app/src/main/assets/droiddeck-esync
|
||||
rm sync-bundle.tzst
|
||||
curl -fsSL --retry 3 -o sync-index.json "$release_url/index.json"
|
||||
revoked=$(python3 -c 'import json, sys; print(" ".join(p["id"] for p in json.load(open(sys.argv[1]))["packs"] if p.get("revoked") is True))' sync-index.json)
|
||||
for id in $revoked; do
|
||||
if [ -e "app/src/main/assets/droiddeck-esync/packs/$id.tzst" ]; then
|
||||
echo "::warning::$SYNC_BUNDLE_ASSET carries revoked pack $id; it is left out of the APK (point tools/droiddeck-esync/release.env at a newer bundle)"
|
||||
rm -f "app/src/main/assets/droiddeck-esync/packs/$id.tzst"
|
||||
fi
|
||||
done
|
||||
rm sync-index.json
|
||||
test -f app/src/main/assets/droiddeck-esync/index.json
|
||||
test -f app/src/main/assets/droiddeck-esync/index.json.sig
|
||||
ls -lR app/src/main/assets/droiddeck-esync
|
||||
fi
|
||||
|
||||
- name: Cache PulseAudio 13.0 source
|
||||
id: pa-src
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: pa13
|
||||
key: pulseaudio-src-13.0
|
||||
- name: Fetch PulseAudio 13.0 source
|
||||
if: steps.pa-src.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSL -o pa.tar.gz https://github.com/pulseaudio/pulseaudio/archive/refs/tags/v13.0.tar.gz
|
||||
mkdir -p pa13 && tar -xzf pa.tar.gz -C pa13 --strip-components=1 && rm pa.tar.gz
|
||||
- name: Fetch DirectAudio (tools/directaudio/release.env)
|
||||
run: tools/directaudio/fetch.sh "$PWD" "$PWD/sink-out"
|
||||
- name: Build the AAudio sink into the audio bundle
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
tools/aaudio-sink/build.sh "$PWD/pa13" "$PWD/sink-out"
|
||||
rm -rf pa-bundle && mkdir -p pa-bundle
|
||||
tar --use-compress-program=unzstd -xf app/src/main/assets/pulseaudio.tzst -C pa-bundle
|
||||
test ! -f pa-bundle/modules/arm64/module-aaudio-sink.so
|
||||
install -m755 sink-out/module-aaudio-sink.so pa-bundle/modules/arm64/module-aaudio-sink.so
|
||||
install -m755 sink-out/module-directaudio-native-sink.so pa-bundle/modules/arm64/module-directaudio-native-sink.so
|
||||
(cd pa-bundle && tar --use-compress-program='zstd -19' -cf ../pulseaudio.tzst .)
|
||||
mkdir -p app/build/prepared-assets
|
||||
mv pulseaudio.tzst app/build/prepared-assets/pulseaudio.tzst
|
||||
tar --use-compress-program=unzstd -tf app/build/prepared-assets/pulseaudio.tzst | grep -c '\.so$'
|
||||
rm -rf pa-bundle sink-out
|
||||
- name: Cache proot
|
||||
id: proot
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/src/main/jniLibs/arm64-v8a/libproot.so
|
||||
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
|
||||
key: proot-${{ env.NDK_VERSION }}-${{ hashFiles('tools/proot/**') }}
|
||||
- name: Build proot
|
||||
if: steps.proot.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
tools/proot/build.sh app/src/main/jniLibs/arm64-v8a
|
||||
|
||||
- name: Check the preloads
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash tools/linuxfs/check-preloads.sh app/src/main/assets/linuxfs
|
||||
|
||||
- name: Check Quest tracking IPC
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
VK_INCLUDE=$(ls -d "$NDK"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1)
|
||||
mkdir -p build/vr/include
|
||||
cp -R "$VK_INCLUDE/vulkan" build/vr/include/
|
||||
if [ -d "$VK_INCLUDE/vk_video" ]; then cp -R "$VK_INCLUDE/vk_video" build/vr/include/; fi
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_tracking.cpp -o /tmp/test-xr-tracking
|
||||
/tmp/test-xr-tracking
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
|
||||
/tmp/test-xr-touch
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
|
||||
/tmp/test-pad-controller-config
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
|
||||
/tmp/test-xr-vsync
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
|
||||
/tmp/test-xr-frames
|
||||
cc -Itools/vr/third_party/wayland -c tools/vr/src/wayland_client_stub.c -o /tmp/test-wayland-stub.o
|
||||
cc -Itools/vr/third_party/wayland -c app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c -o /tmp/test-xdg-shell.o
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers -pthread -Ibuild/vr/include \
|
||||
-Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \
|
||||
tools/vr/tests/test_presenter_shutdown.cpp tools/vr/src/presenter.cpp /tmp/test-wayland-stub.o /tmp/test-xdg-shell.o \
|
||||
-ldl -o /tmp/test-presenter-shutdown
|
||||
timeout 10 /tmp/test-presenter-shutdown
|
||||
|
||||
- name: Rust toolchain for the store engines (aarch64-linux-android)
|
||||
id: rust
|
||||
run: |
|
||||
@@ -525,9 +450,10 @@ jobs:
|
||||
~/.cargo/git/db
|
||||
~/.cargo/bin/cargo-ndk
|
||||
app/src/main/rust/stores/target
|
||||
key: stores-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}
|
||||
key: stores-v2-${{ runner.os }}-${{ runner.arch }}-${{ env.NDK_VERSION }}-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}-${{ hashFiles('app/src/main/rust/stores/src/**', 'app/src/main/rust/stores/Cargo.toml', 'app/src/main/rust/stores/build.rs', 'app/src/main/rust/stores/.cargo/**', 'app/build.gradle') }}
|
||||
restore-keys: |
|
||||
stores-${{ steps.rust.outputs.rustc }}-
|
||||
stores-v2-${{ runner.os }}-${{ runner.arch }}-${{ env.NDK_VERSION }}-${{ steps.rust.outputs.rustc }}-${{ hashFiles('app/src/main/rust/stores/Cargo.lock') }}-
|
||||
stores-v2-${{ runner.os }}-${{ runner.arch }}-${{ env.NDK_VERSION }}-${{ steps.rust.outputs.rustc }}-
|
||||
|
||||
- name: cargo-ndk
|
||||
run: |
|
||||
@@ -545,18 +471,26 @@ jobs:
|
||||
restore-keys: |
|
||||
cxx-${{ env.NDK_VERSION }}-
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: prepared-components
|
||||
path: app/build
|
||||
- name: Verify and unpack prepared components
|
||||
env:
|
||||
SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
|
||||
run: python3 tools/components/prepare.py unpack --tree "$SOURCE_TREE"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: xbox-arm64
|
||||
path: app/src/main/assets/linuxfs/usr/local/lib/droiddeck-xbox
|
||||
|
||||
- name: Build GOG Galaxy companion
|
||||
if: steps.preloads.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
rustup toolchain install 1.90.0 --profile minimal
|
||||
rustup target add --toolchain 1.90.0 aarch64-unknown-linux-gnu
|
||||
RUSTUP_TOOLCHAIN=1.90.0 bash tools/comet/build.sh
|
||||
|
||||
- name: Cache checksum-pinned pack inputs
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/droiddeck-pack-inputs
|
||||
key: pack-inputs-v1-${{ hashFiles('tools/packs/build.sh', 'tools/packs/fetch-verified.sh', 'tools/packs/tracking/release.env') }}
|
||||
restore-keys: pack-inputs-v1-
|
||||
- name: Build the add-on packs
|
||||
run: bash tools/packs/build.sh app/build/packs
|
||||
|
||||
@@ -567,7 +501,6 @@ jobs:
|
||||
SGDB_API_KEY: ${{ secrets.SGDB_API_KEY }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
python3 tools/quest/build-driver.py "$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
python3 tools/release/runtime_inputs.py record
|
||||
./gradlew assembleRelease --console=plain -PndkVersion="$NDK_VERSION" 2>&1 | tee /tmp/build.log
|
||||
python3 tools/release/check_session_assets.py app/build/outputs/apk/release/app-release.apk
|
||||
@@ -598,16 +531,11 @@ jobs:
|
||||
path: app/src/main/rust/stores/Cargo.lock
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Install Python test dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq python3-yaml gcc-aarch64-linux-gnu qemu-user
|
||||
|
||||
- name: Steam import and game environment tests
|
||||
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
|
||||
|
||||
- name: Unit tests
|
||||
id: unit-tests
|
||||
env:
|
||||
DROIDDECK_BUILD_LABEL: ${{ steps.build-label.outputs.label }}
|
||||
SGDB_API_KEY: ${{ secrets.SGDB_API_KEY }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
mkdir -p app/build
|
||||
@@ -683,86 +611,84 @@ jobs:
|
||||
TEST_DIAGNOSTICS_URL: ${{ steps.unit-test-diagnostics.outputs.artifact-url }}
|
||||
APK_OUTCOME: ${{ steps.apk-upload.outcome }}
|
||||
SAME_REPO: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
run: bash tools/release/report-build.sh
|
||||
|
||||
python-tests:
|
||||
name: Python and native regression tests
|
||||
needs: source
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Merge the pinned APK base
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
BUILD_BASE_SHA: ${{ needs.source.outputs.base-sha }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
MARK="<!-- droiddeck-pr-build -->"
|
||||
{
|
||||
echo "$MARK"
|
||||
echo "## Build of this pull request"
|
||||
echo
|
||||
[ -f /tmp/pr-merge.md ] && cat /tmp/pr-merge.md
|
||||
echo
|
||||
case "$BUILD_OUTCOME:$UNIT_TEST_OUTCOME:$APK_OUTCOME" in
|
||||
success:success:success)
|
||||
echo "### Build passed"
|
||||
echo
|
||||
echo "The \`droiddeck-apk\` artifact is [on this run]($RUN_URL), signed with the public test key."
|
||||
echo
|
||||
if [ "$SAME_REPO" = true ]; then
|
||||
echo "A release-signed tester build that installs over Stable or Preview is being published now and will appear in DroidDeck's **Test builds** list. Label the PR \`no-test-build\` to skip that."
|
||||
else
|
||||
echo "**Maintainer:** add the \`publish-test\` label to publish a release-signed tester build that installs over Stable or Preview and appears in DroidDeck's **Test builds** list. The label is removed automatically after publishing."
|
||||
fi
|
||||
echo
|
||||
echo "Fallback: [open **PR Release Publish**]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/workflows/pr-release-publish.yml) on \`main\` and enter PR \`$PR\`."
|
||||
;;
|
||||
success:success:*)
|
||||
echo "### APK publication incomplete"
|
||||
echo
|
||||
echo "Compilation and unit tests passed, but APK publication did not complete. See [the run]($RUN_URL)."
|
||||
;;
|
||||
success:failure:*)
|
||||
echo "### ERROR: Unit tests failed"
|
||||
echo
|
||||
echo "The APK compiled, but unit tests failed. No installable artifact was published."
|
||||
echo
|
||||
if [ -n "$TEST_DIAGNOSTICS_URL" ]; then
|
||||
echo "The [unit-test-results]($TEST_DIAGNOSTICS_URL) artifact contains the test log and any generated reports."
|
||||
else
|
||||
echo "The test diagnostics upload did not complete. See [the test log]($RUN_URL)."
|
||||
fi
|
||||
;;
|
||||
success:*)
|
||||
echo "### Build incomplete"
|
||||
echo
|
||||
echo "The APK compiled, but unit tests did not complete successfully. See [the run]($RUN_URL)."
|
||||
;;
|
||||
failure:*)
|
||||
echo "### ERROR: Build failed"
|
||||
echo
|
||||
echo "The compiler's errors ([full log]($RUN_URL)):"
|
||||
echo
|
||||
echo '```'
|
||||
{ grep -E '^e: |^w: .*error|error:|CMake Error|fatal error|ninja: error|FAILED|What went wrong|Execution failed for task|Unresolved reference|Type mismatch|Unresolved' /tmp/build.log 2>/dev/null \
|
||||
| sed -e 's#file:///home/runner/work/[^/]*/[^/]*/##' -e 's#/home/runner/work/[^/]*/[^/]*/##' \
|
||||
| grep -v '^w: ' | awk '!seen[$0]++' | head -80; } || true
|
||||
echo '```'
|
||||
echo
|
||||
echo "<details><summary>What went wrong (Gradle)</summary>"
|
||||
echo
|
||||
echo '```'
|
||||
awk '/^\* What went wrong/{p=1} /^\* Try:/{p=0} p{print}' /tmp/build.log 2>/dev/null | head -60
|
||||
echo '```'
|
||||
echo "</details>"
|
||||
;;
|
||||
skipped:*|cancelled:*)
|
||||
echo "### Not built"
|
||||
echo
|
||||
echo "The build did not run (see above, or [the run]($RUN_URL))."
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
} > /tmp/pr-report.md
|
||||
cat /tmp/pr-report.md >> "$GITHUB_STEP_SUMMARY"
|
||||
ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" --paginate --jq ".[] | select(.body | startswith(\"$MARK\")) | .id" | head -1 || true)
|
||||
if [ -n "$ID" ]; then
|
||||
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${ID}" -F body=@/tmp/pr-report.md >/dev/null
|
||||
else
|
||||
gh api -X POST "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" -F body=@/tmp/pr-report.md >/dev/null
|
||||
fi
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin "$BUILD_BASE_SHA"
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@users.noreply.github.com"
|
||||
git merge --no-ff --no-edit FETCH_HEAD
|
||||
- name: Verify backend and test source match
|
||||
env:
|
||||
XBOX_SOURCE_TREE: ${{ needs.source.outputs.tree-sha }}
|
||||
run: test "$(git rev-parse 'HEAD^{tree}')" = "$XBOX_SOURCE_TREE"
|
||||
- name: Install Python test dependencies
|
||||
timeout-minutes: 7
|
||||
run: bash tools/ci/install-ubuntu-packages.sh python3-yaml gcc-aarch64-linux-gnu libc6-dev-arm64-cross qemu-user libvulkan-dev mesa-vulkan-drivers
|
||||
- name: Resolve the pinned Android NDK
|
||||
run: bash tools/ci/resolve-ndk.sh
|
||||
|
||||
- name: Check Quest tracking IPC
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
|
||||
VK_INCLUDE=$(ls -d "$NDK"/toolchains/llvm/prebuilt/*/sysroot/usr/include | head -1)
|
||||
mkdir -p build/vr/include
|
||||
cp -R "$VK_INCLUDE/vulkan" build/vr/include/
|
||||
if [ -d "$VK_INCLUDE/vk_video" ]; then cp -R "$VK_INCLUDE/vk_video" build/vr/include/; fi
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_tracking.cpp -o /tmp/test-xr-tracking
|
||||
/tmp/test-xr-tracking
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_xr_touch.cpp -o /tmp/test-xr-touch
|
||||
/tmp/test-xr-touch
|
||||
g++ -std=c++17 -Wall -Wextra -Werror tools/vr/tests/test_pad_controller_config.cpp -o /tmp/test-pad-controller-config
|
||||
/tmp/test-pad-controller-config
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_vsync.cpp -o /tmp/test-xr-vsync
|
||||
/tmp/test-xr-vsync
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -pthread tools/vr/tests/test_xr_frames.cpp -o /tmp/test-xr-frames
|
||||
/tmp/test-xr-frames
|
||||
cc -Itools/vr/third_party/wayland -c tools/vr/src/wayland_client_stub.c -o /tmp/test-wayland-stub.o
|
||||
cc -Itools/vr/third_party/wayland -c app/src/main/cpp/waylandcomp/generated/xdg-shell-protocol.c -o /tmp/test-xdg-shell.o
|
||||
g++ -std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers -pthread -Ibuild/vr/include \
|
||||
-Itools/vr/third_party/wayland -Iapp/src/main/cpp/waylandcomp/generated \
|
||||
tools/vr/tests/test_presenter_shutdown.cpp tools/vr/src/presenter.cpp /tmp/test-wayland-stub.o /tmp/test-xdg-shell.o \
|
||||
-ldl -o /tmp/test-presenter-shutdown
|
||||
timeout 10 /tmp/test-presenter-shutdown
|
||||
|
||||
- name: Steam import and game environment tests
|
||||
run: python3 -m unittest discover -s tools/tests -p 'test_*.py'
|
||||
|
||||
checks:
|
||||
name: Compile and test APK
|
||||
needs: [build, python-tests]
|
||||
if: ${{ always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Require compilation and every test job
|
||||
env:
|
||||
BUILD_RESULT: ${{ needs.build.result }}
|
||||
PYTHON_RESULT: ${{ needs.python-tests.result }}
|
||||
run: test "$BUILD_RESULT" = success && test "$PYTHON_RESULT" = success
|
||||
|
||||
sign:
|
||||
needs: build
|
||||
needs: [build, checks]
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -774,7 +700,7 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
packs:
|
||||
needs: build
|
||||
needs: [build, checks]
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -794,6 +720,10 @@ jobs:
|
||||
CI_REPO: Droid-Deck/DroidDeck-CI
|
||||
KEEP: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: droiddeck-signed-*
|
||||
@@ -811,72 +741,13 @@ jobs:
|
||||
- name: Describe the change
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pr=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls" \
|
||||
--jq '[.[] | select(.merged_at != null and .base.ref == "main")] | first // empty')
|
||||
if [ -n "$pr" ]; then
|
||||
number=$(jq -r .number <<<"$pr")
|
||||
title=$(jq -r .title <<<"$pr")
|
||||
merged=$(jq -r '.merged_at[:10]' <<<"$pr")
|
||||
summary=$(jq -r '.body // ""' <<<"$pr" | tr -d '\r' | { grep -v '^🤖' || true; } |
|
||||
awk '!p && /^#/ { next } NF { p = 1; print; next } p { exit }' | head -n 6 | cut -c1-400)
|
||||
{
|
||||
echo "**Merged to main** - [PR #$number]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$number): $title (merged $merged)"
|
||||
if [ -n "$summary" ]; then
|
||||
echo
|
||||
echo "$summary"
|
||||
fi
|
||||
} > notes-head.md
|
||||
else
|
||||
subject=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA" --jq '.commit.message | split("\n")[0]')
|
||||
echo "**Pushed to main** without a PR - $subject" > notes-head.md
|
||||
fi
|
||||
cat notes-head.md
|
||||
run: bash tools/release/describe-build.sh
|
||||
|
||||
- name: Publish to DroidDeck-CI
|
||||
id: publish
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag=main-${GITHUB_SHA::7}
|
||||
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
|
||||
rm signed/SHA256SUMS-*.txt
|
||||
notes="$(cat notes-head.md)
|
||||
|
||||
Signed build of $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA - in main, not yet in a DroidDeck release; for testing.
|
||||
Commit: $GITHUB_SHA
|
||||
VersionCode: $(sed -n 's/^ *versionCode \([0-9][0-9]*\)$/\1/p' app/build.gradle)
|
||||
Each apk installs over a DroidDeck release and keeps its data. \`standard\` is the normal app; the others are the same app under another package name, installed beside it.
|
||||
|
||||
SHA-256:
|
||||
\`\`\`
|
||||
$(cat signed/SHA256SUMS.txt)
|
||||
\`\`\`"
|
||||
gh release delete "$tag" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null || true
|
||||
gh release create "$tag" -R "$CI_REPO" --target main --prerelease \
|
||||
--title "main ${GITHUB_SHA::7}" --notes "$notes" signed/*
|
||||
gh release list -R "$CI_REPO" -L 200 --json tagName,publishedAt \
|
||||
--jq '[.[] | select(.tagName | startswith("main-"))] | sort_by(.publishedAt) | reverse | .['"$KEEP"':][] | .tagName' |
|
||||
while IFS= read -r old; do
|
||||
if [ "$old" = "$tag" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! gh release delete "$old" -R "$CI_REPO" --yes --cleanup-tag 2>/dev/null; then
|
||||
if gh release view "$old" -R "$CI_REPO" >/dev/null 2>&1; then
|
||||
echo "::error::Could not delete old release $old"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
published=$(gh release view "$tag" -R "$CI_REPO" --json tagName,assets)
|
||||
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<< "$published" >/dev/null
|
||||
url="$GITHUB_SERVER_URL/$CI_REPO/releases/tag/$tag"
|
||||
apk=$(cd signed && ls DroidDeck-*.apk | awk 'length(a) == 0 || length($0) < length(a) { a = $0 } END { print a }')
|
||||
echo "release=$url" >> "$GITHUB_OUTPUT"
|
||||
echo "apk=$GITHUB_SERVER_URL/$CI_REPO/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
|
||||
echo "Published $url"
|
||||
run: bash tools/release/publish-ci.sh
|
||||
|
||||
- name: Post the Preview build to Discord
|
||||
env:
|
||||
@@ -884,26 +755,8 @@ jobs:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE: ${{ steps.publish.outputs.release }}
|
||||
APK: ${{ steps.publish.outputs.apk }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$WEBHOOK" ]; then
|
||||
echo "::notice::No DISCORD_BUILDS_WEBHOOK secret; nothing posted"
|
||||
exit 0
|
||||
fi
|
||||
commit=$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA")
|
||||
subject=$(jq -r '.commit.message | split("\n")[0]' <<<"$commit")
|
||||
title=$(jq -r '.commit.message | split("\n") | map(select(length > 0)) | .[1] // ""' <<<"$commit")
|
||||
pr=$(sed -n 's/^Merge pull request #\([0-9]*\).*/\1/p' <<<"$subject")
|
||||
if [ -n "$pr" ]; then
|
||||
what="[#$pr]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$pr): ${title:-$subject}"
|
||||
else
|
||||
what="[\`${GITHUB_SHA::7}\`]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA): $subject"
|
||||
fi
|
||||
get=$(printf 'Download: [standard APK](<%s>) · [all variants](<%s>)' "$APK" "$RELEASE")
|
||||
content=$(printf '**New signed Preview build** `main-%s`\n%s\nIn the app: **Updates → Preview**\n%s' "${GITHUB_SHA::7}" "$what" "$get")
|
||||
jq -n --arg content "$content" '{content: $content, allowed_mentions: {parse: []}}' |
|
||||
curl -fsS -H 'Content-Type: application/json' -d @- "$WEBHOOK" -o /dev/null
|
||||
echo "Posted: $content"
|
||||
run: bash tools/release/announce-preview.sh
|
||||
|
||||
|
||||
catalog:
|
||||
needs: announce
|
||||
|
||||
@@ -9,6 +9,16 @@ DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big
|
||||
|
||||
<p align="center"><a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/267852?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-267852" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/daily?language=C" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"/></a>
|
||||
<a href="https://trendshift.io/repositories/267852?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-267852" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/daily?language=Kotlin" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/267852?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-267852" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/weekly?language=C" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"/></a>
|
||||
<a href="https://trendshift.io/repositories/267852?utm_source=trendshift-badge&utm_medium=badge&utm_campaign=badge-trendshift-267852" target="_blank" rel="noopener noreferrer"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/weekly?language=Kotlin" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="docs/releases/media/0.2.0/launch-into-steam.gif" width="80%" alt="Tapping DroidDeck on the Android home screen and landing in Steam Big Picture"></p>
|
||||
|
||||
> Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.
|
||||
@@ -47,7 +57,7 @@ Compatibility and performance vary by device; hardware validation is limited. De
|
||||
|
||||
## Credits and licence
|
||||
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. The registry entries a GOG game's install script (`goggame-<id>.script`) sets are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher)'s GOG setup (GPL-3.0) reads them. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence, which also starts the arm64 emulator AppImages whose own runtime cannot run its extractor on Android. The Steam client's audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink` with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest). x86 programs that link OpenSSL 3 under FEX use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt) platform. Hooks written with `WriteProcessMemory` into code [FEX](https://github.com/FEX-Emu/FEX) has translated go through `/proc/<pid>/mem` again when the kernel refuses `process_vm_writev`, as [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` wrote them before Wine 11. x86 Linux games under FEX get the same `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes into the Steam Linux Runtime, so ALSA plays through PulseAudio. Games render with [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's own per-render-pass choice between tile memory and system memory, while the Steam client keeps `TU_DEBUG=sysmem`; [FEX](https://github.com/FEX-Emu/FEX)'s code disk cache was checked in its source and is already on through Proton. The proot fast path picks the guest name of a host file bound under several names in the order [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later) sorts its bindings, so links and `/proc/self/fd` read back as they do through proot. The session's [PipeWire](https://gitlab.freedesktop.org/pipewire/pipewire) (MIT) opens its sockets with the per-socket access settings documented in its `module-access.c`, and [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber) (MIT) keeps those clients at full permissions through its `access.rules`. Wine write watches use the userfaultfd support in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/ntdll/unix/virtual.c` (LGPL-2.1-or-later): libblsession gives it the arm64 syscall number and answers its `PAGEMAP_SCAN` from `/proc/self/pagemap` where Android refuses the ioctl. Wine on arm64 sets a thread's full context by raising `SIGUSR2` from `syscall_dispatcher_return_slowpath` in [Wine](https://gitlab.winehq.org/wine/wine)'s `dlls/ntdll/unix/signal_arm64.c` (LGPL-2.1-or-later); libblsession runs that handler in place on a frame laid out as the [Linux](https://github.com/torvalds/linux) kernel's `arch/arm64/kernel/signal.c` builds it and returns through `rt_sigreturn`, so proot does not stop the thread for it. Wine's process list reads each process's memory counters from `/proc/<pid>/status` in `fill_vm_counters` ([Wine](https://gitlab.winehq.org/wine/wine) `dlls/ntdll/unix/process.c`, LGPL-2.1-or-later), so libblsession gives those reads the kernel's file instead of a copy with `TracerPid` cleared. See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on [Phobos’s](https://github.com/phobos665) adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl), respectively. The registry entries a GOG game's install script (`goggame-<id>.script`) sets are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher)'s GOG setup (GPL-3.0) reads them. LSFG frame generation is from the work of Camille LaVey and the [Eden](https://eden-emu.dev) emulator project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported to WinNative and DroidDeck by [@maxjivi05](https://github.com/maxjivi05); it needs your own copy of [Lossless Scaling](https://store.steampowered.com/app/993090/) and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence, which also starts the arm64 emulator AppImages whose own runtime cannot run its extractor on Android. The Steam client's audio goes through [@The412Banner](https://github.com/The412Banner)'s [DirectAudio](https://github.com/The412Banner/directaudio) PulseAudio sink (LGPL-2.1-or-later), built from `tools/directaudio/sink` with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest). x86 programs that link OpenSSL 3 under FEX use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt) platform. Hooks written with `WriteProcessMemory` into code [FEX](https://github.com/FEX-Emu/FEX) has translated go through `/proc/<pid>/mem` again when the kernel refuses `process_vm_writev`, as [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` wrote them before Wine 11. x86 Linux games under FEX get the same `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes into the Steam Linux Runtime, so ALSA plays through PulseAudio. Games render with [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's own per-render-pass choice between tile memory and system memory, while the Steam client keeps `TU_DEBUG=sysmem`; [FEX](https://github.com/FEX-Emu/FEX)'s code disk cache was checked in its source and is already on through Proton. The proot fast path picks the guest name of a host file bound under several names in the order [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later) sorts its bindings, so links and `/proc/self/fd` read back as they do through proot. The session's [PipeWire](https://gitlab.freedesktop.org/pipewire/pipewire) (MIT) opens its sockets with the per-socket access settings documented in its `module-access.c`, and [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber) (MIT) keeps those clients at full permissions through its `access.rules`. Wine write watches use the userfaultfd support in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/ntdll/unix/virtual.c` (LGPL-2.1-or-later): libblsession gives it the arm64 syscall number and answers its `PAGEMAP_SCAN` from `/proc/self/pagemap` where Android refuses the ioctl. Wine on arm64 sets a thread's full context by raising `SIGUSR2` from `syscall_dispatcher_return_slowpath` in [Wine](https://gitlab.winehq.org/wine/wine)'s `dlls/ntdll/unix/signal_arm64.c` (LGPL-2.1-or-later); libblsession runs that handler in place on a frame laid out as the [Linux](https://github.com/torvalds/linux) kernel's `arch/arm64/kernel/signal.c` builds it and returns through `rt_sigreturn`, so proot does not stop the thread for it. Wine's process list reads each process's memory counters from `/proc/<pid>/status` in `fill_vm_counters` ([Wine](https://gitlab.winehq.org/wine/wine) `dlls/ntdll/unix/process.c`, LGPL-2.1-or-later), so libblsession gives those reads the kernel's file instead of a copy with `TracerPid` cleared. droiddeck-ntsync is checked against the object, wait and error semantics documented for the [Linux](https://github.com/torvalds/linux) ntsync driver in `Documentation/userspace-api/ntsync.rst` (GPL-2.0). See [LICENSE](LICENSE). Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.
|
||||
|
||||
XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) by [@wheaney](https://github.com/wheaney), including the VITURE protocol header it ships, [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) by [@badicsalex](https://github.com/badicsalex) and the [go-macos/iokit VITURE package](https://pkg.go.dev/github.com/go-macos/iokit/viture); the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts [@informalTechCode](https://github.com/informalTechCode)'s [RayNeo-Air-Series-3DoF-Scaffold](https://github.com/informalTechCode/RayNeo-Air-Series-3DoF-Scaffold) (Apache-2.0) and references [@verncat](https://github.com/verncat)'s [RayNeo-Air-3S-Pro-OpenVR](https://github.com/verncat/RayNeo-Air-3S-Pro-OpenVR) (MIT) and [Monado](https://gitlab.freedesktop.org/monado/monado) (BSL-1.0); see [docs/third-party/rayneo](docs/third-party/rayneo/README.md). XREAL 1S support references [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver) and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) by [@rohitsangwan01](https://github.com/rohitsangwan01) (MIT) and [xr-tools](https://github.com/0xcaff/xr-tools) by [@0xcaff](https://github.com/0xcaff). Rokid support follows the Rokid protocol notes in [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) and @badicsalex's [Void Computing write-ups](https://voidcomputing.hu/blog/worse-better-prettier/), and [Monado](https://gitlab.freedesktop.org/monado/monado)'s Rokid driver. The REDMAGIC glasses-display findings draw on [@mekhontsev](https://github.com/mekhontsev)'s [MagicDesk investigation](https://github.com/mekhontsev/magicdesk/blob/main/docs/xr-resolution-fix.md) and [@Coding-BR](https://github.com/Coding-BR)'s [decompiled Nubia kernel](https://github.com/Coding-BR/android_kernel_nubia_sm8850_qwjujube). VITURE, RayNeo, XREAL, Rokid and REDMAGIC are trademarks of their owners; this project is not affiliated with them.
|
||||
|
||||
|
||||
+5
-1
@@ -80,7 +80,7 @@ def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getO
|
||||
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
|
||||
def ciBuild = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true'
|
||||
def releaseSigner = rootProject.file('keystore/release-signer.sha256').text.trim()
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', 'HEAD']) ?: '0').with { it.isLong() ? it : '0' }
|
||||
def commitTime = (gitValue(['show', '-s', '--format=%ct', '--end-of-options', sourceCommit]) ?: '0').with { it.isLong() ? it : '0' }
|
||||
def desktopKde = rootProject.file('tools/desktop-kde/release.env').readLines()
|
||||
.findAll { it.contains('=') && !it.startsWith('#') }
|
||||
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
|
||||
@@ -220,6 +220,10 @@ android {
|
||||
unitTests.returnDefaultValues = true
|
||||
unitTests.includeAndroidResources = true
|
||||
unitTests.all {
|
||||
if (ciBuild) {
|
||||
outputs.upToDateWhen { false }
|
||||
outputs.doNotCacheIf('CI executes the test suite on every run') { true }
|
||||
}
|
||||
// Robolectric keeps a sandbox per SDK level the tests ask for; seven no longer fit in Gradle's default 512 MB.
|
||||
maxHeapSize = '2g'
|
||||
def logTarget = providers.systemProperty('robolectric.logging').orNull
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:allowBackup="false"
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
|
||||
@@ -8,6 +8,7 @@ add_library(droiddeckwayland SHARED
|
||||
src/sc_layer.c
|
||||
src/ahb_swapchain.c
|
||||
src/waylandcomp_jni.c
|
||||
src/gpu_clock_pin.c
|
||||
src/droiddeck_ext.c
|
||||
src/wl_dmabuf.c
|
||||
src/wl_clipboard.c
|
||||
|
||||
@@ -808,10 +808,14 @@ static uint32_t nominal_refresh_ns(void) {
|
||||
}
|
||||
|
||||
static int64_t presented_ns(int64_t render_ns) {
|
||||
if (!g_tick_vsync_ns || render_ns - g_last_vsync_ns > (int64_t)nominal_refresh_ns() ||
|
||||
render_ns < g_tick_vsync_ns)
|
||||
int64_t tick = g_tick_vsync_ns;
|
||||
int64_t refresh = (int64_t)nominal_refresh_ns();
|
||||
if (!tick || render_ns - g_last_vsync_ns > refresh)
|
||||
return render_ns;
|
||||
return g_tick_vsync_ns;
|
||||
int64_t delta = render_ns - tick;
|
||||
if (delta > refresh || -delta > refresh)
|
||||
return render_ns;
|
||||
return tick;
|
||||
}
|
||||
|
||||
static void feedback_present_all(struct wl_list *list, int64_t t) {
|
||||
|
||||
@@ -238,7 +238,7 @@ static int ensure_ring(uint32_t w, uint32_t h, VkFormat fmt, uint32_t n) {
|
||||
}
|
||||
g_ring_w = w; g_ring_h = h; g_ring_fmt = fmt;
|
||||
LOGD("framegen: generation ring %ux%u x%u", w, h, n);
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -350,6 +350,7 @@ int vkp_framegen_run(VkCommandBuffer cmd, VkImage scene, VkImageView scene_view,
|
||||
fge_configure((uint32_t)mult, (uint32_t)atomic_load(&g_target), flow, hz,
|
||||
atomic_load(&g_model), atomic_load(&g_preset));
|
||||
}
|
||||
const int size_changed = g_built_w != (uint32_t)w || g_built_h != (uint32_t)h || g_built_fmt != fmt;
|
||||
if (!fge_prepare((uint32_t)w, (uint32_t)h, fmt)) {
|
||||
if (fmt != VK_FORMAT_R8G8B8A8_UNORM && fge_unavailable()) {
|
||||
refuse_format(fmt, kind, w, h, "build its chain");
|
||||
@@ -361,17 +362,25 @@ int vkp_framegen_run(VkCommandBuffer cmd, VkImage scene, VkImageView scene_view,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (g_built_w != (uint32_t)w || g_built_h != (uint32_t)h || g_built_fmt != fmt) {
|
||||
if (size_changed) {
|
||||
g_built_w = (uint32_t)w; g_built_h = (uint32_t)h; g_built_fmt = fmt;
|
||||
g_generating_logged = 0;
|
||||
}
|
||||
uint32_t want = (uint32_t)(mult - 1);
|
||||
if (!ensure_ring((uint32_t)w, (uint32_t)h, fmt, want)) {
|
||||
const int ring = ensure_ring((uint32_t)w, (uint32_t)h, fmt, want);
|
||||
if (!ring) {
|
||||
if (fmt != VK_FORMAT_R8G8B8A8_UNORM) { refuse_format(fmt, kind, w, h, "make its generation ring"); return 0; }
|
||||
FGLOG("no memory for the generation ring (%dx%d x%u); frame generation stays off", w, h, want);
|
||||
mark_failed(kind);
|
||||
return 0;
|
||||
}
|
||||
/* The ring and the engine's images are new on this frame. Dispatching into them now
|
||||
* null-dereferenced Turnip (vkp_framegen_run, first Steam frame, 2560x1440 to the panel).
|
||||
* Show this frame as it is; generation starts once the size has settled. */
|
||||
if (size_changed || ring == 2) {
|
||||
FGLOG("frame generation catches up at %dx%d; this frame is shown as it is", w, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The pacer's source rate must be the game's, not this loop's: a loop slowed by its own
|
||||
* generations would otherwise read as a slower game and justify even more generations. */
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "gpu_clock_pin.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../adrenotools/src/hook/kgsl.h"
|
||||
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t worker;
|
||||
static bool joinable;
|
||||
static atomic_bool running;
|
||||
|
||||
struct pin {
|
||||
int fd;
|
||||
unsigned int context;
|
||||
unsigned int memory;
|
||||
unsigned int timestamp;
|
||||
unsigned long gpuaddr;
|
||||
uint32_t nop;
|
||||
void *commands;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static struct pin pin = { .fd = -1 };
|
||||
|
||||
static void release_pin(void) {
|
||||
if (pin.context) {
|
||||
struct kgsl_drawctxt_destroy destroy = { .drawctxt_id = pin.context };
|
||||
ioctl(pin.fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &destroy);
|
||||
}
|
||||
if (pin.commands) munmap(pin.commands, pin.size);
|
||||
if (pin.memory) {
|
||||
struct kgsl_gpumem_free_id free_memory = { .id = pin.memory };
|
||||
ioctl(pin.fd, IOCTL_KGSL_GPUMEM_FREE_ID, &free_memory);
|
||||
}
|
||||
if (pin.fd >= 0) close(pin.fd);
|
||||
pin = (struct pin) { .fd = -1 };
|
||||
}
|
||||
|
||||
static bool submit_pin(void) {
|
||||
struct kgsl_command_object object = {
|
||||
.gpuaddr = pin.gpuaddr,
|
||||
.size = 2 * sizeof(uint32_t),
|
||||
.flags = KGSL_CMDLIST_IB,
|
||||
.id = pin.memory,
|
||||
};
|
||||
struct kgsl_gpu_command command = {
|
||||
.flags = KGSL_CMDBATCH_PWR_CONSTRAINT,
|
||||
.cmdlist = (uintptr_t)&object,
|
||||
.cmdsize = sizeof(object),
|
||||
.numcmds = 1,
|
||||
.context_id = pin.context,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPU_COMMAND, &command) < 0) return false;
|
||||
pin.timestamp = command.timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prepare_pin(void) {
|
||||
pin.fd = open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC);
|
||||
if (pin.fd < 0) return false;
|
||||
struct kgsl_devinfo info = {0};
|
||||
struct kgsl_device_getproperty device_info = {
|
||||
.type = KGSL_PROP_DEVICE_INFO, .value = &info, .sizebytes = sizeof(info),
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_DEVICE_GETPROPERTY, &device_info) < 0) return false;
|
||||
unsigned int core = info.chip_id >> 24;
|
||||
if (!core) core = info.gpu_id / 100;
|
||||
if (core < 3) { errno = ENODEV; return false; }
|
||||
pin.nop = core >= 5 ? 0x70100001 : 0xc0001000;
|
||||
struct kgsl_drawctxt_create context = {
|
||||
.flags = KGSL_CONTEXT_NO_GMEM_ALLOC | KGSL_CONTEXT_PREAMBLE | KGSL_CONTEXT_PWR_CONSTRAINT,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_DRAWCTXT_CREATE, &context) < 0) return false;
|
||||
pin.context = context.drawctxt_id;
|
||||
struct kgsl_device_constraint_pwrlevel level = { .level = KGSL_CONSTRAINT_PWR_MAX };
|
||||
struct kgsl_device_constraint constraint = {
|
||||
.type = KGSL_CONSTRAINT_PWRLEVEL,
|
||||
.context_id = pin.context,
|
||||
.data = &level,
|
||||
.size = sizeof(level),
|
||||
};
|
||||
struct kgsl_device_getproperty property = {
|
||||
.type = KGSL_PROP_PWR_CONSTRAINT,
|
||||
.value = &constraint,
|
||||
.sizebytes = sizeof(constraint),
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_SETPROPERTY, &property) < 0) return false;
|
||||
struct kgsl_gpumem_alloc_id memory = { .size = 4096 };
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPUMEM_ALLOC_ID, &memory) < 0) return false;
|
||||
pin.memory = memory.id;
|
||||
pin.gpuaddr = memory.gpuaddr;
|
||||
pin.size = memory.mmapsize;
|
||||
void *commands = mmap(NULL, pin.size, PROT_READ | PROT_WRITE, MAP_SHARED, pin.fd,
|
||||
(off_t)pin.memory << 12);
|
||||
if (commands == MAP_FAILED) return false;
|
||||
pin.commands = commands;
|
||||
// A3xx/A4xx use type-3 CP_NOP; newer cores use type-7 with zero odd-parity bits.
|
||||
uint32_t *words = commands;
|
||||
words[0] = pin.nop;
|
||||
words[1] = 0;
|
||||
struct kgsl_gpumem_sync_cache sync = { .id = pin.memory, .op = KGSL_GPUMEM_CACHE_CLEAN };
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_GPUMEM_SYNC_CACHE, &sync) < 0) return false;
|
||||
return submit_pin();
|
||||
}
|
||||
|
||||
static void *hold_pin(void *unused) {
|
||||
(void)unused;
|
||||
while (atomic_load(&running)) {
|
||||
struct kgsl_device_waittimestamp_ctxtid wait = {
|
||||
.context_id = pin.context,
|
||||
.timestamp = pin.timestamp,
|
||||
.timeout = 100,
|
||||
};
|
||||
if (ioctl(pin.fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, &wait) < 0) {
|
||||
if (errno == EINTR || errno == ETIMEDOUT) continue;
|
||||
break;
|
||||
}
|
||||
// Constraints expire after idle_timer (normally 80 ms); renew with a NOP, keeping devfreq alive.
|
||||
usleep(25000);
|
||||
if (atomic_load(&running) && !submit_pin()) break;
|
||||
}
|
||||
if (atomic_exchange(&running, false))
|
||||
__android_log_print(ANDROID_LOG_WARN, "GpuClockPin", "constraint stopped: %s", strerror(errno));
|
||||
release_pin();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool gpu_clock_pin_set(bool on) {
|
||||
pthread_mutex_lock(&lock);
|
||||
if (joinable) {
|
||||
if (on && atomic_load(&running)) {
|
||||
pthread_mutex_unlock(&lock);
|
||||
return true;
|
||||
}
|
||||
atomic_store(&running, false);
|
||||
pthread_join(worker, NULL);
|
||||
joinable = false;
|
||||
}
|
||||
bool ok = true;
|
||||
if (on) {
|
||||
ok = prepare_pin();
|
||||
if (ok) {
|
||||
atomic_store(&running, true);
|
||||
int error = pthread_create(&worker, NULL, hold_pin, NULL);
|
||||
if (error) {
|
||||
atomic_store(&running, false);
|
||||
errno = error;
|
||||
ok = false;
|
||||
} else {
|
||||
joinable = true;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
int error = errno;
|
||||
release_pin();
|
||||
errno = error;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
return ok;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool gpu_clock_pin_set(bool on);
|
||||
@@ -1626,6 +1626,10 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i
|
||||
VkImageLayout result_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
if (fx) {
|
||||
int mapped_w = (int)(scene_w * g_map.kx + 0.5f), mapped_h = (int)(scene_h * g_map.ky + 0.5f);
|
||||
/* Frame generation samples every output pixel. Upscaling first made it interpolate
|
||||
* the whole panel (3200x1440 on a 1440p phone) and rebuild on Steam's first frame.
|
||||
* Grade at the game's size instead; the screen blit still scales to the panel. */
|
||||
if (fg) { mapped_w = scene_w; mapped_h = scene_h; }
|
||||
vkp_effects_set_formats(scene_fmt, scene_fmt);
|
||||
result = vkp_effects_run(cmd, scene_img, scene_w, scene_h, mapped_w, mapped_h, &rw, &rh);
|
||||
result_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "effects_chain.h"
|
||||
#include "cardboard.h"
|
||||
#include "droiddeck_color.h"
|
||||
#include "gpu_clock_pin.h"
|
||||
|
||||
extern int droiddeck_wayland_run(void);
|
||||
extern void droiddeck_wayland_set_scale_mode(int mode, int alignment);
|
||||
@@ -634,8 +635,14 @@ Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeTextInputDelete(JNIE
|
||||
extern void adrenotools_set_turbo(bool turbo);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetGpuTurbo(JNIEnv *env, jclass clazz, jboolean on) {
|
||||
(void)env; (void)clazz;
|
||||
adrenotools_set_turbo(on == JNI_TRUE);
|
||||
(void)clazz;
|
||||
if (!gpu_clock_pin_set(on == JNI_TRUE)) {
|
||||
int saved_errno = errno;
|
||||
jclass error = (*env)->FindClass(env, "java/lang/IllegalStateException");
|
||||
if (error) (*env)->ThrowNew(env, error, strerror(saved_errno));
|
||||
}
|
||||
// Clear a legacy global pin when recovering its marker after an app upgrade or crash.
|
||||
if (on != JNI_TRUE) adrenotools_set_turbo(false);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -28,6 +28,7 @@ class App : Application() {
|
||||
if (process == "$packageName:vr_recovery" || process == "$packageName:rgb_diagnostic") return
|
||||
CrashHandler.install(this)
|
||||
com.droiddeck.launcher.session.QuestExperimental.initialize(this)
|
||||
com.droiddeck.launcher.session.SessionPrefs.applyNtsyncDefault(this)
|
||||
com.droiddeck.launcher.session.SessionPrefs.settleGpuDriverMode(this)
|
||||
com.droiddeck.launcher.update.AppUpdates.init(this)
|
||||
com.droiddeck.launcher.core.DownloadSource.init(this)
|
||||
|
||||
@@ -55,6 +55,8 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
private set
|
||||
private var runtimePending by mutableStateOf(false)
|
||||
private var sessionRunning by mutableStateOf(false)
|
||||
var tpUbwcMode: String by mutableStateOf(SessionPrefs.TP_UBWC_AUTO)
|
||||
private set
|
||||
|
||||
val uiMode: String get() = when (mode) {
|
||||
SessionPrefs.GPU_DRIVERS_AUTO -> GPU_MODE_AUTO
|
||||
@@ -73,8 +75,16 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
mode = uiMode, display = displaySlot(), runtime = runtimeSlot(),
|
||||
canRestart = !sessionRunning,
|
||||
checkedAt = TurnipReleases.cached(activity)?.checkedAt ?: 0L,
|
||||
tpUbwcMode = tpUbwcMode.takeIf { gpu.model == 740 },
|
||||
tpUbwcRestartRequired = com.droiddeck.launcher.gpu.TpUbwcHint.restartRequired(activity),
|
||||
)
|
||||
|
||||
fun chooseTpUbwcMode(mode: String) {
|
||||
if (gpu.model != 740) return
|
||||
SessionPrefs.setTpUbwcMode(activity, mode)
|
||||
tpUbwcMode = mode
|
||||
}
|
||||
|
||||
private fun slotSource(chosenDefault: Boolean, defaultLabel: Int): String = activity.getString(when {
|
||||
uiMode == GPU_MODE_AUTO -> R.string.gpu_src_auto
|
||||
uiMode == GPU_MODE_PAIR && pairRows.any { it.active } -> R.string.gpu_src_pair
|
||||
@@ -124,6 +134,7 @@ internal class DriverMenus(private val activity: Activity, private val ui: Handl
|
||||
var androidSelected by mutableStateOf("")
|
||||
|
||||
fun refreshDrivers() {
|
||||
tpUbwcMode = SessionPrefs.tpUbwcMode(activity)
|
||||
val lm = LinuxVulkanDriverManager(activity)
|
||||
fun origin(id: String) = if (TurnipReleases.isDownloaded(activity, id)) DriverRow.DOWNLOADED else DriverRow.IMPORTED
|
||||
val bundles = DriverBundle.all(activity)
|
||||
|
||||
@@ -120,6 +120,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private var glThread by mutableStateOf(true)
|
||||
private var noGlError by mutableStateOf(true)
|
||||
private var returnAfterGame by mutableStateOf(true)
|
||||
private var shaderPrecache by mutableStateOf(true)
|
||||
private var steamRepairQueued by mutableStateOf(false)
|
||||
private var mangoapp by mutableStateOf(true)
|
||||
private var steamController by mutableStateOf(SessionPrefs.CONTROLLER_DECK)
|
||||
@@ -154,6 +155,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private var gpuClockPin by mutableStateOf(false)
|
||||
private var prootNoSeccomp by mutableStateOf(false)
|
||||
private var prootFastPath by mutableStateOf(true)
|
||||
private var cursorComposite by mutableStateOf(false)
|
||||
private var guestHostname by mutableStateOf(SessionPrefs.DEFAULT_GUEST_HOSTNAME)
|
||||
private var phantomWarning by mutableStateOf<String?>(null)
|
||||
private var phantomProcessStatus by mutableStateOf(PhantomProcessStatus.NOT_APPLICABLE)
|
||||
@@ -161,7 +163,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
private var directAudio by mutableStateOf(false)
|
||||
private var picturePosition by mutableStateOf("center")
|
||||
private var stretch16x9 by mutableStateOf(false)
|
||||
private var orientation by mutableStateOf(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
private var orientation by mutableStateOf(android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
|
||||
private var launcherFullscreen by mutableStateOf(true)
|
||||
private var downloadSource by mutableStateOf(DownloadSource.MODE_AUTO)
|
||||
private var downloadMirror by mutableStateOf("")
|
||||
@@ -1509,6 +1511,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
onRestoreBundled = { TurnipDriver(this).restoreBundled(); drivers.refreshDrivers() },
|
||||
onMode = { m -> drivers.chooseMode(m) },
|
||||
onRestart = { drivers.restartApp() },
|
||||
onTpUbwcMode = { drivers.chooseTpUbwcMode(it) },
|
||||
),
|
||||
protons = com.droiddeck.launcher.ui.ProtonsState(
|
||||
rows = protons.protonRows, busyId = protons.protonBusyId, stage = protons.protonStage, percent = protons.protonPercent,
|
||||
@@ -1634,6 +1637,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
syncBackend = if (mode == SessionService.MODE_STEAM) SessionPrefs.syncBackendOf(fastSync, fsyncFirst, syncFallback) else null,
|
||||
steamClientArch = if (mode == SessionService.MODE_STEAM) steamClientArch else null,
|
||||
returnAfterGame = if (mode == SessionService.MODE_STEAM) returnAfterGame else null,
|
||||
shaderPrecache = if (mode == SessionService.MODE_STEAM) shaderPrecache else null,
|
||||
steamRepairQueued = steamRepairQueued,
|
||||
mangoapp = mangoapp,
|
||||
steamController = if (mode == SessionService.MODE_STEAM) steamController else null,
|
||||
@@ -1720,9 +1724,15 @@ class FrontEndActivity : ComponentActivity() {
|
||||
syncFallback = SessionPrefs.syncFallback(this)
|
||||
},
|
||||
onSteamRepair = { steamRepairQueued = SteamRepair.queue(this) },
|
||||
onSteamClientArch = { id -> SessionPrefs.setSteamClientArch(this, id); steamClientArch = id; refresh() },
|
||||
onSteamClientArch = { id ->
|
||||
SessionPrefs.setSteamClientArch(this, id)
|
||||
steamClientArch = id
|
||||
shaderPrecache = SessionPrefs.shaderPrecache(this)
|
||||
refresh()
|
||||
},
|
||||
onMangoapp = { on -> SessionPrefs.setMangoapp(this, on); mangoapp = on },
|
||||
onReturnAfterGame = { on -> SessionPrefs.setReturnAfterGame(this, on); returnAfterGame = on },
|
||||
onShaderPrecache = { on -> SessionPrefs.setShaderPrecache(this, on); shaderPrecache = on },
|
||||
onSteamController = { id -> SessionPrefs.setSteamController(this, id); steamController = id },
|
||||
onRunSteamAtStartup = { on ->
|
||||
SessionPrefs.setRunSteamAtStartup(this, on)
|
||||
@@ -1763,7 +1773,8 @@ class FrontEndActivity : ComponentActivity() {
|
||||
fsyncFirst = fsyncFirst,
|
||||
gamescopeRealtime = gamescopeRealtime,
|
||||
gpuClockPin = gpuClockPin,
|
||||
prootNoSeccomp = prootNoSeccomp, prootFastPath = prootFastPath, guestHostname = guestHostname, phantomWarning = phantomWarning,
|
||||
prootNoSeccomp = prootNoSeccomp, prootFastPath = prootFastPath, cursorComposite = cursorComposite,
|
||||
guestHostname = guestHostname, phantomWarning = phantomWarning,
|
||||
onClientOverride = { on -> SessionPrefs.setClientCpusOverride(this, on); clientOverride = on },
|
||||
onTuSysmem = { on -> SessionPrefs.setTuSysmem(this, on); tuSysmem = on },
|
||||
onZinkLazy = { on -> SessionPrefs.setZinkLazy(this, on); zinkLazy = on },
|
||||
@@ -1777,6 +1788,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
onGpuClockPin = { on -> SessionPrefs.setGpuClockPin(this, on); gpuClockPin = on },
|
||||
onProotNoSeccomp = { on -> SessionPrefs.setProotNoSeccomp(this, on); prootNoSeccomp = on },
|
||||
onProotFastPath = { on -> SessionPrefs.setProotFastPath(this, on); prootFastPath = on },
|
||||
onCursorComposite = { on -> SessionPrefs.setCursorComposite(this, on); cursorComposite = on },
|
||||
onGuestHostname = { name -> SessionPrefs.setGuestHostname(this, name) },
|
||||
onClientCore = { core, on ->
|
||||
clientCores = if (on) clientCores + core else clientCores - core
|
||||
@@ -1892,6 +1904,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
steamRepairQueued = SteamRepair.queued(this)
|
||||
mangoapp = SessionPrefs.mangoapp(this)
|
||||
returnAfterGame = SessionPrefs.returnAfterGame(this)
|
||||
shaderPrecache = SessionPrefs.shaderPrecache(this)
|
||||
steamController = SessionPrefs.steamController(this)
|
||||
runSteamAtStartup = SessionPrefs.runSteamAtStartup(this)
|
||||
loadBootMovie()
|
||||
@@ -1958,6 +1971,7 @@ class FrontEndActivity : ComponentActivity() {
|
||||
gpuClockPin = SessionPrefs.gpuClockPin(this)
|
||||
prootNoSeccomp = SessionPrefs.prootNoSeccomp(this)
|
||||
prootFastPath = SessionPrefs.prootFastPath(this)
|
||||
cursorComposite = SessionPrefs.cursorComposite(this)
|
||||
guestHostname = SessionPrefs.guestHostname(this)
|
||||
refreshPhantomStatus()
|
||||
}
|
||||
|
||||
@@ -329,6 +329,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var glassesSettings by mutableStateOf(Glasses.settings.value)
|
||||
private var glassesScreen: GlassesScreen? = null
|
||||
private var glassesOutputActive by mutableStateOf(false)
|
||||
private var pausedFrame by mutableStateOf<com.droiddeck.launcher.ui.PausedFrame?>(null)
|
||||
private var pausedFrameWanted = false
|
||||
private var trackingRecovery by mutableStateOf<TrackingRecovery?>(null)
|
||||
private var glasses3dForVr = false
|
||||
private var ended = false
|
||||
@@ -1013,9 +1015,20 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
SessionAchievements(inFront.game, inFront.title, onClose = ::closeAchievements)
|
||||
}
|
||||
com.droiddeck.launcher.ui.AchievementNotificationOverlay()
|
||||
if (SessionState.suspended) SessionPausedOverlay(
|
||||
// Auto and Native resume on their own as the session comes back; only a failed resume needs the button.
|
||||
val autoResume = suspendPolicy == SessionPrefs.SUSPEND_AUTO || suspendPolicy == SessionPrefs.SUSPEND_NATIVE
|
||||
SessionPausedOverlay(
|
||||
suspended = SessionState.suspended && (!autoResume || SessionState.resumeFailed),
|
||||
engaged = SessionState.resumeRequested || SessionState.resuming,
|
||||
failures = SessionState.resumeFailures,
|
||||
frame = pausedFrame,
|
||||
title = pausedTitle(),
|
||||
onResume = { SessionService.resume(this@SessionActivity) },
|
||||
onFrameNeeded = ::requestPausedFrame,
|
||||
onHidden = {
|
||||
pausedFrame = null
|
||||
pausedFrameWanted = false
|
||||
},
|
||||
)
|
||||
if (leaving) com.droiddeck.launcher.ui.FloodSpread(
|
||||
com.droiddeck.launcher.ui.LocalPalette.current.signal,
|
||||
@@ -1693,7 +1706,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
com.droiddeck.launcher.gpu.DriverSetupLog.write(this, "compositor.start driver=$driverId")
|
||||
CompositorHost.startOrAttach(
|
||||
holder.surface, runtimeDir.path,
|
||||
this, holder.surface, runtimeDir.path,
|
||||
driverId?.let { turnip.driverPath(it) }, driverId?.let { turnip.libraryName(it) },
|
||||
applicationInfo.nativeLibraryDir, size.first, size.second, refreshHz(), outputFpsLimit(),
|
||||
)
|
||||
@@ -2860,6 +2873,50 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
if (down || code !in gestureKeysDown) WaylandCompositor.nativeSendKey(code, if (down) 1 else 0)
|
||||
}
|
||||
|
||||
// The frozen game cannot draw again, so the paused overlay shows a copy taken while the surface still holds its frame.
|
||||
private fun capturePausedFrame() {
|
||||
if (!SessionState.running || pipUi || isInPictureInPictureMode || glassesOutputActive) return
|
||||
if (SessionState.suspended && pausedFrame != null) return
|
||||
if (suspendPolicy == SessionPrefs.SUSPEND_NEVER || !::surfaceView.isInitialized) return
|
||||
if (surfaceView.holder.surface?.isValid != true) return
|
||||
val width = surfaceView.width
|
||||
val height = surfaceView.height
|
||||
if (width <= 0 || height <= 0) return
|
||||
val scale = minOf(1f, PAUSED_FRAME_MAX_EDGE / maxOf(width, height).toFloat())
|
||||
val bitmap = try {
|
||||
android.graphics.Bitmap.createBitmap(
|
||||
maxOf(1, (width * scale).toInt()), maxOf(1, (height * scale).toInt()), android.graphics.Bitmap.Config.ARGB_8888,
|
||||
)
|
||||
} catch (_: OutOfMemoryError) {
|
||||
return
|
||||
}
|
||||
val surfaceAt = IntArray(2).also { surfaceView.getLocationInWindow(it) }
|
||||
val overlayAt = IntArray(2).also { sessionOverlay.getLocationInWindow(it) }
|
||||
val left = surfaceAt[0] - overlayAt[0]
|
||||
val top = surfaceAt[1] - overlayAt[1]
|
||||
val bounds = android.graphics.Rect(left, top, left + width, top + height)
|
||||
try {
|
||||
android.view.PixelCopy.request(surfaceView, bitmap, { result ->
|
||||
if (result == android.view.PixelCopy.SUCCESS && !isDestroyed) {
|
||||
pausedFrame = com.droiddeck.launcher.ui.PausedFrame(bitmap.asImageBitmap(), bounds)
|
||||
pausedFrameWanted = false
|
||||
} else {
|
||||
bitmap.recycle()
|
||||
}
|
||||
}, uiHandler)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
bitmap.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions that froze from picture-in-picture, or before onPause could copy a frame, have none; the
|
||||
// compositor shows the frozen frame again once its output is back, so copy it from there.
|
||||
private fun requestPausedFrame() {
|
||||
if (pausedFrame != null) return
|
||||
pausedFrameWanted = true
|
||||
if (resumed && surfaceW > 0) capturePausedFrame()
|
||||
}
|
||||
|
||||
private fun pipBounds(): android.graphics.Rect {
|
||||
val rect = android.graphics.Rect()
|
||||
if (!::surfaceView.isInitialized || !surfaceView.getGlobalVisibleRect(rect)) return rect
|
||||
@@ -2990,6 +3047,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
applyFollowOrientation(resources.configuration)
|
||||
if (runtimeRemovalBlocked) return
|
||||
if (!SessionState.suspended) pausedFrame = null
|
||||
com.droiddeck.launcher.ui.Motion.refresh(this)
|
||||
refreshHomeApp()
|
||||
if (intent?.action == SessionService.ACTION_RESUME) {
|
||||
@@ -3015,6 +3073,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
|
||||
override fun onPause() {
|
||||
if (runtimeRemovalBlocked) { super.onPause(); return }
|
||||
capturePausedFrame()
|
||||
sessionClipboard.stop()
|
||||
if (::touchscreenGestures.isInitialized) touchscreenGestures.cancel()
|
||||
if (::touchpad.isInitialized) touchpad.cancel()
|
||||
@@ -3253,6 +3312,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
surfaceView.visibility = if (glassesOutputActive) View.INVISIBLE else View.VISIBLE
|
||||
Log.i(TAG, "compositor output attached: " +
|
||||
if (glassesOutputActive) "glasses display ${glassesScreen?.display?.displayId}" else "phone")
|
||||
if (pausedFrameWanted) uiHandler.postDelayed({
|
||||
if (pausedFrameWanted && pausedFrame == null && resumed) capturePausedFrame()
|
||||
}, PAUSED_FRAME_REDRAW_MS)
|
||||
}
|
||||
|
||||
private fun restorePhoneOutput(screen: GlassesScreen) {
|
||||
@@ -3512,6 +3574,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
const val EXTRA_STEAMVR = "steamVr"
|
||||
const val EXTRA_REPAIR_RUNTIME = "repairRuntime"
|
||||
private const val LEAVE_TIMEOUT_MS = 1_500L
|
||||
private const val PAUSED_FRAME_MAX_EDGE = 1920f
|
||||
private const val PAUSED_FRAME_REDRAW_MS = 150L
|
||||
private const val TAG = "SessionActivity"
|
||||
private var startedScreens = 0
|
||||
private const val FEX_MISSING = "droiddeck-fex: x86 Linux programs need"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.droiddeck.launcher.core
|
||||
|
||||
import com.droiddeck.launcher.gpu.GpuInfo
|
||||
import com.droiddeck.launcher.gpu.SocNames
|
||||
|
||||
object CpuAllocationDefaults {
|
||||
data class Allocation(val client: String, val game: String)
|
||||
|
||||
val device: Allocation? by lazy {
|
||||
forDevice(SocNames.model(), GpuInfo.systemProperty("ro.board.platform"),
|
||||
CpuCores.all.associateWith(CpuCores::maxGhz))
|
||||
}
|
||||
|
||||
fun forDevice(model: String, platform: String, frequencies: Map<Int, Double?>): Allocation? {
|
||||
val chip = model.trim().uppercase().substringBefore('-')
|
||||
val gen2 = chip in setOf("SM8550", "QCS8550") ||
|
||||
(chip.isEmpty() && platform.trim().equals("kalama", ignoreCase = true))
|
||||
if (!gen2 || frequencies.size != 8 || frequencies.keys.any { it < 0 } ||
|
||||
frequencies.values.any { it == null || !it.isFinite() || it <= 0.0 }) return null
|
||||
val clusters = frequencies.entries.groupBy { it.value }.toSortedMap(compareBy { it ?: 0.0 }).values.toList()
|
||||
if (clusters.map { it.size } != listOf(3, 4, 1)) return null
|
||||
return Allocation(
|
||||
client = CpuCores.format(clusters[2].map { it.key }.toSet()),
|
||||
game = CpuCores.format(clusters[1].map { it.key }.toSet()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,12 @@ object DeviceReport {
|
||||
h("Drivers")
|
||||
val gpu = com.droiddeck.launcher.gpu.GpuInfo.detect()
|
||||
k("GPU", "${gpu.name} · ${gpu.family.label} · ${gpu.supportText}" + if (gpu.oneUi8Gen2) " · One UI 8 Gen 2" else "")
|
||||
if (gpu.model == 740) k("TP UBWC hint", com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice() ?: "driver default")
|
||||
if (gpu.model == 740) {
|
||||
val hint = com.droiddeck.launcher.gpu.TpUbwcHint
|
||||
k("TP UBWC hint", hint.forThisDevice(context) ?: "driver default")
|
||||
k("TP UBWC mode", hint.activeMode ?: SessionPrefs.TP_UBWC_AUTO)
|
||||
if (hint.restartRequired(context)) k("TP UBWC pending", "${SessionPrefs.tpUbwcMode(context)} (restart required)")
|
||||
}
|
||||
k("Driver mode", SessionPrefs.gpuDriverMode(context) +
|
||||
(com.droiddeck.launcher.gpu.DriverPairs.recommendedKey(gpu, com.droiddeck.launcher.gpu.DriverPairs.from(
|
||||
com.droiddeck.launcher.gpu.TurnipReleases.cached(context)), com.droiddeck.launcher.gpu.TurnipReleases.autoPicks(context),
|
||||
|
||||
@@ -29,7 +29,7 @@ class FileManagerActivity : ComponentActivity() {
|
||||
setContent {
|
||||
DroidDeckTheme(appScale = com.droiddeck.launcher.core.AppUiPrefs.scale(this)) {
|
||||
Surface(modifier = Modifier.fillMaxSize().systemBarsPadding(), color = MaterialTheme.colorScheme.background) {
|
||||
FileManagerScreen(initialDir = initialDir)
|
||||
FileManagerScreen(initialDir = initialDir, onExit = { finish() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ExitToApp
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardReturn
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ContentPaste
|
||||
import androidx.compose.material.icons.filled.CreateNewFolder
|
||||
@@ -90,12 +92,18 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
|
||||
private enum class FileManagerNavigation { EXIT, RETURN_TO_START, BACK }
|
||||
|
||||
internal fun canNavigateUp(currentDir: File, currentRoot: File): Boolean =
|
||||
currentDir != currentRoot && currentDir.parentFile?.exists() == true
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FileManagerScreen(
|
||||
@@ -105,6 +113,7 @@ fun FileManagerScreen(
|
||||
initialDir: File? = null,
|
||||
pickerTitle: String? = null,
|
||||
onPick: ((File) -> Unit)? = null,
|
||||
onExit: (() -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -118,12 +127,14 @@ fun FileManagerScreen(
|
||||
|
||||
val pickPrefs = remember { context.getSharedPreferences("file_manager", android.content.Context.MODE_PRIVATE) }
|
||||
val browsePrefs = pickPrefs
|
||||
val rootDir = remember {
|
||||
val rootDir = remember(initialDir) {
|
||||
initialDir?.takeIf { it.isDirectory } ?: File("/storage/emulated/0")
|
||||
}
|
||||
|
||||
val launchDir = remember(initialDir, pickMode) {
|
||||
initialDir?.takeIf { !pickMode }
|
||||
}
|
||||
var currentDir by remember { mutableStateOf(rootDir) }
|
||||
var currentRoot by remember {
|
||||
val initialRoot = remember(rootDir) {
|
||||
val abs = rootDir.absolutePath
|
||||
val internal = "/storage/emulated/0"
|
||||
val vol = when {
|
||||
@@ -139,8 +150,9 @@ fun FileManagerScreen(
|
||||
?: rootDir
|
||||
}
|
||||
}
|
||||
mutableStateOf(vol)
|
||||
vol
|
||||
}
|
||||
var currentRoot by remember { mutableStateOf(initialRoot) }
|
||||
var entries by remember { mutableStateOf(listOf<File>()) }
|
||||
var selectedEntry by remember { mutableStateOf<File?>(null) }
|
||||
var showMenuFor by remember { mutableStateOf<File?>(null) }
|
||||
@@ -151,8 +163,10 @@ fun FileManagerScreen(
|
||||
var pendingConflict by remember { mutableStateOf<File?>(null) }
|
||||
var conflictChoice by remember { mutableStateOf<ConflictChoice?>(null) }
|
||||
var conflictApplyToAll by remember { mutableStateOf(false) }
|
||||
var operationJob by remember { mutableStateOf<kotlinx.coroutines.Job?>(null) }
|
||||
var cancelledJob by remember { mutableStateOf<kotlinx.coroutines.Job?>(null) }
|
||||
var operationJob by remember { mutableStateOf<Job?>(null) }
|
||||
var cancelledJob by remember { mutableStateOf<Job?>(null) }
|
||||
var pendingNavigation by remember { mutableStateOf<FileManagerNavigation?>(null) }
|
||||
var cancellingNavigation by remember { mutableStateOf(false) }
|
||||
var pendingBulkDelete by remember { mutableStateOf<List<File>>(emptyList()) }
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
val visibleEntries = if (searchQuery.isBlank()) entries
|
||||
@@ -178,9 +192,10 @@ fun FileManagerScreen(
|
||||
var showFavorites by remember { mutableStateOf(false) }
|
||||
var favTick by remember { mutableIntStateOf(0) }
|
||||
|
||||
var listingJob by remember { mutableStateOf<kotlinx.coroutines.Job?>(null) }
|
||||
var listingJob by remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
fun loadDirectory(dir: File, resetScroll: Boolean = true) {
|
||||
if (cancellingNavigation) return
|
||||
currentDir = dir
|
||||
if (pickMode) pickPrefs.edit().putString("lastFilePickerDir", dir.absolutePath).apply()
|
||||
listingJob?.cancel()
|
||||
@@ -205,25 +220,90 @@ fun FileManagerScreen(
|
||||
}
|
||||
|
||||
fun openDrive(dir: File) {
|
||||
if (cancellingNavigation) return
|
||||
currentRoot = dir
|
||||
loadDirectory(dir)
|
||||
}
|
||||
|
||||
fun returnToStart() {
|
||||
if (cancellingNavigation) return
|
||||
val dir = launchDir ?: return
|
||||
if (!dir.isDirectory) {
|
||||
Toast.makeText(context, R.string.fm_start_unavailable, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
showFavorites = false
|
||||
showSearch = false
|
||||
searchQuery = ""
|
||||
currentRoot = initialRoot
|
||||
loadDirectory(dir)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) { loadDirectory(rootDir) }
|
||||
|
||||
BackHandler(enabled = showFavorites || currentDir != currentRoot) {
|
||||
if (showFavorites) {
|
||||
showFavorites = false
|
||||
return@BackHandler
|
||||
val canGoUp = currentDir != currentRoot && currentDir.parentFile?.exists() == true
|
||||
fun isOperationActive() = isOperationRunning || operationJob?.isCompleted == false || cancelledJob?.isCompleted == false
|
||||
val operationActive = isOperationActive()
|
||||
|
||||
fun navigate(action: FileManagerNavigation) {
|
||||
if (cancellingNavigation) return
|
||||
when (action) {
|
||||
FileManagerNavigation.EXIT -> onExit?.invoke()
|
||||
FileManagerNavigation.RETURN_TO_START -> returnToStart()
|
||||
FileManagerNavigation.BACK -> {
|
||||
if (showFavorites) showFavorites = false
|
||||
else if (canNavigateUp(currentDir, currentRoot)) currentDir.parentFile?.let { loadDirectory(it) }
|
||||
else onExit?.invoke()
|
||||
}
|
||||
}
|
||||
val parent = currentDir.parentFile
|
||||
if (parent != null && parent.exists()) loadDirectory(parent)
|
||||
}
|
||||
|
||||
val canGoUp = currentDir != currentRoot && currentDir.parentFile?.exists() == true
|
||||
fun goUp() {
|
||||
val parent = currentDir.parentFile
|
||||
if (currentDir != currentRoot && parent != null && parent.exists()) loadDirectory(parent)
|
||||
fun requestNavigation(action: FileManagerNavigation) {
|
||||
if (cancellingNavigation) return
|
||||
if (isOperationActive()) pendingNavigation = action else navigate(action)
|
||||
}
|
||||
|
||||
BackHandler(enabled = operationActive || cancellingNavigation || showFavorites || currentDir != currentRoot) {
|
||||
requestNavigation(FileManagerNavigation.BACK)
|
||||
}
|
||||
|
||||
if (pendingNavigation != null) {
|
||||
OutlinedAlertDialog(
|
||||
onDismissRequest = { pendingNavigation = null },
|
||||
title = { Text(stringResource(R.string.fm_cancel_operation_title)) },
|
||||
text = { Text(stringResource(R.string.fm_cancel_operation_confirmation)) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
val action = pendingNavigation ?: return@TextButton
|
||||
pendingNavigation = null
|
||||
val job = operationJob?.takeUnless(Job::isCompleted)
|
||||
?: cancelledJob?.takeUnless(Job::isCompleted)
|
||||
if (job == null) {
|
||||
navigate(action)
|
||||
} else {
|
||||
cancellingNavigation = true
|
||||
cancelledJob = job
|
||||
job.cancel()
|
||||
scope.launch {
|
||||
job.join()
|
||||
if (operationJob === job) operationJob = null
|
||||
if (cancelledJob === job) cancelledJob = null
|
||||
pendingConflict = null
|
||||
isOperationRunning = false
|
||||
operationDeterminate = false
|
||||
cancellingNavigation = false
|
||||
if (action != FileManagerNavigation.EXIT) loadDirectory(currentDir, resetScroll = false)
|
||||
navigate(action)
|
||||
}
|
||||
}
|
||||
}) { Text(stringResource(R.string.fm_accept)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { pendingNavigation = null }) {
|
||||
Text(stringResource(R.string.fm_decline))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun uniqueDestination(dir: File, name: String): File {
|
||||
@@ -241,11 +321,14 @@ fun FileManagerScreen(
|
||||
}
|
||||
|
||||
fun performDelete(file: File) {
|
||||
scope.launch {
|
||||
if (isOperationActive()) return
|
||||
operationJob = scope.launch {
|
||||
isOperationRunning = true
|
||||
operationLabel = context.getString(R.string.fm_op_deleting)
|
||||
val ok = withContext(Dispatchers.IO) { FileOps.delete(file) }
|
||||
val job = coroutineContext[Job]
|
||||
val ok = withContext(Dispatchers.IO) { FileOps.delete(file) { job?.isActive == false } }
|
||||
isOperationRunning = false
|
||||
if (operationJob === job) operationJob = null
|
||||
loadDirectory(currentDir, resetScroll = false)
|
||||
if (!ok) Toast.makeText(context, R.string.fm_delete_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -260,7 +343,7 @@ fun FileManagerScreen(
|
||||
|
||||
fun performPaste() {
|
||||
val sources = clipboardFiles
|
||||
if (sources.isEmpty() || isOperationRunning || operationJob?.isActive == true || cancelledJob?.isCompleted == false) return
|
||||
if (sources.isEmpty() || isOperationActive()) return
|
||||
val dstDir = currentDir
|
||||
val cut = isCutOperation
|
||||
|
||||
@@ -269,7 +352,7 @@ fun FileManagerScreen(
|
||||
operationDeterminate = true
|
||||
operationLabel = context.getString(if (cut) R.string.fm_op_moving else R.string.fm_op_copying)
|
||||
isOperationRunning = true
|
||||
val job = coroutineContext[kotlinx.coroutines.Job]
|
||||
val job = coroutineContext[Job]
|
||||
|
||||
var applyToAll: ConflictChoice? = null
|
||||
var failed = 0
|
||||
@@ -322,7 +405,7 @@ fun FileManagerScreen(
|
||||
|
||||
isOperationRunning = false
|
||||
operationDeterminate = false
|
||||
operationJob = null
|
||||
if (operationJob === job) operationJob = null
|
||||
clipboardFiles = emptyList()
|
||||
isCutOperation = false
|
||||
selectionMode = false
|
||||
@@ -340,32 +423,38 @@ fun FileManagerScreen(
|
||||
}
|
||||
|
||||
fun performRename(file: File, newName: String) {
|
||||
if (isOperationActive()) return
|
||||
val target = File(file.parentFile, newName)
|
||||
if (target.exists()) {
|
||||
Toast.makeText(context, context.getString(R.string.fm_already_exists, newName), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
operationJob = scope.launch {
|
||||
val job = coroutineContext[Job]
|
||||
isOperationRunning = true
|
||||
operationLabel = context.getString(R.string.fm_op_renaming)
|
||||
val ok = withContext(Dispatchers.IO) { file.renameTo(target) }
|
||||
isOperationRunning = false
|
||||
if (operationJob === job) operationJob = null
|
||||
loadDirectory(currentDir, resetScroll = false)
|
||||
if (!ok) Toast.makeText(context, R.string.fm_rename_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun createFolder(parent: File, name: String) {
|
||||
if (isOperationActive()) return
|
||||
val target = File(parent, name)
|
||||
if (target.exists()) {
|
||||
Toast.makeText(context, context.getString(R.string.fm_already_exists, name), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
scope.launch {
|
||||
operationJob = scope.launch {
|
||||
val job = coroutineContext[Job]
|
||||
isOperationRunning = true
|
||||
operationLabel = context.getString(R.string.fm_op_creating_folder)
|
||||
val ok = withContext(Dispatchers.IO) { target.mkdirs() }
|
||||
isOperationRunning = false
|
||||
if (operationJob === job) operationJob = null
|
||||
loadDirectory(currentDir, resetScroll = false)
|
||||
if (!ok) Toast.makeText(context, R.string.fm_create_folder_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@@ -481,18 +570,20 @@ fun FileManagerScreen(
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (isOperationActive()) return@TextButton
|
||||
pendingBulkDelete = emptyList()
|
||||
selectionMode = false
|
||||
selectedPaths = emptySet()
|
||||
operationJob = scope.launch {
|
||||
isOperationRunning = true
|
||||
val job = coroutineContext[Job]
|
||||
var failed = 0
|
||||
victims.forEachIndexed { i, f ->
|
||||
operationLabel = context.getString(R.string.fm_op_deleting_batch, i + 1, victims.size, f.name)
|
||||
if (!withContext(Dispatchers.IO) { FileOps.delete(f) }) failed++
|
||||
if (!withContext(Dispatchers.IO) { FileOps.delete(f) { job?.isActive == false } }) failed++
|
||||
}
|
||||
isOperationRunning = false
|
||||
operationJob = null
|
||||
if (operationJob === job) operationJob = null
|
||||
loadDirectory(currentDir, resetScroll = false)
|
||||
if (failed > 0) {
|
||||
Toast.makeText(context, context.resources.getQuantityString(R.plurals.fm_delete_some_failed, failed, failed), Toast.LENGTH_SHORT).show()
|
||||
@@ -582,7 +673,7 @@ fun FileManagerScreen(
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
) {
|
||||
IconButton(onClick = { goUp() }, enabled = canGoUp) {
|
||||
IconButton(onClick = { requestNavigation(FileManagerNavigation.BACK) }, enabled = canGoUp && !cancellingNavigation) {
|
||||
Icon(Icons.Filled.ArrowBack, stringResource(R.string.fm_back), tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
|
||||
@@ -672,6 +763,13 @@ fun FileManagerScreen(
|
||||
)
|
||||
}
|
||||
|
||||
if (launchDir != null) {
|
||||
IconButton(onClick = { requestNavigation(FileManagerNavigation.RETURN_TO_START) }, enabled = !cancellingNavigation) {
|
||||
Icon(Icons.AutoMirrored.Filled.KeyboardReturn, stringResource(R.string.fm_return_to_start), tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
if (LocalConfiguration.current.screenWidthDp >= 700) Spacer(Modifier.width(56.dp))
|
||||
}
|
||||
|
||||
if (!showFavorites) {
|
||||
if (!pickMode) {
|
||||
OutlinedButton(
|
||||
@@ -747,6 +845,11 @@ fun FileManagerScreen(
|
||||
Icon(Icons.Filled.StarBorder, stringResource(R.string.fm_show_favorites), tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
if (!pickMode && onExit != null) {
|
||||
IconButton(onClick = { requestNavigation(FileManagerNavigation.EXIT) }, enabled = !cancellingNavigation) {
|
||||
Icon(Icons.AutoMirrored.Filled.ExitToApp, stringResource(R.string.fm_exit), tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showSearch && !showFavorites) {
|
||||
@@ -986,7 +1089,7 @@ fun FileManagerScreen(
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
) {
|
||||
if (canGoUp) item(key = "..", span = { GridItemSpan(maxLineSpan) }) {
|
||||
ParentFolderRow(compact = compactRows, onTap = { goUp() })
|
||||
ParentFolderRow(compact = compactRows, onTap = { requestNavigation(FileManagerNavigation.BACK) })
|
||||
}
|
||||
items(shownEntries, key = { it.absolutePath }) { file ->
|
||||
val isFav = remember(file.absolutePath, favTick) {
|
||||
@@ -1052,7 +1155,7 @@ fun FileManagerScreen(
|
||||
} else
|
||||
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
|
||||
if (canGoUp) item(key = "..") {
|
||||
ParentFolderRow(compact = compactRows, onTap = { goUp() })
|
||||
ParentFolderRow(compact = compactRows, onTap = { requestNavigation(FileManagerNavigation.BACK) })
|
||||
}
|
||||
if (entries.isEmpty()) {
|
||||
item {
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
object FileOps {
|
||||
private const val TAG = "FileOps"
|
||||
@@ -24,24 +25,27 @@ object FileOps {
|
||||
|
||||
fun isSymlink(file: File): Boolean = try { Files.isSymbolicLink(file.toPath()) } catch (e: Exception) { false }
|
||||
|
||||
fun delete(target: File?): Boolean {
|
||||
fun delete(target: File?, isCancelled: () -> Boolean = { false }): Boolean {
|
||||
if (isCancelled()) throw CancellationException("Delete cancelled: $target")
|
||||
if (target == null) return false
|
||||
if (target.isDirectory && !isSymlink(target)) {
|
||||
if (!clear(target)) return false
|
||||
if (!clear(target, isCancelled)) return false
|
||||
}
|
||||
if (isCancelled()) throw CancellationException("Delete cancelled: $target")
|
||||
return target.delete()
|
||||
}
|
||||
|
||||
fun clear(dir: File): Boolean {
|
||||
fun clear(dir: File, isCancelled: () -> Boolean = { false }): Boolean {
|
||||
val children = dir.listFiles() ?: return true
|
||||
var ok = true
|
||||
for (child in children) if (!delete(child)) ok = false
|
||||
for (child in children) if (!delete(child, isCancelled)) ok = false
|
||||
return ok
|
||||
}
|
||||
|
||||
fun totalSize(f: File?): Long {
|
||||
fun totalSize(f: File?, isCancelled: () -> Boolean = { false }): Long {
|
||||
if (isCancelled()) throw CancellationException("Size scan cancelled: $f")
|
||||
if (f == null || !f.exists() || isSymlink(f)) return 0
|
||||
if (f.isDirectory) return f.listFiles()?.sumOf { totalSize(it) } ?: 0
|
||||
if (f.isDirectory) return f.listFiles()?.sumOf { totalSize(it, isCancelled) } ?: 0
|
||||
return f.length()
|
||||
}
|
||||
|
||||
@@ -53,12 +57,12 @@ object FileOps {
|
||||
return true
|
||||
}
|
||||
if (!copyWithProgress(src, dst, progress, isCancelled)) return false
|
||||
if (isCancelled()) return false
|
||||
// Once the destination is committed, finish deleting the source.
|
||||
return delete(src)
|
||||
}
|
||||
|
||||
fun copyWithProgress(src: File, dst: File, progress: ProgressCallback?, isCancelled: () -> Boolean = { false }): Boolean {
|
||||
val total = maxOf(totalSize(src), 1L)
|
||||
val total = maxOf(totalSize(src, isCancelled), 1L)
|
||||
return copyWithProgress(src, dst, longArrayOf(0), total, progress, isCancelled)
|
||||
}
|
||||
|
||||
@@ -71,15 +75,23 @@ object FileOps {
|
||||
return false
|
||||
}
|
||||
if (src.isDirectory) {
|
||||
if (!dst.exists() && !dst.mkdirs()) return false
|
||||
var allOk = true
|
||||
src.list()?.forEach { name ->
|
||||
if (!copyWithProgress(File(src, name), File(dst, name), done, total, progress, isCancelled)) {
|
||||
Log.e(TAG, "Failed to copy: ${File(src, name)}")
|
||||
allOk = false
|
||||
val created = if (dst.exists()) false else {
|
||||
if (!dst.mkdirs()) return false
|
||||
true
|
||||
}
|
||||
try {
|
||||
var allOk = true
|
||||
src.list()?.forEach { name ->
|
||||
if (!copyWithProgress(File(src, name), File(dst, name), done, total, progress, isCancelled)) {
|
||||
Log.e(TAG, "Failed to copy: ${File(src, name)}")
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
return allOk
|
||||
} catch (e: CancellationException) {
|
||||
if (created) delete(dst)
|
||||
throw e
|
||||
}
|
||||
return allOk
|
||||
}
|
||||
val parent = dst.parentFile
|
||||
if (!src.exists() || (parent != null && !parent.exists() && !parent.mkdirs())) return false
|
||||
@@ -118,6 +130,7 @@ object FileOps {
|
||||
val size = input.size()
|
||||
var position = 0L
|
||||
while (position < size) {
|
||||
if (isCancelled()) throw CancellationException("Copy cancelled: $src")
|
||||
val transferred = input.transferTo(position, minOf(chunk, size - position), channel)
|
||||
if (transferred <= 0) break
|
||||
position += transferred
|
||||
@@ -125,12 +138,19 @@ object FileOps {
|
||||
progress?.onProgress(done[0], total)
|
||||
}
|
||||
if (position < size) throw IOException("Incomplete copy: $position/$size bytes")
|
||||
if (size > 0 && isCancelled()) throw CancellationException("Copy cancelled: $src")
|
||||
}
|
||||
}
|
||||
staging?.let { directory ->
|
||||
val copied = File(directory, "file").toPath()
|
||||
if (permissions != null && Files.getPosixFilePermissions(copied) != permissions) {
|
||||
Files.setPosixFilePermissions(copied, permissions)
|
||||
if (permissions != null) {
|
||||
try {
|
||||
if (Files.getPosixFilePermissions(copied) != permissions) {
|
||||
Files.setPosixFilePermissions(copied, permissions)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to restore destination permissions: $target", e)
|
||||
}
|
||||
}
|
||||
if (isCancelled()) throw java.util.concurrent.CancellationException("Copy cancelled: $src")
|
||||
Files.move(copied, target.toPath(), java.nio.file.StandardCopyOption.ATOMIC_MOVE)
|
||||
|
||||
@@ -50,6 +50,7 @@ class FilePickerActivity : ComponentActivity() {
|
||||
setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_SELECTED_FILE, file.absolutePath))
|
||||
finish()
|
||||
},
|
||||
onExit = { finish() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ object AddedGames {
|
||||
|
||||
class FolderResult(val added: List<Game>, val already: Int)
|
||||
|
||||
fun addFolder(context: Context, dir: File, onGame: (Game) -> Unit = {}, onChecking: (String?) -> Unit = {}): FolderResult {
|
||||
fun addFolder(context: Context, dir: File, onGame: (Game) -> Unit = {}, onChecking: (String?) -> Unit = {}, showHidden: Boolean = false): FolderResult {
|
||||
val app = context.applicationContext
|
||||
// Binds are mounted at session start, so a new one reaches the session from the next start.
|
||||
if (guestPath(app, dir) == null) bindFolder(app, dir)
|
||||
@@ -283,7 +283,7 @@ object AddedGames {
|
||||
var already = 0
|
||||
val fresh = ArrayList<String>()
|
||||
val added = ArrayList<Game>()
|
||||
for (sub in dir.listFiles { f -> f.isDirectory && !f.name.startsWith(".") }?.sortedBy { it.name.lowercase() }.orEmpty()) {
|
||||
for (sub in dir.listFiles { f -> f.isDirectory && (showHidden || !f.name.startsWith(".")) }?.sortedBy { it.name.lowercase() }.orEmpty()) {
|
||||
val path = canonical(sub)
|
||||
if (listed.any { it == path || inside(path, it) || inside(it, path) }) { already++; continue }
|
||||
onChecking(sub.name)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.droiddeck.launcher.gpu
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
|
||||
object TpUbwcHint {
|
||||
const val ENV = "FD_DEV_FEATURES"
|
||||
@@ -8,31 +10,43 @@ object TpUbwcHint {
|
||||
|
||||
@Volatile private var resolved = false
|
||||
@Volatile private var cached: String? = null
|
||||
@Volatile var activeMode: String? = null
|
||||
private set
|
||||
|
||||
// On Adreno 740, TPL1_DBG_ECO_CNTL1.TP_UBWC_FLAG_HINT is shared by every driver on the GPU and must
|
||||
// match the system driver: blob v6xx leaves it off, newer blobs set it. A mismatch corrupts Android UI.
|
||||
// match the system driver. A mismatch corrupts Android UI.
|
||||
// Samsung's One UI firmware sets it whatever blob version it reports.
|
||||
internal fun features(model: Int, systemDriverVersion: String?, samsung: Boolean = false): String? {
|
||||
internal fun features(model: Int, systemDriverVersion: String?, samsung: Boolean = false,
|
||||
mode: String = SessionPrefs.TP_UBWC_AUTO): String? {
|
||||
if (model != 740) return null
|
||||
if (mode == SessionPrefs.TP_UBWC_ON) return "enable_tp_ubwc_flag_hint=1"
|
||||
if (mode == SessionPrefs.TP_UBWC_OFF) return "enable_tp_ubwc_flag_hint=0"
|
||||
if (samsung) return "enable_tp_ubwc_flag_hint=1"
|
||||
val blob = systemDriverVersion?.split('.')?.getOrNull(1)?.toIntOrNull() ?: return null
|
||||
return "enable_tp_ubwc_flag_hint=" + if (blob >= 700) "1" else "0"
|
||||
val parts = systemDriverVersion?.split('.') ?: return null
|
||||
val blob = parts.getOrNull(1)?.toIntOrNull() ?: return null
|
||||
val patch = parts.getOrNull(2)?.toIntOrNull() ?: 0
|
||||
val on = blob >= 700 || (blob == 676 && patch >= 76)
|
||||
return "enable_tp_ubwc_flag_hint=" + if (on) "1" else "0"
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun forThisDevice(): String? {
|
||||
fun forThisDevice(context: Context): String? {
|
||||
if (resolved) return cached
|
||||
val mode = SessionPrefs.tpUbwcMode(context)
|
||||
val gpu = GpuInfo.detect()
|
||||
val model = gpu.model
|
||||
val version = if (model == 740) VulkanInfo.query()["driver_version"] else null
|
||||
cached = features(model, version, gpu.oneUi8Gen2)
|
||||
cached = features(model, version, gpu.oneUi8Gen2, mode)
|
||||
activeMode = mode
|
||||
resolved = true
|
||||
Log.i(TAG, "Adreno $model${if (gpu.oneUi8Gen2) " (One UI)" else ""}, system driver ${version ?: "n/a"}: ${cached ?: "driver default"}")
|
||||
Log.i(TAG, "Adreno $model${if (gpu.oneUi8Gen2) " (One UI)" else ""}, system driver ${version ?: "n/a"}, mode $mode: ${cached ?: "driver default"}")
|
||||
return cached
|
||||
}
|
||||
|
||||
fun applyToProcess() {
|
||||
val value = forThisDevice() ?: return
|
||||
fun restartRequired(context: Context): Boolean = resolved && activeMode != SessionPrefs.tpUbwcMode(context)
|
||||
|
||||
fun applyToProcess(context: Context) {
|
||||
val value = forThisDevice(context) ?: return
|
||||
runCatching { android.system.Os.setenv(ENV, value, true) }
|
||||
.onFailure { Log.w(TAG, "setenv $ENV failed: ${it.javaClass.simpleName}") }
|
||||
}
|
||||
|
||||
@@ -301,15 +301,18 @@ class LinuxNetworkLinkComponent(
|
||||
}
|
||||
}
|
||||
|
||||
private fun seededMac(): String {
|
||||
val id = Settings.Secure.getString(appContext.contentResolver, Settings.Secure.ANDROID_ID) ?: "bannerlator"
|
||||
val bytes = java.security.MessageDigest.getInstance("SHA-1").digest(id.toByteArray())
|
||||
val b0 = (bytes[0].toInt() and 0xfc) or 0x02
|
||||
return listOf(b0, bytes[1].toInt(), bytes[2].toInt(), bytes[3].toInt(), bytes[4].toInt(), bytes[5].toInt())
|
||||
.joinToString(":") { "%02x".format(it and 0xff) }
|
||||
}
|
||||
private fun seededMac(): String =
|
||||
seededMac { Settings.Secure.getString(appContext.contentResolver, Settings.Secure.ANDROID_ID) }
|
||||
|
||||
companion object {
|
||||
internal fun seededMac(readId: () -> String?): String {
|
||||
val id = runCatching(readId).getOrNull() ?: "bannerlator"
|
||||
val bytes = java.security.MessageDigest.getInstance("SHA-1").digest(id.toByteArray())
|
||||
val b0 = (bytes[0].toInt() and 0xfc) or 0x02
|
||||
return listOf(b0, bytes[1].toInt(), bytes[2].toInt(), bytes[3].toInt(), bytes[4].toInt(), bytes[5].toInt())
|
||||
.joinToString(":") { "%02x".format(it and 0xff) }
|
||||
}
|
||||
|
||||
private const val TAG = "LinuxNetworkLink"
|
||||
private const val LINK_FILE = "etc/droiddeck-net"
|
||||
private const val LINK_INDEX = 2
|
||||
|
||||
@@ -721,13 +721,19 @@ object ComponentsManager {
|
||||
class StoredOriginal(val protonId: String, val protonName: String?, val comp: String, val protonVersion: String, val size: Long, val kept: Boolean)
|
||||
class Stored(val packages: List<StoredPackage>, val originals: List<StoredOriginal>)
|
||||
|
||||
private fun packagesInUse(state: JSONObject): Set<String> {
|
||||
internal fun liveSelection(key: String, entry: JSONObject, proton: Proton?): Boolean =
|
||||
proton != null && (key == "queued" || entry.optString("protonVersion") == proton.version)
|
||||
|
||||
private fun packagesInUse(state: JSONObject, installed: Map<String, Proton>): Set<String> {
|
||||
val names = HashSet<String>()
|
||||
for (key in listOf("active", "queued")) {
|
||||
val byProton = state.optJSONObject(key) ?: continue
|
||||
for (pid in byProton.keys()) {
|
||||
val comps = byProton.optJSONObject(pid) ?: continue
|
||||
for (comp in comps.keys()) comps.optJSONObject(comp)?.optString("file")?.takeIf { it.isNotEmpty() }?.let(names::add)
|
||||
for (comp in comps.keys()) {
|
||||
val entry = comps.optJSONObject(comp) ?: continue
|
||||
if (liveSelection(key, entry, installed[pid])) entry.optString("file").takeIf { it.isNotEmpty() }?.let(names::add)
|
||||
}
|
||||
}
|
||||
}
|
||||
val profiles = state.optJSONObject("profiles")
|
||||
@@ -747,8 +753,8 @@ object ComponentsManager {
|
||||
|
||||
fun stored(context: Context): Stored = synchronized(lock) {
|
||||
val state = loadState(context)
|
||||
val used = packagesInUse(state)
|
||||
val installed = protons(context).associateBy { it.id }
|
||||
val installed = protons(context).distinctBy { it.id }.associateBy { it.id }
|
||||
val used = packagesInUse(state, installed)
|
||||
val originals = originalsDir(context).listFiles()?.filter { it.isDirectory }?.flatMap { proton ->
|
||||
proton.listFiles()?.filter { it.isDirectory }?.flatMap { version ->
|
||||
COMPONENTS.mapNotNull { comp ->
|
||||
@@ -777,24 +783,36 @@ object ComponentsManager {
|
||||
fun deletePackage(context: Context, file: String): String = synchronized(lock) {
|
||||
val name = safeName(file)
|
||||
val state = loadState(context)
|
||||
val queued = state.sub("queued")
|
||||
for (pid in queued.keys()) {
|
||||
val comps = queued.optJSONObject(pid) ?: continue
|
||||
for (comp in comps.keys()) check(comps.optJSONObject(comp)?.optString("file") != name) { context.getString(R.string.cmgr_package_in_use, name, pid) }
|
||||
}
|
||||
val active = state.sub("active")
|
||||
for (pid in active.keys()) {
|
||||
val comps = active.optJSONObject(pid) ?: continue
|
||||
for (comp in comps.keys()) check(comps.optJSONObject(comp)?.optString("file") != name) { context.getString(R.string.cmgr_package_in_use, name, pid) }
|
||||
}
|
||||
val profiles = state.sub("profiles")
|
||||
for (scope in profiles.keys()) {
|
||||
val profile = profiles.optJSONObject(scope) ?: continue
|
||||
check(COMPONENTS.none { profile.optString(it) == name }) { context.getString(R.string.cmgr_package_in_use, name, scope) }
|
||||
val installed = protons(context).distinctBy { it.id }.associateBy { it.id }
|
||||
var changed = false
|
||||
for (key in listOf("queued", "active")) {
|
||||
val byProton = state.sub(key)
|
||||
for (pid in byProton.keys().asSequence().toList()) {
|
||||
val comps = byProton.optJSONObject(pid) ?: continue
|
||||
for (comp in comps.keys().asSequence().toList()) {
|
||||
val entry = comps.optJSONObject(comp) ?: continue
|
||||
if (entry.optString("file") != name) continue
|
||||
check(!liveSelection(key, entry, installed[pid])) { context.getString(R.string.cmgr_package_in_use, name, installed[pid]?.name ?: pid) }
|
||||
comps.remove(comp)
|
||||
changed = true
|
||||
}
|
||||
if (comps.length() == 0) byProton.remove(pid)
|
||||
}
|
||||
}
|
||||
val f = File(packagesDir(context), name)
|
||||
check(f.isFile) { context.getString(R.string.cmgr_package_not_stored, name) }
|
||||
f.delete()
|
||||
val profiles = state.sub("profiles")
|
||||
for (scope in profiles.keys().asSequence().toList()) {
|
||||
val profile = profiles.optJSONObject(scope) ?: continue
|
||||
for (comp in COMPONENTS.filter { profile.optString(it) == name }) {
|
||||
rememberProfiledComponent(state, comp)
|
||||
profile.remove(comp)
|
||||
changed = true
|
||||
}
|
||||
if (profile.length() == 0) profiles.remove(scope)
|
||||
}
|
||||
check(f.delete()) { name }
|
||||
if (changed) saveState(context, state)
|
||||
context.getString(R.string.cmgr_deleted, name)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ object GpuClockPin {
|
||||
pinned = true
|
||||
"gpu clock: held at its top"
|
||||
} catch (t: Throwable) {
|
||||
File(context.filesDir, MARKER).delete()
|
||||
Log.w(TAG, "pin", t)
|
||||
"gpu clock: pin failed"
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ object SessionFiles {
|
||||
"usr/local/bin/droiddeck-steam-install" to "usr/local/bin/droiddeck-steam-install",
|
||||
"usr/local/bin/droiddeck-steam-ui-scale" to "usr/local/bin/droiddeck-steam-ui-scale",
|
||||
"usr/local/bin/droiddeck-steam-language" to "usr/local/bin/droiddeck-steam-language",
|
||||
"usr/local/bin/droiddeck-steam-shaders" to "usr/local/bin/droiddeck-steam-shaders",
|
||||
"usr/local/bin/droiddeck-steam-desktop-ui" to "usr/local/bin/droiddeck-steam-desktop-ui",
|
||||
"usr/local/bin/droiddeck-steam-library" to "usr/local/bin/droiddeck-steam-library",
|
||||
"usr/local/bin/droiddeck-seed-redists" to "usr/local/bin/droiddeck-seed-redists",
|
||||
@@ -78,6 +79,7 @@ object SessionFiles {
|
||||
"usr/local/bin/droiddeck-steam-games" to "usr/local/bin/droiddeck-steam-games",
|
||||
"usr/local/bin/droiddeck-pad-defaults" to "usr/local/bin/droiddeck-pad-defaults",
|
||||
"usr/local/bin/droiddeck-steam-x64" to "usr/local/bin/droiddeck-steam-x64",
|
||||
"usr/local/bin/droiddeck-steamrt-shim" to "usr/local/bin/droiddeck-steamrt-shim",
|
||||
"usr/local/bin/droiddeck-x64-arm-proton" to "usr/local/bin/droiddeck-x64-arm-proton",
|
||||
"usr/local/bin/droiddeck-x64-arm-webhelper" to "usr/local/bin/droiddeck-x64-arm-webhelper",
|
||||
"usr/local/bin/droiddeck-x64-webhelper" to "usr/local/bin/droiddeck-x64-webhelper",
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import androidx.annotation.StringRes
|
||||
import com.droiddeck.launcher.R
|
||||
import com.droiddeck.launcher.core.CpuAllocationDefaults
|
||||
import com.droiddeck.launcher.core.GameEnvironment
|
||||
import com.droiddeck.launcher.core.TextureFiltering
|
||||
import com.droiddeck.launcher.gpu.Cardboard
|
||||
@@ -50,9 +49,9 @@ object SessionPrefs {
|
||||
)
|
||||
|
||||
fun orientation(context: Context): Int =
|
||||
prefs(context).getInt("orientation", ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
prefs(context).getInt("orientation", ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE)
|
||||
.takeIf { value -> orientationOptions.any { it.first == value } }
|
||||
?: ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
?: ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
|
||||
fun setOrientation(context: Context, orientation: Int) {
|
||||
require(orientationOptions.any { it.first == orientation })
|
||||
@@ -263,6 +262,19 @@ object SessionPrefs {
|
||||
const val GPU_DRIVERS_MANUAL = "manual"
|
||||
const val GPU_DRIVERS_MIX = "mix"
|
||||
|
||||
const val TP_UBWC_AUTO = "auto"
|
||||
const val TP_UBWC_ON = "on"
|
||||
const val TP_UBWC_OFF = "off"
|
||||
|
||||
fun tpUbwcMode(context: Context): String =
|
||||
prefs(context).getString("tpUbwcMode", TP_UBWC_AUTO)
|
||||
?.takeIf { it in setOf(TP_UBWC_AUTO, TP_UBWC_ON, TP_UBWC_OFF) } ?: TP_UBWC_AUTO
|
||||
|
||||
fun setTpUbwcMode(context: Context, mode: String) {
|
||||
require(mode in setOf(TP_UBWC_AUTO, TP_UBWC_ON, TP_UBWC_OFF))
|
||||
prefs(context).edit().putString("tpUbwcMode", mode).commit()
|
||||
}
|
||||
|
||||
fun gpuDriverMode(context: Context): String =
|
||||
prefs(context).getString("gpuDriverMode", GPU_DRIVERS_AUTO) ?: GPU_DRIVERS_AUTO
|
||||
|
||||
@@ -304,6 +316,34 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean("returnAfterGame", on).apply()
|
||||
}
|
||||
|
||||
private fun shaderPrecacheApplied(context: Context) =
|
||||
java.io.File(com.droiddeck.launcher.runtime.LinuxRuntime.rootDir(context), "root/.bl-shader-precache-applied")
|
||||
|
||||
/**
|
||||
* Shader pre-caching as the launcher last set it, until a session reports it written into the
|
||||
* client's config.vdf; after that the client's own toggle is the user's again.
|
||||
*/
|
||||
fun pendingShaderPrecache(context: Context): Boolean? {
|
||||
val prefs = prefs(context)
|
||||
if (!prefs.contains("shaderPrecache")) return null
|
||||
val on = prefs.getBoolean("shaderPrecache", true)
|
||||
val applied = shaderPrecacheApplied(context)
|
||||
val written = runCatching { applied.readText().trim() }.getOrNull() ?: return on
|
||||
applied.delete()
|
||||
if (written != if (on) "1" else "0") return on
|
||||
prefs.edit().remove("shaderPrecache").apply()
|
||||
return null
|
||||
}
|
||||
|
||||
fun setShaderPrecache(context: Context, on: Boolean) {
|
||||
shaderPrecacheApplied(context).delete()
|
||||
prefs(context).edit().putBoolean("shaderPrecache", on).apply()
|
||||
}
|
||||
|
||||
fun shaderPrecache(context: Context): Boolean = pendingShaderPrecache(context)
|
||||
?: runCatching { com.droiddeck.launcher.storage.SteamStorage.shaderPrecache(
|
||||
java.io.File(steamRoot(context, steamClientArch(context)), "config/config.vdf").readText()) }.getOrDefault(true)
|
||||
|
||||
fun stretch16x9(context: Context): Boolean = prefs(context).getBoolean("stretch16x9", false)
|
||||
|
||||
fun setStretch16x9(context: Context, on: Boolean) {
|
||||
@@ -341,30 +381,19 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean("wifiDiscoveryAsked", true).apply()
|
||||
}
|
||||
|
||||
internal data class CoreAllocation(val clientOverride: Boolean, val clientCpus: String, val gameCpus: String)
|
||||
|
||||
internal fun coreAllocation(context: Context, defaults: CpuAllocationDefaults.Allocation? = CpuAllocationDefaults.device): CoreAllocation {
|
||||
val saved = prefs(context)
|
||||
return CoreAllocation(
|
||||
saved.getBoolean("clientCpusOverride", defaults != null && !saved.contains("clientCpus")),
|
||||
saved.getString("clientCpus", defaults?.client.orEmpty()).orEmpty(),
|
||||
saved.getString("gameCpus", defaults?.game.orEmpty()).orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
fun clientCpusOverride(context: Context): Boolean = coreAllocation(context).clientOverride
|
||||
fun clientCpusOverride(context: Context): Boolean = prefs(context).getBoolean("clientCpusOverride", false)
|
||||
|
||||
fun setClientCpusOverride(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("clientCpusOverride", on).apply()
|
||||
}
|
||||
|
||||
fun clientCpus(context: Context): String = coreAllocation(context).clientCpus
|
||||
fun clientCpus(context: Context): String = prefs(context).getString("clientCpus", "").orEmpty()
|
||||
|
||||
fun setClientCpus(context: Context, list: String) {
|
||||
prefs(context).edit().putString("clientCpus", list).apply()
|
||||
}
|
||||
|
||||
fun gameCpus(context: Context): String = coreAllocation(context).gameCpus
|
||||
fun gameCpus(context: Context): String = prefs(context).getString("gameCpus", "").orEmpty()
|
||||
|
||||
fun setGameCpus(context: Context, list: String) {
|
||||
prefs(context).edit().putString("gameCpus", list).apply()
|
||||
@@ -376,7 +405,7 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean("noXalia", on).apply()
|
||||
}
|
||||
|
||||
fun fastSync(context: Context): Boolean = prefs(context).getBoolean("fastSync", false)
|
||||
fun fastSync(context: Context): Boolean = prefs(context).getBoolean("fastSync", true)
|
||||
|
||||
fun setFastSync(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("fastSync", on).apply()
|
||||
@@ -411,6 +440,17 @@ object SessionPrefs {
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun applyNtsyncDefault(context: Context) {
|
||||
val prefs = prefs(context)
|
||||
if (prefs.getBoolean("ntsyncDefaultV1", false)) return
|
||||
prefs.edit()
|
||||
.putBoolean("fastSync", true)
|
||||
.putBoolean("fsyncFirst", false)
|
||||
.putBoolean("syncFallback", true)
|
||||
.putBoolean("ntsyncDefaultV1", true)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun syncFallback(context: Context): Boolean = prefs(context).getBoolean("syncFallback", true)
|
||||
|
||||
fun setSyncFallback(context: Context, on: Boolean) {
|
||||
@@ -444,6 +484,13 @@ object SessionPrefs {
|
||||
prefs(context).edit().putBoolean("prootFastPath", on).apply()
|
||||
}
|
||||
|
||||
/** gamescope draws the cursor itself. Off by default: on a phone that plane costs frames. */
|
||||
fun cursorComposite(context: Context): Boolean = prefs(context).getBoolean("cursorComposite", false)
|
||||
|
||||
fun setCursorComposite(context: Context, on: Boolean) {
|
||||
prefs(context).edit().putBoolean("cursorComposite", on).apply()
|
||||
}
|
||||
|
||||
const val DEFAULT_GUEST_HOSTNAME = "DroidDeck"
|
||||
|
||||
@JvmStatic
|
||||
@@ -764,6 +811,14 @@ object SessionPrefs {
|
||||
it.getInt("gradientSecondary", com.droiddeck.launcher.ui.Themes.DEFAULT_SECONDARY)
|
||||
}
|
||||
|
||||
/** How fast the Inner Gamer theme cycles its colours, 1 to 10. */
|
||||
fun rgbSpeed(context: Context): Int =
|
||||
prefs(context).getInt("innerGamerSpeed", com.droiddeck.launcher.ui.Themes.DEFAULT_RGB_SPEED).coerceIn(1, com.droiddeck.launcher.ui.Themes.RGB_SPEEDS)
|
||||
|
||||
fun setRgbSpeed(context: Context, speed: Int) {
|
||||
prefs(context).edit().putInt("innerGamerSpeed", speed.coerceIn(1, com.droiddeck.launcher.ui.Themes.RGB_SPEEDS)).apply()
|
||||
}
|
||||
|
||||
fun setGradientColors(context: Context, primary: Int, secondary: Int) {
|
||||
prefs(context).edit().putInt("gradientPrimary", primary).putInt("gradientSecondary", secondary).apply()
|
||||
}
|
||||
|
||||
@@ -335,6 +335,9 @@ class SessionService : Service() {
|
||||
suspendAttemptFailed = false
|
||||
suspendController = null
|
||||
SessionState.suspended = false
|
||||
SessionState.resumeRequested = false
|
||||
SessionState.resuming = false
|
||||
SessionState.resumeFailed = false
|
||||
SessionState.pipActive = false
|
||||
pipTask = false
|
||||
SessionState.program = intent?.getStringExtra(EXTRA_PROGRAM)
|
||||
@@ -401,7 +404,7 @@ class SessionService : Service() {
|
||||
|
||||
private fun runSession(gen: Int) {
|
||||
SessionTerminal.clear()
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice()
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice(this)
|
||||
try {
|
||||
LinuxRuntime.writeAccounts(this)
|
||||
} catch (e: Exception) {
|
||||
@@ -678,6 +681,7 @@ class SessionService : Service() {
|
||||
com.droiddeck.launcher.core.AppLanguage.system(this).country,
|
||||
))
|
||||
guest.add("TZ=" + java.util.TimeZone.getDefault().id)
|
||||
if (SessionState.mode == MODE_STEAM) SessionPrefs.pendingShaderPrecache(this)?.let { guest.add("BL_SHADER_PRECACHE=" + if (it) "1" else "0") }
|
||||
guest.add("XDG_RUNTIME_DIR=" + LinuxRuntime.GUEST_RUNTIME_DIR)
|
||||
guest.add("XDG_SESSION_TYPE=wayland")
|
||||
guest.add("WAYLAND_DISPLAY=wayland-0")
|
||||
@@ -700,7 +704,6 @@ class SessionService : Service() {
|
||||
val tuOverride = tuDebugOverride()
|
||||
val tuDriver = sysmemDriver(linuxDriverId)
|
||||
(tuOverride ?: "sysmem".takeIf { tuDriver || SessionPrefs.tuSysmem(this) })?.let { guest.add("TU_DEBUG=$it") }
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice()?.let { guest.add("${com.droiddeck.launcher.gpu.TpUbwcHint.ENV}=$it") }
|
||||
if (SessionPrefs.zinkLazy(this)) {
|
||||
guest.add("ZINK_DESCRIPTORS=lazy")
|
||||
guest.add("ZINK_DEBUG=compact")
|
||||
@@ -724,11 +727,13 @@ class SessionService : Service() {
|
||||
guest.add("BL_SYNC_FALLBACK=" + (if (SessionPrefs.syncFallback(this)) "1" else "0"))
|
||||
guest.add("BL_FSYNC_FIRST=" + (if (SessionPrefs.fsyncFirst(this)) "1" else "0"))
|
||||
guest.add("BL_GAMESCOPE_REALTIME=" + (if (SessionPrefs.gamescopeRealtime(this)) "1" else "0"))
|
||||
if (SessionPrefs.cursorComposite(this)) guest.add("BL_CURSOR_COMPOSITE=1")
|
||||
if (com.droiddeck.launcher.core.ZramSupport.supported()) guest.add("BL_ZRAM=1")
|
||||
guest.add("BANNER_AUDIO_DIRECT_DECAY=0")
|
||||
val extra = extraEnv() + com.droiddeck.launcher.agent.AgentEnv.beginSession(this)
|
||||
if (extra.none { it.startsWith("TU_DEBUG=") }) guest.add("$GAME_TU_DEBUG=" + (tuOverride ?: if (tuDriver) "sysmem" else ""))
|
||||
extra.forEach { guest.add(it) }
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.forThisDevice(this)?.let { guest.add("${com.droiddeck.launcher.gpu.TpUbwcHint.ENV}=$it") }
|
||||
val clientPinned = steamHere && SessionPrefs.clientCpusOverride(this)
|
||||
if (clientPinned) {
|
||||
guest.add("BL_CLIENT_CPUS=" + CpuCores.listOrAll(SessionPrefs.clientCpus(this)))
|
||||
@@ -1218,6 +1223,7 @@ class SessionService : Service() {
|
||||
}
|
||||
|
||||
private fun resumeSession(panelVisible: Boolean = true) {
|
||||
if (SessionState.suspended) SessionState.resumeRequested = true
|
||||
if (panelVisible) activityVisible = true
|
||||
screenOn = readScreenOn()
|
||||
manualPauseRequested = false
|
||||
@@ -1418,6 +1424,9 @@ class SessionService : Service() {
|
||||
val downloadsBlockingSuspend = downloadsKeepAwake() && hidden && !suspendDownloadNow
|
||||
if (suspendPolicy == SessionPrefs.SUSPEND_MANUAL && hidden && !downloadsBlockingSuspend) {
|
||||
manualPauseRequested = true
|
||||
} else if (!hidden && !SessionState.suspended) {
|
||||
// Coming back before the freeze finished keeps the game running instead of pausing it a moment later.
|
||||
manualPauseRequested = false
|
||||
}
|
||||
val controller = suspendController ?: return
|
||||
if (downloadsBlockingSuspend) acquireLocks()
|
||||
@@ -1443,6 +1452,7 @@ class SessionService : Service() {
|
||||
freezeCancelled = true
|
||||
controller.cancelFreeze()
|
||||
}
|
||||
if (shouldSuspend && SessionState.suspended && !suspendOperationPending) SessionState.resumeRequested = false
|
||||
if (suspendOperationPending || suspendAttemptFailed || shouldSuspend == SessionState.suspended) return
|
||||
val gen = sessionGen
|
||||
suspendOperationPending = true
|
||||
@@ -1466,6 +1476,7 @@ class SessionService : Service() {
|
||||
freezePending = false
|
||||
if (success) {
|
||||
SessionState.suspended = true
|
||||
SessionState.resumeFailed = false
|
||||
SessionEvents.transition(SessionPhase.SUSPENDED, "session.suspended")
|
||||
releaseLocks()
|
||||
refreshNotification()
|
||||
@@ -1484,16 +1495,22 @@ class SessionService : Service() {
|
||||
}
|
||||
} else {
|
||||
acquireLocks()
|
||||
SessionState.resuming = true
|
||||
controller.resume { success ->
|
||||
mainHandler.post {
|
||||
if (gen != sessionGen || !SessionState.running) return@post
|
||||
suspendOperationPending = false
|
||||
SessionState.resuming = false
|
||||
SessionState.resumeRequested = false
|
||||
if (success) {
|
||||
SessionState.suspended = false
|
||||
SessionState.resumeFailed = false
|
||||
SessionEvents.transition(SessionEvents.resumePhase(), "session.resumed")
|
||||
refreshNotification()
|
||||
} else {
|
||||
suspendAttemptFailed = true
|
||||
SessionState.resumeFailures++
|
||||
SessionState.resumeFailed = true
|
||||
Log.w(TAG, "could not confirm that the session resumed")
|
||||
}
|
||||
updateSuspendPolicy()
|
||||
@@ -1583,6 +1600,9 @@ class SessionService : Service() {
|
||||
return@post
|
||||
}
|
||||
SessionState.suspended = false
|
||||
SessionState.resumeRequested = false
|
||||
SessionState.resuming = false
|
||||
SessionState.resumeFailed = false
|
||||
SessionState.pipActive = false
|
||||
pipTask = false
|
||||
SessionState.guestPid = -1
|
||||
@@ -2047,6 +2067,7 @@ class SessionService : Service() {
|
||||
|
||||
fun resume(context: Context) {
|
||||
if (!SessionState.running) return
|
||||
if (SessionState.suspended) SessionState.resumeRequested = true
|
||||
context.startService(Intent(context, SessionService::class.java).setAction(ACTION_RESUME))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.droiddeck.launcher.session
|
||||
|
||||
import java.io.File
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
|
||||
@@ -46,6 +47,10 @@ object SessionState {
|
||||
@Volatile
|
||||
var runtimeDriver: String? = null
|
||||
var suspended by mutableStateOf(false)
|
||||
var resumeRequested by mutableStateOf(false)
|
||||
var resuming by mutableStateOf(false)
|
||||
var resumeFailures by mutableIntStateOf(0)
|
||||
var resumeFailed by mutableStateOf(false)
|
||||
@Volatile var pipActive = false
|
||||
@Volatile
|
||||
var program: String? = null
|
||||
|
||||
@@ -20,7 +20,7 @@ object SettingsReset {
|
||||
"gpuDriverMode", "androidDriver", "linuxDriver", "linuxDriver.steam", "linuxDriver.desktop",
|
||||
"tuSysmem", "glThread", "noGlError", "zinkLazy", "mangoapp", "gpuClockPin", "gamescopeRealtime",
|
||||
"directAudio", "mic", "wifiDiscovery", "clientCpusOverride", "clientCpus", "gameCpus",
|
||||
"noXalia", "fastSync", "fsyncFirst", "syncFallback", "prootNoSeccomp", "prootFastPath",
|
||||
"noXalia", "fastSync", "fsyncFirst", "syncFallback", "prootNoSeccomp", "prootFastPath", "cursorComposite",
|
||||
"guestHostname", "fexPreset", "forceSsbs", "logs", "storageDiagnostics",
|
||||
"upscaler", "upscaleSharpness", "screenEffects", "textureAnisotropy", "textureLodBias",
|
||||
"steamDownloadsInBackground", "steam_offline", "bootMovie", "bootMovieSound", "bootMovieVolume", "bootMovieSeconds",
|
||||
|
||||
@@ -28,6 +28,8 @@ object SteamStorage {
|
||||
private val LAST_PLAYED = Regex("^\\s*\"LastPlayed\"\\s*\"(\\d+)\"", RegexOption.MULTILINE)
|
||||
private val MAPPING = Regex("\"(\\d+)\"\\s*\\{\\s*\"name\"\\s*\"([^\"]*)\"")
|
||||
private val COMPAT_NAME = Regex("\"compat_tools\"\\s*\\{\\s*\"([^\"]+)\"")
|
||||
private val VDF_TOKEN = Regex("\"((?:\\\\.|[^\"\\\\])*)\"|[{}]|//[^\n]*")
|
||||
private val SHADER_CACHE_OFF = listOf("InstallConfigStore", "Software", "Valve", "Steam", "ShaderCacheManager", "DisableShaderCache")
|
||||
|
||||
data class Manifest(
|
||||
val file: File, val appId: Int, val name: String, val installDir: String?,
|
||||
@@ -118,6 +120,28 @@ object SteamStorage {
|
||||
return MAPPING.findAll(configVdf.substring(open, end)).associate { it.groupValues[1] to it.groupValues[2] }
|
||||
}
|
||||
|
||||
fun shaderPrecache(configVdf: String): Boolean = vdfValue(configVdf, SHADER_CACHE_OFF) != "1"
|
||||
|
||||
private fun vdfValue(text: String, path: List<String>): String? {
|
||||
val blocks = ArrayList<String>()
|
||||
var key: String? = null
|
||||
for (token in VDF_TOKEN.findAll(text)) {
|
||||
val name = key
|
||||
when {
|
||||
token.value.startsWith("//") -> {}
|
||||
token.value == "{" -> { blocks.add(name.orEmpty()); key = null }
|
||||
token.value == "}" -> { blocks.removeLastOrNull(); key = null }
|
||||
name == null -> key = token.groupValues[1]
|
||||
else -> {
|
||||
val at = blocks + name
|
||||
if (at.size == path.size && at.zip(path).all { (a, b) -> a.equals(b, ignoreCase = true) }) return token.groupValues[1]
|
||||
key = null
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun compatToolName(compatibilityToolVdf: String): String? = COMPAT_NAME.find(compatibilityToolVdf)?.groupValues?.get(1)
|
||||
|
||||
private val ARM_IN_NAME = Regex("arm64|aarch64")
|
||||
|
||||
@@ -308,6 +308,7 @@ internal fun AppDialog(
|
||||
width: Dp? = null, fitContent: Boolean = false,
|
||||
back: (() -> Unit)? = null,
|
||||
scrollable: Boolean = true,
|
||||
scrollState: androidx.compose.foundation.ScrollState = rememberScrollState(),
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
val pal = LocalPalette.current
|
||||
@@ -345,7 +346,7 @@ internal fun AppDialog(
|
||||
.blockTouches()
|
||||
.controllerBack(back ?: close)
|
||||
.then(modifier)
|
||||
.then(if (scrollable) Modifier.verticalScroll(rememberScrollState()) else Modifier)
|
||||
.then(if (scrollable) Modifier.verticalScroll(scrollState) else Modifier)
|
||||
.padding(horizontal = 18.dp, vertical = 16.dp),
|
||||
content = content,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -70,12 +71,15 @@ internal fun ColorPickerDialog(title: String, initial: Int, onDismiss: () -> Uni
|
||||
var hexText by remember { mutableStateOf(ThemeColors.hex(initial)) }
|
||||
val current = ThemeColors.fromHsv(hue, sat, value)
|
||||
val first = remember { FocusRequester() }
|
||||
val scroll = rememberScrollState()
|
||||
var typingHex by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { focusSoon { first } }
|
||||
LaunchedEffect(typingHex, scroll.maxValue) { if (typingHex) scroll.animateScrollTo(scroll.maxValue) }
|
||||
fun picked(h: Float = hue, s: Float = sat, v: Float = value) {
|
||||
hue = h.coerceIn(0f, 360f); sat = s.coerceIn(0f, 1f); value = v.coerceIn(0f, 1f)
|
||||
hexText = ThemeColors.hex(ThemeColors.fromHsv(hue, sat, value))
|
||||
}
|
||||
AppDialog(shown, close, "colorPicker", wide = false, maxWidth = 460.dp) {
|
||||
AppDialog(shown, close, "colorPicker", wide = false, maxWidth = 460.dp, scrollState = scroll) {
|
||||
DialogTitleRow(title, close)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(14.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
ColorWheel(hue, sat, value) { h, s -> picked(h = h, s = s) }
|
||||
@@ -95,6 +99,7 @@ internal fun ColorPickerDialog(title: String, initial: Int, onDismiss: () -> Uni
|
||||
ThemeColors.parseHex(text)?.let { argb -> ThemeColors.toHsv(argb).let { hsv -> hue = hsv.h; sat = hsv.s; value = hsv.v } }
|
||||
},
|
||||
label = "", keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done, placeholder = stringResource(R.string.theme_color_hex),
|
||||
onFocusChange = { typingHex = it },
|
||||
onDone = { ThemeColors.parseHex(hexText)?.let { onApply(it); close() } },
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
|
||||
@@ -27,6 +27,8 @@ import androidx.compose.material.icons.outlined.Image
|
||||
import androidx.compose.material.icons.outlined.InsertDriveFile
|
||||
import androidx.compose.material.icons.outlined.SdCard
|
||||
import androidx.compose.material.icons.outlined.Smartphone
|
||||
import androidx.compose.material.icons.outlined.Visibility
|
||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -81,8 +83,8 @@ internal class PickFolder(val dir: File, val readable: Boolean)
|
||||
|
||||
internal class PickListing(val folders: List<PickFolder>, val files: List<File>)
|
||||
|
||||
internal fun pickListing(dir: File, kind: PickKind): PickListing {
|
||||
val all = dir.listFiles()?.filter { !it.name.startsWith(".") }.orEmpty().sortedBy { it.name.lowercase() }
|
||||
internal fun pickListing(dir: File, kind: PickKind, showHidden: Boolean = false): PickListing {
|
||||
val all = dir.listFiles()?.filter { showHidden || !it.name.startsWith(".") }.orEmpty().sortedBy { it.name.lowercase() }
|
||||
return PickListing(
|
||||
all.filter { it.isDirectory }.map { PickFolder(it, it.canRead() && it.list() != null) },
|
||||
all.filter { it.isFile && it.extension.lowercase() in kind.extensions },
|
||||
@@ -92,11 +94,13 @@ internal fun pickListing(dir: File, kind: PickKind): PickListing {
|
||||
@Composable
|
||||
internal fun ExePickerDialog(
|
||||
title: String, kind: PickKind = PickKind.EXE, start: File? = null,
|
||||
onAddAll: ((File) -> Unit)? = null, onPick: (File) -> Unit, onDismiss: () -> Unit,
|
||||
onAddAll: ((File, Boolean) -> Unit)? = null, onPick: (File) -> Unit, onDismiss: () -> Unit,
|
||||
) {
|
||||
val ctx = LocalContext.current
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val density = LocalDensity.current
|
||||
val prefs = remember(ctx) { ctx.getSharedPreferences("game_picker", Context.MODE_PRIVATE) }
|
||||
var showHidden by remember { mutableStateOf(prefs.getBoolean("show_hidden", false)) }
|
||||
val shown = rememberShown(onDismiss)
|
||||
val close = { shown.targetState = false }
|
||||
val volumes = remember { pickVolumes(ctx) }
|
||||
@@ -107,14 +111,23 @@ internal fun ExePickerDialog(
|
||||
val back = { if (dir == null) close() else up() }
|
||||
val first = remember(dir) { FocusRequester() }
|
||||
LaunchedEffect(dir) { focusWithinFrames({ false }) { first } }
|
||||
val listing = remember(dir) { dir?.let { pickListing(it, kind) } }
|
||||
val listing = remember(dir, showHidden) { dir?.let { pickListing(it, kind, showHidden) } }
|
||||
var locked by remember { mutableStateOf<Dp?>(null) }
|
||||
AppDialog(
|
||||
shown, close, "filePicker", wide = false, maxWidth = 520.dp, width = locked, fitContent = locked == null, back = back,
|
||||
modifier = Modifier.onSizeChanged { if (locked == null && it.width > 0) locked = with(density) { it.width.toDp() } },
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
DialogTitleRow(title, onClose = close)
|
||||
DialogTitleRow(title, onClose = close, leading = {
|
||||
SmallIconButton(
|
||||
if (showHidden) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
|
||||
stringResource(if (showHidden) R.string.fm_hide_hidden else R.string.fm_show_hidden),
|
||||
if (showHidden) colors.primary else colors.onSurfaceVariant,
|
||||
) {
|
||||
showHidden = !showHidden
|
||||
prefs.edit().putBoolean("show_hidden", showHidden).apply()
|
||||
}
|
||||
})
|
||||
if (dir != null && volume != null) {
|
||||
val crumbs = listOf(volume.label) + dir!!.path.removePrefix(volume.root.path).split('/').filter { it.isNotEmpty() }
|
||||
Text(crumbs.joinToString(" › "), fontSize = 12.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
@@ -135,7 +148,7 @@ internal fun ExePickerDialog(
|
||||
val shownListing = listing ?: PickListing(emptyList(), emptyList())
|
||||
PickerRow(Icons.AutoMirrored.Outlined.ArrowBack, "..", null, focus = first, onClick = up)
|
||||
if (onAddAll != null && shownListing.folders.any { it.readable }) {
|
||||
PickerRow(Icons.Filled.Add, stringResource(R.string.games_add_all), null, accent = true) { onAddAll(dir!!); close() }
|
||||
PickerRow(Icons.Filled.Add, stringResource(R.string.games_add_all), null, accent = true) { onAddAll(dir!!, showHidden); close() }
|
||||
}
|
||||
for (f in shownListing.folders) key(f.dir.path) {
|
||||
PickerRow(Icons.Outlined.Folder, f.dir.name, null, enabled = f.readable) { dir = f.dir }
|
||||
|
||||
@@ -424,7 +424,7 @@ private fun GamesDialogs(a: FrontEndActions, games: List<Library.SteamGame>) {
|
||||
val st = GamesDialogState
|
||||
if (st.adding) ExePickerDialog(
|
||||
stringResource(R.string.games_add_title),
|
||||
onAddAll = { dir ->
|
||||
onAddAll = { dir, showHidden ->
|
||||
st.summary = emptyList<com.droiddeck.launcher.frontend.AddedGames.Game>() to 0
|
||||
st.checking = dir.name
|
||||
scope.launch {
|
||||
@@ -433,6 +433,7 @@ private fun GamesDialogs(a: FrontEndActions, games: List<Library.SteamGame>) {
|
||||
ctx.applicationContext, dir,
|
||||
onGame = { g -> scope.launch { st.summary?.let { (list, n) -> st.summary = (list + g) to n } } },
|
||||
onChecking = { name -> scope.launch { st.checking = name } },
|
||||
showHidden = showHidden,
|
||||
)
|
||||
}
|
||||
st.checking = null
|
||||
|
||||
@@ -132,7 +132,7 @@ class FrontEndState(
|
||||
val controller: com.droiddeck.launcher.input.ControllerPrefs.Settings? = null,
|
||||
val phantomProcessStatus: PhantomProcessStatus = PhantomProcessStatus.NOT_APPLICABLE,
|
||||
val showPhantomGate: Boolean = false,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
||||
val orientation: Int = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
val launcherFullscreen: Boolean = true,
|
||||
val downloadSource: String = DownloadSource.MODE_AUTO,
|
||||
val downloadMirror: String = "",
|
||||
|
||||
@@ -359,6 +359,7 @@ internal fun SetupPanel(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.theme == Themes.INNER_GAMER) RgbSpeedRow()
|
||||
if (s.theme == Themes.GRADIENT) GradientColorRows()
|
||||
ChoiceRow(
|
||||
host, "app-scale", stringResource(R.string.setup_app_scale), stringResource(R.string.setup_app_scale_hint),
|
||||
@@ -792,6 +793,13 @@ private fun LanguageRow(host: MenuHost, chosen: String, onPick: (String) -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
/** How fast Inner Gamer cycles its colours. */
|
||||
@Composable
|
||||
private fun RgbSpeedRow() {
|
||||
val ctx = androidx.compose.ui.platform.LocalContext.current.applicationContext
|
||||
SliderRow(stringResource(R.string.theme_rgb_speed), stringResource(R.string.theme_rgb_speed_hint), Themes.rgbSpeed, 1..Themes.RGB_SPEEDS) { Themes.setRgbSpeed(ctx, it) }
|
||||
}
|
||||
|
||||
/** The two colours of the Gradient theme, each a small square that opens the colour picker. */
|
||||
@Composable
|
||||
private fun GradientColorRows() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import com.droiddeck.launcher.gpu.DeviceInfo
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -128,6 +129,8 @@ class GpuDriversState(
|
||||
val runtime: DriverSlot = DriverSlot(),
|
||||
val canRestart: Boolean = true,
|
||||
val checkedAt: Long = 0L,
|
||||
val tpUbwcMode: String? = null,
|
||||
val tpUbwcRestartRequired: Boolean = false,
|
||||
) {
|
||||
val activePair: PairRow? get() = pairs.firstOrNull { it.active }
|
||||
}
|
||||
@@ -147,6 +150,7 @@ class GpuDriversActions(
|
||||
val onRestoreBundled: () -> Unit = {},
|
||||
val onMode: (String) -> Unit = {},
|
||||
val onRestart: () -> Unit = {},
|
||||
val onTpUbwcMode: (String) -> Unit = {},
|
||||
)
|
||||
|
||||
@Composable
|
||||
@@ -259,6 +263,27 @@ internal fun GpuDriversBody(
|
||||
GPU_MODE_PAIR -> PairBody(gpu, actions)
|
||||
else -> MixBody(single, actions, slotFocus, onOpenSlot)
|
||||
}
|
||||
gpu.tpUbwcMode?.let { selected ->
|
||||
var advanced by rememberSaveable { mutableStateOf(false) }
|
||||
SecondaryButton(stringResource(R.string.gpu_advanced) + if (advanced) " ▴" else " ▾", modifier = Modifier.fillMaxWidth()) { advanced = !advanced }
|
||||
AnimatedVisibility(advanced) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
ChoiceRow(
|
||||
rememberMenuHost(), "tp-ubwc", stringResource(R.string.gpu_ubwc_title),
|
||||
stringResource(R.string.gpu_ubwc_hint),
|
||||
listOf(
|
||||
SessionPrefs.TP_UBWC_AUTO to stringResource(R.string.common_auto),
|
||||
SessionPrefs.TP_UBWC_ON to stringResource(R.string.widgets_on),
|
||||
SessionPrefs.TP_UBWC_OFF to stringResource(R.string.widgets_off),
|
||||
), selected, onPick = actions.onTpUbwcMode,
|
||||
)
|
||||
if (gpu.tpUbwcRestartRequired) {
|
||||
Text(stringResource(R.string.gpu_ubwc_restart), fontSize = 13.sp, color = AttentionAmber)
|
||||
if (gpu.canRestart) SecondaryButton(stringResource(R.string.gpu_restart_now), onClick = actions.onRestart)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,8 @@ class ModeSettings(
|
||||
val steamClientArch: String? = null,
|
||||
/** Steam only: a game launched from the front end returns there when it closes; null hides the row. */
|
||||
val returnAfterGame: Boolean? = null,
|
||||
/** Steam only: the client's Shader Pre-Caching setting; null hides the row. */
|
||||
val shaderPrecache: Boolean? = null,
|
||||
val steamRepairQueued: Boolean = false,
|
||||
val mangoapp: Boolean = true,
|
||||
val steamController: String? = null,
|
||||
@@ -136,6 +138,7 @@ class ModeSettingsActions(
|
||||
val onSteamClientArch: (String) -> Unit = {},
|
||||
val onMangoapp: (Boolean) -> Unit = {},
|
||||
val onReturnAfterGame: (Boolean) -> Unit = {},
|
||||
val onShaderPrecache: (Boolean) -> Unit = {},
|
||||
val onSteamController: (String) -> Unit = {},
|
||||
val onUnfoldedControls: (Boolean) -> Unit = {},
|
||||
val onUnfoldedControlsWithController: (Boolean) -> Unit = {},
|
||||
@@ -478,6 +481,11 @@ fun ModeSheetRows(s: ModeSettings, a: ModeSettingsActions, tab: String, extras:
|
||||
stringResource(R.string.mode_return_after_game_hint),
|
||||
s.returnAfterGame, onChange = a.onReturnAfterGame,
|
||||
)
|
||||
if (s.shaderPrecache != null) ToggleRow(
|
||||
host, "shaderprecache", stringResource(R.string.mode_shader_precache),
|
||||
stringResource(R.string.mode_shader_precache_hint),
|
||||
s.shaderPrecache, onChange = a.onShaderPrecache,
|
||||
)
|
||||
SettingsRow(
|
||||
stringResource(R.string.mode_steam_repair),
|
||||
stringResource(if (s.steamRepairQueued) R.string.mode_steam_repair_queued else R.string.mode_steam_repair_hint),
|
||||
|
||||
@@ -46,6 +46,7 @@ fun PerformancePage(
|
||||
gpuClockPin: Boolean,
|
||||
prootNoSeccomp: Boolean,
|
||||
prootFastPath: Boolean,
|
||||
cursorComposite: Boolean,
|
||||
guestHostname: String,
|
||||
phantomWarning: String?,
|
||||
onClientOverride: (Boolean) -> Unit,
|
||||
@@ -61,6 +62,7 @@ fun PerformancePage(
|
||||
onGpuClockPin: (Boolean) -> Unit,
|
||||
onProotNoSeccomp: (Boolean) -> Unit,
|
||||
onProotFastPath: (Boolean) -> Unit,
|
||||
onCursorComposite: (Boolean) -> Unit,
|
||||
onGuestHostname: (String) -> Unit,
|
||||
onClientCore: (Int, Boolean) -> Unit,
|
||||
onGameCore: (Int, Boolean) -> Unit,
|
||||
@@ -158,6 +160,11 @@ fun PerformancePage(
|
||||
else stringResource(R.string.perf_fastpath_hint),
|
||||
prootFastPath && !prootNoSeccomp, enabled = !prootNoSeccomp, onChange = onProotFastPath,
|
||||
)
|
||||
ToggleRow(
|
||||
host, "cursorcomposite", stringResource(R.string.perf_cursor_composite),
|
||||
stringResource(R.string.perf_cursor_composite_hint),
|
||||
cursorComposite, onChange = onCursorComposite,
|
||||
)
|
||||
}
|
||||
SettingsGroup(stringResource(R.string.perf_identity)) {
|
||||
var draft by remember(guestHostname) { mutableStateOf(guestHostname) }
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.foundation.Canvas
|
||||
@@ -345,37 +344,3 @@ private fun CancelHint(onCancel: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Text(stringResource(R.string.common_cancel), fontSize = 13.sp, color = colors.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SessionPausedOverlay(title: String = stringResource(R.string.load_steam_paused), onResume: () -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val resumeFocus = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
androidx.compose.runtime.withFrameNanos { }
|
||||
runCatching { resumeFocus.requestFocus() }
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(pal.background.copy(alpha = 0.94f))
|
||||
.blockTouches(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
modifier = Modifier.widthIn(max = 440.dp).padding(32.dp),
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(bottom = 6.dp).size(64.dp).border(2.dp, pal.line2, CircleShape)) {
|
||||
Icon(Icons.Filled.Pause, contentDescription = null, tint = colors.onBackground, modifier = Modifier.size(30.dp))
|
||||
}
|
||||
Text(title, fontSize = 28.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, textAlign = TextAlign.Center)
|
||||
PrimaryButton(
|
||||
stringResource(R.string.resume_session),
|
||||
modifier = Modifier.padding(top = 14.dp).focusRequester(resumeFocus).controllerConfirm(onClick = onResume),
|
||||
onClick = onResume,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.droiddeck.launcher.R
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
/** The last game frame, captured as the session left the screen, and where it sat in the overlay. */
|
||||
class PausedFrame(val image: ImageBitmap, val bounds: android.graphics.Rect)
|
||||
|
||||
private const val CARD_SCALE = 0.82f
|
||||
private const val FRAME_WAIT_MS = 600L
|
||||
|
||||
private fun tone(saturation: Float, brightness: Float): ColorMatrix =
|
||||
ColorMatrix().apply {
|
||||
setToSaturation(saturation)
|
||||
timesAssign(ColorMatrix().apply { setToScale(brightness, brightness, brightness, 1f) })
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SessionPausedOverlay(
|
||||
suspended: Boolean,
|
||||
engaged: Boolean,
|
||||
failures: Int,
|
||||
frame: PausedFrame?,
|
||||
title: String,
|
||||
onResume: () -> Unit,
|
||||
onFrameNeeded: () -> Unit,
|
||||
onHidden: () -> Unit,
|
||||
) {
|
||||
val currentFrame by rememberUpdatedState(frame)
|
||||
var shown by remember { mutableStateOf(suspended) }
|
||||
val lift = remember { Animatable(if (suspended) 1f else 0f) }
|
||||
val orb = remember { Animatable(if (suspended) 1f else 0f) }
|
||||
val press = remember { Animatable(1f) }
|
||||
val ring = remember { Animatable(0f) }
|
||||
val shake = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(suspended, engaged) {
|
||||
when {
|
||||
suspended && !engaged -> {
|
||||
if (!shown) {
|
||||
if (currentFrame == null) {
|
||||
onFrameNeeded()
|
||||
withTimeoutOrNull(FRAME_WAIT_MS) { snapshotFlow { currentFrame }.first { it != null } }
|
||||
}
|
||||
// Pausing mostly happens while the screen is off or the app is away, so it appears already settled.
|
||||
press.snapTo(1f)
|
||||
ring.snapTo(0f)
|
||||
lift.snapTo(1f)
|
||||
orb.snapTo(1f)
|
||||
shown = true
|
||||
return@LaunchedEffect
|
||||
}
|
||||
launch { press.animateTo(1f, Motion.sp(0.5f, Spring.StiffnessMedium)) }
|
||||
launch { ring.animateTo(0f, Motion.tw(200)) }
|
||||
orb.animateTo(1f, Motion.tw(200))
|
||||
}
|
||||
suspended -> {
|
||||
if (!shown) return@LaunchedEffect
|
||||
launch { orb.animateTo(1f, Motion.tw(200)) }
|
||||
launch { press.animateTo(0.92f, Motion.tw(140)) }
|
||||
ring.animateTo(0.25f, Motion.tw(140))
|
||||
ring.animateTo(0.8f, Motion.tw(650, easing = CubicBezierEasing(0.1f, 0.6f, 0.3f, 1f)))
|
||||
ring.animateTo(0.92f, Motion.tw(2000, easing = LinearOutSlowInEasing))
|
||||
}
|
||||
shown -> {
|
||||
ring.animateTo(1f, Motion.tw(110))
|
||||
coroutineScope {
|
||||
launch { orb.animateTo(0f, Motion.tw(220, easing = FastOutLinearInEasing)) }
|
||||
lift.animateTo(0f, Motion.sp(0.62f, 380f))
|
||||
}
|
||||
shown = false
|
||||
ring.snapTo(0f)
|
||||
press.snapTo(1f)
|
||||
onHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val seenFailures = remember { mutableIntStateOf(failures) }
|
||||
LaunchedEffect(failures) {
|
||||
if (failures == seenFailures.intValue) return@LaunchedEffect
|
||||
seenFailures.intValue = failures
|
||||
if (!shown || Motion.scale == 0f) return@LaunchedEffect
|
||||
shake.animateTo(0f, keyframes {
|
||||
durationMillis = Motion.ms(360)
|
||||
10f at Motion.ms(60)
|
||||
-8f at Motion.ms(140)
|
||||
5f at Motion.ms(220)
|
||||
-2f at Motion.ms(300)
|
||||
})
|
||||
}
|
||||
|
||||
if (!shown) return
|
||||
|
||||
val presence = lift.value.coerceIn(0f, 1f)
|
||||
val idle = suspended && !engaged
|
||||
val focus = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
withFrameNanos { }
|
||||
runCatching { focus.requestFocus() }
|
||||
}
|
||||
val resumeLabel = stringResource(R.string.resume_session)
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.semantics { contentDescription = title }
|
||||
.focusRequester(focus)
|
||||
.controllerConfirm(enabled = idle, onClick = onResume)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
role = Role.Button,
|
||||
onClickLabel = resumeLabel,
|
||||
) { if (idle) onResume() },
|
||||
) {
|
||||
if (frame != null) {
|
||||
PausedCard(frame, lift.value, presence)
|
||||
} else {
|
||||
Box(Modifier.fillMaxSize().graphicsLayer { alpha = presence }.background(Color.Black.copy(alpha = 0.55f)))
|
||||
}
|
||||
ResumeOrb(orb.value, press.value, ring.value, shake.value, idle)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.PausedCard(frame: PausedFrame, lift: Float, presence: Float) {
|
||||
val density = LocalDensity.current
|
||||
val bounds = frame.bounds
|
||||
val ambient = remember { ColorFilter.colorMatrix(tone(0.1f, 0.3f)) }
|
||||
Box(Modifier.fillMaxSize().graphicsLayer { alpha = presence }.background(Color.Black)) {
|
||||
Image(
|
||||
frame.image, contentDescription = null, contentScale = ContentScale.Crop, colorFilter = ambient,
|
||||
modifier = Modifier.fillMaxSize().then(if (Build.VERSION.SDK_INT >= 31) Modifier.blur(32.dp) else Modifier),
|
||||
)
|
||||
}
|
||||
Image(
|
||||
frame.image, contentDescription = null, contentScale = ContentScale.FillBounds,
|
||||
colorFilter = ColorFilter.colorMatrix(tone(1f - 0.92f * presence, 1f - 0.45f * presence)),
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.offset { IntOffset(bounds.left, bounds.top) }
|
||||
.size(with(density) { bounds.width().toDp() }, with(density) { bounds.height().toDp() })
|
||||
.graphicsLayer {
|
||||
val scale = 1f - (1f - CARD_SCALE) * lift
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
shape = RoundedCornerShape((20f * presence).dp)
|
||||
clip = true
|
||||
shadowElevation = 24.dp.toPx() * presence
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResumeOrb(presence: Float, press: Float, ring: Float, shake: Float, idle: Boolean) {
|
||||
val glintAlpha by animateFloatAsState(if (idle) 1f else 0f, Motion.tw(160), label = "glintAlpha")
|
||||
val glint: State<Float>? = if (Motion.scale > 0f) {
|
||||
rememberInfiniteTransition(label = "glint").animateFloat(
|
||||
0f, 360f, infiniteRepeatable(tween(Motion.ms(3600), easing = LinearEasing)), label = "glintAngle",
|
||||
)
|
||||
} else null
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.size(96.dp)
|
||||
.graphicsLayer {
|
||||
val scale = press * (0.4f + 0.6f * presence)
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
alpha = presence.coerceIn(0f, 1f)
|
||||
translationX = shake.dp.toPx()
|
||||
}
|
||||
.drawBehind {
|
||||
val halo = size.minDimension * 0.8f
|
||||
drawCircle(Brush.radialGradient(0.6f to Color.Black.copy(alpha = 0.35f), 1f to Color.Transparent, radius = halo), radius = halo)
|
||||
}
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.08f))
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
val stroke = 2.5.dp.toPx()
|
||||
val radius = size.minDimension / 2f * 0.92f
|
||||
if (glint != null && glintAlpha > 0f) {
|
||||
rotate(glint.value) {
|
||||
drawCircle(
|
||||
Brush.sweepGradient(
|
||||
0f to Color.Transparent,
|
||||
0.083f to Color.White.copy(alpha = 0.4f * glintAlpha),
|
||||
0.166f to Color.Transparent,
|
||||
1f to Color.Transparent,
|
||||
),
|
||||
radius = radius, style = Stroke(stroke),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (ring > 0f) {
|
||||
drawArc(
|
||||
Color.White, startAngle = -90f, sweepAngle = 360f * ring.coerceAtMost(1f), useCenter = false,
|
||||
topLeft = Offset(center.x - radius, center.y - radius), size = Size(radius * 2f, radius * 2f),
|
||||
style = Stroke(stroke, cap = StrokeCap.Round),
|
||||
)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(Icons.Rounded.PlayArrow, contentDescription = null, tint = Color.White, modifier = Modifier.size(48.dp))
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,15 @@ package com.droiddeck.launcher.ui
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -13,6 +21,7 @@ import android.content.Context
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.droiddeck.launcher.R
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
@@ -33,6 +42,7 @@ object Themes {
|
||||
const val PAPER = "paper"
|
||||
const val ICON_BLUE = "blue"
|
||||
const val ELECTRIC = "electric"
|
||||
const val INNER_GAMER = "inner_gamer"
|
||||
const val GRADIENT = "gradient"
|
||||
|
||||
const val DEFAULT_PRIMARY = 0xFF8B5CF6.toInt()
|
||||
@@ -42,10 +52,42 @@ object Themes {
|
||||
var gradientColors by mutableStateOf(DEFAULT_PRIMARY to DEFAULT_SECONDARY)
|
||||
private set
|
||||
|
||||
const val RGB_SPEEDS = 10
|
||||
const val DEFAULT_RGB_SPEED = 3
|
||||
const val RGB_HUE_STEP = 3f
|
||||
|
||||
/** How fast Inner Gamer cycles, 1 (slow) to [RGB_SPEEDS] (fast). */
|
||||
var rgbSpeed by mutableStateOf(DEFAULT_RGB_SPEED)
|
||||
private set
|
||||
|
||||
/** Where Inner Gamer is on the colour wheel, in whole [RGB_HUE_STEP] steps so the app restyles only when the colour visibly moves. */
|
||||
var rgbHue by mutableStateOf(0f)
|
||||
private set
|
||||
|
||||
fun moveRgbHue(hue: Float) {
|
||||
rgbHue = hue
|
||||
}
|
||||
|
||||
fun setRgbSpeed(context: Context, speed: Int) {
|
||||
val value = speed.coerceIn(1, RGB_SPEEDS)
|
||||
SessionPrefs.setRgbSpeed(context, value)
|
||||
rgbSpeed = value
|
||||
}
|
||||
|
||||
/** Milliseconds for one trip round the colour wheel. */
|
||||
fun rgbCycleMillis(speed: Int): Int = 30_000 / speed.coerceIn(1, RGB_SPEEDS)
|
||||
|
||||
fun loadGradient(context: Context) {
|
||||
gradientColors = SessionPrefs.gradientColors(context)
|
||||
rgbSpeed = SessionPrefs.rgbSpeed(context)
|
||||
}
|
||||
|
||||
/** The Inner Gamer palette at one point on the colour wheel. */
|
||||
fun innerGamer(hue: Float): Palette = ThemeColors.palette(
|
||||
Color.hsv(hue % 360f, 0.9f, 1f).toArgb(), Color.hsv((hue + 60f) % 360f, 0.9f, 1f).toArgb(),
|
||||
R.string.theme_inner_gamer, R.string.theme_inner_gamer_detail, INNER_GAMER,
|
||||
)
|
||||
|
||||
fun setGradient(context: Context, primary: Int, secondary: Int) {
|
||||
SessionPrefs.setGradientColors(context, primary, secondary)
|
||||
gradientColors = primary to secondary
|
||||
@@ -54,7 +96,7 @@ object Themes {
|
||||
/** The theme the person chose, with the custom colours read first; for code that is not in a composition. */
|
||||
fun current(context: Context): Palette {
|
||||
val id = SessionPrefs.theme(context)
|
||||
if (id == GRADIENT) loadGradient(context)
|
||||
if (id == GRADIENT || id == INNER_GAMER) loadGradient(context)
|
||||
return byId(id)
|
||||
}
|
||||
|
||||
@@ -87,10 +129,12 @@ object Themes {
|
||||
primary = Color(0xFF2E86FF), primary2 = Color(0xFF2684FF), onPrimary = Color(0xFF03111F), signal = Color(0xFF2E86FF),
|
||||
good = Color(0xFF4CD37F),
|
||||
),
|
||||
innerGamer(0f),
|
||||
ThemeColors.palette(DEFAULT_PRIMARY, DEFAULT_SECONDARY, R.string.theme_gradient, R.string.theme_gradient_detail),
|
||||
)
|
||||
|
||||
fun byId(id: String): Palette {
|
||||
if (id == INNER_GAMER) return innerGamer(rgbHue)
|
||||
if (id == GRADIENT) return ThemeColors.palette(gradientColors.first, gradientColors.second, R.string.theme_gradient, R.string.theme_gradient_detail)
|
||||
return all.firstOrNull { it.id == id } ?: all[0]
|
||||
}
|
||||
@@ -103,6 +147,7 @@ val Palette.onSignal: Color get() = if (signal.luminance() > 0.18f) Color(0xFF03
|
||||
@Composable
|
||||
fun DroidDeckTheme(theme: String? = null, appScale: Int = AppUiPrefs.DEFAULT_SCALE, content: @Composable () -> Unit) {
|
||||
val id = theme ?: SessionPrefs.theme(LocalContext.current)
|
||||
if (id == Themes.INNER_GAMER) InnerGamerHue(Themes.rgbSpeed)
|
||||
val p = Themes.byId(id)
|
||||
val scheme = darkColorScheme(
|
||||
primary = p.primary, onPrimary = p.onPrimary, secondary = p.onSurfaceVariant,
|
||||
@@ -121,3 +166,21 @@ fun DroidDeckTheme(theme: String? = null, appScale: Int = AppUiPrefs.DEFAULT_SCA
|
||||
LocalConfiguration provides configuration,
|
||||
) { MaterialTheme(colorScheme = scheme, content = content) }
|
||||
}
|
||||
|
||||
/** Turns the colour wheel on the system's own animation clock; with animations off, in the app or in Android, the colour holds still. */
|
||||
@Composable
|
||||
private fun InnerGamerHue(speed: Int) {
|
||||
if (Motion.scale == 0f) return
|
||||
key(speed) { InnerGamerCycle(speed) }
|
||||
}
|
||||
|
||||
/** One run of the cycle at one speed, picking up from the colour on screen so a new speed takes over at once without a jump. */
|
||||
@Composable
|
||||
private fun InnerGamerCycle(speed: Int) {
|
||||
val start = remember { Themes.rgbHue }
|
||||
val hue by rememberInfiniteTransition(label = "innerGamer").animateFloat(
|
||||
start, start + 360f, infiniteRepeatable(tween(Themes.rgbCycleMillis(speed).coerceAtLeast(1), easing = LinearEasing), RepeatMode.Restart), label = "hue",
|
||||
)
|
||||
val stepped = ((hue % 360f) / Themes.RGB_HUE_STEP).toInt() * Themes.RGB_HUE_STEP
|
||||
SideEffect { Themes.moveRgbHue(stepped) }
|
||||
}
|
||||
@@ -132,7 +132,7 @@ internal object ThemeColors {
|
||||
}
|
||||
}
|
||||
|
||||
fun palette(primary: Int, secondary: Int, label: Int, detail: Int): Palette {
|
||||
fun palette(primary: Int, secondary: Int, label: Int, detail: Int, id: String = Themes.GRADIENT): Palette {
|
||||
val seen = readable(primary)
|
||||
val seenSecond = readable(secondary)
|
||||
val ink = inkFor(seen, seenSecond)
|
||||
@@ -146,7 +146,7 @@ internal object ThemeColors {
|
||||
second = nextSecond
|
||||
}
|
||||
return Palette(
|
||||
Themes.GRADIENT, label, detail,
|
||||
id, label, detail,
|
||||
background = tint(first, 0.50f, 0.045f), surface = tint(first, 0.45f, 0.075f), surfaceVariant = tint(first, 0.42f, 0.115f),
|
||||
line = tint(first, 0.40f, 0.16f), line2 = tint(first, 0.36f, 0.22f),
|
||||
onBackground = tint(first, 0.05f, 0.97f), onSurfaceVariant = tint(first, 0.16f, 0.68f),
|
||||
|
||||
@@ -23,6 +23,7 @@ object CompositorHost {
|
||||
|
||||
@Synchronized
|
||||
fun startOrAttach(
|
||||
context: android.content.Context,
|
||||
surface: Surface,
|
||||
xdgRuntimeDir: String,
|
||||
driverPath: String?,
|
||||
@@ -41,7 +42,7 @@ object CompositorHost {
|
||||
resumeVsync()
|
||||
return false
|
||||
}
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.applyToProcess()
|
||||
com.droiddeck.launcher.gpu.TpUbwcHint.applyToProcess(context)
|
||||
WaylandCompositor.nativeStartWithSurface(
|
||||
surface, xdgRuntimeDir, driverPath, libraryName, nativeLibDir,
|
||||
)
|
||||
|
||||
@@ -549,8 +549,8 @@
|
||||
<string name="perf_sysmem">Turnip: التصيير عبر sysmem</string>
|
||||
<string name="perf_sysmem_hint">أسرع لعميل Steam؛ في الألعاب يختار برنامج التشغيل بين العرض المقسّم إلى مربعات والعرض في ذاكرة النظام لكل مرحلة. مطلوب على Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">تجاوز مساعد xalia في Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (تجريبي)</string>
|
||||
<string name="perf_fastsync_hint">بديل في مساحة المستخدم لبرنامج تشغيل ntsync في Linux، للاختبار. أثناء تفعيله، يستخدمه Proton بدلًا من droiddeck-esync وdroiddeck-fsync. يعمل مع Proton Experimental ARM64 وGE-Proton وproton-cachyos. يُطبَّق في الجلسة التالية.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">بديل في مساحة المستخدم لبرنامج تشغيل ntsync في Linux. أثناء تفعيله، يستخدمه Proton بدلًا من droiddeck-esync وdroiddeck-fsync. يعمل مع Proton Experimental ARM64 وGE-Proton وproton-cachyos. يُطبَّق في الجلسة التالية.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync أولًا</string>
|
||||
<string name="perf_fsync_hint">يشغّل خاصية fsync المدمجة في Proton على ملفات Proton الأصلية، مع توفير futex_waitv في مساحة المستخدم، فلا يحتاج إلى حزمة ويستمر في العمل لحظة تحديث Steam لـ Proton. أثناء تفعيله، تستخدمه الألعاب قبل droiddeck-esync، باستثناء القليل الذي يبقيه Proton معطّلًا لـ fsync. يُطبَّق في الجلسة التالية.</string>
|
||||
@@ -1457,6 +1457,10 @@
|
||||
<string name="theme_icon_blue_detail">خلفية سوداء ونص أبيض، والأزرق لكل عنصر محدَّد أو مُركَّز عليه أو مضغوط.</string>
|
||||
<string name="theme_electric">كحلي كهربائي</string>
|
||||
<string name="theme_electric_detail">أزرق أكثر سطوعًا، على أسطح تميل إلى الكحلي بدلاً من الرمادي.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">ألوان تتدرّج عبر ألوان قوس قزح. تتبع إعدادات الرسوم المتحركة في Android.</string>
|
||||
<string name="theme_rgb_speed">السرعة</string>
|
||||
<string name="theme_rgb_speed_hint">مدى سرعة تغيّر الألوان.</string>
|
||||
<string name="theme_gradient">تدرج لوني</string>
|
||||
<string name="theme_gradient_detail">لونك الأساسي والثانوي، ممزوجان على الأزرار وعناصر التمييز.</string>
|
||||
<string name="theme_gradient_primary">الأساسي</string>
|
||||
@@ -1598,6 +1602,13 @@
|
||||
<string name="fm_conflict_skip">تخطّي</string>
|
||||
<string name="fm_select_this_folder">اختيار هذا المجلد</string>
|
||||
<string name="fm_back">رجوع</string>
|
||||
<string name="fm_exit">خروج</string>
|
||||
<string name="fm_return_to_start">العودة إلى المجلد الأول</string>
|
||||
<string name="fm_start_unavailable">لم يعد المجلد الأول متاحًا</string>
|
||||
<string name="fm_cancel_operation_title">إلغاء العملية؟</string>
|
||||
<string name="fm_cancel_operation_confirmation">سيتم إلغاء العملية. هل أنت متأكد؟</string>
|
||||
<string name="fm_accept">موافق</string>
|
||||
<string name="fm_decline">رفض</string>
|
||||
<string name="fm_drive_not_readable">تم تركيب %1$s لكن تتعذّر قراءته حاليًا</string>
|
||||
<string name="fm_list_view">عرض القائمة</string>
|
||||
<string name="fm_grid_view">عرض الشبكة</string>
|
||||
|
||||
@@ -536,8 +536,8 @@
|
||||
<string name="perf_sysmem">Turnip: renderizado sysmem</string>
|
||||
<string name="perf_sysmem_hint">Más rápido para el cliente de Steam; en los juegos, el controlador elige entre renderizado por mosaicos o en memoria del sistema en cada pasada. Obligatorio en Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Omitir el asistente xalia de Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">Un sustituto en espacio de usuario del controlador ntsync de Linux, para pruebas. Mientras está activado, Proton lo usa en lugar de droiddeck-esync y droiddeck-fsync. Funciona con Proton Experimental ARM64, GE-Proton y proton-cachyos. Se aplica en la próxima sesión.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Un sustituto en espacio de usuario del controlador ntsync de Linux. Mientras está activado, Proton lo usa en lugar de droiddeck-esync y droiddeck-fsync. Funciona con Proton Experimental ARM64, GE-Proton y proton-cachyos. Se aplica en la próxima sesión.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync primero</string>
|
||||
<string name="perf_fsync_hint">Usa el fsync propio de Proton con los archivos originales de Proton, con futex_waitv proporcionado en espacio de usuario, así que no necesita paquete y sigue funcionando en cuanto Steam actualiza Proton. Mientras está activado, los juegos lo usan antes que droiddeck-esync, salvo los pocos que Proton mantiene sin fsync. Se aplica en la próxima sesión.</string>
|
||||
@@ -1466,6 +1466,10 @@
|
||||
<string name="theme_icon_blue_detail">Fondo negro, texto blanco y el azul para todo lo seleccionado, enfocado o presionado.</string>
|
||||
<string name="theme_electric">Azul marino eléctrico</string>
|
||||
<string name="theme_electric_detail">Un azul más vivo, sobre superficies que tiran a azul marino en vez de gris.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Colores que recorren el arcoíris. Respeta los ajustes de animación de Android.</string>
|
||||
<string name="theme_rgb_speed">Velocidad</string>
|
||||
<string name="theme_rgb_speed_hint">Qué tan rápido cambian los colores.</string>
|
||||
<string name="theme_gradient">Degradado</string>
|
||||
<string name="theme_gradient_detail">Tus propios colores primario y secundario, combinados en botones y resaltados.</string>
|
||||
<string name="theme_gradient_primary">Primario</string>
|
||||
@@ -1592,6 +1596,13 @@
|
||||
<string name="fm_conflict_skip">Omitir</string>
|
||||
<string name="fm_select_this_folder">Seleccionar esta carpeta</string>
|
||||
<string name="fm_back">Atrás</string>
|
||||
<string name="fm_exit">Salir</string>
|
||||
<string name="fm_return_to_start">Volver a la carpeta inicial</string>
|
||||
<string name="fm_start_unavailable">La carpeta inicial ya no está disponible</string>
|
||||
<string name="fm_cancel_operation_title">¿Cancelar la operación?</string>
|
||||
<string name="fm_cancel_operation_confirmation">La operación se cancelará. ¿Estás seguro?</string>
|
||||
<string name="fm_accept">Aceptar</string>
|
||||
<string name="fm_decline">Rechazar</string>
|
||||
<string name="fm_drive_not_readable">%1$s está montado, pero ahora mismo no se puede leer</string>
|
||||
<string name="fm_list_view">Vista de lista</string>
|
||||
<string name="fm_grid_view">Vista de cuadrícula</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: рендиране sysmem</string>
|
||||
<string name="perf_sysmem_hint">По-бързо за клиента на Steam; в игрите драйверът избира за всеки проход между рендериране на плочки и в системната памет. Задължително на Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Пропускане на помощника xalia на Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (експериментално)</string>
|
||||
<string name="perf_fastsync_hint">Заместител на потребителско ниво за драйвера ntsync на Linux, за тестване. Докато е включен, Proton го използва вместо droiddeck-esync и droiddeck-fsync. Работи с Proton Experimental ARM64, GE-Proton и proton-cachyos. Прилага се от следващата сесия.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Заместител на потребителско ниво за драйвера ntsync на Linux. Докато е включен, Proton го използва вместо droiddeck-esync и droiddeck-fsync. Работи с Proton Experimental ARM64, GE-Proton и proton-cachyos. Прилага се от следващата сесия.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync първо</string>
|
||||
<string name="perf_fsync_hint">Стартира собствения fsync на Proton върху оригиналните файлове на Proton, с futex_waitv, подаден от потребителско ниво, затова не изисква пакет и продължава да работи веднага след актуализация на Proton от Steam. Докато е включен, игрите го използват преди droiddeck-esync, освен малкото, които Proton държи извън fsync. Прилага се от следващата сесия.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Черен фон, бял текст, синьото за всичко избрано, на фокус или натиснато.</string>
|
||||
<string name="theme_electric">Яркосиньо</string>
|
||||
<string name="theme_electric_detail">По-яркото синьо, върху повърхности, по-близки до тъмносиньо, вместо сиво.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Цветове, които преминават през дъгата. Следва настройките за анимации на Android.</string>
|
||||
<string name="theme_rgb_speed">Скорост</string>
|
||||
<string name="theme_rgb_speed_hint">Колко бързо се сменят цветовете.</string>
|
||||
<string name="theme_gradient">Градиент</string>
|
||||
<string name="theme_gradient_detail">Вашите основен и вторичен цвят, смесени в бутоните и акцентите.</string>
|
||||
<string name="theme_gradient_primary">Основен</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Пропускане</string>
|
||||
<string name="fm_select_this_folder">Избор на тази папка</string>
|
||||
<string name="fm_back">Назад</string>
|
||||
<string name="fm_exit">Изход</string>
|
||||
<string name="fm_return_to_start">Връщане в началната папка</string>
|
||||
<string name="fm_start_unavailable">Началната папка вече не е достъпна</string>
|
||||
<string name="fm_cancel_operation_title">Отмяна на операцията?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Операцията ще бъде отменена. Сигурни ли сте?</string>
|
||||
<string name="fm_accept">Приемам</string>
|
||||
<string name="fm_decline">Отказвам</string>
|
||||
<string name="fm_drive_not_readable">%1$s е монтирано, но в момента не може да се прочете</string>
|
||||
<string name="fm_list_view">Изглед като списък</string>
|
||||
<string name="fm_grid_view">Изглед като мрежа</string>
|
||||
|
||||
@@ -545,8 +545,8 @@
|
||||
<string name="perf_sysmem">Turnip: vykreslování sysmem</string>
|
||||
<string name="perf_sysmem_hint">Rychlejší pro klienta Steam; ve hrách ovladač pro každý průchod volí dlaždicové vykreslování, nebo vykreslování v systémové paměti. Vyžadováno na Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Vynechat Steamova pomocníka xalia</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimentální)</string>
|
||||
<string name="perf_fastsync_hint">Uživatelská náhrada linuxového ovladače ntsync, pro testování. Když je zapnuto, Proton ho používá místo droiddeck-esync a droiddeck-fsync. Funguje s Proton Experimental ARM64, GE-Proton a proton-cachyos. Použije se od příští relace.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Uživatelská náhrada linuxového ovladače ntsync. Když je zapnuto, Proton ho používá místo droiddeck-esync a droiddeck-fsync. Funguje s Proton Experimental ARM64, GE-Proton a proton-cachyos. Použije se od příští relace.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync jako první</string>
|
||||
<string name="perf_fsync_hint">Spouští vlastní fsync Protonu na původních souborech Protonu, s futex_waitv dodaným v uživatelském prostoru, takže nepotřebuje balíček a funguje i hned po aktualizaci Protonu Steamem. Když je zapnuto, hry ho používají před droiddeck-esync, kromě těch pár, které Proton u fsync nechává vypnuté. Použije se od příští relace.</string>
|
||||
@@ -1429,6 +1429,10 @@
|
||||
<string name="theme_icon_blue_detail">Černé pozadí, bílé písmo, modrá pro vše vybrané, zaměřené nebo stisknuté.</string>
|
||||
<string name="theme_electric">Elektrická modř</string>
|
||||
<string name="theme_electric_detail">Jasnější modrá na povrchách, které jsou spíše tmavomodré než šedé.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Barvy, které procházejí celou duhu. Respektuje nastavení animací v Androidu.</string>
|
||||
<string name="theme_rgb_speed">Rychlost</string>
|
||||
<string name="theme_rgb_speed_hint">Jak rychle se barvy střídají.</string>
|
||||
<string name="theme_gradient">Přechod</string>
|
||||
<string name="theme_gradient_detail">Vaše vlastní primární a sekundární barva, prolnuté na tlačítkách a zvýrazněních.</string>
|
||||
<string name="theme_gradient_primary">Primární</string>
|
||||
@@ -1560,6 +1564,13 @@
|
||||
<string name="fm_conflict_skip">Přeskočit</string>
|
||||
<string name="fm_select_this_folder">Vybrat tuto složku</string>
|
||||
<string name="fm_back">Zpět</string>
|
||||
<string name="fm_exit">Ukončit</string>
|
||||
<string name="fm_return_to_start">Zpět do výchozí složky</string>
|
||||
<string name="fm_start_unavailable">Výchozí složka již není dostupná</string>
|
||||
<string name="fm_cancel_operation_title">Zrušit operaci?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operace bude zrušena. Opravdu chcete pokračovat?</string>
|
||||
<string name="fm_accept">Přijmout</string>
|
||||
<string name="fm_decline">Odmítnout</string>
|
||||
<string name="fm_drive_not_readable">%1$s je připojeno, ale momentálně není čitelné</string>
|
||||
<string name="fm_list_view">Zobrazení seznamu</string>
|
||||
<string name="fm_grid_view">Zobrazení mřížky</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem-rendering</string>
|
||||
<string name="perf_sysmem_hint">Hurtigere til Steam-klienten; i spil vælger driveren flise- eller systemhukommelses-rendering for hvert pass. Påkrævet på Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Spring Steams xalia-hjælper over</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (eksperimentel)</string>
|
||||
<string name="perf_fastsync_hint">En stand-in til ntsync-driveren i Linux-kernen, i brugerrum, til test. Mens den er til, bruger Proton den i stedet for droiddeck-esync og droiddeck-fsync. Fungerer med Proton Experimental ARM64, GE-Proton og proton-cachyos. Gælder fra næste session.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">En stand-in til ntsync-driveren i Linux-kernen, i brugerrum. Mens den er til, bruger Proton den i stedet for droiddeck-esync og droiddeck-fsync. Fungerer med Proton Experimental ARM64, GE-Proton og proton-cachyos. Gælder fra næste session.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync først</string>
|
||||
<string name="perf_fsync_hint">Kører Protons egen fsync på de uændrede Proton-filer, med futex_waitv leveret i brugerrum, så det kræver ingen pack og fortsætter med at virke, lige så snart Steam opdaterer Proton. Mens den er til, bruger spil den før droiddeck-esync, undtagen de få, Proton holder uden for fsync. Gælder fra næste session.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Sort baggrund, hvid skrift, blå til alt, der er valgt, fokuseret eller trykket.</string>
|
||||
<string name="theme_electric">Elektrisk marineblå</string>
|
||||
<string name="theme_electric_detail">Den blå er klarere, på overflader der trækker mod marineblåt i stedet for gråt.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Farver, der skifter gennem regnbuen. Følger Androids animationsindstillinger.</string>
|
||||
<string name="theme_rgb_speed">Hastighed</string>
|
||||
<string name="theme_rgb_speed_hint">Hvor hurtigt farverne skifter.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Dine egne primære og sekundære farver, blandet på knapper og fremhævninger.</string>
|
||||
<string name="theme_gradient_primary">Primær</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Spring over</string>
|
||||
<string name="fm_select_this_folder">Vælg denne mappe</string>
|
||||
<string name="fm_back">Tilbage</string>
|
||||
<string name="fm_exit">Afslut</string>
|
||||
<string name="fm_return_to_start">Tilbage til startmappen</string>
|
||||
<string name="fm_start_unavailable">Startmappen er ikke længere tilgængelig</string>
|
||||
<string name="fm_cancel_operation_title">Annuller handlingen?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Handlingen bliver annulleret. Er du sikker?</string>
|
||||
<string name="fm_accept">Accepter</string>
|
||||
<string name="fm_decline">Afvis</string>
|
||||
<string name="fm_drive_not_readable">%1$s er monteret, men kan ikke læses lige nu</string>
|
||||
<string name="fm_list_view">Listevisning</string>
|
||||
<string name="fm_grid_view">Gittervisning</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: Sysmem-Rendering</string>
|
||||
<string name="perf_sysmem_hint">Schneller für den Steam-Client; in Spielen wählt der Treiber pro Durchgang zwischen Kachel- und Systemspeicher-Rendering. Erforderlich bei Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Steams xalia-Helfer überspringen</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimentell)</string>
|
||||
<string name="perf_fastsync_hint">Ein Userspace-Ersatz für den Linux-Treiber ntsync, zum Testen. Solange er aktiv ist, nutzt Proton ihn statt droiddeck-esync und droiddeck-fsync. Funktioniert mit Proton Experimental ARM64, GE-Proton und proton-cachyos. Gilt ab der nächsten Sitzung.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Ein Userspace-Ersatz für den Linux-Treiber ntsync. Solange er aktiv ist, nutzt Proton ihn statt droiddeck-esync und droiddeck-fsync. Funktioniert mit Proton Experimental ARM64, GE-Proton und proton-cachyos. Gilt ab der nächsten Sitzung.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync bevorzugen</string>
|
||||
<string name="perf_fsync_hint">Führt Protons eigenes fsync mit den unveränderten Proton-Dateien aus, wobei futex_waitv im Userspace bereitgestellt wird. Es braucht daher kein Pack und funktioniert weiter, sobald Steam Proton aktualisiert. Solange es aktiv ist, nutzen Spiele es vor droiddeck-esync, außer den wenigen, die Proton von fsync fernhält. Gilt ab der nächsten Sitzung.</string>
|
||||
@@ -1393,6 +1393,10 @@
|
||||
<string name="theme_icon_blue_detail">Schwarzer Grund, weiße Schrift, das Blau für alles Ausgewählte, Fokussierte oder Gedrückte.</string>
|
||||
<string name="theme_electric">Elektrisches Marineblau</string>
|
||||
<string name="theme_electric_detail">Das Blau heller, auf Flächen, die eher zu Marineblau als zu Grau tendieren.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Farben, die durch den Regenbogen wechseln. Folgt den Animationseinstellungen von Android.</string>
|
||||
<string name="theme_rgb_speed">Geschwindigkeit</string>
|
||||
<string name="theme_rgb_speed_hint">Wie schnell die Farben wechseln.</string>
|
||||
<string name="theme_gradient">Farbverlauf</string>
|
||||
<string name="theme_gradient_detail">Eigene Primär- und Sekundärfarbe, als Verlauf auf Schaltflächen und Hervorhebungen.</string>
|
||||
<string name="theme_gradient_primary">Primär</string>
|
||||
@@ -1514,6 +1518,13 @@
|
||||
<string name="fm_conflict_skip">Überspringen</string>
|
||||
<string name="fm_select_this_folder">Diesen Ordner auswählen</string>
|
||||
<string name="fm_back">Zurück</string>
|
||||
<string name="fm_exit">Beenden</string>
|
||||
<string name="fm_return_to_start">Zurück zum Startordner</string>
|
||||
<string name="fm_start_unavailable">Startordner ist nicht mehr verfügbar</string>
|
||||
<string name="fm_cancel_operation_title">Vorgang abbrechen?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Der Vorgang wird abgebrochen. Bist du sicher?</string>
|
||||
<string name="fm_accept">Akzeptieren</string>
|
||||
<string name="fm_decline">Ablehnen</string>
|
||||
<string name="fm_drive_not_readable">%1$s ist eingebunden, aber derzeit nicht lesbar</string>
|
||||
<string name="fm_list_view">Listenansicht</string>
|
||||
<string name="fm_grid_view">Rasteransicht</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: απόδοση sysmem</string>
|
||||
<string name="perf_sysmem_hint">Ταχύτερο για το πρόγραμμα-πελάτη Steam· στα παιχνίδια ο οδηγός επιλέγει σε κάθε πέρασμα απόδοση με πλακίδια ή στη μνήμη συστήματος. Απαιτείται σε Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Παράβλεψη του βοηθού xalia του Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (πειραματικό)</string>
|
||||
<string name="perf_fastsync_hint">Ένα υποκατάστατο σε επίπεδο χρήστη για το πρόγραμμα οδήγησης ntsync του Linux, για δοκιμές. Όσο είναι ενεργό, το Proton το χρησιμοποιεί αντί για το droiddeck-esync και το droiddeck-fsync. Λειτουργεί με Proton Experimental ARM64, GE-Proton και proton-cachyos. Εφαρμόζεται στην επόμενη συνεδρία.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Ένα υποκατάστατο σε επίπεδο χρήστη για το πρόγραμμα οδήγησης ntsync του Linux. Όσο είναι ενεργό, το Proton το χρησιμοποιεί αντί για το droiddeck-esync και το droiddeck-fsync. Λειτουργεί με Proton Experimental ARM64, GE-Proton και proton-cachyos. Εφαρμόζεται στην επόμενη συνεδρία.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync πρώτα</string>
|
||||
<string name="perf_fsync_hint">Εκτελεί το δικό του fsync του Proton στα απλά αρχεία Proton, με το futex_waitv παρεχόμενο σε επίπεδο χρήστη, οπότε δεν χρειάζεται πακέτο και συνεχίζει να λειτουργεί τη στιγμή που το Steam ενημερώνει το Proton. Όσο είναι ενεργό, τα παιχνίδια το χρησιμοποιούν πριν από το droiddeck-esync, εκτός από τα λίγα που το Proton κρατά εκτός fsync. Εφαρμόζεται στην επόμενη συνεδρία.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Μαύρο φόντο, λευκά γράμματα, το μπλε για ό,τι είναι επιλεγμένο, εστιασμένο ή πατημένο.</string>
|
||||
<string name="theme_electric">Ηλεκτρικό ναυτικό μπλε</string>
|
||||
<string name="theme_electric_detail">Το μπλε πιο έντονο, σε επιφάνειες που τείνουν προς το ναυτικό μπλε αντί για γκρι.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Χρώματα που διατρέχουν το ουράνιο τόξο. Ακολουθεί τις ρυθμίσεις animation του Android.</string>
|
||||
<string name="theme_rgb_speed">Ταχύτητα</string>
|
||||
<string name="theme_rgb_speed_hint">Πόσο γρήγορα αλλάζουν τα χρώματα.</string>
|
||||
<string name="theme_gradient">Διαβάθμιση</string>
|
||||
<string name="theme_gradient_detail">Δικά σας πρωτεύον και δευτερεύον χρώμα, σε μίξη στα κουμπιά και τις επισημάνσεις.</string>
|
||||
<string name="theme_gradient_primary">Πρωτεύον</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Παράλειψη</string>
|
||||
<string name="fm_select_this_folder">Επιλογή αυτού του φακέλου</string>
|
||||
<string name="fm_back">Πίσω</string>
|
||||
<string name="fm_exit">Έξοδος</string>
|
||||
<string name="fm_return_to_start">Επιστροφή στον αρχικό φάκελο</string>
|
||||
<string name="fm_start_unavailable">Ο αρχικός φάκελος δεν είναι πλέον διαθέσιμος</string>
|
||||
<string name="fm_cancel_operation_title">Ακύρωση ενέργειας;</string>
|
||||
<string name="fm_cancel_operation_confirmation">Η ενέργεια θα ακυρωθεί. Είστε σίγουροι;</string>
|
||||
<string name="fm_accept">Αποδοχή</string>
|
||||
<string name="fm_decline">Απόρριψη</string>
|
||||
<string name="fm_drive_not_readable">Το %1$s είναι προσαρτημένο αλλά δεν είναι αναγνώσιμο αυτή τη στιγμή</string>
|
||||
<string name="fm_list_view">Προβολή λίστας</string>
|
||||
<string name="fm_grid_view">Προβολή πλέγματος</string>
|
||||
|
||||
@@ -545,8 +545,8 @@
|
||||
<string name="perf_sysmem">Turnip: renderizado sysmem</string>
|
||||
<string name="perf_sysmem_hint">Más rápido para el cliente de Steam; en los juegos, el controlador elige entre renderizado por mosaicos o en memoria del sistema en cada pasada. Obligatorio en Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Omitir el asistente xalia de Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">Un sustituto en espacio de usuario del controlador ntsync de Linux, para pruebas. Mientras está activado, Proton lo usa en lugar de droiddeck-esync y droiddeck-fsync. Funciona con Proton Experimental ARM64, GE-Proton y proton-cachyos. Se aplica en la próxima sesión.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Un sustituto en espacio de usuario del controlador ntsync de Linux. Mientras está activado, Proton lo usa en lugar de droiddeck-esync y droiddeck-fsync. Funciona con Proton Experimental ARM64, GE-Proton y proton-cachyos. Se aplica en la próxima sesión.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync primero</string>
|
||||
<string name="perf_fsync_hint">Usa el fsync propio de Proton con los archivos originales de Proton, con futex_waitv proporcionado en espacio de usuario, así que no necesita paquete y sigue funcionando en cuanto Steam actualiza Proton. Mientras está activado, los juegos lo usan antes que droiddeck-esync, salvo los pocos que Proton mantiene sin fsync. Se aplica en la próxima sesión.</string>
|
||||
@@ -1417,6 +1417,10 @@
|
||||
<string name="theme_icon_blue_detail">Fondo negro, texto blanco y el azul para todo lo seleccionado, enfocado o pulsado.</string>
|
||||
<string name="theme_electric">Azul marino eléctrico</string>
|
||||
<string name="theme_electric_detail">Un azul más vivo, sobre superficies que tiran a azul marino en vez de gris.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Colores que recorren el arcoíris. Respeta los ajustes de animación de Android.</string>
|
||||
<string name="theme_rgb_speed">Velocidad</string>
|
||||
<string name="theme_rgb_speed_hint">Con qué rapidez cambian los colores.</string>
|
||||
<string name="theme_gradient">Degradado</string>
|
||||
<string name="theme_gradient_detail">Tus propios colores primario y secundario, combinados en botones y resaltados.</string>
|
||||
<string name="theme_gradient_primary">Primario</string>
|
||||
@@ -1543,6 +1547,13 @@
|
||||
<string name="fm_conflict_skip">Omitir</string>
|
||||
<string name="fm_select_this_folder">Seleccionar esta carpeta</string>
|
||||
<string name="fm_back">Atrás</string>
|
||||
<string name="fm_exit">Salir</string>
|
||||
<string name="fm_return_to_start">Volver a la carpeta inicial</string>
|
||||
<string name="fm_start_unavailable">La carpeta inicial ya no está disponible</string>
|
||||
<string name="fm_cancel_operation_title">¿Cancelar la operación?</string>
|
||||
<string name="fm_cancel_operation_confirmation">La operación se cancelará. ¿Estás seguro?</string>
|
||||
<string name="fm_accept">Aceptar</string>
|
||||
<string name="fm_decline">Rechazar</string>
|
||||
<string name="fm_drive_not_readable">%1$s está montado, pero ahora mismo no se puede leer</string>
|
||||
<string name="fm_list_view">Vista de lista</string>
|
||||
<string name="fm_grid_view">Vista de cuadrícula</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem-renderöinti</string>
|
||||
<string name="perf_sysmem_hint">Nopeampi Steam-asiakasohjelmalle; peleissä ajuri valitsee jokaiselle vaiheelle ruuduittaisen tai järjestelmämuistiin tehtävän renderöinnin. Pakollinen Adreno 710/720/722:lla.</string>
|
||||
<string name="perf_xalia">Ohita Steamin xalia-avustaja</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (kokeellinen)</string>
|
||||
<string name="perf_fastsync_hint">Käyttäjätilainen korvike Linuxin ntsync-ajurille, testausta varten. Kun se on päällä, Proton käyttää sitä droiddeck-esyncin ja droiddeck-fsyncin sijaan. Toimii Proton Experimental ARM64:n, GE-Protonin ja proton-cachyosin kanssa. Tulee voimaan seuraavassa istunnossa.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Käyttäjätilainen korvike Linuxin ntsync-ajurille. Kun se on päällä, Proton käyttää sitä droiddeck-esyncin ja droiddeck-fsyncin sijaan. Toimii Proton Experimental ARM64:n, GE-Protonin ja proton-cachyosin kanssa. Tulee voimaan seuraavassa istunnossa.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync etusijalle</string>
|
||||
<string name="perf_fsync_hint">Käynnistää Protonin omaa fsynciä vakiotiedostoilla, futex_waitv tarjottuna käyttäjätilassa, niin se ei tarvitse pakettia ja toimii edelleen heti kun Steam päivittää Protonin. Kun se on päällä, pelit käyttävät sitä droiddeck-esyncin edellä, paitsi harvat, jotka Proton pitää pois fsyncistä. Tulee voimaan seuraavassa istunnossa.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Musta pohja, valkoinen teksti, sininen kaikessa valitussa, kohdistetussa tai painetussa.</string>
|
||||
<string name="theme_electric">Sähköinen laivastonsininen</string>
|
||||
<string name="theme_electric_detail">Sininen kirkkaampana pinnoilla, jotka taittuvat harmaan sijaan laivastonsiniseen.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Värit, jotka kiertävät sateenkaaren läpi. Noudattaa Androidin animaatioasetuksia.</string>
|
||||
<string name="theme_rgb_speed">Nopeus</string>
|
||||
<string name="theme_rgb_speed_hint">Kuinka nopeasti värit vaihtuvat.</string>
|
||||
<string name="theme_gradient">Liukuväri</string>
|
||||
<string name="theme_gradient_detail">Omat ensisijainen ja toissijainen värisi, sulautettuina painikkeisiin ja korostuksiin.</string>
|
||||
<string name="theme_gradient_primary">Ensisijainen</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Ohita</string>
|
||||
<string name="fm_select_this_folder">Valitse tämä kansio</string>
|
||||
<string name="fm_back">Takaisin</string>
|
||||
<string name="fm_exit">Poistu</string>
|
||||
<string name="fm_return_to_start">Palaa aloituskansioon</string>
|
||||
<string name="fm_start_unavailable">Aloituskansio ei ole enää käytettävissä</string>
|
||||
<string name="fm_cancel_operation_title">Peruutetaanko toiminto?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Toiminto peruutetaan. Oletko varma?</string>
|
||||
<string name="fm_accept">Hyväksy</string>
|
||||
<string name="fm_decline">Hylkää</string>
|
||||
<string name="fm_drive_not_readable">%1$s on liitetty, mutta ei ole luettavissa juuri nyt</string>
|
||||
<string name="fm_list_view">Luettelonäkymä</string>
|
||||
<string name="fm_grid_view">Ruudukkonäkymä</string>
|
||||
|
||||
@@ -545,8 +545,8 @@
|
||||
<string name="perf_sysmem">Turnip : rendu sysmem</string>
|
||||
<string name="perf_sysmem_hint">Le client Steam sera plus rapide ; en jeu, le pilote choisit à chaque passe entre rendu par tuiles et rendu en mémoire système. Requis pour Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Ignorer l\'assistant xalia de Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (expérimental)</string>
|
||||
<string name="perf_fastsync_hint">Un substitut en espace utilisateur du pilote Linux ntsync, pour les tests. Lorsqu\'il est activé, Proton l\'utilise à la place de droiddeck-esync et de droiddeck-fsync. Fonctionne avec Proton Experimental ARM64, GE-Proton et proton-cachyos. Prendra effet à la prochaine session.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Un substitut en espace utilisateur du pilote Linux ntsync. Lorsqu\'il est activé, Proton l\'utilise à la place de droiddeck-esync et de droiddeck-fsync. Fonctionne avec Proton Experimental ARM64, GE-Proton et proton-cachyos. Prendra effet à la prochaine session.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync en priorité</string>
|
||||
<string name="perf_fsync_hint">Exécute le fsync propre à Proton sur les fichiers Proton d\'origine, avec futex_waitv fourni en espace utilisateur : aucun pack n\'est nécessaire et il continue de fonctionner dès que Steam met à jour Proton. Lorsqu\'il est activé, les jeux l\'utilisent avant droiddeck-esync, sauf les quelques-uns que Proton exclut de fsync. Prendra effet à la prochaine session.</string>
|
||||
@@ -1417,6 +1417,10 @@
|
||||
<string name="theme_icon_blue_detail">Fond noir, texte blanc, et le bleu pour tout élément sélectionné, ciblé ou pressé.</string>
|
||||
<string name="theme_electric">Marine électrique</string>
|
||||
<string name="theme_electric_detail">Un bleu plus vif, sur des surfaces qui tirent vers le marine plutôt que vers le gris.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Des couleurs qui parcourent l\'arc-en-ciel. Respecte les réglages d\'animation d\'Android.</string>
|
||||
<string name="theme_rgb_speed">Vitesse</string>
|
||||
<string name="theme_rgb_speed_hint">La vitesse de défilement des couleurs.</string>
|
||||
<string name="theme_gradient">Dégradé</string>
|
||||
<string name="theme_gradient_detail">Vos propres couleurs primaire et secondaire, fondues sur les boutons et les éléments en surbrillance.</string>
|
||||
<string name="theme_gradient_primary">Primaire</string>
|
||||
@@ -1543,6 +1547,13 @@
|
||||
<string name="fm_conflict_skip">Ignorer</string>
|
||||
<string name="fm_select_this_folder">Sélectionner ce dossier</string>
|
||||
<string name="fm_back">Retour</string>
|
||||
<string name="fm_exit">Quitter</string>
|
||||
<string name="fm_return_to_start">Revenir au dossier de départ</string>
|
||||
<string name="fm_start_unavailable">Le dossier de départ est indisponible</string>
|
||||
<string name="fm_cancel_operation_title">Annuler l’opération ?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Cette opération sera annulée. Confirmez-vous ?</string>
|
||||
<string name="fm_accept">Accepter</string>
|
||||
<string name="fm_decline">Refuser</string>
|
||||
<string name="fm_drive_not_readable">%1$s est monté, mais illisible pour le moment</string>
|
||||
<string name="fm_list_view">Vue en liste</string>
|
||||
<string name="fm_grid_view">Vue en grille</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem रेंडरिंग</string>
|
||||
<string name="perf_sysmem_hint">Steam क्लाइंट के लिए तेज़; गेम में ड्राइवर हर पास के लिए टाइल या सिस्टम मेमोरी रेंडरिंग चुनता है। Adreno 710/720/722 पर ज़रूरी।</string>
|
||||
<string name="perf_xalia">Steam के xalia हेल्पर को छोड़ें</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (एक्सपेरिमेंटल)</string>
|
||||
<string name="perf_fastsync_hint">जांच के लिए Linux ntsync ड्राइवर की यूज़रस्पेस जगह लेने वाली चीज़। चालू रहते हुए, Proton इसे droiddeck-esync और droiddeck-fsync के बदले इस्तेमाल करता है। यह Proton Experimental ARM64, GE-Proton और proton-cachyos के साथ काम करता है। अगले सेशन से लागू होता है।</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync ड्राइवर की यूज़रस्पेस जगह लेने वाली चीज़। चालू रहते हुए, Proton इसे droiddeck-esync और droiddeck-fsync के बदले इस्तेमाल करता है। यह Proton Experimental ARM64, GE-Proton और proton-cachyos के साथ काम करता है। अगले सेशन से लागू होता है।</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync पहले</string>
|
||||
<string name="perf_fsync_hint">स्टॉक Proton फ़ाइलों पर Proton का अपना fsync चलाता है, futex_waitv यूज़रस्पेस में दिया जाता है, इसलिए इसे किसी पैक की ज़रूरत नहीं और Steam के Proton अपडेट करते ही यह काम करता रहता है। चालू रहते हुए, गेम इसे droiddeck-esync से पहले इस्तेमाल करते हैं, सिवाय उन कुछ के जिन्हें Proton fsync से दूर रखता है। अगले सेशन से लागू होता है।</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">काली ज़मीन, सफ़ेद टाइप, चुने, फ़ोकस या दबाए गए हर चीज़ के लिए ब्लू रंग।</string>
|
||||
<string name="theme_electric">इलेक्ट्रिक नेवी</string>
|
||||
<string name="theme_electric_detail">ब्लू रंग ज़्यादा चमकीला, उन सतहों पर जो ग्रे के बजाय नेवी की ओर झुकती हैं।</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">इंद्रधनुष के रंगों में बदलते रंग। Android की एनिमेशन सेटिंग का पालन करती है।</string>
|
||||
<string name="theme_rgb_speed">गति</string>
|
||||
<string name="theme_rgb_speed_hint">रंग कितनी तेज़ी से बदलें।</string>
|
||||
<string name="theme_gradient">ग्रेडिएंट</string>
|
||||
<string name="theme_gradient_detail">आपके अपने प्राथमिक और द्वितीयक रंग, बटनों और हाइलाइट पर मिश्रित।</string>
|
||||
<string name="theme_gradient_primary">प्राथमिक</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">छोड़ें</string>
|
||||
<string name="fm_select_this_folder">यह फ़ोल्डर चुनें</string>
|
||||
<string name="fm_back">बैक</string>
|
||||
<string name="fm_exit">बाहर निकलें</string>
|
||||
<string name="fm_return_to_start">शुरुआती फ़ोल्डर पर लौटें</string>
|
||||
<string name="fm_start_unavailable">शुरुआती फ़ोल्डर अब उपलब्ध नहीं है</string>
|
||||
<string name="fm_cancel_operation_title">कार्रवाई रद्द करें?</string>
|
||||
<string name="fm_cancel_operation_confirmation">कार्रवाई रद्द कर दी जाएगी। क्या आप सुनिश्चित हैं?</string>
|
||||
<string name="fm_accept">स्वीकार करें</string>
|
||||
<string name="fm_decline">अस्वीकार करें</string>
|
||||
<string name="fm_drive_not_readable">%1$s माउंट है लेकिन अभी पढ़ा नहीं जा सकता</string>
|
||||
<string name="fm_list_view">लिस्ट व्यू</string>
|
||||
<string name="fm_grid_view">ग्रिड व्यू</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem renderelés</string>
|
||||
<string name="perf_sysmem_hint">Gyorsabb a Steam kliensnek; játékokban a meghajtó menetenként választ a csempés és a rendszermemóriás renderelés között. Adreno 710/720/722-n szükséges.</string>
|
||||
<string name="perf_xalia">A Steam xalia segítőjének kihagyása</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (kísérleti)</string>
|
||||
<string name="perf_fastsync_hint">A Linux ntsync illesztőprogram felhasználói szintű helyettesítője, teszteléshez. Amíg be van kapcsolva, a Proton ezt használja a droiddeck-esync és a droiddeck-fsync helyett. A Proton Experimental ARM64-gyel, a GE-Protonnal és a proton-cachyosszal működik. A következő munkamenettől érvényes.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">A Linux ntsync illesztőprogram felhasználói szintű helyettesítője. Amíg be van kapcsolva, a Proton ezt használja a droiddeck-esync és a droiddeck-fsync helyett. A Proton Experimental ARM64-gyel, a GE-Protonnal és a proton-cachyosszal működik. A következő munkamenettől érvényes.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync előre</string>
|
||||
<string name="perf_fsync_hint">A Proton saját fsyncjét futtatja az eredeti Proton-fájlokon, felhasználói szinten biztosított futex_waitv-vel, így nincs szükség csomagra, és azonnal működik, amint a Steam frissíti a Protont. Amíg be van kapcsolva, a játékok ezt használják a droiddeck-esync előtt, kivéve azt a néhányat, amelyet a Proton kizár az fsyncből. A következő munkamenettől érvényes.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Fekete alap, fehér betűk, a kék minden kijelölt, fókuszált vagy lenyomott elemhez.</string>
|
||||
<string name="theme_electric">Elektromos tengerészkék</string>
|
||||
<string name="theme_electric_detail">Fényesebb kék, szürke helyett tengerészkék felületeken.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">A szivárvány színein végigfutó színek. Követi az Android animációs beállításait.</string>
|
||||
<string name="theme_rgb_speed">Sebesség</string>
|
||||
<string name="theme_rgb_speed_hint">Milyen gyorsan váltanak a színek.</string>
|
||||
<string name="theme_gradient">Színátmenet</string>
|
||||
<string name="theme_gradient_detail">Saját elsődleges és másodlagos színed, átmenettel a gombokon és a kiemeléseken.</string>
|
||||
<string name="theme_gradient_primary">Elsődleges</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Kihagyás</string>
|
||||
<string name="fm_select_this_folder">Ennek a mappának a kiválasztása</string>
|
||||
<string name="fm_back">Vissza</string>
|
||||
<string name="fm_exit">Kilépés</string>
|
||||
<string name="fm_return_to_start">Vissza a kezdőmappába</string>
|
||||
<string name="fm_start_unavailable">A kezdőmappa már nem érhető el</string>
|
||||
<string name="fm_cancel_operation_title">Művelet megszakítása?</string>
|
||||
<string name="fm_cancel_operation_confirmation">A művelet megszakad. Biztos benne?</string>
|
||||
<string name="fm_accept">Elfogadom</string>
|
||||
<string name="fm_decline">Elutasítom</string>
|
||||
<string name="fm_drive_not_readable">A %1$s csatolva van, de jelenleg nem olvasható</string>
|
||||
<string name="fm_list_view">Listanézet</string>
|
||||
<string name="fm_grid_view">Rácsnézet</string>
|
||||
|
||||
@@ -539,8 +539,8 @@
|
||||
<string name="perf_sysmem">Turnip: rendering sysmem</string>
|
||||
<string name="perf_sysmem_hint">Lebih cepat untuk klien Steam; di gim, driver memilih rendering ubin atau memori sistem untuk setiap pass. Diperlukan pada Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Lewati bantuan xalia milik Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (eksperimental)</string>
|
||||
<string name="perf_fastsync_hint">Pengganti userspace untuk driver ntsync Linux, untuk pengujian. Saat aktif, Proton menggunakannya sebagai pengganti droiddeck-esync dan droiddeck-fsync. Berfungsi dengan Proton Experimental ARM64, GE-Proton, dan proton-cachyos. Berlaku pada sesi berikutnya.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Pengganti userspace untuk driver ntsync Linux. Saat aktif, Proton menggunakannya sebagai pengganti droiddeck-esync dan droiddeck-fsync. Berfungsi dengan Proton Experimental ARM64, GE-Proton, dan proton-cachyos. Berlaku pada sesi berikutnya.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync lebih dulu</string>
|
||||
<string name="perf_fsync_hint">Menjalankan fsync milik Proton sendiri pada file Proton standar, dengan futex_waitv yang disediakan di userspace, sehingga tidak memerlukan paket dan tetap berfungsi begitu Steam memperbarui Proton. Saat aktif, gim menggunakannya sebelum droiddeck-esync, kecuali beberapa gim yang oleh Proton dijaga tetap di luar fsync. Berlaku pada sesi berikutnya.</string>
|
||||
@@ -1387,6 +1387,10 @@
|
||||
<string name="theme_icon_blue_detail">Dasar hitam, tipe putih, biru untuk semua yang dipilih, difokuskan, atau ditekan.</string>
|
||||
<string name="theme_electric">Navy elektrik</string>
|
||||
<string name="theme_electric_detail">Biru yang lebih cerah, pada permukaan yang condong ke navy, bukan abu-abu.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Warna yang berganti menyusuri pelangi. Mengikuti pengaturan animasi Android.</string>
|
||||
<string name="theme_rgb_speed">Kecepatan</string>
|
||||
<string name="theme_rgb_speed_hint">Seberapa cepat warna berganti.</string>
|
||||
<string name="theme_gradient">Gradasi</string>
|
||||
<string name="theme_gradient_detail">Warna primer dan sekunder pilihan Anda, dipadukan pada tombol dan sorotan.</string>
|
||||
<string name="theme_gradient_primary">Primer</string>
|
||||
@@ -1503,6 +1507,13 @@
|
||||
<string name="fm_conflict_skip">Lewati</string>
|
||||
<string name="fm_select_this_folder">Pilih folder ini</string>
|
||||
<string name="fm_back">Kembali</string>
|
||||
<string name="fm_exit">Keluar</string>
|
||||
<string name="fm_return_to_start">Kembali ke folder awal</string>
|
||||
<string name="fm_start_unavailable">Folder awal tidak lagi tersedia</string>
|
||||
<string name="fm_cancel_operation_title">Batalkan operasi?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operasi akan dibatalkan. Anda yakin?</string>
|
||||
<string name="fm_accept">Terima</string>
|
||||
<string name="fm_decline">Tolak</string>
|
||||
<string name="fm_drive_not_readable">%1$s terpasang tetapi tidak dapat dibaca saat ini</string>
|
||||
<string name="fm_list_view">Tampilan daftar</string>
|
||||
<string name="fm_grid_view">Tampilan kisi</string>
|
||||
|
||||
@@ -543,8 +543,8 @@
|
||||
<string name="perf_sysmem">Turnip: rendering sysmem</string>
|
||||
<string name="perf_sysmem_hint">Più veloce per il client di Steam; nei giochi il driver sceglie per ogni passata tra rendering a tile e in memoria di sistema. Necessario su Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Salta l\'assistente xalia di Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (sperimentale)</string>
|
||||
<string name="perf_fastsync_hint">Un sostituto in userspace del driver ntsync di Linux, per i test. Quando è attivo, Proton lo usa al posto di droiddeck-esync e droiddeck-fsync. Funziona con Proton Experimental ARM64, GE-Proton e proton-cachyos. Si applica dalla prossima sessione.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Un sostituto in userspace del driver ntsync di Linux. Quando è attivo, Proton lo usa al posto di droiddeck-esync e droiddeck-fsync. Funziona con Proton Experimental ARM64, GE-Proton e proton-cachyos. Si applica dalla prossima sessione.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">Prima droiddeck-fsync</string>
|
||||
<string name="perf_fsync_hint">Esegue il fsync di Proton sui file Proton originali, con futex_waitv fornito in userspace, quindi non richiede pacchetti e continua a funzionare appena Steam aggiorna Proton. Quando è attivo, i giochi lo usano prima di droiddeck-esync, tranne i pochi che Proton esclude da fsync. Si applica dalla prossima sessione.</string>
|
||||
@@ -1407,6 +1407,10 @@
|
||||
<string name="theme_icon_blue_detail">Fondo nero, testo bianco, il blu per tutto ciò che è selezionato, attivo o premuto.</string>
|
||||
<string name="theme_electric">Blu elettrico</string>
|
||||
<string name="theme_electric_detail">Il blu più brillante, su superfici che tendono al blu navy invece che al grigio.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Colori che scorrono lungo l\'arcobaleno. Rispetta le impostazioni di animazione di Android.</string>
|
||||
<string name="theme_rgb_speed">Velocità</string>
|
||||
<string name="theme_rgb_speed_hint">Quanto velocemente cambiano i colori.</string>
|
||||
<string name="theme_gradient">Gradiente</string>
|
||||
<string name="theme_gradient_detail">I tuoi colori primario e secondario, sfumati su pulsanti ed evidenziazioni.</string>
|
||||
<string name="theme_gradient_primary">Primario</string>
|
||||
@@ -1533,6 +1537,13 @@
|
||||
<string name="fm_conflict_skip">Salta</string>
|
||||
<string name="fm_select_this_folder">Seleziona questa cartella</string>
|
||||
<string name="fm_back">Indietro</string>
|
||||
<string name="fm_exit">Esci</string>
|
||||
<string name="fm_return_to_start">Torna alla cartella iniziale</string>
|
||||
<string name="fm_start_unavailable">La cartella iniziale non è più disponibile</string>
|
||||
<string name="fm_cancel_operation_title">Annullare operazione?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Questa operazione verrà annullata. Confermi?</string>
|
||||
<string name="fm_accept">Accetta</string>
|
||||
<string name="fm_decline">Rifiuta</string>
|
||||
<string name="fm_drive_not_readable">%1$s: montata ma al momento non leggibile</string>
|
||||
<string name="fm_list_view">Visualizzazione elenco</string>
|
||||
<string name="fm_grid_view">Visualizzazione griglia</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip:sysmemレンダリング</string>
|
||||
<string name="perf_sysmem_hint">Steamクライアントが高速になります。ゲームではドライバーがパスごとにタイルレンダリングとシステムメモリレンダリングを選びます。Adreno 710/720/722では必須です。</string>
|
||||
<string name="perf_xalia">Steamのxalia補助をスキップ</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync(実験的)</string>
|
||||
<string name="perf_fastsync_hint">Linuxのntsyncドライバーをユーザー空間で代替するテスト用機能です。オンの間、Protonはdroiddeck-esyncとdroiddeck-fsyncの代わりにこれを使用します。Proton Experimental ARM64、GE-Proton、proton-cachyosで動作します。次回のセッションで有効になります。</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Linuxのntsyncドライバーをユーザー空間で代替する機能です。オンの間、Protonはdroiddeck-esyncとdroiddeck-fsyncの代わりにこれを使用します。Proton Experimental ARM64、GE-Proton、proton-cachyosで動作します。次回のセッションで有効になります。</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsyncを優先</string>
|
||||
<string name="perf_fsync_hint">futex_waitvをユーザー空間で提供し、標準のProtonファイルのままProton自身のfsyncを実行します。パックが不要なため、SteamがProtonを更新してもすぐに動作し続けます。オンの間、Protonがfsyncを使わない一部のゲームを除き、ゲームはdroiddeck-esyncよりこちらを優先します。次回のセッションで有効になります。</string>
|
||||
@@ -1389,6 +1389,10 @@
|
||||
<string name="theme_icon_blue_detail">黒の背景に白の文字、選択・フォーカス・押下時には青を使用します。</string>
|
||||
<string name="theme_electric">エレクトリックネイビー</string>
|
||||
<string name="theme_electric_detail">より明るい青を、グレーではなくネイビー寄りの画面に使用します。</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">虹色に変化し続けるカラー。Android のアニメーション設定に従います。</string>
|
||||
<string name="theme_rgb_speed">速度</string>
|
||||
<string name="theme_rgb_speed_hint">色が切り替わる速さ。</string>
|
||||
<string name="theme_gradient">グラデーション</string>
|
||||
<string name="theme_gradient_detail">自分で選んだプライマリとセカンダリの色を、ボタンやハイライトにグラデーションで使います。</string>
|
||||
<string name="theme_gradient_primary">プライマリ</string>
|
||||
@@ -1505,6 +1509,13 @@
|
||||
<string name="fm_conflict_skip">スキップ</string>
|
||||
<string name="fm_select_this_folder">このフォルダを選択</string>
|
||||
<string name="fm_back">戻る</string>
|
||||
<string name="fm_exit">終了</string>
|
||||
<string name="fm_return_to_start">開始フォルダーに戻る</string>
|
||||
<string name="fm_start_unavailable">開始フォルダーは利用できなくなりました</string>
|
||||
<string name="fm_cancel_operation_title">操作をキャンセルしますか?</string>
|
||||
<string name="fm_cancel_operation_confirmation">操作がキャンセルされます。よろしいですか?</string>
|
||||
<string name="fm_accept">承諾</string>
|
||||
<string name="fm_decline">拒否</string>
|
||||
<string name="fm_drive_not_readable">%1$sはマウントされていますが、現在読み取れません</string>
|
||||
<string name="fm_list_view">リスト表示</string>
|
||||
<string name="fm_grid_view">グリッド表示</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem 렌더링</string>
|
||||
<string name="perf_sysmem_hint">Steam 클라이언트가 빨라집니다. 게임에서는 드라이버가 패스마다 타일 렌더링과 시스템 메모리 렌더링 중 하나를 고릅니다. Adreno 710/720/722에 필수입니다.</string>
|
||||
<string name="perf_xalia">Steam의 xalia 도우미 건너뛰기</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync(실험적)</string>
|
||||
<string name="perf_fastsync_hint">테스트용으로 Linux ntsync 드라이버를 사용자 공간에서 대신하는 기능입니다. 켜져 있으면 Proton이 droiddeck-esync와 droiddeck-fsync 대신 이 기능을 사용합니다. Proton Experimental ARM64, GE-Proton 및 proton-cachyos에서 작동합니다. 다음 세션에 적용됩니다.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync 드라이버를 사용자 공간에서 대신하는 기능입니다. 켜져 있으면 Proton이 droiddeck-esync와 droiddeck-fsync 대신 이 기능을 사용합니다. Proton Experimental ARM64, GE-Proton 및 proton-cachyos에서 작동합니다. 다음 세션에 적용됩니다.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync 우선</string>
|
||||
<string name="perf_fsync_hint">futex_waitv를 사용자 공간에서 제공하여 기본 Proton 파일로 Proton 자체의 fsync를 실행하므로, 팩이 필요 없고 Steam이 Proton을 업데이트해도 바로 작동합니다. 켜져 있으면 게임이 droiddeck-esync보다 이 기능을 우선 사용합니다. 단, Proton이 fsync를 끄는 일부 게임은 예외입니다. 다음 세션에 적용됩니다.</string>
|
||||
@@ -1389,6 +1389,10 @@
|
||||
<string name="theme_icon_blue_detail">검은색 배경에 흰색 글자, 선택·포커스·누름 상태에는 파란색을 사용합니다.</string>
|
||||
<string name="theme_electric">일렉트릭 네이비</string>
|
||||
<string name="theme_electric_detail">회색 대신 네이비 톤의 화면에 더 밝은 파란색을 사용합니다.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">무지개 색으로 계속 바뀌는 색상입니다. Android의 애니메이션 설정을 따릅니다.</string>
|
||||
<string name="theme_rgb_speed">속도</string>
|
||||
<string name="theme_rgb_speed_hint">색상이 바뀌는 속도입니다.</string>
|
||||
<string name="theme_gradient">그라데이션</string>
|
||||
<string name="theme_gradient_detail">직접 고른 기본색과 보조색이 버튼과 강조 표시에 섞여 적용됩니다.</string>
|
||||
<string name="theme_gradient_primary">기본색</string>
|
||||
@@ -1505,6 +1509,13 @@
|
||||
<string name="fm_conflict_skip">건너뛰기</string>
|
||||
<string name="fm_select_this_folder">이 폴더 선택</string>
|
||||
<string name="fm_back">뒤로</string>
|
||||
<string name="fm_exit">종료</string>
|
||||
<string name="fm_return_to_start">시작 폴더로 돌아가기</string>
|
||||
<string name="fm_start_unavailable">시작 폴더를 더 이상 사용할 수 없습니다</string>
|
||||
<string name="fm_cancel_operation_title">작업을 취소할까요?</string>
|
||||
<string name="fm_cancel_operation_confirmation">작업이 취소됩니다. 계속하시겠습니까?</string>
|
||||
<string name="fm_accept">수락</string>
|
||||
<string name="fm_decline">거절</string>
|
||||
<string name="fm_drive_not_readable">%1$s이(가) 마운트되었지만 지금은 읽을 수 없습니다</string>
|
||||
<string name="fm_list_view">목록 보기</string>
|
||||
<string name="fm_grid_view">격자 보기</string>
|
||||
|
||||
@@ -539,8 +539,8 @@
|
||||
<string name="perf_sysmem">Turnip: rendering sysmem</string>
|
||||
<string name="perf_sysmem_hint">Lebih pantas untuk klien Steam; dalam permainan, pemacu memilih pemaparan jubin atau memori sistem bagi setiap laluan. Diperlukan pada Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Langkau pembantu xalia Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (eksperimen)</string>
|
||||
<string name="perf_fastsync_hint">Pengganti ruang pengguna untuk pemacu ntsync Linux, untuk pengujian. Semasa ia hidup, Proton menggunakannya sebagai ganti droiddeck-esync dan droiddeck-fsync. Berfungsi dengan Proton Experimental ARM64, GE-Proton dan proton-cachyos. Berkuat kuasa pada sesi seterusnya.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Pengganti ruang pengguna untuk pemacu ntsync Linux. Semasa ia hidup, Proton menggunakannya sebagai ganti droiddeck-esync dan droiddeck-fsync. Berfungsi dengan Proton Experimental ARM64, GE-Proton dan proton-cachyos. Berkuat kuasa pada sesi seterusnya.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync dahulu</string>
|
||||
<string name="perf_fsync_hint">Menjalankan fsync Proton sendiri pada fail Proton standard, dengan futex_waitv dibekalkan dalam ruang pengguna, jadi ia tidak memerlukan pek dan terus berfungsi sebaik sahaja Steam mengemas kini Proton. Semasa ia hidup, permainan menggunakannya mendahului droiddeck-esync, kecuali beberapa Proton yang mematikan fsync. Berkuat kuasa pada sesi seterusnya.</string>
|
||||
@@ -1387,6 +1387,10 @@
|
||||
<string name="theme_icon_blue_detail">Latar hitam, teks putih, biru untuk semua yang dipilih, difokus atau ditekan.</string>
|
||||
<string name="theme_electric">Navy elektrik</string>
|
||||
<string name="theme_electric_detail">Biru lebih cerah, pada permukaan yang lebih cenderung navy daripada kelabu.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Warna yang bertukar merentasi pelangi. Mengikut tetapan animasi Android.</string>
|
||||
<string name="theme_rgb_speed">Kelajuan</string>
|
||||
<string name="theme_rgb_speed_hint">Seberapa pantas warna bertukar.</string>
|
||||
<string name="theme_gradient">Kecerunan</string>
|
||||
<string name="theme_gradient_detail">Warna utama dan sekunder pilihan anda, diadun pada butang dan sorotan.</string>
|
||||
<string name="theme_gradient_primary">Utama</string>
|
||||
@@ -1503,6 +1507,13 @@
|
||||
<string name="fm_conflict_skip">Langkau</string>
|
||||
<string name="fm_select_this_folder">Pilih folder ini</string>
|
||||
<string name="fm_back">Kembali</string>
|
||||
<string name="fm_exit">Keluar</string>
|
||||
<string name="fm_return_to_start">Kembali ke folder asal</string>
|
||||
<string name="fm_start_unavailable">Folder asal tidak lagi tersedia</string>
|
||||
<string name="fm_cancel_operation_title">Batalkan operasi?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operasi akan dibatalkan. Adakah anda pasti?</string>
|
||||
<string name="fm_accept">Terima</string>
|
||||
<string name="fm_decline">Tolak</string>
|
||||
<string name="fm_drive_not_readable">%1$s dilekapkan tetapi tidak dapat dibaca sekarang</string>
|
||||
<string name="fm_list_view">Paparan senarai</string>
|
||||
<string name="fm_grid_view">Paparan grid</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem-rendering</string>
|
||||
<string name="perf_sysmem_hint">Raskere for Steam-klienten; i spill velger driveren flis- eller systemminnerendering for hvert pass. Påkrevd på Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Hopp over Steams xalia-hjelper</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (eksperimentell)</string>
|
||||
<string name="perf_fastsync_hint">En erstatning i brukerrom for Linux-driveren ntsync, for testing. Mens dette er på, bruker Proton den i stedet for droiddeck-esync og droiddeck-fsync. Fungerer med Proton Experimental ARM64, GE-Proton og proton-cachyos. Gjelder fra neste sesjon.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">En erstatning i brukerrom for Linux-driveren ntsync. Mens dette er på, bruker Proton den i stedet for droiddeck-esync og droiddeck-fsync. Fungerer med Proton Experimental ARM64, GE-Proton og proton-cachyos. Gjelder fra neste sesjon.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync først</string>
|
||||
<string name="perf_fsync_hint">Kjører Protons egen fsync på de uendrede Proton-filene, med futex_waitv levert i brukerrom, så den trenger ingen pakke og fortsetter å fungere i det Steam oppdaterer Proton. Mens dette er på, bruker spill den foran droiddeck-esync, med unntak av de få Proton holder utenfor fsync. Gjelder fra neste sesjon.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Svart bakgrunn, hvit skrift, det blå for alt som er valgt, fokusert eller trykket.</string>
|
||||
<string name="theme_electric">Elektrisk marineblå</string>
|
||||
<string name="theme_electric_detail">Det blå lysere, på flater som lener mer mot marineblått enn grått.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Farger som går gjennom regnbuen. Følger Androids animasjonsinnstillinger.</string>
|
||||
<string name="theme_rgb_speed">Hastighet</string>
|
||||
<string name="theme_rgb_speed_hint">Hvor raskt fargene skifter.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Dine egne primær- og sekundærfarger, blandet på knapper og uthevinger.</string>
|
||||
<string name="theme_gradient_primary">Primær</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Hopp over</string>
|
||||
<string name="fm_select_this_folder">Velg denne mappen</string>
|
||||
<string name="fm_back">Tilbake</string>
|
||||
<string name="fm_exit">Avslutt</string>
|
||||
<string name="fm_return_to_start">Tilbake til startmappen</string>
|
||||
<string name="fm_start_unavailable">Startmappen er ikke lenger tilgjengelig</string>
|
||||
<string name="fm_cancel_operation_title">Avbryt operasjonen?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operasjonen blir avbrutt. Er du sikker?</string>
|
||||
<string name="fm_accept">Godta</string>
|
||||
<string name="fm_decline">Avslå</string>
|
||||
<string name="fm_drive_not_readable">%1$s er montert, men ikke lesbar akkurat nå</string>
|
||||
<string name="fm_list_view">Listevisning</string>
|
||||
<string name="fm_grid_view">Rutenettvisning</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem-rendering</string>
|
||||
<string name="perf_sysmem_hint">Sneller voor de Steam-client; in games kiest het stuurprogramma per pass tussen tegel- en systeemgeheugenrendering. Vereist op Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Sla de xalia-helper van Steam over</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimenteel)</string>
|
||||
<string name="perf_fastsync_hint">Een userspace-vervanger voor de ntsync-driver van Linux, voor tests. Terwijl dit aan staat, gebruikt Proton dit in plaats van droiddeck-esync en droiddeck-fsync. Werkt met Proton Experimental ARM64, GE-Proton en proton-cachyos. Geldt vanaf de volgende sessie.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Een userspace-vervanger voor de ntsync-driver van Linux. Terwijl dit aan staat, gebruikt Proton dit in plaats van droiddeck-esync en droiddeck-fsync. Werkt met Proton Experimental ARM64, GE-Proton en proton-cachyos. Geldt vanaf de volgende sessie.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync eerst</string>
|
||||
<string name="perf_fsync_hint">Draait de eigen fsync van Proton op de standaard Proton-bestanden, met futex_waitv in userspace geleverd, zodat er geen pakket nodig is en dit blijft werken zodra Steam Proton bijwerkt. Terwijl dit aan staat, gebruiken games dit vóór droiddeck-esync, behalve de paar die Proton niet op fsync laat draaien. Geldt vanaf de volgende sessie.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Zwarte achtergrond, witte tekst, het blauw voor alles wat geselecteerd, gefocust of ingedrukt is.</string>
|
||||
<string name="theme_electric">Elektrisch marineblauw</string>
|
||||
<string name="theme_electric_detail">Het blauw feller, op vlakken die naar marineblauw neigen in plaats van grijs.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Kleuren die door de regenboog lopen. Volgt de animatie-instellingen van Android.</string>
|
||||
<string name="theme_rgb_speed">Snelheid</string>
|
||||
<string name="theme_rgb_speed_hint">Hoe snel de kleuren wisselen.</string>
|
||||
<string name="theme_gradient">Kleurverloop</string>
|
||||
<string name="theme_gradient_detail">Je eigen primaire en secundaire kleur, doorlopend op knoppen en accenten.</string>
|
||||
<string name="theme_gradient_primary">Primair</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Overslaan</string>
|
||||
<string name="fm_select_this_folder">Deze map selecteren</string>
|
||||
<string name="fm_back">Terug</string>
|
||||
<string name="fm_exit">Afsluiten</string>
|
||||
<string name="fm_return_to_start">Terug naar beginmap</string>
|
||||
<string name="fm_start_unavailable">De beginmap is niet meer beschikbaar</string>
|
||||
<string name="fm_cancel_operation_title">Bewerking annuleren?</string>
|
||||
<string name="fm_cancel_operation_confirmation">De bewerking wordt geannuleerd. Weet je het zeker?</string>
|
||||
<string name="fm_accept">Accepteren</string>
|
||||
<string name="fm_decline">Weigeren</string>
|
||||
<string name="fm_drive_not_readable">%1$s is gekoppeld, maar nu niet leesbaar</string>
|
||||
<string name="fm_list_view">Lijstweergave</string>
|
||||
<string name="fm_grid_view">Rasterweergave</string>
|
||||
|
||||
@@ -545,8 +545,8 @@
|
||||
<string name="perf_sysmem">Turnip: renderowanie sysmem</string>
|
||||
<string name="perf_sysmem_hint">Szybsze dla klienta Steam; w grach sterownik dla każdego przebiegu wybiera renderowanie kafelkowe lub w pamięci systemowej. Wymagane na Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Pomijaj pomocnika xalia w Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (eksperymentalne)</string>
|
||||
<string name="perf_fastsync_hint">Zastępnik sterownika Linux ntsync w przestrzeni użytkownika, do testów. Gdy jest włączony, Proton używa go zamiast droiddeck-esync i droiddeck-fsync. Zgodny z Proton Experimental ARM64, GE-Proton i proton-cachyos. Działa od następnej sesji.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Zastępnik sterownika Linux ntsync w przestrzeni użytkownika. Gdy jest włączony, Proton używa go zamiast droiddeck-esync i droiddeck-fsync. Zgodny z Proton Experimental ARM64, GE-Proton i proton-cachyos. Działa od następnej sesji.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">Najpierw droiddeck-fsync</string>
|
||||
<string name="perf_fsync_hint">Uruchamia własny fsync Proton na standardowych plikach Proton, z futex_waitv dostarczanym w przestrzeni użytkownika, więc nie wymaga pakietu i działa od razu po aktualizacji Proton przez Steam. Gdy jest włączony, gry używają go przed droiddeck-esync, z wyjątkiem nielicznych, którym Proton wyłącza fsync. Działa od następnej sesji.</string>
|
||||
@@ -1429,6 +1429,10 @@
|
||||
<string name="theme_icon_blue_detail">Czarne tło, biały tekst i niebieski dla wszystkiego, co zaznaczone, aktywne lub naciśnięte.</string>
|
||||
<string name="theme_electric">Elektryczny granat</string>
|
||||
<string name="theme_electric_detail">Jaśniejszy niebieski na powierzchniach wpadających w granat zamiast w szarość.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Kolory przechodzące przez całą tęczę. Szanuje ustawienia animacji Androida.</string>
|
||||
<string name="theme_rgb_speed">Prędkość</string>
|
||||
<string name="theme_rgb_speed_hint">Jak szybko zmieniają się kolory.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Własny kolor podstawowy i dodatkowy, płynnie przechodzące na przyciskach i wyróżnieniach.</string>
|
||||
<string name="theme_gradient_primary">Podstawowy</string>
|
||||
@@ -1560,6 +1564,13 @@
|
||||
<string name="fm_conflict_skip">Pomiń</string>
|
||||
<string name="fm_select_this_folder">Wybierz ten folder</string>
|
||||
<string name="fm_back">Wstecz</string>
|
||||
<string name="fm_exit">Wyjdź</string>
|
||||
<string name="fm_return_to_start">Wróć do folderu początkowego</string>
|
||||
<string name="fm_start_unavailable">Folder początkowy nie jest już dostępny</string>
|
||||
<string name="fm_cancel_operation_title">Anulować operację?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operacja zostanie anulowana. Czy na pewno?</string>
|
||||
<string name="fm_accept">Akceptuj</string>
|
||||
<string name="fm_decline">Odrzuć</string>
|
||||
<string name="fm_drive_not_readable">%1$s: zamontowano, ale obecnie nie można odczytać</string>
|
||||
<string name="fm_list_view">Widok listy</string>
|
||||
<string name="fm_grid_view">Widok siatki</string>
|
||||
|
||||
@@ -543,8 +543,8 @@
|
||||
<string name="perf_sysmem">Turnip: renderização em sysmem</string>
|
||||
<string name="perf_sysmem_hint">Mais rápido para o cliente Steam; nos jogos, o controlador escolhe em cada passagem entre renderização em mosaico ou na memória do sistema. Obrigatório no Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Ignorar o assistente xalia do Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">Um substituto em espaço de utilizador para o controlador ntsync do Linux, para testes. Enquanto estiver ativado, o Proton utiliza-o em vez do droiddeck-esync e do droiddeck-fsync. Funciona com o Proton Experimental ARM64, o GE-Proton e o proton-cachyos. Aplica-se na próxima sessão.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Um substituto em espaço de utilizador para o controlador ntsync do Linux. Enquanto estiver ativado, o Proton utiliza-o em vez do droiddeck-esync e do droiddeck-fsync. Funciona com o Proton Experimental ARM64, o GE-Proton e o proton-cachyos. Aplica-se na próxima sessão.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync primeiro</string>
|
||||
<string name="perf_fsync_hint">Executa o fsync do próprio Proton sobre os ficheiros originais do Proton, com o futex_waitv fornecido em espaço de utilizador, por isso não precisa de pacote e continua a funcionar assim que o Steam atualiza o Proton. Enquanto estiver ativado, os jogos utilizam-no antes do droiddeck-esync, exceto os poucos que o Proton mantém fora do fsync. Aplica-se na próxima sessão.</string>
|
||||
@@ -1407,6 +1407,10 @@
|
||||
<string name="theme_icon_blue_detail">Fundo preto, texto branco e o azul para tudo o que está selecionado, focado ou premido.</string>
|
||||
<string name="theme_electric">Azul-marinho elétrico</string>
|
||||
<string name="theme_electric_detail">O azul mais vivo, em superfícies que tendem para o azul-marinho em vez do cinzento.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Cores que percorrem o arco-íris. Respeita as definições de animação do Android.</string>
|
||||
<string name="theme_rgb_speed">Velocidade</string>
|
||||
<string name="theme_rgb_speed_hint">Com que rapidez as cores mudam.</string>
|
||||
<string name="theme_gradient">Gradiente</string>
|
||||
<string name="theme_gradient_detail">As suas próprias cores primária e secundária, combinadas nos botões e destaques.</string>
|
||||
<string name="theme_gradient_primary">Primária</string>
|
||||
@@ -1533,6 +1537,13 @@
|
||||
<string name="fm_conflict_skip">Ignorar</string>
|
||||
<string name="fm_select_this_folder">Selecionar esta pasta</string>
|
||||
<string name="fm_back">Voltar</string>
|
||||
<string name="fm_exit">Sair</string>
|
||||
<string name="fm_return_to_start">Voltar à pasta inicial</string>
|
||||
<string name="fm_start_unavailable">A pasta inicial já não está disponível</string>
|
||||
<string name="fm_cancel_operation_title">Cancelar operação?</string>
|
||||
<string name="fm_cancel_operation_confirmation">A operação será cancelada. Tem a certeza?</string>
|
||||
<string name="fm_accept">Aceitar</string>
|
||||
<string name="fm_decline">Recusar</string>
|
||||
<string name="fm_drive_not_readable">%1$s está montado, mas não é possível lê-lo de momento</string>
|
||||
<string name="fm_list_view">Vista de lista</string>
|
||||
<string name="fm_grid_view">Vista de grelha</string>
|
||||
|
||||
@@ -543,8 +543,8 @@
|
||||
<string name="perf_sysmem">Turnip: renderização sysmem</string>
|
||||
<string name="perf_sysmem_hint">Mais rápido para o cliente da Steam; nos jogos, o driver escolhe a cada passagem entre renderização em blocos ou na memória do sistema. Obrigatório nas Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Pular o assistente xalia da Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">Um substituto em espaço de usuário para o driver ntsync do Linux, para testes. Enquanto estiver ativado, o Proton o usa no lugar do droiddeck-esync e do droiddeck-fsync. Funciona com o Proton Experimental ARM64, o GE-Proton e o proton-cachyos. Vale a partir da próxima sessão.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Um substituto em espaço de usuário para o driver ntsync do Linux. Enquanto estiver ativado, o Proton o usa no lugar do droiddeck-esync e do droiddeck-fsync. Funciona com o Proton Experimental ARM64, o GE-Proton e o proton-cachyos. Vale a partir da próxima sessão.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync primeiro</string>
|
||||
<string name="perf_fsync_hint">Roda o fsync do próprio Proton nos arquivos originais do Proton, com o futex_waitv fornecido em espaço de usuário, então não precisa de pacote e continua funcionando assim que a Steam atualiza o Proton. Enquanto estiver ativado, os jogos o usam antes do droiddeck-esync, exceto os poucos que o Proton mantém fora do fsync. Vale a partir da próxima sessão.</string>
|
||||
@@ -1407,6 +1407,10 @@
|
||||
<string name="theme_icon_blue_detail">Fundo preto, texto branco e o azul em tudo que estiver selecionado, em foco ou pressionado.</string>
|
||||
<string name="theme_electric">Marinho elétrico</string>
|
||||
<string name="theme_electric_detail">O azul mais vivo, sobre superfícies puxadas para o marinho em vez do cinza.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Cores que percorrem o arco-íris. Respeita as configurações de animação do Android.</string>
|
||||
<string name="theme_rgb_speed">Velocidade</string>
|
||||
<string name="theme_rgb_speed_hint">Com que rapidez as cores mudam.</string>
|
||||
<string name="theme_gradient">Gradiente</string>
|
||||
<string name="theme_gradient_detail">Suas próprias cores primária e secundária, combinadas em botões e destaques.</string>
|
||||
<string name="theme_gradient_primary">Primária</string>
|
||||
@@ -1533,6 +1537,13 @@
|
||||
<string name="fm_conflict_skip">Pular</string>
|
||||
<string name="fm_select_this_folder">Selecionar esta pasta</string>
|
||||
<string name="fm_back">Voltar</string>
|
||||
<string name="fm_exit">Sair</string>
|
||||
<string name="fm_return_to_start">Voltar à pasta inicial</string>
|
||||
<string name="fm_start_unavailable">A pasta inicial não está mais disponível</string>
|
||||
<string name="fm_cancel_operation_title">Cancelar operação?</string>
|
||||
<string name="fm_cancel_operation_confirmation">A operação será cancelada. Tem certeza?</string>
|
||||
<string name="fm_accept">Aceitar</string>
|
||||
<string name="fm_decline">Recusar</string>
|
||||
<string name="fm_drive_not_readable">%1$s está montado, mas não pode ser lido agora</string>
|
||||
<string name="fm_list_view">Visualização em lista</string>
|
||||
<string name="fm_grid_view">Visualização em grade</string>
|
||||
|
||||
@@ -543,8 +543,8 @@
|
||||
<string name="perf_sysmem">Turnip: randare sysmem</string>
|
||||
<string name="perf_sysmem_hint">Mai rapidă pentru clientul Steam; în jocuri, driverul alege la fiecare trecere între randarea pe dale și cea în memoria sistemului. Necesară pe Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Omite asistentul xalia al Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">Un substitut la nivel de utilizator pentru driverul Linux ntsync, pentru testare. Cât timp este activ, Proton îl folosește în locul droiddeck-esync și droiddeck-fsync. Funcționează cu Proton Experimental ARM64, GE-Proton și proton-cachyos. Se aplică la sesiunea următoare.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Un substitut la nivel de utilizator pentru driverul Linux ntsync. Cât timp este activ, Proton îl folosește în locul droiddeck-esync și droiddeck-fsync. Funcționează cu Proton Experimental ARM64, GE-Proton și proton-cachyos. Se aplică la sesiunea următoare.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync primul</string>
|
||||
<string name="perf_fsync_hint">Rulează propriul fsync al Proton pe fișierele Proton standard, cu futex_waitv furnizat la nivel de utilizator, deci nu are nevoie de pachet și continuă să funcționeze imediat ce Steam actualizează Proton. Cât timp este activ, jocurile îl folosesc înaintea droiddeck-esync, cu excepția celor puține pe care Proton le ține în afara fsync. Se aplică la sesiunea următoare.</string>
|
||||
@@ -1415,6 +1415,10 @@
|
||||
<string name="theme_icon_blue_detail">Fundal negru, text alb, albastrul pentru tot ce este selectat, focalizat sau apăsat.</string>
|
||||
<string name="theme_electric">Bleumarin electric</string>
|
||||
<string name="theme_electric_detail">Albastrul mai strălucitor, pe suprafețe care tind spre bleumarin în loc de gri.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Culori care parcurg curcubeul. Respectă setările de animație din Android.</string>
|
||||
<string name="theme_rgb_speed">Viteză</string>
|
||||
<string name="theme_rgb_speed_hint">Cât de repede se schimbă culorile.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Culorile tale primară și secundară, combinate pe butoane și evidențieri.</string>
|
||||
<string name="theme_gradient_primary">Primară</string>
|
||||
@@ -1541,6 +1545,13 @@
|
||||
<string name="fm_conflict_skip">Omite</string>
|
||||
<string name="fm_select_this_folder">Selectează acest folder</string>
|
||||
<string name="fm_back">Înapoi</string>
|
||||
<string name="fm_exit">Ieșire</string>
|
||||
<string name="fm_return_to_start">Înapoi la dosarul inițial</string>
|
||||
<string name="fm_start_unavailable">Dosarul inițial nu mai este disponibil</string>
|
||||
<string name="fm_cancel_operation_title">Anulați operația?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Operația va fi anulată. Sunteți sigur?</string>
|
||||
<string name="fm_accept">Acceptă</string>
|
||||
<string name="fm_decline">Refuză</string>
|
||||
<string name="fm_drive_not_readable">%1$s este montat, dar nu poate fi citit acum</string>
|
||||
<string name="fm_list_view">Vizualizare listă</string>
|
||||
<string name="fm_grid_view">Vizualizare grilă</string>
|
||||
|
||||
@@ -547,8 +547,8 @@
|
||||
<string name="perf_sysmem">Turnip: рендеринг sysmem</string>
|
||||
<string name="perf_sysmem_hint">Клиент Steam будет быстрее; в играх драйвер для каждого прохода выбирает тайловый рендеринг или рендеринг в системной памяти. Требуется для Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Пропустить помощник xalia в Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (экспериментально)</string>
|
||||
<string name="perf_fastsync_hint">Замена драйвера ntsync Linux в пространстве пользователя, для тестирования. Пока включено, Proton использует ее вместо droiddeck-esync и droiddeck-fsync. Работает с Proton Experimental ARM64, GE-Proton и proton-cachyos. Вступит в силу в следующей сессии.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Замена драйвера ntsync Linux в пространстве пользователя. Пока включено, Proton использует ее вместо droiddeck-esync и droiddeck-fsync. Работает с Proton Experimental ARM64, GE-Proton и proton-cachyos. Вступит в силу в следующей сессии.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync в приоритете</string>
|
||||
<string name="perf_fsync_hint">Запускает собственный fsync Proton на стандартных файлах Proton, а futex_waitv предоставляется в пространстве пользователя: пакет не нужен, и все продолжает работать сразу после обновления Proton в Steam. Пока включено, игры используют его раньше droiddeck-esync, кроме тех немногих, для которых Proton отключает fsync. Вступит в силу в следующей сессии.</string>
|
||||
@@ -1431,6 +1431,10 @@
|
||||
<string name="theme_icon_blue_detail">Черный фон, белый текст, синий — для всего выбранного, активного или нажатого.</string>
|
||||
<string name="theme_electric">Электрик</string>
|
||||
<string name="theme_electric_detail">Более яркий синий на поверхностях темно-синего, а не серого оттенка.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Цвета, плавно переходящие по всей радуге. Учитывает настройки анимации Android.</string>
|
||||
<string name="theme_rgb_speed">Скорость</string>
|
||||
<string name="theme_rgb_speed_hint">Как быстро меняются цвета.</string>
|
||||
<string name="theme_gradient">Градиент</string>
|
||||
<string name="theme_gradient_detail">Ваши основной и дополнительный цвета, плавно переходящие на кнопках и выделениях.</string>
|
||||
<string name="theme_gradient_primary">Основной</string>
|
||||
@@ -1562,6 +1566,13 @@
|
||||
<string name="fm_conflict_skip">Пропустить</string>
|
||||
<string name="fm_select_this_folder">Выбрать эту папку</string>
|
||||
<string name="fm_back">Назад</string>
|
||||
<string name="fm_exit">Выход</string>
|
||||
<string name="fm_return_to_start">Вернуться в начальную папку</string>
|
||||
<string name="fm_start_unavailable">Начальная папка больше недоступна</string>
|
||||
<string name="fm_cancel_operation_title">Отменить операцию?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Операция будет отменена. Вы уверены?</string>
|
||||
<string name="fm_accept">Принять</string>
|
||||
<string name="fm_decline">Отклонить</string>
|
||||
<string name="fm_drive_not_readable">%1$s: подключено, но сейчас недоступно для чтения</string>
|
||||
<string name="fm_list_view">Список</string>
|
||||
<string name="fm_grid_view">Сетка</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem-rendering</string>
|
||||
<string name="perf_sysmem_hint">Snabbare för Steam-klienten; i spel väljer drivrutinen kakel- eller systemminnesrendering för varje pass. Krävs på Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Hoppa över Steams xalia-hjälp</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimentell)</string>
|
||||
<string name="perf_fastsync_hint">En ersättning i användarutrymmet för Linux ntsync-drivrutinen, för testning. Medan det är på använder Proton det i stället för droiddeck-esync och droiddeck-fsync. Fungerar med Proton Experimental ARM64, GE-Proton och proton-cachyos. Gäller från nästa session.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">En ersättning i användarutrymmet för Linux ntsync-drivrutinen. Medan det är på använder Proton det i stället för droiddeck-esync och droiddeck-fsync. Fungerar med Proton Experimental ARM64, GE-Proton och proton-cachyos. Gäller från nästa session.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync först</string>
|
||||
<string name="perf_fsync_hint">Kör Protons egen fsync på de ursprungliga Proton-filerna, med futex_waitv tillhandahållen i användarutrymmet, så det behöver inget paket och fortsätter fungera så snart Steam uppdaterar Proton. Medan det är på använder spel det före droiddeck-esync, förutom de få som Proton håller borta från fsync. Gäller från nästa session.</string>
|
||||
@@ -1401,6 +1401,10 @@
|
||||
<string name="theme_icon_blue_detail">Svart bakgrund, vit text, den blå färgen för allt som är markerat, i fokus eller nedtryckt.</string>
|
||||
<string name="theme_electric">Elektrisk marinblå</string>
|
||||
<string name="theme_electric_detail">Den blå färgen klarare, på ytor som lutar åt marinblått i stället för grått.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Färger som växlar genom regnbågen. Följer Androids animationsinställningar.</string>
|
||||
<string name="theme_rgb_speed">Hastighet</string>
|
||||
<string name="theme_rgb_speed_hint">Hur snabbt färgerna växlar.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Dina egna primära och sekundära färger, blandade på knappar och markeringar.</string>
|
||||
<string name="theme_gradient_primary">Primär</string>
|
||||
@@ -1522,6 +1526,13 @@
|
||||
<string name="fm_conflict_skip">Hoppa över</string>
|
||||
<string name="fm_select_this_folder">Välj den här mappen</string>
|
||||
<string name="fm_back">Tillbaka</string>
|
||||
<string name="fm_exit">Avsluta</string>
|
||||
<string name="fm_return_to_start">Tillbaka till startmappen</string>
|
||||
<string name="fm_start_unavailable">Startmappen är inte längre tillgänglig</string>
|
||||
<string name="fm_cancel_operation_title">Avbryt åtgärden?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Åtgärden avbryts. Är du säker?</string>
|
||||
<string name="fm_accept">Acceptera</string>
|
||||
<string name="fm_decline">Avböj</string>
|
||||
<string name="fm_drive_not_readable">%1$s är monterad men inte läsbar just nu</string>
|
||||
<string name="fm_list_view">Listvy</string>
|
||||
<string name="fm_grid_view">Rutnätsvy</string>
|
||||
|
||||
@@ -539,8 +539,8 @@
|
||||
<string name="perf_sysmem">Turnip: การเรนเดอร์ sysmem</string>
|
||||
<string name="perf_sysmem_hint">เร็วขึ้นสำหรับไคลเอนต์ Steam ในเกมไดรเวอร์จะเลือกการเรนเดอร์แบบไทล์หรือในหน่วยความจำระบบในแต่ละพาส จำเป็นสำหรับ Adreno 710/720/722</string>
|
||||
<string name="perf_xalia">ข้ามตัวช่วย xalia ของ Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (ทดลอง)</string>
|
||||
<string name="perf_fastsync_hint">ตัวแทนในระดับยูสเซอร์สเปซของไดรเวอร์ ntsync ของ Linux สำหรับการทดสอบ เมื่อเปิดใช้ Proton จะใช้สิ่งนี้แทน droiddeck-esync และ droiddeck-fsync ใช้ได้กับ Proton Experimental ARM64, GE-Proton และ proton-cachyos จะมีผลในเซสชันถัดไป</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">ตัวแทนในระดับยูสเซอร์สเปซของไดรเวอร์ ntsync ของ Linux เมื่อเปิดใช้ Proton จะใช้สิ่งนี้แทน droiddeck-esync และ droiddeck-fsync ใช้ได้กับ Proton Experimental ARM64, GE-Proton และ proton-cachyos จะมีผลในเซสชันถัดไป</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync ก่อน</string>
|
||||
<string name="perf_fsync_hint">รัน fsync ของ Proton เองบนไฟล์ Proton มาตรฐาน โดยมี futex_waitv ที่จัดเตรียมในระดับยูสเซอร์สเปซ จึงไม่ต้องใช้แพ็กและยังคงทำงานได้ทันทีที่ Steam อัปเดต Proton เมื่อเปิดใช้ เกมจะใช้สิ่งนี้ก่อน droiddeck-esync ยกเว้นบางส่วนที่ Proton ปิด fsync ไว้ จะมีผลในเซสชันถัดไป</string>
|
||||
@@ -1387,6 +1387,10 @@
|
||||
<string name="theme_icon_blue_detail">พื้นดำ ตัวอักษรขาว สีฟ้าสำหรับทุกสิ่งที่ถูกเลือก โฟกัส หรือกด</string>
|
||||
<string name="theme_electric">กรมท่าไฟฟ้า</string>
|
||||
<string name="theme_electric_detail">สีฟ้าสว่างขึ้น บนพื้นผิวที่เอียงไปทางกรมท่ามากกว่าเทา</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">สีที่เปลี่ยนวนไปตามสายรุ้ง ทำงานตามการตั้งค่าแอนิเมชันของ Android</string>
|
||||
<string name="theme_rgb_speed">ความเร็ว</string>
|
||||
<string name="theme_rgb_speed_hint">สีเปลี่ยนเร็วแค่ไหน</string>
|
||||
<string name="theme_gradient">ไล่เฉดสี</string>
|
||||
<string name="theme_gradient_detail">สีหลักและสีรองที่คุณเลือกเอง ผสมกันบนปุ่มและส่วนที่เน้น</string>
|
||||
<string name="theme_gradient_primary">สีหลัก</string>
|
||||
@@ -1503,6 +1507,13 @@
|
||||
<string name="fm_conflict_skip">ข้าม</string>
|
||||
<string name="fm_select_this_folder">เลือกโฟลเดอร์นี้</string>
|
||||
<string name="fm_back">กลับ</string>
|
||||
<string name="fm_exit">ออก</string>
|
||||
<string name="fm_return_to_start">กลับไปยังโฟลเดอร์เริ่มต้น</string>
|
||||
<string name="fm_start_unavailable">โฟลเดอร์เริ่มต้นไม่พร้อมใช้งานแล้ว</string>
|
||||
<string name="fm_cancel_operation_title">ยกเลิกการดำเนินการ?</string>
|
||||
<string name="fm_cancel_operation_confirmation">การดำเนินการจะถูกยกเลิก คุณแน่ใจหรือไม่?</string>
|
||||
<string name="fm_accept">ยอมรับ</string>
|
||||
<string name="fm_decline">ปฏิเสธ</string>
|
||||
<string name="fm_drive_not_readable">%1$s ติดตั้งอยู่แต่อ่านไม่ได้ในขณะนี้</string>
|
||||
<string name="fm_list_view">มุมมองรายการ</string>
|
||||
<string name="fm_grid_view">มุมมองตาราง</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem işleme</string>
|
||||
<string name="perf_sysmem_hint">Steam istemcisi için daha hızlı; oyunlarda sürücü her geçişte döşeme tabanlı veya sistem belleğinde işlemeyi seçer. Adreno 710/720/722\'de gereklidir.</string>
|
||||
<string name="perf_xalia">Steam\'in xalia yardımcısını atla</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (deneysel)</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync sürücüsünün test amaçlı, kullanıcı alanında çalışan bir yedeği. Açıkken Proton, droiddeck-esync ve droiddeck-fsync yerine bunu kullanır. Proton Experimental ARM64, GE-Proton ve proton-cachyos ile çalışır. Sonraki oturumda uygulanır.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync sürücüsünün kullanıcı alanında çalışan bir yedeği. Açıkken Proton, droiddeck-esync ve droiddeck-fsync yerine bunu kullanır. Proton Experimental ARM64, GE-Proton ve proton-cachyos ile çalışır. Sonraki oturumda uygulanır.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">Önce droiddeck-fsync</string>
|
||||
<string name="perf_fsync_hint">Proton\'un kendi fsync\'ini standart Proton dosyalarıyla, futex_waitv kullanıcı alanında sağlanarak çalıştırır; bu yüzden paket gerektirmez ve Steam Proton\'u güncellediği anda da çalışmaya devam eder. Açıkken oyunlar, Proton\'un fsync dışında tuttuğu birkaç oyun hariç, bunu droiddeck-esync\'ten önce kullanır. Sonraki oturumda uygulanır.</string>
|
||||
@@ -1395,6 +1395,10 @@
|
||||
<string name="theme_icon_blue_detail">Siyah zemin, beyaz yazı; seçilen, odaklanan veya basılan her şey için mavi.</string>
|
||||
<string name="theme_electric">Elektrik laciverti</string>
|
||||
<string name="theme_electric_detail">Daha parlak mavi; gri yerine laciverte çalan yüzeylerde.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Gökkuşağında dolaşan renkler. Android\'in animasyon ayarlarına uyar.</string>
|
||||
<string name="theme_rgb_speed">Hız</string>
|
||||
<string name="theme_rgb_speed_hint">Renklerin ne kadar hızlı değiştiği.</string>
|
||||
<string name="theme_gradient">Geçiş</string>
|
||||
<string name="theme_gradient_detail">Kendi birincil ve ikincil renklerin, düğmelerde ve vurgularda harmanlanır.</string>
|
||||
<string name="theme_gradient_primary">Birincil</string>
|
||||
@@ -1516,6 +1520,13 @@
|
||||
<string name="fm_conflict_skip">Atla</string>
|
||||
<string name="fm_select_this_folder">Bu klasörü seç</string>
|
||||
<string name="fm_back">Geri</string>
|
||||
<string name="fm_exit">Çıkış</string>
|
||||
<string name="fm_return_to_start">Başlangıç klasörüne dön</string>
|
||||
<string name="fm_start_unavailable">Başlangıç klasörü artık kullanılamıyor</string>
|
||||
<string name="fm_cancel_operation_title">İşlem iptal edilsin mi?</string>
|
||||
<string name="fm_cancel_operation_confirmation">İşlem iptal edilecek. Emin misiniz?</string>
|
||||
<string name="fm_accept">Kabul et</string>
|
||||
<string name="fm_decline">Reddet</string>
|
||||
<string name="fm_drive_not_readable">%1$s bağlı ancak şu anda okunamıyor</string>
|
||||
<string name="fm_list_view">Liste görünümü</string>
|
||||
<string name="fm_grid_view">Izgara görünümü</string>
|
||||
|
||||
@@ -545,8 +545,8 @@
|
||||
<string name="perf_sysmem">Turnip: рендеринг через sysmem</string>
|
||||
<string name="perf_sysmem_hint">Швидше для клієнта Steam; в іграх драйвер для кожного проходу вибирає тайловий рендеринг або рендеринг у системній пам’яті. Потрібно для Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Пропускати помічник Steam xalia</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (експериментально)</string>
|
||||
<string name="perf_fastsync_hint">Програмна заміна драйвера ntsync ядра Linux у просторі користувача, для тестування. Поки це увімкнено, Proton використовує її замість droiddeck-esync і droiddeck-fsync. Працює з Proton Experimental ARM64, GE-Proton і proton-cachyos. Застосовується в наступному сеансі.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Програмна заміна драйвера ntsync ядра Linux у просторі користувача. Поки це увімкнено, Proton використовує її замість droiddeck-esync і droiddeck-fsync. Працює з Proton Experimental ARM64, GE-Proton і proton-cachyos. Застосовується в наступному сеансі.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync першим</string>
|
||||
<string name="perf_fsync_hint">Запускає власний fsync Proton на стандартних файлах Proton, а futex_waitv надається в просторі користувача, тому не потрібен пакет і все працює відразу після оновлення Proton у Steam. Поки це увімкнено, ігри використовують його раніше за droiddeck-esync, окрім кількох, де Proton тримає fsync вимкненим. Застосовується в наступному сеансі.</string>
|
||||
@@ -1429,6 +1429,10 @@
|
||||
<string name="theme_icon_blue_detail">Чорне тло, білий текст і синій для всього вибраного, у фокусі чи натиснутого.</string>
|
||||
<string name="theme_electric">Електричний темно-синій</string>
|
||||
<string name="theme_electric_detail">Яскравіший синій на поверхнях із темно-синім відтінком замість сірого.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Кольори, що плавно змінюються по всій веселці. Враховує налаштування анімації Android.</string>
|
||||
<string name="theme_rgb_speed">Швидкість</string>
|
||||
<string name="theme_rgb_speed_hint">Як швидко змінюються кольори.</string>
|
||||
<string name="theme_gradient">Градієнт</string>
|
||||
<string name="theme_gradient_detail">Ваші основний і додатковий кольори, що плавно переходять на кнопках і виділеннях.</string>
|
||||
<string name="theme_gradient_primary">Основний</string>
|
||||
@@ -1560,6 +1564,13 @@
|
||||
<string name="fm_conflict_skip">Пропустити</string>
|
||||
<string name="fm_select_this_folder">Вибрати цю папку</string>
|
||||
<string name="fm_back">Назад</string>
|
||||
<string name="fm_exit">Вийти</string>
|
||||
<string name="fm_return_to_start">Повернутися до початкової папки</string>
|
||||
<string name="fm_start_unavailable">Початкова папка більше недоступна</string>
|
||||
<string name="fm_cancel_operation_title">Скасувати операцію?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Операцію буде скасовано. Ви впевнені?</string>
|
||||
<string name="fm_accept">Прийняти</string>
|
||||
<string name="fm_decline">Відхилити</string>
|
||||
<string name="fm_drive_not_readable">%1$s: підключено, але читання зараз недоступне</string>
|
||||
<string name="fm_list_view">Список</string>
|
||||
<string name="fm_grid_view">Сітка</string>
|
||||
|
||||
@@ -539,8 +539,8 @@
|
||||
<string name="perf_sysmem">Turnip: render sysmem</string>
|
||||
<string name="perf_sysmem_hint">Nhanh hơn cho ứng dụng Steam; trong game, trình điều khiển chọn kết xuất theo ô hoặc trong bộ nhớ hệ thống cho từng lượt. Bắt buộc trên Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Bỏ qua trình trợ giúp xalia của Steam</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (thử nghiệm)</string>
|
||||
<string name="perf_fastsync_hint">Một bản thay thế ở không gian người dùng cho driver ntsync của Linux, dùng để thử nghiệm. Khi đang bật, Proton dùng nó thay cho droiddeck-esync và droiddeck-fsync. Hoạt động với Proton Experimental ARM64, GE-Proton và proton-cachyos. Áp dụng từ phiên sau.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Một bản thay thế ở không gian người dùng cho driver ntsync của Linux. Khi đang bật, Proton dùng nó thay cho droiddeck-esync và droiddeck-fsync. Hoạt động với Proton Experimental ARM64, GE-Proton và proton-cachyos. Áp dụng từ phiên sau.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync trước</string>
|
||||
<string name="perf_fsync_hint">Chạy fsync riêng của Proton trên các tệp Proton gốc, với futex_waitv được cung cấp ở không gian người dùng, nên không cần pack và vẫn hoạt động ngay khi Steam cập nhật Proton. Khi đang bật, game dùng nó trước droiddeck-esync, trừ một số ít Proton giữ fsync tắt. Áp dụng từ phiên sau.</string>
|
||||
@@ -1387,6 +1387,10 @@
|
||||
<string name="theme_icon_blue_detail">Nền đen, chữ trắng, xanh cho mọi thứ được chọn, lấy nét hoặc nhấn.</string>
|
||||
<string name="theme_electric">Xanh điện navy</string>
|
||||
<string name="theme_electric_detail">Màu xanh sáng hơn, trên các bề mặt nghiêng về navy thay vì xám.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Màu sắc chuyển dần qua cả dải cầu vồng. Tuân theo cài đặt hiệu ứng động của Android.</string>
|
||||
<string name="theme_rgb_speed">Tốc độ</string>
|
||||
<string name="theme_rgb_speed_hint">Màu đổi nhanh đến mức nào.</string>
|
||||
<string name="theme_gradient">Chuyển màu</string>
|
||||
<string name="theme_gradient_detail">Màu chính và màu phụ do bạn chọn, hòa trộn trên các nút và phần nổi bật.</string>
|
||||
<string name="theme_gradient_primary">Chính</string>
|
||||
@@ -1503,6 +1507,13 @@
|
||||
<string name="fm_conflict_skip">Bỏ qua</string>
|
||||
<string name="fm_select_this_folder">Chọn thư mục này</string>
|
||||
<string name="fm_back">Quay lại</string>
|
||||
<string name="fm_exit">Thoát</string>
|
||||
<string name="fm_return_to_start">Quay lại thư mục ban đầu</string>
|
||||
<string name="fm_start_unavailable">Thư mục ban đầu không còn khả dụng</string>
|
||||
<string name="fm_cancel_operation_title">Hủy thao tác?</string>
|
||||
<string name="fm_cancel_operation_confirmation">Thao tác sẽ bị hủy. Bạn có chắc không?</string>
|
||||
<string name="fm_accept">Chấp nhận</string>
|
||||
<string name="fm_decline">Từ chối</string>
|
||||
<string name="fm_drive_not_readable">%1$s đã được gắn nhưng hiện không đọc được</string>
|
||||
<string name="fm_list_view">Xem dạng danh sách</string>
|
||||
<string name="fm_grid_view">Xem dạng lưới</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip:sysmem 渲染</string>
|
||||
<string name="perf_sysmem_hint">Steam 客户端更快;游戏中驱动会为每个渲染通道选择分块渲染或系统内存渲染。Adreno 710/720/722 必需。</string>
|
||||
<string name="perf_xalia">跳过 Steam 的 xalia 助手</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync(实验性)</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync 驱动的用户态替代品,供测试使用。开启时,Proton 使用它代替 droiddeck-esync 和 droiddeck-fsync。适用于 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。将在下次会话生效。</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">Linux ntsync 驱动的用户态替代品。开启时,Proton 使用它代替 droiddeck-esync 和 droiddeck-fsync。适用于 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。将在下次会话生效。</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync 优先</string>
|
||||
<string name="perf_fsync_hint">在原始 Proton 文件上运行 Proton 自带的 fsync,由用户态提供 futex_waitv,因此无需任何包,且 Steam 更新 Proton 后立即继续可用。开启时,除 Proton 自身禁用 fsync 的少数游戏外,各游戏优先使用它而非 droiddeck-esync。将在下次会话生效。</string>
|
||||
@@ -1465,6 +1465,10 @@
|
||||
<string name="theme_icon_blue_detail">黑色背景、白色文字,选中、聚焦或按下的元素使用蓝色。</string>
|
||||
<string name="theme_electric">电光藏青</string>
|
||||
<string name="theme_electric_detail">更明亮的蓝色,配以偏藏青而非灰色的界面。</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">在彩虹色之间循环变化的颜色。遵循 Android 的动画设置。</string>
|
||||
<string name="theme_rgb_speed">速度</string>
|
||||
<string name="theme_rgb_speed_hint">颜色循环的快慢。</string>
|
||||
<string name="theme_gradient">渐变</string>
|
||||
<string name="theme_gradient_detail">你自己选择的主色和辅色,在按钮和高亮处渐变呈现。</string>
|
||||
<string name="theme_gradient_primary">主色</string>
|
||||
@@ -1581,6 +1585,13 @@
|
||||
<string name="fm_conflict_skip">跳过</string>
|
||||
<string name="fm_select_this_folder">选择此文件夹</string>
|
||||
<string name="fm_back">返回</string>
|
||||
<string name="fm_exit">退出</string>
|
||||
<string name="fm_return_to_start">返回初始文件夹</string>
|
||||
<string name="fm_start_unavailable">初始文件夹已不可用</string>
|
||||
<string name="fm_cancel_operation_title">取消操作?</string>
|
||||
<string name="fm_cancel_operation_confirmation">操作将被取消。确定吗?</string>
|
||||
<string name="fm_accept">接受</string>
|
||||
<string name="fm_decline">拒绝</string>
|
||||
<string name="fm_drive_not_readable">%1$s 已挂载,但当前无法读取</string>
|
||||
<string name="fm_list_view">列表视图</string>
|
||||
<string name="fm_grid_view">网格视图</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip:sysmem 渲染</string>
|
||||
<string name="perf_sysmem_hint">Steam 用戶端會更快;遊戲中驅動程式會為每個渲染通道選擇分塊渲染或系統記憶體渲染。Adreno 710/720/722 必須開啟。</string>
|
||||
<string name="perf_xalia">略過 Steam 的 xalia 輔助程式</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync(實驗性)</string>
|
||||
<string name="perf_fastsync_hint">在用戶空間代替 Linux ntsync 驅動程式,供測試使用。開啟時,Proton 會使用它而非 droiddeck-esync 和 droiddeck-fsync。適用於 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。下次會話生效。</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">在用戶空間代替 Linux ntsync 驅動程式。開啟時,Proton 會使用它而非 droiddeck-esync 和 droiddeck-fsync。適用於 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。下次會話生效。</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">優先使用 droiddeck-fsync</string>
|
||||
<string name="perf_fsync_hint">在原裝 Proton 檔案上執行 Proton 本身的 fsync,並在用戶空間提供 futex_waitv,因此無需套件,Steam 更新 Proton 後亦能立即繼續運作。開啟時,遊戲會優先使用它而非 droiddeck-esync,Proton 刻意不用 fsync 的少數遊戲除外。下次會話生效。</string>
|
||||
@@ -1389,6 +1389,10 @@
|
||||
<string name="theme_icon_blue_detail">黑色背景、白色文字,所有已選取、焦點所在或按下的項目都使用藍色。</string>
|
||||
<string name="theme_electric">電光海軍藍</string>
|
||||
<string name="theme_electric_detail">更明亮的藍色,搭配偏海軍藍而非灰色的介面。</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">在彩虹色之間循環變化的顏色。遵循 Android 的動畫設定。</string>
|
||||
<string name="theme_rgb_speed">速度</string>
|
||||
<string name="theme_rgb_speed_hint">顏色循環的快慢。</string>
|
||||
<string name="theme_gradient">漸層</string>
|
||||
<string name="theme_gradient_detail">你自行選擇的主色與輔色,在按鈕與強調處以漸層呈現。</string>
|
||||
<string name="theme_gradient_primary">主色</string>
|
||||
@@ -1505,6 +1509,13 @@
|
||||
<string name="fm_conflict_skip">略過</string>
|
||||
<string name="fm_select_this_folder">選擇此資料夾</string>
|
||||
<string name="fm_back">返回</string>
|
||||
<string name="fm_exit">離開</string>
|
||||
<string name="fm_return_to_start">返回初始資料夾</string>
|
||||
<string name="fm_start_unavailable">初始資料夾已無法使用</string>
|
||||
<string name="fm_cancel_operation_title">取消操作?</string>
|
||||
<string name="fm_cancel_operation_confirmation">操作將會取消。確定嗎?</string>
|
||||
<string name="fm_accept">接受</string>
|
||||
<string name="fm_decline">拒絕</string>
|
||||
<string name="fm_drive_not_readable">%1$s 已掛載,但目前無法讀取</string>
|
||||
<string name="fm_list_view">清單檢視</string>
|
||||
<string name="fm_grid_view">格狀檢視</string>
|
||||
|
||||
@@ -541,8 +541,8 @@
|
||||
<string name="perf_sysmem">Turnip:sysmem 渲染</string>
|
||||
<string name="perf_sysmem_hint">Steam 用戶端會更快;遊戲中驅動程式會為每個算繪通道選擇分塊算繪或系統記憶體算繪。Adreno 710/720/722 必需。</string>
|
||||
<string name="perf_xalia">跳過 Steam 的 xalia 輔助程式</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync(實驗性)</string>
|
||||
<string name="perf_fastsync_hint">以使用者空間替代 Linux ntsync 驅動程式,供測試使用。開啟時,Proton 會使用它來取代 droiddeck-esync 和 droiddeck-fsync。適用於 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。下次工作階段生效。</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">以使用者空間替代 Linux ntsync 驅動程式。開啟時,Proton 會使用它來取代 droiddeck-esync 和 droiddeck-fsync。適用於 Proton Experimental ARM64、GE-Proton 和 proton-cachyos。下次工作階段生效。</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">優先使用 droiddeck-fsync</string>
|
||||
<string name="perf_fsync_hint">在原版 Proton 檔案上執行 Proton 自己的 fsync,並在使用者空間提供 futex_waitv,因此不需要套件,Steam 更新 Proton 後也能立即繼續運作。開啟時,遊戲會優先使用它而非 droiddeck-esync,Proton 本身停用 fsync 的少數遊戲除外。下次工作階段生效。</string>
|
||||
@@ -1389,6 +1389,10 @@
|
||||
<string name="theme_icon_blue_detail">黑色背景、白色文字,選取、聚焦或按下的項目使用藍色。</string>
|
||||
<string name="theme_electric">電光藏青</string>
|
||||
<string name="theme_electric_detail">更亮的藍色,搭配偏藏青而非灰色的介面。</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">在彩虹色之間循環變化的顏色。遵循 Android 的動畫設定。</string>
|
||||
<string name="theme_rgb_speed">速度</string>
|
||||
<string name="theme_rgb_speed_hint">顏色循環的快慢。</string>
|
||||
<string name="theme_gradient">漸層</string>
|
||||
<string name="theme_gradient_detail">你自行選擇的主色與輔色,在按鈕與強調處以漸層呈現。</string>
|
||||
<string name="theme_gradient_primary">主色</string>
|
||||
@@ -1505,6 +1509,13 @@
|
||||
<string name="fm_conflict_skip">略過</string>
|
||||
<string name="fm_select_this_folder">選擇此資料夾</string>
|
||||
<string name="fm_back">返回</string>
|
||||
<string name="fm_exit">離開</string>
|
||||
<string name="fm_return_to_start">返回起始資料夾</string>
|
||||
<string name="fm_start_unavailable">起始資料夾已無法使用</string>
|
||||
<string name="fm_cancel_operation_title">取消操作?</string>
|
||||
<string name="fm_cancel_operation_confirmation">操作將會取消。確定嗎?</string>
|
||||
<string name="fm_accept">接受</string>
|
||||
<string name="fm_decline">拒絕</string>
|
||||
<string name="fm_drive_not_readable">%1$s 已掛載,但目前無法讀取</string>
|
||||
<string name="fm_list_view">清單檢視</string>
|
||||
<string name="fm_grid_view">格狀檢視</string>
|
||||
|
||||
@@ -320,6 +320,8 @@
|
||||
<string name="mode_mangoapp_hint">Turn off if games crash or go black in Deck mode. Applies next session.</string>
|
||||
<string name="mode_return_after_game">Return to DroidDeck after a game</string>
|
||||
<string name="mode_return_after_game_hint">When a game you started from DroidDeck or another launcher closes, Steam exits cleanly (cloud saves finish first) and you go back there. Off: stay in Steam.</string>
|
||||
<string name="mode_shader_precache">Shader pre-caching</string>
|
||||
<string name="mode_shader_precache_hint">Steam downloads shaders prebuilt for your games and processes them in the background, so games stutter less. Off saves data, storage and battery; games build shaders as they play instead. The same setting as Steam’s Downloads > Shader Pre-Caching. Applies next session.</string>
|
||||
<string name="mode_steam_repair">Repair Steam client</string>
|
||||
<string name="mode_steam_repair_hint">Downloads the Steam client again the next time Steam starts. Use it if Steam stays on its loading screen. Games, sign-in and settings are kept.</string>
|
||||
<string name="mode_steam_repair_queued">The Steam client will be downloaded again the next time Steam starts.</string>
|
||||
@@ -554,8 +556,8 @@
|
||||
<string name="perf_sysmem">Turnip: sysmem rendering</string>
|
||||
<string name="perf_sysmem_hint">Faster for the Steam client; in games the driver picks tile or system-memory rendering per pass. Required on Adreno 710/720/722.</string>
|
||||
<string name="perf_xalia">Skip Steam\'s xalia helper</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync (experimental)</string>
|
||||
<string name="perf_fastsync_hint">A userspace stand-in for the Linux ntsync driver, for testing. While it is on, Proton uses it instead of droiddeck-esync and droiddeck-fsync. Works with Proton Experimental ARM64, GE-Proton and proton-cachyos. Applies next session.</string>
|
||||
<string name="perf_fastsync">droiddeck-ntsync</string>
|
||||
<string name="perf_fastsync_hint">A userspace stand-in for the Linux ntsync driver. While it is on, Proton uses it instead of droiddeck-esync and droiddeck-fsync. Works with Proton Experimental ARM64, GE-Proton and proton-cachyos. Applies next session.</string>
|
||||
<string name="perf_esync">droiddeck-esync</string>
|
||||
<string name="perf_fsync">droiddeck-fsync first</string>
|
||||
<string name="perf_fsync_hint">Runs Proton\'s own fsync on the stock Proton files, with futex_waitv supplied in userspace, so it needs no pack and keeps working the moment Steam updates Proton. While it is on, games use it ahead of droiddeck-esync, except the few Proton keeps off fsync. Applies next session.</string>
|
||||
@@ -566,6 +568,8 @@
|
||||
<string name="perf_fastpath">proot fast path</string>
|
||||
<string name="perf_fastpath_needs_seccomp">Needs proot\'s seccomp filter.</string>
|
||||
<string name="perf_fastpath_hint">Faster file lookups and less stutter. Turn off if a program can\'t find its files.</string>
|
||||
<string name="perf_cursor_composite">Draw the cursor inside gamescope</string>
|
||||
<string name="perf_cursor_composite_hint">Gamescope paints the pointer itself instead of handing it to Android. Turn on for a controller-driven cursor. Off is faster on a phone. Applies next session.</string>
|
||||
<string name="perf_identity">Session identity</string>
|
||||
<string name="perf_hostname">Host name</string>
|
||||
<string name="perf_hostname_hint">The name Steam sees for this device. Blank restores %1$s.</string>
|
||||
@@ -1099,6 +1103,9 @@
|
||||
<string name="gpu_hide_other">Hide drivers for other GPUs</string>
|
||||
<string name="gpu_show_other">Show drivers for other GPUs</string>
|
||||
<string name="gpu_advanced">Advanced</string>
|
||||
<string name="gpu_ubwc_title">UBWC compatibility</string>
|
||||
<string name="gpu_ubwc_hint">Try On or Off if Steam or Android shows stripes or flickering. Applies to both drivers after restarting DroidDeck.</string>
|
||||
<string name="gpu_ubwc_restart">Restart DroidDeck to apply.</string>
|
||||
<string name="gpu_set_with_display">Set together with the display driver: %1$s carries both.</string>
|
||||
<string name="gpu_set_with_runtime">Set together with the runtime driver: %1$s carries both.</string>
|
||||
<string name="gpu_runtime_hint">Used by games and the desktop.</string>
|
||||
@@ -1458,6 +1465,10 @@
|
||||
<string name="theme_icon_blue_detail">Black ground, white type, the blue for everything selected, focused or pressed.</string>
|
||||
<string name="theme_electric">Electric navy</string>
|
||||
<string name="theme_electric_detail">The blue brighter, on surfaces that lean navy instead of grey.</string>
|
||||
<string name="theme_inner_gamer">Inner Gamer</string>
|
||||
<string name="theme_inner_gamer_detail">Colors that cycle through the rainbow. Follows Android\'s animation settings.</string>
|
||||
<string name="theme_rgb_speed">Speed</string>
|
||||
<string name="theme_rgb_speed_hint">How fast the colors cycle.</string>
|
||||
<string name="theme_gradient">Gradient</string>
|
||||
<string name="theme_gradient_detail">Your own primary and secondary colors, blended on buttons and highlights.</string>
|
||||
<string name="theme_gradient_primary">Primary</string>
|
||||
@@ -1579,6 +1590,13 @@
|
||||
<string name="fm_conflict_skip">Skip</string>
|
||||
<string name="fm_select_this_folder">Select this folder</string>
|
||||
<string name="fm_back">Back</string>
|
||||
<string name="fm_exit">Exit</string>
|
||||
<string name="fm_return_to_start">Return to start</string>
|
||||
<string name="fm_start_unavailable">Starting folder is no longer available</string>
|
||||
<string name="fm_cancel_operation_title">Cancel operation?</string>
|
||||
<string name="fm_cancel_operation_confirmation">The operation will be cancelled. Are you sure?</string>
|
||||
<string name="fm_accept">Accept</string>
|
||||
<string name="fm_decline">Decline</string>
|
||||
<string name="fm_drive_not_readable">%1$s is mounted but not readable right now</string>
|
||||
<string name="fm_list_view">List view</string>
|
||||
<string name="fm_grid_view">Grid view</string>
|
||||
|
||||
@@ -6,9 +6,11 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.droiddeck.launcher.session.QuestExperimental
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import com.droiddeck.launcher.gpu.TpUbwcHint
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -17,11 +19,15 @@ import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.shadows.ShadowBuild
|
||||
import org.robolectric.util.ReflectionHelpers
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class DriverMenusAutoTest {
|
||||
@Before fun reset() {
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "resolved", false)
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "cached", null)
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "activeMode", null)
|
||||
RuntimeEnvironment.getApplication().getSharedPreferences("session", Context.MODE_PRIVATE)
|
||||
.edit().clear().commit()
|
||||
ShadowBuild.setModel("Quest 3")
|
||||
@@ -53,4 +59,36 @@ class DriverMenusAutoTest {
|
||||
assertEquals("chosen-display", SessionPrefs.androidDriver(activity))
|
||||
assertEquals("chosen-linux", SessionPrefs.linuxDriver(activity))
|
||||
}
|
||||
|
||||
@Test fun driverMenusCanBeConstructedBeforeActivityContextIsAttached() {
|
||||
val menus = DriverMenus(Activity(), Handler(Looper.getMainLooper()))
|
||||
assertEquals(SessionPrefs.TP_UBWC_AUTO, menus.tpUbwcMode)
|
||||
}
|
||||
|
||||
@Test fun refreshingDriversLoadsTheSavedUbwcModeAfterContextAttachment() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
|
||||
SessionPrefs.setTpUbwcMode(activity, SessionPrefs.TP_UBWC_ON)
|
||||
val menus = DriverMenus(activity, Handler(Looper.getMainLooper()))
|
||||
menus.refreshDrivers()
|
||||
assertEquals(SessionPrefs.TP_UBWC_ON, menus.tpUbwcMode)
|
||||
assertFalse(menus.state().tpUbwcRestartRequired)
|
||||
}
|
||||
|
||||
@Test fun changingUbwcBeforeTheFirstSessionDoesNotRequireRestart() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
|
||||
val menus = DriverMenus(activity, Handler(Looper.getMainLooper()))
|
||||
menus.refreshDrivers()
|
||||
SessionPrefs.setTpUbwcMode(activity, SessionPrefs.TP_UBWC_ON)
|
||||
menus.refreshDrivers()
|
||||
assertFalse(menus.state().tpUbwcRestartRequired)
|
||||
}
|
||||
|
||||
@Test fun recreatingTheMenuKeepsTheActiveHintRestartWarning() {
|
||||
val activity = Robolectric.buildActivity(Activity::class.java).setup().get()
|
||||
TpUbwcHint.forThisDevice(activity)
|
||||
SessionPrefs.setTpUbwcMode(activity, SessionPrefs.TP_UBWC_ON)
|
||||
val menus = DriverMenus(activity, Handler(Looper.getMainLooper()))
|
||||
menus.refreshDrivers()
|
||||
assertTrue(menus.state().tpUbwcRestartRequired)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.droiddeck.launcher.core
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class CpuAllocationDefaultsTest {
|
||||
private val thor = (0..7).associateWith { when (it) { in 0..2 -> 2.016; in 3..6 -> 2.8032; else -> 3.1872 } }
|
||||
|
||||
@Test fun gen2UsesTheFourMiddleCoresAndOnePrime() {
|
||||
for (model in listOf("SM8550", "QCS8550", "sm8550-ac")) {
|
||||
assertEquals(CpuAllocationDefaults.Allocation("7", "3,4,5,6"), CpuAllocationDefaults.forDevice(model, "", thor))
|
||||
}
|
||||
assertEquals(CpuAllocationDefaults.Allocation("7", "3,4,5,6"), CpuAllocationDefaults.forDevice("", "kalama", thor))
|
||||
}
|
||||
|
||||
@Test fun coreNumbersAndClockVariantsAreDetectedInsteadOfHardcoded() {
|
||||
val reordered = mapOf(0 to 3.36, 1 to 2.0, 2 to 2.0, 3 to 2.0, 4 to 2.8, 5 to 2.8, 6 to 2.8, 7 to 2.8)
|
||||
assertEquals(CpuAllocationDefaults.Allocation("0", "4,5,6,7"), CpuAllocationDefaults.forDevice("SM8550-AC", "kalama", reordered))
|
||||
}
|
||||
|
||||
@Test fun anotherOrUnknownChipDoesNotAcquireThisDefault() {
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8650", "pineapple", thor))
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8650", "kalama", thor))
|
||||
assertNull(CpuAllocationDefaults.forDevice("", "", thor))
|
||||
}
|
||||
|
||||
@Test fun missingOrUnexpectedTopologyKeepsTheSchedulerDefault() {
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8550", "kalama", thor - 7))
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8550", "kalama", thor + (7 to null)))
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8550", "kalama", thor + (7 to 0.0)))
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8550", "kalama", thor + (7 to Double.NaN)))
|
||||
assertNull(CpuAllocationDefaults.forDevice("SM8550", "kalama", (0..7).associateWith { 2.8 }))
|
||||
}
|
||||
|
||||
@Test fun allCoreGamesUndoTheClientsInheritedRestriction() {
|
||||
val all = CpuCores.listOrAll("")
|
||||
assertEquals(all, CpuCores.gameLaunchList("", clientPinned = true))
|
||||
assertEquals(all, CpuCores.gameLaunchList(all, clientPinned = true))
|
||||
assertEquals("", CpuCores.gameLaunchList("", clientPinned = false))
|
||||
assertEquals("0", CpuCores.gameLaunchList("0", clientPinned = true))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.droiddeck.launcher.files
|
||||
|
||||
import java.io.File
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class FileManagerNavigationTest {
|
||||
@get:Rule val tmp = TemporaryFolder()
|
||||
|
||||
@Test fun backCannotNavigateAboveTheCurrentDriveRoot() {
|
||||
val drive = tmp.newFolder("drive")
|
||||
val child = File(drive, "folder").apply { mkdir() }
|
||||
|
||||
assertTrue(canNavigateUp(child, drive))
|
||||
assertFalse(canNavigateUp(drive, drive))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.droiddeck.launcher.files
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.CancellationException
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class FileOpsCancellationTest {
|
||||
@get:Rule val tmp = TemporaryFolder()
|
||||
|
||||
@Test fun cancellingCopyRemovesAnIncompleteNewFile() {
|
||||
val source = tmp.newFile("source").apply { writeBytes(ByteArray(5 * 1024 * 1024)) }
|
||||
val destination = File(tmp.root, "destination")
|
||||
var cancelled = false
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(source, destination, { _, _ -> cancelled = true }) { cancelled }
|
||||
}
|
||||
|
||||
assertTrue(source.exists())
|
||||
assertFalse(destination.exists())
|
||||
}
|
||||
|
||||
@Test fun cancellingMoveFallbackPreservesTheSourceAndExistingDestination() {
|
||||
val source = tmp.newFile("source").apply { writeText("replacement") }
|
||||
val destination = tmp.newFile("destination").apply { writeText("original") }
|
||||
var cancelled = false
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.moveWithProgress(source, destination, { _, _ -> cancelled = true }) { cancelled }
|
||||
}
|
||||
|
||||
assertEquals("replacement", source.readText())
|
||||
assertEquals("original", destination.readText())
|
||||
}
|
||||
|
||||
@Test fun aCommittedMoveFinishesRemovingTheSourceDespiteCancellation() {
|
||||
val source = tmp.newFile("source").apply { writeText("replacement") }
|
||||
val destination = tmp.newFile("destination").apply { writeText("original") }
|
||||
val isCancelled = { destination.readText() == "replacement" }
|
||||
|
||||
assertTrue(FileOps.moveWithProgress(source, destination, null, isCancelled))
|
||||
|
||||
assertTrue(isCancelled())
|
||||
assertEquals("replacement", destination.readText())
|
||||
assertFalse(source.exists())
|
||||
assertEquals(setOf("destination"), tmp.root.list()!!.toSet())
|
||||
}
|
||||
|
||||
@Test fun cancellingTheSizeScanStopsBeforeCreatingTheDestination() {
|
||||
val source = tmp.newFolder("source")
|
||||
repeat(3) { File(source, "file-$it").writeText("content") }
|
||||
val destination = File(tmp.root, "destination")
|
||||
var checks = 0
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(source, destination, null) {
|
||||
assertFalse(destination.exists())
|
||||
++checks > 2
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(3, checks)
|
||||
assertFalse(destination.exists())
|
||||
assertEquals(3, source.listFiles()?.size)
|
||||
}
|
||||
|
||||
@Test fun cancellingCopyRemovesANewDestinationDirectory() {
|
||||
val source = tmp.newFolder("source")
|
||||
val payload = File(source, "nested/payload").apply {
|
||||
parentFile!!.mkdirs()
|
||||
writeText("content")
|
||||
}
|
||||
val destination = File(tmp.root, "destination")
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(source, destination, { _, _ -> throw CancellationException("cancel") })
|
||||
}
|
||||
|
||||
assertEquals("content", payload.readText())
|
||||
assertFalse(destination.exists())
|
||||
}
|
||||
|
||||
@Test fun cancellingCopyPreservesAnExistingDestinationDirectory() {
|
||||
val source = tmp.newFolder("source")
|
||||
File(source, "nested/payload").apply {
|
||||
parentFile!!.mkdirs()
|
||||
writeText("replacement")
|
||||
}
|
||||
val destination = tmp.newFolder("destination")
|
||||
val existing = File(destination, "keep").apply { writeText("original") }
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(source, destination, { _, _ -> throw CancellationException("cancel") })
|
||||
}
|
||||
|
||||
assertEquals("original", existing.readText())
|
||||
assertEquals(setOf("keep"), destination.list()!!.toSet())
|
||||
}
|
||||
|
||||
@Test fun cancellingRecursiveDeleteStopsBeforeTheNextFile() {
|
||||
val directory = tmp.newFolder("directory")
|
||||
repeat(3) { File(directory, "file-$it").writeText("content") }
|
||||
var checks = 0
|
||||
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.delete(directory) { ++checks > 3 }
|
||||
}
|
||||
|
||||
assertTrue(directory.exists())
|
||||
assertEquals(2, directory.listFiles()?.size)
|
||||
}
|
||||
}
|
||||
@@ -53,27 +53,25 @@ class FileOpsCopyTest {
|
||||
assertEquals("replacement", src.readText())
|
||||
}
|
||||
|
||||
@Test fun cancellingStopsATreeOfEmptyFiles() {
|
||||
@Test fun cancellingRemovesANewTreeOfEmptyFiles() {
|
||||
val src = tmp.newFolder("source")
|
||||
for (name in listOf("a", "b", "c")) File(src, name).createNewFile()
|
||||
val dst = File(tmp.root, "dest")
|
||||
var checks = 0
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(src, dst, null) { checks++ > 1 }
|
||||
FileOps.copyWithProgress(src, dst, null) { dst.list()?.isNotEmpty() == true }
|
||||
}
|
||||
assertEquals(1, dst.list()!!.size)
|
||||
assertFalse(dst.exists())
|
||||
assertEquals(3, src.list()!!.size)
|
||||
}
|
||||
|
||||
@Test fun cancellingStopsATreeOfEmptyFolders() {
|
||||
@Test fun cancellingRemovesANewTreeOfEmptyFolders() {
|
||||
val src = tmp.newFolder("source")
|
||||
for (name in listOf("a", "b", "c")) File(src, name).mkdir()
|
||||
val dst = File(tmp.root, "dest")
|
||||
var checks = 0
|
||||
assertThrows(CancellationException::class.java) {
|
||||
FileOps.copyWithProgress(src, dst, null) { checks++ > 1 }
|
||||
FileOps.copyWithProgress(src, dst, null) { dst.list()?.isNotEmpty() == true }
|
||||
}
|
||||
assertEquals(1, dst.list()!!.size)
|
||||
assertFalse(dst.exists())
|
||||
}
|
||||
|
||||
@Test fun aCompletedCopyReplacesTheDestination() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.frontend
|
||||
|
||||
import com.droiddeck.launcher.BuildConfig
|
||||
import com.droiddeck.launcher.stores.CredentialCipher
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@@ -92,8 +93,9 @@ class AddedGameArtLookupTest {
|
||||
assertEquals("build", SteamGridDb.pick("", "build"))
|
||||
assertEquals("build", SteamGridDb.pick(" ", "build"))
|
||||
assertEquals("", SteamGridDb.pick("", ""))
|
||||
assertEquals("", SteamGridDb.builtInKey())
|
||||
assertEquals("", SteamGridDb.key(app))
|
||||
val builtIn = SteamGridDb.builtInKey()
|
||||
assertTrue(builtIn == BuildConfig.SGDB_API_KEY)
|
||||
assertTrue(SteamGridDb.key(app) == builtIn.trim())
|
||||
}
|
||||
|
||||
@Test fun theUsersKeyIsSealedOnDiskAndClears() {
|
||||
@@ -106,7 +108,7 @@ class AddedGameArtLookupTest {
|
||||
assertEquals("abcdef0123456789secretkey", SteamGridDb.key(app))
|
||||
SteamGridDb.setUserKey(app, "")
|
||||
assertFalse(file.exists())
|
||||
assertEquals("", SteamGridDb.key(app))
|
||||
assertTrue(SteamGridDb.key(app) == SteamGridDb.builtInKey().trim())
|
||||
}
|
||||
|
||||
@Test fun aFailedLookupIsNotRememberedAsNoMatch() {
|
||||
|
||||
@@ -3,13 +3,23 @@ package com.droiddeck.launcher.gpu
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
|
||||
class TpUbwcHintTest {
|
||||
@Test fun a740OnABlobV6xxTurnsTheHintOff() {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, "512.676.53"))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, "512.676.75"))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, "512.676"))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, "512.699.10"))
|
||||
}
|
||||
|
||||
@Test fun a740OnThePocoBlobTurnsTheHintOn() {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, "512.676.76"))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, "512.676.80"))
|
||||
}
|
||||
|
||||
@Test fun a740OnANewerBlobTurnsTheHintOn() {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, "512.700.0"))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, "512.762.10"))
|
||||
}
|
||||
|
||||
@@ -18,6 +28,24 @@ class TpUbwcHintTest {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, null, samsung = true))
|
||||
}
|
||||
|
||||
@Test fun onOverridesThePocoRuleEvenWhenTheVersionIsUnreadable() {
|
||||
for (version in listOf("512.676.1", "512.676.53", "garbage", null)) {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=1", TpUbwcHint.features(740, version, mode = SessionPrefs.TP_UBWC_ON))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun offOverridesSamsungAndNewerBlobs() {
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, "512.762.10", mode = SessionPrefs.TP_UBWC_OFF))
|
||||
assertEquals("enable_tp_ubwc_flag_hint=0", TpUbwcHint.features(740, null, samsung = true, mode = SessionPrefs.TP_UBWC_OFF))
|
||||
}
|
||||
|
||||
@Test fun overridesDoNotApplyToOtherGpus() {
|
||||
for (mode in listOf(SessionPrefs.TP_UBWC_ON, SessionPrefs.TP_UBWC_OFF)) {
|
||||
assertNull(TpUbwcHint.features(750, "512.762.10", mode = mode))
|
||||
assertNull(TpUbwcHint.features(0, null, mode = mode))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun otherGpusKeepTheirDriverDefault() {
|
||||
assertNull(TpUbwcHint.features(750, "512.762.10"))
|
||||
assertNull(TpUbwcHint.features(830, "512.676.53"))
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.droiddeck.launcher.runtime
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
|
||||
class SeededMacTest {
|
||||
@Test
|
||||
fun theAndroidIdSeedsAStableLocalMac() {
|
||||
val mac = LinuxNetworkLinkComponent.seededMac { "0123456789abcdef" }
|
||||
assertEquals(mac, LinuxNetworkLinkComponent.seededMac { "0123456789abcdef" })
|
||||
assertNotEquals(mac, LinuxNetworkLinkComponent.seededMac { null })
|
||||
assertEquals(0x02, mac.substringBefore(':').toInt(16) and 0x03)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aRomThatThrowsOnTheAndroidIdReadFallsBackToTheDefaultSeed() {
|
||||
val mac = LinuxNetworkLinkComponent.seededMac { throw IllegalArgumentException("Illegal char: - at offset 8") }
|
||||
assertEquals(LinuxNetworkLinkComponent.seededMac { null }, mac)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.io.File
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ComponentsSelectionTest {
|
||||
private val proton = ComponentsManager.Proton("DroidDeck-Proton_11-11.1", "DroidDeck-Proton 11-11.1", File("/p"), "/p", "DroidDeck-Proton-11-11.1", false)
|
||||
private val entry = JSONObject().put("file", "dxvk-low-latency-arm64ec-3.1.1-2-linux.wcp").put("protonVersion", "DroidDeck-Proton-11-11.1")
|
||||
|
||||
@Test
|
||||
fun `a package swapped into an installed Proton is in use`() {
|
||||
assertTrue(ComponentsManager.liveSelection("active", entry, proton))
|
||||
assertTrue(ComponentsManager.liveSelection("queued", JSONObject().put("file", "x.wcp"), proton))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a package left on a removed Proton is not in use`() {
|
||||
assertFalse(ComponentsManager.liveSelection("active", entry, null))
|
||||
assertFalse(ComponentsManager.liveSelection("queued", entry, null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a package from before a Proton update is not in use`() {
|
||||
assertFalse(ComponentsManager.liveSelection("active", JSONObject(entry.toString()).put("protonVersion", "DroidDeck-Proton-11-9.1"), proton))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.core.CpuAllocationDefaults
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -14,36 +13,22 @@ import org.robolectric.annotation.Config
|
||||
@Config(sdk = [33])
|
||||
class CoreAllocationPrefsTest {
|
||||
private val app get() = RuntimeEnvironment.getApplication()
|
||||
private val defaults = CpuAllocationDefaults.Allocation("7", "3,4,5,6")
|
||||
private val saved get() = app.getSharedPreferences("session", Context.MODE_PRIVATE)
|
||||
|
||||
@Before fun clear() { saved.edit().clear().commit() }
|
||||
|
||||
@Test fun absentPreferencesUseTheDeviceDefaultWithoutWritingIt() {
|
||||
assertEquals(SessionPrefs.CoreAllocation(true, "7", "3,4,5,6"), SessionPrefs.coreAllocation(app, defaults))
|
||||
assertTrue(saved.all.isEmpty())
|
||||
assertEquals(SessionPrefs.CoreAllocation(false, "", ""), SessionPrefs.coreAllocation(app, null))
|
||||
@Test fun absentPreferencesLeaveEveryCoreToTheScheduler() {
|
||||
assertFalse(SessionPrefs.clientCpusOverride(app))
|
||||
assertEquals("", SessionPrefs.clientCpus(app))
|
||||
assertEquals("", SessionPrefs.gameCpus(app))
|
||||
}
|
||||
|
||||
@Test fun explicitDisableAndAllCoreChoicesSurviveAnUpdate() {
|
||||
saved.edit().putBoolean("clientCpusOverride", false).putString("clientCpus", "").putString("gameCpus", "").commit()
|
||||
assertEquals(SessionPrefs.CoreAllocation(false, "", ""), SessionPrefs.coreAllocation(app, defaults))
|
||||
}
|
||||
|
||||
@Test fun existingCustomMasksArePreserved() {
|
||||
saved.edit().putBoolean("clientCpusOverride", true).putString("clientCpus", "0,1").putString("gameCpus", "2,7").commit()
|
||||
assertEquals(SessionPrefs.CoreAllocation(true, "0,1", "2,7"), SessionPrefs.coreAllocation(app, defaults))
|
||||
}
|
||||
|
||||
@Test fun anOldClientMaskIsNotEnabledWithoutAnOverrideChoice() {
|
||||
saved.edit().putString("clientCpus", "0,1").commit()
|
||||
assertEquals(SessionPrefs.CoreAllocation(false, "0,1", "3,4,5,6"), SessionPrefs.coreAllocation(app, defaults))
|
||||
}
|
||||
|
||||
@Test fun changingOneChoiceDoesNotReplaceTheOtherDefaults() {
|
||||
SessionPrefs.setGameCpus(app, "0,1,2,3,4,5,6,7")
|
||||
assertEquals(SessionPrefs.CoreAllocation(true, "7", "0,1,2,3,4,5,6,7"), SessionPrefs.coreAllocation(app, defaults))
|
||||
SessionPrefs.setClientCpusOverride(app, false)
|
||||
assertEquals(SessionPrefs.CoreAllocation(false, "7", "0,1,2,3,4,5,6,7"), SessionPrefs.coreAllocation(app, defaults))
|
||||
@Test fun savedChoicesAreKept() {
|
||||
SessionPrefs.setClientCpusOverride(app, true)
|
||||
SessionPrefs.setClientCpus(app, "7")
|
||||
SessionPrefs.setGameCpus(app, "3,4,5,6")
|
||||
assertTrue(SessionPrefs.clientCpusOverride(app))
|
||||
assertEquals("7", SessionPrefs.clientCpus(app))
|
||||
assertEquals("3,4,5,6", SessionPrefs.gameCpus(app))
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class OrientationTest {
|
||||
}
|
||||
|
||||
@Test fun landscapeChoicesPersistGloballyAndCanRestoreAutomaticRotation() {
|
||||
assertEquals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, SessionPrefs.orientation(context))
|
||||
assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, SessionPrefs.orientation(context))
|
||||
for (value in listOf(
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
|
||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
|
||||
@@ -34,7 +34,7 @@ class OrientationTest {
|
||||
@Test fun invalidStoredOrientationFallsBackAndInvalidChoicePreservesTheSavedValue() {
|
||||
val prefs = context.getSharedPreferences("session", Context.MODE_PRIVATE)
|
||||
prefs.edit().putInt("orientation", ActivityInfo.SCREEN_ORIENTATION_PORTRAIT).commit()
|
||||
assertEquals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, SessionPrefs.orientation(context))
|
||||
assertEquals(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, SessionPrefs.orientation(context))
|
||||
SessionPrefs.setOrientation(context, ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
SessionPrefs.setOrientation(context, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||
|
||||
@@ -13,9 +13,33 @@ class SyncBackendTest {
|
||||
private val context: Context = RuntimeEnvironment.getApplication()
|
||||
|
||||
@Test
|
||||
fun aFreshInstallStartsOnEsync() {
|
||||
assertEquals(SessionPrefs.SYNC_ESYNC, SessionPrefs.syncBackend(context))
|
||||
fun aFreshInstallStartsOnNtsync() {
|
||||
assertEquals(SessionPrefs.SYNC_NTSYNC, SessionPrefs.syncBackend(context))
|
||||
assertEquals(false, SessionPrefs.fsyncFirst(context))
|
||||
SessionPrefs.applyNtsyncDefault(context)
|
||||
assertEquals(SessionPrefs.SYNC_NTSYNC, SessionPrefs.syncBackend(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anEarlierChoiceMovesToNtsyncOnce() {
|
||||
for (earlier in listOf(SessionPrefs.SYNC_ESYNC, SessionPrefs.SYNC_FSYNC, SessionPrefs.SYNC_WINESERVER)) {
|
||||
context.getSharedPreferences("session", Context.MODE_PRIVATE).edit().clear().commit()
|
||||
SessionPrefs.setSyncBackend(context, earlier)
|
||||
SessionPrefs.applyNtsyncDefault(context)
|
||||
assertEquals(SessionPrefs.SYNC_NTSYNC, SessionPrefs.syncBackend(context))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aChoiceMadeAfterTheMoveIsKept() {
|
||||
SessionPrefs.setSyncBackend(context, SessionPrefs.SYNC_ESYNC)
|
||||
SessionPrefs.applyNtsyncDefault(context)
|
||||
SessionPrefs.setSyncBackend(context, SessionPrefs.SYNC_ESYNC)
|
||||
SessionPrefs.applyNtsyncDefault(context)
|
||||
assertEquals(SessionPrefs.SYNC_ESYNC, SessionPrefs.syncBackend(context))
|
||||
SessionPrefs.setSyncBackend(context, SessionPrefs.SYNC_WINESERVER)
|
||||
SessionPrefs.applyNtsyncDefault(context)
|
||||
assertEquals(SessionPrefs.SYNC_WINESERVER, SessionPrefs.syncBackend(context))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -34,6 +58,7 @@ class SyncBackendTest {
|
||||
|
||||
@Test
|
||||
fun pickingATabTurnsSyncBackOn() {
|
||||
SessionPrefs.setFastSync(context, false)
|
||||
SessionPrefs.setSyncFallback(context, false)
|
||||
SessionPrefs.setFsyncFirst(context, false)
|
||||
assertEquals(SessionPrefs.SYNC_WINESERVER, SessionPrefs.syncBackend(context))
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import android.content.Context
|
||||
import com.droiddeck.launcher.gpu.TpUbwcHint
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.util.ReflectionHelpers
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], manifest = Config.NONE)
|
||||
class TpUbwcPrefsTest {
|
||||
private val context: Context get() = RuntimeEnvironment.getApplication()
|
||||
|
||||
@Before fun reset() {
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "resolved", false)
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "cached", null)
|
||||
ReflectionHelpers.setStaticField(TpUbwcHint::class.java, "activeMode", null)
|
||||
context.getSharedPreferences("session", Context.MODE_PRIVATE).edit().clear().commit()
|
||||
}
|
||||
|
||||
@Test fun missingOrInvalidChoiceUsesAuto() {
|
||||
assertEquals(SessionPrefs.TP_UBWC_AUTO, SessionPrefs.tpUbwcMode(context))
|
||||
context.getSharedPreferences("session", Context.MODE_PRIVATE).edit().putString("tpUbwcMode", "invalid").commit()
|
||||
assertEquals(SessionPrefs.TP_UBWC_AUTO, SessionPrefs.tpUbwcMode(context))
|
||||
}
|
||||
|
||||
@Test fun overrideIsStoredWithoutChangingDriverChoices() {
|
||||
SessionPrefs.setAndroidDriver(context, "display")
|
||||
SessionPrefs.setLinuxDriver(context, "runtime")
|
||||
for (mode in listOf(SessionPrefs.TP_UBWC_ON, SessionPrefs.TP_UBWC_OFF, SessionPrefs.TP_UBWC_AUTO)) {
|
||||
SessionPrefs.setTpUbwcMode(context, mode)
|
||||
assertEquals(mode, SessionPrefs.tpUbwcMode(context))
|
||||
assertEquals("display", SessionPrefs.androidDriver(context))
|
||||
assertEquals("runtime", SessionPrefs.linuxDriver(context))
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun changingTheSavedChoiceKeepsTheProcessChoiceUntilRestart() {
|
||||
SessionPrefs.setTpUbwcMode(context, SessionPrefs.TP_UBWC_ON)
|
||||
val initial = TpUbwcHint.forThisDevice(context)
|
||||
SessionPrefs.setTpUbwcMode(context, SessionPrefs.TP_UBWC_OFF)
|
||||
assertTrue(TpUbwcHint.restartRequired(context))
|
||||
assertEquals(SessionPrefs.TP_UBWC_ON, TpUbwcHint.activeMode)
|
||||
assertEquals(initial, TpUbwcHint.forThisDevice(context))
|
||||
SessionPrefs.setTpUbwcMode(context, SessionPrefs.TP_UBWC_ON)
|
||||
assertFalse(TpUbwcHint.restartRequired(context))
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,30 @@ class SteamStorageTest {
|
||||
assertEquals(emptyMap<String, String>(), SteamStorage.compatMappings("\"x\" {}"))
|
||||
}
|
||||
|
||||
@Test fun readsShaderPrecacheFromConfig() {
|
||||
val vdf = """
|
||||
"InstallConfigStore"
|
||||
{
|
||||
"Software" { "Valve" { "Steam" { "ShaderCacheManager"
|
||||
{
|
||||
"HasCurrentBucket" "1"
|
||||
"DisableShaderCache" "1"
|
||||
} } } }
|
||||
}
|
||||
""".trimIndent()
|
||||
assertEquals(false, SteamStorage.shaderPrecache(vdf))
|
||||
assertEquals(true, SteamStorage.shaderPrecache(vdf.replace("\"DisableShaderCache\"\t\t\"1\"", "\"DisableShaderCache\"\t\t\"0\"")))
|
||||
assertEquals(true, SteamStorage.shaderPrecache("\"InstallConfigStore\" {}"))
|
||||
assertEquals(true, SteamStorage.shaderPrecache("""
|
||||
"InstallConfigStore"
|
||||
{
|
||||
// "DisableShaderCache" "1"
|
||||
"Other" { "DisableShaderCache" "1" }
|
||||
"Software" { "Valve" { "Steam" { "ShaderCacheManager" { "DisableShaderCache" "0" } } } }
|
||||
}
|
||||
""".trimIndent()))
|
||||
}
|
||||
|
||||
@Test fun readsCustomToolName() {
|
||||
val vdf = "\"compatibilitytools\"\n{\n \"compat_tools\"\n {\n \"GE-Proton11-9-aarch64\"\n {\n \"install_path\" \".\"\n }\n }\n}"
|
||||
assertEquals("GE-Proton11-9-aarch64", SteamStorage.compatToolName(vdf))
|
||||
|
||||
@@ -29,6 +29,17 @@ class PickListingTest {
|
||||
assertEquals(listOf("alarms", "Android", "DCIM", "Documents", "Download", "Games", "Winlator"), listing.folders.map { it.dir.name })
|
||||
}
|
||||
|
||||
@Test fun hiddenDirectoriesAndExecutablesCanBeShownOnRequest() {
|
||||
val root = storage()
|
||||
val hidden = File(root, ".Games").apply { mkdirs() }
|
||||
File(hidden, "Game.exe").writeText("game")
|
||||
assertFalse(pickListing(root, PickKind.EXE).folders.any { it.dir.name == ".Games" })
|
||||
val listing = pickListing(root, PickKind.EXE, showHidden = true)
|
||||
assertTrue(listing.folders.any { it.dir.name == ".Games" })
|
||||
assertTrue(listing.files.any { it.name == ".hidden.exe" })
|
||||
assertEquals(listOf("Game.exe"), pickListing(hidden, PickKind.EXE).files.map { it.name })
|
||||
}
|
||||
|
||||
@Test fun onlyExesForAGameOnlyImagesForArt() {
|
||||
val root = storage()
|
||||
assertEquals(listOf("game.EXE", "Setup.exe"), pickListing(root, PickKind.EXE).files.map { it.name })
|
||||
|
||||
@@ -132,4 +132,40 @@ class ThemeColorsTest {
|
||||
assertTrue(Themes.all.any { it.id == Themes.GRADIENT })
|
||||
assertEquals(Themes.GRAPHITE, Themes.byId("nonsense").id)
|
||||
}
|
||||
|
||||
@Test fun innerGamerSitsJustAboveGradientInTheThemeList() {
|
||||
val ids = Themes.all.map { it.id }
|
||||
assertEquals(ids.indexOf(Themes.GRADIENT) - 1, ids.indexOf(Themes.INNER_GAMER))
|
||||
}
|
||||
|
||||
@Test fun innerGamerButtonTextIsReadableAlongTheGradientAtEveryHue() {
|
||||
var hue = 0f
|
||||
while (hue < 360f) {
|
||||
val p = Themes.innerGamer(hue)
|
||||
assertEquals(Themes.INNER_GAMER, p.id)
|
||||
for (i in 0..100) {
|
||||
val f = i / 100f
|
||||
val blend = Color(
|
||||
p.primary.red + (p.primary2.red - p.primary.red) * f,
|
||||
p.primary.green + (p.primary2.green - p.primary.green) * f,
|
||||
p.primary.blue + (p.primary2.blue - p.primary.blue) * f,
|
||||
)
|
||||
assertTrue("hue $hue at $f", contrast(p.onPrimary, blend) >= 4.49f)
|
||||
}
|
||||
hue += Themes.RGB_HUE_STEP
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun innerGamerFollowsTheHueAndTheSpeedIsSavedAndKeptInRange() {
|
||||
Themes.moveRgbHue(120f)
|
||||
assertEquals(Themes.innerGamer(120f).primary, Themes.byId(Themes.INNER_GAMER).primary)
|
||||
Themes.setRgbSpeed(app, 7)
|
||||
assertEquals(7, SessionPrefs.rgbSpeed(app))
|
||||
Themes.setRgbSpeed(app, 99)
|
||||
assertEquals(Themes.RGB_SPEEDS, Themes.rgbSpeed)
|
||||
Themes.setRgbSpeed(app, 0)
|
||||
assertEquals(1, SessionPrefs.rgbSpeed(app))
|
||||
Themes.setRgbSpeed(app, 1)
|
||||
assertTrue(Themes.rgbCycleMillis(1) > Themes.rgbCycleMillis(10))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# Native arm64 Steam client: channel and runtime
|
||||
|
||||
The default client is Valve's arm64 Linux build (`steamrtarm64/steam`). Valve does not support it, but
|
||||
publishes it on the Steam Deck client channels. `droiddeck-steam-install` installs it and
|
||||
`droiddeck-session` starts it with `STEAM_RUNTIME=1`.
|
||||
|
||||
## Channel
|
||||
|
||||
- The default is `steamdeck_publicbeta` (manifest `steam_client_steamdeck_publicbeta_linuxarm64`).
|
||||
Valve also publishes `steam_client_steamdeck_stable_linuxarm64`.
|
||||
- `droiddeck-steam-install --channel` prints the channel in use. The session passes that value as
|
||||
`-clientbeta` and writes it to `package/beta`. The two must match, or the client loops on rc 42.
|
||||
- Setting: `BL_STEAM_CHANNEL`, otherwise `~/.config/droiddeck/steam-channel`, set to `beta` or
|
||||
`stable`. A set channel never changes on its own.
|
||||
- With no setting, the client falls back to `steamdeck_stable` automatically. It stays on stable
|
||||
(`package/droiddeck-channel`) until a setting says otherwise. The fallback happens:
|
||||
- when the client was started two more times without being shown after the start-failure repair
|
||||
(`package/droiddeck-unconfirmed`: two unconfirmed starts trigger a repair, which writes
|
||||
`repaired`; two more unconfirmed starts trigger the fallback);
|
||||
- when the beta manifest has no `*_linuxarm64_linuxarm64` component. The install log then lists the
|
||||
manifest's block names, so a renamed or removed arm64 build can be seen.
|
||||
- The x86-64 client keeps `steamdeck_publicbeta` (see [x86-64-steam-client.md](x86-64-steam-client.md)).
|
||||
|
||||
## Steam Runtime container
|
||||
|
||||
Valve's 2026-10-15 beta made the x86 Linux client 64-bit and put it in a Steam Runtime container
|
||||
(pressure-vessel). The container needs unprivileged user and mount namespaces and a D-Bus session bus,
|
||||
which an Android app does not have. The x86-64 path already works around this. The arm64 client gets the
|
||||
same treatment if Valve containerizes it too:
|
||||
|
||||
- `STEAM_STEAMRT_RECURSION_GUARD=1` is exported for both clients. It tells the client that it is
|
||||
already inside its container, so after an update it does not re-execute itself through
|
||||
`pressure-vessel-wrap` (bwrap).
|
||||
- `droiddeck-steamrt-shim <client dir> <shim dir>` builds the shim when the client carries
|
||||
`pv-runtime/steam-runtime-steamrt` or a `steam-runtime-check-requirements` probe:
|
||||
- Valve's runtime is linked file by file;
|
||||
- the probe is replaced with `exit 0`;
|
||||
- its own `steam-runtime-launch-client` runs commands directly.
|
||||
|
||||
For the arm64 client the session then starts it with `STEAM_RUNTIME_STEAMRT` and `PATH` pointing
|
||||
at `~/.droiddeck-steamrt-shim` and logs "Valve containerized the arm64 Steam client". Without those
|
||||
files the shim is not built and the start is unchanged.
|
||||
|
||||
## What Valve's manifests carry (checked 2026-10-22)
|
||||
|
||||
- `steam_client_steamdeck_publicbeta_linuxarm64` (1792626604) and `steam_client_steamdeck_stable_linuxarm64`
|
||||
(1792618285) have the same blocks. Three platforms share them:
|
||||
- `*_linuxarm64_linuxarm64`: the native client (`steamrtarm64/`, `linuxarm64/`). No pressure-vessel, no
|
||||
`steam-runtime-check-requirements`. These are the only platform blocks the installer keeps.
|
||||
- `*_steamrt_linuxarm64`: Valve's x86-64 SteamRT 3c client (`steamrt64/steam` is an x86-64 ELF) with
|
||||
`steamrt64/pv-runtime/steam-runtime-steamrt` (pressure-vessel, `steam-runtime-check-requirements`,
|
||||
`steam-runtime-launch-client`). This is the containerized client the 2026-10-15 beta made the default on
|
||||
x86. Valve ships it to arm64 hosts as well, presumably to run under FEX. The installer skips it.
|
||||
- `bins_androidarm64_linuxarm64`: `androidarm64/libsteamclient.so`, `steamservice.so` and friends.
|
||||
- `steam_client_steamdeck_publicbeta_ubuntu12` has stopped at 1792108788, while stable `ubuntu12` follows
|
||||
stable (1792618285). On beta the 32-bit `ubuntu12` client no longer gets updates.
|
||||
- If a future manifest drops `*_linuxarm64_linuxarm64`, the installer logs the block list and moves to
|
||||
stable. If stable drops it too, the remaining option is the x86-64 client under FEX
|
||||
([x86-64-steam-client.md](x86-64-steam-client.md)).
|
||||
@@ -52,6 +52,12 @@ environment entries and is intentionally narrower than **Reset game profile**.
|
||||
Values are literal strings, with no shell expansion; quotes are only needed when
|
||||
the consuming program expects them.
|
||||
|
||||
A Unity game under FEX (`UnityPlayer.dll` for Windows builds under Proton,
|
||||
`UnityPlayer.so` or `GameAssembly.so` for native Linux builds started by
|
||||
**Linux x86 (FEX)**) keeps FEX's TSO on when the Default profile's FEX preset
|
||||
turns it off: without TSO such games can deadlock or crash (seen with Megabonk
|
||||
and Crab Game). A FEX preset chosen for the game itself still applies.
|
||||
|
||||
## Defaults and available suggestions
|
||||
|
||||
The initial configuration enables Mesa shader caching with
|
||||
|
||||
@@ -23,3 +23,17 @@ Setup shows Primary and Secondary under the theme when Gradient is chosen, each
|
||||
square. It opens `ColorPickerDialog`: a wheel (hue and saturation, touch), hue / saturation /
|
||||
brightness sliders (they work with a controller) and a hex box, with the old and new colour side by
|
||||
side. OK applies it.
|
||||
|
||||
## Inner Gamer theme
|
||||
|
||||
`Themes.INNER_GAMER` is an RGB theme: the accent walks round the colour wheel. It sits just above
|
||||
Gradient in the list, and Setup shows a Speed slider (1 to 10, `SessionPrefs.rgbSpeed`) under the
|
||||
theme while it is chosen.
|
||||
|
||||
`Themes.innerGamer(hue)` builds the palette for one hue through `ThemeColors.palette`, so button text
|
||||
keeps the same contrast along the gradient at every hue. `DroidDeckTheme` runs one
|
||||
`rememberInfiniteTransition` while Inner Gamer is chosen, so the system's animation clock drives it.
|
||||
Compose applies Android's animator scale to it, and the colour holds still when animations are off
|
||||
in the app (`Motion.scale`) or in Android's own settings. The hue is written to `Themes.rgbHue` in 3° steps, which restyles the
|
||||
app only when the colour visibly moves; a slow cycle costs few recompositions.
|
||||
|
||||
@@ -53,7 +53,8 @@ arm64 client down first.
|
||||
("Steam now requires user namespaces") when `steam-runtime-check-requirements`' bwrap probe cannot
|
||||
create a user namespace, which no Android app can. `STEAM_RUNTIME_STEAMRT` points at
|
||||
`steamrt-shim`: Valve's runtime linked file by file with only the probe replaced (`exit 0`). proot
|
||||
is the container, as for the arm64 client.
|
||||
is the container, as for the arm64 client. `droiddeck-steamrt-shim` builds it, and builds the same
|
||||
shim for the arm64 client if that one is containerized too ([arm64-steam-client.md](arm64-steam-client.md)).
|
||||
- **Re-exec after an update.** The client re-executes itself inside the SteamRT container through
|
||||
`pressure-vessel-wrap` (bwrap). `STEAM_STEAMRT_RECURSION_GUARD=1` says it is already inside one; the
|
||||
FEX rootfs with the client's `steamrt64` libraries is that container.
|
||||
|
||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user