Commit Graph

55 Commits (fc9ac17f1ec992af16e8a14acbaf5ecc47048060)

Author SHA1 Message Date
Sean Doran fc9ac17f1e
feat: replace free-text target fields with device/app dropdowns
- sink_volume and source_volume knobs now show a dropdown of active
  PulseAudio/PipeWire output/input devices (monitors excluded), with
  the system-default device marked in the label
- app_volume knob shows a dropdown of running apps (was already a
  dropdown, previously had a textarea for group_volume)
- group_volume knob shows a <select multiple> listbox so several apps
  can be selected at once with ctrl/shift-click
- Add GET /api/sinks and GET /api/sources backend endpoints
- Add fetchSinks() / fetchSources() in api.ts with AudioDevice type
- Load sinks and sources in App.tsx alongside running apps on refresh
- All 23 frontend tests pass
2026-03-06 20:44:56 -05:00
Sean 3126227d2b
Merge pull request #7 from sean351/feat/running-apps-dropdown
feat: show running PulseAudio apps in dropdown for easier knob targeting
2026-02-28 18:06:56 -05:00
Sean Doran 2a13248446
test: add Vitest + Testing Library unit tests for running-apps feature
Set up Vitest (jsdom environment) and @testing-library/react.

api.test.ts — fetchRunningApps:
  - returns parsed JSON on 2xx
  - returns [] on non-ok HTTP status
  - returns [] on network error (non-throwing contract)

KnobCard.test.tsx — app_volume:
  - datalist is rendered with all supplied runningApps as options
  - no datalist / list attr when runningApps is empty or omitted
  - onChange called with the new value on input change

KnobCard.test.tsx — group_volume:
  - app-picker select rendered with runningApps options
  - no picker when runningApps is empty or omitted
  - Add button disabled until an app is selected
  - Add button enabled after selection
  - clicking Add appends app to targets
  - clicking Add skips duplicates (onChange not called)
  - picker resets to placeholder after a successful Add
2026-02-28 17:32:28 -05:00
Sean Doran 88216187d4
feat: show running PulseAudio apps in a dropdown for easier knob targeting
- Add GET /api/apps endpoint that queries pulsectl for active sink inputs
  and returns deduplicated app names (application.name + process.binary)
- Add fetchRunningApps() API helper (returns [] on any failure, non-fatal)
- Fetch running apps on page load; expose a Refresh button in the Knobs
  section header to re-query without a full page reload
- KnobCard app_volume: wire target input to an HTML5 <datalist> so the
  browser shows a native autocomplete dropdown of running apps
- KnobCard group_volume: add a select + Add button beneath the textarea so
  users can pick a running app and append it without typing
- CSS: style the app-picker row and the compact refresh button in h2
2026-02-28 17:29:52 -05:00
Sean Doran 8963b494c3
chore: bump version to 2.1.0 2026-02-28 14:56:52 -05:00
Sean 3a06261fa3
Merge pull request #6 from sean351/fix/led-flicker-partial-frame
fix: prevent LED flicker from dropped knob messages at read-buffer boundary
2026-02-28 14:54:48 -05:00
Sean Doran 390e290b57
fix: prevent LED flicker from dropped knob messages at read-buffer boundary
parse_messages() previously advanced past a 0xFE start byte whenever no
complete frame could be matched, silently consuming partial frames at the
end of each ser.read(64) call as garbage.  When the device sends 20-50
rapid knob messages during a fast turn and one message straddles the
64-byte read boundary, the split message was discarded: the daemon skipped
a knob-position update, the LED color lagged, and when the next complete
message arrived the color jumped — producing a visible flicker.

Fix: break out of the parse loop when a 0xFE is followed by a known type
byte (0x02/0x03/0x06/0x07) but the buffer does not yet hold a complete
frame.  The partial bytes are returned in the remainder and prepended to
the next ser.read() chunk, so no message is ever lost.

Also sync last_led_colors after the initial send_leds() on connect so the
dedup guard is accurate from the first knob event instead of from the
first heartbeat.

Adds 5 tests covering partial heartbeat, button, and knob frames, the
split-read round-trip, and unknown frame type skipping (61 pass total).
2026-02-28 14:45:46 -05:00
Sean Doran 4c1d9e53d1
fix: sync AUR workflow .SRCINFO with repo; bump to 2.0.2
The workflow was generating a hardcoded .SRCINFO that didn't include
python-fastapi or uvicorn, overwriting the correct file on every
release. Now it patches version/checksum in the existing .SRCINFO.
2026-02-27 21:43:38 -05:00
Sean Doran f8abb7dcfa
chore: bump version to 2.0.1 2026-02-27 21:35:59 -05:00
Sean bba148561c
Merge pull request #5 from sean351/fix/uvicorn-package-name
fix: correct uvicorn package name in PKGBUILD
2026-02-27 21:34:58 -05:00
Sean Doran 719a89465e
fix: correct uvicorn package name to 'uvicorn' in PKGBUILD
Arch Linux packages uvicorn as 'uvicorn', not 'python-uvicorn',
causing dependency resolution to fail during makepkg.
2026-02-27 21:34:22 -05:00
Sean Doran 163619a925
chore: bump version to 2.0.0 2026-02-27 21:28:10 -05:00
Sean bf034fdefe
Merge pull request #4 from sean351/feat/vite-ui
feat: PWA web UI + LED flicker fix
2026-02-27 21:26:54 -05:00
Sean Doran 2dbc58f4e1
fix: update last_led_colors on heartbeat to prevent LED flicker
The heartbeat path called send_leds but never updated last_led_colors,
so the dedup guard in handle_knob had no visibility into what was last
actually sent. Heartbeats arriving mid-knob-turn injected extra serial
writes that bypassed the guard and overwhelmed the device's LED renderer,
causing visible flicker.
2026-02-27 21:25:21 -05:00
Sean Doran 8f8d9ac3fa
feat: add React/Vite PWA web UI with FastAPI backend
- FastAPI + uvicorn server (turnup-ui) serving config API and static files
- React + TypeScript frontend: connection, global LEDs, per-knob/button
  cards, preset save/load/apply, toast notifications, dark theme
- Vite builds into src/turnup/ui/static/ so no Node.js needed at install time
- PWA assets (manifest, service worker, icon) in ui/public/
- turnup-ui.service systemd user unit; enabled globally via turnupd.install
- PKGBUILD: add python-fastapi + python-uvicorn deps, install UI service file
- pyproject.toml: ui extras group, turnup-ui entry point
2026-02-27 21:25:16 -05:00
Sean Doran 1c73dc1883
Bump version to v1.4.0 2026-02-27 19:26:18 -05:00
Sean 6ba47efec2
Merge pull request #3 from sean351/feat/fix-led-flicker
fix: eliminate LED flicker during knob turns
2026-02-27 19:05:18 -05:00
Sean Doran d69d94eaa2
fix: eliminate LED flicker during knob turns
Two targeted fixes:

1. Deduplicate LED writes in handle_knob: compute new colours and only
   call send_leds() when they differ from last_led_colors.  A single
   physical knob turn produces 20-50 ADC samples in quick succession;
   without this guard each sample triggers a 47-byte write and the
   firmware can't keep up, causing visible flicker.

2. Gate reapply_app_volumes on a 200 ms knob-quiet period.  Calling
   playerctl/pulsectl while the user is actively turning a knob stalls
   the main loop, backing up serial data, missing heartbeats, and
   causing the device LEDs to time out mid-turn.

Both state variables (last_led_colors, last_knob_event) are mutable
lists initialised in main() and threaded through handle_knob, keeping
module-level globals out of the picture.

Adds 6 new unit tests (56 total, all passing).
2026-02-27 19:04:53 -05:00
Sean 983ba276e0
Merge pull request #2 from sean351/feat/refactor-and-tests
Refactor: extract audio.py, add 50 unit tests
2026-02-27 19:04:25 -05:00
Sean Doran 1552d73412
Refactor: extract audio.py, add 50 unit tests
- Extract MPRISController and PulseController into src/turnup/audio.py;
  turnupd.py imports from there, dropping ~270 lines of duplicated code
- Add [project.optional-dependencies] dev = [pytest] and pytest testpaths
  to pyproject.toml
- Add tests/test_audio.py: 31 tests covering MPRISController player
  matching, volume clamping, cache TTL, and PulseController MPRIS-first
  routing, PA fallback, and drain_events
- Add tests/test_protocol.py: 19 tests covering parse_messages framing,
  multi-message buffers, garbage skipping, and knob conversion helpers
- All 50 tests pass (pytest 9.0, Python 3.14)
2026-02-27 18:40:13 -05:00
Sean Doran dbdafabcce
Bump version to v1.3.0 2026-02-27 18:31:20 -05:00
Sean db3d9e34a6
Merge pull request #1 from sean351/feat/mpris2-volume-routing
Add MPRIS2 volume routing and PA event watcher; fix Spotify song transition reset
2026-02-27 18:30:23 -05:00
Sean Doran eba8e66879
Add MPRIS2 volume routing and PA event watcher; fix Spotify song transition reset
- Add MPRISController class: wraps playerctl with 3s player-list cache,
  fuzzy app-name matching, and thread-safe get/set_volume via MPRIS2 D-Bus
- Add PulseController event watcher thread: separate pulsectl connection
  listens for sink-input events and queues them so reapply fires within
  ~0.1s for PA-only apps instead of waiting up to 1s
- Route set_app_volume and get_app_volume_norm through MPRIS first,
  falling back to PA stream for non-MPRIS apps (e.g. Brave/Chromium)
- Update reapply_app_volumes to prefer MPRIS path, preventing turnupd
  from fighting Spotify on the PA layer during song transitions
- Wire MPRISController and start_watching() into main(); drain_events()
  triggers immediate reapply on any PA sink-input event
2026-02-27 18:28:56 -05:00
Sean Doran f1fa3080b5
Cap max volume at 100%; bump to v1.2.0 2026-02-27 15:03:50 -05:00
Sean Doran 916541d815
Add TOML config, playerctl dep, and media button defaults; bump to v1.1.0
- Switch config format from JSON to TOML (tomllib built-in on 3.11+,
  tomli fallback for 3.10) so the generated config can include comments
- Write an annotated config.toml on first run with descriptions for every
  knob/button and hints for all available actions
- Promote playerctl from optdepends to depends so media buttons work
  out of the box
- Default buttons 1–3 now pre-configured for playerctl previous/play-pause/next
- Rename contrib/config.example.json → config.example.toml with full
  TOML rewrite including per-knob LED colour override examples
- Log a migration hint when a legacy config.json is detected
2026-02-27 14:38:36 -05:00
Sean Doran 2e77bf804e
Fix AUR publish: include turnupd.install; bump to v1.0.0 2026-02-27 14:02:47 -05:00
Sean Doran 25da5b46ad
Fix: auto-enable service on install, fix service startup, add config hot-reload; bump to v0.3.3 2026-02-27 13:54:57 -05:00
Sean Doran 642a1212c7
Fix: service ExecStart path for pacman install; bump to v0.3.2 2026-02-27 12:50:43 -05:00
Sean Doran 60f6e6cb1d
Fix: keep LEDs lit via heartbeat refresh, init from actual volumes; bump to v0.3.1 2026-02-27 12:32:56 -05:00
Sean Doran 14ff10e959
Fix: always use XDG config path, drop script_dir fallback; bump to v0.3.0 2026-02-27 12:08:37 -05:00
Sean Doran c3a7ea811e
Fix: handle AUR SSH exit code 1, bump to v0.2.9 2026-02-27 12:04:33 -05:00
Sean Doran 85b8d8de26
Bump version to v0.2.8 2026-02-27 12:03:44 -05:00
Sean Doran 900f502c5c
Fix: re-key AUR secret, add fingerprint debug, bump to v0.2.7 2026-02-27 11:59:27 -05:00
Sean Doran 7aeecf76a0
Bump version to v0.2.6 2026-02-27 11:52:51 -05:00
Sean Doran 46f47d89a7
Fix: pass AUR_SSH_KEY via env var to avoid secret masking corruption 2026-02-27 11:52:43 -05:00
Sean Doran 2b618b2fb0
Bump version to v0.2.5 2026-02-27 11:49:26 -05:00
Sean Doran f4c4781966
Fix: decode base64-encoded AUR SSH key from secret 2026-02-27 11:48:36 -05:00
Sean Doran 2a3ca2739f
Bump version to v0.2.4 2026-02-27 11:47:45 -05:00
Sean Doran d00ab45366
Fix: use GIT_SSH_COMMAND and printf to avoid SSH key corruption 2026-02-27 11:47:36 -05:00
Sean Doran eee6d63d81
Bump version to v0.2.3 2026-02-27 11:46:37 -05:00
Sean Doran 873e7fa7bb
Fix: use printf for SSH config to avoid heredoc indentation issues 2026-02-27 11:46:29 -05:00
Sean Doran 6de3f93653
Bump version to v0.2.2 2026-02-27 11:45:09 -05:00
Sean Doran da6142fab4
Fix: generate .SRCINFO directly instead of using makepkg in Docker 2026-02-27 11:44:25 -05:00
Sean Doran db4a003fcf
Fix: run makepkg as non-root user in Docker 2026-02-27 11:43:28 -05:00
Sean Doran c0676b8d91
Bump version to v0.2.1 2026-02-27 11:42:35 -05:00
Sean Doran 2443986ff6
Add GitHub Action to publish to AUR on version tag 2026-02-27 11:41:51 -05:00
Sean Doran 44d4df4171
Update PKGBUILD and .SRCINFO for v0.2.0 2026-02-27 11:37:00 -05:00
Sean Doran c82f69b67e
Bump version to 0.2.0 2026-02-27 11:36:26 -05:00
Sean Doran 0cd1d6a926
Add LED support and remove legacy root-level scripts
- Add per-knob RGB LED configuration, validation, and color interpolation
- Add build_led_packet, send_leds, all_led_colors to turnupd
- Update config with VALID_LED_MODES, _validate_color, _validate_leds
- Update contrib/config.example.json with LED configuration examples
- Remove legacy root-level config.py and turnupd.py (moved to src/)
2026-02-27 11:35:54 -05:00
Sean Doran b9e7ef0df6
Add sha256 checksum for v0.1.0 2026-02-27 10:33:08 -05:00