Files
Modus/src/utils/utils.py
T
sankalp e4813f35a0 feat(root): implement dynamic todo list refresh and UI enhancements
This change adds a mechanism to refresh the todo list dynamically when the underlying service updates, improving UI responsiveness and user experience. It also updates documentation and removes deprecated files.

- New Features:
  - Added `_refresh_todos` method to rebuild the todo list when `get_todo_service().todos` changes.
  - Introduced `TodoListCapture` class for mouse capture handling of the todo list window.
- Enhancements:
  - Updated README with corrected image paths, installation tips, and manual installation commands.
  - Introduced `start.py` to launch the application or lock mode.
  - Removed deprecated `config.json` and `debug_memory.py`.
- Bug Fixes:
  - Fixed lock screen screenshot path in README.
2026-04-10 17:11:32 +05:45

157 lines
4.3 KiB
Python

from pathlib import Path
from typing import Literal
from fabric.utils import Gdk, bulk_connect, exec_shell_command, exec_shell_command_async
from fabric.widgets.scale import Scale, ScaleMark
from fabric.widgets.svg import Svg
from shared.widgets.animator import Animator
class AnimatedScale(Scale):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.animator = None
def animate_value(self, value: float):
if not self.animator:
self.animator = Animator(
bezier_curve=(0.34, 1.56, 0.64, 1.0),
duration=0.8,
min_value=self.min_value,
max_value=self.value,
tick_widget=self,
notify_value=lambda p, *_: self.set_value(p.value),
)
self.animator.pause()
self.animator.min_value = self.value
self.animator.max_value = value
self.animator.play()
def is_app_running(app_name: str) -> bool:
return len(exec_shell_command(f"pidof {app_name}")) != 0
# Function to set up cursor hover
def setup_cursor_hover(
widget, cursor_name: Literal["pointer", "crosshair", "grab"] = "pointer"
):
display = Gdk.Display.get_default()
cursor = Gdk.Cursor.new_from_name(display, cursor_name)
def on_enter_notify_event(widget, _):
widget.get_window().set_cursor(cursor)
def on_leave_notify_event(widget, _):
# Restore default cursor when leaving the widget's area
widget.get_window().set_cursor(None)
bulk_connect(
widget,
{
"enter-notify-event": on_enter_notify_event,
"leave-notify-event": on_leave_notify_event,
},
)
# Create a scale widget
def create_scale(
name,
marks=None,
value=0,
min_value: float = 0,
max_value: float = 100,
increments=(1, 1),
curve=(0.34, 1.56, 0.64, 1.0),
orientation="h",
h_expand=True,
h_align="center",
style_classes="",
duration=0.8,
**kwargs,
) -> AnimatedScale:
if marks is None:
marks = (ScaleMark(value=i) for i in range(1, 100, 10))
return AnimatedScale(
name=name,
marks=marks,
value=value,
min_value=min_value,
max_value=max_value,
increments=increments,
orientation=orientation,
curve=curve,
h_expand=h_expand,
h_align=h_align,
duration=duration,
style_classes=style_classes,
**kwargs,
)
# Base path to your SVG assets (resolve from repository root)
BASE_SVG_PATH = (Path(__file__).resolve().parent.parent / "assets" / "icons").resolve()
def svg_file(relative_path: str, **kwargs) -> Svg:
"""
Return a fresh Svg widget for a given relative path.
Avoids reusing the same widget instance across multiple containers, which
can cause GTK warnings. Example:
svg_file("misc/logo.svg", size=16)
"""
def _resolve_path(path_like):
s = str(path_like)
marker = "/src/assets/icons/"
if marker in s:
rel = s.split(marker, 1)[1]
return (BASE_SVG_PATH / rel).resolve()
p = Path(s)
if p.is_absolute():
return p
return (BASE_SVG_PATH / p).resolve()
full_path = _resolve_path(relative_path)
svg = Svg(svg_file=str(full_path), **kwargs)
original_set_from_file = svg.set_from_file
def _set_from_file_resolved(file_path):
return original_set_from_file(str(_resolve_path(file_path)))
svg.set_from_file = _set_from_file_resolved # type: ignore[attr-defined]
# Provide a convenience alias for dynamic updates
def _dynamic_file(file_path):
_set_from_file_resolved(file_path)
return svg
svg.dynamic_file = _dynamic_file # type: ignore[attr-defined]
# Convenience method to update style and return the same instance
def _dynamic_style(
style: str,
*,
compiled: bool = True,
append: bool = False,
add_brackets: bool = True,
):
svg.set_style(
style, compiled=compiled, append=append, add_brackets=add_brackets
)
return svg
svg.dynamic_style = _dynamic_style # type: ignore[attr-defined]
return svg
def generate_colors_from_wallpaper(image_path: str) -> bool:
cmd = f'matugen image "{image_path}" --source-color-index 0'
return bool(exec_shell_command_async(cmd))