From dbfce93c52a571fc223228c65c3f127e2cafb97a Mon Sep 17 00:00:00 2001 From: deanlee Date: Sun, 16 Feb 2025 21:21:17 +0800 Subject: [PATCH] wifi manager --- system/ui/lib/keyboard.py | 103 ++++++++++++++ system/ui/lib/label.py | 10 +- system/ui/lib/scroll_panel.py | 18 ++- system/ui/lib/wifi_manager.py | 258 ++++++++++++++++++++++++++++++++++ system/ui/network.py | 146 +++++++++++++++++++ system/ui/text.py | 4 +- 6 files changed, 524 insertions(+), 15 deletions(-) create mode 100644 system/ui/lib/keyboard.py create mode 100644 system/ui/lib/wifi_manager.py create mode 100644 system/ui/network.py diff --git a/system/ui/lib/keyboard.py b/system/ui/lib/keyboard.py new file mode 100644 index 000000000000000..12228e6a32a4d43 --- /dev/null +++ b/system/ui/lib/keyboard.py @@ -0,0 +1,103 @@ +import pyray as rl +from openpilot.system.ui.lib.button import gui_button +from openpilot.system.ui.lib.label import gui_label + +# Constants for special keys +BACKSPACE_KEY = "<-" +ENTER_KEY = "Enter" +SPACE_KEY = " " +SHIFT_KEY = "↑" +SHIFT_DOWN_KEY = "↓" +NUMERIC_KEY = "123" +SYMBOL_KEY = "#+=" +ABC_KEY = "ABC" + +# Define keyboard layouts as a dictionary for easier access +keyboard_layouts = { + "lowercase": [ + ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], + ["a", "s", "d", "f", "g", "h", "j", "k", "l"], + [SHIFT_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY], + [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], + ], + "uppercase": [ + ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], + ["A", "S", "D", "F", "G", "H", "J", "K", "L"], + [SHIFT_DOWN_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY], + [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], + ], + "numbers": [ + ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + ["-", "/", ":", ";", "(", ")", "$", "&", "@", "\""], + [SYMBOL_KEY, ".", ",", "?", "!", "`", BACKSPACE_KEY], + [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], + ], + "specials": [ + ["[", "]", "{", "}", "#", "%", "^", "*", "+", "="], + ["_", "\\", "|", "~", "<", ">", "€", "£", "¥", "•"], + [NUMERIC_KEY, ".", ",", "?", "!", "'", BACKSPACE_KEY], + [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], + ], +} + + +class Keyboard: + def __init__(self, max_text_size: int = 255): + self._layout = keyboard_layouts["lowercase"] + self._input_text = "" + self._max_text_size = max_text_size + + @property + def text(self) -> str: + return self._input_text + + def clear(self): + self._input_text = "" + + def render(self, rect, title, sub_title): + gui_label((rect.x, rect.y, rect.width, 95), title, 90) + gui_label((rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) + gui_button((rect.x + rect.width - 300, rect.y, 300, 100), "Cancel") + + # Text box for input + rl.gui_text_box(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._input_text, self._max_text_size, True) + + h_space, v_space = 15, 15 + row_y_start = rect.y + 300 # Starting Y position for the first row + key_height = (rect.height - 300 - 3 * v_space) / 4 + key_max_width = (rect.width - (len(self._layout[2]) - 1) * h_space) / len(self._layout[2]) + + # Iterate over the rows of keys in the current layout + for row, keys in enumerate(self._layout): + key_width = min((rect.width - (180 if row == 1 else 0) - h_space * (len(keys) - 1)) / len(keys), key_max_width) + start_x = rect.x + (90 if row == 1 else 0) + + for i, key in enumerate(keys): + if i > 0: + start_x += h_space + + new_width = (key_width * 3 + h_space * 2) if key == SPACE_KEY else (key_width * 2 + h_space if key == ENTER_KEY else key_width) + key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height) + start_x += new_width + + if rl.gui_button(key_rect, key): + if key == ENTER_KEY: + return 1 + else: + self.handle_key_press(key) + + return 0 + + def handle_key_press(self, key): + if key in (SHIFT_DOWN_KEY, ABC_KEY): + self._layout = keyboard_layouts["lowercase"] + elif key == SHIFT_KEY: + self._layout = keyboard_layouts["uppercase"] + elif key == NUMERIC_KEY: + self._layout = keyboard_layouts["numbers"] + elif key == SYMBOL_KEY: + self._layout = keyboard_layouts["specials"] + elif key == BACKSPACE_KEY and len(self._input_text) > 0: + self._input_text = self._input_text[:-1] + elif key != BACKSPACE_KEY and len(self._input_text) < self._max_text_size: + self._input_text += key diff --git a/system/ui/lib/label.py b/system/ui/lib/label.py index 37b66582f9df795..5ca3627df161f1e 100644 --- a/system/ui/lib/label.py +++ b/system/ui/lib/label.py @@ -1,13 +1,17 @@ import pyray as rl from openpilot.system.ui.lib.utils import GuiStyleContext -def gui_label(rect, text, font_size): + +def gui_label(rect, text, font_size, color: rl.Color = None, alignment: rl.GuiTextAlignment = rl.GuiTextAlignment.TEXT_ALIGN_LEFT): styles = [ (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, font_size), (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_LINE_SPACING, font_size), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP), - (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD) + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_ALIGNMENT_VERTICAL, rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE), + (rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_WRAP_MODE, rl.GuiTextWrapMode.TEXT_WRAP_WORD), + (rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_ALIGNMENT, alignment) ] + if color: + styles.append((rl.GuiControl.LABEL, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(color))) with GuiStyleContext(styles): rl.gui_label(rect, text) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index b8fa211ec170003..d04f67fe6d11c4e 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -3,19 +3,17 @@ MOUSE_WHEEL_SCROLL_SPEED = 30 + class GuiScrollPanel: - def __init__(self, bounds: rl.Rectangle, content: rl.Rectangle, show_vertical_scroll_bar: bool = False): + def __init__(self): self._dragging: bool = False self._last_mouse_y: float = 0.0 - self._bounds = bounds - self._content = content - self._scroll = rl.Vector2(0, 0) + self._scroll: rl.Vector2 = rl.Vector2(0, 0) self._view = rl.Rectangle(0, 0, 0, 0) - self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar - def handle_scroll(self)-> rl.Vector2: + def handle_scroll(self, bounds: rl.Rectangle, content: rl.Rectangle, show_vertical_scroll_bar: bool = False) -> rl.Vector2: mouse_pos = rl.get_mouse_position() - if rl.check_collision_point_rec(mouse_pos, self._bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + if rl.check_collision_point_rec(mouse_pos, bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): if not self._dragging: self._dragging = True self._last_mouse_y = mouse_pos.y @@ -29,12 +27,12 @@ def handle_scroll(self)-> rl.Vector2: self._dragging = False wheel_move = rl.get_mouse_wheel_move() - if self._show_vertical_scroll_bar: + if show_vertical_scroll_bar: self._scroll.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20) - rl.gui_scroll_panel(self._bounds, FFI().NULL, self._content, self._scroll, self._view) + rl.gui_scroll_panel(bounds, FFI().NULL, content, self._scroll, self._view) else: self._scroll.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED - max_scroll_y = self._content.height - self._bounds.height + max_scroll_y = max(content.height - bounds.height, 0) self._scroll.y = max(min(self._scroll.y, 0), -max_scroll_y) return self._scroll diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py new file mode 100644 index 000000000000000..8a04b71189467f6 --- /dev/null +++ b/system/ui/lib/wifi_manager.py @@ -0,0 +1,258 @@ +import asyncio +from dbus_next.aio import MessageBus +from dbus_next import BusType, Variant, Message +from dbus_next.errors import DBusError +from dbus_next.constants import MessageType +from enum import IntEnum +import uuid +from dataclasses import dataclass + +NM = "org.freedesktop.NetworkManager" +NM_DBUS_PATH = '/org/freedesktop/NetworkManager' +NM_DBUS_INTERFACE = 'org.freedesktop.NetworkManager' +NM_DBUS_PATH_SETTINGS = '/org/freedesktop/NetworkManager/Settings' +NM_DBUS_INTERFACE_SETTINGS = 'org.freedesktop.NetworkManager.Settings' +NM_DBUS_INTERFACE_SETTINGS_CONNECTION = 'org.freedesktop.NetworkManager.Settings.Connection' +NM_DBUS_INTERFACE_DEVICE_WIRELESS = 'org.freedesktop.NetworkManager.Device.Wireless' +NM_DBUS_INTERFACE_PROPERTIES = 'org.freedesktop.DBus.Properties' + +NM_DEVICE_STATE_NEED_AUTH = 60 + + +class SecurityType(IntEnum): + OPEN = 0 + WPA = 1 + WPA2 = 2 + UNSUPPORTED = 3 + + +@dataclass +class NetworkInfo: + ssid: str + strength: int + is_connected: bool + security_type: SecurityType + path: str + is_saved: bool + + +class WifiManager: + def __init__(self): + self.networks = [] + self.connected_network = None + self.bus = None + self.device_path = None + self.device_proxy = None + self.saved_connections = dict() + self.active_ap_path = '' + + async def connect(self): + """Connect to the DBus system bus.""" + try: + self.bus = await MessageBus(bus_type=BusType.SYSTEM).connect() + nm_interface = await self.get_interface(NM, '/org/freedesktop/NetworkManager', NM) + + # Get the list of available devices (WiFi devices) + devices = await nm_interface.get_devices() + + for device_path in devices: + # Introspect each device and check if it's a WiFi device (DeviceType == 2) + device = await self.bus.introspect(NM, device_path) + device_proxy = self.bus.get_proxy_object(NM, device_path, device) + device_interface = device_proxy.get_interface('org.freedesktop.NetworkManager.Device') + device_type = await device_interface.get_device_type() + + if device_type == 2: # WiFi device + self.device_proxy = device_proxy + self.device_path = device_path + rule = f"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='{device_path}'" + # await self.bus.call_add_match(rule) + await self._dbus_add_match(rule) + rule = f"type='signal',interface='org.freedesktop.NetworkManager.Device',member='StateChanged',path='{device_path}'" + await self._dbus_add_match(rule) + rule = "type='signal',interface='org.freedesktop.NetworkManager.Settings',member='NewConnection',path='/org/freedesktop/NetworkManager/Settings'" + await self._dbus_add_match(rule) + break + + self.active_ap_path = await self.get_active_access_point() + self.saved_connections = await self.get_saved_ssids() + + except DBusError as e: + print(f"Failed to connect to DBus: {e}") + + async def request_scan(self): + interface = self.device_proxy.get_interface(NM_DBUS_INTERFACE_DEVICE_WIRELESS) + await interface.call_request_scan({}) + + async def get_active_access_point(self): + properties_interface = self.device_proxy.get_interface(NM_DBUS_INTERFACE_PROPERTIES) + response = await properties_interface.call_get(NM_DBUS_INTERFACE_DEVICE_WIRELESS, 'ActiveAccessPoint') + return response.value + + async def _dbus_add_match(self, body): + """ "Add a match rule on the bus.""" + reply = await self.bus.call( + Message( + message_type=MessageType.METHOD_CALL, + destination='org.freedesktop.DBus', + interface="org.freedesktop.DBus", + path='/org/freedesktop/DBus', + member='AddMatch', + signature='s', + body=[body], + ) + ) + + assert reply.message_type == MessageType.METHOD_RETURN + return reply + + async def get_available_networks(self): + """Get a list of available networks via NetworkManager.""" + networks = [] + try: + wifi_interface = self.device_proxy.get_interface(NM_DBUS_INTERFACE_DEVICE_WIRELESS) + access_points = await wifi_interface.get_access_points() + + for ap_path in access_points: + ap = await self.bus.introspect(NM, ap_path) + ap_proxy = self.bus.get_proxy_object(NM, ap_path, ap) + properties_interface = ap_proxy.get_interface(NM_DBUS_INTERFACE_PROPERTIES) + properties = await properties_interface.call_get_all('org.freedesktop.NetworkManager.AccessPoint') + + ssid_variant = properties['Ssid'].value + ssid = ''.join([chr(byte) for byte in ssid_variant]) + if not ssid: + continue + + flags = properties['Flags'].value + wpa_flags = properties['WpaFlags'].value + rsn_flags = properties['RsnFlags'].value + + networks.append( + NetworkInfo( + ssid=ssid, + strength=properties['Strength'].value, + security_type=self._get_security_type(flags, wpa_flags, rsn_flags), + path=ap_path, + is_connected=self.active_ap_path == ap_path, + is_saved=ssid in self.saved_connections, + ) + ) + + except DBusError as e: + print(f"Error fetching networks: {e}") + except Exception as e: + print({e}) + finally: + self.networks = sorted( + networks, + key=lambda network: ( + not network.is_connected, + -network.strength, # Higher signal strength first + network.ssid.lower(), + ), + ) + + async def get_connection_settings(self, path): + """Fetch connection settings for a specific connection path.""" + connection_proxy = await self.bus.introspect(NM, path) + connection = self.bus.get_proxy_object(NM, path, connection_proxy) + settings = connection.get_interface(NM_DBUS_INTERFACE_SETTINGS_CONNECTION) + return await settings.call_get_settings() + + async def process_chunk(self, paths_chunk): + """Process a chunk of connection paths.""" + tasks = [self.get_connection_settings(path) for path in paths_chunk] + results = await asyncio.gather(*tasks) + return results + + async def get_saved_ssids(self): + # Get the NetworkManager object + introspection = await self.bus.introspect(NM, NM_DBUS_PATH_SETTINGS) + network_manager = self.bus.get_proxy_object(NM, NM_DBUS_PATH_SETTINGS, introspection) + settings_interface = network_manager.get_interface(NM_DBUS_INTERFACE_SETTINGS) + connection_paths = await settings_interface.call_list_connections() + + saved_ssids = dict() + batch_size = 120 + for i in range(0, len(connection_paths), batch_size): + chunk = connection_paths[i : i + batch_size] + results = await self.process_chunk(chunk) + + # Loop through the results and filter Wi-Fi connections + for path, config in zip(chunk, results, strict=True): + if '802-11-wireless' in config: + ssid_variant = config['802-11-wireless']['ssid'] + ssid = ''.join(chr(b) for b in ssid_variant.value) + saved_ssids[ssid] = path + + return saved_ssids + + async def get_interface(self, bus_name: str, path: str, name: str): + introspection = await self.bus.introspect(bus_name, path) + proxy_object = self.bus.get_proxy_object(bus_name, path, introspection) + return proxy_object.get_interface(name) + + def _get_security_type(self, flags, wpa_flags, rsn_flags): + """Helper function to determine the security type of a network.""" + if flags == 0: + return SecurityType.OPEN + elif wpa_flags != 0: + return SecurityType.WPA + elif rsn_flags != 0: + return SecurityType.WPA2 + else: + return SecurityType.UNSUPPORTED + + async def activate_connection(self, ssid: str): + connection_path = self.saved_connections.get(ssid) + if connection_path: + print('activate connection:', connection_path) + introspection = await self.bus.introspect(NM, NM_DBUS_PATH) + proxy = self.bus.get_proxy_object(NM, NM_DBUS_PATH, introspection) + interface = proxy.get_interface(NM_DBUS_INTERFACE) + + await interface.call_activate_connection(connection_path, self.device_path, '/') + + async def connect_to_network(self, ssid: str, password: str = None, is_hidden: bool = False): + """Connect to a selected WiFi network.""" + try: + print('connect_to_network', ssid, password) + network_manager = await self.bus.introspect(NM, '/org/freedesktop/NetworkManager/Settings') + proxy_object = self.bus.get_proxy_object(NM, '/org/freedesktop/NetworkManager/Settings', network_manager) + settings_interface = proxy_object.get_interface('org.freedesktop.NetworkManager.Settings') + + # Create a connection dictionary + connection = { + 'connection': { + 'type': Variant('s', '802-11-wireless'), + 'uuid': Variant('s', str(uuid.uuid4())), + 'id': Variant('s', ssid), + 'autoconnect-retries': Variant('i', 0), + }, + '802-11-wireless': {'ssid': Variant('ay', ssid.encode('utf-8')), 'hidden': Variant('b', is_hidden), 'mode': Variant('s', 'infrastructure')}, + 'ipv4': {'method': Variant('s', 'auto')}, + 'ipv6': {'method': Variant('s', 'ignore')}, + } + + if password: + connection['802-11-wireless-security'] = { + 'key-mgmt': Variant('s', 'wpa-psk'), + 'auth-alg': Variant('s', 'open'), + 'psk': Variant('s', password), + } + + await settings_interface.call_add_connection(connection) + + self.connected_network = ssid + + except DBusError as e: + print(f"Error connecting to network: {e}") + + async def forgot_connection(self, ssid: str): + path = self.saved_connections.get(ssid) + if path: + connection_proxy = await self.bus.introspect(NM, path) + connection = self.bus.get_proxy_object(NM, path, connection_proxy) + settings = connection.get_interface(NM_DBUS_INTERFACE_SETTINGS_CONNECTION) + await settings.call_delete() diff --git a/system/ui/network.py b/system/ui/network.py new file mode 100644 index 000000000000000..75e6181eb9d2890 --- /dev/null +++ b/system/ui/network.py @@ -0,0 +1,146 @@ +import asyncio +import pyray as rl +from enum import IntEnum +from dbus_next.constants import MessageType +from openpilot.system.ui.lib.wifi_manager import WifiManager, NetworkInfo +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.label import gui_label +from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +from openpilot.system.ui.lib.keyboard import Keyboard + +NM_DEVICE_STATE_NEED_AUTH = 60 + + +class ActionState(IntEnum): + NONE = 0 + CONNECT = 1 + CONNECTING = 2 + FORGOT = 3 + FORGETTING = 4 + NEED_AUTH = 5 + + +class WifiManagerUI: + def __init__(self, wifi_manager): + self._wifi_manager = wifi_manager + self._selected_network = None + self.item_height = 160 + self.btn_width = 200 + self.current_action: ActionState = ActionState.NONE + self._saved_networks: set[NetworkInfo] = {} + self.scroll_panel = GuiScrollPanel() + self.Keyboard = Keyboard() + + asyncio.create_task(self.periodic_network_fetch()) + + async def periodic_network_fetch(self): + await self._wifi_manager.connect() + self._wifi_manager.bus.add_message_handler(self.handle_signal) + await self._wifi_manager.get_available_networks() + while True: + await self._wifi_manager.request_scan() + await asyncio.sleep(60) # Wait for 1 minute before refetching + + async def draw_network_list(self, rect: rl.Rectangle): + if not self._wifi_manager.networks: + gui_label(rect, "Loading Wi-Fi networks...", 40, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + return + + if self.current_action == ActionState.NEED_AUTH: + result = self.Keyboard.render(rect, 'Enter password', f'for {self._selected_network.ssid}') + if result == 0: + return + else: + self.current_action = ActionState.NONE + asyncio.create_task(self.connect_to_network(self.Keyboard.text)) + + content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._wifi_manager.networks) * self.item_height) + offset = self.scroll_panel.handle_scroll(rect, content_rect) + rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) + clicked = offset.y < 10 and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) + for i, network in enumerate(self._wifi_manager.networks): + y_offset = i * self.item_height + offset.y + item_rect = rl.Rectangle(rect.x, y_offset, rect.width, self.item_height) + + if rl.check_collision_recs(item_rect, rect): + self.render_network_item(item_rect, network, clicked) + if i != len(self._wifi_manager.networks) - 1: + line_y = int(item_rect.y + item_rect.height - 1) + rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY) + + rl.end_scissor_mode() + + def render_network_item(self, rect, network: NetworkInfo, clicked: bool): + # Calculate the rectangles for the label and state button + label_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, self.item_height) + state_rect = rl.Rectangle(rect.x + rect.width - self.btn_width * 2 - 30, rect.y, self.btn_width, self.item_height) + + # Render the SSID as a label + gui_label(label_rect, network.ssid, 55) + + # Render the connection state + if network.is_connected and self.current_action == ActionState.NONE: + rl.gui_label(state_rect, "Connected") + elif self.current_action == "Connecting" and self._selected_network and self._selected_network.ssid == network.ssid: + rl.gui_label(state_rect, "CONNECTING...") + + # If the network is saved, show the "Forget" button + if network.is_saved: + forget_btn_rect = rl.Rectangle(rect.x + rect.width - self.btn_width, rect.y + (self.item_height - 80) / 2, self.btn_width, 80) + if rl.gui_button(forget_btn_rect, "Forget") and self.current_action == ActionState.NONE: + self._selected_network = network + asyncio.create_task(self.forgot_network()) + + if self.current_action == ActionState.NONE and rl.check_collision_point_rec(rl.get_mouse_position(), label_rect) and clicked: + self._selected_network = network + asyncio.create_task(self.connect_to_network()) + + async def forgot_network(self): + self.current_action = ActionState.FORGETTING + await self._wifi_manager.forgot_connection(self._selected_network.ssid) + self._selected_network.is_saved = False + self.current_action = ActionState.NONE + + async def connect_to_network(self, password=''): + self.current_action = ActionState.CONNECTING + if self._selected_network.is_saved and not password: + await self._wifi_manager.activate_connection(self._selected_network.ssid) + else: + await self._wifi_manager.connect_to_network(self._selected_network.ssid, password) + self.current_action = ActionState.NONE + + def handle_signal(self, message): + if message.message_type != MessageType.SIGNAL: + return + + if message.member == 'StateChanged': + if len(message.body) >= 2: + _, new_state = message.body[0], message.body[1] + if new_state == NM_DEVICE_STATE_NEED_AUTH: + self.current_action = ActionState.NEED_AUTH + elif message.interface == 'org.freedesktop.DBus.Properties' and message.member == 'PropertiesChanged': + body = message.body + if len(body) >= 2: + changed_properties = body[1] + if 'LastScan' in changed_properties: + asyncio.create_task(self._wifi_manager.get_available_networks()) + + +async def main(): + gui_app.init_window("Wifi Manager") + + wifi_manager = WifiManager() + wifi_ui = WifiManagerUI(wifi_manager) + + while not rl.window_should_close(): + rl.begin_drawing() + rl.clear_background(rl.BLACK) + + await wifi_ui.draw_network_list(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100)) + + rl.end_drawing() + await asyncio.sleep(0) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/system/ui/text.py b/system/ui/text.py index 82c19e5b5dd566d..cad1630c699a18f 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -42,13 +42,13 @@ def main(): textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2 - BUTTON_SIZE.y - SPACING) wrapped_lines = wrap_text(text_content, FONT_SIZE, textarea_rect.width - 20) content_rect = rl.Rectangle(0, 0, textarea_rect.width - 20, len(wrapped_lines) * LINE_HEIGHT) - scroll_panel = GuiScrollPanel(textarea_rect, content_rect, show_vertical_scroll_bar=True) + scroll_panel = GuiScrollPanel() while not rl.window_should_close(): rl.begin_drawing() rl.clear_background(rl.BLACK) - scroll = scroll_panel.handle_scroll() + scroll = scroll_panel.handle_scroll(textarea_rect, content_rect, show_vertical_scroll_bar=True) rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height)) for i, line in enumerate(wrapped_lines):