IQ.Pilot Release Commit @ 0babf78
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
|
||||
"""
|
||||
from openpilot.system.ui.iqpilot.widgets.list_view import IQButtonAction
|
||||
from openpilot.system.ui.iqwidgets.widgets.list_view import IQButtonAction
|
||||
|
||||
|
||||
class NoElideButtonAction(IQButtonAction):
|
||||
class WideButtonAction(IQButtonAction):
|
||||
def get_width_hint(self):
|
||||
return super().get_width_hint() + 1
|
||||
@@ -7,9 +7,9 @@ import time
|
||||
from collections.abc import Callable
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.iqpilot.lib.styles import ink, metrics
|
||||
from openpilot.system.ui.iqwidgets.lib.styles import ink, metrics
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.iqpilot.lib import canvas
|
||||
from openpilot.system.ui.iqwidgets.lib import canvas
|
||||
from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets.toggle import Toggle
|
||||
@@ -899,8 +899,8 @@ def progress_item(title):
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.iqpilot.lib.styles import ink
|
||||
from openpilot.system.ui.iqpilot.widgets.helpers.glyphs import draw_star
|
||||
from openpilot.system.ui.iqwidgets.lib.styles import ink
|
||||
from openpilot.system.ui.iqwidgets.widgets.helpers.glyphs import draw_star
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
@@ -925,13 +925,13 @@ _FRAME_PAD = 50
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeNode:
|
||||
class PickerItem:
|
||||
ref: str
|
||||
data: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeFolder:
|
||||
class PickerGroup:
|
||||
folder: str
|
||||
nodes: list
|
||||
|
||||
@@ -999,7 +999,7 @@ class _TreeRow(Button):
|
||||
return super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
|
||||
class TreeOptionDialog(MultiOptionDialog):
|
||||
class PickerDialog(MultiOptionDialog):
|
||||
"""Folder/leaf picker with search, favourites, and a pinned current selection."""
|
||||
|
||||
def __init__(self, title, folders, current_ref="", fav_param="", option_font_weight=FontWeight.MEDIUM, search_prompt=None,
|
||||
@@ -1018,7 +1018,7 @@ class TreeOptionDialog(MultiOptionDialog):
|
||||
self.on_exit = on_exit
|
||||
self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref))
|
||||
self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')]
|
||||
self.search_title = search_title or tr("Enter search query")
|
||||
self.search_title = search_title or tr("Type to search")
|
||||
self.search_subtitle = search_subtitle
|
||||
self._search_rect: rl.Rectangle | None = None
|
||||
self._search_pressed = False
|
||||
@@ -26,7 +26,7 @@ from openpilot.system.hardware import HARDWARE, PC
|
||||
from openpilot.system.ui.lib.multilang import multilang
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
|
||||
from openpilot.system.ui.iqpilot.lib.application import IQAppHooks
|
||||
from openpilot.system.ui.iqwidgets.lib.application import IQAppHooks
|
||||
from openpilot.system.ui.lib.screen_recorder import ScreenRecorder
|
||||
|
||||
_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20, 'tici': 20}.get(HARDWARE.get_device_type(), 60)))
|
||||
|
||||
@@ -33,10 +33,19 @@ EMOJI_REGEX = re.compile(
|
||||
flags=re.UNICODE
|
||||
)
|
||||
|
||||
_emoji_font_loaded = False
|
||||
|
||||
def _load_emoji_font() -> ImageFont.FreeTypeFont | None:
|
||||
global _emoji_font
|
||||
if _emoji_font is None:
|
||||
_emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109)
|
||||
global _emoji_font, _emoji_font_loaded
|
||||
if not _emoji_font_loaded:
|
||||
_emoji_font_loaded = True
|
||||
try:
|
||||
# FONT_DIR is an importlib.resources path. Inside the setup zipapp it points into the archive,
|
||||
# so str() yields a path through the .zip that PIL can't open ("cannot open resource"). Read
|
||||
# the bytes and hand PIL a file object so it works both on disk and inside the zipapp.
|
||||
_emoji_font = ImageFont.truetype(io.BytesIO(FONT_DIR.joinpath("NotoColorEmoji.ttf").read_bytes()), 109)
|
||||
except Exception:
|
||||
_emoji_font = None # never crash the whole UI over an emoji glyph
|
||||
return _emoji_font
|
||||
|
||||
def find_emoji(text):
|
||||
@@ -44,12 +53,15 @@ def find_emoji(text):
|
||||
|
||||
def emoji_tex(emoji):
|
||||
if emoji not in _cache:
|
||||
font = _load_emoji_font()
|
||||
if font is None:
|
||||
return None
|
||||
img = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.text((0, 0), emoji, font=_load_emoji_font(), embedded_color=True)
|
||||
draw.text((0, 0), emoji, font=font, embedded_color=True)
|
||||
with io.BytesIO() as buffer:
|
||||
img.save(buffer, format="PNG")
|
||||
l = buffer.tell()
|
||||
buffer.seek(0)
|
||||
_cache[emoji] = rl.load_texture_from_image(rl.load_image_from_memory(".png", buffer.getvalue(), l))
|
||||
return _cache[emoji]
|
||||
return _cache.get(emoji)
|
||||
|
||||
@@ -67,6 +67,9 @@ class MeteredType(IntEnum):
|
||||
NO = 2
|
||||
|
||||
|
||||
_WARNED_UNSUPPORTED_NETWORKS: set[tuple[int, int, int]] = set()
|
||||
|
||||
|
||||
def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType:
|
||||
wpa_props = wpa_flags | rsn_flags
|
||||
|
||||
@@ -83,7 +86,10 @@ def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityTyp
|
||||
# WPA2, WPA2+WPA3 mixed, or WPA — all handled via WPA key_mgmt (NM negotiates SAE if available)
|
||||
return SecurityType.WPA2
|
||||
else:
|
||||
cloudlog.warning(f"Unsupported network! flags: {flags}, wpa_flags: {wpa_flags}, rsn_flags: {rsn_flags}")
|
||||
_key = (flags, wpa_flags, rsn_flags)
|
||||
if _key not in _WARNED_UNSUPPORTED_NETWORKS:
|
||||
_WARNED_UNSUPPORTED_NETWORKS.add(_key)
|
||||
cloudlog.warning(f"Unsupported network! flags: {flags}, wpa_flags: {wpa_flags}, rsn_flags: {rsn_flags}")
|
||||
return SecurityType.UNSUPPORTED
|
||||
|
||||
|
||||
@@ -630,6 +636,7 @@ class WifiManager:
|
||||
cloudlog.warning("No WiFi device found")
|
||||
return
|
||||
|
||||
self._set_device_autoconnect(True)
|
||||
self._connecting_to_ssid = ssid
|
||||
self._router_main.send(new_method_call(self._nm, 'ActivateConnection', 'ooo',
|
||||
(conn_path, self._wifi_device, "/")))
|
||||
@@ -639,6 +646,37 @@ class WifiManager:
|
||||
else:
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def disconnect_connection(self, ssid: str, block: bool = False):
|
||||
def worker():
|
||||
if self._router_main is None:
|
||||
cloudlog.warning(f"WiFi not ready while disconnecting {ssid}")
|
||||
return
|
||||
|
||||
if ssid not in self._get_connections():
|
||||
return
|
||||
|
||||
# the profile stays saved and untouched; without clearing autoconnect on the device
|
||||
# NetworkManager re-associates within seconds
|
||||
self._set_device_autoconnect(False)
|
||||
self._connecting_to_ssid = ""
|
||||
self._deactivate_connection(ssid)
|
||||
self._update_networks()
|
||||
self._enqueue_callbacks(self._disconnected)
|
||||
|
||||
if block:
|
||||
worker()
|
||||
else:
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _set_device_autoconnect(self, enabled: bool) -> None:
|
||||
if self._router_main is None or self._wifi_device is None:
|
||||
return
|
||||
|
||||
dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE)
|
||||
reply = self._router_main.send_and_get_reply(Properties(dev_addr).set('Autoconnect', 'b', enabled))
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f'Failed to set device autoconnect={enabled}: {reply}')
|
||||
|
||||
def _deactivate_connection(self, ssid: str):
|
||||
target_conn_path = self._get_connections().get(ssid, None)
|
||||
if target_conn_path is None:
|
||||
|
||||
@@ -50,6 +50,8 @@ INSTALLER_URL_PATH = "/tmp/installer_url"
|
||||
# "<user>/<branch>" maps to a GitHub fork. IQ.OS uses the DRM "magic" compositor, not Wayland,
|
||||
# so comma's downloaded installers (installer.comma.ai) crash on launch; clone the fork directly.
|
||||
GITHUB_FORK_URL = "https://github.com/{user}/openpilot.git"
|
||||
# IQ.Pilot lives on the IQ Lvbs git server; github.com/IQLvbs is DMCA'd and dead.
|
||||
GIT_URL_OVERRIDES = {"IQLvbs": "https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot.git"}
|
||||
|
||||
CONTINUE = """#!/usr/bin/env bash
|
||||
|
||||
@@ -756,7 +758,7 @@ class Setup(Widget):
|
||||
pass
|
||||
|
||||
def _fork_install_thread(self, user: str, branch: str):
|
||||
git_url = GITHUB_FORK_URL.format(user=user)
|
||||
git_url = GIT_URL_OVERRIDES.get(user) or GITHUB_FORK_URL.format(user=user)
|
||||
label = f"{user}/{branch}"
|
||||
try:
|
||||
subprocess.run(["rm", "-rf", TMP_INSTALL_PATH], check=False)
|
||||
@@ -774,7 +776,8 @@ class Setup(Widget):
|
||||
subprocess.run(["git", "-C", TMP_INSTALL_PATH, "submodule", "update", "--init"], check=False)
|
||||
|
||||
run_cmd(["rm", "-f", VALID_CACHE_PATH])
|
||||
run_cmd(["rm", "-rf", INSTALL_PATH])
|
||||
# sudo: a prior *run* install can leave root-owned .pyc here that a comma-user rm can't delete.
|
||||
run_cmd(["sudo", "rm", "-rf", INSTALL_PATH])
|
||||
run_cmd(["mv", TMP_INSTALL_PATH, INSTALL_PATH])
|
||||
|
||||
self._ble_progress("installing", 90)
|
||||
@@ -882,6 +885,11 @@ class Setup(Widget):
|
||||
# AGNOS might try to execute the installer before this process exits.
|
||||
# Therefore, important to close the fd before renaming the installer.
|
||||
os.close(fd)
|
||||
# comma's ELF installer does `rm -rf /data/openpilot` as the comma user and asserts it
|
||||
# succeeds; a prior *run* install leaves root-owned __pycache__ .pyc it can't delete, so it
|
||||
# aborts before continue.sh and bounces back to setup. Clear the old tree first (sudo) so the
|
||||
# installer's rm succeeds.
|
||||
subprocess.run(["sudo", "rm", "-rf", INSTALL_PATH], check=False)
|
||||
os.rename(tmpfile, INSTALLER_DESTINATION_PATH)
|
||||
|
||||
with open(INSTALLER_URL_PATH, "w") as f:
|
||||
|
||||
@@ -51,6 +51,8 @@ INSTALLER_DESTINATION_PATH = "/tmp/installer"
|
||||
INSTALLER_URL_PATH = "/tmp/installer_url"
|
||||
|
||||
GITHUB_FORK_URL = "https://github.com/{user}/openpilot.git"
|
||||
# IQ.Pilot lives on the IQ Lvbs git server; github.com/IQLvbs is DMCA'd and dead.
|
||||
GIT_URL_OVERRIDES = {"IQLvbs": "https://git.konn3kt.com/IQ.Lvbs/IQ.Pilot.git"}
|
||||
|
||||
CONTINUE = """#!/usr/bin/env bash
|
||||
|
||||
@@ -589,7 +591,7 @@ class Setup(Widget):
|
||||
pass
|
||||
|
||||
def _fork_install_thread(self, user: str, branch: str):
|
||||
git_url = GITHUB_FORK_URL.format(user=user)
|
||||
git_url = GIT_URL_OVERRIDES.get(user) or GITHUB_FORK_URL.format(user=user)
|
||||
label = f"{user}/{branch}"
|
||||
fail_msg = "Ensure the entered URL is valid, and the device's internet connection is good."
|
||||
try:
|
||||
@@ -609,7 +611,8 @@ class Setup(Widget):
|
||||
subprocess.run(["git", "-C", TMP_INSTALL_PATH, "submodule", "update", "--init"], check=False)
|
||||
|
||||
run_cmd(["rm", "-f", VALID_CACHE_PATH])
|
||||
run_cmd(["rm", "-rf", INSTALL_PATH])
|
||||
# sudo: a prior *run* install can leave root-owned .pyc here that a comma-user rm can't delete.
|
||||
run_cmd(["sudo", "rm", "-rf", INSTALL_PATH])
|
||||
run_cmd(["mv", TMP_INSTALL_PATH, INSTALL_PATH])
|
||||
|
||||
self._ble_progress("installing", 90)
|
||||
@@ -718,6 +721,11 @@ class Setup(Widget):
|
||||
# AGNOS might try to execute the installer before this process exits.
|
||||
# Therefore, important to close the fd before renaming the installer.
|
||||
os.close(fd)
|
||||
# comma's ELF installer does `rm -rf /data/openpilot` as the comma user and asserts it
|
||||
# succeeds; a prior *run* install leaves root-owned __pycache__ .pyc it can't delete, so it
|
||||
# aborts before continue.sh and bounces back to setup. Clear the old tree first (sudo) so the
|
||||
# installer's rm succeeds.
|
||||
subprocess.run(["sudo", "rm", "-rf", INSTALL_PATH], check=False)
|
||||
os.rename(tmpfile, INSTALLER_DESTINATION_PATH)
|
||||
|
||||
with open(INSTALLER_URL_PATH, "w") as f:
|
||||
|
||||
@@ -406,7 +406,8 @@ class Label(Widget):
|
||||
line_pos.x += width_before.x
|
||||
|
||||
tex = emoji_tex(emoji)
|
||||
rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height * FONT_SCALE, self._text_color)
|
||||
if tex is not None:
|
||||
rl.draw_texture_ex(tex, line_pos, 0.0, self._font_size / tex.height * FONT_SCALE, self._text_color)
|
||||
line_pos.x += self._font_size * FONT_SCALE
|
||||
prev_index = end
|
||||
rl.draw_text_ex(self._font, text[prev_index:], line_pos, self._font_size, 0, self._text_color)
|
||||
@@ -849,8 +850,9 @@ class UnifiedLabel(Widget):
|
||||
|
||||
# Draw emoji
|
||||
tex = emoji_tex(emoji)
|
||||
emoji_scale = self._font_size / tex.height * FONT_SCALE
|
||||
rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color)
|
||||
if tex is not None:
|
||||
emoji_scale = self._font_size / tex.height * FONT_SCALE
|
||||
rl.draw_texture_ex(tex, line_pos, 0.0, emoji_scale, self._text_color)
|
||||
# Emoji width is font_size * FONT_SCALE (as per measure_text_cached)
|
||||
line_pos.x += self._font_size * FONT_SCALE
|
||||
prev_index = end
|
||||
|
||||
@@ -17,10 +17,10 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item
|
||||
|
||||
if gui_app.iqpilot_ui():
|
||||
from openpilot.system.ui.iqpilot.widgets.list_view import button_item
|
||||
from openpilot.system.ui.iqpilot.widgets.list_view import IQListItem as ListItem
|
||||
from openpilot.system.ui.iqpilot.widgets.list_view import IQToggleAction as ToggleAction
|
||||
from openpilot.system.ui.iqpilot.widgets.list_view import IQMultipleButtonAction as MultipleButtonAction
|
||||
from openpilot.system.ui.iqwidgets.widgets.list_view import button_item
|
||||
from openpilot.system.ui.iqwidgets.widgets.list_view import IQListItem as ListItem
|
||||
from openpilot.system.ui.iqwidgets.widgets.list_view import IQToggleAction as ToggleAction
|
||||
from openpilot.system.ui.iqwidgets.widgets.list_view import IQMultipleButtonAction as MultipleButtonAction
|
||||
|
||||
# These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI
|
||||
try:
|
||||
@@ -66,6 +66,7 @@ class UIState(IntEnum):
|
||||
NEEDS_AUTH = 2
|
||||
SHOW_FORGET_CONFIRM = 3
|
||||
FORGETTING = 4
|
||||
DISCONNECTING = 5
|
||||
|
||||
|
||||
class NavButton(Widget):
|
||||
@@ -328,6 +329,7 @@ class WifiManagerUI(Widget):
|
||||
self._state_network: Network | None = None # for CONNECTING / NEEDS_AUTH / SHOW_FORGET_CONFIRM / FORGETTING
|
||||
self._password_retry: bool = False # for NEEDS_AUTH
|
||||
self.btn_width: int = 200
|
||||
self.disconnect_btn_width: int = 300
|
||||
self.scroll_panel = GuiScrollPanel()
|
||||
self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True)
|
||||
self._load_icons()
|
||||
@@ -335,6 +337,7 @@ class WifiManagerUI(Widget):
|
||||
self._networks: list[Network] = []
|
||||
self._networks_buttons: dict[str, Button] = {}
|
||||
self._forget_networks_buttons: dict[str, Button] = {}
|
||||
self._disconnect_networks_buttons: dict[str, Button] = {}
|
||||
|
||||
self._wifi_manager.add_callbacks(need_auth=self._on_need_auth,
|
||||
activated=self._on_activated,
|
||||
@@ -417,7 +420,9 @@ class WifiManagerUI(Widget):
|
||||
|
||||
def _draw_network_item(self, rect, network: Network):
|
||||
spacing = 50
|
||||
ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT)
|
||||
show_disconnect = network.is_connected and self.state not in (UIState.CONNECTING, UIState.DISCONNECTING)
|
||||
reserved = self.btn_width * 2 + (self.disconnect_btn_width + spacing if show_disconnect else 0)
|
||||
ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - reserved, ITEM_HEIGHT)
|
||||
signal_icon_rect = rl.Rectangle(rect.x + rect.width - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
|
||||
security_icon_rect = rl.Rectangle(signal_icon_rect.x - spacing - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE)
|
||||
|
||||
@@ -434,6 +439,10 @@ class WifiManagerUI(Widget):
|
||||
if self._state_network.ssid == network.ssid:
|
||||
self._networks_buttons[network.ssid].set_enabled(False)
|
||||
status_text = tr("FORGETTING...")
|
||||
elif self.state == UIState.DISCONNECTING and self._state_network:
|
||||
if self._state_network.ssid == network.ssid:
|
||||
self._networks_buttons[network.ssid].set_enabled(False)
|
||||
status_text = tr("DISCONNECTING...")
|
||||
elif network.security_type == SecurityType.UNSUPPORTED:
|
||||
self._networks_buttons[network.ssid].set_enabled(False)
|
||||
else:
|
||||
@@ -455,6 +464,15 @@ class WifiManagerUI(Widget):
|
||||
)
|
||||
self._forget_networks_buttons[network.ssid].render(forget_btn_rect)
|
||||
|
||||
if show_disconnect:
|
||||
disconnect_btn_rect = rl.Rectangle(
|
||||
forget_btn_rect.x - self.btn_width - spacing,
|
||||
forget_btn_rect.y,
|
||||
self.btn_width,
|
||||
80,
|
||||
)
|
||||
self._disconnect_networks_buttons[network.ssid].render(disconnect_btn_rect)
|
||||
|
||||
self._draw_status_icon(security_icon_rect, network)
|
||||
self._draw_signal_strength_icon(signal_icon_rect, network)
|
||||
|
||||
@@ -470,6 +488,9 @@ class WifiManagerUI(Widget):
|
||||
self.state = UIState.SHOW_FORGET_CONFIRM
|
||||
self._state_network = network
|
||||
|
||||
def _disconnect_networks_buttons_callback(self, network):
|
||||
self.disconnect_network(network)
|
||||
|
||||
def _draw_status_icon(self, rect, network: Network):
|
||||
"""Draw the status icon based on network's connection state"""
|
||||
icon_file = None
|
||||
@@ -506,6 +527,11 @@ class WifiManagerUI(Widget):
|
||||
self._state_network = network
|
||||
self._wifi_manager.forget_connection(network.ssid)
|
||||
|
||||
def disconnect_network(self, network: Network):
|
||||
self.state = UIState.DISCONNECTING
|
||||
self._state_network = network
|
||||
self._wifi_manager.disconnect_connection(network.ssid)
|
||||
|
||||
def _on_network_updated(self, networks: list[Network]):
|
||||
self._networks = networks
|
||||
for n in self._networks:
|
||||
@@ -515,6 +541,9 @@ class WifiManagerUI(Widget):
|
||||
self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI,
|
||||
font_size=45)
|
||||
self._forget_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid())
|
||||
self._disconnect_networks_buttons[n.ssid] = Button(tr("Disconnect"), partial(self._disconnect_networks_buttons_callback, n),
|
||||
button_style=ButtonStyle.FORGET_WIFI, font_size=45)
|
||||
self._disconnect_networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid())
|
||||
|
||||
def _on_need_auth(self, ssid):
|
||||
network = next((n for n in self._networks if n.ssid == ssid), None)
|
||||
@@ -532,7 +561,7 @@ class WifiManagerUI(Widget):
|
||||
self.state = UIState.IDLE
|
||||
|
||||
def _on_disconnected(self):
|
||||
if self.state == UIState.CONNECTING:
|
||||
if self.state in (UIState.CONNECTING, UIState.DISCONNECTING):
|
||||
self.state = UIState.IDLE
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user