Files
2025-03-31 20:14:11 +02:00

129 lines
4.5 KiB
Python

import asyncio
from ignis.widgets import Widget
from ignis.utils import Utils
from ignis.services.audio import AudioService, Stream
from typing import Literal
from ..menu import Menu
from ...shared_widgets import MaterialVolumeSlider
audio = AudioService.get_default()
AUDIO_TYPES = {
"speaker": {"menu_icon": "audio-headphones-symbolic", "menu_label": "Sound Output"},
"microphone": {
"menu_icon": "microphone-sensitivity-high-symbolic",
"menu_label": "Sound Input",
},
}
class DeviceItem(Widget.Button):
def __init__(self, stream: Stream, _type: Literal["speaker", "microphone"]):
super().__init__(
child=Widget.Box(
child=[
Widget.Icon(image="audio-card-symbolic"),
Widget.Label(
label=stream.description,
ellipsize="end",
max_width_chars=30,
halign="start",
css_classes=["volume-entry-label"],
),
Widget.Icon(
image="object-select-symbolic",
halign="end",
hexpand=True,
visible=stream.bind("is_default"),
),
]
),
css_classes=["volume-entry", "unset"],
hexpand=True,
setup=lambda self: stream.connect("removed", lambda x: self.unparent()),
on_click=lambda x: setattr(audio, _type, stream),
)
class DeviceMenu(Menu):
def __init__(self, _type: Literal["speaker", "microphone"], style: str = ""):
data = AUDIO_TYPES[_type]
super().__init__(
name=f"volume-{_type}",
child=[
Widget.Box(
child=[
Widget.Icon(image=data["menu_icon"], pixel_size=24),
Widget.Label(
label=data["menu_label"],
halign="start",
css_classes=["volume-entry-list-header-label"],
),
],
css_classes=["volume-entry-list-header-box"],
),
Widget.Box(
vertical=True,
setup=lambda self: audio.connect(
f"{_type}-added",
lambda x, stream: self.append(DeviceItem(stream, _type)),
),
),
Widget.Separator(css_classes=["volume-entry-list-separator"]),
Widget.Button(
child=Widget.Box(
child=[
Widget.Icon(image="preferences-system-symbolic"),
Widget.Label(
label="Sound Settings",
halign="start",
css_classes=["volume-entry-label"],
),
]
),
css_classes=["volume-entry", "unset"],
style="margin-bottom: 0;",
on_click=lambda x: asyncio.create_task(Utils.exec_sh_async("pavucontrol")),
),
],
)
self.box.add_css_class(f"volume-menubox-{_type}")
class VolumeSlider(Widget.Box):
def __init__(self, _type: Literal["speaker", "microphone"]):
stream = getattr(audio, _type)
icon = Widget.Button(
child=Widget.Icon(
image=stream.bind("icon_name"),
pixel_size=18,
),
css_classes=["material-slider-icon", "unset", "hover-surface"],
on_click=lambda x: stream.set_is_muted(not stream.is_muted),
)
device_menu = DeviceMenu(_type=_type)
scale = MaterialVolumeSlider(
stream=stream,
on_change=lambda x: stream.set_volume(x.value),
sensitive=stream.bind("is_muted", lambda value: not value),
)
arrow = Widget.Button(
child=Widget.Arrow(pixel_size=20, rotated=device_menu.bind("reveal_child")),
css_classes=["material-slider-arrow", "hover-surface"],
on_click=lambda x: device_menu.toggle(),
)
super().__init__(
vertical=True,
child=[
Widget.Box(child=[icon, scale, arrow]),
device_menu,
],
css_classes=[f"volume-mainbox-{_type}"],
)