Commit Graph
86 Commits
Author SHA1 Message Date
Kurt Himebauch f05e0df39e Merge remote-tracking branch 'origin/main' into review/malibridge-1950 2026-11-02 18:14:00 -05:00
FX1922 3583643a11 feat: Add the Game Jolt store (#1986) 2026-11-02 10:05:30 -05:00
Kurt Himebauch 2bc6985ae3 feat: choose Steam or Desktop to open when DroidDeck starts (#1993) 2026-11-02 07:03:02 -05: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 <136133082+xXJSONDeruloXx@users.noreply.github.com>
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
DroidDeck Merge 8171bf20fd Merge latest origin/main and resolve conflicts 2026-11-02 10:44:24 +08: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 6cd844ea51 feat: Follow screen resolution can be scaled down by a factor (#1964) 2026-11-01 14:51:28 -05: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
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
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
DroidDeck Merge d4a6d95127 Merge MaliBridge integration (feat/malibridge) into 0.6.7 2026-10-31 19:01:40 +08: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
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 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 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
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
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 23bb361006 fix: Steam no longer restarts every launch to import added games, and asks first (#1795) 2026-10-29 21:14:11 -04:00
Kurt Himebauch 30923635af fix: remove performance HUD detail text (#1768) 2026-10-29 10:37: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 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
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
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
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
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
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
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 <maxjividen@gmail.com>
2026-10-26 18:15:24 -04:00
maxjivi05 3639196c83 Translate the Mali bridge settings 2026-10-26 13:55:31 -04:00
Kurt Himebauch 5d945d7784 refactor: remove duplicate session frame limiter (#1616) 2026-10-26 11:25:31 -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
kingokksa 9f4794769c feat: on-screen keys for any key or key combo (#1305) 2026-10-25 17:27:07 -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
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 2a69505199 feat: merge Games and store libraries into one Library and one game page (#1460) 2026-10-25 08:39:56 -04:00
FX1922 7b518d7e6a itch.io: platform and tag filters (#1497) 2026-10-24 13:39:57 -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 6855dd4467 fix(xbox): say to sign in again when Microsoft rejects the download's licenses 2026-10-24 09:07:13 -04: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
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
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
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
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
MaxsTechReview d9c03369a2 Merge branch 'main' into Test 2026-10-22 14:52:13 -04:00
Kurt Himebauch e8a8de1b22 Merge current main into PR #1317 2026-10-22 12:13:54 -04:00