from __future__ import annotations
from typing import Literal
from pycaw.constants import EDataFlow, ERole
from .com import com_initialized
from .device_ids import pack_capture_device_id, pack_render_device_id, unpack_device_id
from .devices import (
_list_input_devices_noinit,
_list_output_devices_noinit,
match_input_device,
match_output_device,
)
from .models import AudioDeviceInfo, AudioSessionInfo
from .policy_config import PolicyConfigFactory
from .sessions import (
_enumerate_input_sessions_noinit,
_enumerate_output_sessions_noinit,
_resolve_process_ids_from_sessions,
)
AudioFlow = Literal["output", "input"]
def _data_flow_value(flow: AudioFlow) -> int:
return EDataFlow.eCapture.value if flow == "input" else EDataFlow.eRender.value
def _pack_device_id(flow: AudioFlow, device_id: str | None) -> str | None:
if flow == "input":
return pack_capture_device_id(device_id)
return pack_render_device_id(device_id)
def _matched_device(flow: AudioFlow, device: str) -> AudioDeviceInfo:
if flow == "input":
candidates = _list_input_devices_noinit()
return match_input_device(device, candidates)
candidates = _list_output_devices_noinit()
return match_output_device(device, candidates)
def _sessions_for_flow(flow: AudioFlow) -> list[AudioSessionInfo]:
if flow == "input":
return _enumerate_input_sessions_noinit()
return _enumerate_output_sessions_noinit()
def _set_route_for_processes(
process_ids: list[int],
device_id: str | None,
flow: AudioFlow,
) -> None:
packed_device_id = _pack_device_id(flow, device_id)
flow_value = _data_flow_value(flow)
with PolicyConfigFactory() as factory:
for pid in process_ids:
factory.set_persisted_default_endpoint(
process_id=pid,
flow=flow_value,
role=ERole.eConsole.value,
packed_device_id=packed_device_id,
)
factory.set_persisted_default_endpoint(
process_id=pid,
flow=flow_value,
role=ERole.eMultimedia.value,
packed_device_id=packed_device_id,
)
def _set_app_device(
*,
process_id: int | None,
process_name: str | None,
device: str,
flow: AudioFlow,
) -> dict[int, AudioDeviceInfo]:
with com_initialized():
target = _matched_device(flow, device)
sessions = _sessions_for_flow(flow)
pids = _resolve_process_ids_from_sessions(process_id, process_name, sessions)
_set_route_for_processes(pids, target.id, flow)
return {pid: target for pid in pids}
def _clear_app_device(
*,
process_id: int | None,
process_name: str | None,
flow: AudioFlow,
) -> list[int]:
with com_initialized():
sessions = _sessions_for_flow(flow)
pids = _resolve_process_ids_from_sessions(process_id, process_name, sessions)
_set_route_for_processes(pids, None, flow)
return pids
def _get_app_device(
*,
process_id: int | None,
process_name: str | None,
flow: AudioFlow,
) -> dict[int, str]:
with com_initialized():
sessions = _sessions_for_flow(flow)
pids = _resolve_process_ids_from_sessions(process_id, process_name, sessions)
if not pids:
return {}
results: dict[int, str] = {}
with PolicyConfigFactory() as factory:
for pid in pids:
policy_device_id = factory.get_persisted_default_endpoint(
process_id=pid,
flow=_data_flow_value(flow),
role=ERole.eMultimedia.value,
)
unpacked_device = unpack_device_id(policy_device_id)
if unpacked_device:
results[pid] = unpacked_device
return results
[docs]
def set_app_output_device(
*,
process_id: int | None = None,
process_name: str | None = None,
device: str,
) -> dict[int, AudioDeviceInfo]:
"""Route one app (or all matching app PIDs) to a specific output device."""
return _set_app_device(
process_id=process_id,
process_name=process_name,
device=device,
flow="output",
)
[docs]
def clear_app_output_device(
*, process_id: int | None = None, process_name: str | None = None
) -> list[int]:
"""Clear persisted per-app output routing (app returns to system default)."""
return _clear_app_device(
process_id=process_id,
process_name=process_name,
flow="output",
)
[docs]
def get_app_output_device(
*,
process_id: int | None = None,
process_name: str | None = None,
) -> dict[int, str]:
"""Return current persisted route for the PID as a plain MMDevice id."""
return _get_app_device(
process_id=process_id,
process_name=process_name,
flow="output",
)