From f8923155888cff60aa531e12e21d2f4b3c15f3da Mon Sep 17 00:00:00 2001 From: zkk <1007518571@qq.com> Date: Mon, 3 Mar 2025 15:27:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=8A=B6=E6=80=81=E6=A0=8F?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=BD=91=E7=BB=9C=E5=9B=BE=E6=A0=87=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96IP=E4=BF=A1=E6=81=AF=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=B8=8D=E5=8F=8A=E6=97=B6=E9=97=AE=E9=A2=98=EF=BC=8C=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=B3=A8=E5=86=8C=E7=A0=81=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed commit of the following: commit 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 Merge: 2dba9f8e 964a81c3 Author: zkk <1007518571@qq.com> Date: Mon Mar 3 15:25:49 2025 +0800 Merge commit '964a81c37cd165b9f8df20db87fd915ba03d10b5' into release commit 964a81c37cd165b9f8df20db87fd915ba03d10b5 Author: zkk <1007518571@qq.com> Date: Fri Feb 28 16:27:57 2025 +0800 状态栏增加网络连接图标 commit 9c99cd91250c7aeb3d9728e540590299a1caf150 Author: zkk <1007518571@qq.com> Date: Fri Feb 28 16:20:52 2025 +0800 修复网络页面IP更新不及时的问题 commit 842a616c2558757d19b7553d23aee9f1daffdc80 Author: zkk <1007518571@qq.com> Date: Tue Feb 25 14:53:31 2025 +0800 注册码功能的实现 commit 2dba9f8ee2725847f31c891b19df6d9b81e03568 Merge: 03cd3722 18f05cc5 Author: zkk <1007518571@qq.com> Date: Fri Feb 14 15:21:36 2025 +0800 Merge commit '18f05cc52dfb7889853a4f84aad975309ab7dbbe' into release commit 18f05cc52dfb7889853a4f84aad975309ab7dbbe Author: zkk <1007518571@qq.com> Date: Wed Feb 5 10:59:00 2025 +0800 修复连接不了未加密网络的旧问题 commit 025a26209cbe15a0b7f5f1aba5166da63d6eaebf Author: zkk <1007518571@qq.com> Date: Fri Jan 24 15:48:52 2025 +0800 重构网络页面,解决卡顿问题 commit fdc851c4e66e3b872eb2564442f0c67369b9e3e2 Author: zkk <1007518571@qq.com> Date: Fri Jan 24 15:13:41 2025 +0800 优化菜单页面的点击效果和增加列表风格属性 commit e6cfa874208f8d98d0360fb073e4da15c935b202 Author: zkk <1007518571@qq.com> Date: Tue Jan 21 15:11:28 2025 +0800 优化进入工厂模式的方式为10s内点击5次 commit 03cd37229ee6c606e9814af602709d81b2c79bec Merge: cf7cafef adcbaa17 Author: zkk <1007518571@qq.com> Date: Fri Jan 17 14:38:18 2025 +0800 Merge commit 'adcbaa1795bffedc3000bc9e59eabba04d946dd4' into release commit adcbaa1795bffedc3000bc9e59eabba04d946dd4 Author: zkk <1007518571@qq.com> Date: Tue Jan 14 15:34:10 2025 +0800 实现探针偏移值校准保存前范围检测,避免保存错误值无法启动问题 commit cf7cafefbcd2274c9a481e58042d11db8cd79961 Merge: dc3478b4 8d259cea Author: zkk <1007518571@qq.com> Date: Tue Jan 7 17:26:59 2025 +0800 Merge commit '8d259ceadd5026cecc1dc61224d91aef4f3877b4' into release commit 8d259ceadd5026cecc1dc61224d91aef4f3877b4 Author: zkk <1007518571@qq.com> Date: Tue Jan 7 17:15:46 2025 +0800 实现 打印中恢复喷头温度 提示语的翻译 commit dd11c9683cb5af5407e650eea9b683b1066256c7 Author: zkk <1007518571@qq.com> Date: Tue Jan 7 16:52:04 2025 +0800 优化单个喷头时不显示喷头偏移菜单 commit c3ec7a81bbe1490b76202485d0502a4829a3cee5 Author: zkk <1007518571@qq.com> Date: Fri Jan 3 17:52:24 2025 +0800 修复屏幕耗材检测显示偶尔不准确的问题 commit 413bf6fc4a740f53d265c63abe7c51eaad483cb0 Author: zkk <1007518571@qq.com> Date: Fri Jan 3 15:00:54 2025 +0800 修复耗材检测弹窗异常问题 commit dc3478b48c7da1dbae120d1270c9ab64646d6647 Merge: 02c7556c 3bfc1aa7 Author: zkk <1007518571@qq.com> Date: Thu Jan 2 11:18:07 2025 +0800 Merge commit '3bfc1aa714e282d74b801155d830377ca58d8f59' into release commit 3bfc1aa714e282d74b801155d830377ca58d8f59 Author: zkk <1007518571@qq.com> Date: Thu Jan 2 11:17:15 2025 +0800 修复移轴页面移轴功能失效问题 commit 02c7556cdadf8de1ef3c54d2831920927cadbe30 Merge: 6bfa42e0 991003e6 Author: zkk <1007518571@qq.com> Date: Tue Dec 31 15:07:54 2024 +0800 Merge commit '991003e6cbea335eca73d3783aa1837059614724' into release commit 991003e6cbea335eca73d3783aa1837059614724 Author: zkk <1007518571@qq.com> Date: Tue Dec 31 15:03:34 2024 +0800 优化完整 简体中文和繁体中文的翻译 commit 1a177e90d09b9b9949bff2a1e3c6b12173420620 Author: zkk <1007518571@qq.com> Date: Tue Dec 31 14:10:03 2024 +0800 优化排除对象的英文语法错误 commit e8d509cb6c2883b1fadb5ab9f9ca658e61849055 Author: zkk <1007518571@qq.com> Date: Fri Dec 27 16:40:52 2024 +0800 优化耗材检测显示内容 commit 1b7670485a918cb334119175777525f768e670be Author: zkk <1007518571@qq.com> Date: Fri Dec 27 14:56:25 2024 +0800 完善断料自动切头功能描述 commit ecc0c3dd16442497e56f5f8ab3a0c6be00bd180e Author: zkk <1007518571@qq.com> Date: Fri Dec 27 13:40:01 2024 +0800 实现弹窗翻译功能 commit bc6d60fa183af50b7b16fea685139ca9d5df90ea Author: zkk <1007518571@qq.com> Date: Fri Dec 27 09:53:16 2024 +0800 优化不合理名称和不合理大小写 commit 2e650926bdba1c65baa506be1b1dd09621e8d8ea Author: zkk <1007518571@qq.com> Date: Thu Dec 26 15:06:11 2024 +0800 修复10寸屏幕偏移值微调页面出界问题 commit c481b551f777a3a6cadf7be3bfcab38448b51694 Author: zkk <1007518571@qq.com> Date: Wed Dec 25 15:28:26 2024 +0800 优化移轴设置页面没有返回按钮的问题 commit 2b3c9936bd693c8fc265d6352799f382680f4920 Author: zkk <1007518571@qq.com> Date: Wed Dec 25 10:05:04 2024 +0800 删除移动设置页面中轴反转的选项 commit 5e37d59497c54086d3f1e215cbabc03254857a58 Author: zkk <1007518571@qq.com> Date: Wed Dec 25 09:05:03 2024 +0800 优化选择语言标题 commit 5214c3697baac78bd0a465e33d6f4f3c1e30862e Author: zkk <1007518571@qq.com> Date: Tue Dec 24 10:57:41 2024 +0800 增加设置hostname功能 commit 3709a7465cc2e0d677ecc9979fb18144f7e0ec33 Author: zkk <1007518571@qq.com> Date: Tue Dec 24 10:43:46 2024 +0800 增加工厂设置中打包模式 commit 0c2a2618beb68d50601e433beef06122fb0ffd40 Author: zkk <1007518571@qq.com> Date: Mon Dec 23 16:25:37 2024 +0800 实现恢复出厂设置功能 commit e3a38f85613d53a4a063e835c34c701196010c6e Author: zkk <1007518571@qq.com> Date: Mon Dec 23 15:48:17 2024 +0800 一点格式化 commit 4e9e452e8bed4b53eaaae8f0a69142de8aa50611 Author: zkk <1007518571@qq.com> Date: Sat Dec 21 14:20:22 2024 +0800 优化探针偏移值校准时如果当前激活为第一个喷头时不会重复激活喷头 commit 3b85e8e8d59b0d4eae75f80d9e637fe9dc272cbe Merge: 882850db 10ec2029 Author: zkk <1007518571@qq.com> Date: Wed Dec 18 17:26:08 2024 +0800 Merge branch 'develop' of https://server.creatbot.com/Gitea/CreatBot/CreatBotKlipperScreen into develop commit 882850dbde648598e5f91281101d0ea01fd56d56 Author: zkk <1007518571@qq.com> Date: Wed Dec 18 17:21:16 2024 +0800 补充双喷头偏移校准页面二维码图片 commit 10ec2029eb73e66874d5a0ec492c016480641110 Author: ruipeng <1041589370@qq.com> Date: Wed Dec 18 17:17:18 2024 +0800 增加自动切换喷头开关选项 commit 3d6eed9d9526b77472ba7df29014b5768c594026 Author: zkk <1007518571@qq.com> Date: Mon Dec 16 16:33:34 2024 +0800 喷头偏移值校准功能的实现 commit 40ecbb3ea4827c9bac1aa271cccc958c94c1114a Author: zkk <1007518571@qq.com> Date: Fri Dec 13 09:20:00 2024 +0800 z探针校准时指定默认激活工具头为第一个头 commit bce3caa409618ef5bc2067865940d14a1441c0f8 Author: zkk <1007518571@qq.com> Date: Fri Dec 13 09:19:19 2024 +0800 优化z探针校准时候移动过慢问题 commit 6bfa42e036a521c8ff7db1bf8ccb65500eabb6ea Merge: 1a87ced3 4f3aa9aa Author: zkk <1007518571@qq.com> Date: Thu Dec 12 10:33:58 2024 +0800 Merge commit '4f3aa9aa4c581ae9e7a740bd37f9e80ba064c27f' into release commit 4f3aa9aa4c581ae9e7a740bd37f9e80ba064c27f Merge: e3fd413d 1a69b518 Author: zkk <1007518571@qq.com> Date: Thu Dec 12 10:30:00 2024 +0800 Merge branch 'develop' of https://server.creatbot.com/Gitea/CreatBot/CreatBotKlipperScreen into develop commit 1a69b5180de733f15550a1294a7221d6070306a0 Author: ruipeng <1041589370@qq.com> Date: Wed Dec 11 11:44:36 2024 +0800 新增D600pro2、D1000的V0版机型 commit e3fd413d6256414441e9fe653c1132b1799a5cb2 Author: zkk <1007518571@qq.com> Date: Tue Dec 10 14:34:13 2024 +0800 修复打印时没有修改z偏移值 仍提示保存z偏移的按钮的bug commit 594fb668fe94fe907c028bf65ecced43ea8660cb Author: zkk <1007518571@qq.com> Date: Tue Dec 10 14:25:04 2024 +0800 设置自适应调平选项默认为关闭状态 commit 1a87ced3f5725569a6b9a7ee5f5250044d01d852 Merge: 629416d1 6064a6e1 Author: zkk <1007518571@qq.com> Date: Fri Nov 29 10:18:32 2024 +0800 Merge branch 'develop' into release # Conflicts: # ks_includes/locales/KlipperScreen.pot resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.mo resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # ks_includes/locales/zh_CN/LC_MESSAGES/KlipperScreen.po resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # ks_includes/locales/zh_TW/LC_MESSAGES/KlipperScreen.mo resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # ks_includes/locales/zh_TW/LC_MESSAGES/KlipperScreen.po resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # panels/factory_settings.py resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version # panels/network.py resolved by 69bcfa4bd00fc1af8da8df191b6a4d86309c7d23 version --- ks_includes/locales/KlipperScreen.pot | 80 +++++- .../zh_CN/LC_MESSAGES/KlipperScreen.mo | Bin 22445 -> 23500 bytes .../zh_CN/LC_MESSAGES/KlipperScreen.po | 97 ++++++-- .../zh_TW/LC_MESSAGES/KlipperScreen.mo | Bin 22148 -> 23219 bytes .../zh_TW/LC_MESSAGES/KlipperScreen.po | 95 +++++-- ks_includes/sdbus_nm.py | 4 + ks_includes/sdbus_reg.py | 165 ++++++++++++ panels/base_panel.py | 90 +++++++ panels/factory_settings.py | 46 +++- panels/gcodes.py | 11 + panels/license.py | 234 ++++++++++++++++++ panels/network.py | 15 +- panels/onboarding.py | 3 + screen.py | 3 + styles/dark/images/license.svg | 3 + styles/light/images/license.svg | 3 + 16 files changed, 797 insertions(+), 52 deletions(-) create mode 100644 ks_includes/sdbus_reg.py create mode 100644 panels/license.py create mode 100644 styles/dark/images/license.svg create mode 100644 styles/light/images/license.svg 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 171336a2d346b69a00508450f38ce1d861825055..b36aa86b3a48bc2e2839f6483c51f12e9372b957 100644 GIT binary patch delta 9361 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 delta 8367 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 a=NC8iKVQ7cKY7%?z_8IjdHe&%-1k3vW}b}z 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) `y%Qu9QE(xI0U{xZBmpTpBtWETz>z90 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 delta 8334 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\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 @@ + + +