234 lines
7.4 KiB
Python
234 lines
7.4 KiB
Python
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)
|