Skip to content

Commit d4d30c9

Browse files
Copilotballoob
andauthored
Scope ALSA get_state() to Playback channels on mixed Capture/Playback controls (#237)
`AlsaVolumeController.get_state()` could parse the wrong value on mixer elements that expose both Capture and Playback channels, because `amixer sget` output was not constrained to Playback. This change makes `get_state()` explicitly read Playback state and locks that behavior with focused tests. - **Playback-only ALSA state read** - Updated `get_state()` to call: - `amixer -M -c <card> sget <element> playback` - This aligns read-path behavior with `set_state()` (already using `playback`) and avoids capture-line ambiguity in mixed controls. - **Regression coverage for mixed-channel elements** - Added a targeted test asserting `get_state()` invokes `sget ... playback`. - Updated existing command expectation in ALSA controller tests to include the playback qualifier. ```python proc = await asyncio.create_subprocess_exec( "amixer", "-M", "-c", self._card, "sget", self._element, "playback", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) ``` --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
1 parent ccb2bfd commit d4d30c9

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

sendspin/alsa_volume.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ async def get_state(self) -> tuple[int, bool]:
205205
self._card,
206206
"sget",
207207
self._element,
208+
"playback",
208209
stdout=asyncio.subprocess.PIPE,
209210
stderr=asyncio.subprocess.PIPE,
210211
)

tests/test_alsa_volume.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,30 @@ async def test_get_state_mono_channel(monkeypatch) -> None:
271271
assert muted is False
272272

273273

274+
async def test_get_state_requests_playback_only(monkeypatch) -> None:
275+
"""get_state scopes amixer query to playback channels."""
276+
calls: list[tuple[str, ...]] = []
277+
amixer_output = (
278+
"Simple mixer control 'Headset',0\n"
279+
" Capabilities: pvolume pswitch cvolume cswitch\n"
280+
" Playback channels: Front Left - Front Right\n"
281+
" Capture channels: Front Left - Front Right\n"
282+
" Front Left: Playback 20 [80%] [on]\n"
283+
" Front Right: Playback 20 [80%] [on]\n"
284+
)
285+
286+
async def fake_exec(*argv: object, **kwargs: object) -> _FakeProcess:
287+
calls.append(argv)
288+
return _FakeProcess(stdout=amixer_output.encode())
289+
290+
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_exec)
291+
ctrl = AlsaVolumeController(card=0, element="Headset")
292+
volume, muted = await ctrl.get_state()
293+
assert calls == [("amixer", "-M", "-c", "0", "sget", "Headset", "playback")]
294+
assert volume == 80
295+
assert muted is False
296+
297+
274298
# -- AlsaVolumeController.start_monitoring ------------------------------------
275299

276300

@@ -422,7 +446,7 @@ async def fake_exec(*argv: object, **kwargs: object) -> _FakeProcess:
422446

423447
# Read back the volume
424448
volume, muted = await ctrl.get_state()
425-
assert calls[-1] == ("amixer", "-M", "-c", "1", "sget", "Digital")
449+
assert calls[-1] == ("amixer", "-M", "-c", "1", "sget", "Digital", "playback")
426450
assert volume == 74
427451
assert muted is False
428452

0 commit comments

Comments
 (0)