refactor(modules): improve exception handling and simplify service usage

Enhancements
- Replace bare `except:` with `except Exception:` in multiple modules- Cast step calculation to `int` for consistent type
- Simplify `get_battery_icon_file` to return a string directly
- Use `get_todo_service()` instead of module-level `todo_service` throughout todo widget
- Remove unused `"zen": "zen-browser"` mapping entry

Bug Fixes
- Prevent bare exception swallowing by catching `Exception` explicitly
- Ensure proper cleanup of timer IDs and other resources
- Delete unused modules/osdd components

New Features
- None
This commit is contained in:
sankalp committed 2026-03-23 23:34:03 +05:45
1 parent 34cfb2f49e
commit ef4065bc1f
34 files changed
+682 -519

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
from .audio import AudioOSDContainer # noqa: F401
from .brightness import BrightnessOSDContainer # noqa: F401
from .microphone import MicrophoneOSDContainer # noqa: F401
from .capslock import CapsLockOSDContainer # noqa: F401
from .keyboard_layout import KeyboardLayoutOSDContainer # noqa: F401
+28
View File
@@ -0,0 +1,28 @@
from functools import partial
from fabric.widgets.scale import Scale
from utils.animator import Animator, cubic_bezier
class AnimatedScale(Scale):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.animator = (
Animator(
duration=0.8,
timing_function=partial(cubic_bezier, 0.34, 1.56, 0.64, 1.0),
min_value=self.min_value,
max_value=self.value,
tick_widget=self,
notify_value=lambda anim: self.set_value(anim.value),
)
.build()
.play()
.unwrap()
)
def animate_value(self, value: float):
if self.animator:
self.animator.pause()
self.animator.min_value = self.value
self.animator.max_value = value
self.animator.play()
+105
View File
@@ -0,0 +1,105 @@
import math
from fabric.audio import Audio
from fabric.utils import get_relative_path
from fabric.widgets.svg import Svg
from fabric.widgets.scale import ScaleMark
from .animated_scale import AnimatedScale
from .base import BaseOSDContainer
class AudioOSDContainer(BaseOSDContainer):
def __init__(self, window, **kwargs):
super().__init__(window, **kwargs)
self.audio = Audio()
self._last_volume = None
self._last_muted = None
self._setup_specific_components()
self._connect_specific_signals()
def _setup_specific_components(self):
self.osd_window_image = Svg(
get_relative_path("../../../config/assets/icons/volume/audio-volume.svg"),
size=(100, 100),
name="osd-image",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.scale = 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",
)
self.add(self.osd_window_image)
self.add(self.scale)
def _connect_specific_signals(self):
self.audio.connect("notify::speaker", self._on_speaker_changed)
if self.audio.speaker:
self._connect_speaker_signals()
def _connect_speaker_signals(self):
if self.audio.speaker:
self.audio.speaker.connect("changed", self._on_speaker_stream_changed)
self._sync_with_audio()
def _on_speaker_changed(self, *_):
self._connect_speaker_signals()
self.update()
def _on_speaker_stream_changed(self, *_):
if self.audio.speaker:
current_volume = round(self.audio.speaker.volume)
current_muted = self.audio.speaker.muted
if self._last_volume != current_volume or self._last_muted != current_muted:
self._last_volume = current_volume
self._last_muted = current_muted
self.update()
def _sync_with_audio(self):
if self.audio.speaker:
self._last_volume = round(self.audio.speaker.volume)
self._last_muted = self.audio.speaker.muted
self.scale.set_value(self._last_volume)
self._update_display()
def _update_display(self):
if not self.audio.speaker:
return
volume = (
self._last_volume
if self._last_volume is not None
else round(self.audio.speaker.volume)
)
muted = (
self._last_muted
if self._last_muted is not None
else self.audio.speaker.muted
)
display_volume = 0 if (volume == 0 or muted) else volume
level = (
0 if display_volume == 0 else min(int(math.ceil(display_volume / 33)), 3)
)
self.osd_window_image.set_from_file(
get_relative_path(
f"../../../config/assets/icons/volume/audio-volume-{level}.svg"
)
)
if muted or volume == 0:
self.scale.add_style_class("muted")
else:
self.scale.remove_style_class("muted")
self.scale.animate_value(volume)
def update(self, *_):
self._update_display()
super().update()
+145
View File
@@ -0,0 +1,145 @@
from abc import abstractmethod
from fabric.utils import Gdk, GLib, invoke_repeater, remove_handler, time
from fabric.widgets.box import Box
from fabric.widgets.wayland import WaylandWindow as Window
class BaseOSDContainer(Box):
MAX_VISIBLE_TIME = 6.0
COOLDOWN_MS = 200
def __init__(self, window: Window, **kwargs):
super().__init__(**kwargs, orientation="v", spacing=12, name="osd-container")
self.last_handler: int = 0
self.window = window
self.osd = None
self.show_timestamp: float = 0.0
self.watchdog_handler: int = 0
self._update_in_progress = False
self._is_hovered = False
self._last_trigger_time: float = 0.0
GLib.idle_add(self._setup_window_signals)
def _setup_window_signals(self):
self.window.add_events(
Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK
)
self.window.connect("enter-notify-event", self._on_enter_notify)
self.window.connect("leave-notify-event", self._on_leave_notify)
return False
def _on_enter_notify(self, widget, event):
if event.detail != Gdk.NotifyType.INFERIOR:
self._is_hovered = True
def _on_leave_notify(self, widget, event):
if event.detail != Gdk.NotifyType.INFERIOR:
self._is_hovered = False
def is_hovered(self):
return self._is_hovered
def remove_last_handler(self):
if self.last_handler:
try:
remove_handler(self.last_handler)
except Exception:
pass
self.last_handler = 0
def remove_watchdog_handler(self):
if self.watchdog_handler:
try:
GLib.source_remove(self.watchdog_handler)
except Exception:
pass
self.watchdog_handler = 0
def cleanup_all_handlers(self):
self.remove_last_handler()
self.remove_watchdog_handler()
self.show_timestamp = 0.0
self._update_in_progress = False
def hide_window(self):
if self.osd and hasattr(self.osd, "revealer"):
self.osd.revealer.set_reveal_child(False)
GLib.timeout_add(150, lambda: self.window.hide() or False)
else:
self.window.hide()
def watchdog_force_hide(self, *_):
if not self.window.get_visible():
self.cleanup_all_handlers()
return False
elapsed = time.time() - self.show_timestamp
if elapsed >= self.MAX_VISIBLE_TIME:
if not self.is_hovered():
self.hide_window()
self.cleanup_all_handlers()
return False
else:
return True
return True
def update(self, *_):
if self._update_in_progress:
return
current_time = time.time()
time_since_last_trigger = (current_time - self._last_trigger_time) * 1000
if time_since_last_trigger < self.COOLDOWN_MS:
return
self._last_trigger_time = current_time
self._update_in_progress = True
self.cleanup_all_handlers()
self.show_timestamp = time.time()
if self.osd:
self.osd.show_container(self)
self.window.show()
self.focus()
self.last_handler = invoke_repeater(1700, self.unfocus, initial_call=False)
self.watchdog_handler = GLib.timeout_add(1000, self.watchdog_force_hide)
self._update_in_progress = False
def focus(self, *_):
self.add_style_class("focused")
return False
def unfocus(self, *_):
if not self.window.get_visible():
self.cleanup_all_handlers()
return False
self.remove_style_class("focused")
self.remove_last_handler()
self.last_handler = invoke_repeater(1700, self.unpop, initial_call=False)
return False
def unpop(self, *_):
if not self.window.get_visible():
self.cleanup_all_handlers()
return False
if not self.is_hovered():
self.hide_window()
self.cleanup_all_handlers()
else:
self.last_handler = invoke_repeater(500, self.unpop, initial_call=False)
return False
@abstractmethod
def _setup_specific_components(self):
pass
@abstractmethod
def _connect_specific_signals(self):
pass
+64
View File
@@ -0,0 +1,64 @@
import math
from services.brightness import Brightness
from fabric.utils import get_relative_path
from fabric.widgets.svg import Svg
from fabric.widgets.scale import ScaleMark
from .animated_scale import AnimatedScale
from .base import BaseOSDContainer
class BrightnessOSDContainer(BaseOSDContainer):
def __init__(self, window, **kwargs):
super().__init__(window, **kwargs)
self.brightness_service = Brightness.get_initial()
self._setup_specific_components()
self._connect_specific_signals()
def _setup_specific_components(self):
self.osd_window_image = Svg(
get_relative_path("../../../config/assets/icons/brightness/brightness.svg"),
size=(100, 100),
name="osd-image",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.scale = AnimatedScale(
marks=(ScaleMark(value=i) for i in range(0, 101, 10)),
value=70,
min_value=0,
max_value=100,
increments=(1, 1),
orientation="h",
)
self.add(self.osd_window_image)
self.add(self.scale)
def _connect_specific_signals(self):
self.brightness_service.connect("screen", self._on_screen_brightness_changed)
def _on_screen_brightness_changed(self, _sender, value, *_args):
self.update()
def _get_normalized_brightness(self):
return (
self.brightness_service.screen_brightness
/ self.brightness_service.max_screen
) * 100
def _update_display(self):
normalized = self._get_normalized_brightness()
level = 0 if normalized == 0 else min(int(math.ceil(normalized / 33)), 3)
self.osd_window_image.set_from_file(
get_relative_path(
f"../../../config/assets/icons/brightness/brightness-{level}.svg"
)
)
self.scale.animate_value(normalized)
def update(self, *_):
self._update_display()
super().update()
+50
View File
@@ -0,0 +1,50 @@
from fabric.widgets.label import Label
from fabric.widgets.svg import Svg
from fabric.utils import get_relative_path
from .base import BaseOSDContainer
from services.capslock import CapsLock
class CapsLockOSDContainer(BaseOSDContainer):
def __init__(self, window, **kwargs):
super().__init__(window, **kwargs)
self.capslock = CapsLock.get_initial()
self._setup_specific_components()
self._connect_specific_signals()
def _setup_specific_components(self):
self.osd_image = Svg(
get_relative_path("../../../assets/icons/misc/caps-lock.svg"),
size=(100, 100),
name="osd-image",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.label = Label(name="osd-label")
self.add(self.osd_image)
self._update_display(self.capslock.is_on)
def _connect_specific_signals(self):
self.capslock.connect("state_changed", self._on_caps_lock_state_changed)
def _on_caps_lock_state_changed(self, _, is_on: bool):
self._update_display(is_on)
self.update()
def _update_display(self, is_on: bool):
icon_path = "caps-lock.svg" if is_on else "caps-lock-off.svg"
self.osd_image.set_from_file(
get_relative_path(f"../../../assets/icons/misc/{icon_path}")
)
if is_on:
self.add_style_class("capslock-on")
self.remove_style_class("capslock-off")
else:
self.add_style_class("capslock-off")
self.remove_style_class("capslock-on")
def update(self, *_):
# We don't need a separate display update here as it's handled in the signal
super().update()
+40
View File
@@ -0,0 +1,40 @@
from fabric.widgets.label import Label
from fabric.widgets.svg import Svg
from fabric.utils import get_relative_path
from .base import BaseOSDContainer
from services.keyboard_layout import KeyboardLayout
class KeyboardLayoutOSDContainer(BaseOSDContainer):
def __init__(self, window, **kwargs):
super().__init__(window, **kwargs)
self.keyboard_layout = KeyboardLayout.get_initial()
self._setup_specific_components()
self._connect_specific_signals()
def _setup_specific_components(self):
self.osd_image = Svg(
get_relative_path("../../../assets/icons/misc/keyboard-layout.svg"),
size=(100, 100),
name="osd-image",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.label = Label(name="osd-label")
self.add(self.osd_image)
self.add(self.label)
self.label.set_text(self.keyboard_layout.current_layout)
def _connect_specific_signals(self):
self.keyboard_layout.connect("layout_changed", self._on_layout_changed)
def _on_layout_changed(self, service, layout: str):
if not self.is_hovered():
self.label.set_text(layout)
self.update()
def update(self, *_):
self.label.set_text(self.keyboard_layout.current_layout)
super().update()
+105
View File
@@ -0,0 +1,105 @@
import math
from fabric.audio import Audio
from fabric.utils import get_relative_path
from fabric.widgets.svg import Svg
from fabric.widgets.scale import ScaleMark
from .animated_scale import AnimatedScale
from .base import BaseOSDContainer
class MicrophoneOSDContainer(BaseOSDContainer):
def __init__(self, window, **kwargs):
super().__init__(window, **kwargs)
self.audio = Audio()
self._last_volume = None
self._last_muted = None
self._setup_specific_components()
self._connect_specific_signals()
def _setup_specific_components(self):
self.osd_window_image = Svg(
get_relative_path("../../../config/assets/icons/mic/microphone.svg"),
size=(100, 100),
name="osd-image",
h_align="center",
v_align="center",
h_expand=True,
v_expand=True,
)
self.scale = 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",
)
self.add(self.osd_window_image)
self.add(self.scale)
def _connect_specific_signals(self):
self.audio.connect("notify::microphone", self._on_microphone_changed)
if self.audio.microphone:
self._connect_microphone_signals()
def _connect_microphone_signals(self):
if self.audio.microphone:
self.audio.microphone.connect("changed", self._on_microphone_stream_changed)
self._sync_with_audio()
def _on_microphone_changed(self, *_):
self._connect_microphone_signals()
self.update()
def _on_microphone_stream_changed(self, *_):
if self.audio.microphone:
current_volume = round(self.audio.microphone.volume)
current_muted = self.audio.microphone.muted
if self._last_volume != current_volume or self._last_muted != current_muted:
self._last_volume = current_volume
self._last_muted = current_muted
self.update()
def _sync_with_audio(self):
if self.audio.microphone:
self._last_volume = round(self.audio.microphone.volume)
self._last_muted = self.audio.microphone.muted
self.scale.set_value(self._last_volume)
self._update_display()
def _update_display(self):
if not self.audio.microphone:
return
volume = (
self._last_volume
if self._last_volume is not None
else round(self.audio.microphone.volume)
)
muted = (
self._last_muted
if self._last_muted is not None
else self.audio.microphone.muted
)
display_volume = 0 if (volume == 0 or muted) else volume
level = (
0 if display_volume == 0 else min(int(math.ceil(display_volume / 33)), 3)
)
self.osd_window_image.set_from_file(
get_relative_path(
f"../../../config/assets/icons/mic/microphone-{level}.svg"
)
)
if muted or volume == 0:
self.scale.add_style_class("muted")
else:
self.scale.remove_style_class("muted")
self.scale.animate_value(volume)
def update(self, *_):
self._update_display()
super().update()