Merge commit '964a81c37cd165b9f8df20db87fd915ba03d10b5' into release

This commit is contained in:
张开科 2025-03-03 15:25:49 +08:00
commit 69bcfa4bd0
16 changed files with 797 additions and 52 deletions

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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) <https://hosted.weblate.org/projects/"
@ -63,7 +63,7 @@ msgid "ADXL Not Configured"
msgstr "未配置加速度计"
msgid "Abort"
msgstr "中止打印"
msgstr "终止"
msgid "Acceleration:"
msgstr "加速度:"
@ -71,6 +71,9 @@ msgstr "加速度:"
msgid "Accept"
msgstr "确定"
msgid "Activate"
msgstr "激活"
msgid "Adaptive Bed Leveling"
msgstr "自适应床调平"
@ -235,9 +238,6 @@ msgstr "紧急停止需要二次确认"
msgid "Confirm factory reset?\n"
msgstr "确定恢复出厂设置?\n"
msgid "Connected"
msgstr "已连接"
msgid "Connecting"
msgstr "连接中"
@ -307,15 +307,24 @@ msgstr "您要忘记或断开%s的连接吗?"
msgid "Do you want to recover %s?"
msgstr "你想恢复 %s 吗?"
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 "错误"
@ -463,9 +472,6 @@ msgstr "Z轴归零"
msgid "Host"
msgstr "主机"
msgid "Hostname"
msgstr "主机名"
msgid "ID"
msgstr "ID"
@ -508,6 +514,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 "重启Klipper"
@ -560,6 +578,9 @@ msgstr "剩余时间:"
msgid "Leveling only in the actual print area"
msgstr "仅在实际打印区域进行床调平"
msgid "License key"
msgstr "许可证密钥"
msgid "Limits"
msgstr "打印机限制"
@ -683,6 +704,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 "开始测试第二个喷嘴的 Z 偏移值吗?\n"
msgid "Starting WiFi Association"
msgstr "开始WiFi连接"
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 "未知加热器"
@ -1136,6 +1193,9 @@ msgid "hour"
msgid_plural "hours"
msgstr[0] "小时"
msgid "license"
msgstr "许可证"
msgid "macros that use 'rename_existing' are hidden"
msgstr "使用\"rename_existing\"的宏将被隐藏"
@ -1168,6 +1228,15 @@ msgid "second"
msgid_plural "seconds"
msgstr[0] "秒"
#~ msgid "Connected"
#~ msgstr "已连接"
#~ msgid "Hostname"
#~ msgstr "主机名"
#~ msgid "Starting WiFi Association"
#~ msgstr "开始WiFi连接"
#~ msgid "Restoring Left extruder temperature, this may take some time"
#~ msgstr "恢复左挤出机温度,这可能需要一些时间"
@ -1243,9 +1312,6 @@ msgstr[0] "秒"
#~ msgid "KlipperScreen will drop support in June 2024"
#~ msgstr "KlipperScreen将于2024 年6月停止适配"
#~ msgid "Print Time"
#~ msgstr "打印用时"
#~ msgid "Warning"
#~ msgstr "警告"
@ -1420,6 +1486,3 @@ msgstr[0] "秒"
#~ msgid "Load Average"
#~ msgstr "平均负载"
#~ msgid "Screen Blanking Time"
#~ msgstr "关闭屏幕时间"

View File

@ -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-01 12:09+0000\n"
"Last-Translator: 峻瑜哥 <a728728728@gmail.com>\n"
"Language-Team: Chinese (Traditional) <https://hosted.weblate.org/projects/"
@ -71,6 +71,9 @@ msgstr "加速度:"
msgid "Accept"
msgstr "接受"
msgid "Activate"
msgstr "激活"
msgid "Adaptive Bed Leveling"
msgstr "自適應床調平"
@ -235,9 +238,6 @@ msgstr "緊急停止需確認"
msgid "Confirm factory reset?\n"
msgstr "確定恢復出廠設置?\n"
msgid "Connected"
msgstr "已連線"
msgid "Connecting"
msgstr "連接中"
@ -307,15 +307,24 @@ msgstr "您要忘記或斷開%s的連接嗎?"
msgid "Do you want to recover %s?"
msgstr "你想恢復 %s 嗎?"
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 "錯誤"
@ -463,9 +472,6 @@ msgstr "Z軸歸零"
msgid "Host"
msgstr "主機"
msgid "Hostname"
msgstr "主機名"
msgid "ID"
msgstr "ID"
@ -508,6 +514,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 "重啟Klipper"
@ -560,6 +578,9 @@ msgstr "剩餘時間:"
msgid "Leveling only in the actual print area"
msgstr "僅在實際打印區域進行床調平"
msgid "License key"
msgstr "許可證密鑰"
msgid "Limits"
msgstr "列印機限制"
@ -683,6 +704,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 "開始測試第二個噴嘴的 Z 偏移值嗎?\n"
msgid "Starting WiFi Association"
msgstr "開始WiFi連線"
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 "未知加熱器"
@ -1136,6 +1193,9 @@ msgid "hour"
msgid_plural "hours"
msgstr[0] "小時"
msgid "license"
msgstr "許可證"
msgid "macros that use 'rename_existing' are hidden"
msgstr "使用\"rename_existing\"的宏將被隱藏"
@ -1168,6 +1228,15 @@ msgid "second"
msgid_plural "seconds"
msgstr[0] "秒"
#~ msgid "Connected"
#~ msgstr "已連線"
#~ msgid "Hostname"
#~ msgstr "主機名"
#~ msgid "Starting WiFi Association"
#~ msgstr "開始WiFi連線"
#~ msgid "Restoring Left extruder temperature, this may take some time"
#~ msgstr "恢復左擠出機溫度,這可能需要一些時間"
@ -1216,9 +1285,6 @@ msgstr[0] "秒"
#~ msgid "Hidden"
#~ msgstr "隱藏的"
#~ msgid "Print Time"
#~ msgstr "列印時間"
#~ msgid "dBm"
#~ msgstr "dBm"
@ -1390,6 +1456,3 @@ msgstr[0] "秒"
#~ msgid "Load Average"
#~ msgstr "平均负载"
#~ msgid "Screen Blanking Time"
#~ msgstr "关闭屏幕时间"

View File

@ -230,6 +230,10 @@ class SdbusNm:
else None
)
def get_signal_strength(self):
ap = self.get_connected_ap()
return ap.strength if ap else None
def get_security_type(self, ssid):
return next(
(

165
ks_includes/sdbus_reg.py Normal file
View File

@ -0,0 +1,165 @@
from asyncio import new_event_loop, run_coroutine_threadsafe
from threading import Thread
import ast
import logging
from typing import Optional
from sdbus import DbusInterfaceCommonAsync, dbus_method_async, dbus_property_async
logger = logging.getLogger(__name__)
class RegistrationInterface(DbusInterfaceCommonAsync, interface_name="org.registration.interface"):
def __init__(self, bus_name: str, object_path: str):
super().__init__()
self.proxy = self.new_proxy(bus_name, object_path)
@dbus_property_async(property_signature="s")
def get_unique_id(self) -> 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

View File

@ -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:

View File

@ -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):

View File

@ -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)

234
panels/license.py Normal file
View File

@ -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"<big>{self.title_text}</big>\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)

View File

@ -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()

View File

@ -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)

View File

@ -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))

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.4 25.6C37 21.2 33.6 18.2 29 18H26C25.8 15.4 26.8 11.8 31 11.4C32.8 11.2 34.4 11.6 35.8 13C37 14.4 37.4 16 37.4 17.8C37.4 18 37.4 18.4 37.6 18.6L39.4 20.4C40 18.6 40 16.8 39.6 15C38.6 11.4 35.6 9.2 31.8 9C29.6 9 27.6 9.4 26 11C23.2 13.6 22.2 19.4 26.2 23.2C26.8 23.8 27.6 24.2 28.4 24.6C29.4 25 30.2 24.2 30.2 23.4C30.2 22.6 29.8 22 28.8 21.8C28.6 21.8 28.4 21.6 28.2 21.4V21.2C29 21 30 21.2 30.6 21.8C31.2 22.4 31.4 23.2 31.2 24.2C30.8 25.6 29.6 26.2 28 25.8C25.8 25.2 24.4 23.8 23.4 21.8C23.2 21.2 22.8 20.6 22.6 20C21.2 21.4 20 22.8 19.6 24.8C18.8 28.4 19.8 31.4 22.4 33.8C22.6 34 22.8 34.2 22.6 34.6C22.4 36.2 22.2 37.6 21.8 39.2C21.2 42.6 20.8 45.8 20.2 49.2C20 50.2 20 51.4 20.2 52.4C20.4 53.2 20.8 53.8 21.8 54.2C22 53.4 22 52.6 22.2 51.8C22.8 48.4 23.2 45.2 23.8 41.8C24.2 39.8 24.4 37.6 24.8 35.6C24.8 35.2 25 35 25.4 35C25.8 35 25.8 35.4 25.8 35.8C25.6 37.8 25.2 39.8 24.8 41.6C24.2 45.8 23.4 50 22.8 54.2C22.8 54.4 23 54.6 23 54.6C24 54.6 24.8 54.8 25.8 54.8C26.8 54.8 27.6 54 28.2 53.2C28.8 52.6 28.8 51.8 28.4 51C28.2 50.8 28 50.6 28 50.4C27.4 49.6 27.6 49 28.4 48.6C28.6 48.4 29 48.4 29.2 48.2C29.6 48 29.8 47.4 29.4 47C28.8 46.4 28.4 45.8 27.8 45.4C27.4 45 27.6 44.2 28 44C28.6 43.6 29.4 43.4 30 43C30.4 42.8 30.6 42.4 30.2 42C30 41.8 29.8 41.6 29.6 41.2C29 40.6 29.2 39.6 29.8 39.2C30 39 30.2 38.8 30.6 38.8C31 38.6 31.2 38.4 31.2 38C31.2 37.4 31.4 36.8 31.4 36.2C31.4 36 31.6 35.6 31.8 35.4C32.4 34.8 33.2 34.4 34 34C36.8 32 37.8 29 37.4 25.6ZM44.6 48.4C44.6 48 44.4 47.6 43.8 47.6C43.4 47.6 42.8 47.6 42.6 47C42.4 46.4 42.4 45.8 42.6 45.2C43 44.4 42.8 43.8 41.8 43.6C41.4 43.6 41 43.4 40.4 43.4C40 43.4 39.8 43.2 39.8 42.8C39.8 42.4 39.6 42 39.6 41.6C39.6 41 40 40.6 40.4 40.2C40.8 39.6 40.8 39.4 40.2 39C40 38.8 39.6 38.6 39.4 38.4C38.6 38 38.6 37.6 39 36.8C39.2 36.6 39.4 36.4 39.4 36.2C39.8 35.8 39.8 35.4 39.6 35C39.4 34.6 39.2 34 38.8 33.6C38.6 33.2 38.6 32.8 38.8 32.4C40.4 29.2 40.8 26 39.4 22.6C38.4 20.4 36.6 19 34.2 18.4L34 18.6C34.2 18.8 34.4 19.2 34.6 19.4C37 21.4 38.4 24 38.4 27.2C38.4 30.6 37 33.4 34 35.6C33.6 35.8 33.4 36.2 33.6 36.6C34.4 38.6 35 40.4 35.8 42.4C37.4 46.2 39 49.8 40.6 53.6C40.8 53.8 40.8 54 41.2 54C43.6 52.8 45 50.6 44.6 48.4ZM32.6 36.6C32.4 37.4 32.4 38 32.2 38.6C32.2 39 32 39.4 32.2 39.8C33.6 43.2 35 46.8 36.4 50.2C36.8 51.2 37.4 52.2 38.4 53C38.8 53.2 39.2 53.4 39.4 53.6L39.6 53.4C37.4 47.8 35 42.2 32.6 36.6Z" fill="#DBCD68"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,3 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.4 25.6C37 21.2 33.6 18.2 29 18H26C25.8 15.4 26.8 11.8 31 11.4C32.8 11.2 34.4 11.6 35.8 13C37 14.4 37.4 16 37.4 17.8C37.4 18 37.4 18.4 37.6 18.6L39.4 20.4C40 18.6 40 16.8 39.6 15C38.6 11.4 35.6 9.2 31.8 9C29.6 9 27.6 9.4 26 11C23.2 13.6 22.2 19.4 26.2 23.2C26.8 23.8 27.6 24.2 28.4 24.6C29.4 25 30.2 24.2 30.2 23.4C30.2 22.6 29.8 22 28.8 21.8C28.6 21.8 28.4 21.6 28.2 21.4V21.2C29 21 30 21.2 30.6 21.8C31.2 22.4 31.4 23.2 31.2 24.2C30.8 25.6 29.6 26.2 28 25.8C25.8 25.2 24.4 23.8 23.4 21.8C23.2 21.2 22.8 20.6 22.6 20C21.2 21.4 20 22.8 19.6 24.8C18.8 28.4 19.8 31.4 22.4 33.8C22.6 34 22.8 34.2 22.6 34.6C22.4 36.2 22.2 37.6 21.8 39.2C21.2 42.6 20.8 45.8 20.2 49.2C20 50.2 20 51.4 20.2 52.4C20.4 53.2 20.8 53.8 21.8 54.2C22 53.4 22 52.6 22.2 51.8C22.8 48.4 23.2 45.2 23.8 41.8C24.2 39.8 24.4 37.6 24.8 35.6C24.8 35.2 25 35 25.4 35C25.8 35 25.8 35.4 25.8 35.8C25.6 37.8 25.2 39.8 24.8 41.6C24.2 45.8 23.4 50 22.8 54.2C22.8 54.4 23 54.6 23 54.6C24 54.6 24.8 54.8 25.8 54.8C26.8 54.8 27.6 54 28.2 53.2C28.8 52.6 28.8 51.8 28.4 51C28.2 50.8 28 50.6 28 50.4C27.4 49.6 27.6 49 28.4 48.6C28.6 48.4 29 48.4 29.2 48.2C29.6 48 29.8 47.4 29.4 47C28.8 46.4 28.4 45.8 27.8 45.4C27.4 45 27.6 44.2 28 44C28.6 43.6 29.4 43.4 30 43C30.4 42.8 30.6 42.4 30.2 42C30 41.8 29.8 41.6 29.6 41.2C29 40.6 29.2 39.6 29.8 39.2C30 39 30.2 38.8 30.6 38.8C31 38.6 31.2 38.4 31.2 38C31.2 37.4 31.4 36.8 31.4 36.2C31.4 36 31.6 35.6 31.8 35.4C32.4 34.8 33.2 34.4 34 34C36.8 32 37.8 29 37.4 25.6ZM44.6 48.4C44.6 48 44.4 47.6 43.8 47.6C43.4 47.6 42.8 47.6 42.6 47C42.4 46.4 42.4 45.8 42.6 45.2C43 44.4 42.8 43.8 41.8 43.6C41.4 43.6 41 43.4 40.4 43.4C40 43.4 39.8 43.2 39.8 42.8C39.8 42.4 39.6 42 39.6 41.6C39.6 41 40 40.6 40.4 40.2C40.8 39.6 40.8 39.4 40.2 39C40 38.8 39.6 38.6 39.4 38.4C38.6 38 38.6 37.6 39 36.8C39.2 36.6 39.4 36.4 39.4 36.2C39.8 35.8 39.8 35.4 39.6 35C39.4 34.6 39.2 34 38.8 33.6C38.6 33.2 38.6 32.8 38.8 32.4C40.4 29.2 40.8 26 39.4 22.6C38.4 20.4 36.6 19 34.2 18.4L34 18.6C34.2 18.8 34.4 19.2 34.6 19.4C37 21.4 38.4 24 38.4 27.2C38.4 30.6 37 33.4 34 35.6C33.6 35.8 33.4 36.2 33.6 36.6C34.4 38.6 35 40.4 35.8 42.4C37.4 46.2 39 49.8 40.6 53.6C40.8 53.8 40.8 54 41.2 54C43.6 52.8 45 50.6 44.6 48.4ZM32.6 36.6C32.4 37.4 32.4 38 32.2 38.6C32.2 39 32 39.4 32.2 39.8C33.6 43.2 35 46.8 36.4 50.2C36.8 51.2 37.4 52.2 38.4 53C38.8 53.2 39.2 53.4 39.4 53.6L39.6 53.4C37.4 47.8 35 42.2 32.6 36.6Z" fill="#DBCD68"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB