From 32478b7ca6fa97adb30718080c4bc95751be2509 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:04:04 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D0=90=D0=B4=D0=B0=D0=BF=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=81=D0=B5=D1=82=D0=B5=D0=B2=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/network.py | 59 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/panels/network.py b/panels/network.py index 937834af6..32a08d5fa 100644 --- a/panels/network.py +++ b/panels/network.py @@ -64,11 +64,27 @@ def __init__(self, screen, title): self.interface = self.sdbus_nm.get_primary_interface() logging.info(f"Primary interface: {self.interface}") + self.is_ap_mode = False - self.labels['interface'] = Gtk.Label(hexpand=True) - self.labels['ip'] = Gtk.Label(hexpand=True) + self.labels['interface'] = Gtk.Label( + hexpand=True, + halign=Gtk.Align.START, + wrap=True, + wrap_mode=Pango.WrapMode.WORD_CHAR, + ) + self.labels['interface'].set_xalign(0) + self.labels['interface'].set_max_width_chars(30 if self._screen.vertical_mode else 60) + self.labels['interface'].set_no_show_all(True) + self.labels['ip'] = Gtk.Label( + hexpand=True, + halign=Gtk.Align.START, + wrap=True, + wrap_mode=Pango.WrapMode.WORD_CHAR, + ) + self.labels['ip'].set_xalign(0) + self.labels['ip'].set_max_width_chars(30 if self._screen.vertical_mode else 60) if self.interface is not None: - self.labels['interface'].set_text(_("Interface") + f': {self.interface}') + self.update_interface_display() self.labels['ip'].set_text(f"IP: {self.sdbus_nm.get_ip_address()}") self.reload_button = self._gtk.Button("refresh", None, "color1", self.bts) @@ -84,11 +100,17 @@ def __init__(self, screen, title): ) self.wifi_toggle.connect("notify::active", self.toggle_wifi) - sbox = Gtk.Box(hexpand=True, vexpand=False) - sbox.add(self.labels['interface']) - sbox.add(self.labels['ip']) - sbox.add(self.reload_button) - sbox.add(self.wifi_toggle) + info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, hexpand=True, vexpand=False) + info_box.add(self.labels['interface']) + info_box.add(self.labels['ip']) + + controls_box = Gtk.Box(spacing=5, hexpand=False, vexpand=False) + controls_box.add(self.reload_button) + controls_box.add(self.wifi_toggle) + + sbox = Gtk.Box(spacing=5, hexpand=True, vexpand=False) + sbox.add(info_box) + sbox.pack_end(controls_box, False, False, 0) scroll = self._gtk.ScrolledWindow() self.labels['main_box'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True) @@ -99,7 +121,6 @@ def __init__(self, screen, title): # 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) @@ -120,8 +141,7 @@ def __init__(self, screen, title): ap_container.add(self.ap_label) ap_container.add(self.ap_toggle) - # Insert AP container at the beginning of sbox - sbox.pack_start(ap_container, False, False, 5) + controls_box.pack_start(ap_container, False, False, 5) # Check initial AP mode state and restore if needed if self.sdbus_nm.is_access_point_mode(): @@ -394,18 +414,20 @@ 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.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 hasattr(self, "ap_toggle"): + self.ap_toggle.set_active(ap_mode) + self.update_ip_display() if ap_mode: self.update_ap_display() return True + self.update_ip_display() + # If in AP mode, don't update network list if self.is_ap_mode: return True @@ -464,6 +486,14 @@ def update_single_network_info(self): self.labels['networkinfo'].show_all() return True + def update_interface_display(self): + if self.is_ap_mode: + self.labels['interface'].hide() + return + if self.interface is not None: + self.labels['interface'].set_text(_("Interface") + f': {self.interface}') + self.labels['interface'].show() + def reload_networks(self, widget=None): if self.is_ap_mode: return @@ -620,6 +650,7 @@ def update_ap_display(self): def update_ip_display(self): """Update IP address display""" + self.update_interface_display() if self.is_ap_mode: ip_info = self.sdbus_nm.get_ap_mode_ips() wifi_ip = ip_info.get("wifi", "?") From 0fb8e986b97c80d7a6db4bf765fb494934b5dd88 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:07:52 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=92=D1=8B=D1=80=D0=B0=D0=B2=D0=BD=D0=B8?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B5=D1=82=D0=B5=D0=B9?= =?UTF-8?q?=20=D0=B2=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/panels/network.py b/panels/network.py index 32a08d5fa..eae86a0bb 100644 --- a/panels/network.py +++ b/panels/network.py @@ -203,6 +203,8 @@ def add_network(self, bssid): buttons = Gtk.Box(spacing=5) name = Gtk.Label(hexpand=True, halign=Gtk.Align.START, wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR) + name.set_xalign(0) + name.set_justify(Gtk.Justification.LEFT) if bssid == self.sdbus_nm.get_connected_bssid(): ssid += ' (' + _("Connected") + ')' name.set_markup(f"{ssid}") @@ -214,6 +216,8 @@ def add_network(self, bssid): buttons.add(connect) info = Gtk.Label(halign=Gtk.Align.START) + info.set_xalign(0) + info.set_justify(Gtk.Justification.LEFT) labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True, halign=Gtk.Align.START, valign=Gtk.Align.CENTER) labels.add(name) From 7ec0edeb289cca55af39d8268d2c8a822eb806b7 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:11:13 +0300 Subject: [PATCH 3/7] =?UTF-8?q?=D0=90=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20?= =?UTF-8?q?=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/system.py | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/panels/system.py b/panels/system.py index 28553b88c..a773d3b99 100644 --- a/panels/system.py +++ b/panels/system.py @@ -2,13 +2,14 @@ import os import subprocess import shutil +import threading import zipfile from datetime import datetime import gi gi.require_version("Gtk", "3.0") -from gi.repository import Gtk +from gi.repository import Gtk, GLib from ks_includes.screen_panel import ScreenPanel @@ -272,7 +273,29 @@ def _first_key(d, *keys, default=None): f.write(f"- Total filament used: {filament_mm_str} ({filament_m_str})\n") def export_logs(self, widget): + self._gtk.Button_busy(widget, True) + threading.Thread(target=self._export_logs_thread, args=(widget,), daemon=True).start() + + def _export_logs_thread(self, widget): + """Экспортирует журналы на съемный носитель""" + messages = [] + try: + messages = self._export_logs() + except Exception as e: + logging.error(f"Error exporting logs: {e}") + messages = [(_("Error exporting logs") + f": {str(e)}", 3)] + finally: + GLib.idle_add(self._finish_export_logs, widget, messages) + + def _finish_export_logs(self, widget, messages): + self._gtk.Button_busy(widget, False) + for message, level in messages: + self._screen.show_popup_message(message, level=level) + return False + + def _export_logs(self): """Экспортирует журналы на съемный носитель""" + messages = [] # Ищем примонтированное устройство mounted_device = self.find_mounted_device() @@ -285,9 +308,8 @@ def export_logs(self, widget): export_dest_dir = mounted_device else: export_dest_dir = logs_dir - self._screen.show_popup_message( - _("No removable device found") + "\n" + _("Saving to logs folder"), - level=2 + messages.append( + (_("No removable device found") + "\n" + _("Saving to logs folder"), 2) ) logging.warning( "No mounted device found in /home/pi/printer_data/gcodes/. Saving to ~/printer_data/logs" @@ -388,9 +410,8 @@ def export_logs(self, widget): shutil.rmtree(temp_dir) # Показываем уведомление об успехе - self._screen.show_popup_message( - _("Logs saved successfully") + f"\n{dest_zip_path}", - level=1 + messages.append( + (_("Logs saved successfully") + f"\n{dest_zip_path}", 1) ) # Отмонтируем устройство @@ -410,17 +431,15 @@ def export_logs(self, widget): logging.error(f"Error unmounting device: {e}") except Exception as e: - logging.error(f"Error exporting logs: {e}") - self._screen.show_popup_message( - _("Error exporting logs") + f": {str(e)}", - level=3 - ) # Удаляем временную директорию в случае ошибки if os.path.exists(temp_dir): try: shutil.rmtree(temp_dir) except Exception: pass + raise + + return messages def process_update(self, action, data): if not self.sysinfo: From b2390cab52301629892edab9b247576577ec6e97 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:48:49 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=90=D0=B4=D0=B0=D0=BF=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/base_panel.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/panels/base_panel.py b/panels/base_panel.py index 889b7b7d6..8fac1af3d 100644 --- a/panels/base_panel.py +++ b/panels/base_panel.py @@ -31,6 +31,8 @@ def __init__(self, screen, title=None): self.titlebar_items = [] self.titlebar_name_type = None self.current_extruder = None + self.title_printer = "" + self.title_panel = "" self.last_usage_report = datetime.now() self.usage_report = 0 # Action bar buttons @@ -90,6 +92,7 @@ def __init__(self, screen, title=None): self.control['temp_box'] = Gtk.Box(spacing=10) self.titlelbl = Gtk.Label(hexpand=True, halign=Gtk.Align.CENTER, ellipsize=Pango.EllipsizeMode.END) + self.titlelbl.connect("size-allocate", self.update_title_visibility) self.control['time'] = Gtk.Label(label="00:00 AM") self.control['time_box'] = Gtk.Box(halign=Gtk.Align.END) @@ -377,7 +380,9 @@ def set_title(self, title): else: printer = "" if not title: - self.titlelbl.set_label(f"{printer}") + self.title_printer = printer + self.title_panel = "" + self.update_title_visibility() return try: env = Environment(extensions=["jinja2.ext.i18n"], autoescape=True) @@ -387,7 +392,29 @@ def set_title(self, title): except Exception as e: logging.debug(f"Error parsing jinja for title: {title}\n{e}") - self.titlelbl.set_label(f"{printer} {title}") + self.title_printer = printer + self.title_panel = title + self.update_title_visibility() + + def update_title_visibility(self, *args): + if self.titlebar.get_style_context().has_class("message_popup_error"): + return False + if not self.title_panel: + label = self.title_printer + else: + label = f"{self.title_printer} {self.title_panel}".strip() + if self.title_printer and self._screen.vertical_mode: + allocation = self.titlelbl.get_allocation() + if allocation.width and not self.title_fits(label, allocation.width): + label = self.title_panel + if self.titlelbl.get_label() != label: + self.titlelbl.set_label(label) + return False + + def title_fits(self, text, width): + layout = self.titlelbl.create_pango_layout(text) + text_width, _ = layout.get_pixel_size() + return text_width <= width def update_time(self): now = datetime.now() From c60b559b0be2b4c9c7a1f8f208a6e5000c142a1a Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:51:44 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=80=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=B3=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/base_panel.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/panels/base_panel.py b/panels/base_panel.py index 8fac1af3d..f4f99d900 100644 --- a/panels/base_panel.py +++ b/panels/base_panel.py @@ -373,7 +373,8 @@ def show_printer_select(self, show=True): def set_title(self, title): self.titlebar.get_style_context().remove_class("message_popup_error") if ( - self._screen.connecting_to_printer != "Printer" + self._screen.connecting_to_printer + and self._screen.connecting_to_printer != "Printer" and 'printer_select' not in self._screen._cur_panels ): printer = self._screen.connecting_to_printer @@ -400,7 +401,7 @@ def update_title_visibility(self, *args): if self.titlebar.get_style_context().has_class("message_popup_error"): return False if not self.title_panel: - label = self.title_printer + label = self.title_printer or "" else: label = f"{self.title_printer} {self.title_panel}".strip() if self.title_printer and self._screen.vertical_mode: From 9d37b8c9dc8471217377e741d4b2904656b9a9b0 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 13:58:12 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=97=D0=B0=D0=BF=D1=80=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=B8=20=D1=81=20USB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/gcodes.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index dc02ea849..3bdf41557 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -221,12 +221,13 @@ def create_item(self, item): # Определяем позиции кнопок в зависимости от типа элемента button_col = 2 if 'filename' in item: + file_on_usb = is_file_on_usb(f"gcodes/{path}") icon.connect("clicked", self.confirm_print, path) image_args = (path, icon, self.thumbsize / 2, True, "file") delete.connect("clicked", self.confirm_delete_file, f"gcodes/{path}") rename.connect("clicked", self.show_rename, f"gcodes/{path}") # Показываем кнопку переноса только если файл находится на USB - if is_file_on_usb(f"gcodes/{path}"): + if file_on_usb: move = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) move.get_style_context().add_class("color3") move.set_image(self._gtk.Image("sd", self.list_button_size, self.list_button_size)) @@ -244,8 +245,9 @@ def create_item(self, item): action.set_vexpand(False) action.set_halign(Gtk.Align.END) if self._screen.width >= 400: - row.attach(action, button_col, 0, 1, 2) - else: + if not file_on_usb: + row.attach(action, button_col, 0, 1, 2) + elif not file_on_usb: icon.get_style_context().add_class("color3") row.attach(icon, button_col, 0, 1, 2) elif 'dirname' in item: @@ -497,7 +499,6 @@ def confirm_print(self, widget, filename): buttons_usb = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, - {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-secondary'}] if show_start_settings else []), {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} @@ -553,6 +554,10 @@ def confirm_print_response(self, dialog, response_id, filename): self._screen.show_panel("print_start_settings") return elif response_id == Gtk.ResponseType.OK: + if is_file_on_usb(f"gcodes/{filename}"): + logging.warning(f"Refusing to print from removable storage: {filename}") + self._screen.show_popup_message(_("Move the file to internal storage before printing"), level=2) + return logging.info(f"Starting print: {filename}") self._screen._ws.klippy.print_start(filename) elif response_id == Gtk.ResponseType.APPLY: From 21a97df154b9b01fdf6b40e6083937bcf2186474 Mon Sep 17 00:00:00 2001 From: Vlad <427departament@gmail.com> Date: Fri, 19 Jun 2026 14:01:32 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=A6=D0=B2=D0=B5=D1=82=D0=B0=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA=20gcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- panels/gcodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/panels/gcodes.py b/panels/gcodes.py index 3bdf41557..eab05dd4e 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -491,15 +491,15 @@ def confirm_print(self, widget, filename): buttons = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, - *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-secondary'}] + *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-info'}] if show_start_settings else []), {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ] buttons_usb = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, - {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, - *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-secondary'}] + {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-primary'}, + *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-info'}] if show_start_settings else []), {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ]