From 92c68a9ab3f662e34cd7398357d6a8d57fd5db9d Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 19:09:14 +0300 Subject: [PATCH 1/8] Add access point functionality to network panel - Introduced toggle for Access Point (AP) mode in the network panel. - Implemented methods to create and remove AP connections. - Updated IP address display logic to accommodate AP mode. - Enhanced error handling and logging for network operations. - Added UI elements for AP SSID and password configuration. --- ks_includes/config.py | 2 +- ks_includes/sdbus_nm.py | 151 ++++++++++++++++++++++++++++++++++++++-- panels/network.py | 134 ++++++++++++++++++++++++++++++++--- 3 files changed, 274 insertions(+), 13 deletions(-) diff --git a/ks_includes/config.py b/ks_includes/config.py index 7703ccefc..31deaf29a 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -167,7 +167,7 @@ def validate_config(self, config, string="", remove=False): strs = ( 'default_printer', 'language', 'print_sort_dir', 'theme', 'screen_blanking_printing', 'font_size', 'print_estimate_method', 'screen_blanking', "screen_on_devices", "screen_off_devices", 'print_view', - "lock_password" + "lock_password", 'ap_ssid', 'ap_password' ) numbers = ( 'job_complete_timeout', 'job_error_timeout', 'move_speed_xy', 'move_speed_z', diff --git a/ks_includes/sdbus_nm.py b/ks_includes/sdbus_nm.py index e0e148b4b..eb965119d 100644 --- a/ks_includes/sdbus_nm.py +++ b/ks_includes/sdbus_nm.py @@ -2,7 +2,9 @@ # TODO device selection/swtichability # Alfredo Monclus (alfrix) 2024 import logging +import re import subprocess +import socket from uuid import uuid4 import sdbus @@ -165,10 +167,15 @@ def is_known(self, ssid): def get_ip_address(self): active_connection_path = self.nm.primary_connection if not active_connection_path or active_connection_path == "/": - return "?" - active_connection = ActiveConnection(active_connection_path) - ip_info = IPv4Config(active_connection.ip4_config) - return ip_info.address_data[0]["address"][1] + # Try to get IP address directly from interface + return self.get_interface_ip_address() + try: + active_connection = ActiveConnection(active_connection_path) + ip_info = IPv4Config(active_connection.ip4_config) + return ip_info.address_data[0]["address"][1] + except Exception as e: + logging.debug(f"Failed to get IP from active connection: {e}") + return self.get_interface_ip_address() def get_networks(self): networks = [] @@ -395,3 +402,139 @@ def monitor_connection_status(self): def enable_monitoring(self, enable): self.monitor_connection = enable + + def get_interface_ip_address(self): + """Get IP address directly from network interface""" + try: + interface = self.get_primary_interface() + if not interface: + return "?" + # Try using socket + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + pass + # Fallback to ip command + result = subprocess.run( + ["ip", "-4", "addr", "show", interface], + capture_output=True, + text=True, + timeout=2 + ) + if result.returncode == 0: + match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', result.stdout) + if match: + return match.group(1) + except Exception as e: + logging.debug(f"Failed to get interface IP: {e}") + return "?" + + def is_access_point_mode(self): + """Check if wireless device is in AP mode""" + if not self.wlan_device: + return False + try: + # Check active connection type + active_connection_path = self.wlan_device.active_connection + if active_connection_path and active_connection_path != "/": + active_connection = ActiveConnection(active_connection_path) + connection_path = active_connection.connection + if connection_path and connection_path != "/": + connection_settings = NetworkConnectionSettings(connection_path) + settings = connection_settings.get_settings() + if "802-11-wireless" in settings: + mode = settings["802-11-wireless"].get("mode", [None, None])[1] + return mode == "ap" + except Exception as e: + logging.debug(f"Failed to check AP mode: {e}") + return False + + def get_access_point_connection_path(self): + """Get connection path for access point if exists""" + try: + saved_network_paths = NetworkManagerSettings().list_connections() + for netpath in saved_network_paths: + saved_con = NetworkConnectionSettings(netpath) + con_settings = saved_con.get_settings() + if (con_settings["connection"]["type"][1] == "802-11-wireless" and + con_settings.get("802-11-wireless", {}).get("mode", [None, None])[1] == "ap"): + return netpath + except Exception as e: + logging.debug(f"Failed to get AP connection path: {e}") + return None + + def create_access_point(self, ssid, password): + """Create and activate access point""" + try: + # Delete existing AP connection if exists + ap_path = self.get_access_point_connection_path() + if ap_path: + NetworkConnectionSettings(ap_path).delete() + + # Disconnect current connection + if self.wlan_device.active_connection and self.wlan_device.active_connection != "/": + self.wlan_device.disconnect() + + properties: NetworkManagerConnectionProperties = { + "connection": { + "id": ("s", "KlipperScreen-AP"), + "uuid": ("s", str(uuid4())), + "type": ("s", "802-11-wireless"), + "interface-name": ("s", self.wlan_device.interface), + "autoconnect": ("b", True), + }, + "802-11-wireless": { + "mode": ("s", "ap"), + "ssid": ("ay", ssid.encode("utf-8")), + "security": ("s", "802-11-wireless-security"), + }, + "802-11-wireless-security": { + "key-mgmt": ("s", "wpa-psk"), + "psk": ("s", password), + }, + "ipv4": { + "method": ("s", "shared"), + }, + "ipv6": { + "method": ("s", "ignore"), + }, + } + + connection_path = NetworkManagerSettings().add_connection(properties) + logging.info(f"Created AP connection: {connection_path}") + + # Activate the connection + self.popup(f"{ssid}\nStarting Access Point", 1) + self.nm.activate_connection(connection_path) + return {"status": "success"} + except exceptions.NmSettingsPermissionDeniedError: + logging.exception("Insufficient privileges") + return { + "error": "insufficient_privileges", + "message": "Insufficient privileges", + } + except Exception as e: + logging.exception("Couldn't create access point") + return {"error": "unknown", "message": "Couldn't create access point" + f"\n{e}"} + + def remove_access_point(self): + """Remove access point and return to normal mode""" + try: + # Disconnect current connection + if self.wlan_device.active_connection and self.wlan_device.active_connection != "/": + self.wlan_device.disconnect() + + # Delete AP connection + ap_path = self.get_access_point_connection_path() + if ap_path: + NetworkConnectionSettings(ap_path).delete() + logging.info("Removed AP connection") + return {"status": "success"} + except Exception as e: + logging.exception("Couldn't remove access point") + return {"error": "unknown", "message": "Couldn't remove access point" + f"\n{e}"} + return {"status": "success"} diff --git a/panels/network.py b/panels/network.py index e57190d94..72c4472f3 100644 --- a/panels/network.py +++ b/panels/network.py @@ -84,10 +84,29 @@ def __init__(self, screen, title): ) self.wifi_toggle.connect("notify::active", self.toggle_wifi) + # AP toggle switch + self.ap_ssid = self._config.get_main_config().get('ap_ssid', 'zboltprinter') + self.ap_password = self._config.get_main_config().get('ap_password', 'zboltprinter') + self.is_ap_mode = False + + self.ap_toggle = Gtk.Switch( + width_request=round(self._gtk.font_size * 2), + height_request=round(self._gtk.font_size), + active=self.sdbus_nm.is_access_point_mode() + ) + self.ap_toggle.connect("notify::active", self.toggle_ap_mode) + self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) + sbox = Gtk.Box(hexpand=True, vexpand=False) sbox.add(self.labels['interface']) sbox.add(self.labels['ip']) sbox.add(self.reload_button) + + # AP toggle container + ap_container = Gtk.Box(spacing=5, hexpand=False) + ap_container.add(self.ap_label) + ap_container.add(self.ap_toggle) + sbox.add(ap_container) sbox.add(self.wifi_toggle) scroll = self._gtk.ScrolledWindow() @@ -95,7 +114,13 @@ def __init__(self, screen, title): if self.sdbus_nm.wifi: self.labels['main_box'].pack_start(sbox, False, False, 5) - GLib.idle_add(self.load_networks) + # Check initial AP mode state + if self.sdbus_nm.is_access_point_mode(): + self.is_ap_mode = True + self.ap_toggle.set_active(True) + GLib.idle_add(self.update_ap_display) + else: + GLib.idle_add(self.load_networks) scroll.add(self.network_list) self.sdbus_nm.enable_monitoring(True) self.conn_status = GLib.timeout_add_seconds(1, self.sdbus_nm.monitor_connection_status) @@ -355,7 +380,21 @@ def show_add_network(self, widget, ssid): def update_all_networks(self): self.interface = self.sdbus_nm.get_primary_interface() self.labels['interface'].set_text(_("Interface") + f': {self.interface}') - self.labels['ip'].set_text(f"IP: {self.sdbus_nm.get_ip_address()}") + self.update_ip_display() + + # Check if AP mode changed externally + ap_mode = self.sdbus_nm.is_access_point_mode() + if ap_mode != self.is_ap_mode: + self.is_ap_mode = ap_mode + self.ap_toggle.set_active(ap_mode) + if ap_mode: + self.update_ap_display() + return True + + # If in AP mode, don't update network list + if self.is_ap_mode: + return True + nets = self.sdbus_nm.get_networks() remove = [bssid for bssid in self.network_rows.keys() if bssid not in [net['BSSID'] for net in nets]] for bssid in remove: @@ -411,6 +450,8 @@ def update_single_network_info(self): return True def reload_networks(self, widget=None): + if self.is_ap_mode: + return self.deactivate() del self.network_rows self.network_rows = {} @@ -428,12 +469,17 @@ def activate(self): return if self.update_timeout is None: if self.sdbus_nm.wifi: - if self.reload_button.get_sensitive(): - self._gtk.Button_busy(self.reload_button, True) - self.sdbus_nm.rescan() - self.load_networks() - self.update_all_networks() - self.update_timeout = GLib.timeout_add_seconds(5, self.update_all_networks) + if self.is_ap_mode: + # In AP mode, just update IP + self.update_ip_display() + self.update_timeout = GLib.timeout_add_seconds(5, self.update_ip_display) + else: + if self.reload_button.get_sensitive(): + self._gtk.Button_busy(self.reload_button, True) + self.sdbus_nm.rescan() + self.load_networks() + self.update_all_networks() + self.update_timeout = GLib.timeout_add_seconds(5, self.update_all_networks) else: self.update_single_network_info() self.update_timeout = GLib.timeout_add_seconds(5, self.update_single_network_info) @@ -450,6 +496,10 @@ def deactivate(self): def toggle_wifi(self, switch, gparams): enable = switch.get_active() logging.info(f"WiFi {enable}") + if not enable and self.sdbus_nm.is_access_point_mode(): + # If disabling WiFi and AP is active, disable AP first + self.ap_toggle.set_active(False) + self.toggle_ap_mode(self.ap_toggle, None) self.sdbus_nm.toggle_wifi(enable) if enable: self.reload_button.show() @@ -457,6 +507,74 @@ def toggle_wifi(self, switch, gparams): else: self.reload_button.hide() + def toggle_ap_mode(self, switch, gparams): + enable = switch.get_active() + logging.info(f"AP mode {enable}") + + if not self.sdbus_nm.is_wifi_enabled(): + switch.set_active(False) + self._screen.show_popup_message(_("WiFi must be enabled first"), level=2) + return + + if enable: + # Enable AP mode + result = self.sdbus_nm.create_access_point(self.ap_ssid, self.ap_password) + if "error" in result: + switch.set_active(False) + self._screen.show_popup_message(result["message"], level=2) + return + self.is_ap_mode = True + # Hide network list and show only AP info + self.update_ap_display() + else: + # Disable AP mode + result = self.sdbus_nm.remove_access_point() + if "error" in result: + self._screen.show_popup_message(result["message"], level=2) + self.is_ap_mode = False + # Restore normal network list + self.reload_networks() + + # Update IP display + self.update_ip_display() + + def update_ap_display(self): + """Update display to show only AP information""" + # Clear network list + for child in list(self.network_list.get_children()): + self.network_list.remove(child) + self.network_rows.clear() + self.networks.clear() + + # Add AP info display + ap_info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, + valign=Gtk.Align.CENTER, vexpand=True) + ap_info_box.get_style_context().add_class("frame-item") + + ap_name_label = Gtk.Label() + ap_name_label.set_markup(f"{self.ap_ssid}") + ap_name_label.set_halign(Gtk.Align.CENTER) + + ap_status_label = Gtk.Label(label=_("Access Point Mode")) + ap_status_label.set_halign(Gtk.Align.CENTER) + + ap_password_label = Gtk.Label() + ap_password_label.set_markup(f"{_('Password')}: {self.ap_password}") + ap_password_label.set_halign(Gtk.Align.CENTER) + + ap_info_box.add(ap_name_label) + ap_info_box.add(ap_status_label) + ap_info_box.add(ap_password_label) + + self.network_list.add(ap_info_box) + self.network_list.show_all() + + def update_ip_display(self): + """Update IP address display""" + ip = self.sdbus_nm.get_ip_address() + self.labels['ip'].set_text(f"IP: {ip}") + return True + def show_fullscreen_qrcode(self, widget): curr_ip = self.sdbus_nm.get_ip_address() From 58ac0d6c3ffdc48b2a457fe9856405b8402bfcd6 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 19:14:43 +0300 Subject: [PATCH 2/8] Rearrange UI elements in network panel for improved layout - Moved the Access Point (AP) toggle container to the leftmost position in the UI. - Adjusted the order of other UI elements for better visual organization. --- panels/network.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/panels/network.py b/panels/network.py index 72c4472f3..bd253f41d 100644 --- a/panels/network.py +++ b/panels/network.py @@ -98,15 +98,16 @@ def __init__(self, screen, title): self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) sbox = Gtk.Box(hexpand=True, vexpand=False) - sbox.add(self.labels['interface']) - sbox.add(self.labels['ip']) - sbox.add(self.reload_button) - # AP toggle container + # AP toggle container - placed first (leftmost) ap_container = Gtk.Box(spacing=5, hexpand=False) ap_container.add(self.ap_label) ap_container.add(self.ap_toggle) sbox.add(ap_container) + + sbox.add(self.labels['interface']) + sbox.add(self.labels['ip']) + sbox.add(self.reload_button) sbox.add(self.wifi_toggle) scroll = self._gtk.ScrolledWindow() From f23dbf54c070f8192a600052e30acd5c798d39f0 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 19:18:23 +0300 Subject: [PATCH 3/8] Add AP mode restoration and configuration saving in network panel - Implemented logic to restore Access Point (AP) mode from saved configuration. - Added functionality to save the AP mode state when toggled. - Enhanced error handling for AP mode restoration failures. - Updated UI to reflect the current AP mode state based on configuration. --- ks_includes/config.py | 3 ++- panels/network.py | 46 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/ks_includes/config.py b/ks_includes/config.py index 31deaf29a..00e95280c 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -162,13 +162,14 @@ def validate_config(self, config, string="", remove=False): bools = ( 'invert_x', 'invert_y', 'invert_z', '24htime', 'only_heaters', 'show_cursor', 'confirm_estop', 'autoclose_popups', 'use_dpms', 'use_default_menu', 'side_macro_shortcut', 'use-matchbox-keyboard', - 'show_heater_power', 'show_lock_button', "show_scroll_steppers", "auto_open_extrude" + 'show_heater_power', 'show_lock_button', "show_scroll_steppers", "auto_open_extrude", 'ap_mode_enabled' ) strs = ( 'default_printer', 'language', 'print_sort_dir', 'theme', 'screen_blanking_printing', 'font_size', 'print_estimate_method', 'screen_blanking', "screen_on_devices", "screen_off_devices", 'print_view', "lock_password", 'ap_ssid', 'ap_password' ) + # Note: ap_mode_enabled is handled as bool but not in configurable_options numbers = ( 'job_complete_timeout', 'job_error_timeout', 'move_speed_xy', 'move_speed_z', 'print_estimate_compensation', 'width', 'height', diff --git a/panels/network.py b/panels/network.py index bd253f41d..22be8c85f 100644 --- a/panels/network.py +++ b/panels/network.py @@ -89,10 +89,14 @@ def __init__(self, screen, title): self.ap_password = self._config.get_main_config().get('ap_password', 'zboltprinter') self.is_ap_mode = False + # Check saved AP mode state + ap_mode_enabled = self._config.get_main_config().getboolean('ap_mode_enabled', False) + current_ap_mode = self.sdbus_nm.is_access_point_mode() + self.ap_toggle = Gtk.Switch( width_request=round(self._gtk.font_size * 2), height_request=round(self._gtk.font_size), - active=self.sdbus_nm.is_access_point_mode() + active=current_ap_mode or ap_mode_enabled ) self.ap_toggle.connect("notify::active", self.toggle_ap_mode) self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) @@ -115,11 +119,15 @@ def __init__(self, screen, title): if self.sdbus_nm.wifi: self.labels['main_box'].pack_start(sbox, False, False, 5) - # Check initial AP mode state + # Check initial AP mode state and restore if needed if self.sdbus_nm.is_access_point_mode(): self.is_ap_mode = True self.ap_toggle.set_active(True) GLib.idle_add(self.update_ap_display) + elif ap_mode_enabled and not current_ap_mode: + # AP mode was enabled in config but not active, restore it + logging.info("Restoring AP mode from saved configuration") + GLib.idle_add(self.restore_ap_mode) else: GLib.idle_add(self.load_networks) scroll.add(self.network_list) @@ -508,6 +516,32 @@ def toggle_wifi(self, switch, gparams): else: self.reload_button.hide() + def restore_ap_mode(self): + """Restore AP mode from saved configuration""" + if not self.sdbus_nm.is_wifi_enabled(): + logging.warning("Cannot restore AP mode: WiFi is disabled") + return False + + result = self.sdbus_nm.create_access_point(self.ap_ssid, self.ap_password) + if "error" in result: + logging.error(f"Failed to restore AP mode: {result['message']}") + self.ap_toggle.set_active(False) + # Clear saved state if restoration failed + if 'main' not in self._config.get_config().sections(): + self._config.get_config().add_section('main') + self._config.set('main', 'ap_mode_enabled', 'False') + self._config.save_user_config_options() + # Load networks if AP restoration failed + GLib.idle_add(self.load_networks) + return False + + self.is_ap_mode = True + self.ap_toggle.set_active(True) + # Update display after restoring AP + self.update_ap_display() + self.update_ip_display() + return False # Return False to prevent being called again by GLib.idle_add + def toggle_ap_mode(self, switch, gparams): enable = switch.get_active() logging.info(f"AP mode {enable}") @@ -517,11 +551,19 @@ def toggle_ap_mode(self, switch, gparams): self._screen.show_popup_message(_("WiFi must be enabled first"), level=2) return + # Save state to configuration + if 'main' not in self._config.get_config().sections(): + self._config.get_config().add_section('main') + self._config.set('main', 'ap_mode_enabled', 'True' if enable else 'False') + self._config.save_user_config_options() + if enable: # Enable AP mode result = self.sdbus_nm.create_access_point(self.ap_ssid, self.ap_password) if "error" in result: switch.set_active(False) + self._config.set('main', 'ap_mode_enabled', 'False') + self._config.save_user_config_options() self._screen.show_popup_message(result["message"], level=2) return self.is_ap_mode = True From 2bb901f7c6c2bf3ba7351338d79fa7faf0505ee8 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 19:26:40 +0300 Subject: [PATCH 4/8] Update default extruder temperatures and enable input shaper menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed default extruder temperatures for PLA, TPU, PETG, and ABS to 160°C. - Enabled the input shaper menu in the main menu configuration for better accessibility. --- config/defaults.conf | 8 ++++---- config/main_menu.conf | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/defaults.conf b/config/defaults.conf index afc07b4bc..9b7d7c49d 100644 --- a/config/defaults.conf +++ b/config/defaults.conf @@ -6,22 +6,22 @@ [preheat PLA] bed = 60 -extruder = 215 +extruder = 160 extruder1 = 215 [preheat TPU] bed = 65 -extruder = 225 +extruder = 160 extruder1 = 225 [preheat PETG] bed = 80 -extruder = 235 +extruder = 160 extruder1 = 235 [preheat ABS] bed = 110 -extruder = 245 +extruder = 160 extruder1 = 245 [include main_menu.conf] diff --git a/config/main_menu.conf b/config/main_menu.conf index cc5992644..87f47fac8 100644 --- a/config/main_menu.conf +++ b/config/main_menu.conf @@ -106,11 +106,11 @@ name: {{ gettext('Update') }} icon: refresh panel: updater -# [menu __main more input_shaper] -# name: {{ gettext('Input Shaper') }} -# icon: move -# panel: input_shaper -# enable: {{ 'input_shaper' in printer.config_sections }} +[menu __main more input_shaper] +name: {{ gettext('Input Shaper') }} +icon: move +panel: input_shaper +enable: {{ 'mcu adxl345' in printer.config_sections }} # [menu __main more save] # name: {{ gettext('Save Config') }} From c9db7e6b699daea3bde18ed751bc23a056977996 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Thu, 25 Dec 2025 19:31:38 +0300 Subject: [PATCH 5/8] 2 --- config/main_menu.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/main_menu.conf b/config/main_menu.conf index 87f47fac8..d016b0bee 100644 --- a/config/main_menu.conf +++ b/config/main_menu.conf @@ -110,7 +110,7 @@ panel: updater name: {{ gettext('Input Shaper') }} icon: move panel: input_shaper -enable: {{ 'mcu adxl345' in printer.config_sections }} +enable: {{ 'mcu adxl' in printer.config_sections }} # [menu __main more save] # name: {{ gettext('Save Config') }} From 379b775cf2a35ad1c7ffb278f506423afc6d6e3e Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Wed, 28 Jan 2026 16:51:27 +0300 Subject: [PATCH 6/8] Update Russian localization and enhance shutdown panel functionality - Updated the Russian translation file with a new auto power off option. - Added a button for toggling auto power off in the shutdown panel. - Improved safety button logic based on printer configuration. --- .../locales/ru/LC_MESSAGES/KlipperScreen.mo | Bin 29970 -> 30077 bytes .../locales/ru/LC_MESSAGES/KlipperScreen.po | 5 ++++- panels/shutdown.py | 17 +++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo index 82267de0e9972d509216940b5dd6cf94dc7a32aa..7c74ea8a31e303634aa2a1adc560e7bfa7ea778c 100644 GIT binary patch delta 8296 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 R)3$ADv?6+2;Q+6Y{{qmTV;%qi diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po index 1128b1f47..c867711e7 100644 --- a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po +++ b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KlipperScreen\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-26 12:31-0300\n" -"PO-Revision-Date: 2025-12-25 18:27+0300\n" +"PO-Revision-Date: 2026-01-28 16:51+0300\n" "Last-Translator: gfbdrgng \n" "Language-Team: Russian \n" "Language: ru\n" @@ -237,6 +237,9 @@ msgstr "Прошло:" msgid "Emergency Stop" msgstr "Аварийная остановка" +msgid "On/Off auto power off" +msgstr "Вкл/Выкл автовыключение" + msgid "Enable screen power management" msgstr "Включить панель управление питанием" diff --git a/panels/shutdown.py b/panels/shutdown.py index b37d6dc52..57642b552 100644 --- a/panels/shutdown.py +++ b/panels/shutdown.py @@ -13,8 +13,17 @@ def __init__(self, screen, title): title = title or _("Shutdown") super().__init__(screen, title) + self.has_change_poweroff = False + if self._printer: + macros = self._printer.get_config_section_list("gcode_macro ") + self.has_change_poweroff = any("CHANGE_POWEROFF" in macro.upper() for macro in macros) + estop = self._gtk.Button("emergency", _("Emergency Stop"), "color2") estop.connect("clicked", self.emergency_stop) + auto_poweroff = self._gtk.Button("shutdown", _("On/Off auto power off"), "color2") + auto_poweroff.connect("clicked", self.toggle_auto_poweroff) + + safety_button = auto_poweroff if self.has_change_poweroff else estop poweroff = self._gtk.Button("shutdown", _("Shutdown"), "color1") poweroff.connect("clicked", self.reboot_poweroff, "shutdown") @@ -33,7 +42,7 @@ def __init__(self, screen, title): self.main = Gtk.Grid(row_homogeneous=True, column_homogeneous=True) if self._show_lock_button: if self._printer and self._printer.state not in {'disconnected', 'startup', 'shutdown', 'error'}: - self.main.attach(estop, 1, 0, 1, 1) + self.main.attach(safety_button, 1, 0, 1, 1) self.main.attach(restart_ks, 2, 0, 1, 1) self.main.attach(lock_screen, 0, 0, 1, 2) self.main.attach(poweroff, 1, 1, 1, 1) @@ -41,7 +50,7 @@ def __init__(self, screen, title): self.content.add(self.main) else: if self._printer and self._printer.state not in {'disconnected', 'startup', 'shutdown', 'error'}: - self.main.attach(estop, 0, 0, 1, 1) + self.main.attach(safety_button, 0, 0, 1, 1) self.main.attach(restart_ks, 1, 0, 1, 1) self.main.attach(poweroff, 0, 1, 1, 1) self.main.attach(restart, 1, 1, 1, 1) @@ -99,3 +108,7 @@ def turn_off_power_devices(self): if power_devices and self._printer.get_power_devices(): logging.info(f"Turning off associated power devices: {power_devices}") self._screen.power_devices(widget=None, devices=power_devices, on=False) + + def toggle_auto_poweroff(self, widget): + if self._screen and self._screen._ws and self._screen._ws.connected: + self._screen._send_action(widget, "printer.gcode.script", {"script": "CHANGE_POWEROFF"}) From 05f8182c4096ed0ebb8af3b2a894c21fcdff52c6 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Wed, 28 Jan 2026 17:01:53 +0300 Subject: [PATCH 7/8] Add 'Show Access Point toggle' feature and update Russian localization - Introduced a new configuration option to show/hide the Access Point toggle in the network panel. - Updated the UI to include a toggle switch for the Access Point mode, with a warning about potential instability. - Enhanced Russian localization with translations for the new toggle feature and its tooltip. - Adjusted the network panel logic to conditionally display the Access Point toggle based on the new configuration option. --- ks_includes/config.py | 11 ++- .../locales/ru/LC_MESSAGES/KlipperScreen.mo | Bin 30077 -> 30308 bytes .../locales/ru/LC_MESSAGES/KlipperScreen.po | 8 +- panels/network.py | 83 +++++++++++------- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/ks_includes/config.py b/ks_includes/config.py index 00e95280c..0d7d670be 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -162,7 +162,8 @@ def validate_config(self, config, string="", remove=False): bools = ( 'invert_x', 'invert_y', 'invert_z', '24htime', 'only_heaters', 'show_cursor', 'confirm_estop', 'autoclose_popups', 'use_dpms', 'use_default_menu', 'side_macro_shortcut', 'use-matchbox-keyboard', - 'show_heater_power', 'show_lock_button', "show_scroll_steppers", "auto_open_extrude", 'ap_mode_enabled' + 'show_heater_power', 'show_lock_button', "show_scroll_steppers", "auto_open_extrude", + 'ap_mode_enabled', 'show_ap_toggle' ) strs = ( 'default_printer', 'language', 'print_sort_dir', 'theme', 'screen_blanking_printing', 'font_size', @@ -317,6 +318,14 @@ def _create_configurable_options(self, screen): {"show_cursor": {"section": "main", "name": _("Show cursor"), "type": "binary", "tooltip": _("For mouse control or to verify touchscreen accuracy"), "value": "False", "callback": screen.update_cursor}}, + {"show_ap_toggle": { + "section": "main", + "name": _("Show Access Point toggle"), + "type": "binary", + "tooltip": _("Test feature, unstable behavior possible"), + "value": "False", + "callback": screen.reload_panels + }}, # {"": {"section": "main", "name": _(""), "type": ""}} ] diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.mo index 7c74ea8a31e303634aa2a1adc560e7bfa7ea778c..a487528bd09ad5e0940fef5d5398f62d9e39a8f5 100644 GIT binary patch delta 7046 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 LbKp+t+Q$C_Bwc*# delta 6853 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
    k z22_L&U0kx4j5<{F6_r!A&nk+r%}KY!g& zNTp*NYQXnUJ2{6M@E@qNYck#K&&4$AgHXvi2i5OK9o4JY5s#wA`zNMh$P9M_U9cbZ zyD^>l&5J7F2dD@BjhZ-Yrke{X*q-_*TVIUY$#&FR@;>Uhlv!>hb5RlKhgx7cs=pfb z+xT}`)=M^{qpv*KQ?_p#Bz;UnXRZyP#R9?7tuN znmX7W|BO1)A8dd8TzAI>s14kOt#IXB;!pI-+2l>ofh6S zPhTg`J0N&2_bhUjd9yqv&Q5QRr_j0R9U454)=^G&+@RoEO2y8yxERmf&W5F+G@C3yxpn|(>a z-RPO_?DwT5jHlGG@o{5FumAf^a6Iu@DR`Fk){HCI-ZjSAnh+B`f?6Q==r`KMlsZQeQa$%L*Ag-c7SkWamB6L9 z=a=7X>dUDtKqc>esNW_RGt3#87#Z4!R%O~mr!Fze)6dzO7!#aNTa~jn(HC7{pSjK* zeD};muF1}o#C%VVlae$brHFpL!B27Z=bB;fr`Y%#?yO8o@=SGJ;=i-%4<&`PslSx> gwx|A3{-B`1?uB{v;k}l3u0J#LRABw3x)n|T3!9S|(EtDd diff --git a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po index c867711e7..037db9860 100644 --- a/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po +++ b/ks_includes/locales/ru/LC_MESSAGES/KlipperScreen.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KlipperScreen\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-26 12:31-0300\n" -"PO-Revision-Date: 2026-01-28 16:51+0300\n" +"PO-Revision-Date: 2026-01-28 17:01+0300\n" "Last-Translator: gfbdrgng \n" "Language-Team: Russian \n" "Language: ru\n" @@ -941,6 +941,12 @@ msgstr "Обновление" msgid "Show cursor" msgstr "Отображать курсор" +msgid "Show Access Point toggle" +msgstr "Показывать переключатель точки доступа" + +msgid "Test feature, unstable behavior possible" +msgstr "Тестовая функция, возможны сбои" + msgid "For mouse control or to verify touchscreen accuracy" msgstr "Для управления мышью или для проверки точности сенсора" diff --git a/panels/network.py b/panels/network.py index 22be8c85f..ae490ef52 100644 --- a/panels/network.py +++ b/panels/network.py @@ -84,31 +84,7 @@ def __init__(self, screen, title): ) self.wifi_toggle.connect("notify::active", self.toggle_wifi) - # AP toggle switch - self.ap_ssid = self._config.get_main_config().get('ap_ssid', 'zboltprinter') - self.ap_password = self._config.get_main_config().get('ap_password', 'zboltprinter') - self.is_ap_mode = False - - # Check saved AP mode state - ap_mode_enabled = self._config.get_main_config().getboolean('ap_mode_enabled', False) - current_ap_mode = self.sdbus_nm.is_access_point_mode() - - self.ap_toggle = Gtk.Switch( - width_request=round(self._gtk.font_size * 2), - height_request=round(self._gtk.font_size), - active=current_ap_mode or ap_mode_enabled - ) - self.ap_toggle.connect("notify::active", self.toggle_ap_mode) - self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) - sbox = Gtk.Box(hexpand=True, vexpand=False) - - # AP toggle container - placed first (leftmost) - ap_container = Gtk.Box(spacing=5, hexpand=False) - ap_container.add(self.ap_label) - ap_container.add(self.ap_toggle) - sbox.add(ap_container) - sbox.add(self.labels['interface']) sbox.add(self.labels['ip']) sbox.add(self.reload_button) @@ -117,18 +93,59 @@ def __init__(self, screen, title): scroll = self._gtk.ScrolledWindow() self.labels['main_box'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True) + # AP test feature visibility flag + self.show_ap_toggle = self._config.get_main_config().getboolean('show_ap_toggle', False) + + # AP-related attributes (used only if show_ap_toggle is True) + self.ap_ssid = self._config.get_main_config().get('ap_ssid', 'zboltprinter') + self.ap_password = self._config.get_main_config().get('ap_password', 'zboltprinter') + self.is_ap_mode = False + if self.sdbus_nm.wifi: self.labels['main_box'].pack_start(sbox, False, False, 5) - # Check initial AP mode state and restore if needed - if self.sdbus_nm.is_access_point_mode(): - self.is_ap_mode = True - self.ap_toggle.set_active(True) - GLib.idle_add(self.update_ap_display) - elif ap_mode_enabled and not current_ap_mode: - # AP mode was enabled in config but not active, restore it - logging.info("Restoring AP mode from saved configuration") - GLib.idle_add(self.restore_ap_mode) + if self.show_ap_toggle: + # Initialize AP toggle switch (test feature) + ap_mode_enabled = self._config.get_main_config().getboolean('ap_mode_enabled', False) + current_ap_mode = self.sdbus_nm.is_access_point_mode() + + self.ap_toggle = Gtk.Switch( + width_request=round(self._gtk.font_size * 2), + height_request=round(self._gtk.font_size), + active=current_ap_mode or ap_mode_enabled + ) + self.ap_toggle.connect("notify::active", self.toggle_ap_mode) + self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) + + ap_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2, hexpand=False) + ap_label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + ap_label_box.add(self.ap_label) + ap_note = Gtk.Label( + label=_("Test feature, unstable behavior possible"), + hexpand=False + ) + ap_note.set_justify(Gtk.Justification.LEFT) + ap_note.set_xalign(0) + ap_note.get_style_context().add_class("dim-label") + ap_label_box.add(ap_note) + + ap_container.add(ap_label_box) + ap_container.add(self.ap_toggle) + + # Insert AP container at the beginning of sbox + sbox.pack_start(ap_container, False, False, 5) + + # Check initial AP mode state and restore if needed + if self.sdbus_nm.is_access_point_mode(): + self.is_ap_mode = True + self.ap_toggle.set_active(True) + GLib.idle_add(self.update_ap_display) + elif ap_mode_enabled and not current_ap_mode: + logging.info("Restoring AP mode from saved configuration") + GLib.idle_add(self.restore_ap_mode) + else: + GLib.idle_add(self.load_networks) else: + # AP toggle hidden: always use normal network list GLib.idle_add(self.load_networks) scroll.add(self.network_list) self.sdbus_nm.enable_monitoring(True) From 40e8d535a09aab78f8e9f622e219df1449c89890 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Wed, 28 Jan 2026 17:06:00 +0300 Subject: [PATCH 8/8] Refactor Access Point UI in network panel for improved layout - Simplified the Access Point (AP) container by removing the note label and adjusting the layout. - Enhanced the spacing and organization of UI elements for better visual clarity. --- panels/network.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/panels/network.py b/panels/network.py index ae490ef52..a3238a578 100644 --- a/panels/network.py +++ b/panels/network.py @@ -116,19 +116,8 @@ def __init__(self, screen, title): self.ap_toggle.connect("notify::active", self.toggle_ap_mode) self.ap_label = Gtk.Label(label=_("AP"), hexpand=False) - ap_container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2, hexpand=False) - ap_label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) - ap_label_box.add(self.ap_label) - ap_note = Gtk.Label( - label=_("Test feature, unstable behavior possible"), - hexpand=False - ) - ap_note.set_justify(Gtk.Justification.LEFT) - ap_note.set_xalign(0) - ap_note.get_style_context().add_class("dim-label") - ap_label_box.add(ap_note) - - ap_container.add(ap_label_box) + ap_container = Gtk.Box(spacing=5, hexpand=False) + ap_container.add(self.ap_label) ap_container.add(self.ap_toggle) # Insert AP container at the beginning of sbox