Dotfiles/desktopenvs/hyprdrive/supersonic-booster/ui/device_card.py

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}"]