Commit Graph
1549 Commits
Author SHA1 Message Date
maxjivi05 30da4d2bcd fix: count a desktop window game's frames for the performance HUD 2026-10-26 10:50:19 -04:00
maxjivi05 e492a78f55 fix: treat a product.db with a damaged install record as unreadable 2026-10-26 10:30:16 -04:00
maxjivi05 538b5c5270 fix: hide registry-only games when product.db lists none, keep the Agent off without the game storage card 2026-10-26 10:30:16 -04:00
maxjivi05 be5bec2d44 fix: keep every game library mounted for Battle.net's Agent and drop games it no longer tracks 2026-10-26 10:30:16 -04:00
maxjivi05 fd9de4a21e fix: list Battle.net's games in a grid that scrolls down 2026-10-26 10:10:31 -04:00
The412Banner 1b3814a587 Hold games to the frame limit in Performance mode (#1609)
A frame limit only paced what reached the screen: a game without vsync
kept drawing hundreds of frames a second that were thrown away (AIO at
a 60 fps limit drew 155 to 2,446 fps), so the limit saved no power.

With Performance mode on and a frame limit set, the session now sets
gamescope's per-game limit over gamescope_control (gamescopectl
debug_set_fps_limit). gamescope's limiter then has its WSI layer hold
Vulkan games, DXVK and VKD3D included, to the limit. gamescope's own
--framerate-limit option is reset on its first frame in 3.16.29, and
Steam resets the per-game limit while it starts, so the session watches
gamescope's limiter state file and sets the limit again whenever it is
off (a once-a-second check with shell builtins, no process started).
With Performance mode off, a limit paces the screen only, as before.

The session also gives gamescope a fixed limiter state file path, so
gamescope stops leaving a new one behind on every start, and removes
the old ones.

Device-proven on the AYANEO Pocket FIT (Turnip, AIO D3D11, 45 s runs on
battery, three runs each): at a 60 fps limit the game held exactly
60.0 fps and the device drew 7.08 W instead of 9.45 W, with 1.3 instead
of 2.6 busy cores and 5 instead of 11 % GPU load. No limit: unchanged.
2026-10-26 09:16:05 -04:00
Kurt Himebauch 78f95c5a0c feat(ui): support portrait launcher layouts (#1606) 2026-10-26 09:14:35 -04:00
maxjivi05 db56cff880 fix: check a Battle.net game for updates again when the last check failed 2026-10-26 09:01:02 -04:00
maxjivi05 24f0b97c08 fix: report a gamescope that cannot start and ignore Agent answers that are not objects 2026-10-26 09:01:02 -04:00
maxjivi05 3b6b304f55 fix: take the Agent's empty answer for an uninstalled game as gone
Once Blizzard's Agent has removed a game it answers GET /game/<uid> with
an empty object, not installed false, so a finished uninstall read as a
failure and opened Battle.net.
2026-10-26 09:01:02 -04:00
maxjivi05 c48297e0d1 fix: wait for a cancelled Battle.net job before removing it, and bound uninstall waits
A cancelled install now waits for its Agent job to end before the
uninstall runs, and removes the folder only once the Agent no longer
lists the game or the folder is empty. Uninstall gives up when Battle.net
or a game keeps the prefix busy for a minute, so the page falls back to
opening Battle.net. An uninstall only counts when the Agent reports the
game as not installed.
2026-10-26 09:01:02 -04:00
maxjivi05 b887e8653d feat: install Battle.net games from Stores with downloads, updates and uninstall
Battle.net games now install like Epic, Xbox and itch games: Install,
pick a location, and the download runs in the Downloads tab while the
Stores page stays open. droiddeck-battlenet starts Blizzard's own Agent
on its own under a headless gamescope and drives its local API, so no
window opens and nothing waits on the Battle.net client.

- Progress, speed and time left come from the Agent, with pause, resume
  (the Agent keeps what it downloaded) and cancel, which uninstalls the
  partial game and removes its folder.
- A job only finishes once the Agent has really run it and the game is
  playable; the first status after a request can still describe the
  previous operation.
- Installed games check Blizzard's version service and show Update when
  the build on disk is behind; the update repairs missing files too.
- Battle.net games can be uninstalled from their page through the Agent.
- Games land in the chosen location: internal storage, other storage or
  a Games folder.
- The hero keeps its action row clear of the playtime column.
2026-10-26 09:01:02 -04:00
Kurt Himebauch 9fc48515c1 feat(input): unify low latency mode (#1604) 2026-10-26 08:12:47 -04:00
Kurt Himebauch 63d1e4d61d feat(library): remember visibility and store order (#1591) 2026-10-26 07:44:07 -04:00
Kurt Himebauch fbb334263c fix(ui): restore continuous ambient motion (#1592) 2026-10-26 02:05:19 -04:00
Kurt Himebauch 3fd6b1f00d fix(ui): restore consistent focus glide transitions (#1588) 2026-10-25 23:20:57 -04:00
Kurt Himebauch 5978a13f09 fix(stores): restore store capabilities in Library (#1586) 2026-10-25 22:07:42 -04:00
MaxsTechReview c71ba6da96 Run desktop games on the GPU in a window (#1584)
Steam games and Battle.net started from the KDE desktop run on the GPU in gamescope, opened as a KDE window, and Plasma no longer leaves programs with a zero realtime CPU limit that got games killed.
2026-10-25 21:09:24 -04:00
DrummingBird1 cce742766d fix: zero-copy launch log no longer points at a drawer switch that do… (#1354)
fix: describe the actual HDR zero-copy launch policy
2026-10-25 19:25:31 -04:00
Thomas Lee 4922d309c8 perf: allocate only solved DD-FG flow levels (#1556)
The flow solver starts at flowFinest_, but its images, uniform buffers and descriptor sets were allocated for every pyramid level. Keep the seven-level indices and allocate flow resources only where the solver reads and writes them.

The default preset avoids three unused RGBA16F images, saving 38,707,200 bytes of pixel payload at 2560x1440 without changing the shaders or recorded GPU work. Add native allocation/dispatch regressions and document the resource budgets.
2026-10-25 19:21:40 -04:00
MaxsTechReview 71fee6b3cc Keep a still direct touch held unless Long press is on (#1578) 2026-10-25 18:32:28 -04:00
Kurt Himebauch 8cf2ffe2ad fix: say why Flatpak setup failed instead of blaming the mirrors (#1580) 2026-10-25 18:05:41 -04:00
Kurt Himebauch 4e00b40333 fix: start the session even when the agent environment can't be prepared (#1579) 2026-10-25 17:57:52 -04:00
Kurt Himebauch 65bacc067b fix: extract GOG installer fallback natively on ARM (#1174) 2026-10-25 17:56:35 -04:00
Kurt Himebauch 53ac21b4d0 fix: keep pending agent entries until the session's environment is ready 2026-10-25 17:50:51 -04:00
e76489ccea fix: refuse overwriting in-use component packages and clear unpacked store on replace (#1144)
* fix: refuse overwriting in-use component packages and clear unpacked store on replace

* fix: preserve component store until replacement succeeds

* fix: invalidate completed component cache before replace

* fix: constrain unpacked component cache path

* Test rejected package cache dot segments

---------

Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 17:50:03 -04:00
b4b7883648 fix: Reuse the prefix the client made for a shortcut id (#1107)
* fix: Reuse the prefix the client made for a shortcut id

droiddeck-proton-run builds the prefix path as steamapps/compatdata/<appid>,
but the client names that folder with the signed 32-bit form of the shortcut
id. A shortcut whose id has the top bit set therefore got a second, empty
prefix, and the first launch after that ran wineboot from scratch: the
session sat on a black screen for the length of a full prefix init, and the
old prefix was never reused.

Match the folder by its unsigned value instead, the way the client does:
int(name) % 2 ** 32 == appid % 2 ** 32. prefix_of() also accepts a negative
folder name now, and reports its unsigned id so the Steam environment it sets
up stays valid.

* fix: Reject folder names int() cannot read, and skip files

lstrip("-").isdigit() accepts "--1" and superscript digits, which int() then
raises on, and one such entry in compatdata would stop shortcut resolution.
Match r"-?[0-9]+" instead. A matching name is also only used when it is a
directory, so a stray file cannot take STEAM_COMPAT_DATA_PATH.

* fix: reuse signed shortcut compatdata prefix

* fix: prefer initialized Proton shortcut prefixes

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 17:49:58 -04:00
ovurrsl 007166753c fix: start the session even when the agent environment can't be prepared 2026-10-25 17:35:36 -04:00
Kurt Himebauch 02767b7e6c fix: report a full battery at 100% while charging, so Steam stops showing 99% (#1577)
* fix: report a full battery at 100% while charging, so Steam stops showing 99%

* test: charging at 100% is reported full
2026-10-25 17:29:43 -04:00
cf13762939 feat: Show the drawer's game settings when a game starts without Steam (#1113)
* feat: Show the drawer's game settings when a game starts without Steam

A session started without the client has no Steam overview to read the
current title from, so the drawer's game profile group stayed empty and the
per-game settings, FEX preset, environment and unfolded controls were hidden.
The launch already knows which game it starts, so carry its id into the
session and fall back to it when Steam has nothing to report.

* fix: Pass the game id into the service start, and keep the unfolded toggle to Steam

SessionActivity.surfaceCreated() hands the mode, program and args to
SessionService.start(), which builds the service intent. EXTRA_GAME_ID was
only put on the activity intent, so a new no-Steam session never received it
and the drawer fallback stayed empty. Pass it through.

The unfolded-controls toggle also went out for direct sessions, but
applyUnfoldedControls() still requires Steam mode and a Deck pad, neither of
which a direct session sets up. Keep the toggle to Steam.

* fix: hide Steam controls in direct session drawer

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 17:29:39 -04:00
kingokksa 9f4794769c feat: on-screen keys for any key or key combo (#1305) 2026-10-25 17:27:07 -04:00
6e67dbb5be fix: chosen game art survives a Steam account switch or runtime reset (#1026)
Every grid-<slot> record held only a file signature, not which grid
folder it came from. After switching accounts or reinstalling the
runtime, the new, empty grid folder looked like the user removed every
image in Steam, and all chosen art was deleted. The grid folder now
carries a random id and records store it; a record from another folder
counts as a first look, so the choice is placed again.

fix: never accept a record made with the fallback grid id

CodeRabbit: when the grid id could not be read or written, "?" was
stored in records and a later "?" matched it, so an empty grid could
still delete local art. Records now never match the fallback id, and a
blank .droiddeck-grid file is replaced instead of blocking the rename.

Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 17:18:31 -04:00
19c655bd7e fix: update status receiver only acts on DroidDeck's own install session (#1015)
* fix: the update status receiver only acts on this app's own install session

Before Android 13 the receiver was exported, so any app could send a
fake PENDING_USER_ACTION broadcast and have DroidDeck launch an arbitrary
intent. The receiver now ignores broadcasts whose session id does not
match the running install, launches only a system installer activity,
and strips URI grant flags.

* fix: record the install session id before committing it

CodeRabbit: the status broadcast could arrive between commit() and
install() returning, while session was still -1, and be dropped. The id
is now reported through a callback before commit, and reset to -1 at
the start of each attempt so a late broadcast from an earlier session
is ignored.

* fix: declare package installer confirmation query

* Query current PackageInstaller confirmation action

---------

Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 17:03:25 -04:00
Kurt Himebauch 815b93db5f feat: profile PRoot costs and test fast-path libraries without an APK rebuild (#1528) 2026-10-25 16:49:35 -04:00
MaxsTechReview aca4b5dd3e Start World of Warcraft on DirectX 11 by default (#1576) 2026-10-25 16:41:41 -04:00
Kurt Himebauch 8d858b4334 feat: pick a game's CPU cores in its game profile (#1566)
* feat: pick a game's CPU cores in its game profile

* fix: match per-game cores against the online CPU list
2026-10-25 16:38:11 -04:00
ianandKurt Himebauch ee3f32d31e feat: default storage for new installs, with USB/SDCARD/SSD detection (#1520)
* feat: choose default storage for new store installs

Store installs and the Epic Games Launcher can use internal storage, external storage, a folder picked through Choose folder..., or ask every time. The choice is stored in SessionPrefs and used by StoreInstallRoot; Storage > Default storage exposes it.

* fix: allow choosing any folder for default storage

The default storage choice can now hold an arbitrary folder path, not just internal or app-specific external storage. StoreInstallRoot resolves and validates picked folders, keeps a disconnected choice instead of silently falling back, and the folder picker stays available from Storage.

* fix: keep the Epic install folder when external storage is away

* test: use a unique missing folder for the Epic storage check

* fix: detect USB SSDs that the storage manager hides

List storage volumes that have no UUID instead of dropping them, keep volumes that are present but not yet mounted, and fall back to parsing /proc/mounts so USB OTG drives appear even when Android does not expose them as storage volumes or app directories. Prefer the system's volume description for removable drives. (Raw removable drives as move targets, the folder-picker fallback, and the unreadable-drive guidance follow with the move stack.)

* fix: compare mount paths as strings in StorageRootsTest

* fix: never drop a present storage volume from the drive list

Key volumes by description or index when they report no UUID and no directory, keep nofs/unmountable states so a plugged-in drive stays visible with guidance, only attach app directories on strict UUID matches, and accept any fstype under vold-managed locations plus usb-ish mounts directly under /mnt.

* fix: probe folder writes with a fresh temp file and keep Epic's folder unset while external storage is away

---------

Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 16:38:06 -04:00
The412Banner 38926b3157 feat: Performance mode puts frame pacing and per-frame window-check fixes behind one switch (#1563)
Adds a Performance mode switch under Mode settings > Frame rate, off by default and stored per mode like the frame limit. Off keeps today's behaviour. On:

- with no frame limit, the compositor hands buffers back to the game as soon as they are replaced instead of once per refresh, and gamescope's WSI layer is asked for at least 8 swapchain images; a chosen frame limit still paces
- DROIDDECK_WSI_BYPASS_CHECK_MS=100 lets gamescope's WSI layer reuse its present-time Xwayland bypass check (patch 0117, gamescope-3.16.29-p13); WINE_OFFSCREEN_CHECK_MS=100 does the same for winex11 once a DroidDeck-Proton release carries that fix. Off sets both to 0.

The gamescope pack now ships the patched WSI layer and SessionFiles stages it over the runtime's stock copy. Two per-game options, off by default: PROTON_DXVK_D3D8 and WINE_D3D_CONFIG renderer=vulkan|gl.

AIO Graphics Test on an AYANEO Pocket FIT (Adreno 750), off -> on: Vulkan 204 -> 1787, OpenGL 296 -> 2169, D3D12 308 -> 3563, D3D11 1133 -> 2874, D3D9 1253 -> 3098 fps; DiRT Showdown 92.2 -> 93.4 (GPU-bound).
2026-10-25 20:29:26 +00:00
Kurt Himebauch 12eac3e9ee Fix Nova layouts and session drawer shortcuts (#1574)
Improve Nova’s compact layouts with scrolling store tabs and build details, remove the redundant Updates heading, preserve full portrait artwork, and add drawer PiP and Select/Start shortcuts for Guide/QAM.
2026-10-25 16:06:19 -04:00
MaxsTechReview 679e9476c3 Add Battle.net logs to Share logs (#1573) 2026-10-25 15:21:27 -04:00
Kurt Himebauch 8addf7d7f0 fix: correct Nova M1 and M2 mapping (#1571)
* fix: correct Nova M1 and M2 mapping

* fix: recognize Nova Xbox controller ID
2026-10-25 14:40:14 -04:00
MaxsTechReview 1f3f1ebc6a fix: Arabic text reads right to left when it mixes in English words (#1568) 2026-10-25 14:25:43 -04:00
MaxsTechReview 645dc3e781 fix: close the Stores and Library search with its X or Back (#1567) 2026-10-25 14:25:39 -04:00
Kurt Himebauch 9593529197 feat: a setting to keep Steam uncompressed while games run (#1565)
* feat: a setting to keep Steam uncompressed while games run

* fix: say that the RAM cap still compresses Steam when this is off
2026-10-25 13:20:33 -04:00
ae193e9b8d fix: find GOG Galaxy SDK cloud saves when remote-config lists no locations (#970)
* fix: find GOG Galaxy SDK cloud saves when remote-config lists no locations

cloudStorage.enabled with an empty locations list is Galaxy SDK IStorage
under __default. DroidDeck treated that as no cloud.

Use the SDK folder, strip/prefix the __default namespace, and inflate gzip
bodies GOG stores on download. An empty cached template is fetched again
so devices that already recorded "no cloud" recover.

* fix: keep an empty GOG namespace, expire cached none, close gzip

An explicit empty cloud_namespace is Auto Cloud and must not become
__default just because the path template looks like Galaxy storage.
A cached none is retried after a day so a later enablement is picked up.
GZIPInputStream is closed after a compressed download.

* fix: peel GOG cloud gzip only when the service or PUT compressor says so

GET asks for identity encoding so the body is what GOG stored. A
Content-Encoding of gzip is inflated once. Otherwise a gzip body is
inflated only when it round-trips as GOG's PUT gzip (deflate 6, mtime 0),
so a gzip-format save is not rewritten.

* Reject corrupt GOG cloud downloads

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 13:20:29 -04:00
MaxsTechReview 27e390a846 fix: StarCraft starts its 32-bit client from Battle.net (#1564) 2026-10-25 13:10:28 -04:00
MaxsTechReviewandKurt Himebauch 067f82e5e2 fix(stores): let a game's description fill the free height of the page (#1493)
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 12:59:58 -04:00
7ef52865d2 fix: place GOG cloud saves at the location root (#1178)
GOG lists every cloud object as <location name>/<path>. The location name
is not part of the local folder, so Silksong's saves/user1.dat belongs at
default/user1.dat, not default/saves/user1.dat. Strip the segment for local
paths and add it back for HEAD/GET/PUT, cache the location name, and migrate
files written by the old layout without touching files the game wrote.

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 12:58:54 -04:00
0de672f94e fix: a Linux runtime update keeps imported apps and catalog AppImages (#1062)
* fix: a Linux runtime update keeps imported apps and catalog AppImages

The update carried only /root into the new tree and then discarded the
old one, so every imported AppImage, script app, Flatpak override and
catalog emulator AppImage was deleted. User-owned paths are now moved
across as a list, moved back if the carry or swap fails partway, and
recovered after an interrupted update.

* fix(runtime): retain user data when carry and recovery fail

* fix(runtime): mark staging carry start so interrupted extraction is discardable

* test: mark interrupted runtime data for recovery

* fix: make runtime data carry recoverable

* fix: the runtime update with carried user data builds again

carryUserData passed the de-duplicated Set of user paths where a List is expected after the two carry changes met; copy it into a List.

* fix: a runtime update stops instead of dropping app markers it cannot inspect, and keeps app names with line breaks

userPaths now uses the throwing exists() helper for the AppImage, menu entry and marker checks. The carry marker stores each path URL-encoded on its own line, so a name containing a newline cannot split into two paths. The symlink test now links a parent directory, which is what safePath rejects.

* fix: retain conflicting runtime recovery data on every retry

Stop on unreadable carry markers and keep rollback tests independent of add-on downloads.

---------

Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
2026-10-25 12:57:04 -04:00
Kurt Himebauch 0ad4b7cc14 feat: Back in the file manager returns to the folder you came from (#1562)
* feat: Back in the file manager returns to the folder you came from

* fix: the file manager's up arrow closes favorites first, as before
2026-10-25 12:55:11 -04:00