refactor! : drop subprocess usage and centralize brightness service initialization

This commit is contained in:
sankalp committed 2026-07-08 12:25:02 +05:45
1 parent 06be28535a
commit 5b8d6a62aa
21 files changed
+834 -828

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
from .main import Dock
__all__ = ["Dock"]
+72
View File
@@ -0,0 +1,72 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from fabric.utils import GLib
from .constants import ANIM_INTERVAL_MS, IDLE_THRESHOLD, LERP_FACTOR
from .layout import DockLayout
if TYPE_CHECKING:
from .canvas import DockCanvas
class DockAnimator:
def __init__(self, canvas: DockCanvas):
self._canvas = canvas
self._timer_id: Optional[int] = None
self._running = False
def start(self) -> None:
if self._running:
return
self._running = True
self._timer_id = GLib.timeout_add(ANIM_INTERVAL_MS, self._tick)
def stop(self) -> None:
self._running = False
if self._timer_id is not None:
GLib.source_remove(self._timer_id)
self._timer_id = None
def _tick(self) -> bool:
if not self._running:
return False
canvas = self._canvas
items = canvas.model.items
dirty = False
for item in items:
delta = item.target_scale - item.current_scale
if abs(delta) > IDLE_THRESHOLD:
item.current_scale += delta * LERP_FACTOR
dirty = True
else:
item.current_scale = item.target_scale
model_w = canvas._canvas_max_width()
model_h = canvas._canvas_height()
DockLayout.compute(
items,
canvas._mouse_x,
canvas._mouse_y,
canvas._base_icon_size(),
model_w,
model_h,
canvas._mouse_inside,
)
if (
model_w != canvas._last_requested_width
or model_h != canvas._last_requested_height
):
canvas._last_requested_width = model_w
canvas._last_requested_height = model_h
canvas._update_size_request()
if dirty or canvas._needs_redraw:
canvas._needs_redraw = False
canvas.queue_draw()
return self._running
File diff suppressed because it is too large. Load diff
+40
View File
@@ -0,0 +1,40 @@
from __future__ import annotations
from utils.utils import toml_file
PINNED_APPS_FILE = toml_file("dock.toml")
LERP_FACTOR = 0.20
IDLE_THRESHOLD = 0.0005
ANIM_FPS = 60
ANIM_INTERVAL_MS = 1000 // ANIM_FPS
MAX_SCALE = 2.0
MIN_SCALE = 1.0
SIGMA_FACTOR = 1.3
ICON_GAP = 8
BG_PADDING_H = 14
BG_PADDING_V = 12
CANVAS_TOP_PAD = 28
INDICATOR_H = 10
SEPARATOR_WIDTH = 1
SEPARATOR_GAP = 4
BG_RADIUS = 20.0
BG_ALPHA = 0.82
BORDER_ALPHA = 0.50
BORDER_WIDTH = 1.0
ICON_CORNER_RADIUS_FACTOR = 0.22
INDICATOR_RADIUS = 2.5
INDICATOR_COLOR = (1.0, 1.0, 1.0, 0.95)
BADGE_FONT_SIZE = 9.0
BADGE_PADDING = 2
BADGE_RADIUS = 4.0
BADGE_BG = (0.0, 0.0, 0.0, 0.70)
BADGE_FG = (1.0, 1.0, 1.0, 1.0)
SEPARATOR_COLOR = (1.0, 1.0, 1.0, 0.25)
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
import math
from typing import TYPE_CHECKING, List, Tuple
from .constants import (
BG_PADDING_H,
BG_PADDING_V,
ICON_GAP,
INDICATOR_H,
MAX_SCALE,
MIN_SCALE,
SIGMA_FACTOR,
)
if TYPE_CHECKING:
from .canvas import DockItem
class DockLayout:
@staticmethod
def compute(
items: List[DockItem],
mouse_x: float,
mouse_y: float,
base_icon_size: int,
canvas_w: int,
canvas_h: int,
mouse_inside: bool,
) -> None:
if not items:
return
sigma = base_icon_size * SIGMA_FACTOR
amplitude = MAX_SCALE - MIN_SCALE
n = len(items)
base_w = base_icon_size + ICON_GAP
total_base = n * base_icon_size + max(n - 1, 0) * ICON_GAP
start_x = (canvas_w - total_base) / 2.0
for i, item in enumerate(items):
est_cx = start_x + i * base_w + base_icon_size / 2.0
if mouse_inside:
dist = abs(est_cx - mouse_x)
target = MIN_SCALE + amplitude * math.exp(
-(dist * dist) / (2.0 * sigma * sigma)
)
else:
target = MIN_SCALE
item.target_scale = target
bg_h = base_icon_size + 2 * BG_PADDING_V
bg_y = canvas_h - INDICATOR_H - bg_h
baseline_y = bg_y + BG_PADDING_V + base_icon_size
rendered_widths = [base_icon_size * item.current_scale for item in items]
total_w = sum(rendered_widths) + max(n - 1, 0) * ICON_GAP
cursor_x = (canvas_w - total_w) / 2.0
for i, item in enumerate(items):
iw = rendered_widths[i]
ih = base_icon_size * item.current_scale
ix = cursor_x
iy = baseline_y - ih
item.render_x = ix
item.render_y = iy
item.render_w = iw
item.render_h = ih
item.drag_index = i
cursor_x += iw + ICON_GAP
@staticmethod
def background_rect(
items: List[DockItem],
base_icon_size: int,
canvas_w: int,
canvas_h: int,
) -> Tuple[float, float, float, float]:
bg_h = base_icon_size + 2 * BG_PADDING_V
bg_y = canvas_h - INDICATOR_H - bg_h
if items:
min_x = min(item.render_x for item in items)
max_x = max(item.render_x + item.render_w for item in items)
bg_x = min_x - BG_PADDING_H
bg_w = (max_x - min_x) + 2 * BG_PADDING_H
else:
bg_x = BG_PADDING_H
bg_w = canvas_w - 2 * BG_PADDING_H
return bg_x, bg_y, bg_w, bg_h
+163
View File
@@ -0,0 +1,163 @@
from __future__ import annotations
from fabric.utils import GLib, logger, random
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.revealer import Revealer
from fabric.widgets.wayland import WaylandWindow as Window
from services.config import config, on_config_change
from utils.occlusion import check_occlusion
from .canvas import DockCanvas
class Dock(Window):
def __init__(self):
super().__init__(
layer="top",
anchor="bottom center",
exclusivity="none",
title="modus-dock",
name="dock",
)
screen = self.get_screen()
if screen:
visual = screen.get_rgba_visual()
if visual:
self.set_visual(visual)
self.set_app_paintable(True)
self.canvas = DockCanvas(self)
canvas_box = Box(
name="dock-canvas-box",
children=[self.canvas],
)
self.revealer = Revealer(
child=canvas_box,
transition_duration=200,
transition_type="slide-up",
)
hover_strip = Box(
name="dock-hover-strip",
)
outer_box = Box(
name="dock-outer-box",
orientation="v",
children=[self.revealer, hover_strip],
)
self.children = EventBox(
events=["enter-notify", "leave-notify"],
child=outer_box,
on_enter_notify_event=lambda *_: self.on_hover_enter(),
on_leave_notify_event=lambda *_: self.on_hover_leave(),
)
self.dock_height = 100
self.is_hovered = False
self.hide_ticket = 0
self._startup_grace = True
self.revealer.set_reveal_child(True)
self.show_all()
on_config_change(self._on_config_change)
if config().get("dock_auto_hide", True):
GLib.timeout_add(2000, self._start_occlusion_monitoring)
else:
self._occlusion_timer_id = None
def _start_occlusion_monitoring(self) -> bool:
self._startup_grace = False
self.setup_occlusion_monitoring()
return False
def _on_config_change(self, new_config, old_config) -> None:
if config().has_changed("dock_enabled", old_config):
self._update_visibility()
if config().has_changed("dock_icon_size", old_config):
self.canvas.update_icon_size()
if config().has_changed("dock_auto_hide", old_config):
if new_config.get("dock_auto_hide", True):
self.setup_occlusion_monitoring()
else:
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
GLib.source_remove(self._occlusion_timer_id)
self._occlusion_timer_id = None
self.revealer.set_reveal_child(True)
if config().has_changed("dock_hide_special_workspace_apps", old_config):
self.canvas._rebuild_model()
def _update_visibility(self) -> None:
if config().get("dock_enabled", True):
self.show()
self.revealer.set_reveal_child(True)
else:
self.hide()
def on_hover_enter(self) -> None:
self.is_hovered = True
self.hide_ticket = random.getrandbits(32)
self.revealer.set_reveal_child(True)
def on_hover_leave(self) -> None:
self.is_hovered = False
ticket = random.getrandbits(32)
self.hide_ticket = ticket
def delayed_hide(t):
if t == self.hide_ticket and not self.is_hovered:
if config().get("dock_auto_hide", True):
is_occ = config().get(
"dock_always_occluded", False
) or check_occlusion(("bottom", self.dock_height))
if is_occ:
self.revealer.set_reveal_child(False)
return False
GLib.timeout_add(500, delayed_hide, ticket)
def setup_occlusion_monitoring(self) -> None:
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
GLib.source_remove(self._occlusion_timer_id)
self._occlusion_timer_id = None
def check_dock_occlusion() -> bool:
try:
if config().get("dock_always_occluded", False):
is_occ = True
else:
is_occ = check_occlusion(("bottom", self.dock_height))
if is_occ and not self.is_hovered and self.revealer.get_reveal_child():
self.revealer.set_reveal_child(False)
elif not is_occ and not self.revealer.get_reveal_child():
self.revealer.set_reveal_child(True)
elif is_occ and self.is_hovered:
if not self.revealer.get_reveal_child():
self.revealer.set_reveal_child(True)
except Exception as e:
logger.error(f"[Dock] Occlusion check error: {e}")
return True
self._occlusion_timer_id = GLib.timeout_add(300, check_dock_occlusion)
def destroy(self) -> None:
if hasattr(self, "_occlusion_timer_id") and self._occlusion_timer_id:
GLib.source_remove(self._occlusion_timer_id)
self._occlusion_timer_id = None
if hasattr(self, "canvas") and self.canvas:
self.canvas.destroy()
super().destroy()