Compare commits

...
Author SHA1 Message Date
MaxsTechReview 914b3588e1 fix(drivers): leaving a driver bundle no longer keeps its runtime half, and the Auto display picks the latest pair (#2255)
* fix(drivers): leaving a driver bundle's display half also drops its runtime half

Picking either half of an Android + Linux bundle selects both, but picking
another display driver afterwards changed only the display. The session
kept loading the bundle's Linux driver, so switching back to DroidDeck's
drivers could still leave the session failing to start. The runtime driver
now goes back to the default when the display leaves the active bundle; a
runtime driver chosen on its own is kept.

* fix(drivers): picking the Auto display hands both halves to Auto's latest DD-Turnip pair

* fix(drivers): the Auto display pick puts the display on Auto even when no pair fits the GPU
2026-11-07 21:47:04 -05:00
MaxsTechReview 2ade269be3 fix(gamescope): direct-touch drags work again in games that confine a hidden cursor (#2220)
* gamescope: a confine is relative only while it holds the X pointer

Patch 0128 made every hidden-cursor confine relative. Games that hide the
system cursor, draw their own and confine the pointer to their window then
took the host lock, and a direct touch drag became camera motion with no
button held, so cards and items could not be dragged.

A confine now goes relative once the X pointer stays away from the seat
position while input moves, as when a game recentres it or clips it to a
small rectangle, and returns to absolute when the pointer follows again or
the confine ends. Locks and the warp follow rule are unchanged.

* gamescope: a constraint without a hint is relative only while the game holds the X pointer

Xwayland turns a hidden-cursor grab into a lock with no cursor position hint, so the confine-only rule never applied to games that draw their own cursor (Stardew Valley, Balatro). A hinted constraint is relative at once, as upstream. An unhinted lock or confine goes relative once the X pointer stops following the seat's motion, which a recentring or clipping game does and a drawn-cursor game does not.

* gamescope: a pointer constraint is relative only while the game holds the X pointer

Stardew Valley's grab with a hidden cursor becomes an Xwayland lock with a cursor position hint, which upstream made relative at once. Without relative mode that lock still follows gamescope's relative motion and touches still place the pointer, so the hold rule now decides for every constraint, hinted or not.

* gamescope: a hinted lock starts relative and is released once the X pointer follows

Releasing a hinted lock only through the hold rule let a recentring game's first touch swipe press the button. A hinted lock now starts relative as upstream and is released after 30 moving frames on which the X pointer follows the motion gamescope sends, counted from the relative motion while the seat is locked.

* gamescope: count away frames within one half-second window

* gamescope: a hint that arrives with the game's first warp starts the lock relative

* fix(input): end the touch press when a program locks the pointer mid-swipe

A game that only shows its pointer lock after its first warp took the first
swipe as an absolute drag, with the button held until the finger lifted.
The first locked move now ends the forwarded touch before sending deltas.

* gamescope: a new focus window or constraint starts its own pointer hold

A held state from one constrained window carried over to the next one
when focus moved before a reset frame. Track the input focus window and
the constraint, and restart the hold when either changes.

* gamescope: read the pointer constraint under the wlserver lock

GetCursorConstraint asserts the lock is held, so the hold tracker
aborted as soon as a program constrained the pointer.

* fix(input): a mid-swipe pointer lock ends the touch but keeps a held button

The first locked move cancelled the whole direct-touch hold, releasing a
right button held by a long press. It now cancels only the forwarded touch;
the held button is released when the finger lifts, as before.

* fix(input): extra fingers during a locked swipe stay out of the cancelled touch

Once a locked swipe has ended the forwarded touch, a second finger restarted
it with a new touch-down while the swipe carried on as relative motion. Extra
fingers are now ignored until the swipe ends; the final lift still releases a
held button.

* fix(input): a swipe that ended its touch stays closed until the finger lifts
2026-11-07 21:30:21 -05:00
MaxsTechReview 0d034b1996 fix(input): restore the rest of what #1439 removed, and keep the editor on the bottom screen (#2242)
* fix(session): close the bottom panel from the docked menu's Close again

* fix(input): keep imported screen keys on screen when the gamepad is hidden

Screen keys used to be shown on their own, whatever the gamepad did. Since
they were merged into the gamepad layout they disappeared with a controller
connected, in desktop sessions, with on-screen controls set to Never, in
Steam + QAM mode and in Steam's touch mode.

Elements now carry an always-shown flag. The screen key migration sets it on
imported keys (and once on keys merged by earlier builds), and the on-screen
controls keep flagged elements drawn while the rest of the pad is hidden,
in buttons-only mode and with the quick toggle, without duplicating them in
the unfolded strip.

* fix(dual-screen): give the profile controls panel the bottom bar with swap

* fix(dual-screen): keep the layout editor on the bottom screen

The bottom panel was recreated above an open input editor or input
settings window whenever it closed and reopened (session stop/start, or
the bottom screen briefly reported as taken), hiding the editor. The panel
now waits while one of our input windows holds its display and reopens
when the last one closes.

An app arrival recorded before one of our windows covered the bottom
screen no longer marks the screen taken, the editor picks its display only
when a display is added or removed, and it skips a bottom screen another
app holds.

* fix(input): label keyed D-pads and sticks with their keys

Screen key D-pads and sticks used to show each direction's key (W, A, S, D)
when labels were on. The unified renderer drew no direction labels and only
offered Show label for buttons and gamepad sticks, so imported labels did
nothing.

D-pads and sticks bound to directional keys now draw each bound key's name
at its direction through the shared key-label helper, and Show label is
offered for every D-pad and stick.

* fix(input): remember the layout editor's snap choice

* refactor(input): drop controller pref setters and drawer field nothing uses

* fix(input): restore the Deck tabs and right trackpad in the unfolded strip

With Deck controls, the unfolded strip used to offer the Gamepad, Grips,
Left, Right and Both tabs and put a trackpad in the right stick slot. The
unified strip only showed the gamepad with two sticks.

For Deck pads the strip bar now carries the shared Deck panel tabs and
title, the other tabs show the same stock Deck panel layouts as the second
screen, and the docked gamepad takes its stick or trackpad per side from the
Deck gamepad layout. Other pads keep the plain two-stick strip.

* fix(input): carry the Deck gamepad stick or trackpad choice into its layout

The Deck controls Gamepad tab used to let each side switch between a stick
and a trackpad. That choice is now part of the editable Deck gamepad
layout, but the saved choice was never moved over and its setter was left
without callers.

The library upgrade now applies a saved non-default choice to the Deck
gamepad layout once, only while that layout is still the stock one, and the
unused setter is removed.

* chore(i18n): drop the Deck surface switch string nothing shows

* fix(input): offer Show label only where a label can be drawn

* fix(input): Show label follows the unsaved action and diagonal-only sticks

The element properties now get the editor's draft action, so changing a
stick or D-pad to keys offers Show label before saving. A stick bound only
on its diagonals counts as labelled too.

* fix(input): only sticks count their diagonal keys for Show label

Diagonal key names are drawn on sticks only, so a D-pad bound just on its
diagonals no longer offers a label it can't show.
2026-11-07 21:04:34 -05:00
Kurt Himebauch ef46c011db fix: keep the bottom-screen menu and layout editor open when the screen turns off (#2247) 2026-11-07 20:11:20 -05:00
MaxsTechReview 5b2f7a9a6d fix(storage): a Second library on a raw /mnt/media_rw path is used again (#2245)
fix(storage): check a raw /mnt/media_rw library against its storage volume

A Second library picked as /mnt/media_rw/<volume>/... was always treated as
unplugged, because Android only reports a volume's state for its /storage
path. Sessions then ran with internal storage only and the library's games
were missing. Map the path to its /storage form before the lookup, reusing
UsbLink.storagePath, and drop the copy in SteamLibraries.
2026-11-07 19:32:13 -05:00
Kurt Himebauch 10cfc6c6b2 feat: let users turn off the low-storage warning (#2244) 2026-11-07 19:09:51 -05:00
MaxsTechReview 4948f73912 test: skip the unscrubbable session log test when file permissions are not enforced (#2241) 2026-11-07 18:37:44 -05:00
Kurt Himebauch d29a1a2dc2 ci: build and test on root self-hosted runners (#2237) 2026-11-07 17:02:19 -05:00
MaxsTechReview 8fa30b99f9 fix(input): restore the controls, panels and settings #1439 removed (#2238)
* fix(input): restore on-screen touch behaviour: slide between buttons, mouse pass-through, keyboard on release, overlay haptics, stick reach and hit areas

* fix(dual-screen): restore second-screen bars, swapped Terminal and Deck panel tabs

* fix(input): fit on-screen layouts to the real view size, cutout, Steam + QAM mode and unfolded strip

* fix(input): unfolded strip keeps press haptics

* fix(input): restore layout import/export, element editor rows and layout tools

Bring back .icp layout import and export on the saved layouts page, the
per-element image, opacity and label controls, a uniform size slider and
reset positions in the layout editor, and non-destructive Steam, QAM and
keyboard button toggles stored on the layout style. Remove strings left
unused by the input profile unification.

* fix(input): keep hit slack to gamepad-shaped buttons and fit touch tests to saved layout geometry

* fix(input): button size regenerates the gamepad and scales spacing

A generated gamepad now stores its button size and is regenerated at that
size, so spacing follows the radius and the layout keeps regenerating on
rotation. Edited layouts scale positions from their anchor edge or centre
so gaps grow and shrink with the controls.

* fix(input): button size rescales moved gamepads around the generator

Gamepad layouts that no longer match the generated positions exactly were
scaled element by element from screen thirds, which split the face buttons
and D-pad across anchors. Gamepad controls now take the generator's
positions at the new size plus each control's own offset from the
generated position, so groups keep their shape and spacing.
2026-11-07 16:36:23 -05:00
MaxsTechReview a33954b8bd perf: answer Steam's missing-file checks on SD and USB libraries from the directory cache (#2212)
* perf: answer Steam's missing-file checks on FUSE libraries from the directory cache

Before creating and committing each file, the Steam client checks whether it
already exists under its exact, lower-cased and folded spellings. On an SD or
USB library reached through Android's FUSE layer every one of those misses is a
round trip to MediaProvider (Brawlhalla: about 65,000 misses, 92 of the 161 s
of preallocation and 60 of the 93 s of commit).

The client already caches the listings of its steamapps directories on FUSE and
keeps them exact through its own changes, inotify and a short TTL. access() now
answers a name that is absent from a live listing of its directory without
asking the filesystem. Directory paths match case-insensitively only when the
library's filesystem folds case, and names compare case-insensitively, so an
existing file is never reported missing.

* fix: reuse a listing for a differently cased folder only when the whole path folds case

Checking that steamapps alone folds case let a listing of one folder answer
for a different folder below it whose name differs only in case. The fill
now compares the listed folder with its fully case-swapped path below
steamapps, so a listing is reused across case only when the filesystem
folds every level.

* fix(dircache): match a folded listing only below the same prefix and skip it during a tracked change

* fix(linuxfs): a folder renamed or removed above a cached listing drops that listing

* fix(linuxfs): watch the folders above a cached listing so a rename or removal by any process drops it

* fix(linuxfs): also watch the folders above steamapps, and stop a stuck probe from hanging the tests

* fix(linuxfs): a listing under a symlink or an unwatchable folder no longer answers absent lookups

* fix(linuxfs): a symlink check that cannot be watched also stops absent answers

* fix(linuxfs): a differently cased lookup reuses a listing only for the same folder
2026-11-07 16:36:15 -05:00
Kurt Himebauch 3526309f88 feat(session): setting to choose the Android keyboard for game text fields (#2236) 2026-11-07 16:19:40 -05:00
MaxsTechReview be0fe35271 fix(proot): let the fast path fall back when Android 10 refuses statx (#2224) 2026-11-07 15:48:44 -05:00
MaxsTechReview 7f0427b830 fix(input): L2/R2 keys stop overriding analog trigger axes (#2205)
fix(input): stop L2/R2 keys from overriding analog trigger axes

Pads such as the GameSir X2 Pro send KEYCODE_BUTTON_L2/R2 alongside
their analog trigger axes. Each key-down and every key repeat set the
trigger to full, and the next motion event set it back to the axis
value, so a held trigger jumped to full. Android repeats only the most
recently pressed key, so with both triggers held one side kept being
forced to full while the other tracked its axis.

When a device reports an analog axis for a trigger side (LTRIGGER or
BRAKE, RTRIGGER or GAS, or the wired Xbox Z/RZ layout), its key no
longer drives that trigger. Key repeats never change a trigger value.
Pads with digital-only triggers keep the key behaviour unchanged.
2026-11-07 15:48:41 -05:00
MaxsTechReview 2476c824f6 fix(dual-screen): docked handhelds switch to single-screen mode (#2232)
* fix(dual-screen): docked handhelds switch to single-screen mode

With Dual screen on Auto, a dual-screen handheld connected to a wired
external display (dock, HDMI or USB-C monitor) no longer puts the menu,
tabs or session controls on the built-in bottom screen, which nobody can
see while it sits in a dock. The launcher and sessions use the single-screen
layout on the main screen, which the external display mirrors. A bottom
panel opened automatically closes once when the dock is connected and comes
back after undocking.

XR glasses, virtual and overlay displays and switched-off displays do not
count as a dock, and Dual screen set to On keeps the bottom screen.

* fix(dual-screen): only an automatic bottom panel closes on docking, and glasses are told apart from the dock display

* fix(dual-screen): keep the automatic-panel state across activity recreation and reconcile docking before settling the panel
2026-11-07 15:48:17 -05:00
MaxsTechReview d74575ed7f fix(components): DXVK Auto on Adreno 6xx keeps DroidDeck-Proton on DXVK-gplasync 2.7.1 (#2226)
* fix(components): DXVK Auto on Adreno 6xx keeps DroidDeck-Proton on DXVK-gplasync 2.7.1

* fix(components): DXVK Auto says so when the pinned DXVK could not be installed

* fix(components): a DXVK the player picked is shown as their choice, not a failed pinned install
2026-11-07 15:48:14 -05:00
MaxsTechReview e4d4096342 fix(drivers): a driver repo that fails to answer no longer changes the Auto pick (#2233)
* fix(drivers): a driver repo that fails to answer no longer changes the Auto pick

The release check reads every driver repo and saved the list even when one
of them failed, so that repo's drivers vanished until the next good check.
When GitHub rate-limited or dropped the DroidDeck driver repo, Auto on an
Adreno 6xx saw no DD-Turnip release, switched to Banner's Turnip, and then
switched back after the next clean check.

A repo that fails now keeps its releases from the last good check, while
still being recorded as failed. A repo that answers and no longer lists a
driver still drops it.

* test(drivers): carry-over is checked through refresh with one repo failing

* fix(drivers): drop a repo's partial results when its answer fails and keep the refresh test offline
2026-11-07 15:48:09 -05:00
KeegantheVeganat0r 11218f0b24 perf(hud): reduce Strip rendering cost with bitmap graph and logo (#2235)
perf(hud): rasterize Strip graph and logo separately

Replace PR2194's whole-View software layer with a hardware-accelerated
Strip that rasterizes its graph and logo into small reusable bitmaps.
Keep the 66 ms graph invalidation cap, clear stale graph pixels, and
refresh cached logo colours when the theme changes.

Include the comparison method and six sanitized benchmark samples.
With matched Steam navigation, the hybrid averaged 12.2% less UI-thread
CPU and 34.0% lower draw p95 than the PR2194 software path. App-process
CPU averaged 5.9% lower in this run; whole-device and battery savings
are not established. Current-build physical flicker confirmation is
recorded separately from timing validation.

Validation: all 12 DroidDeckHudPanelTest checks passed; comparison APK
built and six alternating device samples completed with matched input
counts, no focus loss, thermal status 0, and no frame metric report drops.
2026-11-07 15:41:46 -05:00
Kurt Himebauch d9bf6d8a17 feat: move UBWC compatibility from Components to Settings (#2008)
* feat: move UBWC compatibility from Components to Settings

* fix: update driver menu tests for the moved UBWC restart check

* fix: load saved UBWC mode at startup and block Settings restart during runtime operations
2026-11-07 15:39:55 -05:00
MaxsTechReview 041c3b1b55 ci: run compile jobs on the org workstation runner when it is idle (#2234)
* ci: run compile jobs on the org workstation runner when it is idle

* ci: pick the compile runner right before each heavy job

* ci: cover the runner picks in the job dependency test
2026-11-07 15:00:39 -05:00
Kurt Himebauch e5434117f2 fix(input): controller navigation works across the Controller tab and profile menus (#2231)
* fix(input): controller navigation works across the Controller tab and profile menus

* fix(input): dialog choice rows expose their checked state
2026-11-07 14:37:00 -05:00
MaxsTechReview b474cba79e fix(dual-screen): bring back the trackpad and Deck panels on the second screen (#2229)
Keyboard + trackpad and Deck grips + trackpads are second-screen panels again, rendering the stock layouts without changing the input profile. The last panel is remembered separately for desktop and game sessions; desktop sessions start on the trackpad. A Deck panel without the Deck pad falls back to the default. Stock panels left Steam blue return to the neutral look once.
2026-11-07 14:36:30 -05:00
Kurt Himebauch ffb76240fd fix(fex): show which preset Use Default resolves to (#2225)
* fix(fex): show which preset Use Default resolves to and rename FEX defaults to Stock FEX

* test(components): the FEX preset chip names its resolved preset instead of Use Default

* fix(fex): the game settings menu keeps Use Default beside the inherited preset

* fix(fex): inherited preset chip follows the current Default preset
2026-11-07 14:22:00 -05:00
MaxsTechReview 875f8e3ce2 feat(wow): download addons from WoWInterface and open CurseForge in the browser (#2219) 2026-11-07 13:09:21 -05:00
MaxsTechReview 5446497301 fix: uninstalling a Flatpak app that Flatpak no longer has removes its folder (#2216) 2026-11-07 12:46:52 -05:00
MaxsTechReviewandKurt Himebauch 98a670fb81 fix(input): classic on-screen gamepad look, quick hide toggle and adaptive sticks switch (#2215)
* fix(input): bring back the classic on-screen gamepad look, quick hide toggle and adaptive sticks switch

The unified input surfaces (#1439) drew the built-in gamepad as large
rounded boxes labelled with control names, swapped the compact keyboard
button for a wide pill, dropped the bottom-left show/hide toggle and left
the Adaptive sticks setting with no effect once a layout was migrated.

Gamepad elements are round again with shoulder pills, D-pad arrows, the
old glyph labels and the tint-derived fill and outline. The keyboard is a
small round button beside a restored quick show/hide toggle. Stick touch
areas reach 25% further outward while keeping their inner edge, and the
session menu gets an Adaptive sticks switch that writes the active
layout's sticks. Libraries saved by the first unified build are restyled
once without touching user edits or other layouts.

* fix(input): overlay keyboard opens on the main screen and the D-pad matches the face buttons

A keyboard command now carries the role of the surface that sent it, so the
overlay's keyboard button opens the main screen keyboard as before the
unified surfaces, while lower-screen and controller bindings still use a
second screen. The D-pad buttons take the face buttons' size and spacing,
and an untouched saved D-pad is resized around its own centre. Classic
outlines sit where the old ones did and shoulder labels use their old size.

* fix(input): keep edited D-pads and keyboard buttons, harden the quick and adaptive toggles

Resize a saved D-pad only while its four buttons keep their original geometry, and
replace the old keyboard button only while its name and shape are untouched.
The quick toggle needs its own press before a release toggles it, and a cancelled
touch does nothing. A rejected library write no longer crashes the session or
changes the controller preference, and the Adaptive sticks switch shows whenever
the built-in gamepad is the surface in use.

* fix(input): show the adaptive sticks switch while the unfolded controls are up

* fix(input): run the gamepad restyle as library version 10 and draw the quick toggle in Gray

* fix(input): show the adaptive sticks switch whenever the built-in gamepad is the surface in use

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-11-07 12:42:45 -05:00
MaxsTechReview a123af7c13 fix(script-run): run x86 programs a native script starts through FEX (#2217) 2026-11-07 12:27:24 -05:00
MaxsTechReview 6026d72435 fix(input): restore the neutral look of the bottom-screen controls (#2221) 2026-11-07 12:06:03 -05:00
MaxsTechReview d5f3d1a0d5 fix(proton-run): follow a removed DroidDeck-Proton build to the newest one (#2223)
Battle.net, store games and desktop programs start through droiddeck-proton-run
without the Steam client. When the Proton auto-update replaced the DroidDeck-Proton
build that Steam's default or a title's mapping named, the name no longer matched
an installed tool and the run fell back to Valve's Proton Experimental ARM64, which
lacks DroidDeck-Proton's patches. World of Warcraft then hung at launch.

A removed DroidDeck-Proton build now resolves to the newest installed one, the same
successor steam-compatibility moves Steam's mappings to.
2026-11-07 11:37:17 -05:00
MaxsTechReview 4cb3a89483 fix: library scan and download cancel crash on Android 10 (#2204)
* fix: library scan and download cancel crash on Android 10

The storage scan read StorageVolume.getDirectory() for every volume to
build its key, but that method only exists from Android 11, so on
Android 10 the steam-library-scan thread died with NoSuchMethodError.
The directory is now read once behind the existing Android 11 check and
reused for both the key and the candidate path; older versions fall
back to the uuid, description and /storage scan as before.

Cancelling a download closes the response stream on a background
thread. The platform okhttp can throw IllegalStateException
("Unbalanced enter/exit") from close() while a read is still in
flight, which was uncaught and killed the app. That close now ignores
IllegalStateException as well as IOException; other failures still
surface.

Fixes #2200

* test: wait for the cancel task to finish instead of polling its thread state

The cancel-close tests decided the close task was done from the worker's
thread state, which can read WAITING while the pool is still cleaning up
after a thrown exception. A close failure that escapes ends its worker
thread, and Java runs the uncaught handler before the thread ends, so the
surfacing test now joins that thread. The suppression test hands the
cancel executor more work until it runs on the same thread, which only
happens once the close task has returned without throwing.

* test: watch the close task finish instead of probing which worker runs next

A cached pool can start a second worker, so a probe task may never land
on the thread that ran the close. The suppression test now waits until
the pool has no task running and that thread is back to waiting for
work, a state a worker that is exiting after a thrown exception never
enters.
2026-11-07 11:16:08 -05:00
Kurt Himebauch 22a675a664 Unify controller and screen input profiles for game and desktop sessions (#1439)
## Problem and intent

Desktop sessions need configurable mouse, keyboard and application
actions from a controller. On dual-screen devices, a lower-screen
trackpad may already handle the pointer, leaving the physical controls
available for other assignments. This PR gives physical controllers and
local touch controls a shared profile/action model instead of separate
configuration paths.

This follows the centralization direction discussed in
[#1202](https://github.com/Droid-Deck/DroidDeck/pull/1202#issuecomment-6299647180)
and addresses the controller-input portion of #860 and #1356. Those
discussions are context, **not prior maintainer approval** of this
implementation. I would appreciate architectural feedback and guidance
on splitting or coordinating this work if that would make review easier.

## Profiles and configuration

- Controller stays within the existing Setup tabs. Game and Desktop each
have a default-profile selector, followed by one profile list and device
settings.
- Named profiles reference three independently reusable parts: Physical
controller, Main screen and Second screen. Layout geometry and action
bindings are stored separately. Profiles can share physical controls
while assigning different touch actions.
- The session menu selects a complete profile, or expands to mix the
three parts. A mixed selection can be saved as a new profile; Game and
Desktop retain separate selections.
- Both screens use the same full-size editor, with add, move, resize,
snapping, undo and shared properties. The main-screen editor has
movable, collapsible tools and is transparent above a running session.
When editing the secondary display, its entire area is the canvas and
the main display hosts tools/properties.
- The old fixed gamepad, fixed Deck controls and separate Screen keys
editor are replaced with editable presets and the shared renderer.
Existing positions, bindings, shapes, symbols, imported images and
toggle behavior migrate; legacy Screen keys become additional profiles
without replacing the user's selected profile. Migration retains a
backup before removing old preferences.
- Saved layouts have thumbnails and screen targeting. Actions include
compatible native output/remapping, keys/combinations, mouse buttons,
pointer movement, vertical/horizontal scrolling, directional keys,
existing menu/keyboard commands, and disabled. Buttons can use symbols,
shapes and toggle behavior.

## Architecture and latency


```mermaid
flowchart LR
    Library[Profiles + reusable layouts and bindings] --> Router[InputRouter]
    Physical[Physical controller adapters] --> Router
    Touch[Main + secondary InputSurfaceView] --> Sources[Owned pad / Deck sources]
    Sources --> Router
    BLE[Steam Controller BLE adapter] --> Router
    Router --> Native[Existing controller publication]
    Native --> Steam[Steam Input / guest controller]
    Router --> Held[Shared key and mouse ownership]
    Held --> Guest[Existing guest input outputs]
    Router --> UI[Existing menu / keyboard hosts]
```

Bindings resolve when the profile changes, outside event dispatch.
Native forwarding has no additional frame/timer queue; identity routes
reuse existing state/reports. Only synthesized continuous stick
pointer/scroll motion uses a frame callback. One source releasing a
key/button does not release another source's hold. Menu, profile,
suspension and removal transitions release old outputs and gate held
controls until neutral.

Steam continues to own game-specific mappings and action sets. Steam
Mobile Touch and VR retain their specialized paths. Raw device adapters
remain responsible for transport/calibration and must join routing
before publishing consumed controls; this does not claim that every
adapter has been unified.

Historical Thor measurements on the native Game path found
kernel-event-to-guest-observer upper-bound medians of **3.061 ms
baseline versus 2.999–3.403 ms candidate**, with a subsequent
installed-APK check at **3.039 ms / 300 matched edges**. These are
relative software-path measurements, not button-to-photon latency. Some
tails increased, simultaneous BLE contention remains unmeasured, and
**this UI revision has not had a new latency measurement**. The previous
cursor fix changed visual update intervals from roughly 33.3 ms to 16.7
ms without changing sensitivity or adding smoothing.

[Full design, migration and
boundaries](https://github.com/Delete228/DroidDeck/blob/codex/unified-controller-input/docs/development/unified-input-proposal.md).

## Validation

Updated through upstream 542bbf89c; current PR head 08526f828. The
latest CodeRabbit review generated no actionable comments.

- **1,017 tests passed in 133 suites after the latest upstream merge**,
with no failures, errors or skips: all input/UI tests, global reset,
component updates, PiP policy, runtime auto-update and Proton menu
auto-selection. The pre-merge review-fix run passed **967
input/UI/global-reset tests**. The subsequent second-screen refresh fix
passed **35 focused surface/presentation tests**, including three new
state-preservation regressions.
- Full local run before the final review follow-ups: **3,598 tests,
3,573 passed, 25 failed**. The 25 are unchanged upstream macOS
limitations: log-export tests requiring SecureDirectoryStream (24) and a
case-sensitive WoW add-on test (1). The global-reset regression also
passes. Earlier disk-exhausted attempts were discarded; the completed
run bounded temporary resources with one worker and JVM recycling,
without excluding tests or changing repository test configuration.
- APK build, runtime/session assets, native dependencies, alignment and
v1/v2/v3 signatures passed. Installed over the separate Input Test app
on **AYN Thor** with its environment retained. Local packaging omits
Rust store engines; the [full Build APK workflow for 08526f828
passed](https://github.com/Droid-Deck/DroidDeck/actions/runs/42295403480),
including Android/JVM, Python/native, Rust store-engine and Xbox checks.
- Review follow-ups keep migration IDs stable, preserve unreadable
supported-version libraries before recovery, retain forward-version
protection, release keyboard owners on focus loss, handle unavailable
presentation displays, and allow controller confirmation/cancellation
after key capture. Deck router cleanup checks ownership; BLE teardown
synchronously blocks late routing/publication, and shared Triton slot
ownership protects a replacement controller from stale instances. Legacy
Select-to-Steam migrates to Desktop without replacing saved bindings;
mouse backend transitions are serialized with publication. Profile
refresh now updates custom controls in place, preserving
terminal/dashboard state, the field keyboard and WoW chat. No physical
BLE-controller test or new latency measurement was performed.
- The latest 08526f828 APK is installed on Thor. Profile preferences
stayed byte-for-byte identical and the retained Steam environment
reached READY at 1280×720 / 60 Hz. The earlier capture-dialog fix was
also exercised on Thor. APK SHA-256:
`ed6ef888648468b4ebeb94e8f1ae74a195bb1f0a3f27e4fb3646d5462a4beb6b`.
- Upstream integration preserves the new WoW chat panel,
platform-pointer and PiP fixes. The fast-double-tap stick-click rule and
its six regressions were adapted to the shared renderer; WoW field
selection also preserves Ctrl held by a profile.
- Latest Thor UI pass: existing Setup navigation, profile/layout
previews, action persistence, centered element creation, main-screen
editing over Steam, secondary canvas with primary tools/properties, and
X returning from the docked menu to controls.
- Across the documented device sessions, the user tested native physical
buttons/sticks/triggers, Deck pads/grips, desktop
pointer/clicks/scrolling/arrows, Android keyboard with physical input,
Ctrl+F and Ctrl+A/X/V/S, simultaneous physical/touch holds, menu release
and Home/resume. These historical checks are distinguished from the
latest UI-only pass.

[Revision-specific results, APK hashes and
limitations](https://github.com/Delete228/DroidDeck/blob/codex/unified-controller-input/docs/development/unified-input-validation.md).

## Coordination and remaining work

- #1202 (jmarti326): shortcut capture/device identity and the
shared-assignment discussion informed this work.
- #1305 (kingokksa): shared PC-key definitions were adapted from that
PR; this does not absorb its entire feature set.
- #1414, #581, #1226 and #1150 remain relevant for external-device
input, calibration, keyboard routing and DualSense integration. The
DualSense coordination discussion in this PR describes adapter
boundaries and duplicate-stream suppression.

Import/export, automatic per-app selection, macros, multi-button
activation chords and automatic text-field keyboard detection remain out
of scope. A single binding emitting a key combination is implemented.

The configuration UI is now implemented and exercised on Thor. I am
happy to continue UX/accessibility/localization refinements here before
merge, or in follow-up PRs, according to maintainer feedback. The main
review questions remain the shared model, source ownership/lifecycle
contract, migration policy and integration with other input work.


## UI walkthrough on AYN Thor

Actual captures from the installed Input Test build. In paired images,
the main display is above the second display. The test application name
and local build badge are not production branding changes.

<details>
<summary>Controller settings: separate defaults for Desktop and Game,
followed by the profile library.</summary>

<img width="960" alt="Controller settings: separate defaults for Desktop
and Game, followed by the profile library."
src="https://github.com/user-attachments/assets/a4c156d6-9ded-43df-b0f8-e5f34028806d"
/>

</details>

<details>
<summary>Physical controller: bindings grouped by control type, with
each current action visible.</summary>

<img width="960" alt="Physical controller: bindings grouped by control
type, with each current action visible."
src="https://github.com/user-attachments/assets/a8a840e3-19ac-473f-9f55-b06ae9c637b5"
/>

</details>

<details>
<summary>Reusable layouts: bounded previews and separate screen
targets.</summary>

<img width="960" alt="Reusable layouts: bounded previews and separate
screen targets."
src="https://github.com/user-attachments/assets/cf0cf7d1-42f3-46cd-b50b-f01e1fa8ed41"
/>

</details>

<details>
<summary>Main-screen editor over the live Steam UI, with the movable
tools expanded. The background is Steam artwork, not a running
game.</summary>

<img width="960" alt="Main-screen editor over the live Steam UI, with
the movable tools expanded. The background is Steam artwork, not a
running game."
src="https://github.com/user-attachments/assets/0781f7c8-fd10-47e5-8a23-817f945a6289"
/>

</details>

<details>
<summary>Dual-screen editor: tools and selection on the main display,
full-size placement canvas on the second display.</summary>

<img width="720" alt="Dual-screen editor: tools and selection on the
main display, full-size placement canvas on the second display."
src="https://github.com/user-attachments/assets/f13d4ef6-be6d-4d95-9d42-e047e0a63b8a"
/>

</details>

<details>
<summary>The selected lower-screen trackpad stays visible while its
properties open on the main display.</summary>

<img width="720" alt="The selected lower-screen trackpad stays visible
while its properties open on the main display."
src="https://github.com/user-attachments/assets/26baf597-f037-419e-9601-e48f086c0d4f"
/>

</details>

<details>
<summary>Session menu: expand Profile to select the physical,
main-screen and second-screen parts independently.</summary>

<img width="720" alt="Session menu: expand Profile to select the
physical, main-screen and second-screen parts independently."
src="https://github.com/user-attachments/assets/27ac4d9c-68b3-4257-8353-23104e2343de"
/>

</details>
2026-11-07 08:32:50 -05:00
MaxsTechReview ca6c409436 fix(drivers): built-in Auto picks match the published table on Adreno 6xx and 8xx (#2151) 2026-11-07 08:25:08 -05:00
Jordan Y 7c429f3a26 fix: droiddeckctl pacing reads timestats dumps with Windows line ends (#2186) 2026-11-07 08:23:06 -05:00
Kurt Himebauch aba55031c3 perf: answer chown and bound thermal paths through the proot fast path (#2203) 2026-11-07 01:08:06 -05:00
MaxsTechReview 18c7d56409 fix: Auto components update only DroidDeck-Proton and delete old builds (#2193) 2026-11-06 20:43:44 -05:00
MaxsTechReview 0b4bc827e7 fix(battlenet): stop Battle.net's pages playing videos on the CPU (#2195) 2026-11-06 20:39:37 -05:00
KeegantheVeganat0randKurt Himebauch a552f1e293 fix(hud): isolate strip rendering in a software layer (#2194)
* fix(hud): skip relayout when size is unchanged and cap graph redraws

* fix(hud): limit the redraw and relayout changes to the strip

* fix(hud): draw the strip background from a cached bitmap like the panels

* fix(hud): drop the strip-only redraw cap and relayout skip

* fix(hud): the strip graph redraws at most 15 times a second

* fix(hud): isolate strip rendering in a software layer

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-11-06 20:23:10 -05:00
MaxsTechReview 066834d17d feat: compress idle game memory before low-RAM devices run out (#2187) 2026-11-06 19:49:06 -05:00
Delete228 aff9738748 Preserve second-screen choices across partial selections and migration retries
Track explicit whole-profile choices, including Auto, separately from inherited profiles so physical and main-screen overrides do not suppress legacy panel migration. Persist this distinction in library version 8 and read older selection records compatibly.

Keep saved Deck panels during unavailable-controller fallbacks and retain legacy preferences when migration fails. Add regressions for all three review findings, explicit Auto selection and older saved libraries.

Validation: all 992 input/UI tests passed.
2026-11-07 02:44:47 +03:00
Kurt Himebauch f33d5b9e09 fix(components): auto updates follow DroidDeck-Proton only (#2190) 2026-11-06 18:41:37 -05:00
Delete228 281c2aea1b Preserve custom profiles when opening second-screen fallbacks
Only migrate stock controls for explicitly saved legacy panel modes. Automatic fallbacks retain the active profile layout and bindings without persisting an override.

Add session regressions for automatic fallbacks, unavailable panels, explicit legacy migration and existing quick selections. Validation: all 986 input/UI tests passed.
2026-11-07 02:29:31 +03:00
Delete228 c2d7765619 Fix desktop profile selection and simplify default touch layouts 2026-11-07 02:10:26 +03:00
MaxsTechReview 250f80fbb5 fix: stop switching between display modes with the same refresh rate (S26 lag and heat since 0.6.7) (#2192)
fix: stop switching between display modes with the same refresh rate
2026-11-06 17:18:24 -05:00
Delete228 08526f8283 Preserve second-screen state when refreshing input profiles 2026-11-07 00:16:45 +03:00
Delete228 f44826ae3a Merge main and retain unified input behavior 2026-11-07 00:01:20 +03:00
Delete228 31b8adbb55 Guard shared input slots and mouse backend transitions 2026-11-06 23:56:30 +03:00
MaxsTechReview 542bbf89c3 fix: launch Battle.net and store games from any game page tab (#2185)
* fix(library): Keep one Play request per game and show Starting while it prepares

* Track launch preparation per game

* Keep each game preparing until its own worker finishes

* Drop store lookups that finish after the activity ends
2026-11-06 15:31:09 -05:00
Kurt Himebauch e1987ca785 chore: pin gamescope to the DroidDeck-Components p22 release (#2183) 2026-11-06 14:35:10 -05:00
MaxsTechReview a81da1e476 feat(components): Auto updates for FEX, DXVK and VKD3D-Proton (#2156)
* feat(components): Auto updates for FEX, DXVK and VKD3D-Proton

Each component gets an Auto or Manual mode on the Default Components page.
Auto installs the newest DroidDeck-Components nightly into every Proton that
ships the component, matching each Proton's ARM64EC or x64 flavour, keeps the
current and replaced builds, and removes older builds it downloaded.

A game that ran on the previous build and exits within 30 seconds twice on the
new one rolls the line back; the failed build is skipped until a newer one is
published. Existing manual choices keep that component on Manual, and DXVK
stays on Manual while a GPU rule pins its version.

* fix(components): Auto notes per line, Original label and first-update rollback

Boot history on the Original is recorded before the first update so that
update can roll back, rollback notes stay with their own component, the
Original shows by name, and new Protons get the current build even when no
newer one is published.

* fix(components): Auto owns only its own downloads and follows in-place Proton updates

Auto deletes only builds it downloaded, re-swaps a Proton whose version
changed under an existing choice, reports a missing nightly listing as a
failed check, writes its state atomically so the page can read it without
waiting on a download, and words the rollback and download messages to
match what it does.

* fix(components): Auto keeps a rollback and reads updated Protons' own files

After a rollback only a build newer than every rejected one is taken, so an
older build still listed in the nightly cannot replace the restored one.
Flavour detection treats a choice saved for an older Proton version as not
applied, so a Proton Steam updated in place is read from its own files and
re-swapped. A failed state rename falls back to a direct write and is logged.

* fix(components): Keep the last good Auto state when a save fails

* fix(components): Serialise Auto state reads and writes

* Install the Linux runtime by itself on first launch

The runtime auto-update skipped devices with no runtime, so FEX, Proton,
DXVK and VKD3D waited for the first Steam launch to install it. Claim a
first install when the device never had a runtime, on a usable network,
outside battery saver and with a supported GPU, and check Proton as
soon as the runtime is ready. A runtime the user removed stays removed.

* fix(components): Check components as soon as Proton is installed

The Auto check stamped its hourly timer even when no Proton was
installed yet, so a fresh device waited an hour after Proton arrived.
Skip the stamp until a Proton exists and run the check right after a
Proton install finishes.

* fix(components): Apply stored builds on every check and retry unfinished rollbacks

A held or failed download no longer skips applying the stored build or
stamps the hourly timer. Stored builds are applied on every check, so a
new Proton is reconciled inside the hour, and a check requested while
one runs is queued. A rollback that could not restore or swap a Proton
is retried and reported only once it finishes. Removing the runtime
marks it as seen so it is not installed again, and the failed-save test
skips where the process can still write read-only files.

* fix(components): Keep rollback restores pending until applied and save state on failure

* fix(components): Hold newer builds until a pending rollback is applied

* fix(components): Drop pending restores for removed Protons

* fix(components): Never auto-install a non-ARM64X build over a Proton's own ARM64X DXVK or VKD3D

* fix(components): Read probe archives outside the lock and block auto updates while the original build is unknown

* fix(components): Require ARM64X when any Proton on the line needs it, and before each swap
2026-11-06 13:56:34 -05:00
MaxsTechReview 116728917f fix(input): Keep the Android pointer hidden over the game after hovering session menus (#2176)
* fix(input): Keep the Android pointer hidden over the game after hovering session menus

* fix(input): Defer the overlay pointer icon from a container, since ComposeView is final
2026-11-06 13:56:30 -05:00
MaxsTechReview 5f180d909b fix(input): Click a touch stick only on a fast double tap (#2173)
* fix(input): Click a touch stick only on a fast double tap

* fix(input): Count the release position in stick tap travel

* fix(input): Treat cancelled pointer-ups as cancelled stick touches
2026-11-06 13:56:25 -05:00
Kurt Himebauch 88acdbc3ab ci: publish gamescope builds to DroidDeck-Components and never replace a tag (#2181) 2026-11-06 13:36:56 -05:00
Kurt Himebauch 42b2810822 ci: retry PR test build cleanup and sweep leftovers every six hours (#2180) 2026-11-06 13:36:52 -05:00
Kurt Himebauch 1001bc00af fix: PiP scales the full-size output instead of shrinking it (#2174)
* fix: PiP scales the full-size output instead of shrinking it

* fix: hide the performance HUD as PiP starts so it does not flash at full size
2026-11-06 13:24:37 -05:00
MaxsTechReview 69c6c14f3f fix(wow): Clear the game field selection after reading and sending chat (#2175)
* fix(wow): Clear the game field selection after reading and sending chat

* fix(wow): Skip the field cleanup when the chat request has closed
2026-11-06 13:17:13 -05:00
MaxsTechReview 6060f2288f feat(wow): voice type and keyboard chat bar for World of Warcraft (#2171)
* Add voice and keyboard chat input for World of Warcraft

Open a chat bar with an editable text field when a WoW client focuses
a text box (reported by Proton's winex11 IME context hook through
agent/wine-ime.json). First use asks for Voice type or Android keyboard
with a remember toggle; the choice is also in the side menu's Keyboard
group while a WoW client runs. Voice type uses the device's speech
recognition service. On dual-screen devices the bar and keyboard open
on the second screen. Send types the text and presses Enter; Cancel
presses Escape.

* Cover the Arm64 WoW client in the chat match test

* Give the second-screen chat bar the session's result and back owners

* Open the WoW chat bar with the game field's current text

When the bar opens, select and copy the game's text field and fill the
bar with it through the session clipboard bridge, then put the guest
clipboard back. Send selects the field first, so the bar's text
replaces what was there instead of being appended.

* Keep WoW chat consistent across screen changes and failed field reads

Close the bar when the game field closes even after the program name
is cleared, move the bar to the top screen when the second screen drops
it, and drop any pending second-screen keyboard request when the bar
opens. A field read that gets no copy of its own leaves the game text
unselected and Send then types without replacing it. Ctrl is only
pressed and released when nothing else holds it, and a pending read
ends when the clipboard bridge stops.

* fix(session): Close WoW chat for other programs and keep the game cursor on cancelled reads
2026-11-06 12:17:37 -05:00
Delete228 915f373d82 Harden input migration and session lifecycle
Keep migration binding IDs stable, preserve unreadable libraries before recovery, and retain forward-version protection. Release keyboard owners on focus loss, handle unavailable settings displays, and allow controller confirmation or cancellation after physical key capture. Stop BLE routing and publication synchronously before asynchronous disconnect. Add regression coverage and record local and Thor validation.
2026-11-06 19:53:40 +03:00
Delete228 7b7722bd25 Preserve replacement session Deck input routing 2026-11-06 19:25:41 +03:00
Delete228 63004567fd Unify configurable control surfaces and profile management 2026-11-06 19:11:58 +03:00
Delete228 aa3757eff1 Merge remote-tracking branch 'origin/main' into codex/unified-controller-input 2026-11-06 18:50:33 +03:00
William Allenandmaxjivi05 a1bc8467fd feat: show Steam launch options on the Launch tab (#2163)
* Show Steam launch options on the Launch tab

* fix: Launch options is one row on the Launch tab, like the shortcut fields

* fix(launch): reread Steam's launch options when the game page resumes

* fix(launch): keep launch options disabled while they reload on resume

* fix(launch): a saved launch option is not replaced by a read that started before it

* fix(launch): an unreadable Steam config keeps the shown launch options instead of clearing them

* fix(launch): an unparsable Steam config and a pending reload keep the shown launch options

* fix(launch): the resume reload follows the game the panel shows

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-11-06 10:43:44 -05:00
Kurt Himebauch d6566f0dbe feat: GOG achievements on by default, drop stores engine row (#2168) 2026-11-06 10:22:42 -05:00
Delete228 ba5876bf6d Merge latest upstream main before Controller UI integration
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/SessionActivity.kt
#	app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt
#	app/src/main/java/com/droiddeck/launcher/input/PadBridge.java
#	app/src/main/java/com/droiddeck/launcher/ui/DeckControlsPanel.kt
#	app/src/test/java/com/droiddeck/launcher/input/PadBridgeQamTest.kt
2026-11-06 14:37:50 +03:00
Kurt Himebauch bf693d7f71 fix: Auto picks the upstream RADV pair on Xclipse 950 and 960 (#2158)
* fix: Auto picks the upstream RADV pair on Xclipse 950 and 960

* fix: Auto reads the Xclipse model after the Vulkan query

* fix: Auto treats an unnamed Xclipse like the 950 and 960

* fix: Auto reads any three digit Xclipse model number
2026-11-05 23:06:06 -05:00
Kurt Himebauch 4cbd7722f8 feat: the PowerVR runtime is a downloaded add-on pack, not bundled in the APK (#2157)
* feat: the PowerVR runtime is a downloaded add-on pack, not bundled in the APK

* fix: a stopped session cancels the PowerVR pack download

* fix: a session stopped during runtime unpacking ends before staging

* fix: a failed PowerVR pack download ends the session with a message
2026-11-05 22:58:10 -05:00
+1 3733c50902 feat: non-Adreno graphics stack (Venus, open driver pairs, gamescope patches) (#2029)
* Retain compositor resources when GPU completion is unconfirmed

* Stop quarantined GPU callers before retries and mapped writes

* Support Android hardware-buffer presentation over Wayland

* Support PowerVR swapchains and linear image imports in Gamescope

* Bundle verified PowerVR graphics components through existing build preparation

* Detect and select the system Vulkan backend on PowerVR

* Apply PowerVR Steam and game compatibility during normal launches

* Show PowerVR support through existing driver and component controls

* Update existing hardware guidance for system Vulkan support

* Import the shared system Vulkan readiness check

* Discover and propagate the actual PowerVR render node

* Prune obsolete PowerVR runtimes after guest processes exit

* Retain external DMA-BUF ownership after GPU quarantine

* Name the PowerVR HAL selection explicitly

* Isolate component UI tests from application startup

* Cover the inherited FEX preset in PowerVR profile tests

* Log test worker failures for CI diagnosis

* Remove temporary test logging after diagnostic CI

* Prefer system dependencies for the Android Vulkan loader

* Capture raw stderr before test worker termination

* Remove the temporary test worker crash probe

* Import pinned DXVK recipe prerequisite from PowerVR PR570

Copy only the standalone build recipe, source pin, BC-format prerequisite
and presentation-sync patch/check from wallentx/DroidDeck at d0f7264ea8.
Source: https://github.com/Droid-Deck/DroidDeck/pull/570
Preserve that proposal attribution; no Android app, compositor, managed
pack, private data or fork development history is imported.

* Add opt-in low32 GPU mapping adapter and integrity probes

* Add opt-in D3D9 single-sample workaround and fix HUD texel format

* Document verified production game workaround and publication scope

* Restrict reset diagnostics and create private output atomically

* Target official runtime review and document PR570 prerequisite provenance

* Fix runtime tool output isolation, mmap64 chaining and DST ambiguity

* Normalize swapchain backbuffers for the opt-in D3D9 single-sample path

* Experimental Samsung Xclipse support on the system Vulkan path

Detect Samsung's Vulkan HAL (vulkan.samsung.so) and its sgpu render node
alongside PowerVR, and run Xclipse through the same libhybris bridge:

- VK_LAYER_DROIDDECK_xclipse (tools/linuxfs/xclipse-layer), built with the
  glibc preloads and enabled only on Xclipse: sets bionic's environ for the
  driver's shader compiler, avoids immutable YCbCr samplers in compute
  layouts, forwards the KHR calibrated-timestamp entry points to the EXT
  ones, and reports textureCompressionBC and BC4/BC5 filtering over the
  compat layer's BC emulation.
- Upstream DXVK 2.6.2, pinned as a tar.gz component and selected on Xclipse:
  the driver has 128 bytes of push constants, DXVK 3 needs 256.
- android_wlegl accepts a zero stride and passes it through unchanged:
  Samsung's gralloc records none for GPU-only buffers.

Tested on a Galaxy A56 (Xclipse 540, driver 24.0.560): Steam Big Picture
and PICO PARK (D3D9).

* Xclipse layer: copy the device chain and track samplers per device

- Clear textureCompressionBC in a copy of the device-create chain (every
  structure up to the VkPhysicalDeviceFeatures2 that enables it) instead of
  writing the caller's structure; a chain led by a structure of unknown size
  is passed on unchanged.
- Track YCbCr samplers per device in a growable list, drop them on
  vkDestroySampler and device teardown, and fail sampler creation rather
  than leave one untracked when the list cannot grow.
- Fail descriptor set layout creation with VK_ERROR_OUT_OF_HOST_MEMORY when
  a replacement sampler array cannot be allocated, instead of passing the
  original YCbCr samplers to the driver.

* Xclipse layer: copy any device-chain structure the headers define

The chain copy that clears the emulated textureCompressionBC only knew a
handful of structure sizes, so a valid chain such as
VkPhysicalDeviceTimelineSemaphoreFeatures -> VkPhysicalDeviceFeatures2 was
passed on unchanged and the compat layer refused the bit. Size every
structure that may extend VkDeviceCreateInfo from a table generated from the
Vulkan registry (gen_device_chain.py, Vulkan-Headers 1.3.275, the headers
the build compiles with), and copy onto the heap instead of a fixed buffer.
Only a structure newer than those headers still passes the chain unchanged.

Add a regression test that runs the layer between a simulated loader and a
mock driver: the timeline-semaphore chain, pEnabledFeatures, an unknown
structure, the caller's structures left untouched, per-device YCbCr sampler
tracking with handle reuse, and the KHR calibrated-timestamp forwarding. CI
installs libvulkan-dev for it.

* Mali bridge (#517, by ASEbrahim) on the non-Adreno test branch

Squash of Droid-Deck/DroidDeck#517 (ASEbrahim/DroidDeck mali-bridge @ 6b823341): the Vulkan
bridge (tools/vkbridge) that runs the guest's Vulkan on the phone's own driver, with BC1-BC7,
clip/cull distance, vertex-divisor and depth-clip emulation and dma-heap memory sharing for Mali.
All of the bridge is ASEbrahim's work; squashed only to carry it on this test branch.

Combined with #570 on this branch, a device draws through exactly one path:
- Adreno: Turnip, unchanged (checked first; the bridge setting is ignored there).
- PowerVR / Xclipse with the system HAL and a render node: the system-Vulkan path (#570).
- Anything else with the Setup toggle on (Mali): the bridge.

* Test branch: keep #570's DXVK build recipe so its pinned PowerVR components verify

#602 adds output checks to tools/dxvk/build.sh; tools/powervr/fetch.py pins the exact
recipe the published PowerVR DXVK was built from, so the branch carries #570's copy.

* Test branch: make #570, #602 and #560/#561 pass each other's tests

- tools/dxvk/build.sh: #602's copy (its output checks) and the PowerVR pin in
  tools/powervr/release.json moved to that file's hash; the recipe's outputs are unchanged.
- droiddeck-session: the presentation block sets _bl_cef for #570's PowerVR path, and the UI
  block keeps main's order, defaulting to -cef-use-angle=vulkan when the block has not run.
- Compositor test harnesses: AHardwareBuffer stubs for #570's AHB import, and a stub for
  vkp_effects_forget_image (main #597) that #561's caller harness predates.

* Auto drivers for Mali and Xclipse on the non-Adreno test branch

Auto now picks an open driver pair for the GPU, the way it does for Adreno:
- Mali (no KGSL, /dev/mali0): the GPU's name ("Mali-G720-Immortalis MC12", from the vendor
  Vulkan driver or kbase's gpuinfo) picks the PanVK build: G52-G78 (Job Manager), G610/G710,
  G615 or G720. A Mali none of them covers (G925...), or one whose pair is not published yet,
  draws through the Mali bridge, switched on by Auto.
- Xclipse keeps the system-driver path as Auto; the RADV pair is offered as a manual choice.
- The pairs come from The412Banner/droiddeck-nonadreno-drivers (one pre-release per driver,
  each a DriverBundle zip). Only a device without an Adreno reads that repo.
- The importers accept PanVK and RADV libraries (libvulkan_panfrost / libvulkan_radeon).
- An open driver pair and the Mali bridge are alternatives: a selected pair turns the bridge off.
- TU_DEBUG is only set for Turnip, on an Adreno.

Every new branch is reached only when the device has no Adreno; an Adreno's detection, release
sources, Auto choice and session environment are unchanged.

* Auto: the one-binary PanVK kbase build for Mali v9-v13 on Android 15+

The412Banner/droiddeck-nonadreno-drivers adds panvk-mali-kbase: one PanVK build for
G57/G68/G77/G78, G610/G710, G615/G715, G720/G725 and G925, whose display half needs Android 15.
On Android 15+ Auto prefers it for every Mali it covers; below 15, and whenever its release is
not listed, Auto falls back to the GPU generation's own pair. G52/G76 keep the Job Manager
build; a G715/G725/G925 below Android 15, or any Mali no pair covers, draws through the bridge.
Every pair stays selectable by hand.

* Test: the G925 is in the kbase build's family now

* Offer faux123's device PanVK builds (RP4 Pro, Ace, RG556) as manual pairs for their Mali

* Detailed graphics logs for non-Adreno test reports

- graphics-report.txt in every session folder: the graphics path the session draws with and why
  (Turnip, system Vulkan #570, Mali bridge #517, or an open driver pair with its release), the
  driver mode and choices, what Auto would pick, the GPU (family, Vulkan device name, kbase
  gpuinfo, Android, SoC, kernel), the GPU nodes the app can see (access, device numbers, SELinux
  label; nothing is opened) and the guest's graphics environment. At the end it adds the
  compositor's driver lines from app.log and the game launches that were logged.
- vulkan-summary.txt: the session's own vulkaninfo --summary with the driver as exported, in the
  background with a 10 s timeout.
- Per-game logs under games/<time>-<appid>/: Proton's log, DXVK's and vkd3d-proton's, Mesa's and
  PanVK's startup messages and the launch environment, from droiddeck-game-env. Each file is cut
  to 20 MB and the folder to 120 MB at the session's end.
- The per-game logs and the Vulkan summary are on for a GPU that is not an Adreno, and on an
  Adreno only with Setup's new "Detailed graphics logs" switch. Share logs already takes the
  whole session folder, so all of it is in the zip, scrubbed as before.

* Mali pairs: a stand-in render node, the driver's real library name, and a Mali known by its chip

- Auto on Jim's Dimensity 9300+ chose the bridge: the GPU had no name (the app had not asked
  Vulkan yet and kbase's gpuinfo is unreadable under SELinux), so a Mali-G720 counted as a Mali
  no build covers. The name now falls back to the GPU the chip is known to carry (Dimensity,
  Helio, Kompanio, Tensor); and when nothing names it, Auto asks the vendor Vulkan driver off the
  main thread before it settles for the bridge. Auto logs what it decided and why.
- An open Mali driver pair gets /dev/mali0 as the guest's render node, as the bridge does
  (bindGpuNode, BL_DRM_STANDIN): gamescope needs the node the driver reports. The session shim
  only answers libdrm's PRIME and GEM-close calls on it, which PanVK on kbase never makes.
- A PanVK or RADV runtime half is installed as libvulkan_panfrost.so / libvulkan_radeon.so rather
  than under Turnip's name; Turnip keeps libvulkan_freedreno.so.
- graphics-report.txt names the render node the guest got, adds the session script's driver lines
  and flags a failed Mali-bridge self-test at the top.

* Auto takes the RADV pair on a Samsung Xclipse, with the system route as its fallback

A tester's Galaxy S24 FE (Xclipse 940) boots to Steam with the RADV-Xclipse pair, where the
system-driver route fails at Steam's OpenGL (glamor and GLX cannot load a driver). Auto now
downloads and selects RADV on an Xclipse like the Mali pairs, and falls back to the system route
only when RADV is not published. #570's reset of both drivers at each start no longer undoes an
open driver pair. PowerVR (vulkan.powervr.so) keeps the system driver as before.

* An Exynos with a Mali is a Mali, not an Xclipse

The system route took vulkan.samsung.so as Samsung's Xclipse, but an Exynos 1280/1380 (Galaxy Tab
S9 FE: Mali-G68) may name its HAL the same; with /dev/mali0 present that HAL is not the system
route's, and the device is detected as the Mali it is. The chip table adds the Exynos 1380 and
1280 (s5e8835, s5e8825: Mali-G68).

* Auto fetches the open drivers itself; Xclipse named as such; sound and frame-gen in the logs

- Auto looks online again when its saved release list predates this device's open drivers, so a
  Samsung or Mali phone gets its pair without pressing refresh first.
- A Samsung Xclipse is labelled "Samsung Xclipse" (not PowerVR), with its own support line.
- Per-game logs add audio-streams.txt: PulseAudio's sink inputs 20 s and 60 s into the launch,
  so a silent game shows whether its stream reached Pulse.
- graphics-report.txt states the frame-generation setting and keeps the LSFG gate, FrameGen and
  audio lines from app.log.

* Client sound retries a briefly disconnected AAudio; kbase ioctl refusals explained in the report

A Galaxy S24 FE's session had no sound at all: the DirectAudio sink's AAudio stream opened with
AAUDIO_ERROR_DISCONNECTED while the output route was still changing at session start, and the
plain AAudio fallback failed the same moment. The sink guard now retries DirectAudio, then the
plain sink, five times over ten seconds and says which one it got. graphics-report.txt keeps the
client-sink and kbase lines, and explains a KBASE_IOCTL_MEM_EXEC_INIT EPERM as the kbase driver's
own answer rather than the session's sandbox.

* Mali pairs draw the display on the phone's own Mali driver; Xwayland falls back without glamor

- Testers' Mali pairs got the guest drawing (gamescope on the stand-in node, Zink on PanVK) but
  the compositor's PanVK Android half failed on Mali gralloc: it probes AHB formats 0x38/0x3b that
  mali_gralloc does not have, and "vkCreateImage w/ native buffer failed". Under Auto a Mali pair
  now leaves the display on the phone's own Vulkan driver (dma-buf, modifiers and AHB all pass the
  testers' checks) and runs the pair's PanVK in the guest; the Android half stays installed for a
  manual pick. "Pair in use" now follows the runtime half.
- A session after one whose Xwayland crashed (segfault, then gamescope's X11 I/O error) starts
  Xwayland with XWAYLAND_NO_GLAMOR=1 on a non-Adreno device, and says so in app.log.
- graphics-report.txt names the display driver the compositor used, and keeps gralloc, dma-buf,
  modifier and import lines from app.log and Xwayland's own lines from the session log.

* Non-Adreno sound: the daemon falls back on its own; the build's commit in device.txt and the report

The S24 FE's session still had no sound: its AAudio refuses DirectAudio's low-latency stream with
AAUDIO_ERROR_DISCONNECTED, and the sink guard never got to retry because pactl did not reach the
daemon in time ("the daemon did not answer in 15 s"). On a GPU that is not an Adreno the daemon's
own start-up script now asks DirectAudio again without low latency and loads the plain AAudio sink
underneath it, so a default sink exists whatever pactl does. device.txt and graphics-report.txt
name the build label and source commit, so a log says which build it came from.

* Non-Adreno sound: no second DirectAudio load (it hung the daemon); the plain AAudio sink underneath

On the S24 FE the daemon stopped after DirectAudio's failed low-latency load: the second
DirectAudio load in the same daemon never returned, so nothing after it ran and pactl never
connected. The daemon now loads only the plain AAudio sink (no low latency) after DirectAudio,
inside .nofail, and sets whichever exists as the default.

* gamescope: explicit sync off where the kernel or the stand-in node cannot signal its timelines

Adds tools/gamescope/patches/0126 (GAMESCOPE_DISABLE_EXPLICIT_SYNC=1 for the nested backends) and
sets it on a GPU that is not an Adreno when the kernel is older than 6.6 (no
DRM_IOCTL_SYNCOBJ_EVENTFD) or the guest's render node is Mali's stand-in. The graphics report
lists it with the rest of the guest's graphics environment.

* Test branch: gamescope with the explicit-sync switch (0126), pinned by sha

Built by build-gamescope.yml run 38335744351 from 0136fb61 and published as a component release
of The412Banner/droiddeck-nonadreno-drivers; the apk build fetches it by tag and checks the sha.

* Sound: nothing that can block in the daemon's start-up; Steam's web compositing in software on open pairs

- The S24 FE (US) daemon hung loading the plain AAudio sink from default.pa (its AAudio open
  blocks rather than fails), so it never finished starting. default.pa is back to DirectAudio
  alone inside .nofail; the fallbacks are loaded from the app once the daemon answers, each pactl
  bounded by its timeout (DirectAudio, then the plain sink with and without low latency). The
  guard waits 30 s and logs, with timestamps, how long the daemon took, whether its socket and
  pactl exist, and what it finally got.
- With an open driver pair on a GPU that is not an Adreno, Steam's CEF composites in software
  (-cef-disable-gpu-compositing) with GPU raster kept: RADV on Xclipse showed stale, half-written
  tiles, as #570 saw on PowerVR. The session log says so.

* Mali under Auto: the display always on Android's own Vulkan loader; a reboot hint for refused audio

Axel's Tab S9 FE still drew its display with the PanVK Android half, set by an earlier build, and
stalled at "Waiting for Steam's first frame" on Mali gralloc's format probes. Under Auto a Mali's
display now goes to Android's own loader whatever was set before (DriverMenus resets it, and the
compositor's driver choice ignores a pair's Android half), with no HAL path to find first. A pair
counts as in use when its runtime half is. A manual pick of the PanVK display is kept.
When audio.log shows AAUDIO_ERROR_DISCONNECTED, the report and the client-sink line say Android
refused the output and that rebooting the phone usually fixes it.

* Fix: call SessionPrefs.gpuDriverMode through its instance from Java

* Compositor: log each new dma-buf format/modifier/size it imports (for colour and swizzle reports)

Steam's software web compositing already covers every open driver pair, Mali included.

* Open driver pairs: vkd3d-proton reports the driver's caps; buffer attach/release tracing; Mesa log

- With a PanVK or RADV pair (BL_OPEN_DRIVER=1), droiddeck-game-env drops the app-wide
  VKD3D_FEATURE_LEVEL=12_2 and VKD3D_SHADER_MODEL=6_9 so vkd3d-proton reports what the driver has
  (an Unreal DX12 game crashed at start on PanVK with the overrides); a game's own setting still
  wins, and launch.txt records DROIDDECK_VKD3D_CAPS=driver.
- The compositor traces, on a GPU that is not an Adreno, the first 400 dma-buf attaches and buffer
  releases by wl_buffer id and sending line, and logs any release of a buffer already released
  and not attached again (gamescope's "Compositor released us but we were not acquired" on G925).
- Per-game logs no longer set MESA_DEBUG=silent, which kept mesa.log empty.

* Tests: the dma-buf quarantine harness follows send_buffer_release's tracing macro

* Mali (Venus): David Cimo's Venus route sources and recipes

From DavidCimo/droiddeck-mali, branch mali-venus @ 4f838891 (prototype 13; his 12 commits on top
of DroidDeck 0.3.1, GPL-3.0), by David Cimo:
- tools/venus: Mesa Venus vtest patch (incl. the per-queue submit_mutex fix and the cached-memory
  frame readback), virglrenderer Android patch, the Mali compatibility layer (the six features
  DXVK needs: fill modes, multi-viewport, robustness2, clip/cull distance, BC1-BC7) and his tools.
- tools/linuxfs/preload/venus.c: keeps Zink's kopper under Venus.
- tools/gamescope/patches/0127 (his 0114): shm buffers uploaded on every commit.
- docs/development/mali-venus.md: his design notes.
- tools/venus/ci-build.sh and build-venus.yml: his build recipes as one CI job that packs the
  component (vtest server, ICD, layer) as venus.tzst for the apk.

Co-authored-by: Dersim Abbas <[email protected]>

* Mali (Venus) as a selectable route, by David Cimo

Ports David Cimo's Venus route (DavidCimo/droiddeck-mali, mali-venus @ 4f838891) onto this branch
as "Mali (Venus)", one tap in Setup next to the Mali bridge:
- VenusComponent (his): stages the Venus ICD and runs virglrenderer's vtest server over the
  phone's own Vulkan driver; the guest gets VK_ICD_FILENAMES, VN_DEBUG=vtest, VTEST_SOCKET_NAME and
  MESA_VK_WSI_DEBUG=sw.
- Selection rule: Venus when Setup's "Mali (Venus)" is on, on a GPU that is neither an Adreno nor
  a system-driver one (PowerVR, Xclipse), and the apk carries it. It then replaces the Mali bridge
  and any open driver pair for the session, and the display stays on the phone's own driver.
  Auto is unchanged (the PanVK pair). Adreno never takes any of it.
- The compositor enables only the dma-buf extensions the driver has, logs a vendor driver's
  extension list, and gives a zero row pitch a packed row (his vk_present.c changes).
- The Mali compatibility layer is staged into the runtime on non-Adreno devices only.
- The component (vtest server, ICD, layer) comes from build-venus.yml as venus.tzst, published on
  The412Banner/droiddeck-nonadreno-drivers and pinned by sha in tools/venus/release.env.
- gamescope 3.16.29-p15 adds his shm upload patch (0127) next to 0126; pinned by sha.
- device.txt and graphics-report.txt name the route.

Co-authored-by: Dersim Abbas <[email protected]>

* Mali: Venus gamescope without robustness2, the bridge switch honoured and kept, clean driver switches

- gamescope 0128: VK_EXT_robustness2 is optional. A Mali driver through Venus has none, and
  gamescope stopped at "Missing required extension: VK_EXT_robustness2" (the wayland_surface error
  after it was only the knock-on). Without it, unused descriptor slots point at a 1x1 texture
  instead of VK_NULL_HANDLE. (David Cimo's Pixel 9 driver has robustness2, so his build never
  needed this.) gamescope component rebuilt and pinned by sha.
- Open-files limit: on a GPU that is not an Adreno the session script raises the soft limit to the
  hard limit for the whole session tree (a PanVK-kbase session froze on "Too many open files"
  after ten minutes) and graphics-report.txt shows before and after. A stopgap; the driver leak is
  fixed separately.
- The Mali bridge switch is honoured: when on and usable it wins over a PanVK pair for the
  session (Venus still wins over it). Selecting a pair no longer switches the bridge off, which is
  why the switch did not survive an app restart (Auto re-selected the pair at start). Setup says
  when this phone cannot use the bridge at all.
- Driver switching: picking a Mali pair by hand keeps the display on the phone's own driver, as
  Auto does (a PanVK Android display stalls on Mali gralloc at "Waiting for Steam's first frame",
  and stayed set after switching back); installing a bundle again replaces it instead of adding a
  "-2" copy beside the old one.
- graphics-report.txt names the runtime driver's folder and libraries, and the previous session's
  drivers, flagged when they changed.

* gamescope p16 (0128 robustness2 optional), pinned by sha; graphics-report says who chose the display driver

* Bundle replace only for open non-Adreno pairs; Adreno bundles keep their numbered copies

* Rebuilt open drivers reach phones: re-downloaded when their digest changes, release list hourly on non-Adreno

The non-Adreno driver releases replace their assets in place (the PanVK kbase sync-file fd leak
fix). A download is now remembered with its GitHub sha256, and an asset whose digest changed
counts as not installed, so Auto fetches it again and the bundle replaces the old one; the release
list is re-read after an hour instead of a day on a GPU that is not an Adreno.

* Mali compatibility layer: multiDrawIndirect, depthClipEnable and robustness2 for DXVK on Venus

A Galaxy A15 (Mali-G57) on Venus got gamescope and Steam up, but DXVK refused the GPU:
"Device does not support required feature 'multiDrawIndirect'". DXVK 3.1 stops at the first
missing feature, so the rest of its required list (dxvk_device_info.cpp) was checked against what
the testers' Mali drivers cannot have, and covered in the same pass, on top of David Cimo's six:
- multiDrawIndirect: reported, taken off the device, and an indirect draw with drawCount > 1 is
  recorded as single draws at offset + i * stride; maxDrawIndirectCount reported as 2^30.
  (drawIndirectCount is optional for DXVK and stays as the driver says.)
- VK_EXT_depth_clip_enable (required): offered when the driver lacks it; depthClipEnable off in a
  pipeline becomes depthClampEnable, and the extension's structures never reach the driver.
- VK_EXT_robustness2 missing entirely (gamescope saw the same on these phones): offered with
  robustBufferAccess2 (as robustBufferAccess, as before) and nullDescriptor, emulated by small
  stand-ins (a zeroed buffer, a 1x1 image) for VK_NULL_HANDLE buffers, image views and vertex
  buffers in plain, template and push descriptor writes and vertex-buffer binds. A null
  texel-buffer view stays null (its format is not known).
The layer logs every feature it emulates per device.

* Venus component 22694a38 (layer: multiDrawIndirect, depthClipEnable, robustness2), pinned by sha

* Non-Adreno round 3: bridge images, Venus GL, shared buffers, Steam GPU drawing

- vkbridge: the memory model skips lazily allocated types. Mali-G615 lists
  DEVICE_LOCAL|LAZILY_ALLOCATED first; taken as the device-local type it left
  images with no memory type (self-test "image clear/draw: setup" FAIL,
  gamescope "No compatible image memory type").
- Session: a bridge self-test that fails images or draws falls back to the
  runtime's driver pair for that session (BL_VKBRIDGE_FALLBACK_ICD); Setup
  and the graphics report say so.
- Venus: the Mali compatibility layer runs in every process but gamescope
  (DROIDDECK_MALI_COMPAT=1), so Zink in Steam and Xwayland gets
  nullDescriptor; null texel-buffer views get stand-ins too. Without
  nullDescriptor the session's GL goes to llvmpipe.
- gamescope 0129: a refused compute pipeline is named with its SPIR-V
  capabilities.
- Compositor: a buffer on two surfaces at once (gamescope's planes while a
  notification is up) is released only when the last one lets go.
- Open driver pairs: processes.txt timeline for the first three minutes;
  Setup "Steam interface: GPU drawing" turns off software web compositing.

* Pin gamescope p17 (0129) and Venus 55b30fbf

* Non-Adreno round 4: Mali GPU-drawing block, gamescope YCbCr retry, GL probe, bridge draw sync

- Setup's "Steam interface: GPU drawing" is hidden on Mali and a saved "on" is
  cleared: with PanVK it rebooted an Edge 60 (G615) as steamwebhelper started.
  Kept for Xclipse.
- gamescope 0130: a composite pipeline refused at start-up is retried with a
  layout without immutable YCbCr samplers (G615 through Venus refused every
  composite shader; their SPIR-V has no Float16, so fp16 was not the cause).
- Venus: droiddeck-glprobe opens GL through GLX the way the Steam client does,
  with the loader's reasons logged; when it fails the client's GL goes to
  llvmpipe.
- vkbridge self-test: the draw check waits for the render pass's stores before
  copying out (a tiler read zeros without the dependency).

* Pin gamescope p18 (0130)

* Non-Adreno round 5: gamescope memory fallback, Venus destroy guard, OpenAL depot

- gamescope 0131: DEVICE_LOCAL|HOST_VISIBLE requests fall back to host-visible
  memory (the bridge's unified-memory model lists none with both), with the
  request, type bits and memory types logged on failure; 0130's probe pipeline
  is kept and skipped when forced.
- Venus: GAMESCOPE_NO_YCBCR_SAMPLERS=1 from the start; virglrenderer ignores
  the destroy of a pipeline it never created instead of marking the context
  fatal (vtest server exit 137 on a Mali-G615).
- Steam's OpenAL depot (229020) maps to oalinst_dll, so games that list it
  (Hotline Miami) get OpenAL in their prefix.

* Pin gamescope p19 (0131) and Venus 6199a93f

* Non-Adreno round 6: PowerVR PRIME on old kernels, thermal, hang-up names, bridge flippable images, Venus Zink variants

- drm.c: PR #570's current PRIME ownership check (F_DUPFD_QUERY, then kcmp,
  then the F_SETOWN_EX owner marker): on a kernel before 6.10 with kcmp
  blocked, drmPrimeFDToHandle answered EOPNOTSUPP for every PowerVR import
  (Pixel 10, Android 17: Zink "failed drmPrimeFDToHandle", Xwayland error 7,
  gamescope X11 I/O error). The Mali stand-in node is kept.
- A /sys/class/thermal the kernel will not list gets an empty directory, so
  mangoapp no longer aborts on it.
- gamescope 0132: a hung-up waitable is named before the abort; 0133: the
  bridge's flippable images use supported-but-unexportable modifiers instead
  of asserting.
- Venus: when Zink does not start, the loader's view is logged and Zink is
  retried without ZINK_DEBUG=compact, with ZINK_DESCRIPTORS=auto and without
  kopper before falling back to llvmpipe.

* Pin gamescope p20 (0132, 0133)

* Non-Adreno round 7: bridge command-buffer guard, Venus Zink trace, PowerVR mangoapp, Xclipse label

- vkbridge server: every command buffer a device hands out is tracked; Begin,
  End (stream replay) and QueueSubmit refuse one it never allocated or has
  freed (VK_ERROR_DEVICE_LOST, logged) instead of passing it to the driver,
  where Mali's vkBeginCommandBuffer crashed the app (SIGSEGV 0x10, Steam's
  Zink on an Edge 60 and a Tab S11).
- vkbridge server: a fault inside the driver while serving a request cuts
  that client off (SIGSEGV/SIGBUS guard per request) and leaves its objects
  alone; the app and the other clients carry on. Other faults go to the
  previous handler.
- vkbridge server: the pipeline cache directory is copied; the JNI string it
  pointed at was freed (garbage in the cache path).
- Venus: DROIDDECK_MALI_COMPAT_TRACE=1 makes the Mali compatibility layer log
  what an application queries before vkCreateDevice; a failed glprobe adds a
  traced run and vulkaninfo --summary under the same environment.
- PowerVR: mangoapp draws with llvmpipe (Zink could not make it a swapchain
  and it crashed at every start).
- Setup: an Xclipse GPU reads "Experimental: RADV or the system Xclipse
  driver", not the PowerVR line.

* Pin Venus e40cd361 (layer trace)

* Non-Adreno round 8: bridge sharing fallback and crash detail, Venus diagnostics and 1.2 gate, compositor disconnect reasons, RADV upstream

- Mali bridge: the self-test fails "memory sharing" when the server offers
  no host-visible memory type (no sharing method passed, RP4 Pro G77); the
  session then falls back to the PanVK pair like a failed draw, and Setup
  says the bridge cannot share memory with that driver. Without a pair to
  fall back on, the log says so.
- Mali bridge: a guarded driver crash now logs the sub-command it was in,
  the fault address and pc (library + offset via dladdr), the command
  buffer's level, begin flags, pool flags and state, and the last 32
  replayed commands. Begin on an executable buffer from a pool without
  RESET_COMMAND_BUFFER_BIT is logged once (implicit reset).
- Venus: vulkan-summary.txt gets the device extension list and key
  features (full vulkaninfo in vulkan-full.txt); a failed GL probe runs
  once with VN_DEBUG=init,result, ZINK_DEBUG=mem, MESA_LOG_LEVEL=debug and
  the layer trace into glprobe-trace.txt. The Mali compatibility layer's
  trace covers every pre-device query (devices, memory, queue families,
  formats, image formats, external handles) and names the extensions.
- Venus: off when the phone's driver reports Vulkan below 1.2 (gamescope
  skips such a device); Setup and the graphics report say why and the
  session uses the PanVK pair.
- Compositor: libwayland's own disconnect reasons go to wayland.log, and a
  client's buffer may grow to 1 MB before a slow reader is dropped. A
  session that ended without teardown records Android's exit reasons.
- Drivers: RADV-Xclipse upstream (radv-xclipse-upstream) is a manual
  choice for Xclipse; Auto keeps radv-xclipse.

* Tests: the terminal-callers harness stubs the Quest layer and Cardboard modules and Android's system properties

* Pin gamescope p21 (main's patches + 0120-0133) and Venus a4d47cf3 (layer trace)

* Display driver Auto: Quest's patched Turnip first; the system driver only for a Mali, other GPUs keep the generic fallback

* Open driver pairs: the installed build's version, its upload date, and an Update when the release is rebuilt in place

- The release check records each asset's GitHub id and updated_at; a
  download records them (with the digest) for an open non-Adreno pair.
- Components > GPU drivers: an installed open pair shows its manifest
  version ("android <tag> + linux <commit>"), one not yet installed shows
  "built YYYY-MM-DD". A different digest, asset id or upload time on the
  same tag marks the installed pair "Update available" with an Update
  action: the pair is downloaded again and replaced in place, and stays
  selected only if it was. Adreno and DD-Turnip pairs are unchanged.
- graphics-report.txt: the pair release line gives the installed and the
  listed upload dates and flags a newer build; an exact bundle id match is
  preferred (RADV-Xclipse-upstream is not RADV-Xclipse).

* Session preload: drop oom_score_adj raises so lmkd does not treat games as cached apps

steam-launch-wrapper starts every game with --oom-score-adjust 900; on
Android 900 is CACHED_APP_MIN_ADJ, so lmkd kills the running game first
when memory runs short (and on Linux 6.1 the raise becomes the process's
permanent minimum). A process in the app cannot go below the score it
inherits, so the writes that succeed from inside are raises: every
write-open of /proc/<pid|self|thread-self>[/task/<tid>]/oom_score_adj (or
oom_adj), through open/openat or fopen, goes to /dev/null, logged once.

Based on the oom_score_adj redirect in the preload's opens.c by jdmg28 (https://github.com/jdmg28/DroidDeck-Mali/commit/6be00e00)

* Display: why presenting failed on the session-ended screen, and the display driver that really loaded

- The compositor records why it cannot present (no Vulkan driver loaded,
  vkCreateInstance/vkCreateDevice failed, no GPU or graphics queue, no
  swapchain on the screen with the driver's reason, device lost, GPU work
  unconfirmed). Each is a DD_PRESENT_FAULT line in wayland.log, read back
  over JNI or from wayland.log; the session-ended screen shows it instead
  of a black screen, and graphics-report.txt gets "!! DISPLAY FAILED". A
  first frame or a working swapchain clears it.
- After the display device is created, the driver that answered is logged
  from VkPhysicalDeviceDriverProperties (device, driverName, driverInfo,
  driver id, Vulkan version) and reported in graphics-report.txt. When a
  driver pair's display half was requested but a vendor driver answered
  (adrenotools falls back to the system driver when the requested one does
  not load), it says so.

Based on DD_PRESENT_FAULT in vk_present.c / waylandcomp_jni.c / SessionActivity by jdmg28 (https://github.com/jdmg28/DroidDeck-Mali/blob/arena/cd8ab18a-droiddeck-mali/app/src/main/cpp/waylandcomp/src/vk_present.c)
Based on the adrenotools silent-fallback finding by ryanistr (https://github.com/ryanistr/DroidDeck-Mali/blob/main/docs/development/panvk-mali-g77-bringup.md)

* tests: steam presentation test sets 0.5.0's steam_client

* Non-Adreno: find every driver pair, Venus before the bridge, fix the bridge's begin info

From the logs testers sent since c9ebb247:
- The app read the drivers repo's newest 15 releases only; the component releases (gamescope,
  Venus) pushed every PanVK pair and RADV-Xclipse past them, so Auto found no pair on any Mali
  and switched the Mali bridge on. The non-Adreno repo is now read in one page of 100.
- Auto's stand-in for a missing pair: Mali (Venus) where the apk carries it (Steam runs on it
  from the G57 to the G1-Ultra), the bridge otherwise, nothing on a driver below Vulkan 1.2. It
  no longer clears the Linux driver, does nothing when the list could not be read, records that
  it chose the route, and turns it off again once a pair is installed. A bridge that earlier
  builds left on under Auto is taken as Auto's.
- Mali bridge: vkBeginCommandBuffer sent its begin info without the count the server decodes, so
  Mali got a zeroed begin info with stale flags and crashed at 0x10 (Pixel 8 Pro G715, Lenovo
  G615, Xiaomi G925, Tab S11).
- Mali-G1 series (MT6993, arch v14) known by its chip; its label says Venus, not the bridge.
- A session Android killed for memory is said so on the next start (the S24 FE "crashes" were
  low-memory kills, not driver faults).
- The in-app updater never installs an official (Adreno-only) build over this test build.

* Venus: destroy the device's object mutex after the objects that lock it

Bionic aborts on locking a destroyed mutex ("pthread_mutex_lock called on a destroyed mutex",
Tab S9 FE G68, Pixel 9a), so a client that exited holding objects took the render worker down.
From BattleTempest's DroidDeck-Pixel (kevinbryant551-ux/DroidDeck-Pixel, GPL-3.0).

* release check: a session helper may be extracted to another runtime path

droiddeck-steam-taskset (#570) goes to usr/local/libexec/droiddeck-steam/taskset, where PATH puts
it in front of Steam's own taskset for the client only; 0.5.0's check wanted every droiddeck-*
helper at its own path.

* Venus component 93038b7d (destroyed-mutex fix)

* Update MaliBridge to v0.5.1 (#1135)

* Mali Auto: never leave a session on the runtime's Turnip

Logs from a Retroid Pocket 4 Pro (G77), Galaxy S21 Ultra (G78), Tab S11 Ultra
(G925) and Xiaomi G610 showed Auto ending on the runtime's Turnip, where
gamescope finds no GPU ("failed to find physical device"): after a pair's
trial rolled back, when the pair is on the bad list, and when the driver
list could not be read. In each case Auto now switches on its stand-in
(Venus, or the bridge) when no open driver, Venus or bridge is set.
Under Auto the Mali display also leaves any imported driver, not only a
pair's PanVK half (a lone PanVK_G720 import gave a black screen).

* Mali bridge: MaliBridge 224b6e36 (v0.5.3 source, by ASEbrahim)

From c65062b6 (v0.5.1). v0.5.2/v0.5.3: Steam mode on older Mali drivers
(G77/G710/G610/G57) gets past "starting display and audio", OpenGL 4.6 on
most Malis through dynamic-rendering and vertex-format emulation, Dimensity
9500 texture fixes, async pipeline compile, RE2 flicker on G925, DirectX on
the Pixel 8 family. JNI entry and session env names are unchanged.

* gamescope 0134: linear when the backend offers no modifier; Venus on Mesa 26.2.5

0134: CVulkanTexture::BInit asserted !modifiers.empty() when the nested Wayland
backend offered no modifier for a flippable image's format (NieR Replicant on a
Pixel 9a under Venus, session exit 134). It now tries linear and logs it.
Venus ICD: mesa-26.2.5 (venus TLS teardown from an unloaded driver, virtio-gpu
devices without 3D skipped); virglrenderer stays on 1.3.0.

* gamescope p22-nonadreno (0134 + main's 0116 cursor fix; build run 40159674290)

* CI: build the non-Adreno additions before the preload check

* build.gradle: drop verifyHandModel (main moved the hand models into the tracking pack)

* Venus component venus-proto13-e24ed8ab (Mesa 26.2.5; build run 40161447324)

* CI: fetch the Mali (Venus) component again (lost when build.yml took main's jobs)

Build a7a1ebf1 (run 40161734101) had no assets/venus or libvirgl_test_server.so, so
VenusComponent.available() was false and Auto's stand-in would have been the bridge.

* Auto: the Mali bridge stands in on a Vulkan 1.1 Mali driver

Auto refused both Venus and the bridge when the phone's driver reports Vulkan
below 1.2, so on the G77, G78 and G610 (r32p1/r38, Vulkan 1.1) a rolled-back or
missing pair left the runtime's Turnip: "failed to find physical device"
(Retroid Pocket 4 Pro after the JM pair's trial, Xiaomi 13T). Only Venus needs
1.2; the bridge raises the driver for gamescope and runs on these phones. A
Venus switch that cannot start on such a driver no longer counts as a route.

* Tests: stub main's g_release_unpaced in the DMA-BUF quarantine probe

The probe copies release_buffer out of compositor.c; since the merge it reads
g_release_unpaced, which the harness did not declare (setUpClass failed to compile).

* Update MaliBridge to v0.5.4 (ASEbrahim)

48477fe6 (tag v0.5.4): Dimensity 9500 green squares and red-tinted textures,
Balatro and other OpenGL text, fewer crashes when memory runs low, one screen
update per game frame, picture kept after a long background pause (SIGHUP no
longer stops the server), wrong colours after changing graphics settings on
the G720, more in the session report. The app-side changes of his DroidDeck
Mali app (Steam menus setting, per-game setup, Send logs, updates) are not
part of the MaliBridge repository and are not in this build.

* gamescope 0135: linear when the driver rejects every offered modifier

An r32p1 Mali-G610 (Vulkan 1.1) behind the Mali bridge answered both of the
backend's modifiers for AR24 with VK_ERROR_FORMAT_NOT_SUPPORTED, so the first
flippable image failed and gamescope asserted in vulkan_create_flat_texture
(Xiaomi 13T). Linear, when offered, is tried anyway; the bridge makes such
images without the handle type and exports them through a dma-heap. If the
image cannot be made, BInit fails as before.

* gamescope: pin the non-Adreno p23 build (0135)

* Tests: the terminal-callers harness stubs take_shm's game-frame detection

Main's take_shm now reads surface damage, parent/role/mapped and the HUD state
to spot game frames; the harness declares them so the copied function compiles
(setUpClass failed in Build APK run 40505286127). No damage, so it stays off.

* gamescope: number the test branch's patches 0920-0935

Main now has its own 0121-0125 (window override walk, host-event wake, commit fences, done at
import, HUD frame times), which sorted in between this branch's 0120-0135. The branch's sixteen
patches move to 0920-0935 with the same last two digits, so they apply after main's set and stay
clear of its next numbers. Applying all 38 to gamescope 3.16.29 in the old order and the new one
gives the same tree (checked with patch -p1, as build-in-arch.sh does). PATCHES.md and the one
code comment that named 0130 follow the new numbers.

* check_session_assets: helpers count in usr/local/bin or the Steam taskset folder only

The merge kept this branch's pattern (any usr/ target), which main's new test rejects: a helper
extracted into a subfolder of usr/local/bin must not count. The pattern now takes usr/local/bin
under any name, as on main, plus usr/local/libexec/droiddeck-steam for droiddeck-steam-taskset.
Every helper in the overlay is still found extracted in SessionFiles.kt.

* gamescope: pin 3.16.29 p24 (non-Adreno)

Built from this branch @d13b9d88 (run 41119284267): main a5b035b2's patches plus this branch's,
now 0920-0935. All 38 applied, gamescope's tests 2/2. sha256 1d1088d4.

* Update MaliBridge to v0.5.4.2 (ASEbrahim)

95a1e600 (tag v0.5.4.2), was v0.5.4 (48477fe6). For the bridge: memory sharing without the handle
type where that is all the self-test passed (unflagged buffers, client/memory.c, server_memory.c),
the older-Mali (G52-G77) game-mode start fixes, the r38p1 beta geometry-shader erratum (gs_hide),
present timeouts and an API cap per process. Client, server and the in-app library all build from
the submodule with MaliBridge's own scripts, which did not change; the protocol only gained
fields an older server sends as 0.

* Session end: the compositor fault query no longer fails the hint when the native library is absent

presentationFault() (6778a224) called WaylandCompositor.compositorFault() directly. Main's new
SessionEndHintTest runs sessionEndHint() in Robolectric, where libdroiddeckwayland cannot load, so
all six cases ended in UnsatisfiedLinkError (Build APK run 41125749537). The call is now wrapped
like runtimeDamageHint()'s, and without an answer the hint reads wayland.log as before.

* test: stub the GPU busy reader in the caller harness

* fix: release build is not debuggable, Mali uses the system display driver, staging failure ends its own session

* fix: address non-Adreno review findings

* fix: preserve current gamescope and GPU warning behavior

* fix: retain graphics failure hints and launch diagnostics

* fix: identify non-Adreno GPUs in driver failure hints

* test: harnesses follow the external client barrier and effects upscaling changes

* fix: client sources return to foreign ownership after the overlay draws

---------

Co-authored-by: Leask Wong <[email protected]>
Co-authored-by: wallentx <[email protected]>
Co-authored-by: Stefan Zijlstra <[email protected]>
Co-authored-by: William Allen <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: Dersim Abbas <[email protected]>
Co-authored-by: root <[email protected]>
Co-authored-by: ASEbrahim <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
2026-11-05 22:06:16 -05:00
MaxsTechReview f4e5032b7d fix(fex): skip local patches already in the FEX release (#2152) 2026-11-05 21:33:57 -05:00
Kurt Himebauch c8a76e7cce perf(agent): run diagnostic cleanup off startup thread (#2147)
* perf(agent): run stage cleanup off startup thread

* fix(agent): preserve active diagnostic stages

* test(agent): verify active stage locks cross-process
2026-11-05 20:04:16 -05:00
Kurt Himebauch 6a681bcbc2 docs: clarify DX12 budget consumers and physical limits (#2148)
* docs: clarify DX12 budget consumers and physical limits

* docs: record controlled Thor gameplay limits
2026-11-05 19:04:11 -05:00
Moroni Granja cd452e2b60 fix: upload GOG cloud saves in Galaxy's gzip + metadata format (#1952)
fix: upload GOG cloud saves in Galaxy's gzip format
2026-11-05 18:56:21 -05:00
MaxsTechReviewandKurt Himebauch 5971b91ed4 fix: an unplugged game drive falls back to internal storage (#1913)
* fix: an unplugged game drive falls back to internal storage

When the drive chosen for game storage is not connected, installs, sessions and the install picker use internal storage instead of failing with "Game storage is unavailable". The saved drive is kept and used again once it is plugged back in.

* fix: store installs set to external use internal storage while the drive is unplugged

With default storage set to external, a missing drive or folder no longer stops installs at the location dialog or leaves the Epic launcher pointing at it. Installs go to internal storage and switch back to the drive once it is connected again.

* fix: external installs do not move to another card while the saved drive is unplugged

* fix(storage): verify saved volume is mounted

* fix(storage): reject unknown external volumes

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-11-05 18:33:38 -05:00
Kurt HimebauchandK11MCH1 906e60a042 feat(desktop): show programs installed by Windows installers (#2146)
* Desktop: programs a Windows installer adds get a menu entry and desktop icon

Opening a Windows installer on the desktop runs it with Proton in a prefix
of its own (droiddeck-proton-run's "own" prefix). Proton's menu builder
turns the installer's Start Menu links into .desktop files, but keeps them
in the prefix's drive_c/proton_shortcuts with a Windows .lnk path as Exec,
and nothing outside the prefix pointed at the installed program: Nexon
Launcher installed fine on an AYANEO Pocket FIT Elite and could only be
found by browsing the hidden compatdata folder.

droiddeck-desktop-programs reads proton_shortcuts in every prefix that is
neither a Steam game's nor a current Steam shortcut's, resolves each link to
its program inside the prefix, and writes a menu entry that opens it with
droiddeck-open, so it runs in that same prefix, with the icon Proton
extracted. A program whose installer also put a link on the Windows desktop
gets a desktop icon once; one the user deletes is not put back. Entries go
away with their program; uninstallers get none. It runs when the desktop
starts and after droiddeck-open finishes a Windows program.

* Desktop programs: only programs that run in their prefix, Windows argument rules, one refresh at a time

A link resolved through Proton's Z: mapping could name a program outside
the scanned prefix, and droiddeck-open would then run it in another one.
A program is now listed only when droiddeck-proton-run's prefix_of puts it
in the prefix it was found in.

Shortcut arguments were split with shlex, so a Windows argument with an
apostrophe raised and stopped the whole refresh; they are now split by
the Windows command-line rules.

The desktop start and the end of a Windows program can refresh at the
same time and race on the same temporary files; refreshes now take turns
on a lock beside the placement record.

* Desktop programs: extract the script at session start

SessionFiles lists the overlay scripts the app copies into the runtime;
droiddeck-desktop-programs was missing, so a real build would never have
installed it and check_session_assets failed the build.

* Desktop programs: a compatdata folder it cannot read changes nothing

Any error listing steamapps/compatdata made the refresh see no prefixes,
so it removed every program entry and forgot which desktop icons it had
placed; the next good run then put back icons the user had deleted. Only a
missing compatdata now means there is nothing installed; any other read
error leaves the menu, the desktop and the placement record as they were.

* Desktop programs: an unreadable shortcut or desktop folder changes nothing

os.path.isdir and glob read an unreadable proton_shortcuts or Windows
desktop folder as empty, so the refresh dropped that prefix's entries and
placement records and a later run put back icons the user had deleted.
Those folders are now listed directly: a missing one is still empty, and
any other error leaves the menu, the desktop and the record as they were.

* Desktop programs: only a missing file reads as absent

read(), link_target() and host_path()'s letter-case lookup turned any
error into "absent", so an unreadable placement record read as empty and
brought back icons the user had deleted, and an unreadable shortcut or
link dropped its program's entry. A missing file, a wrong file type and a
malformed link still read as absent; any other error now reaches main(),
which leaves the menu, the desktop and the record as they were.

* Desktop programs: keep the placement record true when a run fails part way

The placement record was read after the menu was already rewritten, so an
unreadable record changed the menu although the run reported it had not.
It is now read first.

When writing desktop icons failed part way, the icons already written were
not recorded, and the next run recreated any of them the user had deleted.
The record is now written from the icons that exist after the attempt,
icons are written in a fixed order, and a failed write leaves no temporary
file on the desktop.

os.path.exists and isfile also read a program whose path cannot be reached
as missing; a stat that fails only because the file is absent now counts
as missing, and any other error leaves everything as it was.

* Desktop programs: leave the menu alone when Steam's shortcuts cannot be read

The list of Steam shortcuts comes from a reader that treats an unreadable
or damaged shortcuts.vdf as having no shortcuts, so a Steam shortcut's
prefix could get a menu entry and a desktop icon of its own. The file is
now checked first: a missing one still means no shortcuts, and any other
read or parse failure leaves the menu and the desktop as they were.

* Desktop programs: an unreadable Steam account or empty shortcuts file also stops the refresh

account_dir takes an unreadable loginusers.vdf or userdata folder as
missing, so the shortcuts check could be skipped and a Steam shortcut's
prefix still get its own entry. Both are now read first, and an access
error leaves the menu as it was.

An empty shortcuts.vdf is now parsed like any other, so it stops the
refresh too, the same way droiddeck-steam-shortcuts refuses to rewrite it.

---------

Co-authored-by: K11MCH1 <[email protected]>
2026-11-05 18:33:15 -05:00
Kurt Himebauch 9d2580d502 perf: reclaim launcher memory earlier under pressure (#2138)
* perf: reclaim launcher memory under system pressure

* docs: record Caravan save-load OOM with spare swap

* fix: wait for completed reclaim before increasing backoff
2026-11-05 18:27:35 -05:00
Kurt Himebauchanduubooster-team 6a5722c6d1 fix(preload): answer NETLINK_ROUTE queries inside a session (#2141)
* fix(preload): answer NETLINK_ROUTE queries inside a session

ip(8) could not run in a session: the app's SELinux domain has no
netlink_route_socket bind permission, so iproute2 failed at bind and never saw
the link that getifaddrs() and /proc/net already expose.

Serve NETLINK_ROUTE from the same etc/droiddeck-net snapshot: an AF_UNIX
socketpair backs the netlink fd, link/addr/route dumps and route get are
synthesised, writes return EPERM, and multicast subscriptions keep failing
with EACCES so consumers that wait for link events keep their fallback.

Replies carry the port id reported by getsockname() and recvmsg() rewrites
the source address to AF_NETLINK/pid 0. iproute2's dump loop requires both
before it accepts a message or NLMSG_DONE; when either is missing every
message is skipped silently and the process blocks in recvmsg().

* fix(preload): keep socket() and send() semantics for emulated netlink

* fix(preload): deliver netlink replies with the caller's blocking mode

* fix(preload): preserve NETLINK_ROUTE protocol and fd semantics

- Send ACKs for successful queries and honor NETLINK_CAP_ACK
- Reject unsupported subscriptions and apply socket timeouts
- Track socket identity and shared references across dup and close
- Support kernel connect and rewrite recvfrom source addresses
- Resolve local and connected routes before default gateways
- Add netlink and iproute2 regression tests

* fix(preload): handle netlink request limits and socket lifecycle

- Collect large sendmsg requests without falling back to UNIX sockets
- Reject oversized replies explicitly instead of returning partial results
- Allocate unique port IDs and reject duplicate bindings
- Honor socket flags and restore inherited sockets across exec
- Validate socket types and report netlink socket identity
- Replace the fixed socket table with a dynamic registry
- Add regression tests for batching, port conflicts and exec inheritance

* fix(preload): optimize netlink tracking and support link name queries

- Add lock-free fd tracking to bypass netlink locks on ordinary sockets
- Move descriptor restoration to initialization and explicit discovery paths
- Support RTM_GETLINK lookups via IFLA_IFNAME with correct error codes
- Add regression coverage for hot-path overhead and name-based queries

* fix(preload): accept unterminated netlink interface names

- Copy IFLA_IFNAME into a bounded, NUL-terminated buffer before lookup
- Match kernel string length validation and ERANGE errors
- Add regression coverage for unterminated names and boundary cases

* fix(preload): pin peers for netlink replies

* fix(preload): make autobind idempotent

* fix(preload): isolate forked netlink cleanup

* fix(preload): avoid interposed fd scans

* perf(preload): skip PID checks for untracked fds

* fix(preload): verify peer before replay

---------

Co-authored-by: uubooster-team <[email protected]>
2026-11-05 18:27:08 -05:00
MaxsTechReviewandKurt Himebauch b875b39905 fix(input): release held mouse buttons and keys when the session loses focus (#2139)
* fix(input): release held mouse buttons and keys when the session loses focus

A mouse button or key held in the guest stayed down when another window took focus or pointer capture ended before the release, because the release went to that window. A game that hides the cursor while a button is held, such as mouselook in World of Warcraft, then kept turning the camera with the cursor gone until the button was pressed again.

Buttons sent to the guest are now tracked like keys, and every held button and key is released when the session window loses focus or pointer capture, and when picture-in-picture opens.

* fix(input): release a held button where it was pressed

A button pressed through the compositor was released through the raw mouse channel if that channel turned on while it was held, and the reverse, so the guest kept it down. Each held button now remembers where it was pressed. A scroll-touch drag is also cancelled when the session loses focus.

* fix(input): release trackpad buttons on focus loss

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-11-05 18:26:38 -05:00
Kurt Himebauchandxor ecedf131e0 fix(game-env): allow Supergiant mscoree fallback (#2143)
* fix(game-env): allow the builtin mscoree for Supergiant .NET crash reporter

* test(game-env): cover Supergiant mscoree fallback

---------

Co-authored-by: xor <[email protected]>
2026-11-05 18:04:16 -05:00
MaxsTechReview 8ac5652131 fix(components): a native-only override falls back to builtin when the prefix has no native DLL (#2142)
* fix(components): a native-only override falls back to builtin when the prefix has no native DLL

A component that asks for a DLL native-only (for example .NET 4.8's mscoree=n) left the DLL unloadable when its native copy never reached the prefix. On ARM64 prefixes the .NET installer leaves Wine's builtin mscoree in system32, so every program that needs .NET exited at launch with 'mscoree.dll not found'.

At launch, an override of n for a DLL whose system32 or syswow64 copy is missing or is Wine's own (a link into the Proton build, or a builtin or placeholder stub) becomes n,b, and the session log says so. A real native copy is still loaded first, and the saved component state is unchanged, so the override returns to n once the native DLL is there.

* Treat any prefix symlink as builtin when checking native overrides

* Treat an unreadable prefix DLL as missing for native overrides
2026-11-05 18:00:38 -05:00
Kurt HimebauchandMoroni Granja 5624b483fb fix(input): reach full guest screen with glasses pointer (#2145)
fix: the glasses pointer reaches the whole guest screen

Absolute input was clamped to the letterboxed picture rect computed for the phone view, while the compositor reads the input space against the glasses output. With different shapes that limited the guest pointer to a middle band of the scene, so the bottom and top edges were unreachable.

Send absolute pointer positions as the full-view fraction (the compositor already clamps to the scene) and use the whole view for the touchpad bounds and relative scale while the glasses own the output.

Fixes #2055

Co-authored-by: Moroni Granja <[email protected]>
2026-11-05 17:53:15 -05:00
Kurt Himebauch d64205ba5b fix: report Vulkan memory budgets without rejecting allocations (#2014)
Extend the VRAM setting to Vulkan memory-budget reports for DXVK and D3D12 while preserving real heap capacity and allocation results, removing the hard rejection that caused game crashes in FIT and Discord tests.
2026-11-05 17:25:54 -05:00
Kurt Himebauch df76f66e8f gamescope: ship p20 so the EA sign-in stays above the launcher splash (#2045)
#2037 added gamescope patch 0126, but the app does not build gamescope.
It stages the prebuilt binary pinned in `tools/gamescope/release.env`,
which is still `gamescope-3.16.29-p19`, built on Oct 29 before the patch
existed. A fresh install of a main build therefore still draws the EA
Steam Launcher's "Authenticating…" splash over the EA "Verify your
identity" sign-in. The APK of main 951270b carries gamescope
`1a9c836e…`, the same executable as p19.

This points `release.env` at `gamescope-3.16.29-p20`.

**Needs a release first:** please publish the `gamescope-patched`
artifact of the main build of 951270bc, [run
41637838020](https://github.com/Droid-Deck/DroidDeck/actions/runs/41637838020),
as `gamescope-3.16.29-p20` in Droid-Deck/DroidDeck-Components. Until
that tag exists, the runtime component build here cannot download it.

- That run applied patches 0002–0126 and passed gamescope's tests (Ok 2,
Fail 0).
- Archive SHA256:
`4537fa58b042f84e605123c4778ee7089442591cd3bfe0648ad96e113ef6117d`
- Gamescope executable SHA256:
`b22078c80c7262a24339105b2b02c90a8cf1a7929f6e10c212f465eb772dbb6c`
- WSI layer SHA256:
`eabc9dcab72dc52dcbd7def2df9ab99e2a26e662eba2c387059ed655801825c5`

The executable is byte-identical to a local build of the same patch
series.

## Testing

AYANEO Pocket FIT Elite, DroidDeck 0.6.9 (main 951270b), Burnout
Paradise Remastered installed fresh:

- With p19 as shipped, the EA app installed and opened its sign-in, and
the Burnout splash covered most of it.
- With this run's executable staged over `/usr/local/bin/gamescope`,
`EASteamLauncher.exe` (the splash) was running. `gamescopectl
focus_info` showed focus, input and keyboard on the EA sign-in with no
override window, and the sign-in was fully visible.
- `tools.tests.test_prepare_windows_runtime`,
`test_gamescope_override_stacking` and `test_hud_frames` pass.
2026-11-05 16:58:52 -05:00
MaxsTechReview da851dae22 fix(input): taps land where touched while a game holds the pointer (#2133)
* fix(input): taps land where touched while a game holds the pointer

Games that hide the system cursor and draw their own, such as Stardew Valley, hold the pointer the same way first-person games do. Every touch on the default touch mode then went through the touchpad, so a tap clicked wherever the cursor already was: tapping New opened Load, and a tap after Back could press Exit.

A touch now stays a direct touch while the pointer is held. A tap clicks where the finger is, and once a swipe moves past the touch slop it turns the camera with relative motion as before. Touchpad mode is unchanged.

* fix(input): keep the swipe anchor when the first finger lifts

When the first finger of a locked-pointer swipe lifts and another stays down, the next move is reported for the remaining finger. The anchor now moves to that finger, so the camera does not jump.
2026-11-05 16:29:19 -05:00
Kurt Himebauch 7a78d2b66b fix(agent): secure and refresh unrooted log exports (#2131)
## Summary
- Keep raw diagnostic staging in private cache and give each exported
archive a unique request directory.
- Remove that request directory after the CLI pull, including failed
transfers.
- Replace files from the previous extracted snapshot while preserving
unrelated destination files.
- Document that the old rooted-device fallback is not scrubbed.

Follow-up to #2061. The refreshed export tests now cover independent
archive outputs and stale snapshot replacement.

## Validation
- `wsl python3 -m unittest tools.tests.test_droiddeckctl` — 49 passed.
- `:app:testDebugUnitTest -x buildVkBridgeServer --tests
com.droiddeck.launcher.session.SessionLogShareZipTest.anArchiveCanBeWrittenOutsideTheShareFolder`
— passed.
- `git diff --check` — passed.
2026-11-05 15:57:27 -05:00
Kurt Himebauch 604a01d353 fix: map log destination errors 2026-11-05 15:41:47 -05:00
Kurt Himebauch e8ffe1164f fix: detect D3D12 capabilities and remove redundant game workarounds (#2134) 2026-11-05 15:37:46 -05:00
Kurt Himebauch 1ae37055c9 fix: preserve diagnostic staging failures 2026-11-05 15:30:21 -05:00
Kurt Himebauch a431c40b1b fix: preserve per-file diagnostic failures 2026-11-05 15:14:23 -05:00
Kurt Himebauch 285ff7956b test: cover independent agent log exports 2026-11-05 15:09:04 -05:00
Kurt Himebauch 64f114c96f fix: detect graphics capabilities and drop redundant title overrides 2026-11-05 15:03:08 -05:00
Kurt Himebauch 9f22f63222 test: verify independent log export paths 2026-11-05 14:59:37 -05:00
Kurt Himebauch 0bb8384c76 fix: avoid unsupported ray tracing on Stellar Blade startup 2026-11-05 14:53:15 -05:00
Kurt Himebauch 671c4a623f test: assert cleanup after failed log pull 2026-11-05 14:51:17 -05:00
Kurt Himebauch dfc0fd4340 fix: harden diagnostic staging lifecycle 2026-11-05 14:49:11 -05:00
Kurt Himebauch 11c0763fba fix: preserve log cleanup failures 2026-11-05 14:42:22 -05:00
Kurt Himebauch ff686d7c3b fix: expire stale log exports safely 2026-11-05 14:42:22 -05:00
Kurt Himebauch 4765644451 fix: harden log export transactions 2026-11-05 14:42:22 -05:00
Kurt Himebauch 718712eb01 fix: secure and refresh unrooted log exports 2026-11-05 14:42:22 -05:00
Kurt Himebauch ca0689d254 fix(steam): default listed titles with a bundled arm64 Linux build to FEX (#2126)
Refs #1774.

Maldhalla (4385780) ships its x64 and arm64 Linux builds side by side in
one depot. Its launcher starts the arm64 build when it runs on an Arm
host, natively or as an x86 shell under FEX. Steamworks offers no arm64
value for a launch option's CPU architecture, so the appinfo still reads
`oslist` linux+windows and `osarch` 64. `native_targets()` therefore
leaves the title on the Proton ARM64 default, which runs the Windows
build.

This PR adds `FEX_DEFAULT_APPS`, an explicit allowlist (the opt-in
suggested in #1774). A listed title defaults to `droiddeck-fex` instead
of `NATIVE_CHOICE`. Nothing else changes:
- A player's own pick is kept, as before.
- Unlisted titles keep today's behaviour, including the Factorio
native-choice case.

On the Snapdragon 8+ Gen 1 tablet from #1774, our benchmark scores 8–11
with the Windows build under Proton ARM64 and above 100 with the native
arm64 build.

Test:
`ProtonDefaultTest.test_a_listed_title_with_a_bundled_arm64_linux_build_defaults_to_fex`
in `tools/tests/test_game_environment.py`. It fails without the change.
`ProtonDefaultTest`, `test_droiddeck_proton_default`,
`test_steam_compat_mapping`, `test_appinfo_lookup` and
`test_steam_compat_live` pass.
2026-11-05 14:41:54 -05:00
phj1081 7e0859872b fix(steam): keep a cleared FEX default for a listed title as a native choice
A listed title whose automatic FEX mapping the player clears now records
the empty mapping as a native choice, as titles with NATIVE_CHOICE do,
instead of getting FEX again on the next run.
2026-11-05 14:22:15 -05:00
phj1081 28d4f58f33 fix(steam): default listed titles with a bundled arm64 Linux build to FEX
Steamworks has no arm64 launch-option architecture, so a title that ships
x64 and arm64 Linux builds side by side still reads as osarch 64 and gets
the Proton ARM64 default. List such titles in FEX_DEFAULT_APPS so they
default to Linux x86 (FEX), whose launch runs the bundled arm64 build. A
player's own pick is kept, and unlisted titles are unchanged.

Refs #1774
2026-11-05 14:22:15 -05:00
Kurt Himebauch ee0d8d9997 fix: guard session clock logger lifecycle 2026-11-05 14:00:30 -05:00
angelrenefm 5805631be5 Session logs: record clocks, thermal status and battery temperature in every session
ClockLogComponent was only created inside startVkBridge(), so it never ran
on Adreno. Create it for every session (clocks.log, was vkbridge-clocks.log)
and append the battery temperature from ACTION_BATTERY_CHANGED to each
sample.

Part 1 of #1837.
2026-11-05 14:00:30 -05:00
K baf7d90df9 build: drop the unused androidTest variant so the VS Code Kotlin import works
The project has no instrumented tests. The Kotlin extension's Gradle import
reads the debugAndroidTest outputs without running their tasks and fails.
2026-11-05 14:00:11 -05:00
K11MCH1 cb3c3cb2c9 Added games: a reused ranking is not remembered as a fresh walk
A ranking a scan took from added-games-scan.json was also put in the
60-second in-memory cache with the current time, so for the next minute
single(), addFolder() and the exe picker, which ask for a fresh walk, got
a ranking that could be a week old. Kept rankings are now returned without
entering that cache, so it only ever holds rankings from real walks.
2026-11-05 13:59:59 -05:00
K11MCH1 df60fdb194 Added games cache: skip bad records, expire names on read, retry failed saves
A record whose fields could not be decoded threw while the cache loaded,
and since every scan loads it first, one bad record blocked every scan;
each record is now decoded on its own and skipped if it is malformed.

name() returned names older than a week that were still in memory; it now
applies the same age limit as rank().

save() cleared its dirty flag before writing and called startWrite outside
its try, so a write that failed was never retried and could replace the
scan's result with an exception; a failed write now leaves the cache dirty
and is tried again on the next save.
2026-11-05 13:59:59 -05:00
K11MCH1 93923e125d Added games: one scan at a time, and keep what it found across launches
Every Steam session scans the added games before the Linux side starts,
walking each game folder for its exe and reading the exe's version info.
On an AYANEO Pocket FIT Elite with 40 games on an exFAT SD card that walk
took 10 s with a warm cache and up to 155 s after Android had dropped it,
and the front end's art pass and library load ran the same scan at the
same time, so the session waited on all of them.

Scans now run one at a time, so a waiting scan reuses what the one before
it just found. Each game folder's exe ranking and identified name are kept
in added-games-scan.json; a scan reuses a ranking while the folder's
modification time is unchanged, its first exe still exists and it is under
a week old, and walks the folder again otherwise. The exe picker, single
game refreshes and adding a folder still walk the folder fresh.
2026-11-05 13:59:59 -05:00
K11MCH1 fcbd45453f Steam imports: read only the linked manifest, and leave staged ones alone
seed_install read the app's manifest from every library before checking
that it was the linked DroidDeck install, so a manifest in another library
that is not UTF-8 raised past the caller and stopped routing for later
games. It now returns before reading anything but the linked manifest.

An untouched manifest from an older version was migrated even while
steamapps/downloading/<appid> existed; that migration now waits until
Steam is not staging the app, like the rewrite of a queued import.
2026-11-05 13:59:44 -05:00
K11MCH1 1ec1c20be1 Steam: imported games with no known build update only when launched
An added game the account owns is linked into Steam as DroidDeck-<appid>.
When the folder carries no Steam build, Steam marks it Update Required and
checks every file before updating. That check reads the whole game, and an
interrupted one starts over, so on an SD card it ran again in every session
and kept the card busy: Lords of the Fallen (33 GB) restarted 51 times in a
day on an AYANEO Pocket FIT Elite.

The manifest for such an import now asks Steam to update the game when it
is launched. Imports that Steam already rewrote, but whose update has not
started, switch from "always keep updated" to "update when launched"; a
priority of 2, an update in progress, or an import with a recorded build is
left alone.
2026-11-05 13:59:44 -05:00
Kurt Himebauch ccab14384d test: isolate unrooted log archive output 2026-11-05 13:52:43 -05:00
angelrenefm 8cc652e812 fix(droiddeckctl): address review on logs latest
Stream each archive entry to disk instead of reading it into memory,
refuse an archive from a session that started after the state call, and
delete the exported zip from shared storage once it has been pulled.
2026-11-05 13:52:43 -05:00
angelrenefm 2fb066507a fix(droiddeckctl): logs latest works without root
`logs latest` pulled the session folder under getFilesDir(), which adb
cannot read on an unrooted device, so it always failed with "Permission
denied" and droiddeck-scenario kept no logs for failed runs.

The agent bridge gets a `logs` method that writes the Share logs archive
to the app's external files folder (agent-logs/), and droiddeckctl pulls
that zip and unpacks it into the destination with the session's files at
the top, as before. Builds without the method still get the old pull.
2026-11-05 13:52:43 -05:00
Kurt Himebauch 4b6cba0a6d fix(l10n): mark Russian percent string unformatted 2026-11-05 13:49:20 -05:00
flexeykinDev 4f831fbd34 l10n(ru): translate strings added since the last translation pass
Adds the 83 Russian strings that showed in English: extra Steam libraries, session logs, dual screen launcher and sessions, PC keyboard options, the bottom screen desktop, screen swap, the stats HUD and the Mali bridge GPU info.
2026-11-05 13:49:20 -05:00
Kurt Himebauch a28ff05981 fix: handle enhanced log chooser launch failures 2026-11-05 13:32:46 -05:00
Kurt Himebauch 2ab8a1bac3 test: validate enhanced log share descriptor reads 2026-11-05 13:32:46 -05:00
wallentx 7053058db6 Harden enhanced diagnostics collection and sharing 2026-11-05 13:32:46 -05:00
wallentx 44f8d5de70 Check the matching diagnostic manifest entry 2026-11-05 13:32:46 -05:00
wallentx 3c6447528a Make secure log export testable under Robolectric 2026-11-05 13:32:46 -05:00
wallentx 5614bd5d5a Add enhanced one-session diagnostic bundles 2026-11-05 13:32:46 -05:00
KeegantheVeganat0r b563b8d1eb fix(hud): translate low FPS toggles in all supported locales 2026-11-05 12:38:55 -05:00
KeegantheVeganat0r 6b887c42af feat(hud): add independent 1% and 0.1% low toggles 2026-11-05 12:38:55 -05:00
Omar Al Taki 3d1f0f63b9 fix(input): release a held mouse Back only when its own device disconnects 2026-11-05 12:13:51 -05:00
Omar Al Taki aad60fef5b fix(input): keep the right button down until every overlapping mouse Back is released 2026-11-05 12:13:51 -05:00
Omar Al Taki db8c66b107 fix(input): keep mouse Back routed to screen-key text fields while editing them 2026-11-05 12:13:51 -05:00
Omar Al Taki abb28792e8 fix(input): release a mouse Back right click on focus loss and pass nav Back through
Address review: a navigation-bar Back that arrives while an injected mouse
Back is held now stays Back instead of releasing the right button early.
A held right button is released on pause, focus loss and input-device
removal, like the other held Back handling. While the session is
suspended or still loading, Back is no longer sent to the guest.
2026-11-05 12:13:51 -05:00
Omar Al Taki 57b3623a9a fix(input): mouse right click no longer acts as Back in REDMAGIC cast mode
REDMAGIC's cast mode replaces a mouse's right button with a Back key that
names no device and lacks the virtual-hard-key flag that the navigation
bar and back gesture set. During a session, a Back from a mouse, or that
injected form while a mouse is connected, now presses the guest's right
button. The navigation bar, back gesture and controller Back still act
as Back, and so does a right click while a DroidDeck menu is open.

A mouse's Back and Forward buttons no longer also send a left click;
Android delivers them as key events.
2026-11-05 12:13:51 -05:00
Anton bae4ecd867 fix: leave run-mode apps unpinned when all cores are selected 2026-11-05 12:13:45 -05:00
Boran Miao 17fb80ad51 test: quote emulator installer paths with spaces 2026-11-05 12:09:56 -05:00
Boran Miao 5e0f5cb9cd fix: install bundled PPSSPP through the Flatpak helper 2026-11-05 12:09:56 -05:00
HCOOKIELYT 0544933abd fix(steam): show progress while applying client updates
Forward extraction and installation messages from the x64 bootstrap log and replace stale download percentages with the applying-update state. Add regression coverage for phase ordering and log forwarding.

Fixes #812
2026-11-05 11:56:42 -05:00
Kurt Himebauch fe1dd8ca72 test(battlenet): close the folder scan case 2026-11-05 11:56:37 -05:00
visail 969b7e625f fix: list Battle.net games installed under Blizzard's China uids
Blizzard's China client records some products under a CN uid, so the installs
it makes never reach the Library and their Stores card keeps offering Install:

  Diablo II: Resurrected  osic      (catalogue had osi)
  StarCraft II            s2_cn     (catalogue had s2)
  Diablo Immortal         anbs_cn   (catalogue had anbs)

title() now resolves those uids to the international title, so the Library, the
Stores "installed" state, the cover folder and the launch code all agree, while
Game.installUid keeps the uid the Agent recorded for updates and uninstalls.
TITLES and STORE are untouched.
2026-11-05 11:56:37 -05:00
sanogueralorenzo 1c24c600cd refactor: clarify adaptive stick hit testing and test setup 2026-11-05 11:43:46 -05:00
sanogueralorenzo d8c1bc6059 fix: extend left adaptive stick activation to the screen edge 2026-11-05 11:43:46 -05:00
uubooster-team e1fe6f99cf fix(network): handle gatewayless IPv4 VPN links
Provide compatible gateway metadata for IPv4 VPN links without changing
VPN routing, and keep text and JSON network snapshots consistent.

Add regression tests for gateway selection and network snapshots.
2026-11-05 11:43:30 -05:00
coffeemeist3r ef305c93cd fix(mesa): Steam's UI no longer crashes when scrolling the Home shelf fast on PowerVR
Zink's rebind_image() and zink_rebind_all_images() refreshed only a sampler
view's main image view after the resource got a new backing object. The
view's zs_view and cube_array surfaces stayed on the old object and were
freed with it, so the next descriptor update wrote a garbage VkImageView.

On PowerVR (needs_zs_shader_swizzle) every RGBX/BGRX view has a zs_view, and
Steam's web helper segfaulted in vkUpdateDescriptorSetWithTemplate within
seconds of fast Right presses on the Home shelf. The new Mesa patch rebuilds
all of the view's surfaces with init_sampler_view().
2026-11-05 11:28:51 -05:00
Systina12 f6f26d03f9 fix: quote GPU environment values before writing shell exports 2026-11-05 11:18:29 -05:00
dddms e72cddccd9 perf(steam): decode requested appinfo records only
Avoid decoding every app when Start without Steam resolves one game launch. Use wanted-app selection in the single-game helpers while keeping the existing field decoder and full-reader default for bulk callers.

Track partial coverage separately from complete results, share one immutable byte snapshot for incremental queries, and keep timestamp invalidation and warm hits. Preserve the last valid duplicate record and release the raw snapshot after a full decode.

Add format/cache/structural-budget and launch-resolution regressions, reuse the existing binary fixture encoder, and document the host-only performance scope.
2026-11-05 11:17:03 -05:00
vitamin663-sys 92a588ea16 fix(input): add radial stick dead zone setting 2026-11-05 10:53:15 -05:00
cphillamandmaxjivi05 e879375ca4 fix: upscale game buffers before native-resolution composition (#1367)
* fix: upscale game buffers before native-resolution composition

* fix: drop an added code comment from the effects source draw

* fix: effects source keeps pillarboxed games and near-full source rectangles

* fix: a larger opaque draw above keeps the full composition path

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-11-05 10:34:19 -05:00
Kurt Himebauch 7c5bf77f17 fix: fit output after pip resize 2026-11-05 10:18:37 -05:00
visail a0a7a66470 fix: use Blizzard's China hosts for Battle.net installs and update checks
The Agent's install request pointed at http://cn.patch.battle.net:1119/<product>.
That host only 301-redirects to the country's version service today; Blizzard's
China client asks cn.patch.battlenet.com.cn:1119 directly.

The update check built <branch>.version.battle.net, so a China install (branch
cn) asked cn.version.battle.net, which does not answer; the China service is
cn.version.battlenet.com.cn.

Only region cn changes.
2026-11-05 09:56:40 -05:00
kmnkv1 26c562c174 fix(stores): moving through the side menu no longer freezes the launcher (#2105) 2026-11-05 09:44:45 -05:00
Kurt Himebauch e1c2b0e340 ci: trust ASEbrahim for signed test builds (#2124) 2026-11-05 09:38:38 -05:00
MaxsTechReview d4fe440b49 docs: README install step links the current APK (#2122) 2026-11-05 08:43:24 -05:00
MaxsTechReview 4c23092c55 fix(components): repaired components reach prefixes that already have them (#2121)
fix(components): the library sweep refreshes the catalog when it is safe to

A game job reads the cached catalog, and the installed check compares against
the cached revision, so a repaired component never reached a prefix that
already had it. The sweep now fetches the catalog first when online and not on
a metered or constrained connection.
2026-11-05 08:43:21 -05:00
MaxsTechReview a309428a2a feat(battlenet): import, list and delete World of Warcraft addons (#2111)
World of Warcraft, Classic and Forever get an Addons tab in the Library. It imports addon zips into the right Interface/AddOns folder, lists the installed addons, and deletes one after a confirmation.
2026-11-05 02:04:03 -05:00
MaxsTechReview b9dbd006be fix(storage): Windows installs on a natively mounted card no longer ask for an administrator (#2100)
* fix(storage): Windows installs on a natively mounted card no longer ask for an administrator

Cards mounted directly as FAT, exFAT or NTFS were not treated as Android
storage, so wineserver reported an Anonymous owner and refused permission
changes. Its chmod on these volumes also no longer fails the request.

* fix(storage): keep NTFS owners and the chmod error
2026-11-05 00:47:38 -05:00
Kurt Himebauch a9d2d09e64 fix: restore single-screen session menu controls (#2110) 2026-11-04 22:15:03 -05:00
Kurt Himebauch 516e3862fa fix: refresh repaired components and render Ubisoft Connect (#2090)
* fix: refresh repaired components and render Ubisoft Connect

* fix: address component repair and Wine registry review

* fix: repair manually enabled components and check registry writes

* fix: serialize component repairs and use Proton runinprefix

* fix: report queued component repair failures

* fix: preserve newer component repair waits

* Make component queue transitions atomic

* Isolate component apply failures by game

* Preserve pending repair when catalog is stale

* Restore persisted component repair revisions

* Update pending requirements atomically

* Respect disabled pending components per game

* Keep queue key state atomic while worker waits

* Pass selected game to component repairs

* Preserve pending repair when component is absent

* Preserve pending revision on catalog failure

* Test legacy pending repair revision recovery

* Isolate fake worker Steam app info lookup

* Merge queued Steam app info lookups

* Persist launch component revisions before repair

* Gate launch on component repair discovery

* Expand and refresh launch repair discovery

* Include new automatic picks in launch requirements

* Log repair persistence failures and align repair keys

* Use runtime key for Battle.net component repairs

* Retain failed component reapplications for retry

* Harden pending repairs and launch preparation

* Isolate wait-cool sleep mocking

* Allow launches without catalog when no repair is pending

* Queue launch repairs even if persistence fails

* Block launch when repair state cannot persist

* Allow launch when no component repair is needed

* Persist selected repairs when catalog is offline

* Drain offline component repair in test
2026-11-04 21:54:00 -05:00
K11MCH1 1ee5e924b6 Merge remote-tracking branch 'upstream/main' into fix/gamescope-p20
# Conflicts:
#	tools/gamescope/release.env
2026-11-05 08:23:28 +09:00
MaxsTechReview ab92033b90 fix(desktop): Steam's own header moves and resizes its windows (#2097)
* fix(desktop): Steam's own header moves and resizes its windows

Steam never asks KWin to move or resize its windows, and its maximize
button can only maximize. Each normal Steam window now reports presses
on its header and edges, and KWin handles them natively. No extra title
bar is added and other apps are unchanged.

* fix(desktop): drag regions cover the elements inside them

* fix(desktop): find Steam's drag regions by position

The Friends window's drag region ignores pointer events, so a hit test
never finds it. The check now runs once a press moves, so plain clicks
cost nothing.
2026-11-04 17:47:00 -05:00
Kurt Himebauch d62f017bce Add session log management to setup diagnostics (#2099)
* feat: manage session logs in setup

* fix: address session log review feedback

* fix: honor device date and time settings
2026-11-04 16:05:56 -05:00
Delete228 68b0396dfe Merge upstream PC input and dual-screen updates 2026-11-05 00:00:34 +03:00
MaxsTechReview f005241549 fix(desktop): download language packs from the release that has them (#2096)
* fix(desktop): download language packs from the release that has them

The Ubuntu runtime's release has no language packs or index, so the
desktop stayed in English for every language.

* fix(desktop): reinstall language packs when their release changes
2026-11-04 15:09:30 -05:00
MaxsTechReview bba40cd69c fix(desktop): a game's window follows its own window mode (#2094)
* fix(desktop): a game's window follows its own window mode

Desktop games ran in a titled gamescope window whatever mode they asked for. gamescope now fills the desktop for a fullscreen or borderless game and matches the window size of a windowed one.

* Use the p30 gamescope test build

* fix(desktop): a windowed game too big for the desktop is maximised

* fix(desktop): a decorated game window stays a window

* Use the published gamescope p21
2026-11-04 14:59:39 -05:00
Kurt Himebauch f3d6efbcfa docs: update download badge to 0.6.9 2026-11-04 14:14:07 -05:00
MaxsTechReview f1c1ca59fe fix(battlenet): use the right artwork for each game (#2092)
* fix(battlenet): use the right artwork for each game

World of Warcraft: Forever took retail art and its logo from a loose name lookup, and only fetched its own art when the Store tab was opened. Shop pages also supplied trailer stills and seasonal promos.

Each game now has chosen Blizzard key art with the shop page as a fallback, installed games fetch their art without opening the store, and lookups by name must match exactly. Art found by the old lookup is fetched again once.

* fix(battlenet): keep cached art if the new art cannot be fetched

Classic's variants fetch through the shared title, and a failing chosen image falls back to the shop page.
2026-11-04 14:12:16 -05:00
5cf69732ef fix(input): make a hardware mouse and keyboard work like a PC (#2053)
* feat(gamescope): a three way switch for relative mouse motion

Games that confine the cursor and warp it back to read movement never
reach the nested hints rule, so their aim stays bounded by the screen.
The convar defaults to 0 and leaves that rule alone; 1 forces relative
motion for the focused game, 2 keeps the pointer absolute even when the
rule would apply.

(cherry picked from commit db3d7555e0)

* feat(input): the session carries a relative mouse mode per game

Reads the mode from the game's gesture config, hands it to gamescope as
gamescope_relative_mouse, and pushes a change to the running session
through gamescopectl.

(cherry picked from commit e350d88464)

* feat(drawer): the relative mouse switch is a three way choice

Auto leaves it to the game, on forces relative motion, off keeps the
pointer absolute. Named in all 32 locales so none of the three falls
back to English.

(cherry picked from commit e96a187e02)

* fix(input): follow the focused game's relative mouse mode

Switching to a game whose saved mode differs left the session on the
previous game's mode, since only the drawer state was refreshed. The
requests also ran on a thread per selection, so two quick picks could
reach gamescope out of order.

(cherry picked from commit 7ff8c5d568)

* fix(input): retry the relative mouse mode gamescope did not take

The drawer state moved before the request ran, so a failed request left
the two out of step and the next focus change skipped the resend.

(cherry picked from commit cf0f82d8cd)

* fix(input): the relative mouse mode tracks the latest choice

The guest agent answers ok when it handled the exec, so a failing
gamescopectl was recorded as applied. Reconcile the newest desired mode
on the worker thread after each request, and only mark applied when the
command really exited zero.

(cherry picked from commit 5f5e3bd52e)

* gamescope: a hidden cursor under any pointer constraint is relative

Games that hide the cursor and confine the pointer had no lock hint, so the
nested pointer stayed absolute and the camera froze.

* Treat a hardware mouse as a real mouse in sessions

A mouse now moves the guest pointer by relative motion for the whole session
through pointer capture, so it no longer stops at screen edges or reaches
system gestures. Its cursor stays visible in every touch mode, follows the
game showing or hiding it, and clicks no longer press on-screen controls.

* gamescope: use the mouse test release

* Direct touch turns the camera while a game holds the pointer

A swipe in direct touch now sends relative motion when a game has the pointer
locked, including when the lock starts during the swipe, as when a game
hides the cursor while a button is held.

* Address review findings on mouse and touch input

Keep touchpad taps on on-screen controls, cancel a pending long press during a
locked swipe, let the three-finger gesture cancel a locked swipe, refresh
capture when the output changes, and use the menu's key for the relative
mouse setting.

* Keep locked touches inside the game picture and free the mouse in touch editors

* End touch editor ownership when the editor goes away

* Bring the mouse cursor back when a touch editor closes

* Release a pointer lock at the game's pointer

* gamescope: use the pointer position test release

* Fit the X screen to a fullscreen window larger than it

* gamescope: use the X screen fit test release

* Return the X screen when no game window has focus

* gamescope: use the X screen return test release

* ci: avoid redundant gamescope builds

* fix: hardware mouse in Desktop and pointer warping games

KWin nested on the host only reads relative motion while its own pointer lock is on, so a program that locked the pointer in Desktop (Blender orbit and pan) stopped receiving motion. A captured hardware mouse now turns that lock on, and a real Right Ctrl no longer toggles it.

Gamescope follows a game's pointer warps instead of overriding them, and absolute motion also sends raw motion. Also retries relative mouse requests, tidies the unplug path, passes side buttons and fits a second oversized window correctly.

* chore: use the gamescope build with patch 0131

* fix: Desktop mouse lock keeps retrying after early attempts fail

* fix: hold the key that turns on the Desktop mouse lock

KWin ignores a press and release delivered together.

* fix: gamescope enters warp following sooner and keeps steady motion

* chore: use gamescope p26

* Capture system keys in a focused session and keep the first Desktop mouse motion in place

* Use the p27 gamescope build

* Update the system keys row test

* Recognise a captured mouse so the Desktop grab can start

* Keep touch warps out of relative motion

* Send Esc from keyboards that report it as Back

* Use the p28 gamescope build

* Release captured keys on pause, forward motion during a grab and wait for a pending screen fit

* Use the p29 gamescope build

* fix(input): keep held system keys consumed until they are released

Copy each captured key event, since the service owns the original only for the callback, and consume the physical release of a key already released to the guest.

Use the published gamescope p20.

* fix(input): capture a system key only from its first press

---------

Co-authored-by: kingokksa <[email protected]>
Co-authored-by: maxjivi05 <[email protected]>
2026-11-04 13:27:08 -05:00
Kurt Himebauch eed9edaa1c feat: consistent bottom screen bar and menu-first navigation in sessions (#2084) 2026-11-04 13:26:35 -05:00
MaxsTechReviewandmaxjivi05 940efdab3a Reduce DXVK render-path pacing stalls on Adreno 722 (#2066)
Co-authored-by: maxjivi05 <[email protected]>
2026-11-04 12:44:26 -05:00
MaxsTechReviewandAlbert b7878d2841 fix(battlenet): keep games installed inside Battle.net's folder running (#2087)
fix(battlenet): keep games installed inside Battle.net's folder running

A game installed under C:\Program Files (x86)\Battle.net\ (for example
World of Warcraft: Forever in "Battle.net\World of Warcraft\_forever_")
was counted as part of Battle.net itself. Launched from DroidDeck, the
launch never saw a game, said "Battle.net closed before a game started",
stopped everything left in the prefix, game included, and exited 1.

Battle.net's own files are now only those directly in its folder, in its
Battle.net.<build> folders and under ProgramData\Battle.net.

Co-authored-by: Albert <[email protected]>
2026-11-04 12:13:34 -05:00
Kurt Himebauch 430af5e22d feat: desktop and scrolling wall live wallpapers (#2012)
* feat: desktop and scrolling wall live wallpapers selectable from Android settings

* fix: render the wall wallpaper off the main thread and give it its own thumbnail

* fix: serialize wall wallpaper visibility and wait out in-flight draws on surface destroy

* fix: restart the wall loop on surface recreation and keep queued visibility changes

* fix: keep the wall loop alive across a failed canvas lock and finish redraws before returning

* fix: post the wall redraw asynchronously on surface change

* fix: redraw the desktop wallpaper logo from the real mark and show game covers on the wall

* chore: mount the git dir in build containers and seed new worktrees from the main checkout

* fix: decode wall covers as RGB_565 and seed worktree assets only into empty dirs

* feat: drop the desktop wallpaper and add frame rate and speed settings to the wall

* feat: wall settings over the live wallpaper with a match-screen rate, and drop the center vignette

* perf: pace the wall on vsync and draw it on a hardware canvas

* fix: stage worktree seed copies and publish them only after they succeed

* fix: point the wall settings link at the activity class so the picker shows the gear

* fix: dedupe wall covers by app and space repeats like the in-app wall, keep the frame loop alive across surface gaps

* fix: show the picker preview behind wall settings and drop the display-rate lookup

* fix: stop wall frame callbacks when the surface goes and only replace an empty seed destination

* fix: skip a seed when the destination reappears before the staged copy is moved in
2026-11-04 11:38:54 -05:00
Moroni Granja 0110dc3c0f fix: a GOG install launches the game, not its config tool (#1644)
Blood Omen ships dxconfig.exe beside kain.exe and the shortcut pointed at
the config tool: pickExe kept the first .exe the folder listing returned
when no title match was found. Read the game's play task from GOG's own
goggame-<id>.info manifest first - a primary game task, or a primary
launcher task for launcher games, and the product's manifest ahead of any
other goggame-*.info - then the product's temp_executable; without either,
rank the folder's exes so a word of the title wins, config and tool names
lose, and depth then size break the remaining ties.

GOG's galaxy_*.exe support installer is not the game either. The Witcher:
Enhanced Edition ships galaxy_the_witcher_enhanced_edition_2.1.0.15.exe
beside launcher.exe, and its build metadata names that file as the
product's temp executable, so launching it starts a 32-bit installer that
stops at "Path not found" under ARM64 FEX - the same dialog as the setup
stub #1174 removed. Play tasks, the hint and the name search skip it.

Fixes #680
Fixes #694

Only a primary launcher task counts as a launch task: GOG files the
configuration tool beside a game as a non-primary launcher, so it now falls
through to the executable search instead of becoming the game.

The temp_executable hint is matched by basename with either separator and
must resolve inside the install folder, so a nested galaxy_*.exe is skipped
too and a hint cannot point outside the game.

GOG runs a task from the working directory it declares, and from the install
root when it declares none. That directory now reaches the sidecar and the
shortcut's Start In: a game in a subfolder (System/witcher.exe) starts where
GOG starts it instead of in System/, where it cannot find its Data folder.
2026-11-04 11:38:02 -05:00
MaxsTechReview d849e6ae0a fix: Home screen setting keeps DroidDeck in RedMagic Game Space (#2083) 2026-11-04 09:06:37 -05:00
MaxsTechReviewandmaxjivi05 26274114a3 ci: avoid redundant gamescope builds (#2070)
Co-authored-by: maxjivi05 <[email protected]>
2026-11-04 00:38:17 -05:00
Kurt Himebauch 45fb017496 perf(steam): batch compatibility mapping updates (#1755) 2026-11-03 22:01:41 -05:00
Kurt Himebauch a2a05ebc5b fix: preserve the runtime PATH when overriding guest binaries (#1826) 2026-11-03 22:00:41 -05:00
Kurt Himebauch ad0d39f457 fix(quest): gamescope no longer aborts at start with vk.WaitSemaphores -13 (#1938) 2026-11-03 21:58:49 -05:00
Kurt Himebauch 1915e16fe1 fix: controller focus moves into an opened folder (#1962) 2026-11-03 21:56:57 -05:00
Kurt Himebauch c1b8f4c706 fix(hud): show the GPU clock in the Steam overlay on Adreno without root (#2059) 2026-11-03 21:54:55 -05:00
Kurt Himebauch 306cd789df feat(droiddeckctl): thermal and wait cool (#2062) 2026-11-03 21:54:29 -05:00
angelrenefm 8e480d0e84 feat(droiddeckctl): thermal and wait cool
Nothing in droiddeckctl reported temperature, so a before/after
comparison could start one run hot and the next cold.

`thermal` reads the battery temperature from dumpsys battery, the
thermal status from dumpsys thermalservice, and each CPU policy's
scaling_max_freq against cpuinfo_max_freq (capped when lower), all
readable over plain adb. `wait cool --below <C>` polls it every 10 s
until the battery is under that temperature, or exits 5 at --timeout.
2026-11-04 00:13:31 +01:00
angelrenefm 3ad13b1b66 fix(hud): bind the GPU clock and load nodes even when the HUD starts hidden
With the overlay hidden at session start the blocked feeds have no value,
so cur_freq and perf_now were never written before the binds were taken
and stayed unbound for the whole session.
2026-11-04 00:07:13 +01:00
angelrenefm 7901e76561 fix(hud): see the Steam session's mangoapp config when deciding whether the HUD is shown
Steam sessions point mangoapp at /tmp/droiddeck-mangohud.conf, so
OverlayShown never saw the overlay and the GPU clock stayed at 0 MHz.
2026-11-04 00:04:30 +01:00
MaxsTechReview f3650d3d37 Reduce on-screen stick load and honour Low latency off (#2048)
* Reduce on-screen stick load and honour Low latency off

On-screen controls requested unbuffered touch on every press, so turning
Low latency off had no effect on them. They now follow the setting.

Every stick move also redrew the whole overlay. Redraws during movement are
capped at 30 per second in both overlays, and stick moves under a pixel
are dropped in the standard overlay.

* Keep full stick deflection reachable

Moves under a pixel were dropped even at the edge of the stick, so the pad
could stop just short of full range.
2026-11-03 16:12:08 -05:00
Kurt Himebauch ad90d9e200 fix: find Steam libraries whose installs have no appmanifest (#2054) 2026-11-03 15:56:12 -05:00
Kurt Himebauch a9f365851e fix: fall back to completed manifests when depot.config lists none 2026-11-03 15:14:58 -05:00
Kurt Himebauch 4cc117b45b fix: read DepotDownloader's compressed protobuf depot.config 2026-11-03 15:05:04 -05:00
Kurt Himebauch 0cc187025f fix: read installed depots from completed manifest names too 2026-11-03 14:54:33 -05:00
Kurt Himebauch be5f1560b2 fix: find Steam libraries whose installs have no appmanifest 2026-11-03 14:39:45 -05:00
Kurt Himebauch 9f6c73442b fix(quest): answer denied KGSL timestamp reads so Steam starts on Quest 3 (#2050) 2026-11-03 13:06:57 -05:00
Daniel Noam 627aea0e8f Merge main 2026-11-03 19:32:54 +02:00
Kurt Himebauch edbe0c7179 fix(quest): drop a KGSL context's tracking before its destroy ioctl and restore it on failure 2026-11-03 10:55:54 -05:00
Kurt Himebauch ccd317384d fix(quest): ignore a finished wait when its KGSL context was recreated 2026-11-03 10:30:50 -05:00
Kurt Himebauch 9388ef07d8 fix(quest): grow the timestamp table instead of evicting an active context 2026-11-03 10:26:24 -05:00
Kurt Himebauch b7e550ae89 fix(quest): keep active contexts tracked and answer only queued and retired reads 2026-11-03 10:20:49 -05:00
Kurt Himebauch 4e571f1f99 fix(quest): answer denied KGSL timestamp reads so Steam starts on Quest 3 2026-11-03 10:09:44 -05:00
K11MCH1 5766696555 gamescope: ship p20 so the EA sign-in stays above the launcher splash
Patch 0126 (#2037) is in the source tree, but the app still stages the
p19 binary pinned here, built on Oct 29 before the patch existed. A fresh
install keeps drawing the EA Steam Launcher splash over the EA sign-in.

p20 is the gamescope-patched archive from the main build of 951270bc
(Droid-Deck/DroidDeck actions run 41637838020): patches 0002-0126
applied, gamescope tests Ok 2 / Fail 0.
2026-11-03 22:41:58 +09:00
K11MCH1 951270bc8f gamescope: EA sign-in stays above the EA launcher's splash (#2037)
* gamescope: EA sign-in stays above the EA launcher's splash

The EA app's Steam launcher shows the game's splash as a layered tool
window, and the EA app, another process, opens its sign-in window
topmost. gamescope took the splash as the sign-in's override window, so
the splash was painted over the sign-in and became gamescope's keyboard
focus window (Burnout Paradise Remastered). With patch 0126 a window from
another process that is neither topmost nor transient for a topmost
focus window is no longer an override candidate for it, as on Windows.
map_win now also reads _WINE_HWND_EXSTYLE: gamescope only took it from a
PropertyNotify, which it listens for from map on, so a style Wine set
before mapping was never known.

* gamescope: the topmost rule needs both windows' pids

gamescope keeps a window's pid at -1 when XRes cannot name its client,
so an unknown pid next to a known one is not evidence of two processes.
Patch 0126 now applies the rule only when both pids are known. The patch
carries function names in its hunk headers, and the test also checks
that its map_win hunk reads _WINE_HWND_EXSTYLE into hasHwndStyleEx (a
check of the patch text, not of a running gamescope).
2026-11-03 07:41:24 -05:00
K11MCH1 b7b08a40e8 Steam: EA games install the EA app (#2036)
The redistributable seeder marks every process in a game's install scripts
as already run, except Vulkan runtime and Ubisoft Connect installers. EA
games on Steam list the EA app installer (EADesktopSetup) there and start
through steam2ea://, which only the EA app handles. With the installer
skipped, Proton could not open that link (ret 31) and the game closed
without a window (Burnout Paradise Remastered). The EA app installer is
now left to run like Ubisoft Connect's, and a marker an earlier seeding
wrote is taken back out, so an existing prefix installs it on its next
launch.
2026-11-03 07:41:01 -05:00
Kurt Himebauch 8eeea5ef5a feat: add size, opacity and hidden-key settings for the PC keyboard (#1897) 2026-11-03 07:01:33 -05:00
Kurt Himebauch 61cbf91150 fix: the navigation bar stays dark when the launcher is not fullscreen (#1975) 2026-11-03 06:58:51 -05:00
Kurt Himebauch 3dff7d3962 fix: Project Zomboid runs on G1 and the C1 JIT instead of ZGC (#2016) 2026-11-03 06:56:20 -05:00
MaxsTechReview 2b790a8a71 fix(runtime): keep the Linux desktop installed across a runtime update (#2021)
* fix(runtime): keep the Linux desktop installed across a runtime update

A runtime update replaces the whole runtime tree, and the desktop pack
is built against one runtime, so every update removed the installed
desktop. Since the runtime started updating by itself, this happened
without the user doing anything: the next Open showed "Install & open"
and downloaded the whole desktop again.

When the desktop was installed before the update, the same update job
now installs the new runtime's desktop pack right after the runtime,
with the update's progress. If that install fails, the runtime update
still counts as done and Open offers the install as before. A repair
from the session no longer installs the desktop a second time when the
runtime install already did.

* fix(runtime): roll back a failed desktop restore after a runtime update

If putting the desktop back fails after its archive started unpacking,
the install journal stayed behind, and every later launch, Steam
included, tried to resume it and stopped when that failed. The update
now rolls back the partial desktop with the desktop removal, which
restores the runtime's own files and clears the journal. Open then
offers the desktop install as before.

Whether the desktop was installed is now checked on its own, so a
repair of a damaged runtime also restores it.

* fix(session): do not install the desktop again after resuming it

Opening the desktop with an interrupted desktop install resumed that
install and then installed the desktop a second time. The second
install is now skipped when the resume already completed the desktop.
2026-11-02 21:20:18 -05:00
Kurt Himebauch 4b8f63d84d feat: Mali Vulkan support through MaliBridge (#2027) 2026-11-02 20:23:21 -05:00
Kurt Himebauch 3dcb3eac42 chore: revert the Mali Vulkan squash to re-land it with history (#2026) 2026-11-02 20:22:29 -05:00
张先生 1f91283f40 feat: Mali Vulkan support through MaliBridge (#1950) 2026-11-02 20:16:48 -05:00
LingWindT c43a493ad8 preload: set PR_SET_CHILD_SUBREAPER so double-forked Xwayland is not orphaned to init (#1716) 2026-11-02 20:00:31 -05:00
Kurt Himebauch 80da86a5d3 fix: resolver order is active DNS, discovered DNS, public, then gateways 2026-11-02 19:58:58 -05:00
Kurt Himebauch ece3fec8ba fix: the active network's first three DNS servers stay ahead of public fallbacks 2026-11-02 19:43:57 -05:00
Kurt Himebauch 3a4c6a4a7a fix: DNS gateway pick and public fallback slot, drop reference to an unshipped script 2026-11-02 18:56:45 -05:00
Kurt Himebauch 0a0bbb68d1 fix: a stale session's startup failure no longer stops the newer session (#1984)
* fix: a stale session's startup failure no longer stops the newer session

* fix: stale session failures and queued-launch cleanup are checked against the current session under the stop lock
2026-11-02 18:44:12 -05:00
Kurt Himebauch 34a1cbbead fix: keep PRoot out of the APK, VPN DNS first, bridge failure exits, HUD polling stays on 2026-11-02 18:17:00 -05:00
Kurt Himebauch f05e0df39e Merge remote-tracking branch 'origin/main' into review/malibridge-1950 2026-11-02 18:14:00 -05:00
Moroni Granja cdde9d3390 fix: a GOG game whose installer cannot run gets its install registry (#2022) 2026-11-02 18:06:08 -05:00
Kurt Himebauch cae095ce30 fix: download monitor polls off the main thread so slow storage can't ANR the session (#2020)
* fix: download monitor polls off the main thread so slow storage can't ANR the session

* fix: cancel the queued download poll before the service shuts its executor down

* fix: ignore download monitor submissions that arrive after the service shut it down
2026-11-02 17:53:02 -05:00
Kurt Himebauch 5fe3021e5e feat: suggest Steam libraries found on internal storage and cards (#2023)
* feat: suggest Steam libraries found on internal storage and cards

* fix: add found card libraries by their readable path and drop superseded scan results
2026-11-02 17:40:55 -05:00
MaxsTechReview bc5edb81db fix(battlenet): list World of Warcraft: Forever in the Library (#2018)
Forever was missing for two reasons. Its install was not a known title,
so the scan skipped it as "not listed". And every WoW version installs
into the same World of Warcraft folder, so once one version was listed
the Library dropped the others as the same game.

- Forever is a title: launch code WoWF, product wowf, folder _forever_.
- An install whose uid is not known is matched by the product code
  product.db records for it.
- When versions share the folder, the one listed first keeps it and the
  others use their own version folder (_forever_, _classic_, ...). Each
  title's folder is remembered, so a shortcut keeps its app id when
  another version is installed or removed.

Games installed before this update are listed on the next Library scan.
They do not need to be installed again.
2026-11-02 17:05:16 -05:00
The412Banner 8d3616eccd feat(dual-screen): swap screens - game on the bottom screen, panel on top (#2006)
Swap screens for dual-screen handhelds: the game moves to the built-in
bottom screen while the top screen shows the Dashboard or the KDE desktop.
Swap button in the Dashboard and Desktop bars and a Show the game on
row in the session menu. The desktop resizes to the screen it is on,
Follow screen and Match screen sessions take the bottom screen's shape,
the keyboard opens on the top screen, and the game swaps back on session
end, picture-in-picture, glasses, or when the bottom screen is taken.
Review follow-ups cover desktop restart, refresh pacing, key and pointer
ownership, touch cancellation and bottom surface recovery.
2026-11-02 21:43:22 +00:00
Kurt Himebauch d2d5540ff1 fix: Project Zomboid JVM rewrite drops other collectors and covers Windows vmArgs 2026-11-02 14:32:36 -05:00
Kurt Himebauch 3fbd86c89d fix: bottom screen frame graph updates smoothly and no longer looks jagged (#2013) 2026-11-02 14:29:39 -05:00
Kurt Himebauch ce3f31a48c fix: Project Zomboid runs on G1 and the C1 JIT instead of ZGC 2026-11-02 14:20:41 -05:00
Kurt Himebauch 89a2ae87d7 fix: drop the HUD watermark and use the wordmark in headers (#2011) 2026-11-02 13:39:53 -05:00
Daniel Noam 730acbeeb7 Merge main 2026-11-02 18:48:59 +02:00
FX1922 3583643a11 feat: Add the Game Jolt store (#1986) 2026-11-02 10:05:30 -05:00
Kurt Himebauch 0050b5eb6b fix: docked HUD tab FPS number sits between the old and halved sizes (#2003) 2026-11-02 09:23:22 -05:00
Kurt Himebauch b16b67e6c4 feat: desktop session follows the app language (#1971) 2026-11-02 09:08:48 -05:00
Kurt Himebauch 55362d68ef fix: wall scrims overhang the window so no edge pixels stay unshaded (#1994) 2026-11-02 07:03:20 -05:00
Kurt Himebauch 2bc6985ae3 feat: choose Steam or Desktop to open when DroidDeck starts (#1993) 2026-11-02 07:03:02 -05:00
The412Banner 55da0afd06 fix(hud): GPU clock and load without root on Adreno 8xx and hardware-scheduled kernels (#1981)
On phones where Android blocks the GPU's sysfs files for apps, the HUD
reads the GPU without root (#1969). On Adreno 8xx that showed no GPU
clock and only the partial load from Vulkan timing (OnePlus 13, Adreno
830, reported by iamTooShaar).

- GPU clock: the clock job used the Adreno 6xx/7xx registers. The
  register layout is now picked from the KGSL chip id: on 8xx
  CP_ALWAYS_ON_COUNTER is 0x8e7 and the RBBM cycle countable is 1.
  Unknown later generations read nothing.
- GPU load: hardware-scheduled kgsl kernels (every 8xx, and some 7xx
  such as the AYANEO Pocket FIT's) never write the memstore ringbuffer
  timestamps. When those stay 0, the reader samples the contexts' own
  memstore entries (every context that has submitted work, up to 64,
  contexts with new or running work first).
- HUD diagnostics report: a "GPU readers without root" line with the
  reason each reader works or fails, "none" instead of "null", and the
  per-core CPU % source named honestly.

Tested: iamTooShaar's OnePlus 13 shows GPU clock and load with the PR
test build (06be2c5). Pocket FIT standalone build of the final readers
against the kernel's sysfs numbers: idle 1-2 % vs 3-4 %, GPU job loop
25-29 % vs 28-30 %, clock 1050 MHz vs 1050 MHz. The review fixes after
the test build were not tested on the OnePlus 13.
2026-11-02 10:07:26 +00:00
siyuan.qsy 2fdc8b0841 docs: preserve unrelated settings when recovering guest PATH 2026-11-01 23:35:27 -08:00
MaxsTechReviewandKurt Himebauch 313b6a59d2 feat(dual-screen): single-screen mode on the Retroid Pocket Duo and AYN Thor while another window holds the bottom screen (#1979)
* feat(dual-screen): step aside on the Retroid Pocket Duo's bottom screen while another window holds it

When the Duo's double-Home performance panel or another app (Chrome, YouTube) is on the bottom screen, DroidDeck switches to single-screen mode: the launcher's bottom panel and the in-game bottom controls close and the top screen takes its single-screen layout. When the bottom screen is back on its home screen, dual-screen mode returns, with the in-game controls reopening in the mode they had.

The DroidDeck shortcuts accessibility service watches the bottom screen's windows on the Duo only. An app opened under DroidDeck's panel is caught by its window opening on that display; the Retroid panel and any app on top are read from the window list.

* fix(dual-screen): log the bottom screen's holders only when they change

* fix(dual-screen): the topmost window other than DroidDeck's decides who holds the bottom screen

Apps opened under DroidDeck's bottom panel are still listed beneath it, so DroidDeck's own windows are skipped and the next full-size window decides. Fallback home screens such as Settings' boot-time one no longer count as a home screen.

* fix(dual-screen): catch apps opened under the in-game bottom controls

The in-game bottom controls hide the windows beneath them from the window list, so an app opening a window on the bottom screen while only DroidDeck is listed now holds the screen until the list can see it.

* fix(dual-screen): keyboards never hold the bottom screen; watch the built-in panel only

Keyboard windows opening on the bottom screen no longer count as an app arriving, so typing with the in-game bottom keyboard keeps the controls up. Only the built-in bottom panel is watched, so an external display can't close the controls. An arrival recorded while a check runs is kept for the next check, and stopping the watcher clears the held state after any running check.

* feat(dual-screen): step aside on the AYN Thor's bottom screen too

The bottom-screen watch now runs on every handheld in DualScreen's known dual-screen models, the Retroid Pocket Duo and the AYN Thor, including model names that extend them such as AYN Thor Max.

* fix(dual-screen): only the current bottom-screen watch publishes; Thor variants match when the display type is hidden

* fix: session drawer top row scrolls and Stop stays an icon-only button

* fix: docked drawer drops the second-screen page; tapping the active mode returns to none

* fix: docked drawer drops the bottom-screen mode icons; the Mode page keeps them

* fix: restore the second-screen drawer page when docked

* feat: dual screen launcher and dual screen sessions are separate Automatic/On/Off settings

* fix: session drawer top row keeps its right alignment

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-11-01 23:14:18 -05:00
Kurt Himebauch 92ec0929ae fix: remove the Bottom screen HUD descriptions from the HUD settings (#1988)
* fix: remove the Bottom screen HUD descriptions from the performance HUD settings

* fix: remove the telemetry blurb, speed units row and tap hint from the bottom stats page

* fix: keep steamClientLabel
2026-11-01 22:17:24 -05:00
FX1922 57b7c99b9a fix: keep games being updated in the installed list for every store (#1989) 2026-11-01 22:05:39 -05:00
DroidDeck Merge 8171bf20fd Merge latest origin/main and resolve conflicts 2026-11-02 10:44:24 +08:00
DroidDeck Merge 99dce74b09 fix: address maintainer review (restore README, revert version bump) 2026-11-02 10:43:42 +08:00
The412Banner 40dfcd94f9 fix(game-env): DOOM (2016) keeps the swapchain image count it asks for (#1987)
DOOM (2016) asks for exactly two swapchain images and stops with "FATAL
ERROR: getSwapchainImagesKHR failed with error (VK_SUCCESS)" when it gets
more. Every game launch gets GAMESCOPE_WSI_MIN_IMAGE_COUNT=8 and
GAMESCOPE_WSI_ENSURE_MIN_IMAGE_COUNT=1, so gamescope's WSI layer gave it 8
images (3 with only the count removed) and the game crashed on start.

When DOOMx64vk.exe sits beside the launched program (DOOMx64.exe for a GOG
install, or DOOMx64vk.exe), both variables are removed and
vk_x11_strict_image_count=true is set, the option Mesa's driconf gives the
game. Other games keep the defaults. A per-game value in the environment
editor still wins.

Tested on an AYANEO Pocket FIT with DroidDeck 0.6.9 and the same values set
per game: the swapchain gets 2 images and the game reaches its main menu at
60 fps; before, it crashed on every launch.
2026-11-02 02:02:18 +00:00
Kurt Himebauch 84a04d9ead fix: Steam shows cached library art when it starts without reaching Steam (#1985) 2026-11-01 19:55:18 -05:00
The412Banner e1d241d723 feat(dual-screen): bottom-screen HUD, Line strip, strip under every bottom tab, Bottom screen HUD settings (#1982)
- In-game stats shows a bottom-screen HUD while no game sends dashboard data: 11 styles, tap to change; it reads the game's frames and the device itself, so it works with the top-screen HUD off.
- Line strip: one line like the top HUD's Strip (API + FPS, frame-time graph, 1 / 0.1 / 0.01 % lows, GPU, CPU with temperature, VRAM, RAM, clock); default strip above a game's dashboard; numbers that do not fit are left out by importance.
- The same strip under the tabs on every other bottom page during a session.
- Bottom screen HUD settings, shown even with the top HUD off: one switch for the whole bottom HUD, strip on every tab, and which numbers the Line strip shows.
- The bottom HUD redraws about 10 times a second (was about 3) and reads device stats twice a second.
- GPU load, clock and VRAM readers also run while the bottom HUD shows them (BottomHudPresence).
- Night style stays dim instead of flickering with the FPS.
- Review fixes: slow-frame ring 2048, tab strip redraw without gamescope frames, Proton name per game, Graphs' duration from the kept readings, Fahrenheit in Tiles, Rings and Cores.
2026-11-01 19:23:31 -05:00
Kurt Himebauch 6a30a9bb4b fix: docked HUD tab FPS number is half its previous size (#1983) 2026-11-01 18:38:36 -05:00
MaxsTechReview da665bb14f feat: Auto picks DD-Turnip on Adreno 7xx (#1978)
Auto now prefers the newest DD-Turnip release on the Adreno 7xx, 710/720/722 included, and falls back to the previous Banners-Turnip pick when no DD-Turnip pair is published or the build fails to start a session on the device. The 8 Gen 2 on One UI keeps its patched Banners build and the 6xx keep Banners-Turnip.
2026-11-01 17:57:57 -05:00
Kurt Himebauch e30e722f8b feat: manage several Steam libraries beside internal and the card (#1967)
* feat: manage several Steam libraries beside internal and the card

* fix: serialize Steam library registry edits, refuse folders inside a Games root, refresh games after changes

* fix: rename refreshes games, added x86-64 libraries join redist seeding and direct mapping
2026-11-01 17:35:05 -05:00
MaxsTechReview c5577478ca chore: remove source comments and unused code, translate every missing string (#1965)
* chore(i18n): translate every missing string in all locales

Adds the 87 strings the bottom-screen Dashboard and in-game stats pages
introduced to all 32 locales, plus the 51 older settings strings Japanese
and the 50 Chinese locales were still missing. Every locale now has all
3456 translatable strings with the same placeholders as English.

Also translates 36 entries that were still English where the language
does not use the English word, and makes es-419 touch_app_id use the same
%1$s placeholder as English.

* chore(patches): drop comments from the lines downstream patches add

Removes comment lines and trailing comments from lines our patches add,
with hunk headers recounted. Lines that a later patch uses as context
and the upstream backport 0123 are left as they are.

The gamescope, FEX, proot and xodus series still apply to their pinned
sources, and the patched trees differ only in those comments.
test_hud_frames now finds the HUD writer by its first declaration.

* docs: credit Greenlight, Better xCloud, viture-v2, GameNative and DiRTTelemetryErrorFix

These were credited only in source headers and NOTICE files.

* chore: remove source comments and unused functions

Removes explanatory comments from first-party Kotlin, Java, C, C++,
Rust, TypeScript, Python, shell, Gradle, CMake and workflow files.
License and attribution headers stay, and vendored third-party trees,
generated files and license files are untouched. A shellcheck directive
is kept, and udevmon's fallthrough comments became fallthrough attributes.

Syntax trees and token streams match the previous sources apart from the
comments and Python docstrings. Module docstrings that argparse prints
are kept, and device-smoke.sh prints the same --help text from a heredoc.

Also removes 37 functions and 4 fields that nothing referenced.

* chore: remove comments from the Syncthing integration and the latest main sources

* docs: credit Syncthing and the syncthing-android fork

* chore(i18n): translate the Syncthing strings in all locales

The 107 strings the Syncthing integration added are now in all 32 locales.
sync_web_path_locked names the Syncthing tab by its label.

* docs: credit @xXJSONDeruloXx for the HUD metric readers

The CPU, GPU load, thermal and battery readers come from their
PerformanceHudView pull requests #777, #877, #991, #1406 and #1573.
The FusionHUD credit line is unchanged.

* docs: credit the HUD metric readers to @xXJSONDeruloXx's work
2026-11-01 16:27:27 -05:00
Kurt Himebauch d730f0fa8b fix: docked HUD tab follows the HUD outline setting (#1976) 2026-11-01 16:20:35 -05:00
Kurt Himebauch 4798fadc12 docs: update README tagline (#1977) 2026-11-01 16:18:04 -05:00
Kurt Himebauch bf9435cd36 feat: KDE desktop on the bottom screen beside the session (#1966)
* feat: KDE desktop as a bottom-screen mode beside the top-screen session

* test: bottom-desktop launch test sets DISPLAY instead of inheriting it

* fix: drop the bottom desktop's launch when the session stops while preparing it

* fix: bottom desktop restarts on a fresh surface, trackpad/touch toggle, loading throbber, DroidDeck's display driver

* fix: bottom desktop pauses with the session and opens the keyboard for a tapped text field

* fix: no bottom desktop guest starts or keeps running unfrozen while the session is paused

* fix: bottom desktop treats a freeze in progress as paused

* fix: only the session that began a freeze clears its pausing flag

* fix: bottom desktop launch is stored and cleared only for its own session generation

* fix: a stopped session tears down the bottom desktop it started even after a newer launch is prepared

* fix: a new session's bottom desktop replaces one an earlier session left running

* fix: release the bottom desktop's keyboard watcher when its compositor dies

* fix: bottom desktop starts its guest only for the launch its compositor was attached for

* fix: bottom desktop ignores compositor replies to an earlier session's attach
2026-11-01 16:13:52 -05:00
Kirk fdea9f0435 fix: the navigation bar stays dark when the launcher is not fullscreen
From Android 14 the Material theme leaves the navigation bar
transparent, so with launcher fullscreen off a white system bar showed
under the dark launcher. AppTheme now sets the navigation bar to black,
matching the status bar.
2026-11-02 04:51:54 +08:00
The412Banner 8cb5c2168e feat(hud): GPU load and clock without root where Android blocks kgsl (#1969)
On some Adreno devices (the Retroid Pocket Duo) SELinux hides kgsl's sysfs
nodes from apps, so the HUD showed no GPU % and no GPU clock. Devices whose
nodes are readable keep using them; nothing changes there or on GPUs
without kgsl.

Where the nodes are blocked:

- GPU %: a native reader maps kgsl's memstore read-only (any kgsl client
  may) and samples the ringbuffer start/end timestamps once a millisecond
  while a HUD is shown. That covers all GPU work, Android's own
  composition included.
- Backup for GPU %: an implicit Vulkan layer in the guest
  (tools/gpuload) and the compositor time their own GPU work with
  timestamp queries. It runs until the memstore stamps are seen moving,
  and for the whole session if they never move.
- GPU clock: a small GPU job of our own reads the always-on and RBBM
  cycle counters (libgpuclock).

The HUD diagnostics export lists which source each GPU value came from.
tools/gpuload/build.sh takes the Vulkan headers from build_local.sh's NDK
first and keeps NDK paths with spaces intact.
2026-11-01 20:22:51 +00:00
Kurt Himebauch 6cd844ea51 feat: Follow screen resolution can be scaled down by a factor (#1964) 2026-11-01 14:51:28 -05:00
The412Banner 7eb9175def fix(dual-screen): In-game stats hint says "a game's data output", not only racing games (#1968)
War Thunder and Elite Dangerous show on this page too, so the hint no longer says racing game.
2026-11-01 19:26:03 +00:00
Daniel Noam af821184d5 fix: folder focus survives renaming, scrolling and filters 2026-11-01 20:07:55 +02:00
superboo07 8c09b2c6a8 Intergrated syncthing (#1442) 2026-11-01 12:58:09 -05:00
Kurt Himebauch 5b7e401b11 fix(ci): fork PR builds no longer fail pinning the source tree (#1963) 2026-11-01 12:56:33 -05:00
Daniel Noam d22ef554bb fix: controller focus moves into an opened folder 2026-11-01 19:26:31 +02:00
Kurt Himebauch 3566779895 feat: setting to turn off held-Back Steam shortcuts (#1955) 2026-11-01 11:15:59 -05:00
Kurt Himebauch 9c5f1e4df9 feat: HUD shrinks into its tab as it is dragged off an edge, and snaps to the sides (#1937) 2026-11-01 10:15:36 -05:00
DroidDeck Merge a2a5f84711 Merge origin/main into feat/malibridge-v25 2026-11-01 19:59:31 +08:00
DroidDeck Merge 55897a47a9 docs: show openMali worklog on repository home 2026-11-01 19:51:53 +08:00
DroidDeck Merge 67197aafec docs: add openMali MaliBridge v25 worklog 2026-11-01 19:47:33 +08:00
DroidDeck Merge ddfc94d7ce local: version 25 with mscoree fallback 2026-11-01 18:29:08 +08:00
DroidDeck Merge 22a806d05a local: CPU temperature fallback from adb feeder 2026-11-01 18:22:54 +08:00
DroidDeck Merge 65e2c55e9b fix: fall back to builtin mscoree when dotnet40 has no native 2026-11-01 18:20:40 +08:00
openMali e97e6b29df local: CPU clock before temperature in HUD 2026-11-01 17:54:04 +08:00
openMali 6d4961f7b6 local: session resolution and desktop HUD visibility 2026-11-01 17:32:51 +08:00
openMali e31a6eef18 local: hide unreadable Mali HUD GPU metrics 2026-11-01 17:24:06 +08:00
openMali a3d76b9a94 local: keep GPU bridge row visible after data clear 2026-11-01 16:03:31 +08:00
openMali 234b137328 local: show GPU via Mali bridge in Setup 2026-11-01 15:57:32 +08:00
openMali 9419a19c66 local: MaliBridge rc4, auto local DNS, KWin render node 2026-11-01 15:17:41 +08:00
Anuar Lukpanov 3193a7a706 fix(quest): keep KGSL submitted timestamps monotonic
An older submission that returns late must not hide a newer one. Contexts
with user-generated timestamps can have gaps, so they keep the failed read
instead of waiting once per integer.
2026-11-01 11:46:01 +05:00
Anuar Lukpanov 1259abed5f fix(quest): check a caught-up KGSL context at timestamp zero too
A context with no submissions skipped the ownership check. Check whenever
nothing is pending, since a pending timestamp's wait already checks it.
2026-11-01 11:33:03 +05:00
Anuar Lukpanov 6d7732710c fix(quest): check a tracked KGSL context before answering a read
A descriptor can close without destroying its contexts and its number can
be reused. KGSL checks the owner on every wait, so one wait on the retired
timestamp keeps a read of a dead context an error.
2026-11-01 11:27:57 +05:00
The412Banner ec9573643a fix(game-env): give Codemasters EGO games a thread plan for the core count Windows sees (#1936)
Codemasters EGO games (DiRT 3, DiRT Showdown, GRID, F1 ...) pick their thread plan by core count from system/hardware_settings_restrictions.xml, which only has plans for 2, 3, 4, 6 and 8+ cores. When DroidDeck leaves a phone's slow cores out Windows can see 5 (Retroid Pocket Duo), and DiRT 3 then puts every worker on one core and stops on a loading screen; Showdown closes at start.

When a game starts with WINE_CPU_TOPOLOGY set and its restrictions have no cpu rule for that count, droiddeck-game-env adds one copied from the nearest smaller plan (5 -> the game's 4-core plan), only if that plan's worker map file exists. The file as it was is kept as .droiddeck-original; later launches change nothing. Only ASCII digits count as a core count, and the edited file is staged under a unique name.

Tested on a Retroid Pocket Duo: with DiRT 3's shipped file, the plan is added at launch and the game loads to its menus and races.
2026-11-01 06:22:36 +00:00
Anuar Lukpanov bf0ee99b6d fix(quest): keep timestamp read failures for unknown contexts
Answer a failed read only for a context created on the same file
descriptor, so an invalid context or a closed descriptor stays an error
instead of reading as timestamp zero.
2026-11-01 11:01:58 +05:00
Anuar Lukpanov 9fce12730c fix(quest): answer a refused KGSL timestamp read from waits
Mesa 27 polls timeline points with CMDSTREAM_READTIMESTAMP_CTXTID and
reports any failure as VK_ERROR_UNKNOWN, so gamescope aborted at start on
Quest with vk.WaitSemaphores result -13. Track each context's submitted
timestamps in the preload and answer a failed read from one millisecond
WAITTIMESTAMP calls. Log refused KGSL ioctls to session.log.
2026-11-01 10:52:06 +05:00
Kurt Himebauch 09d170eb22 fix: performance HUD cycles colors with the Inner Gamer theme (#1932) 2026-11-01 01:41:42 -04:00
Kurt Himebauch 65b414df93 feat: Strip HUD size, a one-line bar across the top or bottom edge (#1935) 2026-11-01 01:26:30 -04:00
The412Banner 730b3affae fix(dual-screen): open the bottom-screen Dashboard in every session, not only the first (#1933)
On dual-screen handhelds the bottom-screen Dashboard only opened by itself in the first Steam session after DroidDeck started. SessionActivity.onCreate checks for a new session twice; the first block calls SessionEvents.begin(), which moves the phase to PREPARING, so the second block (which held the per-session reset of secondScreenAutoOpened / secondScreenProvisional) never ran. The reset now happens in the first block, right after begin().

Tested on a Retroid Pocket Duo: two Steam sessions in one app process, Dashboard on the bottom screen both times.
2026-11-01 04:44:44 +00:00
Moroni Granja dbf114a0f2 fix: draw the guest cursor into the glasses picture (#1925) 2026-11-01 00:26:14 -04:00
l4nr3 3fc80a03d9 fix: read the paged-out session back from zram after a resume (#1839) 2026-11-01 00:24:04 -04:00
K11MCH1 901c453c16 Session: the drawer's frame limit works in desktop mode, up to 165 (#1885) 2026-11-01 00:16:11 -04:00
Kurt Himebauch 9b024ffcdc chore: quick_deploy reuses runtime assets prepared on another branch (#1934) 2026-11-01 00:09:11 -04:00
MaxsTechReview 6e57af02af perf: Little Nightmares keeps its render thread (+12.7% FPS on the Retroid Pocket Duo) (#1930)
perf: Little Nightmares keeps its render thread (up to 13% more FPS on the Retroid Pocket Duo)
2026-10-31 23:49:56 -04:00
MaxsTechReview 86983092af fix: Flathub installs work again outside a session (#1923) 2026-10-31 23:36:24 -04:00
0x142857 85c41f9032 fix: opening DroidDeck from iisu on the AYN Thor no longer bounces back (#1901) 2026-10-31 23:33:40 -04:00
Kurt Himebauch 2ff3ab447d fix: local builds bundle the addon packs they pin (#1908) 2026-10-31 23:32:58 -04:00
The412Banner 8a7022fc14 feat(dual-screen): bottom-screen Dashboard with downloads, notifications, screenshots, Steam chat and In-game stats (#1920)
A Dashboard mode for the second screen of dual-screen handhelds (developed on a Retroid Pocket Duo), the default on dual-screen devices. It opens on Chat at each Steam start and switches to In-game stats when a game starts.

- Downloads: Steam downloads and updates (SD-card libraries too), shader caches and DroidDeck store downloads.
- Notifications: other apps' notifications once access is allowed, kept in memory only.
- Screenshots: a game screenshot without the overlay, and a gallery of DroidDeck's and Steam's screenshots.
- Chat & voice: mic and game volume buttons, and Steam's web chat signed in with the Steam client's own session.
- In-game stats: FPS, CPU, GPU, RAM and battery plus the running game or Steam client type, and a racing dash from Codemasters, Forza and OutGauge games' data output, with War Thunder and Elite Dangerous layouts; speed in km/h or mph.
- Send dashboard data: turns a Codemasters game's data output on for the dash (per game or Automatic) and keeps the original setting to put back.
- DiRT 3 / DiRT Showdown: with their data output on they stop with error 10038 at race end; a ws2_32.dll stand-in (tools/dirt-ws2) is swapped into the prefix while the game runs, skipped on DroidDeck-Proton 11-11.10 which has the fix built in. Same behaviour as DiRTTelemetryErrorFix by RoccoC, based on Carsten Gottschling's fix.

About "telemetry": here it means a racing game's own live driving data (speed, gear, rpm, pedals), not app analytics. For In-game stats no information leaves the device and nothing is shared: the game sends its data to 127.0.0.1 on the same device, DroidDeck only listens there while the page is open and keeps the numbers in memory just long enough to draw them. DroidDeck sends no data about the user or their use of the app; logs leave the device only when the user taps Share logs.
2026-11-01 02:36:57 +00:00
MaxsTechReview bfa61b5139 fix: "DroidDeck isn't responding" on dual-screen handhelds after the bottom screen sits idle (#1917)
The bottom-screen panel handed focus back to the main screen only when it opened or was touched within the last second. When the panel became the active screen later, the paused launcher kept no focused window, so controller input waited until Android reported the app as not responding. The panel now hands focus back whenever it becomes the active screen or gains window focus, unless bottom-screen typing is open or an app the launcher opened is in front.
2026-10-31 20:40:57 -04:00
Dantalian 113ba7b77c fix: keep each key cap's gesture keyed and make the default size selectable 2026-11-01 05:38:54 +08:00
MaxsTechReview 0a07926ef3 docs: DroidDeck 0.6.9 release notes (#1904)
* docs: DroidDeck 0.6.9 release notes

* docs: explain the banking app fix in the 0.6.9 notes

* docs: banking app line in the 0.6.9 summary

* docs: dual-screen line in the 0.6.9 summary

* docs: move the dual-screen line into the 0.6.9 intro

* Update release notes for version 0.6.9

Reformatted the release notes for clarity and improved readability.

* docs: add #1888 and #1896 to the 0.6.9 notes

* docs: add #1903 to the 0.6.9 notes
2026-10-31 13:58:57 -04:00
Kurt Himebauch b63feb5eaa fix: three-finger tap opens the keyboard in touchpad mode (#1903) 2026-10-31 13:55:03 -04:00
MaxsTechReview d691bcf832 chore: bump version to 0.6.9 (17) (#1902) 2026-10-31 13:54:30 -04:00
K11MCH1 583f34fc55 fix: stop importing games as Spacewar (480) and follow a changed Steam app id (#1896) 2026-10-31 13:36:43 -04:00
K11MCH1 88719f25d2 Games: launch options for Steam games in the Edit screen, shared with Steam's Properties (#1888) 2026-10-31 13:36:39 -04:00
MaxsTechReview ac132a23e2 fix: dual-screen update prompt survives a bottom-screen tap, home launcher stays out of Recents, Quest panel opens landscape (#1893)
* fix: dual-screen launcher keeps the update prompt on screen and stays out of Recents as home; Quest panel opens landscape

Outside screens the launcher opens (the update prompt, Settings, pickers) now hold the top screen when the bottom screen is touched, so a bottom-screen tap no longer buries the Android install prompt and leaves updates stuck waiting for confirmation (DD-368).

When DroidDeck is the home app its launcher task is excluded from Recents, so it can no longer be swiped closed; a running game session is still listed (DD-377).

The launcher declares a 1024x640 dp default panel size so Meta Quest opens it landscape again after the orientation manifest change in #1606 (DD-360).

* fix: Recents exclusion follows the top DroidDeck activity and only undoes its own change

The launcher task is hidden from Recents only while FrontEndActivity is on top, DroidDeck is home and no session runs; the file manager and a running session show it again, the check repeats when the launcher pauses, and tasks hidden by someone else are left alone.

* fix: Recents ownership survives process death and never claims a task someone else excluded
2026-10-31 09:21:49 -04:00
MaxsTechReview 85dd8beae1 zRAM: compress Battle.net with the Steam client while one of its games runs (#1882) 2026-10-31 08:10:57 -04:00
DroidDeck Merge 69ffc0966b chore(mali): sync MaliBridge to v0.5.4.2 and document Mali support 2026-10-31 19:02:12 +08:00
DroidDeck Merge d4a6d95127 Merge MaliBridge integration (feat/malibridge) into 0.6.7 2026-10-31 19:01:40 +08:00
Dantalian 3df48d5625 feat: make the PC keyboard's size, opacity, and key groups configurable 2026-10-31 17:37:05 +08:00
MaxsTechReview b07eca3c06 perf: .NET games keep the FEX preset's multiblock setting (Stardew Valley starts in 45 s instead of 85 s) (#1880)
perf: .NET games keep the FEX preset's multiblock setting
2026-10-31 00:17:09 -04:00
Kurt Himebauch 6eef0721c9 feat: the HUD is DroidDeck HUD, pinch resizes it, and pushing it off an edge docks it as an FPS tab (#1868) 2026-10-30 22:44:23 -04:00
Kurt Himebauch e3c3471d2f Update README (#1874) 2026-10-30 21:57:32 -04:00
MaxsTechReview a81d1a4d4e Ask for All files access when a USB drive is mounted but unreadable (#1872) 2026-10-30 21:21:26 -04:00
MaxsTechReview 28067b3bbe Run x86_64 Flatpak apps through FEX (#1875)
* Run x86_64 Flatpak apps through FEX

* proot: run an execveat with an absolute path like execve

* Find x86_64 Flatpak installs and resolve sandbox paths like the sandbox does
2026-10-30 21:21:22 -04:00
MaxsTechReview 8088e47d14 fix: "DroidDeck isn't responding" on dual-screen handhelds after the bottom screen takes focus (#1871) 2026-10-30 19:52:17 -04:00
MaxsTechReview 6673a892e2 fix: x86 Flatpak bundles are refused with a clear message instead of installing and failing to start (#1873) 2026-10-30 19:03:27 -04:00
MaxsTechReview 8cf01ebb77 fix: Linux games on an NTFS USB drive start the first time instead of failing to load a library (#1850)
* fix: a read-only file on a USB drive no longer fails to close the first time it is read

On the RedMagic Astra 2, Android's NTFS USB drive returns EIO from close() for a file opened read-only when that first read updates its access time; the update itself succeeds and the descriptor is released. The x86 loader under FEX treats a failed close as fatal, so every Linux game newly installed or updated on such a drive failed to start once (libfreetype.so.6: cannot close file descriptor: Input/output error). The session library's close() now reports success for EIO on read-only descriptors; errors on descriptors opened for writing are still returned.

* Check the read-write close still reports EIO
2026-10-30 18:36:08 -04:00
MaxsTechReview 7e47db2e2a fix: a game with a launch-option picker is no longer closed while the picker is open (MK11) (#1869)
fix: a game with a launch-option picker is no longer closed while the picker is open
2026-10-30 18:16:11 -04:00
MaxsTechReview 484afffffd Translate the strings added since the last translation pass (#1855)
* Translate the strings added since the last translation pass

* Fix translation wording from review

Use the Android settings labels for the pairing guide in bg, cs, da, de, fi, ru and zh-CN, correct the frame limit divisibility wording in de and nl, describe owned games without implying purchase in hi and ru, fix the VRAM label in es-419, describe GOG replacement files correctly in tr and name the Guide button in zh-CN.
2026-10-30 18:08:05 -04:00
MaxsTechReview 0b1af34a5f fix: library filter, sort and display menus aren't cut off in portrait (#1827)
* fix: library menus open toward the side with room, so in portrait they are no longer cut off

In portrait the Filter, Sort, Display and Collections buttons sit at the left of the screen, but their menus still grew to the left and were squeezed into the space before the button, cutting the labels. A menu now opens the other way when its own side is narrower than the menu and the other side has more room.

* fix: step-out menus keep their outline with the content when a resize flips the side

* fix: step-out menus still take keyboard focus when the layout shifts while opening
2026-10-30 17:04:59 -04:00
MaxsTechReview 2ff682a4f5 fix: a folder of games with an extra non-game folder still lists each game (Rance 01) (#1822)
* fix: a folder of games still splits into its games when it also holds a folder with no game, as with Rance 01 in a VNs folder

A visual-novel folder on game storage held a steamapps folder with no exe in it. The group check asked every subfolder to hold a game, so the whole folder stayed one game and a pick in Rance 01 switched that game instead of adding its own. Folders without a game and Steam library folders no longer count, and old games folders being moved to entries split such a folder the same way.

* Check a folder's own entry, removal and store ownership before splitting it, and keep folders with redist or extras as one game

An old Games folder that was removed keeps its games removed after the split, a folder already added stays as it is, and a store game's folder is never split.

* fix: a split games folder keeps the game it launched and that game's settings

When an old games folder entry is split into one entry per game, the
folder holding the selected exe is kept even if ranking left it out (or
the parent stays whole when the exe sits directly in it), and the
parent's name, app ID, launch options and other settings carry over to
that game without overwriting settings it already has.

* fix: a split games folder never migrates steamapps and keeps the launched exe

A saved exe under a steamapps or hidden folder keeps the old entry whole
instead of adding that folder as a game, and the game that inherits the
old entry's settings keeps the exe the old entry launched when nothing
was picked.
2026-10-30 16:58:23 -04:00
MaxsTechReview 18f6305977 fix: banking apps no longer refuse to open while DroidDeck is installed (#1824)
* fix: banking apps open again with DroidDeck installed; the shortcuts accessibility service is listed only once the user sets it up

0.6.7 added HomeButtonService, a key-filtering accessibility service, enabled in the manifest. Android lists it as an installed accessibility service on every phone, and banking apps such as UBS refuse to start while one from outside the Play Store is installed. The service is now disabled by default, turned on when the user opens the Shortcuts setup, turned back off if they leave it unset, and kept on for anyone who already uses it.

* Keep the shortcuts service declared and turn it off at start unless the user chose it, so an update never drops their choice

A failed read of the enabled-services setting now changes nothing.

* Hide the unchosen shortcuts service right after an update, before DroidDeck is opened

A package-replaced receiver in its own light process runs the same listing sync as startup. The manifest default stays enabled because Android drops an unavailable service from the enabled-services setting, which would erase the choice of users updating from 0.6.7.

* Hide the shortcuts service as soon as the user turns it off in Accessibility settings
2026-10-30 16:55:09 -04:00
MaxsTechReview 7bc3529722 fix(audio): DirectAudio no longer crackles where Android refuses low-latency output (#1862)
* fix(audio): DirectAudio no longer crackles where Android refuses low-latency output

On devices where Android won't give the game a low-latency output (AYN Thor,
Android 13), DirectAudio kept Wine's 10 ms period and 12 ms buffer. An
emulated game audio thread can't keep that pace there, so audio crackled.
The session now checks for low-latency output and, only when it is refused,
asks DirectAudio for a 20 ms period and a 60 ms buffer.

DirectAudio's settings are now AUDIO_DIRECT_* everywhere in DroidDeck and are
handed to the driver under the names it reads when DirectAudio is set up.
AUDIO_DIRECT_MS and AUDIO_DIRECT_PERIOD_MS can be set per game.

* fix(audio): game profile DirectAudio settings and the microphone-off setting reach the driver
2026-10-30 16:34:45 -04:00
Kurt Himebauch 9cc56631cd feat: test and local builds show a faint PR, commit and title watermark (#1863) 2026-10-30 15:29:41 -04:00
Kurt Himebauch a5b035b247 test: the required-status gate expects the skip-on-superseded condition (#1866) 2026-10-30 15:25:28 -04:00
MaxsTechReview a8cebd024b fix(ci): required-check test matches the gate's !cancelled() condition (#1864)
fix(ci): the required-check test expects the gate's !cancelled() condition
2026-10-30 15:20:30 -04:00
MaxsTechReview 7c49eef79f fix(dual-screen): keep the bottom-screen controls up while a game loads (#1840)
* fix(dual-screen): keep the bottom-screen controls up while a game loads

The session opened its bottom-screen trackpad only at the game's first frame. The launcher's bottom menu closes when a game starts, so on dual-screen handhelds the bottom screen showed whatever Android app was behind it for the whole load, which is long for Xbox games while Xodus prepares them. The controls now open when the session starts. Devices without a built-in second screen, and players who turned the in-game controls off, are unchanged.

* fix(dual-screen): pick the bottom-screen panel again once the session's controller is known

Opening the bottom-screen controls at session start can run before SessionService has prepared the Steam Deck pad, while SessionState.deckPad is still false, so the trackpad could be chosen where the Deck controls belong. That early choice is now provisional: at the game's first frame it switches to the panel the remembered choice and the real deckPad give, unless the player has already picked one.

* fix(dual-screen): keep the loading-time panel choice through PiP, background and new sessions

* fix(dual-screen): retry a terminal panel opened before it was ready and keep a manual close made during loading
2026-10-30 14:32:34 -04:00
Kurt Himebauch c8c5e22a34 fix: new Steam installs start with shader pre-caching off (#1861) 2026-10-30 14:17:45 -04:00
Kurt Himebauch fa4a54f64a Default game library to vertical covers (#1857) 2026-10-30 14:09:44 -04:00
MaxsTechReview 83980035c6 fix(desktop): video wallpapers no longer race ahead after DroidDeck returns from the background (#1833)
* fix(desktop): video wallpapers no longer race ahead after DroidDeck returns from the background

* test: give the vrclient loader harness the clock syscall hook
2026-10-30 13:58:49 -04:00
MaxsTechReview 8f7cdb7106 fix: Battle.net scales its windows to large desktops instead of opening tiny on high-resolution screens (#1832) 2026-10-30 13:58:44 -04:00
MaxsTechReview 9a1334b73b fix: on dual-screen handhelds DroidDeck no longer jumps back over apps opened from outside it (#1828)
fix: the bottom-screen panel no longer pulls DroidDeck back over an app opened on the main screen from outside DroidDeck

On dual-screen handhelds the panel took focus and moved DroidDeck's task forward whenever another app opened over it on the main screen, unless DroidDeck had opened that app itself. A frontend such as iisu, or Settings opened from a shortcut, was covered again within a fraction of a second. The panel now stops reclaiming once the launcher is paused, and starts again when it resumes.
2026-10-30 13:58:40 -04:00
MaxsTechReview ab949a4a5a fix(runtime): the x86-64 Steam client starts again on the Ubuntu runtime (liblzo2-2 for FEX) (#1825)
* fix(runtime): ship liblzo2-2 so the x86-64 Steam client can install FEX

FEX's unsquashfs links liblzo2. The Ubuntu runtime did not ship it, so the FEX check failed on every build, the x86-64 client was never set up, and the session went straight back to the home screen. Only phones with the desktop installed had it.

* fix: the x86-64 Steam client starts again after it updates itself under FEX

On its first start the x86-64 client downloads a 500 MB update and, under FEX, ends on an illegal instruction (rc=132) instead of asking for a restart with rc=42, so the session ended there. The client is started again once.
2026-10-30 13:58:35 -04:00
MaxsTechReview b91e8fdeab fix: a session in picture-in-picture draws at 30 fps instead of the full refresh (#1842)
* fix: a session in picture-in-picture draws at 30 fps instead of the full refresh

Since auto picture-in-picture became the default (#1727), pressing Home leaves the session running in a small window that takes no input. A game kept drawing at its full rate there: on a REDMAGIC Astra 2 Among Us in picture-in-picture drew 3.31 W, the same as playing it and about 1.05 W more than a suspended session. While in picture-in-picture the compositor now paces the session at 30 fps (or a lower limit already set). Mirroring to glasses keeps the glasses rate, and the display mode request ignores the picture-in-picture limit so the app in front is not slowed.

* fix: keep the no-argument output limit the glasses pacing tests read

* fix: hold scene renders to the output limit and keep frame generation off in picture-in-picture

A frame limit only paced dmabuf buffer releases, so a shared-memory client was still drawn on every vsync with its frame callbacks sent at the panel's rate. The vsync tick now waits until the limit's interval, less half a refresh, has passed since the last scene. Frame generation is disarmed while picture-in-picture is shown and armed again when it ends; frame generation requests are applied in order.

* Give the commit render harness the frame limit hold

* Hold scene renders to a frame limit deadline, also on the fallback timer, and keep frame generation for glasses mirroring in picture in picture
2026-10-30 13:29:10 -04:00
MaxsTechReview 021e7153fa fix: Automatic dual screen needs a real second touch panel (Odin keeps its side rail) (#1801)
* fix: AYN Odin handhelds are single-screen in automatic dual-screen mode

* Use a second screen only when more than one physical screen is lit, instead of excluding AYN models

* fix: open the session panel when the second screen turns on, and keep the On mode's display choice

* fix: let the session panel open again after its screen goes away and returns

* fix: Automatic dual screen needs a touchscreen for the second panel instead of counting lit displays

* fix: check the second screen again when a touchscreen comes back after the display

* fix: check the second screen again when a touchscreen is added or removed, and on resume

* Keep the second screen panel closed once the session has ended
2026-10-30 13:19:21 -04:00
Kurt Himebauch 524292f457 fix: drop the runtime installs on first Play chip from the Steam home (#1854) 2026-10-30 13:04:16 -04:00
Kurt Himebauch 92c1560f63 feat: Steam's sleep-after timers start every session as Disabled (#1848) 2026-10-30 12:13:12 -04:00
maxjivi05 0a5955d6fa Merge remote-tracking branch 'droiddeck/main' into feat/malibridge
# Conflicts:
#	README.md
#	app/src/main/java/com/droiddeck/launcher/gpu/TurnipDriver.java
#	app/src/main/java/com/droiddeck/launcher/runtime/BwrapSpawner.kt
#	app/src/main/res/values-ar/strings.xml
#	app/src/main/res/values-b+es+419/strings.xml
#	app/src/main/res/values-bg/strings.xml
#	app/src/main/res/values-cs/strings.xml
#	app/src/main/res/values-da/strings.xml
#	app/src/main/res/values-de/strings.xml
#	app/src/main/res/values-el/strings.xml
#	app/src/main/res/values-es/strings.xml
#	app/src/main/res/values-fi/strings.xml
#	app/src/main/res/values-fr/strings.xml
#	app/src/main/res/values-hi/strings.xml
#	app/src/main/res/values-hu/strings.xml
#	app/src/main/res/values-in/strings.xml
#	app/src/main/res/values-it/strings.xml
#	app/src/main/res/values-ja/strings.xml
#	app/src/main/res/values-ko/strings.xml
#	app/src/main/res/values-ms/strings.xml
#	app/src/main/res/values-nb/strings.xml
#	app/src/main/res/values-nl/strings.xml
#	app/src/main/res/values-pl/strings.xml
#	app/src/main/res/values-pt-rPT/strings.xml
#	app/src/main/res/values-pt/strings.xml
#	app/src/main/res/values-ro/strings.xml
#	app/src/main/res/values-ru/strings.xml
#	app/src/main/res/values-sv/strings.xml
#	app/src/main/res/values-th/strings.xml
#	app/src/main/res/values-tr/strings.xml
#	app/src/main/res/values-uk/strings.xml
#	app/src/main/res/values-vi/strings.xml
#	app/src/main/res/values-zh-rCN/strings.xml
#	app/src/main/res/values-zh-rHK/strings.xml
#	app/src/main/res/values-zh-rTW/strings.xml
#	app/src/main/res/values/strings.xml
#	tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env
2026-10-30 11:58:55 -04:00
MaxsTechReview 41615d2460 fix: Steam's UI runs at the panel's full refresh rate again (#1841)
#1809 held Steam's own UI to an even share of the refresh near 60 fps whenever it had focus. On 120 Hz and faster screens Big Picture then felt locked at 60 while games ran at the full rate, and players reported it as a regression. The compositor now releases Steam UI buffers unpaced again, like a game's. The larger swapchain stays limited to games, so the client UI still uses gamescope's default image count.
2026-10-30 11:10:03 -04:00
Kurt Himebauch bc1a010411 fix: launching a game from a frontend starts it instead of sitting on the home screen (#1844) 2026-10-30 10:34:30 -04:00
MaxsTechReview d31f234f74 fix(xbox): games run from their own folder on the short drive, not the drive root (#1804)
* fix(xbox): keep the game in its own folder on the short drive instead of at the drive root

* fix(xbox): SD card Xbox games can read their own package files

Xodus mounts a store library on the selected SD card at /mnt/droiddeck-stores/<card> and links the launch view to it, but game sessions only bound that library as /mnt/droiddeck-sd. The links were dangling inside the game, so MicrosoftGame.Config, appxmanifest.xml and XCurl.dll could not be read and GDK games stopped with "Failed to initialize GDK". Game sessions now bind the same store roots Xodus uses.

* fix(xbox): give the SD Steam library its own store path when another store root shares the card

externalRootsForXodus added the selected Steam library's Games folder with the card's guest path even when another store root on the same card already used it, so both bound to one destination. It now takes a path-derived suffix in that case, the same way externalRoots separates its own roots.
2026-10-30 10:10:23 -04:00
Delete228 d6b4b0589b Merge upstream shortcuts and dual-screen session updates
Preserve profile bindings, shared held-input ownership and custom panels alongside upstream physical shortcuts, mouse routing and docked menus. Add integration regressions and record the full 2,918-test JVM run.
2026-10-30 17:00:29 +03:00
FX1922 6ffe675a57 fix: match the game page's check for updates, uninstall and edit art buttons to the frosted design (#1831) 2026-10-30 09:31:05 -04:00
MaxsTechReview fb5af9d35e fix(runtime): cutscenes and in-game videos play again with DroidDeck-Proton (libva-drm2, libva-x11-2) (#1823)
fix(runtime): ship libva-drm2 and libva-x11-2 so DroidDeck-Proton's video demuxer loads

DroidDeck-Proton's ffmpeg links libva-drm and libva-x11. The runtime only had libva2, so winedmo.so failed to load and Media Foundation video stopped: Resident Evil Revelations hung on its loading screen with "Failed to create demuxer, status 0xc000007a", and RE engine and Kingdom Hearts cutscenes broke. Installing the desktop hid it because Plasma pulls both in.
2026-10-30 09:18:09 -04:00
Kurt Himebauch b75060c280 feat: cap the frame rate in session for games launched without Steam (#1813) 2026-10-30 07:39:31 -04:00
FX1922 67d391958f feat: apply itch updates as butler patches (#1829) 2026-10-30 07:38:06 -04:00
4770d11e39 feat: Add physical controller button shortcuts (#1202)
* feat(controller): add physical button shortcuts

* fix(controller): improve shortcut capture and assignment feedback

* refactor(controller): simplify menu button assignment UI

* feat(controller): save menu assignments on button release

* refactor(controller): remove obsolete shortcut UI resources

* fix(controller): place drawer remaps below Back

* refactor(controller): simplify shortcut capture wording

* fix(controller): refresh active shortcut bindings on changes

* fix(controller): preserve drawer confirmation for mapped A

* fix: preserve controller shortcut navigation and cancel

* test: align shortcut dialog assertions

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-30 07:08:59 -04:00
qinsiyuan ab43524bbc fix: preserve the runtime PATH when overriding guest binaries 2026-10-30 18:35:06 +08:00
MaxsTechReview f69e2d0171 fix: Steam's UI draws at an even share of the refresh near 60 fps instead of the panel's full rate (lag and heat since 0.6.7) (#1809) 2026-10-30 02:16:25 -04:00
MaxsTechReview b0a8c2065d fix: a folder of games on game storage lists each game instead of one combined game (#1808) 2026-10-30 02:01:56 -04:00
MaxsTechReview d4fd7ac5c3 fix: "DroidDeck isn't responding" on dual-screen handhelds when input reaches the bottom screen (#1806) 2026-10-30 01:56:44 -04:00
MaxsTechReview ed661d7007 fix: the performance HUD is off by default and keeps the user's own choice (#1807) 2026-10-30 00:10:06 -04:00
MaxsTechReview 265335d550 fix: the Stop button no longer wraps letter by letter on narrow bottom screens (#1805) 2026-10-30 00:09:53 -04:00
MaxsTechReview 2b0d633535 fix: the Duo's bottom screen keeps the panel you picked across games (#1803) 2026-10-29 23:45:06 -04:00
MaxsTechReview 3b917c0db3 fix(runtime): ship GTK 3 and the libraries Proton's arm64 builds need (#1802) 2026-10-29 23:44:50 -04:00
MaxsTechReview 58968c9286 fix: itch.io installs unpack a game archive nested inside the download (#1800) 2026-10-29 23:21:38 -04:00
Kurt Himebauch 5736297987 fix: keep the launcher from reclaiming focus when an app opens on the primary screen (#1797) 2026-10-29 21:58:23 -04:00
MaxsTechReview 26e2f26ec9 fix: Steam's Browse buttons work when another app holds port 8080 with Decky enabled (#1798) 2026-10-29 21:22:14 -04:00
MaxsTechReview 23bb361006 fix: Steam no longer restarts every launch to import added games, and asks first (#1795) 2026-10-29 21:14:11 -04:00
MaxsTechReview ee3876b965 Windows components: restore bundle parts, wait for restores and keep older picks (#1788) 2026-10-29 20:31:18 -04:00
Kurt Himebauch 5d6f9c7d74 feat: show the DroidDeck D mark as the fastfetch logo (#1778) 2026-10-29 20:21:49 -04:00
MaxsTechReview d5d3aa2ae4 fix: ARMSX2, melonDS and Cemu start without the desktop installed (#1793) 2026-10-29 20:16:55 -04:00
MaxsTechReview 31c943c82a fix: Windows installs to Android storage no longer ask for an administrator (#1791) 2026-10-29 19:12:22 -04:00
MaxsTechReview 3b9abf6302 Update the Linux runtime by itself when a new one is out (#1787) 2026-10-29 18:50:26 -04:00
MaxsTechReview 161a93a4b3 Session follows the Android time zone, and Teardown keeps its own vsync (#1790) 2026-10-29 18:49:11 -04:00
Kurt Himebauch 7d3d338fb9 Keep the manual second screen option when Dual screen is Off (#1777) 2026-10-29 18:39:17 -04:00
MaxsTechReview a3da6f0bda Launch frontend game links without a tap on dual-screen devices (#1789) 2026-10-29 18:37:52 -04:00
MaxsTechReview b726e19fa4 Keep Windows components across runtime updates and reinstall lost ones (#1786) 2026-10-29 17:48:04 -04:00
Kurt Himebauch 001ec02a17 chore: bump version to 0.6.7 (16) (#1783) 2026-10-29 15:20:25 -04:00
Kurt Himebauch a69ae77fc5 docs: DroidDeck 0.6.7 release notes (#1765) 2026-10-29 15:18:55 -04:00
MaxsTechReview 22d39998f0 fix(desktop): let desktop apps save read-only game files, and make game libraries writable (#1781) 2026-10-29 15:16:17 -04:00
MaxsTechReview 5786e89ff4 feat(desktop): one bottom panel with the system tray instead of a top bar (#1772) 2026-10-29 12:32:38 -04:00
Kurt Himebauch e892805c2f fix: ship libxcb-keysyms1 in the Ubuntu runtime so DD-Turnip loads (#1773) 2026-10-29 12:22:25 -04:00
MaxsTechReview 0258f10df0 fix(desktop): start Electron and Chromium apps from apt without the sandbox, on X11 (#1775) 2026-10-29 12:02:40 -04:00
Kurt Himebauch 30923635af fix: remove performance HUD detail text (#1768) 2026-10-29 10:37:25 -04:00
The412Banner 15b00fc23c fix(release): rename the dual-screen and typing task affinities for variant APKs (#1767) 2026-10-29 10:12:02 -04:00
MaxsTechReview 9f7f6d821d perf(epic): keep the hidden launcher from loading its store and Fab pages, and compress it while games run (#1764)
* perf(epic): keep the hidden launcher from loading its store and Fab pages, and compress it while games run

The hidden Epic Games Launcher that stays up for sign-in while a game runs pre-warmed its store and Fab pages on a display nobody sees. Under FEX each page used about one core and grew past a gigabyte, crashing and reloading in a loop. The hidden instance now skips the store pre-warm, gets a blank page in place of Fab, and is paged out to zRAM with the Steam client once a game has focus. The visible launcher is unchanged.

* test(zram): keep the scan fixture PIDs above the kernel PID range
2026-10-29 10:01:25 -04:00
MaxsTechReview 29c83976d0 Move the Linux runtime to Ubuntu 26.04 LTS with a separate Kubuntu desktop (#1733) 2026-10-29 09:05:20 -04:00
The412Banner 5de690ef39 fix: FusionHUD Pill battery row, and a locked HUD lets on-screen controls work (#1763)
Pill: battery level, temperature and watts share one "BAT" row, as on the
other sizes, and each part follows its own switch. Turning Memory off no
longer hides the battery temperature (reported by Keegan), and watts show
without the battery level.

Touch (part of #1752, reported by ZZCSK): a locked HUD lets a touch through
where an on-screen control under it takes it: controller buttons and sticks,
Steam touch controls, screen keys and the controls panel below the game.
Each layer answers takesTouchAt with its own touch checks. Everywhere else
the HUD keeps the touch, so a long press still locks or unlocks it. A new
HUD starts locked. The "Lock in place" hint says so in every language.

Tests: Pill battery row per switch combination; takesTouchAt agrees with a
real touch over a grid for the controller buttons, screen keys and both
controls panel layouts (Deck and gamepad, landscape and portrait); a locked
HUD passes touches to a control under it, keeps the rest, and unlocks on a
long press.
2026-10-29 12:51:28 +00:00
MaxsTechReview 283e3b4549 Use the second screen on dual-screen handhelds (#1734)
* Use the second screen on dual-screen handhelds

Detect built-in second screens such as the Retroid Pocket Duo and AYN Thor.
The sections move to a menu on the bottom screen, L2 and R2 switch sections,
and text fields type on the bottom screen so the top keeps the full view.
Sessions open the bottom-screen trackpad and keyboard when they start.
Setup gains a Dual screen choice (Automatic, On, Off) with toggles for the
bottom menu and the in-game controls. Closing the bottom menu no longer ends
a running session.

* Type into Steam from the bottom screen

While the bottom-screen trackpad and keyboard controls are open in a Steam
session, Steam's own on-screen keyboard no longer covers the top screen.
The agent keeps Steam's text target and asks the app instead, which opens
the Android keyboard on the bottom screen and hands focus back to the game
when it closes. Modal and VR keyboards, and sessions without the bottom
controls, keep Steam's keyboard. The Show keyboard button now follows the
keyboard when it is closed another way.

* Open the bottom controls after the first frame and tidy dual-screen translations

* Move the in-game menu to the bottom screen on dual-screen handhelds

Fix controller input and focus on the second screen: keys and sticks reach the game after touching the bottom controls, closing the bottom controls returns focus to the game, B closes bottom typing, and the typing panel keeps a focusable window until focus has left it.

* Let B close the open layer of the session menu first and focus the selected bottom-screen mode

* Let the Retroid Pocket Duo Home double press open its quick menu during sessions

* Open the keyboard on the bottom screen for Steam text fields and hide it on Enter or when the field loses focus

* Keep controller focus on the top screen when a section is picked on the bottom screen

* Keep LB and RB on the store tabs and let Back return from a store to its tab

* Show the Adreno 722 as supported on the Retroid Pocket Duo

* Label the Steam integration suggestion's close button Close and cover that closing it is permanent

* Close the docked menu with its presentation, keep a focus picked during the stop delay, and ignore panel picks on process-settings pages
2026-10-29 07:52:18 -04:00
MaxsTechReview e9e0186e66 perf(fex): answer raw stat, readlink and statx syscalls through the proot fast path (#1757)
* perf(fex): answer raw stat, readlink and statx syscalls through the proot fast path

FEX issues newfstatat, readlinkat and statx as raw syscall() calls, which skipped the libc wrappers the fast path hooks and fell through to ptrace. Route them through libblfastpath the same way raw openat already is, and keep the fast path's own /proc/self/exe lookup off the hooked syscall().

* test(fex): keep the raw lookup test passing on kernels without statx
2026-10-29 07:25:39 -04:00
Kurt Himebauch 8329033121 feat: setting to hide sidebar labels (#1749) 2026-10-29 07:19:40 -04:00
Kurt Himebauch 81a6186f9e fix: smooth grid scrolling when focus moves past the visible rows (#1750) 2026-10-29 07:01:32 -04:00
dddms 2ea78cb238 perf(steam): batch compatibility mapping updates
Apply planned compatibility changes in one traversal instead of copying and searching the whole token list for each game. Wire retired-Proton replacement, default/tool changes and x86-64 repointing through the batch writer.

Share entry and name updates to preserve metadata, first-match semantics and reverse insertion order. Retain the single-item path and sequential alias/non-string behavior without changing the planner, tool policy, file locks or atomic writes.

Add reference, work-budget and integration regressions, and document the host-only bulk configuration benefit.
2026-10-29 14:22:42 +08:00
MaxsTechReview d1bd23df16 feat: warn when a game library drive is linked at USB 2.0 speed (#1746)
* Warn when a drive used as a game library is linked at USB 2.0 speed

Reads the drive's USB link speed from sysfs through its mount, and shows a
dismissible warning with the USB generation, link speed and a tip for a
faster cable or drive. The warning returns after the drive is plugged in
again unless Don't show this again is switched on.

* Keep slow USB dismissals per plug-in across restarts and recheck after a dismiss

Dismissals are stored with the boot count and the USB bus and device number,
so they survive a process restart, end when that drive is plugged in again
even while the launcher is stopped, and never clear other drives. A dismiss
looks for the next slow drive, stale scans are dropped, and an unknown port
speed is no longer treated as a USB 2.0 port.

* Drop slow USB scans that a newer library or mount check has superseded

* Invalidate pending slow USB scans as soon as a media broadcast arrives

* Revalidate an open slow USB warning on every check and match raw mount library paths

Every check now rescans, so a warning that is already showing is replaced or
cleared when its drive is removed or the library changes. Library paths saved
under /mnt/media_rw are treated as their /storage volume.

* Keep the Don't show this again choice when a rescan closes the slow USB warning

* Save the slow USB opt-out before the launcher activity is torn down
2026-10-29 00:18:07 -04:00
Kurt Himebauch 2410b8c67c feat: art-first store cards (#1747)
* feat: art-first store cards that share the library display settings

* fix: keep price and installed state on store cards when size is hidden

* fix: darken the compact store badge for contrast on light artwork
2026-10-28 23:47:00 -04:00
Kurt Himebauch 64950099a3 fix(storage): stack the summary card in narrow panes (#1736) 2026-10-28 23:08:59 -04:00
Kurt Himebauch 0516d023fa feat: art-first library cards with a display menu (#1744)
* feat: art-first library cards with a display menu for size, title and storage

* build: use native arm64 docker on Apple Silicon and add a fast deploy loop

* fix: address review feedback on library cards and local build scripts
2026-10-28 22:40:53 -04:00
The412Banner 110cfc28f8 feat: FusionHUD replaces the session HUD, with MangoHud's readings (#1743)
The session HUD is FusionHUD (Bannerlator's HUD) with every reading taken from
the same source, and worked out the same way, as the MangoHud build that runs
Steam's performance overlay in a DroidDeck session.

- gamescope patch 0125 (gamescope-3.16.29-p19) shares the frame times that
  mangoapp reads (one per signalled commit of connector 0) in a ring named by
  DROIDDECK_HUD_FRAMES; the app reads it once per display frame and counts the
  compositor's frames when gamescope sends none.
- MangoHud's rules: FPS over periods of at least 500 ms, frame time = 1000/FPS,
  200-frame graph on a fixed 0-50 ms scale; CPU, GPU, temperatures, RAM, swap,
  VRAM and battery from MangoHud's sources. Zones 22/26/28 are typed as GPU and
  DDR sensors, so Steam's overlay CPU temperature no longer includes them.
- Five sizes in a panel shaped like the DroidDeck mark, with a centred header,
  one left edge, neon outline and FPS; Minimal is the default size. Settings on
  a new "Performance HUD" drawer page, with Bannerlator's options.
- The HUD reads sensors and polls frames only while it is drawn (not while
  hidden, in picture-in-picture or behind another app), keeps its place after
  picture-in-picture, and the diagnostics export has its own reader.

Device-tested on the AYANEO Pocket FIT: same readings as Steam's overlay side
by side in Dead Cells; the HUD updates in a game, hides in picture-in-picture
and comes back in place, no crashes.
2026-10-29 01:53:45 +00:00
devSharaku e871557bf2 Update Xbox backend to 0.2.12 and accept compact JWE tickets (#1729)
Some Microsoft accounts get a delegation-compact (d=) ticket, which the
pinned Xbox backend (Nexus 0.2.8) rejected: the library stayed empty
(store-ticket-format) and downloads failed. This moves the backend to
Nexus 0.2.12, which has the upstream store-ticket fix, rebases the
DroidDeck credentials, launch-view and cloud-token patches, and strips
the d= prefix from compact JWE tickets so downloads work again.

Tested by seven users in the Discord Xbox Store Issues thread on the
pr-1729 build at d55e05a0.

* Update Xbox backend to 0.2.12
* Fix cloud offering fallback and add regression tests
* Fix Xbox authentication with compact JWE tickets
2026-10-29 00:07:53 +00:00
The412Banner a8b3c2d5dc docs(license): name DroidDeck in the GPL notice instead of Pluvia (#1741)
The notice at the end of LICENSE still carried Pluvia's name and
copyright line from GameNative's copy of the file.
2026-10-28 23:56:12 +00:00
Kurt Himebauch 87572a7f91 feat: game page with art backdrop and frosted controls (#1738) 2026-10-28 19:33:33 -04:00
Kurt Himebauch 1e92d10fa5 fix(ui): keep the ring veil count per test (#1739)
* ci: run unit tests in four forks

* fix(ui): keep the ring veil count per test so tests do not share snapshot state

* ci: keep two unit test forks, four were slower
2026-10-28 18:19:15 -04:00
Kurt Himebauch 92787dd0f0 ci: run unit tests in two parallel forks (#1737)
* ci: run unit tests in two forks

* test: start spatial controller mode tests with glasses output selected
2026-10-28 17:35:11 -04:00
Kurt Himebauch aef1f85aa9 ci: run store engine host tests in a parallel job (#1735)
* ci: run store engine host tests in a parallel non-gating job

* ci: harden the store tests job permissions, timeout and exit status
2026-10-28 16:47:42 -04:00
Kurt Himebauch 198f667bf0 feat: add GOG maintenance 2026-10-28 16:10:17 -04:00
Kurt Himebauch 52fa41767c fix: stop stale Android readbacks snapping Steam's brightness slider back (#1731)
* fix: stop stale Android readbacks snapping Steam's brightness slider back during a held stick

* fix: let deploy_fast sign with the release env on macOS and Linux

* fix: honor DROIDDECK_SIGNING_ENV first and cover the env signer in tests

* fix: honor DROIDDECK_SIGNING_ENV first and cover the env signer in tests

* fix: retry held brightness readbacks, use a monotonic hold clock and resolve the signing env path

* fix: resolve the build-tools path before signing changes directory

* fix: pick the newest build-tools numerically and keep the env file from replacing them

* fix: only pick build-tools that have aapt, zipalign and apksigner
2026-10-28 15:25:48 -04:00
Kurt Himebauch 6469a7693c ci: check out without history blobs or the release-note GIFs (#1725) 2026-10-28 13:11:14 -04:00
Kurt Himebauch cd1caf523d Default to Native background behavior and automatic PiP (#1727) 2026-10-28 13:10:08 -04:00
Kurt Himebauch 37e0f30cc6 Reset a game's Wine prefix from Game data (#1724) 2026-10-28 13:09:47 -04:00
Kurt Himebauch 23811147aa Guide the child-process toggle when developer options are off (#1726)
* feat(setup): guide build number and the child-process toggle when developer options are off

* fix(setup): only guide the child-process toggle on devices that have it
2026-10-28 13:01:44 -04:00
Kurt Himebauch 27bdc68867 ci: faster pipeline and fewer network flakes (#1719) 2026-10-28 11:31:20 -04:00
Kurt Himebauch 6ecf1ede2c tool: deploy session helper changes by grafting them into the latest main APK (#1721) 2026-10-28 11:07:12 -04:00
Kurt Himebauch 9294a60172 perf(proot): accelerate and cache glibc syscall relays (#1687)
Libc-internal file operations bypass the existing preload wrappers and
still stop in PRoot. Patch recognized ARM64 SVC sites in libc and the
runtime loader, reuse the current translator for 20 path syscall
numbers, and return declined calls to PRoot. Enabled by default after
the tracer-key check; `PROOT_FP_PATCH_SVC=0` remains the troubleshooting
opt-out.

The hardened implementation patches 47 sites on Thor's glibc 2.43.
Relays preserve general/SIMD registers, flags, FP state and errno, and
provide unwind metadata back to the original libc frame. Text is patched
through `/proc/self/mem` after relay pages become executable.
Initialization skips multiple live threads and SVE/SME. Alternate signal
stacks and insufficient remaining stack use PRoot; main-stack growth
respects the current limit. No proroot code or binaries are imported.

Robust-list set/get sites deliberately remain with PRoot/the kernel.
Device tests reproduced stale state when the earlier private-TLS
implementation mixed patched and raw callers. Removing that shortcut
fixes coherence and preserves the host's actual return policy; the
earlier thread-creation gain is retired. SDK forwarding still enables
faccessat2 ENOSYS fallback and clone3 ENOSYS only before Android 15.
Successful clone3 is never executed from the C relay. Existing fsync
handling is unchanged. No in-process RET_TRAP filter is added, so
SIGSYS-mask/handler machinery is not applicable.

Version-2 build-ID/API tables cache relative patch sites in `/tmp`.
Every hit revalidates executable bounds, alignment, instructions and
syscall numbers. Invalid/unsafe files rescan; publication uses a private
temporary file and atomic rename. The PRoot dependency fetch also gains
a mirror matching the existing pinned checksum, resolving upstream
availability failures without changing versions.

Five alternating pairs using the final bundled library on Thor/Android
13:

| operation | wrappers only | hardened relays |
|---|---:|---:|
| libc-internal stat | 49.6 us | 6.0 us |
| libc-internal open/close | 50.4 us | 7.8 us |
| libc faccessat | 49.7 us | 22.0 us |
| libc getcwd | 54.9 us | 5.0 us |
| thread create/join | 136 us | 144 us |
| complete tiny exec/wait | 4.44 ms | 5.60 ms |

An earlier five-pair hardened-library run measured stat 50.1 to 5.9 us
and open/close 51.9 to 7.1 us. The metadata gains survive the safety
checks. Neither run shows a thread-creation gain, and tiny execs retain
roughly 0.9-1.2 ms overhead.

Three full Steam startup pairs yielded Home-ready medians of 47.02 s off
/ 45.25 s on, with overlapping 43-50 s ranges. Steam/CEF-only PSS
medians were 1,223.1 / 1,233.9 MiB; the roughly 11 MiB difference is
smaller than observed variation, but does not establish zero memory
cost. Startup/memory used one unchanged signed bundled-library APK
without overrides; the final stack-gap guard only increases distance
reserved above other mappings. No reliable whole-client startup
improvement is claimed.

Earlier fresh Half-Life SD installs took 350/341 s off and 326/327 s on.
These are historical results from the preceding robust-list revision,
not a post-hardening download retest. Network/shader scheduling varied,
allocation did not reliably improve, and the repeat baseline committed
faster. This PR demonstrates metadata acceleration; it does not
demonstrate a reliable fix for Steam's SD allocation/commit bottleneck.

Validation on `2c3c135b` (production code unchanged since `4998b046`):

- Local release build, runtime/native-library checks, release signing
and four focused Android tests pass. [Final-head
CI](https://github.com/Droid-Deck/DroidDeck/actions/runs/40704837477)
passed: 1,431 Python/native tests (49 skips), 2,648 Android tests (zero
failures/skips), and 162 Rust tests. CodeRabbit is green on the same
head. The installed production APK forwards API 33 and patches 47 sites
with an unset enable flag and no override; Steam Home renders.
- Embedded and shared-PIC matrices pass on glibc 2.43; shared-PIC also
passes on separately loaded glibc 2.39. Coverage includes raw
results/errno/registers/FP state, path/flag/error and protected-pointer
cases, exact readlink boundaries, mixed raw/relay robust state,
concurrent threads, fork, thread churn, alternate-stack signals,
deferred/FIFO cancellation, signal siglongjmp recovery, forced unwind
with caller cleanup, and limited remaining stack.
- Four device test-process filters deny the memory patch file, relay
mapping, executable permission, or patch writes. Every case leaves
original syscall execution working. Host injection also verifies partial
installation after an individual site fails.
- Concurrent atomic symlink replacement passes 3,000 internal
stat/readlink iterations at default/zero TTL. Eight focused host tests
pass, covering instruction/branch limits, stack/limit fallback,
untrusted cache files and NDEBUG checks. API selection gives 45/47/46
sites for unknown/33/simulated-35; unset and explicit 1 agree, and 0
leaves sites untouched. This does not replace Android 15 hardware
testing.
- All bench tools compile with -DNDEBUG. Relative/absolute fixture
workloads agree, and thread/ABI/failure checks pass on Thor with those
builds.
- Earlier internal fts/realpath/NSS/dlopen and normalized read-path
equivalence pass. The write suite retains the existing wrapper SD
Unix-socket mismatch against PRoot.

CodeRabbit disposition: fixed relative fixture paths, prevented
benchmark checks disappearing under NDEBUG, corrected the
limited-fallback description, and narrowed memory-file denial so the
thread check still succeeds. The default-off recommendation was
addressed by fixing/testing the underlying guard and unwind issue while
retaining the requested default-on behavior. The readlink count
suggestion is intentionally not adopted: device boundary comparisons
reproduce current PRoot behavior, so changing that inherited policy
belongs in a separate correctness change.

This is not consequence-free instruction patching: constructor time,
private text copies and stack checks have costs. Android 15+ ARM64 and
16 KiB-page hardware, arbitrary asynchronous cancellation/non-local
exits, user-managed stacks, and attributable tracer-counter measurements
remain gaps. Loader calls before the constructor and arbitrary guest ELF
syscall sites remain outside scope.

Thor was restored to its original APK and bundled library, with app data
preserved and test settings, overrides, temporary files and version-2
test caches cleared.

Detailed evidence and revision boundaries: [proroot
analysis](https://github.com/Droid-Deck/DroidDeck/blob/2c3c135b52c3dd949ef5a99a67d8c12eba24406c/docs/development/proroot-analysis.md).
2026-10-28 10:53:26 -04:00
Kurt Himebauch 2c3c135b52 test(proot): address benchmark review findings 2026-10-28 10:19:29 -04:00
Kurt Himebauch ac8d97e098 test(proot): target memory patch access denial 2026-10-28 10:11:05 -04:00
Kurt Himebauch 4998b04636 fix(proot): harden relay state and unwinding 2026-10-28 10:06:44 -04:00
Kurt Himebauch 4aa19b6a39 fix: cancelling the first-run desktop or Proton install stops its download (#1042)
Pressing Cancel (or B) while a session is installing the desktop or the
Steam-mode Proton seed now stops the download. Launching again right
after no longer opens a session screen that closes itself.

Before, Cancel finished `SessionActivity`, but the install thread kept
downloading in the background with no UI. Two things went wrong:
- `Downloader` and `DesktopCatalog.install` had no way to be cancelled.
- "an install is running" lived in a per-activity field
(`installingRuntime`). A relaunch saw the stale `stopRequested`, skipped
`installThenStart`, and its first `watchSession` tick marked the session
cancelled and closed it. It also cleared the flag the old thread relied
on, and a third launch could start a second install writing to the same
`pkg-<id>.download`.

Changes:
- `Downloader.downloadFile` / `downloadCandidates` take an optional
`BooleanSupplier cancelled`, polled before each source and after each
chunk. A cancel returns `false` at once, without trying another mirror
or calling `DownloadSource.failed()`. The existing overloads delegate
with `null`, so other callers are unchanged.
- `DesktopCatalog.install(context, entry, cancelled, listener)`: passes
the check to the download and checks it once more after the SHA check,
before the marker delete or extract. Extraction itself still runs to the
end. The old 3-argument overload stays, so `MainActivity.installPackage`
is unchanged.
- `SessionActivity`: `installDesktop` / `installProton` pass `{
SessionState.stopRequested }`. `installingRuntime` now reads the
process-wide `SessionState.installing`.
- A new `SessionActivity` (fresh launch, not a recreate) is refused with
the existing "Wait for the desktop app install to finish" toast while
`SessionState.installing != null && !running`.
`MainActivity.startActivity` applies the same check before the launch
animation.
- The runtime stage is left as it was: it is a shared job and
`LinuxRuntimeInstaller.isBusy()` already blocks relaunch.

Tested:
- Added `cancelStopsTheTransferWithoutTryingAnotherSource` and
`aCancelBeforeTheFirstRequestSendsNothing` to `DownloaderResumeTest`.
That class passes (9/9), as does `DesktopCatalogTest`, and the app
compiles.
- Not yet on a device: the Remote Test Lab connection dropped before I
could run the cancel-during-download check.

## Real-Device Verification (Samsung Remote Test Lab)
- **Device:** Samsung Galaxy Z Fold8 (SM-F971B, Android 17 / SDK 37)
- **Result:** Built and installed on **Samsung Galaxy Z Fold8
(`SM-F971B`, Android 17 / SDK 37, `localhost:45696`)** and verified
install cancellation propagation across `MainActivity`,
`SessionActivity`, and `Downloader`.
2026-10-28 10:04:06 -04:00
Kurt Himebauch 8f6b2a4123 feat(itch): update detection and safe updates for installed games (#1665)
## Summary

Adds update detection for installed itch.io games. This is PR4 of the
itch.io store rework (browse, filters, game page and collections are
merged; Linux support is #1589). Butler patching comes later as an
add-on pack. Updates here download the full build.

## What changed

- `ItchUpdates` (new): compares each installed itch game with the
uploads itch lists for it (`GET /games/{id}/uploads`), using what the
install already saves: `installVersion` (the upload's `updated_at`) plus
`gameId`, `downloadKeyId` and `uploadId` in `extra`.
- The installed upload is still listed and has a later `updated_at`: an
update.
- The installed upload is gone: the newest Windows upload is offered as
a replaced build.
- An install without an `uploadId` compares the newest Windows upload's
date.
- An unknown installed version, or no answer from itch, never reports an
update.
- The check walks installed games one at a time, one second apart, and
logs `itch.io: checked <n> installed games, <m> with updates`. It runs
when the itch tab opens (at most every 6 hours) and from the new button.
A game itch did not answer for keeps its last result. Results are cached
in the itch preferences until the update is installed or the account
signs out.
- Library: games with an update show an "Update" label on their tile and
row. A Check for updates button in the Library header (itch tab only)
runs the check on demand, shows progress, and ends with a toast:
"Updates available: N" or "Up to date".
- Game page: an Update button appears for installed itch games with an
update. A replaced build asks first and names the new file. A plain
newer build updates directly.
- Safe updates: an update never extracts into the live game. The archive
is unpacked into `.droiddeck-update` inside the game folder, and the new
build must contain an executable before any game file is touched. A
failed or cancelled download or extraction removes the staging folder
and leaves the game as it was. Then `ItchStaging.merge` moves the staged
files into place with renames inside one folder. A staged file replaces
the file with the same path, directories are merged, and anything the
new build doesn't contain (saves, settings, install record, launcher,
art) stays. The install record is marked updating just before the move,
so an interruption there is resumable.
- New strings `stores_itch_updates_found` and
`stores_itch_update_replaced` in all 33 locales. The button label, toast
and dialog buttons reuse existing strings.
- `docs/development/itch-updates.md` describes the feature.

## Notes

- One extra copy of the build's size must be free while an update runs.
- Files the developer removed in the new version stay in the game
folder, because everything the new build doesn't contain is kept.
- The final rename step is not an all-or-nothing swap. A crash or power
loss inside it leaves the game marked updating and resumable.
- The update reruns the normal install over the game's own folder and
storage location.

## Tests

- `ItchUpdatesTest` (Robolectric, like the other itch tests): newer,
same and older stamps, replaced upload falling back to the newest
Windows build, unknown installed version, no answer, an install without
an upload id, and the cache round trip.
- `ItchStagingTest`: new files replace old ones, other files stay, and a
file replacing a directory and the other way round.
- Not run on a device or compiled locally; relying on CI. The controller
focus behaviour of the new buttons is untested.
2026-10-28 09:36:19 -04:00
The412Banner 9d82df6981 fix(game-env): OpenGL games stop waiting for each screen refresh (#1715)
With no frame limit, AIO's Direct3D 7 (DirectDraw) test ran at exactly
144 fps on a 144 Hz panel: every frame waited one refresh (6.94 ms).
Wine asks for a swap interval of 1 on every OpenGL window, and WineD3D's
front-buffer drawing used by DirectDraw and Direct3D 1-7 never lowers
it; it only sets the interval in its present path. Mesa's Zink then
presents in FIFO.

droiddeck-game-env sets vblank_mode=0 for games launched through Proton
before the game's environment edits, so a value set in the launch
options or the editor wins and an editor Remove leaves Mesa's default.
Steam's own runs in compatdata/0 are unchanged. Steam's Frame Limit
still holds a game: gamescope's WSI layer presents every swapchain in
FIFO while its limiter is on.

On the AYANEO Pocket FIT, AIO Direct3D 7 went from 144 to 2,345 and
2,265 fps (1 % low 99 -> 620) with no limit; with the Quick Access
limit at 144 and 72 it held at 143.76 and 71.92.
2026-10-28 13:34:45 +00:00
Kurt Himebauch ddb8ed32cb test(proot): record expanded relay validation 2026-10-28 08:05:50 -04:00
Kurt Himebauch 40b7c480a3 fix(build): mirror pinned talloc downloads 2026-10-28 07:44:59 -04:00
Kurt Himebauch 9a8f54dee7 fix(proot): preserve cleared robust-list state 2026-10-28 07:31:52 -04:00
Kurt Himebauch c62b9b5d1b perf(proot): expand and cache syscall relays 2026-10-28 07:17:53 -04:00
Delete228 b245017593 Merge upstream main and preserve routed Home input
Integrate upstream 213c2da5, keeping Home/Guide player-slot handling compatible with remapped controls and disabled routing. Preserve the input-settings overlay gate for Home and Steam volume keys.

Validation: 2,707 JVM tests across 389 suites pass. Device validation and latency measurements were not repeated.
2026-10-28 12:45:41 +03:00
The412Banner 213c2da57e fix(steam): answer Steam's D3D driver query without starting Wine (#1708)
At every start, Steam's shader pre-caching runs d3ddriverquery64.exe
through each Proton tool (DroidDeck-Proton and the ARM64 Proton) in
compatdata/0. Here the query has never succeeded (948 of 948 runs in
the client's shader log failed, mostly exit code 1), and each run
started Wine in that prefix, where the two Protons upgraded it back and
forth (11.0-100 <-> GE-Proton11-11) with the 32-bit wine.inf install.
When that overlapped a game's start, AIO ran 25-30 % slower for its
first 5-7 seconds.

Both Proton wrappers now exit 1 for Steam's own query, "run
.../d3ddriverquery64.exe" (or the 32-bit name) with STEAM_COMPAT_DATA_PATH
in compatdata/0, before doing anything else, so Steam gets the same
failure without Wine. Every other verb and prefix goes on to Proton.
2026-10-28 09:21:49 +00:00
The412Banner 8549ac9ce8 perf(compositor): draw a new frame when it arrives (#1707)
A new game frame waited for the next screen tick before the compositor
drew it: 1.9-3.5 ms on average at 144 Hz, measured with the pacing
counters. With frames going straight back to the game (no compositor
limit), the compositor now draws as soon as the clients' requests are
handled, at most once per refresh, and presentation times stay on the
tick.

gamescope delivers about one frame per refresh, so the tick leaves a
frame younger than one refresh for the next arrival to draw, unless the
tick itself changed the window, effects or headset pose; a frame no
other frame follows is drawn at the tick after. Once a held frame is
still waiting at the next tick (a client paced by frame callbacks), the
tick draws every frame for a second before holding one again. The
queued draw checks again that drawing on arrival is still on.

Paced output (glasses), streaming and frame generation keep drawing on
the tick. The hidden pref renderOnCommit (default on) turns it off. The
pacing log line counts the scenes drawn on arrival. On the device new
frames wait 0.12 ms instead of ~2 ms.
2026-10-28 09:21:07 +00:00
The412Banner 8c4f97a170 perf(gamescope): p18 – handle a finished frame in one loop pass (#1706)
0123 backports upstream 0efe47c and 666c52b by Matthew Schwartz (commit
completion list lifetime, no Rc to a dying commit). 0124 polls a new
commit's fence at import; a finished frame is marked done at once and
handled in the same steamcompmgr pass instead of a second wake through
the image waiter. commit_ready_check (default on) turns it off.

Pins gamescope 3.16.29-p18 (0123 + 0124). On the AYANEO Pocket FIT about
81 % of commit fences are ready at import; gamescope-xwm 22-26 % -> 16 %
of a core, gamescope_img 10-11 % -> under 2.5 %.
2026-10-28 09:20:51 +00:00
FX192 52e9810fbc Pick the newest Windows upload across types for replaced builds and let an update past its commit point finish 2026-10-28 09:00:36 +03:00
FX192 9b44bf798d Offer only Windows builds as a replaced itch upload and stop a cancelled update from completing 2026-10-28 08:47:50 +03:00
Kurt Himebauch f4656ad83a fix: show every held volume step in Steam's volume slider (#1704) 2026-10-28 00:01:00 -04:00
Kurt Himebauch 3fd9662161 fix: show every held volume step in Steam's volume slider 2026-10-27 23:48:05 -04:00
Kurt Himebauch 8164f9fae1 feat(session): manual pause button in the session menu (#1701) 2026-10-27 22:34:00 -04:00
Kurt Himebauch 374d4783fc fix: avoid incompatible Turnip selection and explain Steam startup failures (#1380) 2026-10-27 22:29:40 -04:00
Kurt Himebauch 3716c8acdf fix: an unproven update-channel guess no longer moves stable users to Nightly (#1067) 2026-10-27 22:27:20 -04:00
Kurt Himebauch 3cb7022cc2 merge main into feat/manual-pause 2026-10-27 22:26:02 -04:00
Kurt Himebauch eceda6e840 Add opt-in game debug logs and bounded log sharing (#1061) 2026-10-27 22:22:33 -04:00
Kurt Himebauch bc094d1920 fix(session): keep wake locks released under a manual hold and retry the paused frame 2026-10-27 22:06:05 -04:00
Kurt Himebauch 82c07c4be7 fix(library): keep installed/all owned filter when stores tab is off (#1700) 2026-10-27 21:43:36 -04:00
Kurt Himebauch c5cd52b5c9 feat(session): manual pause button in the session menu for every suspend policy 2026-10-27 21:40:53 -04:00
FX192 0e173ed05a Block itch updates while the game is being uninstalled 2026-10-28 02:13:48 +03:00
The412Banner 4ef6b53eb0 fix(proton): Windows CPU 0 is the fastest core (#1697)
Wine numbers the Windows CPUs like the host, so Windows CPU 0 was host
core 0, a small core on most Android chips. Games that pin their main
thread to CPU 0 ran it there: GTA IV 32 fps on the AYANEO Pocket FIT,
61 fps once CPU 0 is the fastest core.

droiddeck-game-env hands Wine the game's cores fastest first, from each
core's cpu_capacity (or its top clock). Equal cores keep their order.
Cores picked globally or in a game profile stay the only cores the game
sees, now fastest first; a pick only counts when it narrows the cores.
Games on Proton's own default_cpu_limit list keep their count on the
fastest cores (quoted and unquoted counts); other values such as "P"
are left to Proton. A typed WINE_CPU_TOPOLOGY / PROTON_CPU_TOPOLOGY
still wins, an empty one turns it off for a game, and
DROIDDECK_FASTEST_CORES=0 turns it off. Steam's own Proton runs in
compatdata/0 are left as before.

Builds on maxjivi05's "Windows games see only the cores they are pinned
to" (9d85af75).
2026-10-27 19:07:59 -04:00
FX192 ab130c7754 Forget a pending itch update when its game is uninstalled 2026-10-28 01:55:02 +03:00
FX192 f371149e1f Keep launch options across itch updates and stop cancellation from racing the file merge 2026-10-28 01:29:46 +03:00
FX192 bcec13ab63 Keep an updating itch game's folder out of every failed-download cleanup 2026-10-28 01:14:18 +03:00
FX192 29e8b2f679 Never delete the install record while replacing it and clean the staging folder if marking fails 2026-10-28 01:10:47 +03:00
FX192 e7907d0c74 Check itch updates per game from the game page instead of from the Library header 2026-10-28 01:08:33 +03:00
ovurrsl 42b799c194 runtime: finalize package markers once extraction completes 2026-10-28 00:05:04 +03:00
ovurrsl 1b5cd6add9 fix(install): recheck cancellation after sha256 and wire package resume cancel 2026-10-27 23:51:46 +03:00
ovurrsl 36c8ea2110 fix(update): keep preview-only channel guess transient 2026-10-27 23:41:40 +03:00
ovurrsl 97480e5e83 Merge remote-tracking branch 'origin/main' into fix/update-channel-guess 2026-10-27 23:38:04 +03:00
kingokksa 4f58230a20 fix(session): look up the launched game without rescanning the library (#1595) 2026-10-27 15:53:31 -04:00
maxjivi05 2d19c31872 fix(release): rename the pin task affinity for variant APKs 2026-10-27 15:50:51 -04:00
Kurt Himebauch c9044a27e3 fix(release): rename pairing guide affinity for variant APKs 2026-10-27 15:16:58 -04:00
maxjivi05 10174c1bd5 fix(manifest): name the pairing guide's task from the application id 2026-10-27 15:16:58 -04:00
Kurt Himebauch da3b8017cb fix(steam): hide duplicate Android volume indicator in Steam sessions (#1689) 2026-10-27 15:00:13 -04:00
Kurt Himebauch e906d2f077 feat(launcher): full launcher Apps page (#1686)
* feat(launcher): apps page with search, sort, filter, folders, widgets and page swipes

* feat(launcher): app shortcuts, pinned shortcuts, hidden apps and LauncherApps listing

* fix(ui): let page content scroll to the bottom edge

* feat(launcher): accept widgets other apps ask to pin

* i18n(launcher): translate apps page strings

* fix(launcher): keep disabled pins, serialize widget adds, add many plurals

* fix(launcher): drop in-flight pin refreshes after unpinning

* fix(launcher): skip unpin when pinned shortcuts can't be read

* fix(launcher): drop in-flight app lists when home role is lost

* fix(launcher): only migrate folder entries with one launcher activity
2026-10-27 14:50:11 -04:00
Kurt Himebauch 785d1bf1da perf(proot): enable syscall patching by default 2026-10-27 14:29:58 -04:00
Kurt Himebauch 4b230cbd0f perf(proot): add opt-in glibc syscall patching 2026-10-27 14:06:52 -04:00
Kurt Himebauch ef9866e423 fix(input): stop controller rumble when the device sleeps (#1668) 2026-10-27 13:14:23 -04:00
Kurt Himebauch f07dd5eb97 feat(setup): guided Wireless debugging onboarding for the child-process limit (#1682)
* feat(setup): guide wireless pairing from a picture-in-picture window

* feat(setup): share one child-process fix flow between Setup and Play Steam

* fix(setup): read the child-process limit from the settings provider

* fix(setup): re-check the child-process limit while Setup is open

* fix(setup): skip a dead saved pairing and quiet the pairing guide

* fix(setup): handle repeat guide launches and start pairing after the display choice
2026-10-27 12:33:10 -04:00
The412Banner 067dabe81c perf(compositor): screen tick on its own thread, pacing counters (#1678)
The Choreographer frame callback that drives the compositor ran on the
app's main thread, so main-thread work delayed the tick before it
reached the compositor thread. It now runs on a display-priority
HandlerThread with its own Choreographer; the session pref vsyncThread
(default true) switches back to the main thread. Each resume posts a new
callback with its own generation, so one left queued from an earlier
resume, on either thread, stops itself instead of ticking twice.

A new 10 s "pacing" log line reports how late the tick reaches the
compositor, how long a new frame waits to be drawn, game frames against
buffers re-attached after an empty attach, empty attaches and distinct
buffers, and presentation feedback shown, replaced before drawing and
not drawn.
2026-10-27 12:32:15 -04:00
Kurt Himebauch 378beacfac fix(ui): portrait nav highlight drift and blurry Android app icons (#1677)
* fix(ui): keep nav highlight on its tab while the bar scrolls

* fix(ui): render Android app icons at display density
2026-10-27 10:15:05 -04:00
Kurt Himebauch e8bfe0c4e4 fix: hold Home as Steam on Retroid Nova pads (#1674)
* fix: hold Home as Steam on Retroid Nova pads

* fix(input): track Home Guide apart from controller Guide

* fix(input): keep held controls on Home cancel and resume on Home
2026-10-27 10:14:43 -04:00
The412Banner b5cc6dd185 perf(gamescope): skip the repeated window lookup and wake on host events (#1672)
The WSI layer sends override_window_content on every present, and
gamescope walked the X tree to the top-level window (a blocking
XQueryTree round trip per level, under the wlserver lock) before
noticing the same swapchain already overrides it. A swapchain repeating
the same window now skips the walk for 100 ms (0121).

Backport of upstream gamescope 2962992 by Matthew Schwartz: the nested
Wayland backend's input thread reads the host display, but steamcompmgr
does not watch that fd, so buffer releases and presentation feedback
could wait up to one refresh for its next poll; the input thread now
wakes it after each read (0122).

Pins gamescope-3.16.29-p17, the archive that was A/B-tested on the
device. PATCHES.md also describes the p16 change to 0120.
2026-10-27 09:10:53 -04:00
FX192 2cce33d6f8 Keep the install record out of update merges and drop update results that went stale mid-check 2026-10-27 14:53:06 +03:00
FX192 efaf5bf9b7 Make staging cleanup symlink-safe and drop update checks that outlive a sign-out 2026-10-27 14:43:13 +03:00
FX192 e34db5adcb Count an itch install without a game id as unanswered in the update pass 2026-10-27 14:41:38 +03:00
FX192 0a7397cd0e Fix the itch update cache reader for Kotlin 2.1 and keep symlinked staged entries out of the merge recursion 2026-10-27 14:25:42 +03:00
Kurt Himebauch 0f839bef5d fix(ui): bloom into the compatibility tools loading screen (#1666) 2026-10-27 07:23:47 -04:00
Delete228 ed121f247c Fix routed QAM publication and redundant input state writes 2026-10-27 14:23:37 +03:00
FX192 c90ad997b1 Unpack itch updates into a staging folder before touching the game 2026-10-27 14:00:29 +03:00
FX192 8a16bd3b3b Detect itch.io updates and offer to install them
Installed itch games are compared with the uploads itch lists for them. A game with a newer upload shows an Update label in the Library and an Update button on its page, a replaced build asks first, and a Check for updates button in the itch Library header runs the check on demand.
2026-10-27 13:53:35 +03:00
Delete228 92b4361100 Merge upstream main into unified input profiles
Integrate screen keys, portrait controls, and Home/Back shortcuts with routed input. Preserve remapped axes, neutral gating, and explicit Back bindings; discard stale queued chord reports when routing changes.

Validation: 2,632 debug JVM tests across 385 suites pass with Java 17 and skipRust. Device testing and latency measurements were not repeated for this merge.
2026-10-27 13:14:29 +03:00
The412Banner 43a0ce93f9 fix: Frame Limit sync and refresh follow on a still screen (review follow-ups) (#1663)
The Steam helper checked the Frame Limit only when the offered frame
rates changed. With the limit off during a refresh change, turning it
back on left a value the new rate does not offer, and a failed write was
not tried again. It now checks on every state change and every 5 s, and
waits for Steam's settings write so a rejected write is caught and tried
again on the next check.

gamescope 3.16.29-p16 (0120 changed): gamescope took a new host refresh
only from the presentation feedback of a frame it drew, so with nothing
moving on screen it kept the old rate. It now presents once when the
host's wl_output mode changes and keeps presenting while a new rate is
pending, so the 300 ms check runs on a still screen too.

Found by CodeRabbit in the reviews of #1650 and #1663.
2026-10-27 05:08:21 -04:00
Kurt Himebauch 07a0a7e344 fix: show gesture settings only in gesture mode (#1654) 2026-10-27 00:23:44 -04:00
maxjivi05 c25d54aced fix: leave published component choices alone when state.json cannot be read 2026-10-27 00:07:45 -04:00
maxjivi05 c781cfd525 fix: publish component choices again when a new Proton build appears
Game and global component choices were written to desired.tsv only when a
selection changed, so a Proton build installed later (DroidDeck-Proton
11-11.8) had no rows and kept its bundled FEX. Battle.net's d2rfix FEX never
reached it and StarCraft died in ClientSdk.dll's DllMain. The choices are now
republished at session start and on Battle.net's FEX request whenever the
installed tools no longer match what was published.
2026-10-27 00:07:45 -04:00
The412Banner 4c7e59ed8b feat: Steam's Frame Limit decides; gamescope and the limit follow the refresh rate (#1650)
Remove the Performance mode switch. The session always runs the way
Performance mode did: replaced buffers go back to the game at once
(except while mirroring to glasses), gamescope's WSI layer and winex11
reuse their per-present window checks, and games get eight swapchain
images while uncapped. Steam's Frame Limit in the Quick Access menu is
the only limit.

gamescope 3.16.29-p15 (p14 plus two patches):
- 0119: with the Frame Limit on, gamescope forces FIFO while the WSI
  layer hands the driver MAILBOX, so a game with eight images could
  queue up to seven frames ahead of the screen. While the limiter forces
  FIFO the layer uses its default of 3 images; DXVK and VKD3D-Proton
  recreate their swapchain when the limiter changes, other apps are asked
  to with VK_SUBOPTIMAL_KHR.
- 0120: -r fixed gamescope's pacing and limiter rate for the whole
  session. With GAMESCOPE_NESTED_REFRESH_FOLLOWS_HOST=1 a rate the host
  has reported for 300 ms replaces it (a switch from 144 to 120 Hz passes
  through 72 Hz for a few frames), and Steam gets the display information
  again. The session sets it whenever it passes -r.

Steam keeps its Frame Limit as the client setting
gamescope_app_target_framerate and offers the values gamescope's display
state lists. After a refresh change, a limit the new rate does not offer
(72 at 120 Hz) left the slider on its first value while the game kept the
old limit. The Steam helper watches the offered values and, once they
settle, keeps the limit when it is still offered and moves it to the
highest value otherwise, through Steam's own setting so the slider shows
it. Steam's webpack does not expose its module cache, so the gamescope
service is found by the calls its module makes, and the helper retries
the install every 10 s until Steam is ready.

The session window asks for the fastest mode the peak refresh setting
allows and chose again only when the display changed, so after it had
asked for 60 Hz, raising Android's refresh setting left the display,
gamescope and the limit at 60. It now also chooses again when
peak_refresh_rate or min_refresh_rate changes.
2026-10-26 23:17:35 -04:00
K11MCH1 e450765eb6 Mali bridge with under 4 GB of RAM: Steam's browser UI in software
On a 2.9 GB Mali-G76 MC4 phone the GPU-drawn Steam UI took about 400 MB,
and Android's low-memory killer ended DroidDeck itself while The Binding
of Isaac loaded; with the UI in software Isaac reached its title screen.
Below 4 GB of RAM, bridge sessions now default BL_VKBRIDGE_CEF_GPU to 0;
a value already set is kept. The software UI takes about a core while it
animates and idles while a game has focus.
2026-10-26 23:15:05 -04:00
K11MCH1 018d0e1db9 Steam UI on the Mali bridge: gamescope composites every frame
Through the Mali bridge the Steam UI flickered: frames alternated with
and without its footer while gamescope handed its layers to the phone's
compositor as separate planes. Turning gamescope's overlay planes off
nearly stopped it (2 flips), and a composited image has none (Mali-G76
MC4 r26p0 at rest: 145 footer flips in 6 s, 0 composited). Where in the
plane path it goes wrong is not known. Steam sessions on the bridge set
gamescope_composite_force=1 unless it is already set.
2026-10-26 23:15:05 -04:00
Kurt Himebauch 0e9a31ccfe feat: trust contributors for test builds (#1646)
* feat: trust contributors for test builds

* fix: restrict trusted list changes

* fix: retain same-repository test signing

* fix: publish completed fork builds

* feat: trust K11MCH1 test builds

* fix: recognize maintain collaborator role
2026-10-26 22:54:57 -04:00
Kurt Himebauch 8749154cbf fix(input): keep AYN system key out of Home shortcuts (#1643) 2026-10-26 21:11:46 -04:00
maxjivi05 bb9a64fa5f Reconfigure the app decoder when the capture layout changes
A capture format or buffer change now closes the app's decoder so the
next packet reopens it with the new stride and height, and a capture
slot whose remap fails no longer keeps its old mapping.
2026-10-26 20:36:24 -04:00
maxjivi05 916a39585f Decode Remote Play streams on the arm64 Steam client
Steam's arm64 streaming_client only decodes through a V4L2 stateful
decoder and libdrm dumb buffers, neither of which exist on Android, so
streams from a PC never showed a picture. The preload now offers it a
/dev/video-dec0 and a libdrm stand-in backed by dma-heap buffers, and
each frame is decoded by the app's MediaCodec (Qualcomm low-latency
mode) over the encoder socket, falling back to the FFmpeg in Steam's
runtime.
2026-10-26 20:36:24 -04:00
maxjivi05 9555cb9f65 fix(skate.): run ReSkate at D3D12 feature level 12_1
skate. under ReSkate crashed with an access violation in Skate.exe 12 to
60 seconds after it started at the default feature level 12_2; at 12_1
it loads San Vansterdam and plays. The rule matches Skate.exe with
ReSkate.dll beside it, so it covers ReSkate started from skate.'s own
Steam entry and as a non-Steam game, and a game profile can still set
another level.
2026-10-26 20:35:59 -04:00
maxjivi05 70d8fc6559 Hand a profile's WINE_CPU_TOPOLOGY to Proton as well 2026-10-26 18:31:11 -04:00
maxjivi05 70c512693a Set PROTON_CPU_TOPOLOGY too so Proton's per-game CPU list doesn't replace it
Proton sets WINE_CPU_TOPOLOGY from its own default_cpu_limit for games
like Prototype (8 cores) unless PROTON_CPU_TOPOLOGY is set, so the
pinned topology never reached the game the issue was about.
2026-10-26 18:31:11 -04:00
maxjivi05 43266b2923 Keep an empty WINE_CPU_TOPOLOGY from a game profile 2026-10-26 18:31:11 -04:00
maxjivi05 9d85af7558 fix(proton): Windows games see only the cores they are pinned to
Game cores pinned the game with taskset, but Wine counts the online
CPUs, so a game held to two cores still saw eight processors and sized
its thread pools for them. Prototype freezes at its splash screen that
way. When the session or a game profile limits the game cores, Wine now
gets WINE_CPU_TOPOLOGY for exactly those cores unless the profile sets
its own.
2026-10-26 18:31:11 -04:00
FX1922andmaxjivi05 d905f03bf7 feat(itch): collections filter in the Library (#1597)
* Add itch.io collections to the Library

A Collections button on the Library's itch tab filters the library to one collection of the signed-in account. Its games that are not installed show in the Not installed section.

* Keep itch collections complete across failures, sign-out and empty libraries

A failed later page no longer publishes a partial collection, selecting a collection refreshes it, pagination runs until an empty page, signing out clears collection state, and the itch chip shows for a signed-in account with no owned games.

* Tie itch collection requests to the account that started them

Requests capture the account generation before reading the key and dedupe per generation, sign-out clears collections after the token is removed, and the collections load follows the itch sign-in state.

* Keep paging itch collections past a page of already seen games

* Run the itch collections tests under Robolectric like the other itch tests

* Let the Installed filter apply inside a collection and keep the grid for an empty itch account

* fix(itch): stop paging a collection when a page adds no new games

* Keep the collections icon on the itch collections tab when it opens

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-10-26 18:15:24 -04:00
Kurt Himebauch f13916ee25 feat(steam): optional Steam integration setup (#1637)
* feat(steam): add optional device integration setup

* feat(steam): show integration setup as inline settings rows

* fix(steam): offer App info when shortcuts stay blocked
2026-10-26 17:51:44 -04:00
Kurt Himebauch 73daaf7adc fix(gamescope): keep Fill scaling and magnification visible (#1634)
* fix(gamescope): preserve signed plane positions

* fix(gamescope): pin validated scaling component
2026-10-26 16:06:40 -04:00
maxjivi05 aa17b0553d fix(input): end the forwarded touchpad finger when the Steam menu opens 2026-10-26 15:52:02 -04:00
maxjivi05 46076e165b fix(input): let on-screen controls work while the touchpad finger is down
The game surface only took touches in gestures mode, so in touchpad and
direct mode a finger on the game left the window with no touch target and
Android sent every later finger to the touchpad, even over a stick or
button. The surface now owns those fingers in every mode, and the
on-screen and Steam touch controls hand fingers that miss a control back
to the touchpad instead of dropping them.
2026-10-26 15:52:02 -04:00
Kurt Himebauch 2d8957a401 feat(launch): show compatibility install progress (#1629) 2026-10-26 15:41:46 -04:00
Kurt Himebauch b3561bf658 fix(input): discard queued chords on disconnect (#1632) 2026-10-26 15:31:51 -04:00
Kurt Himebauch 13eb291ba8 fix(input): handle immediate Back chords and tap timing (#1628)
* feat(input): support instant Back chords and tap timing

* fix(input): send QAM before shortcut inputs

* fix(input): stop delayed stick input on Back release
2026-10-26 15:04:55 -04:00
Kurt Himebauch c8f650e167 feat(ui): add game resource links (#1630) 2026-10-26 14:53:21 -04:00
maxjivi05 c300342a29 Update MaliBridge to v0.5.4
v0.5.4 backs the exported memory with AHardwareBuffers on phones without a
dma-buf heap (older Mali such as the G68, G77 and G78) and fixes green and
red textures on the Dimensity 9000 series.
2026-10-26 14:08:24 -04:00
The412Banner 830c753312 fix: turn off D3DGear's capture hook when it sits beside a game (#1627)
The Crew 2 loads D3DGear64.dll from its folder. Its DXGI Present hook
faults over a thousand times within seconds under Wine and the game
never presents again: black screen while the sound keeps playing.
droiddeck-game-env now disables the DLL when it finds it beside the
game, and DLL overrides from several engine fixes are kept together
instead of the last one replacing the others.
2026-10-26 18:07:15 +00:00
maxjivi05 fdc4ff607d Credit MaliBridge by ASEbrahim 2026-10-26 13:55:43 -04:00
maxjivi05 3639196c83 Translate the Mali bridge settings 2026-10-26 13:55:31 -04:00
maxjivi05 9c8d2acacc Turn the Mali bridge on by default on a Mali GPU
A device with /dev/mali0 and no KGSL starts with the bridge on, so it draws
without a trip to Setup. An Adreno keeps Turnip and its automatic driver
route whatever the setting says, other GPUs keep the switch off until it is
turned on, and a choice made in Setup always wins. The GPU checks are read
once per process.
2026-10-26 13:55:31 -04:00
ASEbrahim 7aeb194d6e Mali old DXVK: a setting to keep Proton's own, and Proton's own back when the bridge is off
Setup has a "DXVK" row under the bridge's safe mode: automatic (the default, as before) or
Proton's own (SessionPrefs.bridgeProtonDxvk). With Proton's own, nothing is chosen and an earlier
automatic choice goes back to the Proton's copy.

Automatic choices also go back when the session is not a bridge session (the bridge turned off),
even when nothing is known of the Vulkan; a queued automatic swap is dropped with them, the
player's own queued swaps stay. Turning the bridge off, or choosing Proton's own, does it right
away in the background instead of at the next session. A failed detection in a bridge session
still keeps the last choice.
2026-10-26 13:55:31 -04:00
ASEbrahim 678e16af4d Mali old DXVK: a failed detection keeps the last automatic choice, one Proton failing does not stop the others, the bridge file's driver_api counts unless promotion=on
When nothing could be read (no apps-vulkan file and VulkanInfo failing), every automatic choice was
undone: a 1.1 phone went back to a DXVK that cannot start, and the next session flipped it again.
Now an automatic choice is undone only when the Vulkan is known and wants none.

Each Proton's swap or undo is on its own: a failure (a missing saved copy, an I/O error) is said in
the result and the remaining Protons still get theirs. When the Proton is the same but its saved copy
is gone, the choice stays and is said, instead of being forgotten with DXVK 1.10.3 still installed.

apps-vulkan: the version counts only with promotion=on; otherwise, and in files from servers before
the field (which wrote a promoted version even where it was not trusted), driver_api does. A file
whose driver_api is not the driver's own version (a driver update, a server rolled back) is left out.
2026-10-26 13:55:31 -04:00
ASEbrahim 9040f828e9 Mali bridge: DXVK 1.10.3 on phones whose Vulkan stops below 1.3
Proton's DXVK 3 refuses a device below Vulkan 1.3, without shaderInt64 or
with less than 256 bytes of push constants, so on a Mali with a 1.1 driver
(OnePlus Ace 2V, Mali-G710, r32p1) every D3D9-11 game exits. On a bridge
session the forced-package step now picks the newest DXVK that starts on
what games see: 1.10.3 below Vulkan 1.3, 2.6.2 on 1.3 without shaderInt64
or 256 bytes of push constants, Proton's own otherwise.

What games see comes from <bridge cache dir>/apps-vulkan (a bridge with
Vulkan promotion writes it after its self-test) or else from the phone's
driver (vkinfo now reports shader_int64 and max_push_constants). These
choices are marked automatic: a later session with another answer swaps or
restores them, and the player's own choice in Components is never touched.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim e63fad1daa fix: droiddeck-session: ANGLE on Vulkan brings Chromium's Vulkan compositing
With BL_VKBRIDGE_CEF_ANGLE=vulkan the CEF GPU process stopped crashing but
Chromium still composited in software (gpu_compositing: disabled_software,
Steam "Disabling GPU acceleration: Disabled/RuntimeDetect", VizCompositorThread
at a full core). The GPU report shows why: "eglCreateWindowSurface failed with
error EGL_BAD_CONFIG". ANGLE's Vulkan backend on X11 gives every EGL config the
root window's visual; Chromium's X11 surface asks for a config whose
EGL_NATIVE_VISUAL_ID is the ARGB visual whenever a compositing manager owns
_NET_WM_CM_S0 (gamescope does), finds none and passes a null config.

bl_webhelper_layout() now adds +Vulkan +VulkanFromANGLE +DefaultANGLEVulkan
for ANGLE on Vulkan, so Skia composites through an xcb swapchain on ANGLE's
Vulkan device and no EGL window surface is needed. BL_VKBRIDGE_CEF_VULKAN=0
leaves it off; unset BL_VKBRIDGE_CEF_ANGLE still means Steam's stock layout.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim e527d4a9f3 fix: droiddeck-session: BL_VKBRIDGE_CEF_ANGLE reaches Chromium through the web helper preload
Steam drops -cef-use-angle: on the bridge the CEF GPU process reports
ANGLE_OPENGL (ANGLE on Zink) with -cef-use-angle=vulkan on the command line.
An explicit BL_VKBRIDGE_CEF_ANGLE now also becomes --use-gl=angle
--use-angle=<backend> on steamwebhelper's own argv (stock process layout kept);
unset, nothing changes.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim 30a70a0f7c Setup: show the stored Mali bridge switch in a new process; log the server's start time
FrontEndActivity read vkBridge and vkBridgeSafe only when the Performance page
opened, so after an app restart (every APK update) Setup showed
"Mali bridge: off" while sessions used the bridge. refresh() now reads them.
VkBridgeComponent logs how long the server took to listen.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim 9c28b41d49 droiddeck-session: wait up to 10 s for the Mali bridge socket before falling back
The bridge check tested the server's socket once. It now polls every 50 ms
(BL_VKBRIDGE_WAIT_MS, default 10000) on the first pass, logs how long the
socket took, stops waiting when the session is told to stop, and names what
is missing (icd, library, socket) when it falls back.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim 5b35ace098 Mali bridge: Setup's "Safe mode" switch, on by default
A second row under the GPU check, shown only when the bridge is on: "Safe mode" keeps the
bridge's proven workarounds wherever its startup probe has not verified this GPU and driver;
off is performance mode, native paths wherever the probe passed. It reaches the server as
<cache dir>/mode ("safe" or "performance", written atomically before the server starts or is
reused, so the in-app server's JNI start stays as it is) and the guest as VKBRIDGE_MODE, which
/root/.vkbridge-env can still override. A server without modes ignores the file.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim e04eb84090 Mali bridge: keep the server's cache directory in the app's files, not its cache
Android empties an app's cacheDir when storage runs low; it took the bridge's pipeline
caches, the probe profile, stuck-pipelines and the env file from under a running server.
The directory is now files/vkbridge-pipelines (--cache-dir and the in-app start alike), and
an existing cache/vkbridge-pipelines is moved there once. Nothing trims filesDir, so the
server's own limits bound it (64 MB per pipeline cache file, a total limit in newer servers).
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim b3c5570bdd Mali bridge sessions: keep Steam's web helper GPU process out of process
Steam's web helper now runs its GPU work inside the browser process
(--in-process-gpu --no-zygote, #566). On the Mali bridge that browser stops
answering after its first window, Steam restarts it every 10 s and then quits
before the login page. Bridge sessions keep the stock process layout
(BL_WEBHELPER_ARGS=stock); BL_VKBRIDGE_WEBHELPER_ARGS overrides it for tests.
Adreno sessions are unchanged.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim dae36b0e67 Mali bridge sessions: Setup switch, server, session wiring
On a GPU without KGSL, Setup offers an experimental Mali bridge switch
(off by default; ignored on Adreno). With it on, the session starts the
bridge server, points the Vulkan loader at the bridge ICD, names Mali's
device as the stand-in render node, runs the bridge self-test into the
session log, and caps vkd3d-proton's feature level at 12_0. Flatpak
sandboxes get the bridge ICD and socket. The DRM preload falls back to
flock in a bridge session when kcmp is refused, and a clock/thermal log
is kept beside the bridge logs.
2026-10-26 13:55:31 -04:00
Ahmad Sayed Alsayed Ebrahim 3943794b0e waylandcomp: import layer pool buffers as AHardwareBuffers off Adreno
A non-QTI gralloc handle (Arm/MediaTek gralloc on Mali) has no layout
the compositor can read, so the pool import guessed a linear dma-buf and
failed. On drivers other than Qualcomm's the compositor device enables
VK_ANDROID_external_memory_android_hardware_buffer and
VK_EXT_queue_family_foreign and imports such buffers as AHardwareBuffers;
the linear dma-buf stays as the last resort. Adreno/Turnip keeps the
same device and the dma-buf path.
2026-10-26 13:55:18 -04:00
maxjivi05 c883ce4439 CI builds the MaliBridge client as a component
The components job checks out external/MaliBridge and builds the guest
ICD, its manifest and the self-test as the vkbridge recipe; a submodule
counts in a recipe's identity by its pinned commit.
2026-10-26 13:55:18 -04:00
Ahmad Sayed Alsayed Ebrahim a69ce00b6a Build and package the Mali bridge from external/MaliBridge
The server is built with the NDK into libvkbridge_server.so (a program
the app execs from its native library directory); the Linux ICD, its
manifest and the self-test go into the runtime assets with the other
glibc preloads, locally and in CI.
2026-10-26 13:55:13 -04:00
Ahmad Sayed Alsayed Ebrahim 061ae17cc2 Add MaliBridge as a submodule at external/MaliBridge
MaliBridge (https://github.com/ASEbrahim/MaliBridge) is a Vulkan
command bridge that lets the Linux side draw on a non-Adreno GPU (Mali)
through the phone's own Vulkan driver. Pinned at v0.5.3.1.
2026-10-26 13:55:13 -04:00
Kurt Himebauch a0a68f7280 feat(steam): sync QAM controls and pass held Back shortcuts (#1621)
Steam's native QAM sliders now control Android brightness and media
volume, with device changes reflected back in Steam. Holding Android
Back for 700 ms sends a real held QAM button for Steam shortcuts. Single
and double taps keep the existing menu/QAM order and swap setting; the
double-tap window remains 500 ms.

- Discover native brightness through a guest backlight descriptor and
use Android's Modify system settings permission. Retain the latest
pending request until permission is granted or the session stops, with a
notification action for hidden sessions.
- Give DirectAudio and fallback AAudio sinks Android media volume/mute
callbacks, keeping software gain at unity. Recover when initial volume
state is absent or invalid, brightness acknowledgements differ or never
arrive, or the Display bridge is not ready yet.
- Forward other buttons while QAM is held in Steam Deck and Steam
Controller modes. Consume release after a hold and clear pending input
on overlays, focus loss, pause, disconnect, and teardown. The session
menu retains its compact Back choice without an explanatory paragraph.

Validation:
- Local ARM64 release build passed. The latest timing change passed 20
targeted input tests, including release at 600 ms remaining a tap. The
preceding full feature check passed 61 Android input/device-control
tests, with permission cases on API 28 and 33.
- 66 Linux Python/Node/C tests passed for device controls, audio
recovery, Steam compatibility, and file dialogs. Both audio sink modules
rebuilt with Android NDK 27.2.
- Release-signed integration build 14d12cf0 is installed on the unrooted
AYN Thor, carrying the Android/input changes through 749ade4a plus the
existing portrait changes. Device checks confirmed the configured
QAM/menu tap order, Back dismissal, removal of the inline menu
description, and held QAM overlapping X with clean release. Hands-on
testing confirmed this installed build works.
- Earlier Thor testing covered brightness permission and pending
requests, brightness synchronization in both directions, native volume
changes, hardware volume key readback, and both sink backends. Failure
recovery is covered by the automated tests above.

The subsequent queued-notification fix in 4d7ceb23 is covered by the
Linux regression suite and has not replaced the Thor install.

Bluetooth/wired output switching and other device models remain
untested. CI and CodeRabbit monitoring continue against the pushed head.
2026-10-26 13:46:14 -04:00
maxjivi05 455bf0fba9 fix(fex): install DroidDeck's FEX build again when it no longer loads
A tag that matches no longer vouches for the build: FEX and FEXServer must
also load (ld.so --list), otherwise the session reinstalls it and x86
programs use the Steam or system FEX meanwhile.
2026-10-26 13:40:36 -04:00
maxjivi05 8df90c30dd fix(fex): read the fallback FEX version from droiddeck-fex-update
The pin moved there from droiddeck-steam-x64, so the client settings sheet
and its test read it from the new script. The x86-64 client notes now name
the updater and Components' FEX watcher.
2026-10-26 13:40:36 -04:00
maxjivi05 4de4c65c2a fix: wait before retrying a FEX build that did not install
A failed catalog install is retried after an hour or when the session names
another build, a missing updater falls back to the FEX already here, and the
archive download gives up on a stalled connection.
2026-10-26 13:40:36 -04:00
maxjivi05 bb1cbf95c0 docs: credit sharun and userland-execve 2026-10-26 13:40:36 -04:00
maxjivi05 7e2d13b641 fix(fex): x86 programs use DroidDeck's own FEX build, kept to the catalog's version 2026-10-26 13:40:36 -04:00
maxjivi05 5bdf0d6b04 fix(fex): report the x86 platform under AT_PLATFORM 2026-10-26 13:40:36 -04:00
maxjivi05 13493d44c3 fix: keep Sentry for Godot games that are not .NET
The Sentry GDExtension was turned off for every Godot game that ships it,
because its crash handler takes the .NET runtime down under FEX. GDScript
games such as Pathogenic call SentrySDK from their scripts, so without the
extension their autoloads fail to parse and the game quits. Only Godot .NET
builds have it turned off now.
2026-10-26 13:27:59 -04:00
Kurt Himebauch 4d7ceb2325 fix(steam): drain queued control notifications 2026-10-26 13:27:03 -04:00
maxjivi05 a984d11742 fix: run Xbox games from a short drive letter
The launch view lives under the app's files folder, so games saw an
install path like Z:\data\user\0\com.droiddeck.launcher\files\xbox-runtime
\views\launch-<n>\droiddeck-xbox-<id>. Forza Horizon checks that length and
quits with "Installation path is too long". The view is now mapped to a
free drive letter in the prefix (X: first) for the length of the launch.
2026-10-26 13:21:31 -04:00
Kurt Himebauch 749ade4a45 fix(input): delay held QAM to 700 ms 2026-10-26 13:20:51 -04:00
Kurt Himebauch fa5506df35 fix(steam): preserve Back taps and recover QAM controls 2026-10-26 13:13:08 -04:00
Kurt Himebauch dde1a55d00 feat(input): pass held Back through as Steam QAM 2026-10-26 12:49:51 -04:00
maxjivi05 c446566f42 fix: hold a setup lock instead of guessing from running processes 2026-10-26 12:47:00 -04:00
maxjivi05 2e2b950964 fix: Flatpak setup clears a package lock left by an interrupted run 2026-10-26 12:47:00 -04:00
Kurt Himebauch 53d643dd00 fix(audio): clear volume bridge during sink teardown 2026-10-26 12:34:31 -04:00
Kurt Himebauch 6a5dfe1486 feat(steam): sync QAM brightness and volume with Android 2026-10-26 12:32:36 -04:00
Kurt Himebauch da127d1b4c fix: delay client compression until game focus (#1406) 2026-10-26 12:12:04 -04:00
maxjivi05 9b68b33e73 fix: pick the HUD surface by the scene's draw order 2026-10-26 12:02:17 -04:00
maxjivi05 1f60e57749 fix: time the HUD graph from the game's frames as shown on screen 2026-10-26 12:02:17 -04:00
Kurt Himebauch eaaecf0661 feat(session): add portrait sessions and controls below the game (#1612) 2026-10-26 12:01:44 -04:00
Kurt Himebauch d08afbd878 fix: restart compression delay for new games 2026-10-26 11:55:01 -04:00
Kurt Himebauch 36135327f5 fix: resolve zram focus conflicts 2026-10-26 11:45:17 -04:00
0x1Nawaf d65912f0c5 fix: stop starting a session on the slow proot (#1395) 2026-10-26 11:41:18 -04:00
Kurt Himebauch 5d945d7784 refactor: remove duplicate session frame limiter (#1616) 2026-10-26 11:25:31 -04:00
maxjivi05 ee1b3b26e1 fix: clip damage against the buffer committed with it 2026-10-26 10:50:19 -04:00
maxjivi05 9c02233b06 fix: clip damage to the surface and let the toplevel's size count for coverage 2026-10-26 10:50:19 -04:00
maxjivi05 f98c272300 fix: compare the window's shown size with the scene 2026-10-26 10:50:19 -04:00
maxjivi05 744a42d324 fix: count one shm window, only after new GPU frames stop, with damage in buffer pixels 2026-10-26 10:50:19 -04:00
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
Danbro007 b15a360cf2 test: 覆盖会话启动失败提示的调用路径 2026-10-26 13:59:11 +08:00
Danbro007 35b85952cf merge: 同步官方 main 并解决多语言资源冲突 2026-10-26 13:11:19 +08:00
Kurt Himebauch 3fd6b1f00d fix(ui): restore consistent focus glide transitions (#1588) 2026-10-25 23:20:57 -04:00
wallentx 91cb966f93 Provide captured compositor log in artifact collection test 2026-10-25 21:59:59 -05:00
wallentx d3faca1919 Register stopped-session collection before allowing restart 2026-10-25 21:41:30 -05:00
wallentx d3d12d7585 Import assertion for failed Steam copy regression 2026-10-25 21:35:26 -05:00
wallentx f845b5c44f Keep failed session artifact collections incomplete 2026-10-25 21:23:17 -05: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
wallentx 5eaa6a4902 Preserve scrub cancellation callbacks and initialize test snapshots 2026-10-25 18:39:19 -05: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
Kurt Himebauch 0ec1fe1bdc fix: keep a Nightly guess backed by published preview history 2026-10-25 19:24:43 -04:00
Kurt Himebauch 1ee597e07f Merge remote-tracking branch 'origin/main' into pr1067 2026-10-25 19:22:34 -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
wallentx 3bdf303bbc Sync latest upstream touch handling 2026-10-25 18:16:10 -05:00
wallentx f4adbeb602 Merge upstream main and close diagnostic publication races 2026-10-25 18:15:44 -05: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 <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
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 <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
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 <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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 <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
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
Charles DuBose c4df7e9b3d feat: Battle.net on the desktop with its games and store, plus memory options (#979) 2026-10-25 12:49:27 -04:00
Kurt Himebauch 6918fd9308 feat: install the redists a Steam game's install script runs on Windows (#1561)
* feat: install the redists a Steam game's install script runs on Windows

* fix: rescan a game's components when its install script changes
2026-10-25 12:44:35 -04:00
597402107d [FIX] statx call on old kernels < 4.11 (#947)
* [FIX]
Fix git statx call on old kernels < 4.11 for correct using of "Fast proot path"

Issue
https://github.com/Droid-Deck/DroidDeck/issues/770

* [FIX]
fix: don't call major() from the proot fast path

* fix: preserve statx fallback request semantics

* fix statx fallback argument handling

* fix: silence statx null check warning

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-25 12:42:51 -04:00
Kurt Himebauch 10fe5c5f77 feat: stretch 16:9 games to screens wider than 16:9 too (#1558)
* feat: stretch 16:9 games to screens wider than 16:9 too

* fix: don't offer 16:9 game fit while the session follows the screen
2026-10-25 12:37:47 -04:00
FX1922 411fda343a itch.io: version picker and installing free games (#1503)
* itch.io: temporary Test itch.io button that logs what the API answers (not for merge)

* itch.io: show Test itch.io only while Debugging tools is on

* itch.io: version picker and installing free games

* itch.io: build the platform names outside joinToString so the composable call compiles

* itch.io: ask for the version before resuming an unfinished install

* itch.io: keep the chosen upload, save free games in the queue, don't auto-install a closed picker

* itch.io: install straight away when exactly one build is installable

* itch.io: show the picker again unless the game has exactly one build

* itch.io: ignore a second build tap while the picker is closing

* itch.io: empty a partial install before extracting so builds never mix

* stores: deleteTree removes a symlink without following it

* stores: skip symlinked folders in the uninstall walk, and only empty a partial install that is this game's own
2026-10-25 12:37:04 -04:00
Kurt Himebauch 7d99a24ad5 fix: find the arm64 reaper under XDG_DATA_HOME and serve queued file dialog requests at once (#1560) 2026-10-25 12:27:42 -04:00
Kurt Himebauch 8658ffff90 fix: Steam's Browse buttons open the file chooser with the x86-64 client too (#1557) 2026-10-25 12:08:12 -04:00
angelrenefm 86ecb716eb fix(build): build the local image when Podman reports it missing (#1527) 2026-10-25 12:08:08 -04:00
informalTechCode b1c70f5f81 Add Rokid Max 2 support using its verified USB identity (#1535)
Add Rokid Max 2 USB recognition and validated Max support
2026-10-25 12:07:47 -04:00
VIIII4andVIIII4 c6bc8aef04 Add an optional HTTP proxy for downloads (#1474)
* Add an optional HTTP proxy for downloads

Downloads only reached GitHub directly or through URL-rewrite
mirrors, which does not help where a local proxy is the workable
route. Setup now takes a host:port HTTP proxy that every Downloader
connection uses, independently of the download source mode.

* Reference the Kotlin object instance from Java

Java cannot call a Kotlin object member on the class name without
@JvmStatic; the surrounding code already uses INSTANCE.

* Assert the proxy host literal without a reverse lookup

InetSocketAddress.getHostName reverse-resolves on some JVMs
(127.0.0.1 became localhost on CI); hostString is the literal.

* Pass the app proxy into the guest environment

The Steam client installer and the guest package tools fetch with
plain curl against their own CDNs, outside Downloader. Export the
usual proxy variables for them when the app proxy is set; proot
shares the host network, so a localhost proxy is reachable as-is.

---------

Co-authored-by: VIIII4 <[email protected]>
2026-10-25 12:07:26 -04:00
Jamison Prineandjazzyjames 2be08bc67d feat: adjustable RAM and VRAM caps for the container (#1537)
* feat: adjustable RAM and VRAM caps for the container

Performance > Memory gets two sliders:

- RAM cap (soft, Steam sessions): when the session is resident above the
  cap, the session watcher compresses the Steam client into zRAM, waits for
  its page-out acknowledgment, then compresses the rest of the session
  (processes that loaded libblsession) if still over. Nothing is closed.
  Measured at most every 30 s; the client is read back once usage drops
  below the cap. Invalid values leave the cap off. Needs zRAM swap.
- VRAM cap: sets dxgi.maxDeviceMemory ahead of the game's own DXVK_CONFIG,
  so Direct3D games budget for less video memory. A game profile still
  wins. Applies from the next session.

* fix: back off the RAM cap when paging out cannot get under it

A cap below the game's working set was never met, so the watcher paged out
the client and the whole session every 30 s for the rest of the session.
Usage is still measured every 30 s, but after a page-out that leaves the
session over the cap the next one waits twice as long (60 s up to 10 min),
resetting once usage is under the cap. The client step is skipped while the
client is already under 512 MB resident.

* fix: RAM cap covers desktop Steam, signals each group once, no idle churn

- Steam opened from the desktop runs the same session watcher, so the cap
  is exported whenever the Steam client runs, not only in Steam mode.
- The second page-out goes to the rest of the session only; Steam was
  signalled again even when its own step had been skipped as already small.
- A cap page-out while no game runs stays paged out. Reading the client back
  right away pushed it over the cap again and repeated every minute; only a
  game closing while over the cap still defers a read-back.

* fix: RAM cap marks the client packed only when it compressed it

When the cap skipped the client as already small during a game, it still
recorded the client as packed, so the regular in-game client page-out
waited up to 300 s for a page-out that never happened.

---------

Co-authored-by: jazzyjames <[email protected]>
2026-10-25 12:06:45 -04:00
Kurt Himebauch dfe8227f49 fix: run RoboCop: Rogue City at D3D12 feature level 12_1 (#1553) 2026-10-25 11:58:38 -04:00
Kurt Himebauch 3995fe3829 fix: install XACT audio with Skyrim so its voice lines play (#1554)
* fix: install XACT audio with Skyrim so its voice lines play

* fix: rescan cached component findings when the detectors change
2026-10-25 11:50:47 -04:00
Kurt Himebauch 9cecd528e8 fix: Start without Steam uses the launch options set in Steam's Properties (#1552)
* fix: Start without Steam uses the launch options set in Steam's Properties

* fix: treat a malformed shortcuts.vdf as having no launch options
2026-10-25 11:50:44 -04:00
Kurt Himebauch 709c041b24 perf: pause the home page's ambient motion after 20 s without input (#1550)
* perf: pause the home page's ambient motion after 20 s without input

* fix: restart the idle timer when a touch gesture ends
2026-10-25 11:41:39 -04:00
Kurt Himebauch e62698e395 fix: show when non-Steam games were last played, from Steam's shortcuts (#1551) 2026-10-25 11:33:24 -04:00
MaxsTechReview c9565bc0b9 Skip umu-protonfixes for Black Ops III and Dark Souls Remastered (#1508)
GE-based Protons, the default with the x86-64 Steam client, run umu-protonfixes. Its Black Ops III fix switches the prefix to Wine's ALSA driver, which has no device here and replaces DirectAudio, and its Dark Souls Remastered fix runs the VC++ 2017 installer, which fails under ARM64 Proton.
2026-10-25 11:28:28 -04:00
MaxsTechReview a7753a667c fix: Flatpak apps see the device's network, so WebRTC streams connect (#1548) 2026-10-25 11:28:25 -04:00
maxjivi05 6ef2bcc9c5 docs: Star History rank badge above the awards 2026-10-25 10:24:48 -04:00
MaxsTechReview 3fdc74051c feat: DroidDeck-Proton is downloaded, kept up to date and used by default (#1540) 2026-10-25 10:09:22 -04:00
ovurrsl 287eafed0c Merge current main into PR #1042 2026-10-25 16:29:58 +03:00
ovurrsl 9119ae96a5 fix: the stalled-download cancel test gives a loaded CI runner time to tear the transfer down
The cancel and the download's return each had 2 s, which a busy release-test runner missed while the test passes locally every time. The held server body still guarantees the download only returns because of the cancel.
2026-10-25 16:29:57 +03:00
Kurt Himebauch 2a69505199 feat: merge Games and store libraries into one Library and one game page (#1460) 2026-10-25 08:39:56 -04:00
Kurt Himebauch 0cf5b42788 Add an in-session microphone toggle (#1506) 2026-10-25 08:37:49 -04:00
0x1Nawaf aa1a8da39a merge: with main branch 2026-10-25 10:38:42 +00:00
0x1Nawaf 73e158ab3b fix: compress only while the running game is focused 2026-10-25 10:38:37 +00:00
diegodru 1f42f512e2 fix: GOG sign-in pages use the browser's own user agent (#1500)
The GOG sign-in WebView sent GOG Galaxy's Windows user agent while the page's scripts and client hints report an Android WebView, so Cloudflare's check on gog.com never passed (#1486). The sign-in flow now uses the WebView's own Chrome user agent, like the Epic, Google, Facebook and Apple sign-ins. API calls outside the WebView keep the Galaxy user agent.

Fixes #1486
2026-10-25 00:06:54 -04:00
The412Banner c5d7cdc058 Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation (#1531)
Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation

Stream Xbox games with Xbox Cloud Gaming (Game Pass) from DroidDeck, outside
the Linux session.

- Backend: `xodus-cli cloud-token` (tools/xbox/droiddeck-cloud-token.patch)
  gets the streaming tokens; they stay in memory.
- Web player (default): the Greenlight player page (tools/xcloud-player, MIT)
  in a WebView, plus Microsoft's own xbox.com/play player. In-stream menu with
  screen effects (sharpen, colour, deband, FXAA, CRT, NTSC, Toon, fps cap),
  stream quality, performance overlay (level, layout, top right / top left)
  and rumble.
- Native player (opt-in, Android 10+, Player engine: Native): Google's WebRTC
  (io.github.webrtc-sdk 125.6422.07, BSD) with the Greenlight protocol ported
  to Kotlin, in its own process (:xcloudnative) with its own instance of
  DroidDeck's compositor. Decoded frames reach the compositor as
  AHardwareBuffers (new stream mode, VK_ANDROID_external_memory_android_
  hardware_buffer import), so DDFG 2x and LSFG 2x/3x/4x run on them. Live
  frame generation, frame-rate target (presets and a 10-165 fps slider, even
  pacing), zero-copy picture, small network buffer, fast decoder, instant
  controller input, overlay with engine and on-screen fps, background end,
  restart and web fallback. It takes the main process's tokens through
  XboxCloudTokenService; they are fetched ahead while a cloud game is shown.
- The compositor requests the dma-buf import extensions only where the driver
  has them (the system driver has none).
- Catalog: cloud availability, Game Pass plan CFQ7TTC0K6L8 counted, library
  filter All / Owned / Game Pass / Cloud, Stream and Add to Games on the store
  page, stream-only Games-tab entries (Stream + Download / Play + Stream).
- Xbox games get VC++ 2015-2022 and the ARM64 runtime registry entries
  (vcredist2022_arm64, built in until the catalog has it) before each launch.
- Controller focus reaches pickers opened from a game page (install location,
  uninstall); Add / Remove from Games keep the focus.

Credits: Greenlight by UnknownSKL (Jim Kroon), MIT, player and protocol;
Better xCloud for the resolution / OS mapping; MGamer404's Bannerlator #627
for the ARM64 runtime approach. Ported from the412banner's Bannerlator.
2026-10-24 23:17:57 -04:00
The412BannerandKeegantheVeganat0r 5ca1f610ab Xbox: fix sign-in on phones with a slow StrongBox Keystore (#1521)
On some phones (tester: Honor BKQ-N49, Android 17) Xbox sign-in stopped with
"the Xbox helper stopped with exit code 1" before Microsoft's page opened.
The backend asks the app to read and save credentials over a socket and waits
10 s for each reply. The app sealed credentials with a StrongBox-backed
Keystore key, and each StrongBox operation took 7-15 s on that phone, so the
backend gave up. The app still finished those saves afterwards, leaving a
record without a username that every later attempt had to read back slowly.

- Seal new store credentials with a TEE Keystore key (v2 envelopes); v1
  envelopes still open with the old key and move to v2 on their next write.
- A sign-in discards an Xbox record without a username (an unfinished
  sign-in) before starting the backend.
- The backend waits 30 s for the app's credential reply.
- The backend names the failing step instead of only credential-io (socket
  connect/exchange, session lock, missing socket name/port/key), and falls
  back to a keyed 127.0.0.1 port when the abstract socket is refused.
- The app's credential listener logs and retries accept errors, serves each
  connection on its own thread, and logs slow requests with per-stage times.

Refs #1337, #1300


Co-authored-by: KeegantheVeganat0r <[email protected]>
2026-10-25 01:39:09 +00:00
maxjivi05 82d8eb3b79 Keep a Proton choice unless the shared build needs the other platform 2026-10-24 21:13:58 -04:00
maxjivi05 5f0e6a8b7e Match Proton tools across clients by their arm64 and a64 names 2026-10-24 21:13:58 -04:00
maxjivi05 c866bb90db Share installed games between the arm64 and x86-64 Steam clients 2026-10-24 21:13:58 -04:00
MaxsTechReview 8d407aa1e8 Focus flatpak and other sandboxed shortcuts in gaming mode (#1524) 2026-10-24 20:04:54 -04:00
Kurt Himebauch aca7fca19f fix: keep cancellation polling from ending session teardown early 2026-10-24 19:50:10 -04:00
Kurt Himebauch 96ba915378 fix: publish session stopping before clearing the running flag 2026-10-24 19:50:10 -04:00
Kurt Himebauch 71f84f0593 fix: defer automatic Proton updates until session teardown ends 2026-10-24 19:50:10 -04:00
Kurt Himebauch b56ea6122a fix: exclude pending launches and retain Proton update cooldowns 2026-10-24 19:50:10 -04:00
Kurt Himebauch 8a948fbe45 feat: automatically update installed Proton builds on app open 2026-10-24 19:50:10 -04:00
maxjivi05 74a6a88df8 fix(desktop): keep the Desktop actions and Settings handle on small phones 2026-10-24 18:58:45 -04:00
maxjivi05 c9493993af fix(desktop): keep the Desktop page from overlapping on phones 2026-10-24 18:58:45 -04:00
MaxsTechReview dc08385411 Stop the library helper when the KDE Steam client exits (#1519) 2026-10-24 17:57:59 -04:00
MaxsTechReview 49b6a0739d Present games from the KDE Steam client through software WSI (#1518) 2026-10-24 17:56:40 -04:00
MaxsTechReview 8471682b1c Collect only the Steam log lines a session wrote (#1505) 2026-10-24 14:47:21 -04:00
MaxsTechReview 4dda1c1e52 fix(epic): a paused install keeps what it downloaded (#1504) 2026-10-24 14:47:18 -04:00
Thomas Lee 8156a9649d perf: reuse Steam artwork directory scans (#1466) 2026-10-24 14:35:14 -04:00
FX1922 7b518d7e6a itch.io: platform and tag filters (#1497) 2026-10-24 13:39:57 -04:00
maxjivi05 8d66570d33 Steam file dialog: keep spaces in the chosen path, cancel when GTK 3 is missing or the bridge reconnects 2026-10-24 13:18:04 -04:00
maxjivi05 5a56d9f785 fix(steam): Browse buttons open a file chooser on the ARM64 client
Steam's ARM64 client has no desktop-portal file chooser, so
SteamClient.System.OpenFileDialog failed at once and every Browse button
(Add a Non-Steam Game, shortcut target, library folder) did nothing.
The Steam UI bridge now answers it with a GTK file chooser run under
Steam's reaper, so gamescope shows it over Steam.
2026-10-24 13:18:04 -04:00
MaxsTechReview 77e23cb397 itch.io: browse the Store tab by sort and price, with paging and a disk cache (#1473)
## What this does

The itch.io Store tab used to show only four fixed shelves. This is the
first of a series of PRs that rework it. It adds real browsing: pick an
order and a price filter, and the tab loads itch's own listing for that
choice and keeps loading as you scroll. Login, the library, downloads
and installs are unchanged.

Platform and tag filters, the game page with the install picker, updates
and collections are not in this PR. They come in the following ones.

## How it works

itch serves every listing page as an RSS feed if you add `.xml` to the
address. The app already reads those feeds for the shelves, so browsing
reuses the same parser (`ItchShelves.parse`). Nothing here needs the API
key.

The address is built the way itch builds its own links: sort, then
price, then `platform-<name>`, then `tag-<slug>`.

| Choice | Address |
|---|---|
| Popular (default) | `https://itch.io/games` |
| New & popular | `/games/new-and-popular` |
| Top sellers | `/games/top-sellers` |
| Top rated | `/games/top-rated` |
| Most recent | `/games/newest` |
| Free | `/games/free` |
| On sale | `/games/on-sale` |
| Paid | `/games/store` |
| $5 or less | `/games/5-dollars-or-less` |
| $15 or less | `/games/15-dollars-or-less` |

Choices combine, for example `/games/top-rated/free`, then `.xml` for
the feed. Page 2 and later add `?page=N`.

## Changes

**`stores/itch/ItchBrowse.kt` (new)**
- `BrowseQuery` holds sort, price, platforms and tags. The default query
is the front page, which keeps the existing shelves.
- `ItchBrowse.path` and `url` build the addresses above. Only the
spellings listed above are used. Platforms and tags are already accepted
by the builder, ready for the next PR.
- `ItchBrowse.page` loads one page of results:
- Disk cache under `cacheDir/itch-browse`, keyed by the SHA-1 of the
address, fresh for 3 hours.
  - Requests are made one at a time, at least 1.2 s apart.
  - A 429 answer waits 8 s and tries once more.
- Each request is written to the store log (address, status, number of
games).
- `ItchBrowseState` is the Compose state for the tab: the query, the
games loaded so far, and the loading, ended and failed flags. A
generation counter discards answers that arrive after the user has
changed the query. Paging skips games it has already shown.

**`ui/ItchBrowseBar.kt` (new)**
- A sort chip row and a price chip row, plus a load-more trigger at the
end of the grid.
- Chips are selectable, work with the controller (`controllerConfirm`)
and use the same glide border as the rest of the app.

**`ui/StoresPage.kt`**
- The storefront shows the chip bar for itch.io and, when a non-default
choice is active, the browse results in the existing grid instead of the
shelves.
- Games you already own are marked as owned in the results.
- Empty and failed states have their own messages.

**`stores/itch/ItchShelves.kt`**
- `open(url)` is now `internal` so browsing can reuse it.

**`stores/StoresState.kt`**
- `detailItem` also looks in the browse results for itch.io, so tapping
a result opens its detail page.

**Strings**
- 13 new strings (sort names, price names, "Load more", "Couldn't load
games"), translated in all 33 locales.

**Docs and tests**
- `docs/development/itch-browse.md` explains the address scheme, the
cache and the paging.
- `ItchBrowseTest` checks the addresses the code builds against the ones
listed above.

## Testing

I checked every address with `.xml` added, and all of them load without
errors:
- Every sort, every price filter and every platform on its own.
- Combinations: `top-rated/free`, `top-rated/on-sale`,
`top-rated/store`, `top-rated/5-dollars-or-less`,
`top-rated/free/platform-windows` and
`top-rated/free/platform-windows/tag-pixel-art`.
- Paging: `?page=2` and `top-rated.xml?page=2`.

The unit test locks those addresses in.

## Not in this PR

- Platform and tag filters in the UI, and search.
- The game page, the version picker and install for free games.
- Updates for installed games and itch collections.
- Android builds will link out to the itch.io page. Butler patching
comes last, as an add-on pack.
2026-10-24 11:33:47 -04:00
maxjivi05 1883a8c7cf itch.io browse: drop code comments 2026-10-24 11:15:35 -04:00
maxjivi05 aefd8a3b60 fix(xbox): report a failed class registration in the launch log 2026-10-24 11:14:20 -04:00
maxjivi05 fee8ce91f3 fix(xbox): register the runtime's WinRT classes and load its wintypes so Minecraft starts 2026-10-24 11:14:20 -04:00
maxjivi05 bcb18e3fb7 fix(xbox): match only the 401 rejection Xodus prints 2026-10-24 09:07:13 -04:00
maxjivi05 6855dd4467 fix(xbox): say to sign in again when Microsoft rejects the download's licenses 2026-10-24 09:07:13 -04:00
maxjivi05 a66d7d74a4 fix: Steam Input mouse clicks, scrolling and pointer moves reach games again 2026-10-24 09:07:04 -04:00
Kurt Himebauch 2bd279a0f3 fix: read Steam shader settings off the main thread 2026-10-24 08:54:41 -04:00
maxjivi05 0790cb4390 fix: keep a Proton's originals when its folder could not be fully removed 2026-10-24 08:22:00 -04:00
maxjivi05 ad6d8d824a fix: removing a Proton also deletes its stored component originals 2026-10-24 08:22:00 -04:00
Delete228 5ee3ed07be fix: release panel touch source after tab teardown
Release the panel touch source after child detach callbacks finish publishing button releases. Cover tab switching and whole-panel removal with ownership regressions.

Validation: 22 targeted JVM tests, debug APK build, session-asset check and runtime-input verification passed. Device validation remains pending.
2026-10-24 13:43:09 +03:00
maxjivi05 0b671b2144 fix: match an annotated overlay header and keep the settings file's mode 2026-10-24 06:27:46 -04:00
maxjivi05 31cead9b32 fix: keep blank lines inside the overlay section and stage settings under a unique name 2026-10-24 06:27:46 -04:00
maxjivi05 26c3dcc885 fix: turn off the Ubisoft Connect overlay so Ubisoft games stop crashing at start 2026-10-24 06:27:46 -04:00
FX192 dc57f367e1 itch.io browse: don't re-fire the load-more trigger when a page is appended 2026-10-24 13:07:16 +03:00
FX192 5838587639 itch.io browse: let a failed page be retried instead of ending the list 2026-10-24 13:02:17 +03:00
FX192 7e87cdacb5 Merge remote-tracking branch 'upstream/main' into feat/itch-browse 2026-10-24 12:46:26 +03:00
FX192 dbcbf0b6fa itch.io browse: use itch's own sort, price and platform paths
The sort and price chips now cover everything itch's browse page offers, with the
paths copied from its links: new-and-popular, top-sellers, top-rated and newest
for sorts; free, on-sale, store, 5-dollars-or-less and 15-dollars-or-less for
prices; platform-windows, -osx, -linux, -android, -ios, -web and -mobile-web.
The guessed alternative spelling of the free filter is gone, so a query is one
address. The tests assert the addresses itch itself uses.

New and reworded strings in all 33 locales.
2026-10-24 09:27:49 +03:00
maxjivi05 476642be71 chore: drop the added comments 2026-10-24 01:42:20 -04:00
Nikolay Nadorichev 8c86b718a0 fix: find the signed-in Steam account when loginusers.vdf has no MostRecent
droiddeck-steam-shortcuts and AddedGames.accountConfig took the account
marked "MostRecent" in loginusers.vdf, else the only userdata folder.
The arm64 client here writes no MostRecent, so with two accounts in one
install nothing was picked: no added or store game reached
shortcuts.vdf ("no signed-in account yet; nothing written") and Steam
answered their launch with "App configuration unavailable".

Without MostRecent, take the account marked AutoLogin, then the one with
the latest timestamp. Both sides use the same order so the app reads the
config the guest script writes.
2026-10-24 01:42:20 -04:00
maxjivi05 473ac666b3 Take over Steam's Default startup movie pick 2026-10-24 01:41:08 -04:00
FX192 cc0a4f5aa9 itch.io: browse the Store tab by sort and price, with paging and a disk cache
The Store tab only had four fixed shelves and a title search. A row of sort
(Popular, New, Top rated, Top sellers) and price (Any, Free, On sale) chips now
sets a BrowseQuery; anything but the defaults swaps the shelves for that query's
games, loaded page by page as the grid scrolls.

ItchBrowse builds itch's listing-feed URL (platform and tag segments are in the
builder for the next change), tries each spelling of a price filter until one
answers, spaces requests 1.2 s apart, backs off once on a 429, and keeps pages
in the app cache for three hours. Every request is logged with its status and
game count so a wrong URL guess can be fixed from a device. Owned games replace
their browse copy so Install and the owned markers are right, and the detail
page finds browse results.

Strings for all 33 locales; URL-builder tests; docs/development/itch-browse.md.
2026-10-24 07:49:05 +03:00
maxjivi05 f89dc04072 fix: Proton's EasyAntiCheat and BattlEye runtimes are not listed as games
Steam installs Proton EasyAntiCheat Runtime (1826330) and Proton BattlEye Runtime (1161040) as tool depots. The launcher's Games tab and the desktop's game entries now skip them like the other runtimes.
2026-10-24 00:47:45 -04:00
maxjivi05 dd79f44b3a fix: the Steam download monitor no longer stalls the main thread on a long content log
The content log was read with RandomAccessFile.readLine, one byte per read, on the main thread. A 4 MB log took about 2 s on the first poll, enough for an ANR on slower devices while a download runs. It is now read through a buffered reader with the same Latin-1 lines and offset, and the per-line regexes only run on lines that can match.
2026-10-24 00:27:22 -04:00
maxjivi05 b0e405ebf3 fix: Epic client imports get the launch command, versioned installs need a matching manifest build
Games the Epic client installed now carry their LaunchCommand arguments too, split by a new launch.split op. A doubled quote inside a quoted argument is one literal quote, and an install with a version ignores a manifest without the same build.
2026-10-23 23:59:37 -04:00
maxjivi05 42026b382f fix: keep manifest launch arguments to the installed build and their own additions
The manifest's arguments apply only when its build matches the installed version, only arguments the merge inserted are tracked as its own, escaped quotes split like a Windows command line, and the REDprelauncher rule needs Cyberpunk2077.exe beside it.
2026-10-23 23:59:37 -04:00
maxjivi05 8209a707f4 fix: Caravan SandWitch and Cyberpunk 2077 run at D3D12 feature level 12_1
Feature level 12_2 makes vkd3d-proton report mesh shaders and ray tracing that Turnip doesn't provide. Caravan SandWitch (Unreal Engine 5) then builds mesh shader pipelines without a pipeline layout and the driver crashes; Cyberpunk 2077 stops at ray tracing initialization. Only these two games get 12_1, and a value set in a game's own settings still wins.
2026-10-23 23:59:37 -04:00
maxjivi05 46cd8e1777 fix: Epic games get their manifest launch command
Epic manifests carry a launch command next to the launch executable, which the Epic launcher passes to the game. It was read and dropped, so Caravan SandWitch never got -PCStore=EGS and quit with "started outside of any known PC store". The manifest's launch command now joins the game's arguments, including for games already installed.
2026-10-23 23:59:37 -04:00
maxjivi05 6e1621fe53 fix: Steam's Ubisoft games install Ubisoft Connect again
The redistributable seeder marked every process in a game's install scripts as already run, so the Ubisoft Connect installer that Ubisoft games list was skipped and they stopped with "Unable to locate Uplay PC". It is now left to run like the Vulkan runtime installer, and a marker an earlier seeding wrote is taken back out.
2026-10-23 23:58:39 -04:00
XxJSONDeruloxX 546574e982 fix(xbox): enable proot fast path in Xodus 2026-10-23 23:56:24 -04:00
XxJSONDeruloxX b1ab55856e fix(ui): clear search focus and use store accents 2026-10-23 23:56:24 -04:00
XxJSONDeruloxX 4a0eb14d1f fix(xbox): mount selected SD library in Xodus 2026-10-23 23:56:24 -04:00
XxJSONDeruloxX 2c8ae3753c fix(xbox): mount external storage for downloads 2026-10-23 23:56:24 -04:00
Kurt Himebauch fcfbb74068 fix: one refused thermal or power query no longer drops the others from the session report (#991) 2026-10-23 23:43:01 -04:00
MaxsTechReview 0340be5a7d Steam client download: resumes, retries, shows megabytes, and isn't paused out of sight (#587)
A user's first Steam setup never finished on an Honor Pad (Snapdragon 6
Gen 1, metered Wi-Fi with a 14 Mbit/s link). Their log stops at
`downloading Steam: bins_linuxarm64_linuxarm64 (2/17)`. The log was
shared 31 s into the session, so nothing had failed yet. The way the
download works explains why it never got further.

## Why

- **The session pauses the download.** With the default background
behavior (Manual), a session pauses as soon as DroidDeck is hidden or
the screen turns off. In the user's log that happened one second after
start, before the download had begun.
- "Keep downloads running" doesn't help. It's off by default, and it
only recognizes Steam's own game downloads, not the runtime fetching the
client.
- A first run shows nothing for minutes, so leaving the app or letting
the screen time out is exactly what people do.
- **The download is bigger than the screen says.** The client is about
360 MB in 17 parts, and part 2 alone is 110 MB. The loading screen said
"~1-2 min" and counted parts, not bytes, so on a slow link it looks
frozen at about 5%.
- **Nothing handles a bad connection.** Each part was a plain `curl
-fsSL` with no timeout, no retry and no progress output.
  - A stalled connection could wait forever.
- A failed one ended the session and threw away everything fetched, so
the next try started from part 1.

## What changes

- **`droiddeck-steam-install`**:
- Parts go to `package/droiddeck-download` and stay there until the
client is installed.
  - Each part resumes from the bytes on disk (`curl -C -`).
  - A transfer under 4 KB/s for a minute is dropped and resumed.
- Attempts that make no progress back off 5–30 s, then give up with a
clear error. Whatever arrived is kept.
  - A finished part is marked unpacked, so a later session skips it.
- Bytes that fail their checksum are fetched whole again. A server that
won't resume starts the part over. Parts left over from another client
version are dropped.
- A repair redoes every part once, and keeps what an interrupted repair
already redid.
- Every 5 s the step line also carries megabytes overall: `… (2/17), 45
of 360 MB`.
- **Loading screen** (`LoadingState`): shows `Downloading the Steam
client · 45 / 360 MB`, with the bar following the bytes. Older runtimes
still show the part name.
- **`SessionService`**: until the session's first frame, the download
watch also checks whether the client is still to be fetched (no install
stamp or client binary, or a repair requested). While it is:
- the session stays awake with its wake and Wi-Fi locks, whatever the
downloads setting says;
  - the notification shows "Steam download active" with **Suspend now**;
  - a `steam.client_download` event marks start and end.
- The watch is set up before the first suspend decision, so a session
started with the screen off isn't paused first. Once the client is
there, the chosen behavior applies again.
- **`droiddeck-session`**: the step no longer promises 1–2 minutes.

## Testing

- `python3 -m unittest discover -s tools/tests -p 'test_*.py'`: 497 OK,
1 skipped.
- `test_steam_install.py` gets a fake curl that drops connections,
refuses to resume or fails on cue.
- New cases cover: megabyte progress; resuming after a dropped
connection; keeping finished and partial parts across a failed session;
restarting a part the server won't resume; re-fetching bad bytes;
dropping another version's parts; a repair resumed once.
- Kotlin: `:app:testDebugUnitTest --tests
'com.droiddeck.launcher.session.*'` passes. That includes the new
`LoadingStateTest` and the `clientInstallPending` cases in
`SteamDownloadMonitorTest`.
- RedMagic NP06J, with Background behavior set to Manual and "Keep
downloads running" off (the user's settings), and a client repair
requested:
- Started Steam and sent DroidDeck to the background straight away. The
client download stayed active for 30 s with no pause, and all 360 MB
arrived. Then the session paused as Manual should.
- Stopped a second repair during part 2. Part 1 was marked unpacked, the
110 MB part 2 was kept, and the repair marker stayed.
- Started again: it resumed at 110 of 360 MB, unpacked part 2 without
fetching it again, finished, and Steam started normally.
- The loading screen showed `Downloading the Steam client · 248 / 360
MB`.

Retested 2026-10-23 on the REDMAGIC tablet after merging main, which
brought in the stable-channel fallback. Background behavior was set to
Manual with "Keep downloads running" off. A client repair was forced,
then Home was pressed right after Start:

| Build | Session while hidden | Client download |
|---|---|---|
| main | suspended 1 s after Home | stuck at part 3/17 after 90 s |
| this PR | kept awake until the client was in | all 17 parts, 362 MB in
46 s with MB progress; suspended once done |

`tools.tests.test_steam_install` (22), `SteamDownloadMonitorTest` and
`LoadingStateTest` pass.

## Not changed

- **The download is the plain zips.** Valve's manifest also lists `.vz`
(LZMA) versions, 228 MB instead of 360 MB. Using them needs a VZ decoder
in the guest; that could be a follow-up.
- **The stall and retry path** is covered by the tests but wasn't forced
on the device.
2026-10-23 23:28:05 -04:00
maxjivi05 132f40ac97 fix: install step progress cannot overflow 2026-10-23 23:11:34 -04:00
maxjivi05 5981bbb1e2 fix: an oversized install step count falls back to the step text 2026-10-23 22:51:28 -04:00
maxjivi05 7419ba114f fix: the client install stays awake until Steam's first frame and its end is recorded 2026-10-23 22:22:14 -04:00
maxjivi05 96e2bf992d fix: an HTTP 4xx part hands over to the next CDN at once 2026-10-23 21:20:50 -04:00
Kurt Himebauch ad9eb8eb29 xbox backend x86 to arm cross compilation (#1440) 2026-10-23 21:02:20 -04:00
MaxsTechReview 116d0e4f1a fix: a game started from another launcher returns to that launcher (#1438)
A game started from another launcher (ES-DE, a `droiddeck://game/` link,
a pinned shortcut) should send the player back to that launcher when it
closes. Instead DroidDeck always opened the Android home screen, which
is a different place whenever that launcher isn't the default Home app.

## Cause
The link is handled by `FrontEndActivity`, which starts the session in
its own task and stays behind it in a second DroidDeck task. When the
session ended, DroidDeck started `CATEGORY_HOME` to cover that leftover
task, so the player always landed on the system Home.

## Fix
When a link-launched session ends, `SessionActivity` removes the task
that handled the link (a `FrontEndActivity` task whose base intent is a
game link), then removes its own task. Android then shows whatever was
underneath: ES-DE, any other app that launched the game, or Home when
Home launched it.

Sessions started from DroidDeck's own Games tab are unchanged.

## Testing
On a REDMAGIC NP06J, with the `droiddeck://game/945360` link started
using ES-DE's flags (`NEW_TASK | CLEAR_TASK`). Among Us was closed and
"Return to launcher after a game" was on.

| Launched from | main | this branch |
|---|---|---|
| Calculator (stands in for a non-Home frontend) | Android home |
Calculator |
| Home | Android home | Android home |
| DroidDeck's Games tab | DroidDeck | DroidDeck |

## Credits
Reported on Discord by whatsupbros.
2026-10-23 20:45:32 -04:00
maxjivi05 4a57c6f6f1 chore: drop the comments this change added 2026-10-23 20:45:14 -04:00
maxjivi05 2d9d4dbeb0 Merge branch 'main' into fix/steam-client-download
# Conflicts:
#	tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-install
#	tools/tests/test_steam_install.py
2026-10-23 20:23:44 -04:00
MaxsTechReview 866551e797 fix: Snapdragon 8 Gen 2 no longer pins Steam to one core by default (#1441)
## Summary

Since 0.5.0, Snapdragon 8 Gen 2 devices (Retroid Pocket 6, Galaxy S23,
AYN Thor) pin the Steam client, its UI helper and gamescope to the
single prime core whenever the core settings were never touched. The
Steam menu and overlay lag on those devices, and games launched from
Steam get CPU-starved.

This drops that default, so 8 Gen 2 behaves like 0.4.0 and every other
chip: Android schedules Steam on all cores. Core choices users saved in
Performance are kept, and the all-core restore for games launched from a
pinned Steam client stays.

## Evidence

The device reports attached to both bug reports show the default in
effect:

| Device | SoC | Client core override | Client cores |
|---|---|---|---|
| Retroid Pocket 6 | QCS8550 | true (never set) | 7 |
| Galaxy S23 | SM8550 | true (never set) | 7 |

Both reporters say 0.4.0, which left Steam on all 8 cores, was smooth.
The PR that added the default did not benchmark it.

Tablet bench (REDMAGIC, SM8850): the 8 Gen 2 default was reproduced by
pinning Steam to one prime core (7) and games to cores 0-3. The test
scrolls the Big Picture library with the d-pad for 20 s and records
frame pacing. Two runs each:

| Steam client | UI fps | 1% low | p99 frame | off-cadence |
|---|---|---|---|---|
| all cores (this PR) | 114.4 / 115.2 | **62.5 / 62.5** | 16 ms | 4.7 /
4.0 % |
| one core (old default) | 109.7 / 110.9 | 41.7 / 41.7 | 24 ms | 8.0 /
7.0 % |

Among Us ran the same either way (58.9 vs 59.0 fps).

## Tests

`CoreAllocationPrefsTest`: absent preferences leave every core to the
scheduler, and saved choices are kept.

## Credits

- shanume (RP6) and Kacper (S23): reports, and the 8-cores-vs-1 finding.
- Kurt Himebauch (#654): the core allocation work this keeps, apart from
the default.
2026-10-23 20:22:23 -04:00
maxjivi05 a0c6545e77 fix: a link that joins DroidDeck's open task still returns to the caller 2026-10-23 20:22:05 -04:00
Delete228 884671a065 fix: keep select mapping and panel input ownership consistent
Keep Select-as-Steam in the existing one-time profile migration and remove the obsolete bridge flag. Raw SELECT state stays stable across router attachment changes, while explicit native, disabled and local profile bindings remain authoritative.

Let the panel close its shared DeckControls source. Detached sticks, D-pads and face buttons release only their own controls, preserving other trackpad contacts and clicks.

Add seven regressions. All 2,246 JVM tests pass; the debug APK build, session assets and runtime inputs validate. Thor validation remains pending.
2026-10-24 02:52:09 +03:00
maxjivi05 c7bcd82782 fix: Snapdragon 8 Gen 2 no longer pins Steam to one core by default 2026-10-23 19:47:41 -04:00
maxjivi05 caaf6b3a01 fix: keep the link task id when an in-app resume reaches the session 2026-10-23 19:47:32 -04:00
superboo07 8123d5b361 xbox backend x86 to arm cross compilation 2026-10-23 23:40:49 +00:00
Delete228 1dae4bf8ee fix: preserve input lifecycle and system actions
Keep unused pad rings closed during profile and enable changes. Route Steam menu and VR stick-switch pulses independently of primary bindings, preserving physical holds and cancelling stale releases.

Seed replacement routes with retained source state and require held controls to return to neutral. Add nine regressions and document validation: 2,239 JVM tests and the debug APK build pass; device validation remains pending.
2026-10-24 02:30:11 +03:00
Kurt Himebauch 2b4bba63c5 docs: add the Trendshift #1 daily and weekly badges to the README (#1435) 2026-10-23 19:18:00 -04:00
maxjivi05 69b2b8fcdf fix: only close the game-link task that started the session 2026-10-23 19:17:08 -04:00
MaxsTechReview 0d8b5a7cf2 fix: stored components can be deleted after a Proton update (#1434)
After a Proton update, stored DXVK / VKD3D / FEX packages can't be
deleted. Components says they're "in use" by a Proton that's gone
(`DroidDeck-Proton_11-9.1` after updating to 11-11.1) or by a bare
number (`4005446849`).

## Cause
`deletePackage` and the Storage page's in-use check count every recorded
selection:
- **Removed Protons:** swaps on Protons that are no longer installed.
- **Out-of-date swaps:** swaps from before the Proton's version changed.
The launcher already ignores these.
- **Game profiles:** a per-game pick blocked the delete with the "swap
that Proton" message, naming the game's id.

## Fix
- **Out-of-date selections:** a selection only counts while its Proton
is installed and at the version the swap was made on. That's the rule
launches already use. Deleting a package removes its stale entries.
- **Profile picks:** deleting a package also clears it from any game's
component profile, so those games go back to the Proton's own copy.
Storage cleanup still keeps profile picks.
- **Live swaps:** a package swapped into an installed Proton is still
blocked. The message now shows the Proton's name rather than its id.

No new strings.

## Testing
On a REDMAGIC NP06J, with the state from the reports recreated:

| Case | main | this branch |
|---|---|---|
| DXVK swapped into a removed `DroidDeck-Proton_11-9.1` | Deleting
failed: in use in DroidDeck-Proton_11-9.1 | Deleted, stale entry removed
|
| FEX picked in game profile `4005446849` | Deleting failed: in use in
4005446849 | Deleted, profile cleared |
| DXVK swapped into installed Proton Experimental | blocked | blocked:
in use in Proton Experimental (ARM64) |

<img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/main-stale-proton.jpg"
width="420"> <img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/main-game-profile.jpg"
width="420">
<img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/fix-game-profile.jpg"
width="420"> <img
src="https://raw.githubusercontent.com/maxjivi05/DroidDeck/pr-media/1434/fix-live-blocked.jpg"
width="420">

`ComponentsSelectionTest` covers removed, out-of-date and live
selections.

## Credits
Reported on Discord by Siigonis, Bado and woodrow1712.
2026-10-23 19:13:29 -04:00
maxjivi05 7261b26264 fix: a game started from another launcher returns to that launcher 2026-10-23 18:56:44 -04:00
maxjivi05 db9aae7b87 fix: delete the package before saving, and match Protons the same way 2026-10-23 18:56:24 -04:00
delete d202233beb docs: describe input architecture and Thor validation limits 2026-10-24 01:53:51 +03:00
delete 6530faaf78 fix: preserve shared input ownership and reset profile settings 2026-10-24 01:53:51 +03:00
delete 3dd6aeef46 fix: keep mapped desktop pointer visible and update it each frame 2026-10-24 01:43:34 +03:00
delete 45a496b802 fix: preserve temporary-file modes in input preload open wrappers 2026-10-24 01:43:34 +03:00
delete ab8f41b80e Unify input profiles and configurable controller surfaces 2026-10-24 01:43:34 +03:00
maxjivi05 70b8ec5de4 docs: add the Trendshift #1 daily and weekly badges to the README 2026-10-23 18:36:24 -04:00
maxjivi05 94b530ecb2 fix: stored components can be deleted after a Proton update 2026-10-23 18:28:35 -04:00
MaxsTechReview 906e0d1f7d fix: other apps can record game audio (#1430)
Fixes #1314.

Screen recorders and apps that use `AudioPlaybackCapture`
(MediaProjection) get silence from DroidDeck. The app targets SDK 28,
and Android blocks playback capture for apps below SDK 29 unless they
opt in. That was a side effect of the target SDK, not a choice.
`android:allowAudioPlaybackCapture="true"` opts in. Game audio already
plays with `AAUDIO_USAGE_GAME`, a usage that capture allows.

REDMAGIC NP06J with Among Us running, `dumpsys media.audio_policy`
("Allow playback capture log"); the game's streams are AAudio
`USAGE_GAME` with no capture-blocking flags:

| Build | |
| --- | --- |
| main | `uid=10494, allowPlaybackCapture=false,
packageName=com.droiddeck.launcher` |
| this PR | `uid=10494, allowPlaybackCapture=true,
packageName=com.droiddeck.launcher` |

## Credits
Requested and diagnosed by @WilsonNet in #1314.
2026-10-23 18:01:24 -04:00
maxjivi05 992912fdf8 fix: other apps can record game audio 2026-10-23 17:43:44 -04:00
MaxsTechReview 4ff4be2371 fix: keep a slightly early vsync tick for frame pacing (#1405)
Gamescope paces from the presentation timestamp. The Choreographer tick
is often still slightly ahead of the render thread, and that tick was
thrown away, so the draw time was reported instead.

A tick within one refresh of the draw is now kept, whether it is
slightly ahead or slightly behind. A missing tick, or one more than a
refresh away, still falls back to the draw time.
2026-10-23 17:00:32 -04:00
Kurt Himebauch 61482ffee7 perf(ci): reuse verified build inputs
## Summary

Follow-up to #1412, focused on repeat work observed in completed CI
logs.

- Give APK compilation and Android tests the same build-label and
API-key inputs. Previously `generateReleaseBuildConfig` changed between
invocations and caused Kotlin/Java recompilation before tests.
- Continue executing the Android test suite on every CI run: disable
reuse of the test task's results in CI while retaining compilation
caching. Keep the existing heap and process isolation settings.
- Cache the four remote checksum-pinned model/AAR downloads used by
add-on packs. Hash-check every cache hit, atomically replace corrupt
entries only after a verified download, and verify the destination
bytes. Packs are still regenerated from current prepared components and
their checksums remain part of the APK.
- Bound download time and retry connection resets as well as HTTP
errors; keep checksum mismatches fatal.
- Include the pack recipes in runtime source freshness checks.
- Restrict PR report updates to the workflow bot's own marker comment,
addressing the minor extraction review finding from #1412.

## Evidence and validation

The previous successful run explicitly recorded changed
`BuildConfig.java` as the reason for repeated Kotlin/Java compilation
between assembly and tests. The final #1412 normal run spent 38 seconds
building/downloading packs, 5m59s building the APK, and 3m03s in the
Android test step. A forced run also hit a connection reset while
fetching pack inputs and passed on retry.

Focused tests cover cold/warm input reuse, corrupt-cache repair,
changed-pin invalidation, rejected download bytes, actual local HTTP
connection failure/retry, identical build metadata and bot-only comment
selection. Actionlint passed. The clean Linux full suite passed 1,149
tests (18 skipped) before the final Gradle test-execution policy block.
Host, APK/ABI and Android checks are green on the final head, including
the ordinary PR run against the updated main branch and both controlled
branch attempts below.

Validation will compare cold and warm runs on the same commit, confirm
cache-hit hash verification, confirm app compilation remains up-to-date
in the test invocation, and confirm the Android suite actually executes
on both runs. Source-tree, preload, APK/ABI, all-test, signing and
publishing gates remain.

Existing Gradle, CMake and Cargo caching remains. Native compilation was
tens of seconds in the inspected baseline; no speculative native cache
changes or unmeasured speed claim are included. Android test execution
itself was about 2m30s; this PR removes avoidable setup/recompilation
rather than weakening coverage or changing concurrency.

## Follow-up correction

The first CI run failed two artwork-key tests that assumed the
compiled-in API key was empty. Keeping build inputs consistent exposed
that assumption. Tests now check the configured build key and its
trimmed fallback after clearing a user key; existing pure-function
empty-key and precedence coverage remains. No key values are included in
assertion messages. The failed run confirmed BuildConfig generation and
Kotlin/Java compilation were UP-TO-DATE in the test invocation;
subsequent full runs passed. The merged #1412 main run40007656633 passed
signing, pack publishing, preview announcement and catalog updates.

## Gradle cache audit fixes

Completed-run logs showed setup-gradle was read-only on PRs.
Same-repository PRs and branch runs can now persist their own cache
entries; fork PRs remain read-only. GitHub cache ref isolation is
retained. See [Gradle cache
guidance](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md)
and [GitHub cache
scopes](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching).
PR caches consume additional shared quota; no claim of reuse between
different PR refs is made.

COMMIT_TIME now comes from SOURCE_COMMIT, matching the provenance field
and avoiding timestamp changes from temporary PR merge commits. The
source tree and pack validation remain unchanged. This enables a
meaningful same-commit warm check. Per-commit generated Java metadata
may still cause cross-commit recompilation; moving metadata into
packaged data is a separate candidate, not part of this patch. Head
9835c4e3 passed full CI and the same-commit warm rerun below.

## Confirmed final results

Both controlled runs use exactly
9835c4e3e1 and workflow_dispatch, avoiding
PR merge-tree changes between attempts.

| Measurement | First attempt | Warm attempt | Reduction |
| --- | ---: | ---: | ---: |
| Request to required gate green | 13m09s | 7m11s | 5m58s (45%) |
| Gradle APK build step | 6m11s | 2m08s | 4m03s (66%) |
| Android test step | 2m57s | 2m08s | 49s |
| Add-on pack build step | 40s | 31s | 9s |

[First
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/1)
and [warm
attempt](https://github.com/Droid-Deck/DroidDeck/actions/runs/40015059221/attempts/2)
both passed. Initial queue delay was 3 seconds in each. This is one
observed pair, not a guaranteed latency or isolated attribution of every
timing difference to cache changes. The first attempt already had some
dependency/build cache reuse.

Warm logs confirm the exact head's Gradle-home cache restored; release
Kotlin and Java compilation were FROM-CACHE during assembly, then
UP-TO-DATE during tests. Assembly reported 22 tasks FROM-CACHE. The
Android test executor actually ran; test results were neither cached nor
skipped. All four remote pack input hits were hash-verified; packs were
regenerated. Runtime inputs and APK native exports/dependencies
verified. The ordinary [PR
workflow](https://github.com/Droid-Deck/DroidDeck/actions/runs/40013127993/attempts/2)
also passed against the updated main merge tree.

A fresh Gradle daemon and "no cached configuration" remain expected on a
new runner: configuration/task-graph persistence is distinct from
build-output caching and currently has no configured encryption key. The
measured Kotlin/Java FROM-CACHE results demonstrate output reuse despite
that message.

This proves same-commit warm reuse. New pushes can still invalidate
compilation through changed source, toolchain, generated provenance or
pack metadata, and cache eviction/ref scope can cause misses.
Cross-commit compilation reuse is not established by this pair. Branch
validation intentionally skips main-only publishing; merged #1412's main
run40007656633 separately passed signing and publication. The user
authorized merging after the final cache-recovery correction passed.

## Final cache-recovery correction

Commit c95e1147 addresses the remaining minor review finding: after a
download passes its SHA check, an unexpected directory at the exact
hash-named cache entry is removed before the verified file is promoted.
Invalid downloads leave that directory untouched. A regression test
covers failed verification, successful repair and subsequent warm reuse.
All five focused download tests passed in a clean Linux export. All
final-head PR checks passed in
[run40018498031](https://github.com/Droid-Deck/DroidDeck/actions/runs/40018498031):
host suite ran 1,164 tests (49 platform skips), APK build and native
validation passed, Android tests executed successfully, and the
source/build validation status is green. Benchmark results above remain
measurements of 9835c4e3.
2026-10-23 16:34:50 -04:00
MaxsTechReview 1deae2fa5b chore: drop the comments added with the Unity TSO fix (#1423)
Follow-up to #1415: removes the code comment and docstring it added. No
behavior change.
2026-10-23 16:32:35 -04:00
XxJSONDeruloxX c95e114766 fix(ci): recover directory cache entries 2026-10-23 16:20:42 -04:00
maxjivi05 7eb4717d4c chore: drop the comments added with the Unity TSO fix 2026-10-23 16:16:10 -04:00
MaxsTechReview 0c26a829ec fix: keep FEX's TSO on for Unity games (#1415)
The default FEX preset (Performance + TSO) is fine for Unity. But when
the Default profile is switched to a preset
that turns TSO off (Performance, Extreme, Extreme-gn, Denuvo), Unity
players break. DroidDeck already keeps full TSO
for .NET games (`coreclr.dll`) whatever the preset; this gives Unity
players the same safety net for TSO.

Tested on a Pixel 10 Pro Fold (PowerVR DXT-48-1536), shared preset
Performance (TSO off):

| Game | Build | TSO off | TSO on |
| --- | --- | --- | --- |
| Megabonk | Unity 2023.2 IL2CPP, native Linux, "Linux x86 (FEX)" |
froze on about half of launches (every thread parked on a futex) | 5 of
5 starts, played run stayed responsive |
| Crab Game | Unity IL2CPP, Windows, Proton arm64 | 9 of 10 starts; 1
access violation in `UnityPlayer.dll` (read of a garbage pointer) | 10
of 10 starts |

Measured cost of TSO on Megabonk: none (+2% fps with TSO on, within
noise).

What changes:
- `droiddeck-game-env`: `unity_memory_ordering` sets `FEX_TSOENABLED=1`
when `UnityPlayer.dll`, `UnityPlayer.so` or
`GameAssembly.so` sits next to the executable (or one folder below). A
FEX preset chosen for the game still wins
(fixes sit between the shared preset and the per-game entry, as for
.NET).
- `droiddeck-fex`: the FEX tool now applies the environment half of
these game-file fixes, so native Linux Unity builds
  get it too. Launch arguments some fixes add stay Proton-only.
- Docs paragraph in `docs/development/game-environment.md`; tests in
`test_game_environment.py` and
  `test_droiddeck_fex.py`.

Device check of the change itself: with the session's
`droiddeck-game-env` patched, shared TSO off and no per-game
entry, Crab Game started with `FEX_TSOENABLED=1`.

Related to #1386, but not a fix for its Risk of Rain 2 crash: that game
is Unity Mono, which with the default preset
already has TSO on; the report points at multiblock instead (as the .NET
fix turns off). Both games tested here are
IL2CPP, so this PR doesn't change multiblock. #1386's other points
(`steam_appid.txt`, `device.txt`) aren't covered.

Tests (WSL, on main `16bedf36` with the patch): `test_game_environment`
+ `test_droiddeck_fex` 119 OK. Full `tools/tests`: 1114 tests, one
failure,
`test_appimage_run…test_a_failed_program_shows_the_log_it_wrote`, which
fails the same way without the patch.
2026-10-23 16:15:59 -04:00
MaxsTechReview 9010d275ab fix(steam): the last change to a game's compatibility tool wins (#1347)
Refs #1248

## Problem
Users report that changing a game's compatibility tool in Steam's game
properties doesn't stick: it goes back to DroidDeck's default. #1248 was
closed by #1250, but its reporter still saw it after clearing the game's
DroidDeck profile.

A game's tool can be chosen in two places, DroidDeck's per-game Proton
(`games.json`, written by `ProtonDefault.requestGame`) and Steam.
`plan_mapping` treats every `games.json` entry as an override that
always wins, so the live helper (`droiddeck-steam-compat`, on
`CompatManager.NotifyStateChanged`) sets it back within a second, and
`steam-compatibility` does again at every session start. A `null` entry
("use the default", also written by a profile reset) is forced as the
default, so resetting the profile made it worse.

Reproduced with the real `plan_mapping` (game 220, Steam set to
`droiddeck-fex`):

| `games.json` entry | before |
|---|---|
| none | kept |
| `null` (profile reset / "Default") | reverted to default |
| a DroidDeck Proton | reverted to it |

## Change
Whichever side changed a game's tool last wins:
- A DroidDeck choice is applied once (as now). Once applied, a different
pick in Steam stands:
- a pick of one of DroidDeck's Protons becomes the DroidDeck choice too
(`games.json` updated, so the app shows it);
- any other pick (FEX, a Steam Linux Runtime, clearing the forced tool)
removes the game from `games.json`.
- `null` is applied once and then dropped, so the game follows the
default like one DroidDeck never managed.
- What was applied is kept in `state.json` as `games_applied`. Choices
an earlier build already applied count as applied (when `state.json` is
from an earlier build), so upgrading respects the Steam picks users
already made.
- `games.json` is rewritten only if the app didn't replace it after it
was read, so a newer app choice is never overwritten.
- An x86-only Valve Proton picked in Steam is still replaced
(`unrunnable`), unchanged.

Both paths share the logic: `register_default` (session start, reads
config.vdf; with the client stopped, an app missing from the mapping
counts as cleared) and the live helper's `reconcile`.

## Tests
- `test_steam_compat_live.py` + `test_game_environment.py`: 109 pass
locally (103 existing + 6 new). The 6 new tests fail on the old scripts.
- Steam pick after an app choice is kept live, and a new app choice
applies again
  - Steam pick of a DroidDeck Proton becomes the app's choice
  - "use the default" applies once, then Steam picks stand
  - upgrade: choices an earlier build applied give way to Steam picks
  - an app choice written during a sync is not overwritten
  - session start keeps a Steam pick from config.vdf

Not device-tested.

## Side note: `test_authorship` writes into the global git hooks
`InstalledHooksTest.setUp` runs
`tools/release/install_authorship_hooks.py` in a temp repo, but the
installer reads `core.hooksPath` from the **global** config. On a
machine with a global hooks path, running the suite replaced the user's
global `commit-msg`/`prepare-commit-msg`/`pre-push` with hooks pointing
into the deleted temp repo (every later commit failed), and the saved
originals were deleted with the temp repo. The test should isolate
`HOME`/`GIT_CONFIG_GLOBAL`. Not changed in this PR.
2026-10-23 16:13:28 -04:00
MaxsTechReview 92ef850999 fix: Steam starts on ROMs that refuse the Android ID read (#1421)
On a Redmi K70 (Android 14), tapping Steam drops straight back to the
main screen. The session thread dies while writing the guest network
link:

```
java.lang.IllegalArgumentException: Illegal char: - at offset 8
at android.provider.Settings$Secure.getStringForUser(Settings.java:6486)
at com.droiddeck.launcher.runtime.LinuxNetworkLinkComponent.describe(LinuxNetworkLinkComponent.kt:281)
```

The guest MAC is seeded from `ANDROID_ID`. That ROM throws from the
read, which had no guard. A failed read now uses the same fallback seed
as a missing ID. Devices that can read it keep the MAC they have now.

- `SeededMacTest`: stable MAC from an ID, and a read that throws the
ROM's exception falls back instead of crashing.
- REDMAGIC NP06J: the guest MAC is the same before and after the change
(`16:4e:5b:53:e9:ef`).

## Credits
Reported by lc666 on Discord.
2026-10-23 16:08:53 -04:00
MaxsTechReview 64b94f7cdb fix: the app follows automatic landscape rotation again unless an orientation was picked (#1420)
Fixes #1348, fixes #1372, fixes #1388.

Since 0.6.0 the app ignores the sensor and stays in one landscape side,
even with auto-rotate on. #1287 changed the default orientation from
sensor landscape to fixed landscape, so everyone who never picked an
orientation in settings got locked.

The default is sensor landscape again. A picked orientation is still
saved and used as before.

| | Before | After |
| --- | --- | --- |
| No orientation picked | `SCREEN_ORIENTATION_LANDSCAPE`, no flip |
`SCREEN_ORIENTATION_SENSOR_LANDSCAPE`, follows the sensor |
| Orientation picked in settings | that orientation | that orientation |

Tested on REDMAGIC NP06J (Android 16) with no saved orientation:
`dumpsys activity` reports the requested orientation, and the app flips
with the device.
2026-10-23 16:08:02 -04:00
maxjivi05 06946fe8ae chore: drop comments 2026-10-23 16:01:41 -04:00
MaxsTechReview 36dc4014b7 fix: x86 Linux games under FEX keep LD_PRELOAD libraries given by name (#1419)
## Summary

`droiddeck-fex` kept an `LD_PRELOAD` entry only if `os.path.exists()`
found it. A bare soname that the loader resolves through
`LD_LIBRARY_PATH` doesn't exist relative to the working directory, so it
was dropped before the x86 game started. MelonLoader's bootstrap in
Drova is one example.

Names without a `/` are now passed through. Paths that don't exist and
arm64 libraries are still dropped, as before.

## Proof (REDMAGIC tablet, Stardew Valley on x86 Linux (FEX), launch
options `LD_PRELOAD=libz.so.1 %command%`)

| | before | after |
|---|---|---|
| `LD_PRELOAD` in the FEX game process | missing | `libz.so.1` |
| `libz.so` mappings in the game process | 4 (the arm64 host's) | 10
(the x86 one loaded too) |
| Game reaches its title screen | yes | yes |

## Tests

`tools/tests/test_droiddeck_fex.py` now has a bare soname in the
guest-environment case. It fails on main and passes here; all 51 tests
pass.

## Credits

Reported with the fix idea on the DroidDeck Discord by whatsupbros.
2026-10-23 16:00:27 -04:00
maxjivi05 3e85e4a614 chore: drop comments 2026-10-23 15:51:06 -04:00
maxjivi05 a4abb797a7 Merge remote-tracking branch 'origin/main' into fix/steam-compat-pick-wins 2026-10-23 15:51:00 -04:00
maxjivi05 b6ca673d6e fix: Steam starts on ROMs that refuse the Android ID read 2026-10-23 15:50:16 -04:00
maxjivi05 6378c3f3d2 fix: the app follows automatic landscape rotation again unless an orientation was picked 2026-10-23 15:48:03 -04:00
maxjivi05 f909c7a69c fix: x86 Linux games under FEX keep LD_PRELOAD libraries given by name
droiddeck-fex dropped every LD_PRELOAD entry that was not an existing path, so a bare soname the loader finds through LD_LIBRARY_PATH (MelonLoader's bootstrap in Drova, for one) never reached the game. Names without a slash are now kept; missing paths and arm64 libraries are still dropped.
2026-10-23 15:44:35 -04:00
Kurt Himebauch 2d59f03e7b fix: preserve GPU stats while pinning clocks (#1416)
Enabling GPU clock pinning froze Thor's kernel utilization counters,
leaving the HUD stuck at an arbitrary value (10–11% in the reproduced
sessions). KGSL_PROP_PWRCTRL disables both the governor and its
utilization sampling.

Use a dedicated KGSL context with a maximum-power constraint, renewed by
a small NOP submission every 25 ms. Keep devfreq running, bound the
queue to one submission, and release the context, mapped command buffer
and GPU allocation on stop or failure. Preserve recovery of legacy
global pins and report native setup failures instead of claiming
success.

Validation:
- AYN Thor: reproduced frozen counters with the old pin. The replacement
native implementation, run as a standalone ARM64 harness alongside the
installed Steam/Bulletstorm session, kept 680 MHz while utilization
changed from 17% in Steam to about 4% loading and 44% at the animated
title. A final-source smoke run also returned changing 60–66% samples at
680 MHz.
- Native lifecycle regression passed on Linux and as an ARM64 executable
on Thor: setup failure cleanup, renewal, bounded queue, runtime failure,
restart, and type-3/type-7 NOP selection for older/newer Adreno cores.
- Android native build and Kotlin compilation passed locally (NDK
27.0.12077973).
- CI build, Android tests, native regressions, and signing passed.
Installed the release-signed PR APK on Thor with app data preserved.
With GPU pin enabled, the normal Steam/Bulletstorm session held 680 MHz
while the HUD changed from 3% loading to 53% in the animated game scene
at 60 FPS; raw gpubusy samples also continued changing. This checks
utilization reporting, not long-term gameplay stability.

The earlier session exit was recorded by Android as LOW_MEMORY; this PR
addresses the utilization freeze.
2026-10-23 15:41:12 -04:00
XxJSONDeruloxX 9835c4e3e1 ci: persist PR caches and stabilize build time 2026-10-23 15:39:22 -04:00
XxJSONDeruloxX a7d6279ee3 test: check artwork key normalization 2026-10-23 15:22:12 -04:00
XxJSONDeruloxX 5a806b35f1 test: support configured artwork key 2026-10-23 15:22:01 -04:00
MaxsTechReview 8f0bd51fd8 Merge pull request #1401 from Droid-Deck/perf/ntsync-fast-paths
## Summary

droiddeck-ntsync (the userspace stand-in for the Linux ntsync driver,
Wine sync = ntsync) was slower than droiddeck-esync on every cheap
operation. On the tablet a `SetEvent` cost 0.94 µs against esync's 0.21
µs, and a wait on a signaled event 0.45 µs against 0.08 µs. Four things
made it slow:

- **Signal masking:** every operation blocked and restored the signal
mask, so each call made 2 `rt_sigprocmask` syscalls, and a blocking wait
made 4. The masking itself is needed: if Wine's suspend signal stopped a
thread while it held an object lock, a GC thread that suspended it and
then touched the same object would deadlock.
- **Queueing for objects that were already free:** a wait linked itself
onto every object and unlinked again even when the first object could
simply be taken.
- **Wakes nobody needed:** a futex wake syscall went out for every
handoff, even when the waiter was still spinning and would have seen the
change anyway.
- **One shared counter:** every call took and dropped a reference on a
single process-wide device counter, so threads working on unrelated
objects bounced one cache line between cores.

### Changes

- **Signals are deferred instead of masked:** handlers installed by
Wine's `ntdll.so` run behind a small trampoline. If such a signal
arrives inside a critical section (a per-thread depth count), it is
saved with its `siginfo` and sent again with `rt_tgsigqueueinfo`
(falling back to `tgkill`) as soon as the section ends.
  - `sigaction` queries still return Wine's own handler.
- Synchronous signals (SEGV, BUS, ILL, FPE, TRAP, SYS) are never
wrapped, as before.
  - Handlers installed by anything other than `ntdll.so` are left alone.
- **Wait-any fast path:** an object that is signaled and has nobody
queued on it is taken directly, in index order. A wait whose timeout has
already passed returns `ETIMEDOUT` without building a queue.
- **Short spin before sleeping:** a waiter spins for up to 4 µs, bounded
by time rather than by iteration count, before it sleeps on the futex.
- **Wake only sleeping waiters:** the waiter publishes a `sleeping`
flag, and the waker only calls `FUTEX_WAKE` when it is set. Both sides
use seq_cst ordering, so a wake can't be lost. The queue layout changed,
so `NS_VERSION` is now 2.
- **Per-thread device reference:** a thread takes one reference on the
device the first time it uses it and keeps it until it exits or moves to
another device, instead of two atomics on the shared counter per call.
- **More than four signals in one section:** a fifth distinct signal is
held pending with the thread's signal mask, then released once the
section ends. Its handler never runs while an object lock is held.
- **Replay:** deferred signals are replayed with every signal blocked,
so each one is sent exactly once. A held signal is released at the same
signal-nesting level that held it. Handlers installed with
`SA_RESETHAND` are not wrapped, so a one-shot handler keeps its kernel
behaviour.
- **Waits from a handler during another wait's sleep:** they get their
own queue and, for a second device, a reference for that call only. The
outer wait keeps its queue and can't pick up the inner wait's result.

### ntsync becomes the default

- **New installs:** start on ntsync.
- **Existing installs:** move to ntsync once, on the first start of this
version, whatever sync they were on before (a one-time `ntsyncDefaultV1`
marker). Anything picked after that is kept, including esync, fsync and
wineserver.
- **Wording:** the ntsync switch in Performance no longer says
"experimental" or "for testing", in all 33 languages.
- **Unchanged:** per-game sync choices on a game's settings page still
win over the app-wide choice. Proton builds without ntsync still fall
back to droiddeck-esync, then droiddeck-fsync.
- **Tests:** `SyncBackendTest` covers a fresh install, each earlier
choice moving to ntsync once, and a later choice surviving the next
start. All 2,160 unit tests pass.

## Performance (REDMAGIC tablet)

Windows microbenchmark (`syncbench.exe`, x86-64 under FEX), µs per
operation:

| | esync | ntsync before | ntsync after |
|---|---|---|---|
| SetEvent | 0.57 / 0.70 | 0.51 / 0.65 | 0.38 / 0.35 |
| wait on signaled event | 0.13 / 0.13 | 0.44 / 0.43 | 0.27 / 0.20 |
| wait, timeout 0 | 0.27 / 0.25 | 0.52 / 0.51 | 0.19 / 0.18 |
| semaphore release + wait | 0.72 / 0.68 | 0.66 / 0.63 | 0.12 / 0.16 |
| mutex wait + release | 0.77 / 0.73 | 0.66 / 0.64 | 0.12 / 0.12 |
| wait on 8 objects | 0.10 / 0.09 | 0.59 / 0.57 | 0.15 / 0.15 |
| alertable wait | 0.06 / 0.05 | 0.43 / 0.42 | 0.07 / 0.07 |
| event ping-pong, 2 threads | 25.6 / 24.5 | 24.4 / 29.3 | 1.97 / 1.66 |
| 4 threads on one semaphore | 9.4 / 4.6 | 20.3 / 19.1 | 7.3 / 7.6 |

The backend each run actually used was checked from its file
descriptors:
- **esync:** wineserver held eventfds.
- **ntsync:** wineserver held the `droiddeck-ntsync` device and its
timerfd objects.

Native arm64 benchmark against the device directly, µs (before / after):
- set: 0.31–0.36 → 0.06–0.08
- signaled wait: 0.37–0.45 → 0.04–0.08
- semaphore pair: 0.56–0.58 → 0.06–0.07
- mutex pair: 0.56–0.61 → 0.04
- ping-pong: 15–30 → 1.7–2.5

Removing the shared device counter on its own takes 4 threads working on
their own objects from 0.21 to 0.04 µs per operation (x86-64 host).

Games, default Steam launch, The Forest and PEAK at the same scene each
run, 20 s pacing. Columns are esync, ntsync before and ntsync after;
each cell gives two runs.

| | esync | ntsync before | ntsync after |
|---|---|---|---|
| The Forest Low, fps | 64.5 / 61.9 | 70.1 / 74.8 | 72.1 / 72.1 |
| The Forest Low, CPU | 489 / 511% | 434 / 419% | 436 / 426% |
| The Forest High, fps | 58.3 / 57.8 | 72.0 / 69.8 | 72.5 / 69.9 |
| The Forest High, CPU | 506 / 504% | 417 / 429% | 425 / 435% |
| PEAK, fps | 50.4 / 48.6 | — | 47.0 |
| RDR2, launch to signed in / game process | — | — | 29.2 / 42.0 s and
28.3 / 41.1 s |

Two more games, esync against ntsync after, three runs each, default
settings:

| | esync | ntsync after |
|---|---|---|
| Among Us, menu fps (59 cap) | 59.0 / 58.9 / 59.3 | 59.0 / 59.0 / 58.7
|
| Among Us, launch to main menu | 6.65 / 6.84 / 6.85 s | 6.39 / 6.52 /
6.64 s |
| Among Us, CPU | 123 / 132 / 133% | 133 / 133 / 124% |
| Mortal Kombat X attract fight, fps | 56.7 / 54.1 / 56.3 | 61.1 / 56.9
/ 56.2 |
| Mortal Kombat X, 1% low | 20.0 / 30.3 / 30.3 | 30.3 / 30.3 / 30.3 |
| Mortal Kombat X, launch to window | 7.75 / 7.79 / 7.75 s | 7.81 / 7.71
/ 7.63 s |

Mortal Kombat X's CPU moves between 140% and 275% in both modes because
the attract mode changes fighters and stage, so it isn't compared.

The Forest's 1% low was 41.7 fps in every run. PEAK is GPU-bound and
stays in run-to-run noise. Its second ntsync run stopped at the title
menu because the rig's menu input didn't register; the game was still
running at 87 fps, and the same rig miss happened on esync before. RDR2
signs in and reaches the game on the new code.

## Tests

- **`tools/tests/test_ntsync_preload.py` (new):** builds the preload and
checks the emulation against the semantics documented for the kernel
driver:
  - semaphore counts and `EOVERFLOW`;
- mutex owners, recursion, `EPERM`, kill/abandon with `EOWNERDEAD` on
read and on the next wait;
  - auto and manual events, set/reset/pulse previous state;
- wait-any index order, duplicates, the alert object, `EINVAL` limits,
timeouts;
  - wait-all atomicity, duplicates and alert;
- wakes across threads and across processes (inherited fds and fds sent
with `SCM_RIGHTS`);
- 20,000-round ping-pong and 4-thread semaphore stress with infinite
timeouts;
- two signal cases. In one, a handler that re-enters the same object is
fired 2,000 times at a busy thread. In the other, a handler parks the
thread while another thread uses the same object, as Wine's suspend does
under a GC.
- eight real-time signals fired in bursts at a thread busy on one
object, each handler touching that object, so more signals arrive inside
a section than can be deferred;
- a handler that waits on the same device and on a second device while
its thread sleeps in another wait;
- a one-shot (`SA_RESETHAND`) handler installed and fired 300 times at a
busy thread, every one of which must run.
- **The signal tests catch a broken implementation:** with signal
protection removed, the program hangs and the test fails. Before the
last fix, the burst test hung and the outer wait returned the nested
wait's result.
- **The suite passes:** 8 of 8 runs natively on arm64 under proot on the
tablet, and 40 of 40 on the host.
- **On the final build:** The Forest Low ran at 77.7 fps, PEAK at 47.5
fps, and Among Us and two Mortal Kombat X runs played through.
- **The new paths stay out of the way in games:** a build that logged
them saw about 1,400 Wine signals land during ntsync sleeps over two
Mortal Kombat X runs and one The Forest run. None of those handlers
waited on ntsync and no signal was held back, so games run the same code
as before the last fix. The Forest Low ran at 71.6 fps and PEAK at 47.2
fps on the final build.
- **Existing preload tests still pass:** fsync, openat2, usr2 (also with
`BL_SYNC=1`), tracer, dircache, noexec, binfmt, faultreport and esync
packs.

## Credits

- [Linux](https://github.com/torvalds/linux)
`Documentation/userspace-api/ntsync.rst` (GPL-2.0): the object, wait and
error semantics the new test checks. Also added to the README credits.
2026-10-23 15:10:28 -04:00
XxJSONDeruloxX 6cc289f7d3 test: avoid GPU pin scheduling races 2026-10-23 15:06:27 -04:00
XxJSONDeruloxX 9c66a1ac0a fix: select NOP packets for older Adreno cores 2026-10-23 15:04:50 -04:00
XxJSONDeruloxX 556bd96d5a ci: reuse build metadata and verified pack downloads 2026-10-23 15:01:04 -04:00
XxJSONDeruloxX 4d86839910 fix: preserve GPU stats while pinning clocks 2026-10-23 15:00:11 -04:00
Kurt Himebauch 3d9d90d832 ci: consolidate verified runtime components
## Summary

Consolidate runtime preparation into a component registry and reusable
recipes, and reuse approved preload binaries with independent main-build
provenance checks.

- Pin the source tree once. Components, Xbox and Python/native tests
then run independently against that exact tree; APK compilation consumes
verified component artifacts.
- Centralize component inputs, outputs, tool requirements and build
commands under `tools/components`. Cache identities derive from that
registry. Install required Linux tools in one preflight and pin the same
Android NDK across workers.
- Keep small preload input/output manifests in Git. Store the 1.48 MB
binary snapshot in an immutable content-addressed release, rather than
committing executable blobs.
- New or PR-modified manifests must match binaries in an authenticated
successful main APK artifact and its producing source/recipe. Changed
inputs rebuild; missing or temporarily unavailable artifacts rebuild;
corrupt artifacts fail.
- Record the source tree, recipe identities and output hashes in the
components-to-APK handoff. Reject mismatched inventories, traversal and
symlinks.
- Extract component and release shell bodies out of workflow YAML.
Clarify the trusted validation job label. Preserve legacy
workflow/status and gate names consumed by branch rules and trusted main
workflows.

```mermaid
flowchart LR
  Source[Pin source tree] --> Components[Prepare verified components]
  Source --> Xbox[Xbox ARM64 backend]
  Source --> Tests[Python and native tests]
  Components --> APK[APK and Android tests]
  Xbox --> APK
  APK --> Gate[Required aggregate gate]
  Tests --> Gate
  Gate --> Publish[Main only signing and publishing]
```

## Validation

The initial preload implementation passed both [PR
CI](https://github.com/Droid-Deck/DroidDeck/actions/runs/39999620253)
and a [forced source
rebuild](https://github.com/Droid-Deck/DroidDeck/actions/runs/39999657706).
Snapshot restore took 3 seconds and skipped the ARM64 compile; the
forced run compiled both groups. These runs precede the final pipeline
consolidation.

Local clean Linux validation passed 1,142 tests (17 skipped) on the
consolidation commit; the follow-up x86 recipe check passed all 15
focused snapshot tests. Actionlint and focused preparation, NDK,
source-pinning and gate tests passed. Final head d32b1a13 passed [PR
reuse
validation](https://github.com/Droid-Deck/DroidDeck/actions/runs/40004459466)
and a [forced source
rebuild](https://github.com/Droid-Deck/DroidDeck/actions/runs/40004459597);
both full runs are green. PR push-to-green was 19m00s, including 57s
initial queue, a cold Xbox job of 5m48s (compile 5m30s), 50s component
preparation (snapshot restore 8s), APK build 5m59s and Android tests
3m03s. The forced run passed on attempt 2 after a connection reset in
the unchanged pack fetch on attempt 1; rerun APK build 5m24s and tests
2m57s. Both runs verified the prepared artifact handoff and APK/ABI
contents. This is correctness evidence, not a controlled warm-cache
speed benchmark. All existing source checks, regression tests, APK/ABI
checks and signing/publication gates are retained. `workflow_dispatch`
with `rebuild_preloads=true` exercises actual source compilation.

## Trust and scope

The initial snapshot covers 14 core ARM64/x86 preload binaries. It
originates from successful main run
[39992155294](https://github.com/Droid-Deck/DroidDeck/actions/runs/39992155294),
commit `b76f13f7acc0af0cc00369dde61fb88e40529e6d`, APK artifact
`12520502054`; its ZIP SHA-256 is
`b7e800a1884a516ec52b050b7f904f9881427bca4579f660b909fd18f15e49ff`.

This is approved-build reuse, not a signed SLSA attestation or a claim
of reproducible compilation. The pinned-main manifest is the approval
anchor. New manifests require the original main artifact to remain
available for independent verification; otherwise builds fall back to
source. Compiler/system package updates within the same Ubuntu runner
label require a deliberate refresh or forced rebuild. Other components
retain their existing release pins or caches behind registered recipes.

The within-run artifact handoff verifies source/output consistency; it
is not independent provenance for arbitrary artifacts. Main-only
publishing cannot be exercised by a branch dispatch. No controlled
end-to-end speed claim is made before final runs complete. Leave this PR
open for review.
2026-10-23 14:58:10 -04:00
maxjivi05 9438aac0db fix: droiddeck-ntsync leaves one-shot handlers alone and replays deferred signals exactly once
- Handlers installed with SA_RESETHAND are no longer wrapped, so a deferred one-shot signal can't fall through to the default action.
- Deferred signals are replayed with every signal blocked, so a signal landing mid-replay can't send an entry twice.
- Taking a deferral slot and holding a signal are safe when one signal interrupts another, and a held signal is released only at the nesting level that held it, so a handler's return can't block it again.
2026-10-23 14:55:33 -04:00
coffeemeist3r 5d1e70cd20 Keep FEX's TSO on for Unity games
A FEX preset that turns TSO off (Performance, Extreme, Extreme-gn, Denuvo)
breaks Unity players under FEX:
- Megabonk (Unity 2023.2, IL2CPP, native Linux build, "Linux x86 (FEX)")
  froze on about half of its launches, every thread parked on a futex.
  With TSO on: 5 of 5 starts, and a played run stayed responsive.
- Crab Game (Windows IL2CPP build under Proton arm64) crashed on 1 of 5
  starts with an access violation in UnityPlayer.dll on a garbage pointer.
  With TSO on: 5 of 5 starts.

A Unity player (UnityPlayer.dll, UnityPlayer.so or GameAssembly.so beside
the executable) now keeps FEX_TSOENABLED=1 over the shared preset, the way
.NET games already keep full TSO. The FEX tool applies the environment half
of the game-file fixes too, so native Linux builds get it as well. A FEX
preset chosen for the game itself still wins.

Related: #1386 (its Mono crash happens with TSO already on)
2026-10-23 13:38:59 -05:00
XxJSONDeruloxX d32b1a13fd ci: verify x86 container build recipe 2026-10-23 14:34:44 -04:00
XxJSONDeruloxX d7bf0b274f ci: consolidate verified component preparation 2026-10-23 14:31:13 -04:00
XxJSONDeruloxX d111b2b3dd ci: reuse preload snapshots verified against main builds 2026-10-23 13:59:04 -04:00
maxjivi05 293e84a64c feat: droiddeck-ntsync is the default Wine sync
- New installs start on ntsync.
- Existing installs move to ntsync once on the first start of this version, whatever sync they used before. A choice made after that is kept.
- The ntsync switch is no longer labelled experimental or for testing, in every language.
2026-10-23 13:57:38 -04:00
Kurt Himebauch 16bedf3635 fix: keep the UBWC hint consistent after custom guest environment 2026-10-23 13:54:01 -04:00
Kurt Himebauch e06b942e67 fix: warn about UBWC restart only after a hint is active 2026-10-23 13:54:01 -04:00
Kurt Himebauch 2e1156038e test: cover the inclusive A740 blob 700 boundary 2026-10-23 13:54:01 -04:00
Kurt HimebauchandVabian124 dccd5a7c69 fix: match the A740 UBWC hint on driver 512.676.76
Co-authored-by: Vabian124 <[email protected]>
2026-10-23 13:54:01 -04:00
Kurt Himebauch fc768a3c45 feat: add an A740 UBWC compatibility override 2026-10-23 13:54:01 -04:00
XxJSONDeruloxX a4447039b9 ci: install native Vulkan and ARM64 C headers for tests 2026-10-23 13:48:55 -04:00
XxJSONDeruloxX 8355c1495b ci: bound apt phases and retry the official Ubuntu mirror 2026-10-23 13:48:55 -04:00
XxJSONDeruloxX fcf4c09c50 test: verify dependency installation fails closed 2026-10-23 13:48:55 -04:00
XxJSONDeruloxX 74d5daaa5d ci: parallelize regression checks and isolate component caches 2026-10-23 13:48:55 -04:00
0x1Nawaf 7c51e6e904 fix: draw the gamescope cursor only when that option is on
The extra plane costs frames on a phone, so compositing stays off unless Performance asks for it.
2026-10-23 13:30:42 -04:00
Kurt Himebauch b76f13f7ac fix: keep the shader pre-caching choice until a session confirms it, and read it from its own VDF section 2026-10-23 13:05:04 -04:00
Kurt Himebauch f88227ed74 feat: toggle Steam shader pre-caching from DroidDeck's Steam settings 2026-10-23 13:05:04 -04:00
Kurt Himebauch f683ca1dea fix: show the paused card already settled, skip it on auto resume, and cancel manual pauses on a quick return 2026-10-23 12:28:01 -04:00
Kurt Himebauch 6b6d7d2ac7 feat: lift paused frame into a grey card with a glass resume button 2026-10-23 12:28:01 -04:00
maxjivi05 77dd04ec8c fix: droiddeck-ntsync keeps protection when more than four signals arrive and keeps waits intact when a handler waits again
- A fifth distinct signal inside a critical section is now held pending with the thread's signal mask and released once the section ends, instead of running its handler while an object lock is held.
- A wait from a signal handler during another wait's sleep uses its own queue and, for a second device, a reference for that call only, so the outer wait keeps its queue and can't return the inner wait's result.
2026-10-23 12:15:43 -04:00
ovurrsl 70eec3cc32 fix: a half-built steamrt shim is removed, and its launcher accepts --directory DIR 2026-10-23 12:03:41 -04:00
ovurrsl 2eb18f5540 fix: document which Steam client builds Valve's arm64 manifests carry 2026-10-23 12:03:41 -04:00
ovurrsl 7aeb7779bb fix: fall back to Valve's stable Steam client when the beta one keeps failing
The native arm64 client follows steamdeck_publicbeta, so a Valve beta
regression reaches every user at once. If the repaired beta client is still
started twice without being shown, or the beta manifest carries no arm64
client, droiddeck-steam-install now installs steamdeck_stable, keeps it, and
logs why (listing the manifest's blocks in the second case). The session asks
the installer for the channel, so -clientbeta and package/beta stay in sync.
BL_STEAM_CHANNEL or ~/.config/droiddeck/steam-channel (beta|stable) pins it.

Valve's 2026-10-15 beta moved the x86 client into a Steam Runtime container.
In case the arm64 client follows, its session now exports
STEAM_STEAMRT_RECURSION_GUARD=1 and, only if steamrtarm64 carries pv-runtime
or a steam-runtime-check-requirements probe, starts it through the same
steamrt shim the x86-64 client uses (now droiddeck-steamrt-shim) and logs it.
2026-10-23 12:03:41 -04:00
0x1Nawaf 0a8358d11f fix: keep the gamescope cursor out of the zram change
The session script had also picked up the cursor switch from the other branch.
2026-10-23 15:52:35 +00:00
0x1Nawaf 1f1873a18e fix: compress the client only after the game window is focused 2026-10-23 15:46:00 +00:00
0x1Nawaf 53c63b6dc2 fix: keep a slightly early vsync tick for frame pacing 2026-10-23 15:43:52 +00:00
XxJSONDeruloxX b2185be039 fix: close stalled download streams on cancel 2026-10-23 11:26:41 -04:00
FX192 b88eebe09b Inner Gamer: let Compose apply the animator scale once 2026-10-23 11:20:34 -04:00
FX192 5f341dda16 Inner Gamer: a new speed takes over at once, continuing from the current colour 2026-10-23 11:20:34 -04:00
FX192 5a1819a713 Themes.current also reloads the saved Inner Gamer speed 2026-10-23 11:20:34 -04:00
FX192 6b010baa9e Inner Gamer: read the animated hue while composing so the colors keep cycling 2026-10-23 11:20:34 -04:00
FX192 c7961bdacf Fix the build: rename moveRgbHue to avoid clashing with the rgbHue setter; Hindi verb agreement 2026-10-23 11:20:34 -04:00
FX192 f7acb7fcd7 Inner Gamer RGB theme with a speed slider; keep the colour picker's hex box in view above the keyboard
The hex box sat at the bottom of the picker, so with the keyboard up the dialog
was cut off and the field could not be seen. While the box has focus the dialog
now scrolls to its end, and again whenever the visible height changes, so the
hex box and OK / Cancel stay above the keyboard.

Inner Gamer is a new theme just above Gradient. Its accent walks round the
colour wheel on a Compose infinite transition, so Android's animation clock
drives it and it holds still when animations are off. The hue moves in 3 degree
steps so the app restyles only when the colour visibly changes. Setup shows a
Speed slider (1 to 10) while it is selected. The palette comes from
ThemeColors.palette, so button text stays readable at every hue.
2026-10-23 11:20:34 -04:00
Kurt Himebauch bf4aa58c68 Add File Manager Return and Exit navigation (#1301) 2026-10-23 11:04:35 -04:00
Kurt Himebauch 747c6eca87 feat: show dot-prefixed folders in game picker (#1363) 2026-10-23 10:59:38 -04:00
maxjivi05 4bcd4e3cca perf: droiddeck-ntsync without signal-mask syscalls, waits that skip the queue, and per-thread device references 2026-10-23 10:48:06 -04:00
Kurt Himebauch 04dd421699 Merge main and harden diagnostic publication and streaming cleanup 2026-10-23 10:31:46 -04:00
Kurt Himebauch 4babf24433 Merge branch 'main' into fix/install-cancel 2026-10-23 10:16:23 -04:00
Kurt Himebauch a25081df7c Merge branch 'main' into fix/steam-client-download 2026-10-23 10:16:19 -04:00
Kurt Himebauch 8ebba02688 fix: stop the authorship check from flagging people whose names contain an assistant's name (#1391) 2026-10-23 09:14:25 -04:00
Kurt Himebauch 4cbda4e51b fix: release the microphone before freezing a session so the SoC can sleep (#1389) 2026-10-23 08:52:10 -04:00
Kurt Himebauch b7bf189166 fix: stop the authorship check from flagging people whose names contain an assistant's name 2026-10-23 08:32:52 -04:00
Kurt Himebauch f3b5cadb7b fix: release the microphone before freezing a session so the SoC can sleep 2026-10-23 07:57:34 -04:00
MaxsTechReview ac0909eaa3 perf: let Wine read process memory counters from /proc directly (#1374) 2026-10-23 07:18:56 -04:00
Danbro007 abd90ce238 test: 隔离凭据测试的应用启动任务 2026-10-23 17:35:24 +08:00
Danbro007 42de5f3dd1 fix: avoid incompatible Turnip selection and explain Steam startup failures 2026-10-23 16:37:04 +08:00
SuziJay 789209ec37 Merge branch 'main' into feat/show-hidden-game-folders 2026-10-23 14:14:14 +08:00
MaxsTechReview ffa40a15d5 perf: run Wine's arm64 SIGUSR2 context switch without a kernel signal (#1371) 2026-10-23 02:12:49 -04:00
ovurrsl ac032be525 Merge current main into PR #1042 2026-10-23 08:35:44 +03:00
SuziJay 0127fa24aa feat: show dot-prefixed folders in game picker 2026-10-23 09:36:54 +08:00
The412Banner 449dc301bd fix(steam): keep the applied record for a game the client did not report, and apply nothing after a lost write
Review: a game the client briefly refused lost its games_applied record, so its
unchanged request later looked new and overwrote the Steam pick; it now keeps
what was applied before. When the app rewrote games.json during both tries,
the stale second read was applied; that pass now applies no per-game choice
and keeps the record.
2026-10-22 22:29:03 +00:00
The412Banner 663da2948c fix(steam): the last change to a game's compatibility tool wins
A game's tool can be chosen in DroidDeck (per-game Proton) and in Steam's
game properties. When they disagreed, DroidDeck always put its own choice
back: the live helper within a second of the change, and again at every
session start. Resetting the game's DroidDeck profile made it worse - it
saved "use the default" (null), which was then forced onto the game.

A DroidDeck choice is now applied once, and the client's later pick stands:
a pick of one of DroidDeck's Protons becomes the DroidDeck choice too, any
other pick (FEX, a Steam Linux Runtime, clearing the forced tool) ends it.
"Use the default" and a profile reset are applied once and then dropped, so
the game follows the default like one DroidDeck never touched. What was
applied is kept in state.json (games_applied); choices an earlier build
already applied count as applied. games.json is rewritten only if the app
did not replace it meanwhile. An x86-only Proton picked in Steam is still
replaced, since it cannot start on the phone.
2026-10-22 22:12:20 +00:00
MaxsTechReview 22eac0831d fix: keep the default launcher entry named MainActivity so RedMagic Game Space keeps DroidDeck (#1345) 2026-10-22 20:54:12 +00:00
MaxsTechReview 7b7c978edd docs: DroidDeck 0.6.0 release notes (#1343)
* docs: DroidDeck 0.6.0 release notes

* docs: put the 0.6.0 feature GIFs above their features

* docs: say which way is better on every 0.6.0 chart
2026-10-22 16:18:18 -04:00
MaxsTechReview de505d79a1 chore: bump version to 0.6.0 (15) (#1344) 2026-10-22 16:07:07 -04:00
The412Banner 32c85dfba1 fix(xbox): put the helper's sign-in failure code in the shared log (#1342)
A user's log (#1300) showed "the Xbox helper stopped with exit code 1", but
not why. Before it asks for the Microsoft page the helper can only fail while
opening the credential store or registering the device, and it prints a code
for that (NEXUS_CREDENTIAL_ERROR_V1 keyring-unavailable, device-
authentication, ...). Sign-in runs in its own process, where
StoresState.logLine writes no file, so that line only reached logcat. The
sign-in window now writes the helper's known diagnostics to the stores log
itself, and the credential bridge writes why it refused the helper there too.
Lines are stamped by a shared StoreLogFiles.stamped.
2026-10-22 15:56:09 -04:00
MaxsTechReview f27d0b006f perf: Wine's write watches stop costing a signal per page on arm64 (#1333)
Wine asks for userfaultfd with the i386 syscall number on arm64, so it
never gets one and falls back to mprotect and a SIGSEGV per first write,
which under proot is a ptrace stop each time. libblsession hands it the
real userfaultfd and, where Android refuses the PAGEMAP_SCAN ioctl, answers
it from /proc/self/pagemap and UFFDIO_WRITEPROTECT.
2026-10-22 19:53:12 +00:00
MaxsTechReview 9a4a611d38 fix: Steam starts again on phones where PipeWire took every client for a sandboxed app (#1338)
PipeWire's access module checks each client's /proc/<pid>/root for a
Flatpak marker. Where that lookup fails under proot (seen on a nubia
NX809J), PipeWire treats the client as sandboxed and holds it until the
session manager grants permissions, and WirePlumber, itself held, never
does. gamescope then waits forever for its capture stream and never
starts Steam: a black screen until the session is closed.

The session's PipeWire now gives both of its sockets unrestricted access
and WirePlumber keeps those clients at full permissions, so nothing
depends on that lookup.
2026-10-22 19:30:35 +00:00
MaxsTechReviewandKurt Himebauch 006db14561 perf: SD card library files resolve through the fast path instead of proot (#1331)
perf: files in an SD card library resolve through the fast path instead of proot

A library bound at several guest names (droiddeck-sd, bannerlator-sd and
case variants) made every symlink and /proc/self/fd readlink under it fall
back to proot. The fast path now orders its binds the way proot does and
gives each host path the name proot would, and lstat("link/.") follows
the link as the kernel does.

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 19:11:29 +00:00
MaxsTechReview d9c03369a2 Merge branch 'main' into Test 2026-10-22 14:52:13 -04:00
Kurt Himebauch f61525162d feat: games show their logo, and store games get one from the store (#999) 2026-10-22 14:48:23 -04:00
The412BannerandxXJsonDeruloXx 58f0f27f39 fix(xbox): show why Microsoft sign-in fails, and the gamertag instead of the email (#1322)
Sign-in sat on a black "Preparing Microsoft sign-in…" screen while the Xbox
components downloaded and the helper started, then only said "did not
complete" (#1300). The window now names each step (with download progress),
and a failure says which step failed and why. Our own messages are shown as
written; anything else is named by its exception type only. The line also goes
to the stores log, which the shared log zip carries.

The Stores page showed the account's sign-in email. It now shows the Xbox
gamertag, read with `xodus-cli profile` after the library sync on its own
thread (75 s limit: the command allows itself 45 s, plus PRoot start-up), once
per signed-in account. Until it is known the email is shown masked, and only
the store label while credentials are temporarily unreadable. The account key
used for caches and launches is unchanged.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 14:30:38 -04:00
ovurrsl e1e2017590 Merge current main into PR #1042 2026-10-22 21:24:19 +03:00
Kurt Himebauch 633f66fd49 Fix main APK 7zip dependency download (#1334) 2026-10-22 14:13:05 -04:00
Kurt Himebauch 5461886a82 Pin CI 7zip download to verified package mirror 2026-10-22 13:54:22 -04:00
Kurt Himebauch 498a33170f fix: the performance overlay shows the GPU load on Adreno phones whose kgsl busy percentage is unreadable (#1326) 2026-10-22 13:31:38 -04:00
xXJSONDeruloXx 13249402ad Merge main and share GPU busy reads 2026-10-22 13:31:17 -04:00
Kurt Himebauch a50f7333ea fix: latch kgsl GPU busy windows so MangoHud's GPU load stops reading 0% (#1327) 2026-10-22 13:28:24 -04:00
Kurt Himebauch a4eee5c2a4 feat: page the session out to zram before suspending it, and zram is always on where supported (#1317) 2026-10-22 13:28:20 -04:00
Kurt Himebauch 65aeeabfb5 Merge current main into PR #1326 2026-10-22 13:11:10 -04:00
Kurt Himebauch 90efeacb41 Merge current main into PR #1327 2026-10-22 13:11:10 -04:00
Kurt Himebauch 39b5b3cdcd Merge current main into PR #1301 2026-10-22 13:11:09 -04:00
Kurt Himebauch 8c0fcd8475 Merge current main into PR #1317 2026-10-22 13:11:09 -04:00
Kurt Himebauch 1da489aebb Merge current main into PR #1174 2026-10-22 13:11:08 -04:00
Kurt Himebauch e550b3354f Merge current main into PR #1067 2026-10-22 13:11:07 -04:00
Kurt Himebauch 60b9c6602e Merge current main into PR #1061 2026-10-22 13:11:06 -04:00
Kurt Himebauch 18ffc6ea70 Merge current main into PR #1042 2026-10-22 13:11:06 -04:00
Kurt Himebauch 958446ab7c Merge current main into PR #991 2026-10-22 13:11:05 -04:00
Kurt Himebauch 9851339d47 Merge current main into PR #999 2026-10-22 13:11:05 -04:00
Kurt Himebauch bde9ca733f Merge current main into PR #587 2026-10-22 13:11:02 -04:00
Kurt Himebauch e4a7e52f81 Fix nested KWin rendering on Android (#1330)
Select the explicitly configured Android render node for nested KWin 6.8, apply OpenGL settings before Plasma starts, and pin the original signed Mesa 26.2.4 package that passed Thor rendering. Remove build-directory runtime search paths and fail closed on ELF inspection errors.

Native ARM64 build passed. Thor validated a visible nested Wayland terminal, active OpenGL ES/Zink on Turnip, 1280x720 output, and keyboard input. Mesa pin regression tests, launcher and ELF failure controls, shell syntax, and human authorship checks passed. Published desktop pin remains r2; full latest-head CI is pending and maintainer authorized this merge after device validation.
2026-10-22 13:10:16 -04:00
ovurrsl 62d218fe37 fix: cancelling the first-run Proton install also stops its catalog download
installProton fetched the catalog before its cancellation existed, so a stop had to wait for the catalog request. The cancellation is now registered first and passed through DesktopCatalog.fetch to a cancellable downloadString, which disconnects the request and does not count a cancelled fetch as a source failure.
2026-10-22 19:36:10 +03:00
Kurt Himebauch 3c3f550e96 Merge current main into PR #1326 2026-10-22 12:13:54 -04:00
Kurt Himebauch 3501d13d29 Merge current main into PR #1327 2026-10-22 12:13:54 -04:00
Kurt Himebauch e8a8de1b22 Merge current main into PR #1317 2026-10-22 12:13:54 -04:00
Kurt Himebauch 848f13d6a4 Merge current main into PR #1301 2026-10-22 12:13:53 -04:00
Kurt Himebauch 0b616146ff Merge current main into PR #1174 2026-10-22 12:13:52 -04:00
Kurt Himebauch 96a8501343 Merge current main into PR #1067 2026-10-22 12:13:50 -04:00
Kurt Himebauch b4a9dec170 Merge current main into PR #1061 2026-10-22 12:13:50 -04:00
Kurt Himebauch 65a5f69873 Merge current main into PR #1042 2026-10-22 12:13:49 -04:00
Kurt Himebauch 495ff952d2 Merge current main into PR #991 2026-10-22 12:13:48 -04:00
Kurt Himebauch de4b778bd1 Merge current main into PR #999 2026-10-22 12:13:48 -04:00
Kurt Himebauch 0819aa725d Merge current main into PR #587 2026-10-22 12:13:46 -04:00
Kurt Himebauch e1a7d527fd Update the nested KWin builder to 6.8.0 (#1328)
Match the native builder and desktop package override to Arch Linux ARM KWin 6.8.0, pin the official source hash, and port the nested-scaling patch. Check package/source version consistency in both build jobs.

Official source checksum, patch application, live package closure, and shell syntax verified. The published desktop remains pinned to r2; candidate 6.8 device/rendering validation remains pending before any package publication.
2026-10-22 12:13:03 -04:00
Kurt Himebauch 792a69ebfe Combine authorship validation with the required APK result (#1329)
Combine trusted source and squash-metadata validation with the exact-head APK compilation result. Preserve automatic trailer cleanup in the installed hooks.

Validated with 10 authorship/hook tests and 9 combined-gate tests.
2026-10-22 12:11:05 -04:00
ovurrsl 430e77178e fix: one refused thermal or power query no longer drops the others from the session report
Thermal status, thermal headroom and power saver are now read one by
one, so a vendor that throws on one of them still gets the other two
lines, and the refused one shows as unknown.
2026-10-22 18:48:39 +03:00
Resul Övür 9b176753cc fix: keep ambiguous update-channel guesses temporary 2026-10-22 18:46:40 +03:00
ovurrsl ac8957ae56 fix: an unproven update-channel guess no longer moves stable users to Nightly for good
Any release-signed build not running the catalog's current stable commit
was saved as NIGHTLY, so an older stable APK, a release-day install or a
stale cached catalog put stable users on pre-release updates. The guess
is now saved only when the catalog proves it (PR build, the catalog
stable, or a version older than it); otherwise it applies to this run
and is guessed again with a newer catalog.
2026-10-22 18:46:40 +03:00
angelrenefm 6c22248d77 fix: feeds that share a reader also share a failed read 2026-10-22 17:44:34 +02:00
Kurt Himebauch 701e70bd3e fix: the emulated Steam Controller reports the firmware build Steam ships (#1323)
* fix: the emulated Steam Controller reports the firmware build Steam ships

* fix Triton firmware lookup for Steam root binary
2026-10-22 11:43:05 -04:00
Kurt Himebauch c3f3c04fc5 fix: latch kgsl GPU busy windows so MangoHud's GPU load stops reading 0% 2026-10-22 11:39:50 -04:00
angelrenefm 2dc2a44115 fix: the performance overlay shows the GPU load on Adreno phones whose kgsl busy percentage is unreadable
GpuStatsComponent read kgsl's gpubusy once per feed, so the msm reader path
(perf_now), read second, got the empty read and showed 0 %. Feeds that share a
reader now share one read, and one empty read between gpubusy windows keeps the
last load.
2026-10-22 17:35:05 +02:00
Kurt Himebauch 3a09e5698b fix: stop Steam's Steam Controller welcome tour from opening on every boot (#1321)
* fix: stop Steam's Steam Controller welcome tour from opening on every boot

* fix steam tour failure not to block helper attach
2026-10-22 11:27:59 -04:00
Kurt Himebauch 4421e851dd Enforce human authorship with automatic trailer scrubbing (#1325)
Install commit and push hooks that remove bot co-author trailers, preserve human credits, and reject bot primary authors. Add a trusted default-branch CI checker and publish its required status on the exact proposed commit. Validate main pushes as well. Ten regression tests pass, including real Git hook tests and the --no-verify case.
2026-10-22 11:12:41 -04:00
7bb1b5b74b feat: tools/device-smoke.sh checks a build on a real device over plain adb (#911)
* feat: tools/device-smoke.sh checks a build on a real device over plain adb

Installs and launches the app, changes the density and the window size, cycles a foldable's device state, then reads the newest debug session (first frame, last frame rate, memory page size, process exits). It restores the display and the device state it changed. No python needed; complements tools/droiddeck-scenario, which drives games.

* fix: device-smoke.sh restores only what it changed and checks more carefully

Restore the device state by cancelling the override the script made, or
by putting back the override that was already pinned (a test lab pins
one), instead of pinning the committed state. Restore density and size
only after the script changed them. Keep adb's exit status, count only
crashes of the tested app and fail when the crash buffer cannot be read,
compare the top activity after the density change, and exit 2 when an
option value is missing.

* fix: check wm density/size exit statuses and keep restore flags on failure

* fix: verify smoke-test display changes and restoration

* fix: reject false smoke-test launches and quote remote paths

* Run final display cleanup before reporting smoke results

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:48:38 -04:00
Kurt Himebauchandzooluis e49557a02d fix: register Android CJK fonts with fontconfig (#1319)
* fix: write CJK fontconfig as valid Kotlin

* fix: choose CJK fonts for requested language

* fix: prefer exact Chinese fontconfig locales

---------

Co-authored-by: zooluis <[email protected]>
2026-10-22 10:47:12 -04:00
3c622a9323 feat: the unfolded controls can stay while a controller is connected (#996)
On foldables the unfolded-screen controls panel hides whenever a
controller is connected (#829). Phones held in a telescopic controller
still want its trackpads and back grips, so a new setting under
"Controls on the unfolded screen" keeps the panel with a controller.
It is off by default and appears in the launcher settings and the
in-session menu while unfolded controls are on.

The visibility decision moves into SessionDisplay.unfoldedControlsWanted
with unit tests.

Closes #861

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:43:41 -04:00
Kurt Himebauch 8ed98b570b Keep visible external-display sessions running (#1320)
Track visibility on the activity display

Separate activity visibility from the compositor display so a powered external screen keeps the session visible when the handset is locked. Notify the service as the activity moves between displays and avoid reporting a removed host display before Android moves the activity.
2026-10-22 10:42:57 -04:00
xXJsonDeruloXx e6265c1262 Merge current main while preserving subscription access and game logos 2026-10-22 10:41:17 -04:00
3331edf265 perf: session log step times carry milliseconds (#993)
The == STEP lines in session.log now read HH:MM:SS.mmm, so the setup
steps before Steam starts can be timed; with whole seconds most of them
showed the same time.

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:40:48 -04:00
d60f683b62 fix(gamescope): release cursor SHM buffers during long sessions (#1189)
* fix(gamescope): avoid retaining pointer cursor SHM commits

* test(gamescope): verify cursor release and pending window control

* test(gamescope): keep live probe inert during test discovery

* test: match Gamescope process name in cursor probe

* test: clarify cursor-role probe serial behavior

---------

Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:34:24 -04:00
xXJsonDeruloXx e163b4fdba Preserve newer sleep requests when a freeze is cancelled 2026-10-22 10:32:29 -04:00
4513940717 a11y: give the button-like clickables a Button role for screen readers (#891)
* a11y: give the button-like clickables a Button role for screen readers

33 clickable / combinedClickable modifiers on buttons, tiles, list rows and
rail items had no semantic role, so TalkBack could not announce them as
buttons. Pass role = Role.Button on those. Scrims and pointer-consuming
backdrops are left alone. No visual or focus change.

* a11y: expose selection state for the drawer tabs, driver rows and suggested icons

The three selectable items (drawer page tabs, driver radio rows, suggested app icons) were given a plain Button role. Use selectable with Role.Tab or Role.RadioButton and the selected value so a screen reader announces which one is chosen.

* a11y: the rail's top tabs expose selection to screen readers

The tab cells were plain buttons, so a screen reader could not tell which
section was open. Tabs use selectable with Role.Tab; the link cells keep
the Button role.

* fix: expose selected store header tabs

* Group selectable accessibility controls

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:30:55 -04:00
xXJsonDeruloXx 88644dcad5 Merge origin/main into cancellable package installs 2026-10-22 10:29:50 -04:00
xXJsonDeruloXx 9c1cacd8f4 Merge remote-tracking branch 'origin/main' into fix/pr-1301-filemanager-safety 2026-10-22 10:29:24 -04:00
xXJsonDeruloXx 3b1dfadcf9 fix: keep deferred back inside selected drive root 2026-10-22 10:29:24 -04:00
3d9f200a7e feat: add SDK-free 3DoF tracking for VITURE Pro 2 (#1115)
* feat: itch.io store, library and installs for DRM-free Windows builds (#1064)

* feat: add experimental SDK-free VITURE Pro 2 tracking

* docs: record full-app Pro 2 hardware validation

* docs: record successful Pro 2 user game test

* fix: translate Pro 2 tracking notice for all supported locales

* fix: respect model capabilities for glasses controls

* fix: handle fold configuration during glasses probe

---------

Co-authored-by: kingokksa <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:28:14 -04:00
FX1922andxXJsonDeruloXx 7482f797c7 feat: custom Gradient theme with primary and secondary colors (#1309)
* feat: a custom Gradient theme with a primary and a secondary colour, picked with a wheel, sliders and a hex box

* Gradient theme: OK waits for a valid hex, deep blues are made paler, button text suits the worse gradient colour

* Gradient theme: nudge only the gradient colour that button text cannot read on

* fix: meet readable contrast across gradient themes

* fix: preserve contrast through gradient interpolation

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:26:53 -04:00
xXJsonDeruloXx f67ff86470 Merge origin/main into game debug logging 2026-10-22 10:24:09 -04:00
Danbro007 d2abaef2a3 fix: recognize SM8975 and mark Adreno 850 support experimental (#1249)
* fix: recognize SM8975 and mark Adreno 850 support experimental

* fix: retain A850 experimental status with spoofed Vulkan names
2026-10-22 10:19:23 -04:00
xXJsonDeruloXx 6cbf8f4d73 Merge remote-tracking branch 'origin/main' into fix/pr-1174-gog-installer
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/stores/gog/GogBackend.kt
#	app/src/main/java/com/droiddeck/launcher/stores/gog/GogDownloadManager.java
2026-10-22 10:16:32 -04:00
Kurt HimebauchandDelete228 0577a22459 fix: keep Steam shortcuts out of desktop controls (#1318)
* Enable existing second-screen controls in desktop sessions

* Keep controller drawer navigation consistent in desktop sessions

* Cover desktop and Steam second-screen controls with regression tests

* fix: hide Steam shortcuts in desktop controls

---------

Co-authored-by: Delete228 <[email protected]>
2026-10-22 10:16:25 -04:00
de8b5568c1 Fix: Keep Android library names visible in diagnostic logs (#1063)
* Preserve Android shared-library names in logs

* Restrict library redaction exceptions to loader diagnostics

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 10:12:57 -04:00
87e4b42ed8 fix: third-party GitHub mirrors only serve downloads with a trusted digest (#1057)
fix: a third-party GitHub mirror can only serve downloads that carry a trusted digest

Downloads without an expected SHA-256 were still sent through gh-proxy,
ghfast and other mirrors, and the first answer was accepted, so a mirror
could swap an AppImage or a Windows component. Downloader.downloadFile
now tries only the original URL when there is no digest. UserApps passes
GitHub's own asset digest, and WinComponents passes the snapshot's
archive digest, so those large files keep their mirror fallback.

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-22 10:12:33 -04:00
Jordan Y d9328c39df fix: Game cores also apply to games started through "Linux x86 (FEX)" (#1294)
* Run FEX-tool games on the chosen game cores

Performance > Game cores reached only the Proton launchers: games started
through "Linux x86 (FEX)" ran on every core, including the slowest cluster.

* Test that SteamVR FEX launches keep the game cores
2026-10-22 10:08:25 -04:00
9ed658c45c fix: seed HKCU redist markers in the user registry (#1286)
* fix: seed current-user redist markers in user.reg

* test: skip permission case when running as root

---------

Co-authored-by: Jammo <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-22 10:07:56 -04:00
xXJsonDeruloXx 1b14939731 fix: suspend page-out waits for each handler's ack and can be cancelled 2026-10-22 10:01:49 -04:00
xXJsonDeruloXx c8cc17bf3a Merge main into Steam client download PR 2026-10-22 10:01:34 -04:00
xXJsonDeruloXx 0ab2172857 fix: cancel Proton seed downloads promptly 2026-10-22 09:57:45 -04:00
xXJsonDeruloXx c02832ac59 fix: disconnect desktop download on session cancel 2026-10-22 09:56:05 -04:00
xXJsonDeruloXx 4e4a731221 test: cancel downloads after the request starts 2026-10-22 09:52:32 -04:00
kingokksa eb0eecda7a fix: let a connected controller keep the first player slot (#1304) 2026-10-22 09:37:01 -04:00
Resul Övürandovurrsl 4361182b0e fix(ui): stop touch-blocking dialog and drawer containers from merging descendant semantics (#1145)
Co-authored-by: ovurrsl <[email protected]>
2026-10-22 09:35:11 -04:00
Resul Övürandovurrsl 0444b3eff9 l10n: Turkish for the Storage page, GPU driver mode and the other untranslated screens (#989)
l10n(tr): translate the Storage page, GPU driver mode and other missing strings

Adds 213 strings and 9 plurals that were missing from values-tr, so the
new Storage page, GPU driver mode UI, SteamGridDB art, downloads source,
VR and latency toggles no longer fall back to English on Turkish devices.

Co-authored-by: ovurrsl <[email protected]>
2026-10-22 09:34:17 -04:00
xXJsonDeruloXx 240b7fcce6 feat: page the session out to zram before suspending it, and zram is always on where supported 2026-10-22 09:02:37 -04:00
K11MCH1 cb153ece87 Guard File Manager jobs and clean up cancelled copies
Description:
- Reject bulk delete while another operation is active. Check current operation state at action time, and clear operationJob on completion only when the finishing coroutine still owns it.
- Retain the requested Return directory even when it is unavailable, so Return can display the existing unavailable-directory message while browsing starts from the valid fallback.
- Observe cancellation during the recursive size scan before copying. Remove destination directories created by a cancelled copy, while preserving existing destination directories and ordinary copy-failure behavior.
- After a move's copy commits, finish deleting the source instead of reporting cancellation with both copies left behind. Preserve cancellation before copying and the existing copy-failure path.
- Log permission-restoration failures in staged copies and continue publishing the copied file.
- Exercise cancellation signals during transfers and size scans, directory cleanup, preservation of existing directories, and committed move completion without leftover staging entries.

Why it was needed:
- Overlapping operations could replace the tracked job, and an older completion could clear that reference, allowing navigation before the remaining work stopped.
- Cancellation could leave new partial directory trees or wait through the size scan. Late move cancellation could leave the source behind after publishing its replacement.
- A missing launch directory suppressed Return entirely, and a permission-restoration error could prevent an otherwise successful overwrite.

Validation:
- Debug Kotlin compilation passed with skipRust=true.
- Focused FileOps cancellation, copy, and link suites: 17 passed, 10 platform-dependent cases skipped on Windows, no failures.
- git diff --check passed.
2026-10-22 21:29:18 +09:00
Kurt Himebauch 22f7d5b909 feat: make desktop mode easy to uninstall safely (#1311) 2026-10-22 08:17:14 -04:00
K11MCH1 7432a4f1a3 Merge upstream main into File Manager Test branch
Resolve File Manager conflicts with the upstream copy cancellation and symlink changes. Preserve the original navigation, confirmation, and localization commits. Pass cancellation into move cleanup and retain the upstream empty-file copy behavior.
2026-10-22 21:15:46 +09:00
K11MCH1 0247831c7e Make File Manager cancellation stop active file I/O
Description:
- Pass the active coroutine job into copy, move, and delete operations so synchronous file I/O observes cancellation while it is running.
- Check for cancellation between copy chunks and recursive delete entries. Remove a newly created partial copy, and stage overwrites so cancelling a copy or move preserves the previous destination and move source.
- Add focused tests for cancelling a copy, move fallback, and recursive delete.

Why it was needed:
- Accept cancelled the Compose coroutine, but FileOps continued its synchronous I/O work. The confirmation could therefore wait for a large copy or delete to finish instead of stopping it.
- Copy and move must avoid replacing an existing destination with partial data when cancellation occurs.

Copy and move safeguards adapted from upstream commit a74ff3d6 (#1029).

Added and adjusted:
- Added cancellation checks between file transfer chunks and recursive delete entries, including single-file and batch operations.
- Adjusted FileManagerScreen to pass the active job to FileOps and check it during copy progress. Accept now waits for the file operation to stop before Exit, Return, or Back proceeds.
- Adjusted overwrite handling to use a temporary file so cancellation preserves the previous destination; an incomplete new destination is removed and a move source remains intact.
- Added tests for cancelling a new copy, a move fallback with an existing destination, and a recursive delete. The focused debug unit tests passed.

Localization already included on this branch:
- The earlier File Manager update added translations for Exit, Return to start, and the unavailable starting-folder message.
- It also translated the cancel-operation title and warning, plus the Accept and Decline buttons, across all 32 supported locale resource sets alongside the English defaults.
- These strings let localized users understand the navigation controls and the consequences of cancelling an in-progress operation. They were added in the preceding commit 7928effd; this commit makes Accept actually stop the file I/O.
2026-10-22 21:06:13 +09:00
Kurt Himebauch d012a1f5b6 fix: pause Steam Controller BLE reconnects while the session is suspended (#1312) 2026-10-22 12:05:17 +00:00
K11MCH1 1eb1e154ab Confirm navigation during File Manager operations
Description:
- Ask for confirmation when Exit, Return to start, Android Back, or the parent-folder controls are used while a file operation is active.
- Accept cancels the active job and waits for it to finish before navigating; Decline keeps the File Manager open and lets the operation continue.
- Track single-file operations as jobs and handle Back from the file picker root through the same navigation guard.
- Translate the existing Exit, Return, and unavailable-folder labels plus the new confirmation dialog in all 32 supported locale resource sets.

Why it was needed:
- Leaving the File Manager while a batch copy, move, or delete is running cancels its composition-bound coroutine, which can stop the batch after only some files are processed without a clear result.
- The new File Manager controls previously appeared in English when the app was set to another supported language.
2026-10-22 20:27:07 +09:00
The412Banner aaa2e40def feat: DroidDeck's linux-nightly components, with a Stable/Nightly switch (#1284)
The component catalog also reads the linux-nightly release of
Droid-Deck/DroidDeck-Components (hourly builds of DXVK, VKD3D-Proton and
FEX for glibc Protons). That release holds all three components, so each
file goes to its list by name: DD-dxvk-, DD-vkd3d-proton-, DD-FEXCore-.

A nightly that matches a stable build (same flavor and version, e.g.
DD-dxvk-3.1.1+153-nightly and dxvk-3.1.1-linux) has no row of its own;
if two nightlies match, the newer one is used. On the components page
the stable row gets a Stable | Nightly switch before Get, styled like
the Get button, one focus stop that flips with A or a tap and reports
its state as a switch. In the in-session drawer's download menus the
row gets Stable and Nightly tabs beside it (the existing TabStrip, now
with optional bumpers/compact), each its own focus stop. Get or A on the
row downloads whichever channel is selected. A nightly without a stable
counterpart keeps its own row, listed below the stable ones.

Downloads are allowed from that repository's releases too. A failed
nightly download says the file may have been replaced and asks for a
refresh. Strings in all locales.
2026-10-22 04:23:46 -04:00
K11MCH1 3f2aea4d08 Add File Manager Return and Exit navigation
Description:
- Add an Exit button at the right end of the File Manager toolbar. It finishes the File Manager activity from any directory.
- Remember the directory supplied when Game files or Proton prefix opens the File Manager. Show a Return to start button in the toolbar for those launches, and use it to reopen that directory after the user switches storage locations or browses elsewhere.
- When returning, restore the starting storage root and close search or favorites views. Show a message if the starting directory is no longer available.
- Add accessible labels for both buttons while retaining the existing toolbar actions and File Manager styling.

Why it was needed:
- Exiting previously required repeatedly navigating back through directories to the original location.
- Switching from the game's initial directory to Internal storage offered no direct route back, leaving users to find the path manually through Linux system storage.
2026-10-22 16:00:56 +09:00
Tomasz Kloc 593014fe8c Let GOG and Epic installs pick a game language (#1199) 2026-10-22 00:38:01 -04:00
Kurt Himebauch 138d313b7f Default Deck controls to both trackpads (#1295) 2026-10-22 00:35:46 -04:00
Resul Övür e0b3f928a6 fix: auto-driver trial rolls back when the display driver crashes at startup (#1031) 2026-10-21 23:56:43 -04:00
Resul Övür e865fa6b62 fix: Steam Controller (Triton) output/feature reports are no longer overwritten unread (#1032) 2026-10-21 23:55:00 -04:00
Resul Övür 37535a3d38 fix: a full disk no longer crashes the app during cover art fetches (#1030) 2026-10-21 23:52:35 -04:00
kingokksa 60043873f2 Switch the GOG store to GOG's official listing endpoint: fewer requests, the tab now opens in seconds (#786) 2026-10-21 23:51:16 -04:00
The412Banner cd18e87864 Return to the launcher when a front-end game closes (#395) 2026-10-21 23:20:18 -04:00
Resul Övür ec5a08ddf1 fix: in-app update works on Android 9 and 10 (#1066) 2026-10-21 23:18:02 -04:00
Kurt Himebauch 0de6413914 Revert dedicated second-screen activity (#1288) 2026-10-22 02:53:35 +00:00
Moroni Granja 4cae235154 fix: Xbox search results open with their metadata and Game Pass installs (#1279) 2026-10-21 22:43:51 -04:00
MaxsTechReview 3305724f06 perf: FEX's file lookups and opens go through the fast path instead of proot (#1283)
* perf: FEX's in-root path lookups go through the fast path instead of proot

Android has no openat2, so libblsession walks RESOLVE_IN_ROOT lookups for
FEX one component at a time. Every fstatat, readlinkat and openat in that
walk was a raw syscall that stopped in proot. When libblfastpath is loaded,
the walk now calls its fstatat, readlinkat and openat, which answer without
proot and fall back to the raw syscall themselves. Without it the walk uses
the raw syscalls as before.

One lookup drops from 250-940 us to 26-86 us. A Graveyard Keeper launch
makes 37.5k proot stops instead of 54.0k and reaches its first scene
0.31 s sooner.

* test: count fstatat64 where the preload asks for it

* perf: FEX's own file opens go through the fast path instead of proot

FEX opens host files for the game with syscall(SYS_openat), so every one
stopped in proot even when the fast path could answer it. libblsession's
syscall() now sends SYS_openat to the fast path's openat as well, the same
way the in-root walk does, and makes the raw syscall when the fast path is
not loaded.

A Graveyard Keeper launch makes 28.0k proot stops instead of 37.5k; FEX's
openat stops go from 6,739 to none.

* fix: the fast path's start-up reads don't come back through syscall()

libblsession's syscall() now sends SYS_openat to the fast path. A process
named FEX starts the fast path by reading /proc/self/cmdline with
syscall(SYS_openat), which came straight back into the fast path and
waited on its own pthread_once. Its start-up reads now call libc's
syscall directly.
2026-10-21 22:33:39 -04:00
Kurt Himebauch 1251e9d3ee Default session orientation to landscape (#1287) 2026-10-21 21:27:25 -04:00
kingokksa 2b0c55f72e feat: let the built-in controller be turned off (#1207) 2026-10-21 19:49:31 -04:00
MaxsTechReview ed29f77fc2 fix: Chromium browsers start on the desktop (#1280)
A process's status read for another process is served read-only, so
Chromium's ReadOnlyFile check no longer kills the browser. Chromium-based
AppImages without chrome-sandbox get --no-sandbox, and Flathub Chromium and
Electron apps skip zypak's spawn portal, which cannot work here.
2026-10-21 18:17:07 -04:00
kingokksa 400325fa8f fix: some games never loaded their own Steam touch layout, and the sticks could not be placed (#1058) 2026-10-21 17:39:05 -04:00
Resul Övür 2f4fe2c5be fix: manual cloud-save upload/download no longer reports a skipped sync as success (#1068) 2026-10-21 17:32:28 -04:00
Kurt Himebauch e5b54f6248 feat: SteamVR controller types, phone controls on glasses, flat games as VR panels (#1268) 2026-10-21 21:22:21 +00:00
MaxsTechReview 2111197bea perf: performance overlay updates ride the next game frame instead of an extra composite (#1274)
* perf: performance overlay updates ride the next game frame instead of an extra composite

* gamescope: a held overlay repaint is only cleared once an output was painted

* gamescope: release.env points at p12
2026-10-21 17:20:12 -04:00
MaxsTechReview 7fa8008944 perf: network tables Android refuses are served from the synthesized copy without retrying the open (#1275) 2026-10-21 17:18:11 -04:00
MaxsTechReview e04727dbaf perf: SysV message queues stay mapped instead of reopening per call (#1272) 2026-10-21 17:14:03 -04:00
MaxsTechReview bd1350ac25 perf: games skip Steam's install-script evaluator once it has finished (#1273) 2026-10-21 16:59:52 -04:00
greg 99136ea23b Add support for Java 17 via mise (#1231) 2026-10-21 18:30:34 +00:00
kingokksa cad260b450 fix: give the touch gesture dialog room to scroll on phones (#1214) 2026-10-21 14:14:03 -04:00
Kurt Himebauch d44c20f2bb feat: Rokid Max and Air glasses with 3DoF, SBS modes, 120 Hz and brightness (#1266) 2026-10-21 14:11:01 -04:00
Kurt Himebauch e9891d5a29 feat: Remote Play host: PipeWire capture, hardware encoding, touch and game audio (#1139)
* feat: run SteamOS PipeWire and WirePlumber for gamescope screen capture

* feat: hardware Remote Play encoding through MediaCodec behind Steam's V4L2 stage

* feat: deliver Remote Play touches to gamescope as touches

* feat: route game audio through PulseAudio while a Remote Play client is connected

* fix: keep the Remote Play marker when a forked child closes the encoder

* fix: allow standalone preload tests without V4L2 encoder hook
2026-10-21 17:57:24 +00:00
Kurt Himebauch d70f5f0636 feat: local builds reuse the add-on packs a CI build installed (#1265) 2026-10-21 17:23:16 +00:00
FX1922 7bb66de192 fix: keep controller focus after installing a boot movie; Xbox and itch artwork covered (#1229) 2026-10-21 13:13:31 -04:00
kingokksa ff4a7943f5 fix: store dots stay dim when signed out and stores open on the first arranged tab (#1236) 2026-10-21 13:12:49 -04:00
0x1Nawaf 83b2164e4a feat: the performance HUD adds a colored frame-time graph, battery temperature, and opacity controls (#1256) 2026-10-21 13:12:23 -04:00
Kurt Himebauch cf17f583d4 feat: Decky Loader update channel selector (#1264) 2026-10-21 17:08:46 +00:00
Kurt Himebauch 5d8fd3f5f0 feat: warn before installing a Decky plugin ZIP (#1263) 2026-10-21 12:47:10 -04:00
Kurt Himebauch bd76b39b84 fix: keep Steam downloads running across suspend with Manual policy (#1262) 2026-10-21 15:45:39 +00:00
Moroni Granja d006538f13 Merge branch 'main' into fix/gog-native-installer-extract 2026-10-21 07:56:53 -03:00
MaxsTechReview daeef1995f fix: moving a mouse no longer recomposes the whole session overlay (#1253) 2026-10-21 06:36:30 -04:00
MaxsTechReview 3eb0400df8 fix: clearing a game's compatibility tool in Steam keeps the Linux build (#1250) 2026-10-21 06:07:16 -04:00
MaxsTechReview 093a986b88 fix: Google sign-in from a store shows the real device instead of a Galaxy S23 Ultra (#1252) 2026-10-21 05:42:41 -04:00
MaxsTechReview 11674b711b fix: browsers added as non-Steam games show their window and load pages in Gaming Mode (#1243) 2026-10-21 02:02:18 -04:00
MaxsTechReview 20577ec045 fix: a failing image or script on a store sign-in page no longer replaces the page with an SSL error (#1238) 2026-10-21 01:04:03 -04:00
Resul Övür abeaf7ccb6 fix: a failed Steam client download ends the session with a message instead of silently (#997) 2026-10-21 00:56:05 -04:00
MaxsTechReview 152b961940 fix: GOG sign-in starts without the previous account's web session (#1235) 2026-10-21 00:44:40 -04:00
MaxsTechReview 705da08a0e fix: game files keep the full game name instead of cutting it at 40 characters (#1230) 2026-10-20 23:24:29 -04:00
Resul Övür 25938cc249 fix: file manager copy/move keeps symlinks (#1027) 2026-10-20 23:24:25 -04:00
Resul Övür 3149567328 fix: cancelling a paste stops the copy and keeps a move's source (#1029) 2026-10-20 20:05:05 -04:00
Juan Martí 8ccd771059 fix: open game Components on the game's profile (#1146) 2026-10-20 19:31:17 -04:00
Resul Övür aa5e9c3e91 fix: paste bar ignores taps while a paste is running (#1025) 2026-10-20 19:22:33 -04:00
Resul Övür e032f25114 fix: ignore uinput-* system virtual input devices in controller detection (#954) 2026-10-20 18:39:35 -04:00
Resul Övür 87763976d8 fix: empty App ID no longer crashes the game environment editor (#1014) 2026-10-20 18:31:27 -04:00
Resul Övür bdd2485e96 fix: a slow folder listing no longer replaces the folder being shown (#1028) 2026-10-20 18:31:21 -04:00
Resul Övür 6eefcf4267 fix: file manager selection actions respect the search filter (#1021) 2026-10-20 18:31:14 -04:00
MaxsTechReview 1c06ddeaf2 fix: Mono P/Invoke readv calls reach the controller hook too (#1227) 2026-10-20 18:18:10 -04:00
Resul Övür 57e1b4a71b fix: moving a stick no longer releases a D-pad or trigger held as a key (#1060) 2026-10-20 18:17:37 -04:00
maxjivi05 750780b722 fix: Mono P/Invoke readv calls reach the controller hook too 2026-10-20 18:02:29 -04:00
maxjivi05 1df7760e99 Merge branch 'main' into fix/readv-follow-up 2026-10-20 18:02:29 -04:00
Resul Övür 4575bfc32d fix: file provider rejects document ids with . or .. segments (#1016) 2026-10-20 17:58:36 -04:00
Charan Rathore 2699666766 fix: treat Unreal bootstrap launcher PE name as junk (#1020) 2026-10-20 17:52:47 -04:00
Delete228 92dbd1cd5a fix: restore Factorio ARM64 controller input through readv (#1195) 2026-10-20 17:52:40 -04:00
Resul Övür 67ab3fa634 fix: shared session logs are always scrubbed, whatever their size (#1065) 2026-10-20 17:16:36 -04:00
MaxsTechReview b3122aece0 fix: Rewired games see DroidDeck's pads (udev listing, dlsym-resolved libc calls) (#1222) 2026-10-20 16:53:37 -04:00
Resul Övür 5d87822a8a fix: added-games scan keeps games like Reach, Breach, Crashday and Observer (#1023) 2026-10-20 16:53:29 -04:00
Resul Övür 385af59e9f fix: Steam Touch labels no longer corrupt the controller config (#1017) 2026-10-20 16:53:22 -04:00
Kurt HimebauchandDetinator10 4f8a7c5596 fix: let the device sleep while a session is suspended (#1220)
* fix: stop the DirectAudio output while the sink is suspended

* fix: verify every session thread before reporting suspension

* fix: freeze PRoot first and stop each guest thread on suspend

---------

Co-authored-by: Detinator10 <[email protected]>
2026-10-20 16:43:09 -04:00
MaxsTechReview 8ea48dc039 fix: leave the PC keyboard's pad handling to #956 and fix the red keyboard test (#1221) 2026-10-20 16:30:51 -04:00
Resul Övür 746a850a44 fix: preserve Electric contrast for filled and text buttons (#1149) 2026-10-20 15:45:08 -04:00
kingokksa 2457f27cdf feat: uninstall or remove a game from the Games page (#1190) 2026-10-20 15:37:21 -04:00
Resul Övür 6090da0eb5 fix: hide on-screen controls while the PC keyboard is open (#956) 2026-10-20 15:06:38 -04:00
Kurt Himebauch 2a8618ab5c feat: launch Easy Anti-Cheat games offline when they support it (#1039) 2026-10-20 18:58:56 +00:00
MaxsTechReview 8ca389149b fix: native Linux games under FEX reach FEXServer, and a refused compat app no longer loops the sync (#1213)
FEX launched through a script (Valve's _v2-entry-point) was judged 32-bit, so the
fast path stayed off in the FEX client while FEXServer bound its socket through it.
The fast path now follows #! lines before judging the program.

The live compat sync drops apps the client refuses GetCompatTools for instead of
reattaching forever.
2026-10-20 14:58:30 -04:00
MaxsTechReview 862afdf0a9 fix: the on-screen pad steps aside while Steam's or DroidDeck's keyboard is open (#1217) 2026-10-20 14:29:50 -04:00
MaxsTechReview b8b3ecf915 fix: a store game's first launch creates its prefix before the cloud save download (#1215) 2026-10-20 14:11:46 -04:00
MaxsTechReview 78932ef836 fix: a session on an external display stays awake while the phone screen is off (#1211) 2026-10-20 14:11:42 -04:00
MaxsTechReview 2862b77495 Work around Android's denial of executable stacks (#1208) 2026-10-20 12:44:49 -04:00
Kurt Himebauch 4131d9a978 fix: prefetch Comet and Xbox packs for existing users (#1204) 2026-10-20 16:29:08 +00:00
Kurt Himebauch e3246b7dd5 feat: ship proot, DirectAudio, Xbox, Comet and hand tracking as CI-built add-on packs (#1198)
* perf: download hand tracking models and native libs on first use

* feat: build and publish add-on packs in CI, add Xbox and Comet packs

* feat: ship proot and DirectAudio drivers as CI-built packs

* test: hand tracking readiness requires the tracking pack

* ci: attach PR test-build packs to the PR release instead of Components
2026-10-20 11:18:44 -04:00
Kurt Himebauch 0663593920 Fix Game Pass library sync when subscription plan rows are omitted (#1193) 2026-10-20 11:14:06 -04:00
kingokksa 7775a87129 feat: arrange and hide/show store tabs from stores settings (#1121) 2026-10-20 11:12:27 -04:00
Delete228 a59ed6075e test: cover controller destination faults and partial reads
Exercise keyframe and ring reads with unmapped, PROT_NONE, read-only and page-boundary destinations. Verify complete-event progress within one vector and across vectors, including a NULL destination. The Linux fakeinput suite passes 11 tests with 3 i386 skips; both native ARM64 probes pass on the Thor.
2026-10-20 17:55:25 +03:00
Delete228 58382e5182 fix: safely copy controller events to caller buffers
Stage ring and keyframe events before copying them with process_vm_writev. Return EFAULT for inaccessible destinations and advance the reader only for complete events delivered, preserving partial readv progress. Retested Factorio on the Thor with the exact ARM64 library.
2026-10-20 17:53:40 +03:00
MaxsTechReview e308224391 fix: RetroArch fills the screen from the emulator rail (#1194) 2026-10-20 10:38:52 -04:00
Delete228 0b3e7428a6 test: reject inaccessible readv descriptor arrays
Cover unmapped, PROT_NONE, and partially accessible iovec arrays. The probe crashes with SIGSEGV before the descriptor-copy fix and returns EFAULT afterward. The Linux fakeinput suite and native ARM64 probe pass; Factorio was manually retested on the Thor with the updated library.
2026-10-20 17:34:14 +03:00
Delete228 e2d6b5dabf fix: safely copy readv descriptors before validation
Copy the caller's iovec array through process_vm_readv before inspecting it, so unmapped, protected, or partially accessible arrays return EFAULT instead of crashing the process. Verified valid and invalid copies in the Android guest.
2026-10-20 17:29:42 +03:00
Delete228 d8978639c4 build: require the controller readv export
Check that packaged libfakeinput libraries export readv alongside the existing read hook.
2026-10-20 16:43:35 +03:00
Delete228 4a1aba3c02 test: cover vectored controller reads
Cover readv on original and virtual controller rings, an overlay-style read-to-readv fallback, event-aligned vectors, empty vectors, nonblocking errors, and ordinary descriptor passthrough.
2026-10-20 16:42:24 +03:00
MaxsTechReview 4f799ea59c fix: open Flatpak apps in renamed app variants (#1192)
Flatpak starts bwrap with an empty environment, so the bwrap shim lost DROIDDECK_BWRAP_SOCKET and dialled the default package's socket. The session now writes the socket name to /etc/droiddeck/bwrap-socket and the shim reads it when the variable is missing.

Fixes #1092
2026-10-20 09:38:14 -04:00
Delete228 d5e87a8c7c fix: restore Factorio ARM64 controller input through readv
Steam's ARM64 overlay can fall back from read() to readv(). Route registered controller reads through the existing private ring reader so the game receives input events instead of backing-file bytes. Preserve ordinary descriptor passthrough and evdev per-vector reads.
2026-10-20 16:38:14 +03:00
FX1922 e53232b301 feat: Better boot movies system; Added steamdeckrepo support (#1036) 2026-10-20 09:36:25 -04:00
MaxsTechReview b868863133 perf: tile rendering for games, faster session start (#1188)
Games launched from Steam get Turnip's own per-render-pass choice between
tile memory and system memory again; TU_DEBUG=sysmem now stays with the
Steam client, whose interface it was set for. A per-game TU_DEBUG, the
debug override file and the Adreno 710/720 driver still win. On the
REDMAGIC tablet (Adreno 840) PEAK runs 46.8 -> 52.3 fps over 7 alternating
pairs and Valheim's 3D menu needs 12.6 % less GPU time per frame; light
games are unchanged.

droiddeck-seed-redists remembers the identity (inode, size, mtime) of each
prefix registry it has verified and skips re-reading it while it is
unchanged. Steam started after it on every session, ~1.8 s later.

SessionFiles keeps a ledger of the files it staged for the installed APK
and copies only the ones that changed, ~180 ms less per session start.

Session start to Steam ready: 5.57 -> 3.57 s.
2026-10-20 09:19:46 -04:00
Moroni Granja 38c72c6304 Merge branch 'main' into fix/gog-native-installer-extract 2026-10-20 09:54:16 -03:00
MaxsTechReview ca352bf225 fix: RPCS3 opens from the emulator rail again (#1191)
RPCS3's AppImage could not start: its own runtime unpacks the image with
a dwarfsextract it runs from a memfd, which Android refuses (EACCES), and
it has no other way to run it. Emulator AppImages for arm64 now start
through the shipped uruntime, which falls back to a file in /tmp.

RPCS3 then stopped with "Null object" in utils::shm (errno 13): it reads
/proc/sys/vm/overcommit_memory to pick memfd for guest memory, and apps
cannot read it. The guest now gets the kernel's default policy (0) there
when the real file is unreadable, like file-max.
2026-10-20 08:52:31 -04:00
Moroni Granja b9d703db34 fix: extract GOG installers natively on ARM
The GOG installer fallback downloaded only the setup stub and then launched it through Proton/FEX, where the 32-bit Inno Setup stub corrupts its own path strings before CreateFileW. Download every installer part and extract them with the bundled ARM64 innoextract in the Linux guest instead, registering the real game executable. Also classify truncated response bodies as server faults so the native engine retries on the other CDN mirror.
2026-10-20 08:09:20 -03:00
MaxsTechReview a5c587bd71 fix: add a picked exe the session cannot see by binding its folder (#1173)
Adding a game from a single exe outside internal storage and the game
library, such as a microSD card, only showed "This location is not
available in the session" and added nothing. #1153 made "Add all games in
this folder" bind such a folder under /root/Games, but a picked exe still
had no guest path.

addExe now binds the folder the same way when the session cannot see the
exe (the game it belongs to, or else the exe's own folder), then adds the
game. The bind is mounted from the next session start.
A folder the session already sees is not bound again, and a folder the app
cannot read is still refused.
2026-10-20 00:45:56 -04:00
MaxsTechReview bc3715aad6 fix: route ALSA to PulseAudio for x86 Linux games under FEX (no game audio) (#1166)
fix: route ALSA to PulseAudio for x86 Linux games under FEX
2026-10-20 00:44:56 -04:00
MaxsTechReview 79f43d8def fix: GOG games miss the registry entries their install script sets (#1159)
A GOG download only places the files, so games that look up their own registry keys (Fallout: New Vegas reads HKLM\Software\Bethesda Softworks\FalloutNV "Installed Path") said they were not installed. Every GOG launch now reads the setRegistry actions of the game's goggame-<id>.script and imports them into the prefix's 32-bit view through Proton's reg.exe, once per change of the script, so games already downloaded are fixed on their next launch.
2026-10-20 00:38:08 -04:00
MaxsTechReview 6ef042487a fix: find and save Steam touch layouts of non-Steam shortcuts (#1171)
The touch layout editor opened "App -<n> Layout" for a non-Steam shortcut
(an added game, the Epic Games Launcher) with LB, LT, RT, RB, both sticks
and LS/RS greyed out, so they could not be placed. Steam keys a shortcut's
controller config by the shortcut's lowercase name and logs its app id
unsigned, but the editor looked both up by the signed app id, found
neither, and fell back to a blank layout with nothing bound.

SteamTouchConfig now reads the shortcut's name from shortcuts.vdf, loads
and saves its layout under that name the way Steam does (autosave folder
and configset entry), and matches Steam's log line by the unsigned id. The
editor is titled with the game's name, and the app id in the panel is
shown unsigned. A name that is not a plain folder name (a
path separator, "." or "..", a quote, a backslash or a control character)
keeps the numeric app id. Existing configset entries are matched
without regard to case, as Steam reads them.
2026-10-20 00:34:01 -04:00
MaxsTechReview 6ba159a2f2 fix: let Wine write hooks into code FEX has translated (Ubisoft "Error at hooking API") (#1163)
fix: let Wine write hooks into code FEX has translated

Wine 11's wineserver writes another process's memory with process_vm_writev,
which the kernel refuses on a page that is not writable. FEX's WoW64 layer
keeps the pages it has translated read-only to catch self-modifying code and
re-protects them right after a program unprotects them, so WriteProcessMemory
and NtWriteVirtualMemory into ntdll failed with a partial copy and Ubisoft
games stopped at "Error at hooking API NtProtectVirtualMemory". Wine only
falls back to /proc/<pid>/mem on ENOSYS or EPERM.

libblsession now completes a single-buffer write that wineserver was refused
with EFAULT through /proc/<pid>/mem, the way Wine wrote before Wine 11. Other
programs and every other error keep the kernel's answer. The iovecs are read
with process_vm_readv, so a bad pointer still returns EFAULT, and offsets are
64-bit in the i386 build.
2026-10-20 00:31:03 -04:00
MaxsTechReview b924dc02de fix: add games picked inside a folder game as their own games (#1155)
Picking an exe anywhere under an added folder treated it as that game, so a
folder added with "Add all games in this folder" (or a removed one) swallowed
every game below it: picking another game's exe switched the folder game's
target, or brought the removed folder game back under its old name and art.

Only treat a pick as the same game when it sits in the folder root, in the
same top-level subfolder as the game's current exe, or is another build of
that exe, and prefer the deepest matching folder.
2026-10-20 00:29:21 -04:00
MaxsTechReview eaa46e1fd6 fix: show the app's cursor in sessions started from the front end (#1154)
gamescope makes its cursor surfaces by attaching an shm buffer, committing,
destroying the buffer and only then calling wl_pointer.set_cursor. The
compositor kept that buffer only as a reference, so set_cursor found nothing
to publish: apps run from the front end (store apps, Flatpaks, emulators)
showed DroidDeck's fallback arrow, or no cursor at all after a desktop
session had hidden its own.

Copy a role-less surface's pixels when its held buffer is destroyed and
publish that copy when the surface becomes the cursor, for as long as the
surface lives.
2026-10-20 00:28:14 -04:00
Resul Övür 0953a38765 fix: confirm before bulk-removing unused components in Storage (#1140) 2026-10-19 23:51:11 -04:00
Resul Övür 68b4c87d9a fix: keep held Steam Touch controls pressed across action-set and layer changes (#1143) 2026-10-19 23:50:29 -04:00
Kurt Himebauch 6b8ce9e675 fix: an interrupted package install can no longer break the Linux runtime (#1078) 2026-10-19 23:40:30 -04:00
Grimarius fe7b75318a feat: add configurable touchscreen gestures (#940) 2026-10-20 03:22:41 +00:00
MaxsTechReview fd74819c50 fix: Steam no longer sleeps again right after a paused session resumes (#1162) 2026-10-19 23:06:48 -04:00
Resul Övürandovurrsl 421ffc7506 fix: keep ModeHeader search from stealing focus when recomposed (#1141)
Co-authored-by: ovurrsl <[email protected]>
2026-10-19 21:28:22 -04:00
Resul Övürandovurrsl 2d6393df24 fix: keep touchpad two-finger scroll from jumping the cursor or starting a drag (#1142)
Co-authored-by: ovurrsl <[email protected]>
2026-10-19 21:28:06 -04:00
Resul Övür c2a3740237 fix: uppercase section headings in the app language (#1148)
* fix: uppercase section headings in the app language

* fix: localize store section headings
2026-10-19 21:27:50 -04:00
MaxsTechReview da916e7ed7 fix: give x86 programs under FEX the runtime's OpenSSL 3 (libcrypto.so.3 not found) (#1167)
fix: give x86 programs under FEX the runtime's OpenSSL 3
2026-10-19 21:27:25 -04:00
MaxsTechReview cfc6dfb05f fix: let x86 apps under FEX use the device's fonts (Epic launcher CJK text shows boxes) (#1168)
fix: let x86 apps under FEX use the device's fonts for CJK text
2026-10-19 21:27:18 -04:00
MaxsTechReview af4f0a7edb fix: user apps open at the Desktop's resolution (#1172)
A user app (script, AppImage or Flatpak) started from its shortcut runs in
a Run session, which took the Steam resolution (720p by default). It
ignored the resolution chosen under Open Desktop and was letterboxed at
1280x720 on screens that are not 16:9.

User apps now use the Desktop's resolution once one has been chosen. Until
then, and for every other Run program, nothing changes.
2026-10-19 21:27:06 -04:00
Kurt Himebauch 80a62cd0f0 Restore Xbox launches from Steam (#1170) 2026-10-20 00:46:35 +00:00
Kurt Himebauch 7eae6ca3e7 Restrict Xbox credential IPC and address launch review findings (#1164)
* Restrict Xbox credentials to host backends and lease game launches

* Verify mapped PRoot backend images and retry lease heartbeat timeouts

* Bound Xbox backend shutdown and isolate service socket lifetimes

* Keep unique Xbox service sockets within Unix address limits

* Own licensed launch directories and recover abandoned views safely

* Serialize Xbox account revocation with sign-in commits and launch authorization
2026-10-19 20:10:54 -04:00
Kurt Himebauch a63f3b1fbc Add experimental Xbox and PC Game Pass store support (#1156) 2026-10-19 18:44:10 -04:00
The412Banner 8e02904a04 fix: adding a games folder on an SD card found nothing (#1153)
Adding a folder on an SD card outside the Steam library added nothing and said
"Nothing new in this folder": every game's exe had no guest path, so each one
was skipped. Since #542 nothing created the game-folder binds that let older
builds use such folders; only migrated ones remained.

addFolder now binds the picked folder under /root/Games (named as
GamesFolderMigration names them, with a suffix when the name is taken) when the
session cannot see it, then adds its games. The bind is mounted from the next
session start.
2026-10-19 19:43:03 +00:00
Kurt Himebauch aa6fae1924 Fix intermittent CI package updates and escaped test worker (#1151)
Fix escaped cloud-save test worker and CI package updates
2026-10-19 14:52:30 -04:00
Kurt Himebauch adcb04f624 fix: skip DirectAudio for Steam install scripts (#1046) 2026-10-19 14:19:10 -04:00
Kurt Himebauch 2ea86b4abb fix: run x86 Proton under FEX in the Steam runtime it requires (#1034) 2026-10-19 14:18:52 -04:00
Kurt Himebauch 7c20d4b5c3 Fix direct-touch right-click and Steam Touch mode restoration (#677) 2026-10-19 14:18:26 -04:00
Kurt Himebauch a789d60f6a fix: register SteamVR only during a DroidDeck VR session (#1137) 2026-10-19 12:31:23 -04:00
Kurt Himebauch 2415ff0518 fix: DD-FG 3x/4x flicker and rebrand Win-FG to DD-FG (#1132) 2026-10-19 11:21:32 -04:00
Kurt Himebauch ab6d100cea fix: keep the TP UBWC hint on for Samsung One UI 8 Gen 2 (#1128) 2026-10-19 09:58:24 -04:00
kingokksa 55bf51cf37 Ship the desktop's translations as per-language packs (#942) 2026-10-19 09:54:19 -04:00
ovurrsl b3483349e3 fix(install): check cancellation around retries/completion and keep install worker ownership 2026-10-19 16:18:04 +03:00
Kurt Himebauch 7733e29d10 feat: capsule view with DVD-case store art across all stores (#1117) 2026-10-19 08:45:58 -04:00
Kurt Himebauch c5f7f43726 feat: play button on installed store game pages (#1125) 2026-10-19 12:23:02 +00:00
ovurrsl 8faf88de85 feat: the game page shows the game's logo
The hero on the Games page draws the game's logo instead of the plain title
when there is one: Steam's cached logo.png or a chosen logo for Steam games,
the resolved logo for added and store games. The title text stays as the
fallback and as the logo's description for screen readers.
2026-10-19 15:12:47 +03:00
ovurrsl be384ca2a2 feat: games that already have a cover still get a logo and icon
The automatic art fill no longer skips a game just because its folder has a
cover. Store games and added games with their own cover now get a missing logo
(the store's own logo first, then Steam, then SteamGridDB) and a missing icon
from SteamGridDB. Existing, folder and chosen art is never replaced; a slot
that stays empty is asked about again after a week, as before.
2026-10-19 15:12:47 +03:00
ovurrsl 8ceff5641a feat: store games get their logo from the store
GOG (api.gog.com v2 `_links.logo`), Epic (catalog keyImage `DieselGameBoxLogo`)
and Amazon (`productDetail.details.logoUrl`) logos are kept in the install
record as an optional `logo` field and written next to the game as logo.png
when the install finishes. The PNG is kept as it came, transparency included,
and anything that does not decode as an image is dropped. Older install records
without the field still read as before.
2026-10-19 15:12:46 +03:00
ovurrsl f6d44376ed fix: cancelling the first-run desktop or Proton install stops its download
Cancel closed the session screen, but the desktop or Proton download kept
running with no UI, and a relaunch saw the stale stop flag and closed
itself. Downloads now take a cancel check, DesktopCatalog.install stops
before changing anything on disk, the install state is process-wide, and
a new session is refused while a cancelled install is still winding down.
2026-10-19 15:12:16 +03:00
Kurt Himebauch 5970de928e feat: library and store tabs per store with installed games listed first (#1116) 2026-10-19 02:06:38 -04:00
kingokksaandxXJSONDeruloXx 7370145f89 feat: itch.io store, library and installs for DRM-free Windows builds (#1064)
Co-authored-by: xXJSONDeruloXx <[email protected]>
2026-10-19 05:25:00 +00:00
Kurt Himebauch 2cf009d025 feat: add light launcher icon option (#1104) 2026-10-19 02:54:50 +00:00
MaxsTechReview 1e06e908e8 Fill missing plural forms and translate English left in the app strings (#1102)
Nine storage plurals lacked the forms Arabic, French, Spanish, Italian
and Portuguese use (zero/two/few/many), and 71 strings across 16
languages were still the English text.
2026-10-18 21:28:36 -04:00
MaxsTechReview 5c07c122fe Steam x86-64 client: menus come up, sign-in stays put, games listed once (#1096)
* Steam x86-64 client: menus come up, sign-in stays put, games listed once

Switching the Steam client from arm64 to x86-64 left the client on a blank
screen behind "Downloading the Steam client update 100%", and each switch
signed one of the clients out.

- The x86-64 client is Valve's steamrt64 platform, but it was installed and
  synced from the ubuntu12 manifest, which lags it (1792108788 against
  1792192922). The client was therefore never the arm64 client's build, the
  arm64 web helper was never used, and the client then updated itself in
  place after the helper had been chosen. Install, sync and the build check
  now read the steamrt64 manifest.
- The x86 web helper's GPU process loads libpci, which exits when
  /sys/bus/pci/devices lists a device whose files the app may not read (a
  RedMagic's PCIe root ports). Every GPU process exited with status 1, even
  in software, until Chromium gave up and the menus stayed blank. An empty
  folder is bound over /sys/bus/pci/devices on such devices.
- Steam refuses one client's login token from the other ("Invalid
  Password" for a token the arm64 client signed in with minutes before), so
  copying the newer token at every start signed the other client out. Each
  client now keeps its own token; a client with no accounts only gets the
  other's account list.
- Games could be listed twice: an added game whose folder a Steam import
  already covers is now shown as that Steam game only, a client switch
  drops the old client's imported entries, an import stays one when its
  ownership cannot be confirmed instead of gaining a shortcut beside it,
  and only the client's own library folders count as an install.

* Steam x86-64 client: share the arm64 sign-in again

The x86-64 client refused the arm64 client's login token because it did
not know the account's SteamID: it takes it from Accounts in
config/config.vdf, which this script creates without one. It logged on as
[U:1:0] with a valid token, Steam answered "Invalid Password", and the
client dropped the token and AutoLoginUser, so every switch asked for the
password.

The newer sign-in is copied between the clients again, each start adds
the accounts either client knows to the other's config.vdf, and an
AutoLoginUser or RememberPassword one client lacks is filled in. Device:
the x86 client signs in with the arm64 token on a fresh install and on an
install left signed out, and the arm64 client stays signed in.
2026-10-18 20:38:48 -04:00
MaxsTechReview 48a70c8229 Boot Linux games under the FEX compatibility tool again (#1100)
Valve's FEX opens rootfs paths with openat2(RESOLVE_IN_ROOT), which the
preload emulates under proot. droiddeck-fex's rootfs links to the Steam
runtimes with absolute paths outside the rootfs, so every lookup missed
and FEX fell back to the arm64 host files: x86 bash, and with it Stardew
Valley's launcher, failed on libdl.so.2. An absolute link whose target
is missing in the root but present on the host now finishes there.
2026-10-18 20:38:08 -04:00
MaxsTechReview 7800d3d810 fix: Steam client responds sooner and its sounds play on time (#1094)
- Pace the session at the rate the panel actually runs. When the system
  keeps a lower rate than the mode DroidDeck asked for (a vendor game
  assistant holding 120 Hz while 185 Hz was requested), gamescope,
  Xwayland and the compositor's presentation feedback were told 185 Hz,
  and Steam's interface drew every 10.8 ms out of step with the panel.
- Build the DirectAudio PulseAudio sink from source (LGPL-2.1-or-later,
  The412Banner/directaudio v1.1.0) with its max request kept to its ring
  target. Advertising 100 ms made PulseAudio grow every client's buffer
  to about 112 ms, so Steam's interface sounds played that late.
2026-10-18 19:26:26 -04:00
MaxsTechReview 123fbc723c fix(ci): give unit tests a 2 GB heap (#1091)
Robolectric keeps one sandbox per SDK level the tests ask for, and the
suite now asks for seven (26, 28, 29, 31, 33, 34 and the default). They
no longer fit in Gradle's default 512 MB test heap, so testReleaseUnitTest
runs out of memory part way through (SteamHomeHeadsetTest,
GlassesMenuCaptureTest) on main and on every open PR.
2026-10-18 19:24:09 -04:00
Kurt Himebauch df146d011f fix: keep hidden and private Steam games off the Steam home art wall (#1097) 2026-10-18 19:11:30 -04:00
xXJsonDeruloXx 6d105c1649 chore: merge main to catch up with 0.5.0 2026-10-18 17:05:49 -04:00
xXJsonDeruloXx 5de7af9b41 chore: merge main to catch up with 0.5.0 2026-10-18 16:52:02 -04:00
Kurt Himebauch e4503129ea fix: install Decky Loader from upstream repository (#1090) 2026-10-18 19:31:14 +00:00
Resul Övür 25082786aa fix: added games are handed to Steam right away and retried only if they never take focus (#994) 2026-10-18 14:59:39 -04:00
Kurt Himebauch 1fbe103ffe feat: auto driver picks come from a Drivers-CI manifest with per-GPU fallbacks (#977) 2026-10-18 14:23:41 -04:00
Kurt Himebauch 632f13d1b2 fix: keep second-screen controls in their own activity (#1041) 2026-10-18 14:23:04 -04:00
Kurt Himebauch 5d07c575e2 fix: skip DirectAudio setup for Proton queries (#1044) 2026-10-18 14:21:46 -04:00
wallentx cbd3d8c313 Merge remote-tracking branch 'upstream/main' into wallentx/game-debug-logging 2026-10-18 12:37:08 -05:00
MaxsTechReview 9ac663aae0 chore: bump version to 0.5.0 (14) (#1081) 2026-10-18 11:25:10 -04:00
Kurt Himebauchandmaxjivi05 5f807a0a26 docs: draft DroidDeck 0.5.0 release notes (#986)
* docs: draft DroidDeck 0.5.0 release notes

* docs: expand DroidDeck 0.5.0 release notes

* docs: complete 0.5.0 contributor credits

* docs: update the 0.5.0 release notes for everything merged so far, with screenshots and GIFs

Adds the x86-64 Steam client, Linux games under FEX, app-wide glasses tracking, store-game artwork and Adjust logo, and the merged Epic launcher and XR glasses entries. Media is in docs/releases/media/0.5.0.

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-10-18 11:22:00 -04:00
MaxsTechReview 64a4cc887c fix: only start SteamVR for VR-only games from Steam's library (#1076)
Games that merely support VR (No Man's Sky) were held at Play while
DroidDeck downloaded and started SteamVR, so they always opened in VR.
They now launch normally; VR-only games still wait for SteamVR.
2026-10-18 09:26:01 -04:00
MaxsTechReview 41abcd203a feat: fetch Proton, FEX and the runtimes automatically, and make Linux games run under FEX (#1045)
* fix: x86 Linux games that also ship for Windows run under Proton again

Since #749 the live compatibility helper treated every installed game with
both Windows and x86 Linux builds as a user's native choice, because the
client reports an empty tool for unmapped games. Those games were left on
the Steam Linux Runtime, which cannot start them here (bwrap fails, or FEX
looks for a RootFS that is not shipped), so Valheim, Moonlighter, Half-Life 2,
TF2 and others exited within seconds.

- An unmapped game the helper never managed now gets the default Proton.
  Clearing a mapping the helper set is still kept as a native choice.
- Native choices are stored under "native_choices". The old "native" list
  also held games nobody picked, so it is dropped.

* feat: Linux builds run under FEX by default, with the runtimes fetched automatically

- Steam fetches Proton, FEX, Steam Linux Runtime 3.0 and 4.0 on the first start and keeps them updated instead of leaving them pending.
- Games that ship a Linux build go to FEX once it can run them; Windows-only games and installed Windows builds keep the Proton default.
- FEX runs games on a combined x86 runtime with the newer glibc and libstdc++ the GL and Vulkan thunks need.
- Vulkan display extensions the driver cannot handle are hidden in front of the thunk, without an audit module.
- Mono's incremental garbage collector is turned off under FEX, which stops Unity games crashing.
- x86 Steamworks finds the x86 steamclient.
- 32-bit games read the virtual controller.

* fix: controllers work in Linux Unity games such as Valheim

Joystick ioctls on an event node now fail with EINVAL like the kernel's evdev, so the SDL2 that Unity embeds reads the pad's real ID instead of ignoring it as 0000:0000.

* test: skip the switch script test without node, and the ring tests without gcc

* fix: games with a Linux build keep the Proton default

The Windows build often runs better, so titles that ship both builds stay on the default Proton again. Users can still pick FEX for one, and that choice is kept. Linux-only titles still go to FEX.

* fix: unmapped dual-platform games get Proton even when config.vdf cannot be read
2026-10-18 08:59:54 -04:00
+13 e7bc4b03a5 Add app-wide 3DoF for VITURE, RayNeo and XREAL glasses and 6DoF for Luma Ultra (#1035)
* Add VITURE glasses support: hardware control, 3D, head tracking

Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page.

* Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs

* Hang the glasses Settings lip over the top of the button

* Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses

* Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

* Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad

* Let the pad hands reach a metre out so buttons with deep colliders press

* Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters

* Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad

* Glasses: SteamVR field of view defaults to 90°

* VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand

* Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's

* Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam

* Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end

* Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs

* Harden VITURE stereo negotiation and output refresh tracking

* Localize stereo recovery feedback and record Astra reconnect tests

* Improve test failure diagnostics and refresh Astra firmware findings

* Handle VITURE docks separately and reject truncated USB frames

* Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated

* Glasses: drop USB frames with a bad checksum

* Glasses: count frames dropped for a bad checksum

* Feat/viture xr (#907)

* Add RayNeo Air and GT support to the XR glasses pipeline

* Add RayNeo Air and GT support to the XR glasses pipeline

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Document confirmed XREAL 1S brightness control

* Document confirmed XREAL 1S brightness control

* Fix SteamVR glasses defaults and rendering controls

* Fix SteamVR glasses defaults and rendering controls

* chore: keep only the 3 newest session log folders (#852)

* chore: add agent comment rules (#748)

Co-authored-by: Kurt Himebauch <[email protected]>

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* fix: wireless ADB page names the child-process switch the way Android shows it (#848)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>

* fix: a failed cloud save recovery no longer ends the app (#846)

The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>

* fix: an interrupted Linux runtime download resumes instead of starting again (#847)

* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: droiddeckctl reaches variant-package builds (recover #842) (#854)

fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>

* feat: droiddeckctl reports on-screen pacing (recover #843) (#855)

feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>

* fix: Steam can restart more than twice in a session (#754)

* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>

* fix: the front end keeps its page when the Fold is folded or unfolded (#832)

* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>

* Release pointer buttons without replaying stale motion (#803)

* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>

* l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)

* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* Rebuild and verify Windows VR runtime components

* Rebuild and verify Windows VR runtime components

* Add top, bottom, and centered picture positions for Steam sessions (#871)

* fix: dependency scan ignores folders above the game folder (#859)

* DroidDeck-Proton: get releases from the Droid-Deck org repo

DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.

* feat: low-latency controller and touch toggles in the session drawer (#808)

* fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876)

* feat: Steam home wall fills from the signed-in library instead of store picks (#878)

* test: cover GPU family and support classification (#880)

* Fix local builds on Linux failing after the first Comet build (#866)

* feat: sort the store library and installed tabs, and switch between card and list views (#798)

* feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782)

* fix: keep Quest-patched Turnip as the auto default (#853)

* Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864)

* fix: keep proot sessions alive when the kernel loses fork events (#457)

* fix(quest): correct desktop and Steam display corruption across sessions (#733)

fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: keep SteamVR syscall hooks and report Steam crashes (#905)

* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope

* fix: honor glasses pacing and resolution in SteamVR launches

---------

Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>

* Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture

* Translate the glasses calibration, XREAL and SteamVR recovery strings

* SteamVR recovery: give a finished teardown time to collect logs before restarting

* Credit the projects the XR glasses support builds on

* Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise

* Add experimental Luma tracking and stabilize SteamVR launches

Add native Luma Ultra spatial tracking, optical controllers, and stereo
calibration with DroidDeck styling, imperial/metric measurements, and
pending recenter recovery. Include the pinned hand model and calibration board.

Keep live guest and SDK files outside Android's evictable cache. Filter the
Adreno 840 sysmem override in the shared SteamVR helper, serialize timeline
mirroring, retain invalid optical controllers without event floods, and
prevent an unprivileged scheduling fallback from lowering compositor RTTIME.

Validation: latest APK build and runtime freshness checks pass; 247 affected
glasses/calibration tests pass. Native VR, helper, and packaging regressions
pass. Two earlier phone library launches sustained 60 fps for 308 and 188
seconds without recovery or restart. The newer calibration APK is installed
and checksum-verified; calibrated-hand and sustained gameplay acceptance
remain pending. One existing Android test requires Windows symlink privileges.

* Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected

* Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves

- Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates)
- MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con
- No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field
- SteamVR safe mode cleared before each start so one crash no longer blocks the driver

* Improve Luma Ultra tracking and isolate device-specific controls

Refine stereo hand and Joy-Con tracking, gesture release, calibration,
recovery UI, diagnostics, display modes, and compositor handling.
Keep Ultra controls and input routing behind a live SDK-confirmed Carina
connection; clear retained spatial mode when other glasses connect.
Add optional UVC transport with pinned dependencies and packaged notices.

Validation: 456 Android tests, ARM64 VR build and native regressions,
47 tooling tests, SDK/UVC-enabled and disabled builds, and 150 packaged
runtime checksums. Two shader compilation tests skipped because the
compiler is unavailable. Hardware interaction gates remain experimental.

* Delay Luma Ultra 6DoF until SteamVR settles

Suspend spatial SDK startup while SteamVR launches, then restore it two seconds
after RUNNING is confirmed. Preserve saved tracking preferences and automatically
recenter same-device restoration, including cancellation and early stop.

Discard superseded launch requests and status replies after session teardown.

Validation: 128 focused JVM tests passed on the isolated commit snapshot; 129
passed with the current local hand-tracking work. Debug build and runtime checks
passed, and the reviewed APK was installed and hash-verified on the phone.

* Clarify glasses and phone camera tracking options

Separate Luma Ultra camera controls from phone-camera tracking so the two
hand-tracking switches have clear sources. Label bare-hand controllers and
Joy-Con position tracking explicitly, explain physical Joy-Con inputs and
camera priority, and update the camera prompt and calibration guidance.

Use fresh phone section and toggle labels so existing translations do not
hide the distinction. Preserve tracking behavior and saved preferences.

Validation: 39 existing UI tests, debug build, and packaged runtime verification
passed. Reviewed changes and installed the APK on the connected phone.

* Fix tracking handoff and shared runtime regressions

Release spatial ownership and controller overrides when output returns to the phone. Preserve USB-loss recovery, keep Vulkan submissions asynchronous, clean persistent guest scratch on runtime removal, and clarify the 6DoF head-tracking control.

* Apply smart-glasses virtual screens across DroidDeck menus

* Finalize Luma hand baseline and document smart-glasses support

Keep raw grayscale-to-RGB Full hand inference, wrist-only Joy-Con reconstruction, bounded visual holds, and the dead-zone rollback. Retain opt-in steady aim, recenter controls, reproducible Lite models, and the JPEG-padding fix.

Remove unsuccessful benchmark-only and USB transport experiments. Document the cross-brand 3DoF matrix, Luma Ultra 6DoF, SDK requirements, and app-wide screen behavior.

* Fix executable permission for SteamVR launcher

* Address hand tracking and VR review findings

* Bound stereo recovery test image comparison memory

* Localize smart-glasses setup and tracking diagnostics

Move calibration controls, status messages, validation errors, distance labels,
and printed-board instructions into Android string resources. Add and update
machine-assisted translations in all 32 supported non-English locale catalogs,
with corrections for recentering, hand tracking, product names, and measurements.

Keep print instructions within the page using text layout that supports wrapping
and right-to-left text. Correct the Lite model toggle description to reflect its
Lite palm detector and Lite landmark model.

Add locale coverage, format-argument, line-break, and measurement checks, plus
French and Arabic calibration UI tests. Preserve tracking calculations and the
shared runtime storage behavior.

Validation: Android resource and Kotlin compilation; 3 localization checks;
68 focused tests; 1,597 JVM tests at a 512 MB heap; French and Arabic UI renders.

* Correct smart-glasses translations from PR review

Fix the 42 localization findings covering hand and head terminology, disabled states, calibration dimensions, camera visibility, capture rejection, and menu references. Correct the repeated capture-error ambiguity in related locales and clarify its English source. Keep Romanian setup instructions consistent with the existing informal voice.

Extend localization measurement validation to distance hints and retry guidance. Verify the recovery screen's palette-dependent text color with one row-sized pixel buffer and guaranteed bitmap cleanup.

Validation: Android resource and Kotlin compilation; 3 localization checks across all supported locales; 68 focused JVM tests at a 512 MB heap.

* Work around SteamVR device loss with Adreno 840 Turnip v0.2.0

* Withdraw ineffective SteamVR flushall workaround

---------

Co-authored-by: maxjivi05 <[email protected]>
Co-authored-by: MaxsTechReview <[email protected]>
Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>
2026-10-18 08:37:09 -04:00
wallentx 93c358c541 Assert empty crash snapshot retention 2026-10-18 04:26:34 -05:00
wallentx 5c55b84d94 Preserve recovered crash snapshots 2026-10-18 04:15:04 -05:00
wallentx 3e90875be8 Restrict library redaction exceptions to loader diagnostics 2026-10-18 04:05:36 -05:00
wallentx 4ab4a86507 Defer abandoned log scrubbing past launch 2026-10-18 03:56:09 -05:00
wallentx 66f5d39bab Fix game diagnostics recovery races 2026-10-18 03:36:30 -05:00
wallentx 536bb11df8 Preserve Android shared-library names in logs 2026-10-18 03:22:32 -05:00
The412Banner 292295ba30 feat: x86-64 client follows DroidDeck-Components' fex.json; fuller x86-64 session logs (#1053)
FEX builds can reach devices without an app release: while the x86-64
client is selected the app reads DroidDeck-Components' fex.json (directly,
on its own thread, not in Offline Mode) and a session passes that build to
droiddeck-steam-x64, which installs it only once its sha256 matches and its
programs load, keeping the replaced files aside until the installed copy
loads too; it keeps a working installed build otherwise and falls back to
the build it pins. FEX is built and published by Components' build-fex.yml
on its single fex release; this repository's build-fex.yml only checks the
build. x86-64 session logs name the FEX build and its source, the rootfs and
the client's builds, report the rootfs download in tenths, and say in one
line when the web helper's GPU process keeps exiting, with x86 glxinfo and
vulkaninfo through FEX. Settings shows the FEX build installed.
2026-10-18 03:40:17 -04:00
wallentx 6521c3c3ad Add opt-in game debug logs and bounded log sharing 2026-10-18 02:22:15 -05:00
The412Banner 798386d02a fix: x86-64 Steam client fails without the KDE desktop (FEX r5 bundles libxxhash) (#1050)
FEX r4 links libxxhash, which the Linux runtime does not ship; devices
without the KDE desktop package (where the library came from) exited 127
at the x86-64 client's first FEX call. FEX r5 bundles it beside libFEXCore
with RUNPATH $ORIGIN/../lib, and the FEX build now fails when a program in
the package needs a library that is neither bundled nor listed in
tools/fex/runtime-libs.txt. droiddeck-steam-x64 moves to fex-2610-r5
(DroidDeck-Components #5). Progress log: the org review of #152 and this fix.
2026-10-18 02:00:29 -04:00
The412Banner db8d1c9910 feat: optional x86-64 Steam client (Valve's Linux client under FEX) beside the arm64 one (#152)
An opt-in second Steam client (Steam settings, Steam client: x86-64): Valve's
x86-64 Linux client, unchanged, runs under FEX in its own home with its own
game library and the same sign-in. Linux builds run directly, so titles such
as Team Fortress 2 and Half-Life use their Linux versions; Windows games use
DroidDeck-Proton as on arm64. The arm64 client stays the default.

- droiddeck-steam-x64: FEX and the x86-64 rootfs from DroidDeck-Components
  (sha256-pinned), client install and sync from Valve's CDN, sign-in shared
  with the arm64 client, its web helper on the GPU (ANGLE GL over FEX's GL
  thunk, zink, Turnip), Proton wrappers for the arm64 client's tools,
  DroidDeck direct for Linux builds, SD-card library (x86-64/ beside the
  arm64 one), repair.
- FEX 2610 built by build-fex.yml with thunks and three patches: an openat2
  fallback without openat2 (proot), newer-clang thunkgen, MAP_32BIT
  exhaustion returning ENOMEM. libblsession resolves RESOLVE_IN_ROOT paths
  inside the root.
- Session: x86-64 gating, shortcuts and compatibility tools per client,
  logs from the client that ran.
- App: client setting with the FEX version, an x86-64 chip on the Games
  header, the active client's library, art, storage, downloads, repair,
  Offline Mode, Decky, desktop mode, optional VAC diagnostics; per-client
  state for art, the content log, repair and leftovers.
- Strings in every locale.
- Review fixes: the library cache is saved only by the newest scan; an
  abandoned session's Steam logs come from the client it recorded; distinct
  VAC console names; FEXInterpreter in CPU pinning; clearer VAC hint in
  pl, ru, uk, cs and bg.
2026-10-17 23:57:52 -04:00
MaxsTechReview ab799e6938 fix: rename the VR recovery task affinity in variant builds (#1038) 2026-10-17 17:24:31 -04:00
MaxsTechReview b77135cee8 fix: rename the VR recovery task affinity in variant builds
The PUBG, AnTuTu and Ludashi signing jobs stopped on
com.droiddeck.launcher.vr_recovery, the task affinity of the VR
recovery activity added with the VITURE XR controls, because the
package rename did not cover it.
2026-10-17 17:14:06 -04:00
MaxsTechReview 2fe4d5303c feat: Epic Games Launcher for Linux (#928)
* feat: Epic Games Launcher for Linux

Install Epic's official Linux launcher (EGLv2) from Epic's CDN with Valve's
Steam Runtime 4 and run it under FEX, signed in with the linked Epic account.
Epic games launch through a hidden launcher instance so they get the
launcher's own arguments, with the exchange-code launch as the fallback, and
DroidDeck installs show up in the launcher as installed.

In the launcher, touch scrolls like a phone app and the gamepad drives the UI:
D-pad and left stick move the selection, A opens, B goes back, the right stick
and triggers scroll proportionally, X opens the options menu, Y the keyboard
and LB/RB switch tabs.

* feat: Epic Games Launcher for Linux

Install Epic's official Linux launcher (EGLv2) from Epic's CDN with Valve's
Steam Runtime 4 and run it under FEX, signed in with the linked Epic account.
Epic games launch through a hidden launcher instance so they get the
launcher's own arguments, with the exchange-code launch as the fallback, and
DroidDeck installs show up in the launcher as installed.

In the launcher, touch scrolls like a phone app and the gamepad drives the UI:
D-pad and left stick move the selection, A opens, B goes back, the right stick
and triggers scroll proportionally, X opens the options menu, Y the keyboard
and LB/RB switch tabs.

* Route Epic games through Steam, import launcher installs, offer the second library

- Every Epic launch goes Steam -> Epic Games Launcher -> game; Play inside the open launcher hands the game to Steam
- Games installed from inside the launcher get a Games-tab entry and launch through Steam; the launcher installs to the last Stores target
- Stores install picker offers the Steam second library, with side-by-side choices
- uruntime xattr-only failures no longer fail the launcher unpack (app-extract)

* Hand launcher Play and Steam links to Steam, wait for FEX on first run

Play on a game the launcher just installed is matched to its install
record and handed to Steam. Steam games listed in the launcher open
steam://rungameid links, which now start the game in a Steam session.
Opening the launcher before Steam has downloaded FEX asks Steam for it
and starts Steam instead of failing with no-fex.

* Address review: FEX check, play-in-Steam action, touch route thread, scroll scale

fexReady needs a FEX binary in Steam's FEX tool, not just its folder.
The play-in-Steam action stays until the link is handled, so Run Steam
at startup can't start a session first. The pad touch route reads the
mouse mode without side effects off the main thread and posts the pad
update there. Vertical scroll uses the output height. A status-2 unpack
counts only when AppRun is there. The xdg-open command is tested
end to end for Steam links.

* Open the launcher through Steam, recover dropped shortcuts, sign out of both

The Epic Games Launcher now runs as its own Steam shortcut, so opening
it starts Steam, which opens the launcher, and Play inside it goes to
the running Steam.

Steam dropped a running shortcut from its library when the game exited
after a sleep/wake replay or a server re-logon, and launching it then
waited forever at ShowInterstitials. login1 only replays sleep/wake
when Steam is asleep. The session restarts Steam when a requested
shortcut is missing, and when a game exits with its shortcut gone and
nothing else runs.

Resuming after a long pause no longer reports failure while traced
processes are still waking, so "Steam is paused" goes away on the first
tap.

Signing out of the launcher signs DroidDeck's Epic store out, and
signing out in DroidDeck restarts the launcher signed out and clears
the Epic web cookies.

* Address review: one session-switch poll, Epic setup can't block a session

A new switch confirmation replaces the pending poll instead of adding a
second one that could start the session again. Epic library sync and
install-folder setup at session start are each guarded, so a bad
preference no longer stops the guest from launching.

* Uninstall the Epic Games Launcher from the Games tab

An Uninstall button next to Launch asks first, closes a running
session, and removes the launcher and its runtime. The launcher's
sign-in and install records stay, so its games keep their Games-tab
entries and a reinstall comes back signed in. Its Steam shortcut goes
away on the next Steam start.

* Address review: uninstall only after the session has ended

The launcher's files are removed only once the session is not running
and its phase has ended; a shutdown still in progress after the wait
reports failure instead. The confirmation reads the session state from
the front-end state.

* Find FEX that Steam installed on the second library

Steam's tools on a second library live in DroidDeck's private copy,
which the session binds over the library. The launcher's FEX check
only looked on the shared storage, so with FEX there it always asked
Steam for FEX and only Steam opened. It now checks the private copy
too.

* Start games without Steam, per-game Settings menu with Wine sync

"Start without Steam" now really leaves the Steam client out: the game
runs in its own Proton/FEX through droiddeck-proton-run in a run
session, and the Epic Games Launcher opens on its own with its games
playing in it. Run sessions for these games are not pinned to the big
cores and get DirectAudio and the added-games list like Steam.

The Games tab card is now "Settings", titled "<game> Settings": Start
without Steam, FEX preset, Wine sync (ntsync, fsync, esync, wineserver
or the app default) and Default, which puts everything changed for the
game back to the app's settings. The app-wide Wine sync choice is shown
in the Steam settings Session tab.

* Address review: uninstall guard, routed games, shortcut matching

Opening or reinstalling the Epic Games Launcher waits while it is being
uninstalled. Added games that are Steam installs keep their Start
without Steam setting under their Steam app id and start as that Steam
game. Live shortcut lookups wait for Steam's library to load and match
a same-name shortcut by its executable, so no duplicate is added. The
session treats the open launcher as itself even when it could not tell
its Steam app id.

* Address review: drop a queued launcher switch when it is uninstalled

A confirmed switch to the Epic Games Launcher that is still waiting for
the old session to end is cancelled when the launcher is uninstalled,
and never starts while the uninstall runs. The launcher id test sets
its environment for its own runs only.
2026-10-17 14:40:39 -04:00
+13 16f9916825 Add VITURE XR controls, head tracking, and SteamVR (#732)
* Add VITURE glasses support: hardware control, 3D, head tracking

Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page.

* Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs

* Hang the glasses Settings lip over the top of the button

* Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses

* Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

* Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad

* Let the pad hands reach a metre out so buttons with deep colliders press

* Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters

* Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad

* Glasses: SteamVR field of view defaults to 90°

* VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand

* Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's

* Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam

* Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end

* Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs

* Harden VITURE stereo negotiation and output refresh tracking

* Localize stereo recovery feedback and record Astra reconnect tests

* Improve test failure diagnostics and refresh Astra firmware findings

* Handle VITURE docks separately and reject truncated USB frames

* Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated

* Glasses: drop USB frames with a bad checksum

* Glasses: count frames dropped for a bad checksum

* Feat/viture xr (#907)

* Add RayNeo Air and GT support to the XR glasses pipeline

* Add RayNeo Air and GT support to the XR glasses pipeline

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Document confirmed XREAL 1S brightness control

* Document confirmed XREAL 1S brightness control

* Fix SteamVR glasses defaults and rendering controls

* Fix SteamVR glasses defaults and rendering controls

* chore: keep only the 3 newest session log folders (#852)

* chore: add agent comment rules (#748)

Co-authored-by: Kurt Himebauch <[email protected]>

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* fix: wireless ADB page names the child-process switch the way Android shows it (#848)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>

* fix: a failed cloud save recovery no longer ends the app (#846)

The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>

* fix: an interrupted Linux runtime download resumes instead of starting again (#847)

* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: droiddeckctl reaches variant-package builds (recover #842) (#854)

fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>

* feat: droiddeckctl reports on-screen pacing (recover #843) (#855)

feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>

* fix: Steam can restart more than twice in a session (#754)

* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>

* fix: the front end keeps its page when the Fold is folded or unfolded (#832)

* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>

* Release pointer buttons without replaying stale motion (#803)

* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>

* l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)

* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* Rebuild and verify Windows VR runtime components

* Rebuild and verify Windows VR runtime components

* Add top, bottom, and centered picture positions for Steam sessions (#871)

* fix: dependency scan ignores folders above the game folder (#859)

* DroidDeck-Proton: get releases from the Droid-Deck org repo

DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.

* feat: low-latency controller and touch toggles in the session drawer (#808)

* fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876)

* feat: Steam home wall fills from the signed-in library instead of store picks (#878)

* test: cover GPU family and support classification (#880)

* Fix local builds on Linux failing after the first Comet build (#866)

* feat: sort the store library and installed tabs, and switch between card and list views (#798)

* feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782)

* fix: keep Quest-patched Turnip as the auto default (#853)

* Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864)

* fix: keep proot sessions alive when the kernel loses fork events (#457)

* fix(quest): correct desktop and Steam display corruption across sessions (#733)

fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: keep SteamVR syscall hooks and report Steam crashes (#905)

* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope

* fix: honor glasses pacing and resolution in SteamVR launches

---------

Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>

* Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture

* Translate the glasses calibration, XREAL and SteamVR recovery strings

* SteamVR recovery: give a finished teardown time to collect logs before restarting

* Credit the projects the XR glasses support builds on

* Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise

* Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected

* Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves

- Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates)
- MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con
- No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field
- SteamVR safe mode cleared before each start so one crash no longer blocks the driver

---------

Co-authored-by: MaxsTechReview <[email protected]>
Co-authored-by: informalTechCode <[email protected]>
Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>
2026-10-17 13:17:05 -04:00
kingokksa 8acf3037fd l10n: translate the missing strings in every locale, and complete es-419 (#1013)
* l10n: translate the missing strings into zh-CN, zh-HK and zh-TW

* l10n: translate the missing strings into ja and ko

* l10n: translate the missing strings into it, pt, pt-PT, ru and tr

* l10n: translate the missing strings into nl, pl, sv, da, nb and fi

* l10n: translate the missing strings into ar, bg, cs and el

* l10n: translate the missing strings into hi, hu, in, ms and ro

* l10n: translate the missing strings into th, uk and vi

* l10n: complete the es-419 translation

* l10n: translate the missing strings into de, es and fr
2026-10-17 11:44:59 -04:00
FX1922 77c3fd323e feat: Adjust a game’s logo position and size in the artwork editors (#1004)
* Add an Adjust logo screen to move and resize a game's logo in Steam

* Report a failed write of the local logo position instead of throwing

* Record a Steam-side logo position change only once it has been taken over here
2026-10-17 10:02:22 -04:00
FX1922 dc7ed61d38 Feat/store game artwork (#976) 2026-10-17 01:19:02 -04:00
Resul Övür fb0f73e300 feat: the session report lists the panel refresh modes, thermal state and hint-session support (#990) 2026-10-17 02:31:17 +00:00
Resul Övür 197098f92e Launch an added game after a bounded wait for its Steam overview (#973) 2026-10-16 17:21:53 -04:00
Kurt Himebauch 602a6fd734 feat: auto drivers update on app open and roll back if a new pair never starts a session (#972) 2026-10-16 16:17:44 -04:00
FX1922 4b2537a36b fix: Keep game art in step with Steam's grid folder; apply review fixes (#948) 2026-10-16 16:14:49 -04:00
Kurt Himebauch 6bc6edafea feat: boot movie settings for 30/60 fps, sound and custom imports (#969) 2026-10-16 15:07:46 -04:00
Kurt Himebauch 23479d15f5 Feat/gpu drivers redesign (#858) 2026-10-16 17:50:45 +00:00
kingokksa 7c90879b39 feat: downloads resume where they stopped, and fall back to another source (#623) 2026-10-16 13:45:35 -04:00
Kurt Himebauch 5e57d78b5d fix: reduce flickering on thor and a7xx: match the A740 TP UBWC flag hint to the system driver so Android UI stops flickering (#963) 2026-10-16 13:37:28 -04:00
TideGear 3f3ed3923b fix: hold XInput rumble until the game changes it, instead of stopping after a second (#938) 2026-10-16 17:30:21 +00:00
Kurt Himebauch 2c37f79701 fix: session achievements page no longer sticks on Reading (#961) 2026-10-16 15:51:57 +00:00
Kurt Himebauch 8b7a22e19e feat: merge SteamVR and Cardboard into one VR drawer tab (#959) 2026-10-16 14:59:34 +00:00
angelrenefm 91b02a9dda fix: the proot fast path finds unix sockets whose host path is longer than sun_path (#841)
* fix: the proot fast path finds unix sockets whose host path is longer than sun_path

When a socket's host path did not fit sun_path, bind() went to proot, which
bound it to a short temporary name the fast path cannot see. The fast path's
own connect, stat and unlink then answered ENOENT, so FEXServer's socket under
compatdata/<app>/fex-emu/Server could never be reached (#839, #579).

Name such a socket through its directory (/proc/self/fd/N/<name>) instead, so
it lives at its real host path for every process, and report the guest path
back from getsockname, getpeername and accept. equiv3.py's long-path section
binds, connects, stats, names and unlinks a socket there.

* fix: key long socket names per socket and reset their lock after fork

Review follow-up. A long name is now looked up by the socket's inode and the
name, separately for its own and its peer's address, so two sockets with the
same file name in different directories no longer mix up; a missing entry is
left as the kernel reports it. The lock is re-initialised in the child after
fork, and the copies use snprintf. equiv3.py binds a second socket with the
same name in a sibling directory and checks both names.
2026-10-16 10:05:12 -04:00
angelrenefm 4cf4cf57b7 fix: droiddeckctl refuses a DROIDDECK_PACKAGE that is not a package name (#896)
The value goes to the device shell inside `am start -n` and `content call --uri`.
Anything but a dotted Android package name is refused before adb runs.
2026-10-16 10:05:02 -04:00
angelrenefm cd04e056db fix: droiddeckctl pacing judges cadence per refresh-rate record and always turns timestats off (#894)
* fix: droiddeckctl pacing judges cadence per refresh-rate record and always turns timestats off

Follow-up to the CodeRabbit reviews of #843/#855:
- off-cadence is counted per layer record against that record's own cadence,
  allowing the neighbouring millisecond bucket (16.7 ms lands in 16 or 17);
- refreshRates comes from the chosen layer records only;
- records with no counted frames are skipped, so an empty game layer no longer
  hides the session's SurfaceView, and a capture with no frames exits NO_FRAMES;
- adb failures while enabling or dumping map to PACING_FAILED, and timestats
  is disabled in every case.

* fix: droiddeckctl pacing reports when timestats could not be turned off
2026-10-16 09:50:43 -04:00
Resul Övürandovurrsl a4f2c3b3f0 Keep waiting for a shortcut's Steam overview instead of dropping its cold launch (#914)
fix: keep waiting for a shortcut's Steam overview instead of dropping its launch

The cold-launch gate waited once for 45 s and, on a timeout, held the
launch URL for good. A library that loads slower than that (or a client
still at the sign-in screen) left the requested game never started, with
nothing on screen. Retry the wait up to 40 times, 45 s each, and launch
as soon as the overview exists. The no-overview case still does not
forward the CGameID.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:35 -04:00
Resul Övürandovurrsl ca6a26e078 fix: the store sign-in page keeps its place when the display density changes (#886)
* fix: the store sign-in page keeps its place when the display density changes

StoreLoginActivity sized its toolbar and error panel in code with dp(), so it
was left out of the density change and a fold between two displays of
different density still recreated it and reloaded the sign-in page. Handle
density in the manifest and apply the dp sizes again in onConfigurationChanged.

Checked on the foldable emulator with the GOG sign-in open: wm density
420 -> 560 -> 320 does not relaunch the activity, the page stays, and the top
bar follows the new density.

* fix: re-apply the sign-in text sizes together with the other dp sizes

The title (16sp) and error text (15sp) were set in sp at creation, so after a density change they kept the old scale. Set them again in onConfigurationChanged.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:28 -04:00
Resul Övürandovurrsl 631213728c fix: the store sign-in WebView cannot read local files or content URIs (#890)
The sign-in pages are ordinary web pages. file access and content access
default to on below targetSdk 30, so switch both off explicitly.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:21 -04:00
Resul Övürandovurrsl d573b60e4c fix: the launcher does not offer its own display as a second screen (#889)
* fix: the launcher does not offer its own display as a second screen

MainActivity asked for the second-screen candidates with the default
display as the primary one. When the launcher is itself shown on another
display (DeX, an external monitor), that display was listed as a second
screen. Pass the Activity's own display id, as SessionActivity already does.

* fix: read the Activity display only on API 30 and newer

minSdk is 26 and Activity.getDisplay needs API 30. Use windowManager.defaultDisplay below that.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:14 -04:00
Resul Övürandovurrsl 0d2e268dd5 fix: store background jobs log a failure instead of ending the app (#898)
* fix: store background jobs log a failure instead of ending the app

refresh, sign-out, uninstall and the EOS overlay cleanup ran on plain threads. An exception there reaches the crash handler, which ends the process. Run them through one helper that logs the failure; a failed uninstall also clears its row and calls onDone so the UI does not stay on uninstalling.

* ci: run the build again

The first run failed while installing packages (archive.debian.org returned 404 for libnsl-dev). Nothing in the change differs.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:50:06 -04:00
Resul Övürandovurrsl c74a6b3888 l10n: refresh outdated strings, one name for the child-process switch, pt-rPT gaps (#884)
l10n: refresh outdated strings and name the child-process switch the same way everywhere

- hu: ctrl_size_hint showed a literal "%%".
- es, fr, ru: game_env_reset, mode_controller_note and comp_no_proton still
  described older English text.
- fr: the x86_64 AppImage (through FEX) strings were missing the x86_64 case.
- fr: put the spaces back inside the guillemets of the switch name.
- All locales: gate_toggle_steps and phantom_fix_toggle now use the same
  switch name as wadb_restrict_on/off, which is the title Android 17 shows.
- pt-rPT: add the 14 strings it was missing (reset settings, zRAM, store
  free space), as a European Portuguese draft.

Machine-assisted; native review is welcome.

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:49:58 -04:00
Resul Övür 2388527fe5 feat: the session report lists the memory page size and the last process exits (#897) 2026-10-16 09:46:10 -04:00
Resul Övürandovurrsl 9db3b7a20b fix: read Settings.Global phantom process flag on Android 12 (SDK 31) (#955)
On Android 12 (SDK 31), read() only checked the android12-off preference written by in-app Wireless ADB, so disabling phantom process monitoring over USB ADB left the status as UNREADABLE. Also write settings_enable_monitor_phantom_procs in the SDK 31 shell commands and accept a disabled Settings.Global / fflag value in read().

Co-authored-by: ovurrsl <[email protected]>
2026-10-16 09:37:03 -04:00
Zeyad Saber Refaei Kenawi d269f26604 Add support for android auto (#952)
Add CAR_LAUNCHER intent filter to support android auto
2026-10-16 09:35:39 -04:00
Kurt Himebauch 700a843b10 Fix Quest CI headers when preloads are cached (#951) 2026-10-16 12:35:55 +00:00
FX1922 bab09b798a feat: Change artwork for Steam games; remove “Show more” from the SteamGridDB search. (#939) 2026-10-16 02:08:09 -04:00
kmnkv1 8726e43658 Sync session logs every 2 s so a hard reset keeps their last minutes (#513) 2026-10-16 01:51:56 -04:00
kmnkv1 78429c93a8 perf: boost the game's busiest threads onto the prime cores on the Odin 3 (#682) 2026-10-16 01:51:00 -04:00
Kurt Himebauch 679e3d8934 feat: add immersive Quest SteamVR to the standard app (#910) 2026-10-16 01:48:00 -04:00
Kurt Himebauch 559120313a update readme (#937)
docs: credit mmap input work
2026-10-15 23:00:15 -04:00
Kurt Himebauch 54d5f30ee7 fix: pace adaptive frame generation on the game's frame rate, not the compositor loop's (#927) 2026-10-15 18:36:40 -04:00
Resul Övür 9d6251c37b feat: the second-screen Deck controls get the Gamepad tab (#895) 2026-10-15 16:54:43 -04:00
Resul Övür bf5a33a0c8 fix: update zstd-jni so the Linux runtime installs on a 16 KB page-size device (#900) 2026-10-15 16:54:04 -04:00
Kurt Himebauch ccafdf99c3 feat: storage page to see and free DroidDeck's storage (#913) 2026-10-15 16:45:37 -04:00
Kurt Himebauch 9ce5a24459 fix: keep SteamVR syscall hooks and report Steam crashes (#905)
* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope
2026-10-15 15:50:26 +00:00
Jagger15andxXJsonDeruloXx a692436f67 fix(quest): correct desktop and Steam display corruption across sessions (#733)
fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-15 09:51:02 -04:00
Jamie Schenker c3cd955686 fix: keep proot sessions alive when the kernel loses fork events (#457) 2026-10-15 09:50:29 -04:00
Muaz Kadan cd18bdb3dc Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864) 2026-10-15 09:47:01 -04:00
Kurt Himebauch ea0a61ca2b fix: keep Quest-patched Turnip as the auto default (#853) 2026-10-15 09:46:26 -04:00
Kurt Himebauch 356f048f00 feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782) 2026-10-15 09:36:28 -04:00
kingokksa 82f757f000 feat: sort the store library and installed tabs, and switch between card and list views (#798) 2026-10-15 09:20:49 -04:00
Muaz Kadan aa9552d999 Fix local builds on Linux failing after the first Comet build (#866) 2026-10-15 09:20:03 -04:00
Resul Övür 091f5e45f8 test: cover GPU family and support classification (#880) 2026-10-15 09:19:40 -04:00
Kurt Himebauch e3c197d94e feat: Steam home wall fills from the signed-in library instead of store picks (#878) 2026-10-15 09:18:07 -04:00
FX1922 8874276458 fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876) 2026-10-15 08:36:48 -04:00
Kurt Himebauch 7c73adb3aa feat: low-latency controller and touch toggles in the session drawer (#808) 2026-10-15 07:53:25 -04:00
The412Banner ef8e748a94 DroidDeck-Proton: get releases from the Droid-Deck org repo (#875) 2026-10-15 07:29:42 -04:00
The412Banner 454ae3e9c2 DroidDeck-Proton: get releases from the Droid-Deck org repo
DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.
2026-10-15 07:20:09 -04:00
Kurt Himebauch 9e7b4c0b50 fix: dependency scan ignores folders above the game folder (#859) 2026-10-15 07:03:17 -04:00
asdf17128 d3b0b8e51c Add top, bottom, and centered picture positions for Steam sessions (#871) 2026-10-15 07:01:01 -04:00
9fd13cec4d l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)
* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-15 03:41:17 +00:00
asdf17128andsiyuan.qsy 9cc802dc9c Release pointer buttons without replaying stale motion (#803)
* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>
2026-10-14 23:37:35 -04:00
Resul Övürandovurrsl 700bf18ab2 fix: the front end keeps its page when the Fold is folded or unfolded (#832)
* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 23:30:19 -04:00
8f4e0ac011 fix: Steam can restart more than twice in a session (#754)
* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-15 02:18:26 +00:00
Kurt Himebauchandangelrenefm 704e95edb3 feat: droiddeckctl reports on-screen pacing (recover #843) (#855)
feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>
2026-10-15 02:17:06 +00:00
Kurt Himebauchandangelrenefm af13f04156 fix: droiddeckctl reaches variant-package builds (recover #842) (#854)
fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>
2026-10-14 22:15:29 -04:00
02966a288e l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)
* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 22:09:50 -04:00
Resul Övürandovurrsl 03f1a14e81 fix: an interrupted Linux runtime download resumes instead of starting again (#847)
* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 22:01:29 -04:00
KirkandKirk 98be0838c4 fix: a failed cloud save recovery no longer ends the app (#846)
The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>
2026-10-14 22:01:17 -04:00
Resul Övürandovurrsl 04f7b7e162 l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)
* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 22:00:54 -04:00
Resul Övür 1357fd69ee fix: wireless ADB page names the child-process switch the way Android shows it (#848) 2026-10-14 21:45:18 -04:00
Yassine GherbiandKurt Himebauch 78aba50630 chore: add agent comment rules (#748)
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-14 21:39:47 -04:00
Kurt Himebauch 0ae0d45bdf chore: keep only the 3 newest session log folders (#852) 2026-10-14 21:32:05 -04:00
Kurt Himebauch 6526fffc02 fix: Steam touch controls load the layout Steam picked for DroidDeck's touch controller, including workshop layouts (#840) 2026-10-14 16:09:40 -04:00
RaidennrlandxXJsonDeruloXx f704fabc69 Fix wired PowerA and PDP right-stick and trigger mapping (#692)
Fix wired PowerA and PDP stick and trigger axes

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 13:56:17 -04:00
kmnkv1andxXJsonDeruloXx 60caa85353 Build and test on Windows: LF session scripts, '/' in relative paths (#552)
fix: build and test on Windows

- .gitattributes keeps the session's scripts LF under core.autocrlf=true;
  CRLF scripts made every session exit with status 2.
- runtime_inputs.py and the scrub record / log zip use '/' in relative
  paths, which Windows reported with '\' (runtime inputs never matched).
- The read-only-folder scrub test is skipped on Windows, which still
  creates files in a read-only directory.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-14 13:56:12 -04:00
Gustash 9e1b3c1ac1 feat: hide unfolded controls when a controller is connected (#829)
* feat: hide unfolded controls when a controller is connected

* fix: also hide controls when a BLE controller is connected

* fix: use refreshControls on session resume as well
2026-10-14 13:52:13 -04:00
Tjebbe Kerstens 2e65efcfa7 fix: seed redist markers again when the registry lost them (#793)
* fix: redist markers a replaced registry lost are seeded again

droiddeck-seed-redists recorded what it seeded in .droiddeck-redists and
trusted that record from then on. A Proton update replaces its
default_pfx system.reg but leaves our record beside it, and every new
prefix copies both, so new games got a record of markers their registry
never had: Steam then ran their install scripts for real. Batman: Arkham
City's AMD Dual-Core Optimizer installer hangs there, leaving the game
on "Running install script" on every launch.

Each recorded marker is now looked up in the registry, in both views
where both were written, and seeded again when it is gone; a record of
markers nobody wants any more is rewritten without them.

* Check every VC runtime architecture before keeping the vcrt marker

vcrt writes Installed for x86, x64 and arm64 in both registry views, but
present() only looked at x86. If only an x64 or arm64 value was gone the
runtime would not be seeded again. Require all six values and test the
loss of the arm64 Wow6432Node one.
2026-10-14 13:52:07 -04:00
Tjebbe Kerstens 6528a7ed64 fix: Batman: Arkham City starts with full x87 precision under FEX (#792)
Arkham City (BatmanAC.exe) stopped with Unreal's "Fatal error!" about
1.5 s after start: an access violation in BatmanAC.exe right after
steam.dll loads. The game leans on x87 and needs full precision under
FEX (FEX wiki), while the default FEX preset sets
FEX_X87REDUCEDPRECISION=1. Engine fixes sit above the shared preset and
below the player's own game profile, so a fix keyed on BatmanAC.exe
beside the started exe (BatmanAC.exe or BmLauncher.exe) restores
x87 precision for this game only. Arkham Asylum, which shares
BmLauncher.exe but not BatmanAC.exe, keeps the preset.
2026-10-14 13:52:01 -04:00
KirkandKirk b201162475 fix: game shortcuts use the cover art when Steam's icon is too small (#825)
Steam keeps a 32x32 icon for each game, and the home screen stretched
it to launcher size. A square from the middle of the cover art is used
instead unless the icon is at least 128 px; the small icon is still
the fallback when there is no art.

Co-authored-by: Kirk <[email protected]>
2026-10-14 13:51:56 -04:00
Muaz Kadan ce444fbacb Fix battery time estimates showing thousands of hours on phones that report current in mA (#820)
fix: read battery current reported in mA so time estimates aren't 1000x too long
2026-10-14 13:51:51 -04:00
William Allen 422429d63f fix: read the replaced listing's watch descriptor before detach can free it (#685)
install() replaces an existing cache entry by calling detach(old), which
destroys the listing when no reader holds it, and then reads old->wd to
decide whether to release the watch. When the old listing has no readers,
that read happens after the free.

Two threads that enumerate the same uncached directory at the same time
reach this path: both build a listing, the first installs an entry with
no readers, and the second replaces it.

Save the watch descriptor before detaching, as drop() already does.
2026-10-14 13:51:44 -04:00
Resul Övürandovurrsl 29f5e8c689 fix: Turkish translation for zRAM, reset-settings and store storage messages (#826)
Adds the 14 translatable strings that values-tr was missing: the zRAM toggle and its hints, the Reset all settings row, dialog and toasts, and the store install free-space messages. Keys marked translatable=false stay untranslated on purpose.

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 13:51:38 -04:00
Resul Övürandovurrsl 8810a56df3 docs: README names the child-process setting the way Android shows it (#831)
* docs: README names the child-process setting the way Android shows it

Developer options label this switch "Disable child process restrictions", and it has to be turned on. The README said to turn off "Restrict child processes".

* docs: README limits the child-process toggle to Android 14 and newer

The Developer options switch only exists from Android 14 (PhantomProcessLimit.hasDeveloperToggle); Android 12 and 13 keep the Fix it for me flow.

---------

Co-authored-by: ovurrsl <[email protected]>
2026-10-14 13:51:34 -04:00
Imgau 58140a247d fix: declare all-files access for raw USB storage (#760) 2026-10-14 13:48:14 -04:00
Kurt Himebauch 72bd6be38d feat: store sign-in walls and the Steam home wall fill with curated featured art (#835) 2026-10-14 13:42:27 -04:00
Kushagra 30854422ff feat: map a controller's two back paddles to the Deck's L4 and R4 (#797) 2026-10-14 15:31:59 +00:00
Gustash 3ad5f936bf fix: the touchpad moves a game's own cursor when it locks the pointer (#772) 2026-10-14 11:30:54 -04:00
Kurt Himebauch 48adf4d1ff perf: local builds reuse unchanged native steps across runs and worktrees (#819) 2026-10-14 15:30:24 +00:00
Kurt Himebauch 0f084d1bab fix: store installs stay shortcuts instead of becoming Steam installs (#816) 2026-10-14 10:10:12 -04:00
Kurt Himebauch fed9474539 Show controller-driven cursors in nested Wayland sessions (#759) 2026-10-14 08:25:08 -04:00
The412Banner c8ac2d1e91 fix(stores): drop expired Epic and Amazon tokens when a refresh fails (#804)
A failed token refresh returned the stored access token even after it had expired, so the next request failed with a generic error instead of asking to sign in again. The stored token is now returned only while it is still valid.

The Epic docs now say that achievements are sent with Epic's launcher user agent, which the achievements service requires.
2026-10-14 07:43:02 -04:00
The412Banner 7ad3c60efe feat: rewrite the Epic and Amazon store modules (#802)
Both store modules are rewritten from a protocol-level spec: new Rust engines behind a small JNI surface (nativeCall/nativeInstall) and thin Kotlin facades, with the launcher's own user agent.

Epic: sign-in carries over, library, manifest download with byte-exact verification, assembly, uninstall, launch arguments, achievements.
Amazon: sign-in carries over, library, manifest download with per-file hash checks, path checks, executable detection from fuel.json, uninstall.
On-disk install layout is unchanged, so existing installs keep working.

Store game pages now take controller focus on their lead hero action (Install, Uninstall, Pause, Resume, Get, Buy), fall back to Back, and keep D-pad focus inside the page while it covers the library.
2026-10-14 07:20:06 -04:00
Kurt Himebauch a849556f3f docs: credit Phobos for Epic and GOG integration work (#801) 2026-10-14 06:41:42 -04:00
Kurt Himebauch 7ea4efef98 perf: the session's background watchers share two loops and one D-Bus service process (#575) 2026-10-14 01:02:24 -04:00
Kurt Himebauch a0d3e03ca0 perf: Steam's web helper runs its GPU, audio and zygote work inside one process (#566) 2026-10-14 01:01:49 -04:00
Kurt Himebauch 9f2e364157 Verify native SD-card library paths before binding them (#757) 2026-10-14 00:59:38 -04:00
Kurt Himebauch f9b58837d0 Fix microphone capture in DirectAudio games (#747) 2026-10-14 00:58:52 -04:00
Kurt Himebauch 7ab10aef04 Preserve native Linux and FEX compatibility choices (#749) 2026-10-14 00:57:45 -04:00
Kurt Himebauch 5d8722beca Wait for Steam shortcut overviews before cold launches (#758) 2026-10-14 00:55:04 -04:00
Kurt Himebauch c6f754c936 Run CodeRabbit reviews independently of APK builds (#777) 2026-10-14 00:29:37 -04:00
Kurt Himebauch 277cbc9c01 feat: redesign the store game page's facts, description and achievements (#775) 2026-10-13 23:33:43 -04:00
Kurt Himebauch 2ad57a0105 Configure CodeRabbit reviews (#774) 2026-10-13 22:47:19 -04:00
Juan MartíandThe412Banner 0d64ad8c23 Improve store install storage checks and recovery; adaptive parallel Epic installs (#710)
Space checks before Epic, GOG and Amazon installs (measured zero vs unknown), clear failure reasons with retry/clear, resume-aware checks, and safe per-file replacement so a failed or cancelled update never breaks an installed game.

Epic installs stream chunks straight into staged files in parallel (most bytes written once, a small budgeted cache for shared chunks, about 1x game size instead of 2x), pace updates by free space, and keep the lookahead, batch and bounded Java writers as hidden alternatives.

Co-authored-by: Juan Marti Mercado <[email protected]>
Co-authored-by: The412Banner <[email protected]>
2026-10-13 22:29:23 -04:00
34969ccf44 fix: desktop session: Shift no longer stays held after typing capitals on a physical keyboard (#766)
* fix: desktop session: Shift no longer stays held after typing capitals on a physical keyboard

Android repeats a held key as more key presses. Those were forwarded to the
session as more wl_keyboard presses, and KWin's xkb counts every press of a
modifier, so Shift held for a few capitals took several presses and one
release and stayed held: digits typed !@#, letters stayed capital. The same
applied to Ctrl and Alt.

- the compositor no longer sends a press for a key that is already down;
  releases always go through (clients repeat keys themselves from the
  repeat_info it already sends)
- the app stops forwarding Android's repeat events for keyboard keys

Reported by whatsupbros with a Keychron Q7 over USB - thank you.

* Remove stale comments from statx patch

---------

Co-authored-by: The412Banner <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-13 22:03:28 -04:00
Kurt Himebauch b616c9bef4 Fix Flatpak installs on older Android kernels (#752) 2026-10-13 21:31:26 -04:00
Kurt Himebauch aecd2ebe1d cleanup (#764) 2026-10-13 21:27:42 -04:00
The412Banner 5fd47421f9 fix: Windows components: .NET Core / Desktop runtimes install again (#740)
droiddeck-msi-install: a DuplicateFile entry onto the file itself (dotnet.exe in the .NET host MSI) is skipped like Windows Installer does, instead of stopping the whole install with SameFileError. Fixes .NET Desktop 3/6/7/8, .NET Core 3 and .NET 5. Thanks to whatsupbros for finding and tracing it.
2026-10-13 19:03:38 -04:00
Kurt Himebauch 93352da1d0 fix: pace gamescope's fixed-refresh frame limiter per window by time (#753) 2026-10-13 22:21:01 +00:00
Kirk 6e5be862cd fix: the Debugging tools hint points to Setup > Diagnostics (#725) 2026-10-13 15:01:40 -04:00
Kirk fc9d4d0646 fix: the Android keyboard opens on the first tap after the system dismissed it (#665) 2026-10-13 14:17:31 -04:00
Jagger15 6de6ec821c Add experimental Quest 2 and Adreno 6xx display-driver support (#676) 2026-10-13 14:16:20 -04:00
0x1Nawaf 249ab44533 Feat/reset all settings (#696) 2026-10-13 13:36:31 -04:00
MaxsTechReview 69e68a5008 Add translations for 23 more languages and fill missing strings (#669)
* Add translations for 23 more languages and fill missing strings

* Translate the strings added in main into every language

Cardboard VR and the session drawer's component downloads in all 31
languages, the game details facts and HowLongToBeat labels in the 23 new
ones, and the reworded Components page note there.
2026-10-13 13:34:00 -04:00
MaxsTechReviewandxXJsonDeruloXx 9a6d48c04b Add opt-in zRAM compression for the Steam client while a game runs (#442)
* Add opt-in zRAM compression for the Steam client while a game runs

A "zRAM compression" toggle under Steam > Settings > Session, below
"Keep downloads running". Off by default, kept once set, and disabled
on devices without swap or with a kernel older than 5.4.

When on, the session watches for Steam's reaper. 20 s into a game the
client and its web helpers page their private anonymous memory out to
the device's zram with MADV_PAGEOUT from inside their own process
(libblsession zram.c, SIGURG), again every 5 minutes, and read every
swapped page back once the game exits. No root is needed: only a
process itself may do this without CAP_SYS_NICE. With the toggle off
nothing is installed or started.

RedMagic NP06J, Palworld: Steam's resident memory fell from ~1.7 GB to
~1.1 GB with ~560 MB in zram, available memory during play rose by
~0.5-0.8 GB, and ~2 MB was left in zram after the read-back.

* Turn zRAM compression on by default below 12 GB of RAM

Until the toggle is changed, it follows the device: on when the kernel
supports it and the device has less than 12 GB of RAM, read from the
advertised memory size on Android 14+ and from total memory before that.
Turning it off is kept.

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-13 13:33:19 -04:00
Kurt Himebauch 7987b04529 fix: don't count a re-attached unchanged buffer as a new game frame (#731) 2026-10-13 13:24:50 -04:00
Phobos665 269ce3f422 feat(): Rich Game details for GOG & Epic - Publisher, Developer, Release Date, HLTB & Metacritic (#712) 2026-10-13 11:29:03 -04:00
Kurt Himebauch 0d099ef171 Use preview-builds for the Discord channel name (#729) 2026-10-13 11:27:39 -04:00
Kurt Himebauch ec838c2576 Align Discord build names with the app (#726) 2026-10-13 11:17:41 -04:00
Kurt Himebauch f4b0ab6867 feat: Cardboard VR mode with ARM64 SteamVR (#704)
* feat: add Cardboard mode with side-by-side lens-distorted eyes and 3DoF head tracking

* fix: derive the Cardboard field of view from the panel and recentre fully when far from upright

* feat: share the Cardboard head pose with the guest and add a SteamVR driver for it

* feat: run SteamVR's compositor on Turnip/KGSL with a display-extension layer and a driver-direct HMD

* feat: show SteamVR frames in Cardboard mode through a driver-side presenter and a fake direct-mode display

* feat: install and start SteamVR for Cardboard mode from the session drawer

* docs: describe Cardboard mode and the SteamVR path

* feat: drive SteamVR hands from the game pad and keep the Cardboard headset out of standby

* docs: record where the SteamVR dashboard stops

* fix: map Cardboard head tracking to the screen's axes and keep turns about the real vertical as yaw

* fix: give Cardboard its previous picture back when SteamVR stops

* fix: report sem_otime from emulated SysV semaphores so SteamVR's web helper can start

* fix: bridge SteamVR's shared semaphores across processes and keep Steam alive while SteamVR runs

* feat: present SteamVR straight to DroidDeck's compositor at panel resolution

* docs: update Cardboard VR notes for the dashboard, semaphore bridge and direct presenter

* fix: keep Wine and FEX signal handlers in the VR layer; add semaphore tracing

* fix: clear a stale SteamVR active marker on status

* fix: clear only an old SteamVR active marker

* fix: unblock SteamVR clients on Turnip and keep Steam's web helper alive across SteamVR runs

* fix: judge shared waits before a submit's own signals and on Vulkan 1.0 devices

* docs: record VR games, SteamVR Home and OpenXR results on the Thor

* docs: add Cardboard VR learnings from the Thor bring-up

* docs: add Cardboard VR screenshots from the Thor

* fix: keep the VR layer's display stand-in to SteamVR runs and clear a leftover marker at session start

* fix: report SteamVR start and stop failures from the helper's exit code
2026-10-13 14:36:56 +00:00
Kurt Himebauch 92d038b428 fix: anchor wp_presentation timestamps to the vsync tick (#717) 2026-10-13 13:29:32 +00:00
Kurt Himebauch ef1558527f fix: cap the session refresh rate at the device's peak refresh rate (#715) 2026-10-13 13:16:29 +00:00
Kurt Himebauch 623fb3fb62 Fix FEX dropdown scaling and Discover search focus (#681) 2026-10-13 07:01:05 -04:00
Kurt Himebauch 1c1a96cb19 Download components from the session drawer (#679) 2026-10-12 22:18:47 -04:00
Jagger15 6c35297dba Add Quest 3/3S compatibility to the standard APK (#642) 2026-10-12 17:13:07 -04:00
Kurt Himebauch 8f36eb7f54 Add opt-in GOG achievements through Comet (#651) 2026-10-12 16:40:06 -04:00
Kurt Himebauch 3ed756b067 Default Snapdragon 8 Gen 2 core allocation (#654) 2026-10-12 16:26:24 -04:00
Phobos665andxXJsonDeruloXx d9b152c7d2 feat(): Achievements UI for GOG & Epic game details page (#641)
* feat(): gog & epic achievements UI

* feat(): AI-generated translations - Please get review for these

* build_local: run on macOS hosts with Docker Desktop

Two steps of tools/build_local.sh failed on a Mac:

- The uruntime staging runs on the host, where macOS's BSD install has
  no -D, so `install -Dm644` stopped with "No such file or directory".
  The folders are now made first and the files copied with install -m644.
- Inside the cross-compile container, Docker Desktop's VirtioFS share
  now and then refuses the chmod that install -m makes (EPERM, on a
  different file each run, e.g. "setting permissions for
  .../droiddeck-esync: Operation not permitted"). The overlay and
  desktop scripts are copied with a plain write instead. Nothing is
  lost: the APK keeps no file modes, and the app sets them when it
  stages the runtime.

* feat(): adjust the store

* feat(): store-achievements adjustment for UI

* feat(): translation fixes and separate hidden achievements

* Show all achievements directly on store game details

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-12 19:49:55 +00:00
The412Banner 3b99f2d990 feat: Start without Steam, a per-game switch for games that close right away (#659) 2026-10-12 15:29:59 -04:00
The412Banner c9152103bf Merge remote-tracking branch 'org/main' into feat/start-without-steam 2026-10-12 15:16:35 -04:00
Kirk 1b4d08e448 fix: a touchpad tap holds its click long enough for games to see it (#644) 2026-10-12 19:12:20 +00:00
Gustash ba6f56d5a0 fix: an unfolded Follow screen session turns to portrait again (#626) 2026-10-12 15:07:35 -04:00
The412Banner 0cfb994924 feat: Start without Steam, a per-game switch for games that close right away
A game that closes as soon as it starts can be tripped by Proton's Steam
setup (steam.exe, the Steam registry entries, the Steam client bridge).
The Games page of the session drawer gets a per-game Start without Steam
switch, stored as DROIDDECK_NO_STEAM=1 in the game's environment and listed
in its profile summary.

droiddeck-game-env acts on it for a game launch only: a Proton with umu's
launch (GE-Proton, Proton-CachyOS, DroidDeck-Proton) gets UMU_ID, so it
runs umu.exe in place of steam.exe and still sets up the prefix; a Steam
game keeps its protonfixes under its own id, an added game gets 0. Any
other Proton runs the game straight in the prefix (runinprefix).
2026-10-12 13:55:56 -04:00
Muaz Kadan 1ceb1b87b7 fix: Shut Down in Steam's power menu ends the session, and Restart restarts the client (#629) 2026-10-12 13:44:16 -04:00
Muaz Kadan 0b133a2ed5 Fix the QAM button doing nothing when the pad is an Xbox or PlayStation controller (#631) 2026-10-12 13:35:41 -04:00
Phobos665 513ae84612 chore(): Fix the MacOS build process locally. (#637) 2026-10-12 17:11:01 +00:00
Kurt Himebauch 8c955abfa4 chore: bump version to 0.4.0 (13) (#627) 2026-10-12 10:56:11 -04:00
Kurt Himebauch e6738a699a docs: draft DroidDeck 0.4.0 release notes (#619)
* docs: draft 0.4.0 release notes

* docs: add Thor store and launcher media for 0.4.0

* docs: show Steam switching to KDE on Thor

* docs: show Castle Crashers touch layouts and controller choices

* docs: add Amazon game install view

* docs: add trimmed user recording of store downloads

* docs: smoother touch controller recording, GOG sign-in and per-game profile screenshots

* docs: clean desktop round-trip recording replaces the KDE gif and file explorer still

* docs: add multi-controller and Steam Controller media, rebuild desktop gif without glitch frames

* docs: add foldable touch gamepad screenshots from its PR

* docs: add the Discord badge to the 0.4.0 notes and tidy contributor lines

* docs: fold fixes to new 0.4.0 features into their entries, add Follow screen shots, point the changelog at the 0.4.0 tag

* docs: add the Add a game dialog to the non-Steam games entry

* docs: add a TL;DR for Mr. Sujano to the 0.4.0 notes
2026-10-12 10:49:14 -04:00
Neolyum d71178c955 Fix multiple controller discovery (#614) 2026-10-12 11:45:00 +00:00
Kurt Himebauch aa901c3902 feat: store uninstall confirms in a step-out, shows progress, and lands on Install (#609)
feat: store uninstall confirms in a step-out, shows its progress in place, and the game reads Install the moment it is gone
2026-10-12 11:03:03 +00:00
Kurt Himebauch 486032f5db fix: keep the Steam desktop client in KDE (#610) 2026-10-12 06:56:56 -04:00
Kurt Himebauch 4ec2146019 feat: store game pages flood onto their hero art, and downloads survive a restart (#608)
* feat: a store game's page floods out of its card onto its own hero art, and draws back from it

* fix: installs interrupted by the app closing show in Downloads with Resume and Clear

* feat: finished and interrupted downloads share the running card's art look, and the queue sits under what is running

* fix: the download queue outlives the app: queued, running and paused downloads come back after a restart
2026-10-12 03:58:07 +00:00
Kurt Himebauch 1603009687 fix: a step-out out of a wide control draws its box again (#607) 2026-10-12 03:31:48 +00:00
Kurt Himebauch 06269aab24 feat: components newest first, Steam settings open on Steam with offline mode, smoother chip selection (#606)
* feat: components list newest first, Steam's sheet opens on its Steam tab with offline mode, and Setup's overview drops the account group

* fix: chip selections stretch across on every move, drawn over the chips instead of behind them
2026-10-12 03:25:55 +00:00
Kurt Himebauch 9836dade79 feat: the focus ring is the only fill and keeps up with focus (#605)
* feat: with a pad the focus ring fills what it holds and is the only fill; chip rows mark their pick with a bar

* fix: the focus ring chases focus on a continuous spring per edge instead of restarting on every move
2026-10-12 03:16:07 +00:00
Kurt Himebauch 4bfc7154cd fix: store chips mark the shown store without faking focus, and step-outs open and fold cleanly (#604)
* fix: a chip selection hopping past a chip fades across instead of crawling over as a drop

* fix: the store chips' fill only shows where a pad's focus is, not on the shown store

* feat: the shown store's chip carries a bar on its foot, every chip pulses when picked, and the queue's settings live only in the cog

* fix: step-outs grow and fold in one move with no tail, keep the ring out while moving, and hand focus back to their control
2026-10-12 03:04:33 +00:00
Kurt Himebauch 674e679884 fix: settings sheet focus ring, Left to fold, chips pick on focus (#600) 2026-10-12 02:11:53 +00:00
Kurt Himebauch 5527f9b77e Fix black Steam output with FSR 1 (#597) 2026-10-11 21:03:07 -04:00
Kurt Himebauch b1690852e9 Fix Discord launcher layout and controller navigation (#596) 2026-10-11 20:08:59 -04:00
Gustash 8f5ccbf23f fix: Epic games with a JSON manifest download (#578) 2026-10-11 17:37:47 -04:00
Juan MartíandKurt Himebauch 55c51f843e feat: add persistent per-game compatibility profiles (#449)
* feat(profiles): add persistent per-game compatibility profiles

* fix(profiles): show Windows component overrides

Include per-game Windows component selections in the resolved profile summary so the session overlay marks those games as custom and shows the selected components.

* fix(session): detect Proton games across runtime paths

* fix(profiles): select the active Gamescope game

* fix(profiles): improve per-game reset feedback

* fix(profiles): align session components with game overrides

* fix(session): prefer selected Steam game during refresh

* fix(session): resume profile following when reopening games

* chore: keep local dev testing outside the repository

* test(session): remove local dev package path

* fix(session): preserve profiles when refresh fails

* fix(session): reject empty game profile reset scope

* perf(session): cache drawer inventory by refresh revision

* fix(profiles): align game presets and profile reset behavior

* fix(profiles): preserve originals and normalize shortcut ids

* fix(profiles): keep editor scope stable after rebase

* fix(tests): host Compose tests in both build variants

* fix(profiles): recover interrupted component selection saves

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-11 17:00:28 -04:00
The412Banner ef4807fbd0 DroidDeck-Proton as a Proton source, and update checks for every source (#588) 2026-10-11 16:07:44 -04:00
The412Banner 604763edc7 Merge remote-tracking branch 'org/main' into feat/droiddeck-proton-source 2026-10-11 15:56:18 -04:00
MaxsTechReview 11bd604dfd Merge branch 'main' into fix/steam-client-download 2026-10-11 15:53:38 -04:00
MaxsTechReview d207b35d94 Red Dead Redemption 2: launches again, no screensaver over games, hidden launcher held to one core, /tmp cleaned (#585)
* fix: Red Dead Redemption 2 launches again, its Vulkan runtime installer is left to run

droiddeck-seed-redists marked every process in every game's install script
as already run, Red Dead Redemption 2's VulkanRT-1.1.108.0 installer
included. Steam's compat config for the game says nativevulkanloader, so
Proton loads vulkan-1.dll native only, and Khronos' loader is in the prefix
only once that installer has run. Without it the Rockstar launcher starts
RDR2.exe and it exits with STATUS_DLL_NOT_FOUND (0xc0000135) before it
opens a window.

A Vulkan runtime installer is now left out of the seeding, and a marker an
earlier seeding wrote for one comes out of each prefix and Proton template
once (dates kept), so the client runs the installer on the next launch.
The installer finishes in a few seconds under FEX.

* fix: Steam's screensaver no longer covers a running game

The client's defaults show screensavers while a game runs, after 300 s on
AC power without a button press - and a held stick or trigger is no press.
Red Dead Redemption 2's intro runs longer than that: the slideshow covered
the game, and the button that woke it reached the game too (B asked to
quit to the desktop). Customization/Screensaver/WhileInGame is seeded "0"
in config.vdf once, like the UI scale and native resolution, so turning it
back on in Settings stays the user's choice.

On the RedMagic: 18 minutes from the last button press through the intro
and riding, focus never left the game and no screensaver was created.

* fix: Rockstar's hidden launcher no longer takes cores from its game

The Rockstar Games Launcher keeps drawing its Chromium interface
(SocialClubHelper.exe: renderers, and a GPU process compositing through
DXVK) for as long as the game runs, hidden behind it - and Wine passes
Chromium's priorities on, so those threads ran above the game's (the GPU
process at nice -10), across every core. With Red Dead Redemption 2 in
front that was 1.3 to 2.5 cores under FEX for a page nobody sees.

droiddeck-launcher-cores follows gamescope's focus from the agent's
focus.json: while a game window has it, every SocialClubHelper thread runs
on the last core of the slowest cluster at nice 19; when the launcher's own
window has focus again each thread gets its own mask and priority back
(also on exit). Stopping the helpers would not hold - proot resumes a
stopped tracee. Started for Steam sessions; BL_LAUNCHER_CORES=0 turns it off.

RedMagic, RDR2 all Low, riding at the thermal limit, same spot and session:
released 46.5 fps, 1% low 27.5, p99 31.8 ms, CPU 89%, 2200 MHz;
held 48.7 fps, 1% low 32.6, p99 27.6 ms, CPU 79%, 2638 MHz.

* fix: sessions clean up what earlier ones left in /tmp

The guest's /tmp is a directory in the rootfs that no boot ever empties,
and Android ends sessions by killing them. Every session left a
/tmp/mangohud.XXXXXXXX (mktemp, never removed), and the Steam client, its
web helper, Chromium apps, dbus-run-session, AppImage runs and KDE their
own temporary files and sockets: 1,422 entries on the RedMagic.

mangoapp's config is now one fixed file rewritten each session, and the
session's first pass sweeps each kind of leftover while nothing that could
own one is running, with a single find. On the device that cleared 1,391
entries over two sessions, leaving 38.
2026-10-11 15:53:01 -04:00
MaxsTechReview b834a92244 Merge branch 'main' into fix/steam-client-download 2026-10-11 15:51:21 -04:00
The412Banner 4a373807a4 feat: DroidDeck-Proton as a Proton source, and update checks for every source
Setup > Proton versions lists DroidDeck-Proton (The412Banner/DroidDeck-Proton) beside
GE-Proton and proton-cachyos: same aarch64 tarball pattern as GE, verified against its
sha512 file, installed by droiddeck-proton-extra (also as "droiddeck" in the session queue)
and adopted by steam-compatibility like any other extra, so it shows as
"DroidDeck-Proton <ver> (Compatible)". Its folder has a space; nothing in the install,
adopt, esync or components paths splits on it.

Update checks: each source's newest full release is looked up at app start and when the
page opens, at most every 6 h (offline keeps the last answer). A newer release shows
"Update available -> <tag>" with Update; it installs beside the old build, which stays
until removed ("Remove old ... versions"), since games are mapped to it by name.
Installed builds are ordered by their version numbers (11-9 before 11-10), not by name,
and the tag each was installed from is recorded. Prereleases and drafts are skipped.
2026-10-11 15:32:07 -04:00
Kurt Himebauch fc026263e4 feat: big ass redesign: Carry the stores' motion and layout into Steam settings, Components, Setup, Flathub and Desktop (#548) 2026-10-11 19:05:21 +00:00
maxjivi05 0cd7f07725 fix: a first run's Steam client download is not paused out of sight
With the default background behavior (Manual), a session pauses as soon
as DroidDeck is hidden or the screen turns off - one second after start in
the report this came from, before the download had begun. "Keep downloads
running" did not help: it is off by default, and it only recognizes the
client's own game downloads, not the runtime fetching the client itself.

Until the session shows its first frame, the download watch now also
asks whether the client is still to be fetched (no install stamp or no
client binary, or a repair asked for). While it is, the session is kept
awake with its wake and Wi-Fi locks whatever the downloads setting says,
the notification reads "Steam download active" with Suspend now, and a
steam.client_download event marks start and end. The watch is set up
before the first suspend decision, so a session started with the screen
off is not paused first. Once the client is there, the chosen behavior
applies again.
2026-10-11 14:45:57 -04:00
maxjivi05 342d2e7f20 fix: the Steam client download resumes, retries and shows megabytes
A first run downloads the native client, about 360 MB in 17 parts, before
anything is on screen. The script ran plain `curl -fsSL` per part: no
timeout, no retry, no progress. A stalled connection could wait for ever
on "downloading Steam: bins_linuxarm64_linuxarm64 (2/17)" (a 110 MB part),
a failed one ended the session and threw away everything fetched, and
the loading screen said "~1-2 min" while counting parts, not bytes.

Parts now go to package/droiddeck-download and stay there until the client
is installed. Each one resumes from the bytes on disk (curl -C -), a
transfer under 4 KB/s for a minute is dropped and resumed, attempts
without progress back off 5-30 s before giving up, and a finished part is
marked unpacked so a later session skips it. Bytes that fail their
checksum are fetched whole again; a server that will not resume starts the
part over; parts left for another client version are dropped. A repair
redoes every part once and keeps what an interrupted repair redid.

Every 5 s the step line carries megabytes overall ("(2/17), 45 of 360
MB"), which the loading screen shows as "45 / 360 MB" with the bar
following the bytes. The session's step no longer promises 1-2 minutes.
2026-10-11 14:45:57 -04:00
Kurt Himebauch e5ba165027 feat: the built-in pad can be a DualShock 4 or DualSense, its touchpad fed by the trackpads (#559) 2026-10-11 04:21:31 +00:00
Kurt Himebauch 95f92b157b feat: the built-in pad's controller type switches from the session menu while Steam runs (#558) 2026-10-11 00:12:53 -04:00
Kurt Himebauch 8d34b37b7d feat: the built-in pad can be a Steam Controller to Steam (#545) 2026-10-11 03:24:22 +00:00
Kurt Himebauch d7f89fc32b fix: the Steam Controller's hidraw node reaches the input shim again (#557) 2026-10-10 23:20:09 -04:00
Kurt Himebauch b47302eb8c refactor: Steam always runs in Deck mode on the Deck beta branch (#546) 2026-10-10 22:15:42 -04:00
The412Banner a2e596a200 Windows components: picked automatically by each game's source (#554)
Each game's Windows components now come from the list its own source
trusts, and the ones that list names turn on by themselves. The user's own
on/off switches always win; each automatic row says why it is on.

- Steam games: the Steamworks Shared redistributables Steam installs with
  the game (appmanifest SharedDepots, else the client's appinfo.vdf).
  Steam keeps its install scripts marked as already run.
- GOG: the build manifest's dependencies. Epic: the manifest's
  prerequisite (Unreal's maps to VC++ and DirectX). Amazon: fuel.json
  PostInstall. Older GOG/Epic installs get their list fetched once with the
  saved sign-in; game files are never downloaded again.
- Custom games (the + button, Add all, migrated folders): one shared Steam
  match per game, also used for artwork. An appid from the game's files
  turns components on; a name-only match stays Recommended; no match falls
  back to the folder scan.
- Steam app info for apps the client never saw comes from an anonymous
  Steam PICS lookup (CM websocket, anonymous logon, product info), cached
  per appid; the user's Steam sign-in is never used.
- Missing components download in the background on one low-priority
  worker that pauses during sessions and waits on battery saver, heat or a
  metered network unless the user asked. A game Steam installs during a
  session is readied right away so it can be played without ending the
  session; the launch waits up to 10 s only for a component still on its
  way.
- Page: "On for this game" with reasons, a quiet "Checking what this game
  needs..." only for games never worked out, rows that ease in, pieces that
  need a download greyed out while offline. The intro names no other app.
- The folder scan skips Unity's *_Data/Managed (Mono, not .NET Framework).
2026-10-10 21:15:56 -04:00
MaxsTechReview d45933e559 FEX: preload the fault report only where x86 loaders look (#551)
Preload the FEX fault report only where x86 loaders look

droiddeck-fex put the x86 libfaultreport.so in LD_PRELOAD, which FEX's own
arm64 loader reads too - and FEXServer's, and every child FEX's - so each
x86 start printed "ERROR: ld.so: object '.../libfaultreport.so' from
LD_PRELOAD cannot be preloaded", and an i386 child added "wrong ELF class".
It is now listed in the x86 runtime's own /etc/ld.so.preload, which only
the loaders inside FEX read, as /usr/$LIB/droiddeck/libfaultreport.so.

faultreport.c builds for i386 too, so 32-bit programs get the report as
well instead of an error.
2026-10-10 19:12:51 -04:00
MaxsTechReview 2a06204eb4 Desktop: run programs, scripts and AppImages; Switch to Desktop without Deck mode (#549)
* Run programs, scripts and AppImages from the desktop

Windows programs open with the Proton Steam has chosen, and a Steam game's own
launch program is handed to Steam. x86-64 Linux programs and game scripts run
under FEX, and an exec the kernel refuses goes to droiddeck-open through
libblsession's binfmt.c. Programs on noexec shared storage pass KIO's checks; a
folder of Linux programs there, and sharun AppImages, are added by the app once
and run from the copy. Executable scripts run without KIO's prompt, mpv plays
on the software desktop, FEXServer's socket moves under XDG_RUNTIME_DIR, and
Proton runtimes no longer show up as games.

* Switch to Desktop from Big Picture without Deck mode

Big Picture's Switch to Desktop asks the client's SteamOS manager, which the
client only has when started as SteamOS (Deck mode). Without it the request
failed and the screen stayed on "Switching to Desktop..." for good. The live
compat helper now sees the request on its way to the client and hands it to
steamos-session-select, which opens the KDE desktop as Deck mode already did.

* Run x86 programs from shared storage where they lie

FEX asks for the guest's PROT_EXEC when it maps a program or library, which
the kernel refuses on noexec shared storage; FEX never runs those pages
itself, so libblsession maps them without it in FEX's processes. An x86
game, its libraries and its start script now run in place instead of being
copied in; only native code on noexec storage is still copied.

A Steam library's games are never copied, and a Linux game's launch program
opened from the desktop goes to Steam like a Windows one.
2026-10-10 19:12:20 -04:00
MaxsTechReview 15b2d19f68 Performance: idle front end, proot stops, launch-time passes and log collection (#529)
* Front end: draw the capsule wall without clipping it every frame

The tilted wall clipped each capsule to a rounded rect and recomposed its columns on every drift
step, and a rotated rounded clip is rasterised in software on the render threads: the idle main
page held about 1.7 cores. The art is now loaded at the capsule's size with its corners already
rounded, each column draws its capsules itself in one layer moved by the drift, and the drift
advances at most every 15 ms. The idle page takes about a third of a core.

* libfakeinput: let calls on descriptors it does not serve skip its lock

Every read, write, ioctl, close and poll of every process in a Proton session went through the
controller lock and the three map lookups, Wine's wineserver pipes and the game's files alike.
A bitmap of the descriptors the maps hold, set before one is handed out and cleared when it is
closed, lets everything else go straight to the kernel.

* Pad motion: ask the display for its rotation four times a second

The rotation was looked up for every sensor reading, every 2 ms, and cost more than the rest of
the reading; it is now refreshed every 250 ms, and the readings no longer allocate pairs.

* Session: keep the library passes out of a game's launch

The redist seeder stamped each prefix with the checksum of the whole set of names, which differs
between the session's pass and the game launch's (the game's own names are added), so every
launch re-appended the markers to every prefix in the library while the game loaded. Each
prefix's marker now lists the names it was given and only the missing ones are added.

The beat's seed and compatibility passes, and the Lossless Scaling ownership probe (which loads
steamclient.so), also wait while a wineserver runs: a launch rewrites the manifest and made them
due at exactly that moment. The game's own launch seeds its prefix regardless.

* Agent: keep the focus watcher's X connection open while nothing changes

The connect timeout stayed on the socket, so with no property change the read timed out, and the
watcher dropped the connection, reconnected and took a fresh snapshot every few seconds.

* Game environment: start Cyberpunk 2077 without REDlauncher

The prelauncher opens REDlauncher, a Qt WebEngine window that hands over to the game half a
minute later and then stays up beside it, well over a core busy for as long as the game runs; its
own --launcher-skip started the game twice. The game's executable is started directly instead.

* netif: answer Wine's network driver from a reading of the link

Mortal Kombat X has Wine's network driver enumerate every interface, address and route table
hundreds of times a second. Each enumeration read /etc/droiddeck-net several times, asked the
kernel for its interface dump again (a netlink socket bound under proot, refused each time) and
looked the real functions up with dlsym. The link file is now read at most once a second, a
refused dump is not asked for again within that second, and the real functions are looked up
once.

* Fast path: create memfds without a stop in proot

proot looks at a memfd's name only to refuse the few that are then executed or locked; every other
memfd_create is now issued from the trampoline. The session's /proc/net and status copies made
one per read, a stop each. Without memfd in the kernel the call still goes to proot's ashmem.

* Logs: run each redaction rule only on a line that could match it

A session's end copies some 70 MB of the Steam client's logs and puts every line through all
twenty-odd rules, most of a minute of a core on the phone after every session. Each rule now runs
only on a line holding what it cannot match without: its keyword, the five separators of a MAC,
the four dashes of a GUID, a long enough hex or token run, the learned account or address text.
Lines with non-ASCII text still meet every case-insensitive rule. Over 895,000 lines of real
client logs, with the device's accounts and addresses learned, the output is identical and the
pass takes a twentieth of the time.
2026-10-10 19:00:33 -04:00
SG3xHERO f21f20a578 Add Steam Controller support over Bluetooth LE (#404) 2026-10-10 20:53:07 +00:00
The412Banner cf59d39599 Read the package catalogs from Droid-Deck/DroidDeck-Components (#544)
The Windows components catalog, the desktop and emulator packages, the Linux runtime and the
Steam seed now live on the organisation's components repository, mirrored and built there by
CI; the app reads the catalogs from its main and the files from its releases. The personal
winlator-contents mirror keeps serving older app versions and Bannerlator.
2026-10-10 16:52:33 -04:00
The412Banner 3589ad9127 Games: old Games folders become entries, not Steam libraries (#542)
Games folders saved before the settings section went away were still
registered as Steam libraries on Automatic, with no way left to remove
them. Once, at app start and before any scan, each game such a folder
listed becomes an entry of its own, keeping everything keyed by its folder
(chosen exe, shortcut appid, name, Start in, launch options, art,
removal); store installs and steamapps are skipped. The folders stay bound
at their old guest paths, so no shortcut changes, and the saved roots are
cleared. A Games folder stays a library only when Steam installed a game
into it; the others leave the library list, and the runtime forgets them.
2026-10-10 16:19:17 -04:00
David Kubeša 49f6209c3a Add global manual landscape orientation setting (#441) 2026-10-10 16:07:12 -04:00
Kurt Himebauch ff9792f885 Merge rules (#540) 2026-10-10 19:13:01 +00:00
Ashish Ajin b6428901b1 Hide the touchpad cursor when switching to direct touch (#424) 2026-10-10 15:02:38 -04:00
The412Banner 3ea35abe83 Windows components: a recommended component is listed once, not again under All (#537)
The full list left out only the 'installed for this game' rows, never the recommendations, so
every recommended component appeared twice (DirectX 9 on Final Fantasy Resonance, both rows
switched on).
2026-10-10 14:54:28 -04:00
Muaz Kadan 6c5b9438bb Read the phone's motion sensors only while Steam asks for them (#534)
perf: only read the phone's motion sensors when Steam wants them

The gyro and accelerometer ran at 4 ms for the whole session, even in games without gyro, costing about 6% of a core. Steam already tells the Deck pad when it wants motion (setting 48), so libfakeinput now passes that on and PadMotion follows it, with a short delay to ride out Steam's brief toggles.
2026-10-10 14:44:05 -04:00
2c9267b0ef fix: preserve keyboard input on composite devices (#435)
* fix: preserve keyboard input on composite devices

* Fix composite input merge noteDevice call

---------

Co-authored-by: Jammo <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 14:40:53 -04:00
Muaz Kadan e845f91177 Fix CPU temperature showing 0°C in the performance overlay (#516)
Since the proot speed-up in #494, the overlay couldn't find the CPU sensor and showed 0°C. It shows the real temperature again.
2026-10-10 14:39:30 -04:00
ArthurandJames Yeung 285e271f3e Fix NativeAOT GC region range defaults (#385)
Use the bare hex value accepted by older NativeAOT runtimes and CoreCLR. Cover the default and shared and per-game overrides.

Co-authored-by: James Yeung <[email protected]>
2026-10-10 14:38:47 -04:00
0x142857andxXJsonDeruloXx 20f919d708 Controller: optional Select button that opens the Steam menu (#384)
* Controller: optional Select button that opens the Steam menu

* Controller: translate the Select-opens-Steam-menu option

* Controller: clearer wording for the Select-opens-Steam-menu option

* docs: evidence for the Select-opens-Steam-menu option

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 14:38:07 -04:00
abduznik a4ea72cb90 Terminal: release createSubprocess's JNI local references, and the cwd chars against cwd (#527)
Fix JNI local reference leak in createSubprocess

The argv and envp loops took one local reference per element from
GetObjectArrayElement and never released it, so the references piled up
until the native call returned. ART aborts the process at 512 local
references ("local reference table overflow"), and both loops share the
table, so a long argument list plus a large environment could hit it.

Delete each element's local reference once its chars are copied and
released, after ReleaseStringUTFChars since the chars depend on the string.

Also release the cwd chars against cwd rather than cmd; the call passed
the wrong jstring.

Adds a host-side test that runs termux.c against a mock JNIEnv that counts
local references and checks release pairing. It needs a jni.h (JNI_INCLUDE,
JAVA_HOME or the NDK) and skips without one.

Fixes #507
2026-10-10 14:29:51 -04:00
Nicolás Gómez Solano 2c404ead73 fix(audio): avoid InputStream.readAllBytes() for Android 12 compatibility (#531) 2026-10-10 14:29:46 -04:00
The412Banner 932de3fd72 Windows components: leave out WebView2, which only Microsoft's installer can lay out (#532)
Its files sit in a container no extractor opens and may not be hosted, so a recording cannot
source them; installing it means running the installer in the prefix, which no runtime here
does. With it gone the waiting section is empty.
2026-10-10 14:03:04 -04:00
MaxsTechReviewandxXJsonDeruloXx eb94916eb1 Launch the executable a Store package declares (#400)
Store and GDK folders name their game in MicrosoftGame.config or
appxmanifest.xml. Picking the top-level Unreal bootstrap instead failed
with "CreateProcess() returned 2" when the Store had renamed the
shipping exe.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-10 13:46:03 -04:00
kmnkv1 e8821f4efc Rumble: don't restart the vibrator on every force-feedback packet (#514)
fix: stop restarting the vibrator on every force-feedback packet
2026-10-10 13:34:19 -04:00
kaosjammoandJammo 7570ce7e18 Fix launcher fullscreen around display cutouts (#432)
Co-authored-by: Jammo <[email protected]>
2026-10-10 13:33:59 -04:00
The412Banner e25fa5429b Windows components: leave out the OpenAL setup program, a twin of the DLL copy that installs here (#528) 2026-10-10 13:32:12 -04:00
SG3xHERO 6de130a428 Unfolded deck controls (#407) 2026-10-10 17:24:23 +00:00
Willie Yao 1959b1a875 Controller editor: snap controls into line while dragging (#490) 2026-10-10 15:11:50 +00:00
kmnkv1 c1433ac0b3 Odin 3: skip the low-latency Wi-Fi lock, whose WLAN firmware hang reboots the device (#512) 2026-10-10 11:06:29 -04:00
Kurt Himebauch 67a9ea2f42 feat(desktop): add desktop uninstall and legacy LXQt cleanup (#493) 2026-10-10 10:59:32 -04:00
Kurt Himebauch 79d92111e4 fix(amazon): sync library with nile's headers and registered serial (#500) 2026-10-10 10:58:18 -04:00
The412Banner 8bcd9701b1 Windows components: Access 2000 runtime installs here (an MSI inside a 7-Zip-openable wrapper) (#505)
art2kmin.exe is Setup.exe + Data1.msi in a wrapper 7-Zip opens, so the MSI engine lays it out
like any other package; it was on the setup-program list by assumption.
2026-10-10 01:31:23 -04:00
The412Banner e01a7c9e17 Build: verify the runtime inputs only after the session scripts are staged (#504)
With parallel task execution, verifyRuntimeInputs and stageSessionScripts could run at the
same time; main's build at 3d50e6b1 hashed droiddeck-netmanager mid-copy and failed with
'Missing or changed runtime input'. The check now runs after the staging tasks.
2026-10-10 00:38:23 -04:00
The412Banner d639668e6b Windows components: hide Vulkan RT and the IE8 update; honest captions for the waiting section (#502)
* Windows components: hide Vulkan RT, whose loader every Proton here ships

vulkan-1.dll is in lib/wine/x86_64-windows and i386-windows of Proton Experimental (ARM64),
GE-Proton and proton-cachyos alike, so the catalog's VulkanRT entry would only shadow it. It
joins the list the page leaves out.

* Windows components: the waiting section says what is really missing (a recording), not 'coming next'

* Windows components: leave out the IE8 security update, which patches a browser no prefix has

* Windows components: the waiting caption without an apostrophe the resource compiler rejects
2026-10-10 00:24:14 -04:00
The412Banner 9c052be865 CI: re-pin the Debian image and stop the publish workflow replacing it (#501)
The publish workflow's push trigger fired again on the branch's rebase and overwrote the
release asset with a freshly compressed copy, so main's first build failed the checksum and
fell back to the registry pull. The env file now pins the asset as published; the workflow is
dispatch-only, defaults to a new tag, and refuses to replace an existing asset unless asked.
2026-10-09 23:59:58 -04:00
The412Banner 2755e027da CI: host the Debian image the preloads build in on a release of this repo (#497)
* CI: host the Debian image the preloads build in on a release of this repo

The preloads step pulled debian:bullseye from a public mirror on every build whose preload
cache missed; the mirrors rate-limit the shared runner addresses and failed two builds in an
hour tonight. Debian 11 is frozen upstream, so the image is published once (workflow
ci-debian-image.yml, release ci-debian-bullseye-r1, pinned in tools/linuxfs/debian-image.env)
and the build downloads that file; only if that fails does it pull, with retries.

* CI: pin the published Debian image (ci-debian-bullseye-r1, 34 MB)

* CI: a new Debian image rebuilds the preloads (env file joins the cache key)
2026-10-09 23:21:11 -04:00
The412Banner 32b43694f4 Windows components: hide the CJK fonts and PowerShell, which every Proton here ships (#496)
Proton Experimental (ARM64), GE-Proton and proton-cachyos all carry Malgun, MS Gothic, YaHei
and SimSun in share/fonts and Wine's PowerShell wrapper, so the catalog's cjkfonts and
powershell entries would only shadow what is already there. They join gecko and mono in the
list the page leaves out.
2026-10-09 23:13:53 -04:00
The412Banner d15ffa3df2 Windows components: install from a recording of the installer's result (#495)
* Windows components: install from a recording of the installer's result

A catalog entry may name a 'snapshot': what its own setup program leaves in a prefix,
recorded once in Wine by winlator-contents' tools/snapshot/record.py (the files it places and
the registry values it writes, the self-registering DLLs' CLSID entries included). With one,
the setup program never runs here: the files come from the archive beside the recording, or
out of the vendor's installer by the paths the recording maps (small generated ones inline),
and the registry values go to registry.json for droiddeck-wincomponents to write at launch.
The steps a recording makes redundant are skipped; DLL overrides still apply. A component
with a recording counts as installable; its prerequisites must still be.

* droiddeck-msi-install --unpack: open archives nested inside an installer

QuickTime's cabinets and an NSIS pack's payloads sit inside the unpacked installer; they are
opened in place into '<name>.nested' folders, two levels deep, the same layout the component
recordings map their file sources to, so a recording's paths resolve on the device (QuickTime:
593 of 593).
2026-10-09 22:56:18 -04:00
Kurt Himebauchandmaxjivi05 0da45e53cb Steam's touch controller as on-screen controls (beta) (#431)
* feat: Steam's touch controller as on-screen controls (beta)

* fix: Steam touch controls appear once the session makes the device; tidier default layout

* fix: Steam touch layout decoding, saved layouts and new sessions

* docs: Steam's touch controls

* feat: Steam's touch icons, labels and colours on the touch controls; pick and save them like Steam

* feat: Steam touch icon picker as in-session panels; layout colour; docs

* Give each physical controller its own player slot

Every controller fed the one exported pad, so two pads played as one.
The first controller keeps slot 0 (the Deck in a Steam session); further
ones get slots 1-3 as separate Xbox 360 pads, created on first input and
removed on disconnect. In Deck mode only the Steam client sees those
pads and hands them to games through Steam Input. Rumble follows the
slot it names.

* feat: Steam Link's touch menu, per-game input modes, action set editing, add/copy/paste, oval controls, paste control

* feat: Steam touch controls drawn as Steam Link draws them; Steam Link's touch menu and layout tray

* feat: Steam touch controls in DroidDeck's style, D-mark guide button, shake to hide

* refactor: Steam touch controls become an On-screen controls choice instead of a second dropdown

---------

Co-authored-by: maxjivi05 <[email protected]>
2026-10-09 21:12:46 -04:00
MaxsTechReview a6d15bcd0b Expanded Fastpaths for proot and increased performance up to 57% (#494)
* perf: fast path resolves links, /proc and glue as proot does, and answers uname, isatty and passwd

The fast path handed back to proot whatever crossed a symlink, /proc or a directory proot
only shows as glue, which is most of what Steam and Wine ask. It now walks a path the way
proot's canonicalize() does, so those calls stay in-process:

- links are followed in guest terms and their text detranslated as detranslate_path() does,
  including readlink into a buffer the kernel cut short and /proc's exe, cwd and fd links;
- a binding proot dropped at start is dropped here too, glue above a binding is ENOENT, and a
  last component proot cannot stat is left to the kernel;
- pipe and socket dirfds are ENOTDIR, rmdir("dir/") removes dir, and "." and ".." go to proot;
- a blocking accept() with a connection already waiting no longer goes to proot.

Also answered: uname() and gethostname() (proot's utsname never changes), isatty() (glibc
2.43 asks with TCGETS2, which proot rewrites) and getpwnam/getpwuid when nsswitch.conf
sends passwd to files first and the entry is there (Xwayland asks for every client).

equiv.py, equiv2.py and equiv3.py match line for line with and without the fast path
(222, 507 and 924 lines). Per call on the RedMagic: realpath 240 us -> 1.0 us,
realpath /proc/self/fd/N 408 -> 9.9, getpwuid 41.5 -> 4.6, uname 28 -> 0.006.

* perf: LD_AUDIT module skips library lookups proot would fail

ld.so looks each library up along the game's search path, and Proton puts the game's
directory, Steam's runtime and video directories and its own library directories ahead of
/usr/lib: 72% of ld.so's opens in an Among Us launch failed, each a round trip to proot.

libblaudit.so answers la_objsearch() for a candidate proot would certainly fail with ENOENT
(its directory walked as canonicalize() walks it, the name or a directory on the way missing).
It has no libc and maps the fast path's trampoline itself. Each lookup's first candidate is
still opened, since a skipped one fails with ld.so's errno as it stands, and the default
directories are never skipped, so dlerror() reads the same. droiddeck-proton and the extra
wrapper add it to LD_AUDIT when the fast path is on.

* perf: droiddeck-proton checks a tool's components with one sha256sum

Each program the launcher starts pays a dozen traced library lookups under the game's search
path before it does anything. One sha256sum over every listed file replaces one per file;
the check itself is unchanged.

* perf: build a locale archive for C.UTF-8

Every program loads the locale LANG names before main(). glibc ships C.UTF-8 as a directory
it looks for once per category under two spellings, before reading gconv's module lists:
about 30 opens, each a stop in proot, in every process of the session. droiddeck-session
builds /usr/lib/locale/locale-archive (and gconv's cache) once per glibc package, so that is
one open.

localedef names a finished archive by hard link, which Android refuses; with
DROIDDECK_LINK_RENAME=1 libblsession turns a refused link into a rename that will not replace
an existing name.

* perf: library beat starts its helpers only when what they read changed

Every 15 s the session ran droiddeck-seed-redists (some forty programs under proot) and, when
a library directory had changed, steam-compatibility: a burst of thousands of proot stops each
quarter minute, game loads included. The seeder now runs when a library changed or a prefix
(or Proton's template for them) is unmarked, the compat pass when a manifest, depot, tool or
config.vdf changed, both checked from bash without starting anything, and each every five
minutes regardless. The seeder reads its stamp and the time without a subshell each.

* perf: fakeinput opens pad.log on its first line

Every process the library is loaded in opened the session's pad.log at start, a stop in proot
on shared storage, and most never write to it. The file is now opened when there is a line to
write.

* docs: the fast path as it ships, the audit module and measured launch times

* fix: a game the app launches after an idle spell comes to the front

The Steam client brings a game it launched forward only while it counts someone as present,
which it reads from gamescope's input counter. A game the app launched after four or more
minutes without a touch started, mapped its window, and stayed behind the library page until
the next input (device: Palworld, Stardew Valley, Fallout: New Vegas, Mortal Kombat X; the same
on main). The session now watches a requested launch, and when the game's window is focusable
but the interface keeps focus for two polls, counts one input on gamescope's counter, as a key
would. Nothing changes for a launch the client already brings forward.
2026-10-09 20:55:18 -04:00
The412Banner d250b43eb9 Games: revamped add games feature (#492)
Adding and managing your own (non-Steam) games moves out of the Steam
settings into the Games tab, and works like Steam's own non-Steam
Properties.

- A + beside "Games N" opens a compact in-app picker: the storage first,
  then that storage's root, every folder, .exe files only. Picking an exe
  adds the game with its name, art, Custom badge and Steam shortcut.
- "Add all games in this folder" adds every game folder inside at once.
  A summary opens immediately and fills in as each game is found, with a
  "?" where the exe was a close call and a red X to remove a game.
- An edit button on each Custom game opens its editor: Name, Target
  (ranked exes, Other exe…), Start in, Launch options, the four pieces of
  art with thumbnails, and Remove. Edits go through the same Steam
  shortcut, so the appid never changes and nothing duplicates.
- Art chooser per piece: images in the game folder, Steam's official art,
  SteamGridDB alternatives, your own image, or back to automatic.
- Finding the exe: up to five folders deep, every exe scored (name match,
  Unreal -Shipping builds, bin folders and size up; launchers, crash
  reporters, installers, redistributables and depth down).
- Real titles from the game's files (Steam manifest, GOG info, the exe's
  version info), keeping the folder's spacing when both spell the same
  title, underscores read as spaces.
- Art: the exact Steam appid when the game names one, strict name
  matches otherwise, Steam's hashed asset paths for newer games,
  SteamGridDB as a fallback (built-in key from the SGDB_API_KEY secret,
  optional own key in Setup, sealed with the Keystore), and a cover made
  from the exe's icon when nothing else has one.
- Removing a game is instant and keeps its files; adding it again brings
  it back. Steam's shortcut list is rewritten once, in the background.
- The Games section is gone from Steam settings; folders added before
  keep listing their games. Artwork switch and SteamGridDB key are in
  Setup.
2026-10-09 19:50:03 -04:00
Kurt Himebauch d99ed8e00b fix(desktop): address KDE feedback and keep local builds in sync with CI (#488) 2026-10-09 21:35:37 +00:00
Kurt Himebauch c761b83112 feat(stores): bring the launcher's motion to the stores pages (#482)
* refactor(ui): share End session's step-out as StepOut

* feat(ui): let PageFlood carry a tile's art

* feat(stores): bring the launcher's motion to the stores pages

* fix(stores): lay out the pane's rows in a column

* fix(stores): a game page no longer leaves the pad's focus on a dead card and crashes the next press

* perf(stores): GOG remembers its non-games and the library cache loads off the main thread

* fix(ui): a step-out's backdrop never takes the pad's focus

* feat(stores): modes step out of the store chip, each store in its own colour, search in the header

* feat(stores): sign-in pages in the Steam tab's style and an are-you-sure for sign out

* style(stores): drop the sign-in page's store eyebrow, which the title already names

* style(stores): the sign-in page is the store's name and Sign in

* fix(ui): the focus ring follows a control until it stops and meets one still rising into place

* feat(stores): the store a pad's focus is on shows without A, and a press never lands on where it sent focus

* perf(stores): keep chip motion responsive while libraries change
2026-10-09 14:55:03 -04:00
The412Banner 5af14829b0 Stores: seal the GOG, Epic and Amazon sign-in files with an Android Keystore key (#486)
The stores' sign-ins (files/stores/<store>/credentials.json: access and refresh
tokens, account id and name) were plain JSON. Other apps could not read them, but
every program in the Linux session runs as the app and could. They are now sealed
with AES-256-GCM under a non-exportable AndroidKeyStore key (StrongBox when the
device has one), so the session only ever sees ciphertext.

- Format: a versioned envelope {v, alg, iv, ct}, written atomically and readable
  only by the app. No new dependencies (javax.crypto and KeyStore).
- Migration: an existing plaintext file is sealed at the next start, checked by
  decrypting it, then the plaintext is removed; users stay signed in.
- No Keystore on the device: the file stays plain and the log says so once per
  start.
- A sealed file that can never be opened (tampered, key gone or invalidated,
  broken envelope) is removed and that store asks for sign-in again. A transient
  Keystore error keeps the file and retries.
2026-10-09 14:42:23 -04:00
The412Banner 71e2ae8683 Logs: every log in app-private storage, shared as one scrubbed zip (#484)
Session folders, tool and installer logs and the stores' log now all live under the
app's private files/logs instead of Download/DroidDeck, and every existing "Share
logs" action bundles them into one zip that goes through a final scrub.

- Where: LinuxRuntime.logDir() = files/logs; sessions keep their dated folders and
  30-session pruning, tools/ and stores/ beside them. The "Logs" setting keeps its
  meaning. Game save backups stay in Download/DroidDeck/Saves.
- Migration: at first start the old session folders, tools/ and loose logs move from
  Download/DroidDeck into files/logs (Saves untouched), and every moved folder is
  scrubbed under the current rules.
- Scrubbing: one rule set (SecretScrub) for the stores' log and the zip. The scrub
  marker carries the rules version, so a rules change re-scrubs every folder at the
  next start (in the background, a few at a time). Steam login account names and
  emails, account-name fields, OAuth codes, tokens in URLs and private LAN
  addresses are removed; Windows exception codes and server addresses are kept.
- Share: one zip of the current session folder plus the last 7 days of stores/ and
  tools/, written to cache/share and handed to the share sheet.
2026-10-09 13:28:03 -04:00
The412Banner 06e108a726 Windows components: lay out installer .exe wrappers without running them (#480)
* Windows components: lay out installer .exe wrappers without running them

The components page could only take .msi packages apart; most catalog entries ship an
installer .exe, so VC++ runtimes, .NET Framework 4.0-4.8 and the .NET Core/5+ runtimes sat
under 'Need a Windows installer'. droiddeck-msi-install now opens those wrappers and lays
out the packages inside the same way:

- WiX bundles (Visual C++ 2012-2022, .NET Core/5+ desktop runtimes): the .wixburn section
  names the containers, each a cabinet found by its header (signing moves them); payloads
  get the names the bundle manifest lists, so each package finds its cab1.cab beside it.
  ARM64 packages are skipped, x86 and x64 both laid out.
- Self-extracting cabinets (Visual C++ 2005-2010): cabextract, external cabinet beside the
  package (vc_red.cab).
- Self-extracting 7-Zip archives (.NET Framework 4.0-4.8): 7-Zip, now bundled with the
  msitools (Arch Linux ARM 7zip 26.04: 7z + its 7z.so plugin, licences beside the others).
  The complete product (netfx_Full_*) wins over the 4.0 parts and the per-Windows copies.
- An .exe that is its own setup program (NSIS, InnoSetup) reports that it holds no package.
- --unpack opens an archive for the app: DirectX's redistributable for XACT's cabinet picks.

The app executes the remaining catalog steps offline: set_windows (HKCU\Software\Wine
Version), set_register_key, get_from_cab (through --unpack) and 'uninstall Wine Mono' (the
mscoree override beside it is what matters), and reads url/file_name the catalog nests under
'environment' on a few .NET entries. Setup-program installers stay in the waiting section
with a note that says what they are.

Verified on this branch's engine with msitools, cabextract and 7z: VC++ 2010 x64 (19 files,
61 values), VC++ 2022 x64 bundle (26 files, 66 values), .NET 4.0 (1053 files, 6056 values,
Framework64\v4.0.30319 with clr.dll and mscorlib.dll, NDP\v4 keys) and .NET 4.8 (1633
files, 522 GAC assemblies, 6495 values). Not yet run on a device.

* Windows components: the pressed row greys out and shows its own two-step progress bar

Pressing a switch now greys that row at once and runs a thin bar across it: the download
0..100, then the install 0..100, with a caption naming the file or the stage (a dependency's
name in front while that installs first). Turning a component off greys the row the same way
until the pick is saved. The page-wide bar at the bottom is gone; Done waits on the busy row.

droiddeck-msi-install reports how far along it is ('progress 37% placing files'): reading,
each cabinet, every 5% of files placed, the registry, done - and a wrapper maps each package
into its share, so a bundle's two packages run 10..55 and 55..100. The app parses the percent
and passes a typed Progress (component, stage, phase, percent) up to the editor.

* Windows components: the ones turned on sort to the top of the list

In 'All components' the picks for this game come first, in their usual alphabetical order
among themselves; turning one off returns it to its place. The row moves once the install has
finished and the pick is saved, not while its progress bar is running.

* Windows components: an 'Installed for this game' section under the recommendations

What is turned on for this game gets its own section between Recommended and All components,
alphabetical; the recommendations stay where they are (a recommended pick stays there). A
component in the section leaves All components, and goes back to its place when turned off.
LB/RB jump through the new section too.
2026-10-09 12:36:05 -04:00
The412Banner e99d5f9505 Stores: GOG, Epic Games and Amazon Games libraries, installs and cloud saves (#476)
A Stores section (rail, under Games; Setup › Stores gate) with each store's sign-in,
library, storefront shelves and a download queue. Each store has two tabs, Store and
Library, and Library filters to All or Installed; libraries open from a saved copy
and refresh in the background, with install sizes on every card. Games install to internal storage,
or a card chosen at Install time, and are registered in the Steam client as non-Steam
games, so they launch through Steam and droiddeck-proton from either the front end or
the client.

- Downloads: GOG, Epic and Amazon engines in Rust (app/src/main/rust/stores, built by
  cargo-ndk, nothing compiled in the tree). One adaptive fetch core with a latency
  brake, stalled-host handoff and every CDN a store offers. Per-stage progress,
  pause/resume, two-step cancel that deletes, failed downloads kept for retry, a
  foreground service with Wi-Fi and wake locks and notification controls.
- Games tab: store source chips (Steam, GOG, Epic, Amazon, Custom) and store art.
- Epic: per-launch exchange code from the app and from Steam's Play button,
  "Epic sign-in (EOS)", "Launch offline", and a game's web links opened in Android's
  browser for EOS consent. The EOS overlay crashed games and is left out.
- Cloud saves (GOG, Epic): pulled before launch, pushed after the game or the session
  ends, no upload before a first download, conflicts never overwritten, backups both
  ways.
- Logs: every store line redacted; the engine log goes to daily files.
2026-10-09 11:08:17 -04:00
Kurt Himebauch fe71084f23 feat: replace the LXQt desktop with SteamOS-style KDE Plasma (#473)
* feat: replace the LXQt desktop with SteamOS-style KDE Plasma

* feat: leaving the KDE desktop logs out through Plasma and returns to gaming mode

* feat: DroidDeck logo wallpaper as the KDE desktop default

* feat: build the KDE wallpaper from the DroidDeck mark artwork

* ci: keep the built desktop package as a short-lived artifact for device testing

* fix: the desktop loading screen no longer promises Steam on the desktop

* docs: the output resize comments name the desktop's KWin

* fix: the desktop package verifies every package's Arch Linux ARM signature and never replaces a published tag

* fix: programs sent to the GPU from the desktop drop its software rendering overrides

* fix: an interrupted desktop install no longer counts as installed

* feat: pin the desktop package to its DroidDeck-Components release instead of the catalog
2026-10-09 10:39:50 -04:00
Magarto 79880d1502 Desktop: keep the session when the big cores cannot be pinned (#394) 2026-10-09 07:13:57 -04:00
SG3xHERO e93397011d Add a Follow screen resolution for foldables (#403) 2026-10-09 07:13:06 -04:00
Kurt Himebauch 416c1b9efd fix: proot fast path hands opendir of a directory symlink to proot (#454) 2026-10-09 07:11:09 -04:00
Kurt Himebauch 7c758c817f perf: fast path answers Steam's pre-2.33 stat calls and stays off without the patched proot (#466) 2026-10-09 03:14:29 +00:00
Kurt Himebauch d6ba3d27d8 fix: runtime removal never strands an undeletable leftover (#463) 2026-10-08 21:39:00 -04:00
The412BannerandThe412Banner 28da3dcbde Desktop: Firefox skips shared-memory seals again (Android 16 tab crash) (#460)
Desktop: Firefox skips shared-memory seals again (Android 16 tab crash, #280 line lost in #252)

Co-authored-by: The412Banner <[email protected]>
2026-10-08 19:45:44 -04:00
Kurt Himebauch 527750a366 feat: install Flatpak apps from a .flatpak file (#446) 2026-10-08 14:13:32 -04:00
The412BannerandThe412Banner 789b603022 DirectAudio from its own release: driver set per Proton interface (CachyOS fix), the Steam client's own DirectAudio sink, binaries out of tree (#439)
DirectAudio from its own release: driver set per Proton audio interface (CachyOS fix), the Steam client's own DirectAudio sink, binaries out of tree

Games
- Two driver sets ship from the pinned DirectAudio release (tools/directaudio/release.env =
  directaudio-linux-v1.1.0, sha256-checked by tools/directaudio/fetch.sh in CI and
  build_local.sh): linux-wine11 for Valve Proton 11 / Experimental / GE-Proton, and
  linux-wine11-systhread for Proton-CachyOS, whose Wine carries the system-thread mmdevapi
  interface. steam-compatibility picks by whether the Proton's own winepulse.so imports
  PsCreateSystemThread, never by the version string (both say wine-11.0; the wrong set is
  silence). No matching set: DirectAudio off, Proton's audio runs, logged.
- Proton-CachyOS's launcher pre-sets WINE_AUDIO_DRIVER=pulse,alsa and its mmdevapi reads that
  before the registry (Valve's and GE's do not read it): the wrapper exports
  WINE_AUDIO_DRIVER=directaudio,pulse as well as writing the registry. On CachyOS this also
  makes DirectAudio engage on a game's first launch.
- A wineserver from an earlier Steam verb can save its in-memory registry over the appended
  Audio value; the wrapper says so when one is live. Writing through it (wine reg add) and
  creating a missing prefix first (proton run wineboot) were tried on the device and withdrawn:
  a second Proton/Wine run inside Steam's launch sequence got launches aborted.
- Tests: both sets, no set, no prefix (34 pass).

Steam client audio
- DirectAudio = module-directaudio-native-sink, the game driver's AAudio engine inside the
  PulseAudio daemon, one step from Android (adaptive buffer, two-burst floor, primed ring with
  fades, reopen on route change, watchdog, limiter). Default. Shared output = the previous relay
  route, kept. New pref clientAudioRoute; the old boolean is not read. If the native sink cannot
  open its stream the daemon keeps running (.nofail) and ensureClientSink loads the plain AAudio
  sink instead, logged to audio.log. Audio bundle stamp bumped.

Out of tree
- app/src/main/assets/directaudio/ is generated and ignored; tools/directaudio-relay/ and the
  vendored module-directaudio-sink.c + da_relay_proto.h are gone; tools/aaudio-sink keeps only
  module-aaudio-sink (the fallback). The release carries the relay and sink field fixes by
  MaxsTechReview (#338), credited there and in its AUTHORS.

Device-proven on an AYANEO Pocket FIT: client sink 12 ms / 4 ms bursts, 0 underruns; FEZ on
cachyos-11.0-20261005-slr on DirectAudio (took pulse before); FEZ on Proton Experimental
unchanged; Curse of the Dead Gods on CachyOS from a cold prefix, DirectAudio on first launch.

Co-authored-by: The412Banner <[email protected]>
2026-10-08 12:35:00 -04:00
MaxsTechReview 39220c8fd5 Land taps and the pointer on what is drawn under them (#389)
The compositor takes touch and pointer positions over its whole output and
removes the letterbox bars itself, but the session sent them already
measured from the picture's edge, so the bars were taken off twice. Any
session whose output is a different shape from the panel (16:9 on a 16:10
tablet or a foldable, a fixed resolution on a wide phone) had taps and the
touchpad pointer pulled away from the centre, by up to a bar's height at
the edges: on a 2400x1504 panel showing 1280x720, a tap near the top
reached the first row and one near the bottom landed 32 rows low. Stretch
to panel hid it because that output has no bars.

Direct touch, the touchpad and mouse pointer, and the second-screen pointer
now send positions over the whole view, so the compositor's own mapping
puts them on the pixel under the finger.
2026-10-08 11:52:45 -04:00
Kurt Himebauch f39dc2b97a chore: bump version to 0.3.2 (12) (#437) 2026-10-08 10:49:33 -04:00
Kurt Himebauch 4106569a2d docs: point 0.3.2 full changelog at the 0.3.2 tag (#438) 2026-10-08 10:49:29 -04:00
Kurt Himebauch 60566eca31 Draft 0.3.2 release notes (#417)
* docs: draft 0.3.2 release notes

* docs: update 0.3.2 release notes

* docs: update external storage install note

* docs: add keep downloads running demo gif to 0.3.2 notes

* fix: restore colours in keep downloads running gif

* docs: add native steam sleep demo gif to 0.3.2 notes

* fix: drop glitched frames from native steam sleep gif

* docs: add windows components screenshot to 0.3.2 notes

* docs: use newer windows components screenshot in 0.3.2 notes
2026-10-08 10:42:16 -04:00
MaxsTechReview 0b7529da63 fix: keep Steam's directory listings on FUSE libraries (#410)
* fix: keep Steam's directory listings on FUSE libraries

Before it creates, moves or deletes a content file the Steam client lists
the whole destination directory about ten times to match names
case-insensitively. On Android's FUSE storage every listing goes through
the media provider, so reserving space grew quadratically with the number
of files: 33 minutes for Haunted Paws Demo (3,938 files) in a Games folder.

The client's listings under steamapps/ on FUSE are now kept while the
directory's stat is unchanged, for at most five seconds, and patched by
the client's own creates, renames and removals. Other changes fail the
stat check and the directory is read again. BL_NO_DIR_CACHE=1 turns it off.

Haunted Paws Demo into the internal shared Games folder: reserving space
33m08s -> 25-52s, committing 6m15s -> 19-30s, install 39m50s -> 1m12s-1m51s.
Internal storage is unchanged (5s reserve).

* fix: drop stale directory listings on coarse-timestamp volumes

NTFS reports whole-second directory times and a fixed size, so a change
made outside the client's own calls could leave a cached listing stale.
Listings now carry an inotify watch and are dropped on any event the
client's own patched changes don't account for. A forked child stops
caching, since it shares the parent's queue.

Covers rewinddir, telldir and seekdir on cached listings, and directories
opened by the fast-path preload's opendir, which goes through fdopendir.
2026-10-08 08:50:00 -04:00
Kurt Himebauch 3c5c9b7dab Include PR descriptions and queue catalog updates (#421)
* Include a short PR description in build announcements

* Queue catalog refreshes without cancelling publishing runs
2026-10-08 00:46:34 -04:00
Kurt Himebauch fd75f3c6bd Fix PR build report lookup (#420)
Use standalone jq to filter paginated PR comments
2026-10-08 00:32:11 -04:00
Kurt Himebauch eb9b3967f1 Separate PR build announcements (#418)
* Separate PR build announcements and retire closed builds

* Isolate watcher test clocks and clean up the agent server
2026-10-08 00:23:55 -04:00
Tak-attackandxXJsonDeruloXx d59e97df5c Improvements to the Japanese Translation (#314)
Update strings.xml

Improvements to the Japanese Translation
I have rewritten the translation to make it sound more natural in Japanese.

Also, since “Loader” in “Decky Loader” is a proper noun, I believe it does not need to be translated.

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:11:52 +00:00
kmnkv1andxXJsonDeruloXx 3ca2787108 Update Russian translation (#324)
Translate the 103 missing strings (screen effects, scaling, Wine sync
backends, FEX for AppImages, Steam repair, device info) and fix existing
ones: inverted meaning in mode_suspend_note, content_add_roms and
wadb_connect_address, double space in the update/install buttons,
outdated AppImage/Proton hints, "1 приложений"-style counts, gender
agreement with substituted names, lowercase fragments that are joined
into sentences, and inconsistent terms (Setup, Developer options, Stop
session, Button mapping).

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:06:56 +00:00
0x142857andxXJsonDeruloXx b56c44bd0b Share font defaults across runtime sessions (#252)
* Support CJK text with the device's own fonts

The Linux runtime's rootfs ships DejaVu alone, which has no CJK
glyphs, so Chinese, Japanese and Korean text in Steam Big Picture and
on the LXQt desktop draws as tofu boxes (issues #118, #206, #249).

Bind the device's own CJK fonts - Android's world-readable Noto CJK
.ttc, or DroidSansFallback / Source Han - into the guest under
/usr/share/fonts/droiddeck-cjk, so nothing is added to the ~790 MB
runtime image and the coverage follows the device. A new
droiddeck-fonts script, run by droiddeck-session before any program
that queries fontconfig, links the default rules, writes local.conf
with DejaVu first and the CJK families after it, and rebuilds the
fontconfig cache once per set of font files. The desktop's own
fontconfig block moves there, so every mode gets it.

* Fix CJK and Thai: bind the device font directory, not one file at a time

The first cut bound each device font with its own proot -b host:guest. A
per-file binding into a plain directory is not applied here: the guest sees a
zero-byte placeholder where the font should be (stat size=0, fc-scan empty),
so Chinese stayed tofu even though the app had "bound" the font. A directory
binding is what the rest of the runtime relies on, so bind /system/fonts as a
whole under /usr/share/fonts/droiddeck-device. That also brings Thai, Arabic,
Devanagari and the emoji fonts, which the runtime did not have either.

Verified on the AYN Thor: the session's fontconfig probe reports
sans=DejaVu Sans, zh=Noto Sans CJK SC, th=Noto Serif Thai, and Steam Big
Picture in Simplified Chinese renders without tofu (screenshot under
docs/development/evidence/).

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:06:23 +00:00
bb68a961ed Polish Spanish translation (#234)
* i18n: complete Spanish

Spanish gets the strings added since it landed and the ones that are only a format or a brand name, so it has the same keys as English, in the same order. Each English term keeps one Spanish word across the file, and a few existing strings are reworded to read right with the values they are filled with.

* i18n: use compilación for build in Spanish

* i18n: translate new strings to Spanish

---------

Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-08 04:05:09 +00:00
Kurt Himebauch 415b93f943 feat: agent bridge wayland and gamescope coverage (#406)
* feat: run a guest agent in every session for commands, focus and the Steam client's DevTools

* feat: give the agent bridge game, input, UI and experiment commands behind a Setup toggle

* feat: droiddeckctl commands for the new bridge, a scenario runner and contract tests

* feat: run cleanup steps after every scenario run so a failure cannot leak into the next

* feat: restart a session that died between scenario runs with start --reuse

* fix: give droiddeckctl stop its own, longer wait for the session's logs

* fix: harden debugging tools access and output
2026-10-08 03:50:58 +00:00
Kurt Himebauch 7b1f0f25b2 harden logging (#414) 2026-10-08 02:10:06 +00:00
The412Banner 5954ac58f8 fix: read droiddeck-env from ps (#413) 2026-10-07 21:38:33 -04:00
Kurt Himebauch 6eda278d9b fix: backport upstream gamescope fix for games left behind Steam's loading screen (#401) 2026-10-07 15:28:11 -04:00
Kurt Himebauch 9867a2d4ce fix: keep Updates channels and status side by side (#397) 2026-10-07 13:48:02 -04:00
MaxsTechReview 2a0512d19d Keep the shortcut settings changed in Steam (#393)
Steam saves a shortcut's Properties to shortcuts.vdf as soon as they
change and keeps its appid. The session rewrote every added game's entry
from the app's list before and after each client run, keeping only the
launch options, so a Target or Start In set in Steam, a new name or icon,
the hidden and overlay switches, the last played time and Steam's sort
name were all put back at the next exit.

droiddeck-steam-shortcuts now merges instead: an entry keeps every field
Steam has, and the app's exe is written only where the app changed it.
What the app last asked for and what Steam then had are recorded beside
shortcuts.vdf (.droiddeck-shortcuts.json). A Target changed in Steam to a
file inside the game's folder becomes the exe chosen in Steam > Settings >
Games, and a later pick there replaces it in Steam, so both show the same
exe. The appid now stays with the folder, so changing the exe no longer
turns the game into a new one with a new prefix, playtime and launch
options. A game whose folder is missing is set aside with its settings
and comes back with them. The exit-time merge runs before Steam's logs
are copied.
2026-10-07 09:32:15 -04:00
MaxsTechReview 6ee8866599 Make Games folders Steam libraries and skip the zero-filled reservation (#388)
With Second library on Automatic, every writable Games folder is now a
Steam library: it gets the same private prefix and tool binds as the card,
the app hands the session the list (BL_STEAM_LIBRARIES), and
droiddeck-steam-library registers, relabels and forgets entries from it.
Steam offers them in the install dialog as "<folder> (<volume>)", and
Proton mapping, esync, the redist seeder, desktop entries and the compat
beat read every registered library instead of only the card. Games folder
libraries stop at the fast path's bind budget.

Steam's preallocation on Android's FUSE storage either failed outright
(NTFS answers EPERM, so installs were cancelled as "not enough disk
space") or zero-filled the whole download first (exFAT, minutes for a
small game). After fallocate is refused, the Steam client's ftruncate
extension of its own staging files is reported as done; the writes that
follow fill the file. A 76 MB install to a USB SSD now preallocates
instantly instead of taking about 3.5 minutes.

A library that is not there is left out of the list and forgotten until
it returns, imports whose folder is missing are set aside rather than
offered as broken installs, and the front end refreshes when storage is
mounted or removed.
2026-10-07 09:31:33 -04:00
The412Banner bc0f6e7271 File manager: the Linux system's root under Storage (#392) 2026-10-07 08:12:02 -04:00
The412Banner a84e9cdc08 File manager: the Linux system's root under Storage
A "Linux system" entry in the file manager's Storage list (the rail and the
drive menu) opens the root of the Linux system the sessions run in - the
app's files/linuxfs, the desktop's / - so its files can be browsed, copied
in and out without the desktop. Shown once the runtime is installed; Back
stops at that root like a drive's, and the drive label reads Linux system
anywhere under it. The label is translated in all nine languages.
2026-10-07 08:05:43 -04:00
Kurt Himebauch 2a9cb977cf Let Steam draw at the session's full size above 1080p (#391) 2026-10-07 07:51:50 -04:00
Juan MartíandxXJsonDeruloXx f5e7d73654 Add Steam background download mode (#335)
* Add Steam background download mode

* Keep active Steam downloads running

* Make background downloads a toggle

* fix: keep background downloads alive between Steam's rate log lines

* Track all Steam content update types

---------

Co-authored-by: xXJsonDeruloXx <[email protected]>
2026-10-07 10:36:49 +00:00
Kurt Himebauch 82ad9330d0 fix: revert gamescope patch 0113 now that games run on their own Xwayland (#372) 2026-10-07 06:25:06 -04:00
MaxsTechReview d1760cd49f Draw Steam's CJK and Thai text with the device's fonts (#374)
Steam ships no CJK font and the rootfs only has DejaVu, so a phone set to
Chinese, Japanese or Korean got Steam in that language with every character
drawn as a box. Bind Android's /system/fonts into the guest, index it again
whenever it changes, and prefer the matching Noto CJK face for each language.
2026-10-07 02:23:27 -04:00
The412Banner bd65f1660f File manager: up/back from any start folder, plus a ".." up row (#377) 2026-10-07 00:05:06 -04:00
The412Banner eda019a044 File manager: up/back work from any start folder, and a ".." row to go up
Opening the manager at a folder (a game's files, its Proton prefix, the
saves folder, or the picker's remembered folder) ran openDrive() on the
start dir, which pinned the up/back floor to that folder: the toolbar
arrow was greyed out and Back closed the screen. The start dir now opens
with loadDirectory(), keeping the volume-root floor computed for it, and
folders in the app's private storage (internal Steam library, prefixes)
floor at the app's files dir instead of themselves.

Adds a ".." (Up one level) row at the top of the list and grid whenever
the current folder is above its floor, sharing one goUp() with the
toolbar arrow. Covers both screens built on FileManagerScreen: the File
Manager and the in-app file/folder picker. Strings for all 9 locales.
2026-10-06 23:52:59 -04:00
MaxsTechReview fa99e2aab6 Language setting and translations: Spanish, Japanese, Korean, Chinese; Steam follows it (#187) (#284)
* l10n: move hard-coded UI text into string resources (#212)

About 650 user-facing strings were written in Kotlin and Java, so no
translation could reach them and the UI stayed half English in every
language. They now live in values/strings.xml.

- Dialogs, menus, status lines, toasts and errors shown to the user go
  through getString/stringResource.
- Code that matched on English text now uses IDs: installer and loading
  stages, Decky stages, storage labels and the Back button's order labels.
- Choice lists (upscalers, FPS limits, textures, themes, presets) carry
  @StringRes labels instead of English.
- Robolectric tests read the app's resources (includeAndroidResources).
- Six strings nothing used any more are dropped.

Existing translations are untouched; the new strings show in English
until they are translated.

* Layouts: fit longer text in other languages, and small phones

Found by screenshotting every page, tab and menu in each language on a
phone, a tablet, a 640dp phone and a foldable.

- Rail labels shrink to fit instead of being cut off (FitText).
- Tab rows tighten when they overflow and scroll the chosen tab into view.
- Menus fade over a down arrow when more entries are below, and open
  scrolled to the current choice.
- On a small phone the desktop card's button moves under its title,
  emulator tiles go two across, the GPU support verdict wraps and the
  Updates page stacks the channels under the status.

* l10n: translate every string into Spanish, Japanese, Korean and Chinese (#187)

Simplified Chinese, Traditional Chinese (Taiwan) and Traditional Chinese
(Hong Kong, reviewed for Hong Kong wording) are complete, as are Spanish,
Japanese and Korean: no missing keys and no placeholder mismatches.

* Language setting: follow the system, or a language picked in Setup; Steam uses it too (#187)

- Setup -> Launcher starts with a Language row: "System default (...)"
  and each language by its own name. With nothing picked the app follows
  the system language; a pick applies at once and stays until System
  default is picked again.
- Steam keeps its language in ~/.steam/registry.vdf under both
  Valve/Steam/language and Valve/Steamsteamglobal/language. Before each
  start of the client the guest helper droiddeck-steam-language writes
  both (and exportedsettings.json), and the client gets -language.
  SteamLanguage maps the app's language to Steam's names: english,
  spanish/latam, japanese, koreana, schinese, tchinese.

* l10n: translate the strings main added since, and Chinese review fixes

- Translate the 32 strings added on main since (Windows components,
  device details, Force SSBS, native Steam sleep) into all six languages.
- zh-CN: busy labels say what is in progress again (正在取消, 正在导出
  ...), as the other languages do.
- zh-CN: "set up" is 配置, as Setup is; 设置 stays for Settings.
- zh-TW: proton_compatible_note says 前綴 rather than "prefix".
2026-10-06 22:00:21 -04:00
MaxsTechReview 0b04678bdc l10n: move hard-coded UI text into string resources (#212) (#282)
About 650 user-facing strings were written in Kotlin and Java, so no
translation could reach them and the UI stayed half English in every
language. They now live in values/strings.xml.

- Dialogs, menus, status lines, toasts and errors shown to the user go
  through getString/stringResource.
- Code that matched on English text now uses IDs: installer and loading
  stages, Decky stages, storage labels and the Back button's order labels.
- Choice lists (upscalers, FPS limits, textures, themes, presets) carry
  @StringRes labels instead of English.
- Robolectric tests read the app's resources (includeAndroidResources).
- Six strings nothing used any more are dropped.

Existing translations are untouched; the new strings show in English
until they are translated.
2026-10-06 21:59:31 -04:00
MaxsTechReview 1951d8bfde Fix Audio Crackling on Pulse & DirectAudio (#338)
* Build the DirectAudio relay from source

The relay helper shipped as a prebuilt libdirectaudiorelay.so with no source in the
repo. Its source (The412Banner/directaudio @ 3548a4a, the commit the bundled driver
names in version.txt) now lives in tools/directaudio-relay, unmodified, and CI and
build_local.sh compile it into jniLibs the way proot is built. It shares
da_relay_proto.h with the PulseAudio sink, which is byte-identical to its own copy.

* DirectAudio relay: prime before playing, one consumer, safe buffer floors

- The output stream started before the client had its ring, so every connection
  opened with a run of short callbacks: six clicks per stream on a RedMagic, even idle.
  The callback now plays silence until the ring holds its target, and only a real
  underrun afterwards counts.
- An underrun ends the block with a short fade and waits for the ring to refill to
  target before resuming with a fade in, instead of zero-padding every short callback.
  Each callback spent starved grows the target, so one stall sizes it for the next.
  With a producer freezing 60 ms every 2 s: 14 underruns before, 1 after.
- During a reopen the outgoing and incoming streams could both drain the ring. Only
  the promoted stream reads it now; any other plays silence.
- The ring target starts at 40 ms instead of two bursts (8 ms) and keeps what it
  learned across reopens.
- The device buffer is at least two bursts; 12 ms was a single burst on devices that
  grant a 20 ms legacy stream.

* PulseAudio: the classic path plays through the native-rate AAudio sink

The classic path loaded the prebuilt 0.1.5 module, which opened AAudio at the daemon's
default 44.1 kHz: Android gave it a legacy stream (20 ms bursts) and resampled, after
PulseAudio had already resampled the 48 kHz clients down. Its data callback also waited
on the daemon's IO thread, and it started at two bursts and grew only after an audible
underrun. The app's own module-aaudio-sink, built by CI into the same bundle, now
carries that path:

- The stream opens at the device's rate (48 kHz) with the buffer at two bursts or
  30 ms, whichever is more, still growing a burst per underrun.
- A write that times out part way keeps the rest for the next write instead of
  dropping it.
- A stream reopened after a route change asks for the rate the first one got, so the
  sink keeps its format instead of unloading.

* Audio: DirectAudio sink at audio priority, deeper client buffers, no decay probing

- module-directaudio-sink's IO thread asks for nice -16 like module-aaudio-sink's, so
  the client's producer keeps up under game load.
- PULSE_LATENCY_MSEC=60 for the session: libpulse clients under FEX (the Steam
  client, winepulse when DirectAudio is off) keep enough queued to ride out a late
  wakeup.
- BANNER_AUDIO_DIRECT_DECAY=0: the driver's decay shrinks the device buffer every
  10 s of quiet until it underruns again, a click by design.

Both sit ahead of Downloads/droiddeck-env and per-game environment, which still win.

* Audio: drop the session-wide PULSE_LATENCY_MSEC

On an AYN Thor it took the Steam client's stream to 110 ms (60 ms tlength on top of the
sink's 40 ms) with no underruns to show for it, and the delay was audible. Clients pick
their own buffers again; the per-game option still sets it.

* PulseAudio sinks: wait in the poll loop, never in a blocking call

Clients' audio reaches a sink as messages its IO thread handles in pa_rtpoll_run. The AAudio
sink spent each iteration inside a blocking AAudioStream_write for up to a burst, and the
DirectAudio sink inside a futex wait for up to a burst, so a client's data sat unread and its
stream ran dry: a Chromium-shaped client (44.1 kHz float, 30 ms tlength, 10 ms minreq) had
304 underflows in 30 s on the AAudio sink, heard as crackling in the Steam client. Bursts of
20 ms (a legacy stream, an AYN Thor) made it worst.

- module-aaudio-sink writes without blocking, renders only what the device has room for, and
  comes back after half a burst. 0 underflows in 30 s, with and without 8 busy cores.
- module-directaudio-sink tops the ring up on the same half-burst timer instead of sleeping
  on the relay's futex.

* DirectAudio relay: vendor the source the shipped binary was built from

The import took 3548a4a, the commit the bundled driver names in version.txt, but the relay
binary that shipped came from e4727e9 on the same branch (feat/linux-relay-mic): its strings
match a build of that source exactly. 3548a4a has no --mic-fifo, so with the microphone on the
relay printed its usage and exited, and DirectAudio played nothing. This is e4727e9 as
published; the fixes made on top of 3548a4a follow in the next commit.

* DirectAudio relay: prime before playing, one consumer, safe buffer floors (on e4727e9)

The same changes as ca5d39b, carried onto the e4727e9 source: playback waits until the ring
holds its target, an underrun fades out and re-primes, starved callbacks grow the target, only
the promoted stream drains the ring, the ring target starts at 40 ms and survives reopens, and
the device buffer is at least two bursts.

* Steam client audio: DirectAudio by default

A fresh install plays the Steam client through the DirectAudio relay. Choosing Classic in the
Steam cog is stored and kept; only an install that never chose falls back to DirectAudio.
2026-10-06 17:10:00 -04:00
Juan Martí e3cf9f882a feat: recognize Adreno 650 as supported (#326) 2026-10-06 16:02:54 -04:00
Kurt Himebauch 8617ee1090 fix: give each new session the current screen's output size (#357) 2026-10-06 19:17:52 +00:00
Kurt Himebauch 670c9d1afd fix: share session logs in about a second instead of re-scrubbing them (#352) 2026-10-06 14:51:43 -04:00
The412BannerandThe412Banner 7484f43dba Windows components: install .msi packages (XNA, PhysX, GFWL, MSXML...) without running them (#347)
* Windows components: install .msi packages (XNA, PhysX, GFWL, MSXML...) without running them

droiddeck-msi-install reads a Windows Installer package's tables with msiinfo and unpacks its
cabinets with cabextract, then lays out what Wine's msiexec would have installed: files at their
64-bit Windows paths (Program Files (x86), syswow64...), .NET assemblies in the GAC, side-by-side
assemblies in winsxs, and the registry values in the view the component writes through. Conditions,
features and the folder-setting custom actions are evaluated as on Windows 10 64-bit; what needs
the package's own code (DXSETUP, self-registration) is listed in the component's notes.

droiddeck-wincomponents puts a component's drive_c tree into the prefix (keeping the prefix's own
spelling of each folder) and writes its registry values into system.reg/user.reg before launch,
following Wine's link keys (Wow6432Node\Classes, CurrentControlSet...). Turning it off puts every
value back and takes an added PATH entry out again.

The app installs these from the Windows components menu like the DLL ones: it downloads the .msi
into the runtime and runs the installer there (GuestCommand). msitools 0.106 and cabextract 1.11
come from Arch Linux ARM, pinned in tools/msitools/packages.txt and hosted on the msitools-0.106-r1
release. wine-gecko stays hidden (Proton ships it) and wine-mono keeps waiting (uninstall step).

* Windows components: open with the top switch focused, by touch or by pad

The dialog's window starts in touch mode, where a switch cannot take focus, and the first switch
was asked once, two frames after the list loaded, only when a pad opened it - racing the dialog's
own Done focus. Now it always puts the window in pad mode, holds Done while the list loads and
then asks the top switch every frame until it has focus.

* Windows components: give msitools its own libraries; show a failed install on its row

msiinfo and cabextract sit with their libraries in /usr/local/lib/droiddeck-msitools, which the
loader does not search: every .msi install stopped at "libmsi-1.0.so.0: cannot open shared
object file". droiddeck-msi-install now runs them with that folder on LD_LIBRARY_PATH.

A failed install also says so under the component itself ("Could not install: ..."), not
only at the foot of the dialog, past the list.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 14:14:28 -04:00
Kurt Himebauch 2278274588 feat: remove Force game windows fullscreen now that games run on their own Xwayland (#349) 2026-10-06 18:04:22 +00:00
Kurt Himebauch 5f3c0e83c7 feat: run Steam sessions on two Xwayland servers as SteamOS does (#344) 2026-10-06 17:56:13 +00:00
The412BannerandThe412Banner dba632ea14 Fast path: hold every bind, so added games start in their own folder (#343)
* Fast path: hold every bind, so added games start in their own folder

libblfastpath kept the first 64 binds and dropped the rest without a word. A session has 87 (two
Games folders are the last two), so /root/Games/<folder>/... was mapped into the rootfs - the
empty mount point - instead of the bound storage. Wine resolves a new process's working directory
by those paths, found no drive for it and started every added game in C:\windows ("could not
find DOS drive for current working directory"): DiRT 3 did not mind, FEZ looks for
Content\Essentials.pak there and quits.

The library now holds 256, and turns itself off for the process when a bind does not fit or the
list runs past that, leaving the call to proot, which never maps it wrong. The app keeps the fast
path off for a session with more binds than that.

Verified on an AYANEO Pocket FIT: FEZ (added game) reaches its title screen; the warning is gone.

* Fast path: say in the session log when it stays off

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 13:48:01 -04:00
Kurt Himebauch 9fe5f29f64 fix: resolution presets keep the panel shape at 720p, 900p and 1080p (#337) 2026-10-06 13:19:46 -04:00
Kurt Himebauch 9931dd5d09 fix: hold flood edges at the page edge once reached (#330) 2026-10-06 12:07:24 -04:00
Kurt Himebauch e95009fac2 fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl (#305)
* fix: avoid reusing retired KGSL GEM handles

* fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors

* Revert "fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors"

This reverts commit 191bf4b6f2.

* test: interpose drmIoctl without releasing GEM_CLOSE handles

* fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl
2026-10-06 11:52:16 -04:00
The412BannerandThe412Banner 239ba48291 Windows components for Steam and added games; no more redist installer prompts (#287)
* Put a game's Windows components into its Proton prefix at launch

droiddeck-wincomponents copies the DLLs of the components picked for a game
into compatdata/<appid>/pfx before Proton starts and hands their overrides to
WINEDLLOVERRIDES, so the registry is never touched. It re-checks every launch
(an older Proton deletes the files it once placed), takes a component's files
out again when it is turned off, and forgets Proton's config_info then so
Proton puts its own builtins back.

* Windows components for added games: picker, packages and their catalog

Settings > Games lists a Windows components row under each added game. Its
dialog switches components on per game; the first switch-on for any game
downloads the package (a catalog of its own beside desktop.json, installed
with the desktop package installer to /opt/droiddeck/wincomponents/<id>).
The picks are kept in the app and published to the runtime at every session
start for droiddeck-wincomponents to apply.

tools/wincomponents/package.py builds the packages: Bannerlator's eight
wincomponents archives re-rooted, and OpenAL from OpenAL Soft's release
(soft_oal.dll placed as OpenAL32.dll, 32- and 64-bit).

* Windows components: list the installed ones when the catalog cannot be reached

Offline the dialog only looked up ids it already knew - the catalog's and
the game's picks - so a component downloaded earlier never showed.

* Windows components: Bannerlator's list with Recommended, on the game's launch settings

The picker moves from Settings to a Windows components card in an added
game's launch settings. It offers Bannerlator's whole catalog
(components.json on winlator-contents): what the game's folder points at
first (Bannerlator's DependencyDetector, mapped to the DLL-copy twin where
there is one, oalinst -> oalinst_dll), then every component that installs
here, then the ones that need a Windows installer, greyed until DroidDeck can
run one.

A component installs by running its catalog steps into the runtime once:
archives downloaded (one download per archive for a batch), DLLs found by
name as Bannerlator does and copied to /opt/droiddeck/wincomponents/<name>,
overrides and bundled components written to component.json. The launch
script now takes catalog names (xaudio2.7), expands bundles (directmusic),
places files one level down (drivers/gm.dls) and keeps an override's own
mode (dsound=n). Drops the hand-made packages and their script.

* Windows components: Steam games too, and a pad that can drive the list

The card shows on a Steam title's launch settings as well, keyed by its
appid - the compatdata folder droiddeck-game-env reads - and Recommended
scans the game's install folder.

With a pad: Done takes focus while the catalog loads and the first
component once it is there (the list was not drawn yet when the dialog
asked for focus, so nothing had it); switches stay enabled during a
download and a press then is ignored (a disabled switch dropped the
pad's focus); LB/RB jump between Recommended, All, the installers and
Done.

* Redist markers from games' own install scripts; VC++ runtime as installed

The client still started a game's own _CommonRedist installer - its
script sits in the game's folder, often with another version in the
value name, and only Steamworks Shared's were read. On ARM64 Proton the
x64 VC++ 2015-2022 bundle then stops with "Microsoft Visual C++
2015-2022 Redistributable (arm64) is required" (reported on an AYN Odin 2
Portal Max).

- Read installscript*.vdf from every installed game in each library as
  well; the list is cached until an appmanifest changes, since this runs
  every quarter minute.
- Record the VC++ 2015-2022 runtime as installed where its installers
  look (VisualStudio\14.0\VC\Runtimes\{x86,x64,arm64}, both registry
  views), so any bundle - the client's or a game's own - finds it and
  exits; Proton's msvcp140/vcruntime140 serve the game.
- Join the Wow6432Node path instead of sub(): mawk and gawk read the
  replacement's backslashes differently (gawk, the runtime's, was right).

* Mark a game's install-script processes as run just before the client evaluates them

An Odin 3 bundle (0.3.1, Proton Experimental ARM64) shows the client
running its install-script evaluator on the first launch of three new
titles (409710, 2161700, 3404260) while their Steamworks Shared
redistributables (VC++ 2010/2012/2019/2022, DirectX) were still
downloading - 2161700 at 22:03:22, the shared app finished at 22:03:29 -
so the periodic seeder had nothing to mark yet and the installers ran.

The client writes the exact steps to legacycompat/evaluatorscript_<id>.vdf
and starts iscriptevaluator.exe through our launcher. droiddeck-game-env
now hands that script to droiddeck-seed-redists first, which marks every
process in it in the template and the prefixes before the evaluator
reads them.

The seeder's reader no longer assumes a depth: a block with a HasRunKey
is a process named by the key it opened under, wherever it sits.

* Windows components: keep a file that was not Proton's, put it back when turned off

A component's DLL replaced whatever had that name in the prefix, and
turning it off deleted ours, so a file a real installer (or the user) had
put there was gone for good. Before replacing a file that is neither
Proton's own (a symlink into Proton, or a Wine builtin/placeholder DLL)
nor an earlier copy of ours, keep it under
compatdata/<id>/.droiddeck-wincomponents-backup and move it back when the
component is turned off. The launch note says how many went back.

* Windows components: recommend what Steam installs, say what the game already has

- Steam titles: the appmanifest's SharedDepots (Steamworks Shared, app
  228980) list the VC++ 2005-2022 and DirectX redists Steam installs with
  the game; they live outside the game folder, so the folder scan never
  saw them. They join Recommended as "Steam installs VC++ 2010 with this
  game" (SteamRedists).
- Bannerlator's PrefixInstalledDetector, on compatdata/<id>/pfx:
  OpenAL32, Mono, PhysX and Games for Windows Live that are already there
  from elsewhere show "Already in this game". Files droiddeck-wincomponents
  placed itself do not count.
- The game page passes the prefix and, for Steam titles, the appid.

* Windows components: friendly names

The list showed catalog keys (oalinst_dll, vcredist2010_dll, dotnet452,
d3dcompiler_47). Bannerlator's catalog has no display name and some of
its descriptions do not fit the entry, so WinComponentNames gives one:
families by pattern (Visual C++ <year>, .NET Framework x.y[.z], XAudio,
D3DCompiler, MSXML, Wine Mono, DirectMusic parts), the rest from a
table, the key itself for anything new. The key stays in the row's
detail for logs and bug reports; both lists sort by the name, and the
game page's card names the picks the same way.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 10:46:27 -04:00
The412BannerandThe412Banner 931e5e3362 Force SSBS for Proton games (libssbs.so), switch under FEX preset (#328)
* Force SSBS for a game's Wine: libssbs.so, opt in per game

Wine's arm64 signal code resumes threads from a Windows CONTEXT whose
Cpsr never carries PSTATE.SSBS, so every signal return leaves the
thread running with speculative store bypass disabled - slower on cores
such as Oryon. Bannerlator fixes it in its own Wine builds
(force_ssbs.diff, Sonicadvance1) and with an LD_PRELOAD adapter for
layers it cannot rebuild. DroidDeck's Protons (Valve, GE, CachyOS ARM64)
come prebuilt from Steam but run the same aarch64 Wine signal path, so
the adapter applies here: tools/linuxfs/ssbs/ssbs_adapter.c, the same
source built for glibc (+ sysv_signal), staged to
/usr/local/lib/libssbs.so.

droiddeck-game-env preloads it when the game's (or the shared)
environment sets DROIDDECK_FORCE_SSBS=1, and only into an ARM64 Proton.
Off unless asked for: the gain is unproven on Snapdragon 8 Gen 3.

For testing: SSBS_ADAPTER_OBSERVE=1 counts without changing anything
(the baseline) and SSBS_ADAPTER_DEBUG=1 logs the fixes; both also count
signals that ARRIVE while a thread runs without SSBS, which shows
whether anything besides signal returns clears it. Checked on an ARM64
Linux kernel: a handler that clears SSBS left 4 of 5 later signals
arriving with it clear; with the adapter, 0 of 5.

* Force SSBS: a switch under the FEX preset in Steam settings

Steam settings > Games gets "Force SSBS" right under the FEX preset, off
unless turned on. Like the preset it goes into the game environment's
defaults (DROIDDECK_FORCE_SSBS=1), published at once and read by
droiddeck-game-env on the next game launch, so a game's own environment
can still set it to 0.

Measured on the device with the adapter's counters (AYANEO Pocket FIT,
DiRT 3 on GE-Proton11-7-aarch64): observing only, 8192 of 8277 signals
found the thread running without SSBS; with the adapter, none did - it
keeps the bit set everywhere Wine clears it, as a rebuilt ntdll would.
The speed-up itself is reported on Oryon cores and was not measurable on
the FIT's 8 Gen 3, hence off by default.

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-06 09:27:15 -04:00
UnbelievableFlavour bb43965529 Added support for Adreno 6xx devices (#320) 2026-10-06 09:08:04 -04:00
Kurt Himebauch e152dd0212 Add Native Steam background sleep and automatic wake (#291) 2026-10-06 00:15:39 -04:00
Kurt Himebauch 524de172d3 ci: require isolated version bumps (#303) 2026-10-06 03:26:14 +00:00
Kurt Himebauch 61b79a2ecb Return frontend-launched sessions to Android Home (#300) 2026-10-05 21:39:40 -04:00
xXJsonDeruloXx 3415301da4 Merge remote-tracking branch 'origin/main' into frontend-quit-return 2026-10-05 21:34:49 -04:00
xXJsonDeruloXx e984af6a7a Return frontend-launched sessions to Android Home 2026-10-05 21:34:36 -04:00
Magarto 440ec98f45 Keep the 16:9 stretch (#63) working in Steam Deck mode (#133) 2026-10-05 21:28:31 -04:00
The412BannerandThe412Banner 0bcb4a98fe Device details on the GPU card; Adreno model and SoC name when the kernel leaves them out (#295)
* Device details on the GPU card, and the Adreno model when the kernel leaves it out

The Components page called an AYANEO Pocket FIT (Adreno 750) "Adreno"
and warned it was "Below tested hardware (Adreno 725 and newer)": its
kernel names the GPU "Adreno33v2", GpuInfo wanted three digits, and the
SoC model is blank. When KGSL's name has no model, take it from the
phone's Vulkan driver (once this process has asked it) or from the
platform code name (pineapple -> 750, kalama -> 740, sun -> 830, ...).

The GPU card now opens with a tap or A onto the device's details,
gathered once off the main thread (DeviceInfo): SoC (maker, model,
platform, soc0 id), GPU (model and where it came from, the kernel's
name, clock, load, temperature, support), CPU clusters, memory, system,
the phone's own Vulkan driver - device, API, driver version and build,
conformance, limits, the extensions DXVK and VKD3D lean on, Android's
certified version and level - and the drivers DroidDeck uses. A button
copies it all for a bug report; each section takes pad focus so the
d-pad walks the list.

Vulkan is asked through a new small native library (deviceinfo,
vkinfo.c) that opens the system loader with dlopen and keeps nothing
open.

* Device details: the chip's model and name, CPU clusters, GPU clock range

Ideas from ClusterTune (GPL-2.0, its approach, not its code), which
tunes clocks on AYN, Retroid and other handhelds:
- The SoC model is read where vendors put it - ro.soc.model,
  ro.vendor.qti.soc_model, Android's SOC_MODEL, ro.fota.platform (unless
  it is a branch name like "MSM_13.0") - since handhelds often leave
  Android's blank (Pocket FIT: SG8350P).
- SocNames names it: model codes we are sure of (QCS8550 -> Snapdragon
  8 Gen 2, SM8750 -> Snapdragon 8 Elite, ...), else the platform as a
  family (pineapple -> "Snapdragon 8 Gen 3 family"). The GPU card's
  subtitle shows it: "Snapdragon 8 Gen 3 family (SG8350P)".
- CPU clusters come from the cpufreq policies (the cores each covers,
  min-max speed) rather than grouping cores by top speed.
- GPU clock range from KGSL's step list, or a devfreq GPU node on
  non-Adreno chips (Mali), not only the top clock.

* Device details: drop Qualcomm's own "Driver Build:" label from the driver build line

---------

Co-authored-by: The412Banner <[email protected]>
2026-10-05 20:32:51 -04:00
Kurt Himebauch 3d16dc7ea4 Fix legacy Steam sleep and missed wake handshakes (#289)
Fix legacy Steam sleep and recover missed wake handshakes
2026-10-05 16:49:55 -04:00
輕鈴andKurt Himebauch 86f9b63b79 Refine Simplified Chinese localization (#232)
Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-05 15:58:59 -04:00
starwishHiandKurt Himebauch e9f66aec81 Add French and Russian translate, add fr,ru,jp,ko in locales_config.xml And overlap #234 and #259 (#286)
* Create values-zh-rCN

values language of Simplified Chinese

* Delete app/src/main/res/values-zh-rCN

* Add Korean,Japanese and Tranitional Chinese (zh-TW,zh-HK,zh-MO) translations

* Delete app/src/main/res/values/values-ko/strings.xml

* Create strings.xml

Add korean translate

* Add French translate

* Add Russian translate

* Fix French translate error

* Add the fr,ko,ja,ru in locales_config.xml
Fix French bulid error

* Overlap #234 and #259

---------

Co-authored-by: Kurt Himebauch <[email protected]>
2026-10-05 15:52:39 -04:00
MaxsTechReview a73d2108e0 sign-apk: rename the agent provider's authority for the other packages (#285) 2026-10-05 14:41:38 -04:00
Kurt Himebauch 2e6e35fb99 Use plain-ID .steam files for frontend exports (#277) 2026-10-05 13:48:18 -04:00
The412BannerandThe412Banner 6f6bd35b96 Firefox: skip shared-memory seals (tabs crash on Android 16) (#280)
On Android 16 phones (Galaxy S25 SM-S931B, SM-S948B, RedMagic NP05J)
every Firefox and LibreWolf tab dies at once: Firefox seals the size of
the shared memory it hands a tab, adding the seal fails there, Firefox
only warns, and the tab refuses the unsealed memory ("IPDL protocol
error: Shared memory PlatformHandle is not safe to map") and exits on
signal 11. The window opens; no page ever loads.

MOZ_SHM_NO_SEALS=1 is Firefox's own switch to skip the seals
(ipc/glue/SharedMemoryPlatform_posix.cpp). Set it beside the sandbox
switches in all three places a browser starts from: the desktop session,
AppImages, and Flatpak sandboxes.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 13:37:08 -04:00
Lobanokivan11 1b3a8c58cb merge bridge to main (second time) (#279) 2026-10-05 17:09:24 +00:00
The412BannerandThe412Banner 5ddcde7023 Force game windows fullscreen: off by default (#276)
A game whose resolution differs from the screen's (DiRT 3 at 1280x720)
fights --force-windows-fullscreen: every forced resize makes it rebuild
its swapchain, and the picture flickers between its own size and the
screen's - often from the first menu, before the player can reach the
game's resolution setting. It also drove the Xwayland dma-buf leak fixed
separately.

The switch stays in Settings > Games and the session drawer, now off
unless turned on; its hint says when to turn it on (a game coming back
small or in a corner after switching apps, e.g. FlatOut) and when to
leave it off (flicker). Users who already set it keep their choice. The
session script's fallback when the app passes nothing is off as well.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 12:44:45 -04:00
The412BannerandThe412Banner 2ccc8204ad Fix Xwayland dma-buf leak: close Zink's emulated GEM handles on KGSL (#275)
Close Zink's emulated GEM handles on KGSL (Xwayland dma-buf leak)

The session preload hands out GEM handles on the KGSL render node by
duplicating the dma-buf descriptor, and released them only through
drmCloseBufferHandle. Zink gives handles back with
drmIoctl(DRM_IOCTL_GEM_CLOSE) instead, which KGSL refuses, so every
duplicated descriptor stayed open and kept its buffer alive.

Xwayland asks for a handle for each buffer it imports through gbm. With
"Force game windows fullscreen" fighting a game's own resolution
(DiRT 3 at 1280x720), the game recreates its swapchain many times a
second and Xwayland piled up ~2,000 old swapchain images (6+ GB) in
seconds until Android killed the session. A preload trace on the device
put 2,090 of 2,118 open dma-buf descriptors in Xwayland on
drmPrimeFDToHandle called from Zink.

Handle GEM_CLOSE on the KGSL node in drmIoctl, and check the three DRM
symbols are exported in CI.

Co-authored-by: The412Banner <[email protected]>
2026-10-05 12:44:16 -04:00
Kurt Himebauch 0d411b4cb6 Add app UI scale presets to Setup (#272) 2026-10-05 11:25:51 -04:00
Kurt Himebauch 2ca051fe71 Fix MangoHud double-counting battery power and remaining life (#270) 2026-10-05 15:07:40 +00:00
Kurt Himebauch 356208ca9e Compact Steam, Desktop, and Setup settings headers (#268) 2026-10-05 10:34:31 -04:00
Kurt Himebauch d3a83befaa Keep the game undimmed while the session menu is open (#267)
Remove background dimming from the session menu
2026-10-05 09:59:35 -04:00
Kurt Himebauch 8cbed6b6da Prepare 0.3.1 release (#263)
* docs: draft 0.3.1 release notes

* chore: bump version to 0.3.1 (11)
2026-10-05 07:46:36 -04:00
MaxsTechReview 06615ffffa Fix controller rings on linked data dirs, route rumble to the controller, capture the mouse for pointer locks (#242)
- #166: export the fake-input ring paths with the spelling the session tree is bound under
  (Context.getFilesDir(), /data/user/0/...) instead of the canonical path. Where /data/user/0
  resolves to /data/data (ColorOS 16) the guest has nothing bound at the canonical prefix, so
  libfakeinput could not open the rings and every pad, physical and on-screen, was dead. The ring
  path logic moves to FakeInputRingPaths so it can be unit-tested without the native library.
- #219: play a game's rumble on the physical controller that last drove the pad, when Android
  exposes its motors (strong and weak separately on Android 12+), and fall back to the device's
  vibrator for the on-screen pad or a controller without rumble. PadBridge records the active
  controller; the on-screen pad taking over moves rumble back to the phone.
- #153: when a program takes a pointer lock (a mouse-look game through gamescope), capture a
  physical mouse and send its raw deltas as relative motion, so the camera no longer stops where
  Android's pointer meets the screen edge. Capture is released for the drawer, the PC keyboard,
  picture in picture, focus loss and pause, and when the lock ends.
2026-10-05 07:33:33 -04:00
Kurt Himebauch cb9038b6a0 Clarify display settings and organize in-session controls (#247)
* Clarify display sizing, game fit and scaling controls

* Replace aspect and resolution controls with one dimension menu

* Organize Steam session settings into tabs

* Keep session resolution out of the live controls panel

* Group live session controls and add a conditional second-screen tab

* Refine session sliders, tab icons and contextual controls

* Simplify screen effects with presets and collapsed advanced controls

* Give in-session tab icons more spacing

* Center the loading cancel button glyph
2026-10-04 23:32:30 -04:00
MaxsTechReview 4c29c6a61e Fetch the Steam client again when it never shows up (#225)
A client stopped part way through updating itself can be left unable to
draw anything. Its files and our install stamp are still on disk, so the
installer never looked at it again and every session sat on "Waiting for
Steam" with nothing retried.

The session now adds a line to package/droiddeck-unconfirmed each time it
starts the client, and the app removes the file on the client's first
frame. After two starts in a row with nothing shown, the installer:
- drops Steam's update manifests, the stamp, .crash and steam.pid
- deletes package zips whose sha1 no longer matches their name
- downloads the client again over the old one

steamapps, config and userdata are left alone. An automatic repair runs
once per failure, until a start is seen again.

The Steam page's Client settings get a "Repair Steam client" item that
queues the same repair for the next start. The request is only cleared
once the download completes, so an interrupted repair is retried.
2026-10-04 21:33:16 -04:00
Kurt Himebauch a554db0fb4 docs: record Thor Steam SD allocation findings (#240) 2026-10-04 20:15:24 -04:00
Kurt Himebauch 4fe5fbab44 Add Decky plugin ZIP import (#79)
* Add Decky plugin ZIP import

* Match Decky plugin identity and loader settings on ZIP import

* Use the shared focus ring for the Decky toggle
2026-10-04 18:09:46 +00:00
MaxsTechReview eb6268f239 Start every title with a Valve pick on the Compatible Proton (#224)
The client runs in Steam Deck mode, where a title's "Default" is not the
"0" mapping but Valve's own pick for it: the Steam Deck compatibility
recommended_runtime (proton-stable, a pinned Proton, or native for the
Steam Linux Runtime) or an app_mappings entry in the SteamPlay manifests.
A title the session had not mapped yet - not installed, or installed
while the client was running on a build without the live helper - showed
"Default (Proton Stable)" and launched with a Proton that cannot run
here.

Every playable Windows title in the appinfo cache with such a pick is now
mapped to the default tool up front, before the client starts and in the
live helper, the same way installed titles are. A runnable tool the user
picks for one of them is kept, and they follow a change of the default.
2026-10-04 13:44:10 -04:00
MaxsTechReview 240d766400 Start fresh installs on droiddeck-esync (#223)
In the Valheim and PEAK runs esync used the least CPU per frame. fsync
stays available, and esync still hands a Proton build without a pack to
droiddeck-fsync until its pack is published. A sync someone already picked
is kept.
2026-10-04 12:55:32 -04:00
MaxsTechReview 27782068a3 ESync, FSync, NTSync + OpenNow + FEX for Desktop Apps (#197)
* Make the Compatible Proton ARM64 the default and label compatible builds

The CompatToolMapping "0" default always points at DroidDeck's Proton
ARM64 tool, and per-game picks of adopted GE/CachyOS builds are kept,
including when the shortcuts writer remaps.

The tool is named after the Proton build it runs, Proton Experimental
ARM64 (Compatible), or Proton 11.0 ARM64 (Compatible) once that newer build
is installed. Adopted builds carry the same Compatible mark, read from a
string resource the app stages into the runtime, and the Proton page
explains which builds have the prefix fixes.

The registrar is renamed steam-compatibility and the tool
droiddeck-proton-arm64. Existing installs migrate on the next session: the
old tool directory is removed, every mapping that named it moves to the new
tool, and the retired bannerlator-steam-compat script is removed from the
runtime. CI and local packaging include the renamed script.

* Drop the duplicate R import left by merging main

* Add userspace ntsync for Proton on Android

Android kernels have no /dev/ntsync and its app policy refuses futex_waitv,
so every Proton 11 build here fell back to wineserver for every wait.
libblsession now serves /dev/ntsync itself: wineserver opens a memfd region,
each sync object is a timerfd that carries its slot, and the kernel ntsync
ioctls are answered in shared memory with futexes, following the kernel
driver's wake order. Valve's Proton Experimental ARM64, GE-Proton and
proton-cachyos use it unchanged.

Locks record their owner so a killed process cannot wedge the others,
signals are held off inside each critical section, dead processes and
exiting threads are swept, and object lifetimes follow close, dup2, dup3,
close_range and received descriptors.

Fast sync is on by default under Performance > Session fixes, and
PROTON_NO_NTSYNC=1 turns it off per game.

* Add droiddeck-esync as the default sync for Proton on Android

Without a kernel ntsync driver, Proton 11 builds here fell back to
wineserver for every wait. droiddeck-esync brings eventfd and shared-memory
sync to the exact Proton build a game runs on: a pack holds that build's
ntdll.so and wineserver with the patch series in tools/droiddeck-esync
applied, and the guest runs Proton from a shadow tree that links the stock
tool and swaps in those two files. A wineserver from a pack still prefers
/dev/ntsync when a kernel driver or droiddeck-ntsync is there.

Packs are built in Valve's SDK image from each release's own wine source
(Proton Experimental ARM64 tags, GE-Proton and proton-cachyos releases); the
control build must reproduce the release binaries byte for byte, and a
signed index lists the packs. The APK bundles the packs pinned in
release.env, the app fetches newer ones, and Steam's compatibility tool
picks the pack for each Proton build, so new Proton releases get packs
without an app update.

The ntdll side reads, writes and polls its eventfds with raw system calls,
so the guest's LD_PRELOAD wrappers stay out of the sync path. The server
abandons a mutex once a thread killed by TerminateThread has really exited
and when its process dies, and a mutex's owner and recursion count change
in one atomic step, so a killed owner cannot touch the next owner's count.

droiddeck-esync is on by default and PROTON_NO_ESYNC=1 turns it off per
game; droiddeck-ntsync, the userspace ntsync, is now an opt-in toggle.

* Watch Valve's Proton releases and build droiddeck-esync packs for them

A scheduled workflow reads the Proton ARM64 builds Steam ships (anonymous
steamcmd app info) and the upstream source tags, compares them with the
previous watch state and starts the pack build for any build that has no
pack yet. The pack build applies the patch series with --fuzz=0 and runs a
smoke test of each new pack against the esstress suite before it is
published.

* Add droiddeck-fsync, keep Steam and the app on one Proton, fix DirectAudio

droiddeck-fsync: libblsession answers futex_waitv(2), which the app
sandbox refuses, in userspace, so Proton's own fsync runs on the stock
Proton files with no pack. Waiters register the words they wait on in a
table beside the fsync shared memory and sleep on a futex of their own;
FUTEX_WAKE on the fsync memory wakes the registered waiters. Slots carry
one owner word that the sweeper and the owner claim with a CAS, and the
mapping table is rebuilt under a lock held across the real munmap, so a
reused address or a recycled slot cannot lose a waiter. It stands in for
a Proton build without a pack (droiddeck-ntsync where fsync is off), takes
over for games with esync off, and the "droiddeck-fsync first" toggle
(BL_FSYNC_FIRST, also per game) puts it ahead of the packs. The games
Proton keeps off fsync and esync never get it. A launch into a prefix
whose wineserver is still running keeps that server's sync choice.

One Proton for Steam and the app: the Components page's Proton is the
default Steam uses, applied live through the client while Steam runs
(droiddeck-steam-compat) and at the next start otherwise; a choice made in
Steam's menu comes back to the app. Requests are ordered by a sequence that
never goes backwards, and the page no longer jumps back to the default
after a swap or restore.

DirectAudio: the wrapper wrote the driver key with its backslashes
stripped, so no game ever used DirectAudio. Wine 11 also loads a builtin
only from the prefix, so the driver is now linked into system32 and
syswow64 before "directaudio,pulse" is selected; mmdevapi falls back to
winepulse whenever DirectAudio cannot start.

* Pick esync, ntsync or fsync from the Steam settings' Games group

A three-way tab row above the FEX preset sets the sync Proton games use. It
writes the existing switches together (droiddeck-ntsync, droiddeck-fsync
first, droiddeck-esync), so the Performance toggles and the tabs always agree,
starts on esync and keeps the choice until it is changed again.

* Make droiddeck-fsync the default sync for Proton games

A fresh install, or one that never picked a sync, starts on fsync; a choice made
in the Steam settings' Games group still sticks. Games Proton keeps off fsync
still fall back to droiddeck-esync.

* Add wineserver to the Wine sync tabs and order them ntsync, fsync, esync, wineserver

wineserver turns every in-process sync off so games wait through wineserver;
picking any other tab turns sync back on. The tabs shrink to fit a narrow pane.

* Shorten the Wine sync description

* Answer openat2 and execveat for FEX in the session preload

FEX opens every file of its x86 rootfs with openat2(RESOLVE_IN_ROOT) and
only falls back to the host path on EXDEV. Under proot on Android openat2
answers ENOSYS, so each lookup of an x86 library silently went to the host
and missed. The exported syscall() now turns openat2 into openat once the
real call reports ENOSYS: RESOLVE_IN_ROOT paths are taken relative to the
root fd (FEX has already resolved the links inside the rootfs), and an
absolute path under RESOLVE_BENEATH fails with EXDEV as the kernel would.

execveat with an absolute path, or relative to AT_FDCWD, and without
AT_EMPTY_PATH or AT_SYMLINK_NOFOLLOW, is plain execve; it is sent as such
because proot refuses execveat with a directory fd, which FEX uses when it
re-executes itself for a child x86 program.

* Run x86 Linux games and programs through FEX

Steam: Linux-only titles that have no ARM64 build (from the client's
appinfo cache: type game, demo, application or beta) are mapped to a new
compatibility tool, droiddeck-fex ("Linux x86 (FEX)", linux to linux,
no required runtime). Valve's own "fex" tool needs pressure-vessel, which
cannot run under proot, so a mapping to it or to a Steam Linux Runtime is
replaced; any other pick the user made stays. Titles that also ship for
Windows stay on Proton. When such a title is installed and FEX or the
sniper runtime is missing, the client is asked to install them.

droiddeck-fex runs the program under the FEX that Steam installs (app
3127680), with Steam Linux Runtime 3.0 (sniper, app 1628350) as the x86
rootfs:
- the sniper platform is linked in place from its usr-mtree (relative
  links only, files/usr -> .), once per runtime version;
- the x86_64 and i386 builds of libblsession and libfakeinput go into the
  runtime's lib/<triplet>/droiddeck and its etc/ld.so.preload, mirroring
  the session's own list, so x86 programs get the pads and the session
  fixes (FEX passes their system calls straight to the kernel, so the
  ARM64 preloads cannot reach them);
- Config.json sets RootFS and the GL and Vulkan thunks, so x86 games draw
  with the device's Turnip and Mesa;
- the FEX preset and the game's own environment from the Steam settings
  apply, and ARM64 entries are dropped from LD_PRELOAD/LD_LIBRARY_PATH.
It also links ~/.steam/sdk64 and sdk32 to the client's x86 steamclient
when the client has one and nothing is there yet.

Desktop: x86_64 and i386 AppImages can be imported now; they unpack
through FEX (or unsquashfs) and record their architecture. Scripts,
AppImages and programs started in a run session use FEX automatically
when they are x86, and each added app has a "Use FEX" Auto/On/Off choice
in its edit dialog. The launchers' "still running" checks also look at
FEX processes, whose exe is FEX rather than the program. A GitHub release
without an ARM64 AppImage falls back to its x86_64 one. Decky's x86
loader starts through droiddeck-fex instead of the fex-mesa rootfs path,
which no runtime ships.

The x86 preloads are built against glibc 2.31 (Debian 11) so they load
in sniper: tools/linuxfs/build-x86-preloads.sh, run in CI and by
build_local.sh. fakeinput's stat hooks fall back to __xstat on a glibc
without a stat export, and mmap64 takes off64_t for the i386 build.

* Keep a FEX unixlib from outliving its DLL

A FEX package or original bundle without the aarch64-unix helpers left
the previous version's libarm64ecfex.so / libwow64fex.so in the Proton,
so the new DLLs loaded a unixlib built for another FEX. A swap now removes
the helpers the package does not carry; FEX then runs DLL-only, which it
already falls back to when Wine cannot load the unixlib.

* Fix the Wine sync choices an audit found

- wineserver tab: it passed Proton's environment through, so a build with
  esync built in (GE, cachyos) still ran esync and a kernel ntsync would
  still be used. It now sets PROTON_NO_ESYNC, PROTON_NO_FSYNC and
  PROTON_NO_NTSYNC (unless the game's own environment sets them).
- ntsync tab: a Proton whose wineserver has no /dev/ntsync client got
  wineserver-only sync although fsync would work. The hash cache records
  whether the wineserver has one; without it the launch goes to
  droiddeck-fsync, and no stand-in or pack turns droiddeck-ntsync on.
- A build with esync built in returned before the per-game fallbacks, so
  a game that turns esync off ran with no in-process sync. It now gets
  droiddeck-fsync, or droiddeck-ntsync where fsync is off too.
- When a pack's proton cannot start and the stock one is used, or the
  selection fails, the prefix's record now says stock, so a second
  launch into the same running prefix no longer picks the pack's ntdll.
- droiddeck-fsync: its mutex is held across fork and re-made in the
  child, a closed or replaced shm fd is forgotten (close, dup2, dup3,
  close_range, closefrom), and a waiter woken while it was leaving on a
  timeout or signal reports the wake instead of losing it.
- BL_SYNC_MIN_NOFILE applies per game to every verb, debug logs carry
  prefixes.json, the device report names the chosen sync instead of the
  three raw switches, and PATCHES.md and the proot notes describe the
  selector, the fsync default and droiddeck-fsync's futex_waitv.

* Name the runtime's scripts and files droiddeck, migrating old installs

The guest scripts are droiddeck-* now (droiddeck-session and the other 19),
as are the files the app and scripts generate (etc/droiddeck, the network
state files, the components dir, the Steam install stamp, the redist marker,
the extra-Proton wrapper and backups) and the compositor's own names
(libdroiddeckwayland, droiddeck_* sources, DROIDDECK_* switches, the
wl_output and seat names).

Installed runtimes carry on: LegacyNames moves etc/bannerlator and
share/bannerlator over and leaves relative links at the old paths before
every proot command, turns any old script into a link to its new name,
moves the generated files and points menu entries at the new scripts.
The session scripts and steam-compatibility move their markers on first
use, the components dir's desired.tsv is rewritten with it, and the
runtime counts as installed under either session script.

Kept: the banner_ahb_v1/banner_desktop_v1 wire names and BANNER_AUDIO_DIRECT
(read by prebuilt Turnip and Wine builds), Banners-Turnip and other
third-party names, BL_* switches, libblsession, and credits to Bannerlator.

* Unpack x86 AppImages without FEX, then ask Steam for FEX

Importing an x86 AppImage failed when FEX was not installed yet ("needs FEX
to unpack and no unsquashfs is installed"). AppImages now unpack with
uruntime (VHSgunzo, MIT, v0.8.1 aarch64, pinned by sha256 and shipped
unmodified with its licence), which reads SquashFS and DwarFS images of any
architecture natively. FEX's own --appimage-extract and unsquashfs stay as
fallbacks, and an ARM64 image whose own runtime cannot unpack it goes the
same way.

An x86 import with FEX missing records that FEX is wanted, so the next
Steam session requests FEX and the Steam Linux Runtime, and the program's
page says FEX is not set up yet and how to get it.

* Run Vulkan Qt Quick AppImages on Wayland

OpenNOW (Qt 6.8, Qt Quick on Vulkan) closed within two seconds in a run
mode. The session sends toolkits to gamescope's Xwayland, and Qt's xcb
backend only presents with Vulkan when Mesa's X11 WSI does, which needs
DRI3; Xwayland on KGSL has none, so Qt found no queue that can present and
aborted ("Failed to create RHI").

An AppImage that bundles Qt Quick, the Vulkan loader and Qt's Wayland
plugin now runs on gamescope's Wayland socket (the desktop's, outside
gamescope), where Turnip presents as it does for games, with Qt's xcb
backend as the fallback. SDL_VIDEODRIVER and XDG_SESSION_TYPE follow, so
helpers that pick their window system from them (OpenNOW's streamer) agree
with Qt. Everything else stays on X11; a qt file in the app's directory
(wayland or xcb) overrides the choice.

* Keep gamescope's WSI layer away from Qt apps on its Wayland socket

OpenNOW still closed at once: its FFmpeg (the Raspberry Pi fork, libavutil
61) creates its Vulkan queues internally synchronized whenever the driver
offers VK_KHR_internally_synchronized_queues, which DD-Turnip does. The
gamescope WSI layer registers every queue of a new device through
vkGetDeviceQueue, which only returns queues created without flags, so it
got VK_NULL_HANDLE and vkroots aborted on assert(obj) before Qt opened a
window.

Programs that draw on gamescope's Wayland socket do not need the layer,
which only carries X11 windows past Xwayland, so it is turned off for
them. OpenNOW bundles no Vulkan loader (it reaches Vulkan through Qt and
dlopen), so a binary that uses QVulkanInstance counts as a Vulkan app too,
and a Qt Quick app left on X11 says why in the session log.

* Keep the end of the session log, and say when FEX is missing

The session sends its output through head -c into session.log. head
writes through stdio, so it held up to 4 KB that proot's --kill-on-exit
threw away when the guest ended: every log stopped mid-line at a 4096-byte
boundary, losing exactly the lines that say why a program quit (an x86
AppImage's "x86 Linux programs need FEX" among them). dd writes each read
as it comes and keeps the same cap.

A run mode always ends with status 0 (gamescope does not pass its child's
on), so a program that could not start just closed. When the log says FEX
is missing, the ended screen now says so and how to get it.

The app reads FEX's readiness from status.json, which only a launch or an
import wrote, so it still said "not set up" after Steam installed FEX.
droiddeck-fex status rewrites it, and a Steam session ends with it while
FEX is wanted. FEX is also looked for at the tool's root and under files/,
and a tool folder without it lists what it holds.

* Let FEX and its runtime install runnable on an SD card library

A Steam library on shared storage loses the executable bits, so native
tools are staged in private directories bound over it; only Proton and
SteamLinuxRuntime folders were. FEX (3127680) installed there could never
run, droiddeck-fex saw no FEX and kept asking Steam for it. FEX and the
sniper runtime (1628350) now get private directories from their first
download, whatever their install folder is called.

* Keep fsync off in the pack smoke test

A runner whose kernel allows futex_waitv started stock Proton with fsync,
so the server-side baseline never said what the test waits for and every
pack was waved through as untestable; the esync run would have measured
fsync too.

* Judge a pack against stock's own esstress failures

Stock Proton fails esstress's dup wait all under server-side sync as well
(a Wine bug, seen on the phone too), and any stock failure made the test
call the runner unusable. A pack now fails only when it crashes, hangs or
fails a check stock passes.

* Ask Steam for FEX and its runtime one at a time

Both steam://install requests were started together, and the running client
took only the first: FEX installed, the sniper runtime FEX needs never did.
Each request now waits for the client to take it before the next.

* Bundle the first published droiddeck-esync packs in the apk

* Log how an x86 program under FEX ends

FEX keeps its own errors quiet unless asked, so a program it could not run
left nothing in the session log. Its log now goes to stderr, and the AppImage
launcher records the program's exit status or the signal that ended it.

* Show the log a failed AppImage program wrote

Programs like Eden stop themselves on a failed check and say why only in
their own log under the home folder. When the program ends badly, the end of
any log it wrote during the run is copied into the session log.

* Report where an x86 program under FEX hit an illegal instruction

A tiny x86_64 preload, built without libc, catches SIGILL and writes the
address, the instruction bytes and the mapping they belong to before the
program dies as it would have. droiddeck-fex preloads it into x86_64 programs.

* Load the fault reporter from the x86 runtime

Programs under FEX look libraries up in the x86 runtime, so the reporter is
copied there with the other preloads and named by its path inside it.

* Hand FEX its x86 runtime and thunks through the environment

Valve's FEX runs portable and reads its config beside itself, so the
Config.json droiddeck-fex writes was not seen: programs ran without the
runtime. The runtime, thunk libraries and thunk list now also go in FEX's
own environment variables, and each launch says what it was given.

* Use the sync pack built for the installed Proton release

Some Proton releases ship byte-identical ntdll.so and wineserver (Valve
11.0-2b/2c, CachyOS 0702/0703), so a pack built for a newer release
matched an older install exactly and the older install never fetched its
own pack. A pack from a newer release no longer counts as an exact match;
among matches the one built for the installed version wins, then rev.

The app ranks the pack a tool already uses the same way and only fetches
a pack that ranks higher, reads the release epoch from tools.tsv and
refetches when tools.tsv changes. A prefix whose server is still running
re-checks its recorded pack on waitforexitandrun and starts fresh when a
better one now applies.

* Keep published sync packs and revocations consistent

The publish step never replaces a pack that has an entry beside it; a
pack left without its entry by an interrupted run is replaced so the
build stops repeating. Discovery counts revocations the signed index does
not show yet, including superseded packs only the flavor releases list,
so the index is signed again. The release watch always builds every
flavor.

* Fetch the droiddeck-esync packs from DroidDeck-Components

The packs, the signed index and the apk bundle now live in the releases
of Droid-Deck/DroidDeck-Components, together with the pipeline that
builds them, so this repository keeps only the app and the guest code
that consumes them. The app's index and asset URLs point there,
release.env names the repository its bundle comes from, and the apk
builds drop bundled packs that the published index revokes.

* Stop the apk build when the published sync index cannot be read

* Ask Steam for one FEX dependency at a time

The client shows a confirmation dialog for each steam://install, and a
second request replaces the first dialog, so FEX was never installed
when the runtime was requested right after it. The next dependency is
now requested once the previous one has an app manifest.

* Use generic names in the AppImage test fixtures

* Give x86-64 apps under FEX the Vulkan thunk

Apps that bring their own glibc and Vulkan loader skipped FEX's thunk
overlay and loaded their bundled desktop Mesa drivers, which crash on the
phone's GPU during Vulkan setup. droiddeck-fex now adds an x86-64 audit
module when a program's bundled glibc and libstdc++ meet the thunk's
version needs. The module sends libvulkan.so.1 lookups to a nodelete stub
that needs FEX's thunk path. That keeps the thunk mapped when an app
unloads Vulkan and loads it again, since FEX keeps its first trampoline
links and the old addresses get reused.

The fault reporter now restores the default action before re-raising,
because FEX ignores SA_RESETHAND and the handler looped.
2026-10-04 12:43:57 -04:00
starwishHi 84bc7d9948 Add Korean,Japanese and Traditional Chinese(Taiwan,Macau and Hongkong)translate (#216) 2026-10-04 12:13:13 -04:00
Kurt Himebauch b2bde1c14d Add opt-in Steam storage diagnostics (#158) 2026-10-04 08:19:48 -04:00
Magarto 32ea22e033 Start .NET and Godot games under FEX without per-game setup (#168) 2026-10-04 08:19:11 -04:00
The412Banner 8502fc517b Effects page in the session menu: scaling modes, screen effects and texture filtering (#198) 2026-10-04 08:17:00 -04:00
Dantalian 986682167e l10n: add Simplified Chinese (#202) 2026-10-04 11:21:24 +00:00
Kurt Himebauch e9204c5e49 Add controller and desktop quality-of-life options (#194) 2026-10-04 00:56:09 -04:00
Kurt Himebauch 6c1681c664 Add Touch Off and compact session menu shortcuts (#196)
Add touch off mode and compact session menu shortcuts
2026-10-04 04:37:56 +00:00
Kurt Himebauch 6b34c57c15 Fix automatic Picture in Picture after session handoffs (#195)
Preserve session readiness for automatic picture in picture
2026-10-04 04:24:25 +00:00
Kurt Himebauch c176a0d6fa Restore 500 ms Back double-press window (#193) 2026-10-04 03:40:10 +00:00
Kurt Himebauch f38def4f4c Remove redundant in-session Desktop button (#188) 2026-10-03 23:00:02 -04:00
MaxsTechReview 8edf05b19a Add the droiddeck-esync pack pipeline (#189)
Builds esync packs for new Valve, GE and CachyOS Proton 11 releases on arm64
runners, smoke-tests them against stock with esstress, and on main publishes
each pack and a signed index as non-latest releases. A watcher checks Steam
and the GE/CachyOS releases every 20 minutes and starts a build for new ones.
The app side lands separately with feat/userspace-esync-ntsync.
2026-10-04 02:58:55 +00:00
Kurt Himebauch 3a8f4889e9 Empty library intro (#182) 2026-10-03 22:27:24 -04:00
Kurt Himebauch bc00bd9919 Fix runtime removal and first-run Steam setup (#185)
* Fix runtime removal races and show removal progress

* Bootstrap Steam from the selected client channel

* Show runtime progress below its Setup row

* Always stage current session helpers and verify APK assets

* Initialize Steam UI scale once using the handheld default
2026-10-03 22:03:26 -04:00
Kurt Himebauch abbbc4a88a Add game file exports and sync for Android frontends (#179) 2026-10-03 21:29:01 -04:00
Magarto e168f7e942 Give protonfixes a readable /proc/sys/fs/file-max (#169) 2026-10-03 15:26:20 -04:00
Kurt Himebauch 59c74e586d Add standard session picture in picture (#156)
* Add session picture in picture controls

* Simplify PiP to standard Android controls

* Remove PiP testing document
2026-10-03 15:06:16 -04:00
Kurt Himebauch 2224c96498 Merge pull request #172 from jmarti326/fix/steam-sleep-resume-black-screen 2026-10-03 14:28:24 -04:00
Kurt Himebauch 39e5ae6341 Merge branch 'main' into fix/steam-sleep-resume-black-screen 2026-10-03 14:18:44 -04:00
Kurt Himebauch ba18b70f59 Merge pull request #159 from Droid-Deck/preview-change-history
Show Preview update history
2026-10-03 13:36:00 -04:00
xXJsonDeruloXx b475a6940a Merge remote-tracking branch 'origin/main' into preview-history-status-card 2026-10-03 13:30:13 -04:00
xXJsonDeruloXx 3f3751804f Move Preview history below the update action 2026-10-03 13:27:09 -04:00
Juan Marti Mercado 9647a38d83 Fix Steam sleep resume handshake 2026-10-03 12:08:40 -04:00
Kurt Himebauch 7a94c75732 Merge pull request #157 from Droid-Deck/game-launcher-shortcuts 2026-10-03 12:04:11 -04:00
xXJsonDeruloXx 3c2064f30c Add Preview update history 2026-10-03 00:06:09 -04:00
xXJsonDeruloXx 37316f4677 Add game launcher shortcuts 2026-10-02 23:49:26 -04:00
Kurt Himebauch abdc46c7a7 Merge pull request #128 from magarto/upscaler-setting
Expose the compositor's upscaler (FSR 1 / GSR / NIS) as a session setting
2026-10-02 23:29:01 -04:00
Kurt Himebauch a8f421c396 Merge branch 'main' into upscaler-setting 2026-10-02 23:25:35 -04:00
Kurt Himebauch c215c045af Merge pull request #155 from Droid-Deck/fix/steam-idle-wake
Fix Steam idle sleep and Android session wake
2026-10-02 21:44:13 -04:00
xXJsonDeruloXx 5a38ff09bf Merge remote-tracking branch 'origin/main' into fix/steam-idle-wake 2026-10-02 21:43:52 -04:00
Kurt Himebauch d24800ae99 Merge pull request #146 from Droid-Deck/fix/deck-controller-discovery 2026-10-02 21:34:08 -04:00
xXJsonDeruloXx a25800149d Restore Steam after idle sleep through session pause and login1 wake 2026-10-02 21:32:12 -04:00
Kurt Himebauch 9fc378579c Merge branch 'main' into fix/deck-controller-discovery 2026-10-02 20:51:17 -04:00
xXJsonDeruloXx 9645032d2d Trim Deck discovery to mount-ID compatibility fix 2026-10-02 20:35:28 -04:00
xXJsonDeruloXx 7cc3e31889 Recover Deck discovery mount IDs on older kernels 2026-10-02 19:08:01 -04:00
Kurt Himebauch 620db5a8ef Merge pull request #151 from Droid-Deck/feat/android-clipboard 2026-10-02 17:47:25 -04:00
xXJsonDeruloXx 41a72ca6a1 Record visible Android copy and Steam paste test on Thor 2026-10-02 17:38:16 -04:00
xXJsonDeruloXx e23cada4ea Add Android clipboard text paste into Steam sessions 2026-10-02 17:29:41 -04:00
Kurt Himebauch 98ab37c638 Merge pull request #150 from Droid-Deck/feat/steam-native-network
Show Android Wi-Fi and cellular networks in Steam BPM
2026-10-02 16:16:46 -04:00
xXJsonDeruloXx f7996275be Merge remote-tracking branch 'origin/main' into feat/steam-native-network 2026-10-02 16:11:04 -04:00
xXJsonDeruloXx 8121a2e07e Add optional Wi-Fi discovery permission flow in Steam settings 2026-10-02 16:05:47 -04:00
xXJsonDeruloXx da6b7993ef Expose Android Wi-Fi names and nearby networks to Steam 2026-10-02 15:39:49 -04:00
xXJsonDeruloXx 2d72507dfc Report Android Wi-Fi and cellular connections to Steam 2026-10-02 15:05:03 -04:00
Kurt Himebauch ed1cdd4905 Merge pull request #147 from Droid-Deck/controller-overlay-toggle 2026-10-02 14:56:02 -04:00
xXJsonDeruloXx 47321773ea Release controls when touch is cancelled 2026-10-02 13:48:19 -04:00
xXJsonDeruloXx cec15326eb Add quick toggle for on-screen controls 2026-10-02 13:28:13 -04:00
xXJsonDeruloXx c6c74fb67d Preserve native HID discovery and trace Deck enumeration 2026-10-02 12:00:12 -04:00
xXJsonDeruloXx f1fb3026e9 Fix Deck controller discovery across sysfs layouts 2026-10-02 10:45:25 -04:00
Magarto 85ed4b952b Merge branch 'main' into upscaler-setting 2026-10-02 15:38:57 +02:00
Kurt Himebauch 40b1163330 Merge pull request #142 from Droid-Deck/xXJSONDeruloXx-patch-1
Update README.md
2026-10-02 01:55:28 -04:00
Kurt Himebauch 488f6aac22 Update README.md 2026-10-02 01:55:15 -04:00
Kurt Himebauch 8a547c9d5c Merge pull request #141 from Droid-Deck/release/0.3.0
Release 0.3.0: version bump, Deck mode for everyone, release notes and media, Updates page fix
2026-10-02 01:39:31 -04:00
xXJSONDeruloXx 41c8bebab0 ci: publish the GitHub release from the Release APKs run, with the notes from docs/releases 2026-10-02 01:34:41 -04:00
xXJSONDeruloXx ab3141e211 discord link and cleanup 2026-10-02 01:32:12 -04:00
xXJSONDeruloXx 71a7955c3c docs: drop the unused overlay GIF and the 'as above' it left behind 2026-10-02 01:16:04 -04:00
xXJSONDeruloXx 4282154b71 docs: in-game GIF for the faster-in-games highlight 2026-10-02 01:15:25 -04:00
xXJSONDeruloXx deb1c4cfd7 docs: GPU drivers shot with DD-Turnip in use 2026-10-02 01:03:16 -04:00
xXJSONDeruloXx 4a027cc68c docs: dual-screen Deck controller and Stop GIFs, Deck mode for everyone in the 0.3.0 notes 2026-10-02 00:59:48 -04:00
xXJSONDeruloXx 2ae8d8fd94 feat: move every install to Deck mode once, so updated installs get it too 2026-10-02 00:59:48 -04:00
xXJSONDeruloXx b7d15061b1 docs: show added apps on the Desktop page in the 0.3.0 notes 2026-10-02 00:49:37 -04:00
xXJSONDeruloXx 614d1cdf54 docs: 0.3.0 release notes with screenshots and GIFs 2026-10-02 00:47:35 -04:00
xXJSONDeruloXx 667a48a6e9 fix: say Can't install, not Update available, for a build Android would refuse 2026-10-02 00:47:35 -04:00
xXJSONDeruloXx da5d7a7825 release: 0.3.0 2026-10-02 00:47:35 -04:00
Kurt Himebauch fbe5c729f2 Merge pull request #140 from Droid-Deck/chore/ui-cleanups
UI cleanup for 0.3.0: motion, focus ring, copy and game environment editor
2026-10-02 00:22:26 -04:00
Kurt Himebauch 7403f457ed Merge branch 'main' into chore/ui-cleanups 2026-10-02 00:18:04 -04:00
xXJSONDeruloXx 1f8da82c19 frontend: draw the Steam wall on the first frame and keep it still
The wall waited about a second and a half for the game list on every start: Steam's own
manifests are quick, but the added games' folder walk and art lookups are not. The last list
is now kept in the app's files (LibraryCache) and shown before the first frame, and the fresh
one replaces it in a single update. The wall's order is fixed (last played, then game ID), so
the same games always land in the same capsules and the refresh moves nothing. The added-games
scan on every resume also leaves the main thread, which it stalled for most of a second.
2026-10-02 00:12:41 -04:00
xXJSONDeruloXx aca6eae55c ui: every way out of a session leaves as a session arrives
Backing out of the loading screen, closing the ended screen and the session ending on its own
used the slide-down card exit. Now, while the session is on screen, the blue spreads out of
the throbber's ball (the middle of the screen once a game is up) until it covers the screen,
and the front end opens on it and draws it back into the button the session came from, as it
already did after the drawer's Stop. A retry, a session closing off screen and animations off
still close at once; a timeout closes it should the flood never run.
2026-10-02 00:05:57 -04:00
xXJSONDeruloXx c89741cb7d i18n: add Spanish
All string resources get a Spanish translation except brand names and strings that are only a
format. The app declares English and Spanish in a locale config, so it is offered in Android's
per-app language setting and follows the system language otherwise. Short rail labels keep
to the rail's width (Configuración, Actualizar).
2026-10-01 23:56:02 -04:00
xXJSONDeruloXx 4b223a10ce ui: the Games page keeps its game while it animates out
It was drawn with the current selection, so leaving it for another rail item turned the
outgoing page into a copy of the new one for a few frames, which then faded out before the
real page rose in: a flash and a dip moving from Games to Steam.
2026-10-01 23:47:50 -04:00
xXJSONDeruloXx ca4ddd74de ui: game environment editor works with a pad, on/off variables use the dropdown
Opened with a pad, the editor and its variable dialog put focus on their first control; as
windows of their own they otherwise gave the pad nothing to move from. On/off variables pick
from their two values in the same dropdown as other choices instead of a switch. App dialogs
keep the page's focus ring out of sight behind them. The footer's Add variable button only
appears once the settings are loaded: drawn disabled first, it came up without its box.
2026-10-01 23:37:41 -04:00
xXJSONDeruloXx c11c6744f5 ui: rebuild the game environment editor in the app's own style
The settings row gets a one-line hint and the same secondary button as every other row. The
editor moves from a stock Material alert, with a paragraph of explanation and a column of text
buttons, into the app's dialog: a profile chip with its menu, the variables in a panel with
each one's value as a chip whose menu changes, removes or restores it, and a footer to add a
variable, reset the profile or finish. Editing a variable uses the same dialog, its name and
value as chips or the app's text fields, and the variable's note in the footer.
2026-10-01 23:24:11 -04:00
xXJSONDeruloXx 3b8faad7af ui: paging the session drawer lands on the page's first option
LB, RB or a tab now start focus at the top of the new page instead of wherever it was last
left on that page; closing a menu or reopening the drawer still returns to where it was. On
Settings the top option is Android apps when DroidDeck is the Home app.
2026-10-01 23:10:01 -04:00
xXJSONDeruloXx b8f412e241 ui: long focus jumps travel as a solid dot and take shape on arrival
The ring fills in as it pinches to a dot, crosses as one round dot on a timed ease with no
stretch, and opens into the control as it gets there instead of settling as a circle first.
2026-10-01 23:02:37 -04:00
xXJSONDeruloXx 7d9010b4e9 ui: focus ring nits - held moves, far jumps, floods, selection rings
- A held direction runs the ring along as one piece and keeps its speed between steps;
  restarting each edge from rest (and the trailing edge's delay) made it stutter.
- A jump of more than 360dp pinches the ring to a drop, carries it over and opens it onto
  the new control instead of stretching a box across the screen.
- The ring keeps out of launch, stop and return floods (RingVeil) and appears on the
  focused control once they are done.
- Leaving a page by moving along the rail no longer pulls focus into the new pane; only
  backing out of a page hands focus to the control it was opened from.
- Selection is a radio dot or a tick, never a ring: the Updates channels and test builds,
  the GPU modes and driver pairs, the Games list and suggested app icons lose their
  persistent rings, and the GPU cards use the sliding ring for focus like everything else.
2026-10-01 22:57:32 -04:00
xXJSONDeruloXx 7435f51d25 ui: the cog floods its settings in as Play floods a session
The cog's tile stretches over the pane on the same loose springs as Play, far edges first and
blobby in flight, while the page behind sinks; the settings rise in once it is covered. Back
draws the tile down into the cog on critically damped springs. The focus ring stays out of
it and appears on the focused control once the motion is done, replacing the ring riding the
edge of a stretching box.
2026-10-01 22:44:32 -04:00
xXJSONDeruloXx af0f64b3de ui: the focus ring carries the page bloom, focus returns to its opener
The bloom is now the opening control's own rounded box stretching out to the pane, not a
circle. With a controller the focus ring rides its edge out to the pane's frame and springs
in onto the page's first control; on Back it rides the box back into the control, which
takes focus again. By touch, with no ring up, the bloom traces the same edge itself.

Closing a page used to leave focus on the page while it animated out, so nothing took it
back afterwards.
2026-10-01 22:37:47 -04:00
xXJSONDeruloXx ed57ce963c ui: bloom session settings out of the cog, open it without blocking
The page grows from the cog as a filled disc with an accent rim and folds back into it on
Back, while the pane under it sinks and returns. Pages also keep drawing while they leave
instead of falling back to the missing-page note for a frame.

Opening the settings no longer scans the added-games folders, reads the driver files and the
storage volumes on the main thread first: the page opens at once and those land while it
animates in.
2026-10-01 22:28:32 -04:00
xXJSONDeruloXx 3b9572d88e ui: move remaining launcher and second-screen copy to string resources 2026-10-01 22:15:45 -04:00
xXJSONDeruloXx 66e3ad812d ui: move content, games, GPU drivers and loading copy to string resources 2026-10-01 22:14:13 -04:00
xXJSONDeruloXx 383c647aaf ui: move store, updates, controller and child-process pages to string resources 2026-10-01 22:12:42 -04:00
xXJSONDeruloXx 1557bab30b ui: move settings, components, drawer and setup copy to string resources, tighten wording 2026-10-01 22:10:22 -04:00
xXJSONDeruloXx 6b45975596 ui: remove controller hints bar, route all motion through Motion
Compose already scales its animation clock by the system animator duration
scale, so Motion.tw no longer multiplies durations a second time; Motion.ms
stays for coroutine delays. The drawer tabs, launch flood and the default-spec
expand/fade transitions now go through Motion, keeping their current speeds.
2026-10-01 22:04:36 -04:00
Kurt Himebauch 8b8b9bb7c5 Merge pull request #138 from Droid-Deck/fix/second-library-steam-installs
fix: keep Steam-installed second-library games out of non-Steam shortcuts
2026-10-01 21:51:05 -04:00
Kurt Himebauch 49c8e84099 Merge branch 'main' into fix/second-library-steam-installs 2026-10-01 21:36:40 -04:00
Kurt Himebauch 963b3e02aa Merge pull request #139 from Droid-Deck/fix/pr-release-publish
fix: find the stable app release and accept fork builds when signing PR test builds
2026-10-01 21:35:02 -04:00
xXJsonDeruloXx d8f91db8f4 ci: keep component releases off latest and pre-release 2026-10-01 21:34:06 -04:00
xXJsonDeruloXx 6b13d8d2a7 fix: check PR test builds against main's versionCode instead of a release 2026-10-01 21:29:01 -04:00
xXJsonDeruloXx a8edc1a2a8 fix: find the stable app release and accept fork builds when signing PR test builds 2026-10-01 21:19:55 -04:00
xXJsonDeruloXx aa1c5bc780 fix: keep Steam-installed second-library games out of non-Steam shortcuts 2026-10-01 20:47:10 -04:00
MaxsTechReview e31801df35 Feature adding custom Linux applications (#130)
* Add your own apps to the Desktop page

An Add tile on Desktop takes a .sh script, an AppImage, a GitHub repo
(newest ARM64 AppImage release) or a Flatpak ID, with an optional name
and icon. Script folders are linked into the session, or copied when
they hold Linux programs (shared storage is noexec); folders outside
internal storage are bound at their own path.

Each app's page opens it under gamescope, edits its name and icon (with
icons suggested from its GitHub repo), checks a GitHub app for a newer
release and updates it in place, or removes it. Replaces the beta
AppImages section.

* Put Add at the end of the installable emulators

Installed emulators and added apps keep the top section, shown once there is one. Add closes the installable grid, or the installed one when nothing is left to install.

* Import DwarFS AppImages and their SVG icons

uruntime images built with sharun keep one program under several names
as hard links, which Android denies apps, so dwarfsextract aborted. The
extraction runs with proot's link2symlink, and each linked name becomes
a file of its own afterwards; their AppDir is followed through the
squashfs-root link.

An image's icon is a PNG of at least 128 px from the name its entry
gives, else its SVG (drawn with AndroidSVG), else the largest PNG, else
.DirIcon.

* Give AppImages the session's pads

sharun images start the app through their own ld.so, patched not to read
/etc/ld.so.preload, and drop LD_PRELOAD unless allowed, so the app never
loaded libfakeinput and saw no controller. The launcher now hands the
preload list over in LD_PRELOAD with SHARUN_ALLOW_LD_PRELOAD set.

Pad defaults also put Player 1 on the controller for yuzu-family
emulators, once, while it is still on the keyboard.

* Redesign the Add and Edit app dialogs

Source and appearance sit in two panels side by side, with a live
preview of the Desktop tile. A disabled primary button keeps an outline
so it still reads as a button.
2026-10-01 18:23:47 -04:00
Kurt Himebauch d506394f87 Merge pull request #113 from maxjivi05/feat/performance-fixes-2 2026-10-01 17:08:17 -04:00
Kurt Himebauch 47345dfd09 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 17:03:19 -04:00
Kurt Himebauch 404dfa19c8 Merge pull request #135 from Droid-Deck/fix/session-focus-overlay 2026-10-01 17:01:14 -04:00
maxjivi05 d09e7eaf32 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 16:40:42 -04:00
xXJsonDeruloXx 31f1c8ac0a Prevent full-screen Android focus highlight in sessions 2026-10-01 16:39:44 -04:00
Kurt Himebauch 7c982a7d65 Merge pull request #134 from Droid-Deck/fix/secondary-library-dependencies 2026-10-01 16:35:02 -04:00
xXJsonDeruloXx 68ce64dea6 Keep secondary-library prefixes and Steam tools on private storage 2026-10-01 16:11:19 -04:00
Magarto 074e4f090b Merge branch 'main' into upscaler-setting 2026-10-01 22:02:07 +02:00
magartus 61f31f502d Add the upscaler to the session drawer, applied live
The drawer's Display page gets the same Upscaler and Upscaler sharpness
choices as the session settings. A pick is saved and handed straight to
the compositor, so it takes effect on the next frame without restarting
the session.
2026-10-01 21:21:52 +02:00
Kurt Himebauch 647ab46659 Merge pull request #131 from Droid-Deck/fix/qam-keeps-game-focused 2026-10-01 15:03:34 -04:00
Kurt Himebauch a5d9e0a943 Merge branch 'main' into fix/qam-keeps-game-focused 2026-10-01 14:56:59 -04:00
xXJsonDeruloXx 33fe6edd0d chore: stage gamescope-3.16.29-p5 2026-10-01 14:53:28 -04:00
xXJsonDeruloXx df7ba1fead fix: skip the pointer warps that jolted the game as the QAM opened and closed 2026-10-01 14:43:30 -04:00
Kurt Himebauch db2f548b3c Merge pull request #119 from Droid-Deck/fix/adopted-storage-runtime-dir 2026-10-01 14:42:26 -04:00
Kurt Himebauch 42aff89ffd Merge branch 'main' into feat/performance-fixes-2 2026-10-01 14:38:33 -04:00
Kurt Himebauch 28bba54aa6 Merge branch 'main' into fix/adopted-storage-runtime-dir 2026-10-01 14:36:18 -04:00
Kurt Himebauch 267e8ec08a Merge pull request #127 from Droid-Deck/fix/mangoapp-drm-listing 2026-10-01 14:35:58 -04:00
Kurt Himebauch 7022c47c85 Merge branch 'main' into fix/adopted-storage-runtime-dir 2026-10-01 14:31:45 -04:00
xXJsonDeruloXx e8c69a89a2 fix: keep the game running behind the Steam menu and QAM, as on a Steam Deck 2026-10-01 14:31:30 -04:00
Kurt Himebauch 5cfed09e3b Merge branch 'main' into fix/mangoapp-drm-listing 2026-10-01 14:31:23 -04:00
MaxsTechReview d8acb25ae8 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 14:03:18 -04:00
xXJsonDeruloXx 2f99f3a50c perf: read GPU mapping sizes from maps and run costly overlay feeds only while it shows 2026-10-01 13:50:10 -04:00
xXJsonDeruloXx a3fafcb10e feat: feed the overlay's fan, GPU memory and Valve msm reader paths 2026-10-01 13:28:26 -04:00
Magarto c3d8395340 Merge branch 'main' into upscaler-setting 2026-10-01 19:10:55 +02:00
Kurt Himebauch 14516bd460 Merge pull request #129 from maxjivi05/fix/lsfg 2026-10-01 13:08:37 -04:00
Magarto f9b112e904 Merge branch 'main' into upscaler-setting 2026-10-01 19:06:00 +02:00
MaxsTechReview d7e5a90682 Merge branch 'main' into fix/lsfg 2026-10-01 13:02:30 -04:00
xXJsonDeruloXx e4a5bf45b9 fix: feed the overlay's GPU stats from every Adreno source other Android overlays read 2026-10-01 12:58:47 -04:00
xXJsonDeruloXx 6f9f265530 fix: give the session a live /proc/stat and read GPU load and clock where other overlays do 2026-10-01 12:53:17 -04:00
Kurt Himebauch 3487535b1a Merge pull request #63 from magarto/stretch-to-panel 2026-10-01 12:45:18 -04:00
Kurt Himebauch ccc3cf1e57 Merge branch 'main' into feat/performance-fixes-2 2026-10-01 12:44:00 -04:00
Kurt Himebauch d659a03467 Merge branch 'main' into stretch-to-panel 2026-10-01 12:41:57 -04:00
xXJsonDeruloXx f274a1ad68 fix: keep the performance overlay running and fed under an enforcing SELinux policy 2026-10-01 12:38:30 -04:00
magartus 19af3bcdd2 Expose the compositor's upscaler as a session setting
The Wayland compositor already carries the X11 renderer's spatial
upscalers (effects_chain.c: FSR 1 EASU+RCAS, Snapdragon GSR and its
quality variant, NIS, RCAS sharpen) behind
WaylandCompositor.nativeSetUpscaler / nativeSetUpscaleSharpness, but
nothing calls them since the Winlator-era drawer was removed, so the
scene is always enlarged to the panel with a plain linear blit.

Add "Upscaler" and "Upscaler sharpness" to the session page's Display
group (off by default, 75%), stored in SessionPrefs and applied next to
nativeSetScaleMode when the session's surface is set up. The choice is
also listed in device.txt.
2026-10-01 18:38:02 +02:00
MaxsTechReview f237fd8a84 Merge branch 'Droid-Deck:main' into fix/lsfg 2026-10-01 12:34:24 -04:00
Kurt Himebauch 6140c5af35 Merge pull request #125 from Droid-Deck/feat/input-evidence-logging 2026-10-01 12:14:15 -04:00
xXJsonDeruloXx 5fe5fa45c7 fix: let the Steam client find the Deck controller under an enforcing SELinux policy 2026-10-01 12:00:24 -04:00
maxjivi05 cd284ca59a Clock frame generation by the game's frames
With frame generation on, a scene drawn for another window (Steam's
overlay) went into the engine as a game frame. Adaptive counted it as
source rate and planned against that (36 base for a game capped at 30),
and a fixed multiplier gave it present slots while game frames were
dropped (26 -> 51 at 2x). Such a change now waits for the game's next
frame.

The HUD's base rate is the game window's own frames, as Steam's overlay
counts them, instead of the compositor's scene rate.
2026-10-01 11:57:58 -04:00
xXJsonDeruloXx 2896e0a945 fix: log an empty focused-app or touch-mode property as none 2026-10-01 10:37:58 -04:00
xXJsonDeruloXx f96e1b2b6e feat: always write the guest pad log to pad.log in the session folder 2026-10-01 10:35:24 -04:00
xXJsonDeruloXx ac40989d40 perf: skip the per-event device lookup when input comes from the same pad 2026-10-01 09:59:13 -04:00
xXJsonDeruloXx 7c0fc1cbfa feat: log controller, touch-mode and pad-ring evidence for input reports 2026-10-01 09:58:51 -04:00
Kurt Himebauch 0d6b544e8d Merge pull request #124 from Droid-Deck/ci/auto-publish-maintainer-prs 2026-10-01 09:19:44 -04:00
xXJsonDeruloXx cbeed44dc5 ci: publish signed test builds automatically for PRs from this repo's branches 2026-10-01 09:15:03 -04:00
Kurt Himebauch aa13ebdb0d Merge pull request #123 from Droid-Deck/fix/deck-pad-touch 2026-10-01 09:02:50 -04:00
xXJsonDeruloXx a53c4a3707 fix: keep touch scrolling the Steam interface when the client finds no controller at start 2026-10-01 08:18:24 -04:00
Kurt Himebauch 445bbcdcbe Merge pull request #122 from Droid-Deck/feat/readable-session-logs 2026-10-01 07:48:03 -04:00
xXJsonDeruloXx fb7c1d2f70 feat: name session log folders by day, number and mode, keep the newest 30, and add Clear logs 2026-10-01 07:43:21 -04:00
Kurt Himebauch dee60a3749 Merge pull request #102 from Droid-Deck/fix/x360-raw-pad-axes 2026-10-01 07:24:19 -04:00
Kurt Himebauch 5790ce7445 Merge branch 'main' into fix/x360-raw-pad-axes 2026-10-01 07:19:45 -04:00
xXJsonDeruloXx 6456127e87 chore: merge main into fix/adopted-storage-runtime-dir 2026-10-01 06:57:26 -04:00
Kurt Himebauch 6ae5940f0d Merge pull request #120 from Droid-Deck/ci/publish-test-waits-for-build
ci: wait for the PR build before publishing a test build instead of failing
2026-10-01 06:57:01 -04:00
xXJsonDeruloXx 9a424aabb7 ci: wait for the PR build before publishing a test build instead of failing 2026-10-01 06:54:05 -04:00
xXJsonDeruloXx 5f1b102e32 fix: give the guest a short XDG_RUNTIME_DIR so gamescope starts with the app on adopted storage 2026-10-01 06:49:03 -04:00
Kurt Himebauch 70b7408f1a Merge branch 'main' into fix/x360-raw-pad-axes 2026-10-01 06:21:24 -04:00
Kurt Himebauch 44c649f6e7 Merge pull request #93 from Droid-Deck/fix/a840v2-linux-turnip 2026-09-30 23:59:27 -04:00
Kurt Himebauch bf34b0c2bd Merge branch 'main' into feat/performance-fixes-2 2026-09-30 23:56:24 -04:00
Kurt Himebauch 8331b7fadf Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 23:55:50 -04:00
Kurt Himebauch 3581fb1e9e Merge pull request #117 from Droid-Deck/fix/qam-battery-eta 2026-09-30 23:55:10 -04:00
Kurt Himebauch 684add97aa Merge branch 'main' into fix/qam-battery-eta 2026-09-30 23:50:02 -04:00
Kurt Himebauch 3d4397ce42 Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 23:43:27 -04:00
Kurt Himebauch 424e714277 Merge pull request #116 from Droid-Deck/fix/pr-publish-post-release
fix: finish PR test publish after signing
2026-09-30 23:29:37 -04:00
xXJsonDeruloXx ff4f6edf08 fix: show QAM battery life and charge time estimates 2026-09-30 23:27:27 -04:00
xXJsonDeruloXx df730c472a fix: finish PR test publish after signing 2026-09-30 23:25:47 -04:00
Kurt Himebauch 77c35e968c Merge pull request #115 from Droid-Deck/feat/update-catalog
feat: publish and consume update catalog
2026-09-30 23:17:38 -04:00
xXJsonDeruloXx 3b849607f6 fix: allow catalog to inspect active tests 2026-09-30 23:06:09 -04:00
xXJsonDeruloXx 9fe3ce7a86 fix: reflect republished PR test freshness 2026-09-30 23:04:34 -04:00
xXJsonDeruloXx aece4b16bc fix: bypass stale update catalog caches 2026-09-30 22:57:43 -04:00
xXJsonDeruloXx 79f04eb69e docs: call main builds Preview 2026-09-30 22:54:32 -04:00
xXJsonDeruloXx a5f2895a6a feat: publish and consume update catalog 2026-09-30 22:46:44 -04:00
Kurt Himebauch b3a87d3d73 ci: streamline signed PR test builds (#114) 2026-09-30 22:18:08 -04:00
Kurt Himebauch a2105c2c0b Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 21:47:17 -04:00
Kurt Himebauch c0597809ec Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 21:46:58 -04:00
Kurt Himebauch 184ed90c51 fix: offer updates to release-signed local builds and align the Updates page (#112) 2026-09-30 21:46:45 -04:00
maxjivi05 fc90b80fd8 Add proot fast path for in-process path lookups
Preload library answers stat/open/access/readlink in-process via a
trampoline page proot 0014 lets through seccomp. On by default with a
Performance toggle; also sets GLIBC_TUNABLES rseq=0 and malloc top_pad.
2026-09-30 21:05:10 -04:00
xXJsonDeruloXx 69b5a17e20 Merge main into PR #93 2026-09-30 19:41:59 -04:00
MaxsTechReview 1d5d3acc46 fix: carry the installed Steam build into imported games so Steam sto… (#108)
* fix: carry the installed Steam build into imported games so Steam stops re-verifying them

A second-library game downloaded by WinNative was registered with a bare
manifest (no buildid or InstalledDepots), so Steam treated it as build 0 and
verified all ~92 GB of Final Fantasy XV through proot. Reuse WinNative's
.winnative-steam manifest, or a completed .DepotDownloader/depot.config, so
Steam only fetches what changed since the recorded build.

* fix: give queued Steam fresh installs of imported games the recorded build

Steam rewrites a bare import manifest into a fresh install at build 0
before downloading anything. While nothing is downloaded or staged, the
WinNative build is still safe to carry over, so Steam skips the full
re-download (FF7 Remake was queued for 98 GB this way).

* fix: mount the game storage at /mnt/droiddeck-sd

The second library was bound at /mnt/bannerlator-sd, a name left from the
Bannerlator port, which is what Steam shows in its paths and errors. Bind
it at /mnt/droiddeck-sd, keep the old path as an alias for links and
prefixes that still name it, move Steam's library entry in place, re-point
import links, read ownership snapshots taken under the old path, and key
added-game shortcut ids by the old path so their prefixes stay put.

* fix: restart Steam once when owned added games are confirmed

On a fresh install the importer runs before any ownership snapshot exists,
so every added game became a non-Steam shortcut and stayed one until the
client happened to restart. Once the watcher confirms owned titles that are
not imported, it asks the session for one restart, while no game runs and
no download is in progress, and the importer turns them into Steam entries.
2026-09-30 19:34:15 -04:00
Kurt Himebauch 771dca7048 Preserve added game launch options (#111) 2026-09-30 19:08:57 -04:00
Kurt HimebauchandMaxsTechReview 7e6df11344 feat: pick GPU drivers as matched pairs, set automatically for the device's GPU (#103)
* feat: pick GPU drivers as matched pairs, set automatically for the device's GPU

* DD-Turnip Driver Support (#106)

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-30 16:57:49 -04:00
MaxsTechReview 5251c6a5f6 Additional performance fixes (#87)
* perf: answer Turnip's zero-timeout KGSL timestamp polls without blocking

Turnip polls a timeline point with IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID
and a timeout of 0, which KGSL reads as wait forever; Mesa does it under
a lock on every signalling submit, so vkd3d-proton, DXVK, gamescope and
Zink ran in lock-step with the GPU. The session preload now answers
those polls from the context's retired timestamp and passes everything
else to the kernel. Bannerlator measured a D3D12 demo at 614 -> 4335 fps
with the same answer in its Vulkan adapter. BL_NO_KGSL_POLL_FIX=1 turns
it off.

* perf: frame limiter, live panel refresh, opt-in GPU clock pin, cheaper cursor sync

- A per-mode frame limit (Steam and desktop settings, off by default):
  gamescope's -r, the compositor's buffer-release pacer, the display
  layer's frame-rate vote and a panel mode the cap divides evenly, as
  WinNative's per-shortcut limit does. BL_FPS was always 0 and the
  pacer's limit was never set.
- The compositor follows the panel's refresh rate when it changes
  mid-session instead of reporting the rate read at start. (WinNative.)
- Performance > GPU: hold the Adreno clock at its top through KGSL's
  power-control property, no root (Bannerlator's PerfGpuTurbo). Off by
  default; cleared when the session stops and at the next app start if
  a killed process left it set.
- The cursor image is copied across JNI only when its serial changes,
  not on every pointer move.

* perf: stop re-seeding every prefix every 5 s, and run the library helpers only on change

- bannerlator-seed-redists stamped its change check from a block that
  carried the current time, so it never matched: every prefix had ~32
  keys appended to system.reg on every beat. The stamp now comes from
  the names; Proton's prefix template is seeded too (date kept), with
  WinNative's built-in list until the client has the install scripts.
- The library beat runs the seeder every 15 s and steam-compat only
  when a directory it reads has changed, or once a minute.
- The client-core pin reads /proc itself and runs taskset -a only for
  new processes or ones that grew threads.
- The games watcher backs off after a failed ownership probe and does
  not probe while a game runs.
- The Proton launcher runs the newest ARM64 depot by build number, and
  PROTON_USE_PIPEWIRE defaults to 0. (WinNative.)

* perf: Mesa's database shader cache, and proot's tracer above the guest

- MESA_DISK_CACHE_DATABASE=1 for the session: the file cache opens a
  file per lookup and opens and renames one per store, each a proot
  stop; the database is opened once and used with pread/pwrite.
- proot's single tracer thread is raised to nice -6 (under the
  compositor's -8) two seconds after it starts, so the first guest
  process it forks keeps nice 0. setpriority() directly, not
  Process.setThreadPriority, which would also move its cgroup.
2026-09-30 16:43:52 -04:00
MaxsTechReview b20f77f988 Added LSFG/Import/License Checking (#96)
* feat(lsfg): take Lossless Scaling from Steam by itself, import it once owned, rebuild on update

- Lossless.kt owns the DLL and its shader cache. The copy the Steam
  client installs (runtime library or the card's) is taken by itself
  and taken again when the client updates it (size/mtime stamp). The
  DLL is copied in and built into a cache named after its SHA-256, so
  a new DLL hands the compositor a new path; a build that fails keeps
  the working cache. Builds are serialized.
- The Steam session asks the signed-in client once per session whether
  the account owns app 993090 until the answer is yes, then records it
  and the app stops asking (BL_LOSSLESS_OWNED). Once owned, the frame
  generation menu offers "Import Lossless.dll…", on the main screen and
  in the session drawer; an import stays until Steam's copy changes.
- The compositor restarts the LSFG engine when its cache path changes,
  and a new engine selection clears an earlier start failure, so an
  updated DLL or a retry no longer needs the app restarted.
- The HUD says when LSFG is waiting on Lossless Scaling instead of
  "starting"; frame generation strings are resources.
- Removed LsfgNative's unused DLL variant and cache-match calls.

* Find Lossless.dll in added Games libraries too

* LSFG: 120, 90 and 60 fps targets alongside 2x-4x

The pacer's target-rate mode picks 0-3 generations per game frame. While it
is warm the input ring and shared chain follow every frame, so frames that
generate nothing don't leave the next one without history.

* Credit LSFG: Camille LaVey / Eden, ported to WinNative and DroidDeck by @maxjivi05

* LSFG: aim 0.1 fps under the target so output never passes the refresh rate
2026-09-30 16:40:46 -04:00
Kurt Himebauch bd5e9f4458 Merge branch 'main' into fix/a840v2-linux-turnip 2026-09-30 16:28:51 -04:00
Kurt Himebauch a690771d57 Merge branch 'main' into fix/x360-raw-pad-axes 2026-09-30 16:27:45 -04:00
Kurt Himebauch aac7e15f2b Feat/step out stop confirm (#105) 2026-09-30 16:26:39 -04:00
Kurt Himebauch a303e5c8a6 docs: add a join-the-Discord badge and community section to the README (#104) 2026-09-30 14:54:35 -04:00
xXJsonDeruloXx 98f5da262b fix: give games the Xbox 360 axis layout when Steam Input is off 2026-09-30 13:36:14 -04:00
Kurt Himebauch a81363d743 feat: turn Deck mode on for new installs and add a performance overlay toggle (#101)
* feat: turn Deck mode on for new installs and add a performance overlay toggle

* feat: hide the session fps HUD where Deck mode's performance overlay runs
2026-09-30 13:06:12 -04:00
Magarto 0041892a4b Merge branch 'main' into stretch-to-panel 2026-09-30 18:52:06 +02:00
Kurt Himebauch 11a8221623 fix: keep Deck mode on the Steam Deck client branch so Steam stops offering an update (#100) 2026-09-30 12:47:57 -04:00
Kurt Himebauch 6689f809e2 fix: stop update channel cards flashing as a controller moves between them (#99) 2026-09-30 11:45:44 -04:00
Kurt Himebauch 62e75e23ed feat: add an Updates page that installs Stable, Nightly or test builds in-app (#98)
* feat: add an Updates page that installs Stable, Nightly or test builds in-app

* style: put update channels on the left and the build on one line

* style: make the update card read like a system updater

* feat: expand update notes, show the build beside the title, fit with button hints

* style: check for updates with the refresh icon Components uses
2026-09-30 11:25:18 -04:00
Kurt Himebauch faf8ce97ba ci: sign PR test builds in every variant and take stable from the org repo (#97) 2026-09-30 10:53:26 -04:00
Kurt Himebauch 50890336c0 Merge pull request #94 from Droid-Deck/fix/remove-pr-approval-gates
ci: remove PR approval gates from signed releases
2026-09-30 09:19:58 -04:00
xXJsonDeruloXx 043547c7ee ci: remove PR approval gates from signed releases 2026-09-30 09:14:30 -04:00
xXJsonDeruloXx be86fd953a fix: report Adreno 840v2 as the A840 the runtime's Turnip knows 2026-09-30 07:53:12 -04:00
Magarto db8928f7ce Merge branch 'main' into stretch-to-panel 2026-09-30 09:30:37 +02:00
Kurt HimebauchandMaxsTechReview 4a234b2824 feat: Flatpak apps and a Flathub store (#77)
* feat: run Flatpak apps through a proot bwrap stand-in and add a Flathub store

* feat: give Flatpak apps the KGSL GPU, the controllers and a CPU LLVM can target

* feat: Flatpak games on the desktop, Store apps on the Desktop page, safer remove

* test: cover the sandbox planner and store helpers; docs: explain Flatpak on DroidDeck

* fix: keep controller focus through Store tabs and app pages; show progress per Flatpak step

* fix: let Chromium, Electron and Firefox Flatpaks start without their own sandboxes

* fix: fetch Flatpak's supporting libraries when the runtime lacks them

* fix: add Flathub without a download, log store commands, keep Electron apps on X11 under gamescope

* feat: import ARM64 AppImages from storage and run them from the Desktop page

* fix: report failed Flatpak installs as failures and deploy runtimes that ship a resolv.conf link

* fix: let Flatpak create its repo where Android denies hard links

* fix: point store commands at the current network's DNS

* fix: keep failed Flatpak extensions non-fatal, give Electron AppImages a bus, X11 and time to relaunch

* docs: AppImage import and what x86 apps would take

* feat: put the Flathub Store and AppImage import behind beta toggles in Setup

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-30 00:35:49 -04:00
The412Banner 970fc3a946 ci: say what each DroidDeck-CI build is at the top of its release notes (#92) 2026-09-30 00:11:55 -04:00
The412Banner d25c94471d Merge pull request #91 from Droid-Deck/feat/game-saves
Game saves: import and export save zips for added games
2026-09-29 23:49:50 -04:00
The412Banner 2805c94f9f Game saves: show Manage saves only on added games 2026-09-29 23:36:49 -04:00
The412Banner f22b8253fd Game saves: Manage saves on each game's page instead of a separate page
The floppy disk beside the Steam cog and its Game saves page are gone. A game's
page now has Manage saves beside Proton prefix, a menu with Import saves (a
GameHub or Winlator zip, picked in the app's file picker; the saves there now are
backed up first), Export as GameHub zip, Export as Winlator zip (a folder picked
the same way) and Open save folder. The game's saves are looked for when the menu
opens. The Proton list and config.vdf reader only that page needed are removed.
2026-09-29 23:21:40 -04:00
The412Banner 174321c0ec Game saves: import and export save zips per game
A floppy disk beside the Steam page's cog opens a Game saves page, laid out like
Components: the Proton at the top (from the client's compatibility tools, with
bannerlator-proton-arm64 shown as Proton Experimental), the zip type beside it
(GameHub or Winlator, LB / RB turn it), and the games the client runs with that
Proton (CompatToolMapping, its default for the rest), each with Import and Export.

Import picks a zip in the app's file picker, backs up the game's steamuser profile
to Download/DroidDeck/Saves/backups, then unzips drive_c with any user folder but
Public renamed to steamuser, so GameHub and Winlator zips both land. Export finds
the save folders by name under the usual roots (Bannerlator's SaveLocator), or
takes the whole profile, and writes <Game>_<date>.zip to a folder picked the same
way, under steamuser (GameHub) or xuser (Winlator). Imports wait while a session
runs. Ported from Bannerlator's GameSaveBackup and SaveLocator.
2026-09-29 23:01:21 -04:00
The412Banner 14de9a734c Merge pull request #89 from Droid-Deck/fix/resume-restore-minimized
Restore a game that minimized itself behind the Steam menu
2026-09-29 22:14:53 -04:00
The412Banner 42e6a45051 Merge branch 'main' into fix/resume-restore-minimized 2026-09-29 22:10:04 -04:00
Kurt Himebauch 8fbcf11fc8 feat: the pad as SteamOS has it - Steam Input's virtual pads for games, a Steam Deck controller for the client (#86)
* feat: stand in for /dev/uinput so Steam Input's virtual pads reach games

* feat: present the pad to the Steam client as a Steam Deck controller over hidraw

* docs: log the uinput stand-in and the Steam Deck controller

* feat: feed the handheld's gyro and accelerometer to the Steam Deck controller

* feat: let the Steam page choose between the Steam Deck controller and the Xbox 360 pad

* feat: Deck back grips and trackpads on the second screen

* fix: serialize ring teardown with its writers, fall back to the Xbox pad when the Deck cannot be set up, and read motion only for a Deck

* fix: keep the second screen's choice through sleep and bring it back on wake
2026-09-29 21:57:52 -04:00
The412Banner d1dcf2974c gamescope-3.16.29-p4: restore a game minimized behind the Steam menu (patch 0112) 2026-09-29 21:56:42 -04:00
The412Banner f50d775912 Restore a game that minimized itself behind the Steam menu
The Steam menu is an overlay that takes input without changing gamescope's focus
window. A fullscreen wine game minimizes itself when it loses input, and gamescope
only takes a window out of iconic when the focus window changes, so after Resume
the game stayed minimized: a black screen with its caption in the top-left corner
(Titanfall 2 on GE-Proton 11, proven on device).

gamescope patch 0112 remembers the iconify request and puts the window back to
NormalState before input returns to it, then hands focus over again so wine
restores and activates it. The session script also asks gamescope to restore the
game once input comes back from the client (GAMESCOPE_RESTORE_FOCUS_WINDOW);
BL_RESTORE_ON_RESUME=0 turns that off.
2026-09-29 21:52:34 -04:00
Kurt Himebauch 7d1e1707be perf: faster local build, skip xalia and gamescope realtime by default, mic off by default, show the saved Deck mode (#85)
* perf: cache pinned downloads, skip unchanged proot rebuilds, and wait out a Docker Desktop restart in the local build

* perf: skip xalia and gamescope realtime queues by default, add a realtime toggle, default the mic off

* fix: show the saved Steam Deck mode on the Steam page before Performance has been opened
2026-09-29 20:00:10 -04:00
Kurt Himebauch 026c1c32e5 Merge pull request #84 from maxjivi05/fix/performance-misc 2026-09-29 19:41:38 -04:00
maxjivi05 72e4147449 perf(gamescope): ask X for the pointer only while a cursor is drawn
The gamepad-cursor port queried the X pointer on every vblank, a
blocking round trip to Xwayland on the paint thread. With no cursor
image the position comes from wlserver as upstream; while the cursor is
hidden for inactivity X is asked every 50 ms. The realtime-queue comment
now says it is a no-op on KGSL Turnip.
2026-09-29 18:44:32 -04:00
maxjivi05 2fd209be83 perf: poll without starting processes, and stop rewriting unchanged Steam files
The session's watchers slept with sleep(1) and read files with cat, an
exec each under proot; they now wait in bash itself. steam-compat
writes its tool, the adopted wrappers and config.vdf only when they
change, the games watcher probes ownership when its matches change or
every five minutes instead of every 30 s, and Proton no longer inherits
the client's steamrtarm64 library path.
2026-09-29 18:44:32 -04:00
maxjivi05 1066ff5730 perf: default to Turnip sysmem, compact Zink descriptors and the Performance + TSO FEX preset
WinNative's defaults for every Linux session. Choices already saved by
a user are kept.
2026-09-29 18:44:32 -04:00
maxjivi05 62bb7a1e80 perf: pace buffer releases to the screen, read the requested refresh, drop sustained mode and ADPF
A replaced buffer went straight back to a game with no frame limit, so a
MAILBOX/IMMEDIATE swapchain drew frames that were never shown; it now
comes back once per refresh as in WinNative. gamescope's -r and the
compositor read the rate of the mode the activity asked for rather than
the one the panel was still in, and the zero-copy layer votes it.
Sustained performance mode, which caps clocks on most power HALs, and
the ADPF session, which reported frame intervals as work, are gone.
2026-09-29 18:44:32 -04:00
maxjivi05 cc37cc8a4d perf: stop proot's fake_id0 and ioctl traps from stopping every stat, sendmsg and GPU ioctl
fake_id0 (loaded by -i for Xwayland's set*id) now traces only the set*id
family and the xattr fixups when the ids given are proot's own, and the
seccomp filter traces ioctl only for the termios and FICLONE requests
proot rewrites. The faccessat2 and statx exit stops are dropped on
kernels that have the calls.
2026-09-29 18:44:32 -04:00
Magarto 92d04d6fa9 Merge branch 'main' into stretch-to-panel 2026-09-29 22:00:43 +02:00
Kurt Himebauch 33460f7342 Merge pull request #81 from Droid-Deck/fix/proot-kompat-utsname-only
perf: stop proot's kompat from trapping hot syscalls, and stop respawning a crashing mangoapp
2026-09-29 15:38:52 -04:00
xXJsonDeruloXx 23b2ffde14 fix: stop gamescope respawning a mangoapp that keeps dying at start 2026-09-29 15:08:28 -04:00
xXJsonDeruloXx 4f14d396bc perf: stop proot's kompat from trapping futex/epoll/fcntl and stripping the vDSO when only the hostname changes 2026-09-29 14:39:53 -04:00
Magarto 9d36f1a73b Merge branch 'main' into stretch-to-panel 2026-09-29 17:49:28 +02:00
Kurt Himebauch b1bb767c65 Merge pull request #78 from Droid-Deck/fix/ci-publish-retention
Fix DroidDeck-CI main build publishing
2026-09-29 10:48:38 -04:00
xXJsonDeruloXx 156e152ffc ci: keep main DroidDeck-CI releases published 2026-09-29 10:46:50 -04:00
Kurt Himebauch d30e34eec1 Merge pull request #55 from Droid-Deck/fix/steamdeck-performance-overlay
Deck mode: working Steam performance overlay (MangoHud)
2026-09-29 09:41:51 -04:00
xXJsonDeruloXx 69a140da28 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/MainActivity.kt
2026-09-29 09:41:11 -04:00
Kurt Himebauch 0ee8f57524 fix: use Android's 300 ms double-tap window for Back so the single-press action comes sooner (#73) 2026-09-29 02:10:51 -04:00
Kurt Himebauch 45f0969c5c fix: send wl_keyboard modifiers so Shift and Caps Lock produce capitals (#71) 2026-09-29 02:10:01 -04:00
Kurt Himebauch 7a0e087bf9 fix: give the QAM chord a Guide lead that stretches with slow frames so A is not taken as a press (#70)
* fix: give the QAM chord a Guide lead that stretches with slow frames so A is not taken as a press

* fix: start the QAM Guide lead at 80 ms and stretch it only when frames are slow

* fix: set the QAM Guide lead minimum to 80 ms

* fix: use Android's 300 ms double-tap window for Back so the single-press action comes sooner

* fix: keep the 500 ms Back double-press window
2026-09-29 02:01:21 -04:00
Kurt Himebauch 9318cd2a59 feat: flood out of Stop and back into the launch button on quit (#72)
* feat: flood out of Stop and back into the launch button on quit

* refactor: drop the stop flood's warble to match the launch
2026-09-29 01:53:13 -04:00
Kurt Himebauch e7d889e443 perf: cache the Steam client's repeated path lookups to cut startup from 31s to 6s (#69) 2026-09-28 23:18:48 -04:00
Kurt Himebauch 6840b9139f fix: let the Steam client shut down cleanly when a session stops (#68) 2026-09-28 22:12:19 -04:00
Kurt Himebauch 00e8c285ba feat: pair wireless debugging from a notification for the child-process gate (#67)
* feat: pair wireless debugging from a notification for the child-process gate

* fix: pair over loopback, handle Android 12 and 14 child-process routes, use D logo for notifications

* fix: read the adb tls port directly before falling back to mdns

* fix: end pairing on the same notification so progress never lingers
2026-09-28 21:37:10 -04:00
MaxsTechReviewandMaxsTechReview d647879299 Steam Owned Games Importing (#62)
* Add ownership-aware Steam game folder imports

* Fix Steam title lookup and installed game manifests

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-28 18:13:08 -04:00
Magarto 3061c5528c Merge branch 'main' into stretch-to-panel 2026-09-28 23:13:01 +02:00
Kurt Himebauch dd5bdcb3a5 Merge pull request #61 from magarto/guest-hostname-setting 2026-09-28 17:07:59 -04:00
magartus 93db74642c Add an opt-in 16:9 stretch for screens narrower than 16:9
On a foldable's inner panel (Galaxy Z Fold8: 2448x1848, ~4:3) gamescope
is started with the nested size equal to the output, so games are asked
for the panel's own shape. Many cannot do it and letterbox themselves,
and gamescope's scaler has nothing to stretch. "Stretch games to fill
the screen" only forces window size, so it does not help.

New Steam setting "Stretch 16:9 games to a narrower screen" (off by
default): bannerlator-session then gives games a 16:9 surface at the full
output height (-w) and adds -S stretch, so they render at a shape they
support and fill the panel with no bars and no crop. An output that is
already 16:9 or wider is left untouched. Passed as
BL_GAMESCOPE_STRETCH_16X9 and listed in device.txt.
2026-09-28 23:03:10 +02:00
magartus f823209ac5 Let the user choose the guest host name
0.2.0 hardcodes the proot utsname nodename to "DroidDeck". Steam sends
it as the machine name, so an install updated from an earlier build
(which reported Android's "localhost") shows up in Steam under a new
name, with the same machine-id and login. /etc/hostname cannot fix it
from inside: proot's -k override wins.

Add a "Host name" field (Performance > Session identity), stored in
SessionPrefs and passed to guestUtsname(). Only a valid RFC 1123 label
is saved; blank or invalid falls back to the old default, "DroidDeck",
so nothing changes for anyone who leaves it alone. The value is also
listed in device.txt.
2026-09-28 22:58:54 +02:00
Kurt Himebauch 47d8dc21d1 Merge branch 'main' into fix/steamdeck-performance-overlay 2026-09-28 16:36:33 -04:00
MaxsTechReviewandMaxsTechReview ae4f6727e0 Fix/variables (#60)
* Add editable per-game environment settings below FEX presets

* Add typed preset variable pickers and default D3D12 capability levels

---------

Co-authored-by: MaxsTechReview <[email protected]>
2026-09-28 15:17:57 -04:00
Kurt Himebauch 1ffc23623d Merge pull request #59 from Droid-Deck/ci/discord-build-announce
ci: publish each signed main build to DroidDeck-CI and post it to Discord
2026-09-28 14:54:29 -04:00
xXJSONDeruloXx 5e5ac79517 ci: publish to DroidDeck-CI with the org's CI app, not a personal token
DROIDDECK_CI_TOKEN never existed, so PR Release Publish skipped its
upload, and a personal token would expire and belong to one person. Both
workflows now mint a token of the org's DroidDeck CI Publisher app,
installed on DroidDeck-CI only with Contents: write (its ID in the
DROIDDECK_CI_APP_ID variable, its key in DROIDDECK_CI_APP_KEY). No
fallback: a main build without them fails its announce job.
2026-09-28 14:51:13 -04:00
xXJSONDeruloXx f4afc0ecc3 ci: publish each signed main build to DroidDeck-CI for the Discord post
Run artifacts need a GitHub sign-in to download. Each signed main build
is now also a prerelease main-<sha> of the public DroidDeck-CI (all
variants and their SHA-256s; the last 10 kept, PR releases untouched),
and the Discord post links its standard apk and the release. Without
DROIDDECK_CI_TOKEN the post links the run's artifacts as before.
2026-09-28 14:49:43 -04:00
MaxsTechReview 07c6a85ada Merge pull request #53 from maxjivi05/fix/adaptive-joysticks
Hide idle adaptive sticks and activate them at touch positions
2026-09-28 14:48:26 -04:00
MaxsTechReview 69ffc682e4 Merge branch 'main' into fix/adaptive-joysticks 2026-09-28 14:48:14 -04:00
xXJSONDeruloXx 3decdbd9e7 ci: post each signed main build to Discord
A job after the signing of a main build posts to the Discord's builds
channel: the merged PR (or the commit), and a link to the run's signed
apks. The channel's webhook URL is the DISCORD_BUILDS_WEBHOOK secret;
without it the job posts nothing and passes.
2026-09-28 14:46:41 -04:00
Kurt Himebauch 15fc9be3b0 Merge pull request #57 from Droid-Deck/fix/desktop-hardware-cursor
Desktop: hand labwc's cursor to the app, so full-screen games scan out
2026-09-28 14:40:02 -04:00
MaxsTechReview 0f75c0f3c2 Expand adaptive joystick activation area by fifty percent 2026-09-28 14:39:55 -04:00
Kurt Himebauch 8caf6d038c Merge branch 'main' into fix/desktop-hardware-cursor 2026-09-28 14:39:52 -04:00
Kurt Himebauch 7b7b4a9e3e Merge pull request #58 from Droid-Deck/tools/local-release-signing
build_local: sign with DroidDeck's key when a local .signing.env has it
2026-09-28 14:39:22 -04:00
xXJSONDeruloXx 080f72f7a0 build_local: sign with DroidDeck's key when a local .signing.env has it
A local build was signed with the testkey alone, which a release refuses
as an update ("signatures do not match"): testing on a device with a CI
build meant uninstalling it and losing its data first.

With an untracked .signing.env (RELEASE_KEYSTORE, RELEASE_STORE_PASSWORD,
RELEASE_KEY_ALIAS) in the checkout, or in the main checkout for a
worktree, build_local.sh finishes with tools/release/sign-apk.sh as CI's
main builds do, and the apk installs over a release. Without the file
nothing changes. DROIDDECK_SIGNING_ENV points at the file explicitly.
2026-09-28 14:33:15 -04:00
xXJSONDeruloXx f87289f75b Desktop: hand labwc's cursor to the app, so full-screen games scan out
The desktop forced a software cursor (WLR_NO_HARDWARE_CURSORS=1) because
labwc's cursor never showed when handed to the app's compositor. With a
software cursor wlroots never scans out: every frame of a full-screen game
was composited by labwc first ("Direct scan-out disabled by software
cursor", 2284 times in one Adreno 825 report).

Why the cursor never showed: wlroots' Wayland backend attaches and commits
its cursor surface before wl_pointer.set_cursor gives it the cursor role,
and sets it again with nothing new committed each time the pointer enters.
The compositor released a role-less surface's buffer on commit, so the
cursor surface had no image by the time it became the cursor.

- A role-less surface's last buffer (cursor-sized only) is held until the
  next commit; set_cursor publishes it, and a repeat set_cursor of the
  surface already shown unhides its last image.
- A dma-buf cursor is read back after the client's implicit render fence,
  as the zero-copy path waits for it.
- The desktop no longer forces a software cursor.

Device-tested on the AYN Thor (Adreno 740, 1920x1080): labwc's own cursor
is drawn, with its shapes (I-beam over a terminal); wlroots logs "Direct
scan-out enabled" for a full-screen glxgears. At the same 60 fps on screen
the GPU is 3.0 % busy against 4.6 % with the software cursor, both at
550 MHz.
2026-09-28 14:33:08 -04:00
MaxsTechReview f418b788bc Limit adaptive stick activation near saved positions 2026-09-28 14:21:34 -04:00
MaxsTechReview b1ac0a7820 Merge branch 'main' into fix/adaptive-joysticks 2026-09-28 12:53:44 -04:00
Kurt Himebauch 6d8c84b3b0 Merge pull request #56 from Droid-Deck/ci/sign-all-variants
ci: sign every package variant on main
2026-09-28 12:46:40 -04:00
xXJSONDeruloXx a9ae338bfd ci: sign every package variant on main
main's build signed only the standard package, so the pubg, antutu and
ludashi variants existed only after a manual Release APKs run. The build job
now reads the variant list from tools/release/variants.txt, as release.yml
does, and main's signing job signs all of them.
2026-09-28 12:42:56 -04:00
xXJSONDeruloXx 5128bbd453 mangoapp: stat sources for more Snapdragon generations
KGSL's files and thermal zone names differ between Snapdragon generations.
Each value now takes the first source that exists, in the order GameNative
and the Winlator forks read them: GPU load from gpu_busy_percentage or
devfreq/gpu_load, GPU clock from devfreq/cur_freq or gpuclk, GPU temperature
from KGSL's temp, devfreq/temp or a zone whose sensor name has "gpu". CPU
zones are any whose name has "cpu" and not "gpu" (cpuss-0, cpu0-silver-usr,
apc1-cpu0-usr, ...), matched without regard to case.
2026-09-28 12:28:45 -04:00
xXJSONDeruloXx 458de0acab mangoapp: GPU memory from KGSL
mangoapp's VRAM figure on a Qualcomm driver comes only from the gpu_mem_total
trace event. The tracefs stand-in's event list now declares that event as
Android kernels do, and reading the stand-in's events answers with one
record carrying the total KGSL reports in /sys/class/kgsl/kgsl/page_alloc,
which every Adreno under Android has and the app can read.
2026-09-28 12:28:45 -04:00
xXJSONDeruloXx 6d26eb8f70 mangoapp: show Adreno GPU and CPU stats
Valve's mangoapp reads an Adreno GPU's load, clock and temperatures from the
paths of its own hardware, and the CPU temperature from thermal zones named as
mainline kernels name them. Every Adreno under Android keeps the GPU values in
KGSL's sysfs, readable by the app, and Android kernels name the CPU sensors
cpuss-N / cpu-N-M; the session binds each where mangoapp looks. Thermal zones
are found by sensor name, since their numbering differs per device, and
anything that cannot be found is left alone.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx 016558ca0b mangoapp: run the performance overlay under Android
Three things kept Valve's mangoapp from drawing the overlay in a session:

- GLFW 3.4 takes XDG_SESSION_TYPE=wayland with WAYLAND_DISPLAY set as
  Wayland-only, and mangoapp needs gamescope's X server; the wrapper sets
  XDG_SESSION_TYPE=x11.
- On a Qualcomm display driver it opens a tracefs instance for the GPU memory
  total and aborts when that fails, which it always does for an Android app.
  The session preload now places the instance in an empty directory of its own
  when the real one cannot be made.
- gamescope sends it each frame's timings through a System V message queue,
  and Android kernels have no System V IPC. sysv.c now emulates the queue
  operations gamescope and mangoapp use, beside its semaphores and shared
  memory.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx 3cf51b3814 Deck mode: enable Steam's performance overlay controls
The Quick Access menu's performance overlay toggle and level slider stayed
greyed out: the client exposes SteamClient.System.Perf only with -steamos3,
which SteamOS and Armada pass alongside -steamdeck. Deck mode now launches the
client with both. The session already puts package/beta back to the forced
branch before every start, which keeps -steamos3's own branch switch from
turning into an update loop.

The Deck mode switch moves from the Performance page to the Steam page's
Client settings, next to the client branch it selects by default.
2026-09-28 12:19:01 -04:00
xXJSONDeruloXx e8d36ecfe5 Blend translucent Wayland surfaces
The compositor composed every surface with a blit, which replaces what is under it. gamescope's
Wayland backend hands its overlay and notification planes to the host as full-size translucent
subsurfaces, so a Steam toast, the Quick Access menu or Deck mode's performance overlay blacked
out what was beneath it.

Surfaces whose buffer has alpha and that are not declared opaque now go through an alpha pass
(premultiplied source-over); everything else, and any image the driver cannot sample, keeps the
blit. wl_surface.set_opaque_region is honoured, and only for programs that declare opaque regions
at all, so Wine windows with a meaningless alpha channel are drawn as before. On the SurfaceControl
path a translucent plane is never promoted to the game layer, and the overlay layer is marked
translucent when its plane is.

From WinNative (maxjivi05, feature/wayland-gamescope 69218fc; GPL-3.0), with the SurfaceControl
layer handling added.
2026-09-28 12:18:50 -04:00
Kurt Himebauch 9468a8fe43 Merge pull request #52 from maxjivi05/fix/splash-screen
Reduce Steam startup movie software decoding load
2026-09-28 11:35:25 -04:00
MaxsTechReview 7d0e6b134b Hide idle adaptive sticks and activate them at touch positions 2026-09-28 10:54:24 -04:00
MaxsTechReview 3c8fe938dc Reduce Steam startup movie software decoding load 2026-09-28 10:49:25 -04:00
Kurt Himebauch da0775913f Merge pull request #50 from Droid-Deck/fix/release-blockers
Fix/release blockers
2026-09-28 10:32:44 -04:00
xXJsonDeruloXx 20b63bba9b chore(release): set version 0.2.0 and remove code comments 2026-09-28 10:32:12 -04:00
Kurt Himebauch 545b616179 docs(release): show every 0.2.0 section open (#48) 2026-09-28 10:24:24 -04:00
Kurt Himebauch e406025d49 Merge branch 'main' into fix/release-blockers 2026-09-28 10:22:22 -04:00
The412Banner 35517f94cc Merge pull request #51 from Droid-Deck/fix/child-process-check
Child-process check: read the Developer options toggle too
2026-09-28 09:30:58 -04:00
The412Banner 63db6fe9b7 Child-process check: read the Developer options toggle too
Developer options' "Disable child process restrictions" writes only the
feature-flag property persist.sys.fflag.override.settings_enable_monitor_phantom_procs,
never the global setting. Android (FeatureFlagUtils.isEnabled) uses the global
setting when it is set and that property otherwise, but the check read only the
global setting, so a device switched off in Developer options stayed flagged as
unset however often Check again was pressed.

The check now follows Android's order, and the Setup card no longer says "On"
for a setting that is unset or unreadable.
2026-09-28 09:24:19 -04:00
Kurt Himebauch 738c40cbb9 fix(session): stop the session script's own loops when steam exits 2026-09-28 11:52:03 +00:00
Kurt Himebauch dcf1dc33b1 fix(home): open drawer apps in their own task 2026-09-28 11:52:03 +00:00
Kurt Himebauch eeeba13b6c fix(session): keep a retried session's first-frame and hud listeners 2026-09-28 11:52:03 +00:00
Kurt Himebauch 1ea8eb6cb3 fix(session): cancel a session start that is still setting up 2026-09-28 11:52:02 +00:00
Kurt Himebauch 6e25e0b007 fix(session): don't launch a cancelled session or end a newer one 2026-09-28 11:52:02 +00:00
Kurt Himebauch c029134ba2 chore(release): bump to 0.2.1 and check the version at release time 2026-09-28 11:52:02 +00:00
Kurt Himebauch 2920847939 fix(runtime): keep the user's home when a runtime update fails mid-swap 2026-09-28 11:52:02 +00:00
Kurt Himebauch 1d6827a4dc docs: home app and dual-screen demos, trimmed 0.2.0 visuals, README hero (#47) 2026-09-27 22:48:33 -04:00
Kurt Himebauch c7e692c5ec 0.2.0 release notes with screenshots, plus launch transition and UI fixes (#46)
* docs(release): draft 0.2.0 beta notes

* docs(release): credit prs and authors in 0.2.0 notes

* docs(release): add #39 and #45 to 0.2.0 notes

* chore: update readme

* fix(ui): cover the whole screen with the capsule wall

The wall was shifted right and then tilted around its own centre, which
left the bottom-left of the home and loading screens bare. Centre it and
size it to the tilted page's bounding box so every corner stays covered.

* feat(ui): flood from the play button into the throbber

Starting a session from a blue button no longer slides the session up
over the front end. The button stretches out on staggered springs until
the page is the ball's blue, the session opens on that same blue with no
window animation, and the blue gathers into the throbber's ball before
the throbber starts. Re-joining a running session, tiles and reduced
motion keep the old rise.

* docs(release): cover everything since 0.1 in the 0.2.0 notes

0.2.0 is the first release from the org, so list what DroidDeck does
as a whole, grouped by area: org pull requests as #N, pull requests
from before the move as The412Banner/DroidDeck#N, and the early work
that predates pull requests by author. CI and build work gets one
short section instead of a line each.

* docs(release): add screenshots and animations to the 0.2.0 notes

Lead with a game running and a few visual highlights, fold the full
list into collapsible sections by area, and shorten every line to the
feature and its credit. Pre-move pull requests read as legacy#N.

* fix(session): give the desktop its own loading hints

The loading screen's hints were written for Steam, so starting the
desktop or an emulator said the first run downloads Steam and that
leaving the app pauses Steam. Desktop and program sessions now rotate
through hints about the desktop instead.

* fix(build): skip incomplete NDKs when picking one locally

An interrupted sdkmanager install leaves a directory like
28.2.13676358.failed under ndk/, which sorted last and was picked
without a toolchain in it. Only consider directories that carry
source.properties.

* docs(release): show launching straight into Big Picture

* docs(release): credit the launch transition to #46
2026-09-27 22:14:23 -04:00
Kurt Himebauch d940175e4b fix(session): clear transient UI on pause (#45) 2026-09-27 20:37:58 -04:00
Kurt Himebauch b06c6cb478 Add game file and Proton prefix shortcuts (#39) 2026-09-27 20:37:43 -04:00
The412Banner a85acbf50b Merge pull request #43 from Droid-Deck/fix/session-stragglers
Session stop: kill guest processes proot lost, and cap the session log
2026-09-27 17:19:58 -04:00
The412Banner 9d13b238bd Session stop: kill guest processes proot lost, and cap the session log
Twice today a session's Xwayland aborted from one thread ("The futex
facility returned an unexpected error code"), proot lost the tracee, and
the process lived on reparented to init: proot's seccomp filter, with no
tracer left, answers ENOSYS, so Xwayland's main loop logged
"WaitForSomething(): poll: Function not implemented" until something killed
it - 9 GB after Dolphin, 13 GB after PPSSPP. --kill-on-exit and the tree
sweep only reach what proot still traces, and the straggler sweep ran only
at the next session's start.

Now a session stop also kills every process of ours whose executable lives
in the Linux runtime, and bannerlator-session keeps only the first
BL_LOG_CAP bytes (256 MB) of session.log, draining the rest so a writer
that filled it is not killed by SIGPIPE.
2026-09-27 16:52:51 -04:00
Kurt Himebauch 9aed4e7a5c Merge pull request #41 from Droid-Deck/fix/ui-ux-critique
Fix/UI ux critique
2026-09-27 16:41:18 -04:00
The412Banner c67323538b Merge pull request #42 from Droid-Deck/feat/cemu-wiiu
Cemu (Wii U) from the front end and the desktop, and two session fixes
2026-09-27 16:39:17 -04:00
xXJsonDeruloXx fa3442cefc fix(ui): remove duplicate focus imports 2026-09-27 16:34:34 -04:00
The412Banner fff1e837d3 Cemu (Wii U) from the front end and the desktop, and two session fixes
Cemu never showed a frame on DroidDeck: its Getting Started dialog blocked
every first launch (the folder picker cannot reach ROMs), the session's
controller helper aborted it, and the desktop's Vulkan renderer drew no
cursor. With the Turnip KGSL fix in the driver and the haptic-optional
Cemu in the catalog, this makes the app side complete:

- bannerlator-pad-defaults seeds Cemu's settings.xml once: the Wii U games
  folder, Vulkan, and Cubeb audio on the default device for both the TV
  and the GamePad speaker at full volume (Wipeout 3 plays through the
  GamePad; Cemu's own default is 20 %).
- The Cemu tile launches full screen (-f).
- libfakeinput.so is preloaded into every session process and exported its
  static C++ runtime (~7000 symbols); programs unwound through it and
  aborted (shadPS4 in pthread_exit, Cemu's controller thread). Link with
  --exclude-libs,ALL, and fail the build if the runtime leaks out again.
- On the Vulkan and GLES desktops the pointer worked but no cursor was
  drawn: wlroots hands the cursor to the app's compositor, which draws
  none. WLR_NO_HARDWARE_CURSORS=1 makes labwc composite it itself.

Device-tested on the FIT: Super Mario 3D World in-game at 35-37 fps with
sound, Deus Ex HR DC and Wipeout 3 launch with sound; RPCS3, Dolphin and a
Steam session (FlatOut) unchanged on the same driver.
2026-09-27 16:30:49 -04:00
Kurt Himebauch 68de31ef4b fix(copy): american spelling, clearer keyboard and package labels 2026-09-27 14:23:19 -04:00
Kurt Himebauch d2c9278783 fix(focus): land controller focus by frame instead of fixed delays 2026-09-27 14:22:56 -04:00
Kurt Himebauch d17e590398 fix(motion): honor the system animation scale everywhere and on resume 2026-09-27 14:22:17 -04:00
Kurt Himebauch 4f51416432 refactor(ui): share one frame generation menu and drop the unused dialog 2026-09-27 14:21:41 -04:00
Kurt Himebauch 5702890869 fix(rail): keep sections in fixed places with an empty games state 2026-09-27 14:21:01 -04:00
Kurt Himebauch da672ba00b fix(setup): trim child-process fix to one line and three actions 2026-09-27 14:20:35 -04:00
Kurt Himebauch 64066f7f64 fix(ui): raise buttons, chips and tabs to handheld touch sizes 2026-09-27 14:20:03 -04:00
Kurt Himebauch c4fb6adf7c fix(ui): show launch and tab hints where a pad does that 2026-09-27 14:19:32 -04:00
Kurt Himebauch 18985c581c fix(ui): enable launches like play and say why they are disabled 2026-09-27 14:19:02 -04:00
Kurt Himebauch c6332f88db fix(ui): flip booleans with a switch instead of on/off menus 2026-09-27 14:18:29 -04:00
Kurt Himebauch 8e117a865b fix(ui): use one name each for child-process limit, proton and components 2026-09-27 14:17:49 -04:00
Kurt Himebauch 54b826ff49 fix(session): keep ended screen with retry, share logs and back 2026-09-27 14:17:16 -04:00
Kurt Himebauch d86e6d7776 Merge pull request #40 from Droid-Deck/fix/review-hardening
Fix/review hardening
2026-09-27 13:57:20 -04:00
Kurt Himebauch 6b5bfddf2f Merge branch 'main' into fix/review-hardening 2026-09-27 12:27:22 -04:00
Kurt Himebauch 975f18e572 Merge pull request #37 from maxjivi05/fix/performance-enhancements 2026-09-27 12:26:53 -04:00
xXJsonDeruloXx 9071d574c6 refactor(session): move the overlay setup out of SessionActivity.onCreate 2026-09-27 11:57:43 -04:00
xXJsonDeruloXx bb4223bee3 refactor(session): split runSession into its setup stages 2026-09-27 11:57:08 -04:00
xXJsonDeruloXx c0cc0ae624 refactor(core): share one checksum helper across downloads 2026-09-27 11:56:15 -04:00
xXJsonDeruloXx 64273927f0 refactor(compositor): move zwp_linux_dmabuf_v1 into wl_dmabuf.c 2026-09-27 11:54:48 -04:00
xXJsonDeruloXx f91da6c3ab refactor(launcher): resolve displayNameOf on the activity in DriverMenus 2026-09-27 11:48:05 -04:00
xXJsonDeruloXx 9f04a51810 refactor(launcher): move components, decky and proton state out of MainActivity 2026-09-27 11:43:35 -04:00
xXJsonDeruloXx 39c54ee291 refactor(launcher): move driver menu state out of MainActivity 2026-09-27 11:41:21 -04:00
xXJsonDeruloXx 75db49bb88 refactor(ui): move the session loading screen out of SessionOverlay 2026-09-27 11:39:57 -04:00
xXJsonDeruloXx 63c58e94ce refactor(files): split FileManagerScreen helpers, items and dialog 2026-09-27 11:39:57 -04:00
xXJsonDeruloXx 7997696706 refactor(ui): split FrontEndScreen into per-page files 2026-09-27 11:33:09 -04:00
Kurt Himebauch 76bd76686b Merge branch 'main' into fix/performance-enhancements 2026-09-27 11:31:48 -04:00
xXJsonDeruloXx 4590b0a8c2 feat(runtime): install the runtime from a foreground service 2026-09-27 11:01:55 -04:00
xXJsonDeruloXx 8432fc5306 build(deps): bump commons-compress to 1.27.1 2026-09-27 11:00:47 -04:00
xXJsonDeruloXx a1a4174e1f build(native): enable warnings for the compositor 2026-09-27 10:55:25 -04:00
xXJsonDeruloXx 1ee909eb6b fix(compositor): bound cursor buffer copies by stride and 64-bit size 2026-09-27 10:55:25 -04:00
xXJsonDeruloXx 0976141fa7 fix(security): require a verified checksum for proton downloads 2026-09-27 10:54:31 -04:00
xXJsonDeruloXx 61b7b5486f fix(security): require sha256 for every catalog and release download 2026-09-27 10:54:05 -04:00
xXJsonDeruloXx 7242e3b21a test: add unit tests for archive paths and log redaction 2026-09-27 10:52:01 -04:00
xXJsonDeruloXx c94efd0b01 fix(runtime): refuse hard links that point outside the rootfs 2026-09-27 10:51:14 -04:00
xXJsonDeruloXx 9bc6423cdc fix(ui): keep the launcher alive when a pad connects 2026-09-27 10:49:54 -04:00
xXJsonDeruloXx 2cbd3c975a fix(runtime): single-flight the runtime install and reattach after rebuild 2026-09-27 10:49:54 -04:00
Kurt Himebauch ca466f5b59 Merge pull request #38 from Droid-Deck/feat/steam-startup-toggle
Add optional Steam startup
2026-09-27 09:55:22 -04:00
xXJsonDeruloXx 5110cb6b47 Add optional Steam startup 2026-09-27 09:27:30 -04:00
maxjivi05 866cfbd17a proot: port WinNative's seccomp and tracee performance fixes
Brings the proot patch set up to WinNative 53836ca9 on top of the #772 port:

- trace prctl, setrlimit and prlimit64 in the seccomp filter only for the
  arguments proot acts on, and uname only where the core rewrites it
- answer a seccomp stop's filter flags from the registers fetched for its
  translation, reuse those registers, and skip the sysexit stop of wait4 left
  to the kernel and accept without a sockaddr; the flags come from proot's list
  merged with the enabled extensions, and a syscall an extension replaced
  keeps its exit stop
- take a clone3 child's flags and exit signal from struct clone_args
- name a thread's /proc/self by its thread group and resolve /proc/thread-self
- translate fchmodat2, send openat2 callers to openat, and bound sysnum lookups
  by the table length
- sweep relatives only for tracees that have children or ptracees, and empty
  the per-stop memory collector instead of reallocating it
2026-09-27 08:10:58 -04:00
Kurt Himebauch 0c8882fb26 Merge pull request #36 from Droid-Deck/fix/steam-startup-movie-overrides
Fix Steam startup movie override paths
2026-09-27 00:31:31 -04:00
xXJsonDeruloXx b4dffd7e61 fix: use Steam startup movie override names 2026-09-27 00:16:37 -04:00
Kurt Himebauch 1dab735f8a Merge pull request #35 from Droid-Deck/ui/launcher-refresh
feat(ui): launcher ui refresh
2026-09-26 23:19:09 -04:00
xXJsonDeruloXx 110f5e595d feat(session): animate the logo on the loading screen 2026-09-26 22:44:09 -04:00
xXJsonDeruloXx 2b1ef0d3e6 ui: preserve rail focus on components 2026-09-26 22:16:38 -04:00
xXJsonDeruloXx 2ba872e7f7 ui: add fullscreen launcher and focus tabs 2026-09-26 22:10:17 -04:00
xXJsonDeruloXx 233955c5c1 ui: simplify suspended session overlay 2026-09-26 21:56:55 -04:00
xXJsonDeruloXx 03a624ef47 fix(ui): name the desktop cog's click 2026-09-26 21:37:40 -04:00
xXJsonDeruloXx 10569b5a8d feat(ui): turn the steam tab into a capsule wall 2026-09-26 21:31:26 -04:00
xXJsonDeruloXx a1b8cae36e feat(ui): add a games tab with list and detail 2026-09-26 21:30:19 -04:00
Kurt Himebauch 8a00e54543 Merge branch 'main' into ui/launcher-refresh 2026-09-26 20:46:47 -04:00
Kurt Himebauch 3c02645b1c Merge pull request #34 from Droid-Deck/fix/droiddeck-logo-white-on-black
Invert DroidDeck logo colors
2026-09-26 20:41:45 -04:00
xXJsonDeruloXx b43b803082 Invert DroidDeck logo colors 2026-09-26 20:39:52 -04:00
xXJsonDeruloXx d1ae843f4e fix(ui): raise small text on the remaining pages 2026-09-26 16:43:46 -04:00
The412Banner 747e54cb45 Merge pull request #33 from Droid-Deck/fix/steam-readonly-files
First Steam session: Proton ARM64 ready at sign-in; Steam updates past read-only files
2026-09-26 16:43:35 -04:00
xXJsonDeruloXx 1f426e3ceb feat(session): stack the loading screen on small screens 2026-09-26 16:42:58 -04:00
xXJsonDeruloXx 4438fcf358 feat(session): explain paused sessions and offer stop 2026-09-26 16:42:28 -04:00
xXJsonDeruloXx 2cfc346562 feat(session): compact the session menu on short screens 2026-09-26 16:41:17 -04:00
xXJsonDeruloXx 88b9cdab34 feat(session): redesign the session menu header and actions 2026-09-26 16:40:30 -04:00
xXJsonDeruloXx 2e79090b54 feat(ui): rework the components page layout 2026-09-26 16:38:53 -04:00
xXJsonDeruloXx 3358597dd8 feat(ui): split setup into tabs 2026-09-26 16:36:49 -04:00
xXJsonDeruloXx 72e863ea59 feat(ui): redesign the desktop and emulator pages 2026-09-26 16:35:07 -04:00
xXJsonDeruloXx ef4c03a13f feat(ui): fit the game page to narrow screens 2026-09-26 16:33:53 -04:00
xXJsonDeruloXx 8970bdc516 feat(ui): condense the steam page on small screens 2026-09-26 16:33:19 -04:00
xXJsonDeruloXx e50d7a60f2 feat(ui): scroll launcher pages as one and wrap actions 2026-09-26 16:32:04 -04:00
xXJsonDeruloXx 246f9b6d7b feat(ui): show an icon-only rail on narrow screens 2026-09-26 16:31:10 -04:00
xXJsonDeruloXx d01b536f1b feat(ui): reflow settings pages in narrow panes 2026-09-26 16:30:21 -04:00
xXJsonDeruloXx e31b674493 feat(theme): add readable text colour for signal fills 2026-09-26 16:29:05 -04:00
The412Banner 566018674f Steam updates: the client is not uid 0 in the session - drop that test
The session's proot runs with -i <app uid>, and the rootfs names that uid
root, so the client calls itself root while geteuid() returns the app's
uid. The read-only retry required geteuid() == 0 and never ran: the
Proton Experimental (ARM64) update still failed with Missing file
permissions on wineopenxr.dll (2026-09-26 16:07, 2,471 read-only files).

Also recognise the client by the path it was started by: the kernel's
/proc/self/exe names proot's loader for every process in the session.
2026-09-26 16:10:11 -04:00
The412Banner bc2d85d496 First Steam session: Play sends no mode, and no mode is Steam - seed the Proton then too 2026-09-26 15:44:20 -04:00
The412Banner dcbfc0bb06 First Steam session: place Valve's ARM64 Proton before the client first starts
The runtime setup now also fetches Proton Experimental (ARM64) and its
appmanifest from a catalog of their own (steam-seed.json) and unpacks them
where the client keeps them. At the first sign-in the compatibility tool is
already installed and the default, with no install dialog, no download and
no client restart; the client adopts the manifest and updates it like any
other app.

Done once: an install the client fetched itself, or one the user removed,
is left alone. A failed fetch stops nothing - the session asks the client
for it after sign-in, as before.
2026-09-26 15:36:23 -04:00
The412Banner 6f76bf2b02 Merge remote-tracking branch 'org/main' into fix/steam-readonly-files 2026-09-26 15:33:02 -04:00
The412Banner cb9726b41c Merge remote-tracking branch 'org/fix/first-login-proton-timing' into fix/steam-readonly-files 2026-09-26 15:33:01 -04:00
xXJsonDeruloXx 01c73c3484 fix(ui): guard missing runtime version in setup 2026-09-26 15:19:40 -04:00
xXJsonDeruloXx 6057843d12 feat(ui): add controller button hints 2026-09-26 15:19:00 -04:00
xXJsonDeruloXx 6969d4e2d8 feat(session): show staged progress on loading screen 2026-09-26 15:18:35 -04:00
xXJsonDeruloXx 9527414057 feat(ui): add system check to setup 2026-09-26 15:16:14 -04:00
xXJsonDeruloXx 6fe5ca8d3a feat(ui): add hero and launch settings to game page 2026-09-26 15:14:59 -04:00
xXJsonDeruloXx 85b7477467 feat(ui): add continue hero to steam page 2026-09-26 15:13:20 -04:00
xXJsonDeruloXx b8557d1772 style(ui): drop random hue washes behind pages 2026-09-26 15:12:14 -04:00
xXJsonDeruloXx 7d8f6f530a feat(ui): replace nav drawer with permanent side rail 2026-09-26 15:11:47 -04:00
xXJsonDeruloXx 571a3e2124 refactor(ui): move build label into setup about 2026-09-26 15:09:34 -04:00
xXJsonDeruloXx 60b1125399 feat(library): read hero art and last played from steam 2026-09-26 15:09:07 -04:00
xXJsonDeruloXx f1613bf5b0 feat(ui): show game titles on library tiles 2026-09-26 15:08:30 -04:00
xXJsonDeruloXx 6904beee29 fix(ui): raise small text to 12sp minimum 2026-09-26 15:07:42 -04:00
xXJsonDeruloXx 2ae693ceed feat(theme): add graphite theme as default 2026-09-26 15:07:10 -04:00
Kurt Himebauch 37a1d181cb Merge pull request #31 from Droid-Deck/docs/readme-banner
docs: add readme banner
2026-09-26 15:03:58 -04:00
The412Banner a9125900a2 Steam updates: rewrite read-only content files the way root could
The client runs as proot's root, which the kernel does not see, and
leaves many content files read-only; an update that rewrites one was
refused (EACCES) and cancelled with "Missing file permissions"
(Proton Experimental (ARM64), wineopenxr.dll). For the client's own
processes, a refused write-open of a file the app owns now gets the
owner's write bit - on the file, or on the directory a new file goes
into - and one more try. Games and Wine keep ordinary permissions.
2026-09-26 10:41:06 -04:00
xXJSONDeruloXx e93aab1c1d docs: add readme banner 2026-09-26 09:48:11 -04:00
The412Banner e231efe5e3 First sign-in: ask for the ARM64 Proton after the client's post-logon compat phase, and retry
With Steam publicbeta 1790380355 the client spent 27 s in its post-logon
compat phase and dropped the install request the session sent 15 s after
sign-in, so a clean install never offered Proton Experimental (ARM64).
Wait for the console log's "Waiting for compat in post-logon took" (90 s
at most), then ask, up to three times a minute apart until the install
starts.
2026-09-26 08:21:14 -04:00
The412Banner 7aa760b696 Merge pull request #29 from Droid-Deck/feat/components-manager
Components manager: swap FEX / DXVK / VKD3D-Proton per Proton, enforced at launch
2026-09-26 06:16:04 -04:00
The412Banner 91e02aa119 Components drawer: stacked rows so long names never squeeze the text, a focusable Running now card the pad can reach, and only the Proton a game actually runs on counts as running 2026-09-26 05:30:02 -04:00
The412Banner 2725c64df1 Proton wrappers: run the components check for non-Steam shortcuts too - decide by the game's own prefix rather than the app id, which is 0 for a shortcut 2026-09-26 05:15:36 -04:00
The412Banner bbb09ab909 Merge org main into feat/components-manager 2026-09-26 04:41:10 -04:00
The412Banner 408fbff7da Merge org main into feat/components-manager
Keeps both onResume additions: the phantom status refresh from main and the queued component swaps.
2026-09-26 04:36:02 -04:00
Kurt Himebauch f207076ce2 Merge pull request #28 from Droid-Deck/fix/session-desktop-button-label
Keep the session Desktop button on one line
2026-09-26 00:53:17 -04:00
xXJsonDeruloXx 29623ec389 Keep session desktop button label on one line 2026-09-26 00:52:53 -04:00
Kurt Himebauch ac61bde072 Merge pull request #27 from Droid-Deck/fix/boot-video-pitch-black
Set boot movie background to pitch black
2026-09-26 00:36:22 -04:00
xXJsonDeruloXx 7e012aade9 Set boot movie background to pitch black 2026-09-26 00:35:54 -04:00
The412Banner dd62cae62e Proton wrappers: run the components check only for a real game launch (waitforexitandrun, non-zero app id)
With the check reached from the client's own runs right after login, the Steam client segfaulted
within seconds, four sessions out of four; the same build without the check (test/bisect-no-launch-check)
and the last build before it both reach Big Picture. Now every run that is not a game launch -
the client's probes, app id 0 - is left exactly as before. Each check logs one line.
2026-09-26 00:28:49 -04:00
Kurt Himebauch b0982772d0 Merge pull request #26 from Droid-Deck/ui/build-info-top-bar
Show build identity in the launcher top bar
2026-09-26 00:24:39 -04:00
The412Banner f092391a39 Remove a stray __pycache__ from the overlay scripts and ignore it 2026-09-26 00:20:41 -04:00
xXJsonDeruloXx 23f833d471 Show build identity in launcher top bar 2026-09-26 00:03:56 -04:00
The412Banner c7aca2834f Proton wrappers: drop the per-tool prefix for Steam's tool-0 runs - the client segfaulted right after its probe started (first in 25 sessions); keep the launch-time component check 2026-09-26 00:03:45 -04:00
The412Banner 66ef6ca802 Components: choices enforced at every game launch; Steam's tool-0 probes get their own prefix per Proton
Steam runs its graphics probe through every registered Proton at once after login, all in
compatdata/0. Prefix system32 links point into the Proton that set it up last, and the other
Protons' Wines, running outside Proton's pfx.lock, wrote their builtin DLLs through them into that
Proton's tree (device: GE's FEX DLLs became Proton Experimental's at session start).

- bannerlator-steam-compat: both wrappers (Valve's depot launcher and the third-party
  bannerlator-proton-wrap) give a tool-0 run STEAM_COMPAT_DATA_PATH=.../compatdata/0-<tool>, and
  before every other launch check the Proton's files against the app's desired.tsv and put back
  anything that differs (same Proton build only), logging to reapplied.log.
- ComponentsManager writes desired.tsv plus unpacked copies of the packages in use into the
  runtime after every change; the page and the drawer show 're-applied at launch'.
2026-09-25 23:54:10 -04:00
Kurt Himebauch b5b71bb7bd Merge pull request #21 from Droid-Deck/fix/phantom-process-setup-gate
Gate Steam sessions on Android process limit
2026-09-25 23:51:11 -04:00
xXJsonDeruloXx 06ed076919 Merge remote-tracking branch 'origin/main' into fix/phantom-process-setup-gate 2026-09-25 23:50:57 -04:00
xXJsonDeruloXx e69ed55356 Reuse saved wireless ADB pairing and connection 2026-09-25 23:48:47 -04:00
xXJsonDeruloXx f5f5a8ec3b Clarify Wireless debugging connection address after pairing 2026-09-25 23:39:31 -04:00
Kurt Himebauch 14e59a1fde feat: set DroidDeck startup movie by default (#25) 2026-09-25 23:36:29 -04:00
xXJsonDeruloXx 90d7eede2e Improve wireless ADB text input 2026-09-25 23:17:28 -04:00
The412Banner ff73c44ca9 Session drawer: tab spacing fits the room between LB and RB, so all four tabs show 2026-09-25 22:59:50 -04:00
The412Banner 36a06f1ffc Components: the pad's LB / RB step through FEX, DXVK and VKD3D-Proton (wrapping), with LB / RB badges beside the tabs 2026-09-25 22:49:10 -04:00
The412Banner e1aa6a1d0e Components: fixed columns so details and Download buttons line up; focused button is a solid blue fill with a white outline 2026-09-25 22:37:07 -04:00
The412Banner 51885e95e6 Components: compact layout - one toolbar line (title, Proton drop-down, component tabs, in use, (i), refresh) and one list of short rows for installed and available packages 2026-09-25 22:27:25 -04:00
The412Banner 44e2ffaf80 Merge pull request #23 from Droid-Deck/docs/progress-log-0925
Progress log: 2026-09-25
2026-09-25 22:06:23 -04:00
The412Banner fe00cae540 Components: FEX, DXVK and VKD3D-Proton per Proton
A Components entry under Desktop opens a page built like the driver pages: a
Proton drop-down, FEX / DXVK / VKD3D-Proton tabs, what is in use, the installed
rows (each Proton build's original bundle and the stored packages; tap to
swap in, trash to delete), downloads from the Nightlies -Linux releases behind
the refresh button, and Import .wcp.

The session drawer gets a fourth tab, Components, for quick swaps between
what is installed. A swap into the Proton a running game uses waits until the
game closes and is applied then (drawer refresh, or the main screen on resume).

ComponentsManager keeps the same package format, originals layout and state as
the Decky Components Manager plugin and takes its data over once. A swap clears
only the component's own DLLs, so files a Proton keeps beside them stay.
2026-09-25 22:04:38 -04:00
The412Banner 12ed8fdf2d Merge pull request #24 from Droid-Deck/feat/drawer-tab-icons
Session drawer: QAM-style page tabs (Display, Controls, Settings)
2026-09-25 21:55:16 -04:00
The412Banner de8db505b6 Session drawer: QAM-style page tabs - Display, Controls, Settings
The three page dots between LB and RB become icons standing in a line in page order, as in
Steam's Quick Access Menu. The selected one steps forward (full size, bright, a soft glow in the
accent), the others step back to half size and fade, and the line leans a little toward the
selection; tapping or picking a tab with the pad, and LB / RB, turn the page as before. The
first page's heading becomes Display to match its icon.
2026-09-25 18:11:04 -04:00
The412Banner 2f1d91e8a6 Progress log: 2026-09-25 2026-09-25 17:29:12 -04:00
xXJsonDeruloXx 63f7554f43 Merge remote-tracking branch 'origin/main' into fix/phantom-process-setup-gate 2026-09-25 17:23:08 -04:00
xXJsonDeruloXx 4565bfe88b Compact Steam process limit recovery for split screen 2026-09-25 17:21:43 -04:00
The412Banner 12813f2e81 Merge pull request #22 from Droid-Deck/feat/steam-touch
Steam client: real touch in Big Picture, and a touchpad cursor that hovers and clicks
2026-09-25 17:06:19 -04:00
The412Banner 625f1a8980 Merge remote-tracking branch 'org/main' into feat/steam-touch 2026-09-25 16:58:18 -04:00
The412Banner 126410a05a gamescope: stage 3.16.29-p3 (0111, pointer warps in Passthrough) 2026-09-25 16:38:50 -04:00
xXJsonDeruloXx e31885441d Replace process gate dialogs with compact pages 2026-09-25 16:34:30 -04:00
The412Banner 18e6d8a942 gamescope: the nested pointer moves Steam's pointer in Passthrough too
In touchpad mode the cursor moved but Big Picture highlighted nothing and a tap-to-click
selected nothing: the nested pointer's motion reaches wlserver as touch 0, and in Passthrough
(the touch mode Big Picture sets) a motion for a touch that is not down moves nothing, so the X
pointer stayed where it was. 0111 passes bAlwaysWarpCursor, as the other touch modes already warp.
2026-09-25 16:34:22 -04:00
Kurt Himebauch 62ce42947a Merge branch 'main' into fix/phantom-process-setup-gate 2026-09-25 16:28:53 -04:00
xXJsonDeruloXx b6998206ef Replace process gate dialogs with pages 2026-09-25 16:28:15 -04:00
Kurt Himebauch cc38a1138c Add native Decky Loader support (#16)
* Add native Decky Loader management

* Enable Decky Steam frontend injection

* Disable unavailable stable Decky channel

* Decky: tie CEF debugging to session setting

* Decky: simplify Steam settings controls
2026-09-25 16:26:29 -04:00
xXJsonDeruloXx 09f8eb8ee3 Replace wireless ADB modal with setup page 2026-09-25 16:19:01 -04:00
The412Banner 0c06ede8d3 gamescope: stage 3.16.29-p2 (0110, touch from the host) 2026-09-25 16:06:31 -04:00
The412Banner 62687b8680 gamescope: take the host's touch in the nested Wayland backend
3.16.29's Wayland backend bound only wl_pointer and wl_keyboard; touch from DroidDeck's
compositor was never read, so Steam could not be touched. 0110 binds wl_touch in the input
thread and feeds each finger to wlserver_touchdown/touchmotion/touchup, the Steam Deck
touchscreen's path, so Big Picture's Passthrough touch mode gets real touches.
2026-09-25 15:55:26 -04:00
The412Banner d087f8174e Touch: a lost finger-up no longer blocks every later touch
On the pointer path (a client that takes no wl_touch - gamescope), touch_down counted the
fingers still down before clearing a sequence whose up had been lost, so the stale point left
any_active set and every later tap or swipe was dropped: in Direct touch mode Big Picture
ignored the screen while the pad worked. The count is taken after the clean-up, and the app
ends whatever the compositor still holds when a new gesture's first finger lands (Android has
ended every earlier gesture by then). The compositor logs once per gesture whether the window
got touch or the pointer.
2026-09-25 15:55:26 -04:00
xXJsonDeruloXx f379a1f30a Add reversible process limit controls 2026-09-25 14:52:04 -04:00
xXJsonDeruloXx f9848c23a2 Add wireless ADB process-limit repair 2026-09-25 14:35:37 -04:00
xXJsonDeruloXx 75387211d1 Auto-dismiss process limit gate after status clears 2026-09-25 13:55:04 -04:00
xXJsonDeruloXx 9c0ea07c39 Gate Steam sessions on Android process limit 2026-09-25 13:49:58 -04:00
Kurt Himebauch 93b9be67fc Build local APK with CI payload parity (#20) 2026-09-25 13:20:51 -04:00
The412Banner a9c1920f9f Merge pull request #19 from Droid-Deck/fix/log-privacy
Session logs: scrub public addresses, device id, Steam account names, and every file
2026-09-25 12:53:30 -04:00
The412Banner cf6f2f7969 Logs: scrub Steam account and persona names for every user
The client's UI log names the signed-in account on every login change ("Login:
OnLoginStateChange <account> ...", "OnLoginUsersChanged <account>"), ~200 lines on one device.
An email account was caught by the email rule; a plain account name was not, so a user signing in
with one had it in every shared log. Those two lines now lose the account, and every
AccountName and PersonaName in the client's loginusers.vdf is learned (with the device's own
addresses, learnFromRuntime) and replaced wherever it appears. The one-time pass over older
folders runs again for this (marker .scrubbed-2).
2026-09-25 11:51:37 -04:00
The412Banner 1fb6f088a2 Logs: no public addresses or device id, and every session file scrubbed
A privacy pass over the session folders found three leaks:
- the Steam client's IPv6 check logs "external address <the device's public IPv6>" about
  twenty times a session, and the redactor kept IP addresses on purpose, so steam/
  connection_log.txt carried it;
- network.txt listed the device's addresses (public IPv6 included) and the session's made-up
  MAC, which is seeded from ANDROID_ID and so stable per device;
- session.log, app.log, desktop.log, steam-desktop.log, crash.log were never scrubbed (clean on
  device, by luck).

The redactor now learns the device's own addresses from the session's link file (a global IPv6
as its /64, a public IPv4 exactly) and removes them wherever they appear, along with anything
logged as an "external address"; Valve's server addresses stay. network.txt gives every address
as its kind ("<public IPv6>") and masks the MAC. Every text file in a session folder goes
through the redactor when the session ends (killed sessions at the next app start), and again on
its way into the share-logs zip. Folders written before this are scrubbed once at app start.
2026-09-25 11:30:16 -04:00
The412Banner 495a42368e Merge pull request #18 from Droid-Deck/feat/melonds-preset
melonDS out of the box, cursor shapes and auto-hide, Steam desktop switching
2026-09-25 11:15:19 -04:00
The412Banner 856f694a0b Merge branch 'main' into feat/melonds-preset 2026-09-25 10:32:03 -04:00
The412Banner fa1ce6fb9c Session: start a relaunch request in place, so DESKTOP reaches the desktop
The drawer's DESKTOP ended Big Picture and landed on the front end. The session activity is
singleTop: startActivity(next) from it went to its own onNewIntent and was dropped when it
finished (on device the next session came up as "steam", not the desktop). It now restarts on
the request's intent (setIntent + recreate), as an agent start does; the desktop's Steam
launchers' handoff to a Steam session goes the same way.
2026-09-25 10:09:55 -04:00
The412Banner dad9d65701 Session drawer: DESKTOP in Steam sessions opens the desktop with Steam in it
Big Picture's own Switch to Desktop waits on SteamOS Manager and never finishes here. The
drawer's Steam row gets a DESKTOP button beside STEAM and QAM: the session ends and the desktop
starts in its place with Steam's desktop client open (the relaunch the Steam Desktop UI button
uses).
2026-09-25 09:56:33 -04:00
The412Banner 0bec2a5435 Revert the Steam session's D-Bus session bus
Tried on device: with a session bus the client still sat on "Switching to Desktop..." and
never reached its legacy path (no "No SteamOSManager support" line, steamos-session-select not
called), so the bus was not what it waited on. Sign-in was unaffected.

This reverts commit 40fc5463f3.
2026-09-25 09:55:59 -04:00
Kurt Himebauch 5dae95376a feat: add session log sharing (#17) 2026-09-25 09:44:33 -04:00
The412Banner c8620017a5 Steam session: a D-Bus session bus, so Switch to Desktop reaches steamos-session-select
Big Picture's Switch to Desktop asks SteamOS Manager over the session bus before falling back to
steamos-session-select (steamui.so). The Steam session had no session bus, the ask never
finished, and the client sat on "Switching to Desktop..."; the new stub was never called. A
bus with nothing on it answers at once, and the client takes its legacy path.
2026-09-25 09:41:21 -04:00
The412Banner 7c6df51bfa Steam desktop UI on the desktop; Big Picture's Switch to Desktop works
- The front end's "Steam Desktop UI" started a Steam session without -gamepadui, and the client
  put itself into Big Picture under gamescope anyway. It now opens the desktop with Steam's
  desktop client started in it (BL_DESKTOP_STEAM, the desktop's autostart, the same
  bannerlator-steam-launch the menu's Steam entry uses).
- Big Picture's power menu "Switch to Desktop" runs steamos-session-select, which did not exist,
  so it sat on "Switching to Desktop..." for ever. The new stub leaves the app a request
  (session=desktop in the steam-launch file); the app ends the Steam session and starts the
  desktop with Steam's desktop client open.
2026-09-25 08:59:06 -04:00
The412Banner 09e5172052 Desktop: centre melonDS's 800x600 window
AutoPlace on its own left the resized window in the top-left corner; with policy="center" it
opens in the middle of the desktop. Tried in a running desktop (labwc --reconfigure).
2026-09-25 08:36:04 -04:00
The412Banner 132b434d78 Desktop: melonDS opens as an 800x600 window, centred
melonDS remembers a 1x window for the front end's full-screen toggle, too small on a desktop.
A labwc window rule (only the desktop reads rc.xml) resizes it to 800x600 on open and places
it again by the desktop's centre policy.
2026-09-25 08:28:56 -04:00
The412Banner c331836bce Session: draw the program's own pointer - resize arrows at window edges, I-beams, its hide
The compositor threw every wl_pointer.set_cursor image away and the app drew one fixed arrow,
so the desktop never showed labwc's resize arrows at a window's edges and corners. Ported from
Bannerlator (7f19c669): the cursor surface gets a role, its image is snapshotted on commit with
its hotspot, and a NULL surface or buffer is an authoritative hide; the app's overlay draws the
image at the session's scale. New here: labwc on Vulkan/GLES hands its cursor over as a dma-buf
(wlroots' nested backend, usually UBWC), so it is read back through a small linear host-visible
copy (vkp_image_readback). The built-in arrow stays until a program sets a cursor.
2026-09-25 08:28:26 -04:00
The412Banner 347a5a4dcb Desktop: Qt falls back to X11, so melonDS opens from the menu
The desktop set QT_QPA_PLATFORM=wayland for every program. The melonDS AppImage bundles only
Qt's xcb plugin, so from the desktop it exited at once: "Could not find the Qt platform plugin
wayland ... Available platform plugins are: xcb" (exit 127). "wayland;xcb" keeps LXQt on
Wayland and lets such an AppImage use Xwayland. Proven in a running desktop: labwc mapped
"melonDS 1.1".
2026-09-25 08:18:43 -04:00
The412Banner 79a7f26852 Session: hide the mouse cursor while a controller or the on-screen controls are in use
The arrow is drawn by the app and, outside a Steam session, never went away once shown - on
the desktop and over emulators started from the front end alike. A button press, a stick,
trigger or d-pad past halfway, or any on-screen control now hides it (PadBridge tells the
activity); the next touchpad or mouse move brings it back, once the pad has been quiet for
1.2 s so a touch during play does not flash it up (Bannerlator's rule for its Wayland pointer).
2026-09-25 08:08:09 -04:00
The412Banner 5f7f6bd678 melonDS: leave full screen to a 1x window, so its menus are big enough to use
The guide button's toggle returns to melonDS's saved window size. A 512x288 window (1x wide,
16:9) is stretched by gamescope to fill the panel, menu bar and cursor with it; at 2x and up
the menus stay at their real size, tiny on a handheld. Written once, with the layout.
2026-09-25 07:52:26 -04:00
The412Banner 9adcb3d91f melonDS: big top screen with the bottom screen alone beside it
Hybrid repeats the big screen, small, above the bottom one. Horizontal with the top screen
emphasised fills the panel's height with the top screen and centres the bottom screen in the
rest. Installs set up with Hybrid (or still on melonDS's defaults) move once.
2026-09-25 07:47:59 -04:00
The412Banner e199d7a421 HUD: follow the game window, not a program's cursor or menus
A program's small surfaces (gamescope's 1x1 cursor, Qt's menus and tooltips) announced
themselves after the game's window and took the HUD, which then read 0 fps while melonDS ran
at 60. A new window now takes the HUD only when it is at least as big, and a window quiet for a
second gives way to the next one that draws.
2026-09-25 07:39:16 -04:00
The412Banner e753ab9c66 melonDS: Hybrid screens, full screen from the front end, guide toggles full screen, BIOS found in the ROMs folder
- bannerlator-pad-defaults: new installs start on the Hybrid layout with sharp pixels; an
  existing install is moved once, only where still on melonDS's defaults (marker keeps later
  choices). Guide = HK_FullscreenToggle. The user's DS/DSi BIOS, firmware and NAND are found in
  ROMs/nds/{bios,firmware} and filled in only where melonDS has no path.
- Front end starts melonDS with -f, at the panel's own resolution unless the user chose one.
2026-09-25 07:37:21 -04:00
Kurt Himebauch 2436f6388f Merge pull request #13 from Droid-Deck/feat/hamburger-rail
Use hamburger drawer for launcher navigation
2026-09-25 01:25:53 -04:00
Kurt Himebauch 60a3ff8372 Merge branch 'main' into feat/hamburger-rail 2026-09-25 01:25:28 -04:00
Kurt Himebauch de1bf8da2b Merge pull request #15 from Droid-Deck/feat/droiddeck-steam-hostname
Make Steam identify DroidDeck as the host
2026-09-25 00:50:32 -04:00
xXJsonDeruloXx d2ebb7709e Set PRoot guest hostname to DroidDeck 2026-09-25 00:48:03 -04:00
Kurt Himebauch 16b5e1c5b2 Merge pull request #14 from Droid-Deck/feat/live-session-suspend-policy
Add live suspend policy control
2026-09-25 00:31:16 -04:00
xXJsonDeruloXx c6dd03e061 Add live suspend policy to session drawer 2026-09-25 00:26:58 -04:00
xXJSONDeruloXx 49dbcfa2e2 fix: fit library art on short screens 2026-09-25 00:12:17 -04:00
xXJSONDeruloXx e9b07a508e fix: add headroom above focused game tiles 2026-09-25 00:01:40 -04:00
xXJSONDeruloXx f15426c136 feat: use hamburger drawer for launcher navigation 2026-09-24 23:57:01 -04:00
MaxsTechReview 00057932cd Merge pull request #9 from maxjivi05/feature/controller-editor
Feature/controller editor
2026-09-24 23:08:23 -04:00
MaxsTechReview 00e266d9b4 Merge branch 'main' into feature/controller-editor 2026-09-24 23:08:05 -04:00
maxjivi05 58b2b728ab Fit the on-screen pad to phones, add adaptive sticks
Scale controls down on short screens with a 9.5 mm face-button floor and
keep a bottom margin. On phones the shoulders, View and Menu sit along the
top edge; bumpers and triggers are drawn as side-by-side rectangles.
Adaptive sticks centre under the thumb and return home on release, with a
toggle in the Controller settings. The editor toolbar is a compact pill.
2026-09-24 23:05:45 -04:00
Kurt Himebauch c5001098fd Merge pull request #12 from Droid-Deck/fix/steam-library-capsules
Fix Steam library capsule artwork
2026-09-24 22:57:27 -04:00
Kurt Himebauch bf70ca8f25 Merge branch 'main' into fix/steam-library-capsules 2026-09-24 22:57:17 -04:00
xXJSONDeruloXx 44e4cfb785 Fix Steam library capsule artwork 2026-09-24 22:52:37 -04:00
MaxsTechReview afbead8049 Merge branch 'main' into feature/controller-editor 2026-09-24 22:45:27 -04:00
The412Banner 9767feb84b Merge pull request #10 from Droid-Deck/feat/desktop-steam-dolphin
Desktop: Steam launchers on the desktop, two-finger drag, Dolphin GameCube/Wii
2026-09-24 22:43:34 -04:00
MaxsTechReview 046eb9e319 Merge branch 'main' into feature/controller-editor 2026-09-24 22:38:46 -04:00
The412Banner 1103627ee1 Merge remote-tracking branch 'org/main' into feat/desktop-steam-dolphin
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/session/SessionService.kt
2026-09-24 22:35:37 -04:00
Kurt Himebauch 18dc3dd2de Merge pull request #7 from Droid-Deck/feat/debug-agent-control
Add debug-only DroidDeck agent control
2026-09-24 22:34:41 -04:00
The412Banner a28e2ffe72 Merge remote-tracking branch 'origin/feat/desktop-steam' into feat/desktop-steam-dolphin 2026-09-24 22:24:00 -04:00
MaxsTechReview 02806bb389 Merge branch 'Droid-Deck:main' into feature/controller-editor 2026-09-24 22:23:26 -04:00
xXJSONDeruloXx 1006688914 fix(agent-bridge): launch sessions through debug activity 2026-09-24 22:16:28 -04:00
maxjivi05 db79674d5e Add a Controller section to Setup with an on-screen pad editor
The on-screen pad is now placed from the panel's physical size: the
sticks and face buttons sit at the two-thumb rest point (about 17% of
the width inboard, 28% of the height up, clamped in millimetres), with
the d-pad and right stick below-inboard, shoulders above, and nothing
ever placed off screen or under a display cutout. Button sizes are
unchanged.

Setup > Controller holds the on-screen controls mode, a tint (Steam
blue #1A9FFF by default), opacity, button size, stick click (double-tap
for L3/R3), a full-screen layout editor, per-button mapping including
LT/RT, and resets for each.

Also from WinNative #772: the fakeinput ring poll backoff is capped at
2 ms instead of 16 ms, the session asks for unbuffered joystick input,
and the on-screen controls request unbuffered dispatch per gesture and
release everything when detached. The QAM button no longer draws at the
top-left corner in full-pad mode.
2026-09-24 22:13:48 -04:00
Kurt Himebauch 991d7e24d2 Merge branch 'main' into feat/debug-agent-control 2026-09-24 22:00:40 -04:00
Kurt Himebauch a54e7d45f1 Merge pull request #8 from Droid-Deck/chore/codebase-deslop
chore/cleanup
2026-09-24 22:00:23 -04:00
Kurt Himebauch bd9c60bd11 Merge branch 'main' into chore/codebase-deslop 2026-09-24 22:00:12 -04:00
Kurt Himebauch 0ad96a2d23 Merge pull request #4 from maxjivi05/fix/proot-performance
proot: build from source with WinNative's performance fixes
2026-09-24 21:59:27 -04:00
xXJSONDeruloXx f21e84be91 chore: remove dead code and stale comments 2026-09-24 21:53:31 -04:00
xXJsonDeruloXx 63b24ca4c0 Add debug-only agent control bridge 2026-09-24 21:34:50 -04:00
The412Banner 123a8f8fa3 Dolphin: guide = its Toggle Fullscreen hotkey (full default Hotkeys.ini), Wii Home on R1, input without focus from the rail 2026-09-24 21:31:25 -04:00
maxjivi05 531087be16 Merge Droid-Deck main into fix/proot-performance 2026-09-24 21:27:31 -04:00
Kurt Himebauch 643d82e2fd Merge pull request #6 from Droid-Deck/feat/in-session-sidebar-menu-review
Paged in-session drawer with controller navigation
2026-09-24 21:26:01 -04:00
Kurt Himebauch 7c9ec71a28 Merge branch 'main' into feat/in-session-sidebar-menu-review 2026-09-24 21:20:21 -04:00
xXJSONDeruloXx e95600ed0b feat: confirm session stop from drawer header 2026-09-24 21:19:20 -04:00
Kurt Himebauch c50e58caba Merge pull request #5 from Droid-Deck/chore/protect-main-signing
Gate release-signed PR APKs behind review
2026-09-24 21:15:27 -04:00
xXJSONDeruloXx 870a0b6279 feat: add paged in-session drawer navigation 2026-09-24 21:14:55 -04:00
xXJSONDeruloXx 4bc7c8dc99 Trim signing key script comments 2026-09-24 21:02:13 -04:00
xXJSONDeruloXx 67b675066f Trim workflow commentary and redundant build output 2026-09-24 21:01:35 -04:00
The412Banner d38586edb1 Dolphin from the rail: guide toggles full screen (its window and settings), drawn in the main window 2026-09-24 21:00:43 -04:00
xXJSONDeruloXx 98066f85ef Simplify approved PR signing workflow 2026-09-24 20:45:25 -04:00
The412Banner c0977d4344 Dolphin: its data where it looks for it (fonts, per-game fixes), game folders, Vulkan, no NKit box; full screen from the front end 2026-09-24 20:35:48 -04:00
xXJSONDeruloXx 6d4fc6775c Preserve the current PR APK until upload succeeds 2026-09-24 20:22:36 -04:00
xXJSONDeruloXx 097f56cd6c Match PR APK version to latest stable 2026-09-24 20:19:14 -04:00
xXJSONDeruloXx 2a0f859701 Use the pinned release signer for PR APKs 2026-09-24 20:16:28 -04:00
xXJSONDeruloXx 7aa56118ae Require repo write access for signing approvals 2026-09-24 20:13:40 -04:00
xXJSONDeruloXx 31cea4e0e3 Gate release-signed PR APKs on maintainer approval 2026-09-24 20:08:33 -04:00
xXJSONDeruloXx cbb66ea188 chore: harden release signing boundaries 2026-09-24 19:38:04 -04:00
The412Banner 0c06868c5d Touchpad: one finger held, the other slides = drag (move windows, resize, drag icons) 2026-09-24 19:10:29 -04:00
The412Banner 6e69f252f6 Desktop: the Steam and Big Picture launchers run the client on the desktop itself 2026-09-24 18:56:24 -04:00
MaxsTechReview a9cf5cacbc Merge pull request #3 from maxjivi05/ci/pr-release-publish
CI: publish each pull request's APK to DroidDeck-CI
2026-09-24 18:15:04 -04:00
The412Banner 42cde57c86 Merge pull request #45 from The412Banner/release/0.1.7
Release 0.1.7 (versionCode 8)
2026-09-24 16:42:59 -04:00
The412Banner b3794ffc20 Release 0.1.7 2026-09-24 16:37:31 -04:00
maxjivi05 ff213de6c3 CI: publish each pull request's APK to DroidDeck-CI
After a pull request's Build APK run succeeds, its APK is published as the
pr-<number> prerelease in Droid-Deck/DroidDeck-CI, named
DroidDeck-PR.<number>-<commit>.apk, with release notes taken from the pull
request. New commits to the same pull request replace its APK. Pull requests
from forks are resolved by head owner, branch and commit.

A pull request's build is signed only with the committed testkey, so every CI
build installs over the last; the publish checks the certificate against it
and refuses anything else.

Needs a DROIDDECK_CI_TOKEN secret: a fine-grained token with Contents
read/write on Droid-Deck/DroidDeck-CI. Without it the run skips with a warning.
2026-09-24 16:07:28 -04:00
The412Banner 2e53de0c13 Merge pull request #44 from The412Banner/feat/home-screen-toggle
Home screen: a switch in Setup, off by default (replaces the Non-Launcher apk)
2026-09-24 15:06:39 -04:00
The412Banner 952c28316f Home screen: a switch in Setup (off by default) instead of a second apk 2026-09-24 14:39:20 -04:00
The412Banner efde531208 Merge pull request #31 from The412Banner/docs/progress-log-0924
Progress log: 2026-09-24
2026-09-24 14:02:08 -04:00
The412Banner 7ec1fad2d1 Progress log: PS1, controller focus, own signing key, CI hardening (2026-09-24) 2026-09-24 13:57:10 -04:00
The412Banner cc2c49b3c5 Merge remote-tracking branch 'origin/main' into docs/progress-log-0924 2026-09-24 13:55:03 -04:00
The412Banner 8e11a44f85 Merge pull request #41 from The412Banner/feat/release-signing
Signing with DroidDeck's own key, four package names, file-picker access, CI hardening
2026-09-24 13:42:47 -04:00
The412Banner 68172e5d0b Merge remote-tracking branch 'origin/main' into feat/release-signing 2026-09-24 13:38:31 -04:00
The412Banner 6125d853f4 CI: build pull requests with pull_request, not pull_request_target (no shared caches with main) 2026-09-24 13:37:43 -04:00
The412Banner f1ba5503c9 CI: the key alias is plain text, not a secret 2026-09-24 13:24:47 -04:00
Kurt Himebauch f0c8e08af0 Improve session drawer stick navigation (#40) 2026-09-24 13:18:09 -04:00
The412Banner 709668a479 sign-apk: match apksigner's 'V3.0 Signer:' lines too 2026-09-24 13:17:57 -04:00
The412Banner c0a264564b sign-apk: read the signer digest in either apksigner format 2026-09-24 13:12:09 -04:00
The412Banner bdf246bc1f CI: pass secrets on to sign.yml so it can read the environment's key 2026-09-24 13:06:26 -04:00
The412Banner 38151a150a The release key's certificate digest (public), checked against every signed apk 2026-09-24 12:59:34 -04:00
The412Banner af66e29471 CI: give the signing job the permissions sign.yml asks for 2026-09-24 12:46:39 -04:00
The412Banner cd28b189a2 Signing with DroidDeck's own key: hand-over from the testkey, test builds and a four-package release 2026-09-24 12:45:13 -04:00
The412Banner 498a37db32 The app's files in Android's file picker (documents provider), for every package name 2026-09-24 12:35:32 -04:00
The412Banner e4613d39ab Merge pull request #38 from The412Banner/feat/duckstation-play
PS1 one-tap launch, one tile per disc, controller lands on the main button and first game
2026-09-24 11:33:31 -04:00
Kurt Himebauch 0d85bd3053 Update DroidDeck branding to split D mark (#37) 2026-09-24 11:33:14 -04:00
The412Banner 68ab64d08c Merge remote-tracking branch 'origin/main' into feat/duckstation-play 2026-09-24 11:27:12 -04:00
The412Banner cf36224ee9 Front end: Down from a page's buttons goes to the first tile 2026-09-24 11:19:12 -04:00
Kurt Himebauch 417c119501 Merge pull request #33 from The412Banner/feat/secondary-screen-app-launch
Add per-launch Android display selection
2026-09-24 11:14:27 -04:00
xXJsonDeruloXx 1753341a8f Merge remote-tracking branch 'origin/main' into feat/secondary-screen-app-launch
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt
#	app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt
2026-09-24 11:09:24 -04:00
Kurt Himebauch b41e5ff971 Merge pull request #35 from The412Banner/fix/ci-build-cleanliness
Snapshot CI build cleanliness before generated assets
2026-09-24 10:45:35 -04:00
The412Banner 41e6f461fd Front end: a page opened from a tile takes the pad on its main button 2026-09-24 10:42:46 -04:00
xXJsonDeruloXx 1d2df612e0 Snapshot CI tree state before generated assets 2026-09-24 10:39:38 -04:00
The412Banner fd4c019c9f DuckStation, ARMSX2: first-time setup answered for the user; ARMSX2 names the BIOS dump itself 2026-09-24 10:09:45 -04:00
Kurt Himebauch 78ecfd8be5 Merge pull request #24 from The412Banner/feat/launch-build-status
Show build identity and latest build link
2026-09-24 10:09:09 -04:00
The412Banner c39ea70cf5 DuckStation: don't try to inhibit a screensaver the session doesn't have 2026-09-24 09:46:59 -04:00
xXJsonDeruloXx 9039197349 Put build source below watermark identity 2026-09-24 09:43:09 -04:00
xXJsonDeruloXx 3345f1c32b Show build source and worktree state 2026-09-24 09:40:50 -04:00
The412Banner dbaaa5b541 Front end: one tile per disc - hide tracks a .cue/.gdi/.m3u names, skip BIOS folders 2026-09-24 09:39:38 -04:00
The412Banner 26bb514ccd Merge pull request #34 from The412Banner/feat/duckstation
DuckStation (PS1) setup + front-end controller focus
2026-09-24 09:34:53 -04:00
xXJsonDeruloXx 288e59e865 Add display choice to Android app launches 2026-09-24 09:30:34 -04:00
The412Banner 9584f94681 Front end: come back to the last focused tile or button, not the grid 2026-09-24 09:26:46 -04:00
The412Banner 3d051f018e Front end: leave touch mode on open so the rail item can take focus 2026-09-24 09:19:10 -04:00
The412Banner c5b58a938a Front end: outline the rail item on open, remember the pane control across Left/Right 2026-09-24 09:12:31 -04:00
The412Banner 181b320ee1 DuckStation (PS1) set up like ARMSX2
- From the rail: -batch -bigpicture -fullscreen (the same flags as PCSX2's,
  src/duckstation-qt/qthost.cpp) - straight into the game, gone when it is quit.
- bannerlator-pad-defaults: a new player starts on DuckStation's first-time setup
  with the BIOS folder in the ROMs folder (psx/bios, created, or an existing one),
  psx as the game list, Vulkan, the controller on Player 1 and the guide button
  on the pause menu (not the analog toggle). An existing install gets the guide
  and game-list changes, and once, its own empty BIOS folder moved to the ROMs
  folder and Automatic made Vulkan; later choices are kept. Users bring their own
  BIOS and games.
2026-09-24 09:03:34 -04:00
The412Banner 88803f1276 Front end: controller focus starts on the rail and moves between rail and page predictably
- The app opens with the selected rail item focused (its outline shows).
- Right from the rail enters the page on its main button (Play, Open desktop,
  Open <emulator>, Launch, Launch in <emulator>); Down from there reaches the
  tiles as before.
- Left from anything at the page's left edge - the main button, the first tile of
  any row - returns to the selected rail item, not whichever one is nearest.
  (Up, where the rail sits above the page.)
2026-09-24 08:59:45 -04:00
The412Banner 9ad38e0c3a Merge pull request #32 from The412Banner/feat/fullscreen-toggle
Stretch games to fill the screen (cog + live drawer), Quake-engine titles windowed at the session's size
2026-09-24 08:41:10 -04:00
The412Banner 691dbe80d9 Merge remote-tracking branch 'origin/main' into feat/fullscreen-toggle 2026-09-24 08:35:43 -04:00
The412Banner afdcf05915 Proton launch: Quake-engine titles stay windowed at the session's size
Quake III Arena, Team Arena, Return to Castle Wolfenstein and Jedi Academy set their own
resolution and switch the display to it. Under gamescope with the ARM64 Proton that switch is
refused ("calling CDS: failed, bad mode") and the game never gets OpenGL - device-seen on a
Retroid Pocket 5 on Proton Experimental build 25406135 - and a window left at the game's own size
draws small in a corner. Both wrappers (Valve's depot and an adopted GE or CachyOS) now add
+set r_fullscreen 0 +set r_mode -1 +set r_customwidth/height at BL_WIDTH x BL_HEIGHT for those
app ids, unless the user's launch options already set the video mode; the choice is logged.
2026-09-24 08:26:51 -04:00
xXJsonDeruloXx c11bca2dc2 Keep the build hash visible in rail watermark 2026-09-24 07:50:22 -04:00
xXJsonDeruloXx 2b25e9bedd Log session drawer controller routing 2026-09-24 07:46:20 -04:00
xXJsonDeruloXx 2853962c10 Move build status to launcher rail footer 2026-09-24 07:45:55 -04:00
xXJsonDeruloXx b7270186e0 Fix controller focus in session drawer 2026-09-24 07:36:48 -04:00
xXJsonDeruloXx e1ae162c79 Add direct touchscreen input for Steam sessions 2026-09-24 07:35:47 -04:00
xXJsonDeruloXx 7c1a603ddb Add second-screen controls close button 2026-09-24 07:35:26 -04:00
xXJsonDeruloXx 39141d5c23 Make session menu controller driven 2026-09-24 07:35:26 -04:00
xXJsonDeruloXx 8e3083507a Remove build badge caption 2026-09-24 07:33:16 -04:00
xXJsonDeruloXx c951c71f82 Show build identity on launcher 2026-09-24 07:33:16 -04:00
Kurt Himebauch 6a23556c03 Merge pull request #20 from The412Banner/controller-back-button-gestures 2026-09-24 06:47:23 -04:00
The412Banner 1f67850680 Progress log: GPU desktop, emulators like PS3, ARMSX2, game art (2026-09-24) 2026-09-24 06:30:39 -04:00
The412Banner ee941da5b4 Session drawer: stretch games to fill the screen, live
The same choice as the Steam cog's, in the drawer's Now group, changed while the client runs:
the app writes it to ~/.droiddeck-fill (and again at every session start, so a file an earlier
session left never disagrees), and a watcher in the Steam session hands each change to gamescope
as GAMESCOPE_FORCE_WINDOWS_FULLSCREEN on the root window, which gamescope reads whenever it
changes. The session log says so when it does.
2026-09-24 06:29:33 -04:00
The412Banner 6b109046cd Steam cog: stretch games to fill the screen, as a switch
0.1.6 forced every game window to the screen's size (--force-windows-fullscreen), which keeps
FlatOut filling the screen but leaves Quake 3 drawing small in the bottom-left: it sets its own
resolution and never looks at its window again, and OpenGL draws from that corner. The switch
sits in the Steam page's Games group, on by default as before, and reaches the session as
BL_GAMESCOPE_FORCE_FULLSCREEN, which the session script already reads. Listed in the device
report.
2026-09-24 06:26:12 -04:00
The412Banner abecd2d9d4 Merge pull request #29 from The412Banner/feat/armsx2-polish
ARMSX2 like RPCS3, controllers and on-screen controls everywhere, Back, game art
2026-09-24 05:44:41 -04:00
The412Banner 9b173da142 Front end: wide cover art shown whole, titles without dump tags
- A portrait tile or poster with wide art (a PS3 disc's ICON0, 320x176) shows it
  whole over a blurred, darkened copy of itself instead of cropping its sides
  ('Tomb Raider' read 'B RAI'); box art still fills the tile.
- A game's title drops the dump's tags - (USA), (En,Fr,Es,Pt), (v1.01), [!] -
  which stay in the file name and in the cover lookup.
2026-09-24 05:31:00 -04:00
The412Banner f161e061f6 Cover art: a PS3 disc image gives its own ICON0.PNG
The libretro PS3 collection holds 67 covers (Tomb Raider found none). A PS3 disc
image carries PS3_GAME/ICON0.PNG, unencrypted even on protected discs; it is read
out of the ISO 9660 image, the way an installed game's icon already is. The PS2
serial lookup uses the same reader.
2026-09-24 05:17:20 -04:00
The412Banner 550d8711f2 Front end: box art for emulator games, found automatically and kept
CoverArt looks a game up by its file name in RetroArch's libretro-thumbnails
collection for the system (exactly, then loosely: tags dropped, same region
preferred, against the collection's cached list of names), then by the disc's
own ID - PS2 serials from SYSTEM.CNF against xlenore/ps2-covers, GameCube/Wii IDs
against GameTDB. Covers are downloaded once into files/covers after the list is
shown, and the list is refreshed when some arrive; a game with none is retried
after three days; nothing is fetched offline. PS3 disc dumps use their own
PS3_GAME/ICON0.PNG.
2026-09-24 05:11:38 -04:00
The412Banner e4e93c5f4a Front end: Back steps out of a game or emulator page; PS2 and other games found deeper in their folder
- Back / B on an emulator page went back to Android (the app closed); it now
  returns to Desktop, a game page to its emulator, a Steam game to Steam - the
  same as each page's '‹' link.
- Games are looked for up to three folders inside the system folder
  (ROMs/ps2/games/<game>/<game>.iso showed ARMSX2 with 0 games), and the loose-file
  pass over the ROMs root no longer walks into other systems' folders.
2026-09-24 04:55:01 -04:00
The412Banner 1c7130545f Drawer: a game from the rail is headed by its emulator (ARMSX2, RPCS3), not "Desktop" 2026-09-24 04:43:24 -04:00
The412Banner 9f2c98d28d ARMSX2: a new player starts on the Vulkan renderer (GSRendererType::VK = 14), not Auto's pick 2026-09-24 04:32:58 -04:00
The412Banner 4ef3bc92de ARMSX2 like RPCS3: front-end launches, first-time setup, PCSX2 settings, 16K pages; on-screen controls everywhere
- From the rail ARMSX2 starts the game in its full-screen, controller-driven UI
  and exits with it (-batch -bigpicture -fullscreen).
- bannerlator-pad-defaults: a PCSX2 player's settings and memory cards are
  copied into ARMSX2 once; a new player starts on ARMSX2's first-time setup with
  the BIOS folder in the ROMs folder (ps2/bios, created, or ps2/firmware), the
  ps2 folder as the game list and the controller on Player 1; the guide button
  opens the pause menu (how a rail game is left) instead of toggling analog.
  Users bring their own BIOS and games.
- The catalog can name a file per memory page size ("pages"); the device's own
  size picks it (ARMSX2 has 4K and 16K builds).
- On-screen controls: Auto keeps them off the desktop (the screen is a touchpad
  there) and shows them for rail games and Steam when no controller is
  connected; the drawer offers the setting in every session.
2026-09-24 04:29:57 -04:00
MaxsTechReview e7f6f315b9 Merge branch 'The412Banner:main' into fix/proot-performance 2026-09-24 03:43:13 -04:00
xXJsonDeruloXx 582e467583 Revert "Improve accelerated drawer stick navigation"
This reverts commit 10f5506829.
2026-09-24 01:07:34 -04:00
xXJsonDeruloXx 9222f2e8e9 Improve accelerated drawer stick navigation 2026-09-24 01:04:29 -04:00
xXJsonDeruloXx 46eb874c3d Merge latest main and fix controller drawer focus 2026-09-24 00:54:32 -04:00
The412Banner c942cf2652 Merge pull request #26 from The412Banner/feat/armsx2
PS2: ARMSX2 replaces PCSX2 (ARM64 recompilers, full speed)
2026-09-24 00:12:18 -04:00
The412Banner 5b24a2c49f PS2 is ARMSX2 only: PCSX2 leaves the front end
On device NFS Underground 2 ran at 17 fps in PCSX2 (one core at 100%, the PS2's
CPUs interpreted) and 59.9 fps at 100% speed in ARMSX2 (EE thread ~15%, all
recompilers and fastmem on under proot). Existing PCSX2 configs are left alone;
bannerlator-pad-defaults still covers them.
2026-09-23 23:59:25 -04:00
The412Banner 1b44e5345f ARMSX2 (PS2 with ARM64 recompilers) in the front end, with Player 1 on the controller
Upstream PCSX2 interprets the PS2's CPUs on ARM64 (pcsx2/arm64/RecStubs.cpp):
NFS Underground 2 ran at 17 fps with one core at 100%. ARMSX2 is the PCSX2 fork
with ARM64 recompilers. It is listed beside PCSX2 for the ps2 folder, installs
from the desktop catalog (package armsx2), and bannerlator-pad-defaults gives it
the PCSX2 profile under its own folder (ARMSX2/inis/PCSX2.ini).
2026-09-23 23:55:20 -04:00
The412Banner 0812960935 Merge pull request #25 from The412Banner/feat/desktop-gpu
Desktop on the GPU (Vulkan by default), emulator controllers, PC keyboard, PS3 shader fixes
2026-09-23 23:16:51 -04:00
xXJsonDeruloXx f334738441 Merge remote-tracking branch 'origin/main' into controller-back-button-gestures
# Conflicts:
#	app/src/main/java/com/droiddeck/launcher/ui/FrontEndScreen.kt
2026-09-23 23:07:55 -04:00
xXJsonDeruloXx 06d9b7b1cc Replace Back inversion toggle with order menu 2026-09-23 23:05:12 -04:00
The412Banner 351882f0ea Compositor: xdg_positioner, xdg_popup and wl_output requests no longer abort
Positioners and outputs were created without an implementation, and get_popup
created nothing. libwayland aborts on the first request to a resource with no
implementation - the compositor and the app with it. On device: a gamescope
started from the pixman desktop (droiddeck-gpu) beside labwc took the app down
(tombstone: abort in libwayland-server under wl_event_loop_dispatch). Positioner
requests are accepted and ignored, popups are dismissed at once (popup_done),
wl_output.release destroys the resource.
2026-09-23 22:59:44 -04:00
The412Banner 6192d5be2c Desktop: Vulkan by default; a renderer that fails to start is remembered
vulkan is the default desktop renderer (settings list it first). When labwc
cannot start on a GPU renderer the desktop falls back to pixman as before, and
now records the renderer with the patched wlroots and Linux driver it failed
on, so later desktops go straight to pixman instead of waiting 20 s each time;
an app update, a different driver, or choosing a renderer again in the settings
(which deletes the record) retries.
2026-09-23 22:49:50 -04:00
The412Banner 76300aa8b7 PS3: one shader cache, no interpreter precompile
- A program from the rail draws with the desktop's Linux driver instead of the
  Steam session's. The driver's shader cache is keyed on its build, so with two
  drivers an emulator compiled everything once per way of starting it; seen on
  device: desktop on the imported Turnip 26.3.0, rail on the runtime's own.
- RPCS3's default shader mode builds 6650 interpreter pipelines every boot.
  bannerlator-pad-defaults starts a new install on Async Recompiler
  (multi-threaded) and moves an existing one still on the default once; a
  marker leaves any later choice in RPCS3's settings alone. Enum strings from
  rpcs3/Emu/system_config_types.cpp.
2026-09-23 22:35:28 -04:00
The412Banner ae863cc613 Controller profiles for every desktop emulator; PC keyboard under half the screen
bannerlator-pad-defaults now covers DuckStation, PCSX2, Dolphin (GameCube and
Wii), Cemu, melonDS, PPSSPP and RetroArch as well as RPCS3, from each project's
source: files an emulator would otherwise fill with keyboard-only defaults are
seeded when missing (Dolphin, Cemu, PPSSPP, RPCS3), or given SDL bindings in the
config the emulator wrote itself when nothing binds a controller yet
(DuckStation, PCSX2 with its SettingsVersion, melonDS). RetroArch gets an Xbox
360 profile for its sdl2 driver and is moved off udev, which finds no pads in the
runtime. The desktop re-runs it every 30 s, so an emulator's first config is
covered for its next start.

The PC keyboard takes 47% of the screen height, header included.
2026-09-23 22:20:04 -04:00
The412Banner 1d71e8c74d PC keyboard: shorter rows, so the program stays visible above it 2026-09-23 22:13:59 -04:00
The412Banner a6bab67508 Desktop programs: controller seen by every emulator, Player 1 profiles, big-core pinning
- The pad keeps Steam Input's virtual-gamepad identity only in Steam sessions.
  SDL ignores a Steam virtual gamepad unless run under Steam, so on the desktop
  and from the rail every SDL emulator saw no controller at all; there it is a
  plain Xbox 360 pad now, which SDL and evdev map by themselves.
- bannerlator-pad-defaults writes Player 1 on the controller for emulators that
  have no profile yet (RPCS3 first: without one it takes the keyboard). Run at
  desktop start and before a program from the rail; never overwrites.
- The desktop and everything started from it, and a program from the rail, run
  on the game cores when chosen, else every core but the slowest cluster
  (program_cores); BL_PIN_CORES=0 turns it off. Game cores reach every mode.
2026-09-23 22:06:47 -04:00
maxjivi05 b8fcef622e proot: build the runtime's proot from source with WinNative's performance fixes
The runtime carries Termux's proot 5.1.107-70 as a prebuilt binary, built from
termux/proot 4dba3afb (every file:line site in it matches that commit). The apk
now builds that same commit with the NDK, with three patches ported from
WinNative's feature/proot-enhancements, and prefers it over the runtime's copy:

- Tracees are hashed by pid, so each ptrace stop finds its tracee without
  walking a list of every thread, and terminated tracees are swept only after
  a termination.
- A clean absolute path three or more directories deep under the rootfs alone
  has its parent opened once and taken as canonical when the kernel's path for
  it is the one proot would build, instead of an lstat per component; the final
  component of a call that does not follow it is no longer lstat'ed. Extensions
  still see the parent's host path, and the f2fs workaround keeps the old walk.
- A clone3 thread keeps its inherited flags when clone_args cannot be read.

talloc is linked in statically from Samba's release tarball, so libproot.so
needs only libc and libdl. On an x86_64 Android 15 emulator the same build ran
a path and thread test suite with output identical to the unpatched commit
(fake id, fake root, link2symlink, with and without seccomp), and a stat-heavy
benchmark ran 25-27% faster, 31-36% with 1500 live threads.
2026-09-23 22:03:57 -04:00
The412Banner 3e8bcf6e97 Session: on-screen PC keyboard (Hardware) beside the Android keyboard
The drawer's Keyboard row has two buttons: Hardware opens ui/PcKeyboard, a full
PC layout - Esc, F1-F12, Ins/Del/Home/End/PgUp/PgDn, the number and letter rows,
Tab, Caps, both Shifts, Ctrl, Super, Alt, AltGr, Space and the arrows - whose
keys are evdev codes pressed and released on the compositor's wl_keyboard, as a
USB keyboard would; Android opens the Android keyboard as before. Keys go down
on touch and up on lift (the program's key repeat works); modifiers are sticky
(tap: next key, tap again: lock, again: off). A controller moves over the keys
with the d-pad and presses with A; B closes it.
2026-09-23 21:52:47 -04:00
The412Banner 61836fd532 Compositor: declare send_toplevel_configure before surface_commit 2026-09-23 21:41:23 -04:00
The412Banner b9ec8458f8 Desktop: remote runs from ~/.cache/droiddeck/run behind ~/.droiddeck-remote-run
For driving the desktop from a debugger: a script dropped there runs once inside
the session. Off unless the marker exists.
2026-09-23 21:37:16 -04:00
The412Banner 9263160f5c Compositor: answer a toplevel's initial commit with configure
xdg-shell has the compositor reply to the initial commit; we only sent one at
get_toplevel. wlroots' Wayland backend (the desktop's labwc) moves its toplevel
to a private queue after the initial commit and waits there; on the vulkan
renderer a buffer import in between read the early configure onto the default
queue, and labwc waited for ever (WAYLAND_DEBUG: commit, then nothing). The
early configure stays for Wine's driver.
2026-09-23 21:36:08 -04:00
The412Banner 60d53fda47 Desktop GPU: full labwc log on the GPU renderers; a labwc that hangs at start falls back to pixman
The vulkan desktop sat on the loading screen: labwc connected and never opened
its window, and it did not exit, so the exit-based fallback never fired. The
launcher now asks labwc's socket for an answer (wayland-info, 3 s) for up to
20 s, then stops it and starts the desktop on pixman. labwc runs with -d on
vulkan/gles2 so desktop.log shows where it stopped.
2026-09-23 21:24:24 -04:00
The412Banner f0d6638d26 CI: listing the apk's assets no longer breaks the pipe past 30 entries 2026-09-23 21:09:57 -04:00
The412Banner 4e7fb1664f wlroots: stage the published wlroots-0.20.2-p1 build 2026-09-23 21:04:16 -04:00
The412Banner 29597c3fbe Desktop GPU: menu games run in their own gamescope; labwc on the patched wlroots
droiddeck-gpu runs a program in a gamescope of its own on the app's compositor
(wayland-0), full screen over the desktop - the path the rail's play chips use,
where Vulkan works. droiddeck-desktop-gpu gives every Game/Emulator menu entry a
per-user copy through it (auto: while the desktop is on pixman), keeping 'Run in
a desktop window' as an action.

droiddeck-desktop loads /usr/local/lib/droiddeck-wlroots for the vulkan/gles2
renderers and falls back to pixman when labwc stops at start. The build stages
the wlroots release named by tools/wlroots/release.env, once published.
2026-09-23 21:02:24 -04:00
The412Banner b921ef6245 Desktop GPU: patched wlroots with a Vulkan DMA-BUF allocator; new windows take the keys
wlroots 0.20.2 (Arch's wlroots0.20, what the desktop's labwc links) gains an
allocator that exports Vulkan images as DMA-BUFs for a GPU node that is not a
DRM device - the Adreno KGSL stand-in - where gbm and the DRM lease path fail
('unable to create allocator'). linux-dmabuf skips its GEM import check on such
a node so client buffers are not all refused. Built by build-wlroots.yml.

Compositor: without a Wine desktop, a window that opens on top takes keyboard
focus (a gamescope started from the Linux desktop).
2026-09-23 20:59:57 -04:00
The412Banner 890fefc63f Merge pull request #23 from The412Banner/release/0.1.6
0.1.6
2026-09-23 19:20:02 -04:00
The412Banner 9e066a9b62 0.1.6 notes: say exactly what is fetched and what is bundled 2026-09-23 19:19:56 -04:00
The412Banner 0b4648e04d 0.1.6
versionCode 7, versionName 0.1.6; release notes in docs/releases/0.1.6.md.
2026-09-23 19:19:45 -04:00
The412Banner be45c66bd3 Merge pull request #22 from The412Banner/feat/first-login-proton
First sign-in fetches the ARM64 Proton and restarts the client once
2026-09-23 19:08:29 -04:00
The412Banner 85d555b44e Front end: credits and the primary button show a focus outline
With a controller, credits only showed the ripple's faint box on the
dark rail, and the primary button (Play) only grew slightly with a shine
that barely shows on its light fill. Both now get the 2 dp signal-blue
outline the other controls use; credits' text turns blue as well.
2026-09-23 19:00:52 -04:00
The412Banner c673cb41f5 Session drawer: opens focused on its first control, and the controller drives it
With the drawer open the controller still fed the game - PadBridge
consumed every pad key and axis - and nothing in the drawer had focus.
Now, while it is open, pad events go to Android's own handling instead:
the d-pad and stick move focus, and the framework's fallbacks make A
select and B Back (which closes the drawer), as on the app's main
screen. Opening it releases the game's pad (PadBridge.releaseAll), so a
button or stick held at that moment is not left down in the game.

It opens with focus on the STEAM button (in a desktop session, the first
setting), and the STEAM and ... buttons show a focus outline like the
rest of the app's controls. ToggleRow gains a modifier for its box.
2026-09-23 18:52:50 -04:00
The412Banner 9f33bdd094 First sign-in fetches the ARM64 Proton and restarts the client once, as Bannerlator does
On a fresh install the client starts signed out and drops the
steam://install/4427310 the session hands it, so the Proton Experimental
(ARM64) dialog only appeared after the user restarted the client by
hand. Ported from Bannerlator's session (device-proven there 2026-09-22):

- With no remembered sign-in, a watcher on logs/connection_log.txt waits
  for the sign-in to complete and asks the running client again.
- A watcher on appmanifest_4427310.acf waits for StateFlags 4, registers
  the compatibility tool again, leaves ~/.bl-restart-once and asks the
  client to -shutdown; the start loop sees the marker and starts it once
  more (not counted against the update cap), so the tool lists and
  defaults are current before the user installs a game.
- The client-branch check runs before every start, not once, since the
  client rewrites package/beta during its run.

The loading screen no longer says Steam keeps running in the background:
sessions pause when the app leaves the screen (Background behavior).
2026-09-23 18:48:19 -04:00
The412Banner 26ada56ad6 Merge pull request #21 from The412Banner/rename/droiddeck
Rename the app SteamDeck → DroidDeck
2026-09-23 18:42:42 -04:00
The412Banner ae6dcc9798 Rename the app SteamDeck → DroidDeck
App name, app ID and code package: DroidDeck, com.droiddeck.launcher
(sources moved to com/droiddeck/launcher, the 68 JNI functions renamed
with them). The new app ID installs beside SteamDeck rather than over it.

Everywhere the name was the app's own: the sidebar title, the session
report, share zips, the shell title, wake-lock and log tags, User-Agents,
the Compose theme, the Download folder and switch files (droiddeck-env,
-osc, -driver, -tu-debug, -no-pad, -pad-log, -no-hud, -wlr-renderer; the
old names are not read), and inside the runtime (droiddeck-desktop,
Firefox's droiddeck.js, the shortcuts tag, the rumble socket, debug and
cache paths). Build scripts, CI artifact (droiddeck-apk), CMake project,
README and the progress log follow.

Kept on purpose: Valve's Steam Deck (the -steamdeck flag,
steamdeck_publicbeta/stable, 'Steam Deck mode', the steamos.cloud
package URLs) and names inside the hosted desktop package (steamdeck.png,
steamdeck-steam, steamdeck-bigpicture). 'Desktop installed' now tests for
labwc instead of the launcher the app stages itself. A committed
__pycache__ .pyc is removed.
2026-09-23 18:28:04 -04:00
xXJsonDeruloXx e40b5ced7d Expose Back actions in app settings 2026-09-23 17:26:27 -04:00
xXJsonDeruloXx 3fa05f6c18 Add Back shortcuts for session menu and Steam QAM 2026-09-23 17:16:02 -04:00
The412Banner bd51571530 Merge pull request #16 from The412Banner/feat/qcom-driver-and-deck-fixes
Deck mode performance overlay, killed-session cleanup, NIS
2026-09-23 16:44:57 -04:00
The412Banner 2a679af9aa Driver page: d-pad right reaches a driver's trash can
The trash can sat inside the row's single clickable area, so with a
driver focused there was nothing to the right for a controller to move
to. Each row is now two focus targets side by side - the driver and its
trash can - linked both ways (right/left); the row's outline still
follows the driver part, so it looks the same.
2026-09-23 16:33:17 -04:00
The412Banner c2953f2c1f Session settings open with the first control focused
Opened from the cog, the Steam/Desktop session page had nothing focused:
the cog that was focused is gone and a controller had to find its way
back in. The page now puts focus on its first control (Resolution) as it
opens, so the d-pad works at once; coming back from a driver page still
returns focus to the driver box that opened it. ChoiceRow gains a
modifier for its box.
2026-09-23 16:33:16 -04:00
The412Banner acc04bfabb Driver pages: back returns to where you left the settings page
Backing out of the Runtime or Display driver page rebuilt the session's
settings page from the top with nothing focused. Its scroll position is
now held above the page (SettingsPage takes an optional ScrollState) and
survives the driver page, and controller focus returns to the driver box
that was pressed to open it (ValueChip takes a modifier for its
FocusRequester).
2026-09-23 16:33:16 -04:00
The412Banner c5a2dee234 Driver page: controller navigation and focus throughout
- Every control shows a focus outline: the driver rows (the list at the
  top had none), their trash cans (in red), the refresh button, Restore
  built-in drivers, the confirmation's buttons, and the page's Back
  (shared by every settings page).
- D-pad up from the first driver moves to the refresh button beside the
  title; the page opens with the driver in use focused.
- Back (the controller's B too) closes the driver page only, back to the
  session's settings, instead of leaving settings altogether.
- The delete confirmation opens on Cancel, so a stray A press never
  deletes a driver.
2026-09-23 16:33:16 -04:00
The412Banner 2cd88855f4 Driver selection as a full page with refresh and delete for every driver
Runtime driver and Display driver open a full page instead of a
drop-down, built from the settings widgets (as previewed): INSTALLED
lists every driver as a tappable row with a radio mark and a tag
(BUNDLED / DOWNLOADED / IMPORTED); AVAILABLE TO DOWNLOAD lists what the
last check of Banners-Turnip and WinNative found, with a Download button
and progress; Import stays at the bottom. The refresh button sits beside
the title (SettingsPage gains an action slot) and is the only thing that
goes online; it spins while checking.

Every driver gets a trash can with a confirmation, the bundled ones too:
deleting a bundled display driver removes its unpacked copy and hides it
(TurnipDriver.hideBundled), with 'Restore built-in drivers' to bring
them back. It stays in the apk and Auto still unpacks it on a GPU that
needs it, so a deletion can never leave a device without a driver. Auto
and Runtime default are settings, not drivers, and have no trash can.
2026-09-23 16:33:16 -04:00
The412Banner 97d74e3748 Driver menus: WinNative drivers, newest release per variant, scrolling menus
- WinNative-Emu/Drivers joins Banners-Turnip as a source: WN-Turnip
  (Android, v1.x line) and WN-Linux-Turnip (Linux, linux-v0.x line), each
  in its Balanced and Performance builds for every Adreno.
- Each source's recent releases are read newest first and every variant
  is offered from the newest release carrying it. Banners-Turnip stopped
  publishing its A8xx Linux build after v26.3.0-20260923, so the runtime
  menu listed no 8xx driver at all; it now offers that last one. The
  refresh line names each source's newest release and any that could not
  be reached.
- Drop-down menus are capped at the window's height and their entries
  scroll under the title: a driver menu with its downloads ran off the
  bottom of the screen, hiding the refresh line and Import.
2026-09-23 16:33:16 -04:00
The412Banner 9db6b09ecd Driver menus: latest Banners-Turnip release as downloads, refresh button, trash-can delete
Both driver menus (Runtime and Display) now offer the latest release of
The412Banner/Banners-Turnip: each asset that fits the menu and is not
installed yet is a 'Download Turnip <tag>' entry labelled by GPU
(plain / -A8xx / -710-720-Test zips for the display driver, -Linux ones
for the runtime driver; -Wayland ones are never offered). Nothing goes
online on its own: a refresh line checks GitHub when tapped and says
which release it found and when; the result is remembered. A download
goes through the same importer as a picked zip and lands as an ordinary
driver.

Imported and downloaded drivers get a trash-can button with a
confirmation instead of the small cross. Deleting the driver a mode is
set to puts that mode back on its default (Runtime default / Auto), and
a deleted release download is offered again.
2026-09-23 16:33:16 -04:00
The412Banner 88aab47da5 Deck mode performance overlay: stage Valve's aarch64 mangoapp
The QAM offers Performance Overlay levels but nothing drew them: the
runtime has no mangoapp. CI now fetches Valve's mangohud-20260902.1 and
the five libraries mangoapp needs that the runtime lacks, at the versions
it was built against (fmt 10.2.0, spdlog 1.13.0, glfw 3.4, libtraceevent
1.8.2, libtracefs 1.8.0 from Valve's mash-20240705.2 snapshot), each
pinned by sha256 in tools/mangoapp/packages.txt. They are staged under
usr/local/lib/mangoapp with a wrapper on PATH; the runtime image is not
touched.
2026-09-23 16:33:16 -04:00
The412Banner d21f102482 Session: clear a killed session's leftovers, offer NIS in Deck mode, mangoapp hook
Before the client starts, remove .crash, steam.pid and the web helper's
htmlcache Singleton* locks when no client is running - Android ends
sessions without warning and those files leave the next start on the
update spinner or a blank Gamepad UI (SteamOS-ARM-SM8550's RUNSTEAM.sh
does the same). Deck mode exports STEAM_GAMESCOPE_NIS_SUPPORTED=1 so the
QAM's scaling filter lists NIS, and passes gamescope --mangoapp when
mangoapp is present (BL_MANGOAPP=0 turns it off).
2026-09-23 16:33:16 -04:00
Kurt Himebauch 45dd582a2c Merge pull request #19 from The412Banner/remove-shade-default-home 2026-09-23 16:11:53 -04:00
xXJsonDeruloXx 0da2f0025c Remove Home app chooser from in-game shade 2026-09-23 16:06:49 -04:00
Kurt Himebauch 73e0a3b35d Merge pull request #18 from The412Banner/launcher-panel-refresh
Simplify launcher panels and manage compatibility tools in place
2026-09-23 16:03:26 -04:00
xXJsonDeruloXx 5b301246da Simplify launcher panels and package management 2026-09-23 15:57:44 -04:00
Kurt Himebauch 0a912db34b Merge pull request #13 from The412Banner/steamdeck-second-screen 2026-09-23 14:29:31 -04:00
xXJsonDeruloXx 964c876a3e Merge remote-tracking branch 'origin/main' into fix/termux-system-runtime
# Conflicts:
#	app/src/main/java/com/steamdeck/launcher/SessionActivity.kt
#	app/src/main/java/com/steamdeck/launcher/ui/SessionOverlay.kt
2026-09-23 14:22:49 -04:00
Kurt Himebauch 6c2e88a763 Merge pull request #17 from The412Banner/feat/android-home-launcher
Add Android home app integration
2026-09-23 14:17:56 -04:00
xXJsonDeruloXx 86092b32f8 Hide home setting from session drawer when inactive 2026-09-23 14:12:32 -04:00
xXJsonDeruloXx cd677f0125 Add Android home app integration 2026-09-23 14:08:16 -04:00
xXJsonDeruloXx 81bb9d2213 fix: rebuild Termux PTY helper with the app NDK 2026-09-23 14:05:27 -04:00
The412Banner 1ab25f0919 Merge pull request #15 from The412Banner/fix/play-without-runtime
Play on a fresh install: don't close the session screen before it installs the runtime
2026-09-23 13:49:47 -04:00
xXJsonDeruloXx 341561c570 ci: allow Android system libstdc++ 2026-09-23 13:48:23 -04:00
The412Banner 8e1db35f98 Play on a fresh install: the session screen no longer closes before it can install the runtime
SessionActivity finished at once when no runtime was installed - a guard from before the loading
screen could install it - so Play (and Desktop) opened a screen that closed before anything
happened. It now logs and carries on to installThenStart.
2026-09-23 13:38:35 -04:00
The412Banner e54010e9b9 Progress log: onboarding, Desktop & apps page, 4:3 display, PR #11 audio 2026-09-23 13:30:27 -04:00
The412Banner 0c48ec0954 Merge pull request #14 from The412Banner/fix/exact-panel-shape
Display: Exactly this panel shape + custom resolution per mode
2026-09-23 13:27:13 -04:00
xXJsonDeruloXx 96d3132431 feat: add Steam second-screen controls 2026-09-23 13:23:49 -04:00
The412Banner 92df14bb6e Custom resolution per mode, as in Bannerlator
Resolution gains Custom…: a width × height box (320×240 to 3840×2160, evened) with one-tap
presets for 4:3 (960×720, 1024×768, 1280×960), 16:10 (1280×800) and 16:9. A custom size replaces
the cap and the shape for that mode - the Shape row greys out and says so - and the compositor fits
it to the panel with bars where the shapes differ. Choosing any cap clears it. The device report
lists it for both modes.
2026-09-23 13:18:15 -04:00
The412Banner e3fe4ed94e Display shape: Exactly this panel, for 4:3 and 3:2 handhelds
The panel's shape was never allowed narrower than 16:9 (the foldable guard), so on a 4:3 panel
such as the Retroid Pocket's both choices gave 16:9 with bars. A third choice, Exactly this panel,
presents the panel's own aspect with no floor; the other two are unchanged and foldables still
default to 16:9. Offered in the mode settings and the in-session menu.
2026-09-23 13:14:36 -04:00
The412Banner c597dee0ae Merge pull request #12 from The412Banner/feat/play-installs-runtime
First-run onboarding: Play and Desktop install what they need; Desktop & apps as a page
2026-09-23 13:10:54 -04:00
The412Banner bde8dc0089 Desktop & apps is a page with a dropdown per tier, not a dialog
The package list opens in the front end's pane like the Settings pages. Each tier (Native ARM64,
ARM64 experimental, source builds) is a dropdown row with the rail's caret and an installed/total
pill; Native ARM64 starts open. Each package has its own Install/Remove button, and the one that is
installing shows its own step line and bar in its row (its tier opens when the install starts).
Leaving the page does not stop an install. The rail's Install emulators and Setup's Desktop & apps
highlight while the page is open.
2026-09-23 13:04:02 -04:00
The412Banner 0ea4b584b4 Desktop installs the runtime and the desktop on a fresh install, on the loading screen
Desktop is enabled without a runtime or the desktop package. The session's loading screen installs
whatever is missing first - the runtime, then the desktop ("downloading the desktop · 212 of 426
MB", checking, unpacking) - and starts the session when both are in. A failure ends on the loading
screen with the reason. Desktop UI and Desktop now give the same non-Adreno warning as Play before
any download.
2026-09-23 12:53:36 -04:00
The412Banner 2b5aae9100 Play installs the Linux runtime on a fresh install, on the loading screen
Play and Desktop UI are enabled without a runtime. The session's loading screen downloads and
unpacks it first, reporting on its usual step line and bar ("downloading the Linux runtime · 332
of 791 MB", then checking, then unpacking), and starts the session when it is in. A failure ends
on the loading screen with the reason. Non-Adreno devices still get the warning before any
download. Setup > Linux runtime is unchanged.
2026-09-23 12:48:49 -04:00
The412Banner 1e1160006c Merge pull request #11 from The412Banner/fix/directaudio-default
Audio defaults: classic client sink, DirectAudio for games and as a client option; mic on; Share latest logs
2026-09-23 12:20:07 -04:00
The412Banner ed569f675e Steam client audio: the classic sink by default, DirectAudio as a choice; audio diagnostics on
- The client's sound goes back to the adaptive AAudio sink 0.1.5 shipped (bundled unchanged as
  module-aaudio-classic-sink, sink name AAudioSink). Through the DirectAudio relay it stayed
  choppy on an AYN Thor (Android 13, 20 ms bursts) even with a two-burst buffer.
- Steam cog > Audio: DirectAudio for games (on by default, unchanged) and Steam client audio
  (Classic by default, or DirectAudio). The relay also starts when only the client wants it.
- The relay runs with --log and the daemon at info level, so audio.log carries the stream each
  path was granted and, on the relay, Android's xrun count every 1000 callbacks.
- Device report shows both settings.
2026-09-23 12:10:34 -04:00
The412Banner 0dfdc91425 Audio bundle re-unpacks whenever its contents change; the DirectAudio sink logs the buffer it asked for and what the relay settled at
The unpack check compared a hand-bumped stamp only. Two sink changes shipped without a bump, so a
device that had run the earlier build kept the old module: the 40 ms ring floor and the 24 ms
device buffer never reached the AYN Thor they were for. The stamp now carries a CRC-32 of
pulseaudio.tzst. The sink's connect line names the buffer it asked for, and one line ten seconds
in reports the relay's device buffer, ring target and underruns.
2026-09-23 11:50:24 -04:00
The412Banner 53a2d800e9 DirectAudio sink: ask the relay for a 24 ms device buffer
The relay's default is 12 ms rounded up to whole bursts. On a device where Android grants a fast
stream (4 ms bursts) that is three bursts; on one where it grants a legacy stream (20 ms bursts,
an AYN Thor on Android 13) it is a single burst, and the Thor's client audio stuttered. 24 ms is
two bursts there and 24 ms on the fast devices. buffer_ms= overrides it.
2026-09-23 11:38:46 -04:00
The412Banner 019928e783 Audio: keep at least 40 ms queued for the DirectAudio relay; logs: Share latest logs
- module-directaudio-sink keeps the relay's ring at least 40 ms deep. The relay starts at 8 ms,
  which the producer inside proot missed while the client loads (two underruns on the FIT's fresh
  install, steady stutter reported on another phone).
- Setup > Session logs > Share latest logs zips the newest session folder, wherever it was written
  (Downloads, or app-private files when Downloads is not writable), and opens the share sheet via
  a FileProvider scoped to that zip.
2026-09-23 11:27:25 -04:00
The412Banner fe3b9892a7 Fresh installs get DirectAudio and the microphone on; the old armhf sink leaves the audio bundle
- Microphone defaults to on, and the app asks for RECORD_AUDIO once at first launch, together with
  the storage prompt. A refusal is not asked again at start-up.
- modules/armhf held the inherited Winlator AAudio sink and two protocol modules; the app is
  arm64-only and nothing loads that folder. Removed, bundle stamp bumped so installs re-unpack.
2026-09-23 11:09:01 -04:00
The412Banner 097b67d7e1 DirectAudio on by default
Everyone who never touched the toggle gets it; anyone who turned it off keeps it off (the setting is
only stored when changed). Off, the client's sound came from AAudio driven inside proot, which
stutters on some devices.
2026-09-23 11:08:39 -04:00
Kurt Himebauch 5956154051 Merge pull request #7 from The412Banner/feature/session-suspend-audio-recovery 2026-09-23 11:01:00 -04:00
xXJsonDeruloXx c03e62d583 Add session suspend modes and audio recovery 2026-09-23 10:51:10 -04:00
The412Banner 0af137a06b Merge pull request #9 from The412Banner/feat/armada-and-lineage
Deck mode, added games with art, own gamescope and audio sinks, client sound via DirectAudio, Winlator lineage removed
2026-09-23 10:03:35 -04:00
The412Banner d338f73eec Merge remote-tracking branch 'origin/main' into feat/armada-and-lineage
# Conflicts:
#	app/src/main/java/com/steamdeck/launcher/ui/MainScreen.kt
#	app/src/main/java/com/steamdeck/launcher/ui/ModeSettingsDialog.kt
#	app/src/main/java/com/steamdeck/launcher/ui/PerformanceDialog.kt
2026-09-23 09:56:51 -04:00
Kurt Himebauch 103287a279 Merge pull request #10 from The412Banner/chore/frontend-cleanup 2026-09-23 09:33:00 -04:00
xXJsonDeruloXx af8edfed25 chore: trim remaining frontend copy 2026-09-23 09:26:44 -04:00
The412Banner 5a33870a2d Rebase follow-up: main's copy of the credits and file manager with the Winlator lines removed; progress log entry for the branch 2026-09-23 09:13:12 -04:00
The412Banner a70f7b28da session: gamescope forces game windows to fill the screen in Steam mode (a game that shrank its window while the Steam menu was open came back in the top-left corner) 2026-09-23 09:12:39 -04:00
The412Banner 1b1fbd41da audio: the sink is named DirectAudio when the relay carries the sound, so the client's Audio settings show which road is in use 2026-09-23 09:12:39 -04:00
The412Banner 7f062818d3 settings: the DirectAudio toggle says it now carries the client's sound too 2026-09-23 09:12:39 -04:00
The412Banner bac5d0231a audio: the client's sound through the DirectAudio relay - module-directaudio-sink feeds the relay's render ring
A second sink module: connects to the relay's socket (lazily, the relay comes up after the
daemon; a clocked null sink until then), sends the hello, maps the render ring it gets back and
keeps it topped up to the relay's target with a futex wait per burst. Every AAudio call, the
adaptive buffer and the route handling stay in the relay, outside proot. With DirectAudio on the
daemon loads this sink instead of module-aaudio-sink; the bundle carries both.
2026-09-23 09:12:39 -04:00
The412Banner 6a52d4d58c aaudio sink: open the stream at the device's own rate unless rate= is given (44.1 kHz got a legacy stream: 20 ms bursts, three of them); the priority refusal is debug noise 2026-09-23 09:12:39 -04:00
The412Banner 168ef41ebf aaudio sink: a 30 ms starting buffer, half-buffer writes, the IO thread at audio priority, and underrun counts in the log
Two bursts (8 ms) of buffer with a write per burst left the client's menu sounds choppy on
device: the daemon runs under proot, where every system call is traced. The buffer now starts at
four bursts or 30 ms, whichever is more, each write hands over half of it, the IO thread asks for
nice -16 like Android's own audio threads, and the log says how big the buffer is and how many
underruns the device counted.
2026-09-23 09:12:39 -04:00
The412Banner 798ba5d6f2 front end: the shelf is laid out whole so the pad reaches every tile (a lazy grid only composes the rows on screen, and a press towards the next row bounced back among the visible tiles); added games' store art lookup starts on resume 2026-09-23 09:12:38 -04:00
The412Banner eb2bfa57aa steam shortcuts: art the user set inside the client is never recorded as ours, so it survives when a game's own art goes away 2026-09-23 09:12:26 -04:00
The412Banner 44ea7bae78 added games: artwork - the game folder's own images first, else Steam's store art for the title, into the client's grid
frontend/AddedGameArt: cover/poster/boxart/folder-name images, header, hero, logo and icon from
the game's folder (or its art subfolder); otherwise a one-time store search by folder name and
the capsule, header, hero and logo from Steam's CDN into the app's cache (a miss retried after a
week); an icon pulled from the .exe. The rail shows the portrait. The session's listing carries
the paths; bannerlator-steam-shortcuts copies them into userdata/<acct>/config/grid under the
client's names, never over a file it did not write itself, and sets the shortcut's icon. A toggle
in the cog turns the store lookup off.
2026-09-23 09:12:26 -04:00
The412Banner b834101cad added games: any number of Games folders from anywhere, each bound under /root/Games on its own
Folders are a list (the single folder from before carries over); each gets /root/Games/<name>,
with a short hash of its path only when two chosen folders share a name, so adding or removing
one never moves another's games (their appids follow the guest exe path). Per-game exe choices
are keyed by the folder's path now. The cog lists each folder with its game count and a Forget,
plus an Add row.
2026-09-23 09:12:26 -04:00
The412Banner 8f299d8752 deck mode: jupiter-dock-updater --check answers 7 (up to date); exit 0 read as an update waiting and showed a Dock Firmware Update row 2026-09-23 09:12:25 -04:00
The412Banner 76ba054a84 added games: bind the chosen Games folder into the session at /root/Games, so a folder on an SD card or anywhere else is seen (it was skipped unless it sat inside internal storage, the ROMs folder or the app's own SD library) 2026-09-23 09:12:25 -04:00
The412Banner 0e9460bedb aaudio sink: the sink is AAudioSink, as the daemon config expects; acknowledge rewind requests before rendering (assertion on device) 2026-09-23 09:12:15 -04:00
The412Banner 0ea06a3f05 audio: the APK build compiles the AAudio sink into the bundle; the committed bundle carries no sink module any more
The inherited module is gone from app/src/main/assets/pulseaudio.tzst (74 → 73 modules); CI adds
the app's own at build time and refuses a bundle that already has one. BUNDLE_STAMP bumped so an
installed app re-unpacks.
2026-09-23 09:12:15 -04:00
The412Banner b4c89b6f63 aaudio sink: declare pa__done ahead of use; tolerate the int-conversion in PulseAudio's own atomic.h 2026-09-23 09:12:15 -04:00
The412Banner 32178e6e4a audio: a fresh AAudio sink module for the daemon, built from upstream PulseAudio 13 headers
Blocking-write design: the IO thread pulls one burst at a time from the sink and hands it to a
blocking AAudio output stream, whose buffer paces the thread; latency comes from the stream's
playback timestamp; with adaptive=1 the buffer grows a burst per underrun up to a cap. Same
module arguments as before, so the daemon's config line is unchanged. build.sh compiles it with
the NDK against the app's own libpulse* and a small config.h; a workflow checks it on every push.
2026-09-23 09:12:15 -04:00
The412Banner c232274157 build: fetch the gamescope asset through the API (private repository), and key the preload cache on its pin 2026-09-23 09:12:15 -04:00
The412Banner 5b54bcfa49 gamescope: the apk carries build gamescope-3.16.29-p1 2026-09-23 09:12:15 -04:00
The412Banner cde82be161 steam shortcuts: name the source loader for the suffix-less registrar so the added games get their Proton mapping 2026-09-23 09:12:15 -04:00
The412Banner a6eb2ba1dd steam shortcuts: extend the buffer instead of rebinding it inside the nested writer (UnboundLocalError on device) 2026-09-23 09:12:15 -04:00
The412Banner f764885ecf gamescope build: apply the patches from the checkout Arch's prepare() leaves us in; concrete mirrors ahead of the redirector 2026-09-23 09:12:15 -04:00
The412Banner b0a52ef71f gamescope build: makepkg -A, the PKGBUILD names x86_64 only 2026-09-23 09:12:15 -04:00
The412Banner 93b3d49575 gamescope: the apk stages its patched build over /usr/local/bin when it carries one, the APK build fetches it from the release release.env names, and the session asks for realtime Vulkan queues 2026-09-23 09:12:15 -04:00
The412Banner 9841899cff gamescope build: pacman without its download sandbox inside the container 2026-09-23 09:12:15 -04:00
The412Banner 23f3e3ccf7 added games: plain null check instead of continue inside a lambda; gamescope build also runs on pushes touching its inputs 2026-09-23 09:12:15 -04:00
The412Banner 687b882983 gamescope: a build of the runtime's 3.16.29 with Armada's ARM64 client fixes and a realtime-queue switch, from Arch's own package in CI
Four of Armada's patches apply to 3.16.29 as they are (appid-focus fallback, ARM64 night mode,
ARM64 colour-temperature slider, P3 red); two more are ported by hand: realtime-priority Vulkan
queues on GAMESCOPE_FORCE_VULKAN_REALTIME=1 without CAP_SYS_NICE, and the gamepad cursor sprite
following the X pointer. build-gamescope.yml builds Arch Linux ARM's package with them in an
Arch container, checks every NEEDED library against the runtime's own list, and publishes the
binary as a release asset for the apk to stage over /usr/local/bin/gamescope.
2026-09-23 09:12:15 -04:00
The412Banner 65b00242d9 Added games: the user's own Windows games in the client's library
A Games folder chosen in the Steam cog is scanned one level deep, one subfolder per game; the
program to launch is the .exe named after the folder, else the largest, never an installer,
redistributable or crash reporter, and the settings page lets the user pick another for any
game. Each session the app writes the list, and bannerlator-steam-shortcuts puts the games into
the client's shortcuts.vdf as non-Steam games (the client's own entries kept) with the appid the
client itself derives, mapped to the ARM64 Proton through the compat registrar. They appear in
the rail under Steam and launch by steam://rungameid/ like any title.
2026-09-23 09:12:15 -04:00
The412Banner cc3b963dd0 Deck mode takes the Steam Deck channel unless a branch was chosen (device: publicbeta + Deck mode reinstalls every start) 2026-09-23 09:12:14 -04:00
The412Banner 773f4a6e6d Deck mode: the four helpers the client really calls, at the path it calls them
On device the client's console log showed the calls: /usr/bin/steamos-polkit-helpers/steamos-update,
steamos-select-branch, jupiter-biosupdate and jupiter-dock-updater - the polkit-helpers path, not
/usr/bin. The missing steamos-update there was the 'Update Error' dialog. Stubs at both paths now.

Also seen on device: -steamdeck alone opts the install into the steamdeck_stable branch too, so the
branch guard is what keeps a session from reinstalling; and on the publicbeta channel Deck mode
reports installed version 0 at every start and reinstalls the same client (rc=42, the launch URL
lost with it), while on steamdeck_publicbeta the second launch comes up with no restart at all.
2026-09-23 09:12:14 -04:00
The412Banner 3492229a00 lineage: the pad bridge's hat and Quick Access chord lines, and the audio component's context, on the new names 2026-09-23 09:12:14 -04:00
The412Banner ad21a8147f battery: time to full while charging, so the Quick Access Menu stops saying calculating 2026-09-23 09:12:14 -04:00
The412Banner a604dce6cb The last of the Winlator lineage rewritten out; rumble on the phone
The five files the audit measured as Winlator's are replaced: a one-method callback interface by
java.util.function.Consumer, the component base by SessionPart (attach an application context,
start, stop), the environment list by HostEnvironment, the process starter by HostProcess, and
the pad's state holder by PadState with named sticks, triggers, directions and buttons. The
frame-generation tree is framegen/ (its contents were never Winlator's: lsfg-vk's port and
Bannerlator's Win-FG), the log tags are the app's, and the fake pad's rumble socket is now one
the app actually serves: RumbleComponent turns a game's force-feedback effects into the phone's
vibrator, which used to go nowhere. The two design notes describing Bannerlator's X11 path are
gone, and the README and credits say what is whose.
2026-09-23 09:12:14 -04:00
The412Banner 35dbef0536 Deck mode made safe, a client-branch choice, and a battery the client can see
Deck mode passes -steamdeck alone: -steamos3 opted the install into the steamdeck_stable
branch and started the restart loop Bannerlator hit on device (38996ea1). The SteamOS helpers
the client calls in that mode - the two Armada found it needs under steamos-polkit-helpers and
the three under /usr/bin - are staged from the apk as no-op stubs. The client branch on disk is
kept in step with the one forced on the command line, and an update that cannot install stops
the session after three restarts instead of looping for ever.

The Steam cog gets a Client branch row: public beta (every session so far) or Steam Deck
public beta, the channel Armada bootstraps from.

The client reads /sys/class/power_supply/BAT<n>/; Android's supply is called battery and laid
out differently, so the Quick Access Menu had no battery. BatteryComponent writes BAT0 and BAT1
from Android's battery API every five seconds - status, percentage, charge, energy, power and
time to empty - into a directory bound over /sys/class/power_supply.
2026-09-23 09:11:54 -04:00
Kurt Himebauch f2768e33a8 Merge pull request #8 from The412Banner/chore/cleanup
chore: simplify UI copy and repository text
2026-09-23 08:32:51 -04:00
xXJsonDeruloXx 9de0d01c63 docs: move progress log under docs 2026-09-23 08:27:09 -04:00
xXJsonDeruloXx 7adce2bfd9 Merge remote-tracking branch 'origin/main' into chore/cleanup
# Conflicts:
#	README.md
2026-09-23 08:25:15 -04:00
xXJsonDeruloXx 40c42944f4 chore: remove lineage audit document 2026-09-23 08:23:55 -04:00
xXJsonDeruloXx 28ed06f18d chore: remove generated contributor ledger 2026-09-23 08:22:53 -04:00
xXJsonDeruloXx 751f8ca114 chore: simplify UI copy and repo text 2026-09-23 08:22:11 -04:00
The412Banner af796545f6 docs: lineage audit — inherited from Winlator vs written fresh in the forks; the AAudio sink is the one inherited piece that does real work 2026-09-22 23:58:25 -04:00
The412Banner c64f00eb8e docs: the lineage audit — what is measurably Winlator's, what only carries the name, and what leaving it would take 2026-09-22 23:54:07 -04:00
Kurt Himebauch 384a67f8e1 Merge pull request #6 from The412Banner/steam-qam-buttons 2026-09-22 23:39:23 -04:00
The412Banner 3451624b44 PROGRESS_LOG: after the 0.1.5 cut, and what comes next 2026-09-22 23:32:34 -04:00
xXJsonDeruloXx be54cafa81 Start drawer QAM chord on button press 2026-09-22 23:26:13 -04:00
The412Banner 377191d0ed PROGRESS_LOG: 0.1.5 released 2026-09-22 23:18:08 -04:00
The412Banner 2560edf0cf 0.1.5 notes 2026-09-22 23:17:28 -04:00
xXJsonDeruloXx 39cb09f33b Move Steam shortcuts to the drawer header 2026-09-22 23:17:14 -04:00
The412Banner de74abf10a 0.1.5 2026-09-22 23:13:14 -04:00
xXJsonDeruloXx b79650a2d1 Add QAM shortcut to the session drawer 2026-09-22 23:10:44 -04:00
xXJsonDeruloXx b3cdc14800 Add Steam and QAM touch controls 2026-09-22 23:05:29 -04:00
The412Banner a7aee50ea8 Merge remote-tracking branch 'origin/main' into feat/frontend-motion 2026-09-22 23:02:38 -04:00
The412Banner 21460ca35a front end: every category starts folded when the app opens 2026-09-22 22:55:38 -04:00
The412Banner 587acb4a75 themes: every screen colour comes from the palette; Setup › Theme switches at once 2026-09-22 22:49:21 -04:00
The412Banner 6c7587ec49 themes: the icon's colours three ways, chosen under Setup › Theme, Paper on black by default
Palette (ui/Theme.kt): background, surfaces, lines, type, a primary with its gradient partner,
and a separate signal colour for rules, rings, dots and the eyebrow - the blue in every theme,
while the primary is white in Paper and blue in the other two. Every colour the screens used to
hard-code (the purple gradient end, the green, the two line greys) now comes from the palette,
so a change under Setup applies at once, and the session drawer and the file manager pick the
saved theme up when they open.
2026-09-22 22:43:55 -04:00
Kurt Himebauch cba64a4e77 Merge pull request #5 from The412Banner/build/local-build-deploy 2026-09-22 22:42:59 -04:00
xXJsonDeruloXx a1514ab5ad Add reproducible local build and deploy helpers 2026-09-22 22:31:10 -04:00
The412Banner b010c0ad8e logo: the app's in-app mark is the new launcher icon (its two layers composed and cut to the launcher's circle), in the rail and the loading screen 2026-09-22 22:26:14 -04:00
Kurt Himebauch aca591bc74 Merge pull request #4 from The412Banner/update-app-icon 2026-09-22 22:16:47 -04:00
xXJsonDeruloXx 859ed20ac0 Update app launcher icon 2026-09-22 22:15:06 -04:00
The412Banner 8b2c6451c3 gitignore: the Kotlin compiler's session files 2026-09-22 21:54:33 -04:00
The412Banner 92b1a33471 PROGRESS_LOG: pull-request CI and the contributions ledger 2026-09-22 21:46:47 -04:00
Kurt Himebauch f1175e819f Merge pull request #3 from The412Banner/fix/session-back-key-routing 2026-09-22 21:43:54 -04:00
xXJsonDeruloXx ac2cfc1266 Fix session hardware back routing 2026-09-22 21:39:54 -04:00
3082 changed files with 547260 additions and 44519 deletions

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
language: en-US
early_access: false
tone_instructions: >-
Prioritize actionable correctness, security, and runtime regressions. Keep
feedback concise. Do not request explanatory code comments; preserve license
headers and compiler-consumed annotations.
reviews:
profile: chill
request_changes_workflow: false
high_level_summary: true
high_level_summary_in_walkthrough: true
poem: false
sequence_diagrams: false
tools:
github-checks:
enabled: false
auto_review:
enabled: true
auto_incremental_review: true
auto_pause_after_reviewed_commits: 0
drafts: false
pre_merge_checks:
docstrings:
mode: "off"
title:
mode: "off"
description:
mode: "off"
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
simplify:
enabled: false
autofix:
enabled: false
fix_ci:
enabled: false
resolve_merge_conflict:
enabled: false
path_instructions:
- path: "**/*"
instructions: >-
For cleanup changes, compare executable behavior against the PR merge
base. Check for removed guards, changed return values, weakened compiler
flags, and deleted regression tests. A passing APK build alone does not
establish behavior preservation. Avoid style-only or comment-coverage
requests.
- path: "app/src/main/**"
instructions: >-
Review Android lifecycle, external-display visibility, input cancellation,
permissions, threading, and Compose state. For JNI, native, and Rust
changes, check ABI compatibility, resource ownership, synchronization,
and error paths. Preserve persisted user settings and existing runtime
contracts.
- path: "tools/**"
instructions: >-
Check shell quoting, error handling, path and filesystem semantics, and
Python helper behavior. Preserve MSI same-file protection and strict
compiler checks. Verify downstream patches apply to their pinned sources
and retain intended executable behavior, including embedded scripts.
- path: ".github/workflows/**"
instructions: >-
Check job permissions, triggers, merge validation, build variants,
signing, artifact paths, and release conditions. Verify checks apply to
the current PR head and keep review configuration advisory.
+12
View File
@@ -0,0 +1,12 @@
tools/linuxfs/** text=auto eol=lf
tools/mangoapp/** text=auto eol=lf
tools/comet/LICENSE whitespace=-blank-at-eof
tools/comet/droiddeck.patch whitespace=-blank-at-eol,-blank-at-eof
tools/gamescope/patches/0900-enhanced-frame-diagnostics.patch whitespace=-blank-at-eol,-blank-at-eof,-space-before-tab
tools/linuxfs/overlay/** text eol=lf
tools/linuxfs/desktop/** text eol=lf
tools/mangoapp/mangoapp text eol=lf
app/src/main/assets/desktop-removal/*.json text eol=lf
app/src/main/assets/desktop-removal/*.txt text eol=lf
*.pdf binary
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
# Regenerates the contributions table in README.md, between its markers, from the repository's
# pull requests: one row per pull request by anyone but the owner, and a line of totals per
# contributor. Run by the Contributions ledger workflow when a pull request is opened, merged or
# closed, and by hand with: gh auth status && .github/scripts/contributions.sh OWNER/REPO
set -euo pipefail
REPO=${1:-The412Banner/SteamDeck}
OWNER=${REPO%%/*}
ROWS=$(gh api "repos/$REPO/pulls?state=all&per_page=100" --paginate \
--jq '.[] | select(.user.login != "'"$OWNER"'")
| [.user.login, .number, (.title | gsub("\\|"; "\\\\|")), (.created_at[0:10]),
(if .merged_at then "merged " + .merged_at[0:10] elif .state == "closed" then "closed" elif .draft then "draft" else "open" end),
.html_url] | @tsv' | sort -t"$(printf '\t')" -k1,1 -k2,2n)
{
echo "<!-- contributions:start -->"
echo "_From the repository's pull requests; rewritten when one is opened, merged or closed._"
echo
echo "| Contributor | Pull request | Opened | State |"
echo "|---|---|---|---|"
if [ -z "$ROWS" ]; then
echo "| — | none yet | | |"
else
while IFS=$'\t' read -r login num title opened state url; do
echo "| [@$login](https://github.com/$login) | [#$num]($url) $title | $opened | $state |"
done <<< "$ROWS"
echo
printf '%s\n' "$ROWS" | awk -F'\t' 'NF { t[$1]++; if ($5 ~ /^merged/) m[$1]++ } END { for (l in t) printf("- **@%s**: %d submitted, %d merged\n", l, t[l], m[l] + 0) }' | sort
fi
echo "<!-- contributions:end -->"
} > /tmp/contributions.md
python3 - <<'PY'
import re
s = open('README.md').read()
new = open('/tmp/contributions.md').read().rstrip('\n')
out, n = re.subn(r'<!-- contributions:start -->.*?<!-- contributions:end -->', lambda m: new, s, flags=re.S)
if n != 1:
raise SystemExit("README.md has no contributions markers")
open('README.md', 'w').write(out)
PY
echo "README.md: contributions table rewritten"
+5
View File
@@ -0,0 +1,5 @@
ASEbrahim
FX192
K11MCH1
kingokksa
ovurrsl
+40
View File
@@ -0,0 +1,40 @@
name: Build validation
on:
pull_request_target:
branches: [main]
types: [opened, synchronize, reopened, edited, ready_for_review]
push:
branches: [main]
workflow_run:
workflows: [Build APK]
types: [completed]
workflow_dispatch:
inputs:
pr_number:
description: Pull request to validate; empty for main
required: false
permissions:
contents: read
actions: read
pull-requests: read
statuses: write
jobs:
validate:
name: Validate source and build result
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.workflow_sha }}
fetch-depth: 0
persist-credentials: false
- name: Validate source and APK compilation
env:
GH_TOKEN: ${{ github.token }}
run: |
gh auth setup-git
python3 tools/release/validate_apk.py
+29
View File
@@ -0,0 +1,29 @@
name: Build AAudio sink module
on:
push:
paths:
- 'tools/aaudio-sink/**'
- '.github/workflows/build-aaudio-sink.yml'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: PulseAudio 13.0 source
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
- name: Build
run: |
set -euo pipefail
export NDK="${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-$ANDROID_NDK_ROOT}}"
tools/aaudio-sink/build.sh "$PWD/pa13" "$PWD/out"
- uses: actions/upload-artifact@v4
with:
name: audio-sink-modules
path: out/*.so
+100
View File
@@ -0,0 +1,100 @@
name: Build desktop package (KDE Plasma)
on:
push:
paths:
- 'tools/desktop-kde/**'
- '.github/workflows/build-desktop-kde.yml'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish as (e.g. desktop-kde-r1); empty = artifact only'
required: false
default: ''
permissions:
contents: write
jobs:
kwin:
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Build KWin with nested desktop scaling
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv patch binutils >/dev/null
bash tools/desktop-kde/build-kwin.sh out/kwin
- uses: actions/upload-artifact@v4
with:
name: desktop-kde-kwin
retention-days: 7
path: out/kwin/
build:
needs: kwin
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: desktop-kde-kwin
path: out/kwin
- uses: actions/create-github-app-token@v2
id: components-token
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Refuse a published tag
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
run: |
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
exit 1
fi
- name: Build
run: |
set -euo pipefail
sudo apt-get update -qq && sudo apt-get install -y -qq zstd xz-utils gpgv gcc-aarch64-linux-gnu >/dev/null
KDE_KWIN_OVERRIDE="$PWD/out/kwin" DESKTOP_KDE_RELEASE_TAG='${{ inputs.tag }}' tools/desktop-kde/build.sh out
cp out/kwin/desktop-kde.kwin.txt out/
echo "size $(stat -c %s out/desktop-kde.tar.zst) sha256 $(cat out/desktop-kde.sha256)"
echo "language packs $(ls out/desktop-kde-i18n-*.tar.zst | wc -l)"
echo "pacman repository $(ls out/repo | wc -l) files, $(du -sm out/repo | cut -f1) MiB"
- uses: actions/upload-artifact@v4
with:
name: desktop-kde
retention-days: 7
compression-level: 0
path: |
out/desktop-kde.tar.zst
out/desktop-kde.packages.txt
out/desktop-kde.kwin.txt
out/desktop-kde.sha256
out/desktop-kde.i18n.txt
out/desktop-kde-i18n-*.tar.zst
out/repo/
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
GH_REPO: Droid-Deck/DroidDeck-Components
run: |
set -euo pipefail
TAG='${{ inputs.tag }}'
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
exit 1
fi
prerelease=false
case $TAG in *-rc*) prerelease=true ;; esac
gh release create "$TAG" --latest=false --prerelease="$prerelease" --title "$TAG" \
--notes "KDE Plasma desktop package for DroidDeck's Linux runtime ($GITHUB_REPOSITORY tools/desktop-kde, $GITHUB_SHA). size $(stat -c %s out/desktop-kde.tar.zst), sha256 $(cat out/desktop-kde.sha256). desktop-kde.packages.txt lists the signature-checked Arch Linux ARM packages it was built from." \
out/desktop-kde.tar.zst out/desktop-kde.sha256 out/desktop-kde.packages.txt out/desktop-kde.kwin.txt \
out/desktop-kde.i18n.txt out/desktop-kde-i18n-*.tar.zst out/repo/*
gh release edit "$TAG" --latest=false --prerelease="$prerelease"
+45
View File
@@ -0,0 +1,45 @@
name: Build FEX (x86-64 Steam client)
on:
push:
paths:
- 'tools/fex/**'
- '.github/workflows/build-fex.yml'
workflow_dispatch:
inputs:
thunks:
description: 'Build the host thunks (Vulkan/GL/X11/... to the runtime arm64 libraries)'
required: false
default: 'false'
cxxflags:
description: 'Extra CXXFLAGS for a diagnostic build (e.g. -DDEBUG_STRACE)'
required: false
default: ''
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build in Arch Linux ARM
env:
FEX_EXTRA_CXXFLAGS: ${{ inputs.cxxflags }}
FEX_THUNKS: ${{ inputs.thunks == 'true' && '1' || '0' }}
run: |
set -euo pipefail
docker run --rm -e FEX_EXTRA_CXXFLAGS="$FEX_EXTRA_CXXFLAGS" -e FEX_THUNKS="$FEX_THUNKS" -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/fex/build-in-arch.sh
sudo chown -R "$(id -u):$(id -g)" .
- uses: actions/upload-artifact@v4
with:
name: fex
path: |
fex.tzst
fex.tzst.sha256
fex-needed.txt
fex-files.txt
+63
View File
@@ -0,0 +1,63 @@
name: Build gamescope (patched)
on:
push:
branches: [main]
paths:
- 'tools/gamescope/build-in-arch.sh'
- 'tools/gamescope/patches/**'
- 'tools/gamescope/runtime-sonames.txt'
- '.github/workflows/build-gamescope.yml'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish as (e.g. gamescope-3.16.29-p1); empty = artifact only'
required: false
default: ''
permissions:
contents: write
concurrency:
group: ${{ inputs.tag && format('gamescope-tag-{0}', inputs.tag) || format('gamescope-{0}-{1}', github.event_name, github.ref) }}
cancel-in-progress: ${{ github.event_name == 'push' }}
jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Build in Arch Linux ARM
run: |
set -euo pipefail
docker run --rm -v "$PWD:/work" -w /work menci/archlinuxarm:base-devel bash tools/gamescope/build-in-arch.sh
sudo chown -R "$(id -u):$(id -g)" .
- uses: actions/upload-artifact@v4
with:
name: gamescope-patched
path: |
gamescope.tzst
gamescope.tzst.sha256
- uses: actions/create-github-app-token@v2
id: components-token
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
GH_REPO: Droid-Deck/DroidDeck-Components
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::error::$TAG is already published in DroidDeck-Components, and published tags are never replaced: use a new tag"
exit 1
fi
gh release create "$TAG" --latest=false --prerelease=false --title "$TAG" --notes "gamescope 3.16.29 with tools/gamescope/patches ($GITHUB_REPOSITORY $GITHUB_SHA, run $GITHUB_RUN_ID); staged by the apk over /usr/local/bin/gamescope. sha256 $(cut -d' ' -f1 gamescope.tzst.sha256)" gamescope.tzst gamescope.tzst.sha256
gh release edit "$TAG" --latest=false --prerelease=false
@@ -0,0 +1,95 @@
name: Build Linux runtime (Ubuntu)
on:
push:
paths:
- 'tools/ubuntu/**'
- '.github/workflows/build-ubuntu-runtime.yml'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish as (e.g. ubuntu-r1); empty = artifact only'
required: false
default: ''
permissions:
contents: read
jobs:
debs:
runs-on: ubuntu-24.04-arm
timeout-minutes: 240
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build Mesa with KGSL and the patched KWin
run: |
set -euo pipefail
docker run --rm -v "$PWD:/work" -w /work ubuntu:26.04 bash tools/ubuntu/build-debs.sh /work/out/debs
sudo chown -R "$(id -u):$(id -g)" out
- uses: actions/upload-artifact@v4
with:
name: ubuntu-runtime-debs
retention-days: 7
compression-level: 0
path: out/debs/
rootfs:
needs: debs
runs-on: ubuntu-24.04-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@v4
with:
name: ubuntu-runtime-debs
path: out/debs
- uses: actions/create-github-app-token@v2
id: components-token
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Refuse a published tag
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
run: |
if gh release view '${{ inputs.tag }}' -R Droid-Deck/DroidDeck-Components >/dev/null 2>&1; then
echo "::error::${{ inputs.tag }} is already published, and published tags are never replaced: use a new tag"
exit 1
fi
- name: Build
run: |
set -euo pipefail
sudo apt-get update -qq && sudo apt-get install -y -qq zstd gpgv ubuntu-keyring >/dev/null
tag='${{ inputs.tag }}'
tools/ubuntu/build.sh out/debs out "${tag:-ubuntu-dev}"
- uses: actions/upload-artifact@v4
with:
name: ubuntu-runtime
retention-days: 7
compression-level: 0
path: |
out/*.tar.zst
out/*.sha256
out/*.packages.txt
- name: Publish
if: github.event_name == 'workflow_dispatch' && inputs.tag != ''
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
GH_REPO: Droid-Deck/DroidDeck-Components
run: |
set -euo pipefail
TAG='${{ inputs.tag }}'
prerelease=false
case $TAG in *-rc*) prerelease=true ;; esac
gh release create "$TAG" --latest=false --prerelease="$prerelease" --title "$TAG" \
--notes "Ubuntu 26.04 LTS arm64 runtime for DroidDeck ($GITHUB_REPOSITORY tools/ubuntu, $GITHUB_SHA): size $(stat -c %s "out/$TAG.tar.zst"), sha256 $(cat "out/$TAG.sha256"). $TAG-desktop.tar.zst is the Kubuntu Plasma desktop built on top of it, holding only what the runtime does not already have: size $(stat -c %s "out/$TAG-desktop.tar.zst"), sha256 $(cat "out/$TAG-desktop.sha256"). The .packages.txt files list every installed package and version." \
"out/$TAG.tar.zst" "out/$TAG.sha256" "out/$TAG.packages.txt" \
"out/$TAG-desktop.tar.zst" "out/$TAG-desktop.sha256" "out/$TAG-desktop.packages.txt" out/debs/*.deb
gh release edit "$TAG" --latest=false --prerelease="$prerelease"
+31
View File
@@ -0,0 +1,31 @@
name: Build Venus (Mali)
# The Mali (Venus) route's binaries from tools/venus (David Cimo's recipes): Mesa's Venus ICD and the
# Mali compatibility layer (glibc, for the session) and virglrenderer's vtest server (bionic, run by
# the app over the phone's own Vulkan driver), packed as venus.tzst rooted at app/src/main. The apk
# build fetches it by tools/venus/release.env (tag + sha256); nothing compiled is kept in this repo.
on:
workflow_dispatch:
push:
branches: [test/non-adreno-all]
paths: ['tools/venus/**', '.github/workflows/build-venus.yml']
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Tools
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq ninja-build pkg-config zstd python3-pip
pip3 install --break-system-packages 'meson>=1.5'
- name: Build
run: ANDROID_NDK_HOME="$ANDROID_NDK_LATEST_HOME" WORK=$PWD/venus-work tools/venus/ci-build.sh
- uses: actions/upload-artifact@v4
with:
name: venus
path: |
venus-work/venus.tzst
venus-work/venus.tzst.sha256
File diff suppressed because it is too large. Load diff
+51
View File
@@ -0,0 +1,51 @@
name: Publish the CI Debian image
on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish to (a new -rN tag for a refresh; builds pin the one in tools/linuxfs/debian-image.env)'
required: true
default: 'ci-debian-bullseye-r2'
replace:
description: 'Overwrite the asset if the release already has one (changes its checksum: re-pin it in debian-image.env)'
type: boolean
default: false
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Pull, save and publish
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag }}
REPLACE: ${{ inputs.replace }}
run: |
set -euo pipefail
image=public.ecr.aws/docker/library/debian:bullseye
if [ "$REPLACE" != true ] && gh release view "$TAG" -R "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' 2>/dev/null | grep -qx debian-bullseye.tar.zst; then
echo "::error::$TAG already carries debian-bullseye.tar.zst; publish to a new tag, or set replace and re-pin tools/linuxfs/debian-image.env"
exit 1
fi
for attempt in 1 2 3 4 5 6; do
docker pull "$image" && break
[ "$attempt" -lt 6 ] || { echo "::error::could not pull $image"; exit 1; }
echo "pull attempt $attempt failed (registry busy?); retrying in $((attempt * 30)) s"
sleep $((attempt * 30))
done
docker image inspect "$image" --format 'image id {{.Id}} created {{.Created}}'
docker save "$image" | zstd -19 -T0 -o debian-bullseye.tar.zst
sha256sum debian-bullseye.tar.zst | tee SHA256SUMS
ls -l debian-bullseye.tar.zst
if ! gh release view "$TAG" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release create "$TAG" -R "$GITHUB_REPOSITORY" --title "CI Debian 11 image ($TAG)" --latest=false \
--notes "The debian:bullseye image build.yml compiles the x86 preloads in (glibc 2.31), saved with docker save and compressed with zstd. Pulled from public.ecr.aws/docker/library/debian:bullseye on $(date -u +%Y-%m-%d); image id $(docker image inspect "$image" --format '{{.Id}}'). Pinned in tools/linuxfs/debian-image.env."
fi
gh release upload "$TAG" -R "$GITHUB_REPOSITORY" debian-bullseye.tar.zst SHA256SUMS --clobber
{ echo "Published \`$TAG\`:"; echo; echo '```'; cat SHA256SUMS; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
-40
View File
@@ -1,40 +0,0 @@
name: Contributions ledger
# Keeps the Contributors section of the README current: every pull request by a contributor,
# with its state, and a line of totals each. Runs when a pull request is opened, merged or
# closed and commits the README to main as the Actions bot. The README is documentation, so
# that commit does not buy an apk build.
on:
pull_request_target:
types: [ opened, reopened, closed, edited ]
workflow_dispatch:
permissions:
contents: write
pull-requests: read
concurrency:
group: contributions
cancel-in-progress: false
jobs:
ledger:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: main
- name: Rewrite the table
env:
GH_TOKEN: ${{ github.token }}
run: .github/scripts/contributions.sh "$GITHUB_REPOSITORY"
- name: Commit it when it changed
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
if git diff --cached --quiet; then echo "ledger unchanged"; exit 0; fi
git commit -m "README: contributions ledger"
git push origin HEAD:main
+38
View File
@@ -0,0 +1,38 @@
name: Pick runner
on:
workflow_call:
outputs:
labels:
value: ${{ jobs.pick.outputs.labels }}
secrets:
RUNNER_STATUS_TOKEN:
required: false
permissions: {}
jobs:
pick:
name: Pick runner
runs-on: ubuntu-24.04
timeout-minutes: 2
outputs:
labels: ${{ steps.runner.outputs.labels || '"ubuntu-24.04"' }}
steps:
- name: Pick the compile runner
id: runner
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.RUNNER_STATUS_TOKEN }}
run: |
set -euo pipefail
labels='"ubuntu-24.04"'
if [ -n "$GH_TOKEN" ]; then
idle=$(gh api "orgs/${GITHUB_REPOSITORY_OWNER}/actions/runners" --jq '[.runners[] | select(.status == "online" and .busy == false and any(.labels[]; .name == "droiddeck-ws"))] | length' 2>/dev/null || echo 0)
if [ "${idle:-0}" -gt 0 ]; then
labels='["self-hosted","droiddeck-ws"]'
fi
fi
echo "labels=$labels" >> "$GITHUB_OUTPUT"
echo "Compile runner: \`$labels\`" >> "$GITHUB_STEP_SUMMARY"
+85
View File
@@ -0,0 +1,85 @@
name: PR Release Cleanup
run-name: remove PR
on:
pull_request_target:
types: [ closed ]
branches: [ main ]
permissions:
contents: read
pull-requests: read
concurrency:
group: pr-release-${{ github.event.pull_request.number }}
cancel-in-progress: false
queue: max
jobs:
cleanup:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check the current PR state
id: state
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
state=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq .state)
echo "closed=$([ "$state" = closed ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
- uses: actions/create-github-app-token@v2
if: ${{ steps.state.outputs.closed == 'true' }}
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Delete the test build
if: ${{ steps.state.outputs.closed == 'true' }}
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
set -euo pipefail
retry() {
for attempt in 1 2 3 4; do
"$@" && return 0
[ "$attempt" = 4 ] || sleep $((attempt * 20))
done
return 1
}
existing=$(retry gh api --paginate "repos/Droid-Deck/DroidDeck-CI/releases?per_page=100" --jq '.[].tag_name')
found=0
for tag in "pr-$PR_NUMBER" "pr-$PR_NUMBER.0"; do
if grep -qxF "$tag" <<<"$existing"; then
retry gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag
echo "Deleted $tag"
found=1
fi
done
[ "$found" = 1 ] || echo "No test build for PR #$PR_NUMBER"
- uses: actions/checkout@v4
if: ${{ steps.state.outputs.closed == 'true' }}
with:
ref: main
persist-credentials: false
- name: Retire the Discord PR announcement
if: ${{ steps.state.outputs.closed == 'true' }}
env:
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: python3 tools/release/pr_discord.py retire
catalog:
needs: cleanup
if: ${{ always() }}
uses: ./.github/workflows/update-catalog.yml
secrets: inherit
+419
View File
@@ -0,0 +1,419 @@
name: PR Release Publish
run-name: "sign PR #${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }} with the release key"
on:
workflow_dispatch:
inputs:
pr_number:
description: Open pull request number
required: true
type: string
automatic_build_run_id:
description: Completed build run for automatic publishing
required: false
type: string
pull_request_target:
types: [labeled]
branches: [main]
workflow_run:
workflows: [Build APK]
types: [completed]
permissions:
actions: read
contents: read
issues: write
pull-requests: write
concurrency:
group: pr-release-${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
cancel-in-progress: false
queue: max
jobs:
dispatch-fork-builds:
if: >-
github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name != github.repository
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: write
pull-requests: read
steps:
- name: Find fork PRs for the completed build
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
RUN_SHA: ${{ github.event.workflow_run.head_sha }}
RUN_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
RUN_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
set -euo pipefail
owner=${RUN_REPO%%/*}
gh api --paginate --slurp -X GET "repos/$GITHUB_REPOSITORY/pulls" \
-f state=open -f base=main -f "head=$owner:$RUN_BRANCH" -f per_page=100 |
jq -r --arg sha "$RUN_SHA" --arg repo "$RUN_REPO" --arg branch "$RUN_BRANCH" '
.[][] | select(.head.sha == $sha and .head.repo.full_name == $repo and .head.ref == $branch) | .number' |
while IFS= read -r number; do
gh workflow run pr-release-publish.yml -R "$GITHUB_REPOSITORY" --ref main \
-f "pr_number=$number" -f "automatic_build_run_id=$RUN_ID"
done
authorize:
if: >-
(github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
(github.event_name == 'pull_request_target' &&
(github.event.action != 'labeled' || github.event.label.name == 'publish-test')) ||
(github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.pull_requests[0] != null)
runs-on: ubuntu-24.04
timeout-minutes: 150
outputs:
pr_number: ${{ steps.gate.outputs.pr_number }}
run_id: ${{ steps.gate.outputs.run_id }}
head_sha: ${{ steps.gate.outputs.head_sha }}
base_sha: ${{ steps.gate.outputs.base_sha }}
version_code: ${{ steps.gate.outputs.version_code }}
label: ${{ steps.gate.outputs.label }}
skip: ${{ steps.gate.outputs.skip }}
automatic: ${{ steps.gate.outputs.automatic }}
variants: ${{ steps.variants.outputs.list }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: The packages a signed build ships as
id: variants
run: echo "list=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
- name: Check the requester, PR, and build
id: gate
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
REQUESTER: ${{ github.actor }}
PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || inputs.pr_number }}
ACTION: ${{ github.event.action }}
BUILT_SHA: ${{ github.event.workflow_run.head_sha }}
AUTO_RUN: ${{ inputs.automatic_build_run_id }}
run: |
set -euo pipefail
[[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || { echo "::error::Invalid PR number '$PR_NUMBER'"; exit 1; }
skip() { echo "::notice::Not publishing PR #$PR_NUMBER: $1"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
if [ "$EVENT_NAME" = pull_request_target ] && [ "$ACTION" = labeled ]; then
permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$REQUESTER/permission" --jq .permission)
case "$permission" in
admin|maintain|write) ;;
*) echo "::error::@$REQUESTER does not have permission to publish release-signed test builds"; exit 1 ;;
esac
fi
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
jq -e '.state == "open" and .draft == false and .base.ref == "main"' <<<"$pr" >/dev/null ||
skip "it must be open, ready for review, and target main"
if [ "$EVENT_NAME" = workflow_run ]; then
[ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] || skip "it comes from a fork"
[ "$(jq -r .head.sha <<<"$pr")" = "$BUILT_SHA" ] || skip "a newer push is building"
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
echo "automatic=true" >> "$GITHUB_OUTPUT"
elif [ "$EVENT_NAME" = workflow_dispatch ] && [ -n "$AUTO_RUN" ]; then
[[ "$AUTO_RUN" =~ ^[1-9][0-9]*$ ]] || { echo '::error::Invalid automatic build run ID'; exit 1; }
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$pr" >/dev/null && skip "it is labelled no-test-build"
git fetch --no-tags origin main
opener=$(jq -r .user.login <<<"$pr")
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$opener" || skip "PR opener @$opener is not trusted for automatic signing"
echo "automatic=true" >> "$GITHUB_OUTPUT"
fi
head=$(jq -r '.head.sha' <<<"$pr")
base=$(jq -r '.base.sha' <<<"$pr")
run_id=
for attempt in $(seq 1 240); do
latest=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/build.yml/runs?event=pull_request&head_sha=$head&per_page=100" |
jq -c --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
[.workflow_runs[] | select(.head_sha == $head and .head_repository.full_name == $repo and .head_branch == $ref)] |
sort_by([.created_at, .run_attempt]) | last // empty')
if [[ -z "$latest" && "$attempt" == 1 ]]; then
git fetch --no-tags origin main
git fetch --no-tags origin "refs/pull/$PR_NUMBER/head"
git diff --no-renames --name-only -z "$base...FETCH_HEAD" > "$RUNNER_TEMP/pr-changed-paths"
excluded=true
while IFS= read -r -d '' path; do
case "$path" in
*.md|LICENSE|.gitignore) ;;
*) excluded=false; break ;;
esac
done < "$RUNNER_TEMP/pr-changed-paths"
[ "$excluded" = false ] || skip "its changes do not trigger the APK build"
fi
if [[ -n "$latest" && $(jq -r .status <<<"$latest") == completed ]]; then
conclusion=$(jq -r .conclusion <<<"$latest")
if [[ "$conclusion" == success ]]; then
run_id=$(jq -r .id <<<"$latest")
if [ -n "$AUTO_RUN" ] && [ "$run_id" != "$AUTO_RUN" ]; then
skip "a newer build exists"
fi
break
fi
[[ "$conclusion" == action_required ]] || { echo "::error::The latest PR build is not green ($conclusion)"; exit 1; }
fi
status=not-started
[[ -z "$latest" ]] || status=$(jq -r .status <<<"$latest")
echo "Waiting for the PR build of ${head:0:7} to finish ($status)"
sleep 30
done
[[ -n "$run_id" ]] || { echo '::error::The PR build did not finish within 120 minutes'; exit 1; }
run=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id")
jq -e --arg head "$head" --arg repo "$(jq -r .head.repo.full_name <<<"$pr")" --arg ref "$(jq -r .head.ref <<<"$pr")" '
.event == "pull_request" and .head_sha == $head and .conclusion == "success" and
.head_repository.full_name == $repo and .head_branch == $ref
' <<<"$run" >/dev/null ||
{ echo "::error::Build run $run_id is not a green build of PR #$PR_NUMBER at ${head:0:7}"; exit 1; }
sdk=${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}
tools=$(find "$sdk/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1)
main_code=$(sed -n 's/^[[:space:]]*versionCode[[:space:]][[:space:]]*\([0-9][0-9]*\)[[:space:]]*$/\1/p' app/build.gradle)
[[ "$main_code" =~ ^[0-9]+$ ]] || { echo "::error::No numeric versionCode in main's app/build.gradle"; exit 1; }
mkdir -p "$RUNNER_TEMP/pr"
gh run download "$run_id" -R "$GITHUB_REPOSITORY" -n droiddeck-apk -D "$RUNNER_TEMP/pr"
mapfile -t apks < <(find "$RUNNER_TEMP/pr" -type f -name '*.apk')
[[ ${#apks[@]} == 1 ]] || { echo '::error::Expected one PR APK'; exit 1; }
package_line() {
"$tools/aapt" dump badging "$1" | sed -n "s/^package: name='\\([^']*\\)' versionCode='\\([^']*\\)'.*/\\1 \\2/p"
}
candidate=$(package_line "${apks[0]}")
[[ "$candidate" =~ ^com\.droiddeck\.launcher\ [0-9]+$ && ${candidate##* } -ge $main_code ]] || {
echo "::error::PR package/versionCode '$candidate' cannot install over main (versionCode $main_code)"; exit 1;
}
current=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
jq -e --arg head "$head" --arg base "$base" '
.state == "open" and .draft == false and .base.ref == "main" and
.head.sha == $head and .base.sha == $base
' <<<"$current" >/dev/null ||
{ echo "::error::PR #$PR_NUMBER moved on while its build was checked; publish again"; exit 1; }
if [ "$EVENT_NAME" = workflow_run ] || { [ "$EVENT_NAME" = workflow_dispatch ] && [ -n "$AUTO_RUN" ]; }; then
jq -e 'any(.labels[]; .name == "no-test-build")' <<<"$current" >/dev/null && skip "it is labelled no-test-build"
if [ "$EVENT_NAME" = workflow_dispatch ]; then
git fetch --no-tags origin main
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$(jq -r .user.login <<<"$current")" || skip "the PR opener is no longer trusted"
fi
fi
{
echo "pr_number=$PR_NUMBER"
echo "run_id=$run_id"
echo "head_sha=$head"
echo "base_sha=$base"
echo "version_code=${candidate##* }"
echo "label=PR.$PR_NUMBER-${head:0:7}"
} >> "$GITHUB_OUTPUT"
sign:
needs: authorize
uses: ./.github/workflows/sign.yml
with:
environment: signing-main
variants: ${{ needs.authorize.outputs.variants }}
run_id: ${{ needs.authorize.outputs.run_id }}
label: ${{ needs.authorize.outputs.label }}
artifact: droiddeck-pr-signed
secrets: inherit
if: ${{ needs.authorize.outputs.skip != 'true' }}
publish:
needs: [authorize, sign]
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- uses: actions/download-artifact@v4
with:
pattern: droiddeck-pr-signed-*
merge-multiple: true
path: signed
- name: The add-on packs main has not published
env:
GH_TOKEN: ${{ github.token }}
RUN: ${{ needs.authorize.outputs.run_id }}
run: |
set -euo pipefail
gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
mkdir -p pr-packs
for file in packs/*.tar.zst; do
name=$(basename "$file" .tar.zst)
sha=$(sha256sum "$file" | cut -d' ' -f1)
tag="pack-$name-${sha:0:16}"
digest=$({ gh api "repos/Droid-Deck/DroidDeck-Components/releases/tags/$tag" 2>/dev/null || echo '{}'; } |
jq -r --arg n "$name.tar.zst" '.assets // [] | .[] | select(.name == $n) | .digest // empty')
if [ "$digest" = "sha256:$sha" ]; then
echo "$name: served from DroidDeck-Components $tag"
else
cp "$file" "pr-packs/$tag.tar.zst"
echo "$name: attached to the PR release as $tag.tar.zst"
fi
done
- name: Describe the change
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
set -euo pipefail
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
title=$(jq -r .title <<<"$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 "**Not merged** - a test build of open [PR #$PR_NUMBER]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$PR_NUMBER): $title"
if [ -n "$summary" ]; then
echo
echo "$summary"
fi
} > notes-head.md
cat notes-head.md
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Publish to DroidDeck-CI
id: publish
env:
GH_TOKEN: ${{ steps.ci-token.outputs.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
BASE_SHA: ${{ needs.authorize.outputs.base_sha }}
VERSION_CODE: ${{ needs.authorize.outputs.version_code }}
run: |
set -euo pipefail
repo=Droid-Deck/DroidDeck-CI
tag="pr-$PR_NUMBER"
cat signed/SHA256SUMS-*.txt > signed/SHA256SUMS.txt
rm signed/SHA256SUMS-*.txt
notes="$(cat notes-head.md)
Source: https://github.com/$GITHUB_REPOSITORY/pull/$PR_NUMBER
Commit: $HEAD_SHA
Base: $BASE_SHA
VersionCode: $VERSION_CODE
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)
\`\`\`"
first=false
if ! gh release view "$tag" -R "$repo" >/dev/null 2>&1; then
gh release create "$tag" -R "$repo" --target main --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
first=true
fi
echo "first=$first" >> "$GITHUB_OUTPUT"
cp pr-packs/* signed/ 2>/dev/null || true
gh release upload "$tag" signed/* -R "$repo" --clobber
gh release edit "$tag" -R "$repo" --title "PR #$PR_NUMBER" --notes "$notes" --prerelease
keep=$(cd signed && ls | jq -R . | jq -sc .)
gh api "repos/$repo/releases/tags/$tag" |
jq -r --argjson keep "$keep" '.assets[] | select(.name as $n | $keep | index($n) | not) | .id' |
while IFS= read -r id; do
gh api --method DELETE "repos/$repo/releases/assets/$id" >/dev/null
done
published=$(gh release view "$tag" -R "$repo" --json tagName,assets)
jq -e --arg tag "$tag" '.tagName == $tag and (.assets | length) > 0' <<<"$published" >/dev/null
release="$GITHUB_SERVER_URL/$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=$release" >> "$GITHUB_OUTPUT"
echo "apk=$GITHUB_SERVER_URL/$repo/releases/download/$tag/$apk" >> "$GITHUB_OUTPUT"
- name: Whether the PR opener is trusted
id: trusted-opener
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
opener=$(jq -r .user.login <<<"$pr")
git fetch --no-tags origin main
if [ "$(jq -r .head.repo.full_name <<<"$pr")" = "$GITHUB_REPOSITORY" ] ||
git show origin/main:.github/trusted-test-builds.txt | grep -Fixq -- "$opener"; then
echo "trusted=true" >> "$GITHUB_OUTPUT"
else
echo "trusted=false" >> "$GITHUB_OUTPUT"
fi
- name: Clear the publish request label
if: ${{ github.event_name == 'pull_request_target' && github.event.action == 'labeled' && steps.publish.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
run: |
gh api --method DELETE "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/labels/publish-test"
- name: Report the signed build on the PR
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
TRUSTED_OPENER: ${{ steps.trusted-opener.outputs.trusted }}
run: |
set -euo pipefail
short=${HEAD_SHA:0:7}
mark='<!-- droiddeck-pr-release -->'
if [ "$TRUSTED_OPENER" = true ]; then
NEXT='Each green build of a new push is signed and published here on its own. Label the PR `no-test-build` to stop that.'
else
NEXT='Pushes to this PR do not re-sign automatically. After a new green build, a maintainer can add the `publish-test` label again.'
fi
body=$(cat <<EOF
$mark
## Signed test build
A release-signed build of this PR at \`$short\` is ready. It installs over Stable or Preview and keeps app data.
[Download the standard APK]($APK) · [All variants and checksums]($RELEASE)
It is also available in **DroidDeck → Updates → Test builds → PR #$PR_NUMBER**.
$NEXT
EOF
)
comment=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate --slurp | \
jq "[.[][] | select(.user.login == \"github-actions[bot]\") | select(.body | startswith(\"$mark\"))] | first // {}")
id=$(jq -r '.id // empty' <<<"$comment")
if [ -n "$id" ]; then
state=$(jq -r '.body | split("\n")[] | select(startswith("<!-- droiddeck-discord: ") and endswith(" -->"))' <<<"$comment")
if [ -n "$state" ]; then body=$(printf '%s\n%s' "$body" "$state"); fi
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -f body="$body" >/dev/null
else
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" -f body="$body" >/dev/null
fi
- name: Update the Discord PR announcement
if: ${{ always() && steps.publish.outcome == 'success' }}
env:
WEBHOOK: ${{ secrets.DISCORD_PR_BUILDS_WEBHOOK }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ needs.authorize.outputs.pr_number }}
HEAD_SHA: ${{ needs.authorize.outputs.head_sha }}
RELEASE: ${{ steps.publish.outputs.release }}
APK: ${{ steps.publish.outputs.apk }}
run: python3 tools/release/pr_discord.py publish
catalog:
needs: publish
if: ${{ always() && needs.publish.result != 'skipped' }}
uses: ./.github/workflows/update-catalog.yml
secrets: inherit
+57
View File
@@ -0,0 +1,57 @@
name: PR Release Sweep
run-name: sweep test builds of closed PRs
on:
schedule:
- cron: '17 */6 * * *'
workflow_dispatch:
permissions:
contents: read
pull-requests: read
concurrency:
group: pr-release-sweep
cancel-in-progress: false
jobs:
sweep:
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Delete test builds whose PR is no longer open
env:
CI_TOKEN: ${{ steps.ci-token.outputs.token }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
retry() {
for attempt in 1 2 3 4; do
"$@" && return 0
[ "$attempt" = 4 ] || sleep $((attempt * 20))
done
return 1
}
tags=$(GH_TOKEN="$CI_TOKEN" retry gh api --paginate "repos/Droid-Deck/DroidDeck-CI/releases?per_page=100" --jq '.[].tag_name' |
grep -E '^pr-[0-9]+(\.0)?$' || true)
failed=0
for tag in $tags; do
number=${tag#pr-}
number=${number%.0}
state=$(retry gh api "repos/$GITHUB_REPOSITORY/pulls/$number" --jq .state) || { failed=1; continue; }
if [ "$state" != open ]; then
if GH_TOKEN="$CI_TOKEN" retry gh release delete "$tag" -R Droid-Deck/DroidDeck-CI --yes --cleanup-tag; then
echo "Deleted $tag (PR #$number is $state)"
else
failed=1
fi
fi
done
exit "$failed"
+77
View File
@@ -0,0 +1,77 @@
name: Publish add-on packs
on:
workflow_call:
inputs:
run_id:
description: The Build APK run whose packs to publish; empty for the calling run's own
type: string
default: ''
permissions:
contents: read
actions: read
jobs:
publish:
name: publish add-on packs
runs-on: ubuntu-24.04
timeout-minutes: 15
concurrency:
group: publish-packs
cancel-in-progress: false
steps:
- name: The packs (this run's build)
if: inputs.run_id == ''
uses: actions/download-artifact@v4
with:
name: droiddeck-packs
path: packs
- name: The packs (another Build APK run)
if: inputs.run_id != ''
env:
GH_TOKEN: ${{ github.token }}
RUN: ${{ inputs.run_id }}
run: gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-packs -D packs
- uses: actions/create-github-app-token@v2
id: components-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-Components
- name: Publish
env:
GH_TOKEN: ${{ steps.components-token.outputs.token }}
REPO: Droid-Deck/DroidDeck-Components
BUILD_RUN: ${{ inputs.run_id || github.run_id }}
run: |
set -euo pipefail
published_digest() {
{ gh api "repos/$REPO/releases/tags/$1" 2>/dev/null || echo '{}'; } |
jq -r --arg n "$2" '.assets // [] | .[] | select(.name == $n) | .digest // empty'
}
commit=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN" --jq .head_sha 2>/dev/null || echo "$GITHUB_SHA")
for name in proot directaudio tracking xbox comet butler powervr; do
file="packs/$name.tar.zst"
[ -f "$file" ] || { echo "::error::$name.tar.zst is missing from Build APK run $BUILD_RUN"; exit 1; }
sha=$(sha256sum "$file" | cut -d' ' -f1)
tag="pack-$name-${sha:0:16}"
digest=$(published_digest "$tag" "$name.tar.zst")
if [ -z "$digest" ]; then
notes="DroidDeck add-on pack \`$name\`, built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$commit\` with tools/packs/build.sh and published by [run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID).
Size: $(stat -c %s "$file") bytes. SHA-256: $sha. APKs built in the same run pin this digest."
gh release create "$tag" -R "$REPO" --title "$tag" --notes "$notes" --latest=false "$file" \
|| echo "::warning::creating $tag failed; checking whether another run published it"
digest=$(published_digest "$tag" "$name.tar.zst")
fi
if [ "$digest" != "sha256:$sha" ]; then
echo "::error::$tag holds ${digest:-nothing} for $name.tar.zst, not sha256:$sha"
exit 1
fi
echo "- \`$name\`: [$tag]($GITHUB_SERVER_URL/$REPO/releases/tag/$tag) \`$sha\`" >> "$GITHUB_STEP_SUMMARY"
done
+127
View File
@@ -0,0 +1,127 @@
name: Release APKs
run-name: release ${{ inputs.version }} from main ${{ github.sha }}
on:
workflow_dispatch:
inputs:
version:
description: Version for the file names, e.g. 0.1.7
required: true
permissions:
contents: read
actions: read
concurrency:
group: release
cancel-in-progress: false
jobs:
find:
name: find the main build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
outputs:
run_id: ${{ steps.find.outputs.run_id }}
variants: ${{ steps.find.outputs.variants }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- id: find
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
[[ $VERSION =~ ^[0-9A-Za-z._-]+$ ]] || { echo "::error::version '$VERSION': letters, digits, . _ - only"; exit 1; }
name=$(sed -n 's/^ *versionName "\(.*\)"$/\1/p' app/build.gradle)
[[ $VERSION == "$name" || $VERSION == "$name"-* ]] || {
echo "::error::version '$VERSION' does not match versionName '$name' in app/build.gradle: bump versionName and versionCode first"; exit 1;
}
runs=$(gh run list -R "$GITHUB_REPOSITORY" --workflow build.yml --branch main --commit "$GITHUB_SHA" \
--status success --json databaseId,event,headSha -L 20)
id=$(jq -r --arg sha "$GITHUB_SHA" \
'[.[] | select(.headSha == $sha and (.event == "push" or .event == "workflow_dispatch"))][0].databaseId // empty' <<<"$runs")
if [ -z "$id" ]; then
echo "::error::main ${GITHUB_SHA::7} has no successful Build APK run yet: wait for it, then release"
exit 1
fi
variants=$(awk '!/^#/ && NF { print $1 }' tools/release/variants.txt | jq -R . | jq -sc .)
echo "run_id=$id" >> "$GITHUB_OUTPUT"
echo "variants=$variants" >> "$GITHUB_OUTPUT"
echo "Signing Build APK run $id (main ${GITHUB_SHA::7}) as $VERSION: $variants" >> "$GITHUB_STEP_SUMMARY"
sign:
needs: find
uses: ./.github/workflows/sign.yml
with:
environment: release
variants: ${{ needs.find.outputs.variants }}
run_id: ${{ needs.find.outputs.run_id }}
label: ${{ inputs.version }}
artifact: droiddeck-release
secrets: inherit
packs:
needs: find
permissions:
contents: read
actions: read
uses: ./.github/workflows/publish-packs.yml
with:
run_id: ${{ needs.find.outputs.run_id }}
secrets: inherit
publish:
name: publish the release
needs: [find, sign, packs]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@v4
with:
pattern: droiddeck-release-*
merge-multiple: true
path: out
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
BUILD_RUN: ${{ needs.find.outputs.run_id }}
run: |
set -euo pipefail
notes="docs/releases/$VERSION.md"
[ -f "$notes" ] || { echo "::error::$notes is missing: add the release notes first"; exit 1; }
if gh release view "$VERSION" -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "::error::release $VERSION already exists"; exit 1
fi
title=$(sed -n '1s/^# *//p' "$notes")
[ -n "$title" ] || title="DroidDeck $VERSION"
raw="https://raw.githubusercontent.com/$GITHUB_REPOSITORY/$VERSION/docs/releases/media/"
{
sed '1{/^# /d;}' "$notes" | sed -E "s#(\]\(|src=\")media/#\1$raw#g"
echo
echo "---"
echo
echo "Built by [Build APK run $BUILD_RUN]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$BUILD_RUN) from \`$GITHUB_SHA\`, signed and published by [Release APKs run $GITHUB_RUN_ID]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID)."
} > "$RUNNER_TEMP/body.md"
( cd out && cat SHA256SUMS-*.txt | sort -k2 > SHA256SUMS.txt && rm SHA256SUMS-*.txt && cat SHA256SUMS.txt )
gh release create "$VERSION" -R "$GITHUB_REPOSITORY" out/*.apk out/SHA256SUMS.txt \
--target "$GITHUB_SHA" --title "$title" --notes-file "$RUNNER_TEMP/body.md" --latest
echo "Published [$title]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$VERSION)" >> "$GITHUB_STEP_SUMMARY"
catalog:
needs: publish
uses: ./.github/workflows/update-catalog.yml
permissions:
contents: read
pull-requests: read
secrets: inherit
+95
View File
@@ -0,0 +1,95 @@
name: Sign APKs
on:
workflow_call:
inputs:
environment:
type: string
required: true
variants:
description: JSON array of names from tools/release/variants.txt
type: string
required: true
run_id:
description: The Build APK run whose apk to sign; empty for the calling run's own
type: string
default: ''
label:
description: Goes in the file names (a version); empty for <branch>-<short sha>
type: string
default: ''
artifact:
type: string
default: droiddeck-signed
permissions:
contents: read
actions: read
jobs:
sign:
name: sign ${{ matrix.variant }}
runs-on: ubuntu-24.04
timeout-minutes: 15
environment: ${{ inputs.environment }}
strategy:
fail-fast: false
matrix:
variant: ${{ fromJSON(inputs.variants) }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: The apk (this run's build)
if: inputs.run_id == ''
uses: actions/download-artifact@v4
with:
name: droiddeck-apk
path: in
- name: The apk (a main build)
if: inputs.run_id != ''
env:
GH_TOKEN: ${{ github.token }}
RUN: ${{ inputs.run_id }}
run: gh run download "$RUN" -R "$GITHUB_REPOSITORY" -n droiddeck-apk -D in
- name: Sign
env:
RELEASE_KEYSTORE_B64: ${{ secrets.RELEASE_KEYSTORE_B64 }}
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_ALIAS: droiddeck
ENVIRONMENT: ${{ inputs.environment }}
VARIANT: ${{ matrix.variant }}
LABEL: ${{ inputs.label }}
run: |
set -euo pipefail
if [ -z "$RELEASE_KEYSTORE_B64" ]; then
echo "::error::environment '$ENVIRONMENT' has no signing key: run tools/release/make-release-key.sh once"
exit 1
fi
export RELEASE_KEYSTORE="$RUNNER_TEMP/release.p12"
( umask 077; base64 -d <<<"$RELEASE_KEYSTORE_B64" > "$RELEASE_KEYSTORE" )
BUILD_TOOLS=$(ls -d "$ANDROID_HOME"/build-tools/* | sort -V | tail -n1)
export BUILD_TOOLS
[ -n "$LABEL" ] || LABEL="${GITHUB_REF_NAME//\//-}-${GITHUB_SHA::7}"
suffix=$(awk -v v="$VARIANT" '!/^#/ && $1 == v { print $3 }' tools/release/variants.txt)
[ "$suffix" = - ] && suffix=
apks=(in/*.apk)
[ ${#apks[@]} = 1 ] && [ -f "${apks[0]}" ] || { echo "::error::expected one apk in the build"; exit 1; }
mkdir -p out
out="out/DroidDeck-$LABEL$suffix.apk"
tools/release/sign-apk.sh "${apks[0]}" "$VARIANT" "$out"
( cd out && sha256sum -- *.apk > "SHA256SUMS-$VARIANT.txt" && cat "SHA256SUMS-$VARIANT.txt" )
echo "- \`$(basename "$out")\` - $(cut -c1-16 "out/SHA256SUMS-$VARIANT.txt")…" >> "$GITHUB_STEP_SUMMARY"
- name: Forget the key
if: always()
run: rm -f "$RUNNER_TEMP/release.p12"
- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact }}-${{ matrix.variant }}
path: out/*
if-no-files-found: error
+42
View File
@@ -0,0 +1,42 @@
name: Update app catalog
on:
workflow_call:
workflow_dispatch:
release:
types: [published, edited, deleted, unpublished]
permissions:
contents: read
pull-requests: read
concurrency:
group: update-app-catalog
cancel-in-progress: false
queue: max
jobs:
catalog:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- uses: actions/create-github-app-token@v2
id: ci-token
with:
app-id: ${{ vars.DROIDDECK_CI_APP_ID }}
private-key: ${{ secrets.DROIDDECK_CI_APP_KEY }}
owner: Droid-Deck
repositories: DroidDeck-CI
- name: Regenerate catalog.json
env:
SOURCE_GITHUB_TOKEN: ${{ github.token }}
CI_GITHUB_TOKEN: ${{ steps.ci-token.outputs.token }}
run: |
set -euo pipefail
python3 tools/release/update_catalog.py
+28 -2
View File
@@ -7,6 +7,32 @@ build/
captures/
.externalNativeBuild/
.cxx/
# Built in CI from tools/linuxfs and app/src/main/cpp/fakeinput.cpp, never committed, so the two
# copies cannot drift.
.kotlin/
app/src/main/assets/linuxfs/
app/src/main/jniLibs/arm64-v8a/libproot.so
app/src/main/jniLibs/arm64-v8a/.proot-inputs
app/src/main/jniLibs/arm64-v8a/.proot-outputs
app/src/main/jniLibs/arm64-v8a/libproot-loader.so
app/src/main/jniLibs/arm64-v8a/libsyncthingnative.so
app/src/main/jniLibs/arm64-v8a/.syncthing-inputs
app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so
app/src/main/rust/stores/target/
app/src/main/assets/droiddeck-esync/
.kotlin/
*.jks
*.keystore
*.p12
*.pfx
*.pk8
*.pem
!keystore/testkey.p12
droiddeck-release-key/
password.txt
__pycache__/
.signing.env
app/src/main/assets/directaudio/
app/src/main/assets/hybris/
/tools/vr/out/
+6
View File
@@ -0,0 +1,6 @@
[submodule "external/MaliBridge"]
path = external/MaliBridge
url = https://github.com/ASEbrahim/MaliBridge.git
[submodule "tools/syncthing/syncthing-android"]
path = tools/syncthing/syncthing-android
url = https://github.com/researchxxl/syncthing-android
+4
View File
@@ -0,0 +1,4 @@
# Copy to .signing.env, fill in, chmod 600 .signing.env
RELEASE_KEYSTORE=/path/to/release.p12
RELEASE_STORE_PASSWORD=change-me
RELEASE_KEY_ALIAS=droiddeck
+3
View File
@@ -0,0 +1,3 @@
# Rules
- Comments should be rare, factual, and explain only non-obvious external constraints or business invariants.
+3 -3
View File
@@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Pluvia is an unofficial Steam client app for Android
Copyright (C) 2024 Oxters Wyzgowski
DroidDeck brings the SteamOS experience to Android
Copyright (C) 2026 DroidDeck contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Pluvia Copyright (C) 2024 Oxters Wyzgowski
DroidDeck Copyright (C) 2026 DroidDeck contributors
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
-155
View File
@@ -1,155 +0,0 @@
# SteamDeck — Progress Log
Running engineering log for the SteamDeck app (`com.steamdeck.launcher`). Newest state first, then
the timeline, then lessons and backlog. Companion to the README (what the app *does*) and to
`docs/releases/` (what each version said) — this is *how it got here and where it stands*.
---
## 2026-09-22 (late) — motion front end + run-as-a-game, branch `feat/frontend-motion` (unmerged)
- Front end rebuilt around a motion system (`ui/FrontEndScreen.kt`, same state/actions API): the rail's selection is one pill that springs between rows; sub-lists unfold and stagger their children; a page change sinks out and cascades in over a blurred wash of the selection's art; tiles lift/ring/shine on focus and pop a play badge; the launch button sweeps a sheen and squashes on press; the session activity rises over the front end (`res/anim/session_*`). Durations follow the system animator scale.
- The session now runs as a game: manifest `appCategory=game` + `game_mode_config` (Performance mode welcome; the OS FPS cap and downscaling refused), sustained performance mode, the panel's fastest mode at its size, GameManager game state, and an ADPF hint session over the compositor thread fed with each presented frame's interval (`session/PerfMode.kt`, `session/PerfHints.kt`, `nativeCompositorTid`). One `perf:` line in the session log says what took on the device.
- App libraries link 16 KB-aligned. Still 4 KB-only (prebuilt, need a rebuild): libpulse, libpulseaudio, libpulsecommon-13.0, libpulsecore-13.0, libsndfile, libltdl.
- The session's display caps at 720p by default now, client and desktop alike (`SessionPrefs.defaultResolutionCap`); a cap the user chose still wins. The cog's list marks the default per mode.
- Builds: r1 `7ddfb57` (motion only), r2 `546c063` (+ run-as-a-game), r3 `d1ced87` (+ client 720p), r4 `fb2a3d4` (+ desktop 720p) — all CI-green, none device-tested. Staged as `SteamDeck-motion-r1..r4.apk`; r4 has everything.
- r5 `1ba803b` + TZ in the guest; r6 `39272cf` Setup folds, "Linux desktop environment", help link above the grid (r4 seen running on the FIT); r7 `af3f377` settings without a pop-up: the cog and Performance are pages in the pane with anchored menus under each value, frame generation / logs / offline open in place on the rail. **r7 merged fast-forward to `main` (`af3f377`) and main's auto-build staged as `SteamDeck-r52.apk`; no tag, no release.**
- r8 `90ff23a` (branch only, unmerged): the session drawer in the front end's dress with the same rows and menus (Now / Next session / leave), FEXCore presets carried over from Bannerlator (`core/FexPreset`, Steam settings page + drawer, FEX_* into the Steam session's environment; default = FEX defaults as before), file manager and picker locked to landscape.
- Collaborators: `maxjivi05` (push) and `xXJSONDeruloXx` (push, invited 2026-09-22) on the private repo.
## 🔖 Checkpoint 2026-09-22 22:xx — known-good point for the front end
- **`main` @ `36cde7d`**, APK `SteamDeck-r51.apk` staged (sha `adb5acf5…`, run 35761437514) =
frontend-r6 + docs. Same versionCode 5 / 0.1.4 inside; **0.1.5 not cut yet**.
- **Proven on the Pocket FIT this evening:** the front end renders and navigates (rail, art,
square emulator icons, focus outline); RPCS3 lists Tomb Raider (ISO in a subfolder) and God of
War II HD (its HDD); ▶ God of War II HD boots the game under gamescope at ~40 fps
(`session-20260922-133044`: `run: rpcs3.AppImage --no-gui …NPUA80491/USRDIR/EBOOT.BIN`, 399
frames on screen in 10 s).
- **Not yet proven:** desktop→Steam hand-off after the three fixes (r47–r50); ▶ FlatOut from the
rail (Steam session with rungameid); Running tile; volume keys; automatic SD game storage
(Install drive drop-down); Steam desktop-UI session; Tomb Raider ISO boot.
- **Open decision:** rename the app away from Valve's marks before it spreads (shortlist offered:
Linuxlator / Pocketscope / Portascope); rename = label, icon text, `applicationId` (fresh
install + runtime re-download), `Download/SteamDeck/` log folder, repo, release-tag pattern.
- **Rollback:** `git checkout 36cde7d` (or 0.1.4 tag `8e58e8e` for the last release), reinstall
`SteamDeck-r51.apk` / `SteamDeck-0.1.4.apk` from Downloads.
- Loose ends: `ui/MainScreen.kt` keeps ConfirmDialog/CreditsDialog/EmulatorHelpDialog but its
`MainScreen`/tiles are dead; worktree `~/steamdeck-frontend` still exists (branch merged).
## Current state (2026-09-22, evening)
- **`main` @ the front end merge** (`feat/frontend` fast-forwarded): the launcher main screen -
Steam ▸ games, Desktop ▸ emulators ▸ games, Running tile, focus outline. ✅ First emulator game
proven from the app: God of War II HD in RPCS3 under gamescope at ~40 fps on the Pocket FIT.
- Since 0.1.4 on main, unreleased: volume keys to Android; ▶ emulators under gamescope; ROMs chip
+ ? explainer; sysmem copy + ENOSYS hint; automatic game storage (a card = a Steam library);
the client's games on the desktop with a `steam` shim; desktop→Steam hand-off (three causes
fixed from FIT logs: Surface detach race, the replaced session's exit ending the new one,
shared log folder); the front end. Next release = 0.1.5 once the hand-off is seen working.
## State at 0.1.4 (2026-09-22)
- **Latest release: 0.1.4** — private: https://github.com/The412Banner/SteamDeck/releases/tag/0.1.4
· public: https://github.com/The412Banner/winlator-contents/releases/tag/SteamDeck-0.1.4
`main` @ `8e58e8e`, CI run 35682844106, versionCode 5, APK sha256 `4b53ce6a…` (20,957,823 B).
- **Runtime:** `linuxfs-r9` on winlator-contents (790 MB), the only runtime release left; desktop
packages from `steamdeck-desktop-r1`. The app rewrites its own scripts (`bannerlator-*`), the
driver, `bannerlator-netmanager` and the DirectAudio pieces into the installed runtime at every
launch, so a fix in those reaches an installed runtime without a re-download.
- **Shipped feature set:** the native ARM64 Steam client under gamescope on the in-app Wayland
compositor; a LXQt-on-labwc desktop with Firefox and a shelf of emulators; Proton tools
(GE / CachyOS) as downloads; graphics drivers importable per mode; DirectAudio + microphone;
client/game core masks + four session switches; NetworkManager stand-in (Max's); overlay
restored; ROMs folder + Storage bound into the session's home; Bannerlator's File Manager;
a cog per launch mode (resolution, shape, HDR10, drivers, touch, OSC/audio, renderer); per-session
log folders that survive a crash; leftover-process sweep; frame generation (Win-FG, LSFG).
- **Verification level:** every release is CI-green and staged to the maintainer's device. Proven
on hardware (Pocket FIT / Fold): sign-in, store, install + launch (FlatOut 144 Hz), frame
generation, controllers + OSC, sound, desktop + Firefox, folding mid-session, driver import, the
log folder, the ANR fix, the network page. **Not yet proven:** everything 0.1.4 added (ROMs
folder inside an emulator, File Manager, resolution cap, HDR10, crash sweep, orphan sweep, the
drawer's Steam menu), DirectAudio *voice*, the core masks' effect, offline start, the soft
keyboard, any emulator with a game, Adreno 710.
- **Open field reports (Thor Pro, 8 Gen 2):** trackpad-mode tap does not click (code sends the
click; cause not found by reading); crash after 1–2 min in Ballionaire / Geometry Wars (no log
survived — 0.1.4's sweep will leave `crash.log`); a Fold's 10–20 fps client menus (Chromium →
ANGLE → Zink on an experimental A8xx Turnip); Fold crash loop (xalia ENOSYS storm — switches
shipped, untested); FlatOut shrinking after Resume (gamescope forcing the swapchain extent).
## Release pipeline (how every version is cut)
1. Work on `main`, pushed → `Build APK` runs `assembleRelease` (AOSP test key, v1+v2+v3 re-signed
by CI with zipalign + apksigner) → artifact `steamdeck-apk`. Dev builds keep the last release's
versionCode/versionName; a release bumps both in `app/build.gradle` in its own commit.
2. Verify: `gh run view <id> --json conclusion` (never trust a run listing's first row — a
`workflow_dispatch` run gets cancelled by concurrency in favour of the push-triggered one),
download the artifact, sha256 it.
3. Stage: `cp` to `/sdcard/Download/SteamDeck-rN.apk` (dev) or `SteamDeck-X.Y.Z.apk` (release).
Never `pm install` — the maintainer installs.
4. Private release `X.Y.Z` targeting the **full 40-char sha** (a short sha is refused), notes from
the draft in the session scratchpad, `--latest`. Public release `SteamDeck-X.Y.Z` on
winlator-contents with the README-style notes; one public release per version.
5. README ledger, this log, memory.
## Timeline
- **2026-09-19 — 0.1 groundwork.** Lifted out of Bannerlator's gamescope runtime on the user's
ask: one screen, one button. r2 reached Big Picture sign-in on the Pocket FIT. Package renamed to
`com.steamdeck.launcher` (r4), foreground service, test key, on-screen controls.
- **2026-09-20 — 0.1.** The Linux Steam client and a desktop; runtime r6→r9 in a day (Proton
self-selection, proot shipped, refresh rate, video, first run reaches a game). Public explainer
page for non-Linux users.
- **2026-09-21 — 0.1.1.** Catch-up with Bannerlator: driver selection (two lists, per mode),
overlay restored + GameHub force-stop, DirectAudio + microphone, core masks, four session
switches as toggles, per-session log folders with credentials scrubbed, Adreno 710 path via
Banners-Turnip r3.
- **2026-09-22 — 0.1.2.** The teardown ANR (log collection on the main thread) fixed; two
compositor log lines (frame-size change, window rename); README ledger.
- **2026-09-22 — 0.1.3.** Max's NetworkManager stand-in ported (the client's network page); shim
audit vs WinNative = zero functional drift.
- **2026-09-22 (later) — after 0.1.4, on main.** Volume keys; ▶ emulators under gamescope (the
desktop cannot: labwc on pixman offers no dma-buf); ROMs chip; sysmem warning + ENOSYS hint;
automatic game storage; the client's games on the desktop (`steam` shim hands off to a
gamescope session); the hand-off's three faults found in FIT logs and fixed; the front end
merged (branch `feat/frontend`, r1–r6); God of War II HD booted in RPCS3 from the rail.
- **2026-09-22 — 0.1.4.** ROMs folder + Storage in the session's home; Bannerlator's File Manager
ported whole; a cog per mode (railed settings window) with resolution cap, shape, HDR10 gated on
the panel, drivers, touch, OSC/audio/renderer; two-column main menu; crash-safe log folders +
Session logs toggle; OrphanReaper; drawer Steam menu. Public releases reorganised: one per
version, old `Steamdeck` release and runtimes r1–r8 deleted.
## Architecture (where things live)
- `MainActivity` / `ui/MainScreen.kt` — the main screen; `ui/ModeSettingsDialog.kt` the cogs;
`ui/*Dialog.kt` the rest.
- `SessionActivity` — the Surface, input (touch, touchpad, pads, keyboard), the drawer, the HDR
decision, the compositor start (`wayland/CompositorHost`, `wayland/WaylandCompositor`).
- `session/SessionService` — proot + gamescope/labwc + PulseAudio + DirectAudio relay + network
link; binds (`/root/Storage`, `/root/ROMs`); env for the guest; teardown; `OrphanReaper`;
`SessionArtifacts` + `CrashHandler` + `SessionPaths` for the log folders; `SessionPrefs`.
- `files/` — Bannerlator's File Manager (`FileManagerScreen.kt` and its helpers), the picker
activity and `InAppFilePicker` intent API.
- `gpu/` — Turnip (bionic, for the compositor) and Linux Turnip (glibc, for the runtime) managers.
- `tools/linuxfs/overlay/usr/local/bin/bannerlator-*` — the guest scripts, staged into the
runtime by `SessionFiles` at every launch (CI packages only `bannerlator-*` names).
- `app/src/main/cpp/waylandcomp/` — the compositor (shared lineage with Bannerlator).
## Lessons learned (don't repeat these)
- Nothing in teardown may do bulk filesystem work on the main thread (0.1.1's ANR).
- A CI closure check on a multi-line variable must flatten it first (`libaaudio.so` refusal).
- A runtime script must be named `bannerlator-*` or CI never packages it.
- `gh release create --target` needs the full sha.
- A proot bind is invisible to a program's "Computer" list; put what users need under home.
- The compositor decides its driver and its HDR gate once per app process — anything that changes
them applies after the app is fully closed, and the UI must say so.
- Bannerlator's File Manager ports mechanically (`port_fm.py` anchors) once the container hooks
are cut; do not hand-edit 2,500 lines.
- Deleting a release asset on GitHub can drop a sibling asset — re-list and restore.
## Backlog / next
- Device-prove 0.1.4 on the FIT (list above).
- Thor Pro: trackpad tap; the 1–2 min in-game crash once a `crash.log` arrives.
- Xfce as a second desktop shell (Max's branch runs XFCE 4.20 on labwc) — a catalog package + a
shell choice in the Desktop cog; comfort, not performance.
- Quick Access Menu for non-Deck pads — no confirmed chord; needs research, not a guess.
- FlatOut shrink: try `vk_wsi_force_swapchain_to_current_extent=false` via `steamdeck-env`.
- Max's stricter `winnative-directaudio` guards; `winnative-epic-launch` (a feature).
- Rename before anything truly public: "SteamDeck" is Valve's mark.
+143 -530
View File
@@ -1,548 +1,161 @@
# SteamDeck
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="artwork/droiddeck-banner-dark.svg">
<img alt="DroidDeck" src="artwork/droiddeck-banner-light.svg" width="100%">
</picture>
</p>
Valve's **native aarch64 Linux Steam client**, running on an Android phone or handheld: a glibc
rootfs under proot, gamescope as a Wayland client of an in-app Vulkan compositor, Xwayland and
Zink for the client's CEF, and the device's Turnip driver underneath. There is no Wine here, and
no x86 translation for the client itself — it is ARM code running as ARM code. Windows games it
launches go through Valve's own ARM64 Proton build, which is where FEX comes in.
DroidDeck brings the SteamOS experience to Android: Steam Gaming Mode, full Linux desktop, integration with GOG, Epic Games, Xbox, and other storefronts, Steam VR, and more!
One screen, one button: install the runtime, press Play, Big Picture comes up. Or start the
**desktop** instead — LXQt, a browser and a shelf of emulators, in the same session.
<p align="center"><b>100% open source. No ads. No telemetry. No source withheld.</b></p>
Around that: the Vulkan driver each half of the session draws with is yours to import and change,
audio can bypass PulseAudio and carry a microphone, the cores the client and a game get are
separate, and every session writes a folder of logs meant to be attached to a report as it is.
<p align="center">
<a href="https://github.com/Droid-Deck/DroidDeck/releases/download/0.6.9/DroidDeck-0.6.9.apk"><img src="https://img.shields.io/badge/Download-DroidDeck%200.6.9%20APK-3DDC84?style=for-the-badge&logo=android&logoColor=white" alt="Download DroidDeck 0.6.9"></a>
<a href="https://discord.gg/JRGAvawjsm"><img src="https://img.shields.io/badge/Discord-Join%20us-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join the DroidDeck Discord"></a>
</p>
## Contributors
<p align="center">
<a href="https://www.star-history.com/droid-deck/droiddeck"><img src="https://api.star-history.com/badge?repo=Droid-Deck/DroidDeck&type=rank&theme=dark" alt="Star History Rank"></a>
</p>
Building this alongside the owner. A pull request from either of them builds by itself and gets
a comment saying whether it merges, what conflicts, or what failed to compile.
<p align="center">
<a href="https://trendshift.io/repositories/267852"><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"><img src="https://trendshift.io/api/badge/trendshift/repositories/267852/daily?language=Kotlin" alt="Droid-Deck%2FDroidDeck | Trendshift" width="250" height="55"></a>
</p>
- **MaxsTechReview** — [@maxjivi05](https://github.com/maxjivi05)
- **Kurt Himebauch** — [@xXJSONDeruloXx](https://github.com/xXJSONDeruloXx)
<p align="center"><img src="docs/media/steam-to-desktop.gif" width="100%" alt="On a Retroid Pocket Duo: Play Steam from the DroidDeck launcher, open Pac-Man in Steam Big Picture and play it, exit the game, then Switch to Desktop from the Power menu"></p>
<!-- contributions:start -->
_From the repository's pull requests; rewritten when one is opened, merged or closed._
> DroidDeck has no website. Only download it from this repository's [Releases](https://github.com/Droid-Deck/DroidDeck/releases).
| Contributor | Pull request | Opened | State |
|---|---|---|---|
| [@xXJSONDeruloXx](https://github.com/xXJSONDeruloXx) | [#1](https://github.com/The412Banner/SteamDeck/pull/1) initial decky installer stuff | 2026-09-23 | draft |
<p align="center">
<img src="docs/releases/media/0.6.7/library.gif" width="49%" alt="The Library switching source chips, changing the card size and opening the Filter menu">
<img src="docs/releases/media/0.3.0/in-game.gif" width="49%" alt="A Windows game running through Proton with the on-screen controls">
<img src="docs/releases/media/0.4.0/multi-controller.gif" width="49%" alt="Two controllers playing split screen on one handheld">
<img src="docs/releases/media/0.6.7/ubuntu-desktop-discover.jpg" width="49%" alt="The Kubuntu desktop with Discover open">
</p>
- **@xXJSONDeruloXx**: 1 submitted, 0 merged
<!-- contributions:end -->
## Install
## Requirements
1. Android 9 or newer on Adreno 730 or newer, or any 8xx. Adreno 6xx is experimental. Mali, Xclipse, PowerVR and Adreno 710 are unsupported. No root needed.
2. About 3 GB free for the runtime, plus 1.1 GB for the desktop and emulators.
3. Install the [APK](https://github.com/Droid-Deck/DroidDeck/releases/download/0.6.9/DroidDeck-0.6.9.apk), install the Linux runtime, press **Play** and sign in to Steam.
4. On Android 14 and newer, turn on **Disable child process restrictions** in Developer options. On Android 12 and 13, Steam's first launch shows a **Fix it for me** button that walks you through it.
- An **arm64 Android device with an Adreno GPU that Turnip supports** — in practice Adreno **730 or
newer** (Snapdragon 8 Gen 1 onward) and the **8xx** series. Mali, Xclipse and PowerVR are not
supported here, and neither is the low-end Adreno 710 (Snapdragon 6 Gen 1): Mesa has no entry
for it at all, so the session log ends at `device (chip_id = 7010000, gpu_id = 710) is
unsupported` before anything is drawn.
- **Android 9 or newer**, and roughly **3 GB free** for the runtime before any games. The desktop
and its emulator packages are another ~1.1 GB if you install them.
- **No root required.** Root only helps if you want to copy an existing Bannerlator runtime across
instead of downloading one.
Smart glasses setup is in the [support matrix](docs/smart-glasses-support.md). Release notes are in [docs/releases](docs/releases).
## Installing
Help, Preview builds and device reports are on [Discord](https://discord.gg/JRGAvawjsm). For bugs, post in **#bug-reports** with the zip from **Share logs**.
1. Install the APK from the release page.
2. Launch it and press **Install Linux runtime** — about 790 MB, once.
3. Press **Play**. On the very first run the app fetches Valve's client (a minute or two with
nothing on screen), then Big Picture comes up and you sign in.
## Build
For a desktop, open **Desktop & apps**, install the *Desktop* package, then press **Desktop** on
the main screen.
Needs Docker, Java 17, the Android SDK and NDK, Rust via rustup, GitHub CLI, Python 3 and `zstd`.
**Do this once per device if the client dies with nothing in its log**: Android 12 and later kill
the extra processes an app starts for itself, and a session is made of dozens of them. Some phones
have a **"restrict child processes"** switch in Developer options — turn it off. See
[If Steam dies with nothing in the log](#if-steam-dies-with-nothing-in-the-log).
## What works today
This is version 0.1.2, and most of it has been exercised on one or two devices only. An honest
ledger rather than a feature list — where something has not been run on hardware, it says so:
| | State |
|---|---|
| Steam sign-in, store, install, launch | ✅ proven — *FlatOut* at 144 Hz through ARM64 Proton |
| Importing and choosing a driver | ✅ proven — an imported Mesa 26.3.0 glibc Turnip drew a whole session, and an imported AdrenoTools build ran the compositor |
| Frame generation (LSFG 2×) | ✅ proven — 30 → 61, 60 → 118, 61 → 123 fps |
| Performance HUD | ✅ proven |
| Desktop, panel, file manager | ✅ proven at the panel's native resolution |
| Firefox and the network (IPv4 + IPv6) | ✅ proven — live pages at 93 fps |
| Controller as an Xbox 360 pad | ✅ proven |
| Foldable, opened and closed mid-session | ✅ proven |
| Background / foreground, wake locks | ✅ proven |
| The session log folder | ✅ proven — all of it written, including this app's own log |
| Session folder finished after a crash or a kill | ⚠️ built: crash handler + next-start sweep; not yet forced on a device |
| ROMs folder and Storage in the session's home | ⚠️ built; not yet opened from an emulator on a device |
| The File Manager (Bannerlator's) and its pick mode | ⚠️ built; not yet used on a device |
| The cog beside Play / Desktop: resolution, shape, HDR, drivers, touch, audio, renderer | ⚠️ built; the resolution cap and HDR not yet seen on a device |
| Leftover session processes swept at the next start | ⚠️ built; not yet forced on a device |
| The drawer's "Steam menu" (Guide) | ⚠️ built; not yet tried |
| Emulators launched under gamescope (▶ on the rail) | ✅ proven — God of War II HD in RPCS3, ~40 fps on the FIT |
| The front end (rail, games, focus outline, Running tile) | ⚠️ built and used on the FIT; the Running tile and Steam-game ▶ not yet exercised |
| The device's volume keys during a session | ⚠️ fixed (they were swallowed); not yet re-tried |
| Game storage: a second Steam library on a card or a folder | ⚠️ built; not yet seen installing to it |
| The client's games in the desktop menu, launching Steam's desktop UI under gamescope | ⚠️ built; not yet tried |
| Stopping a competing Steam client | ⚠️ it is asked to stop and says so, but one that restarts itself from boot wins the race — uninstall it |
| DirectAudio for games, and the microphone | ⚠️ wired and the modules load; voice not yet confirmed in Steam's tester |
| Client and game core masks | ⚠️ applied and logged; no measured difference yet |
| The four session fixes (xalia, proot seccomp, sysmem, Zink) | ⚠️ they set what they say; each is a hypothesis for a device that misbehaves |
| GE-Proton / proton-cachyos | ⚠️ adopted and registered on device; no game launched through one yet |
| The emulators | ⚠️ they build, publish and install — none has been run with a game |
| Starting Steam offline | ⚠️ built, not yet tested |
| The soft keyboard | ⚠️ built, not yet tested |
| Adreno 710 / 720 / 722 | ⚠️ a path exists through imported drivers; **nobody has run this on one yet** |
## Known limits
- **The desktop composites in software.** wlroots allocates through gbm on a real DRM render node,
and the Adreno stand-in is not one, so labwc falls back to pixman. Programs on the desktop still
reach the GPU themselves through Vulkan and Turnip — it is the desktop's own compositing that
runs on the CPU.
- **Firefox's child-process sandboxes are off.** Seccomp and namespace sandboxes cannot be set up
under proot, and its tabs crash otherwise. This is a genuine reduction in isolation: treat
browsing here as less protected than on an ordinary desktop.
- **The display shape changes at the next session**, not immediately — gamescope fixes its display
size when it starts.
- **No Switch emulator.** Eden's repository was taken down in February; there is nothing to mirror
or to build from.
- PS3 (RPCS3) and Wii U (Cemu) are demanding even on desktop hardware — expect light titles only.
## How it fits together
```
Android app (this repo)
├── libbannerwayland.so Wayland compositor, presents into the activity's Surface
│ on a bionic Turnip: bundled, or one you imported
├── PulseAudio 13 + AAudio the guest's audio server, on a socket bound into the session
├── directaudio relay owns AAudio out and the mic for the games that ask for it
├── fake-evdev rings a physical pad, republished as /dev/input/eventN for the client
├── frame generation Win-FG or LSFG, on gamescope's finished output
└── proot ── linuxfs (~790 MB, downloaded once)
├── gamescope ── Xwayland ── Zink
│ drawing on a glibc Turnip: the runtime's, or one you imported
└── Steam (steamrtarm64, fetched from Valve on first run) ── Proton ARM64 ── FEX
└── a game's audio: Proton's own, or DirectAudio straight to Android
```bash
tools/build_local.sh
```
The two Turnips are separate copies of the same driver and cannot be swapped: the compositor's is
built against Android's C library, the runtime's against glibc. That is why there are two import
lists rather than one.
The APK lands in `app/build/outputs/apk/release/`. Install it with `tools/deploy_local.sh`. After the first run, `./gradlew :app:assembleRelease` reuses the prepared inputs. Unit tests run with `./gradlew -PskipRust=true :app:testDebugUnitTest`, and the helper and preload suite with `bash tools/test_local.sh`.
The runtime image is the one published for Bannerlator (`linuxfs.json` in
`The412Banner/winlator-contents`); this app installs and updates it from that catalog. **No Valve
software is redistributed** — the client is fetched from Valve's own manifest on the device at
first use.
## Building
Nothing is built locally. Push, and the `Build APK` workflow produces `steamdeck-apk`, a
debug-signed arm64 apk. It cross-compiles the two glibc preloads the session needs
(`libfakeinput.so`, `libblsession.so`) from the sources in this repo before the app build, so
those can never drift from what ships.
## If Bannerlator is already installed
The runtime image is the same one Bannerlator installs, so on a device that already has it the
790 MB download can be skipped entirely — copy it across with root, once, before the first launch:
```sh
su -c 'cp -a /data/data/com.tencent.ig/files/linuxfs /data/data/com.steamdeck.launcher/files/ \
&& chown -R $(stat -c %u /data/data/com.steamdeck.launcher/files) \
/data/data/com.steamdeck.launcher/files/linuxfs \
&& chcon -R u:object_r:app_data_file:s0 /data/data/com.steamdeck.launcher/files/linuxfs'
```
The two runtimes are independent after that: each app has its own Steam install, its own login and
its own games.
## Controls
A physical controller is republished into the session as a synthetic Xbox 360 pad, which is the
identity SDL and Steam have a mapping for. With nothing attached, an on-screen pad appears —
d-pad, A/B/X/Y, shoulders, select/start and the Steam button — writing into the same rings, so the
client sees one pad either way. Connect a controller and the on-screen one disappears; unplug it
and it comes back. Anywhere the controls are not is a touchpad: touch moves the pointer and a tap
clicks, which is how the client's own on-screen keyboard is used to sign in.
## Desktop
Besides the Steam client the app can start a **desktop**: LXQt on labwc, in the same session and
the same compositor, with Xwayland for the X11 programs. It is a second choice on the main
screen, not a replacement — the Steam button still hands the session to gamescope.
What it is made of is downloaded on request from a catalog hosted beside the runtime, so the app
stays small and nothing ships that a given device will not use:
| Package | What it brings |
|---|---|
| **Desktop** (406 MB) | labwc, LXQt's panel, file manager, terminal, text and image viewers, Firefox, mpv |
| **Emulators** (694 MB) | PPSSPP and RetroArch with 28 cores — PS1, PS2, N64, SNES, NES, Game Boy, GBA, DS, Genesis, Dreamcast, Saturn, PC Engine, arcade — plus ScummVM, DOSBox, Mednafen, Snes9x |
| **RPCS3 · DuckStation · melonDS** | PS3, PS1 and DS, mirrored from the projects' own ARM64 builds |
| **PCSX2 · Dolphin · Cemu** | PS2, GameCube/Wii and Wii U. No ARM64 Linux build exists upstream for any of the three, so they are compiled from source against the same glibc the runtime uses |
The desktop composites in software: wlroots allocates through gbm on a real DRM render node, and
the Adreno stand-in is not one, so labwc runs on pixman. The programs on it still reach the GPU
themselves through Vulkan and Turnip — it is the desktop's own compositing that is on the CPU.
`Download/steamdeck-wlr-renderer` (`pixman`, `gles2` or `vulkan`) overrides the choice.
Programs whose child processes sandbox themselves with seccomp and namespaces cannot set those up
under proot; Firefox's are turned off in the session's environment, without which its tabs crash.
### The front end
The main screen is a launcher: a rail with **Steam** and its installed games (from the client's
own manifests, with the library-cache art), **Desktop** and the installed emulators (their own
icons) and each emulator's games from the ROMs folder (a system folder such as `ps3/` as a hint,
by extension, one folder deep - a dump comes as a folder named for the game) plus what RPCS3 has
installed on its own HDD; then Files, Desktop & apps, Compatibility tools, Frame generation,
Performance, ROMs, Session logs, Start offline, the runtime and credits. The chosen thing sits on
the right with its ▶. A Steam game launches its session with `steam://rungameid/…` directly; a
ROM launches its emulator under gamescope with the game on the command line (RPCS3 `--no-gui`,
Dolphin `-e`, Cemu `-g`, the rest plain). A session alive in the background is the first thing on
the rail; tapping it goes back to it. The controller's focus draws a thin outline. ✅ God of War
II HD booted in RPCS3 from the rail at ~40 fps on the Pocket FIT.
### Game storage: internal, an SD card, or a folder
The client's own library is internal storage and always there. An **SD card in the phone is
registered as a second library by itself** (through the app's own folder on it, the one place a
card lets this app write), so the choice is made where it is anywhere else - inside Steam, on
every Install. **Game storage** in the Steam cog is only for turning that off or pointing it at a
folder chosen in the File Manager instead. The session binds it at
`/mnt/bannerlator-sd` and `bannerlator-steam-library` registers it with the client under the name
the app gives it; the client then asks where to install every game, lists both on its Storage
page and moves games between them itself. Set to internal only, the entry is removed again. A
card and shared storage are FUSE-backed and stream slowly - keep games that stream video or big
assets internal. ⚠️ Built; not yet seen installing to a card.
### The client's games on the desktop
The Steam client writes a menu entry for every game it installs (Games category, the game's
icon), and the home folder has **Steam Games** and **SD Games** links to their files. Those
entries run `steam steam://rungameid/…`; on this desktop `steam` is a shim, because the client
cannot run where nothing can draw with Vulkan. It - and the menu's **Steam** / **Steam Big
Picture** entries - hand off to the app (`bannerlator-steam-launch`), which ends the desktop
session and starts a Steam session in its place: the client's **desktop UI** under gamescope, and the game straight away when one was
chosen. Back opens the drawer as in any session; Stop session returns to the app. The list is
rebuilt at every desktop start. ⚠️ Built; not yet tried on a device.
### Running an emulator with the GPU
The desktop composites in software (labwc on pixman) and so offers its programs no dma-buf: a
Vulkan program on it has no swapchain to draw into and dies at its first frame — RPCS3 with
`VK_ERROR_SURFACE_LOST`. The GPU path that works on this hardware is gamescope's, the one Steam's
games render through. So an emulator is started **as a session of its own**: ▶ beside an installed
emulator under **Desktop & apps** runs it fullscreen under gamescope (the session script's `run`
mode, an AppImage or a binary inside the runtime), with Steam's display, driver and HDR settings.
The desktop remains for files, Firefox and everything that draws in software. ⚠️ Built; not yet
seen with a game.
### The device's files, and the ROMs folder
Inside a session the phone's storage is at `/storage/emulated/0`, as it is outside - but no
program's file dialog will show it: they list "Computer" from `/proc/mounts`, where a proot bind
never appears, and open in the home folder, where nothing pointed at the phone. So the home
folder now carries two entries every session:
- **`Storage`** - all of internal storage.
- **`ROMs`** - the folder chosen with **ROMs folder** on the main screen, wherever it is on the
device, an SD card included (it is a bind, not a link). Point any emulator's game directory at
`/root/ROMs` once and it is the same folder in every session after.
Both are the live folders, not copies: a save an emulator writes next to its ROM lands on the
phone.
### Files
**Files** on the main screen is Bannerlator's File Manager, carried over whole: grid and list,
sort, search, multi-select, copy / cut / paste with conflict handling and a cancellable progress
bar, rename, delete, new folder, properties (read-only and hidden), favourites, and a locations
rail with every mounted card, Downloads, the ROMs folder and the session logs. Its pick mode is
what chooses the ROMs folder and imports a driver. What is not here is what has no meaning without
a Wine container: running a file, adding it to shortcuts, the C: / Z: drives and the archive
unpacker.
## Pointer
The app draws the arrow. On the desktop, touch is a **touchpad** (Bannerlator's desktop-container
feel): drag anywhere and the pointer moves from where it is; tap to click; a second finger tapping
while one holds, or a two-finger tap, right-clicks; two fingers scroll; hold still then drag, or
double-tap-and-hold, drags. In Steam, touch is **direct**: the pointer jumps under the finger. The
drawer's *Touch* switch pins either. A USB or Bluetooth mouse works as itself.
## Networking
The desktop's programs use the phone's network directly — proot makes no network namespace, so
IPv4 and IPv6 both pass through — and the app writes the runtime's `/etc/resolv.conf` from the
active network's own DNS servers whenever the link changes (IPv4 first, never a link-local IPv6 one,
public servers as fallback), which is what Firefox and everything else in the desktop resolve with.
## Keyboard
**Keyboard** in the drawer opens the soft keyboard over the session. Steam's UI and the games
under it are X11 clients of gamescope and know nothing of Wayland text input, so what the
keyboard produces is turned into the key presses that would have typed it — Shift included, so
capitals and the symbol row (`@`, `!`, `_` …) arrive as themselves — and fed to the compositor as
key events. A hardware keyboard works as it is.
## Starting Steam offline
The client runs without a connection — installed games launch, Proton and the frame generation
are local — provided it has credentials from a sign-in that succeeded earlier. **Start offline**
on the main screen is where that is decided, and not in the drawer, because the client reads the
two keys that control it once, while it starts: changed during a session it would write the file
and change nothing until the next launch. Without an earlier sign-in the button says so and does
nothing. The client rewrites those keys when it exits, so the app writes them again at every
session start. Games with always-online DRM will not start offline, and nothing can be installed
or updated.
## Audio
The Steam client always plays through **PulseAudio** — it is a native Linux program with no other
way to make a sound, and its menus, music and voice chat all go through it. **Audio** on the main
screen adds two things on top, each applying at the next session start:
- **DirectAudio for games** — replaces Wine's audio driver *inside the games the client launches*
with Bannerlator's DirectAudio, which talks straight to Android over a socket instead of through
PulseAudio: lower latency, and no resampling in between. The client is untouched. It only pairs
with a Wine 11 Proton (Valve's ARM64 builds, GE-Proton, proton-cachyos); on anything else the
wrapper leaves it off and says so in the log, because a mismatch would be silence rather than an
error. A game's first launch after turning it on still uses Proton's own audio — the driver is
named in the game's prefix, which does not exist until that first launch — and the second uses
DirectAudio.
- **Microphone** — the relay helper opens Android's input stream under the app's own uid (which is
what the recording permission is checked against) and PulseAudio exposes it to the client as a
source named **DirectAudioMic**, so Steam voice chat works. Asks for the recording permission when
turned on; off, nothing in the session can record and Android's indicator stays off.
The helper is a small Android program the app runs beside the session; the driver is a glibc build
staged into the runtime at every launch like the session scripts, so a fix reaches an installed
runtime without re-hosting it. Proven in Bannerlator's Linux session with voice in Steam's own
tester; ⚠️ not yet exercised in this app.
## Cores
**Performance** on the main screen carries the two core masks Bannerlator's Linux session has, kept
separate because they are wanted at the same time and suit different things:
- **Steam client cores** — Steam pins its own interface renderer to a subset of cores it chooses (on
one device 5 of 8, leaving out both little cores and the fastest), which is reasonable while a
game runs and makes Big Picture sluggish when the client is all there is. Turning the override
on pins the client, its UI helper and gamescope to the cores you tick, re-applied every few
seconds because the UI keeps spawning helpers that inherit Steam's choice. Every core ticked is
the usual fix — and, unlike the game mask, it *is* sent when it names every core, since undoing
Steam's pin is the whole point.
- **Game cores** — applied by the Proton wrapper, which execs the game through `taskset` so every
thread inherits the mask from its first instruction. Leaving every core ticked sends nothing;
untick the small cores to keep a heavy game off them.
Both apply at the next session start.
Under the same dialog, **session fixes** for a device the runtime does not sit well on, tried one at
a time. **Skip Steam's xalia helper** (`PROTON_USE_XALIA=0`) is the one to reach for when a session
dies seconds after Big Picture appears: xalia is an x86 Windows program Proton starts for gamepad
navigation, it cannot load the session's preload shim under FEX, and where the vendor's seccomp
answers its `socket()` and `memfd` calls with ENOSYS it storms until the session collapses (89
refusals then a broken pipe, in the log). It costs gamepad navigation in Windows programs that are
not games. Then two for a runtime that renders slowly: **Turnip: sysmem rendering** (`TU_DEBUG=sysmem`, Banners-Turnip's advice for an Adreno 8xx that
looks glitchy or slow; a 710/720/722 gets it on its own) and **Zink: lazy descriptors**
(`ZINK_DESCRIPTORS=lazy`, for Steam's menus, which Chromium draws through Zink — the first thing to
try when they are slow while games are fast). The `steamdeck-tu-debug` and `steamdeck-env` files in
Downloads still win over both.
## Networking and the client's own network page
The session uses the phone's connection directly - proot makes no network namespace, so IPv4 and
IPv6 both pass through - and the app writes the runtime's `/etc/resolv.conf` from the active
network's own DNS servers whenever the link changes.
The client's **Settings → Internet** page and its connection indicator are a different matter:
they are built on NetworkManager, which the client looks for on the system bus as it starts. Without
one, that page stopped with *"StartScanningForNetworks is not a function"* under a header showing no
connection even while downloads ran. The session now starts a small system bus and a **stand-in that
answers for NetworkManager**, reporting what is true and nothing more - one connection that is up
while Android has a route out - and refusing what it cannot do, since nothing in this runtime can
scan for or join a network. The session log says `network: system bus up, NetworkManager stand-in
running` when it is working.
That stand-in is **maxjivi05's**, from WinNative, by way of Bannerlator.
## Compatibility tools
**Compatibility tools** (main screen and drawer) installs GE-Proton or proton-cachyos — native
ARM64 builds — beside the ARM64 Proton Valve ships. The runtime's own registrar downloads and
installs a requested build when the next session starts (several hundred MB), patches it past
pressure-vessel like Valve's, and it then appears in Steam under Properties → Compatibility for
any game. The dialog queues or cancels a request and removes an installed build.
## The cog beside Play and Desktop
Each launch button has a cog, and everything that only matters for that one mode lives behind it,
so the main screen keeps only what applies to both: **resolution** (up to 720p by default; 900p, 1080p, or the
panel's own), the display **shape**, **HDR10 output**, the
**Linux runtime driver** for that mode and the shared **display driver**, **touch**, and for Steam
the on-screen controls and audio, for the desktop its renderer.
**HDR10** is a switch that is greyed out, with the reason, on a panel that does not list HDR10 —
the compositor's HDR gate is decided from the display's own word (`android.view.Display`), once,
when the compositor starts. On, the compositor offers `wp_color_manager_v1` with BT.2020 + PQ and
10-bit buffers, gamescope is started with `--hdr-enabled` and speaks the same protocol to it, and
games get `DXVK_HDR=1`; a game that renders HDR then reaches the panel as HDR on its own display
layer, tagged BT2020_PQ. The compositor lives for the whole app process, so a change applies after
the app is fully closed and opened again. ⚠️ Wired end to end; not yet seen on a device from this
app (Bannerlator proved the same compositor path on a Fold).
## Graphics drivers
Two drivers, two lists, because a zip for one cannot serve the other — the same split Bannerlator's
Contents screen makes. Both are chosen in the cog beside Play / Desktop:
- **Linux runtime driver** — the glibc Turnip everything *inside* the runtime renders on: the Steam
client's UI, every game it launches, and in desktop mode every program on the desktop. Import a
"-Linux" Turnip zip from Banners-Turnip; a plain Android or "-Wayland" Turnip is refused with the
reason, since a Linux process cannot load a bionic library. The choice is per mode, so Steam and
the desktop can run different builds, and it takes effect at the next session start. Nothing in
the runtime is modified: the session is handed the imported driver's ICD manifest and points the
Vulkan loader at it, and the runtime's own Turnip is what a removed or unreadable import falls
back to. This is how a driver fix reaches an installed runtime without a ~790 MB re-download.
- **Display driver (Android)** — the bionic Turnip the app's compositor puts the frame on the panel
with, the last step of every session. *Auto* picks one of the two bundled builds by GPU; an
imported AdrenoTools zip can be chosen instead. The compositor loads its driver once per app
process, so this one applies after the app is fully closed and started again.
Both imports open the app's own file picker rather than Android's document picker: an SD card is a
card in its drive menu, the folder last picked from is where it opens next time, and it hands back
a plain path.
## Frame generation
Extra frames are generated between the real ones on the way to the screen — inside the app's
compositor, on gamescope's final output — so it works for any game with nothing changed in the
runtime. **Win-FG** is built in. **LSFG** uses the shader chain from your own copy of Lossless
Scaling: install it from the Steam client in this app and the app reads its `Lossless.dll` from
the runtime's Steam library (parsed as data, never executed; nothing of it is redistributed).
Pick an engine and 2×/3×/4× on the main screen; it applies immediately, mid-game included.
## Background and foreground
A session belongs to a foreground service, not to the activity, so leaving Big Picture does not
end it: the process stays at perceptible priority, a partial wake lock keeps the CPU from dropping
the guest's threads, and a high-performance WiFi lock keeps a backgrounded download from being
throttled to nothing. **Back opens the drawer**: the performance HUD switch, frame generation, on-screen controls
(auto / always / never), the display shape (the panel's own, or a fixed 16:9 — the default on a
foldable, so opening or closing it mid-session only changes the bars, never the picture; takes
effect on the next session), *Send to background* and *Stop session*. Backgrounded, Steam keeps
running and the (silent) notification brings it back. A session ends only when you say so — the
drawer or the notification's **Stop session** — or when the app is swiped out of recents.
## Device switches
Files in `/sdcard/Download`, for a device that cannot be reached with a debugger:
| File | Effect |
|---|---|
| `steamdeck-no-pad` | Turns the controller feature off entirely (a true baseline) |
| `steamdeck-pad-log` | Traces every interposer call into the session log |
| `steamdeck-driver` | `a7xx`, `a8xx` or `system` — overrides the Turnip build the compositor loads |
| `steamdeck-no-hud` | Hides the top-right performance line (fps, and `base → generated` while frame generation runs) |
| `steamdeck-osc` | `always` or `never` — pins the on-screen controls instead of following what is attached |
| `steamdeck-env` | `KEY=VALUE` lines added to the session's environment as written, after the app's own — Zink and Turnip tunables (`ZINK_DESCRIPTORS=lazy`, `MESA_*`), gamescope's, the client's; `#` comments allowed |
| `steamdeck-tu-debug` | Turnip's `TU_DEBUG` for the runtime's driver, verbatim (`sysmem`, `sysmem,deck_emu`). Without it, an imported A710/A720/A722 driver gets `sysmem` on its own |
## Session logs
Every session writes a folder under `/sdcard/Download/SteamDeck/`, and it is the first thing to
look at when something does not start:
```
Download/SteamDeck/session-20260921-161256/
device.txt what this device is, and every setting the session ran with
session.log the guest session: proot, gamescope, the client's own output
wayland.log the app's compositor - what it presented, at what size, and how fast
app.log what the app itself decided and reported
crash.log Android's crash buffer, as it stood when the session ended
crash-app.txt the stack trace, if the app itself crashed
audio.log the PulseAudio daemon and the DirectAudio relay helper
network.txt the link, the DNS the runtime was given, and what it means
steam.log the Steam client's log, scrubbed (Steam mode)
steam/ the rest of the client's logs, scrubbed (Steam mode)
desktop.log labwc, the panel and the programs on it (desktop mode)
```
`app.log` is this app's own logcat, filtered to its process: which driver it chose and why, the
audio line, a rival Steam client being stopped, a helper that was missing, the session's exit
status. `crash.log` is Android's crash buffer at teardown, which is where a session the *system*
killed leaves its only trace - proot once died before `main` over a missing library and said so
there and nowhere else. `audio.log` is the PulseAudio daemon's own output plus the relay helper's,
so a module that refuses to load is visible instead of being a silent absence of sound.
`network.txt` records the transport, whether the link validated, the address families and the DNS
servers the runtime was actually given - **never the network's name**.
A folder is finished **however the session ends**, not only at a clean stop. Everything written
*during* a session is on disk as it happens; only the ending - the compositor's log, Steam's
scrubbed logs, the crash buffer - is gathered afterwards, and three things gather it: the ordinary
stop; the app's own crash handler, which writes `crash-app.txt` with the stack trace and finishes
the folder before the process dies; and a sweep at the next app start, for a folder whose process
was killed outright (Android's phantom-process killer, a native crash in the compositor, a dead
battery) - that folder gets `ended-without-teardown.txt` saying so, and `crash.log` as the buffer
stands then, which still holds the entry unless the device rebooted. A finished folder carries a
`.complete` marker; one without it is what the sweep looks for.
**Session logs** on the main screen turns the folder off. The session still writes its logs - the
scripts and the compositor need somewhere to - but into the app's cache, and they are deleted
when the session ends, so nothing accumulates in Downloads.
A session's process tree is also swept at the next start: proot, gamescope and Steam outlive an
app that was killed or crashed, holding the rootfs, the GPU and Steam's lock, and the next session
then never came up until the app was force-stopped (a Thor Pro report). Anything under the app's
uid other than the app itself is a leftover at that point, and `app.log` lists what was killed.
`device.txt` is written before the session starts, so a session that dies in its first second
still says what it ran on: model, SoC, Android and kernel, cores and their ceilings, RAM, the GPU
as KGSL names it, the panel, **which driver each half of the session chose**, the runtime version,
and every switch that was in effect. **Nothing identifying is collected** - no serial number, no
device or advertising id, no account name, no network names - and the Steam logs are scrubbed line
by line on the way in: session tokens, machine-auth GUIDs, WebAPI keys, Guard codes and e-mail
addresses are replaced, and a SteamID is masked to its last four digits so lines can still be
correlated. `loginusers.vdf`, `config.vdf` and the `ssfn` files are never copied at all. A session
folder is meant to be attachable to a bug report exactly as it is.
For more from the desktop than it normally says, create `~/.steamdeck-desktop-debug` inside the
runtime: labwc then logs at debug level, each program's window-protocol traffic is recorded, and the
session does a name lookup plus an IPv4 and an IPv6 fetch of its own — which is how to tell a broken
network apart from a program that only believes it has one.
### If Steam dies with nothing in the log
That is usually not Steam. **Android 12 and later kill the extra processes an app starts for
itself** once there are more than a few, and a session is made of dozens - proot, gamescope,
Xwayland, the client and its helpers, Wine, FEX. Where that is left on, the OS ends the session and
no log of ours says why, because nothing in the session did it. Some phones expose it in Developer
options as a **"restrict child processes"** switch; turn it off. Otherwise, once per device:
```sh
adb shell settings put global settings_enable_monitor_phantom_procs false
```
`device.txt` records what this phone reports, and the Performance dialog says so when it is not
disabled.
## Authors
- **The412Banner** — the app, the Wayland compositor, the runtime plumbing and the Steam session.
- **maxjivi05 (Max)** — the gamescope runtime this is built on: the proot session, the session
shim, the fake-evdev interposer and the controller work, from WinNative.
## Lineage and licence
GPL-3.0. The gamescope runtime, the session shim and the fake-evdev interposer come from
maxjivi05's WinNative work and from Bannerlator, both GPL-3.0; the Wayland compositor, the frame
generation engines and the runtime plumbing are Bannerlator's. Underneath all of it is Winlator
(brunodev85, GPL-3.0): the PulseAudio-on-AAudio audio stack, the gamepad state model and the shape
of a session's host-side components are his, and WinNative and Bannerlator are both Winlator
lineage. Valve, Steam and Proton are Valve Corporation's; this project
is not affiliated with Valve.
On Windows, see `tools/run_windows.ps1` and `tools/prepare_windows_runtime.py`.
## Limits
Compatibility and performance vary by device. Desktop compositing is software rendered, and Firefox sandboxing is reduced under proot.
## Credits and licence
DroidDeck is [GPL-3.0](LICENSE). Steam and Proton belong to Valve Corporation. VITURE, RayNeo, XREAL, Rokid, REDMAGIC, Nintendo Switch and Joy-Con are trademarks of their owners. This project is not affiliated with any of them.
<details>
<summary><b>Runtime, Wine and Proton</b></summary>
- Runtime, shim, input and controller work build on WinNative and Bannerlator by maxjivi05.
- mmap ring buffer controller input by ribbit384 ([WinNative #345](https://github.com/WinNative-Emu/WinNative/pull/345)).
- [FEX](https://github.com/FEX-Emu/FEX) (MIT) runs x86 code. DroidDeck's build puts the platform name under `AT_PLATFORM` so [sharun](https://github.com/VHSgunzo/sharun) AppImages by VHSgunzo (MIT) start through its [userland-execve](https://crates.io/crates/userland-execve) loader (MIT). FEX's code disk cache was checked in its source.
- x86 AppImages, and arm64 ones whose runtime can't unpack on Android, are unpacked with [uruntime](https://github.com/VHSgunzo/uruntime) by VHSgunzo (MIT), shipped unmodified with its licence.
- x86 programs linking OpenSSL 3 use the copy in the [Steam Linux Runtime 4.0](https://gitlab.steamos.cloud/steamrt/steamrt). x86 Linux games get the `/etc/asound.conf` that [pressure-vessel](https://gitlab.steamos.cloud/steamrt/steam-runtime-tools) writes.
- `WriteProcessMemory` hooks fall back to `/proc/<pid>/mem` the way [Wine](https://gitlab.winehq.org/wine/wine)'s `server/ptrace.c` did before Wine 11.
- Wine write watches use userfaultfd support in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/ntdll/unix/virtual.c` (LGPL-2.1-or-later). libblsession supplies the arm64 syscall number and answers `PAGEMAP_SCAN` from `/proc/self/pagemap`.
- libblsession runs Wine's arm64 `SIGUSR2` context handler (`dlls/ntdll/unix/signal_arm64.c`, LGPL-2.1-or-later) in place on a frame laid out like [Linux](https://github.com/torvalds/linux)'s `arch/arm64/kernel/signal.c`, and serves `fill_vm_counters` in `dlls/ntdll/unix/process.c` the kernel's own `/proc/<pid>/status`.
- droiddeck-ntsync follows the [Linux](https://github.com/torvalds/linux) ntsync semantics in `Documentation/userspace-api/ntsync.rst` (GPL-2.0).
- The proot fast path orders bindings like [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later).
- DiRT 3 and DiRT Showdown keep their telemetry socket the way RoccoC's DiRTTelemetryErrorFix (MIT) does, based on the fix by Carsten Gottschling.
- Games use [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's per render pass memory choice. The Steam client keeps `TU_DEBUG=sysmem`.
- Black Ops III and Dark Souls Remastered start without [umu-protonfixes](https://github.com/Open-Wine-Components/umu-protonfixes) (BSD-2-Clause), whose `311210.py` and `570940.py` fail under ARM64 Proton.
- [ReSkate](https://github.com/Dingo-Shenanigans/ReSkate) (GPL-3.0) by Dingo Shenanigans uses the launch option from its README and D3D12 feature level 12_1.
- 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).
</details>
<details>
<summary><b>Stores</b></summary>
- Epic Games and GOG build on [Phobos](https://github.com/phobos665)'s adaptation of [Legendary](https://github.com/legendary-gl/legendary) and [gogdl](https://github.com/Heroic-Games-Launcher/heroic-gogdl). GOG install script registry entries are read the way [Heroic Games Launcher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) (GPL-3.0) reads them.
- Epic's official Linux launcher preview runs through FEX. That setup is outside Epic's listed Linux support.
- Battle.net is by [@kayakyakr](https://github.com/kayakyakr). It runs Blizzard's own client in Proton (downloaded from Blizzard, not included) and shows key art from the [Battle.net Shop](https://shop.battle.net). Without Steam, its games see the pad as an Xbox 360 controller via `PROTON_SPOOF_STEAMINPUT_VIDPID` in [Proton](https://github.com/ValveSoftware/wine)'s `dlls/winebus.sys`.
- Battle.net installs drive Blizzard's Agent the way [@barncastle](https://github.com/barncastle)'s [Battle.Net-Installer](https://github.com/barncastle/Battle.Net-Installer) does, with requests from [wowdev.wiki](https://wowdev.wiki/Agent). Read without taking code: [@mubaraknumann](https://github.com/mubaraknumann)'s [unifideck](https://github.com/mubaraknumann/unifideck), [@djmittens](https://github.com/djmittens)'s [neo-mittens](https://github.com/djmittens/neo-mittens) `bnetswitch`, [@BCD1210](https://github.com/BCD1210)'s [soju](https://github.com/BCD1210/soju) and [Lutris](https://github.com/lutris/lutris)'s `product.db` reader.
- World of Warcraft addon imports replace an addon's folder from a temporary copy, as [WowUp](https://github.com/WowUp/WowUp) does, and read flavour folders the way [WoWDeck](https://github.com/sigboe/wowdeck) does. No code from either is used.
- The World of Warcraft addon Download window searches [WoWInterface](https://www.wowinterface.com) through its MMOUI API. [CurseForge](https://www.curseforge.com/wow) opens in the user's browser, with no API and no key.
- Xbox games load [@domsvoboda](https://github.com/domsvoboda)'s [xodus-nexus](https://github.com/domsvoboda/xodus-nexus) modules (LGPL-2.1-or-later), including `wintypes.dll`. Minecraft's GameInput setup was checked against [@Wyze3306](https://github.com/Wyze3306)'s [BedrockOnLinux](https://github.com/Wyze3306/BedrockOnLinux) (MIT).
- The Xbox Cloud Gaming player uses the WebRTC client of [@unknownskl](https://github.com/unknownskl)'s [Greenlight](https://github.com/unknownskl/greenlight) (MIT), and its offer tweaks and sharpening follow [@redphx](https://github.com/redphx)'s [Better xCloud](https://github.com/redphx/better-xcloud) (MIT). See `tools/xcloud-player/NOTICE.md`.
- The itch.io butler pack ships butler's `7z.so`, itch.io's build of [7-Zip](https://www.7-zip.org) by Igor Pavlov (LGPL-2.1, unRAR restriction on RAR code), unmodified. Other archives use Apache Commons Compress and the runtime's bsdtar (libarchive).
- The Store uses [Flatpak](https://github.com/flatpak/flatpak) (LGPL-2.1) through DroidDeck's own bubblewrap shim. Chromium and Electron apps start directly, since [zypak](https://github.com/refi64/zypak) by [@refi64](https://github.com/refi64) (BSD-3-Clause) always uses Flatpak's spawn portal.
- Folder sync runs [Syncthing](https://github.com/syncthing/syncthing) (MPL-2.0), built from [@researchxxl](https://github.com/researchxxl)'s [syncthing-android](https://github.com/researchxxl/syncthing-android) fork (MPL-2.0).
</details>
<details>
<summary><b>Audio, video and HUD</b></summary>
- Steam 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`.
- The DroidDeck HUD is [@The412Banner](https://github.com/The412Banner)'s [FusionHUD](https://github.com/The412Banner/FusionHUD) (GPL-3.0 with an attribution term). Its readings match Valve's [MangoHud](https://github.com/flightlessmango/MangoHud) build (MIT), with frame times from gamescope patch 0125.
- The HUD's CPU, GPU load, thermal and battery readers come from [@xXJSONDeruloXx](https://github.com/xXJSONDeruloXx)'s work.
- LSFG frame generation comes from Camille LaVey and the [Eden](https://eden-emu.dev) project, following [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk), ported 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.
- Remote Play decodes through Android's MediaCodec or the [FFmpeg](https://ffmpeg.org) (LGPL-2.1-or-later) in Steam's runtime. Qualcomm low latency settings follow [Moonlight](https://github.com/moonlight-stream/moonlight-android) (GPL-3.0).
- [PipeWire](https://gitlab.freedesktop.org/pipewire/pipewire) (MIT) sockets use the settings in its `module-access.c`, and [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber) (MIT) keeps those clients at full permissions.
</details>
<details>
<summary><b>Desktop</b></summary>
- The KDE pacman repository snapshots [Arch Linux ARM](https://archlinuxarm.org) and installs with [pacman](https://gitlab.archlinux.org/pacman/pacman) (GPL-2.0-or-later).
- [KWin](https://invent.kde.org/plasma/kwin) (GPL-2.0-or-later) is patched to pass on the scroll data its `wayland_backend.cpp` leaves as a TODO.
- Read without taking code: [Qt](https://code.qt.io/cgit/qt/qtbase.git)'s `qwaylandinputdevice.cpp` and `qguiapplication.cpp`, [Konsole](https://invent.kde.org/utilities/konsole)'s `TerminalDisplay.cpp`, and [Mesa](https://gitlab.freedesktop.org/mesa/mesa)'s `u_threaded_context.c` and `zink_resource.c`.
- The Steam Keyboard window rule follows Valve's `steamdeck-kde-presets` with [Bazzite](https://github.com/ublue-os/bazzite)'s `0001-steam-virtual-keyboard.patch`.
- Steam's Browse buttons open a GTK 3 file chooser via PyGObject (LGPL-2.1-or-later), shown over Steam by [gamescope](https://github.com/ValveSoftware/gamescope) (BSD-2-Clause) through `GetAppIdFromReaper`.
- The AYN Odin 2 phantom second display was confirmed by [@SapphireRhodonite](https://github.com/SapphireRhodonite)'s [WatermelonDS 0.0.5](https://github.com/SapphireRhodonite/WatermelonDS/releases/tag/0.0.5) notes and [iiSU #363](https://github.com/iisu-network/iiSU/issues/363), without taking their code.
</details>
<details>
<summary><b>Smart glasses</b></summary>
- Builds on WinNative's VITURE work. Protocol checked against [@wheaney](https://github.com/wheaney)'s [XRLinuxDriver](https://github.com/wheaney/XRLinuxDriver), [@badicsalex](https://github.com/badicsalex)'s [ar-drivers-rs](https://github.com/badicsalex/ar-drivers-rs) and [go-macos/iokit](https://pkg.go.dev/github.com/go-macos/iokit/viture). The optional VITURE SDK is not included.
- The VITURE protocol V2 frames adapt [@elasticjava](https://github.com/elasticjava)'s [viture-v2](https://github.com/elasticjava/viture-v2) (MIT) by Holger Bartnick. See `docs/third-party/viture-v2`.
- RayNeo 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 references XRLinuxDriver and [xrealOneDeviceKit](https://github.com/wheaney/xrealOneDeviceKit) by @wheaney, [@rohitsangwan01](https://github.com/rohitsangwan01)'s [xreal_one_driver](https://github.com/rohitsangwan01/xreal_one_driver) (MIT) and [@0xcaff](https://github.com/0xcaff)'s [xr-tools](https://github.com/0xcaff/xr-tools).
- Rokid follows ar-drivers-rs, @badicsalex's [Void Computing write-ups](https://voidcomputing.hu/blog/worse-better-prettier/) and Monado's Rokid driver.
- REDMAGIC 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).
</details>
<details>
<summary><b>VR, Joy-Con and hand tracking</b></summary>
- Joy-Con VR ports the arm model from Google's [GVR SDK for Unity](https://github.com/googlevr/gvr-unity-sdk) (Apache-2.0) and adapts the hand skeleton from Valve's [OpenVR](https://github.com/ValveSoftware/openvr) `handskeletonsimulation` sample (BSD-3-Clause), which follows Moshi Turner's work in Monado. Index offsets come from Monado's `vive_poses.c` (BSL-1.0).
- Joy-Con research: Linux's [hid-nintendo](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-nintendo.c) by Daniel J. Ogorchock, [@dekuNukem](https://github.com/dekuNukem)'s [Nintendo_Switch_Reverse_Engineering](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering), [SDL](https://github.com/libsdl-org/SDL) and AOSP. Checked against [ALVR](https://github.com/alvr-org/ALVR), [OpenComposite](https://gitlab.com/znixian/OpenOVR), [PSMoveSteamVRBridge](https://github.com/HipsterSloth/PSMoveSteamVRBridge), [@F8LEFT](https://github.com/F8LEFT)'s [JoyConVr](https://github.com/F8LEFT/JoyConVr), [@krnelpan1c](https://github.com/krnelpan1c)'s [Daydream-Controller-SteamVR](https://github.com/krnelpan1c/Daydream-Controller-SteamVR), [@yoyobuae](https://github.com/yoyobuae)'s [joyconvr](https://github.com/yoyobuae/joyconvr), [Moonlight](https://github.com/moonlight-stream/moonlight-android) and [Dolphin](https://github.com/dolphin-emu/dolphin). See [docs/third-party/joycon](docs/third-party/joycon/README.md).
- Camera hand tracking runs Google's [MediaPipe](https://github.com/google-ai-edge/mediapipe) Pose Landmarker (Lite), BlazePose GHUM 3D and Hand Landmarker (Apache-2.0) on [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) frames (Apache-2.0), with usage reports off. Checked against JoyConVr's camera app (MIT).
- Tracking math: the GVR arm model's accelerometer mode, Eric Foxlin's zero velocity updates, Frosio, Pedersini and Borghese's accelerometer autocalibration, a Kalman filter (Kalman, 1960), Horn's quaternion method (1987) and the 1€ filter (Casiez, Roussel and Vogel, 2012).
- Gestures follow [ALVR](https://github.com/alvr-org/ALVR)'s `hand_gestures.rs` (MIT) and Meta Quest's menu pinch, tuned on [HaGRID](https://github.com/hukenovs/hagrid) photos (CC BY-SA 4.0, not included).
- The glove model follows OpenVR's render model reference, is meshed with surface nets (Gibson, 1998; Mikola Lysenko), and uses Inigo Quilez's smooth minimum, distance field ambient occlusion and polygon distance (MIT). See [docs/third-party/handtracking](docs/third-party/handtracking/README.md).
</details>
## Star history
<a href="https://www.star-history.com/#Droid-Deck/DroidDeck&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Droid-Deck/DroidDeck&type=Date&theme=dark">
<img alt="Star history chart" src="https://api.star-history.com/svg?repos=Droid-Deck/DroidDeck&type=Date" width="100%">
</picture>
</a>
+314 -28
View File
@@ -4,22 +4,153 @@ plugins {
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
def localBuildOptions = new Properties()
def localBuildOptionsFile = rootProject.layout.projectDirectory.file('local.properties')
if (localBuildOptionsFile.asFile.exists()) {
localBuildOptions.load(new StringReader(providers.fileContents(localBuildOptionsFile).asText.get()))
}
def pythonExecutable = project.findProperty('pythonExecutable')
?: localBuildOptions.getProperty('droiddeck.python', 'python3')
def vitureSdkDir = project.hasProperty('vitureSdkDir') ? project.property('vitureSdkDir') : localBuildOptions.getProperty('droiddeck.vitureSdk', '')
def uvcDir = project.hasProperty('uvcDir') ? project.property('uvcDir') : localBuildOptions.getProperty('droiddeck.uvcDir', '')
if (uvcDir) {
['arm64-v8a/libusb.so', 'arm64-v8a/libuvc.a', 'include/libusb.h', 'include/libuvc/libuvc.h',
'licenses/libusb-COPYING', 'licenses/libuvc-LICENSE.txt'].each { required ->
if (!new File(uvcDir, required).isFile()) throw new GradleException("Incomplete UVC build: missing ${required}")
}
}
if (vitureSdkDir) {
['android/arm64-v8a/libglasses.so', 'android/arm64-v8a/libcarina_vio.so',
'android/arm64-v8a/libcloud_protocol.so', 'include/viture_glasses_provider.h',
'android/LICENSE', 'android/NOTICE'].each { required ->
if (!new File(vitureSdkDir, required).isFile()) {
throw new GradleException("Incomplete VITURE Android SDK: missing ${required}")
}
}
}
def gitValue = { List<String> arguments ->
try {
def output = providers.exec {
commandLine(['git'] + arguments)
workingDir rootProject.projectDir
ignoreExitValue = true
}
output.result.get().exitValue == 0 ? output.standardOutput.asText.get().trim() : null
} catch (Exception ignored) {
null
}
}
def buildLabel = providers.environmentVariable('DROIDDECK_BUILD_LABEL').getOrNull()
if (buildLabel == null || buildLabel.trim().isEmpty()) {
def eventName = providers.environmentVariable('GITHUB_EVENT_NAME').getOrNull()
def eventPath = providers.environmentVariable('GITHUB_EVENT_PATH').getOrNull()
if (eventName == 'pull_request_target' && eventPath) {
try {
def eventFile = layout.file(providers.provider { new File(eventPath) })
def eventText = providers.fileContents(eventFile).asText.get()
def pullRequest = new groovy.json.JsonSlurper().parseText(eventText).pull_request
if (pullRequest?.number && pullRequest?.head?.sha) {
buildLabel = "pr${pullRequest.number} at ${pullRequest.head.sha.take(7)}"
}
} catch (Exception ignored) {
}
}
if (buildLabel == null || buildLabel.trim().isEmpty()) {
def branch = providers.environmentVariable('GITHUB_REF_NAME').getOrNull()
?: gitValue(['branch', '--show-current']) ?: 'local'
def commit = providers.environmentVariable('GITHUB_SHA').getOrNull()
?.take(7) ?: gitValue(['rev-parse', '--short=7', 'HEAD']) ?: 'unknown'
buildLabel = "${branch} at ${commit}"
}
}
def buildOrigin = providers.environmentVariable('GITHUB_ACTIONS').getOrNull() == 'true' ? 'remote' : 'local'
def treeState = providers.environmentVariable('DROIDDECK_BUILD_TREE_STATE').getOrNull()
if (!(treeState in ['clean', 'dirty'])) {
def status = gitValue(['status', '--porcelain'])
treeState = status == null ? 'unknown' : (status.isEmpty() ? 'clean' : 'dirty')
}
buildLabel = "${buildOrigin} ${treeState} · ${buildLabel}"
def escapedBuildLabel = buildLabel.replace('\\', '\\\\').replace('"', '\\"')
def sourceCommit = providers.environmentVariable('DROIDDECK_SOURCE_COMMIT').getOrNull()
?: providers.environmentVariable('GITHUB_SHA').getOrNull() ?: gitValue(['rev-parse', 'HEAD']) ?: ''
def prNumber = (providers.environmentVariable('DROIDDECK_PR_NUMBER').getOrNull() ?: '0').with { it.isInteger() ? it : '0' }
def prTitle = providers.environmentVariable('DROIDDECK_PR_TITLE').getOrNull() ?: ''
def buildBranch = providers.environmentVariable('GITHUB_HEAD_REF').getOrNull()?.trim()
?: gitValue(['branch', '--show-current']) ?: ''
def escapeJava = { String value -> value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', ' ').replace('\r', ' ') }
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', '--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()] }
def trackingPack = rootProject.file('tools/packs/tracking/release.env').readLines()
.findAll { it.contains('=') && !it.startsWith('#') }
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
def prootPackPin = rootProject.file('tools/packs/proot/release.env').readLines()
.findAll { it.contains('=') && !it.startsWith('#') }
.collectEntries { def kv = it.split('=', 2); [(kv[0].trim()): kv[1].trim()] }
def addonPacks = ['proot', 'directaudio', 'tracking', 'xbox', 'comet', 'butler', 'powervr'].collectEntries { name ->
def pack = file("build/packs/${name}.tar.zst")
if (!pack.isFile()) {
if (ciBuild) throw new GradleException("Add-on pack ${pack} is missing: run tools/packs/build.sh app/build/packs")
if (name == 'proot' && prootPackPin.PROOT_PACK_SHA256) {
return [(name): [sha256: prootPackPin.PROOT_PACK_SHA256, size: (prootPackPin.PROOT_PACK_SIZE ?: '0') as long]]
}
return [(name): [sha256: '', size: 0L]]
}
def digest = java.security.MessageDigest.getInstance('SHA-256')
pack.withInputStream { input ->
byte[] buffer = new byte[1 << 20]
int n
while ((n = input.read(buffer)) > 0) digest.update(buffer, 0, n)
}
[(name): [sha256: digest.digest().encodeHex().toString(), size: pack.length()]]
}
def desktopRemovalSources = new groovy.json.JsonSlurper().parse(file('src/main/assets/desktop-removal/sources.json'))
if (desktopRemovalSources.archives[desktopKde.DESKTOP_KDE_TAG] != desktopKde.DESKTOP_KDE_SHA256) {
throw new GradleException('Desktop removal inventories do not match the pinned archive')
}
if (!file('src/main/assets/desktop-removal/versions.txt').readLines().contains(desktopKde.DESKTOP_KDE_TAG)) {
throw new GradleException('Regenerate desktop removal inventories for ' + desktopKde.DESKTOP_KDE_TAG)
}
def variantSuffixes = rootProject.file('tools/release/variants.txt').readLines()
.findAll { it.trim() && !it.startsWith('#') }
.collect { def f = it.trim().split(/\s+/); "${f[1]}=${f[2] == '-' ? '' : f[2]}" }
.join(';')
android {
namespace 'com.steamdeck.launcher'
namespace 'com.droiddeck.launcher'
compileSdk 34
// The NDK is pinned by property so CI can hand in whatever the runner already has rather than
// downloading a second copy; see gradle.properties.
ndkVersion project.findProperty('ndkVersion') ?: '27.0.12077973'
ndkVersion project.findProperty('ndkVersion') ?: '27.3.13750724'
defaultConfig {
applicationId "com.steamdeck.launcher"
applicationId "com.droiddeck.launcher"
minSdkVersion 26
// 28 is not a leftover. An app on targetSdk 29+ runs in a domain that may not execute a
// file out of its own data directory, and this app's entire job is to exec a rootfs:
// proot, gamescope, the Steam client and everything it launches all live under files/.
targetSdkVersion 28
versionCode 5
versionName "0.1.4"
versionCode 17
versionName "0.6.9"
buildConfigField "String", "BUILD_LABEL", "\"${escapedBuildLabel}\""
buildConfigField "String", "SOURCE_COMMIT", "\"${sourceCommit}\""
buildConfigField "int", "PR_NUMBER", "${prNumber}"
buildConfigField "String", "PR_TITLE", "\"${escapeJava(prTitle)}\""
buildConfigField "String", "BUILD_BRANCH", "\"${escapeJava(buildBranch)}\""
buildConfigField "boolean", "CI_BUILD", "${ciBuild}"
buildConfigField "String", "RELEASE_SIGNER", "\"${releaseSigner}\""
buildConfigField "long", "COMMIT_TIME", "${commitTime}L * 1000L"
buildConfigField "String", "VARIANT_SUFFIXES", "\"${variantSuffixes}\""
buildConfigField "String", "SGDB_API_KEY", "\"${System.getenv("SGDB_API_KEY") ?: project.findProperty("sgdbApiKey") ?: ""}\""
buildConfigField "String", "DESKTOP_KDE_TAG", "\"${desktopKde.DESKTOP_KDE_TAG}\""
buildConfigField "String", "DESKTOP_KDE_URL", "\"https://github.com/${desktopKde.DESKTOP_KDE_REPO}/releases/download/${desktopKde.DESKTOP_KDE_TAG}/desktop-kde.tar.zst\""
buildConfigField "String", "DESKTOP_KDE_SHA256", "\"${desktopKde.DESKTOP_KDE_SHA256}\""
buildConfigField "long", "DESKTOP_KDE_SIZE", "${desktopKde.DESKTOP_KDE_SIZE}L"
addonPacks.each { name, pack ->
buildConfigField "String", "PACK_${name.toUpperCase()}_SHA256", "\"${pack.sha256}\""
buildConfigField "long", "PACK_${name.toUpperCase()}_SIZE", "${pack.size}L"
}
ndk {
abiFilters 'arm64-v8a'
@@ -27,6 +158,8 @@ android {
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_shared'
arguments "-DDROIDDECK_VITURE_SDK_DIR=${vitureSdkDir.toString().replace('\\', '/')}"
arguments "-DDROIDDECK_UVC_DIR=${uvcDir.toString().replace('\\', '/')}"
}
}
}
@@ -39,10 +172,6 @@ android {
}
signingConfigs {
// AOSP testkey ([email protected]), the same key Bannerlator ships with. It is publicly
// known, so it proves nothing about who built the apk — what it buys is ONE stable
// signature: every build installs over the last one instead of asking the user to
// uninstall first. v1 (JAR) + v2 + v3 so every installer and Android version accepts it.
testkey {
storeFile file("${rootDir}/keystore/testkey.p12")
storeType "PKCS12"
@@ -59,6 +188,11 @@ android {
debug {
minifyEnabled false
signingConfig signingConfigs.testkey
def localSuffix = localBuildOptions.getProperty('droiddeck.debugApplicationIdSuffix')
if (localSuffix) {
applicationIdSuffix localSuffix
resValue 'string', 'app_name', 'DroidDeck XR Dev'
}
}
release {
minifyEnabled false
@@ -67,24 +201,43 @@ android {
}
lint {
// targetSdk 28 is deliberate and structural: an app on 29+ may not execute a file out of
// its own data directory, and executing a rootfs is the whole of what this app does. Lint
// fails a release build over it because Google Play will not accept an sdk that old —
// true, and not something this app can trade away. Every other check stays on.
disable 'ExpiredTargetSdkVersion'
}
androidResources {
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~:<dir>droiddeck-xbox:<dir>droiddeck-comet:<dir>directaudio:<dir>hybris'
}
packagingOptions {
resources {
excludes += ['META-INF/versions/9/OSGI-INF/MANIFEST.MF']
}
jniLibs {
// The PulseAudio daemon is executed from the native library directory and its
// libraries are read out of the apk by the class loader: both want the old,
// uncompressed-and-extracted layout.
useLegacyPackaging = true
pickFirsts += ['**/libtermux.so']
excludes += ['**/libproot.so', '**/libproot-loader.so', '**/libopencv_java4.so', '**/libmediapipe_tasks_jni.so']
}
}
buildFeatures {
prefab true
compose true
buildConfig true
}
testOptions {
unitTests.returnDefaultValues = true
unitTests.includeAndroidResources = true
unitTests.all {
if (ciBuild) {
outputs.upToDateWhen { false }
outputs.doNotCacheIf('CI executes the test suite on every run') { true }
}
maxHeapSize = '2g'
maxParallelForks = 2
def logTarget = providers.systemProperty('robolectric.logging').orNull
if (logTarget != null) systemProperty 'robolectric.logging', logTarget
}
}
compileOptions {
@@ -96,19 +249,152 @@ android {
}
}
// No instrumented tests; the unused androidTest variant breaks the VS Code Kotlin import.
androidComponents {
beforeVariants(selector().all()) { it.androidTest.enable = false }
}
// Vendor binaries remain in the user's SDK directory; never vendor them into Git.
if (vitureSdkDir) {
android.sourceSets.main.jniLibs.srcDir(new File(vitureSdkDir, 'android'))
def stageVitureNotices = tasks.register('stageVitureNotices', Copy) {
from(new File(vitureSdkDir, 'android')) { include 'LICENSE', 'NOTICE' }
into(layout.buildDirectory.dir('vitureAssets/licenses/viture'))
}
tasks.named('preBuild').configure { dependsOn(stageVitureNotices) }
}
def stageSessionScripts = tasks.register('stageSessionScripts', Copy) {
from(rootProject.file('tools/linuxfs/overlay')) {
include 'usr/local/bin/droiddeck-*', 'usr/local/bin/steam-compatibility', 'usr/bin/**'
}
into(layout.projectDirectory.dir('src/main/assets/linuxfs'))
}
tasks.named('preBuild').configure { dependsOn(stageSessionScripts) }
def runtimeAssetsDir = layout.buildDirectory.dir('runtimeAssets')
def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
dependsOn(stageSessionScripts)
from(layout.projectDirectory.dir('src/main/assets')) { exclude 'pulseaudio.tzst' }
from(rootProject.file('tools/linuxfs/innoextract'))
from(layout.buildDirectory.dir('prepared-assets'))
into(runtimeAssetsDir)
}
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
if (vitureSdkDir) android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('vitureAssets'))
if (uvcDir) {
def stageUvcNotices = tasks.register('stageUvcNotices', Copy) {
from(new File(uvcDir, 'licenses'))
into(layout.buildDirectory.dir('uvcAssets/licenses/uvc'))
}
tasks.named('preBuild').configure { dependsOn(stageUvcNotices) }
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('uvcAssets'))
}
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
def stageGogNotices = tasks.register('stageGogNotices', Copy) {
from(layout.projectDirectory.dir('src/main/rust/stores/vendor/xdelta3')) { include 'LICENSE', 'README.droiddeck.md' }
into(layout.buildDirectory.dir('gogNotices/licenses/xdelta3'))
}
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('gogNotices'))
tasks.named('preBuild').configure { dependsOn(stageGogNotices) }
if (!ciBuild) {
def stageLocalPacks = tasks.register('stageLocalPacks', Sync) {
from(layout.buildDirectory.dir('packs')) { include '*.tar.zst' }
into(layout.buildDirectory.dir('localPackAssets/packs'))
}
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('localPackAssets'))
tasks.named('preBuild').configure { dependsOn(stageLocalPacks) }
}
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
mustRunAfter(stageSessionScripts, stageRuntimeAssets)
workingDir rootProject.projectDir
def allowStale = (project.findProperty('allowStaleRuntime')
?: localBuildOptions.getProperty('droiddeck.allowStaleRuntime', 'false')).toString().toBoolean()
commandLine([pythonExecutable, 'tools/release/runtime_inputs.py', 'verify'] + (allowStale ? ['--allow-stale-sources'] : []))
}
tasks.configureEach { task ->
if (task.name in ['packageDebug', 'packageRelease']) task.dependsOn(verifyRuntimeInputs)
}
def rustCrateDir = layout.projectDirectory.dir('src/main/rust/stores')
def rustJniLibsDir = layout.buildDirectory.dir('rustJniLibs')
def skipRust = (project.findProperty('skipRust')
?: localBuildOptions.getProperty('droiddeck.skipRust', 'false')).toString().toBoolean()
def buildRustStores = tasks.register('buildRustStores', Exec) {
description = 'Cross-compiles the store download engines (libdroiddeckstores.so) with cargo-ndk.'
onlyIf { !skipRust }
inputs.dir(rustCrateDir.dir('src'))
inputs.dir(rustCrateDir.dir('vendor'))
inputs.file(rustCrateDir.file('build.rs'))
inputs.files(rustCrateDir.file('Cargo.toml'), rustCrateDir.file('Cargo.lock'))
inputs.property('ndkVersion', android.ndkVersion)
if (!skipRust) inputs.property('rustToolchain', providers.exec {
environment 'PATH', "${System.getProperty('user.home')}/.cargo/bin${File.pathSeparator}${System.getenv('PATH')}"
commandLine 'rustup', 'run', 'stable', 'rustc', '-Vv'
}.standardOutput.asText)
outputs.dir(rustJniLibsDir)
workingDir rustCrateDir
def cargoBin = "${System.getProperty('user.home')}/.cargo/bin"
environment 'PATH', "${cargoBin}${File.pathSeparator}${System.getenv('PATH')}"
environment 'ANDROID_NDK_HOME', android.ndkDirectory.absolutePath
environment 'CARGO_TERM_COLOR', 'never'
if (!skipRust) environment 'RUSTC', providers.exec {
commandLine 'rustup', 'which', '--toolchain', 'stable', 'rustc'
}.standardOutput.asText.get().trim()
commandLine 'rustup', 'run', 'stable', 'cargo', 'ndk', '--target', 'arm64-v8a', '--platform', '26',
'--output-dir', rustJniLibsDir.get().asFile.absolutePath,
'build', '--release', '--locked'
}
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
def maliBridgeDir = rootProject.file('external/MaliBridge')
def vkbridgeJniLibsDir = layout.buildDirectory.dir('vkbridgeJniLibs')
def buildVkBridgeServer = tasks.register('buildVkBridgeServer', Exec) {
description = 'Builds the Vulkan bridge server (libvkbridge_server.so) with the NDK.'
inputs.dir(new File(maliBridgeDir, 'common'))
inputs.dir(new File(maliBridgeDir, 'server'))
inputs.dir(new File(maliBridgeDir, 'third_party'))
inputs.file(new File(maliBridgeDir, 'scripts/build-server-android.sh'))
outputs.dir(vkbridgeJniLibsDir)
commandLine 'bash', new File(maliBridgeDir, 'scripts/build-server-android.sh').absolutePath,
android.ndkDirectory.absolutePath, vkbridgeJniLibsDir.get().asFile.absolutePath
}
android.sourceSets.main.jniLibs.srcDir(vkbridgeJniLibsDir)
tasks.named('preBuild').configure { dependsOn(buildVkBridgeServer) }
dependencies {
// The runtime tarball is a zstd-compressed tar.
implementation 'com.github.luben:zstd-jni:1.5.2-3@aar'
implementation 'org.apache.commons:commons-compress:1.20'
// The UI: every menu, dialog and overlay is Compose. The session's SurfaceView and the
// on-screen pad are Views underneath it, which is what they are in every Compose app.
implementation "org.opencv:opencv:${trackingPack.OPENCV_VERSION}"
implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.49'
implementation 'com.github.luben:zstd-jni:1.5.7-4@aar'
implementation 'org.apache.commons:commons-compress:1.27.1'
implementation 'org.tukaani:xz:1.10'
implementation platform('androidx.compose:compose-bom:2024.02.00')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation 'androidx.activity:activity-compose:1.8.2'
// The File Manager, Bannerlator's: its icon set, image thumbnails and the coroutines it
// copies on. Same versions as Bannerlator so the screen stays a straight copy.
implementation 'androidx.compose.material:material-icons-extended'
implementation 'io.coil-kt:coil-compose:2.6.0'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'com.flyfishxu:kadb-android:1.2.1'
implementation 'com.termux.termux-app:terminal-view:0.118.0'
implementation 'androidx.camera:camera-camera2:1.4.2'
implementation 'androidx.camera:camera-lifecycle:1.4.2'
implementation 'androidx.webkit:webkit:1.12.1'
implementation 'io.github.webrtc-sdk:android:125.6422.07'
implementation("com.google.mediapipe:tasks-vision:${trackingPack.MEDIAPIPE_VERSION}") {
exclude group: 'com.google.auto.value', module: 'auto-value'
}
implementation 'com.google.zxing:core:3.5.3'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.16.1'
testImplementation 'androidx.compose.ui:ui-test-junit4'
testRuntimeOnly 'com.github.luben:zstd-jni:1.5.7-4@jar'
}
+311 -40
View File
@@ -1,33 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<queries>
<package android:name="cn.nubia.gamelauncher" />
<intent>
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
</intent>
<intent>
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
</intent>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent>
<intent>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SECONDARY_HOME" />
</intent>
</queries>
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Session logs go to Downloads so a failed run can be handed over as one folder. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- The session keeps running while the app is not on screen: a foreground service holds the
process at perceptible priority, and a high-performance WiFi lock keeps a backgrounded
download from being throttled to nothing by the radio's power-save. -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Ends background processes of another app that holds the Steam login. GameHub starts
itself with the phone via boot receivers and signs this client out seconds after every
login; a normal permission, and it cannot touch anything in the foreground. -->
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<!-- The microphone for the Linux session, only when the user turns it on: the DirectAudio
relay helper opens the input stream under this uid, and PulseAudio exposes it to the
Steam client as DirectAudioMic. -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x401000" android:required="true" />
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.vr.headtracking" android:version="1" android:required="false" />
<application
android:name=".App"
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"
@@ -37,63 +66,305 @@
android:appCategory="game"
android:isGame="true">
<!-- A game, to the OS: Android 12+ routes the category through GameManager and the OEM
performance frameworks (Game Booster, Game Turbo, Game Space) key off it. Without it
a regular flagship runs the session under its "app" power profile — a conservative
governor and GPU DVFS — which is why the same chipset feels slower here than in a
gaming phone whose mode is on for everything. The config says what the OS may do. -->
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" />
<activity
android:name=".MainActivity"
android:exported="true"
android:screenOrientation="sensorLandscape">
<service android:name=".glasses.LumaRgbCaptureService" android:exported="false"
android:process=":rgb_diagnostic" />
<service android:name=".wallpaper.SteamWallService"
android:label="@string/steam_wall_label"
android:permission="android.permission.BIND_WALLPAPER"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data android:name="android.service.wallpaper" android:resource="@xml/steam_wall_wallpaper" />
</service>
<activity android:name=".wallpaper.WallpaperSettingsActivity"
android:label="@string/steam_wall_label"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true" />
<service android:name=".input.HomeButtonService"
android:label="@string/integration_shortcuts_service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice" android:resource="@xml/home_button_service" />
</service>
<service android:name=".session.BottomNotificationListener"
android:label="@string/bottom_notifications_listener"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<receiver android:name=".core.ShortcutsListingReceiver" android:exported="false"
android:process=":shortcuts_sync">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
<meta-data android:name="com.oculus.supportedDevices" android:value="quest2|quest3|quest3s" />
<activity android:name=".QuestXrActivity" android:exported="true" android:launchMode="singleTask"
android:permission="android.permission.DUMP"
android:resizeableActivity="false" android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation|uiMode"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<meta-data android:name="android.app.lib_name" android:value="droiddeckxr" />
<intent-filter>
<action android:name="com.droiddeck.launcher.XR" />
<category android:name="com.oculus.intent.category.VR" />
<category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" />
</intent-filter>
</activity>
<!--
A session keeps a Surface, a compositor thread and a proot tree alive; a configuration
change must not tear that down and rebuild it, so the activity handles them itself.
Landscape only: gamescope is told one output size when it starts.
<activity
android:name=".PairingGuideActivity"
android:exported="false"
android:launchMode="singleTask"
android:taskAffinity="${applicationId}.pairingguide"
android:excludeFromRecents="true"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation"
android:theme="@style/AppTheme" />
<activity
android:name=".FrontEndActivity"
android:exported="true"
android:screenOrientation="user"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density|keyboard|keyboardHidden|navigation">
<layout android:defaultWidth="1024dp" android:defaultHeight="640dp" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
<data android:scheme="droiddeck" android:host="game" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.droiddeck.launcher.LAUNCH_GAME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity>
<activity-alias
android:name=".MainActivity"
android:targetActivity=".FrontEndActivity"
android:icon="@mipmap/ic_launcher"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".LauncherLight"
android:targetActivity=".FrontEndActivity"
android:icon="@mipmap/ic_launcher_light"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CAR_LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:name=".HomeActivity"
android:enabled="false"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity="${applicationId}.home"
android:screenOrientation="user"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</activity>
<activity
android:name=".PinRequestActivity"
android:exported="true"
android:excludeFromRecents="true"
android:taskAffinity="${applicationId}.pin"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
<action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
</intent-filter>
</activity>
<activity
android:name=".sync.SyncthingWebActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
Resizable, deliberately: a foldable opening or closing changes the window, and the
compositor re-fits the picture with bars. Declared non-resizable, the system keeps a
fixed-shape window instead and STRETCHES it onto the other panel — the app never sees a
size change, so nothing it does can help.
-->
<activity
android:name=".files.FileManagerActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:screenOrientation="user"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".files.FilePickerActivity"
android:exported="false"
android:screenOrientation="sensorLandscape"
android:screenOrientation="user"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity android:name=".stores.xbox.XboxLoginActivity" android:exported="false"
android:process=":xboxlogin" android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity android:name=".stores.xbox.XboxCloudPlayerActivity" android:exported="false"
android:screenOrientation="sensorLandscape" android:hardwareAccelerated="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
android:theme="@style/AppTheme" />
<service android:name=".stores.xbox.XboxCloudTokenService" android:exported="false" />
<activity android:name=".stores.xbox.XboxNativePlayerActivity" android:exported="false"
android:process=":xcloudnative" android:screenOrientation="sensorLandscape" android:hardwareAccelerated="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
android:theme="@style/AppTheme" />
<activity android:name=".stores.xbox.XboxCloudActivity" android:exported="false"
android:process=":xboxcloud" android:launchMode="singleTop" android:screenOrientation="sensorLandscape"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation|uiMode"
android:theme="@style/AppTheme" />
<activity android:name=".stores.xbox.XboxCloudSettingsActivity" android:exported="false"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".stores.StoreLoginActivity"
android:exported="false"
android:screenOrientation="user"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|density"
android:theme="@style/AppTheme" />
<activity
android:name=".agent.AgentStartActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:permission="android.permission.DUMP"
android:theme="@style/SessionTheme" />
<activity
android:name=".SessionActivity"
android:exported="false"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:theme="@style/SessionTheme"
android:preferMinimalPostProcessing="true"
android:screenOrientation="sensorLandscape"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
<!--
Owns the session — gamescope, the Steam client, audio — so it survives the activity
going away. Without it Android demotes the process as soon as the last visible
activity goes, and the low-memory killer reaps the guest: leaving Big Picture to
answer a message would come back to a dead Steam and a half-finished download.
-->
<activity
android:name=".ui.DualScreenActivity"
android:exported="false"
android:excludeFromRecents="true"
android:taskAffinity="${applicationId}.dualscreen"
android:theme="@style/SessionTheme"
android:resizeableActivity="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
<activity
android:name=".ui.SessionTypingActivity"
android:exported="false"
android:excludeFromRecents="true"
android:noHistory="true"
android:taskAffinity="${applicationId}.typing"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
android:resizeableActivity="true"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density" />
<activity
android:name=".gpu.SteamVrRestartActivity"
android:exported="false"
android:process=":vr_recovery"
android:taskAffinity="${applicationId}.vr_recovery"
android:excludeFromRecents="true"
android:screenOrientation="sensorLandscape"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboard|keyboardHidden|navigation|uiMode|density"
android:theme="@style/SessionTheme" />
<service
android:name=".session.SessionService"
android:exported="false"
android:stopWithTask="false" />
<!-- The compositor's native state is one per process, so the bottom-screen desktop's compositor runs in its own. -->
<service
android:name=".wayland.BottomDesktopCompositorService"
android:process=":bottom_desktop"
android:exported="false" />
<service
android:name=".runtime.RuntimeInstallService"
android:exported="false" />
<service
android:name=".core.WirelessAdbPairingService"
android:exported="false" />
<service
android:name=".stores.download.StoreDownloadService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".sync.SyncthingService"
android:exported="false"
android:foregroundServiceType="dataSync" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.logs"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/log_share_paths" />
</provider>
<provider
android:name=".files.AppFilesProvider"
android:authorities="${applicationId}.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<provider
android:name=".agent.AgentBridgeProvider"
android:authorities="${applicationId}.agent"
android:exported="true"
android:permission="android.permission.DUMP"
android:grantUriPermissions="false" />
<activity android:name=".ui.SpatialTrackingActivity" android:exported="false" />
<service
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
tools:node="merge">
<meta-data
android:name="backend:com.google.android.datatransport.cct.CctBackendFactory"
tools:node="remove" />
</service>
</application>
</manifest>
@@ -0,0 +1,102 @@
{
"version": 1,
"identity": "luma-ultra-base-v1",
"width": 640,
"height": 480,
"fisheye": false,
"error": 1.1209395390660726,
"verified": false,
"k1": {
"rows": 3,
"cols": 3,
"data": [
226.0499636040297,
0,
321.62348950766386,
0,
228.36987999988767,
242.2674105020684,
0,
0,
1
]
},
"d1": {
"rows": 5,
"cols": 1,
"data": [
-0.28622915867944043,
0.1194613930041054,
0.007984341063494766,
-0.0008220922923559441,
-0.025579805296512992
]
},
"k2": {
"rows": 3,
"cols": 3,
"data": [
240.3338202496282,
0,
320.8790079027563,
0,
241.14047924325277,
260.45566640784716,
0,
0,
1
]
},
"d2": {
"rows": 5,
"cols": 1,
"data": [
-0.27073208928390136,
0.08065508519805098,
-0.001460778493930769,
-0.0003694718992635806,
-0.011452505620454932
]
},
"r": {
"rows": 3,
"cols": 3,
"data": [
0.900501479835182,
0.04473903344740378,
-0.43254537762047496,
-0.08255720039597626,
0.9941919046988283,
-0.06904176485356042,
0.4269442510181227,
0.0978819468416306,
0.8989648107712929
]
},
"t": {
"rows": 3,
"cols": 1,
"data": [
-0.14117171913441395,
0.0014051874404824838,
-0.007555740272006347
]
},
"cameraToHead": [
-0.05607918,
0.023585627,
-0.07998472,
0.01024512,
0.9945703,
0.005859665,
-0.10339546
],
"deviceModel": "VITURE Luma Ultra",
"cameraPair": "left0-right0",
"sourceValidation": {
"exportDate": "2026-10-16",
"sourceDeviceVerified": true,
"capturedViews": 25,
"scope": "Starting geometry from one calibrated Luma Ultra; other units have not been individually validated."
}
}
Binary file not shown.
@@ -0,0 +1,70 @@
{
"archives": {
"lxqt-r1": "c2bb860b3b1688287451e2495be5db313ec07f8c8af501dfac2768a9f417bc86",
"desktop-kde-r1": "55747e75d0ec055ab7ea1e92a757f3be227cc3205bb9101f5b53cadef6318579",
"desktop-kde-r2": "9ba832677b498654136c69d8c8330434d17e4d53cd099acac9bde3ba429b35bf",
"emulators-r1": "a1f4806a9663d28f424b4cab1277e0ede80a92e69de5bc063cdc8b45af220164",
"desktop-kde-r3": "8b7142fcec8d86143ae39fc14ffc86907d30d9a5b5a45f49cb1b55109d6fc4eb"
},
"desktopApplications": {
"7zip-26.05-1-aarch64.pkg.tar.xz": "17569bafbc5bd1a044ca1db164b96cb15857a22a3575f36679de46e693ae285e",
"ark-26.08.2-1-aarch64.pkg.tar.xz": "b93cc90c4fc8a6cb0247fe8fb0f9de1c2074f5173fa2735d3d1959c9162c08f2",
"breeze-6.8.1-1-aarch64.pkg.tar.xz": "70a2ff3afd41602f184814af5c6e298ff84ef7cb7e5880ca7cb4f6fb28521511",
"breeze-gtk-6.8.1-1-any.pkg.tar.xz": "e92b9acf3155db22d4a288a43e8d95c20c72b8d8cffd028dd6800eb01a304bf0",
"breeze-icons-6.31.0-1-aarch64.pkg.tar.xz": "b252ef02395621b5891d72c5f7fccd1a1ead1ce770886e9fced4bf21e1130096",
"discover-6.8.1-1-aarch64.pkg.tar.xz": "64cfca615e7c18530c684325eb36a5c45e18c63b31f651335474f53b0178958c",
"dolphin-26.08.2-1-aarch64.pkg.tar.xz": "4b5f7a9212c88d47146b374742f67f5620ab128ad893d945721bb43501066b70",
"elisa-26.08.2-1-aarch64.pkg.tar.xz": "fd2df84a2905b59fba0ab1b7fadafe567c5a1dfd44d53ae4cd4996f683b83ba2",
"filelight-26.08.2-1-aarch64.pkg.tar.xz": "17f39435104baf0743ab508bdde6e8bd1b3417b025b67ca8f343032d2fdef3e6",
"firefox-158.0.1-1-aarch64.pkg.tar.xz": "4940e14c05522bfee8df901e1cfe81f56036c82e8f3d95f9f7a2a12e6b38d232",
"flatpak-kcm-6.8.1-1-aarch64.pkg.tar.xz": "3145601002fb87d61307949a3011a8f27a7b1e9851fc59835460f934f8e00098",
"git-2.56.0-1-aarch64.pkg.tar.xz": "b152a20edb215e98cada4b2dfbd02a9e925bda0b2de6ac1441e9e60b6698d3b5",
"gwenview-26.08.2-1-aarch64.pkg.tar.xz": "02aae3973ec0563022c89e312ce7696d05d33514101ae2a01df3275717929de2",
"htop-3.5.3-1-aarch64.pkg.tar.xz": "a58899bd2596d55575493509b3a5aa6f53075cb3d88bb6c712088c57068d9eb7",
"kate-26.08.2-1-aarch64.pkg.tar.xz": "89c21ad8e62d20961b13f8fac8d9f4d358b1a3feebed243f6412e4ad996d5e9e",
"kcalc-26.08.2-1-aarch64.pkg.tar.xz": "79b3303ccc5ae956e97313439456243e59f0b79a8166ca91606a25d1c9e41029",
"kde-cli-tools-6.8.1-1-aarch64.pkg.tar.xz": "b052e9c25e7d6896be67846696f14c9e00430c0445f358ee64cf2a8ea16aa2eb",
"kde-gtk-config-6.8.1-1-aarch64.pkg.tar.xz": "3095da034ac5fe8594165e3396f61f083b0c011cd96807146ddfb53690118608",
"kfind-26.08.2-1-aarch64.pkg.tar.xz": "b4108cc3bd9efd62970379aadb3cb22e99dd1f54873ff432392edd0a833bb393",
"kinfocenter-6.8.1-1-aarch64.pkg.tar.xz": "758882ef13a166205acab0e6bb96ce756d638d91e501db400cb579b9e1331d31",
"konsole-26.08.2-1-aarch64.pkg.tar.xz": "0cf27f69d1792a094d4a935f578c82f8151b2d359198d9cd32bfdbd8dec544e4",
"kscreen-6.8.1-1-aarch64.pkg.tar.xz": "72c948dfdebfef305623527de27274baf1a2328c87e71033fd852e386d9fdf5c",
"kwin-6.8.1-2-aarch64.pkg.tar.xz": "726c98a3f8682b487c2b313633edbd80aa8b20ff657a6b63bbb1b73454f6e0c4",
"less-1:710-1-aarch64.pkg.tar.xz": "8e1d4cbecf6d2ae734cfc1c5495367c0a9245f92dfd2bc03d4d0ab14f8b26ff2",
"man-db-2.13.1-2-aarch64.pkg.tar.xz": "67515751f848717e1f6d3470f57ad2ea6900ee07618fd2726b6fcaa695687e80",
"man-pages-6.19-1-any.pkg.tar.xz": "69df38f1a1aa35240d239ff82a2e067950ac394f23b8a6c7307580ccd7e95f07",
"mpv-1:0.41.0-6-aarch64.pkg.tar.xz": "6c89d00736820210a3c7f32af4cf95d155191d463f9df8bd22137c45f30a554a",
"okular-26.08.2-1-aarch64.pkg.tar.xz": "aef87cf74730d2345092184d7e9b14a0068ba4030de58564aa770506175cbf06",
"plasma-desktop-6.8.1-1-aarch64.pkg.tar.xz": "4b24662a2059b21ca98b5b35b72216f22c9e316057247ea018bf6318b07cfdb6",
"plasma-integration-6.8.1-1-aarch64.pkg.tar.xz": "514f7d2a96370814b9374d7c52e1873257af6e18190d130e5a8d3d6d1fffdc54",
"plasma-pa-6.8.1-1-aarch64.pkg.tar.xz": "13dff66d30072744c7870bd1a294ae96f0fee086a1533b8052d7dfc038d57c45",
"plasma-systemmonitor-6.8.1-1-aarch64.pkg.tar.xz": "551b7c1f24cd5df64de4f5ba199bc71c00e2924c16a8241b21c020584794e10d",
"plasma-workspace-6.8.1-1-aarch64.pkg.tar.xz": "4c713990aeab118d50865c818f9689ae7637dc6556e4a0e2fec5e0b348cdcd29",
"spectacle-1:6.8.1-1-aarch64.pkg.tar.xz": "0d697803bd0135701688be3869d4d99eec6b8b43ca761610da73f44cb77bfa5c",
"systemsettings-6.8.1-1-aarch64.pkg.tar.xz": "db528089fa328d67dadb50be3b822858238785d8c659a9ef95389b993efcb234",
"unrar-1:7.3.1-1-aarch64.pkg.tar.xz": "9883e19b1f1adfa430285191b1f96db4cc208608baebd955f3fe9fee92cd3bdd",
"unzip-6.0-23-aarch64.pkg.tar.xz": "edc91c7e4e400f20345439744262d1ceded104c78d940659a6b9876076ef33fc",
"wget-1.25.0-6-aarch64.pkg.tar.xz": "e2bf0067cca93877c737c7d48bc36fff32e184c7874829429b9473c10a12dd39",
"wl-clipboard-1:2.3.0-1-aarch64.pkg.tar.xz": "723a63b140e00085a533ebb1ec36fa9305b5e6cbd8fb80c0521fbeff77a72982",
"xclip-0.13-6-aarch64.pkg.tar.xz": "772ec9336b679b0ef277d1ef0b69b74777467239e16ec845af00c07fa849d166",
"xdg-desktop-portal-kde-6.8.1-1-aarch64.pkg.tar.xz": "8098c0ef0454335bb7c806049d66977c512657346222606737d72a7ae1de3146",
"zip-3.0-14-aarch64.pkg.tar.xz": "9887f177544021809d065d0298514e98f76b55f9120119f748f5a28c8bb51994"
},
"legacyApplications": {
"featherpad-1.6.3-1-aarch64.pkg.tar.xz": "fcac55215815c0b712453f9bcc09abfe4b518e36025a111a7a7598ef04913f3d",
"labwc-0.20.2-1-aarch64.pkg.tar.xz": "bcd4cc3945165b0fdd33c0117cd7b9dfd05cf8c327326c37f3c3a27ef23ee5ca",
"lximage-qt-2.4.0-1-aarch64.pkg.tar.xz": "5d6bdd5472ea6bcb4b993b36df3ab7010bb2e62b36a78a649708fc9a52cb0928",
"lxqt-config-2.4.0-1-aarch64.pkg.tar.xz": "4b601d798fc7689ee76787dbef8c8958c02429ee60248cd99d950c919dbefba5",
"lxqt-panel-2.4.1-1-aarch64.pkg.tar.xz": "a141e0f8049e73f859d843510c6f45d6ed9087c990bcebf44f5f2430621024c4",
"lxqt-qtplugin-2.4.0-3-aarch64.pkg.tar.xz": "61c8f828be73111ee0bd5e3163b0d64c2098dd0189710a190f42be11137695cf",
"lxqt-runner-2.4.0-1-aarch64.pkg.tar.xz": "5afa74652a37366ea4e7774cf33379977582fbdca599cef1005ebf637cbcae4b",
"lxqt-session-2.4.0-2-aarch64.pkg.tar.xz": "e9c7c8a0011859029622ad202e0d3826e4e5bc8d64659031f6efa211456ff6eb",
"lxqt-themes-2.4.0-1-any.pkg.tar.xz": "7ea16c62a1fa70cdac13cb3f8fdcdaad299ae1f5bd514acfda671484755787bf",
"lxqt-wayland-session-0.4.1-1-any.pkg.tar.xz": "cd8dc11f5035469d23b6bbd937a90593402428c43de37517412aa43fe1fc6b10",
"papirus-icon-theme-20260801-1-any.pkg.tar.xz": "4aae3f47b800a60d6fd360e55adc581408ceaafc481c5a420a681741a3966510",
"pcmanfm-qt-2.4.1-1-aarch64.pkg.tar.xz": "7ec1a6b39dd0e4055b39f0e2c0549ea263d83004e1b821fb9e42f1d7f5fff0f9",
"qterminal-2.4.0-1-aarch64.pkg.tar.xz": "aa4adc9c9d47641b9452ec8d879038a9cb4762c3b6b5299045fac968fd0ed22d",
"swaybg-1.2.2-1-aarch64.pkg.tar.xz": "a26caa16ca7d581b5ca05758677058b4b60af10dc8d83e447ad6824b762b48b8",
"wlr-randr-0.5.0-1-aarch64.pkg.tar.xz": "58c3651c6a15dc8f95a310c7e225f759aa766be2fb3c0981a9a8b567854675b3"
}
}
@@ -0,0 +1,3 @@
desktop-kde-r1
desktop-kde-r2
desktop-kde-r3
@@ -1,115 +0,0 @@
# DirectAudio for the Linux Steam client (relay build)
This is the driver for games that run under **Valve's ARM64 Proton inside a Linux
runtime on Android** - Bannerlator's linuxfs session with the native Steam client -
where the game process is a glibc process and cannot touch Android's audio
libraries itself.
Two pieces, both required:
| piece | what it is | where it runs |
|---|---|---|
| `aarch64-unix/winedirectaudio.so` + the two PE shells | the Wine `mmdevapi` driver, glibc build, **no AAudio inside** | in the game, inside the Linux rootfs |
| `directaudio-relay` | a small bionic program that owns the AAudio output and microphone streams | on the Android side, under the app's uid |
They meet over a unix socket. The driver connects, sends its launch config, and
gets back two shared-memory rings: one it fills with mixed game audio, one the
helper fills with microphone audio. The helper plays and records; the driver
keeps doing everything else it always did.
## Which Proton
Built inside `ValveSoftware/wine` at `proton_11.0`. The private `mmdevapi`
interface it implements is byte-identical across **Proton 11.0 (ARM64)**,
**Proton Experimental (ARM64)** and our own Wine-11 layers (checked 2026-09-19),
so this one build serves any of them. A future Proton on a Wine 12 base will
need a rebuild - the interface is not versioned, and a mismatch is silence, not
an error.
## Installing the driver next to Proton (not into it)
Steam verifies and updates its Proton depots, so files added under
`steamapps/common/Proton .../files/lib/wine/` will not survive. Put the driver
in a directory of its own and point Wine at it:
```
<somewhere>/directaudio/lib/wine/aarch64-unix/winedirectaudio.so
<somewhere>/directaudio/lib/wine/aarch64-windows/winedirectaudio.drv
<somewhere>/directaudio/lib/wine/i386-windows/winedirectaudio.drv
```
and set, for the game process:
```
WINEDLLPATH=<somewhere>/directaudio/lib/wine
```
Wine searches `WINEDLLPATH` entries with the per-arch subdirectories appended,
so both the PE shell and the unixlib are found there. In Bannerlator's runtime
the natural place to export it is the `bannerlator-proton` compatibility-tool
wrapper, which every Proton launch goes through.
## Selecting the driver
`mmdevapi` only tries the drivers named in the registry (or its built-in
`pulse,alsa,oss,coreaudio` list, which does not know about this one). Set, in the
game's prefix:
```
wine reg add "HKCU\Software\Wine\Drivers" /v Audio /d directaudio /f
```
Under Proton the prefix is `compatdata/<appid>/pfx`; run the command with that
prefix as `WINEPREFIX` using Proton's own `bin-arm64/wine`, or write the key from
the wrapper before it hands off to Proton.
## Running the helper
```
directaudio-relay --socket <path> [--log]
```
- Run it **under the app's uid**, the way the app already runs its PulseAudio
daemon. Android checks `RECORD_AUDIO` against the calling uid, so a helper
started any other way can play but cannot record.
- Start it **before** the game: the driver asks "is a helper there?" once, when
Wine picks an audio driver, and falls back to unavailable if not.
- The socket path must be reachable from inside the Linux rootfs. Bannerlator
binds the app's files directory and its runtime directory into the session at
their own paths, so any socket under either works unchanged on both sides.
- The helper serves any number of game processes; each gets its own streams.
It exits on `SIGTERM`; a game exiting tears down that game's streams only.
## Telling the driver where the helper is
```
BANNER_AUDIO_DIRECT_RELAY=<the same socket path>
```
Unset, the driver looks for `$XDG_RUNTIME_DIR/directaudio-relay`.
## Everything else is unchanged
All `BANNER_AUDIO_DIRECT_*` knobs mean what they mean in the in-process build:
`_PERF`, `_ADAPTIVE`, `_DECAY`, `_MS`, `_MAXMS`, `_BF`, `_MBF`, `_EXCLUSIVE`,
`_WATCHDOG`, `_STALL_MS`, `_DECAY_*`, `_LOG`, `_PERIOD_MS`, `_MINPERIOD_MS`, and
the live-config mailbox `_RUNTIME` (the helper watches the file; it must be a
path the helper can read on the Android side). **`BANNER_AUDIO_DIRECT_MIC=1`**
exposes the capture endpoint exactly as before: the helper opens the input
stream (`VOICE_COMMUNICATION` preset) when the game connects and makes it hot on
the game's first capture start.
One thing is different: there is one extra hand-off, the ring. The helper's
callback pulls a burst from it, so the driver keeps two bursts queued (about 8 ms
on a 192-frame device) on top of the AAudio buffer the presets control, and the
helper raises that if a callback ever finds the ring short. `get_latency` reports
the sum, so a game that sizes its own buffers from it still sees the truth.
## Proving it is running
- Helper side: `logcat -s DA-Relay:I` shows `hello from <exe>`, then `open: buffer
... ring target ...`, then `capture open` / `capture start` for the mic.
- Game side: the session log carries `DirectAudio: relay: connected - ...`.
- The game process's `/proc/<pid>/maps` shows `winedirectaudio.so` and **no**
`libaaudio.so` (it is in the helper's maps instead). The AudioFlinger track is
owned by the helper's pid.
@@ -1,4 +0,0 @@
directaudio linux-wine11 relay build
source 3548a4a730e8d01cb0cb0268d51a2fb8f23dcd33
wine ValveSoftware/wine@proton_11.0
unixlib.h sha256 fdbed263338d0719c4d12e02b045c23721de33219ec8878e4c2d7ebc9efcbe09
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Holger Bartnick
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style>
html, body { margin: 0; height: 100%; background: #000; overflow: hidden; }
#player { position: absolute; inset: 0; }
#status { position: absolute; left: 0; right: 0; bottom: 12%; text-align: center; color: #ddd;
font: 16px sans-serif; text-shadow: 0 0 4px #000; pointer-events: none; }
</style>
</head>
<body>
<div id="player"></div>
<div id="status"></div>
<script src="player.js"></script>
</body>
</html>
+187
View File
@@ -0,0 +1,187 @@
/* DroidDeck Xbox Cloud Gaming player. Includes Greenlight's player (c) UnknownSKL (Jim Kroon), MIT. */
(()=>{var _=class{_player;_dataChannel;constructor(e){this._player=e,this._dataChannel=this._player._peerConnection.createDataChannel(this.getChannelName(),this.getChannelConfig()),this._dataChannel.onopen=this.onOpen.bind(this),this._dataChannel.onmessage=this.onMessage.bind(this),this._dataChannel.onclosing=this.onClosing.bind(this),this._dataChannel.onclose=this.onClose.bind(this),this._dataChannel.onerror=this.onError.bind(this)}getChannelName(){return"channel"}getChannelConfig(){return{}}onOpen(e){console.log(this.getChannelName(),"Opening channel:",e)}onMessage(e){console.log(this.getChannelName(),"Message channel:",e)}onClosing(e){console.log(this.getChannelName(),"Closing channel:",e)}onClose(e){console.log(this.getChannelName(),"Closed channel:",e)}onError(e){console.log(this.getChannelName(),"Error channel:",e)}send(e){if(this._dataChannel.readyState!=="open"){console.log("["+this.getChannelName()+"] Tried to send data while channel is not open. readyState is:",this._dataChannel.readyState);return}return typeof e=="string"&&(e=new TextEncoder().encode(e)),this._dataChannel.send(e)}getPlayer(){return this._player}destroy(){}};var S=class extends _{_micPermissions="prompt";_micStream;constructor(e){super(e),navigator.permissions.query({name:"microphone"}).then(t=>{this._micPermissions=t.state,t.onchange=()=>{this._micPermissions=t.state}})}getChannelName(){return"chat"}getChannelConfig(){return{ordered:!0,protocol:"chatV1"}}destroy(){}};var v=class r{_player;_index;_physicalGamepadId=-1;_rumbleInterval;_isFocused=!0;_gamepadOverride={buttons:{}};_options={enable_keyboard:!1,enable_gamepad:!0,enable_vibration:!0,keyboard_mapping:{A:"Enter",B:"Backspace",X:"x",Y:"y",DPadUp:"ArrowUp",DPadDown:"ArrowDown",DPadLeft:"ArrowLeft",DPadRight:"ArrowRight",LeftShoulder:"[",RightShoulder:"]",LeftThumb:"l",RightThumb:"r",LeftTrigger:"-",RightTrigger:"=",Menu:"m",View:"v",Nexus:"n"},gamepad_mapping:{A:"0",B:"1",X:"2",Y:"3",DPadUp:"12",DPadDown:"13",DPadLeft:"14",DPadRight:"15",LeftShoulder:"4",RightShoulder:"5",LeftThumb:"10",RightThumb:"11",LeftTrigger:"6",RightTrigger:"7",Menu:"9",View:"8",Nexus:"16"},gamepad_axes_mapping:{LeftThumbXAxis:"0",LeftThumbYAxis:"1",RightThumbXAxis:"2",RightThumbYAxis:"3"},gamepad_deadzone:.08,gamepad_force_capture:!1};_listener={keyDown:this.onKeyDown.bind(this),keyUp:this.onKeyUp.bind(this),gamepadConnected:this.onGamepadConnected.bind(this),gamepadDisconnected:this.onGamepadDisconnected.bind(this),windowFocus:this.onWindowFocus.bind(this),windowBlur:this.onWindowBlur.bind(this)};constructor(e,t={}){this._index=e,this._options={...this._options,...t}}attach(e){this._player=e,this._player._channels.control.sendGamepadState(this._index,!0,this),this._options.enable_keyboard===!0&&(window.addEventListener("keydown",this._listener.keyDown),window.addEventListener("keyup",this._listener.keyUp)),this._options.enable_gamepad===!0&&(window.addEventListener("gamepadconnected",this._listener.gamepadConnected),window.addEventListener("gamepaddisconnected",this._listener.gamepadDisconnected)),this._options.enable_gamepad===!0&&this._options.gamepad_force_capture===!1&&(window.addEventListener("blur",this._listener.windowBlur),window.addEventListener("focus",this._listener.windowFocus)),this.detectActiveGamepad()}detach(){if(this._player===void 0){console.log("[Gamepad] Player is not attached. this._player is:",this._player);return}this._player._channels.control.sendGamepadState(this._index,!1),this._physicalGamepadId=-1,this._rumbleInterval!==void 0&&clearInterval(this._rumbleInterval),this._options.enable_keyboard===!0&&(window.removeEventListener("keydown",this._listener.keyDown),window.removeEventListener("keyup",this._listener.keyUp)),this._options.enable_gamepad===!0&&(window.removeEventListener("gamepadconnected",this._listener.gamepadConnected),window.removeEventListener("gamepaddisconnected",this._listener.gamepadDisconnected)),this._options.enable_gamepad===!0&&this._options.gamepad_force_capture===!1&&(window.removeEventListener("blur",this._listener.windowBlur),window.removeEventListener("focus",this._listener.windowFocus))}sendButtonState(e,t){if(this._player===void 0){console.log("[Gamepad] Player is not attached. this._player is:",this._player);return}if(!(e in this.getDefaultFamepadFrame())){console.log("[Gamepad] Invalid button:",e);return}let n=this.getDefaultFamepadFrame();n.GamepadIndex=this._index,this._gamepadOverride.buttons[e]=t}onKeyDown(e){if(this._isFocused!==!1)for(let t in this._options.keyboard_mapping)this._options.keyboard_mapping[t]===e.key&&(this._gamepadOverride.buttons[t]=1,e.preventDefault())}onKeyUp(e){if(this._isFocused!==!1)for(let t in this._options.keyboard_mapping)this._options.keyboard_mapping[t]===e.key&&(this._gamepadOverride.buttons[t]=0,e.preventDefault())}onWindowFocus(){this._isFocused=!0}onWindowBlur(){}getDefaultFamepadFrame(){return{GamepadIndex:0,Nexus:0,Menu:0,View:0,A:0,B:0,X:0,Y:0,DPadUp:0,DPadDown:0,DPadLeft:0,DPadRight:0,LeftShoulder:0,Line truncated
`+t.ContentText)?this.completeTransaction(e.id,{Result:0}):this.completeTransaction(e.id,{Result:1})}};var M=class extends _{_handshakeCompleted=!1;getChannelName(){return"message"}getChannelConfig(){return{protocol:"messageV1",ordered:!0}}onOpen(){console.log("[MessageChannel] Sending handshake...");let e=JSON.stringify({type:"Handshake",version:"messageV1",id:"be0bfc6d-1e83-4c8a-90ed-fa8601c5a179",cv:"0"});this.send(e)}onMessage(e){let t=JSON.parse(e.data);switch(t.type){case"HandshakeAck":console.log("[MessageChannel] Handshake completed"),this._handshakeCompleted=!0,this.getPlayer()._channels.control.sendAuthorization(),this.getPlayer()._channels.input.start(),this.sendConfig();break;case"TransactionStart":case"Message":new D(this,t);break;default:console.log("[MessageChannel] Unhandled message type:",t.type,t)}}sendConfig(){let e=[],t=[0,2,0],n=JSON.stringify(this.generateMessage("/streaming/systemUi/configuration",{version:t,systemUis:e}));this.send(n);let i=JSON.stringify(this.generateMessage("/streaming/properties/clientappinstallidchanged",{clientAppInstallId:"c97d7ee0-73b2-4239-bf1d-9d805a338429"}));this.send(i);let a=JSON.stringify(this.generateMessage("/streaming/characteristics/orientationchanged",{orientation:0}));this.send(a);let s=JSON.stringify(this.generateMessage("/streaming/characteristics/touchinputenabledchanged",{touchInputEnabled:!1}));this.send(s);let o=JSON.stringify(this.generateMessage("/streaming/characteristics/clientdevicecapabilities",{}));this.send(o);let d=JSON.stringify(this.generateMessage("/streaming/characteristics/dimensionschanged",{horizontal:1920,vertical:1080,preferredWidth:1920,preferredHeight:1080,safeAreaLeft:0,safeAreaTop:0,safeAreaRight:1920,safeAreaBottom:1080,supportsCustomResolution:!0}));this.send(d)}generateMessage(e,t){return{type:"Message",content:JSON.stringify(t),id:Q(),target:e,cv:""}}destroy(){}};var E=class{_address;_ipv4Address;_ipv4Port;constructor(e){this._address=e;let t=this._address.split(":"),n=t[6]+t[7],i=t[5],a=~parseInt(n.substring(0,2),16)&255,s=~parseInt(n.substring(2,4),16)&255,o=~parseInt(n.substring(4,6),16)&255,d=~parseInt(n.substring(6,8),16)&255,u=~parseInt(i.substring(0,4),16)&65535;this._ipv4Address=a+"."+s+"."+o+"."+d,this._ipv4Port=u}getIpv4Address(){return this._ipv4Address}getIpv4Port(){return this._ipv4Port}};var R=class{_peerConnection;_iceCandidates=[];constructor(e){this._peerConnection=e._peerConnection,this._peerConnection.addEventListener("icecandidate",t=>{t.candidate&&this._iceCandidates.push(t.candidate)})}getCandidates(){return this._iceCandidates}setRemoteCandidates(e){e.length===0&&console.warn("Error: No candidates received!");for(let t in e){if(e[t].candidate==="a=end-of-candidates")continue;let n=e[t].candidate.split(" ");if(n.length>4&&n[4].substr(0,4)==="2001"){let i=new E(n[4]);this._peerConnection.addIceCandidate({candidate:"a=candidate:10 1 UDP 1 "+i.getIpv4Address()+" 9002 typ host ",sdpMLineIndex:e[t].sdpMLineIndex,sdpMid:e[t].sdpMid}),this._peerConnection.addIceCandidate({candidate:"a=candidate:11 1 UDP 1 "+i.getIpv4Address()+" "+i.getIpv4Port()+" typ host ",sdpMLineIndex:e[t].sdpMLineIndex,sdpMid:e[t].sdpMid})}this._peerConnection.addIceCandidate({candidate:e[t].candidate,sdpMid:e[t].sdpMid,sdpMLineIndex:e[t].sdpMLineIndex})}}};var I=class{_player;_peerConnection;constructor(e){this._player=e,this._peerConnection=e._peerConnection}getAvailableCodecs(){let e=RTCRtpReceiver.getCapabilities("video")?.codecs;if(e===void 0)return[];let t=[];for(let n in e)t.includes(e[n].mimeType)||t.push(e[n].mimeType);return t}setLocalSDP(e){return console.log("[SDP] setLocalSDP() Setting local SDP:",e),e.sdp=e.sdp?.replace("useinbandfec=1","useinbandfec=1; stereo=1"),e}setRemoteSDP(e){return console.log("[SDP] setRemoteSDP() Setting remote SDP:",e),e}getDefaultCodecPreferences(){let e=RTCRtpReceiver.getCapabilities("video")?.codecs;if(e===void 0)return[];let t=[],n=[],i=[],a=[];for(let o in e)e[o].mimeType.includes("H264")?e[o].sdpFmtpLine?.includes("profile-level-id=4d")?t.push(e[o]):e[o].sdpFmtpLine?.includes("profile-level-id=42e")?n.push(e[o]):e[o].sdpFmtpLine?.includes("profile-level-id=420")&&i.push(e[o]):(e[o].mimeType.includes("ulpfec")||e[o].mimeType.includes("flexfec")||e[o].mimeType.includes("VP9")||e[o].mimeType.includes("VP8"))&&a.push(e[o]);return[...t,...n,...i,...a]}_setBitrate(e,t,n){let i=e.split(`
`),a=-1;for(let o=0;o<i.length;o++)if(i[o].indexOf("m="+t)===0){a=o;break}if(a===-1)return console.debug("Could not find the m line for",t),e;for(a++;i[a].indexOf("i=")===0||i[a].indexOf("c=")===0;)a++;if(i[a].indexOf("b")===0)return i[a]="b=AS:"+n,i.join(`
`);let s=i.slice(0,a);return s.push("b=AS:"+n),s=s.concat(i.slice(a,i.length)),s.join(`
`)}};var P=class{_player;_peerConnection;_videoCodec="";_audioCodec="";_videoWidth=0;_videoHeight=0;_videoFps=0;_rtt=0;_remoteHost="";_remotePort=0;_remoteIsLocal=!1;_remoteIsIpv6=!1;_activeRemoteCandidate="";constructor(e){this._player=e,this._peerConnection=e._peerConnection,setInterval(this.loop.bind(this),1e3)}loop(){this._peerConnection.getStats().then(e=>{e.forEach(t=>{t.type==="inbound-rtp"?this.readInboundRtp(t):t.type==="candidate-pair"&&t.packetsReceived!==void 0&&t.packetsReceived>0?this.readCandidatePair(t):t.type==="remote-candidate"&&this.readRemoteCandidate(t)})})}readInboundRtp(e){e.kind==="video"&&e.codecId&&e.packetsReceived!==void 0&&e.packetsReceived>0?(e.codecId.includes("profile-level-id=4d")?this._videoCodec="H264 (High)":e.codecId.includes("profile-level-id=42e")?this._videoCodec="H264 (Normal)":e.codecId.includes("profile-level-id=420")?this._videoCodec="H264 (Low)":this._videoCodec="Other ("+e.codecId+")",e.frameWidth&&(this._videoWidth=e.frameWidth),e.frameHeight&&(this._videoHeight=e.frameHeight),e.framesPerSecond&&(this._videoFps=e.framesPerSecond)):e.kind==="audio"&&e.codecId&&e.packetsReceived!==void 0&&e.packetsReceived>0&&(this._audioCodec=e.codecId)}readCandidatePair(e){this._activeRemoteCandidate=e.remoteCandidateId,this._rtt=e.currentRoundTripTime}readRemoteCandidate(e){e.id===this._activeRemoteCandidate&&(this.isPrivateIP(e.address)?this._remoteIsLocal=!0:this._remoteIsLocal=!1,this.isIpv6(e.address)?this._remoteIsIpv6=!0:this._remoteIsIpv6=!1,this._remoteHost=e.address,this._remotePort=e.port)}isPrivateIP(e){let t=e.split(".");return t[0]==="10"||t[0]==="172"&&parseInt(t[1],10)>=16&&parseInt(t[1],10)<=31||t[0]==="192"&&t[1]==="168"}isIpv6(e){return e.split(":").length>3}};var G=class{_player;_videoComponent;_overlays={debug:void 0};constructor(e,t){this._videoComponent=e,this._player=t}toggleDebug(){this._overlays.debug===void 0?this.createDebugOverlay():this.destroy()}createDebugOverlay(){this._overlays.debug=document.createElement("div"),this._overlays.debug.id="playerDebugOverlay",this._overlays.debug.style.position="absolute",this._overlays.debug.style.top="0",this._overlays.debug.style.left="0",this._overlays.debug.style.width=this._videoComponent.getElement()?.style.width||"100%",this._overlays.debug.style.height=this._videoComponent.getElement()?.style.height||"auto",this._overlays.debug.style.background="linear-gradient(0deg, rgba(0,0,0, 0) 0%, rgba(0,0,0, 0) 75%, rgba(0,0,0, 0.5) 100%)",this._overlays.debug.style.padding="10px",this._overlays.debug.style.pointerEvents="none",document.getElementById(this._player.getElementId())?.appendChild(this._overlays.debug),setTimeout(()=>{this.refreshDebugOverlay()},500),this.refreshDebugOverlay()}destroy(){this._overlays.debug?.remove(),this._overlays.debug=void 0}refreshDebugOverlay(){if(this._overlays.debug!==void 0){this._overlays.debug.innerHTML="",this._overlays.debug.appendChild(this.createLabel("Local Play",this._player.getStats()._remoteIsLocal?"Local":"Remote","ok")),this._overlays.debug.appendChild(this.createLabel("Resolution",this._player.getStats()._videoWidth+"x"+this._player.getStats()._videoHeight,"ok")),this._overlays.debug.appendChild(this.createLabel("Stream FPS",this._player.getStats()._videoFps.toString(),this._player.getStats()._videoFps>=58?"ok":"warning"));let e=this._videoComponent.getFrameStats?.();e&&(this._overlays.debug.appendChild(this.createLabel("Render FPS",e.renderingFps.toString(),e.renderingFps>=58?"ok":"warning")),this._overlays.debug.appendChild(this.createLabel("Render Delay",e.renderDelayMs.toString()+" ms",e.renderDelayMs<16?"ok":e.renderDelayMs<33?"warning":"error")),this._overlays.debug.appendChild(this.createLabel("Dropped Frames",e.droppedFrames.toString(),e.droppedFrames===0?"ok":e.droppedFrames<30?"warning":"error"))),this._overlays.debug.appendChild(this.createLabel("Connection",this._player.getStats()._remoteIsIpv6?"IPv6":"IPv4","ok")),this._overlays.debug.appendChild(this.createLabel("Video",this._player.getStats()._videoCodec,"ok"));let t=this._player.getStats()._rtt*1e3>75?"error":this._player.getStats()._rtt*1e3>40?"warning":"ok";this._overlays.debug.appendChild(this.createLabel("RTT",(this._player.getStats()._rtt*1e3).toString()+" ms",t)),setTimeout(()=>{this.refreshDebugOverlay()},500)}}createLabel(e,t="",n=""){let i={borderRadius:"5px",background:"linear-gradient(0deg, rgba(28,26,26,1) 0%, rgba(47,45,45,1) 100%)",padding:"10px",fontSize:"12px",marginTop:"3px",marginRight:"10px",marginBottom:"2px",textTransform:"uppercase",color:"white",display:"inline-block"},a=document.createElement("span"),s={error:"rgb(220, 53, 69)",warning:"rgb(255, 193, 7)",ok:"rgb(25, 135, 84)"};t!==""?a.innerHTML=`${e}: <span style="background: ${s[n]}; border-radius: 5px; padding: 4px; padding-left: 8px; padding-right: 8px;">${t}</span>`:a.innerText=e;for(let o in i)a.style[o]=i[o];return a}};var L=class{_player;_element;_overlay;constructor(e){this._player=e,this._overlay=new G(this,this._pLine truncated
in vec2 position;
out vec2 vUV;
void main() { vUV = position * 0.5 + 0.5; gl_Position = vec4(position, 0.0, 1.0); }`,j=`attribute vec2 position;
varying vec2 vUV;
void main() { vUV = position * 0.5 + 0.5; gl_Position = vec4(position, 0.0, 1.0); }`,Z=`#version 300 es
precision mediump float;
uniform sampler2D screenTexture;
uniform vec2 resolution;
uniform int filterId;
uniform float sharpenFactor;
uniform float brightness;
uniform float contrast;
uniform float gamma;
uniform float saturation;
uniform float deband;
out vec4 fragColor;
const float CAS_CONTRAST_PEAK = 0.8 * -3.0 + 8.0;
vec3 sharpen(vec2 coord, vec3 e) {
vec2 px = 1.0 / resolution;
vec3 b = texture(screenTexture, coord + px * vec2(0, 1)).rgb;
vec3 d = texture(screenTexture, coord + px * vec2(-1, 0)).rgb;
vec3 f = texture(screenTexture, coord + px * vec2(1, 0)).rgb;
vec3 h = texture(screenTexture, coord + px * vec2(0, -1)).rgb;
vec3 a = texture(screenTexture, coord + px * vec2(-1, 1)).rgb;
vec3 c = texture(screenTexture, coord + px * vec2(1, 1)).rgb;
vec3 g = texture(screenTexture, coord + px * vec2(-1, -1)).rgb;
vec3 i = texture(screenTexture, coord + px * vec2(1, -1)).rgb;
if (filterId == 1) {
vec3 blur = ((a + c + g + i) + (b + d + f + h) * 2.0 + e * 4.0) / 16.0;
return e + (e - blur) * sharpenFactor / 3.0;
}
vec3 minRgb = min(min(min(d, e), min(f, b)), h) + min(min(a, c), min(g, i));
vec3 maxRgb = max(max(max(d, e), max(f, b)), h) + max(max(a, c), max(g, i));
vec3 amplify = inversesqrt(clamp(min(minRgb, 2.0 - maxRgb) / maxRgb, 0.0, 1.0));
vec3 weight = -(1.0 / (amplify * CAS_CONTRAST_PEAK));
vec3 outColor = clamp(((b + d + f + h) * weight + e) / (4.0 * weight + 1.0), 0.0, 1.0);
return mix(e, outColor, sharpenFactor / 2.0);
}
float ign(vec2 p) { return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); }
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec3 color = texture(screenTexture, uv).rgb;
if (sharpenFactor > 0.0) color = sharpen(uv, color);
color = clamp(color + brightness, 0.0, 1.0);
color = (color - 0.5) * clamp(contrast + 1.0, 0.5, 2.0) + 0.5;
color = mix(vec3(dot(color, vec3(0.2126, 0.7152, 0.0722))), color, saturation);
color = pow(max(color, 0.0), vec3(1.0 / gamma));
if (deband > 0.0) color += vec3(((ign(gl_FragCoord.xy) + ign(gl_FragCoord.xy + vec2(11.0, 17.0))) - 1.0) * (deband / 255.0));
fragColor = vec4(color, 1.0);
}`,ee=`precision highp float;
#define FXAA_MIN_REDUCE (1.0 / 128.0)
#define FXAA_MUL_REDUCE (1.0 / 8.0)
#define MAX_SPAN 8.0
uniform sampler2D screenTexture;
uniform vec2 resolution;
const vec3 luma = vec3(0.299, 0.587, 0.114);
void main() {
vec2 invResolution = 1.0 / resolution;
vec3 rgbNW = texture2D(screenTexture, (gl_FragCoord.xy + vec2(-1.0, -1.0)) * invResolution).rgb;
vec3 rgbNE = texture2D(screenTexture, (gl_FragCoord.xy + vec2( 1.0, -1.0)) * invResolution).rgb;
vec3 rgbSW = texture2D(screenTexture, (gl_FragCoord.xy + vec2(-1.0, 1.0)) * invResolution).rgb;
vec3 rgbSE = texture2D(screenTexture, (gl_FragCoord.xy + vec2( 1.0, 1.0)) * invResolution).rgb;
vec3 rgbM = texture2D(screenTexture, gl_FragCoord.xy * invResolution).rgb;
float lumaNW = dot(rgbNW, luma);
float lumaNE = dot(rgbNE, luma);
float lumaSW = dot(rgbSW, luma);
float lumaSE = dot(rgbSE, luma);
float lumaM = dot(rgbM, luma);
float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
vec2 dir;
dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_MUL_REDUCE, FXAA_MIN_REDUCE);
float minDirFactor = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
dir = clamp(dir * minDirFactor, vec2(-MAX_SPAN), vec2(MAX_SPAN)) * invResolution;
vec4 rgbA = 0.5 * (
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * (1.0 / 3.0 - 0.5)) +
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * (2.0 / 3.0 - 0.5)));
vec4 rgbB = rgbA * 0.5 + 0.25 * (
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * -0.5) +
texture2D(screenTexture, gl_FragCoord.xy * invResolution + dir * 0.5));
float lumaB = dot(rgbB, vec4(luma, 0.0));
gl_FragColor = lumaB < lumaMin || lumaB > lumaMax ? rgbA : rgbB;
}`,te=`precision highp float;
uniform sampler2D screenTexture;
uniform vec2 resolution;
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
float edgeThreshold = 0.2;
vec2 offset = vec2(1.0) / resolution;
vec3 colorCenter = texture2D(screenTexture, uv).rgb;
vec3 colorLeft = texture2D(screenTexture, uv - vec2(offset.x, 0.0)).rgb;
vec3 colorRight = texture2D(screenTexture, uv + vec2(offset.x, 0.0)).rgb;
vec3 colorUp = texture2D(screenTexture, uv - vec2(0.0, offset.y)).rgb;
vec3 colorDown = texture2D(screenTexture, uv + vec2(0.0, offset.y)).rgb;
float diffHorizontal = length(colorRight - colorLeft);
float diffVertical = length(colorUp - colorDown);
float edgeFactor = step(edgeThreshold, diffHorizontal + diffVertical);
vec3 outlineColor = mix(colorCenter, vec3(0.0), edgeFactor);
gl_FragColor = vec4(outlineColor, 1.0);
}`,ne=`precision highp float;
#define CA_AMOUNT 1.0025
#define SCANLINE_INTENSITY_X 0.125
#define SCANLINE_INTENSITY_Y 0.375
#define SCANLINE_SIZE 1024.0
uniform sampler2D screenTexture;
varying vec2 vUV;
void main() {
vec4 finalColor = texture2D(screenTexture, vUV);
finalColor.rgb = vec3(
texture2D(screenTexture, (vUV - 0.5) * CA_AMOUNT + 0.5).r,
finalColor.g,
texture2D(screenTexture, (vUV - 0.5) / CA_AMOUNT + 0.5).b
);
float scanlineX = abs(sin(vUV.x * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_X);
float scanlineY = abs(sin(vUV.y * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_Y);
gl_FragColor = vec4(mix(finalColor.rgb, vec3(0.0), scanlineX + scanlineY), finalColor.a);
}`,ie=`precision highp float;
#define PI 3.14159265
#define SCANLINE_INTENSITY 0.35
#define CHROMA_OFFSET 0.005
#define BLUR_RADIUS 0.002
#define WARP_AMOUNT 0.01
#define SCANLINE_DARKEN 0.5
uniform sampler2D screenTexture;
uniform int FrameCount;
uniform vec2 TextureSize;
uniform vec2 resolution;
varying vec2 vUV;
const mat3 yiq_mat = mat3(
0.299, 0.587, 0.114,
0.596, -0.275, -0.321,
0.212, -0.523, 0.311
);
const mat3 yiq2rgb_mat = mat3(
1.0, 0.956, 0.621,
1.0, -0.272, -0.647,
1.0, -1.106, 1.705
);
vec3 applyNTSC(vec2 uv) {
vec3 col = texture2D(screenTexture, uv).rgb;
vec3 yiq = col * yiq_mat;
float chromaPhase = PI * (mod(uv.y * TextureSize.y, 2.0) + float(FrameCount));
yiq.y *= cos(chromaPhase * 0.5);
yiq.z *= sin(chromaPhase * 0.5);
vec3 rgb = yiq * yiq2rgb_mat;
vec3 finalColor;
finalColor.r = texture2D(screenTexture, uv + vec2(CHROMA_OFFSET, 0.0)).r;
finalColor.g = texture2D(screenTexture, uv + vec2(0.0, BLUR_RADIUS)).g;
finalColor.b = texture2D(screenTexture, uv - vec2(CHROMA_OFFSET, 0.0)).b;
return finalColor;
}
vec3 applyScanlines(vec2 uv) {
vec3 col = texture2D(screenTexture, uv).rgb;
float scanline = abs(sin(uv.y * resolution.y * 2.0)) * SCANLINE_INTENSITY;
col *= 1.0 - (scanline * SCANLINE_DARKEN);
return col;
}
vec2 applyWarp(vec2 uv) {
uv = uv * 2.0 - 1.0;
float r = sqrt(uv.x * uv.x + uv.y * uv.y);
uv += uv * (r * r) * WARP_AMOUNT;
return uv * 0.5 + 0.5;
}
void main() {
vec2 warpedUV = applyWarp(vUV);
vec3 ntscColor = applyNTSC(warpedUV);
vec3 scanlineColor = applyScanlines(warpedUV);
vec3 finalColor = mix(ntscColor, scanlineColor, 0.7);
gl_FragColor = vec4(finalColor, 1.0);
}`,H=class{constructor(e,t){this.video=e;this.effects=t;this.canvas=document.createElement("canvas"),Object.assign(this.canvas.style,{position:"absolute",inset:"0",width:"100%",height:"100%",objectFit:"contain",zIndex:"1"}),e.parentElement?.appendChild(this.canvas);let n=this.canvas.getContext("webgl2",{alpha:!1,antialias:!1,depth:!1,desynchronized:!0,powerPreference:"high-performance"});if(n===null)throw new Error("WebGL2 unavailable");this.gl=n;let i=n.createBuffer();n.bindBuffer(n.ARRAY_BUFFER,i),n.bufferData(n.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),n.STATIC_DRAW),this.passes.set("base",this.compile(J,Z,["screenTexture","resolution","filterId","sharpenFactor","brightness","contrast","gamma","saturation","deband"])),this.videoTexture=this.texture(),this.schedule()}canvas;gl;passes=new Map;videoTexture;targets=[];width=0;height=0;frame=0;lastDraw=0;stopped=!1;idle=!1;pending=!1;update(e){this.effects=e}pause(){this.idle||this.stopped||(this.idle=!0,this.canvas.style.display="none")}resume(e){this.effects=e,!(!this.idle||this.stopped)&&(this.idle=!1,this.canvas.style.display="",this.schedule())}schedule(){this.pending||(this.pending=!0,this.video.requestVideoFrameCallback(this.onFrame))}compile(e,t,n){let i=this.gl,a=(o,d)=>{let u=i.createShader(o);if(i.shaderSource(u,d),i.compileShader(u),!i.getShaderParameter(u,i.COMPILE_STATUS))throw new Error(`shader: ${i.getShaderInfoLog(u)}`);return u},s=i.createProgram();if(i.attachShader(s,a(i.VERTEX_SHADER,e)),i.attachShader(s,a(i.FRAGMENT_SHADER,t)),i.bindAttribLocation(s,0,"position"),i.linkProgram(s),!i.getProgramParameter(s,i.LINK_STATUS))throw new Error(`program: ${i.getProgramInfoLog(s)}`);return{program:s,uniforms:new Map(n.map(o=>[o,i.getUniformLocation(s,o)]))}}extra(e){let t=this.passes.get(e);if(t===void 0){let n={fxaa:ee,toon:te,crt:ne,ntsc:ie}[e];t=this.compile(j,n,["screenTexture","resolution","TextureSize","FrameCount"]),this.passes.set(e,t)}return t}texture(){let e=this.gl,t=e.createTexture();return e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),t}resize(e,t){let n=this.gl;this.width=e,this.height=t,this.canvas.width=e,this.canvas.height=t;for(let i of this.targets)n.deleteFramebuffer(i.framebuffer),n.deleteTexture(i.texture);this.targets=[0,1].map(()=>{let i=this.texture();n.texImage2D(n.TEXTURE_2D,0,n.RGBA,e,t,0,n.RGBA,n.UNSIGNED_BYTE,null);let a=n.createFramebuffer();return n.bindFramebuffer(n.FRAMEBUFFER,a),n.framebufferTexture2D(n.FRAMEBUFFER,n.COLOR_ATTACHMENT0,n.TEXTURE_2D,i,0),{framebuffer:a,texture:i}}),n.bindFramebuffer(n.FRAMEBUFFER,null)}onFrame=e=>{if(this.pending=!1,this.stopped||this.idle)return;this.schedule();let t=this.effects.maxFps;if(!(t>0&&e-this.lastDraw<1e3/t-2)){this.lastDraw=e;try{this.draw()}catch(n){window.DDCloud?.log(`effects: ${String(n).slice(0,200)}`),this.destroy()}}};draw(){let e=this.gl,t=this.effects,n=this.video.videoWidth,i=this.video.videoHeight;if(n===0||i===0)return;(n!==this.width||i!==this.height)&&this.resize(n,i),this.frame++,e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.videoTexture),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,this.video);let a=["fxaa","toon","crt","ntsc"].filter(d=>t[d]);e.viewport(0,0,n,i),e.enableVertexAttribArray(0),e.vertexAttribPointer(0,2,e.FLOAT,!1,0,0);let s=this.passes.get("base");e.bindFramebuffer(e.FRAMEBUFFER,a.length>0?this.targets[0].framebuffer:null),e.useProgram(s.program);let o=s.uniforms;e.uniform1i(o.get("screenTexture"),0),e.uniform2f(o.get("resolution"),n,i),e.uniform1i(o.get("filterId"),t.sharpenMode==="usm"?1:2),e.uniform1f(o.get("sharpenFactor"),t.sharpen),e.uniform1f(o.get("brightness"),t.brightness/100),e.uniform1f(o.get("contrast"),t.contrast/100),e.uniform1f(o.get("gamma"),Math.min(5,Math.max(.1,t.gamma))),e.uniform1f(o.get("saturation"),t.saturation/100),e.uniform1f(o.get("deband"),t.deband),e.drawArrays(e.TRIANGLES,0,3),a.forEach((d,u)=>{let h=this.extra(d),g=this.targets[u%2],p=u===a.length-1;e.bindFramebuffer(e.FRAMEBUFFER,p?null:this.targets[(u+1)%2].framebuffer),e.useProgram(h.program),e.bindTexture(e.TEXTURE_2D,g.texture);let c=h.uniforms;e.uniform1i(c.get("screenTexture")??null,0),e.uniform2f(c.get("resolution")??null,n,i),e.uniform2f(c.get("TextureSize")??null,n,i),e.uniform1i(c.get("FrameCount")??null,this.frame),e.drawArrays(e.TRIANGLES,0,3)})}destroy(){this.stopped=!0,this.canvas.remove(),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var V=class{constructor(e){this.pc=e;this.element=document.createElement("div"),Object.assign(this.element.style,{position:"absolute",top:"8px",right:"8px",zIndex:"3",padding:"4px 8px",borderRadius:"6px",background:"rgba(0,0,0,0.55)",color:"#e8e8e8",font:"12px monospace",lineHeight:"16px",pointerEvent Line truncated
`:" \xB7 ")}destroy(){this.show(0,this.vertical,this.server,this.left),this.element.remove()}};function z(r,e){let t=e==="high"?"4d":e==="low"?"420":"42e",n=[];for(let d of r.matchAll(/a=fmtp:(\d+).*profile-level-id=([0-9a-f]{6})/g))d[2].startsWith(t)&&n.push(d[1]);if(n.length===0)return r;let i=r.split(`\r
`),a=i.findIndex(d=>d.startsWith("m=video"));if(a<0)return r;let s=i[a].trim().split(" "),o=s.slice(3).filter(d=>!n.includes(d));return i[a]=s.slice(0,3).concat(n,o).join(" "),i.join(`\r
`)}function K(r,e){let t=r.split(`\r
`),n=t.findIndex(a=>a.startsWith("m=video"));if(n<0)return r;let i=`b=AS:${e}`;for(let a=n+1;a<=t.length;a++){let s=t[a];if(s===void 0||s.startsWith("m=")){t.splice(a,0,i);break}if(!(s.startsWith("i=")||s.startsWith("c="))){if(s.startsWith("b=AS:")){t[a]=i;break}t.splice(a,0,i);break}}return t.join(`\r
`)}var m=JSON.parse(DDCloud.settings()),ae=RTCPeerConnection.prototype.setLocalDescription;RTCPeerConnection.prototype.setLocalDescription=function(r){return r?.sdp&&((m.codec==="high"||m.codec==="normal"||m.codec==="low")&&(r.sdp=z(r.sdp,m.codec)),m.bitrateMbps>0&&(r.sdp=K(r.sdp,m.bitrateMbps*1e3))),ae.call(this,r)};window.__ddRumble=m.rumble/100;window.addEventListener("error",r=>DDCloud.log(`error: ${r.message} @${r.lineno}`));window.addEventListener("unhandledrejection",r=>DDCloud.log(`rejection: ${String(r.reason).slice(0,200)}`));window.addEventListener("gamepadconnected",r=>DDCloud.log(`controller: ${r.gamepad.id.slice(0,60)} mapping=${r.gamepad.mapping||"none"} buttons=${r.gamepad.buttons.length} axes=${r.gamepad.axes.length}`));var W=new Map,re=1;window.__ddResolve=(r,e,t)=>{let n=W.get(r);W.delete(r),n&&n({status:e,body:t})};function C(r,e=""){return new Promise(t=>{let n=re++;W.set(n,t),DDCloud.call(n,r,typeof e=="string"?e:JSON.stringify(e))})}var B=r=>new Promise(e=>setTimeout(e,r)),se=r=>r.status>=200&&r.status<300;function x(r){let e=document.getElementById("status");e&&(e.textContent=r),DDCloud.status(r)}var T=class extends Error{constructor(t,n){super(t);this.stage=t;this.detail=n}};function k(r,e){if(!se(e))throw new T(r,`${e.status} ${e.body.slice(0,300)}`);return e}function oe(r,e){return new Promise(t=>{if(r.iceGatheringState==="complete")return t();let n=setTimeout(t,e);r.addEventListener("icegatheringstatechange",()=>{r.iceGatheringState==="complete"&&(clearTimeout(n),t())})})}async function de(){x("Starting the game on Xbox Cloud Gaming\u2026"),k("start",await C("start"));let r=!1,e=0;for(;;){let l=k("state",await C("state")),f=JSON.parse(l.body||"{}");if(f.state==="Provisioned")break;if(f.state==="Failed")throw new T("provision",JSON.stringify(f.errorDetails??{}).slice(0,300));f.state==="ReadyToConnect"&&!r?(r=!0,x("Signing in to the cloud console\u2026"),k("connect",await C("connect"))):f.state==="WaitingForResources"?x(`In the queue for a cloud console\u2026 ${Math.round(e/1e3)} s`):x(`Getting a cloud console ready\u2026 ${Math.round(e/1e3)} s`),await B(1e3),e+=1e3}x("Connecting\u2026");let t=new U("player");t.init();let n=t._peerConnection,i=t._channels.input,a=i.queueMetadataFrame.bind(i);i.queueMetadataFrame=l=>{if(m.preventDrops){let f=l.frameRenderedTimeMs-l.frameDecodedTimeMs;l.frameDecodedTimeMs=l.frameSubmittedTimeMs+10,l.frameRenderedTimeMs=l.frameDecodedTimeMs+f}a(l)};let s,o,d=()=>{if(!(o===void 0||s===void 0&&m.volume===100)){if(s===void 0){let l=new AudioContext;s=l.createGain(),l.createMediaStreamSource(o).connect(s).connect(l.destination);let f=document.querySelector("#player audio");f&&(f.muted=!0)}s.gain.value=m.volume/100}};n.addEventListener("track",l=>{l.track.kind==="audio"&&(o=l.streams[0],d())});let u=new V(n),h,g=()=>{let l=t.getVideoElement();if(l!==void 0){if(!X(m)){h?.pause();return}if(h===void 0)try{h=new H(l,m)}catch(f){DDCloud.log(`effects unavailable: ${String(f).slice(0,200)}`)}else h.resume(m)}};window.__ddApply=l=>{m=JSON.parse(l),window.__ddRumble=m.rumble/100,u.show(m.statsLevel,m.statsLayout==="vertical",m.server,m.statsCorner==="left"),d(),g()},n.addEventListener("connectionstatechange",()=>{n.connectionState==="connected"?(x(""),DDCloud.playing(),u.show(m.statsLevel,m.statsLayout==="vertical",m.server,m.statsCorner==="left"),setTimeout(g,500)):n.connectionState==="failed"?DDCloud.failed("webrtc","connection failed"):n.connectionState==="disconnected"&&x("Connection lost\u2026")});let p=await t.createOffer(),c=k("sdp",await C("sdp",p.sdp??"")),O=JSON.parse(JSON.parse(c.body).exchangeResponse);if(O.status&&O.status!=="success")throw new T("sdp-answer",JSON.stringify(O).slice(0,300));await t.setRemoteSDP(O.sdp),await oe(n,3e3);let Y=(await t.getICECandidates()).map(l=>JSON.stringify({candidate:l.candidate,sdpMid:l.sdpMid,sdpMLineIndex:l.sdpMLineIndex,usernameFragment:l.usernameFragment})),$=k("ice",await C("ice",Y));t.setRemoteIceCandidates(JSON.parse(JSON.parse($.body).exchangeResponse));let q=Date.now();for(;!t._channels.message._handshakeCompleted&&Date.now()-q<2e4;)await B(100);DDCloud.log(`handshake ${t._channels.message._handshakeCompleted?"done":"missing"} after ${Date.now()-q} ms`),await B(300);let b=new v(0,{enable_keyboard:!1});b.attach(t),DDCloud.log(`controllers at attach: ${Array.from(navigator.getGamepads()).filter(Boolean).length}`),window.__ddNexus=()=>{b.sendButtonState("Nexus",1),setTimeout(()=>b.sendButtonState("Nexus",0),200)},window.__ddButton=(l,f)=>b.sendButtonState(l,f),window.__ddInput=l=>{l?b.onWindowFocus():b.onWindowBlur()},setInterval(()=>{C("keepalive")},3e4),window.__ddStop=()=>{try{h?.destroy(),u.destroy(),b.detach(),t.destroy()}catch{}}}de().catch(r=>{r instanceof T?DDCloud.failed(r.stage,r.detail):DDCloud.failed("page",String(r).slice(0,300))});})();
+47 -27
View File
@@ -1,45 +1,65 @@
# The app's native side: the Wayland compositor the session presents into, the adrenotools loader
# it opens Turnip with, and one small library for the fake-input ring's memory fence.
cmake_minimum_required(VERSION 3.22.1)
project(steamdeck C CXX)
project(droiddeck C CXX)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration")
# Every library the app builds is linked with 16 KB-aligned LOAD segments. A 16 KB-page kernel
# (Android 15+ devices) refuses to load a 4 KB-only ELF; a 4 KB kernel loads a 16 KB-aligned one
# unchanged (the alignment is a multiple of its page), so this costs nothing on today's devices
# but a little padding in the file. Prebuilt libraries under jniLibs/ carry their own alignment.
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
add_subdirectory(adrenotools)
# DXVK's DXBC->SPIR-V translator (zlib licence), vendored under thirdparty/dxbc from the same
# subset upstream lsfg-vk uses. Native LSFG frame generation needs it: the Lossless Scaling
# shader chain is DXBC in every build downloadable from Steam.
add_subdirectory(thirdparty/dxbc)
# The Win-FG chain self-identifies by two halves: the upstream tag it was copied from (hand-set,
# UPDATE IT when re-copying) and a hash over the sources themselves, computed here, which cannot
# rot. The compositor's frame-generation leg (waylandcomp/CMakeLists.txt) stamps both in.
set(WINFG_UPSTREAM "v0.3.0")
set(WINFG_CHAIN_SRC
winlator/winfg/framegen.cpp
winlator/winfg/framegen.hpp
winlator/winfg/record_impl.inc
winlator/winfg/config.hpp
winlator/winfg/embedded_shaders.hpp
winlator/winfg/winfg_engine.cpp
set(DDFG_UPSTREAM "v0.3.0")
set(DDFG_CHAIN_SRC
framegen/ddfg/framegen.cpp
framegen/ddfg/framegen.hpp
framegen/ddfg/record_impl.inc
framegen/ddfg/config.hpp
framegen/ddfg/embedded_shaders.hpp
framegen/ddfg/ddfg_engine.cpp
)
set(WINFG_HASH_INPUT "")
foreach(f ${WINFG_CHAIN_SRC})
set(DDFG_HASH_INPUT "")
foreach(f ${DDFG_CHAIN_SRC})
file(SHA256 "${CMAKE_CURRENT_SOURCE_DIR}/${f}" _h)
string(APPEND WINFG_HASH_INPUT "${_h}")
string(APPEND DDFG_HASH_INPUT "${_h}")
endforeach()
string(SHA256 WINFG_CHAIN_HASH_FULL "${WINFG_HASH_INPUT}")
string(SUBSTRING "${WINFG_CHAIN_HASH_FULL}" 0 12 WINFG_CHAIN_HASH)
message(STATUS "win-fg chain: ${WINFG_UPSTREAM} (src ${WINFG_CHAIN_HASH})")
string(SHA256 DDFG_CHAIN_HASH_FULL "${DDFG_HASH_INPUT}")
string(SUBSTRING "${DDFG_CHAIN_HASH_FULL}" 0 12 DDFG_CHAIN_HASH)
message(STATUS "dd-fg chain: win-fg ${DDFG_UPSTREAM} (src ${DDFG_CHAIN_HASH})")
add_subdirectory(waylandcomp)
add_subdirectory(questxr)
add_library(sdnative SHARED sdnative/sdnative.c)
target_link_libraries(sdnative log)
add_library(deviceinfo SHARED deviceinfo/vkinfo.c)
target_link_libraries(deviceinfo dl)
add_library(gpuclock SHARED gpuclock/kgsl_clock.c gpuclock/kgsl_busy.c)
add_library(termux SHARED termux/termux.c)
add_library(ddvenc SHARED venc/venc_server.cpp)
target_compile_features(ddvenc PRIVATE cxx_std_17)
target_link_libraries(ddvenc mediandk log)
if(DROIDDECK_VITURE_SDK_DIR)
add_library(droiddeck_viture SHARED viture/sdk_bridge.cpp)
target_compile_features(droiddeck_viture PRIVATE cxx_std_17)
target_include_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/include")
target_link_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/android/${ANDROID_ABI}")
target_link_libraries(droiddeck_viture PRIVATE glasses log)
endif()
if(DROIDDECK_UVC_DIR)
add_library(rgb_usb SHARED IMPORTED)
set_target_properties(rgb_usb PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libusb.so")
add_library(rgb_uvc STATIC IMPORTED)
set_target_properties(rgb_uvc PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libuvc.a")
add_library(droiddeck_uvc SHARED viture/uvc_bridge.cpp)
target_compile_features(droiddeck_uvc PRIVATE cxx_std_17)
target_include_directories(droiddeck_uvc PRIVATE "${DROIDDECK_UVC_DIR}/include")
target_link_libraries(droiddeck_uvc PRIVATE rgb_uvc rgb_usb log)
target_link_options(droiddeck_uvc PRIVATE -Wl,-z,max-page-size=16384)
endif()
@@ -3,7 +3,6 @@
aarch64-linux-gnu-gcc -c src/bcenabler_patch.s -o /tmp/patch.o
aarch64-linux-gnu-objcopy -O binary -j .text /tmp/patch.o /tmp/patch.bin
# Must be smaller than a single page
bin2c -o gen/bcenabler_patch.h -n PatchRawData /tmp/patch.bin
rm /tmp/patch.o
@@ -1,6 +1,4 @@
/* Generated by bin2c, do not edit manually */
/* Contents of file /tmp/patch.bin */
const long int PatchRawData_size = 264;
const unsigned char PatchRawData[264] = {
0x1F, 0x0C, 0x02, 0x71, 0x61, 0x00, 0x00, 0x54, 0xE0, 0x08, 0x80, 0x52, 0x3E, 0x00, 0x00, 0x14,
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -11,29 +12,14 @@ extern "C" {
#include <stdint.h>
/**
* @brief Describes the level of BCeNabler support for a driver
*/
enum adrenotools_bcn_type {
ADRENOTOOLS_BCN_INCOMPATIBLE, //!< Driver isn't supported by BCeNabler
ADRENOTOOLS_BCN_BLOB, //!< Driver already supports BCn textures so BCeNabler isn't necessary
ADRENOTOOLS_BCN_PATCH //!< Driver can be patched with BCeNabler to support BCn textures
ADRENOTOOLS_BCN_INCOMPATIBLE,
ADRENOTOOLS_BCN_BLOB,
ADRENOTOOLS_BCN_PATCH
};
/**
* @brief Checks the status of BCn support in the supplied driver
* @param major The major part of VkPhysicalDeviceProperties::driverVersion
* @param minor The minor part of VkPhysicalDeviceProperties::driverVersion
* @param vendorId VkPhysicalDeviceProperties::vendorId
*/
enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t minor, uint32_t vendorId);
/**
* @brief Patches the Adreno graphics driver to enable support for BCn compressed formats
* @note adrenotools_get_bcn_type MUST be checked to equal ADRENOTOOLS_BCN_PATCH before calling this
* @param vkGetPhysicalDeviceFormatPropertiesFn A pointer to vkGetPhysicalDeviceFormatProperties obtained through vkGetInstanceProcAddr. This is used to find the correct function to patch
* @return If the patching succeeded, if false the driver will be in an undefined state
*/
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn);
#ifdef __cplusplus
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -10,60 +11,16 @@ extern "C" {
#include <stdbool.h>
#include "priv.h"
/**
* @brief Opens a new libvulkan.so instance according to `flags`
* @remark IMPORTANT: The app MUST be packaged w/ useLegacyPackaging = true. The reason for this is
* that we install the hook in hookLibDir; but this folder won't be set correctly when useLegacyPackaging == false
* because Android expects to read the *.so directly from the apk (instead of decompressing them into nativeLibraryDir).
* @param dlopenMode The dlopen mode to use when opening libvulkan
* @param featureFlags Which adrenotools driver features to enable
* @param tmpLibDir A writable directory to hold patched libraries, only used on api < 29 due to the lack of memfd support. If nullptr is passed and the API version is < 29 memfd usage will be attempted and if unsupported nullptr will be returned
* @param hookLibDir The directory holding the built hooks.
* IMPORTANT: This path MUST point to whatever `getApplicationInfo().nativeLibraryDir` (Kotlin code) returns.
* Failing to do so will result in this function returning a valid ptr but then the hook will fail and either the original driver is loaded as fallback, or vkEnumeratePhysicalDevices returns 0 devices.
* @param customDriverDir The directory to load a custom GPU driver named according to `customDriverName` from. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`.
* IMPORTANT: This path MUST NOT be on sdcard/storage. i.e. use ANativeActivity::internalDataPath and often looks like "/data/user/0/com.example.app/files/".
* Otherwise you will get permission denied because dlopen() can't be done on *.so libraries that any random user or application can manipulate (which would be a security risk).
* @param customDriverName The soname of the custom driver to load. Only used if ADRENOTOOLS_DRIVER_CUSTOM is set in `featureFlags`
* @param fileRedirectDir The directory which to redirect all file accesses performed by the driver to. Only used if ADRENOTOOLS_DRIVER_FILE_REDIRECT is set in `featureFlags`
* @param userMappingHandle A pointer to a void* which will be set to the mapping handle if ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT is set in `featureFlags`
*/
void *adrenotools_open_libvulkan(int dlopenMode, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle);
/**
* @brief Imports the given CPU mapped memory range into the GSL allocator. This should then be followed by a call to vkAllocateMemory with a matching size which will return a VkDeviceMemory view over the input region
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param hostPtr The host pointer to import
* @param size The size of the region to import
* @return True on success
*/
bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size);
/**
* @brief Maps GPU memory and imports it into the GSL allocator. This should then be followed by a call to adrenotools_mem_cpu_map to map the memory on the CPU, then finally vkAllocateMemory with a matching size
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param size Pointer to a variable containing the size of the region to import, will be updated to contain the required size of the region to allocate CPU side
* @return true on success
*/
bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size);
/**
* @brief Maps the last mapping allocated using adrenotools_mem_gpu_allocate into the given host memory region, such that vkAllocateMemory can then be called
* @param handle Mapping handle that was returned by adrenotools_open_libvulkan
* @param hostPtr A pointer to where the mapping should be mapped
* @param size The size of the mapping. MUST be equal to the size returned by adrenotools_mem_gpu_allocate
*/
bool adrenotools_mem_cpu_map(void *handle, void *hostPtr, uint64_t size);
/**
* @note This function should be called after adrenotools_open_libvulkan and Vulkan driver init to check if the mapping import hook loaded successfully
* @return True if the mapping was successfully imported (or initialization succeeded)
*/
bool adrenotools_validate_gpu_mapping(void *handle);
/**
* @brief Provides a way to force the GPU to run at the maximum possible clocks (thermal constraints will still be applied)
*/
void adrenotools_set_turbo(bool turbo);
#ifdef __cplusplus
@@ -1,13 +1,11 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#include <stdint.h>
/**
* @brief Bitfield enum of additional driver features that can be used with adrenotools_open_libvulkan
*/
enum {
ADRENOTOOLS_DRIVER_CUSTOM = 1 << 0,
ADRENOTOOLS_DRIVER_FILE_REDIRECT = 1 << 1,
@@ -16,12 +14,9 @@ enum {
#define ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC 0xDEADBEEF
/**
* @brief A replacement GPU memory mapping for use with ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT
*/
struct adrenotools_gpu_mapping {
void *host_ptr;
uint64_t gpu_addr; //!< The GPU address of the mapping to import, if mapping import/init succeeds this will be set to ADRENOTOOLS_GPU_MAPPING_SUCCEEDED_MAGIC
uint64_t gpu_addr;
uint64_t size;
uint64_t flags;
};
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <array>
#include <cstdio>
#include <cstdlib>
@@ -21,7 +22,6 @@ static loader_android_create_namespace_t loader_android_create_namespace;
static bool lib_loaded;
/* Public API */
bool linkernsbypass_load_status() {
return lib_loaded;
}
@@ -55,8 +55,6 @@ android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
android_link_namespaces_t android_link_namespaces;
bool linkernsbypass_link_namespace_to_default_all_libs(android_namespace_t *to) {
// Creating a shared namespace with the default parent will give a copy of the default namespace that we can actually access
// This is needed since there is no way to access a direct handle to the default namespace as it's not exported
static auto defaultNs{android_create_namespace_escape("default_copy", nullptr, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
return android_link_namespaces_all_libs(to, defaultNs);
}
@@ -81,7 +79,6 @@ void *linkernsbypass_namespace_dlopen(const char *filename, int flags, android_n
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, android_namespace_t *ns) {
static std::array<char, PATH_MAX> PathBuf{};
// Used as a unique ID for overwriting soname and creating target lib files
static uint16_t TargetId{};
int libTargetFd{[&] () {
@@ -89,7 +86,6 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
snprintf(PathBuf.data(), PathBuf.size(), "%s/%d_patched.so", libTargetDir, TargetId);
return open(PathBuf.data(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
} else {
// If memfd isn't supported errno will contain ENOSYS after calling
errno = 0;
int fd{static_cast<int>(syscall(__NR_memfd_create, libPath, 0))};
if (errno == ENOSYS || fd < 0)
@@ -101,21 +97,18 @@ void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *li
if (libTargetFd == -1)
return nullptr;
// Partially overwrite soname with 3 digits (replacing lib...) with to make sure a cached so isn't loaded
std::array<char, 3> sonameOverwrite{};
snprintf(sonameOverwrite.data(), sonameOverwrite.size(), "%03u", TargetId++);
if (!elf_soname_patch(libPath, libTargetFd, sonameOverwrite.data()))
return nullptr;
// Load our patched library into the hook namespace
android_dlextinfo hookExtInfo{
.flags = ANDROID_DLEXT_USE_NAMESPACE | ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = libTargetFd,
.library_namespace = ns
};
// Make a path that looks about right
snprintf(PathBuf.data(), PathBuf.size(), "/proc/self/fd/%d", libTargetFd);
return android_dlopen_ext(PathBuf.data(), flags, &hookExtInfo);
@@ -125,21 +118,19 @@ static void *align_ptr(void *ptr) {
return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) & ~(getpagesize() - 1));
}
/* Private */
__attribute__((constructor)) static void resolve_linker_symbols() {
using loader_dlopen_t = void *(*)(const char *, int, const void *);
if (android_get_device_api_level() < 28)
return;
// ARM64 specific function walking to locate the internal dlopen handler
auto loader_dlopen{[]() {
union BranchLinked {
uint32_t raw;
struct {
int32_t offset : 26; //!< 26-bit branch offset
uint8_t sig : 6; //!< 6-bit signature
int32_t offset : 26;
uint8_t sig : 6;
};
bool Verify() {
@@ -148,10 +139,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
};
static_assert(sizeof(BranchLinked) == 4, "BranchLinked is wrong size");
// Some devices ship with --X mapping for exexecutables so work around that
mprotect(align_ptr(reinterpret_cast<void *>(&dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
// dlopen is just a wrapper for __loader_dlopen that passes the return address as the third arg hence we can just walk it to find __loader_dlopen
auto blInstr{reinterpret_cast<BranchLinked *>(&dlopen)};
while (!blInstr->Verify())
blInstr++;
@@ -159,10 +148,8 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
return reinterpret_cast<loader_dlopen_t>(blInstr + blInstr->offset);
}()};
// Protect the loader_dlopen function to remove the BTI attribute (since this is an internal function that isn't intended to be jumped indirectly to)
mprotect(align_ptr(reinterpret_cast<void *>(&loader_dlopen)), getpagesize(), PROT_WRITE | PROT_READ | PROT_EXEC);
// Passing dlopen as a caller address tricks the linker into using the internal unrestricted namespace letting us access libraries that are normally forbidden in the classloader namespace imposed on apps
auto ldHandle{loader_dlopen("ld-android.so", RTLD_LAZY, reinterpret_cast<void *>(&dlopen))};
if (!ldHandle)
return;
@@ -187,6 +174,5 @@ __attribute__((constructor)) static void resolve_linker_symbols() {
if (!android_get_exported_namespace)
return;
// Lib is now safe to use
lib_loaded = true;
}
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -9,7 +10,6 @@ extern "C" {
#include <stdint.h>
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=25;bpv=1;bpt=1
enum {
ANDROID_NAMESPACE_TYPE_REGULAR = 0,
ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
@@ -19,14 +19,8 @@ enum {
ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
};
/**
* @brief Checks if linkernsbypass loaded successfully and is safe to use
* @note IMPORTANT: This should be called before any calls to the rest of the library are made
* @return true if loading succeeded
*/
bool linkernsbypass_load_status();
// https://cs.android.com/android/platform/superproject/+/0a492a4685377d41fef2b12e9af4ebfa6feef9c2:art/libnativeloader/include/nativeloader/dlext_namespaces.h;l=86;bpv=1;bpt=1
struct android_namespace_t *android_create_namespace(const char *name,
const char *ld_library_path,
const char *default_library_path,
@@ -41,39 +35,19 @@ struct android_namespace_t *android_create_namespace_escape(const char *name,
const char *permitted_when_isolated_path,
struct android_namespace_t *parent_namespace);
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=3554
typedef struct android_namespace_t *(*android_get_exported_namespace_t)(const char *);
extern android_get_exported_namespace_t android_get_exported_namespace;
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2499
typedef bool (*android_link_namespaces_all_libs_t)(struct android_namespace_t *, struct android_namespace_t *);
extern android_link_namespaces_all_libs_t android_link_namespaces_all_libs;
// https://cs.android.com/android/platform/superproject/+/dcb01ef31026b3b8aeb72dada3370af63fe66bbd:bionic/linker/linker.cpp;l=2473
typedef bool (*android_link_namespaces_t)(struct android_namespace_t *, struct android_namespace_t *, const char *);
extern android_link_namespaces_t android_link_namespaces;
/**
* @brief Like android_link_namespaces_all_libs but links from the default namespace
*/
bool linkernsbypass_link_namespace_to_default_all_libs(struct android_namespace_t *to);
/**
* @brief Loads a library into a namespace
* @note IMPORTANT: If `filename` is compiled with the '-z global' linker flag and RTLD_GLOBAL is supplied in `flags` the library will be added to the namespace's LD_PRELOAD list
* @param filename The name of the library to load
* @param flags The rtld flags for `filename`
* @param ns The namespace to dlopen into
*/
void *linkernsbypass_namespace_dlopen(const char *filename, int flags, struct android_namespace_t *ns);
/**
* @brief Force loads a unique instance of a library into a namespace
* @param libPath The path to the library to load with hooks applied
* @param libTargetDir A temporary directory to hold the soname patched library at `libPath`, will attempt to use memfd if nullptr
* @param flags The rtld flags for `libName`
* @param ns The namespace to dlopen into
*/
void *linkernsbypass_namespace_dlopen_unique(const char *libPath, const char *libTargetDir, int flags, struct android_namespace_t *ns);
#ifdef __cplusplus
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <initializer_list>
#include <cstdint>
#include <errno.h>
@@ -20,7 +21,6 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
if (ftruncate(targetFd, libStat.st_size) == -1)
return false;
// Map the memory so we can read our elf into it
auto mappedLib{reinterpret_cast<uint8_t *>(mmap(nullptr, libStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, targetFd, 0))};
if (!mappedLib)
return false;
@@ -29,30 +29,25 @@ bool elf_soname_patch(const char *libPath, int targetFd, const char *sonamePatch
if (libFd == false)
return false;
// Read lib elf into target file
if (read(libFd, mappedLib, libStat.st_size) != libStat.st_size)
return false;
// No longer needed
close(libFd);
auto eHdr{reinterpret_cast<ElfW(Ehdr) *>(mappedLib)};
auto sHdrEntries{reinterpret_cast<ElfW(Shdr) *>(mappedLib + eHdr->e_shoff)};
// Iterate over section headers to find the .dynamic section
for (ElfW(Half) i{}; i < eHdr->e_shnum; i++) {
auto &sHdr{sHdrEntries[i]};
if (sHdr.sh_type == SHT_DYNAMIC) {
auto strTab{reinterpret_cast<char *>(mappedLib + sHdrEntries[sHdr.sh_link].sh_offset)};
auto dynHdrEntries{reinterpret_cast<ElfW(Dyn) *>(mappedLib + sHdr.sh_offset)};
// Iterate over .dynamic entries to find DT_SONAME
for (ElfW(Xword) k{}; k < (sHdr.sh_size / sHdr.sh_entsize); k++) {
auto &dynHdrEntry{dynHdrEntries[k]};
if (dynHdrEntry.d_tag == DT_SONAME) {
char *soname{strTab + dynHdrEntry.d_un.d_val};
// Partially replace the old soname with the soname patch
size_t charIdx{};
for (; soname[charIdx] != 0 && sonamePatch[charIdx] != 0; charIdx++)
soname[charIdx] = sonamePatch[charIdx];
@@ -1,19 +1,13 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Overwrites a portion of the soname in an elf by loading it into shared memory and modifying .dynstr
* @note IMPORTANT: The supplied soname patch will overwrite the first strlen(sonamePatch) chars of the soname
* @param elfPath Full path to the elf to patch
* @param targetFd FD to use for storing the patched library
* @return True on success
*/
bool elf_soname_patch(const char *elfPath, int targetFd, const char *newSoname);
#ifdef __cplusplus
@@ -11,8 +11,3 @@ linkernsbypass_lib = static_library (
include_directories: ([linkernsbypass_inc, adrenotools_inc]),
dependencies: ([libandroid_dep, libdl_dep])
)
# linkernsbypass_include = static_library('linkernsbypass', sources)
# target_compile_options(['linkernsbypass', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['linkernsbypass', 'android', 'dl'])
# target_include_directories(['linkernsbypass', 'PUBLIC', '.'])
-15
View File
@@ -33,18 +33,3 @@ adrenotools_lib = shared_library(
)
subdir('src/hook')
# target_include_directories(['adrenotools', 'PUBLIC', 'include'])
# target_include_directories(['adrenotools', 'PRIVATE', '.'])
# target_compile_options(['adrenotools', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['adrenotools', 'android', 'linkernsbypass'])
# hooks_deb = subproject('src/hook')
# if build_shared_libs
# target_link_options(['adrenotools', 'PRIVATE', '-Wl,-s,--exclude-libs,liblinkernsbypass.a'])
# endif
# if gen_install_target
# configure_file(['adrenotools.pc.in', 'adrenotools.pc', '@ONLY'])
# install(['FILES', cmake_binary_dir, '/adrenotools.pc', 'DESTINATION', 'lib/pkgconfig'])
# install(['TARGETS', 'adrenotools', 'main_hook', 'file_redirect_hook', 'gsl_alloc_hook', 'hook_impl'])
# install(['DIRECTORY', 'include/', 'DESTINATION', 'include', 'FILES_MATCHING', 'PATTERN', '*.h*'])
# endif
+3 -18
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <fstream>
#include <string>
#include <cstring>
@@ -19,7 +20,6 @@ enum adrenotools_bcn_type adrenotools_get_bcn_type(uint32_t major, uint32_t mino
return ADRENOTOOLS_BCN_PATCH;
}
// Searches /proc/self/maps for the first free page after the given address
static void *find_free_page(uintptr_t address) {
std::ifstream procMaps("/proc/self/maps");
@@ -45,62 +45,48 @@ static void *align_ptr(void *ptr) {
bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
union Branch {
struct {
int32_t offset : 26; //!< 26-bit branch offset
uint8_t sig : 6; //!< 6-bit signature (0x25 for linked, 0x5 for jump)
int32_t offset : 26;
uint8_t sig : 6;
};
uint32_t raw{};
};
static_assert(sizeof(Branch) == 4, "Branch size is invalid");
// Find the nearest unmapped page where we can place patch code
void *patchPage{find_free_page(reinterpret_cast<uintptr_t>(vkGetPhysicalDeviceFormatPropertiesFn))};
if (!patchPage)
return false;
// Map patch region
void *ptr{mmap(patchPage, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0)};
if (ptr != patchPage)
return false;
// Allow reading from the blob's .text section since some devices enable ---X
// Protect two pages just in case we happen to land on a page boundary
if (mprotect(align_ptr(vkGetPhysicalDeviceFormatPropertiesFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
return false;
// First branch in this function is targeted at the function we want to patch
Branch *blInst{reinterpret_cast<Branch *>(vkGetPhysicalDeviceFormatPropertiesFn)};
constexpr uint8_t BranchLinkSignature{0x25};
// Search for first instruction with the BL signature
while (blInst->sig != BranchLinkSignature)
blInst++;
// Internal QGL format conversion function that we need to patch
uint32_t *convFormatFn{reinterpret_cast<uint32_t *>(blInst) + blInst->offset};
// See mprotect call above
// This time we also set PROT_WRITE so we can write our patch to the page
if (mprotect(align_ptr(convFormatFn), getpagesize() * 2, PROT_WRITE | PROT_READ | PROT_EXEC))
return false;
// This would normally set the default result to 0 (error) in the format not found case
constexpr uint32_t ClearResultSignature{0x2a1f03e0};
// We replace it with a branch to our own extended if statement which adds in the extra things for BCn
uint32_t *clearResultPtr{convFormatFn};
while (*clearResultPtr != ClearResultSignature)
clearResultPtr++;
// Ensure we don't write out of bounds
if (PatchRawData_size > getpagesize())
return false;
// Copy the patch function to our mapped page
memcpy(patchPage, PatchRawData, PatchRawData_size);
// Fixup the patch code so it correctly returns back to the driver after running
constexpr uint32_t PatchReturnFixupMagic{0xffffffff};
constexpr uint8_t BranchSignature{0x5};
@@ -130,6 +116,5 @@ bool adrenotools_patch_bcn(void *vkGetPhysicalDeviceFormatPropertiesFn) {
asm volatile("ISB");
// Done!
return true;
}
@@ -80,4 +80,4 @@
.L18:
mov w0, 0
.L3:
.word 0xffffffff // Branch fixup
.word 0xffffffff
+3 -13
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#include <string>
#include <string_view>
#include <sys/stat.h>
@@ -17,17 +18,14 @@
#include <unistd.h>
void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *tmpLibDir, const char *hookLibDir, const char *customDriverDir, const char *customDriverName, const char *fileRedirectDir, void **userMappingHandle) {
// Bail out if linkernsbypass failed to load, this probably means we're on api < 28
if (!linkernsbypass_load_status()) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Could not load linkernsbypass\n");
return nullptr;
}
// Use memfd on Q+ since it's guaranteed to work, but only if tmpLibDir wasn't set
if (android_get_device_api_level() >= 29 && !tmpLibDir)
tmpLibDir = nullptr;
// Verify that params for specific features are only passed if they are enabled
if (!(featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) && fileRedirectDir) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_FILE_REDIRECT present but no file redirect folder found\n");
return nullptr;
@@ -43,7 +41,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
return nullptr;
}
// Verify that params for enabled features are correct
struct stat buf{};
if (featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
@@ -58,7 +55,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
}
}
// Verify that params for enabled features are correct
if (featureFlags & ADRENOTOOLS_DRIVER_FILE_REDIRECT) {
if (!fileRedirectDir) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "ADRENOTOOLS_DRIVER_REDIRECT_DIR present but no folder parameter was found\n");
@@ -71,22 +67,18 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
}
}
// Create a namespace that can isolate our hook from the classloader namespace
auto hookNs{android_create_namespace("adrenotools-libvulkan", hookLibDir, nullptr, ANDROID_NAMESPACE_TYPE_SHARED, nullptr, nullptr)};
// Link it to the default namespace so the hook can use libandroid etc
if (!linkernsbypass_link_namespace_to_default_all_libs(hookNs)) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to hook our namespace to the default one\n");
return nullptr;
}
// Preload the hook implementation, otherwise we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, hookNs)};
if (!hookImpl) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't preload the hook implementation\n");
return nullptr;
}
// Pass parameters to the hook implementation
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
if (!initHookParam) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Couldn't init hook params\n");
@@ -95,7 +87,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
auto importMapping{[&]() -> adrenotools_gpu_mapping * {
if (featureFlags & ADRENOTOOLS_DRIVER_GPU_MAPPING_IMPORT) {
// This will be leaked, but it's not a big deal since it's only a few bytes
adrenotools_gpu_mapping *mapping{new adrenotools_gpu_mapping{}};
*userMappingHandle = mapping;
return mapping;
@@ -107,7 +98,6 @@ void *adrenotools_open_libvulkan(int dlopenFlags, int featureFlags, const char *
initHookParam(new HookImplParams(featureFlags, tmpLibDir, hookLibDir, customDriverDir, customDriverName, fileRedirectDir, importMapping));
// Load the libvulkan hook into the isolated namespace
if (!linkernsbypass_namespace_dlopen("libmain_hook.so", RTLD_GLOBAL, hookNs)) {
__android_log_print( ANDROID_LOG_ERROR, "adrenotools", "Failed to load libvulkan into the isolated namespace\n");
return nullptr;
@@ -150,7 +140,7 @@ bool adrenotools_import_user_mem(void *handle, void *hostPtr, uint64_t size) {
importMapping->host_ptr = hostPtr;
importMapping->gpu_addr = info.gpuaddr;
importMapping->size = size;
importMapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
importMapping->flags = 0xc2600;
close(kgslFd);
return true;
@@ -189,7 +179,7 @@ bool adrenotools_mem_gpu_allocate(void *handle, uint64_t *size) {
mapping->host_ptr = nullptr;
mapping->gpu_addr = info.gpuaddr;
mapping->size = *size;
mapping->flags = 0xc2600; //!< Unknown flags, but they are required for the mapping to work
mapping->flags = 0xc2600;
close(kgslFd);
return true;
@@ -15,7 +15,7 @@
#define TAG "hook_impl"
#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)
const HookImplParams *hook_params; //!< Bunch of info needed to load/patch the driver
const HookImplParams *hook_params;
int (*gsl_memory_alloc_pure_sym)(uint32_t, uint32_t, void *);
int (*gsl_memory_alloc_pure_64_sym)(uint64_t, uint32_t, void *);
int (*gsl_memory_free_pure_sym)(void *);
@@ -43,7 +43,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: filename: %s", filename);
// Ignore non-vulkan libraries
if (!strstr(filename, "vulkan."))
return android_dlopen_ext(filename, flags, extinfo);
@@ -52,7 +51,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
return fallback();
}
// customDriverDir will be empty if ADRENOTOOLS_DRIVER_CUSTOM isn't set therefore it's fine to have either way
auto driverNs{android_create_namespace(filename, hook_params->customDriverDir.c_str(),
hook_params->hookLibDir.c_str(), ANDROID_NAMESPACE_TYPE_SHARED,
nullptr, extinfo->library_namespace)};
@@ -61,16 +59,12 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
return fallback();
}
// We depend on libandroid which is unlikely to be in the supplied driver namespace so we have to link it over
android_link_namespaces(driverNs, nullptr, "libandroid.so");
// Preload ourself, a new instance will be created since we have different linker ancestory
// If we don't preload we get a weird issue where despite being in NEEDED of the hook lib the hook's symbols will overwrite ours and cause an infinite loop
auto hookImpl{linkernsbypass_namespace_dlopen("libhook_impl.so", RTLD_NOW, driverNs)};
if (!hookImpl)
return nullptr;
// Pass parameters to ourself
auto initHookParam{reinterpret_cast<void (*)(const void *)>(dlsym(hookImpl, "init_hook_param"))};
if (!initHookParam)
return nullptr;
@@ -86,7 +80,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: applied libfile_redirect_hook");
}
// Use our new namespace to load the vulkan driver
auto newExtinfo{*extinfo};
newExtinfo.library_namespace = driverNs;
@@ -122,8 +115,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
LOGI("hook_android_dlopen_ext: hook failed: failed to apply libgsl_alloc_hook!");
}
// TODO: If there is already an instance of a Vulkan driver loaded hooks won't be applied, this will only be the case for skiavk generally
// To fix this we would need to search /proc/self/maps for the file to a loaded instance of the library in order to read it to patch the soname and load it uniquely
if (hook_params->featureFlags & ADRENOTOOLS_DRIVER_CUSTOM) {
LOGI("hook_android_dlopen_ext: loading custom driver: %s%s", hook_params->customDriverDir.c_str(), hook_params->customDriverName.c_str());
void *handle{android_dlopen_ext(hook_params->customDriverName.c_str(), flags, &newExtinfo)};
@@ -142,7 +133,6 @@ __attribute__((visibility("default"))) void *hook_android_dlopen_ext(const char
__attribute__((visibility("default"))) void *hook_android_load_sphal_library(const char *filename, int flags) {
LOGI("hook_android_load_sphal_library: filename: %s", filename);
// https://android.googlesource.com/platform/system/core/+/master/libvndksupport/linker.cpp
for (const char *name : {"sphal", "vendor", "default"}) {
if (auto vendorNs{android_get_exported_namespace(name)}) {
android_dlextinfo dlextinfo{
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#ifdef __cplusplus
@@ -1,15 +1,12 @@
// SPDX-License-Identifier: BSD-2-Clause
// Copyright © 2021 Billy Laws
#pragma once
#include <string>
#include <adrenotools/priv.h>
/**
* @brief Holds the parameters needed for all hooks
* @note See comments for adrenotools_open_libvulkan as a reference for member variables
*/
struct HookImplParams {
int featureFlags;
std::string tmpLibDir;
File diff suppressed because it is too large. Load diff
@@ -1,32 +1,12 @@
hook_impl_files = files(['hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h'])
hook_impl_inc = include_directories('../../include')
# hook_impl_lib = shared_library('hook_impl', 'hook_impl.cpp', 'hook_impl.h', 'hook_impl_params.h')
# target_compile_options(['hook_impl', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_libraries(['hook_impl', 'linkernsbypass', 'log'])
# target_include_directories(['hook_impl', 'PRIVATE', '../../include'])
# set_target_properties(['hook_impl', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
main_hook_files = files(['main_hook.c'])
# main_hook_lib = shared_library('main_hook', 'main_hook.c')
# target_compile_options(['main_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['main_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['main_hook', 'hook_impl'])
# set_target_properties(['main_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
file_redirect_hook_files = files(['file_redirect_hook.c'])
# file_redirect_hook_lib = shared_library('file_redirect_hook', 'file_redirect_hook.c')
# target_compile_options(['file_redirect_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['file_redirect_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['file_redirect_hook', 'hook_impl'])
# set_target_properties(['file_redirect_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
gsl_alloc_hook_files = files(['gsl_alloc_hook.c'])
# gsl_alloc_hook_lib = shared_library('gsl_alloc_hook', 'gsl_alloc_hook.c')
# target_compile_options(['gsl_alloc_hook', 'PRIVATE', '-Wall', '-Wextra'])
# target_link_options(['gsl_alloc_hook', 'PRIVATE', '-z', 'global'])
# target_link_libraries(['gsl_alloc_hook', 'hook_impl'])
# set_target_properties(['gsl_alloc_hook', 'PROPERTIES', 'CXX_VISIBILITY_PRESET', 'hidden'])
hook_impl_lib = shared_library(
'hook_impl',
@@ -5,7 +5,6 @@
thread_local int satisfy_driver_emutls;
// Check libllvm-glnext.so in older drivers for a full list of these, newer ones are slightly more awkward due to the split
enum class OptionType : uint32_t {
ArgsStringList = 1,
QcArgsString = 4,
@@ -21,7 +20,6 @@ struct StringListVal {
struct Option
{
OptionType type;
// PAD
union {
bool boolVal;
int intVal;
@@ -49,7 +47,6 @@ int CompileHook(void *state, OptionsSet *opts) {
Option replacementOpts[ReplacementOptsNum];
uint32_t patchedOptNum{};
// Insert your desired args here - results will be written to stderr
const char *argv[] = {"-help", "-help-hidden", "", NULL };
StringListVal listVal{
.len = 3,
@@ -83,7 +80,6 @@ int CompileHook(void *state, OptionsSet *opts) {
struct LlvmInterface {
uint32_t unk0[3];
// PAD
void *makeContext;
void *delContext;
decltype(CompileHook) *compile;
@@ -30,53 +30,31 @@
#ifndef __QTIGRALLOCDEFS_H__
#define __QTIGRALLOCDEFS_H__
// From gralloc_priv.h
/* Gralloc usage bits indicating the type of allocation that should be used */
/* Refer to BufferUsage in hardware/interfaces/graphics/common/<ver>/types.hal */
/* The bits below are in officially defined vendor space
* i.e bits 28-31 and 48-63*/
/* Non linear, Universal Bandwidth Compression */
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC (UINT32_C(1) << 28)
/* Set this for allocating uncached memory (using O_DSYNC),
* cannot be used with noncontiguous heaps */
#define GRALLOC_USAGE_PRIVATE_UNCACHED (UINT32_C(1) << 29)
/* This flag is used to indicate 10 bit format.
* When both GRALLOC_USAGE_PRIVATE_ALLOC_UBWC & GRALLOC_USAGE_PRIVATE_10BIT
* are set then it will indicate UBWC_TP10 format.
* When only GRALLOC_USAGE_PRIVATE_10BIT is set it will indicate linear P010 format.
*/
#define GRALLOC_USAGE_PRIVATE_10BIT (UINT32_C(1) << 30)
/* This flag is used for SECURE display usecase */
#define GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY (UINT32_C(1) << 31)
/* This flag is used to indicate video NV21 format */
#define GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER 1ULL << 48
/* unused legacy flags */
#define GRALLOC_USAGE_PRIVATE_MM_HEAP 0
#define GRALLOC_USAGE_PRIVATE_IOMMU_HEAP 0
/* This flag is set for WFD usecase */
#define GRALLOC_USAGE_PRIVATE_WFD 1ULL << 51
/* This flag is set for HEIF usecase */
#define GRALLOC_USAGE_PRIVATE_HEIF (UINT32_C(1) << 27)
/* TODO(user): Remove when clients stop referencing this flag */
#define GRALLOC_USAGE_PRIVATE_10BIT_TP 0
/* This flag indicates PI format is being used */
#define GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI 1ULL << 49
/* This flag is set while CDSP accesses the buffer */
#define GRALLOC_USAGE_PRIVATE_CDSP 1ULL << 50
// OEM specific HAL formats
#define HAL_PIXEL_FORMAT_RGBA_5551 6
#define HAL_PIXEL_FORMAT_RGBA_4444 7
#define HAL_PIXEL_FORMAT_NV12_ENCODEABLE 0x102
@@ -98,7 +76,6 @@
#define HAL_PIXEL_FORMAT_RAW8 0x123
#define HAL_PIXEL_FORMAT_NV12_HEIF 0x116
// 10 bit
#define HAL_PIXEL_FORMAT_ARGB_2101010 0x117
#define HAL_PIXEL_FORMAT_RGBX_1010102 0x118
#define HAL_PIXEL_FORMAT_XRGB_2101010 0x119
@@ -106,7 +83,7 @@
#define HAL_PIXEL_FORMAT_ABGR_2101010 0x11B
#define HAL_PIXEL_FORMAT_BGRX_1010102 0x11C
#define HAL_PIXEL_FORMAT_XBGR_2101010 0x11D
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36 // HAL_PIXEL_FORMAT_YCBCR_P010
#define HAL_PIXEL_FORMAT_YCbCr_420_P010 0x36
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_UBWC 0x124
#define HAL_PIXEL_FORMAT_YCbCr_420_P010_VENUS 0x7FA30C0A
@@ -115,7 +92,6 @@
#define HAL_PIXEL_FORMAT_INTERLACE 0x180
// Camera utils format
#define HAL_PIXEL_FORMAT_NV12_LINEAR_FLEX 0x125
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX 0x126
#define HAL_PIXEL_FORMAT_MULTIPLANAR_FLEX 0x127
@@ -123,22 +99,16 @@
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_4_BATCH 0x129
#define HAL_PIXEL_FORMAT_NV12_UBWC_FLEX_8_BATCH 0x130
// FSC Formats
#define HAL_PIXEL_FORMAT_RGB888_UBWC_FSC 0x131
#define HAL_PIXEL_FORMAT_RGB101010_UBWC_FSC 0x132
// v4l2_fourcc('Y', 'U', 'Y', 'L'). 24 bpp YUYV 4:2:2 10 bit per component
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT 0x4C595559
// v4l2_fourcc('Y', 'B', 'W', 'C'). 10 bit per component. This compressed
// format reduces the memory access bandwidth
#define HAL_PIXEL_FORMAT_YCbCr_422_I_10BIT_COMPRESSED 0x43574259
// UBWC aligned Venus format
#define HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC 0x7FA30C06
#define HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC 0x7FA30C09
// Khronos ASTC formats
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1
#define HAL_PIXEL_FORMAT_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2
@@ -170,4 +140,4 @@
enum { BUFFER_TYPE_UI = 0, BUFFER_TYPE_VIDEO };
#endif //__QTIGRALLOCDEFS_H__
#endif
@@ -26,7 +26,6 @@
using namespace android::hardware;
using namespace android;
/* Simplified version of hidl::base::V1_0::IBase */
struct IBase : virtual public RefBase {
virtual bool isRemote() const;
@@ -80,8 +80,6 @@ AdrenoMemInfo::AdrenoMemInfo() {
ALOGE(" Failed to load libadreno_utils.so");
}
// Check if the overriding property debug.gralloc.gfx_ubwc_disable
// that disables UBWC allocations for the graphics stack is set
char property[PROPERTY_VALUE_MAX];
property_get(DISABLE_UBWC_PROP, property, "0");
if (!(strncmp(property, "1", PROPERTY_VALUE_MAX)) ||
@@ -123,16 +121,13 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
break;
}
int raster_mode = 0; // Adreno unknown raster mode.
int padding_threshold = 512; // Threshold for padding surfaces.
// the function below computes aligned width and aligned height
// based on linear or macro tile mode selected.
int raster_mode = 0;
int padding_threshold = 512;
if (LINK_adreno_compute_fmt_aligned_width_and_height) {
// We call into adreno_utils only for RGB formats. So plane_id is 0 and
// num_samples is 1 always. We may have to add uitility function to
// find out these if there is a need to call this API for YUV formats.
LINK_adreno_compute_fmt_aligned_width_and_height(
width, height, 0 /*plane_id*/, GetGpuPixelFormat(format), 1 /*num_samples*/, tile_enabled,
width, height, 0 , GetGpuPixelFormat(format), 1 , tile_enabled,
raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
reinterpret_cast<int*>(aligned_h));
} else if (LINK_adreno_compute_aligned_width_and_height) {
@@ -140,7 +135,7 @@ void AdrenoMemInfo::AlignUnCompressedRGB(int width, int height, int format, int
width, height, bpp, tile_enabled, raster_mode, padding_threshold,
reinterpret_cast<int*>(aligned_w), reinterpret_cast<int*>(aligned_h));
} else if (LINK_adreno_compute_padding) {
int surface_tile_height = 1; // Linear surface
int surface_tile_height = 1;
*aligned_w = UINT(LINK_adreno_compute_padding(width, bpp, surface_tile_height, raster_mode,
padding_threshold));
ALOGW("%s: Warning!! Old GFX API is used to calculate stride", __FUNCTION__);
@@ -157,9 +152,9 @@ void AdrenoMemInfo::AlignCompressedRGB(int width, int height, int format, unsign
unsigned int* aligned_h) {
if (LINK_adreno_compute_compressedfmt_aligned_width_and_height) {
int bytesPerPixel = 0;
int raster_mode = 0; // Adreno unknown raster mode.
int padding_threshold = 512; // Threshold for padding
// surfaces.
int raster_mode = 0;
int padding_threshold = 512;
LINK_adreno_compute_compressedfmt_aligned_width_and_height(
width, height, format, 0, raster_mode, padding_threshold, reinterpret_cast<int*>(aligned_w),
@@ -341,4 +336,4 @@ bool AdrenoMemInfo::IsPISupportedByGPU(int format, uint64_t usage) {
return false;
}
} // namespace gralloc
}
@@ -35,11 +35,10 @@
namespace gralloc {
// Adreno Pixel Formats
typedef enum {
ADRENO_PIXELFORMAT_UNKNOWN = 0,
ADRENO_PIXELFORMAT_R16G16B16A16_FLOAT = 10,
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24, // Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R10G10B10A2_UNORM = 24,
ADRENO_PIXELFORMAT_R8G8B8A8 = 28,
ADRENO_PIXELFORMAT_R8G8B8A8_SRGB = 29,
ADRENO_PIXELFORMAT_D32_FLOAT = 40,
@@ -55,140 +54,87 @@ typedef enum {
ADRENO_PIXELFORMAT_P010 = 104,
ADRENO_PIXELFORMAT_YUY2 = 107,
ADRENO_PIXELFORMAT_B4G4R4A4 = 115,
ADRENO_PIXELFORMAT_NV12_EXT = 506, // NV12 with non-std alignment and offsets
ADRENO_PIXELFORMAT_R8G8B8X8 = 507, // GL_RGB8 (Internal)
ADRENO_PIXELFORMAT_R8G8B8 = 508, // GL_RGB8
ADRENO_PIXELFORMAT_A1B5G5R5 = 519, // GL_RGB5_A1
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520, // GL_SRGB8
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521, // GL_SRGB8
ADRENO_PIXELFORMAT_NV12_EXT = 506,
ADRENO_PIXELFORMAT_R8G8B8X8 = 507,
ADRENO_PIXELFORMAT_R8G8B8 = 508,
ADRENO_PIXELFORMAT_A1B5G5R5 = 519,
ADRENO_PIXELFORMAT_R8G8B8X8_SRGB = 520,
ADRENO_PIXELFORMAT_R8G8B8_SRGB = 521,
ADRENO_PIXELFORMAT_A2B10G10R10_UNORM = 532,
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R10G10B10X2_UNORM = 537,
ADRENO_PIXELFORMAT_D24_UNORM_X8_UINT = 548,
ADRENO_PIXELFORMAT_D24_UNORM = 549,
ADRENO_PIXELFORMAT_D32_FLOAT_X24S8_UINT = 551,
ADRENO_PIXELFORMAT_S8_UINT = 552,
ADRENO_PIXELFORMAT_ASTC_4X4 = 568, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X4 = 569, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X5 = 570, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X5 = 571, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X6 = 572, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X5 = 573, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X6 = 574, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X8 = 575, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X5 = 576, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X6 = 577, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X8 = 578, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X10 = 579, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X10 = 580, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X12 = 581, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594, // ASTC Compressed
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595, // ASTC Compressed
// Vertex, Normalized GL_UNSIGNED_INT_10_10_10_2_OES
ADRENO_PIXELFORMAT_R5G6B5 = 610, // RGBA version of B5G6R5
ADRENO_PIXELFORMAT_R5G5B5A1 = 611, // RGBA version of B5G5R5A1
ADRENO_PIXELFORMAT_R4G4B4A4 = 612, // RGBA version of B4G4R4A4
ADRENO_PIXELFORMAT_UYVY = 614, // YUV 4:2:2 packed progressive (1 plane)
ADRENO_PIXELFORMAT_ASTC_4X4 = 568,
ADRENO_PIXELFORMAT_ASTC_5X4 = 569,
ADRENO_PIXELFORMAT_ASTC_5X5 = 570,
ADRENO_PIXELFORMAT_ASTC_6X5 = 571,
ADRENO_PIXELFORMAT_ASTC_6X6 = 572,
ADRENO_PIXELFORMAT_ASTC_8X5 = 573,
ADRENO_PIXELFORMAT_ASTC_8X6 = 574,
ADRENO_PIXELFORMAT_ASTC_8X8 = 575,
ADRENO_PIXELFORMAT_ASTC_10X5 = 576,
ADRENO_PIXELFORMAT_ASTC_10X6 = 577,
ADRENO_PIXELFORMAT_ASTC_10X8 = 578,
ADRENO_PIXELFORMAT_ASTC_10X10 = 579,
ADRENO_PIXELFORMAT_ASTC_12X10 = 580,
ADRENO_PIXELFORMAT_ASTC_12X12 = 581,
ADRENO_PIXELFORMAT_ASTC_4X4_SRGB = 582,
ADRENO_PIXELFORMAT_ASTC_5X4_SRGB = 583,
ADRENO_PIXELFORMAT_ASTC_5X5_SRGB = 584,
ADRENO_PIXELFORMAT_ASTC_6X5_SRGB = 585,
ADRENO_PIXELFORMAT_ASTC_6X6_SRGB = 586,
ADRENO_PIXELFORMAT_ASTC_8X5_SRGB = 587,
ADRENO_PIXELFORMAT_ASTC_8X6_SRGB = 588,
ADRENO_PIXELFORMAT_ASTC_8X8_SRGB = 589,
ADRENO_PIXELFORMAT_ASTC_10X5_SRGB = 590,
ADRENO_PIXELFORMAT_ASTC_10X6_SRGB = 591,
ADRENO_PIXELFORMAT_ASTC_10X8_SRGB = 592,
ADRENO_PIXELFORMAT_ASTC_10X10_SRGB = 593,
ADRENO_PIXELFORMAT_ASTC_12X10_SRGB = 594,
ADRENO_PIXELFORMAT_ASTC_12X12_SRGB = 595,
ADRENO_PIXELFORMAT_R5G6B5 = 610,
ADRENO_PIXELFORMAT_R5G5B5A1 = 611,
ADRENO_PIXELFORMAT_R4G4B4A4 = 612,
ADRENO_PIXELFORMAT_UYVY = 614,
ADRENO_PIXELFORMAT_NV21 = 619,
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620, // YUV 4:4:4 packed (1 plane)
ADRENO_PIXELFORMAT_Y8 = 625, // Single 8-bit luma only channel YUV format
ADRENO_PIXELFORMAT_TP10 = 654, // YUV 4:2:0 planar 10 bits/comp (2 planes)
ADRENO_PIXELFORMAT_Y8U8V8A8 = 620,
ADRENO_PIXELFORMAT_Y8 = 625,
ADRENO_PIXELFORMAT_TP10 = 654,
} ADRENOPIXELFORMAT;
typedef enum {
SURFACE_TILE_MODE_DISABLE = 0x0, // used for linear surface
SURFACE_TILE_MODE_ENABLE = 0x1 // used for tiled surface
SURFACE_TILE_MODE_DISABLE = 0x0,
SURFACE_TILE_MODE_ENABLE = 0x1
} surface_tile_mode_t;
class AdrenoMemInfo {
public:
/*
* Function to compute aligned width and aligned height based on
* width, height, format and usage flags.
*
* @return aligned width, aligned height
*/
void GetAlignedWidthAndHeight(int width, int height, int format, int usage,
unsigned int* aligned_w, unsigned int* aligned_h, bool ubwc_enabled,
bool tile_enabled);
/*
* Function to compute the adreno aligned width and aligned height
* based on the width and format.
*
* @return aligned width, aligned height
*/
void AlignUnCompressedRGB(int width, int height, int format, int tileEnabled,
unsigned int* aligned_w, unsigned int* aligned_h);
/*
* Function to compute the adreno aligned width and aligned height
* based on the width and format.
*
* @return aligned width, aligned height
*/
void AlignCompressedRGB(int width, int height, int format, unsigned int* aligned_w,
unsigned int* aligned_h);
/*
* Function to compute the pixel alignment requirement.
*
* @return alignment
*/
uint32_t GetGpuPixelAlignment();
/*
* Function to query whether GPU supports UBWC for given HAL format
* @return > 0 : supported
* 0 : not supported
*/
bool IsUBWCSupportedByGPU(int format);
/*
* Function to check if GPU supports PI or not
*/
bool IsPISupportedByGPU(int format, uint64_t usage);
/*
* Function to get the corresponding Adreno format for given HAL format
*/
ADRENOPIXELFORMAT GetGpuPixelFormat(int hal_format);
/*
* Function to get graphics metadata blob size
* @return graphics metadata size
*/
uint32_t AdrenoGetMetadataBlobSize();
/*
* Function to populate the graphics metadata blob
* @return 1 : Successful
* 2 : Unsuccessful
*/
int AdrenoInitMemoryLayout(void* metadata_blob, int width, int height, int depth, int format,
int num_samples, int isUBWC, uint64_t usage, uint32_t num_planes);
/*
* Function to get buffer size for based on graphcis metadata
* @return buffer size
*/
uint32_t AdrenoGetAlignedGpuBufferSize(void* metadata_blob);
/*
* Function to check if adreno size calculation APIs are avaliable
* @return true : Avaliable
* false : Unavaliable
*/
bool AdrenoSizeAPIAvaliable();
static AdrenoMemInfo* GetInstance();
@@ -196,7 +142,6 @@ class AdrenoMemInfo {
private:
AdrenoMemInfo();
~AdrenoMemInfo();
// link(s)to adreno surface padding library.
int (*LINK_adreno_compute_padding)(int width, int bpp, int surface_tile_height,
int screen_tile_height, int padding_threshold) = NULL;
void (*LINK_adreno_compute_aligned_width_and_height)(int width, int height, int bpp,
@@ -229,6 +174,6 @@ class AdrenoMemInfo {
static AdrenoMemInfo* s_instance;
};
} // namespace gralloc
}
#endif // __GR_ADRENO_INFO_H__
#endif
@@ -57,29 +57,27 @@ struct private_handle_t {
PRIV_FLAGS_CAMERA_READ = 0x00040000,
PRIV_FLAGS_HW_COMPOSER = 0x00080000,
PRIV_FLAGS_HW_TEXTURE = 0x00100000,
PRIV_FLAGS_ITU_R_601 = 0x00200000, // Unused from display
PRIV_FLAGS_ITU_R_601_FR = 0x00400000, // Unused from display
PRIV_FLAGS_ITU_R_709 = 0x00800000, // Unused from display
PRIV_FLAGS_ITU_R_601 = 0x00200000,
PRIV_FLAGS_ITU_R_601_FR = 0x00400000,
PRIV_FLAGS_ITU_R_709 = 0x00800000,
PRIV_FLAGS_SECURE_DISPLAY = 0x01000000,
PRIV_FLAGS_TILE_RENDERED = 0x02000000,
PRIV_FLAGS_CPU_RENDERED = 0x04000000,
PRIV_FLAGS_UBWC_ALIGNED = 0x08000000,
PRIV_FLAGS_DISP_CONSUMER = 0x10000000,
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000, // Ion buffer allocated outside of gralloc
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000, // PI format
PRIV_FLAGS_CLIENT_ALLOCATED = 0x20000000,
PRIV_FLAGS_UBWC_ALIGNED_PI = 0x40000000,
};
// file-descriptors dup'd over IPC
int fd;
int fd_metadata;
// values sent over IPC
int magic;
int flags;
int width; // holds width of the actual buffer allocated
int height; // holds height of the actual buffer allocated
int unaligned_width; // holds width client asked to allocate
int unaligned_height; // holds height client asked to allocate
int width;
int height;
int unaligned_width;
int unaligned_height;
int format;
int buffer_type;
unsigned int layer_count;
@@ -126,7 +124,6 @@ struct private_handle_t {
numFds = kNumFds;
}
// Legacy constructor used by some clients
private_handle_t(int fd, unsigned int size, int usage, int buf_type, int format, int w, int h)
: private_handle_t(fd, -1, PRIV_FLAGS_CLIENT_ALLOCATED, w, h, 0, 0, format, buf_type, size,
static_cast<uint64_t>(usage)) {}
@@ -176,7 +173,6 @@ struct private_handle_t {
unsigned int GetLayerCount() const { return layer_count; }
int GetStride() const {
// In handle we currently store aligned width after allocation.
return width;
}
@@ -187,4 +183,4 @@ struct private_handle_t {
};
#pragma pack(pop)
#endif // __GR_PRIV_HANDLE_H__
#endif
@@ -162,7 +162,6 @@ bool CpuCanRead(uint64_t usage) {
bool CpuCanWrite(uint64_t usage) {
if (usage & BufferUsage::CPU_WRITE_MASK) {
// Application intends to use CPU for rendering
return true;
}
@@ -177,7 +176,6 @@ uint32_t GetDataAlignment(int format, uint64_t usage) {
if (usage & BufferUsage::PROTECTED) {
if ((usage & BufferUsage::CAMERA_OUTPUT) || (usage & GRALLOC_USAGE_PRIVATE_SECURE_DISPLAY)) {
// The alignment here reflects qsee mmu V7L/V8L requirement
align = SZ_2M;
} else {
align = SECURE_ALIGN;
@@ -202,7 +200,6 @@ bool IsGPUFlagSupported(uint64_t usage) {
return ret;
}
// Returns the final buffer size meant to be allocated with ion
unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int alignedh) {
unsigned int size = 0;
int format = info.format;
@@ -223,7 +220,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
} else if (IsCompressedRGBFormat(format)) {
size = alignedw * alignedh * ASTC_BLOCK_SIZE;
} else {
// Below switch should be for only YUV/custom formats
switch (format) {
case HAL_PIXEL_FORMAT_RAW16:
case HAL_PIXEL_FORMAT_Y16:
@@ -237,15 +233,15 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
case HAL_PIXEL_FORMAT_Y8:
size = alignedw * alignedh * 1;
break;
// adreno formats
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO: // NV21
case HAL_PIXEL_FORMAT_YCrCb_420_SP_ADRENO:
size = ALIGN(alignedw * alignedh, SIZE_4K);
size += (unsigned int)ALIGN(2 * ALIGN(width / 2, 32) * ALIGN(height / 2, 32), SIZE_4K);
break;
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED: // NV12
// The chroma plane is subsampled,
// but the pitch in bytes is unchanged
// The GPU needs 4K alignment, but the video decoder needs 8K
case HAL_PIXEL_FORMAT_YCbCr_420_SP_TILED:
size = ALIGN(alignedw * alignedh, SIZE_8K);
size += ALIGN(alignedw * (unsigned int)ALIGN(height / 2, 32), SIZE_8K);
break;
@@ -296,7 +292,6 @@ unsigned int GetSize(const BufferInfo& info, unsigned int alignedw, unsigned int
return size;
}
// Explicitly defined UBWC formats
bool IsUBwcFormat(int format) {
switch (format) {
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
@@ -309,7 +304,6 @@ bool IsUBwcFormat(int format) {
}
bool IsUBwcSupported(int format) {
// Existing HAL formats with UBWC support
switch (format) {
case HAL_PIXEL_FORMAT_BGR_565:
case HAL_PIXEL_FORMAT_RGBA_8888:
@@ -336,7 +330,6 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
return false;
}
// As of now only two formats
switch (format) {
case HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC:
case HAL_PIXEL_FORMAT_YCbCr_420_TP10_UBWC: {
@@ -354,27 +347,21 @@ bool IsUBwcPISupported(int format, uint64_t usage) {
}
bool IsUBwcEnabled(int format, uint64_t usage) {
// Allow UBWC, if client is using an explicitly defined UBWC pixel format.
if (IsUBwcFormat(format)) {
return true;
}
// Allow UBWC, if an OpenGL client sets UBWC usage flag and GPU plus MDP
// support the format. OR if a non-OpenGL client like Rotator, sets UBWC
// usage flag and MDP supports the format.
if (((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC) ||
(usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) ||
(usage & BufferUsage::COMPOSER_CLIENT_TARGET)) &&
IsUBwcSupported(format)) {
bool enable = true;
// Query GPU for UBWC only if buffer is intended to be used by GPU.
if ((usage & BufferUsage::GPU_TEXTURE) || (usage & BufferUsage::GPU_RENDER_TARGET)) {
if (AdrenoMemInfo::GetInstance()) {
enable = AdrenoMemInfo::GetInstance()->IsUBWCSupportedByGPU(format);
}
}
// Allow UBWC, only if CPU usage flags are not set
if (enable && !(CpuCanAccess(usage))) {
return true;
}
@@ -418,13 +405,10 @@ unsigned int GetRgbUBwcMetaBufferSize(int width, int height, uint32_t bpp) {
return size;
}
// Align meta buffer height to 16 blocks
meta_height = ALIGN(((height + block_height - 1) / block_height), 16);
// Align meta buffer width to 64 blocks
meta_width = ALIGN(((width + block_width - 1) / block_width), 64);
// Align meta buffer size to 4K
size = (unsigned int)ALIGN((meta_width * meta_height), 4096);
return size;
@@ -500,12 +484,10 @@ bool UseUncached(int format, uint64_t usage) {
return true;
}
// CPU read rarely
if ((usage & BufferUsage::CPU_READ_MASK) == static_cast<uint64_t>(BufferUsage::CPU_READ_RARELY)) {
return true;
}
// CPU write rarely
if ((usage & BufferUsage::CPU_WRITE_MASK) ==
static_cast<uint64_t>(BufferUsage::CPU_WRITE_RARELY)) {
return true;
@@ -576,8 +558,6 @@ uint64_t GetHandleFlags(int format, uint64_t usage) {
int GetImplDefinedFormat(uint64_t usage, int format) {
int gr_format = format;
// If input format is HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED then based on
// the usage bits, gralloc assigns a format.
if (format == HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ||
format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
if ((usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC || usage & GRALLOC_USAGE_PRIVATE_ALLOC_UBWC_PI) &&
@@ -585,33 +565,30 @@ int GetImplDefinedFormat(uint64_t usage, int format) {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS_UBWC;
} else if (usage & BufferUsage::VIDEO_ENCODER) {
if (usage & GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER) {
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ENCODEABLE;
} else if (usage & GRALLOC_USAGE_PRIVATE_HEIF) {
gr_format = HAL_PIXEL_FORMAT_NV12_HEIF;
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
} else {
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE; // NV12
gr_format = HAL_PIXEL_FORMAT_NV12_ENCODEABLE;
}
} else if (usage & BufferUsage::CAMERA_INPUT) {
if (usage & BufferUsage::CAMERA_OUTPUT) {
// Assumed ZSL if both producer and consumer camera flags set
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
} else {
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP; // NV21
gr_format = HAL_PIXEL_FORMAT_YCrCb_420_SP;
}
} else if (usage & BufferUsage::CAMERA_OUTPUT) {
if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL; // NV21
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
} else {
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS; // NV12 preview
gr_format = HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS;
}
} else if (usage & BufferUsage::COMPOSER_OVERLAY) {
// XXX: If we still haven't set a format, default to RGBA8888
gr_format = HAL_PIXEL_FORMAT_RGBA_8888;
} else if (format == HAL_PIXEL_FORMAT_YCbCr_420_888) {
// If no other usage flags are detected, default the
// flexible YUV format to NV21_ZSL
gr_format = HAL_PIXEL_FORMAT_NV21_ZSL;
}
}
@@ -637,7 +614,7 @@ bool HasAlphaComponent(int32_t format) {
}
void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int32_t height,
int32_t /* flags */, int* plane_count, PlaneLayoutInfo* plane_info) {
int32_t , int* plane_count, PlaneLayoutInfo* plane_info) {
uint64_t usage = info.usage;
*plane_count = 1;
uint32_t bpp = 0;
@@ -659,4 +636,4 @@ void GetRGBPlaneInfo(const BufferInfo& info, int32_t format, int32_t width, int3
plane_info->scanlines = height;
}
} // namespace gralloc
}
@@ -42,7 +42,7 @@
#ifdef SLAVE_SIDE_CP
#define SECURE_ALIGN SZ_1M
#else // MASTER_SIDE_CP
#else
#define SECURE_ALIGN SZ_4K
#endif
@@ -68,59 +68,38 @@ inline Type1 ALIGN(Type1 x, Type2 align) {
}
enum PlaneComponent {
/* luma */
PLANE_COMPONENT_Y = 1 << 0,
/* chroma blue */
PLANE_COMPONENT_Cb = 1 << 1,
/* chroma red */
PLANE_COMPONENT_Cr = 1 << 2,
/* red */
PLANE_COMPONENT_R = 1 << 10,
/* green */
PLANE_COMPONENT_G = 1 << 11,
/* blue */
PLANE_COMPONENT_B = 1 << 12,
/* alpha */
PLANE_COMPONENT_A = 1 << 20,
/* raw data plane */
PLANE_COMPONENT_RAW = 1 << 30,
/* meta information plane */
PLANE_COMPONENT_META = 1 << 31,
};
struct PlaneLayoutInfo {
/** Components represented the type of plane. */
PlaneComponent component;
/** horizontal subsampling. Must be a positive power of 2. */
uint32_t h_subsampling;
/** vertical subsampling. Must be a positive power of 2. */
uint32_t v_subsampling;
/** offset to the first byte of the top-left pixel of the plane
* and it is calculated from the start of the buffer.
* Add base of the handle with offset to get the first byte of the plane.
*/
uint32_t offset;
/** step is the distance in bytes from one pixel value to the next. */
int32_t step;
/** stride of the plane in pixels */
int32_t stride;
/** stride of the plane in in bytes */
int32_t stride_bytes;
/** plane height or vertical stride */
int32_t scanlines;
/** size of the plane in bytes */
uint32_t size;
};
@@ -151,6 +130,6 @@ int GetImplDefinedFormat(uint64_t usage, int format);
int GetCustomFormatFlags(int format, uint64_t usage, int* custom_format, uint64_t* priv_flags);
bool IsGPUFlagSupported(uint64_t usage);
bool HasAlphaComponent(int32_t format);
} // namespace gralloc
}
#endif // __GR_UTILS_H__
#endif
@@ -26,7 +26,6 @@
#include "QtiGrallocDefs.h"
#include "gr_priv_handle.h"
//#pragma message "Warning: gralloc_priv.h is deprecated"
#define GRALLOC_PROP_PREFIX "vendor.gralloc."
#define GRALLOC_PROP(prop_name) GRALLOC_PROP_PREFIX prop_name
@@ -42,8 +41,6 @@ inline int roundUpToPageSize(int x) {
return (x + (getpagesize() - 1)) & ~(getpagesize() - 1);
}
/* Legacy gralloc1 definitions */
/* Some clients may still be using the old flags */
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ADSP_HEAP GRALLOC_USAGE_PRIVATE_ADSP_HEAP
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_ALLOC_UBWC GRALLOC_USAGE_PRIVATE_ALLOC_UBWC
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_UNCACHED GRALLOC_USAGE_PRIVATE_UNCACHED
@@ -54,7 +51,6 @@ inline int roundUpToPageSize(int x) {
#define GRALLOC1_CONSUMER_USAGE_PRIVATE_10BIT_TP GRALLOC_USAGE_PRIVATE_10BIT_TP
#define GRALLOC1_PRODUCER_USAGE_PRIVATE_VIDEO_NV21_ENCODER GRALLOC_USAGE_PRIVATE_VIDEO_NV21_ENCODER
// for PERFORM API :
#define GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER 1
#define GRALLOC_MODULE_PERFORM_GET_STRIDE 2
#define GRALLOC_MODULE_PERFORM_GET_CUSTOM_STRIDE_FROM_HANDLE 3
@@ -72,11 +68,9 @@ inline int roundUpToPageSize(int x) {
#define GRALLOC1_MODULE_PERFORM_GET_INTERLACE_FLAG 15
#define GRALLOC_MODULE_PERFORM_GET_GRAPHICS_METADATA 16
/* possible values for inverse gamma correction */
#define HAL_IGC_NOT_SPECIFIED 0
#define HAL_IGC_s_RGB 1
/* Color Space: Values maps to ColorSpace_t in qdMetadata.h */
#define HAL_CSC_ITU_R_601 0
#define HAL_CSC_ITU_R_601_FR 1
#define HAL_CSC_ITU_R_709 2
@@ -84,20 +78,16 @@ inline int roundUpToPageSize(int x) {
#define HAL_CSC_ITU_R_2020 4
#define HAL_CSC_ITU_R_2020_FR 5
/* possible formats for 3D content*/
enum {
HAL_NO_3D = 0x0,
HAL_3D_SIDE_BY_SIDE_L_R = 0x1,
HAL_3D_SIDE_BY_SIDE_R_L = 0x2,
HAL_3D_TOP_BOTTOM = 0x4,
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000, // unused legacy format
HAL_3D_IN_SIDE_BY_SIDE_L_R = 0x10000,
};
/* Flag to determine interlaced content
* Value maps to Flags presents in types.hal of QtiMapperextensions
*/
enum {
LAYOUT_INTERLACED_FLAG = 1 << 0,
};
#endif // __GRALLOC_PRIV_H__
#endif
@@ -60,43 +60,43 @@ struct IMapper : public IBase {
int32_t height __attribute__((aligned(4)));
};
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V2_0
}
namespace V3_0 {
struct IMapper : public IBase {
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V3_0
}
namespace V4_0 {
struct IMapper : public IBase {
static sp<IMapper> getService(const std::string& /* serviceName */, const bool /* getStub */);
static sp<IMapper> getService(const std::string& , const bool );
};
} // namespace V4_0
} // namespace android::hardware::graphics::mapper
}
}
namespace vendor::qti::hardware::display::mapper {
namespace V4_0 {
struct IQtiMapper : public IBase {
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V4_0::IMapper>& ,
bool );
};
} // namespace V4_0
}
namespace V3_0 {
struct IQtiMapper : public IBase {
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V3_0::IMapper>& ,
bool );
};
} // namespace V3_0
}
namespace V2_0 {
struct IQtiMapper : public IBase {
@@ -175,9 +175,9 @@ struct IQtiMapper : public IBase {
V1_0::IQtiMapperExtensions>& extensions)>;
virtual Return<void> getMapperExtensions(getMapperExtensions_cb _hidl_cb);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& /* parent */,
bool /* emitError */);
static Return<sp<IQtiMapper>> castFrom(const sp<graphics::mapper::V2_0::IMapper>& ,
bool );
};
} // namespace V2_0
} // namespace vendor::qti::hardware::display::mapper
}
}
@@ -45,7 +45,7 @@ struct IQtiMapperExtensions : public IBase {
IQtiMapperExtensions(){};
};
} // namespace V1_0
}
namespace V1_1 {
using Error = V1_0::Error;
using PlaneLayout = V1_0::PlaneLayout;
@@ -64,7 +64,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough
using getInterlacedFlag_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, bool interlaced)>;
virtual ::android::hardware::Return<void> getInterlacedFlag(void* buffer,
@@ -79,7 +78,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough
using getRgbDataAddress_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, void* rgb_data)>;
virtual ::android::hardware::Return<void> getRgbDataAddress(void* buffer,
@@ -95,7 +93,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passhthrough
using getColorSpace_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t color_space)>;
virtual ::android::hardware::Return<void> getColorSpace(void* buffer, getColorSpace_cb _hidl_cb) {
@@ -103,7 +100,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, passthrough with hidl fixups
using getYuvPlaneInfo_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error,
const ::android::hardware::hidl_vec<
@@ -130,27 +126,22 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, cast private data with no passthrough
using getFd_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t fd)>;
virtual ::android::hardware::Return<void> getFd(void* buffer, getFd_cb _hidl_cb);
// Used, cast private data with no passthrough
using getWidth_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t width)>;
virtual ::android::hardware::Return<void> getWidth(void* buffer, getWidth_cb _hidl_cb);
// Used, cast private data with no passthrough
using getHeight_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t height)>;
virtual ::android::hardware::Return<void> getHeight(void* buffer, getHeight_cb _hidl_cb);
// Used, cast private data with no passthrough
using getOffset_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t offset)>;
virtual ::android::hardware::Return<void> getOffset(void* buffer, getOffset_cb _hidl_cb);
// Used, cast private data with no passthrough
using getSize_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size)>;
virtual ::android::hardware::Return<void> getSize(void* buffer, getSize_cb _hidl_cb);
@@ -179,12 +170,10 @@ struct IQtiMapperExtensions : public IBase {
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t usage)>;
virtual ::android::hardware::Return<void> getUsageFlags(void* buffer, getUsageFlags_cb _hidl_cb);
// Used, cast private data with no passthrough
using getFormat_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t format)>;
virtual ::android::hardware::Return<void> getFormat(void* buffer, getFormat_cb _hidl_cb);
// Used, cast private data with no passthrough
using getPrivateFlags_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, int32_t flags)>;
virtual ::android::hardware::Return<void> getPrivateFlags(void* buffer,
@@ -198,7 +187,6 @@ struct IQtiMapperExtensions : public IBase {
std::terminate();
}
// Used, PAIN
using getFormatLayout_cb = std::function<void(
::vendor::qti::hardware::display::mapperextensions::V1_0::Error error, uint64_t size,
const ::android::hardware::hidl_vec<
@@ -208,7 +196,6 @@ struct IQtiMapperExtensions : public IBase {
int32_t height,
getFormatLayout_cb _hidl_cb);
// V1.1, Used, passthrough
virtual ::android::hardware::Return<
::vendor::qti::hardware::display::mapperextensions::V1_0::Error>
getSurfaceMetadata_V1(void* buffer, void* metadata) {
@@ -217,8 +204,8 @@ struct IQtiMapperExtensions : public IBase {
}
static Return<sp<IQtiMapperExtensions>> castFrom(
android::sp<V1_0::IQtiMapperExtensions> const& /* parent */, bool /* emitError */);
android::sp<V1_0::IQtiMapperExtensions> const& , bool );
};
} // namespace V1_1
} // namespace vendor::qti::hardware::display::mapperextensions
}
}
File diff suppressed because it is too large. Load diff
@@ -12,7 +12,6 @@ namespace graphics {
namespace mapper {
namespace V2_0 {
// Forward declaration for forward reference support:
enum class Error : int32_t;
struct YCbCrLayout;
@@ -25,34 +24,8 @@ enum class Error : int32_t {
UNSUPPORTED = 7,
};
/**
* A buffer descriptor is an implementation-defined opaque data returned by
* createDescriptor. It describes the properties of a buffer and is consumed
* by the allocator.
*/
typedef ::android::hardware::hidl_vec<uint32_t> BufferDescriptor;
/**
* Structure for describing YCbCr formats for consumption by applications.
* This is used with PixelFormat::YCBCR_*_888.
*
* Buffer chroma subsampling is defined in the format.
* e.g. PixelFormat::YCBCR_420_888 has subsampling 4:2:0.
*
* Buffers must have a 8 bit depth.
*
* y, cb, and cr point to the first byte of their respective planes.
*
* Stride describes the distance in bytes from the first value of one row of
* the image to the first value of the next row. It includes the width of the
* image plus padding.
* yStride is the stride of the luma plane.
* cStride is the stride of the chroma planes.
*
* chromaStep is the distance in bytes from one chroma pixel value to the
* next. This is 2 bytes for semiplanar (because chroma values are interleaved
* and each chroma value is one byte) and 1 for planar.
*/
struct YCbCrLayout final {
void* y;
void* cb;
@@ -62,9 +35,6 @@ struct YCbCrLayout final {
uint32_t chromaStep;
};
//
// type declarations for package
//
template <typename>
static inline std::string toString(int32_t o);
@@ -107,11 +77,7 @@ constexpr int32_t& operator&=(int32_t& v,
static inline std::string toString(
const ::android::hardware::graphics::mapper::V2_0::YCbCrLayout& o);
// operator== and operator!= are not generated for YCbCrLayout
//
// type header definitions for package
//
template <>
inline std::string toString<::android::hardware::graphics::mapper::V2_0::Error>(int32_t o) {
@@ -218,17 +184,17 @@ static inline std::string toString(
return os;
}
// operator== and operator!= are not generated for YCbCrLayout
} // namespace V2_0
} // namespace mapper
} // namespace graphics
} // namespace hardware
} // namespace android
//
// global type declarations for package
//
}
}
}
}
}
namespace android {
namespace hardware {
@@ -243,8 +209,8 @@ constexpr std::array<::android::hardware::graphics::mapper::V2_0::Error, 6>
::android::hardware::graphics::mapper::V2_0::Error::NO_RESOURCES,
::android::hardware::graphics::mapper::V2_0::Error::UNSUPPORTED,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
#endif // HIDL_GENERATED_ANDROID_HARDWARE_GRAPHICS_MAPPER_V2_0_TYPES_H
#endif
@@ -13,7 +13,6 @@ namespace display {
namespace mapperextensions {
namespace V1_0 {
// Forward declaration for forward reference support:
enum class Error : int32_t;
struct YCbCrLayout;
enum class PlaneComponent : uint32_t;
@@ -36,61 +35,22 @@ struct YCbCrLayout final {
uint32_t chromaStep;
};
/**
* Plane Component type definitions.
*/
enum class PlaneComponent : uint32_t {
PLANE_COMPONENT_Y = 1u /* (1 << 0) */,
PLANE_COMPONENT_Cb = 2u /* (1 << 1) */,
PLANE_COMPONENT_Cr = 4u /* (1 << 2) */,
PLANE_COMPONENT_R = 1024u /* (1 << 10) */,
PLANE_COMPONENT_G = 2048u /* (1 << 11) */,
PLANE_COMPONENT_B = 4096u /* (1 << 12) */,
PLANE_COMPONENT_A = 1048576u /* (1 << 20) */,
PLANE_COMPONENT_RAW = 1073741824u /* (1 << 30) */,
PLANE_COMPONENT_META = 2147483648u /* (1 << 31) */,
PLANE_COMPONENT_Y = 1u ,
PLANE_COMPONENT_Cb = 2u ,
PLANE_COMPONENT_Cr = 4u ,
PLANE_COMPONENT_R = 1024u ,
PLANE_COMPONENT_G = 2048u ,
PLANE_COMPONENT_B = 4096u ,
PLANE_COMPONENT_A = 1048576u ,
PLANE_COMPONENT_RAW = 1073741824u ,
PLANE_COMPONENT_META = 2147483648u ,
};
/**
* Flags to send with getFormatLayout for extra information for e.g. interlaced,
* otherwise send 0.
*/
enum class Flags : int32_t {
LAYOUT_INTERLACED_FLAG = 1 /* (1 << 0) */,
LAYOUT_INTERLACED_FLAG = 1 ,
};
/**
* Structure for describing plane layout information for YUVA/RGBA/RAW formats
* YUV formats can be planar or semi-planar.
*
* This layout structure is used with any pixel format that can be
* represented by it, such as:
* - HAL_PIXEL_FORMAT_YCbCr_*
* - HAL_PIXEL_FORMAT_RGB*_*
* - even implementation defined formats that can be represented by
* the structures
*
* Each plane can be made up of one or more PlaneComponents depending on
* the format.
*
* Each plane can be subsampled either vertically or horizontally by
* a power-of-two factor.
*
* Pixel increment describes the distance in bytes from one pixel to the
* next pixel (to the right) on the same row for the component plane.
* This can be negative.
*
* Stride is provided in both pixels and bytes.
* Stride_bytes describes the distance in bytes from the first pixel of
* one row to the first pixel of the next row (below) for the component plane.
* This can be negative.
*
* Scanlines describes the height of the plane in terms of number of rows.
*
* Size of plane is stride_bytes * scanlines + any padding required for
* alignment as per format specification.
*
*/
struct PlaneLayout final {
::android::hardware::hidl_bitfield<
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent>
@@ -137,9 +97,6 @@ static_assert(sizeof(::vendor::qti::hardware::display::mapperextensions::V1_0::P
static_assert(__alignof(::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout) == 4,
"wrong alignment");
//
// type declarations for package
//
template <typename>
static inline std::string toString(int32_t o);
@@ -185,7 +142,6 @@ constexpr int32_t& operator&=(
static inline std::string toString(
const ::vendor::qti::hardware::display::mapperextensions::V1_0::YCbCrLayout& o);
// operator== and operator!= are not generated for YCbCrLayout
template <typename>
static inline std::string toString(uint32_t o);
@@ -284,9 +240,6 @@ static inline bool operator!=(
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& lhs,
const ::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneLayout& rhs);
//
// type header definitions for package
//
template <>
inline std::string toString<::vendor::qti::hardware::display::mapperextensions::V1_0::Error>(
@@ -379,7 +332,6 @@ static inline std::string toString(
return os;
}
// operator== and operator!= are not generated for YCbCrLayout
template <>
inline std::string
@@ -642,16 +594,13 @@ static inline bool operator!=(
return !(lhs == rhs);
}
} // namespace V1_0
} // namespace mapperextensions
} // namespace display
} // namespace hardware
} // namespace qti
} // namespace vendor
}
}
}
}
}
}
//
// global type declarations for package
//
namespace android {
namespace hardware {
@@ -664,9 +613,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::E
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::NO_RESOURCES,
::vendor::qti::hardware::display::mapperextensions::V1_0::Error::UNSUPPORTED,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
namespace android {
namespace hardware {
@@ -688,9 +637,9 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::P
::vendor::qti::hardware::display::mapperextensions::V1_0::PlaneComponent::
PLANE_COMPONENT_META,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
namespace android {
namespace hardware {
@@ -700,8 +649,8 @@ constexpr std::array<::vendor::qti::hardware::display::mapperextensions::V1_0::F
hidl_enum_values<::vendor::qti::hardware::display::mapperextensions::V1_0::Flags> = {
::vendor::qti::hardware::display::mapperextensions::V1_0::Flags::LAYOUT_INTERLACED_FLAG,
};
} // namespace details
} // namespace hardware
} // namespace android
}
}
}
#endif // HIDL_GENERATED_VENDOR_QTI_HARDWARE_DISPLAY_MAPPEREXTENSIONS_V1_0_TYPES_H
#endif
@@ -46,14 +46,13 @@
using namespace android::hardware;
using namespace android;
// These two functions are used by the blob and aren't present on Q so weakly stub them
extern "C" uint64_t __attribute__((weak)) atrace_get_enabled_tags() {
return 0;
}
namespace android::hardware::details {
void __attribute__((weak)) return_status::onValueRetrieval() const {}
} // namespace android::hardware::details
}
bool IBase::isRemote() const {
return false;
@@ -146,7 +145,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V2_0
}
namespace V3_0 {
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
@@ -154,7 +153,7 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V3_0
}
namespace V4_0 {
sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getStub) {
@@ -162,8 +161,8 @@ sp<IMapper> IMapper::getService(const std::string& serviceName, const bool getSt
return nullptr;
}
} // namespace V4_0
} // namespace android::hardware::graphics::mapper
}
}
namespace vendor::qti::hardware::display {
namespace mapper {
namespace V4_0 {
@@ -174,7 +173,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V4_0::IMa
return nullptr;
}
} // namespace V4_0
}
namespace V3_0 {
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMapper>& parent,
@@ -183,7 +182,7 @@ Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V3_0::IMa
return nullptr;
}
} // namespace V3_0
}
namespace V2_0 {
Return<sp<IQtiMapper>> IQtiMapper::castFrom(const sp<graphics::mapper::V2_0::IMapper>& parent,
@@ -200,8 +199,8 @@ Return<void> IQtiMapper::getMapperExtensions(getMapperExtensions_cb _hidl_cb) {
return Void();
}
} // namespace V2_0
} // namespace mapper
}
}
namespace mapperextensions {
namespace V1_1 {
@@ -467,6 +466,6 @@ Return<void> IQtiMapperExtensions::getFormatLayout(int32_t format, uint64_t usag
return Void();
}
} // namespace V1_1
} // namespace mapperextensions
} // namespace vendor::qti::hardware::display
}
}
}
+109
View File
@@ -0,0 +1,109 @@
#include <dlfcn.h>
#include <jni.h>
#include <stdio.h>
#include <string.h>
#define VK_NO_PROTOTYPES
#include <vulkan/vulkan.h>
#define LINE(...) do { if (used < sizeof(out)) used += snprintf(out + used, sizeof(out) - used, __VA_ARGS__); } while (0)
static const char *const EXTENSIONS[] = {
"VK_EXT_robustness2", "VK_EXT_transform_feedback", "VK_EXT_custom_border_color",
"VK_KHR_dynamic_rendering", "VK_EXT_extended_dynamic_state3", "VK_EXT_descriptor_indexing",
"VK_KHR_timeline_semaphore", "VK_EXT_mesh_shader", "VK_KHR_ray_query",
};
static const char *type_name(VkPhysicalDeviceType type) {
switch (type) {
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return "integrated GPU";
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return "discrete GPU";
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return "virtual GPU";
case VK_PHYSICAL_DEVICE_TYPE_CPU: return "CPU";
default: return "other";
}
}
JNIEXPORT jstring JNICALL
Java_com_droiddeck_launcher_gpu_VulkanInfo_nativeQuery(JNIEnv *env, jclass clazz) {
static char out[4096];
size_t used = 0;
out[0] = 0;
void *lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!lib) { LINE("error=no Vulkan loader\n"); return (*env)->NewStringUTF(env, out); }
PFN_vkGetInstanceProcAddr gipa = (PFN_vkGetInstanceProcAddr) dlsym(lib, "vkGetInstanceProcAddr");
PFN_vkCreateInstance create = gipa ? (PFN_vkCreateInstance) gipa(NULL, "vkCreateInstance") : NULL;
PFN_vkEnumerateInstanceVersion instanceVersion = gipa ? (PFN_vkEnumerateInstanceVersion) gipa(NULL, "vkEnumerateInstanceVersion") : NULL;
if (!create) { LINE("error=no vkCreateInstance\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
uint32_t loader = VK_API_VERSION_1_0;
if (instanceVersion) instanceVersion(&loader);
LINE("loader=%u.%u.%u\n", VK_API_VERSION_MAJOR(loader), VK_API_VERSION_MINOR(loader), VK_API_VERSION_PATCH(loader));
VkApplicationInfo app = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "DroidDeck device info",
.apiVersion = loader >= VK_API_VERSION_1_1 ? VK_API_VERSION_1_1 : VK_API_VERSION_1_0 };
VkInstanceCreateInfo info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app };
VkInstance instance = VK_NULL_HANDLE;
if (create(&info, NULL, &instance) != VK_SUCCESS) { LINE("error=vkCreateInstance failed\n"); dlclose(lib); return (*env)->NewStringUTF(env, out); }
#define GET(name) PFN_##name name = (PFN_##name) gipa(instance, #name)
GET(vkDestroyInstance); GET(vkEnumeratePhysicalDevices); GET(vkGetPhysicalDeviceProperties);
GET(vkGetPhysicalDeviceProperties2); GET(vkGetPhysicalDeviceMemoryProperties);
GET(vkEnumerateDeviceExtensionProperties); GET(vkGetPhysicalDeviceFeatures);
uint32_t count = 1;
VkPhysicalDevice device = VK_NULL_HANDLE;
VkResult listed = vkEnumeratePhysicalDevices ? vkEnumeratePhysicalDevices(instance, &count, &device) : VK_ERROR_INITIALIZATION_FAILED;
if ((listed != VK_SUCCESS && listed != VK_INCOMPLETE) || count == 0 || !device || !vkGetPhysicalDeviceProperties) {
LINE("error=no Vulkan device\n");
} else {
VkPhysicalDeviceProperties p;
vkGetPhysicalDeviceProperties(device, &p);
LINE("device=%s\n", p.deviceName);
LINE("type=%s\n", type_name(p.deviceType));
LINE("api=%u.%u.%u\n", VK_API_VERSION_MAJOR(p.apiVersion), VK_API_VERSION_MINOR(p.apiVersion), VK_API_VERSION_PATCH(p.apiVersion));
LINE("driver_version=%u.%u.%u\n", p.driverVersion >> 22, (p.driverVersion >> 12) & 0x3ff, p.driverVersion & 0xfff);
LINE("vendor_id=0x%04x\ndevice_id=0x%08x\n", p.vendorID, p.deviceID);
LINE("max_image_2d=%u\n", p.limits.maxImageDimension2D);
LINE("max_push_constants=%u\n", p.limits.maxPushConstantsSize);
uint32_t extCount = 0;
if (vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, NULL);
static VkExtensionProperties exts[512];
if (extCount > 512) extCount = 512;
if (extCount && vkEnumerateDeviceExtensionProperties) vkEnumerateDeviceExtensionProperties(device, NULL, &extCount, exts);
int driverProps = p.apiVersion >= VK_API_VERSION_1_2;
for (uint32_t i = 0; i < extCount; i++)
if (!strcmp(exts[i].extensionName, "VK_KHR_driver_properties")) driverProps = 1;
if (driverProps && vkGetPhysicalDeviceProperties2) {
VkPhysicalDeviceDriverProperties driver = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES };
VkPhysicalDeviceProperties2 p2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driver };
vkGetPhysicalDeviceProperties2(device, &p2);
LINE("driver_name=%s\n", driver.driverName);
LINE("driver_info=%s\n", driver.driverInfo);
LINE("conformance=%u.%u.%u.%u\n", driver.conformanceVersion.major, driver.conformanceVersion.minor,
driver.conformanceVersion.subminor, driver.conformanceVersion.patch);
}
LINE("extension_count=%u\n", extCount);
for (size_t e = 0; e < sizeof(EXTENSIONS) / sizeof(*EXTENSIONS); e++) {
int has = 0;
for (uint32_t i = 0; i < extCount && !has; i++) has = !strcmp(exts[i].extensionName, EXTENSIONS[e]);
LINE("ext.%s=%s\n", EXTENSIONS[e], has ? "yes" : "no");
}
if (vkGetPhysicalDeviceFeatures) {
VkPhysicalDeviceFeatures f;
vkGetPhysicalDeviceFeatures(device, &f);
LINE("geometry_shader=%s\ntessellation_shader=%s\ntexture_bc=%s\n",
f.geometryShader ? "yes" : "no", f.tessellationShader ? "yes" : "no", f.textureCompressionBC ? "yes" : "no");
LINE("shader_int64=%s\n", f.shaderInt64 ? "yes" : "no");
}
if (vkGetPhysicalDeviceMemoryProperties) {
VkPhysicalDeviceMemoryProperties m;
vkGetPhysicalDeviceMemoryProperties(device, &m);
VkDeviceSize largest = 0;
for (uint32_t i = 0; i < m.memoryHeapCount; i++)
if ((m.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) && m.memoryHeaps[i].size > largest) largest = m.memoryHeaps[i].size;
LINE("device_heap_mb=%llu\n", (unsigned long long) (largest >> 20));
}
}
if (vkDestroyInstance) vkDestroyInstance(instance, NULL);
dlclose(lib);
return (*env)->NewStringUTF(env, out);
}
File diff suppressed because it is too large. Load diff
@@ -84,12 +84,11 @@ struct VkTable {
PFN_vkCmdSetScissor CmdSetScissor;
PFN_vkCmdPipelineBarrier CmdPipelineBarrier;
PFN_vkCmdCopyImage CmdCopyImage;
PFN_vkCmdBlitImage CmdBlitImage; // frame-gen capture resolution: composite -> swapchain upscale
// Compute: the native LSFG chain is 25 compute dispatches.
PFN_vkCmdBlitImage CmdBlitImage;
PFN_vkCmdDispatch CmdDispatch;
PFN_vkCreateComputePipelines CreateComputePipelines;
PFN_vkUnmapMemory UnmapMemory;
// win-fg native: clears its flow scratch, resets its scratch pool on resize.
PFN_vkCmdClearColorImage CmdClearColorImage;
PFN_vkResetDescriptorPool ResetDescriptorPool;
PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage;
@@ -108,10 +107,10 @@ struct VkTable {
#include <android/log.h>
#include <string>
#define WLOG_TAG "Winlator_Renderer"
#define WLOG_TAG "DroidDeck_Renderer"
#define RLOG(...) if(verboseLog) __android_log_print(ANDROID_LOG_DEBUG,WLOG_TAG,__VA_ARGS__)
#define RLOG_E(...) __android_log_print(ANDROID_LOG_ERROR,WLOG_TAG,__VA_ARGS__)
#define SCANOUT_LOG(...) __android_log_print(ANDROID_LOG_DEBUG,"Winlator_Scanout",__VA_ARGS__)
#define SCANOUT_LOG(...) __android_log_print(ANDROID_LOG_DEBUG,"DroidDeck_Scanout",__VA_ARGS__)
#include <vulkan/vulkan_android.h>
#include <android/hardware_buffer.h>
@@ -125,110 +124,94 @@ struct VkTable {
#include <condition_variable>
#include <chrono>
// Renderer-neutral direct-scanout impl (owns the SurfaceControl/AHB state).
// Included after SCANOUT_LOG is defined above; its own definition is guarded.
#include "../scanout/ScanoutContext.h"
// Native (compositor-side) LSFG frame generation — capability gate.
#include "lsfg/lsfg_probe.h"
#include <memory>
#include <string>
namespace lsfg { class Engine; }
namespace winfg { class Engine; }
namespace ddfg { class Engine; }
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
struct WindowPushConstants { float ndcX0, ndcY0, ndcX1, ndcY1; int useTexAlpha; };
// Push-constant layouts for the spatial-upscaler post passes. The leading vec4
// `ndc` matches upscale.vert; the remaining members are read only by the
// fragment stage. std430 offsets line up with these C structs.
struct SgsrPushConstants { // 36 bytes
struct SgsrPushConstants {
float ndc[4];
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
float edgeSharpness; // SGSR EdgeSharpness, from the slider
float viewportInfo[4];
float edgeSharpness;
};
struct NisPushConstants { // 36 bytes
struct NisPushConstants {
float ndc[4];
float viewportInfo[4]; // xy = 1/inputSize, zw = inputSize px
float sharpness; // NIS sharpness [0..1], from the slider
float viewportInfo[4];
float sharpness;
};
struct EasuPushConstants { // 88 bytes
struct EasuPushConstants {
float ndc[4];
uint32_t con0[4], con1[4], con2[4], con3[4];
float outW, outH;
};
struct RcasPushConstants { // 40 bytes
struct RcasPushConstants {
float ndc[4];
uint32_t con[4]; // con.x = bit-packed sharpness
uint32_t con[4];
float outW, outH;
};
struct DownscalePushConstants { // 32 bytes
struct CasPushConstants {
float ndc[4];
float srcW, srcH; // render (source) resolution
float dstW, dstH; // display (output) resolution
};
// Composable post effects (AMD CAS sharpen + fake-HDR). Both lead with vec4 ndc
// (offset 0) like the upscaler PCs and stay well under the 88-byte PC range.
struct CasPushConstants { // 32 bytes
float ndc[4];
float resolution[2]; // input texture size in px
float sharpness; // CAS SHARPNESS term [0..1]
float resolution[2];
float sharpness;
float _pad;
};
struct HdrPushConstants { // 24 bytes
struct HdrPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
// Phase 2 screen effects ported from the GL EffectComposer. Each leads with vec4
// ndc (offset 0) like the upscaler PCs and stays well under the 88-byte PC range.
struct FxaaPushConstants { // 24 bytes
struct FxaaPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
struct ToonPushConstants { // 24 bytes
struct ToonPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px
float resolution[2];
};
struct ColorPushConstants { // 32 bytes
struct ColorPushConstants {
float ndc[4];
float brightness; // additive [-1,1]
float contrast; // [0,2]
float gamma; // [0.1,5]
float saturation; // [0,2]; 1 = unchanged, 0 = greyscale.
// APPENDED last so the three offsets above
// stay where color.frag already had them.
float brightness;
float contrast;
float gamma;
float saturation;
};
struct NtscPushConstants { // 28 bytes
struct NtscPushConstants {
float ndc[4];
float resolution[2]; // screen size in px (TextureSize/resolution)
float frameCount; // animated chroma-phase counter
float resolution[2];
float frameCount;
};
struct CrtPushConstants { // 24 bytes
struct CrtPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px (unused by shader)
float resolution[2];
};
struct DebandPushConstants { // 28 bytes
struct DebandPushConstants {
float ndc[4];
float resolution[2]; // input texture size in px (layout parity)
float strength; // dither amplitude in LSBs (1.0 = +/-1/255)
float resolution[2];
float strength;
};
class VulkanRendererContext {
public:
// `lsfgVk11Compat` (experimental) lets the LSFG capability probe accept a
// Vulkan 1.1/1.2 device that offers the extensions the chain needs; it has
// to be known before vkCreateDevice, hence a constructor argument.
VulkanRendererContext(ANativeWindow* window, int cWidth, int cHeight, void* adrenotoolsHandle = nullptr,
bool lsfgVk11Compat = false);
~VulkanRendererContext();
void onSurfaceResized(int width, int height);
void setTransform(float ox, float oy, float sx, float sy);
// #413: game-region clip rect (surface px). Stored and applied as the swapchain scissor in
// recordCmdBuf so a FILL/STRETCH game confined to a half (TOP/BOTTOM alignment) can't bleed into
// the on-screen-controls half. A full-surface (or w<=0) rect means "no clip" (CENTER).
void setClipRegion(int x, int y, int w, int h);
void updatePointerPosition(short x, short y);
void updateWindowContent(int64_t id, void* pixels, short w, short h, short stride, int x, int y);
@@ -248,13 +231,7 @@ public:
void scanoutSetBuffer(AHardwareBuffer* ahb, int x, int y, int w, int h, int fenceFd = -1);
void scanoutSetCursorImage(void* pixels, short w, short h, short stride);
void scanoutSetCursorPos(short x, short y, short hotX, short hotY);
// The renderer-neutral scanout implementation. The scanout methods above are
// thin forwarders to this (see VulkanRendererScanout.cpp); the cursor render-
// thread signalling stays in the forwarders, not in ScanoutContext.
ScanoutContext scanout;
// Bound to scanout's atomics so the existing JNI ABI (r->scanoutActive /
// r->gameFrameDelivered direct member access) keeps compiling against a
// single source of truth. Must be declared after `scanout`.
std::atomic<bool>& scanoutActive = scanout.scanoutActive;
std::atomic<bool>& gameFrameDelivered = scanout.gameFrameDelivered;
std::atomic<bool> surfaceDetached{false};
@@ -293,48 +270,17 @@ public:
void setPresentMode(VkPresentModeKHR mode);
std::vector<int> getSupportedPresentModes() const;
// --- Native LSFG frame generation -------------------------------------
// Capability verdict for the compositor-side LSFG engine. Filled at device
// creation and completed once the swapchain format is known; read by the
// UI (through JNI) to grey the engine out with a reason.
const lsfg::Caps& lsfgCaps() const { return lsfgCaps_; }
// Arm/disarm native LSFG frame generation for this session. Changing the
// armed state recreates the swapchain, because the composite path needs
// TRANSFER_DST usage and a deeper image queue that a normal session does
// not pay for.
void setFrameGenArmed(bool armed, int multiplier);
bool frameGenArmed() const { return fgArmed_.load(std::memory_order_relaxed); }
// Live frame-gen telemetry for the in-game readout:
// [0] generations the governor currently trusts
// [1] generations actually planned for the last source frame
// [2] measured source (real) frames per second
// [3] measured presented frames per second
// [4] thermal status, -1 when the device gives no signal
// [5] GPU milliseconds the chain spends per generated frame, -1 unknown
void frameGenStats(float out[6]) const;
// Why the selected native frame-gen engine cannot run here, for the UI:
// -1 not known yet (caps not probed), 0 fine, 1 this driver lacks what the
// engine needs (lsfgCaps().reason says which gate), 2 the engine failed to start.
int frameGenProblem() const;
// Path to the SPIR-V cache built from the user's Lossless.dll. Setting it
// drops any existing engine so the next armed frame rebuilds from it.
void setLsfgCachePath(const char* path);
// Flow scale (0.25-1.0) and the panel's real refresh rate. The pacer never
// generates above the refresh rate.
// Experimental: `captureHeight` sizes the composite ring the chain runs on,
// the width following the swapchain's aspect, so a phone GPU generates at
// game-like resolution and the result is blitted up. 0 = panel;
// kFgCaptureGame = the X screen's height (containerHeight), which already
// carries the per-game override and render scale; otherwise a pixel height.
static constexpr int32_t kFgCaptureGame = -1;
void setFrameGenTuning(float flowScale, float refreshHz, int32_t captureHeight = 0);
// Which native engine generates: 0 = LSFG (needs the cache built from the
// user's Lossless.dll), 1 = win-fg (our own chain, embedded, needs nothing).
// Switching drops the other engine so only one ever holds GPU resources.
void setFrameGenEngine(int kind);
// win-fg only: interpolation model (3/4) and performance preset (0..2).
void setWinFgTuning(int model, int perfPreset);
void setDdFgTuning(int model, int perfPreset);
private:
struct WinTex {
@@ -366,11 +312,9 @@ private:
ANativeWindow* window;
int surfaceWidth, surfaceHeight, containerWidth, containerHeight;
// #413 compositor clip rect (surface px). clipRegionW<=0 => disabled (whole swapchain). Written from
// the renderer thread (setClipRegion), read from the render thread (recordCmdBuf) -> atomic.
std::atomic<int> clipRegionX{0}, clipRegionY{0}, clipRegionW{0}, clipRegionH{0};
void* adrenotoolsHandle = nullptr;
bool lsfgVk11Compat_ = false; // see the constructor
bool lsfgVk11Compat_ = false;
int filterMode = 0;
bool swapRB = false;
float maxAnisotropy = 1.0f;
@@ -393,9 +337,6 @@ private:
std::vector<VkImageMemoryBarrier> framePreUpload;
std::vector<VkImageMemoryBarrier> framePostUpload;
// [P0] All SurfaceControl / AHardwareBuffer scanout state moved to
// ScanoutContext (member `scanout` above). Dead members (lastDst*,
// ScanoutPending/scanoutPending*, fnSTSetBackPressure) were dropped.
std::atomic<int> pointerX{0}, pointerY{0};
float sceneOffsetX=0.f, sceneOffsetY=0.f, sceneScaleX=1.f, sceneScaleY=1.f;
@@ -435,138 +376,69 @@ private:
VkPipeline pipeline = VK_NULL_HANDLE;
// ---- Spatial upscaler (SGSR / FSR1) ----
// upscalerMode enum (mirrored by JNI nativeSetUpscaler):
// 0 = none (passthrough; current sampler governs scaling)
// 1 = linear (rebuild base sampler LINEAR; no shader pass)
// 2 = nearest (rebuild base sampler NEAREST; no shader pass)
// 3 = sgsr (Snapdragon GSR 1.0; single pass; aspect-fit / letterbox)
// 4 = fsr (AMD FSR1 EASU+RCAS; two passes; fill / stretch)
// 5 = fsr_fit (AMD FSR1 EASU+RCAS; two passes; aspect-fit / letterbox)
// 6 = sharpen (RCAS-only; any resolution; aspect-fit / letterbox)
// 7 = nis (NVIDIA Image Scaling NVScaler; single pass; aspect-fit)
// 8 = sgsr_quality (SGSR 1 edge-direction variant; single pass; aspect-fit)
// Shader upscaling only engages for modes 3-8 AND when the game render
// resolution (container) is smaller than the swapchain. Otherwise the
// existing direct-to-swapchain path is used unchanged.
int upscalerMode = 0;
// Independent of upscalerMode: high-quality Lanczos downscale for the
// supersampling case (game render res > display res). When enabled and the
// render res exceeds the swapchain, replaces the bilinear minify with a
// proper downscale pass. Supersampling and the upscale modes are mutually
// exclusive in practice (one is render>display, the other render<display).
bool hqDownscale = false;
// Composable post effects, independent of the scaling mode. CAS layers a sharpen
// on top of any mode (incl. native res); HDR is a binary fake-HDR. The upscaler
// sharpness (RCAS lobe scale / SGSR EdgeSharpness) is driven by its own slider as a
// linear 0..1 value: 0 = neutral (no sharpening; the upscale still runs), 1 = max.
bool casEnabled = false;
int casSharpness = 60; // slider 0..100 -> CAS SHARPNESS
int casSharpness = 60;
bool hdrEnabled = false;
float upscaleSharpness01 = 0.75f; // slider/100; RCAS lobe scale, SGSR edge derived
// Debanding: terminal TPDF/IGN dither pass before the 8-bit swapchain quantize.
float upscaleSharpness01 = 0.75f;
bool debandEnabled = false;
int debandStrength = 100; // slider 0..200 -> strength/100 LSBs
int debandStrength = 100;
// Phase 2 screen effects (GL EffectComposer parity). FXAA/Toon/CRT/NTSC are
// binary; Color is the brightness/contrast/gamma grade (always-applied via the
// sliders, no toggle) and is "enabled" only when not at neutral (0,0,1).
bool fxaaEnabled = false;
bool toonEnabled = false;
bool crtEnabled = false;
bool ntscEnabled = false;
bool colorEnabled = false; // derived: !neutral grade
float colorBrightness = 0.0f; // [-1,1] (slider/100, clamped)
float colorContrast = 0.0f; // [0,2] (slider/100, clamped)
float colorGamma = 1.0f; // [0.1,5]
float colorSaturation = 1.0f; // [0,2] (slider/100, clamped)
uint32_t ntscFrameCounter = 0; // animates NTSC chroma phase
bool colorEnabled = false;
float colorBrightness = 0.0f;
float colorContrast = 0.0f;
float colorGamma = 1.0f;
float colorSaturation = 1.0f;
uint32_t ntscFrameCounter = 0;
VkSampler upscaleSampler = VK_NULL_HANDLE; // linear clamp; offscreen/mid input
VkSampler upscaleSampler = VK_NULL_HANDLE;
// === Native LSFG: composite target ring ===================================
// Frame generation cannot composite straight into a swapchain image: the
// finished frame has to be READABLE (it becomes the next frame's LSFG
// input) and generated frames have to be STORAGE-WRITABLE by a compute
// dispatch. Android swapchain images are COLOR_ATTACHMENT only, and
// storage support on a swapchain format is not something a driver owes us.
//
// So when frame gen is armed the whole existing recording is redirected at
// a composite image we own — format-identical to the swapchain, so every
// existing pipeline stays render-pass compatible — and a copy moves it into
// the acquired swapchain image at the end. With frame gen off, not one of
// these objects is created and the direct-to-swapchain path is untouched.
struct CompositeTarget {
VkImage img = VK_NULL_HANDLE;
VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; // colour attachment + sampled
VkImageView storageView = VK_NULL_HANDLE; // compute writes (generate)
VkImageView view = VK_NULL_HANDLE;
VkImageView storageView = VK_NULL_HANDLE;
VkFramebuffer fb = VK_NULL_HANDLE;
VkDescriptorSet ds = VK_NULL_HANDLE; // sampled, for later passes
VkDescriptorSet ds = VK_NULL_HANDLE;
};
// Hard ceiling: (max generations + 1) presentable frames per source frame,
// times a queue depth of 2, capped so the footprint stays bounded.
static constexpr uint32_t kMaxCompositeTargets = 7;
std::vector<CompositeTarget> compositeTargets;
VkRenderPass compositeRenderPass = VK_NULL_HANDLE; // CLEAR -> GENERAL
VkRenderPass compositeRenderPass = VK_NULL_HANDLE;
uint32_t compositeW = 0, compositeH = 0;
uint32_t compositeIndex = 0; // rotates per composite; gives history for free
bool compositeArmed = false; // targets exist AND this frame uses them
bool swapchainTransferDst = false; // swapchain was created with TRANSFER_DST
uint32_t compositeIndex = 0;
bool compositeArmed = false;
bool swapchainTransferDst = false;
// Set from the app when the native LSFG engine is selected for this
// session. Read on the render thread; false keeps every path as it was.
std::atomic<bool> fgArmed_{false};
std::atomic<int> fgMultiplier_{0};
// Tuning is written by the UI thread and consumed by the render thread, so
// it is published through atomics and applied to the engine at the top of
// the frame-gen block. The UI thread never touches the engine itself.
std::atomic<float> fgFlowScale_{1.0f};
std::atomic<float> fgRefreshHz_{0.0f};
std::atomic<bool> fgConfigDirty_{true};
// Experimental (see setFrameGenTuning): capture height of the composite
// ring. 0 = panel resolution, kFgCaptureGame = the X screen's height.
std::atomic<int32_t> fgCaptureHeight_{0};
// Extent the composite ring should have for the current swapchain and
// capture height (width follows the swapchain aspect, both even).
void compositeExtentFor(uint32_t& w, uint32_t& h) const;
// The extent the scene is rendered at this frame: the composite ring while
// frame gen runs on a ring smaller than the swapchain (experimental capture
// resolution), otherwise the swapchain. Every full-target pass (the effect
// chain, the spatial upscalers, the render-scale downscale) sizes itself by
// this, so those keep working below panel resolution and their
// intermediates shrink with the ring.
VkExtent2D renderExtent() const {
if (compositeActive() && (compositeW != swapchainExt.width || compositeH != swapchainExt.height))
return VkExtent2D{compositeW, compositeH};
return swapchainExt;
}
// Copy (same extent) or blit (composite smaller than the swapchain) a
// composite-ring image, already in TRANSFER_SRC, into a swapchain image
// already in TRANSFER_DST.
void recordCompositeToSwapchainTransfer(VkCommandBuffer cb, VkImage src, uint32_t imgIdx);
bool createCompositeRenderPass();
bool ensureCompositeTargets(uint32_t w, uint32_t h, uint32_t count);
void destroyCompositeTargets();
// True when this frame should composite off-swapchain. Call on the render
// thread; every gate must hold or we fall back to the untouched path.
bool compositeActive() const;
// Render pass / framebuffer this frame draws its FINAL pass into.
VkRenderPass targetRenderPass() const;
VkFramebuffer targetFramebuffer(uint32_t imgIdx) const;
// Copy the finished composite into the acquired swapchain image and leave
// it in PRESENT_SRC. No-op when the composite path is not active.
void copyCompositeToSwapchain(VkCommandBuffer cb, uint32_t imgIdx);
// === Native LSFG: the software cursor ====================================
// LSFG interpolates whatever it is given, so a cursor composited into the
// frame gets warped along the flow field and smears. It is therefore
// excluded from the composite while frame gen is armed and drawn once into
// EVERY presented image instead - real and generated alike - through a
// load-op render pass that leaves the image ready to present.
VkRenderPass cursorOverlayRenderPass = VK_NULL_HANDLE;
struct CursorOverlay {
bool draw = false;
@@ -576,19 +448,16 @@ private:
CursorOverlay cursorOverlay_{};
bool createCursorOverlayRenderPass();
// Draw the cursor into an already-copied swapchain image and leave it in
// PRESENT_SRC. Takes over the final transition from the copy helpers.
void recordCursorOverlay(VkCommandBuffer cb, uint32_t imgIdx);
// True when the cursor is being drawn per present rather than composited.
bool cursorDrawnPerPresent() const;
// === Native LSFG: the per-source-frame present plan =======================
// Interpolation produces frames that belong BETWEEN N-1 and N, so the
// generated frames are presented FIRST and real frame N is held back one
// slot. All presents for one source frame are queued together: with FIFO
// the driver then shows them on consecutive vblanks, so the pacing falls
// out of the present mode for free - no sleeps, no render-mode change.
static constexpr uint32_t kMaxPresentsPerFrame = 4; // 1 real + up to 3 generated
static constexpr uint32_t kMaxPresentsPerFrame = 4;
struct FrameGenPlan {
uint32_t generations = 0;
uint32_t presents = 1;
@@ -599,32 +468,19 @@ private:
uint64_t fgSourceFrames_ = 0;
std::string lsfgCachePath_;
bool lsfgEngineTried_ = false;
std::unique_ptr<winfg::Engine> winfgEngine_;
bool winfgEngineTried_ = false;
std::atomic<int> fgEngineKind_{0}; // 0 = lsfg, 1 = win-fg
std::unique_ptr<ddfg::Engine> ddfgEngine_;
bool ddfgEngineTried_ = false;
std::atomic<int> fgEngineKind_{0};
std::atomic<int> fgModel_{4};
std::atomic<int> fgPerfPreset_{1};
bool ensureWinFgEngine();
// Capability gate for the SELECTED engine. win-fg's shaders need only a
// storage-capable swapchain format; the LSFG chain also needs the fp16 /
// memory-model feature set, which some otherwise capable GPUs lack.
bool ensureDdFgEngine();
bool fgCapsOk() const;
// Sync objects are indexed per PRESENT, not per composite: each pending
// present needs its own image-available and render-finished semaphore.
uint32_t syncSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
// Destroy and rebuild every acquire/present semaphore. Called with the
// swapchain, so no stale pending signal can survive into the new one.
void recreateSyncObjects();
uint32_t fgAcquireFailLog_ = 0;
// Measured PRESENTS per second - the rate that actually reaches the panel,
// counting generated frames. The pacer's own "loop rate" cannot serve this
// purpose: it is sampled once per SOURCE frame, so it always equals the
// guest rate and contains no evidence that generation happened at all.
float fgPresentedRate_ = 0.0f;
// Measured SOURCE frames per second over the same window, for engines that
// do not measure it themselves (win-fg).
float fgSourceRate_ = 0.0f;
uint32_t fgSourceAccum_ = 0;
uint32_t fgPresentAccum_ = 0;
@@ -633,22 +489,10 @@ private:
void trackPresentedRate(uint32_t presents);
bool ensureLsfgEngine();
// One command buffer per pending present: slot 0 carries the composite,
// the chain's shared passes and generated frame 0; each later generated
// frame and the real frame are recorded and submitted on their own, so a
// finished frame reaches the presentation engine without waiting for the
// rest of the chain.
uint32_t cmdSlot(uint32_t k) const { return currentFrame * kMaxPresentsPerFrame + k; }
// Shared chain passes (the 24 shaders every generated frame depends on).
void recordFrameGenProcess(VkCommandBuffer cb);
// Generated frame g: synthesise into a spare composite target and copy it
// into the swapchain image reserved for it.
void recordFrameGenGeneration(VkCommandBuffer cb, uint32_t g);
// Chain cost: a timestamp pair per frame slot, start before the shared
// passes, end after the last generation's compute (before its copy, so a
// vblank wait on the swapchain image is not counted). Read back after the
// slot's fence wait, one frame later.
VkQueryPool fgQueryPool_ = VK_NULL_HANDLE;
bool fgTimestampsOk_ = false;
float fgTimestampPeriodNs_ = 0.0f;
@@ -661,22 +505,21 @@ private:
void readFgQueryResult();
VkFormat offscreenFmt = VK_FORMAT_R8G8B8A8_UNORM;
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE; // CLEAR -> SHADER_READ_ONLY
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
VkPipelineLayout postPipeLayout = VK_NULL_HANDLE;
VkPipeline sgsrPipeline = VK_NULL_HANDLE;
VkPipeline nisPipeline = VK_NULL_HANDLE; // NVIDIA Image Scaling (mode 7)
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE; // SGSR 1 edge-direction (mode 8)
VkPipeline nisPipeline = VK_NULL_HANDLE;
VkPipeline sgsrQualityPipeline = VK_NULL_HANDLE;
VkPipeline easuPipeline = VK_NULL_HANDLE;
VkPipeline rcasPipeline = VK_NULL_HANDLE;
VkPipeline downscalePipeline = VK_NULL_HANDLE;
// CAS/HDR can be either a non-final pass (writes an fx target via
// offscreenRenderPass) or the final pass (writes the swapchain via renderPass);
// a pipeline is render-pass-specific, so keep one variant of each.
VkPipeline casPipelineOff = VK_NULL_HANDLE; // -> fx target (offscreenRenderPass)
VkPipeline casPipelineSwap = VK_NULL_HANDLE; // -> swapchain (renderPass)
VkPipeline casPipelineOff = VK_NULL_HANDLE;
VkPipeline casPipelineSwap = VK_NULL_HANDLE;
VkPipeline hdrPipelineOff = VK_NULL_HANDLE;
VkPipeline hdrPipelineSwap = VK_NULL_HANDLE;
// Phase 2 effects: same Off (-> fx target) / Swap (-> swapchain) variant pair.
VkPipeline fxaaPipelineOff = VK_NULL_HANDLE;
VkPipeline fxaaPipelineSwap = VK_NULL_HANDLE;
VkPipeline toonPipelineOff = VK_NULL_HANDLE;
@@ -687,10 +530,8 @@ private:
VkPipeline ntscPipelineSwap = VK_NULL_HANDLE;
VkPipeline crtPipelineOff = VK_NULL_HANDLE;
VkPipeline crtPipelineSwap = VK_NULL_HANDLE;
// Debanding is always the LAST effect (terminal) -> only a swapchain variant.
VkPipeline debandPipelineSwap = VK_NULL_HANDLE;
// offscreen composite target @ game/container resolution
VkImage offscreenImg = VK_NULL_HANDLE;
VkDeviceMemory offscreenMem = VK_NULL_HANDLE;
VkImageView offscreenView = VK_NULL_HANDLE;
@@ -698,7 +539,6 @@ private:
VkDescriptorSet offscreenDS = VK_NULL_HANDLE;
int offscreenW = 0, offscreenH = 0;
// FSR intermediate (EASU output) @ upscale output resolution
VkImage midImg = VK_NULL_HANDLE;
VkDeviceMemory midMem = VK_NULL_HANDLE;
VkImageView midView = VK_NULL_HANDLE;
@@ -706,8 +546,6 @@ private:
VkDescriptorSet midDS = VK_NULL_HANDLE;
int midW = 0, midH = 0;
// Effect-chain intermediates @ swapchain resolution. fx1 holds the scaled (or
// composited) image fed into CAS/HDR; fx2 ping-pongs when both effects run.
VkImage fx1Img = VK_NULL_HANDLE;
VkDeviceMemory fx1Mem = VK_NULL_HANDLE;
VkImageView fx1View = VK_NULL_HANDLE;
@@ -722,26 +560,22 @@ private:
VkDescriptorSet fx2DS = VK_NULL_HANDLE;
int fx2W = 0, fx2H = 0;
// Per-frame upscale plan, computed in renderFrame, consumed by recordCmdBuf.
// upFrame.mode reuses the upscalerMode enum (3=sgsr,4=fsr,5=fsr_fit,6=sharpen,7=nis,8=sgsr_quality)
// plus an internal sentinel (UPMODE_DOWNSCALE) for the supersampling path.
struct UpscaleFrame {
bool active = false;
int mode = 0;
int outX = 0, outY = 0, outW = 0, outH = 0;
bool cas = false; // run CAS this frame (snapshot of casEnabled)
bool hdr = false; // run HDR this frame (snapshot of hdrEnabled)
bool fxaa = false; // snapshot of fxaaEnabled
bool toon = false; // snapshot of toonEnabled
bool color = false; // snapshot of colorEnabled (non-neutral grade)
bool ntsc = false; // snapshot of ntscEnabled
bool crt = false; // snapshot of crtEnabled
bool deband = false;// snapshot of debandEnabled (terminal dither)
bool cas = false;
bool hdr = false;
bool fxaa = false;
bool toon = false;
bool color = false;
bool ntsc = false;
bool crt = false;
bool deband = false;
SgsrPushConstants sgsrPC{};
NisPushConstants nisPC{};
EasuPushConstants easuPC{};
RcasPushConstants rcasPC{};
DownscalePushConstants dsPC{};
CasPushConstants casPC{};
HdrPushConstants hdrPC{};
FxaaPushConstants fxaaPC{};
@@ -1,4 +1,4 @@
# NOTICE — FidelityFX Optical Flow (model 3)
# NOTICE - FidelityFX Optical Flow (model 3)
win-fg "model 3" (FSR3 Optical Flow) embeds compute shaders that are an
adaptation of the **AMD FidelityFX SDK FSR3 Optical Flow** algorithm.
+295
View File
@@ -0,0 +1,295 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fstream>
#include <sstream>
namespace ddfg {
struct Config {
bool enabled = false;
bool debug = false;
bool pacing = false;
int model = 4;
int multiplier = 2;
int extraImages = 2;
int perfPreset = 1;
float flowScale = 1.0f;
int gmMode = 0;
int frMode = 0;
int frIters = 4;
float frLambda = 2.0f;
float frDt = 0.25f;
float frEdge = 8.0f;
float frEps = 0.05f;
float beta = 8.0f;
float lambda = 0.6f;
float epsilon = 0.07f;
float photoScale = 7.5f;
float hudX0 = 0.0f;
float hudY0 = 0.0f;
float hudX1 = 0.0f;
float hudY1 = 0.0f;
bool capture = false;
int captureW = 1280;
int captureH = 720;
int capMode = 0;
int capPatches = 3;
int capPatchSize = 256;
float capMotion = 2.0f;
std::string capDir;
int capShardMB = 1024;
void sanitize() {
if (model < 3) model = 3; if (model > 4) model = 4;
if (multiplier < 2) multiplier = 2; if (multiplier > 4) multiplier = 4;
if (extraImages < 0) extraImages = 0; if (extraImages > 8) extraImages = 8;
if (perfPreset < 0 || perfPreset > 2) perfPreset = 1;
if (gmMode < 0) gmMode = 0; if (gmMode > 2) gmMode = 2;
if (frMode < 0) frMode = 0; if (frMode > 2) frMode = 2;
if (frIters < 0) frIters = 0; if (frIters > 16) frIters = 16;
if (frLambda < 0.0f) frLambda = 0.0f;
if (frDt < 0.01f) frDt = 0.01f; if (frDt > 4.0f) frDt = 4.0f;
if (frEdge < 0.0f) frEdge = 0.0f;
if (frEps < 1e-3f) frEps = 1e-3f;
if (flowScale < 0.05f) flowScale = 0.05f; if (flowScale > 4.0f) flowScale = 4.0f;
if (beta < 0.0f) beta = 0.0f; if (lambda < 0.0f) lambda = 0.0f;
if (captureW < 16) captureW = 16; if (captureW > 7680) captureW = 7680;
if (captureH < 16) captureH = 16; if (captureH > 4320) captureH = 4320;
if (capMode < 0) capMode = 0; if (capMode > 1) capMode = 1;
if (capPatches < 1) capPatches = 1; if (capPatches > 16) capPatches = 16;
if (capPatchSize < 16) capPatchSize = 16; if (capPatchSize > 1024) capPatchSize = 1024;
if (capMotion < 0.0f) capMotion = 0.0f;
if (capShardMB < 16) capShardMB = 16; if (capShardMB > 65536) capShardMB = 65536;
}
};
static inline float envf(const char* k, float d) {
const char* v = std::getenv(k); return v ? std::strtof(v, nullptr) : d;
}
static inline int envi(const char* k, int d) {
const char* v = std::getenv(k); return v ? std::atoi(v) : d;
}
static inline int parse_tristate(const std::string& v, int dflt) {
if (v.empty()) return dflt;
if (v == "auto") return 0;
if (v == "on" || v == "1" || v == "force" || v == "true" || v == "yes") return 1;
if (v == "off" || v == "0" || v == "false" || v == "no") return 2;
return dflt;
}
static inline bool parse_bool(const std::string& v, bool dflt) {
int t = parse_tristate(v, dflt ? 1 : 2);
return t == 1;
}
static inline int parse_capmode(const std::string& v, int dflt) {
if (v == "patch" || v == "patches" || v == "0") return 0;
if (v == "frame" || v == "frames" || v == "1") return 1;
return dflt;
}
static inline void apply_toml(Config& c, const std::string& path) {
std::ifstream f(path);
if (!f) return;
std::string line;
while (std::getline(f, line)) {
auto h = line.find('#'); if (h != std::string::npos) line = line.substr(0, h);
auto eq = line.find('='); if (eq == std::string::npos) continue;
auto trim = [](std::string s){
size_t a = s.find_first_not_of(" \t\r\n"); size_t b = s.find_last_not_of(" \t\r\n");
return (a == std::string::npos) ? std::string() : s.substr(a, b - a + 1); };
std::string k = trim(line.substr(0, eq)), v = trim(line.substr(eq + 1));
if (k.empty() || v.empty()) continue;
if (k == "enabled") c.enabled = (v == "1" || v == "true");
else if (k == "debug") c.debug = parse_bool(v, c.debug);
else if (k == "pacing") c.pacing = parse_bool(v, c.pacing);
else if (k == "model") c.model = std::atoi(v.c_str());
else if (k == "multiplier") c.multiplier = std::atoi(v.c_str());
else if (k == "extra_images") c.extraImages = std::atoi(v.c_str());
else if (k == "perf_preset") c.perfPreset = std::atoi(v.c_str());
else if (k == "global_motion") c.gmMode = parse_tristate(v, c.gmMode);
else if (k == "flow_reg") c.frMode = parse_tristate(v, c.frMode);
else if (k == "fr_iters") c.frIters = std::atoi(v.c_str());
else if (k == "fr_lambda") c.frLambda = std::strtof(v.c_str(), nullptr);
else if (k == "fr_dt") c.frDt = std::strtof(v.c_str(), nullptr);
else if (k == "fr_edge") c.frEdge = std::strtof(v.c_str(), nullptr);
else if (k == "fr_eps") c.frEps = std::strtof(v.c_str(), nullptr);
else if (k == "flowScale") c.flowScale = std::strtof(v.c_str(), nullptr);
else if (k == "beta") c.beta = std::strtof(v.c_str(), nullptr);
else if (k == "lambda") c.lambda = std::strtof(v.c_str(), nullptr);
else if (k == "epsilon") c.epsilon = std::strtof(v.c_str(), nullptr);
else if (k == "photoScale") c.photoScale = std::strtof(v.c_str(), nullptr);
else if (k == "capture") c.capture = parse_bool(v, c.capture);
else if (k == "capture_dir") c.capDir = v;
else if (k == "capture_width") c.captureW = std::atoi(v.c_str());
else if (k == "capture_height") c.captureH = std::atoi(v.c_str());
else if (k == "capture_mode") c.capMode = parse_capmode(v, c.capMode);
else if (k == "capture_patches") c.capPatches = std::atoi(v.c_str());
else if (k == "capture_patch_size") c.capPatchSize = std::atoi(v.c_str());
else if (k == "capture_motion") c.capMotion = std::strtof(v.c_str(), nullptr);
else if (k == "capture_shard_mb") c.capShardMB = std::atoi(v.c_str());
else if (k == "hudRect") {
float r[4] = {0,0,0,0}; int n = 0;
size_t start = 0; std::string s = v;
for (; n < 4; ++n) {
size_t comma = s.find(',', start);
std::string tok = s.substr(start, comma == std::string::npos ? std::string::npos : comma - start);
r[n] = std::strtof(tok.c_str(), nullptr);
if (comma == std::string::npos) break;
start = comma + 1;
}
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
}
}
}
static inline std::string conf_path() {
if (const char* p = std::getenv("DD_FG_CONF")) return p;
const char* home = std::getenv("HOME");
return home ? std::string(home) + "/.config/dd-fg/conf.toml" : std::string();
}
static inline std::string capture_root(const Config& c) {
if (!c.capDir.empty()) return c.capDir;
if (const char* home = std::getenv("HOME")) return std::string(home) + "/.cache/ddfg-capture";
return std::string("/tmp/ddfg-capture");
}
static inline Config load_config() {
Config c;
c.enabled = envi("DD_FG_ENABLE", 0) != 0;
if (const char* d = std::getenv("DD_FG_DEBUG")) c.debug = parse_bool(d, c.debug);
if (const char* pc = std::getenv("DD_FG_PACING")) c.pacing = parse_bool(pc, c.pacing);
c.model = envi("DD_FG_MODEL", c.model);
c.multiplier = envi("DD_FG_MULT", c.multiplier);
c.extraImages = envi("DD_FG_EXTRA_IMAGES", c.extraImages);
c.perfPreset = envi("DD_FG_PERF_PRESET", c.perfPreset);
if (const char* g = std::getenv("DD_FG_GM")) c.gmMode = parse_tristate(g, c.gmMode);
if (const char* r = std::getenv("DD_FG_FLOWREG")) c.frMode = parse_tristate(r, c.frMode);
c.frIters = envi("DD_FG_FR_ITERS", c.frIters);
c.frLambda = envf("DD_FG_FR_LAMBDA", c.frLambda);
c.frDt = envf("DD_FG_FR_DT", c.frDt);
c.frEdge = envf("DD_FG_FR_EDGE", c.frEdge);
c.frEps = envf("DD_FG_FR_EPS", c.frEps);
c.flowScale = envf("DD_FG_FLOWSCALE", c.flowScale);
c.beta = envf("DD_FG_BETA", c.beta);
c.lambda = envf("DD_FG_LAMBDA", c.lambda);
c.epsilon = envf("DD_FG_EPSILON", c.epsilon);
c.photoScale = envf("DD_FG_PHOTOSCALE", c.photoScale);
if (const char* cc = std::getenv("DD_FG_CAPTURE")) c.capture = parse_bool(cc, c.capture);
c.captureW = envi("DD_FG_CAPTURE_W", c.captureW);
c.captureH = envi("DD_FG_CAPTURE_H", c.captureH);
if (const char* cm = std::getenv("DD_FG_CAPTURE_MODE")) c.capMode = parse_capmode(cm, c.capMode);
c.capPatches = envi("DD_FG_CAPTURE_PATCHES", c.capPatches);
c.capPatchSize = envi("DD_FG_CAPTURE_PATCH", c.capPatchSize);
c.capMotion = envf("DD_FG_CAPTURE_MOTION", c.capMotion);
c.capShardMB = envi("DD_FG_CAPTURE_SHARD_MB", c.capShardMB);
if (const char* cd = std::getenv("DD_FG_CAPTURE_DIR")) c.capDir = cd;
if (const char* h = std::getenv("DD_FG_HUD_RECT")) {
float r[4] = {0,0,0,0}; int n = 0;
std::string s = h; size_t start = 0;
for (; n < 4; ++n) {
size_t comma = s.find(',', start);
r[n] = std::strtof(s.substr(start, comma == std::string::npos ? std::string::npos : comma - start).c_str(), nullptr);
if (comma == std::string::npos) break;
start = comma + 1;
}
c.hudX0 = r[0]; c.hudY0 = r[1]; c.hudX1 = r[2]; c.hudY1 = r[3];
}
const char* home = std::getenv("HOME");
if (home) apply_toml(c, std::string(home) + "/.config/dd-fg/conf.toml");
if (const char* p = std::getenv("DD_FG_CONF")) apply_toml(c, p);
c.sanitize();
return c;
}
struct SynthUBO {
float flowScale, alpha, beta, lambda, epsilon, photoScale, pad0, pad1;
float hudX0, hudY0, hudX1, hudY1;
};
struct FlowUBO { float flowScale; uint32_t level; float occlLo; float occlHi; };
struct GMUBO {
float lin[4];
float linT[4];
float app[4];
float appT[4];
};
struct FlowRegUBO {
float dt;
float lambda;
float edgeAlpha;
float epsTV;
};
}
@@ -1,18 +1,12 @@
// See winfg_engine.h.
#include "winfg_engine.h"
#include "winfg_vkd.h"
#include "ddfg_engine.h"
#include "ddfg_vkd.h"
#include "log.hpp"
#include <algorithm>
#include <cstring>
namespace winfg {
namespace ddfg {
namespace {
// 2x: the guest layer synthesises at 0.35, not 0.5 - biased towards prev to
// cut the double-exposure ghost on HUD/text ("iterative bring-up knob" in
// layer.cpp). Kept for parity, since that is the tuning that was proven.
// 3x/4x: evenly spaced; never exercised by the guest layer, so this is the
// new ground native opens up and the first thing to judge by eye.
float alphaFor(uint32_t k, uint32_t n) {
if (n <= 1) return 0.35f;
return (float)(k + 1) / (float)(n + 1);
@@ -30,25 +24,25 @@ VkImageMemoryBarrier bar(VkImage img, VkImageLayout from, VkImageLayout to,
return b;
}
} // namespace
}
Engine::Engine() = default;
Engine::~Engine() { destroyRing(); fg_.destroy(); }
bool Engine::init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
uint32_t queueFamily, VkQueue queue) {
if (!winfgVkdInit(table, dd_, id_)) {
WFG_LOGE("native: dispatch incomplete; frame generation unavailable");
if (!ddfgVkdInit(table, dd_, id_)) {
DDFG_LOGE("native: dispatch incomplete; frame generation unavailable");
return false;
}
phys_ = phys; dev_ = dev; qf_ = queueFamily; queue_ = queue;
id_.GetPhysicalDeviceMemoryProperties(phys_, &memProps_);
if (!fg_.init(&dd_, &id_, phys_, dev_, qf_, queue_)) {
WFG_LOGE("native: FrameGen::init failed");
DDFG_LOGE("native: FrameGen::init failed");
unavailable_ = true;
return false;
}
WFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
DDFG_LOGI("native: chain ready (10 pipelines, embedded shaders)");
return true;
}
@@ -65,9 +59,9 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
if (unavailable_ || w == 0 || h == 0 || fmt == VK_FORMAT_UNDEFINED) return false;
if (builtW_ == w && builtH_ == h && builtFmt_ == fmt && fg_.valid()) return true;
// onResize rebuilds the pyramids/scratch; our own input ring follows it.
if (!fg_.onResize(VkExtent2D{w, h}, fmt, /*force=*/false)) {
WFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
if (!fg_.onResize(VkExtent2D{w, h}, fmt, false)) {
DDFG_LOGE("native: onResize %ux%u failed; frame generation unavailable", w, h);
unavailable_ = true;
return false;
}
@@ -77,27 +71,25 @@ bool Engine::prepare(uint32_t w, uint32_t h, VkFormat fmt) {
}
builtW_ = w; builtH_ = h; builtFmt_ = fmt;
history_ = 0; curr_ = 0; frames_ = 0;
WFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
DDFG_LOGI("native: built %ux%u fmt=%d model=%d preset=%d flow=%.2f",
w, h, (int)fmt, cfg_.model, cfg_.perfPreset, (double)cfg_.flowScale);
return true;
}
static_assert(kMaxGenerations <= (uint32_t)FrameGen::kSynthSlots, "each generation needs its own synth slot");
uint32_t Engine::plan(uint32_t capacity) const {
if (!valid() || history_ < 2) return 0; // need prev AND curr
if (!valid() || history_ < 2) return 0;
const uint32_t want = (uint32_t)std::max(cfg_.multiplier - 1, 0);
return std::min({want, capacity, kMaxGenerations});
}
void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h,
uint32_t /*generations*/) {
uint32_t ) {
if (!valid()) return;
// Rotate: the slot we are about to overwrite is the OLD prev; after the
// copy it is the new curr and the other slot is prev.
curr_ = 1 - curr_;
Slot& dst = ring_[curr_];
// source is GENERAL (composite render pass leaves it there); the slot may
// be in any layout from last use - UNDEFINED is legal as "discard".
VkImageMemoryBarrier pre[2] = {
bar(source, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT),
@@ -115,8 +107,6 @@ void Engine::process(VkCommandBuffer cmd, VkImage source, uint32_t w, uint32_t h
dd_.CmdCopyImage(cmd, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dst.img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// source back to GENERAL for the real-frame copy that follows; the new
// curr becomes SHADER_READ_ONLY, which is what the chain samples it as.
VkImageMemoryBarrier post[2] = {
bar(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT),
@@ -138,18 +128,13 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
const Slot& curr = ring_[curr_];
if (!prev.valid || !curr.valid) return;
// The chain writes the output through a STORAGE descriptor in GENERAL.
// The composite ring already keeps its targets in GENERAL; make the
// prior contents' writes visible and take ownership for the compute write.
VkImageMemoryBarrier toWrite = bar(targetImage, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT);
dd_.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toWrite);
// gmSlot indexes the C1 global-motion SSBO read back on the CPU next
// frame; kGmSlots (3) >= our frames in flight (2), so rotating per source
// frame keeps a slot from being read while its reduce is still in flight.
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_);
fg_.record(cmd, prev.view, curr.view, targetView, alphaFor(generation, count), gmSlot_,
generation, generation > 0);
if (generation + 1 == count) gmSlot_ = (gmSlot_ + 1) % (uint32_t)FrameGen::kGmSlots;
(void)w; (void)h;
@@ -157,7 +142,6 @@ void Engine::generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t cou
void Engine::reset() { history_ = 0; frames_ = 0; }
// ---- input ring --------------------------------------------------------------
uint32_t Engine::memType(uint32_t bits, VkMemoryPropertyFlags props) const {
for (uint32_t i = 0; i < memProps_.memoryTypeCount; i++)
@@ -201,4 +185,4 @@ void Engine::destroyRing() {
history_ = 0;
}
} // namespace winfg
}
@@ -0,0 +1,69 @@
#pragma once
#include <cstdint>
#include <vulkan/vulkan.h>
#include "framegen.hpp"
#include "vk_dispatch.hpp"
struct VkTable;
namespace ddfg {
constexpr uint32_t kMaxGenerations = 3;
class Engine {
public:
Engine();
~Engine();
Engine(const Engine&) = delete;
Engine& operator=(const Engine&) = delete;
bool init(const VkTable& table, VkPhysicalDevice phys, VkDevice dev,
uint32_t queueFamily, VkQueue queue);
bool valid() const { return fg_.valid() && !unavailable_; }
bool unavailable() const { return unavailable_; }
void configure(uint32_t multiplier, int model, int perfPreset, float flowScale);
bool prepare(uint32_t width, uint32_t height, VkFormat format);
uint32_t plan(uint32_t capacity) const;
void process(VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height,
uint32_t generations);
void generateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t count,
VkImage targetImage, VkImageView targetView,
uint32_t width, uint32_t height);
void reset();
private:
struct Slot { VkImage img = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; bool valid = false; };
bool makeSlot(Slot& s, uint32_t w, uint32_t h, VkFormat fmt);
void destroySlot(Slot& s);
void destroyRing();
uint32_t memType(uint32_t bits, VkMemoryPropertyFlags props) const;
DeviceDispatch dd_{};
InstanceDispatch id_{};
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
VkDevice dev_ = VK_NULL_HANDLE;
uint32_t qf_ = 0;
VkQueue queue_ = VK_NULL_HANDLE;
VkPhysicalDeviceMemoryProperties memProps_{};
FrameGen fg_;
Config cfg_;
bool unavailable_ = false;
Slot ring_[2];
int curr_ = 0;
uint32_t history_ = 0;
uint32_t gmSlot_ = 0;
uint32_t builtW_ = 0, builtH_ = 0;
VkFormat builtFmt_ = VK_FORMAT_UNDEFINED;
uint64_t frames_ = 0;
};
}
@@ -1,15 +1,14 @@
// See winfg_vkd.h.
#include "winfg_vkd.h"
#include "ddfg_vkd.h"
#include "../VulkanRendererContext.h"
#include <android/log.h>
bool winfgVkdInit(const VkTable& table, winfg::DeviceDispatch& dd, winfg::InstanceDispatch& id) {
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id) {
bool ok = true;
#define COPY(dst, fn) \
do { \
dst.fn = table.fn; \
if (!dst.fn) { \
__android_log_print(ANDROID_LOG_ERROR, "WinFgVkd", \
__android_log_print(ANDROID_LOG_ERROR, "DdFgVkd", \
"entry point vk" #fn " did not resolve"); \
ok = false; \
} \
@@ -0,0 +1,4 @@
#pragma once
#include "vk_dispatch.hpp"
struct VkTable;
bool ddfgVkdInit(const VkTable& table, ddfg::DeviceDispatch& dd, ddfg::InstanceDispatch& id);
@@ -1,9 +1,7 @@
// GENERATED by tools/embed_spv.py — do not edit by hand.
// win-fg embedded SPIR-V: our optical-flow front end + synthesis only.
#pragma once
#include <cstdint>
#include <cstddef>
namespace winfg { namespace embedded {
namespace ddfg { namespace embedded {
static const uint32_t OF3_LUMA_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000054u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00030005u,0x00000030u,0x00007675u,0x00030005u,0x0000003bu,0x00626772u,0x00040005u,0x0000003fu,0x63725375u,0x00000000u,0x00030005u,0x00000047u,0x00005966u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x0000003fu,0x00000021u,0x00000020u,0x00040047u,0x0000003fu,0x00000022u,0x00000000u,0x00040047u,0x00000053u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x0004002bu,0x00000013u,0x00000033u,0x3f000000u,0x00040017u,0x00000039u,0x00000013u,0x00000003u,0x00040020u,0x0000003au,0x00000007u,0x00000039u,0x00090019u,0x0000003cu,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x0000003du,0x0000003cu,0x00040020u,0x0000003eu,0x00000000u,0x0000003du,0x0004003bu,0x0000003eu,0x0000003fu,0x00000000u,0x0004002bu,0x00000013u,0x00000042u,0x00000000u,0x00040017u,0x00000043u,0x00000013u,0x00000004u,0x00040020u,0x00000046u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000049u,0x3e59b3d0u,0x0004002bu,0x00000013u,0x0000004au,0x3f371759u,0x0004002bu,0x00000013u,0x0000004bu,0x3d93dd98u,0x0006002cu,0x00000039u,0x0000004cu,0x00000049u,0x0000004au,0x0000004bu,0x0004002bu,0x0000000au,0x00000052u,0x00000010u,0x0006002cu,0x0000000bu,0x00000053u,0x00000052u,0x00000052u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000003au,0x0000003bu,0x00000007u,0x0004003bu,0x00000046u,0x00000047u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000007u,0x00000031u,0x00000009u,0x0004006fu,0x0000002eu,0x00000032u,0x00000031u,0x00050050u,0x0000002eu,0x00000034u,0x00000033u,0x00000033u,0x00050081u,0x0000002eu,0x00000035u,0x00000032u,0x00000034u,0x0004003du,0x00000007u,0x00000036u,0x00000012u,0x0004006fu,0x0000002eu,0x00000037u,0x00000036u,0x00050088u,0x0000002eu,0x00000038u,0x0Line truncated
static const size_t OF3_LUMA_LEN = 481;
static const uint32_t OF3_DOWNSAMPLE_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x0000008au,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000000du,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00030005u,0x00000009u,0x00646967u,0x00080005u,0x0000000du,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000016u,0x74734475u,0x00000000u,0x00040005u,0x00000030u,0x44637273u,0x00006d69u,0x00040005u,0x00000034u,0x63725375u,0x00000000u,0x00040005u,0x0000003au,0x65736162u,0x00000000u,0x00030005u,0x00000044u,0x00000068u,0x00030005u,0x0000004au,0x00000061u,0x00030005u,0x00000059u,0x00000062u,0x00030005u,0x00000065u,0x00000063u,0x00030005u,0x00000071u,0x00000064u,0x00040047u,0x0000000du,0x0000000bu,0x0000001cu,0x00030047u,0x00000016u,0x00000019u,0x00040047u,0x00000016u,0x00000021u,0x00000030u,0x00040047u,0x00000016u,0x00000022u,0x00000000u,0x00040047u,0x00000034u,0x00000021u,0x00000020u,0x00040047u,0x00000034u,0x00000022u,0x00000000u,0x00040047u,0x00000089u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00040015u,0x0000000au,0x00000020u,0x00000000u,0x00040017u,0x0000000bu,0x0000000au,0x00000003u,0x00040020u,0x0000000cu,0x00000001u,0x0000000bu,0x0004003bu,0x0000000cu,0x0000000du,0x00000001u,0x00040017u,0x0000000eu,0x0000000au,0x00000002u,0x00030016u,0x00000013u,0x00000020u,0x00090019u,0x00000014u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000002u,0x00000003u,0x00040020u,0x00000015u,0x00000000u,0x00000014u,0x0004003bu,0x00000015u,0x00000016u,0x00000000u,0x00020014u,0x00000019u,0x0004002bu,0x0000000au,0x0000001au,0x00000000u,0x00040020u,0x0000001bu,0x00000007u,0x00000006u,0x0004002bu,0x0000000au,0x00000024u,0x00000001u,0x00040017u,0x0000002eu,0x00000013u,0x00000002u,0x00040020u,0x0000002fu,0x00000007u,0x0000002eu,0x00090019u,0x00000031u,0x00000013u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000032u,0x00000031u,0x00040020u,0x00000033u,0x00000000u,0x00000032u,0x0004003bu,0x00000033u,0x00000034u,0x00000000u,0x0004002bu,0x00000006u,0x00000036u,0x00000000u,0x0004002bu,0x00000013u,0x0000003du,0x40000000u,0x0004002bu,0x00000013u,0x0000003fu,0x3f800000u,0x0004002bu,0x00000013u,0x00000045u,0x3f000000u,0x00040020u,0x00000049u,0x00000007u,0x00000013u,0x0004002bu,0x00000013u,0x00000055u,0x00000000u,0x00040017u,0x00000056u,0x00000013u,0x00000004u,0x0004002bu,0x00000013u,0x00000085u,0x3e800000u,0x0004002bu,0x0000000au,0x00000088u,0x00000010u,0x0006002cu,0x0000000bu,0x00000089u,0x00000088u,0x00000088u,0x00000024u,0x00050036u,0x00000002u,0x00000004u,0x00000000u,0x00000003u,0x000200f8u,0x00000005u,0x0004003bu,0x00000008u,0x00000009u,0x00000007u,0x0004003bu,0x00000008u,0x00000012u,0x00000007u,0x0004003bu,0x0000002fu,0x00000030u,0x00000007u,0x0004003bu,0x0000002fu,0x0000003au,0x00000007u,0x0004003bu,0x0000002fu,0x00000044u,0x00000007u,0x0004003bu,0x00000049u,0x0000004au,0x00000007u,0x0004003bu,0x00000049u,0x00000059u,0x00000007u,0x0004003bu,0x00000049u,0x00000065u,0x00000007u,0x0004003bu,0x00000049u,0x00000071u,0x00000007u,0x0004003du,0x0000000bu,0x0000000fu,0x0000000du,0x0007004fu,0x0000000eu,0x00000010u,0x0000000fu,0x0000000fu,0x00000000u,0x00000001u,0x0004007cu,0x00000007u,0x00000011u,0x00000010u,0x0003003eu,0x00000009u,0x00000011u,0x0004003du,0x00000014u,0x00000017u,0x00000016u,0x00040068u,0x00000007u,0x00000018u,0x00000017u,0x0003003eu,0x00000012u,0x00000018u,0x00050041u,0x0000001bu,0x0000001cu,0x00000009u,0x0000001au,0x0004003du,0x00000006u,0x0000001du,0x0000001cu,0x00050041u,0x0000001bu,0x0000001eu,0x00000012u,0x0000001au,0x0004003du,0x00000006u,0x0000001fu,0x0000001eu,0x000500afu,0x00000019u,0x00000020u,0x0000001du,0x0000001fu,0x000400a8u,0x00000019u,0x00000021u,0x00000020u,0x000300f7u,0x00000023u,0x00000000u,0x000400fau,0x00000021u,0x00000022u,0x00000023u,0x000200f8u,0x00000022u,0x00050041u,0x0000001bu,0x00000025u,0x00000009u,0x00000024u,0x0004003du,0x00000006u,0x00000026u,0x00000025u,0x00050041u,0x0000001bu,0x00000027u,0x00000012u,0x00000024u,0x0004003du,0x00000006u,0x00000028u,0x00000027u,0x000500afu,0x00000019u,0x00000029u,0x00000026u,0x00000028u,0x000200f9u,0x00000023u,0x000200f8u,0x00000023u,0x000700f5u,0x00000019u,0x0000002au,0x00000020u,0x00000005u,0x00000029u,0x00000022u,0x000300f7u,0x0000002cu,0x00000000u,0x000400fau,0x0000002au,0x0000002bu,0x0000002cu,0x000200f8u,0x0000002bu,0x000100fdu,0x000200f8u,0x0000002cu,0x0004003du,0x00000032u,0x00000035u,0x00000034u,0x00040064u,0x00000031u,0x00000037u,0x00000035u,0x0005006Line truncated
@@ -22,10 +20,10 @@ static const uint32_t OF3_GM_PREWARP_SPV[] = {0x07230203u,0x00010300u,0x0008000b
static const size_t OF3_GM_PREWARP_LEN = 706;
static const uint32_t OF3_FLOWREG_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000144u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x00000037u,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00060005u,0x0000000eu,0x63746566u,0x76285568u,0x763b3269u,0x003b3269u,0x00030005u,0x0000000cu,0x00000070u,0x00030005u,0x0000000du,0x006d6964u,0x00060005u,0x00000013u,0x63746566u,0x76284c68u,0x763b3269u,0x003b3269u,0x00030005u,0x00000011u,0x00000070u,0x00030005u,0x00000012u,0x006d6964u,0x00040005u,0x00000018u,0x6f6c4675u,0x006e4977u,0x00040005u,0x00000026u,0x6d754c75u,0x00000061u,0x00030005u,0x00000034u,0x00646967u,0x00080005u,0x00000037u,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x0000003cu,0x006d6964u,0x00030005u,0x00000055u,0x00007464u,0x00030005u,0x00000056u,0x00000055u,0x00050006u,0x00000056u,0x00000000u,0x61726170u,0x0000736du,0x00030005u,0x00000058u,0x00000000u,0x00040005u,0x0000005cu,0x626d616cu,0x00006164u,0x00050005u,0x0000005fu,0x65676465u,0x68706c41u,0x00000061u,0x00040005u,0x00000063u,0x54737065u,0x00000056u,0x00030005u,0x00000068u,0x00004375u,0x00040005u,0x00000069u,0x61726170u,0x0000006du,0x00040005u,0x0000006bu,0x61726170u,0x0000006du,0x00030005u,0x0000006eu,0x00004c75u,0x00040005u,0x00000073u,0x61726170u,0x0000006du,0x00040005u,0x00000074u,0x61726170u,0x0000006du,0x00030005u,0x00000077u,0x00005275u,0x00040005u,0x0000007bu,0x61726170u,0x0000006du,0x00040005u,0x0000007cu,0x61726170u,0x0000006du,0x00030005u,0x0000007fu,0x00005575u,0x00040005u,0x00000083u,0x61726170u,0x0000006du,0x00040005u,0x00000084u,0x61726170u,0x0000006du,0x00030005u,0x00000087u,0x00004475u,0x00040005u,0x0000008bu,0x61726170u,0x0000006du,0x00040005u,0x0000008cu,0x61726170u,0x0000006du,0x00040005u,0x0000008fu,0x6d69646cu,0x00000000u,0x00030005u,0x00000093u,0x00004c6cu,0x00040005u,0x00000096u,0x61726170u,0x0000006du,0x00040005u,0x00000097u,0x61726170u,0x0000006du,0x00030005u,0x0000009au,0x0000526cu,0x00040005u,0x0000009du,0x61726170u,0x0000006du,0x00040005u,0x0000009eu,0x61726170u,0x0000006du,0x00030005u,0x000000a1u,0x0000556cu,0x00040005u,0x000000a4u,0x61726170u,0x0000006du,0x00040005u,0x000000a5u,0x61726170u,0x0000006du,0x00030005u,0x000000a8u,0x0000446cu,0x00040005u,0x000000abu,0x61726170u,0x0000006du,0x00040005u,0x000000acu,0x61726170u,0x0000006du,0x00040005u,0x000000afu,0x67616d67u,0x00000000u,0x00030005u,0x000000bbu,0x00000067u,0x00030005u,0x000000c4u,0x00004c64u,0x00030005u,0x000000c8u,0x00005264u,0x00030005u,0x000000ccu,0x00005564u,0x00030005u,0x000000d0u,0x00004464u,0x00030005u,0x000000d4u,0x00003265u,0x00030005u,0x000000d8u,0x00004c77u,0x00030005u,0x000000e1u,0x00005277u,0x00030005u,0x000000eau,0x00005577u,0x00030005u,0x000000f3u,0x00004477u,0x00040005u,0x000000fcu,0x576d7573u,0x00000000u,0x00040005u,0x00000104u,0x576d7573u,0x00000055u,0x00040005u,0x00000114u,0x66694475u,0x00000066u,0x00030005u,0x00000120u,0x00003066u,0x00050005u,0x00000121u,0x6f6c4675u,0x73624f77u,0x00000000u,0x00040005u,0x0000012au,0x74656874u,0x00000061u,0x00030005u,0x0000012eu,0x00000072u,0x00040005u,0x00000132u,0x77654e75u,0x00000000u,0x00050005u,0x0000013eu,0x6f6c4675u,0x74754f77u,0x00000000u,0x00040047u,0x00000018u,0x00000021u,0x00000020u,0x00040047u,0x00000018u,0x00000022u,0x00000000u,0x00040047u,0x00000026u,0x00000021u,0x00000022u,0x00040047u,0x00000026u,0x00000022u,0x00000000u,0x00040047u,0x00000037u,0x0000000bu,0x0000001cu,0x00030047u,0x00000056u,0x00000002u,0x00050048u,0x00000056u,0x00000000u,0x00000023u,0x00000000u,0x00040047u,0x00000058u,0x00000021u,0x00000000u,0x00040047u,0x00000058u,0x00000022u,0x00000000u,0x00040047u,0x00000121u,0x00000021u,0x00000021u,0x00040047u,0x00000121u,0x00000022u,0x00000000u,0x00030047u,0x0000013eu,0x00000019u,0x00040047u,0x0000013eu,0x00000021u,0x00000030u,0x00040047u,0x0000013eu,0x00000022u,0x00000000u,0x00040047u,0x00000143u,0x0000000bu,0x00000019u,0x00020013u,0x00000002u,0x00030021u,0x00000003u,0x00000002u,0x00040015u,0x00000006u,0x00000020u,0x00000001u,0x00040017u,0x00000007u,0x00000006u,0x00000002u,0x00040020u,0x00000008u,0x00000007u,0x00000007u,0x00030016u,0x00000009u,0x00000020u,0x00040017u,0x0000000au,0x00000009u,0x00000004u,0x00050021u,0x0000000bu,0x0000000au,0x00000008u,0x00000008u,0x00050021u,0x00000010u,0x00000009u,0x00000008u,0x00000008u,0x00090019u,0x00000015u,0x00000009u,0x00000001u,0x00000000u,0x00000000u,0x00000000u,0x00000001u,0x00000000u,0x0003001bu,0x00000016u,0x00000015u,0x00040020u,0x00000017u,0x00000000u,0x00000016u,0x0004003bu,0x00000017u,0x00000018u,0x00000000u,0x0004002bu,0x00000006u,0x0000001bu,0x00000000u,0x0005002cu,0x00000007u,0x0000001cu,0x0000001bu,0x0000001bu,0x0004002bu,0x00000006u,0x0000001eu,0x00000001u,0x0004003bu,0x00000017u,0x00000026u,0x00000000u,Line truncated
static const size_t OF3_FLOWREG_LEN = 1936;
static const uint32_t WFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0xLine truncated
static const size_t WFG_SYNTH_LEN = 3082;
static const uint32_t DDFG_SYNTH_SPV[] = {0x07230203u,0x00010300u,0x0008000bu,0x00000208u,0x00000000u,0x00020011u,0x00000001u,0x00020011u,0x00000032u,0x0006000bu,0x00000001u,0x4c534c47u,0x6474732eu,0x3035342eu,0x00000000u,0x0003000eu,0x00000000u,0x00000001u,0x0006000fu,0x00000005u,0x00000004u,0x6e69616du,0x00000000u,0x0000001cu,0x00060010u,0x00000004u,0x00000011u,0x00000010u,0x00000010u,0x00000001u,0x00030003u,0x00000002u,0x000001c2u,0x00040005u,0x00000004u,0x6e69616du,0x00000000u,0x00050005u,0x0000000bu,0x616d756cu,0x33667628u,0x0000003bu,0x00030005u,0x0000000au,0x00000063u,0x00030005u,0x00000018u,0x00646967u,0x00080005u,0x0000001cu,0x475f6c67u,0x61626f6cu,0x766e496cu,0x7461636fu,0x496e6f69u,0x00000044u,0x00030005u,0x00000021u,0x006d6964u,0x00040005u,0x00000024u,0x74754f75u,0x00000000u,0x00040005u,0x0000003eu,0x44766e69u,0x00006d69u,0x00030005u,0x00000044u,0x00007675u,0x00030005u,0x0000004du,0x00000055u,0x00060006u,0x0000004du,0x00000000u,0x776f6c66u,0x6c616353u,0x00000065u,0x00050006u,0x0000004du,0x00000001u,0x68706c61u,0x00000061u,0x00050006u,0x0000004du,0x00000002u,0x61746562u,0x00000000u,0x00050006u,0x0000004du,0x00000003u,0x626d616cu,0x00006164u,0x00050006u,0x0000004du,0x00000004u,0x69737065u,0x006e6f6cu,0x00060006u,0x0000004du,0x00000005u,0x746f6870u,0x6163536fu,0x0000656cu,0x00050006u,0x0000004du,0x00000006u,0x30646170u,0x00000000u,0x00050006u,0x0000004du,0x00000007u,0x31646170u,0x00000000u,0x00050006u,0x0000004du,0x00000008u,0x52647568u,0x00746365u,0x00030005u,0x0000004fu,0x00000000u,0x00040005u,0x0000008cu,0x72754375u,0x00000072u,0x00030005u,0x00000098u,0x00676264u,0x00030005u,0x0000009fu,0x00007366u,0x00040005u,0x000000a9u,0x64774273u,0x00000000u,0x00050005u,0x000000aau,0x6f6c4675u,0x64774277u,0x00000000u,0x00040005u,0x000000aeu,0x64774673u,0x00000000u,0x00050005u,0x000000afu,0x6f6c4675u,0x64774677u,0x00000000u,0x00030005u,0x000000b3u,0x00313046u,0x00030005u,0x000000bau,0x00303146u,0x00040005u,0x000000c1u,0x31306663u,0x00000000u,0x00040005u,0x000000c5u,0x30316663u,0x00000000u,0x00030005u,0x000000c9u,0x0031306du,0x00030005u,0x000000d5u,0x0030316du,0x00030005u,0x000000e0u,0x00004170u,0x00030005u,0x000000e8u,0x00004270u,0x00030005u,0x000000f0u,0x00003043u,0x00040005u,0x000000f1u,0x65725075u,0x00000076u,0x00030005u,0x000000f6u,0x00003143u,0x00030005u,0x000000fbu,0x00004165u,0x00040005u,0x000000fcu,0x61726170u,0x0000006du,0x00040005u,0x0000010cu,0x61726170u,0x0000006du,0x00030005u,0x00000111u,0x00004265u,0x00040005u,0x00000112u,0x61726170u,0x0000006du,0x00040005u,0x00000122u,0x61726170u,0x0000006du,0x00040005u,0x00000127u,0x76556266u,0x00000000u,0x00040005u,0x0000012bu,0x65526266u,0x00646973u,0x00040005u,0x00000138u,0x736e6f63u,0x00747369u,0x00030005u,0x00000149u,0x0000305au,0x00030005u,0x00000157u,0x0000315au,0x00040005u,0x00000163u,0x30756174u,0x00000000u,0x00040005u,0x00000167u,0x31756174u,0x00000000u,0x00040005u,0x0000016au,0x78616d7au,0x00000000u,0x00030005u,0x0000016eu,0x00003057u,0x00030005u,0x00000179u,0x00003157u,0x00040005u,0x00000183u,0x6d757357u,0x00000000u,0x00040005u,0x00000187u,0x70726177u,0x00006465u,0x00050005u,0x000001a0u,0x736f7263u,0x64616673u,0x00000065u,0x00050005u,0x000001adu,0x746f6870u,0x7272456fu,0x00000000u,0x00050005u,0x000001b1u,0x746f6870u,0x7572546fu,0x00007473u,0x00040005u,0x000001b7u,0x73757274u,0x00000074u,0x00040005u,0x000001c3u,0x76657270u,0x00000043u,0x00040005u,0x000001c8u,0x72727563u,0x00000043u,0x00050005u,0x000001cdu,0x746e6f63u,0x44746e65u,0x00666669u,0x00050005u,0x000001d2u,0x72746e75u,0x65747375u,0x00000064u,0x00040005u,0x000001d7u,0x44676962u,0x00666669u,0x00050005u,0x000001dcu,0x70616e73u,0x75436f54u,0x00007272u,0x00040005u,0x000001e5u,0x6374756fu,0x00000000u,0x00030005u,0x000001eeu,0x0067616du,0x00030005u,0x000001f2u,0x0000006eu,0x00030005u,0x000001f6u,0x00746173u,0x00040047u,0x0000001cu,0x0000000bu,0x0000001cu,0x00030047u,0x00000024u,0x00000019u,0x00040047u,0x00000024u,0x00000021u,0x00000030u,0x00040047u,0x00000024u,0x00000022u,0x00000000u,0x00030047u,0x0000004du,0x00000002u,0x00050048u,0x0000004du,0x00000000u,0x00000023u,0x00000000u,0x00050048u,0x0000004du,0x00000001u,0x00000023u,0x00000004u,0x00050048u,0x0000004du,0x00000002u,0x00000023u,0x00000008u,0x00050048u,0x0000004du,0x00000003u,0x00000023u,0x0000000cu,0x00050048u,0x0000004du,0x00000004u,0x00000023u,0x00000010u,0x00050048u,0x0000004du,0x00000005u,0x00000023u,0x00000014u,0x00050048u,0x0000004du,0x00000006u,0x00000023u,0x00000018u,0x00050048u,0x0000004du,0x00000007u,0x00000023u,0x0000001cu,0x00050048u,0x0000004du,0x00000008u,0x00000023u,0x00000020u,0x00040047u,0x0000004fu,0x00000021u,0x00000000u,0x00040047u,0x0000004fu,0x00000022u,0x00000000u,0x00040047u,0x0000008cu,0x00000021u,0x00000021u,0x00040047u,0x0000008cu,0x00000022u,0x00000000u,0x00040047u,0x000000aau,0x00000021u,0x00000023u,0x00040047u,0x000000aau,0x00000022u,0x00000000u,0x00040047u,0x000000afu,0x00000021u,0x00000022u,0x00040047u,0x000000afu,0x00000022u,0x00000000u,0x00040047u,0x000000f1u,0x00000021u,0x00000020u,0x00040047u,0x000000f1u,0Line truncated
static const size_t DDFG_SYNTH_LEN = 3082;
struct Blob { const uint32_t* code; size_t words; };
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, WFG_SYNTH, SHADER_COUNT };
enum Shader { OF3_LUMA, OF3_DOWNSAMPLE, OF3_FLOW, OF3_EXPAND, OF3_FLOW_M4, OF3_EXPAND_M4, OF3_GM_REDUCE, OF3_GM_PREWARP, OF3_FLOWREG, DDFG_SYNTH, SHADER_COUNT };
static inline Blob blob(Shader s) {
switch (s) {
case OF3_LUMA: return { OF3_LUMA_SPV, OF3_LUMA_LEN };
@@ -37,8 +35,8 @@ static inline Blob blob(Shader s) {
case OF3_GM_REDUCE: return { OF3_GM_REDUCE_SPV, OF3_GM_REDUCE_LEN };
case OF3_GM_PREWARP: return { OF3_GM_PREWARP_SPV, OF3_GM_PREWARP_LEN };
case OF3_FLOWREG: return { OF3_FLOWREG_SPV, OF3_FLOWREG_LEN };
case WFG_SYNTH: return { WFG_SYNTH_SPV, WFG_SYNTH_LEN };
case DDFG_SYNTH: return { DDFG_SYNTH_SPV, DDFG_SYNTH_LEN };
default: return { nullptr, 0 };
}
}
} } // namespace winfg::embedded
} }
@@ -1,13 +1,12 @@
// win-fg — compute frame-generation engine implementation.
#include "framegen.hpp"
#include "embedded_shaders.hpp"
#include "log.hpp"
#include <cmath>
#include <cstdio>
namespace winfg {
namespace ddfg {
#define VKOK(x) do { if ((x) != VK_SUCCESS) { WFG_LOGE("vk fail: %s", #x); return false; } } while (0)
#define VKOK(x) do { if ((x) != VK_SUCCESS) { DDFG_LOGE("vk fail: %s", #x); return false; } } while (0)
uint32_t FrameGen::findMemType(uint32_t bits, VkMemoryPropertyFlags props) const {
for (uint32_t i = 0; i < memProps_.memoryTypeCount; ++i)
@@ -49,10 +48,6 @@ void FrameGen::destroyImage(Img& i) {
i = Img{};
}
// C1: allocate the per-slot host-visible SSBOs the LK reduce writes partials into.
// Host-visible + coherent so the CPU can read them back after the owning frame's
// fence signals (no staging copy needed; the buffers are tiny — kGmThreads*kGmStride
// floats each, ~64 KB). Persistently mapped.
bool FrameGen::makeGmBuffers() {
const VkDeviceSize sz = (VkDeviceSize)kGmThreads * kGmStride * sizeof(float);
const VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
@@ -75,8 +70,6 @@ bool FrameGen::makeGmBuffers() {
bool FrameGen::makePipe(Pipe& p, int shaderId,
const std::vector<VkDescriptorType>&) {
// binding layout is derived per shader below in init(); here we only build
// the module + a generic layout supplied by caller via setLayout already set.
embedded::Blob b = embedded::blob((embedded::Shader)shaderId);
VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
mi.codeSize = b.words * 4; mi.pCode = b.code;
@@ -101,7 +94,6 @@ void FrameGen::destroyPipe(Pipe& p) {
p = Pipe{};
}
// Build a descriptor-set layout from an explicit (binding, type) list.
static bool buildSetLayout(const DeviceDispatch* dd, VkDevice dev,
const std::vector<std::pair<uint32_t, VkDescriptorType>>& binds,
VkDescriptorSetLayout& out) {
@@ -123,12 +115,11 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
VKOK(dd_->CreateSampler(device_, &si, nullptr, &linear_));
// descriptor pool sized generously for our per-frame sets.
VkDescriptorPoolSize sizes[] = {
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 256},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 256},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 64},
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}, // C1 LK-reduce SSBOs
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16},
};
VkDescriptorPoolCreateInfo pci{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
pci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
@@ -141,19 +132,15 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
const auto U = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
const auto SB = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
// per-shader binding sets (match the GLSL layout(binding=...) declarations)
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pLuma_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{32,S},B{48,W}}, pDown_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlow_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowM4_.setLayout)) return false;
// C1: expand sets gain a second UBO (binding 1 = GMUBO) for the global-motion add-back.
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpand_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{1,U},B{32,S},B{48,W},B{49,W}}, pExpandM4_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{35,S},B{48,W}}, pSynth_.setLayout)) return false;
// C1: LK-reduce (UBO, SSBO, prev, curr) and curr-luma stabilization prewarp (UBO, src, dst).
if (!buildSetLayout(dd_, dev, {B{1,U},B{16,SB},B{32,S},B{33,S}}, pGmReduce_.setLayout)) return false;
if (!buildSetLayout(dd_, dev, {B{1,U},B{32,S},B{48,W}}, pGmPrewarp_.setLayout)) return false;
// C2: flow-reg (UBO, u_in, f0_obs, luma, out).
if (!buildSetLayout(dd_, dev, {B{0,U},B{32,S},B{33,S},B{34,S},B{48,W}}, pFlowReg_.setLayout)) return false;
std::vector<VkDescriptorType> unused;
@@ -166,9 +153,9 @@ bool FrameGen::init(const DeviceDispatch* dd, const InstanceDispatch* id,
if (!makePipe(pGmReduce_, embedded::OF3_GM_REDUCE, unused)) return false;
if (!makePipe(pGmPrewarp_, embedded::OF3_GM_PREWARP, unused)) return false;
if (!makePipe(pFlowReg_, embedded::OF3_FLOWREG, unused)) return false;
if (!makePipe(pSynth_, embedded::WFG_SYNTH, unused)) return false;
if (!makeGmBuffers()) { WFG_LOGE("framegen: GM SSBO alloc failed — C1 disabled"); }
WFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
if (!makePipe(pSynth_, embedded::DDFG_SYNTH, unused)) return false;
if (!makeGmBuffers()) { DDFG_LOGE("framegen: GM SSBO alloc failed - C1 disabled"); }
DDFG_LOGI("framegen init ok (queueFamily=%u, 10 pipelines, model=%d, C1 gm=%d, C2 flow_reg=%d iters=%d)",
queueFamily_, cfg_.model, cfg_.gmMode, cfg_.frMode, cfg_.frIters);
return true;
}
@@ -177,23 +164,19 @@ static VkExtent2D levelExtent(VkExtent2D e, int l) {
return { e.width >> l ? e.width >> l : 1u, e.height >> l ? e.height >> l : 1u };
}
bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force) {
// force skips the same-extent early-out so a perf_preset change (which alters
// flowFinest_ and therefore the per-size flow-image resolutions) rebuilds them.
bool FrameGen::onResize(VkExtent2D extent, VkFormat , bool force) {
if (!force && ready_ && extent.width == extent_.width && extent.height == extent_.height) return true;
// tear down previous size
for (auto& i : pyrA_) destroyImage(i); pyrA_.clear();
for (auto& i : pyrB_) destroyImage(i); pyrB_.clear();
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
destroyImage(flowExpA_); destroyImage(flowExpB_);
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
destroyImage(flowRegA_); destroyImage(flowRegB_);
extent_ = extent; ready_ = false;
const VkImageUsageFlags lumaUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
// flowLvl_ is cleared every frame (per-frame reset) and C2 copies the cleaned
// finest level back into it — both need TRANSFER_DST. The C2 ping-pong images
// are the copy SOURCE (TRANSFER_SRC).
const VkImageUsageFlags flowUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
const VkImageUsageFlags regUsage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
pyrA_.resize(kLevels); pyrB_.resize(kLevels); flowLvl_.resize(kLevels);
@@ -201,19 +184,18 @@ bool FrameGen::onResize(VkExtent2D extent, VkFormat /*colorFormat*/, bool force)
VkExtent2D e = levelExtent(extent, l);
if (!makeImage(pyrA_[l], e, VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
if (!makeImage(pyrB_[l], e, VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
if (!makeImage(flowLvl_[l], e, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
}
// C1: stabilized curr luma — only the levels the dense flow reads (finest..coarsest).
for (int l = flowFinest_; l < kLevels; ++l)
if (!makeImage(flowLvl_[l], levelExtent(extent, l), VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
pyrAs_.resize(kLevels);
for (int l = flowFinest_; l < kLevels; ++l)
if (!makeImage(pyrAs_[l], levelExtent(extent, l), VK_FORMAT_R32_SFLOAT, lumaUsage)) return false;
if (!makeImage(flowExpA_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
if (!makeImage(flowExpB_, extent, VK_FORMAT_R16G16B16A16_SFLOAT, flowUsage)) return false;
// C2: TV-L1 ping-pong scratch at the finest solved flow level (perf_preset-driven res).
if (!makeImage(flowRegA_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
if (!makeImage(flowRegB_, levelExtent(extent, flowFinest_), VK_FORMAT_R16G16B16A16_SFLOAT, regUsage)) return false;
ready_ = true;
WFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
DDFG_LOGI("framegen resized to %ux%u (%d pyramid levels, kFlowFinest=%d perf_preset=%d)",
extent.width, extent.height, kLevels, flowFinest_, cfg_.perfPreset);
return true;
}
@@ -229,13 +211,6 @@ void FrameGen::barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkI
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
// NOTE: descriptor-set allocation/update per record() and the exact per-level
// flow wiring are implemented in record_impl.inc for readability; the graph is:
// luma(curr)->pyrA[0], luma(prev)->pyrB[0]
// downsample pyrA/pyrB to kLevels
// coarse->fine flow (per level) into flowLvl
// expand flowLvl[0] -> flowExpA/flowExpB
// synth(prev,curr,flowExpA,flowExpB, alpha) -> out
void FrameGen::destroy() {
if (!device_) return;
@@ -245,11 +220,11 @@ void FrameGen::destroy() {
for (auto& i : pyrAs_) destroyImage(i); pyrAs_.clear();
for (auto& i : flowLvl_) destroyImage(i); flowLvl_.clear();
destroyImage(flowExpA_); destroyImage(flowExpB_);
destroyImage(flowRegA_); destroyImage(flowRegB_); // C2
destroyImage(flowRegA_); destroyImage(flowRegB_);
destroyPipe(pLuma_); destroyPipe(pDown_); destroyPipe(pFlow_); destroyPipe(pFlowM4_);
destroyPipe(pExpand_); destroyPipe(pExpandM4_); destroyPipe(pSynth_);
destroyPipe(pGmReduce_); destroyPipe(pGmPrewarp_);
destroyPipe(pFlowReg_); // C2
destroyPipe(pFlowReg_);
for (int s = 0; s < kGmSlots; ++s) {
if (gmSsboMem_[s]) dd_->UnmapMemory(device_, gmSsboMem_[s]);
if (gmSsbo_[s]) dd_->DestroyBuffer(device_, gmSsbo_[s], nullptr);
@@ -262,8 +237,6 @@ void FrameGen::destroy() {
descPool_ = VK_NULL_HANDLE; linear_ = VK_NULL_HANDLE; device_ = VK_NULL_HANDLE; ready_ = false;
}
} // namespace winfg
}
// record() + its scratch helpers reopen namespace winfg themselves, so include
// this OUTSIDE the namespace block above (keeps FrameGen:: members attached).
#include "record_impl.inc"
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "vk_dispatch.hpp"
#include "config.hpp"
#include <vector>
namespace ddfg {
class FrameGen {
public:
bool init(const DeviceDispatch* dd, const InstanceDispatch* id,
VkPhysicalDevice phys, VkDevice dev, uint32_t queueFamily, VkQueue queue);
void configure(const Config& c);
bool onResize(VkExtent2D extent, VkFormat colorFormat, bool force = false);
void record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
VkImageView outView, float alpha, uint32_t gmSlot = 0,
uint32_t synthSlot = 0, bool reuseFlow = false);
void destroy();
bool valid() const { return device_ != VK_NULL_HANDLE && ready_; }
const Config& config() const { return cfg_; }
struct Img { VkImage image = VK_NULL_HANDLE; VkDeviceMemory mem = VK_NULL_HANDLE;
VkImageView view = VK_NULL_HANDLE; VkExtent2D extent{}; VkFormat fmt{}; };
struct Pipe { VkPipeline pipeline = VK_NULL_HANDLE; VkPipelineLayout layout = VK_NULL_HANDLE;
VkDescriptorSetLayout setLayout = VK_NULL_HANDLE; VkShaderModule module = VK_NULL_HANDLE; };
static constexpr int kGmSlots = 3;
static constexpr int kSynthSlots = 3;
static constexpr int kGmLevel = 3;
static constexpr int kGmThreads = 512;
static constexpr int kGmStride = 32;
static constexpr int kFlowFinestDefault = 2;
static int flowFinestForPreset(int preset) {
switch (preset) {
case 0: return 1;
case 2: return 3;
case 1: default: return kFlowFinestDefault;
}
}
int flowFinest() const { return flowFinest_; }
private:
uint32_t findMemType(uint32_t bits, VkMemoryPropertyFlags props) const;
bool makeImage(Img& out, VkExtent2D ext, VkFormat fmt, VkImageUsageFlags usage);
void destroyImage(Img& i);
bool makeGmBuffers();
bool updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent);
bool makePipe(Pipe& p, int shaderId,
const std::vector<VkDescriptorType>& bindings);
void destroyPipe(Pipe& p);
void barrier(VkCommandBuffer cmd, VkImage img, VkImageLayout from, VkImageLayout to);
const DeviceDispatch* dd_ = nullptr;
const InstanceDispatch* id_ = nullptr;
VkPhysicalDevice phys_ = VK_NULL_HANDLE;
VkDevice device_ = VK_NULL_HANDLE;
VkQueue queue_ = VK_NULL_HANDLE;
uint32_t queueFamily_ = 0;
VkPhysicalDeviceMemoryProperties memProps_{};
VkSampler linear_ = VK_NULL_HANDLE;
VkDescriptorPool descPool_ = VK_NULL_HANDLE;
Config cfg_;
VkExtent2D extent_{};
bool ready_ = false;
int flowFinest_ = kFlowFinestDefault;
Pipe pLuma_, pDown_, pFlow_, pFlowM4_, pExpand_, pExpandM4_, pSynth_;
Pipe pGmReduce_, pGmPrewarp_;
Pipe pFlowReg_;
static const int kLevels = 7;
std::vector<Img> pyrA_, pyrB_;
std::vector<Img> pyrAs_;
std::vector<Img> flowLvl_;
Img flowExpA_, flowExpB_;
Img flowRegA_, flowRegB_;
VkBuffer gmSsbo_[kGmSlots] {};
VkDeviceMemory gmSsboMem_[kGmSlots]{};
void* gmSsboPtr_[kGmSlots]{};
bool gmBuffersReady_ = false;
double gmRun_[6] {};
double gmLinAt_[kGmSlots][6] {};
bool gmSlotValid_[kGmSlots]{};
bool gmHaveEst_ = false;
int gmStableCount_ = 0;
bool gmEngaged_ = false;
float gmLastTransPx_ = 0.0f;
unsigned long long gmFrames_ = 0;
};
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#if defined(__ANDROID__)
#include <android/log.h>
#define DDFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "dd-fg", __VA_ARGS__)
#define DDFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "dd-fg", __VA_ARGS__)
#else
#include <cstdio>
#define DDFG_LOGI(...) do { std::fprintf(stderr, "[dd-fg] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
#define DDFG_LOGE(...) do { std::fprintf(stderr, "[dd-fg][E] " __VA_ARGS__); std::fprintf(stderr, "\n"); } while(0)
#endif
#define DDFG_LOGD(cond, ...) do { if (cond) DDFG_LOGI(__VA_ARGS__); } while(0)
@@ -0,0 +1,535 @@
namespace {
struct ScratchState {
VkBuffer synthUbo[ddfg::FrameGen::kSynthSlots]{};
VkDeviceMemory synthUboMem[ddfg::FrameGen::kSynthSlots]{};
void* synthUboPtr[ddfg::FrameGen::kSynthSlots]{};
std::vector<VkBuffer> flowUbo; std::vector<VkDeviceMemory> flowUboMem; std::vector<void*> flowUboPtr;
VkBuffer expandUbo = VK_NULL_HANDLE; VkDeviceMemory expandUboMem = VK_NULL_HANDLE; void* expandUboPtr = nullptr;
VkBuffer flowRegUbo = VK_NULL_HANDLE; VkDeviceMemory flowRegUboMem = VK_NULL_HANDLE; void* flowRegUboPtr = nullptr;
VkBuffer gmUbo[ddfg::FrameGen::kGmSlots]{};
VkDeviceMemory gmUboMem[ddfg::FrameGen::kGmSlots]{};
void* gmUboPtr[ddfg::FrameGen::kGmSlots]{};
VkDescriptorSet sLumaCurr = VK_NULL_HANDLE, sLumaPrev = VK_NULL_HANDLE;
std::vector<VkDescriptorSet> sDownA, sDownB, sFlow;
VkDescriptorSet sExpand[ddfg::FrameGen::kGmSlots]{};
VkDescriptorSet sFlowReg0 = VK_NULL_HANDLE, sFlowRegAB = VK_NULL_HANDLE, sFlowRegBA = VK_NULL_HANDLE;
VkDescriptorSet sSynth[ddfg::FrameGen::kSynthSlots]{};
VkDescriptorSet sGmReduce[ddfg::FrameGen::kGmSlots]{};
std::vector<VkDescriptorSet> sGmPrewarp[ddfg::FrameGen::kGmSlots];
VkExtent2D builtFor{0,0};
bool built = false;
bool flowInited = false;
bool flowReady = false;
};
ScratchState g_scratch;
}
static bool makeUbo(const ddfg::DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp,
VkDeviceSize size, VkBuffer& buf, VkDeviceMemory& mem, void*& ptr) {
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bi.size = size; bi.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (dd->CreateBuffer(dev, &bi, nullptr, &buf) != VK_SUCCESS) return false;
VkMemoryRequirements mr; dd->GetBufferMemoryRequirements(dev, buf, &mr);
uint32_t idx = 0; VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
for (uint32_t i = 0; i < mp.memoryTypeCount; ++i)
if ((mr.memoryTypeBits & (1u<<i)) && (mp.memoryTypes[i].propertyFlags & want) == want) { idx = i; break; }
VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = mr.size; ai.memoryTypeIndex = idx;
if (dd->AllocateMemory(dev, &ai, nullptr, &mem) != VK_SUCCESS) return false;
if (dd->BindBufferMemory(dev, buf, mem, 0) != VK_SUCCESS) return false;
return dd->MapMemory(dev, mem, 0, size, 0, &ptr) == VK_SUCCESS;
}
static VkDescriptorSet allocSet(const ddfg::DeviceDispatch* dd, VkDevice dev,
VkDescriptorPool pool, VkDescriptorSetLayout layout) {
VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
ai.descriptorPool = pool; ai.descriptorSetCount = 1; ai.pSetLayouts = &layout;
VkDescriptorSet s = VK_NULL_HANDLE; dd->AllocateDescriptorSets(dev, &ai, &s); return s;
}
static void wImg(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkDescriptorType type, VkImageView view, VkSampler samp, VkImageLayout layout) {
VkDescriptorImageInfo ii{}; ii.imageView = view; ii.sampler = samp; ii.imageLayout = layout;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1; w.descriptorType = type; w.pImageInfo = &ii;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static void wUbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkBuffer buf, VkDeviceSize range) {
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; w.pBufferInfo = &bi;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static void wSsbo(const ddfg::DeviceDispatch* dd, VkDevice dev, VkDescriptorSet set, uint32_t binding,
VkBuffer buf, VkDeviceSize range) {
VkDescriptorBufferInfo bi{}; bi.buffer = buf; bi.range = range;
VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
w.dstSet = set; w.dstBinding = binding; w.descriptorCount = 1;
w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; w.pBufferInfo = &bi;
dd->UpdateDescriptorSets(dev, 1, &w, 0, nullptr);
}
static bool cholSolve6(double A[6][6], const double b[6], double x[6]) {
double L[6][6] = {{0}};
for (int i = 0; i < 6; ++i) {
for (int j = 0; j <= i; ++j) {
double s = A[i][j];
for (int k = 0; k < j; ++k) s -= L[i][k] * L[j][k];
if (i == j) {
if (s <= 1e-20) return false;
L[i][j] = std::sqrt(s);
} else {
L[i][j] = s / L[j][j];
}
}
}
double y[6];
for (int i = 0; i < 6; ++i) { double s = b[i]; for (int k = 0; k < i; ++k) s -= L[i][k]*y[k]; y[i] = s / L[i][i]; }
for (int i = 5; i >= 0; --i) { double s = y[i]; for (int k = i+1; k < 6; ++k) s -= L[k][i]*x[k]; x[i] = s / L[i][i]; }
return true;
}
static void affFromParams(const double p[6], double M[3][3]) {
M[0][0]=1.0+p[0]; M[0][1]=p[2]; M[0][2]=p[4];
M[1][0]=p[1]; M[1][1]=1.0+p[3]; M[1][2]=p[5];
M[2][0]=0.0; M[2][1]=0.0; M[2][2]=1.0;
}
static void paramsFromAff(const double M[3][3], double p[6]) {
p[0]=M[0][0]-1.0; p[2]=M[0][1]; p[4]=M[0][2];
p[1]=M[1][0]; p[3]=M[1][1]-1.0; p[5]=M[1][2];
}
static bool invAff(const double M[3][3], double out[3][3]) {
double a=M[0][0], b=M[0][1], tx=M[0][2], c=M[1][0], d=M[1][1], ty=M[1][2];
double det = a*d - b*c;
if (std::fabs(det) < 1e-12) return false;
double id = 1.0/det;
out[0][0]= d*id; out[0][1]= -b*id; out[0][2]= (b*ty - d*tx)*id;
out[1][0]= -c*id; out[1][1]= a*id; out[1][2]= (c*tx - a*ty)*id;
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
return true;
}
static void mulAff(const double A[3][3], const double B[3][3], double out[3][3]) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j)
out[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j];
out[2][0]=0.0; out[2][1]=0.0; out[2][2]=1.0;
}
namespace ddfg {
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
VkSampler samp, VkExtent2D ext,
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
std::vector<FrameGen::Img>& pyrAs,
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
int kLevels, const Config& cfg);
static void destroyScratch(const DeviceDispatch* dd, VkDevice dev, VkDescriptorPool pool) {
auto freeUbo = [&](VkBuffer& b, VkDeviceMemory& m, void*& p) {
if (m) dd->UnmapMemory(dev, m);
if (b) dd->DestroyBuffer(dev, b, nullptr);
if (m) dd->FreeMemory(dev, m, nullptr);
b = VK_NULL_HANDLE; m = VK_NULL_HANDLE; p = nullptr;
};
auto& S = g_scratch;
for (int s = 0; s < FrameGen::kSynthSlots; ++s) freeUbo(S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
freeUbo(S.expandUbo, S.expandUboMem, S.expandUboPtr);
freeUbo(S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
for (size_t i = 0; i < S.flowUbo.size(); ++i) freeUbo(S.flowUbo[i], S.flowUboMem[i], S.flowUboPtr[i]);
for (int s = 0; s < FrameGen::kGmSlots; ++s) freeUbo(S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
if (pool && dd->ResetDescriptorPool) dd->ResetDescriptorPool(dev, pool, 0);
S = ScratchState{};
}
void FrameGen::configure(const Config& c) {
cfg_ = c; cfg_.sanitize();
int want = flowFinestForPreset(cfg_.perfPreset);
const int maxFinest = kLevels - 2;
if (want > maxFinest) {
DDFG_LOGE("perf-preset=%d wants kFlowFinest=%d but only %d pyramid levels exist "
"(need >=2 solved) - clamping to %d", cfg_.perfPreset, want, kLevels, maxFinest);
want = maxFinest;
}
if (want < 1) want = 1;
const int prevFinest = flowFinest_;
flowFinest_ = want;
const bool live = (flowFinest_ != prevFinest) && ready_;
DDFG_LOGI("perf-preset=%d -> kFlowFinest=%d (was %d) live-rebuild=%d ready=%d",
cfg_.perfPreset, flowFinest_, prevFinest, live ? 1 : 0, ready_ ? 1 : 0);
if (!live) return;
dd_->DeviceWaitIdle(device_);
destroyScratch(dd_, device_, descPool_);
if (!onResize(extent_, VK_FORMAT_UNDEFINED, true))
DDFG_LOGE("perf-preset live rebuild: onResize FAILED at %ux%u - FG disabled until next resize",
extent_.width, extent_.height);
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
for (int s = 0; s < kGmSlots; ++s) { gmSlotValid_[s] = false; for (int i = 0; i < 6; ++i) gmLinAt_[s][i] = 0.0; }
gmHaveEst_ = false; gmEngaged_ = false; gmStableCount_ = 0; gmLastTransPx_ = 0.0f;
DDFG_LOGI("perf-preset live rebuild DONE (kFlowFinest=%d, flow predictor reset)", flowFinest_);
}
bool FrameGen::updateGlobalMotion(uint32_t gmSlot, VkExtent2D fullExtent) {
++gmFrames_;
if (!gmBuffersReady_ || cfg_.gmMode == 2 ) {
gmEngaged_ = false; gmStableCount_ = 0;
for (int i = 0; i < 6; ++i) gmRun_[i] = 0.0;
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
gmLastTransPx_ = 0.0f;
return false;
}
bool good = false;
if (gmSlotValid_[gmSlot]) {
const float* pt = (const float*)gmSsboPtr_[gmSlot];
double sums[32]; for (int k = 0; k < 32; ++k) sums[k] = 0.0;
for (int t = 0; t < kGmThreads; ++t) {
const float* row = pt + (size_t)t * kGmStride;
for (int k = 0; k < kGmStride; ++k) sums[k] += (double)row[k];
}
double coverage = sums[27];
double lvlpx = double((fullExtent.width >> kGmLevel) * (fullExtent.height >> kGmLevel));
double minCov = 0.03 * (lvlpx > 0 ? lvlpx : 1.0);
if (coverage > minCov && coverage > 64.0) {
double H[6][6]; int idx = 0;
for (int r = 0; r < 6; ++r) for (int c = r; c < 6; ++c) { H[r][c] = H[c][r] = sums[idx]; ++idx; }
double b[6]; for (int i = 0; i < 6; ++i) b[i] = sums[21 + i];
for (int i = 0; i < 6; ++i) H[i][i] += 1e-3 * H[i][i] + 1e-9;
double dp[6];
if (cholSolve6(H, b, dp)) {
bool bounded = std::fabs(dp[0]) < 0.5 && std::fabs(dp[1]) < 0.5 &&
std::fabs(dp[2]) < 0.5 && std::fabs(dp[3]) < 0.5 &&
std::fabs(dp[4]) < 0.25 && std::fabs(dp[5]) < 0.25;
if (bounded) {
double Mlin[3][3], Md[3][3], Mdi[3][3], Mnew[3][3];
affFromParams(gmLinAt_[gmSlot], Mlin);
affFromParams(dp, Md);
if (invAff(Md, Mdi)) {
mulAff(Mlin, Mdi, Mnew);
double pnew[6]; paramsFromAff(Mnew, pnew);
bool sane = std::fabs(pnew[0]) < 0.6 && std::fabs(pnew[1]) < 0.6 &&
std::fabs(pnew[2]) < 0.6 && std::fabs(pnew[3]) < 0.6 &&
std::fabs(pnew[4]) < 0.5 && std::fabs(pnew[5]) < 0.5;
if (sane) { for (int i = 0; i < 6; ++i) gmRun_[i] = pnew[i]; good = true; gmHaveEst_ = true; }
}
}
}
}
}
if (good) ++gmStableCount_; else gmStableCount_ = 0;
switch (cfg_.gmMode) {
case 1: gmEngaged_ = gmHaveEst_; break;
case 2: gmEngaged_ = false; break;
default: gmEngaged_ = (gmStableCount_ >= 3); break;
}
for (int i = 0; i < 6; ++i) gmLinAt_[gmSlot][i] = gmRun_[i];
gmSlotValid_[gmSlot] = true;
gmLastTransPx_ = (float)std::sqrt(
(gmRun_[4]*fullExtent.width) * (gmRun_[4]*fullExtent.width) +
(gmRun_[5]*fullExtent.height) * (gmRun_[5]*fullExtent.height));
return gmEngaged_;
}
void FrameGen::record(VkCommandBuffer cmd, VkImageView prevView, VkImageView currView,
VkImageView outView, float alpha, uint32_t gmSlot, uint32_t synthSlot, bool reuseFlow) {
if (!valid()) return;
if (gmSlot >= (uint32_t)kGmSlots) gmSlot = 0;
if (synthSlot >= (uint32_t)kSynthSlots) synthSlot = 0;
const bool m4 = (cfg_.model == 4);
const Pipe& flowP = m4 ? pFlowM4_ : pFlow_;
const Pipe& expandP = m4 ? pExpandM4_ : pExpand_;
if (!g_scratch.built || g_scratch.builtFor.width != extent_.width || g_scratch.builtFor.height != extent_.height) {
ensureScratch(this, dd_, device_, memProps_, descPool_, linear_, extent_,
pyrA_, pyrB_, pyrAs_, flowLvl_, flowExpA_, flowExpB_,
pLuma_, pDown_, flowP, expandP, pSynth_, pGmReduce_, pGmPrewarp_,
pFlowReg_, flowRegA_, flowRegB_, gmSsbo_, kLevels, cfg_);
}
if (reuseFlow && !g_scratch.flowReady) return;
if (!reuseFlow) {
bool gmEngaged = updateGlobalMotion(gmSlot, extent_);
if (g_scratch.gmUboPtr[gmSlot]) {
GMUBO g{};
g.lin[0]=(float)gmRun_[0]; g.lin[1]=(float)gmRun_[1]; g.lin[2]=(float)gmRun_[2]; g.lin[3]=(float)gmRun_[3];
g.linT[0]=(float)gmRun_[4]; g.linT[1]=(float)gmRun_[5]; g.linT[2]=gmEngaged?1.0f:0.0f; g.linT[3]=0.0f;
if (gmEngaged) {
g.app[0]=(float)gmRun_[0]; g.app[1]=(float)gmRun_[1]; g.app[2]=(float)gmRun_[2]; g.app[3]=(float)gmRun_[3];
g.appT[0]=(float)gmRun_[4]; g.appT[1]=(float)gmRun_[5];
}
std::memcpy(g_scratch.gmUboPtr[gmSlot], &g, sizeof(g));
}
{
static int lastEng = -1;
if (gmEngaged != (lastEng == 1) || (gmFrames_ % 300ull) == 0) {
DDFG_LOGI("C1 global-motion: engaged=%d mode=%d stable=%d transPx=%.1f (model=%d)",
gmEngaged ? 1 : 0, cfg_.gmMode, gmStableCount_, gmLastTransPx_, cfg_.model);
lastEng = gmEngaged ? 1 : 0;
}
}
wImg(dd_, device_, g_scratch.sLumaCurr, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, g_scratch.sLumaPrev, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}
SynthUBO su{cfg_.flowScale, alpha, cfg_.beta, cfg_.lambda, cfg_.epsilon, cfg_.photoScale, 0, 0,
cfg_.hudX0, cfg_.hudY0, cfg_.hudX1, cfg_.hudY1};
if (g_scratch.synthUboPtr[synthSlot]) std::memcpy(g_scratch.synthUboPtr[synthSlot], &su, sizeof(su));
const VkDescriptorSet synthSet = g_scratch.sSynth[synthSlot];
wImg(dd_, device_, synthSet, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, prevView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, synthSet, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, currView, linear_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
wImg(dd_, device_, synthSet, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, outView, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL);
auto disp = [&](const Pipe& p, VkDescriptorSet set, VkExtent2D e) {
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, p.layout, 0, 1, &set, 0, nullptr);
dd_->CmdDispatch(cmd, (e.width + 15) / 16, (e.height + 15) / 16, 1);
};
auto bar = [&](FrameGen::Img& i){ barrier(cmd, i.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); };
if (!reuseFlow) {
for (auto& i : pyrA_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
for (auto& i : pyrB_) barrier(cmd, i.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
for (int l = flowFinest_; l < kLevels; ++l) barrier(cmd, pyrAs_[l].image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowExpA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowExpB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowRegA_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
barrier(cmd, flowRegB_.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
{
VkClearColorValue zero{};
VkImageSubresourceRange rng{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
for (int l = flowFinest_; l < kLevels; ++l) {
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
b.srcAccessMask = 0; b.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
b.image = flowLvl_[l].image; b.subresourceRange = rng;
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
dd_->CmdClearColorImage(cmd, flowLvl_[l].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &zero, 1, &rng);
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; b.newLayout = VK_IMAGE_LAYOUT_GENERAL;
b.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; b.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b);
}
}
disp(pLuma_, g_scratch.sLumaCurr, levelExtent(extent_, 0));
disp(pLuma_, g_scratch.sLumaPrev, levelExtent(extent_, 0));
bar(pyrA_[0]); bar(pyrB_[0]);
for (int l = 1; l < kLevels; ++l) {
disp(pDown_, g_scratch.sDownA[l - 1], levelExtent(extent_, l));
disp(pDown_, g_scratch.sDownB[l - 1], levelExtent(extent_, l));
bar(pyrA_[l]); bar(pyrB_[l]);
}
if (gmBuffersReady_ && g_scratch.sGmReduce[gmSlot]) {
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pGmReduce_.layout, 0, 1, &g_scratch.sGmReduce[gmSlot], 0, nullptr);
dd_->CmdDispatch(cmd, (uint32_t)(kGmThreads / 64), 1, 1);
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; mb.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0, 1, &mb, 0, nullptr, 0, nullptr);
}
for (int l = flowFinest_; l < kLevels; ++l) {
disp(pGmPrewarp_, g_scratch.sGmPrewarp[gmSlot][l], levelExtent(extent_, l));
bar(pyrAs_[l]);
}
for (int l = kLevels - 1; l >= flowFinest_; --l) {
disp(flowP, g_scratch.sFlow[l], levelExtent(extent_, l));
bar(flowLvl_[l]);
}
const bool frEngaged = (cfg_.frMode != 2) && (cfg_.frIters > 0);
if (frEngaged) {
const int F = flowFinest_;
const VkExtent2D fe = levelExtent(extent_, F);
if (g_scratch.flowRegUboPtr) {
FlowRegUBO fu{cfg_.frDt, cfg_.frLambda, cfg_.frEdge, cfg_.frEps};
std::memcpy(g_scratch.flowRegUboPtr, &fu, sizeof(fu));
}
FrameGen::Img* last = nullptr;
for (int it = 0; it < cfg_.frIters; ++it) {
VkDescriptorSet set; FrameGen::Img* outImg;
if (it == 0) { set = g_scratch.sFlowReg0; outImg = &flowRegA_; }
else if (it % 2 == 1) { set = g_scratch.sFlowRegAB; outImg = &flowRegB_; }
else { set = g_scratch.sFlowRegBA; outImg = &flowRegA_; }
dd_->CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.pipeline);
dd_->CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pFlowReg_.layout, 0, 1, &set, 0, nullptr);
dd_->CmdDispatch(cmd, (fe.width + 15) / 16, (fe.height + 15) / 16, 1);
bar(*outImg);
last = outImg;
}
VkMemoryBarrier mb{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
mb.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 1, &mb, 0, nullptr, 0, nullptr);
VkImageCopy region{};
region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
region.extent = {fe.width, fe.height, 1};
dd_->CmdCopyImage(cmd, last->image, VK_IMAGE_LAYOUT_GENERAL,
flowLvl_[F].image, VK_IMAGE_LAYOUT_GENERAL, 1, &region);
VkMemoryBarrier mb2{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
mb2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
mb2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
dd_->CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 1, &mb2, 0, nullptr, 0, nullptr);
}
{
static int lastFr = -1;
int fe = frEngaged ? 1 : 0;
if (fe != lastFr || (gmFrames_ % 300ull) == 0) {
DDFG_LOGI("C2 flow-reg: engaged=%d mode=%d iters=%d lambda=%.2f dt=%.2f edge=%.1f eps=%.3f (model=%d)",
fe, cfg_.frMode, cfg_.frIters, cfg_.frLambda, cfg_.frDt, cfg_.frEdge, cfg_.frEps, cfg_.model);
lastFr = fe;
}
}
disp(expandP, g_scratch.sExpand[gmSlot], levelExtent(extent_, 0));
bar(flowExpA_); bar(flowExpB_);
g_scratch.flowReady = true;
}
disp(pSynth_, synthSet, levelExtent(extent_, 0));
}
static bool ensureScratch(FrameGen* fg, const DeviceDispatch* dd, VkDevice dev,
const VkPhysicalDeviceMemoryProperties& mp, VkDescriptorPool pool,
VkSampler samp, VkExtent2D ext,
std::vector<FrameGen::Img>& pyrA, std::vector<FrameGen::Img>& pyrB,
std::vector<FrameGen::Img>& pyrAs,
std::vector<FrameGen::Img>& flowLvl, FrameGen::Img& expA, FrameGen::Img& expB,
const FrameGen::Pipe& pLuma, const FrameGen::Pipe& pDown,
const FrameGen::Pipe& pFlow, const FrameGen::Pipe& pExpand,
const FrameGen::Pipe& pSynth, const FrameGen::Pipe& pGmReduce,
const FrameGen::Pipe& pGmPrewarp, const FrameGen::Pipe& pFlowReg,
FrameGen::Img& flowRegA, FrameGen::Img& flowRegB, VkBuffer* gmSsbo,
int kLevels, const Config& cfg) {
auto& S = g_scratch;
S = ScratchState{};
const auto GEN = VK_IMAGE_LAYOUT_GENERAL;
for (int s = 0; s < FrameGen::kSynthSlots; ++s)
makeUbo(dd, dev, mp, sizeof(SynthUBO), S.synthUbo[s], S.synthUboMem[s], S.synthUboPtr[s]);
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.expandUbo, S.expandUboMem, S.expandUboPtr);
if (S.expandUboPtr) { FlowUBO u{cfg.flowScale, 0, 0.6f, 1.6f}; std::memcpy(S.expandUboPtr, &u, sizeof(u)); }
makeUbo(dd, dev, mp, sizeof(FlowRegUBO), S.flowRegUbo, S.flowRegUboMem, S.flowRegUboPtr);
if (S.flowRegUboPtr) { FlowRegUBO fu{cfg.frDt, cfg.frLambda, cfg.frEdge, cfg.frEps}; std::memcpy(S.flowRegUboPtr, &fu, sizeof(fu)); }
S.flowUbo.resize(kLevels); S.flowUboMem.resize(kLevels); S.flowUboPtr.resize(kLevels);
for (int l = fg->flowFinest(); l < kLevels; ++l) {
makeUbo(dd, dev, mp, sizeof(FlowUBO), S.flowUbo[l], S.flowUboMem[l], S.flowUboPtr[l]);
if (S.flowUboPtr[l]) { FlowUBO u{cfg.flowScale, (uint32_t)l, 0.6f, 1.6f}; std::memcpy(S.flowUboPtr[l], &u, sizeof(u)); }
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
makeUbo(dd, dev, mp, sizeof(GMUBO), S.gmUbo[s], S.gmUboMem[s], S.gmUboPtr[s]);
if (S.gmUboPtr[s]) { GMUBO g{}; std::memcpy(S.gmUboPtr[s], &g, sizeof(g)); }
}
S.sLumaCurr = allocSet(dd, dev, pool, pLuma.setLayout);
S.sLumaPrev = allocSet(dd, dev, pool, pLuma.setLayout);
wImg(dd, dev, S.sLumaCurr, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[0].view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sLumaPrev, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[0].view, VK_NULL_HANDLE, GEN);
S.sDownA.resize(kLevels - 1); S.sDownB.resize(kLevels - 1);
for (int l = 1; l < kLevels; ++l) {
S.sDownA[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
S.sDownB[l-1] = allocSet(dd, dev, pool, pDown.setLayout);
wImg(dd, dev, S.sDownA[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l-1].view, samp, GEN);
wImg(dd, dev, S.sDownA[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrA[l].view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sDownB[l-1], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l-1].view, samp, GEN);
wImg(dd, dev, S.sDownB[l-1], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrB[l].view, VK_NULL_HANDLE, GEN);
}
S.sFlow.resize(kLevels);
for (int l = fg->flowFinest(); l < kLevels; ++l) {
S.sFlow[l] = allocSet(dd, dev, pool, pFlow.setLayout);
int pred = (l + 1 < kLevels) ? l + 1 : l;
wUbo(dd, dev, S.sFlow[l], 0, S.flowUbo[l], sizeof(FlowUBO));
wImg(dd, dev, S.sFlow[l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[l].view, samp, GEN);
wImg(dd, dev, S.sFlow[l], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrAs[l].view, samp, GEN);
wImg(dd, dev, S.sFlow[l], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[pred].view, samp, GEN);
wImg(dd, dev, S.sFlow[l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowLvl[l].view, VK_NULL_HANDLE, GEN);
}
if (gmSsbo) {
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
if (!gmSsbo[s]) continue;
S.sGmReduce[s] = allocSet(dd, dev, pool, pGmReduce.setLayout);
wUbo (dd, dev, S.sGmReduce[s], 1, S.gmUbo[s], sizeof(GMUBO));
wSsbo(dd, dev, S.sGmReduce[s], 16, gmSsbo[s], VK_WHOLE_SIZE);
wImg (dd, dev, S.sGmReduce[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[FrameGen::kGmLevel].view, samp, GEN);
wImg (dd, dev, S.sGmReduce[s], 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[FrameGen::kGmLevel].view, samp, GEN);
}
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
S.sGmPrewarp[s].assign(kLevels, VK_NULL_HANDLE);
for (int l = fg->flowFinest(); l < kLevels; ++l) {
S.sGmPrewarp[s][l] = allocSet(dd, dev, pool, pGmPrewarp.setLayout);
wUbo(dd, dev, S.sGmPrewarp[s][l], 1, S.gmUbo[s], sizeof(GMUBO));
wImg(dd, dev, S.sGmPrewarp[s][l], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrA[l].view, samp, GEN);
wImg(dd, dev, S.sGmPrewarp[s][l], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyrAs[l].view, VK_NULL_HANDLE, GEN);
}
}
for (int s = 0; s < FrameGen::kGmSlots; ++s) {
S.sExpand[s] = allocSet(dd, dev, pool, pExpand.setLayout);
wUbo(dd, dev, S.sExpand[s], 0, S.expandUbo, sizeof(FlowUBO));
wUbo(dd, dev, S.sExpand[s], 1, S.gmUbo[s], sizeof(GMUBO));
wImg(dd, dev, S.sExpand[s], 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[fg->flowFinest()].view, samp, GEN);
wImg(dd, dev, S.sExpand[s], 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expA.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sExpand[s], 49, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, expB.view, VK_NULL_HANDLE, GEN);
}
{
const int F = fg->flowFinest();
S.sFlowReg0 = allocSet(dd, dev, pool, pFlowReg.setLayout);
S.sFlowRegAB = allocSet(dd, dev, pool, pFlowReg.setLayout);
S.sFlowRegBA = allocSet(dd, dev, pool, pFlowReg.setLayout);
VkDescriptorSet all3[3] = {S.sFlowReg0, S.sFlowRegAB, S.sFlowRegBA};
for (VkDescriptorSet s : all3) {
wUbo(dd, dev, s, 0, S.flowRegUbo, sizeof(FlowRegUBO));
wImg(dd, dev, s, 33, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
wImg(dd, dev, s, 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, pyrB[F].view, samp, GEN);
}
wImg(dd, dev, S.sFlowReg0, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowLvl[F].view, samp, GEN);
wImg(dd, dev, S.sFlowReg0, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sFlowRegAB, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegA.view, samp, GEN);
wImg(dd, dev, S.sFlowRegAB, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegB.view, VK_NULL_HANDLE, GEN);
wImg(dd, dev, S.sFlowRegBA, 32, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, flowRegB.view, samp, GEN);
wImg(dd, dev, S.sFlowRegBA, 48, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, flowRegA.view, VK_NULL_HANDLE, GEN);
}
for (int s = 0; s < FrameGen::kSynthSlots; ++s) {
S.sSynth[s] = allocSet(dd, dev, pool, pSynth.setLayout);
wUbo(dd, dev, S.sSynth[s], 0, S.synthUbo[s], sizeof(SynthUBO));
wImg(dd, dev, S.sSynth[s], 34, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expA.view, samp, GEN);
wImg(dd, dev, S.sSynth[s], 35, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, expB.view, samp, GEN);
}
S.builtFor = ext; S.built = true;
return true;
}
}
@@ -1,61 +1,39 @@
// win-fg — minimal Vulkan layer dispatch tables.
// Structure follows the public Khronos loader/layer interface and the
// renderdoc "Vulkan layer guide" skeleton (learning references, Apache-2.0 /
// CC-BY). No code from bionic-fg / lsfg / GameScope.
#pragma once
#include <vulkan/vulkan.h>
#include <vulkan/vk_layer.h>
#include <cstring>
namespace winfg {
namespace ddfg {
// The loader sets the first pointer-sized word of a dispatchable handle to the
// dispatch table pointer; layers key their own maps on that value so that all
// handles sharing a chain resolve to the same table.
static inline void* dispatch_key(void* handle) { return *reinterpret_cast<void**>(handle); }
// Instance-level functions we call from the layer.
struct InstanceDispatch {
PFN_vkGetInstanceProcAddr GetInstanceProcAddr = nullptr;
PFN_vkDestroyInstance DestroyInstance = nullptr;
PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties = nullptr;
PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties = nullptr;
// Device-context logging (affected-device fingerprint). Properties2 is core 1.1;
// may be null on a 1.0 instance — always null-check before calling.
PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties = nullptr;
PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2 = nullptr;
// Surface caps — used to clamp the extra-image-headroom request to the surface's
// maxImageCount so the bump never exceeds what the driver can grant.
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
};
// Device-level functions we call. Populated by walking vkGetDeviceProcAddr on
// the next layer down at device-create time.
struct DeviceDispatch {
PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr;
PFN_vkDestroyDevice DestroyDevice = nullptr;
PFN_vkGetDeviceQueue GetDeviceQueue = nullptr;
PFN_vkQueueSubmit QueueSubmit = nullptr;
// Synchronization2 submit path (core 1.3 + KHR alias). DXVK may use either the
// legacy QueueSubmit or QueueSubmit2/2KHR; both are hooked for the guest-submit
// diagnostic. May be null if the device did not enable them — always null-check.
PFN_vkQueueSubmit2 QueueSubmit2 = nullptr;
PFN_vkQueueSubmit2KHR QueueSubmit2KHR = nullptr;
PFN_vkQueueWaitIdle QueueWaitIdle = nullptr;
PFN_vkDeviceWaitIdle DeviceWaitIdle = nullptr;
// swapchain / present
PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr;
PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr;
PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = nullptr;
PFN_vkAcquireNextImageKHR AcquireNextImageKHR = nullptr;
// Alternate acquire entry (VK_KHR_swapchain 1.1+ / device-group). DXVK may use
// either AcquireNextImageKHR or AcquireNextImage2KHR — both hooked for the
// guest-acquire diagnostic. May be null — always null-check.
PFN_vkAcquireNextImage2KHR AcquireNextImage2KHR = nullptr;
PFN_vkQueuePresentKHR QueuePresentKHR = nullptr;
// resources
PFN_vkCreateImage CreateImage = nullptr;
PFN_vkDestroyImage DestroyImage = nullptr;
PFN_vkCreateImageView CreateImageView = nullptr;
@@ -73,7 +51,6 @@ struct DeviceDispatch {
PFN_vkCreateSampler CreateSampler = nullptr;
PFN_vkDestroySampler DestroySampler = nullptr;
// pipeline / descriptors
PFN_vkCreateShaderModule CreateShaderModule = nullptr;
PFN_vkDestroyShaderModule DestroyShaderModule = nullptr;
PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout = nullptr;
@@ -84,15 +61,10 @@ struct DeviceDispatch {
PFN_vkDestroyPipeline DestroyPipeline = nullptr;
PFN_vkCreateDescriptorPool CreateDescriptorPool = nullptr;
PFN_vkDestroyDescriptorPool DestroyDescriptorPool = nullptr;
// Frees every set in the pool at once — used by FrameGen::configure() to reclaim
// the old scratch descriptor sets when a perf_preset change forces a live rebuild
// (otherwise repeated preset switches would exhaust the pool). May be null in
// theory; the rebuild path null-checks before calling.
PFN_vkResetDescriptorPool ResetDescriptorPool = nullptr;
PFN_vkAllocateDescriptorSets AllocateDescriptorSets = nullptr;
PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr;
// command recording
PFN_vkCreateCommandPool CreateCommandPool = nullptr;
PFN_vkDestroyCommandPool DestroyCommandPool = nullptr;
PFN_vkAllocateCommandBuffers AllocateCommandBuffers = nullptr;
@@ -104,7 +76,7 @@ struct DeviceDispatch {
PFN_vkCmdDispatch CmdDispatch = nullptr;
PFN_vkCmdPipelineBarrier CmdPipelineBarrier = nullptr;
PFN_vkCmdCopyImage CmdCopyImage = nullptr;
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr; // capture readback
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr;
PFN_vkCmdBlitImage CmdBlitImage = nullptr;
PFN_vkCmdClearColorImage CmdClearColorImage = nullptr;
PFN_vkCreateFence CreateFence = nullptr;
@@ -120,4 +92,4 @@ static inline void load(PFN& slot, PFN_vkGetDeviceProcAddr gdpa, VkDevice dev, c
slot = reinterpret_cast<PFN>(gdpa(dev, name));
}
} // namespace winfg
}
Loaded 100 of 3082 files, more files were not shown because too many files have changed in this diff. Show more