feat(src/window/panel/panel): add NumLockIndicator and ChargingIndicator, improve battery service initialization
This commit is contained in:
4 files changed
+193
-25
No files matched your search
@@ -1,10 +1,10 @@
|
||||
from services.bluetooth import BluetoothClient
|
||||
from fabric.utils import logger
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
from services.battery import Battery
|
||||
from services.bluetooth import BluetoothClient
|
||||
from services.network import NetworkClient
|
||||
from shared.window.applet_window import AppletWindow
|
||||
from shared.window.battery_widget import BatteryControl
|
||||
@@ -320,7 +320,7 @@ class BatteryIndicator(Box):
|
||||
super().__init__(name="battery-indicator", orientation="h", **kwargs)
|
||||
self.show_window = show_window
|
||||
|
||||
self.battery_service = Battery()
|
||||
self.battery_service = Battery.get_initial()
|
||||
|
||||
self.battery_icon = svg_file("battery/battery-100.svg", size=23)
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from window.panel.notch.notch import Notch
|
||||
from window.panel.notch.indicators import CapsLockIndicator, KeyboardLayoutIndicator
|
||||
from window.panel.notch.indicators import (
|
||||
CapsLockIndicator,
|
||||
KeyboardLayoutIndicator,
|
||||
NumLockIndicator,
|
||||
)
|
||||
|
||||
__all__ = ["Notch", "CapsLockIndicator", "KeyboardLayoutIndicator"]
|
||||
__all__ = ["Notch", "CapsLockIndicator", "KeyboardLayoutIndicator", "NumLockIndicator"]
|
||||
@@ -3,11 +3,13 @@ from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.label import Label
|
||||
|
||||
from services.battery import Battery
|
||||
from services.capslock import CapsLock
|
||||
from services.keyboard_layout import KeyboardLayout
|
||||
from services.numlock import NumLock
|
||||
from utils.utils import svg_file
|
||||
|
||||
DISPLAY_MS = 2000 # how long each indicator stays visible
|
||||
DISPLAY_MS = 2000 # how long each indicator stays visible (ms)
|
||||
|
||||
|
||||
class KeyboardLayoutIndicator(Box):
|
||||
@@ -19,7 +21,6 @@ class KeyboardLayoutIndicator(Box):
|
||||
v_align="center",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._show_cb = show_cb
|
||||
self._hide_cb = hide_cb
|
||||
self._hide_timer_id = 0
|
||||
@@ -75,6 +76,9 @@ class KeyboardLayoutIndicator(Box):
|
||||
super().destroy()
|
||||
|
||||
|
||||
# ── CapsLock ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class CapsLockIndicator(Box):
|
||||
def __init__(self, show_cb, hide_cb, **kwargs):
|
||||
super().__init__(
|
||||
@@ -84,7 +88,6 @@ class CapsLockIndicator(Box):
|
||||
v_align="center",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._show_cb = show_cb
|
||||
self._hide_cb = hide_cb
|
||||
self._hide_timer_id = 0
|
||||
@@ -99,7 +102,7 @@ class CapsLockIndicator(Box):
|
||||
)
|
||||
self._label = Label(
|
||||
name="notch-indicator-label",
|
||||
label="On",
|
||||
label="On" if self._caps.is_on else "Off",
|
||||
v_align="center",
|
||||
)
|
||||
|
||||
@@ -112,15 +115,11 @@ class CapsLockIndicator(Box):
|
||||
)
|
||||
)
|
||||
|
||||
# Accessing is_on triggers _ensure_monitoring_started() inside the service —
|
||||
# without this the 500ms LED poll never begins and state_changed never fires.
|
||||
_ = self._caps.is_on
|
||||
|
||||
self._caps.connect("state_changed", self._on_state_changed)
|
||||
|
||||
def _on_state_changed(self, _service, is_on: bool):
|
||||
if not is_on:
|
||||
return
|
||||
# Show for BOTH On and Off
|
||||
self._label.set_label("On" if is_on else "Off")
|
||||
self._show_cb(self)
|
||||
self._schedule_hide()
|
||||
|
||||
@@ -143,3 +142,147 @@ class CapsLockIndicator(Box):
|
||||
except Exception as e:
|
||||
logger.warning(f"[NotchIndicators] caps disconnect: {e}")
|
||||
super().destroy()
|
||||
|
||||
|
||||
class NumLockIndicator(Box):
|
||||
def __init__(self, show_cb, hide_cb, **kwargs):
|
||||
super().__init__(
|
||||
name="notch-numlock",
|
||||
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._num = NumLock.get_initial()
|
||||
|
||||
self._icon = svg_file(
|
||||
"misc/num-lock.svg",
|
||||
size=16,
|
||||
name="notch-indicator-icon",
|
||||
v_align="center",
|
||||
)
|
||||
self._label = Label(
|
||||
name="notch-indicator-label",
|
||||
label="On" if self._num.is_on else "Off",
|
||||
v_align="center",
|
||||
)
|
||||
|
||||
self.add(
|
||||
CenterBox(
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
start_children=self._icon,
|
||||
end_children=self._label,
|
||||
)
|
||||
)
|
||||
|
||||
# is_on access also starts the 500ms LED poll inside the service
|
||||
self._num.connect("state_changed", self._on_state_changed)
|
||||
|
||||
def _on_state_changed(self, _service, is_on: bool):
|
||||
# Show for BOTH On and Off
|
||||
self._label.set_label("On" if is_on else "Off")
|
||||
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._num.disconnect_by_func(self._on_state_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"[NotchIndicators] numlock disconnect: {e}")
|
||||
super().destroy()
|
||||
|
||||
|
||||
class ChargingIndicator(Box):
|
||||
def __init__(self, show_cb, hide_cb, **kwargs):
|
||||
super().__init__(
|
||||
name="notch-charging",
|
||||
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._battery = Battery.get_initial()
|
||||
|
||||
self._label = Label(
|
||||
name="notch-indicator-label",
|
||||
label="Charging",
|
||||
v_align="center",
|
||||
)
|
||||
self._icon = svg_file(
|
||||
"notch/charing.svg",
|
||||
size=18,
|
||||
name="notch-indicator-icon",
|
||||
v_align="center",
|
||||
)
|
||||
|
||||
self.add(
|
||||
CenterBox(
|
||||
orientation="h",
|
||||
h_expand=True,
|
||||
start_children=self._label,
|
||||
end_children=self._icon,
|
||||
)
|
||||
)
|
||||
|
||||
self._battery.connect("changed", self._on_battery_changed)
|
||||
|
||||
GLib.timeout_add(500, self._initial_check)
|
||||
|
||||
def _is_charger_connected(self) -> bool:
|
||||
return self._battery.charging or self._battery.charged
|
||||
|
||||
def _initial_check(self):
|
||||
self._last_charger_state = self._is_charger_connected()
|
||||
if self._last_charger_state:
|
||||
self._show_cb(self)
|
||||
self._schedule_hide()
|
||||
return False # one-shot
|
||||
|
||||
def _on_battery_changed(self, _battery):
|
||||
connected = self._is_charger_connected()
|
||||
if connected and not self._last_charger_state:
|
||||
self._show_cb(self)
|
||||
self._schedule_hide()
|
||||
self._last_charger_state = connected
|
||||
|
||||
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._battery.disconnect_by_func(self._on_battery_changed)
|
||||
except Exception as e:
|
||||
logger.warning(f"[NotchIndicators] charging disconnect: {e}")
|
||||
super().destroy()
|
||||
@@ -6,7 +6,12 @@ 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.panel.notch.indicators import (
|
||||
CapsLockIndicator,
|
||||
KeyboardLayoutIndicator,
|
||||
NumLockIndicator,
|
||||
ChargingIndicator,
|
||||
)
|
||||
from window.controlcenter.player import get_shared_mpris_manager
|
||||
|
||||
|
||||
@@ -35,6 +40,14 @@ class Notch(Box):
|
||||
h_expand=True,
|
||||
)
|
||||
|
||||
self._last_scroll_index = 0
|
||||
self._last_scroll_time = 0
|
||||
self._last_recording_check = 0
|
||||
self._is_active_recording = False
|
||||
self._music_services: list[str] = []
|
||||
# Must be set BEFORE indicator widgets are created
|
||||
self._transient_active = False
|
||||
|
||||
# Transient indicator pages — shown on change, hidden after 2 s
|
||||
self.kbd_indicator = KeyboardLayoutIndicator(
|
||||
show_cb=self._show_transient,
|
||||
@@ -44,6 +57,15 @@ class Notch(Box):
|
||||
show_cb=self._show_transient,
|
||||
hide_cb=self._hide_transient,
|
||||
)
|
||||
self.num_indicator = NumLockIndicator(
|
||||
show_cb=self._show_transient,
|
||||
hide_cb=self._hide_transient,
|
||||
)
|
||||
# Persistent indicator — now also transient (2s), deferred via idle_add
|
||||
self.charging_indicator = ChargingIndicator(
|
||||
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)
|
||||
@@ -52,14 +74,6 @@ class Notch(Box):
|
||||
screen_capture_service.connect("recording-started", self._on_recording_started)
|
||||
screen_capture_service.connect("recording-stopped", self._on_recording_stopped)
|
||||
|
||||
self._last_scroll_index = 0
|
||||
self._last_scroll_time = 0
|
||||
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,
|
||||
@@ -71,6 +85,8 @@ class Notch(Box):
|
||||
self.player_widget,
|
||||
self.kbd_indicator,
|
||||
self.caps_indicator,
|
||||
self.num_indicator,
|
||||
self.charging_indicator,
|
||||
],
|
||||
)
|
||||
self._last_stack_page = 0
|
||||
@@ -126,7 +142,7 @@ class Notch(Box):
|
||||
self.notch_stack.set_visible_child(widget)
|
||||
|
||||
def _hide_transient(self):
|
||||
"""Return to the normal notch state after the indicator timer expires."""
|
||||
"""Return to normal notch state after the indicator timer expires."""
|
||||
self._transient_active = False
|
||||
self._apply_stack_state()
|
||||
|
||||
@@ -231,7 +247,10 @@ class Notch(Box):
|
||||
self.notch_stack.set_visible_child(self.idle_widget)
|
||||
return
|
||||
|
||||
pages = {1: self.recording_indicator, 2: self.player_widget}
|
||||
pages = {
|
||||
1: self.recording_indicator,
|
||||
2: self.player_widget,
|
||||
}
|
||||
|
||||
if len(active_pages) == 1:
|
||||
target = active_pages[0]
|
||||
@@ -248,4 +267,6 @@ class Notch(Box):
|
||||
self.player_widget.destroy()
|
||||
self.kbd_indicator.destroy()
|
||||
self.caps_indicator.destroy()
|
||||
self.num_indicator.destroy()
|
||||
self.charging_indicator.destroy()
|
||||
super().destroy()
|
||||
Reference in new issue
Block a user