fix(controlcenter): flush stale-cache races in toggle UI
- Drop rfkill from bluetooth toggle (redundant with D-Bus Powered, was causing stale cache on next read) - Force toggle switch and icon to target state immediately after setting, don't wait for D-Bus callback - Fix nightlight icon race: flip local is_active instead of re-querying async kill state - Guard wifi on_network_ready against ethernet-first device loop (emits device-ready per device; wifi_device None on first) - Share single NetworkClient between main panel and wifi popup (eliminates async-init race of two independent instances) - Strip connected: prefix from wlan label initial display - Add _pending_toggle pattern to wifi notify handler to suppress stale-cache revert; same for bluetooth switch lock
This commit is contained in:
4 files changed
+49
-35
No files matched your search
@@ -1,6 +1,6 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
from fabric.utils import Gdk, GLib, Gtk, exec_shell_command, logger
|
||||
from fabric.utils import Gdk, GLib, Gtk, logger
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
@@ -46,17 +46,6 @@ def get_battery_theme_icon(percentage: float, charging: bool = False) -> str:
|
||||
|
||||
|
||||
def set_bluetooth_enabled_with_fallback(client, enabled: bool):
|
||||
if enabled:
|
||||
command = "rfkill unblock bluetooth"
|
||||
else:
|
||||
command = "rfkill block bluetooth"
|
||||
|
||||
result = exec_shell_command(command)
|
||||
if result is False:
|
||||
logger.error(f"rfkill fallback failed: command '{command}' returned False")
|
||||
elif isinstance(result, str) and result.strip():
|
||||
logger.warning(f"rfkill command output: {result.strip()}")
|
||||
|
||||
try:
|
||||
if hasattr(client, "set_enabled"):
|
||||
client.set_enabled(enabled)
|
||||
@@ -481,6 +470,7 @@ class BluetoothConnections(Box):
|
||||
def handle_user_toggle(self, active: bool):
|
||||
self._switch_lock = True
|
||||
set_bluetooth_enabled_with_fallback(self.client, active)
|
||||
self.toggle_button.set_active(active)
|
||||
GLib.timeout_add(1500, self._unlock_switch)
|
||||
|
||||
def _unlock_switch(self):
|
||||
|
||||
@@ -107,8 +107,14 @@ class ModusControlCenter(AppletWindow):
|
||||
wlan = modus_service.wlan if modus_service.wlan else "No Connection"
|
||||
bluetooth = modus_service.bluetooth if modus_service.bluetooth else "Off"
|
||||
|
||||
if wlan.startswith("connected:"):
|
||||
parts = wlan.split(":")
|
||||
wlan_display = parts[1] if len(parts) >= 2 else "Connected"
|
||||
else:
|
||||
wlan_display = wlan
|
||||
|
||||
self.wlan_label = Label(
|
||||
label=wlan,
|
||||
label=wlan_display,
|
||||
name="wifi-widget-label",
|
||||
max_chars_width=15,
|
||||
h_align="start",
|
||||
@@ -487,8 +493,10 @@ class ModusControlCenter(AppletWindow):
|
||||
],
|
||||
)
|
||||
|
||||
# Initialize managers directly like working version
|
||||
self.wifi_man = WifiConnections(self)
|
||||
# Initialize network service upfront so WifiConnections can use it
|
||||
self.network_service = NetworkClient()
|
||||
self.network_service.connect("device-ready", self.on_network_ready)
|
||||
self.wifi_man = WifiConnections(self, network_service=self.network_service)
|
||||
self.bluetooth_man = BluetoothConnections(self)
|
||||
|
||||
self.has_bluetooth_open = False
|
||||
@@ -565,10 +573,7 @@ class ModusControlCenter(AppletWindow):
|
||||
try:
|
||||
logger.debug("Initializing control center resources...")
|
||||
|
||||
# Initialize network service lazily
|
||||
if self.network_service is None:
|
||||
self.network_service = NetworkClient()
|
||||
# Connect network signal
|
||||
# Connect network signal (service created earlier in __init__)
|
||||
self.network_service.connect("device-ready", self.on_network_ready)
|
||||
|
||||
# Check initial states lazily (only when needed)
|
||||
@@ -896,21 +901,27 @@ class ModusControlCenter(AppletWindow):
|
||||
return
|
||||
try:
|
||||
if hasattr(self, "bluetooth_man") and hasattr(self.bluetooth_man, "client"):
|
||||
current_state = self.bluetooth_man.client.enabled
|
||||
set_bluetooth_enabled_with_fallback(
|
||||
self.bluetooth_man.client, not current_state
|
||||
client = self.bluetooth_man.client
|
||||
target = not client.enabled
|
||||
set_bluetooth_enabled_with_fallback(client, target)
|
||||
client.notify("enabled")
|
||||
self.bluetooth_svg.dynamic_file(
|
||||
"applets/bluetooth.svg" if target else "applets/bluetooth-off.svg"
|
||||
)
|
||||
self.bluetooth_label.set_label("On" if target else "Off")
|
||||
else:
|
||||
logger.warning("Bluetooth client not available for toggling")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to toggle bluetooth: {e}")
|
||||
|
||||
def on_network_ready(self, *_):
|
||||
if self._network_ready_fired:
|
||||
if self._network_ready_fired and self.wifi_service:
|
||||
return
|
||||
wifi = self.network_service.wifi_device
|
||||
if not wifi:
|
||||
return
|
||||
self._network_ready_fired = True
|
||||
self.wifi_service = self.network_service.wifi_device
|
||||
if self.wifi_service:
|
||||
self.wifi_service = wifi
|
||||
self._signal_connections.append(
|
||||
(
|
||||
self.wifi_service,
|
||||
@@ -935,8 +946,8 @@ class ModusControlCenter(AppletWindow):
|
||||
return
|
||||
try:
|
||||
if self.wifi_service:
|
||||
self.wifi_service.toggle_wifi()
|
||||
GLib.timeout_add(100, self.update_wifi_icon)
|
||||
target = not self.wifi_service.enabled
|
||||
self.wifi_service.enabled = target
|
||||
else:
|
||||
logger.warning("WiFi device not available for toggling")
|
||||
except Exception as e:
|
||||
|
||||
@@ -33,8 +33,9 @@ def create_night_light_widget(control_center):
|
||||
)
|
||||
|
||||
def toggle_ui(*_):
|
||||
nonlocal is_active
|
||||
if toggle_night_light():
|
||||
is_active = is_night_light_active()
|
||||
is_active = not is_active
|
||||
night_light_icon.dynamic_file(
|
||||
"applets/redshift-status-on.svg"
|
||||
if is_active
|
||||
|
||||
@@ -260,7 +260,7 @@ class WifiNetworkSlot(Box):
|
||||
|
||||
|
||||
class WifiConnections(Box):
|
||||
def __init__(self, parent, show_back_button=True, **kwargs):
|
||||
def __init__(self, parent, show_back_button=True, network_service=None, **kwargs):
|
||||
super().__init__(
|
||||
spacing=8,
|
||||
orientation="vertical",
|
||||
@@ -269,14 +269,18 @@ class WifiConnections(Box):
|
||||
)
|
||||
|
||||
self.parent = parent
|
||||
self.network_service = NetworkClient()
|
||||
self.network_service = network_service or NetworkClient()
|
||||
self.wifi_service = None
|
||||
self.is_scanning = False # Track scanning state
|
||||
self._update_in_progress = False # Prevent concurrent updates
|
||||
self._destroyed = False # Track if widget is destroyed
|
||||
self._signal_ids = [] # Track all service signal IDs for cleanup
|
||||
self._network_ready_fired = False # Guard against duplicate device-ready
|
||||
self._pending_toggle = None # Force toggle to target after stale revert
|
||||
|
||||
if network_service and network_service.wifi_device:
|
||||
self.on_network_ready()
|
||||
else:
|
||||
self._signal_ids.append(
|
||||
(
|
||||
self.network_service,
|
||||
@@ -438,11 +442,13 @@ class WifiConnections(Box):
|
||||
|
||||
def on_network_ready(self, *_):
|
||||
"""Called when network service is ready"""
|
||||
if self._network_ready_fired:
|
||||
if self._network_ready_fired and self.wifi_service:
|
||||
return
|
||||
wifi = self.network_service.wifi_device
|
||||
if not wifi:
|
||||
return
|
||||
self._network_ready_fired = True
|
||||
self.wifi_service = self.network_service.wifi_device
|
||||
if self.wifi_service:
|
||||
self.wifi_service = wifi
|
||||
self.toggle_button.set_active(self.wifi_service.enabled)
|
||||
|
||||
self._signal_ids.append(
|
||||
@@ -466,12 +472,18 @@ class WifiConnections(Box):
|
||||
def on_toggle_changed(self, toggle_button, *_):
|
||||
"""Handle WiFi toggle button changes"""
|
||||
if self.wifi_service:
|
||||
new_state = toggle_button.get_active()
|
||||
self.wifi_service.enabled = new_state
|
||||
target = toggle_button.get_active()
|
||||
self._pending_toggle = target
|
||||
self.wifi_service.enabled = target
|
||||
self.toggle_button.set_active(target)
|
||||
|
||||
def on_wifi_enabled_changed(self, *_):
|
||||
"""Handle WiFi enabled state changes"""
|
||||
if self.wifi_service:
|
||||
if self._pending_toggle is not None:
|
||||
self.toggle_button.set_active(self._pending_toggle)
|
||||
self._pending_toggle = None
|
||||
else:
|
||||
self.toggle_button.set_active(self.wifi_service.enabled)
|
||||
|
||||
def open_network_settings(self, *_):
|
||||
|
||||
Reference in new issue
Block a user