"""General Settings tab: system-wide defaults (default output/input device) and this panel's own display settings, plus a read-only line confirming what audio server pactl is actually talking to (pipewire-pulse vs a legacy PulseAudio daemon) — the whole point of the "build for the future" ask.""" from __future__ import annotations import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk # noqa: E402 import config class GeneralTab(Gtk.Box): def __init__(self, backend, on_hologram_changed=None) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=16) self.add_css_class("sb-tab-page") self.add_css_class("sb-general") self._backend = backend self._on_hologram_changed = on_hologram_changed self._sink_names: list[str] = [] self._source_names: list[str] = [] self._guard = False self.append(self._field("Default Output Device", self._build_output_dropdown())) self.append(self._field("Default Input Device", self._build_input_dropdown())) if on_hologram_changed is not None: self.append(self._build_hologram_row()) self._server_lbl = Gtk.Label(xalign=0.0) self._server_lbl.add_css_class("sb-server-info") self.append(self._server_lbl) @staticmethod def _field(title: str, widget: Gtk.Widget) -> Gtk.Widget: col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) col.add_css_class("sb-general-field") label = Gtk.Label(label=title, xalign=0.0) label.add_css_class("sb-field-label") col.append(label) col.append(widget) return col def _build_output_dropdown(self) -> Gtk.DropDown: self._output_dropdown = Gtk.DropDown() self._output_dropdown.add_css_class("sb-device-dropdown") self._output_dropdown.connect("notify::selected", self._on_output_selected) return self._output_dropdown def _build_input_dropdown(self) -> Gtk.DropDown: self._input_dropdown = Gtk.DropDown() self._input_dropdown.add_css_class("sb-device-dropdown") self._input_dropdown.connect("notify::selected", self._on_input_selected) return self._input_dropdown def _build_hologram_row(self) -> Gtk.Widget: row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) row.add_css_class("sb-general-field") label = Gtk.Label(label="Hologram Effects", xalign=0.0, hexpand=True) label.add_css_class("sb-field-label") row.append(label) switch = Gtk.Switch(valign=Gtk.Align.CENTER) switch.set_active(config.hologram_enabled()) switch.connect("state-set", self._on_hologram_toggled) row.append(switch) return row def _on_hologram_toggled(self, _switch: Gtk.Switch, value: bool) -> bool: config.set_hologram_enabled(value) if self._on_hologram_changed is not None: self._on_hologram_changed(value) return False # -- data ----------------------------------------------------------------------- def refresh(self) -> None: def got_server(info: dict) -> None: name = info.get("server_name") or "unknown" version = info.get("server_version") or "" self._server_lbl.set_label(f"Audio server: {name} {version}".rstrip()) def got_sinks(sinks: list[dict]) -> None: def got_sources(sources: list[dict]) -> None: self._sync_dropdown(self._output_dropdown, sinks, info.get("default_sink", ""), "_sink_names") self._sync_dropdown(self._input_dropdown, sources, info.get("default_source", ""), "_source_names") self._backend.list_sources(got_sources) self._backend.list_sinks(got_sinks) self._backend.get_server_info(got_server) def _sync_dropdown(self, dropdown: Gtk.DropDown, devices: list[dict], default_name: str, cache_attr: str) -> None: names = [d["name"] for d in devices] if names != getattr(self, cache_attr): setattr(self, cache_attr, names) self._guard = True dropdown.set_model(Gtk.StringList.new([d["description"] for d in devices])) self._guard = False idx = names.index(default_name) if default_name in names else None if idx is not None and dropdown.get_selected() != idx: self._guard = True dropdown.set_selected(idx) self._guard = False def _on_output_selected(self, dropdown: Gtk.DropDown, _pspec) -> None: if self._guard: return idx = dropdown.get_selected() if 0 <= idx < len(self._sink_names): self._backend.set_default_sink(self._sink_names[idx]) def _on_input_selected(self, dropdown: Gtk.DropDown, _pspec) -> None: if self._guard: return idx = dropdown.get_selected() if 0 <= idx < len(self._source_names): self._backend.set_default_source(self._source_names[idx]) def activate(self) -> None: self.refresh() def deactivate(self) -> None: pass