diff --git a/CHANGELOG.md b/CHANGELOG.md index 96948c8..eaeaac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,52 @@ takes its description from its section, so a version cannot be released without У каждой версии здесь свой раздел, на английском и на русском. Описание релиза на GitHub берётся из этого раздела, поэтому без него версия не выйдет. +## 0.5.0 — 2026-09-28 + +### English + +**Added** +- **Controller layouts shared between games.** Steam offers a layout only in the game it was + made for. Now the game page lists the game's layouts in Steam, and "Save to DeckDrop" makes + one available to every game on the Deck: controller settings → Templates → "DeckDrop: name". +- **Apply a saved layout from the phone**: "Layout from DeckDrop" → "Apply" on the game page + (with Steam control). DeckDrop checks that Steam took it; the game's previous layout stays in + Steam. The page also shows the game's current layout. +- **Layout for new games** in Settings: applied when DeckDrop adds a game to Steam; "—" by default. +- **Settings → Controller layouts**: rename, delete, download as `.vdf`, or upload a `.vdf`, + for example one from another Deck. +- Saving, renaming and deleting change only DeckDrop's own layout files: never a game's layouts, + Valve's templates or DeckDrop's settings. If a Steam update clears the templates, DeckDrop + puts its layouts back when it starts. + +**Fixed** +- Settings → Performance → self-check no longer counts as a download of `deckdrop.py` on GitHub: + it checks that the releases page answers instead of knocking on the file. The tests do not touch + GitHub at all, so the download counter shows real downloads again. + +### Русский + +**Добавлено** +- **Раскладки контроллера, общие для всех игр.** Steam предлагает раскладку только в той игре, + для которой её сделали. Теперь на странице игры видны её раскладки в Steam, а «Сохранить + в DeckDrop» делает раскладку доступной в любой игре на деке: настройки контроллера → + Шаблоны → «DeckDrop: название». +- **Применить сохранённую раскладку с телефона**: на странице игры «Раскладка из DeckDrop» → + «Применить» (при управлении Steam). DeckDrop проверяет, что Steam её поставил; прежняя раскладка + игры остаётся в Steam. Там же видно, какая раскладка у игры сейчас. +- **Раскладка для новых игр** в настройках: ставится, когда DeckDrop добавляет игру в Steam; + по умолчанию «—». +- **Настройки → Раскладки контроллера**: переименовать, удалить, скачать `.vdf` или загрузить + `.vdf`, например с другого дека. +- Сохранение, переименование и удаление меняют только собственные файлы раскладок DeckDrop: + ни раскладки игр, ни шаблоны Valve, ни настройки DeckDrop не трогаются. Если обновление Steam + очистит шаблоны, DeckDrop вернёт свои раскладки при запуске. + +**Исправлено** +- Самопроверка (Настройки → Производительность) больше не засчитывается на GitHub как скачивание + `deckdrop.py`: она проверяет, что отвечает страница релизов, а не сам файл. Тесты к GitHub + вообще не обращаются, так что счётчик скачиваний снова показывает настоящие скачивания. + ## 0.4.2 — 2026-09-28 ### English diff --git a/README.en.md b/README.en.md index 7cf2458..c77cd00 100644 --- a/README.en.md +++ b/README.en.md @@ -33,6 +33,8 @@ same Wi-Fi, and from the couch you can: - **drop a patch** straight into the game folder, or unpack a patch archive over the game after seeing exactly what it will replace; - **back up saves** to a zip and restore them; +- **share controller layouts between games**: save a game's layout and apply it to any other, + or give every new game your layout; - **browse Steam screenshots and recordings** in a password-protected gallery and save them to your phone; - do all of this **in English or Russian**: the language follows Steam on the Deck. @@ -51,6 +53,10 @@ developer mode, and SteamOS updates do not break it.





![]() Галерея скриншотов |
![]() Настройки и язык |
+
![]() Раскладки контроллера: сохранить и применить |
+![]() Сохранённые раскладки и раскладка для новых игр |
+

{name}",
"info.from_steam_settings": "from Steam settings",
+ "info.layout": "Layout from DeckDrop: {name}",
+ "info.layout_error": "Layout not applied: {error}",
"info.pending": "The name and Proton are applied once Steam control is on",
"info.pending_n": "Queued until Steam control is on: {n}",
"info.size": "Size: {size} · files: {n}",
@@ -232,6 +234,52 @@
"jobs.title": "Tasks",
"jobs.unpack": "unpack",
"lang.name": "English",
+ "layouts.applied": "“{name}” is now this game's layout",
+ "layouts.apply": "Apply",
+ "layouts.apply_label": "Layout from DeckDrop",
+ "layouts.apply_needs_cdp": "Saved layouts can be applied from here once Steam control is on (Settings → Steam, then restart Steam). Until then pick one on the Deck: Templates → “DeckDrop: …”.",
+ "layouts.applying": "applying…",
+ "layouts.based_on": "based on “{name}”",
+ "layouts.current": "This game's own layout",
+ "layouts.delete": "Delete",
+ "layouts.delete_text": "It disappears from DeckDrop and from Steam's templates. Games where it is already chosen keep their own copy.",
+ "layouts.delete_title": "Delete the layout “{name}”?",
+ "layouts.deleted": "Deleted",
+ "layouts.download": "Download .vdf",
+ "layouts.empty": "No saved layouts yet: open a game and press “Save to DeckDrop”.",
+ "layouts.for_new": "Layout for new games",
+ "layouts.for_new_hint": "Applied when DeckDrop adds a game to Steam. “—”: Steam picks as usual.",
+ "layouts.for_new_none": "—",
+ "layouts.from_file": "from a file",
+ "layouts.from_game": "from {game}",
+ "layouts.hint": "Save a layout of this game to DeckDrop and pick it in any game on the Deck: controller settings → Templates → “DeckDrop: …”.",
+ "layouts.name": "Name",
+ "layouts.need_cdp": "Needs Steam control: turn it on in Settings → Steam and restart Steam",
+ "layouts.need_name": "Give the layout a name",
+ "layouts.no_controller": "Steam does not see the Deck's controls",
+ "layouts.no_templates": "Steam's templates folder was not found: layouts are kept in DeckDrop but Steam does not offer them.",
+ "layouts.none": "Steam has no layout for this game yet: open its controller settings on the Deck once.",
+ "layouts.not_a_layout": "This is not a Steam controller layout",
+ "layouts.not_applied": "Steam did not switch the layout",
+ "layouts.not_deck": "This layout is for another controller, not the Steam Deck",
+ "layouts.not_found": "No such saved layout",
+ "layouts.not_in_steam": "not in Steam yet",
+ "layouts.not_in_steam_game": "Add the game to Steam first",
+ "layouts.now_in_steam_html": "Now in Steam: {name}",
+ "layouts.rename": "Rename",
+ "layouts.renamed": "Renamed",
+ "layouts.save": "Save to DeckDrop",
+ "layouts.save_title": "Save the layout to DeckDrop",
+ "layouts.saved": "Saved. On the Deck: Templates → “DeckDrop: {name}”",
+ "layouts.saved_in_steam": "saved in Steam",
+ "layouts.saved_no_steam": "Saved in DeckDrop, but Steam's templates folder was not found, so Steam does not offer it yet",
+ "layouts.set_hint": "Layouts saved from game pages. Each one is offered to every game on the Deck: controller settings → Templates → “DeckDrop: name”. If one is missing there, restart Steam.",
+ "layouts.source_gone": "This layout is no longer there: refresh the page",
+ "layouts.template_desc": "Saved in DeckDrop from {game}",
+ "layouts.title": "Controller layouts",
+ "layouts.too_big": "The file is too big for a layout",
+ "layouts.upload": "Upload .vdf",
+ "layouts.upload_title": "Add a layout from a file",
"media.all": "All",
"media.clip_empty": "the clip has no video segments",
"media.delete_clip": "Steam may show an empty recording until it restarts.",
diff --git a/src/deckdrop/i18n/ru.json b/src/deckdrop/i18n/ru.json
index 3dc57d7..2c7ac1b 100644
--- a/src/deckdrop/i18n/ru.json
+++ b/src/deckdrop/i18n/ru.json
@@ -215,6 +215,8 @@
"info.disk": "Диск: {disk}",
"info.folder_html": "Папка: {name}",
"info.from_steam_settings": "из настроек Steam",
+ "info.layout": "Раскладка из DeckDrop: {name}",
+ "info.layout_error": "Раскладка не применилась: {error}",
"info.pending": "Имя и Proton применятся, когда включится управление Steam",
"info.pending_n": "В очереди до включения управления Steam: {n}",
"info.size": "Размер: {size} · файлов: {n}",
@@ -232,6 +234,52 @@
"jobs.title": "Задания",
"jobs.unpack": "распаковать",
"lang.name": "Русский",
+ "layouts.applied": "Теперь у игры раскладка «{name}»",
+ "layouts.apply": "Применить",
+ "layouts.apply_label": "Раскладка из DeckDrop",
+ "layouts.apply_needs_cdp": "Применять сохранённые раскладки отсюда можно, когда включено управление Steam (Настройки → Steam, затем перезапуск Steam). Пока его нет, выберите раскладку на Deck: Шаблоны → «DeckDrop: …».",
+ "layouts.applying": "применяю…",
+ "layouts.based_on": "на основе «{name}»",
+ "layouts.current": "Своя раскладка игры",
+ "layouts.delete": "Удалить",
+ "layouts.delete_text": "Она пропадёт из DeckDrop и из шаблонов Steam. Игры, где она уже выбрана, сохранят свою копию.",
+ "layouts.delete_title": "Удалить раскладку «{name}»?",
+ "layouts.deleted": "Удалено",
+ "layouts.download": "Скачать .vdf",
+ "layouts.empty": "Сохранённых раскладок пока нет: откройте игру и нажмите «Сохранить в DeckDrop».",
+ "layouts.for_new": "Раскладка для новых игр",
+ "layouts.for_new_hint": "Ставится, когда DeckDrop добавляет игру в Steam. «—» — Steam выбирает сам, как обычно.",
+ "layouts.for_new_none": "—",
+ "layouts.from_file": "из файла",
+ "layouts.from_game": "из {game}",
+ "layouts.hint": "Сохраните раскладку этой игры в DeckDrop и выбирайте её в любой игре на Deck: настройки контроллера → Шаблоны → «DeckDrop: …».",
+ "layouts.name": "Название",
+ "layouts.need_cdp": "Нужно управление Steam: включите его в Настройки → Steam и перезапустите Steam",
+ "layouts.need_name": "Дайте раскладке название",
+ "layouts.no_controller": "Steam не видит управление дека",
+ "layouts.no_templates": "Папка шаблонов Steam не найдена: раскладки хранятся в DeckDrop, но Steam их не предлагает.",
+ "layouts.none": "У Steam ещё нет раскладки для этой игры: откройте её настройки контроллера на Deck хотя бы раз.",
+ "layouts.not_a_layout": "Это не раскладка контроллера Steam",
+ "layouts.not_applied": "Steam не переключил раскладку",
+ "layouts.not_deck": "Эта раскладка для другого контроллера, не для Steam Deck",
+ "layouts.not_found": "Такой сохранённой раскладки нет",
+ "layouts.not_in_steam": "пока не в Steam",
+ "layouts.not_in_steam_game": "Сначала добавьте игру в Steam",
+ "layouts.now_in_steam_html": "Сейчас в Steam: {name}",
+ "layouts.rename": "Переименовать",
+ "layouts.renamed": "Переименовано",
+ "layouts.save": "Сохранить в DeckDrop",
+ "layouts.save_title": "Сохранить раскладку в DeckDrop",
+ "layouts.saved": "Сохранено. На Deck: Шаблоны → «DeckDrop: {name}»",
+ "layouts.saved_in_steam": "сохранена в Steam",
+ "layouts.saved_no_steam": "Сохранено в DeckDrop, но папка шаблонов Steam не найдена, поэтому в Steam её пока нет",
+ "layouts.set_hint": "Раскладки, сохранённые со страниц игр. Каждую можно выбрать в любой игре на Deck: настройки контроллера → Шаблоны → «DeckDrop: название». Если её там нет, перезапустите Steam.",
+ "layouts.source_gone": "Этой раскладки уже нет: обновите страницу",
+ "layouts.template_desc": "Сохранена в DeckDrop из {game}",
+ "layouts.title": "Раскладки контроллера",
+ "layouts.too_big": "Файл слишком большой для раскладки",
+ "layouts.upload": "Загрузить .vdf",
+ "layouts.upload_title": "Добавить раскладку из файла",
"media.all": "Все",
"media.clip_empty": "в клипе нет фрагментов видео",
"media.delete_clip": "Steam может показывать пустую запись до перезапуска.",
diff --git a/src/deckdrop/state.py b/src/deckdrop/state.py
index 7e361a0..b7a2fa9 100644
--- a/src/deckdrop/state.py
+++ b/src/deckdrop/state.py
@@ -31,10 +31,11 @@
"proxy_downloads": False, # also route game downloads through it
"mega_verify": True, # check Mega's own checksum after a download
"cef_enabled": CEF_ENABLED,
+ "default_layout": "", # id of the saved controller layout new games get; "" = Steam's own
}
SETTING_KEYS = ("default_compat", "prefer_linux", "vndb_auto", "vndb_nsfw", "archive_passwords",
"default_disk", "cef_enabled", "update_url", "proxy", "proxy_downloads",
- "mega_verify")
+ "mega_verify", "default_layout")
PROTECTED_KEYS = ("proxy",) # may carry credentials: PIN required to read or change
diff --git a/src/deckdrop/steam/layouts.py b/src/deckdrop/steam/layouts.py
new file mode 100644
index 0000000..542135a
--- /dev/null
+++ b/src/deckdrop/steam/layouts.py
@@ -0,0 +1,434 @@
+"""Controller layouts of the Deck saved in DeckDrop and offered to every game as Steam templates.
+
+Steam keeps a game's layouts in Steam Controller Configs/${ch.appid}`);if(!ch.linux)info.push(`Proton: ${esc(compatLabel(ch.compat))}${ch.compat_from==='steam'?' · '+t('info.from_steam_settings'):''}`);
- if(ch.art_source)info.push(t('info.art',{source:ch.art_source==='vndb'?'VNDB · '+esc(ch.vndb_title||''):ch.art_source==='custom'?t('art.src.custom'):ch.art_source==='steam'?t('art.src.steam'):t('art.src.icon')}));if(ch.art_note)info.push(esc(ch.art_note));if(ch.art_error)info.push(`${esc(ch.art_error)}`);if(ch.pending)info.push(`${t('info.pending')}`);}
+ if(ch.art_source)info.push(t('info.art',{source:ch.art_source==='vndb'?'VNDB · '+esc(ch.vndb_title||''):ch.art_source==='custom'?t('art.src.custom'):ch.art_source==='steam'?t('art.src.steam'):t('art.src.icon')}));if(ch.art_note)info.push(esc(ch.art_note));if(ch.art_error)info.push(`${esc(ch.art_error)}`);if(ch.pending)info.push(`${t('info.pending')}`);
+ if(ch.layout)info.push(t('info.layout',{name:esc(ch.layout)}));if(ch.layout_error)info.push(`${t('info.layout_error',{error:esc(ch.layout_error)})}`);}
$('#gpInfo').innerHTML=info.join('${esc(s.path)} · ${t('common.files_n',{n:s.files})} · ${fmt(s.size)}