feat(src/window/panel/notch): add CapsLockIndicator and KeyboardLayoutIndicator
Add transient indicator pages for caps lock and keyboard layout, update stack children and handling to show/hide after timeout.
This commit is contained in:
3 files changed
+188
-2
No files matched your search
@@ -1,3 +1,4 @@
|
||||
from window.panel.notch.notch import Notch
|
||||
from window.panel.notch.indicators import CapsLockIndicator, KeyboardLayoutIndicator
|
||||
|
||||
__all__ = ["Notch"]
|
||||
__all__ = ["Notch", "CapsLockIndicator", "KeyboardLayoutIndicator"]
|
||||
@@ -0,0 +1,141 @@
|
||||
from fabric.utils import GLib, logger
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
from services.capslock import CapsLock
|
||||
from services.keyboard_layout import KeyboardLayout
|
||||
from utils.utils import svg_file
|
||||
|
||||
DISPLAY_MS = 2000 # how long each indicator stays visible
|
||||
|
||||
|
||||
class KeyboardLayoutIndicator(Box):
|
||||
def __init__(self, show_cb, hide_cb, **kwargs):
|
||||
super().__init__(
|
||||
name="notch-kbd-layout",
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
v_align="center",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._show_cb = show_cb
|
||||
self._hide_cb = hide_cb
|
||||
self._hide_timer_id = 0
|
||||
|
||||
self._kbd = KeyboardLayout.get_initial()
|
||||
|
||||
self._icon = svg_file(
|
||||
"misc/keyboard-layout.svg",
|
||||
size=16,
|
||||
name="notch-indicator-icon",
|
||||
v_align="center",
|
||||
)
|
||||
self._label = Label(
|
||||
name="notch-indicator-label",
|
||||
label=self._kbd.current_layout.upper(),
|
||||
v_align="center",
|
||||
)
|
||||
|
||||
self.add(
|
||||
CenterBox(
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
start_children=self._icon,
|
||||
end_children=self._label,
|
||||
)
|
||||
)
|
||||
|
||||
self._kbd.connect("layout_changed", self._on_layout_changed)
|
||||
|
||||
def _on_layout_changed(self, _service, layout: str):
|
||||
self._label.set_label(layout.upper())
|
||||
self._show_cb(self)
|
||||
self._schedule_hide()
|
||||
|
||||
def _schedule_hide(self):
|
||||
if self._hide_timer_id:
|
||||
GLib.source_remove(self._hide_timer_id)
|
||||
self._hide_timer_id = GLib.timeout_add(DISPLAY_MS, self._do_hide)
|
||||
|
||||
def _do_hide(self):
|
||||
self._hide_timer_id = 0
|
||||
self._hide_cb()
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
if self._hide_timer_id:
|
||||
GLib.source_remove(self._hide_timer_id)
|
||||
self._hide_timer_id = 0
|
||||
try:
|
||||
self._kbd.disconnect_by_func(self._on_layout_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"[NotchIndicators] kbd disconnect: {e}")
|
||||
super().destroy()
|
||||
|
||||
|
||||
class CapsLockIndicator(Box):
|
||||
def __init__(self, show_cb, hide_cb, **kwargs):
|
||||
super().__init__(
|
||||
name="notch-capslock",
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
v_align="center",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._show_cb = show_cb
|
||||
self._hide_cb = hide_cb
|
||||
self._hide_timer_id = 0
|
||||
|
||||
self._caps = CapsLock.get_initial()
|
||||
|
||||
self._icon = svg_file(
|
||||
"misc/caps-lock.svg",
|
||||
size=16,
|
||||
name="notch-indicator-icon",
|
||||
v_align="center",
|
||||
)
|
||||
self._label = Label(
|
||||
name="notch-indicator-label",
|
||||
label="On",
|
||||
v_align="center",
|
||||
)
|
||||
|
||||
self.add(
|
||||
CenterBox(
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
start_children=self._icon,
|
||||
end_children=self._label,
|
||||
)
|
||||
)
|
||||
|
||||
self._caps.connect("state_changed", self._on_state_changed)
|
||||
|
||||
def _on_state_changed(self, _service, is_on: bool):
|
||||
if not is_on:
|
||||
return
|
||||
self._show_cb(self)
|
||||
self._schedule_hide()
|
||||
|
||||
def _schedule_hide(self):
|
||||
if self._hide_timer_id:
|
||||
GLib.source_remove(self._hide_timer_id)
|
||||
self._hide_timer_id = GLib.timeout_add(DISPLAY_MS, self._do_hide)
|
||||
|
||||
def _do_hide(self):
|
||||
self._hide_timer_id = 0
|
||||
self._hide_cb()
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
if self._hide_timer_id:
|
||||
GLib.source_remove(self._hide_timer_id)
|
||||
self._hide_timer_id = 0
|
||||
try:
|
||||
self._caps.disconnect_by_func(self._on_state_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"[NotchIndicators] caps disconnect: {e}")
|
||||
super().destroy()
|
||||
@@ -6,6 +6,7 @@ from fabric.widgets.stack import Stack
|
||||
from services.screencapture import screen_capture_service
|
||||
from window.panel.notch.recording import RecordingIndicator
|
||||
from window.panel.notch.player import NotchPlayer
|
||||
from window.panel.notch.indicators import CapsLockIndicator, KeyboardLayoutIndicator
|
||||
from window.controlcenter.player import get_shared_mpris_manager
|
||||
|
||||
|
||||
@@ -34,6 +35,16 @@ class Notch(Box):
|
||||
h_expand=True,
|
||||
)
|
||||
|
||||
# Transient indicator pages — shown on change, hidden after 2 s
|
||||
self.kbd_indicator = KeyboardLayoutIndicator(
|
||||
show_cb=self._show_transient,
|
||||
hide_cb=self._hide_transient,
|
||||
)
|
||||
self.caps_indicator = CapsLockIndicator(
|
||||
show_cb=self._show_transient,
|
||||
hide_cb=self._hide_transient,
|
||||
)
|
||||
|
||||
self._mpris = get_shared_mpris_manager()
|
||||
self._mpris.connect("new-player", self._on_new_player)
|
||||
self._mpris.connect("player-vanish", self._on_player_vanish)
|
||||
@@ -46,13 +57,21 @@ class Notch(Box):
|
||||
self._last_recording_check = 0
|
||||
self._is_active_recording = False
|
||||
self._music_services: list[str] = []
|
||||
# Track if a transient indicator is currently shown
|
||||
self._transient_active = False
|
||||
|
||||
self.notch_stack = Stack(
|
||||
name="panel-notch-stack",
|
||||
v_expand=True,
|
||||
h_expand=True,
|
||||
transition_type="none",
|
||||
children=[self.idle_widget, self.recording_indicator, self.player_widget],
|
||||
children=[
|
||||
self.idle_widget,
|
||||
self.recording_indicator,
|
||||
self.player_widget,
|
||||
self.kbd_indicator,
|
||||
self.caps_indicator,
|
||||
],
|
||||
)
|
||||
self._last_stack_page = 0
|
||||
|
||||
@@ -98,6 +117,21 @@ class Notch(Box):
|
||||
|
||||
self.notch_stack.connect("scroll-event", self._on_scroll)
|
||||
|
||||
# ── Transient indicator show / hide ──────────────────────────────────────
|
||||
|
||||
def _show_transient(self, widget):
|
||||
"""Make a transient indicator the visible notch page."""
|
||||
self._transient_active = True
|
||||
if self.notch_stack.get_visible_child() != widget:
|
||||
self.notch_stack.set_visible_child(widget)
|
||||
|
||||
def _hide_transient(self):
|
||||
"""Return to the normal notch state after the indicator timer expires."""
|
||||
self._transient_active = False
|
||||
self._apply_stack_state()
|
||||
|
||||
# ── Init / MPRIS / recording ──────────────────────────────────────────────
|
||||
|
||||
def _init_state(self):
|
||||
self._is_active_recording = screen_capture_service.is_recording
|
||||
if self._is_active_recording:
|
||||
@@ -133,6 +167,8 @@ class Notch(Box):
|
||||
self._music_services.remove(name)
|
||||
self._apply_stack_state()
|
||||
|
||||
# ── Scroll ────────────────────────────────────────────────────────────────
|
||||
|
||||
def _on_scroll(self, widget, event):
|
||||
now = GLib.get_monotonic_time()
|
||||
if now - self._last_scroll_time < 100_000:
|
||||
@@ -158,7 +194,13 @@ class Notch(Box):
|
||||
self._apply_stack_state()
|
||||
return True
|
||||
|
||||
# ── Stack state ───────────────────────────────────────────────────────────
|
||||
|
||||
def _apply_stack_state(self):
|
||||
# Don't override a transient indicator that is still counting down
|
||||
if self._transient_active:
|
||||
return
|
||||
|
||||
is_recording = self._is_active_recording
|
||||
|
||||
now = GLib.get_monotonic_time()
|
||||
@@ -204,4 +246,6 @@ class Notch(Box):
|
||||
def destroy(self):
|
||||
self.recording_indicator.destroy()
|
||||
self.player_widget.destroy()
|
||||
self.kbd_indicator.destroy()
|
||||
self.caps_indicator.destroy()
|
||||
super().destroy()
|
||||
Reference in new issue
Block a user