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

90 lines
3.6 KiB
Python

"""ProfileCard — one card in the Cards tab: a sound *card* (the physical or
virtual device behind one or more sinks/sources — onboard audio, a USB
headset, HDMI, a Bluetooth device) and its profile dropdown.
Profile is a card-level setting, not a per-device one — pavucontrol puts it
in its own "Configuration" tab for the same reason: a single card can back
multiple sinks/sources at once, and switching its profile can make some of
them appear or disappear entirely (e.g. "Analog Stereo Duplex" exposes both
an output and an input from the same card; "Analog Stereo Output" drops the
input side; "Off" drops both; "Pro Audio" breaks the card into raw individual
ports instead of one grouped stereo pair). Output/Input Devices used to carry
their own copy of this dropdown per-device, which was confusing when one
card backed a device in both tabs — this replaces that."""
from __future__ import annotations
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk # noqa: E402
class ProfileCard(Gtk.Box):
def __init__(self, backend, item: dict) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add_css_class("sb-card")
self._backend = backend
self._id = item["id"]
self._profile_names: list[str] = []
self._guard = False
head = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
head.add_css_class("sb-card-head")
icon = Gtk.Image.new_from_icon_name("audio-card")
icon.set_pixel_size(28)
head.append(icon)
self._name_lbl = Gtk.Label(xalign=0.0)
self._name_lbl.add_css_class("sb-card-title")
self._name_lbl.set_ellipsize(3) # Pango.EllipsizeMode.END
self._name_lbl.set_max_width_chars(20)
head.append(self._name_lbl)
self.append(head)
label = Gtk.Label(label="Profile", xalign=0.0)
label.add_css_class("sb-field-label")
self.append(label)
self._dropdown = Gtk.DropDown()
self._dropdown.add_css_class("sb-device-dropdown")
self._dropdown.connect("notify::selected", self._on_selected)
self.append(self._dropdown)
self.update(item)
def update(self, item: dict) -> None:
self._item = item
name = item.get("description") or item.get("name") or f"#{item.get('id')}"
self._name_lbl.set_label(name)
self._name_lbl.set_tooltip_text(name)
profiles = item.get("profiles") or []
names = [p["name"] for p in profiles]
if names != self._profile_names:
self._profile_names = names
self._guard = True
self._dropdown.set_model(Gtk.StringList.new([p["description"] for p in profiles]))
self._guard = False
self._dropdown.set_sensitive(bool(profiles))
active = item.get("active_profile")
idx = names.index(active) if active in names else None
if idx is not None and self._dropdown.get_selected() != idx:
self._guard = True
self._dropdown.set_selected(idx)
self._guard = False
def _on_selected(self, dropdown: Gtk.DropDown, _pspec) -> None:
if self._guard:
return
idx = dropdown.get_selected()
if 0 <= idx < len(self._profile_names):
self._backend.set_card_profile(self._id, self._profile_names[idx])
# ScrollRow's diff-sync calls these on every card unconditionally (see
# scroll_row.py) — cards have no live audio to meter, so these are no-ops.
def start_peak(self) -> None:
pass
def stop_peak(self) -> None:
pass