Skip to content

0.5.0: раскладки контроллера, общие для всех игр - #9

Merged
Aniforka merged 4 commits into
mainfrom
claude/layouts
Sep 28, 2026
Merged

Aniforka merged 4 commits into
mainfrom
claude/layouts

Conversation

@Aniforka

@Aniforka Aniforka commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Что это

Steam хранит сохранённые раскладки файлами в папке самой игры (Steam Controller Configs/<аккаунт>/config/<игра>/<название>_0.vdf), поэтому в другой игре их не видно. Файлы из controller_base/templates Steam предлагает каждой игре. Это и переключение раскладки ниже проверено на деке диагностикой из ветки claude/layouts-probe.

  • Страница игры → «Раскладки контроллера»:
    • раскладки игры в Steam (своя и сохранённые под названием) и «Сохранить в DeckDrop»;
    • «Сейчас в Steam» — текущая раскладка игры;
    • «Раскладка из DeckDrop» → «Применить» (при управлении Steam).
  • Каждая сохранённая раскладка копируется в шаблоны Steam как controller_neptune_deckdrop_<id>.vdf с названием «DeckDrop: <название>». На деке её выбирают в «Шаблонах».
  • Настройки → Раскладки контроллера:
    • переименовать, удалить, скачать .vdf, загрузить .vdf;
    • «Раскладка для новых игр»: по умолчанию «—». Выбранная раскладка ставится, когда DeckDrop добавляет игру в Steam. Без управления Steam она применяется отложенной операцией, как имя и Proton.
  • Если обновление Steam сотрёт шаблоны, DeckDrop вернёт их при запуске и при открытии списка.

Как работает «Применить» (замерено на деке)

  • У встроенного управления дека номер контроллера 15, а не 0. DeckDrop берёт его из ControllerStore в Steam.
  • SetSelectedConfigForApp(appid, 15, "template://<файл>", false, 1) переключает игру. С меньшим числом аргументов Steam ничего не выбирает.
  • DeckDrop перечитывает GetConfigForAppAndController и сообщает об успехе, только если Steam подтвердил новый URL. Иначе показывает «Steam не переключил раскладку».
  • Steam сам меняет только запись игры в своём configset. Прежний файл раскладки игры остаётся на месте.
  • Ответ Steam содержит путь к текущей раскладке, поэтому находится и папка, к которой не ведёт ни одно имя ярлыка.

Код свой: вызовы Steam написаны для DeckDrop, из Decky и плагинов ничего не взято.

Изолированность (тесты)

tests/test_layouts.py: живой сервер на фейковом Steam, устроенном как на деке. До и после каждого действия снимаются хэши всех файлов домашней папки и сравнивается точная разница:

  • сохранение добавляет ровно три своих файла;
  • повторное сохранение с тем же названием не переписывает первую раскладку;
  • переименование меняет только свой .json и шаблон;
  • удаление убирает только свои три файла;
  • чужие и битые источники, ../ и плохие id ничего не пишут;
  • шаблоны восстанавливаются, чужие шаблоны не трогаются;
  • применение в файлах Steam не меняет ничего (выбор записывает сам Steam), отказ Steam и отсутствие контроллера показываются как ошибка, без управления Steam применение отклоняется;
  • раскладка для новых игр ставится при добавлении игры, по умолчанию — никакая.

Steam в тестах заменяет tests/fake_cdp.py — подставной отладочный порт Steam. Сами выражения, которые DeckDrop отправляет в Steam, tests/ui_check.py выполняет в Chromium против SteamClient, который ведёт себя как дек (контроллер 15, пять аргументов). Мутации «4 аргумента», «контроллер 0» и «не проверять, что Steam переключил» этот тест ловит.

tests/test_user_data.py (обязательный шаг CI): сохранённые раскладки переживают обновление байт в байт.

Проверено локально

pyflakes, changelog --check, unittest на 3.11 (89 тестов, включая обновление с v0.4.0, v0.4.1 и v0.4.2) и на 3.8, smoke, ui_check.

Проверить на деке

Поставить сборку, сохранить раскладку, применить её к другой игре со страницы игры и убедиться, что она стала текущей.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SvynXzQdiuoPezVazdEnkr

Steam offers a layout only in the game it was made for: saved layouts are
files in that game's own folder. A file in controller_base/templates,
though, is offered to every game (checked on a Deck).

The game page now lists the game's Deck layouts in Steam (current and saved
ones; the folder is found by the shortcut's name, its first name and the
exe) and saves one into DeckDrop (~/.config/deckdrop/layouts). Each saved
layout is mirrored into Steam's templates as
controller_neptune_deckdrop_<id>.vdf, titled "DeckDrop: <name>", and put
back on start if a Steam update clears it. Settings -> Controller layouts:
rename, delete, download and upload .vdf.

Saving, renaming and deleting touch only DeckDrop's layout files. Tests
check it with a snapshot of every file in the home before and after, on a
Steam folder laid out like the one on the Deck; the update test now keeps
saved layouts too, and the browser check saves one in both languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SvynXzQdiuoPezVazdEnkr
Measured on a Deck: Steam numbers the Deck's own controls 15, not 0, and
SetSelectedConfigForApp(appid, 15, "template://<file>", false, 1) switches
the game, which GetConfigForAppAndController then reports; Steam records it
in its configset and keeps the game's previous layout file.

- Game page: "Now in Steam" (the game's current layout) and "Layout from
  DeckDrop" -> "Apply", which reads the choice back and says when Steam did
  not take it. Steam's answer also tells where the game's layouts are, so
  a folder no name leads to is found.
- Settings: "Layout for new games" ("-" by default), applied when DeckDrop
  adds a game to Steam, or later as a pending op without Steam control.
- DeckDrop writes nothing into Steam's layout files for this: tests check
  it against a stand-in for Steam's debugging port (tests/fake_cdp.py), and
  ui_check runs the very expressions sent to Steam in Chromium against a
  SteamClient that behaves like the Deck did (controller 15, five arguments).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SvynXzQdiuoPezVazdEnkr
…ntrol

Both READMEs get a screenshot row for layouts (the game page with "Apply",
Settings with saved layouts and the one for new games), and mention layouts
in Settings and Steam control. tools/screenshots.py shoots them on the demo
Deck with Steam control provided by tests/fake_cdp.py; all shots retaken
for 0.5.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SvynXzQdiuoPezVazdEnkr
The update check sent a HEAD to the update link, by default the release
file, and GitHub counts that as a download; smoke, ui_check and
test_diagnostics run the self-check on every CI run, so the counter showed
mostly test runs. For a GitHub release file the check now asks the
releases/latest page instead; any other link is checked as it is. The tests
point the update link at a local address, and test_diagnostics checks that
the self-check sends one HEAD there and nothing else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SvynXzQdiuoPezVazdEnkr
@Aniforka
Aniforka merged commit 7b4ed98 into main Sep 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants