mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 20:31:21 +00:00
* feat(input): add gamepad/controller support
Adds full gamepad/controller navigation and playback control, driven
by requests from players who use fee[dB]ack on a TV/console setup and
from wheelchair users for whom a controller is far more convenient
than a keyboard + mouse. Confirmed working end-to-end on a Steam Deck
across several rounds of on-device testing.
- static/v3/gamepad.js: polls navigator.getGamepads() and dispatches
synthetic keydown events (Arrow/Enter/Space/Escape) on the focused
element (falling back to document), reusing the app's existing
keyboard pipeline (static/js/shortcuts.js's scope-aware dispatcher,
player shortcuts, text-field/modal guards) instead of a parallel
action-mapping table. Only acts on gamepads reporting the W3C
"standard" mapping — which is what Steam Input presents for the
Deck's built-in controls, both in Gaming Mode and in Desktop Mode
via a non-Steam shortcut — so button order is guaranteed correct
and a non-standard/raw device safely no-ops instead of misfiring.
Handles Steam Input's virtual-pad duplicates (a real controller
plus 1-2 mirrored XInput slots) without spamming connect toasts or
losing input when the live pad isn't at index 0. Xbox-style face
button mapping: bottom face = Space (play/pause, and activates the
focused control), right face = Escape (back), top face reveals the
player screen's tool rail (focuses it into visibility via the
existing CSS :focus-within rule). D-pad/stick repeat while held,
mirroring OS keyboard auto-repeat.
- static/v3/gamepad-nav.js: fills the one real gap in that reuse
strategy — no screen but the song library grid had any arrow-key
navigation, and Chromium doesn't run native Enter/Space button
activation for untrusted synthetic events even when dispatched at
the focused element. Gated entirely on `!e.isTrusted`, so it only
ever reacts to gamepad-originated events and never touches real
keyboard/mouse users: emulates Tab-order (the sidebar + active
screen's real, already-focusable buttons/links) for Arrow keys,
explicitly .click()s the focused element for Enter/Space, and gives
Escape a consistent "go back" behavior — an existing in-screen back
button if one's visible (reusing each screen's own drill-down logic
for free), else the main menu. Every branch defers via
`e.defaultPrevented` to any screen that already handles the key
itself (the song grid, the player, settings), so nothing here
overrides existing behavior.
- static/v3/songs.js: adds real 2D d-pad/arrow-key navigation to the
song library's virtualized grid (only a slice of the library is
ever in the DOM), including fetching/scrolling off-screen rows into
view and correcting for the sticky filter toolbar's occlusion.
- static/v3/index.html: wires up the two new scripts.
* chore: regenerate stale tailwind.min.css
Rebuilt in a fresh clone (not the local working copy). Several plugin
directories (audio_engine, plugin_manager, community_charts, etc.) are
gitignored locally but present on disk from checking out plugin repos
for local dev/testing — Tailwind's content scan picks them up
regardless, so a rebuild against the contaminated local working copy
bakes in extra utility classes that don't belong in the real,
git-tracked build. A clean checkout reproduces CI's expected output
exactly.
* fix(gamepad): check all matching back buttons, not just the first
document.querySelector on the combined [data-ap-back], [data-albums-back],
#v3-pl-back selector only ever inspects the first match in DOM order —
since screens stay in the DOM (hidden, not removed) when you navigate
away, a hidden back button from an unrelated screen could sort before
the one that's actually visible, incorrectly falling through to
showScreen('v3-home') instead of clicking it. Uses querySelectorAll +
find(visible) instead.
* fix(gamepad): address CodeRabbit findings on connect/disconnect and grid nav
- gamepad.js: anyLiveConnectedPad -> anyLiveStandardPad, filtering by
mapping === 'standard' like firstLiveStandardPad already does, and
applied at the top of the gamepadconnected handler too. A still-
connected non-standard raw mirror could otherwise mask the real
pad's disconnect (toast never fires, polling never stops).
- songs.js _gpMove: an unset cursor now always seeds at index 0
before the first press, instead of applying that press's delta
immediately (ArrowDown/Right previously skipped straight past row
0; Left/Up only looked right by accident of clamping). Matches the
existing convention in shortcuts.js's legacy _handleLibArrowNav.
- songs.js _gpBlockedTarget: form-control/button blocking now
requires the element to be visible (offsetParent !== null), not
just present. Screens stay in the DOM hidden (not removed) when you
navigate away, so a real button focused on some other now-hidden
screen could leave document.activeElement pointing at it and block
all grid navigation indefinitely. (An el.closest('#v3-songs') scope
was tried first and reverted — it fixed that case but broke
blocking for the topbar search input, which lives outside
#v3-songs's DOM subtree even while v3-songs is active; visibility
is the distinction that actually matters, not DOM nesting.)
Skipped two CodeRabbit suggestions, verified against current code:
gating songs.js's grid keydown listener to synthetic-only events
would regress the real keyboard accessibility this PR intentionally
added (v3-songs' grid had none before); renaming the _gp* helpers to
drop their underscore prefix would break from this codebase's own
established module-private naming convention.
Verified in-browser: first arrow press lands on index 0, stale hidden
focus no longer blocks grid nav, the topbar search input still
correctly blocks it, and normal nav resumes after blur.
* test(gamepad): unit-cover the controller + nav state machines
- gamepad.test.js (10): standard-mapping filter, Steam Input duplicate-slot
dedup, disconnect masking, button edge-detection, d-pad/stick repeat timing,
analog deadzone — driven via a fake navigator + manual rAF queue.
- gamepad_nav.test.js (10): !isTrusted/defaultPrevented gating, arrow focus
traversal + clamping, hidden-element skipping, Enter/Space click activation
(not into text fields/body), Escape visible-back-button vs home fallback.
songs.js grid nav is left to on-device coverage (async + windowed-DOM heavy).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Byron Gamatos <xasiklas@gmail.com>
---------
Signed-off-by: Byron Gamatos <xasiklas@gmail.com>
Co-authored-by: Byron Gamatos <xasiklas@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| browser | ||
| fixtures | ||
| js | ||
| plugins | ||
| __init__.py | ||
| conftest.py | ||
| test_acoustid_match.py | ||
| test_albums_view.py | ||
| test_appstate.py | ||
| test_art_candidates.py | ||
| test_art_layer.py | ||
| test_artist_alias.py | ||
| test_artist_page.py | ||
| test_artist_sort_title_order.py | ||
| test_audio_bundled.py | ||
| test_audio_effect_mappings.py | ||
| test_audio_local_path.py | ||
| test_audio.py | ||
| test_batch_user_meta.py | ||
| test_builtin_diagnostic_seed.py | ||
| test_builtin_starter_seed.py | ||
| test_collections_api.py | ||
| test_context_menu_api.py | ||
| test_correlation_id.py | ||
| test_curated_album.py | ||
| test_demo_mode.py | ||
| test_diagnostics_bundle.py | ||
| test_diagnostics_hardware.py | ||
| test_diagnostics_redact.py | ||
| test_dlc_junction.py | ||
| test_drums_lib.py | ||
| test_enrichment_plumbing.py | ||
| test_env_compat.py | ||
| test_feedpak_extension.py | ||
| test_field_overrides.py | ||
| test_gap_fill.py | ||
| test_gp2midi.py | ||
| test_gp2notation.py | ||
| test_gp2rs_drums.py | ||
| test_gp2rs_gpx.py | ||
| test_gp2rs_xml_encoding.py | ||
| test_gp2rs.py | ||
| test_gp_audio_sync.py | ||
| test_gp_autosync_warp.py | ||
| test_group_filter_law.py | ||
| test_highway_3d_routes.py | ||
| test_highway_ws_authors.py | ||
| test_highway_ws_instrument_routing.py | ||
| test_highway_ws_notation.py | ||
| test_library_filters.py | ||
| test_library_keyset.py | ||
| test_library_providers.py | ||
| test_library_tuning_instrument.py | ||
| test_library_tuning_perspectives.py | ||
| test_lift_keys_notation.py | ||
| test_logging_setup.py | ||
| test_loops_concurrency.py | ||
| test_loose_traversal.py | ||
| test_loosefolder.py | ||
| test_lyrics_transcribe_remote.py | ||
| test_lyrics_transcribe.py | ||
| test_mb_enrichment.py | ||
| test_mb_match.py | ||
| test_midi_import_drums.py | ||
| test_midi_import.py | ||
| test_midi_tempo_map.py | ||
| test_migrate_full_mix_stem.py | ||
| test_minigames_routes.py | ||
| test_notation_lib.py | ||
| test_notation_lift.py | ||
| test_notation_to_notes.py | ||
| test_packaging.py | ||
| test_parse_arrangement.py | ||
| test_playlist_cover_errors.py | ||
| test_playlists_api.py | ||
| test_plugin_context_contract.py | ||
| test_plugin_manifest_contract.py | ||
| test_plugin_runtime_idempotence.py | ||
| test_plugin_src_route.py | ||
| test_plugins.py | ||
| test_practice_suggestions.py | ||
| test_profile_api.py | ||
| test_profile_domains.py | ||
| test_progression_api.py | ||
| test_progression.py | ||
| test_resolved_root_cache.py | ||
| test_safepath.py | ||
| test_scraper_options.py | ||
| test_settings_api.py | ||
| test_settings_export_library_db.py | ||
| test_settings_export.py | ||
| test_settings_instrument.py | ||
| test_sloppak_cent_offset.py | ||
| test_sloppak_cover_art.py | ||
| test_sloppak_drumtab_load.py | ||
| test_sloppak_feedpak_version.py | ||
| test_sloppak_file_traversal.py | ||
| test_sloppak_full_mix_load.py | ||
| test_sloppak_jsonc_load.py | ||
| test_sloppak_keys_load.py | ||
| test_sloppak_notation_load.py | ||
| test_sloppak_song_timeline_load.py | ||
| test_sloppak_unpack_cache.py | ||
| test_song_info_stems.py | ||
| test_song_score.py | ||
| test_song_stats_api.py | ||
| test_song_user_meta.py | ||
| test_song.py | ||
| test_songmeta.py | ||
| test_spec_gate.py | ||
| test_starter_suggestions.py | ||
| test_startup_status.py | ||
| test_tailwind_rebuild.py | ||
| test_tailwind_runtime_path.py | ||
| test_tones.py | ||
| test_tuning_provider_isolation.py | ||
| test_tunings.py | ||
| test_version_endpoint.py | ||
| test_vocal_pitch.py | ||
| test_wanted_api.py | ||
| test_work_charts_api.py | ||
| test_work_grouping.py | ||
| test_ws_highway_disconnect.py | ||
| test_xp.py | ||