diff --git a/ks_includes/locales/KlipperScreen.pot b/ks_includes/locales/KlipperScreen.pot index d4cf7ddc..4a1a2111 100644 --- a/ks_includes/locales/KlipperScreen.pot +++ b/ks_includes/locales/KlipperScreen.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-14 16:25+0800\n" +"POT-Creation-Date: 2025-02-26 11:12+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -70,6 +70,9 @@ msgstr "" msgid "Accept" msgstr "" +msgid "Activate" +msgstr "" + msgid "Adaptive Bed Leveling" msgstr "" @@ -234,9 +237,6 @@ msgstr "" msgid "Confirm factory reset?\n" msgstr "" -msgid "Connected" -msgstr "" - msgid "Connecting" msgstr "" @@ -306,15 +306,24 @@ msgstr "" msgid "Do you want to recover %s?" msgstr "" +msgid "Elapsed trial time:" +msgstr "" + msgid "Elapsed:" msgstr "" msgid "Emergency Stop" msgstr "" +msgid "Enable Registration Code" +msgstr "" + msgid "Enable screen power management" msgstr "" +msgid "Enabled successfully" +msgstr "" + msgid "Error" msgstr "" @@ -462,9 +471,6 @@ msgstr "" msgid "Host" msgstr "" -msgid "Hostname" -msgstr "" - msgid "ID" msgstr "" @@ -507,6 +513,18 @@ msgstr "" msgid "Job Status" msgstr "" +msgid "Key is empty" +msgstr "" + +msgid "Key is invalid" +msgstr "" + +msgid "Key is valid" +msgstr "" + +msgid "Key:" +msgstr "" + msgid "Klipper Restart" msgstr "" @@ -559,6 +577,9 @@ msgstr "" msgid "Leveling only in the actual print area" msgstr "" +msgid "License key" +msgstr "" + msgid "Limits" msgstr "" @@ -682,6 +703,9 @@ msgstr "" msgid "Not Inserted" msgstr "" +msgid "Not activated" +msgstr "" + msgid "Not all screens support this" msgstr "" @@ -745,12 +769,18 @@ msgstr "" msgid "Perform a full upgrade?" msgstr "" +msgid "Permanent Activation" +msgstr "" + msgid "Pins" msgstr "" msgid "Please ensure that the Probe Calibrate has been performed" msgstr "" +msgid "Please enter a key to activate" +msgstr "" + msgid "Please enter a valid number" msgstr "" @@ -835,6 +865,9 @@ msgstr "" msgid "Refresh" msgstr "" +msgid "Remaining Time:" +msgstr "" + msgid "Remove network" msgstr "" @@ -844,6 +877,12 @@ msgstr "" msgid "Reprint" msgstr "" +msgid "Reset" +msgstr "" + +msgid "Reset successfully" +msgstr "" + msgid "Restart" msgstr "" @@ -923,6 +962,9 @@ msgstr "" msgid "Send" msgstr "" +msgid "Serial Number:" +msgstr "" + msgid "Settings" msgstr "" @@ -944,6 +986,9 @@ msgstr "" msgid "Size" msgstr "" +msgid "Skip" +msgstr "" + msgid "Slicer" msgstr "" @@ -983,15 +1028,15 @@ msgstr "" msgid "Start testing the Z offset value of the second nozzle?\n" msgstr "" -msgid "Starting WiFi Association" -msgstr "" - msgid "Starting recovery for" msgstr "" msgid "Starting update for" msgstr "" +msgid "State:" +msgstr "" + msgid "System" msgstr "" @@ -1019,6 +1064,9 @@ msgstr "" msgid "The system will reboot!" msgstr "" +msgid "This device is not activated and is available for trial use only" +msgstr "" + msgid "This operation is about to print the model" msgstr "" @@ -1037,6 +1085,15 @@ msgstr "" msgid "Total:" msgstr "" +msgid "Trial" +msgstr "" + +msgid "Trial Time:" +msgstr "" + +msgid "Unknown" +msgstr "" + msgid "Unknown Heater" msgstr "" @@ -1139,6 +1196,9 @@ msgid_plural "hours" msgstr[0] "" msgstr[1] "" +msgid "license" +msgstr "" + msgid "macros that use 'rename_existing' are hidden" msgstr "" diff --git a/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.mo b/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.mo index 171336a2..b36aa86b 100644 Binary files a/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.mo and b/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.mo differ diff --git a/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.po b/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.po index 7d7efb49..f27c6d50 100644 --- a/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.po +++ b/ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: KlipperScreen\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-07 17:11+0800\n" +"POT-Creation-Date: 2025-02-26 11:12+0800\n" "PO-Revision-Date: 2024-06-03 19:09+0000\n" "Last-Translator: wsj20050623 <2129426599@qq.com>\n" "Language-Team: Chinese (Simplified) \n" "Language-Team: Chinese (Traditional) str: + raise NotImplementedError + + @dbus_property_async(property_signature="a{sv}") + def get_time_info(self) -> str: + raise NotImplementedError + + @dbus_property_async(property_signature="b") + def is_active(self) -> bool: + raise NotImplementedError + + @dbus_property_async(property_signature="b") + def is_trial_active(self) -> bool: + raise NotImplementedError + + @dbus_method_async(input_signature="s", result_signature="b") + async def verify_activation_code(self, val: str) -> bool: + raise NotImplementedError + + @dbus_method_async(input_signature="s", result_signature="b") + async def reset_registration(self, val: str) -> bool: + raise NotImplementedError + + @dbus_property_async(property_signature="b") + def enabled_registration(self) -> bool: + raise NotImplementedError + + +class LicenseManager: + + def __init__(self, bus_name: str = "org.registration.link", object_path: str = "/"): + self.loop = new_event_loop() + self.registration_interface: Optional[RegistrationInterface] = None + self.interface_valid = False + self._thread: Optional[Thread] = None + self.callback = None + + try: + self.registration_interface = RegistrationInterface(bus_name, object_path) + self.interface_valid = True + logger.info("DBus connection established successfully") + except Exception as e: + logger.error(f"Failed to initialize DBus connection: {e}") + self._cleanup_resources() + return + + self._thread = Thread(target=self._run_loop, daemon=True) + self._thread.start() + + def _run_loop(self) -> None: + try: + self.loop.run_forever() + finally: + self.loop.close() + + def _cleanup_resources(self) -> None: + if self.loop.is_running(): + self.loop.call_soon_threadsafe(self.loop.stop) + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=1) + self.loop.close() + + def is_interface_valid(self) -> bool: + return self.interface_valid + + def _async_call(self, coroutine_func, default=None): + if not self.is_interface_valid(): + logger.warning("Attempting to use invalid DBus interface") + return default + + try: + future = run_coroutine_threadsafe(coroutine_func(), self.loop) + return future.result() + except Exception as e: + logger.error(f"DBus operation failed: {e}") + self.interface_valid = False + return default + + def get_unique_id(self) -> str: + async def _get(): + return await self.registration_interface.proxy.get_unique_id + + return self._async_call(_get, default="") + + def get_trial_time(self) -> int: + async def _get(): + return await self.registration_interface.proxy.get_time_info + + result = self._async_call(_get, default="{}") + try: + data_dict = ast.literal_eval(result) + return data_dict.get("trial_time", 0) + except Exception as e: + logger.error(f"Parse time info failed: {e}") + return 0 + + def get_total_printed_time(self) -> int: + async def _get(): + return await self.registration_interface.proxy.get_time_info + + result = self._async_call(_get, default="{}") + try: + data_dict = ast.literal_eval(result) + return int(data_dict.get("total_printed_time", 0)) + except Exception as e: + logger.error(f"Parse time info failed: {e}") + return 0 + + def is_active(self) -> bool: + async def _get(): + return await self.registration_interface.proxy.is_active + + return self._async_call(_get, default=False) + + def is_trial_active(self) -> bool: + async def _get(): + return await self.registration_interface.proxy.is_trial_active + + return self._async_call(_get, default=False) + + def is_time_sufficient(self, required_seconds: int = 40 * 3600) -> bool: + trial_time = self.get_trial_time() + printed_time = self.get_total_printed_time() + return (trial_time - printed_time) > required_seconds + + def verify_activation_code(self, code: str) -> bool: + async def _verify(): + return await self.registration_interface.proxy.verify_activation_code(code) + + return self._async_call(_verify, default=False) + + def reset_registration(self, code: str) -> bool: + async def _reset(): + return await self.registration_interface.proxy.reset_registration(code) + + return self._async_call(_reset, default=False) + + def enabled_registration(self) -> bool: + async def _get(): + return await self.registration_interface.proxy.enabled_registration + + return self._async_call(_get, default=False) + + def close(self) -> None: + if self.is_interface_valid(): + logger.info("Closing DBus connection...") + self._cleanup_resources() + self.interface_valid = False diff --git a/panels/base_panel.py b/panels/base_panel.py index 3ffe7c60..9ca192ed 100644 --- a/panels/base_panel.py +++ b/panels/base_panel.py @@ -8,6 +8,7 @@ from gi.repository import GLib, Gtk, Pango from jinja2 import Environment from datetime import datetime from math import log +from ks_includes.sdbus_nm import SdbusNm from ks_includes.screen_panel import ScreenPanel @@ -18,11 +19,37 @@ class BasePanel(ScreenPanel): self.time_min = -1 self.time_format = self._config.get_main_config().getboolean("24htime", True) self.time_update = None + self.network_update = None self.titlebar_items = [] self.titlebar_name_type = None self.current_extruder = None self.last_usage_report = datetime.now() self.usage_report = 0 + + icon_size_width = self._gtk.content_width * 0.05 + icon_size_height = self._gtk.content_height * 0.05 + + network_icons_map = { + "excellent": "wifi_excellent", + "good": "wifi_good", + "fair": "wifi_fair", + "weak": "wifi_weak", + "ethernet": "ethernet", + } + + + self.network_icons = { + key: self._gtk.PixbufFromIcon(value, width=icon_size_width, height=icon_size_height) + for key, value in network_icons_map.items() + } + + try: + self.sdbus_nm = SdbusNm(self.network_interface_refresh) + except Exception as e: + logging.exception("Failed to initialize SdbusNm: %s", e) + self.sdbus_nm = None + + # Action bar buttons abscale = self.bts * 1.1 self.control['back'] = self._gtk.Button('back', scale=abscale) @@ -100,6 +127,26 @@ class BasePanel(ScreenPanel): self.titlelbl = Gtk.Label(hexpand=True, halign=Gtk.Align.CENTER, ellipsize=Pango.EllipsizeMode.END) + if self._screen.license.is_interface_valid() and not self._screen.license.is_active(): + img_size = self._gtk.img_scale * self.bts + self.control["license"] = self._gtk.Image("license", img_size, img_size) + license_eventbox = Gtk.EventBox() + license_eventbox.add(self.control["license"]) + license_eventbox.connect("button-press-event", self.show_license_key_page) + self.control["license_box"] = Gtk.Box(halign=Gtk.Align.END) + self.control["license_box"].pack_end(license_eventbox, True, True, 5) + + if self.sdbus_nm: + img_size = self._gtk.img_scale * self.bts + self.control["network_ico"] = self._gtk.Image("wifi_excellent", img_size, img_size) + network_eventbox = Gtk.EventBox() + network_eventbox.add(self.control["network_ico"]) + network_eventbox.connect("button-press-event", self.show_network_page) + self.control["network_box"] = Gtk.Box(halign=Gtk.Align.END) + self.control["network_box"].pack_end(network_eventbox, True, True, 5) + self.control["network_ico"].set_no_show_all(True) + self.control["network_ico"].set_visible(False) + self.control['time'] = Gtk.Label(label="00:00 AM") self.control['time_box'] = Gtk.Box(halign=Gtk.Align.END) self.control['time_box'].pack_end(self.control['time'], True, True, 10) @@ -108,6 +155,10 @@ class BasePanel(ScreenPanel): self.titlebar.get_style_context().add_class("title_bar") self.titlebar.add(self.control['temp_box']) self.titlebar.add(self.titlelbl) + if self._screen.license.is_interface_valid() and not self._screen.license.is_active(): + self.titlebar.add(self.control["license_box"]) + if self.sdbus_nm: + self.titlebar.add(self.control["network_box"]) self.titlebar.add(self.control['time_box']) self.set_title(title) @@ -127,6 +178,14 @@ class BasePanel(ScreenPanel): self.update_time() + def show_license_key_page(self, widget, event): + if "license" not in self._screen._cur_panels: + self._screen.show_panel("license", remove_all=False) + + def show_network_page(self, widget, event): + if "network" not in self._screen._cur_panels: + self._screen.show_panel("network", remove_all=False) + def reload_icons(self): button: Gtk.Button for button in self.action_bar.get_children(): @@ -212,6 +271,8 @@ class BasePanel(ScreenPanel): def activate(self): if self.time_update is None: self.time_update = GLib.timeout_add_seconds(1, self.update_time) + if self.sdbus_nm and self.network_update is None: + self.network_update = GLib.timeout_add_seconds(5, self.network_interface_refresh) def add_content(self, panel): printing = self._printer and self._printer.state in {"printing", "paused"} @@ -381,6 +442,35 @@ class BasePanel(ScreenPanel): self.time_format = confopt return True + def network_interface_refresh(self, msg=None, level=3): + if self.sdbus_nm: + self.interface = self.sdbus_nm.get_primary_interface() + if self.interface: + if '?' not in self.sdbus_nm.get_ip_address(): + if self.interface == "eth0": + self.control["network_ico"].set_from_pixbuf(self.network_icons["ethernet"]) + self.control["network_ico"].set_visible(True) + elif self.interface == "wlan0": + strength = self.sdbus_nm.get_signal_strength() + if strength: + self.control["network_ico"].set_from_pixbuf(self.get_signal_strength_icon(strength)) + self.control["network_ico"].set_visible(True) + else: + self.control["network_ico"].set_visible(False) + else: + self.control["network_ico"].set_visible(False) + return True + + def get_signal_strength_icon(self, signal_level): + if signal_level > 75: + return self.network_icons["excellent"] + elif signal_level > 60: + return self.network_icons["good"] + elif signal_level > 30: + return self.network_icons["fair"] + else: + return self.network_icons["weak"] + def set_ks_printer_cfg(self, printer): ScreenPanel.ks_printer_cfg = self._config.get_printer_config(printer) if self.ks_printer_cfg is not None: diff --git a/panels/factory_settings.py b/panels/factory_settings.py index 4998576d..786ef4ef 100644 --- a/panels/factory_settings.py +++ b/panels/factory_settings.py @@ -37,6 +37,14 @@ class Panel(ScreenPanel): "callback": self.reset_factory_settings, } }, + { + "License key": { + "section": "main", + "name": _("License key"), + "type": "button", + "callback": self.license_key, + } + }, { "version_info": { "section": "main", @@ -132,19 +140,47 @@ class Panel(ScreenPanel): self.content.show_all() self.select_model = False + def license_key(self, *args): + self._screen.show_panel("license", title="license", remove_all=False, full=True) + def reset_factory_settings(self, *args): - text = _("Are you sure?\n") + "\n\n" + _("The system will reboot!") - label = Gtk.Label(wrap=True, vexpand=True) - label.set_markup(text) buttons = [ {"name": _("Accept"), "response": Gtk.ResponseType.OK, "style": "dialog-error"}, {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": "dialog-info"}, ] - self._gtk.Dialog(_("factory settings"), buttons, label, self.confirm_factory_reset_production) + + text = _("Are you sure?\n") + "\n\n" + _("The system will reboot!") + label = Gtk.Label(wrap=True, vexpand=True) + label.set_markup(text) + label.set_margin_top(100) - def confirm_factory_reset_production(self, dialog, response_id): + checkbox = Gtk.CheckButton(label=" " + _("Enable Registration Code")) + checkbox.set_halign(Gtk.Align.CENTER) + checkbox.set_valign(Gtk.Align.CENTER) + + + grid = Gtk.Grid(row_homogeneous=True, column_homogeneous=True) + grid.set_row_spacing(20) + grid.set_column_spacing(0) + grid.attach(label, 0, 0, 1, 1) + if self._screen.license.is_interface_valid() and self._screen.license.is_active(): + grid.attach(checkbox, 0, 1, 1, 1) + + self._gtk.Dialog( + _("factory settings"), + buttons, + grid, + self.confirm_factory_reset_production, + checkbox, + ) + + + def confirm_factory_reset_production(self, dialog, response_id, checkbox): self._gtk.remove_dialog(dialog) if response_id == Gtk.ResponseType.OK: + if checkbox.get_active(): + if self._screen.license.is_interface_valid(): + self._screen.license.enabled_registration() KlippyFactory.production_factory_reset(self._screen._ws.klippy, self._config) def version_selection(self, val): diff --git a/panels/gcodes.py b/panels/gcodes.py index 987e75de..8c1b6826 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -341,6 +341,17 @@ class Panel(ScreenPanel): def confirm_print_response(self, dialog, response_id, filename): self._gtk.remove_dialog(dialog) if response_id == Gtk.ResponseType.OK: + if self._screen.license.is_interface_valid() and not self._screen.license.is_active(): + if not self._screen.license.is_time_sufficient(): + if "license" not in self._screen._cur_panels: + self._screen.show_panel( + "license", + title="license", + remove_all=False, + func=self._screen._ws.klippy.print_start, + file=filename, + ) + return logging.info(f"Starting print: {filename}") self._screen._ws.klippy.print_start(filename) diff --git a/panels/license.py b/panels/license.py new file mode 100644 index 00000000..e83b818b --- /dev/null +++ b/panels/license.py @@ -0,0 +1,234 @@ +import logging +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Pango +from ks_includes.screen_panel import ScreenPanel + + +class Panel(ScreenPanel): + + def __init__(self, screen, title, **kwargs): + title = title or _("license") + super().__init__(screen, title) + self.title_text = ( + _("This device is not activated and is available for trial use only") + + "\n" + + _("Please enter a key to activate") + ) + self.key_len = 15 + self.full = False + self.interface = screen.license + self.serial_num = self.interface.get_unique_id() or _("Unknown") + self.is_active = self.interface.is_active() + self.args = {} + + def update_time(self): + total_printed_time = max(0, self.interface.get_total_printed_time()) + trial_time = max(0, self.interface.get_trial_time()) + remaining_time = max(0, trial_time - total_printed_time) + + self.license_box["elapsed_trial_time_value"].set_text(self.seconds_to_hms(total_printed_time)) + self.license_box["trial_time_value"].set_text(self.seconds_to_hms(trial_time)) + self.license_box["remain_time_value"].set_text(self.seconds_to_hms(remaining_time)) + + def seconds_to_hms(self, seconds): + + if not isinstance(seconds, (int, float)) or seconds < 0: + raise ValueError(f"seconds must be a non-negative number, got {seconds}") + + hours, remainder = divmod(int(seconds), 3600) + minutes, secs = divmod(remainder, 60) + + return f"{hours:03}:{minutes:02}:{secs:02}" + + def state_update(self, text): + self.license_box["state_text_value"].set_text(text) + + def verify_key(self, key): + try: + res = self.interface.verify_activation_code(key) + if res: + if self.interface.is_active(): + self.is_active = True + self.state_update(_("Permanent Activation")) + else: + self.state_update(_("Key is valid")) + else: + self.state_update(_("Key is invalid")) + except Exception as e: + logging.exception(e) + + def active_refresh(self, **args): + self.args["full"] = args.get("full") + self.args["callback"] = args.get("func") + self.args["file"] = args.get("file") + self.args["onboarding"] = args.get("onboarding") + self.display_dialog() + + def deactivate(self): + self._screen.remove_keyboard() + for child in self.content.get_children(): + self.content.remove(child) + + def display_dialog(self, full=False, key=""): + BUTTON_CONFIGS = { + "trial_with_callback": [ + {"name": _("Activate"), "response": Gtk.ResponseType.OK, "style": "dialog-info"}, + {"name": _("Skip"), "response": Gtk.ResponseType.CANCEL, "style": "dialog-error"}, + ], + "full_features": [ + {"name": _("Reset"), "response": Gtk.ResponseType.APPLY, "style": "dialog-secondary"}, + {"name": _("Activate"), "response": Gtk.ResponseType.OK, "style": "dialog-info"}, + {"name": _("Close"), "response": Gtk.ResponseType.CLOSE, "style": "dialog-error"}, + ], + "Trial": [ + {"name": _("Activate"), "response": Gtk.ResponseType.OK, "style": "dialog-info"}, + {"name": _("Trial"), "response": Gtk.ResponseType.CLOSE, "style": "dialog-error"}, + ], + "default": [ + {"name": _("Activate"), "response": Gtk.ResponseType.OK, "style": "dialog-info"}, + {"name": _("Close"), "response": Gtk.ResponseType.CLOSE, "style": "dialog-error"}, + ], + } + + if self.args.get("callback") and self.interface.is_trial_active(): + buttons = BUTTON_CONFIGS["trial_with_callback"] + elif self.args.get("full"): + buttons = BUTTON_CONFIGS["full_features"] + elif self.args.get("onboarding"): + buttons = BUTTON_CONFIGS["Trial"] + else: + buttons = BUTTON_CONFIGS["default"] + + self.create_license_key_dialog(buttons=buttons, key=key) + + def create_license_key_dialog(self, buttons=None, key=""): + + if buttons is None: + buttons = [ + {"name": _("Activate"), "response": Gtk.ResponseType.OK, "style": "dialog-info"}, + {"name": _("Close"), "response": Gtk.ResponseType.CLOSE, "style": "dialog-error"}, + ] + + self.title_label = Gtk.Label(hexpand=True, vexpand=False, wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR) + self.title_label.set_markup(f"{self.title_text}\n") + self.title_label.set_margin_top(50) + self.title_label.set_margin_start(20) + self.title_label.set_halign(Gtk.Align.START) + + self.license_box = {} + + self.grid = Gtk.Grid(column_spacing=20, row_spacing=20, hexpand=True, vexpand=True) + + def add_labeled_value(row, label_text, value_text): + label = Gtk.Label(label=label_text, use_markup=True, xalign=0, wrap=True) + value = Gtk.Label(label=value_text, use_markup=True, xalign=0, wrap=True) + self.grid.attach(label, 0, row, 1, 1) + self.grid.attach(value, 1, row, 1, 1) + return label, value + + status_text = _("Not activated") + if not self.interface.is_interface_valid(): + status_text = _("Unknown") + elif self.is_active: + status_text = _("Permanent Activation") + self.license_box["state_text"], self.license_box["state_text_value"] = add_labeled_value( + 0, _("State:"), status_text + ) + self.license_box["serial_num_text"], self.license_box["serial_num_value"] = add_labeled_value( + 1, _("Serial Number:"), self.serial_num + ) + self.license_box["trial_time_text"], self.license_box["trial_time_value"] = add_labeled_value( + 2, _("Trial Time:"), "000:00:00" + ) + self.license_box["elapsed_trial_time_text"], self.license_box["elapsed_trial_time_value"] = add_labeled_value( + 3, _("Elapsed trial time:"), "000:00:00" + ) + self.license_box["remain_time_text"], self.license_box["remain_time_value"] = add_labeled_value( + 4, _("Remaining Time:"), "000:00:00" + ) + + self.license_box["key_text"] = Gtk.Label(label=_("Key:"), use_markup=True, xalign=0, wrap=True) + self.grid.attach(self.license_box["key_text"], 0, 5, 1, 1) + + self.license_box["key_input"] = Gtk.Entry(hexpand=False, vexpand=False) + self.license_box["key_input"].set_max_length(self.key_len) + self.license_box["key_input"].set_text(key) + self.license_box["key_input"].connect("button-press-event", self.on_show_keyboard) + self.grid.attach(self.license_box["key_input"], 1, 5, 1, 1) + + image = self._gtk.Image("license", self._gtk.content_width * 0.4, self._gtk.content_height * 0.4) + image.set_margin_start(60) + image.set_margin_end(20) + + main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True) + horizontal_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, vexpand=True) + horizontal_box.set_margin_top(20) + + self.grid.set_margin_start(60) + horizontal_box.pack_start(image, False, True, 0) + horizontal_box.pack_start(self.grid, True, True, 0) + + main_box.pack_start(self.title_label, False, True, 0) + main_box.pack_start(horizontal_box, True, True, 0) + + self.dialog = self._gtk.Dialog("License", buttons, main_box, self.confirm_license_response) + self.update_time() + + def confirm_license_response(self, dialog, response_id): + if response_id == Gtk.ResponseType.YES: + if self.interface.enabled_registration(): + self.state_update(_("Enabled successfully")) + elif response_id == Gtk.ResponseType.APPLY: + if len(self.license_box["key_input"].get_text()) == 0: + self.state_update(_("Key is empty")) + return + else: + if self.interface.reset_registration(self.license_box["key_input"].get_text()): + self.update_time() + self.state_update(_("Reset successfully")) + else: + self.state_update(_("Key is invalid")) + elif response_id == Gtk.ResponseType.CLOSE: + self._gtk.remove_dialog(dialog) + self._screen._menu_go_back() + elif response_id == Gtk.ResponseType.CANCEL: + self._gtk.remove_dialog(dialog) + self._screen._menu_go_back() + if not self.interface.is_active() and self.interface.is_trial_active(): + if self.args.get("callback"): + self.args["callback"](self.args["file"]) + elif response_id == Gtk.ResponseType.OK: + if len(self.license_box["key_input"].get_text()) == 0: + self.state_update(_("Key is empty")) + return + self.verify_key(self.license_box["key_input"].get_text()) + self.update_time() + + def on_show_keyboard(self, entry=None, event=None): + self._gtk.remove_dialog(self.dialog) + lbl = Gtk.Label(_("Please enter a key to activate"), halign=Gtk.Align.START, hexpand=False) + self.labels["entry"] = Gtk.Entry(hexpand=True) + self.labels["entry"].set_max_length(self.key_len) + self.labels["entry"].connect("focus-in-event", self._screen.show_keyboard) + save = self._gtk.Button("complete", _("Save"), "color3") + save.set_hexpand(False) + save.connect("clicked", self.on_save_key) + input_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) + input_box.pack_start(self.labels["entry"], True, True, 5) + input_box.pack_start(save, False, False, 5) + self.labels["input_box"] = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=5, hexpand=True, vexpand=True, valign=Gtk.Align.CENTER + ) + self.labels["input_box"].pack_start(lbl, True, True, 5) + self.labels["input_box"].pack_start(input_box, True, True, 5) + self.content.add(self.labels["input_box"]) + self.labels["entry"].grab_focus_without_selecting() + + def on_save_key(self, dialog): + key_text = self.labels["entry"].get_text() + self._screen.remove_keyboard() + for child in self.content.get_children(): + self.content.remove(child) + self.display_dialog(self.full, key=key_text) diff --git a/panels/network.py b/panels/network.py index e1215e7d..c544425c 100644 --- a/panels/network.py +++ b/panels/network.py @@ -77,9 +77,7 @@ class Panel(ScreenPanel): self.labels['interface'] = Gtk.Label(hexpand=True) self.labels['ip'] = Gtk.Label(hexpand=True) - if self.interface is not None: - self.labels['interface'].set_text(_("Interface") + f': {self.interface}') - self.labels['ip'].set_text(f"IP: {self.sdbus_nm.get_ip_address()}") + self.network_interface_refresh() self.reload_button = self._gtk.Button("refresh", None, "custom-icon-button", self.bts) self.reload_button.set_no_show_all(True) @@ -120,6 +118,7 @@ class Panel(ScreenPanel): self.network_list.connect("row-activated", self.handle_wifi_selection) def popup_callback(self, msg, level=3): + self.network_interface_refresh() if not self.refresh_status(msg): for item in self.network_rows: if self.network_rows[item]["label_state"] is not None: @@ -127,6 +126,12 @@ class Panel(ScreenPanel): self.network_rows[item]["label_state"].hide() self._screen.show_popup_message(msg, level) + def network_interface_refresh(self): + if self.interface is not None: + 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()}") + def handle_wifi_selection(self, list_box, row): index = row.get_index() logging.info(f"clicked SSID is {self.networks[index]['SSID']}") @@ -407,6 +412,7 @@ class Panel(ScreenPanel): if self.delay_reload_timer_id: GLib.source_remove(self.delay_reload_timer_id) self.delay_reload_timer_id = None + self.network_interface_refresh() return self.sdbus_nm.nm.wireless_enabled @@ -438,7 +444,7 @@ class Panel(ScreenPanel): return if self.sdbus_nm.wifi: if self.sdbus_nm.is_wifi_enabled(): - self.delay_reload_networks(1000) + self.delay_reload_networks(2000) self.start_refresh_timer() def deactivate(self): @@ -464,3 +470,4 @@ class Panel(ScreenPanel): self.reload_button.hide() self.network_list.set_no_show_all(True) self.network_list.hide() + self.network_interface_refresh() \ No newline at end of file diff --git a/panels/onboarding.py b/panels/onboarding.py index 3c1daf54..43f6b65e 100644 --- a/panels/onboarding.py +++ b/panels/onboarding.py @@ -67,3 +67,6 @@ class Panel(ScreenPanel): self._screen.show_panel("main_menu", remove_all=True, items=self._config.get_menu_items("__main")) self._config.set("main", "onboarding", "False") self._config.save_user_config_options() + if self._screen.license.is_interface_valid() and not self._screen.license.is_active(): + self._gtk.remove_dialog(dialog) + self._screen.show_panel("license", remove_all=False, onboarding=True) diff --git a/screen.py b/screen.py index 9a558431..d3196e79 100755 --- a/screen.py +++ b/screen.py @@ -28,6 +28,7 @@ from ks_includes.printer import Printer from ks_includes.widgets.keyboard import Keyboard from ks_includes.widgets.prompts import Prompt from ks_includes.config import KlipperScreenConfig +from ks_includes.sdbus_reg import LicenseManager from panels.base_panel import BasePanel logging.getLogger("urllib3").setLevel(logging.WARNING) @@ -89,6 +90,8 @@ class KlipperScreen(Gtk.Window): self.confirm = None self.panels_reinit = [] self.last_popup_time = datetime.now() + self.license = LicenseManager() + logging.info(f"license interface:{self.license}") configfile = os.path.normpath(os.path.expanduser(args.configfile)) diff --git a/styles/dark/images/license.svg b/styles/dark/images/license.svg new file mode 100644 index 00000000..cd9dce3f --- /dev/null +++ b/styles/dark/images/license.svg @@ -0,0 +1,3 @@ + + + diff --git a/styles/light/images/license.svg b/styles/light/images/license.svg new file mode 100644 index 00000000..cd9dce3f --- /dev/null +++ b/styles/light/images/license.svg @@ -0,0 +1,3 @@ + + +