update osd

This commit is contained in:
sankalp committed 2024-11-23 11:12:30 +05:45
1 parent 41e0564a36
commit dbbaa9d8b2
1 file changed
+173 -103
+173 -103
View File
@@ -1,15 +1,44 @@
import time
from dataclasses import dataclass
from typing import Iterator, List
from fabric import Application
from fabric.audio import Audio
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scale import Scale, ScaleMark
from fabric.widgets.wayland import WaylandWindow as Window
from gi.repository import GLib, GObject
from services import Brightness
from snippets import Animator
from snippets import Animator, MaterialIcon
@dataclass
class IconMapping:
threshold: float
name: str
class BrightnessIcons:
MAPPINGS: List[str] = [
"brightness_1",
"brightness_2",
"brightness_3",
"brightness_4",
"brightness_5",
"brightness_6",
"brightness_7",
]
MAX_BRIGHTNESS: float = 100
RATIO: float = 1.6
class VolumeIcons:
MAPPINGS: List[IconMapping] = [
IconMapping(101, "sound_detection_loud_sound"),
IconMapping(67, "volume_up"),
IconMapping(34, "volume_down"),
IconMapping(1, "volume_mute"),
IconMapping(0, "volume_off"),
]
class AnimatedScale(Scale):
@@ -21,38 +50,36 @@ class AnimatedScale(Scale):
min_value=self.min_value,
max_value=self.value,
tick_widget=self,
notify_value=lambda p, *_: self.set_value(p.value),
notify_value=self._update_scale_value,
)
def animate_value(self, value: float):
def animate_value(self, value: float) -> None:
self.animator.pause()
self.animator.min_value = self.value
self.animator.max_value = value
self.animator.play()
def _update_scale_value(self, provider: any, *_) -> None:
self.set_value(provider.value)
class BrightnessOSDContainer(Box):
def __init__(self, **kwargs):
class BaseOSDContainer(Box):
def __init__(self, icon_name: str, icon_size: str, **kwargs):
super().__init__(**kwargs, orientation="h", spacing=12, name="osd-container")
self.brightness_service = Brightness()
self.icon = self._create_icon("display-brightness-symbolic", 28)
self.scale = self._create_brightness_scale()
self.icon = self._create_icon(icon_name, icon_size)
self.scale = self._create_scale()
self._setup_layout()
def _setup_layout(self) -> None:
self.add(self.icon)
self.add(self.scale)
self.update_brightness()
self.scale.connect("value-changed", lambda *_: self.update_brightness())
self.brightness_service.connect("screen", self.on_brightness_changed)
def _create_icon(self, icon_name: str, icon_size: str) -> Label:
return MaterialIcon(icon_name=icon_name, size=icon_size)
def _create_icon(self, icon_name: str, pixel_size: int) -> Image:
icon = Image(icon_name=icon_name)
icon.set_pixel_size(pixel_size)
return icon
def _create_brightness_scale(self) -> AnimatedScale:
def _create_scale(self) -> AnimatedScale:
return AnimatedScale(
marks=(ScaleMark(value=i) for i in range(1, 100, 10)),
marks=self._create_scale_marks(),
value=70,
min_value=0,
max_value=100,
@@ -60,143 +87,186 @@ class BrightnessOSDContainer(Box):
orientation="h",
)
def update_brightness(self):
def _create_scale_marks(self) -> Iterator[ScaleMark]:
return (ScaleMark(value=i) for i in range(1, 100, 10))
class BrightnessOSDContainer(BaseOSDContainer):
def __init__(self, **kwargs):
super().__init__(icon_name="brightness_7", icon_size="28px", **kwargs)
self.brightness_service = Brightness()
self._setup_handlers()
self.update_brightness()
def _setup_handlers(self) -> None:
self.scale.connect("value-changed", lambda *_: self.update_brightness())
self.brightness_service.connect("screen", self.on_brightness_changed)
def update_brightness(self) -> None:
current_brightness = self.brightness_service.screen_brightness
if current_brightness != 0:
normalized_brightness = (
current_brightness / self.brightness_service.max_screen
) * 101
self.scale.animate_value(normalized_brightness)
normalized_brightness = self._normalize_brightness(current_brightness)
self._update_ui(normalized_brightness)
def on_brightness_changed(self, sender, value, *args):
normalized_brightness = (value / self.brightness_service.max_screen) * 101
self.scale.animate_value(normalized_brightness)
def on_brightness_changed(self, _sender: any, value: float, *_args) -> None:
normalized_brightness = self._normalize_brightness(value)
self._update_ui(normalized_brightness)
def _normalize_brightness(self, brightness: float) -> float:
return (brightness / self.brightness_service.max_screen) * 100
def _update_ui(self, brightness: float) -> None:
self.scale.animate_value(brightness)
self.update_icon(brightness)
def update_icon(self, brightness_percentage: float) -> None:
icon_name = self._calculate_icon_name(brightness_percentage)
self.icon.set_label(icon_name)
def _calculate_icon_name(self, brightness_percentage: float) -> str:
thresholds = [
BrightnessIcons.MAX_BRIGHTNESS
/ (BrightnessIcons.RATIO ** (len(BrightnessIcons.MAPPINGS) - i - 1))
for i in range(len(BrightnessIcons.MAPPINGS))
]
for i, threshold in enumerate(thresholds):
if brightness_percentage < threshold:
return BrightnessIcons.MAPPINGS[i]
return BrightnessIcons.MAPPINGS[-1]
class AudioOSDContainer(Box):
class AudioOSDContainer(BaseOSDContainer):
__gsignals__ = {
"volume-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()),
}
def __init__(self, **kwargs):
super().__init__(**kwargs, orientation="h", spacing=13, name="osd-container")
super().__init__(icon_name="volume_up", icon_size="29px", **kwargs)
self.audio = Audio()
self.icon = self._create_icon("audio-volume-medium-symbolic", 29)
self.scale = self._create_audio_scale()
self.add(self.icon)
self.add(self.scale)
self._setup_handlers()
self.sync_with_audio()
def _setup_handlers(self) -> None:
self.scale.connect("value-changed", self.on_volume_changed)
self.audio.connect("notify::speaker", self.on_audio_speaker_changed)
def _create_icon(self, icon_name: str, pixel_size: int) -> Image:
icon = Image(icon_name=icon_name)
icon.set_pixel_size(pixel_size)
return icon
def _create_audio_scale(self) -> AnimatedScale:
return AnimatedScale(
marks=(ScaleMark(value=i) for i in range(1, 100, 10)),
value=70,
min_value=0,
max_value=100,
increments=(1, 1),
orientation="h",
)
def sync_with_audio(self):
def sync_with_audio(self) -> None:
if self.audio.speaker:
volume = round(self.audio.speaker.volume)
self.scale.set_value(volume)
self.update_icon(volume)
self._update_ui(volume)
def on_volume_changed(self, *_):
def on_volume_changed(self, *_) -> None:
if self.audio.speaker:
volume = self.scale.value
if 1 <= volume <= 100:
self.audio.speaker.set_volume(volume)
self.update_icon(volume)
self._update_ui(volume)
self.emit("volume-changed")
def on_audio_speaker_changed(self, *_):
def on_audio_speaker_changed(self, *_) -> None:
if self.audio.speaker:
self.audio.speaker.connect("notify::volume", self.update_volume)
self.update_volume()
def update_volume(self, *_):
def update_volume(self, *_) -> None:
if self.audio.speaker and not self.is_hovered():
volume = round(self.audio.speaker.volume)
self.scale.set_value(volume)
self.update_icon(volume)
self._update_ui(volume)
def update_icon(self, volume):
icon_name = self._get_audio_icon_name(volume)
self.icon.set_from_icon_name(icon_name)
def _update_ui(self, volume: int) -> None:
self.scale.set_value(volume)
self.update_icon(volume)
def _get_audio_icon_name(self, volume: int) -> str:
if volume >= 81:
return "audio-volume-high-symbolic"
elif volume < 51:
return "audio-volume-low-symbolic"
else:
return "audio-volume-medium-symbolic"
def update_icon(self, volume: int) -> None:
icon_name = self._get_volume_icon(volume)
self.icon.set_label(icon_name)
def _get_volume_icon(self, volume: int) -> str:
for mapping in VolumeIcons.MAPPINGS:
if volume >= mapping.threshold:
return mapping.name
class OSDContainer(Window):
def __init__(self, **kwargs):
self.audio_container = AudioOSDContainer()
self.brightness_container = BrightnessOSDContainer()
self.is_initial_display = True
self.last_activity_time = GLib.get_monotonic_time()
self.main_box = Box(
self.main_box = self._create_main_box()
super().__init__(
**kwargs,
layer="overlay",
anchor="bottom",
exclusive=False,
focusable=False,
child=self.main_box,
visible=True,
)
self._initialize()
def _create_main_box(self) -> Box:
return Box(
orientation="v",
spacing=13,
children=[self.audio_container, self.brightness_container],
)
super().__init__(
**kwargs,
layer="top",
anchor="bottom",
child=self.main_box,
visible=False,
)
def _initialize(self) -> None:
self._setup_containers()
self._setup_event_handlers()
self._setup_timers()
self.audio_container.set_visible(False)
self.brightness_container.set_visible(False)
self.last_activity_time = time.time()
def _setup_containers(self) -> None:
self.audio_container.set_visible(True)
self.brightness_container.set_visible(True)
def _setup_event_handlers(self) -> None:
self.audio_container.audio.connect("notify::speaker", self.show_audio)
self.brightness_container.brightness_service.connect(
"screen", self.show_brightness
)
self.audio_container.connect("volume-changed", self.show_audio)
GLib.timeout_add(100, self.check_inactivity)
def _setup_timers(self) -> None:
GLib.timeout_add_seconds(1, self._check_inactivity)
GLib.timeout_add_seconds(1, self._end_initial_display)
def show_audio(self, *_):
self.show_box(self.audio_container)
self.reset_inactivity_timer()
def _end_initial_display(self) -> bool:
self.is_initial_display = False
self._hide_all()
return False
def show_brightness(self, *_):
self.show_box(self.brightness_container)
self.reset_inactivity_timer()
def show_box(self, box_to_show):
self.audio_container.set_visible(box_to_show == self.audio_container)
self.brightness_container.set_visible(box_to_show == self.brightness_container)
self.set_visible(True)
self.reset_inactivity_timer()
def start_hide_timer(self):
def _hide_all(self) -> None:
self.set_visible(False)
self.audio_container.set_visible(False)
self.brightness_container.set_visible(False)
def reset_inactivity_timer(self):
self.last_activity_time = time.time()
def show_audio(self, *_) -> None:
if not self.is_initial_display:
self._show_container(show_audio=True)
def check_inactivity(self):
if time.time() - self.last_activity_time >= 2:
self.start_hide_timer()
def show_brightness(self, *_) -> None:
if not self.is_initial_display:
self._show_container(show_audio=False)
def _show_container(self, show_audio: bool) -> None:
self.audio_container.set_visible(show_audio)
self.brightness_container.set_visible(not show_audio)
self.set_visible(True)
self._reset_inactivity_timer()
def _reset_inactivity_timer(self) -> None:
self.last_activity_time = GLib.get_monotonic_time()
def _check_inactivity(self) -> bool:
current_time = GLib.get_monotonic_time()
if (
not self.is_initial_display
and (current_time - self.last_activity_time) / 1_000_000 >= 2
):
self.set_visible(False)
return True