This commit is contained in:
2026-06-03 21:26:54 +02:00
parent 05e6b8d061
commit d21e5175d5
125 changed files with 41986 additions and 0 deletions
+233
View File
@@ -0,0 +1,233 @@
import math
import gi
from fabric.audio.service import Audio
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.scale import Scale
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GLib
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
import config.data as data
vertical_mode = (
True
if data.PANEL_THEME == "Panel"
and (
data.BAR_POSITION in ["Left", "Right"]
or data.PANEL_POSITION in ["Start", "End"]
)
else False
)
class MixerSlider(Scale):
def __init__(self, stream, **kwargs):
super().__init__(
name="control-slider",
orientation="h",
h_expand=True,
h_align="fill",
has_origin=True,
increments=(0.01, 0.1),
style_classes=["no-icon"],
**kwargs,
)
self.stream = stream
self._updating_from_stream = False
self.set_value(stream.volume / 100)
self.set_size_request(-1, 30) # Fixed height for sliders
self.connect("value-changed", self.on_value_changed)
stream.connect("changed", self.on_stream_changed)
# Apply appropriate style class based on stream type
if hasattr(stream, "type"):
if "microphone" in stream.type.lower() or "input" in stream.type.lower():
self.add_style_class("mic")
else:
self.add_style_class("vol")
else:
# Default to volume style
self.add_style_class("vol")
# Set initial tooltip and muted state
self.set_tooltip_text(f"{stream.volume:.0f}%")
self.update_muted_state()
def on_value_changed(self, _):
if self._updating_from_stream:
return
if self.stream:
self.stream.volume = self.value * 100
self.set_tooltip_text(f"{self.value * 100:.0f}%")
def on_stream_changed(self, stream):
self._updating_from_stream = True
self.value = stream.volume / 100
self.set_tooltip_text(f"{stream.volume:.0f}%")
self.update_muted_state()
self._updating_from_stream = False
def update_muted_state(self):
if self.stream.muted:
self.add_style_class("muted")
else:
self.remove_style_class("muted")
class MixerSection(Box):
def __init__(self, title, **kwargs):
super().__init__(
name="mixer-section",
orientation="v",
spacing=8,
h_expand=True,
v_expand=False, # Prevent vertical stretching
)
self.title_label = Label(
name="mixer-section-title",
label=title,
h_expand=True,
h_align="fill",
)
self.content_box = Box(
name="mixer-content",
orientation="v",
spacing=8,
h_expand=True,
v_expand=False, # Prevent vertical stretching
)
self.add(self.title_label)
self.add(self.content_box)
def update_streams(self, streams):
for child in self.content_box.get_children():
self.content_box.remove(child)
for stream in streams:
label_text = stream.description
if hasattr(stream, "type") and "application" in stream.type.lower():
label_text = getattr(stream, "name", stream.description)
stream_container = Box(
orientation="v",
spacing=4,
h_expand=True,
v_expand=False, # Prevent vertical stretching
)
label = Label(
name="mixer-stream-label",
label=f"[{math.ceil(stream.volume)}%] {stream.description}",
h_expand=True,
h_align="start",
v_align="center",
ellipsization="end",
max_chars_width=45,
height_request=20, # Fixed height for labels
)
slider = MixerSlider(stream)
stream_container.add(label)
stream_container.add(slider)
self.content_box.add(stream_container)
self.content_box.show_all()
class Mixer(Box):
def __init__(self, **kwargs):
super().__init__(
name="mixer",
orientation="v",
spacing=8,
h_expand=True,
v_expand=True, # Allow Mixer to expand to parent height
)
try:
self.audio = Audio()
except Exception as e:
error_label = Label(
label=f"Audio service unavailable: {str(e)}",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.add(error_label)
return
self.main_container = Box(
orientation="h" if not vertical_mode else "v",
spacing=8,
h_expand=True,
v_expand=True, # Allow main_container to expand
)
self.main_container.set_homogeneous(True) # Equal sizing for outputs and inputs
# ScrolledWindow for Outputs
self.outputs_scrolled = ScrolledWindow(
name="outputs-scrolled",
h_expand=True,
v_expand=False, # Prevent vertical expansion
vscrollbar_policy=Gtk.PolicyType.AUTOMATIC, # Vertical scrollbar when needed
hscrollbar_policy=Gtk.PolicyType.NEVER, # Disable horizontal scrollbar
)
self.outputs_section = MixerSection("Outputs")
self.outputs_scrolled.add(self.outputs_section)
self.outputs_scrolled.set_size_request(-1, 150) # Fixed height of 150px
self.outputs_scrolled.set_max_content_height(150) # Enforce max height
# ScrolledWindow for Inputs
self.inputs_scrolled = ScrolledWindow(
name="inputs-scrolled",
h_expand=True,
v_expand=False, # Prevent vertical expansion
vscrollbar_policy=Gtk.PolicyType.AUTOMATIC, # Vertical scrollbar when needed
hscrollbar_policy=Gtk.PolicyType.NEVER, # Disable horizontal scrollbar
)
self.inputs_section = MixerSection("Inputs")
self.inputs_scrolled.add(self.inputs_section)
self.inputs_scrolled.set_size_request(-1, 150) # Fixed height of 150px
self.inputs_scrolled.set_max_content_height(150) # Enforce max height
self.main_container.add(self.outputs_scrolled)
self.main_container.add(self.inputs_scrolled)
self.add(self.main_container)
self.set_size_request(-1, 300) # Optional: Set total height to 300px (150px per section)
self.audio.connect("changed", self.on_audio_changed)
self.audio.connect("stream-added", self.on_audio_changed)
self.audio.connect("stream-removed", self.on_audio_changed)
self.update_mixer()
self.show_all()
def on_audio_changed(self, *args):
self.update_mixer()
def update_mixer(self):
outputs = []
inputs = []
if self.audio.speaker:
outputs.append(self.audio.speaker)
outputs.extend(self.audio.applications)
if self.audio.microphone:
inputs.append(self.audio.microphone)
inputs.extend(self.audio.recorders)
self.outputs_section.update_streams(outputs)
self.inputs_section.update_streams(inputs)