117 lines
4.9 KiB
Python
117 lines
4.9 KiB
Python
"""DeviceCard — one card in the Output Devices / Input Devices tabs: MeterCard's
|
|
usual icon+name/peak/meter/mute, plus a profile dropdown (pro-audio vs the
|
|
plain stereo duplex profile, etc. — whatever the owning card advertises via
|
|
`pactl list cards`) and a "Set Default" button. Shared between both tabs via
|
|
the `kind` parameter ("sink" for outputs, "source" for inputs) — the only
|
|
difference between an output and an input device, as far as pactl's verbs go,
|
|
is which noun you say.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "4.0")
|
|
from gi.repository import Gtk # noqa: E402
|
|
|
|
from ui.card_base import MeterCard
|
|
|
|
_MONITOR_SUFFIX = ".monitor"
|
|
|
|
|
|
class DeviceCard(MeterCard):
|
|
def __init__(self, backend, peaks, kind: str, item: dict,
|
|
cards_by_id: dict[int, dict], default_name: str,
|
|
set_default) -> None:
|
|
self._cards_by_id = cards_by_id
|
|
self._default_name = default_name
|
|
self._set_default = set_default
|
|
self._profile_names: list[str] = []
|
|
self._profile_guard = False
|
|
super().__init__(backend, peaks, kind, item)
|
|
|
|
def _build_extra(self, item: dict):
|
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
|
|
|
profile_col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
|
|
profile_label = Gtk.Label(label="Profile", xalign=0.0)
|
|
profile_label.add_css_class("sb-field-label")
|
|
profile_col.append(profile_label)
|
|
self._profile_dropdown = Gtk.DropDown()
|
|
self._profile_dropdown.add_css_class("sb-device-dropdown")
|
|
self._profile_dropdown.connect("notify::selected", self._on_profile_selected)
|
|
profile_col.append(self._profile_dropdown)
|
|
box.append(profile_col)
|
|
|
|
self._default_btn = Gtk.ToggleButton(label="Set Default")
|
|
self._default_btn.add_css_class("sb-default-btn")
|
|
self._default_btn.connect("toggled", self._on_default_toggled)
|
|
box.append(self._default_btn)
|
|
|
|
self._sync_profile(item, self._cards_by_id)
|
|
self._sync_default(item, self._default_name)
|
|
return box
|
|
|
|
def _update_extra(self, item: dict, cards_by_id: dict[int, dict], default_name: str) -> None:
|
|
self._cards_by_id = cards_by_id
|
|
self._default_name = default_name
|
|
self._sync_profile(item, cards_by_id)
|
|
self._sync_default(item, default_name)
|
|
|
|
# -- profile dropdown -----------------------------------------------------------
|
|
def _owning_card(self, item: dict, cards_by_id: dict[int, dict]) -> dict | None:
|
|
card_id = item.get("card")
|
|
return cards_by_id.get(card_id) if card_id is not None else None
|
|
|
|
def _sync_profile(self, item: dict, cards_by_id: dict[int, dict]) -> None:
|
|
card = self._owning_card(item, cards_by_id)
|
|
profiles = card.get("profiles") if card else []
|
|
if not profiles:
|
|
self._profile_dropdown.set_sensitive(False)
|
|
return
|
|
self._profile_dropdown.set_sensitive(True)
|
|
names = [p["name"] for p in profiles]
|
|
if names != self._profile_names:
|
|
self._profile_names = names
|
|
self._profile_guard = True
|
|
self._profile_dropdown.set_model(
|
|
Gtk.StringList.new([p["description"] for p in profiles]))
|
|
self._profile_guard = False
|
|
active = card.get("active_profile") if card else None
|
|
idx = names.index(active) if active in names else None
|
|
if idx is not None and self._profile_dropdown.get_selected() != idx:
|
|
self._profile_guard = True
|
|
self._profile_dropdown.set_selected(idx)
|
|
self._profile_guard = False
|
|
|
|
def _on_profile_selected(self, dropdown: Gtk.DropDown, _pspec) -> None:
|
|
if self._profile_guard:
|
|
return
|
|
card = self._owning_card(self._item, self._cards_by_id)
|
|
if card is None:
|
|
return
|
|
idx = dropdown.get_selected()
|
|
if 0 <= idx < len(self._profile_names):
|
|
self._backend.set_card_profile(card["id"], self._profile_names[idx])
|
|
|
|
# -- default device ---------------------------------------------------------------
|
|
def _sync_default(self, item: dict, default_name: str) -> None:
|
|
is_default = item.get("name") == default_name
|
|
self._default_btn.handler_block_by_func(self._on_default_toggled)
|
|
self._default_btn.set_active(is_default)
|
|
self._default_btn.handler_unblock_by_func(self._on_default_toggled)
|
|
self._default_btn.set_label("Default" if is_default else "Set Default")
|
|
self._default_btn.set_sensitive(not is_default)
|
|
|
|
def _on_default_toggled(self, btn: Gtk.ToggleButton) -> None:
|
|
if btn.get_active():
|
|
self._set_default(self._item.get("name"))
|
|
|
|
def _peak_target(self, item: dict) -> list[str] | None:
|
|
name = item.get("name")
|
|
if not name:
|
|
return None
|
|
if self._kind == "sink":
|
|
return [f"--device={name}{_MONITOR_SUFFIX}"]
|
|
return [f"--device={name}"]
|