From 5047c6e1dcaa431cfaf3ca84273c57f27ee2e726 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 06:05:22 +0800 Subject: [PATCH 1/9] =?UTF-8?q?chore:=20Windows=20nuitka=20=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=84=9A=E6=9C=AC=EF=BC=88=E9=9D=9E=E6=89=93=E5=8C=85?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 6 +- build.py | 398 ++++++++++++++++++++++-------------- main.py | 9 +- pyproject.toml | 7 +- uv.lock | 65 ++++-- 5 files changed, 302 insertions(+), 183 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ecb58a8..d7ac8863 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,16 +57,16 @@ jobs: - name: Install dependencies run: | uv sync --frozen - uv pip install pyinstaller + uv pip install nuitka - name: Build Windows executable - run: uv run python build.py + run: uv run python build.py --backend nuitka - name: Upload Windows artifact uses: actions/upload-artifact@v4 with: name: AssignSticker-windows-x64 - path: dist/AssignSticker.exe + path: release/AssignSticker-windows-x64-*.zip if-no-files-found: error build-linux: diff --git a/build.py b/build.py index 23f14c9e..6e37f658 100644 --- a/build.py +++ b/build.py @@ -2,232 +2,314 @@ # -*- coding: utf-8 -*- """ AssignSticker 打包脚本 -使用 PyInstaller 打包成 Windows x64 可执行文件 -打包项目根目录下的所有内容(排除特定文件和目录) + +默认使用 Nuitka(standalone)打包,保留 PyInstaller 作为回退方案: + python build.py + python build.py --backend nuitka --onefile + python build.py --backend pyinstaller """ +import argparse import os -import sys import shutil import subprocess -from pathlib import Path +import sys from datetime import datetime +from pathlib import Path -# 设置 Windows 控制台编码为 UTF-8 -if sys.platform == 'win32': - import io - sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') - sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') +if sys.platform == "win32": + import io -def get_project_root(): - """获取项目根目录""" + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") + + +APP_NAME = "AssignSticker" +MAIN_SCRIPT = "main.py" + +EXCLUDE_DIRS = { + "__pycache__", + "build", + "dist", + "release", + "logs", + "data", + ".git", + ".github", + ".vscode", + ".idea", + ".ruff_cache", + ".venv", + "venv", + "env", + ".codex", + "scripts", +} +EXCLUDE_FILES = { + "build.py", + ".gitignore", + ".python-version", +} +EXCLUDE_SUFFIXES = { + ".py", + ".pyc", + ".pyo", + ".spec", + ".md", + ".toml", + ".lock", +} + + +def get_project_root() -> Path: return Path(__file__).parent.absolute() -def clean_build_dirs(): - """清理构建目录""" +def clean_build_dirs() -> None: print("清理构建目录...") - dirs_to_clean = ['build', 'dist', '__pycache__'] - for dir_name in dirs_to_clean: - dir_path = get_project_root() / dir_name + project_root = get_project_root() + for dir_name in ["build", "dist", "__pycache__"]: + dir_path = project_root / dir_name if dir_path.exists(): shutil.rmtree(dir_path) print(f" 已删除: {dir_name}") -def get_all_data_files(): - """获取所有需要打包的数据文件和目录(自动扫描根目录)""" +def collect_data_entries_nuitka() -> list[str]: project_root = get_project_root() + args: list[str] = [] - # 定义需要排除的文件和目录 - exclude_items = { - # Python 相关 - 'build.py', # 本打包脚本 - 'build', # 构建目录 - 'dist', # 输出目录 - '__pycache__', # Python 缓存 - '*.pyc', # 编译后的 Python 文件 - '*.pyo', # 优化的 Python 文件 - '.git', # Git 目录 - '.gitignore', # Git 忽略文件 - '.github', # GitHub 工作流目录 - 'release', # 发布目录 - 'logs', # 日志目录 - 'data', # 运行时数据目录 - # IDE 相关 - '.vscode', # VS Code 配置 - '.idea', # PyCharm 配置 - '*.spec', # PyInstaller spec 文件 - } - - datas = [] - - print("\n扫描项目根目录...") - - # 遍历项目根目录下的所有项目 - for item in project_root.iterdir(): - item_name = item.name - - # 检查是否在排除列表中 - if item_name in exclude_items: - print(f" 排除: {item_name}") - continue + print("\n扫描并收集资源(Nuitka)...") + for item in sorted(project_root.iterdir(), key=lambda p: p.name.lower()): + name = item.name - # 跳过隐藏文件(以.开头) - if item_name.startswith('.') and item_name != '.': - print(f" 排除隐藏项: {item_name}") + if name in EXCLUDE_FILES or name in EXCLUDE_DIRS: + print(f" 排除: {name}") continue - - # 跳过 Python 缓存文件 - if item_name.endswith(('.pyc', '.pyo', '.spec')): - print(f" 排除缓存: {item_name}") + if name.startswith("."): + print(f" 排除隐藏项: {name}") continue if item.is_dir(): - # 目录: --add-data=目录名;目录名 - datas.append(f"--add-data={item_name};{item_name}") - print(f" 包含目录: {item_name}") - elif item.is_file(): - # 文件: --add-data=文件名;. - datas.append(f"--add-data={item_name};.") - print(f" 包含文件: {item_name}") + args.append(f"--include-data-dir={name}={name}") + print(f" 包含目录: {name}") + continue + + if item.suffix.lower() in EXCLUDE_SUFFIXES: + print(f" 排除文件: {name}") + continue - return datas + args.append(f"--include-data-file={name}={name}") + print(f" 包含文件: {name}") + return args -def build_exe(): - """构建可执行文件""" - print("\n" + "="*60) - print("开始构建 AssignSticker Windows 可执行文件") - print("="*60 + "\n") +def collect_data_entries_pyinstaller() -> list[str]: project_root = get_project_root() - os.chdir(project_root) + args: list[str] = [] - # 清理旧的构建文件 - clean_build_dirs() + print("\n扫描并收集资源(PyInstaller)...") + for item in sorted(project_root.iterdir(), key=lambda p: p.name.lower()): + name = item.name + + if name in EXCLUDE_FILES or name in EXCLUDE_DIRS: + print(f" 排除: {name}") + continue + if name.startswith("."): + print(f" 排除隐藏项: {name}") + continue - # 获取数据文件参数 - print("\n收集数据文件...") - data_args = get_all_data_files() + if item.is_dir(): + args.append(f"--add-data={name};{name}") + print(f" 包含目录: {name}") + continue - # 构建 PyInstaller 命令 - cmd = [ - sys.executable, '-m', 'PyInstaller', - '--onefile', # 单文件模式 - '--windowed', # 窗口模式(无控制台) - '--name=AssignSticker', # 输出文件名 - '--icon=icon.ico', # 图标文件 - '--clean', # 清理临时文件 - '--noconfirm', # 不确认覆盖 - ] + if item.suffix.lower() in EXCLUDE_SUFFIXES: + print(f" 排除文件: {name}") + continue - # 添加数据文件参数 - cmd.extend(data_args) + args.append(f"--add-data={name};.") + print(f" 包含文件: {name}") - # 添加主程序文件 - cmd.append('main.py') + return args - print("\n执行命令:") - print(' '.join(cmd)) - print() - # 执行构建 +def ensure_backend_installed(backend: str) -> bool: try: - result = subprocess.run(cmd, check=True, capture_output=False) - print("\n" + "="*60) - print("构建成功!") - print("="*60) + if backend == "nuitka": + import nuitka # noqa: F401 + print(f"Nuitka 已安装") + else: + import PyInstaller # noqa: F401 + print(f"PyInstaller 已安装") return True - except subprocess.CalledProcessError as e: - print("\n" + "="*60) - print("构建失败!") - print("="*60) - print(f"错误代码: {e.returncode}") + except ImportError: + if backend == "nuitka": + print("错误: 未安装 Nuitka,请先执行: uv pip install nuitka") + else: + print("错误: 未安装 PyInstaller,请先执行: uv pip install pyinstaller") return False -def create_distribution(): - """创建发布包(复制dist目录下的exe文件)""" - print("\n创建发布包...") +def run_nuitka(onefile: bool) -> Path: + data_args = collect_data_entries_nuitka() + mode_arg = "--onefile" if onefile else "--standalone" + + cmd = [ + sys.executable, + "-m", + "nuitka", + mode_arg, + "--windows-console-mode=disable", + f"--output-filename={APP_NAME}", + "--windows-icon-from-ico=icon.ico", + "--enable-plugin=tk-inter", + "--assume-yes-for-downloads", + "--include-module=webview.platforms.winforms", + "--include-module=webview.platforms.edgechromium", + "--include-module=webview.platforms.mshtml", + "--include-module=pystray._win32", + "--output-dir=dist", + ] + cmd.extend(data_args) + cmd.append(MAIN_SCRIPT) + print("\n执行命令:") + print(" ".join(cmd)) + subprocess.run(cmd, check=True, capture_output=False) + + dist_root = get_project_root() / "dist" + if onefile: + return dist_root / f"{APP_NAME}.exe" + + dist_dirs = sorted(dist_root.glob("*.dist"), key=lambda p: p.stat().st_mtime, reverse=True) + for folder in dist_dirs: + if (folder / f"{APP_NAME}.exe").exists(): + return folder + if dist_dirs: + return dist_dirs[0] + raise FileNotFoundError("Nuitka 未生成 *.dist 目录") + + +def run_pyinstaller() -> Path: + data_args = collect_data_entries_pyinstaller() + cmd = [ + sys.executable, + "-m", + "PyInstaller", + "--onefile", + "--windowed", + f"--name={APP_NAME}", + "--icon=icon.ico", + "--clean", + "--noconfirm", + ] + cmd.extend(data_args) + cmd.append(MAIN_SCRIPT) + + print("\n执行命令:") + print(" ".join(cmd)) + subprocess.run(cmd, check=True, capture_output=False) + return get_project_root() / "dist" / f"{APP_NAME}.exe" + + +def create_release_package(build_output: Path) -> Path: + print("\n创建发布包...") project_root = get_project_root() - dist_dir = project_root / 'dist' - release_dir = project_root / 'release' / 'AssignSticker-windows' + release_dir = project_root / "release" / "AssignSticker-windows" - # 创建发布目录 if release_dir.exists(): shutil.rmtree(release_dir) release_dir.mkdir(parents=True, exist_ok=True) - # 复制可执行文件 - exe_source = dist_dir / 'AssignSticker.exe' - exe_target = release_dir / 'AssignSticker.exe' - - if exe_source.exists(): - shutil.copy2(exe_source, exe_target) - print(f" 复制: {exe_source.name}") + if build_output.is_dir(): + target_dir = release_dir / build_output.name + shutil.copytree(build_output, target_dir) + print(f" 复制目录: {build_output.name}") + elif build_output.exists(): + shutil.copy2(build_output, release_dir / build_output.name) + print(f" 复制文件: {build_output.name}") else: - print(f" 错误: 找不到 {exe_source}") - return False + raise FileNotFoundError(f"找不到构建产物: {build_output}") - # 创建 ZIP 压缩包 - print("\n创建 ZIP 压缩包...") - timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") zip_name = f"AssignSticker-windows-x64-{timestamp}" - zip_path = project_root / 'release' / zip_name - - try: - shutil.make_archive(str(zip_path), 'zip', str(dist_dir)) - print(f" 创建: {zip_name}.zip") - except Exception as e: - print(f" 错误创建 ZIP: {e}") - return False - - print(f"\n发布包位置: {zip_path}.zip") - return True + zip_path = project_root / "release" / zip_name + shutil.make_archive(str(zip_path), "zip", root_dir=str(release_dir)) + print(f" 创建压缩包: {zip_name}.zip") + return zip_path.with_suffix(".zip") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="AssignSticker 打包工具") + parser.add_argument( + "--backend", + choices=["nuitka", "pyinstaller"], + default="nuitka", + help="打包后端,默认 nuitka", + ) + parser.add_argument( + "--onefile", + action="store_true", + help="仅对 nuitka 生效,启用 onefile 模式(默认 standalone)", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() - -def main(): - """主函数""" print("AssignSticker 打包工具") print(f"Python: {sys.version}") print(f"平台: {sys.platform}") + print(f"后端: {args.backend}") + if args.backend == "nuitka": + print(f"Nuitka 模式: {'onefile' if args.onefile else 'standalone'}") print() - if sys.platform != 'win32': - print("警告: 此脚本设计用于 Windows 平台") - print("在 Linux/macOS 上构建的 EXE 将无法在 Windows 上运行") + if sys.platform != "win32": + print("警告: 此脚本主要用于 Windows 构建 EXE") + print("在 Linux/macOS 上构建的 EXE 无法直接在 Windows 运行") response = input("是否继续? (y/N): ") - if response.lower() != 'y': + if response.lower() != "y": print("已取消") return - # 检查 PyInstaller + if not ensure_backend_installed(args.backend): + sys.exit(1) + + project_root = get_project_root() + os.chdir(project_root) + clean_build_dirs() + try: - import PyInstaller - print(f"PyInstaller 版本: {PyInstaller.__version__}") - except ImportError: - print("错误: 未安装 PyInstaller") - print("请运行: pip install pyinstaller") - return + print("\n" + "=" * 60) + print(f"开始构建 {APP_NAME} Windows 可执行文件") + print("=" * 60) - # 构建 - if build_exe(): - # 创建发布包 - create_distribution() + if args.backend == "nuitka": + build_output = run_nuitka(onefile=args.onefile) + else: + build_output = run_pyinstaller() - print("\n" + "="*60) + zip_file = create_release_package(build_output) + + print("\n" + "=" * 60) print("打包完成!") - print("="*60) - print(f"\n可执行文件: dist/AssignSticker.exe") - print(f"发布包: release/AssignSticker-windows-x64-*.zip") - print("\n注意: 所有资源文件已通过 --add-data 打包到exe内部") - else: + print("=" * 60) + print(f"\n构建产物: {build_output}") + print(f"发布包: {zip_file}") + except subprocess.CalledProcessError as e: print("\n打包失败,请检查错误信息") + print(f"错误代码: {e.returncode}") + sys.exit(1) + except Exception as e: + print("\n打包失败") + print(f"错误: {e}") sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/main.py b/main.py index a16d562a..0ba7e89f 100644 --- a/main.py +++ b/main.py @@ -1271,13 +1271,16 @@ def setAutoStart(self, enabled): elif system == 'Windows': import winreg key_path = r'Software\Microsoft\Windows\CurrentVersion\Run' - app_path = os.path.dirname(os.path.abspath(__file__)) - exe_path = os.path.join(app_path, 'main.py') + is_frozen = getattr(sys, 'frozen', False) or '__compiled__' in globals() + if is_frozen: + startup_cmd = f'"{sys.executable}" --restart' + else: + startup_cmd = f'"{sys.executable}" "{os.path.abspath(__file__)}" --restart' try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE) if enabled: - winreg.SetValueEx(key, 'AssignSticker', 0, winreg.REG_SZ, f'python "{exe_path}"') + winreg.SetValueEx(key, 'AssignSticker', 0, winreg.REG_SZ, startup_cmd) log("已启用开机自启动", "info") else: try: diff --git a/pyproject.toml b/pyproject.toml index 611a870f..0d8e1fec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "assignsticker" version = "1.2.0" description = "一款简洁,易用的作业板" readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.12" dependencies = [ "pillow>=12.1.1", "pygobject>=3.56.0 ; sys_platform == 'linux'", @@ -16,3 +16,8 @@ dependencies = [ url = "https://pypi.tuna.tsinghua.edu.cn/simple/" # 设置为默认索引 default = true + +[dependency-groups] +dev = [ + "nuitka>=4.0.7", +] diff --git a/uv.lock b/uv.lock index 6bbe7c7f..5547b327 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.13" -resolution-markers = [ - "sys_platform == 'linux'", - "sys_platform != 'linux'", -] +requires-python = ">=3.12" [[package]] name = "assignsticker" @@ -17,6 +13,11 @@ dependencies = [ { name = "pywebview" }, ] +[package.dev-dependencies] +dev = [ + { name = "nuitka" }, +] + [package.metadata] requires-dist = [ { name = "pillow", specifier = ">=12.1.1" }, @@ -25,6 +26,9 @@ requires-dist = [ { name = "pywebview", specifier = "==4.4.1" }, ] +[package.metadata.requires-dev] +dev = [{ name = "nuitka", specifier = ">=4.0.7" }] + [[package]] name = "bottle" version = "0.13.4" @@ -39,10 +43,13 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy' and sys_platform != 'linux'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, @@ -59,13 +66,19 @@ name = "clr-loader" version = "0.2.10" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "cffi", marker = "sys_platform != 'linux'" }, + { name = "cffi" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" } wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/61/cf819f8e8bb4d4c74661acf2498ba8d4a296714be3478d21eaabf64f5b9b/clr_loader-0.2.10-py3-none-any.whl", hash = "sha256:ebbbf9d511a7fe95fa28a95a4e04cd195b097881dfe66158dc2c281d3536f282", size = 56483, upload-time = "2026-01-03T23:13:05.439Z" }, ] +[[package]] +name = "nuitka" +version = "4.0.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/0e/2537066db2458843e4a1edfc524409069ad58a456fc4b312b18b1d327431/nuitka-4.0.7.tar.gz", hash = "sha256:26543bfed6009a466ae8608bdc643add81a497e8662e4aaf157cd00aa7fd5b9f", size = 4421537, upload-time = "2026-03-24T13:26:43.368Z" } + [[package]] name = "packaging" version = "26.0" @@ -81,6 +94,17 @@ version = "12.1.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, @@ -159,7 +183,7 @@ name = "pygobject" version = "3.56.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pycairo", marker = "sys_platform == 'linux'" }, + { name = "pycairo" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/86/b87f8d9ecb01effa2b17319b9b0a8852c29374ab7c5268a44597cb1605c7/pygobject-3.56.0.tar.gz", hash = "sha256:4fbb5bf47524e01026f8e309dd54233eb0f75f2281392c5bf0df5d9041cc7891", size = 1407051, upload-time = "2026-02-27T10:02:25.583Z" } @@ -169,6 +193,7 @@ version = "12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" }, @@ -180,10 +205,11 @@ name = "pyobjc-framework-cocoa" version = "12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform != 'linux'" }, + { name = "pyobjc-core" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" }, @@ -195,11 +221,12 @@ name = "pyobjc-framework-quartz" version = "12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform != 'linux'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" }, @@ -211,11 +238,12 @@ name = "pyobjc-framework-security" version = "12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform != 'linux'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284, upload-time = "2025-11-14T10:03:01.722Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162, upload-time = "2025-11-14T10:03:05.337Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348, upload-time = "2025-11-14T10:03:08.926Z" }, @@ -227,11 +255,12 @@ name = "pyobjc-framework-webkit" version = "12.1" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform != 'linux'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform != 'linux'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096, upload-time = "2025-11-14T10:07:10.027Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572, upload-time = "2025-11-14T10:07:14.232Z" }, { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210, upload-time = "2025-11-14T10:07:18.926Z" }, @@ -257,7 +286,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "six", marker = "sys_platform == 'linux'" }, + { name = "six" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } wheels = [ @@ -269,7 +298,7 @@ name = "pythonnet" version = "3.0.5" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "clr-loader", marker = "sys_platform != 'linux'" }, + { name = "clr-loader" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" } wheels = [ @@ -301,7 +330,7 @@ name = "qtpy" version = "2.4.3" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple/" } dependencies = [ - { name = "packaging", marker = "sys_platform != 'linux'" }, + { name = "packaging" }, ] sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" } wheels = [ From 1ef8e73a8622f7c7cac3318e22e00d93fa2105ef Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 06:22:29 +0800 Subject: [PATCH 2/9] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0linux=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E5=92=8C=E6=89=93=E5=8C=85=E8=84=9A=E6=9C=AC=20using?= =?UTF-8?q?=20nuitka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.py | 116 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 96 insertions(+), 20 deletions(-) diff --git a/build.py b/build.py index 6e37f658..6a4307f0 100644 --- a/build.py +++ b/build.py @@ -3,7 +3,7 @@ """ AssignSticker 打包脚本 -默认使用 Nuitka(standalone)打包,保留 PyInstaller 作为回退方案: +默认使用 Nuitka(standalone)打包,支持 Windows/Linux: python build.py python build.py --backend nuitka --onefile python build.py --backend pyinstaller @@ -62,6 +62,31 @@ } +def get_platform_key() -> str: + if sys.platform == "win32": + return "windows" + if sys.platform.startswith("linux"): + return "linux" + if sys.platform == "darwin": + return "macos" + return sys.platform + + +def get_arch_label() -> str: + machine = os.uname().machine.lower() if hasattr(os, "uname") else "" + if not machine and sys.platform == "win32": + machine = os.environ.get("PROCESSOR_ARCHITECTURE", "").lower() + + aliases = { + "amd64": "x64", + "x86_64": "x64", + "x64": "x64", + "arm64": "arm64", + "aarch64": "arm64", + } + return aliases.get(machine, machine or "unknown") + + def get_project_root() -> Path: return Path(__file__).parent.absolute() @@ -109,6 +134,7 @@ def collect_data_entries_nuitka() -> list[str]: def collect_data_entries_pyinstaller() -> list[str]: project_root = get_project_root() args: list[str] = [] + separator = ";" if sys.platform == "win32" else ":" print("\n扫描并收集资源(PyInstaller)...") for item in sorted(project_root.iterdir(), key=lambda p: p.name.lower()): @@ -122,7 +148,7 @@ def collect_data_entries_pyinstaller() -> list[str]: continue if item.is_dir(): - args.append(f"--add-data={name};{name}") + args.append(f"--add-data={name}{separator}{name}") print(f" 包含目录: {name}") continue @@ -130,7 +156,7 @@ def collect_data_entries_pyinstaller() -> list[str]: print(f" 排除文件: {name}") continue - args.append(f"--add-data={name};.") + args.append(f"--add-data={name}{separator}.") print(f" 包含文件: {name}") return args @@ -140,9 +166,11 @@ def ensure_backend_installed(backend: str) -> bool: try: if backend == "nuitka": import nuitka # noqa: F401 + print(f"Nuitka 已安装") else: import PyInstaller # noqa: F401 + print(f"PyInstaller 已安装") return True except ImportError: @@ -156,23 +184,41 @@ def ensure_backend_installed(backend: str) -> bool: def run_nuitka(onefile: bool) -> Path: data_args = collect_data_entries_nuitka() mode_arg = "--onefile" if onefile else "--standalone" + platform_key = get_platform_key() cmd = [ sys.executable, "-m", "nuitka", mode_arg, - "--windows-console-mode=disable", f"--output-filename={APP_NAME}", - "--windows-icon-from-ico=icon.ico", "--enable-plugin=tk-inter", "--assume-yes-for-downloads", - "--include-module=webview.platforms.winforms", - "--include-module=webview.platforms.edgechromium", - "--include-module=webview.platforms.mshtml", - "--include-module=pystray._win32", "--output-dir=dist", ] + + if platform_key == "windows": + cmd.extend( + [ + "--windows-console-mode=disable", + "--windows-icon-from-ico=icon.ico", + "--include-module=webview.platforms.winforms", + "--include-module=webview.platforms.edgechromium", + "--include-module=webview.platforms.mshtml", + "--include-module=pystray._win32", + ] + ) + elif platform_key == "linux": + cmd.extend( + [ + "--include-module=webview.platforms.gtk", + "--include-module=pystray._appindicator", + "--include-module=pystray._gtk", + ] + ) + else: + print(f"提示: 当前平台 {platform_key} 未设置专用 Nuitka 模块参数,使用通用配置") + cmd.extend(data_args) cmd.append(MAIN_SCRIPT) @@ -182,11 +228,31 @@ def run_nuitka(onefile: bool) -> Path: dist_root = get_project_root() / "dist" if onefile: - return dist_root / f"{APP_NAME}.exe" - - dist_dirs = sorted(dist_root.glob("*.dist"), key=lambda p: p.stat().st_mtime, reverse=True) + onefile_candidates = [ + dist_root / f"{APP_NAME}.exe", + dist_root / APP_NAME, + dist_root / f"{APP_NAME}.bin", + ] + for candidate in onefile_candidates: + if candidate.exists(): + return candidate + recent_files = sorted( + [p for p in dist_root.iterdir() if p.is_file()], + key=lambda p: p.stat().st_mtime, + reverse=True, + ) + if recent_files: + return recent_files[0] + raise FileNotFoundError("Nuitka 未生成 onefile 产物") + + dist_dirs = sorted( + dist_root.glob("*.dist"), key=lambda p: p.stat().st_mtime, reverse=True + ) for folder in dist_dirs: - if (folder / f"{APP_NAME}.exe").exists(): + if any( + (folder / name).exists() + for name in (f"{APP_NAME}.exe", APP_NAME, f"{APP_NAME}.bin") + ): return folder if dist_dirs: return dist_dirs[0] @@ -212,13 +278,23 @@ def run_pyinstaller() -> Path: print("\n执行命令:") print(" ".join(cmd)) subprocess.run(cmd, check=True, capture_output=False) - return get_project_root() / "dist" / f"{APP_NAME}.exe" + dist_root = get_project_root() / "dist" + candidates = [ + dist_root / f"{APP_NAME}.exe", + dist_root / APP_NAME, + ] + for candidate in candidates: + if candidate.exists(): + return candidate + raise FileNotFoundError("PyInstaller 未生成预期可执行文件") def create_release_package(build_output: Path) -> Path: print("\n创建发布包...") project_root = get_project_root() - release_dir = project_root / "release" / "AssignSticker-windows" + platform_key = get_platform_key() + arch = get_arch_label() + release_dir = project_root / "release" / f"{APP_NAME}-{platform_key}" if release_dir.exists(): shutil.rmtree(release_dir) @@ -235,7 +311,7 @@ def create_release_package(build_output: Path) -> Path: raise FileNotFoundError(f"找不到构建产物: {build_output}") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - zip_name = f"AssignSticker-windows-x64-{timestamp}" + zip_name = f"{APP_NAME}-{platform_key}-{arch}-{timestamp}" zip_path = project_root / "release" / zip_name shutil.make_archive(str(zip_path), "zip", root_dir=str(release_dir)) print(f" 创建压缩包: {zip_name}.zip") @@ -260,6 +336,7 @@ def parse_args() -> argparse.Namespace: def main() -> None: args = parse_args() + platform_key = get_platform_key() print("AssignSticker 打包工具") print(f"Python: {sys.version}") @@ -269,9 +346,8 @@ def main() -> None: print(f"Nuitka 模式: {'onefile' if args.onefile else 'standalone'}") print() - if sys.platform != "win32": - print("警告: 此脚本主要用于 Windows 构建 EXE") - print("在 Linux/macOS 上构建的 EXE 无法直接在 Windows 运行") + if platform_key not in {"windows", "linux"}: + print(f"警告: 当前平台 {platform_key} 未正式支持,可能打包失败") response = input("是否继续? (y/N): ") if response.lower() != "y": print("已取消") @@ -286,7 +362,7 @@ def main() -> None: try: print("\n" + "=" * 60) - print(f"开始构建 {APP_NAME} Windows 可执行文件") + print(f"开始构建 {APP_NAME} {platform_key} 可执行文件") print("=" * 60) if args.backend == "nuitka": From c3c42f99cd2ade0e56ce0ac9ae0df14cfe212e8d Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 06:26:23 +0800 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=93=E5=8C=85?= =?UTF-8?q?=E4=BA=A7=E7=89=A9=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/build.cpython-313.pyc | Bin 0 -> 16627 bytes build.py | 12 +++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 __pycache__/build.cpython-313.pyc diff --git a/__pycache__/build.cpython-313.pyc b/__pycache__/build.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08334a279ff254e581457f6f729b16eb7267bdfb GIT binary patch literal 16627 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 Dbiivt literal 0 HcmV?d00001 diff --git a/build.py b/build.py index 6a4307f0..5605342a 100644 --- a/build.py +++ b/build.py @@ -301,9 +301,15 @@ def create_release_package(build_output: Path) -> Path: release_dir.mkdir(parents=True, exist_ok=True) if build_output.is_dir(): - target_dir = release_dir / build_output.name - shutil.copytree(build_output, target_dir) - print(f" 复制目录: {build_output.name}") + copied_count = 0 + for item in build_output.iterdir(): + target = release_dir / item.name + if item.is_dir(): + shutil.copytree(item, target) + else: + shutil.copy2(item, target) + copied_count += 1 + print(f" 已平铺复制目录内容: {build_output.name} ({copied_count} 项)") elif build_output.exists(): shutil.copy2(build_output, release_dir / build_output.name) print(f" 复制文件: {build_output.name}") From a1da54e6a086d934778171bb5120bd5061ed8f5b Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 06:29:47 +0800 Subject: [PATCH 4/9] =?UTF-8?q?chore:=20=E5=A2=9E=E5=8A=A0gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 30fe27b2..06188f1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,40 @@ logs/* -.DS_Store \ No newline at end of file +.DS_Store +dist +release +.venv +*.pyc +*.pyo +__pycache__/ +*.pyd +*.pyw +*.egg-info +*.egg +*.manifest +*.spec +*.whl +*.dist-info +*.egg-info +*.egg +*.manifest +*.spec +*.whl +*.dist-info +*.egg-info +*.egg +*.manifest +*.spec +*.whl +*.dist-info +*.egg-info +*.egg +*.manifest +*.spec +*.whl +*.dist-info +*.egg-info +*.egg +*.manifest +*.spec +*.whl +*.dist-info \ No newline at end of file From a887efeef1ecc960ab4d7dad5045abb6696f4c0d Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 06:35:06 +0800 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20=E6=8E=92=E9=99=A4=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 06188f1b..1ab0513c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ logs/* dist release .venv +logs *.pyc *.pyo __pycache__/ From 3b0a0537bd2aebd01d26cc844e4dbb8a9efb279d Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 07:01:37 +0800 Subject: [PATCH 6/9] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0Linux=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=84=9A=E6=9C=AC=EF=BC=8C=E4=BF=AE=E6=AD=A3=E5=8C=85?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E5=B9=B6=E8=B0=83=E6=95=B4=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 170 +++--------------------------------- 1 file changed, 12 insertions(+), 158 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7ac8863..457af856 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ on: default: true type: boolean build_linux: - description: Build Linux x64 deb package + description: Build Linux x64 package required: true default: true type: boolean @@ -85,179 +85,33 @@ jobs: with: enable-cache: true - - name: Resolve app version - id: version - shell: bash - run: | - if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - VERSION="${GITHUB_REF#refs/tags/v}" - elif [[ -n "${{ inputs.release_tag }}" ]]; then - VERSION="${{ inputs.release_tag }}" - VERSION="${VERSION#v}" - else - VERSION="0.0.0-dev" - fi - echo "app_version=${VERSION}" >> "$GITHUB_OUTPUT" - - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y \ + patchelf \ python3-dev \ pkg-config \ libcairo2-dev \ - libgirepository-2.0-dev \ gir1.2-gtk-3.0 \ - gir1.2-webkit2-4.1 \ python3-gi \ python3-gi-cairo \ - libwebkit2gtk-4.1-0 \ - libayatana-appindicator3-1 \ - dpkg-dev + libayatana-appindicator3-1 + sudo apt-get install -y libwebkit2gtk-4.1-0 || sudo apt-get install -y libwebkit2gtk-4.0-37 + sudo apt-get install -y libgirepository-2.0-dev || sudo apt-get install -y libgirepository1.0-dev + sudo apt-get install -y gir1.2-webkit2-4.1 || sudo apt-get install -y gir1.2-webkit2-4.0 - - name: Build with PyInstaller - shell: bash + - name: Build Linux package run: | uv sync --frozen - uv pip install pyinstaller - - cat > assignsticker.spec << 'SPECEOF' - # -*- mode: python ; coding: utf-8 -*- - from PyInstaller.building.build_main import Analysis, PYZ, EXE - - block_cipher = None - - datas = [ - ('htmls', 'htmls'), - ('icons', 'icons'), - ('saying', 'saying'), - ('desktop_widgets', 'desktop_widgets'), - ('homeworktemple', 'homeworktemple'), - ('font.ttf', '.'), - ('icon.ico', '.'), - ('introduce', '.'), - ('banner.png', '.'), - ('作业模板YML格式标准.txt', '.'), - ] - - hiddenimports = [ - 'gi', - 'gi.repository', - 'gi.repository.Gtk', - 'gi.repository.Gdk', - 'gi.repository.GObject', - 'gi.repository.GLib', - 'gi.repository.Gio', - 'gi.repository.AyatanaAppIndicator3', - 'cairo', - 'webview', - 'webview.platforms.gtk', - 'pystray', - 'pystray._appindicator', - 'PIL', - 'PIL._imagingtk', - 'PIL._tkinter_finder', - ] - - a = Analysis( - ['main.py'], - pathex=[], - binaries=[], - datas=datas, - hiddenimports=hiddenimports, - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False, - ) - pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - - exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='assignsticker', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=False, - disable_windowed_traceback=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, - icon='icon.ico', - ) - SPECEOF - - uv run pyinstaller assignsticker.spec - - - name: Build DEB package - shell: bash - env: - APP_VERSION: ${{ steps.version.outputs.app_version }} - run: | - set -euo pipefail - PKG="build/deb/${APP_NAME}" - - mkdir -p "${PKG}/opt/${APP_NAME}" - mkdir -p "${PKG}/usr/bin" - mkdir -p "${PKG}/usr/share/applications" - mkdir -p "${PKG}/DEBIAN" - - cp -r dist/assignsticker/* "${PKG}/opt/${APP_NAME}/" - - cat > "${PKG}/usr/bin/${APP_NAME}" <<'LAUNCHEOF' - #!/bin/bash - export GI_TYPELIB_PATH=/usr/lib/x86_64-linux-gnu/girepository-1.0 - exec /opt/assignsticker/assignsticker "$@" - LAUNCHEOF - chmod +x "${PKG}/usr/bin/${APP_NAME}" - - cat > "${PKG}/usr/share/applications/${APP_NAME}.desktop" < "${PKG}/DEBIAN/control" < - Homepage: https://github.com/SECTL/AssignSticker - Description: Homework Showboard Application - AssignSticker is a homework management and display application for classroom use. - CONTROLEOF - - dpkg-deb --root-owner-group --build "${PKG}" - mv "build/deb/${APP_NAME}.deb" "AssignSticker-${APP_VERSION}-amd64.deb" + uv pip install nuitka + uv run python build.py --backend nuitka - name: Upload Linux artifact uses: actions/upload-artifact@v4 with: - name: AssignSticker-linux-amd64 - path: AssignSticker-*-amd64.deb + name: AssignSticker-linux-x64 + path: release/AssignSticker-linux-x64-*.zip if-no-files-found: error release: @@ -295,7 +149,7 @@ jobs: if: needs.build-linux.result == 'success' uses: actions/download-artifact@v4 with: - name: AssignSticker-linux-amd64 + name: AssignSticker-linux-x64 path: artifacts/linux - name: List release files From 5f2b50306e904fdf72ebd0cb3b6b6d52cd64dd51 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 07:39:12 +0800 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0Linux=20DEB?= =?UTF-8?q?=E5=8C=85=E6=9E=84=E5=BB=BA=E6=AD=A5=E9=AA=A4=E5=92=8C=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 457af856..67c0b443 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,6 +85,20 @@ jobs: with: enable-cache: true + - name: Resolve app version + id: version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then + VERSION="${GITHUB_REF#refs/tags/v}" + elif [[ -n "${{ inputs.release_tag }}" ]]; then + VERSION="${{ inputs.release_tag }}" + VERSION="${VERSION#v}" + else + VERSION="0.0.0-dev" + fi + echo "app_version=${VERSION}" >> "$GITHUB_OUTPUT" + - name: Install system dependencies run: | sudo apt-get update @@ -107,11 +121,80 @@ jobs: uv pip install nuitka uv run python build.py --backend nuitka + - name: Build DEB package + shell: bash + env: + APP_VERSION: ${{ steps.version.outputs.app_version }} + run: | + set -euo pipefail + DIST_DIR="$(find dist -maxdepth 1 -type d -name '*.dist' | head -n1)" + if [[ -z "${DIST_DIR}" ]]; then + echo "Cannot find Nuitka dist directory under ./dist" >&2 + exit 1 + fi + + PKG="build/deb/${APP_NAME}" + mkdir -p "${PKG}/opt/${APP_NAME}" "${PKG}/usr/bin" "${PKG}/usr/share/applications" "${PKG}/DEBIAN" + cp -a "${DIST_DIR}/." "${PKG}/opt/${APP_NAME}/" + + BIN_NAME="" + for c in AssignSticker assignsticker; do + if [[ -f "${PKG}/opt/${APP_NAME}/${c}" ]]; then + BIN_NAME="${c}" + break + fi + done + if [[ -z "${BIN_NAME}" ]]; then + BIN_NAME="$(find "${PKG}/opt/${APP_NAME}" -maxdepth 1 -type f -executable | head -n1 | xargs -r basename)" + fi + if [[ -z "${BIN_NAME}" ]]; then + echo "Cannot detect executable in ${PKG}/opt/${APP_NAME}" >&2 + exit 1 + fi + + cat > "${PKG}/usr/bin/${APP_NAME}" < "${PKG}/usr/share/applications/${APP_NAME}.desktop" < "${PKG}/DEBIAN/control" < + Homepage: https://github.com/SECTL/AssignSticker + Description: Homework Showboard Application + AssignSticker is a homework management and display application for classroom use. + CONTROLEOF + + dpkg-deb --root-owner-group --build "${PKG}" + mv "build/deb/${APP_NAME}.deb" "AssignSticker-${APP_VERSION}-amd64.deb" + - name: Upload Linux artifact uses: actions/upload-artifact@v4 with: name: AssignSticker-linux-x64 - path: release/AssignSticker-linux-x64-*.zip + path: | + release/AssignSticker-linux-x64-*.zip + AssignSticker-*-amd64.deb if-no-files-found: error release: From 3a9addca2febed8fa07f8af905e566ca4e2ddbb4 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 12:59:13 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DLinux=20webkit?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=B8=8B=E9=94=99=E8=AF=AF=E4=BD=BF=E7=94=A8?= =?UTF-8?q?localstorage=E4=BD=BF=E5=BE=97=E6=97=A0=E6=B3=95=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BD=9C=E4=B8=9A=20=E5=92=8C=20=E5=B4=A9=E6=BA=83?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=E5=BC=B9=E5=87=BA=E5=90=8E=E6=95=B4=E4=B8=AA?= =?UTF-8?q?app=E5=86=BB=E7=BB=93=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 1 + __pycache__/build.cpython-313.pyc | Bin 16627 -> 0 bytes __pycache__/main.cpython-313.pyc | Bin 62589 -> 0 bytes __pycache__/main.cpython-38.pyc | Bin 41248 -> 0 bytes htmls/settingspage/settingswindow.html | 45 ++++++++++++++++++++----- index.html | 35 ++++++++++++++++--- main.py | 30 +++++++++++++++-- 7 files changed, 96 insertions(+), 15 deletions(-) delete mode 100644 __pycache__/build.cpython-313.pyc delete mode 100644 __pycache__/main.cpython-313.pyc delete mode 100644 __pycache__/main.cpython-38.pyc diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67c0b443..09e4b7fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,6 +151,7 @@ jobs: echo "Cannot detect executable in ${PKG}/opt/${APP_NAME}" >&2 exit 1 fi + chmod +x "${PKG}/opt/${APP_NAME}/${BIN_NAME}" cat > "${PKG}/usr/bin/${APP_NAME}" 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 Dbiivt diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc deleted file mode 100644 index f9416d1c4ea71eacf1b0436835c5aeaaa6639376..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62589 zcmdqK33yb;nKs({rk2#&TYGB()B;I}eG%9#p@mI=4gwsMC_-w4Y|+A}C1V)JAUwEtQo%b}Ff1w}J;Se*o>MR<~a~e*= zX*X(k;jG=D<8>|}ujlpbnt?a4vynHlvxzscvza%uvxT>?vz528a~vPX&UW6;&hdOa z&bo~W8xr`04T*dr>tDaou_1|1+K|jAi}y?6Q(6p7CDJgJbD?Ib_=8VvN#zWyc4;(i zi5d-OY%w;KD)posXPSpBXKvATr%PoO8a{*5KIp`MCTDpti?i_A$mbxR%UL-a7l%I! zXUAVW{u7X=Ux}Q9ODa#~^SIoq)UBt~r zx|o}Xbcs__sNqVoG@N^>PIxMQDJv^Q*?hTdS<7;60nJdouB{ofP}X8_L~m76 zOBeNgSh~A%#w=)*I$TBUC!pu!*XC(j7AdO-JuXoH;ug-*_MVn|q@~zVgYmDGe^udE znbMD3z7XYeF}g}FS9%+`qo(PIGIlA4T8p^F+!AgnR@t(ZMpo}J>8f&8?&8waYq#i> zF|}F6yDjH3Rd5yP^hjR;Vgl*RrnZDP=@bj$6f9r2FzU z+?qbDNq#-Qp=FNJmX&SfH??fWd)>&bEyHV;-W=b^N;h%$M3io6*^0XBTN>rQx3Q89 zEnDQ0CN|6Wwrot$aGsV;NUK}6v3yg4X0avW>1LY!8g3nDZ`sz;MEks@x}}DzU1@#L zxf}0LI9jT^IXPb=z2mlAan|e9cTrk3otD}tX9;%U9WN}@D0QWo0A#4g{%*!fiMaw= zngNIGEo-@kl?In4PqTZK)VDmUmEx#2s7KXeZhBR!shrt<9Z`Nuxt9AXbjpZ6$uICwWw98dYi2-%Hn(vobTCTKX%{w3M0|WXGIXcsF~`IqKb%52)7o-MyMM z`h3k^s$Yltcqe>4tGl|T0nb-2JqK2Uj;oT^jZ2dcOvl&P)Yoep?-O!+wNZV|Wqn2T z!tY>xuW#8A-Cs)u?zTg^o6>*d+S8@?+sfi z?eKQ@wz+j-K*HAced6yukQ+)HJZAx zVNZ9T_eKJGz0rYWd)UAA-uB+G&A+ebAlG)N%hwr> zV|j0TkFUcYPVDXJ>E6@EZ|(7S^>+36!iipA+n#Q($Je#LjTI!cNm=fI*V_@cw)OS) zaJ_AOZ#cnEr_EicfB|%b6NI)+-u+#^4m^dO7jxTbsvN9g+e6;<^oS3&b+_Yrzs0>~%&*v$w|DwQ^bbmp|u7A)4db&X*I zR!G}vQ&`vI4;v1&_3jHB?(gaHh0PsZysr(jVQ$;wXXTdG*1cWb-qzOBnp@O!*u1~3 z%U6EjP}tt-?QNxgTG5DKfNN{<@2^C1L^Bx|e{A{D<;SXyR=ub@Wj<*>m3T7ojP9)Y zwE1k}>BMuEP>CnxtUg~CirW}4Y~+cAjF@l(3fP1-p-1A~l+ft-`AEV^^w3f}HZwfot7`n;%0v?R2AfE z0?h)A)JF&AX0ML0k_&&0Yb2bxJ5}ldxUrr(_*m2igLw2;`*te5&_c+V(}k>NSC z^5`4uId)dCsN2*S*7CQFeZ70jR-D#`EnZ)HPY0HH9}uhN`q?+9j(zL;t8ZQV*{f48 z|M2=xzW49vo}PN)+c%CJx$)-U^;ch>I&yB6D{Ma4#{1C59nY4xvNOUa@55dGUO;tyL zrp^ha&O5Ss(rkU=;8zYl@#t3`9oRo^E)JTDZ|Y3eM(t!$+Q9xHZe;o8q}8JjPG;tw z%6vX^sPcuJ0n))M#LU6DFBgv|&I=~ao5)x?mQ@qEVzZ|0-iL1`iyqQsL)n7=Si|j?KtvbVeF~v;zEL)99f3etEqt*XhYec?X-fL(| z$d^ILuOkz4A8L2gb#^psRl7>i%sTCaH#UF4XMOaR$!FnTXnr-aVbOtnf~eDm-n* z8a&7^N1r?qrFA!G^MNw4dwu>s-rL&dZ|~{~n+Om4dff(Lk%6hx@N1A@`Sk5S;MRvN z5~Ss8=^6ngKRuGN8vH=mQLBM?hMiQC;FxMPSmXQm4|2oHFIyLnJUD5wAG1Db9Vi*D z9WA(Qslt1=Eg82i{*86^{>AMkUjOYZ%iG1=IZF1 zQ$IX5_4;$5v#-B3eC_K`{QJ578%N%}{?_rSuO7Shlc9e%ejt_@18Luu*7k5}NQ0 zxb7zc8k<}K`iYF{V)ttGgO=wzaGe&CzZc07%}s;NnDskL&O~C;vA&~yPaW(xOkjh@ zAB}%Hal(;0?wA{N%ndn8`i&DwY2!&H!K9K@Rl$+*;8 z8l5p~(h?U)SaYuZe8HQ0M|TD6b(gGTmZr(X%#Uvxarw62i5oxWN%v%GE@axOjrt2k z&T5VRqQ;1P*g%uhPVYfFXfe@(zBQLE^x0{9g{hCV$U(-6XaLJt2cZiVGfOof5{w`v zWRM5UUV!Qw~x^oytFOTUjk z;=1<3H>bY+6fV(AsPOW94__gYGuR!ExTe1L_|&Vf+ioY#xvy-_{<1k8t~m{>=4@Uy zhdZoyJ>m)*1>|Jry$8D6+P#f#E03+93G0152Z51#`Mr#_jsyMD3hc#qdHrEi+X0Y6 z9bw}E9=j!+-0bTD(dDfr(#u;*Ljd8#euPaO;B7m+pnA;vy?#G&9AL*8zISqA~N z|3KI(b$%$4{1LVg?V|_r?>~m*h~@*ECMolT{aO1r62=|#f{uBClGPzc)sgiRDLDi4 z2f3lNA@h)bc=@n9;8=2G{iGpr+>jnLqz@b#GZfr37>upjNlScx*Apz#)f2;PY zC4L}tAZ~E+VBcWp5I3B0Ht%%asQG+m$h~pg-4t{;1-Q?J+`9uMErEp3|HkqK+83>$ zYKUOIV2tx5X)YvHRXg+-6SYX)+OXZ%)7!PTtDW)feZ*WH=sx7-r{4I+^oxHt_3F{< z-+dgk=#3-KUpw`^sV7fe|LV`CM!t4+^qHw+X91=dY@#hMA2RNE0~<}vA*!kGPT-Tj z3_sEVJ%);}A?hpY0M{XNG1xBPAA}<|ha%yU*xN_i65w=XFcOoOim|zN5jZd`)=4H%-#A_+7NG3WXN0%pabU9KFpabwuFixe3%iK>^H7 z^^Z({>xrvx{?sO%rUssg1bnU2XI`3qZloMw!Uky$PX5EDIrq)kKd0lcskM~KZxE6Q zYg-Q+=oh~UzZ8PJkMOYsGe0{F4Qp+EsjMdg&Qq`bNO%Ygf0gTS;wFsBMFe_>tG8#B z%bmz~(;$i{DW+sKlCWie+XG%mko*kF^7qq)2Ph#KOxV!b-LogG@Amo_O$%ay?}B*F zM-_UIxGiA|Jw3^w1i5D`AavLa*%eiT%#YYraI%cE^oNb@J$(Ss79x8|(8WmKu#w>S zAwTVDMs>)9hn4XcbOZhgTzvN>Fj~+bs z$k9gz8-{Ho1tYa1%SVd?bMFZy-pe|+CG_tb)DLxDww8|+OjxYP%ty@uNAs9v$3LgJ z`fDfb@yF_q)(_MUt~NV*F9~m{aikmjQaRG2bz)?K3YiL6tq2h{VA(&iS z#T9G$#~&nU?0H~vS(SmrA2&I6W}I_dB@zy%y-1a`gQPW4iG4;&IwI9EE?afnH-+pQOD`yWcTJxz7 z-ixmK=-Jgh_0c0uf8&{}KYLLqhQv$j>LXM-_3~d|8~EDP$>Hg5|8)A>f4$1pM^vv| zir>_B>!IF#J-%y4`=?(#BNhq#nE+B(x%f`hYINHey%x5BknG(Hl}Xs-KjiQALJh)u zyS;6G$l3R|weRclK`V2B?`a1;2ud;G0PDW>AusQT&LbKwy3w^-BF#w7R-xloqIvlN z)c8LB{l7$VM03?(?zarg9W!K3B9|~|8Z+cmPAMyY(RnKW`TS63X}~#uxbn=d@v=3S z%hrU-?g=coH(>FcUp8jgfF=Q3_KOYUISVf5EC}V42eK;ymW3necqMf=|C{^9(@QR= zmxR(w18MUEmIcFm#|%s968ay{TzENiVJLG^z_~bJSu%o4Ys5>Za-Pq5ArBRTnF|Az z>q42e0cTynQhzCZ%&?W}V4znFnRiN}iA&Vu|CS3dgQN!vZP0|`=mVNxxhR&}Ms?L7 zSClgnBx14&qCKMyRqGggUUfCbZU!|%&w&Gy3r$nJkXNVJJ0e_kX*%^_&*Y0xxZiPr z20?#}oifPml(CUNgqm*MVQb~0<>d?US9v3G9`t&3z@jF9Px}L2aO|GC*7c3G+rx3( zmYR*N-1eqg&t@p-yuDPR=Kz#-+?GwPbP<*8+?Lj++GeiSvu<4z^o0=U0htoN1A^Z` zE&McWQFfcwzR%nK062JGCp1cuu$X@lHD1HNpU?s^^JZh()x@-8UwZOOFYY+?g_B=6 zvtzvMo?sc_HG#2DJev5TY24`!I^AcMjn7{joWJ(GcFa;u{Jiz3HIO`K%rbW(F$L^Q z5~-jFS*86pF_jQhn!utw=2`4nrn#^zwK`sZ(XK^Gcb5+m4$&*Q^|wgX%tfq)6N^h% zy&#!@mwVU55>DNvMGSG3!eU5^Lpm$uEtntqwlT*ZW3ef0Oo`aG4J0bIm%P+sqXMO! zj|6lvSPf3M>L84f0BLbuW;EmhLY5&t0rgM}ropGjzd?0pnb1~R!JtOBYsnHF%wg!A z;Q-r2R0unoTw+Fbick<4cNa;Q6qX0f#aGaeNp(#Y;o%x+PoH^LJ7+D!+bzSEMh@G4 zS8sF)u^`QQp;V}|3Uk?kR-22dei`tK&$63{U3QQeT(gA?a@5R=&#I~c^SBr_Y^oXw zTw;tGam~giZ1?D6SLYJluIHQMIL8tLm$Xvn(rkWEV3an0RXC#^A)Tm0eyU`8{$_*t zj4AXB?;@2;<4jySsSxN*0%XaCfB;W;cCe)~s|QhOw$#1Cj50P>&qb=eOg^1Q2lJ5x zj^$M1(hNp8O!dDo_2wJXM+dLHFgW$XThl{N9JW9h1C=$53k*&5)t)|*-Az3?bp2;P zb4gcRf>ad9oK_{|5D~&r)(w+DG~T!u*CA%7O&y(VM%j(Mln|wK<547C|M;K(`JXrb zm41GQlK(_Ppb`8>_YI;;ZqP0XYdz@ld&os4ZXN%(blpH^6z((???elRpGyh7Ql2&{ zLLHITwWKnvOUl|X;7@#+7==DC_ z>$ZojG)t|_9KZ;>i%u^S((-%gPVLl#uA@Dy?Fwt}4;y(B@9@j0RNEQW?(x$@N=y}< z1vQ=n!dw`swD$6Chgzw7{!P@oh=2e0fiWP|GHH@hPMD9IPuP#!Uu-(H^W@G`ttVU0 zG@adfdgs~J)2-)rh05wex%HQ_LXPc_YN?ccUE^j~(CoUYGh3Urlc|{}Haxpw@K7k# z-Cz5DV(PKOM-LC~2qhLxICD?MpNt>c8*-Kn7$;Jk{hMyuH1UZ?YA2A?P1=)=t$%X; zz+OnedWIhf*{hD!PFjm}#6y`Fm|vF?3m=1JRtaWXYyP(OHR zxHgzq8FW^LQWp(i&{X#D@X}!3!k}|uD78{5YagmT`2g@^dBAgY*P!m`=LhSCJSW!=>rOTX z?DL1Y!19fOrX5G>gNB{&$2pE{`tt{sq7D;tM>@>XGmD2G{Qk1xlE9++fTQ7(d)&VL zvVA*KV>UzJN4x2wVMSe`=Dk8&y+i-rg3S6j{V(E-$j4Tm_2UhH!4yTX(T0jTV&PRa zj!{XvV~||H3SccrHV)PU@@S^eH13CpX^ECI!#vmm(*R2r)aR_k%1R8|O|cX|3QTfo zW~d>n>SwGW;|~ajXqU2McHtn1Jv$($jCVyPlTlGDakkwuzr;qFH0&U2<%3Rlm zpPG8(m{9F@(Z1gO-TsRGJ-oM~o$vGS6V*ZGRDKVm0@vL50Pp(70Ft`XdKJX5@?I!% z$dro!hh3aLac1*sQQ`PA-xJ@4tpuh3NGgAX+r{4G`B-+AU_GO`1J@2brLM7oc zV_+GcX}oPME5odjO&e4aHE70UQhY0NFr+x-2bJEpM`RP3`S`8{QqXcO5+kMfLg-0ZPY-_iKfEBRrQ3^IFxF%d5J3+S|JQ zYs*#j{sVpb*CY8eI8VS22eE>tRPT%- zV$3#sTWq!sn6PdbW9s|(ZZ`jZ0(=r$w9rdy#iel3EVC_D(qm5ig#Lw@PA(l@JUl1h zAeP*|QkdLhTb|r9xMPSL+0v55@FH*3U@iJ<^KUE#+-_4-QM2awbS{I9^akcC@l0 zp5QHinUZlz8Yp2iL=+;CZ7$cnq6Op~GsFK2>RiLW|A$C05jPFx#;QqY-YNS@`?#|- z=qw#x8FDV|j|aiz%pcEKa5-baguQekp>m{bBtKyHOeCjGq@+)zXa6zI~K_R5jvNL&8G#hNKF6KL{tMnJE zjL1h2FocE)@7#I_d-e+eJk1)A5?V$$Xjxt-kQFSiXL;ZpkQfG5Vq_)8*%|T(DOGHM zWrB61NG{FZS1ju;Rg2iYn^iSp5M`fWd=`*PcaAUi-K=1_#k$dF)i`3ewZW?9&ilj| z^Jkg|u**{KOGR=G;sCsU;b@8t)9i|wghY98_GZ&k@d==%T z@Ptc{Mgd|l(d}TUM=;`b2n74YnO{Hl;?y(G0c$c4DA!=nyHP}#5Fmo$AX&M; z7c}N+va127h?GR=rdR|^0{B!0pG1H-4|2=C0}7X^P%M!Za|w)-jDx^9-~||?9b=Xy zzmH3ruqPkecy!~_n<>+HwDF2P=cZ9(PZn^gdacQ{s}7ybHr%QVIPTneTvVnmTs; z>KkuN9Uq)}?R!&?zt%Sww}@N|Q!`X$i{f@-Ss7WMm34R@BBv0)KopBA7DfqUz4Z1R zU_=#v3B3#G{+E=a_00%np0GcE14$&Mn~e3)+tW#MY3T;x`LivasLI8(fEv-Qw17y% zVOew4lJs=k`+`*Z>A0(wsg(W&y2nXs<8={AJKR4&7TJAarKR_-#G-qF6wqdSsCNQup@|RgOxsGFrL88_&-we3rfP2{Em`I zN@m>>ZWI5nRO$bugth(y<^B&!{tbzr)*T}QMRM6e8;0-dgfLLx3B&GLw~M;!BHAq7 z=up}$73xk=ogBjYUUJ+_{m~o!=kG+&*S${^VQ9j1jP{^c##Pl8aQU zszv_sE=KdQY%OOuvyUZbx=V`~x*A0+A!$yevm!UgW|j%4Sfo%au&-=pdE*s8%CC#H zZLy(D&3ZKttuodytLL)Qpt=IHFNL&GjgTygq7DdUR9p6wjsl##?ZY&5CGh5gZIx(I z_44a|MeIrhHNtrKsCp`nl7ARY7*8+Yw%2|XyXR8snHj9gcOyf_9}G^tI1Jsw^bfv^ zjG(9}FE1C&uHzIT!6y;(2WTXZAc5i_a_2JRQ9l#cM;ecY?bx$Sc0dMA;W@MISGigZ z?bYXSACWMs99f2~`a4VJWNP|}`s4K{HXYyea`Gz~FJ-)v|5E->l3&aCVa99uKg@r} z8LHe6D%f~wM<{hWcCqLWNY+vA@wnn(TrosHqIBZEKu*P&eIZGk9i_u{SL}+JZn_RIkyc0jlMDJr5^Iw_@Q_)MHlt3>L2#GorEF=)Z%=+XG z0frhCK;oy8Z-^wYAsVEUm@fw5V#YfqkzUv}+v}tnTj@=fqZbM4mJH7wvs6wJi6Sb_ zpNZ5gU&8+*^&vcNI6gwU+?S2Z zcXBi^DkO>Nlf&Y?z$4)RY-pf13L7?ieSKjIrR#|!uyVCqJx!kNTVRQ>UQ#K+s#;Pp z!Qr>h2lB2LCO6&=HotBQ!*2Zlg?saVqJ+ek{Ku61zevJ{{pg)-FlHV2MTP<;X5u)V^29&^(2H7i2_L& z$SfUh3|UsAfLs#BEqOspo_uxC`SS8ts=iY-X-^TvrGebaz~b7F9a<{6WO-oa`jCCY zEu+5Jc1xqT#=#rS>X5o$II`Bamd=Mqc|Vf zs6(+21h*as)eSv6JD|ELfYRHb$OoXhkr8^Ne~^U2nc^mnc?Tgj4xLqs8++ekbbRfp zGuOWV)SZ$<>^y;poch7Q^|xPu>~wngCAdz|M*JDt@o!P`HYMkg)bSUPw&PzuWPWxS zMFdH=Yns$=RjjDMJC(JeOm4`%!E$nT9#-LCIjx#URkgA!y-7K&6xG}7gy4aH2iN#_ zDIxB>(QOtOKStDEqT=@`QP{#?(a+x?QAvZDJTzQ+ho}?A#cjThw(cIER}m$slpjYh zL4JBI*!*f`$E2j@cri-N5iJ*vkT=S>Yzcb2n-LQ2nJ7hu4uX{bot~}=Nh53t*c=e$ z;MVd%oK(hHO!ylv|71@rDImT^k4t(jQb0)9d?sB3K>$W>lhWodv7TV$B(6v7HG0-7 z=vaK|df}zBR~_>OlbAca=#xFJTHyaAJu(UJ?|8$J0)GCts6&hIpJ-6Or{te0d7qMM zN=Pavc*B_lK5Ir9Ji!@;ctjC=&TPZxlQ6(#2T*XKZ26Yq> z&(3`CzkZk{^iJ5B7V10m>jXH#LvpS?Nt9sfvXkx17?Y*bPcn3FYc&vKBpTnx?gA&aUHZQqiqz7F4*t{_h&($A~5UlRI&@PUy7(f zF(s=hd4!T0O0cmIC6E%rtSn{$bF}9@_zCA`3c$p|;)IhT2Txu~>5)Y=pDcn-Ep=6g zB-;BqO$?eKb&7~dJ^Ufw2^fEZ;5>=bfPfRTpL+DJZb(cS>Kbwm*(4puRc989&jVfU z(5~Sxhn)8W3~4HMJ&-kjJZ?cS4*o#Qn<fz%J9L<@ykamC+!Y0(;G{lJFFcwo^QKsZ$Qj}gcYR5Nhlf4 z9Zn3`SB!$w^}HXKa%9U#R$RLhzx*~cOnf&#r!H0hUaGCG&>Vqu2pj?3Xl|WHayQZ| z7E_^O6J)@LuEZmj;NUJTtWsO$tdPtSAtardu@sFT2O>V{`|^+xq5;6^0!~`S@RL<_UfXGk^Vwzk?h@n0etX+&TZLGtGb2o%1h44)RERja~xr37_I6 z1U7v!6xS9owB7j@&>K5$(eR%`2u)tgfwo3aJw2yGFj^QlAQUZ38xV-r$eTM&WL>he zs{^C-9O&vghxW{pdTQj@wKxA7o~(if7@7{x*7bKVIc%p9t*$2TA`SjH{exOXMl7M) zHe{62Si4F>?Le9a;gLYV#Mn!!dN!$^e3vxbMJ|m+yeX|a4Ujq;1$h~<3GxKQ)^*nr zi%^4l;j}J)D}`1QEs$FGb#)*(PB>0g#$Q84{yNo;N6-vf5u%>kfZARJdouXF-FsDB zUD&}`1yvv6Ecq=7%%1An+q8x@pnGP1ZM6sNf{1X!H78)ny%v}L!AB;IJ@d&mllIJUdtT6e?!XB)^i z!j&rq{8Yh8`K_|vS#qF8b)D`Pb?mAvL&>gcN)@7t#I;cZ8vsJMsw+gKRwb~Bw6#?c z$_R0(XSTL_;j9wb7}%b;_R5=p1M(b(UC z#&CTW-t|qLA>N~%%qTwb<>OyI6Hj6N1}Y~Vx#Nz)prdf8@}<=y+8@SUaa7$jV`ePe!T0wL?+T}R!Xf9Z6o+XHl z<@8kOFH{(jXNakh{JvXT$%DJKHEe5bg{wm!MJWv1TU#IOYwH%TBvSN3-rw5|7fW9c zE}L6hJ9^q%TX~AZ!j~fnr?rw)2PV#~@EPa3_Vo37{iqO$|M)zrPYHoGKk1jhrunTQ z|4#~t0`JA;MGcCv8UnJip( z7MF?o*%UIH&*8F>=5jem^SE53`CK0F>dbe$!g_-26L=m*vQIz%lN&Go=-Qhn5tiuM z_s>EvKsL>H!gt!e48FVA^K)PeN%bon#5#9NGoVhbn&JzK&=n(zE&vP;TTE^&75OvIQ)sh9)vBx{3NE zmWCM)a?ytjnE>WzXAsG+jPTY<9b)su>?;$YaCIzYi8o`Ioqwh2eXB9NE^cCPq5oARS2l(o@;l6xz^eM7}=pT`7vg%+gy9E_* z*9gd_9LC_?hd42IW*y(Nzq$?X0lp5`+_|p1R4KWwbFXIHn{XfT&Z@h~FmDxZNyw4b zf)EwXNKxVZ;W#gXv-Xkm(Ea{s0BB$Ukf)bOF(3|6us{Li8BjzrGy&k{wjN;q3gSb+ z^hb?*!v$UBxUFeY#Tt?An#9X+t^vDOYkkiAf!q9ApWiJHjG#qfGW$8m~L6Tc=W{Ql$SrE3$q(oAS zWjPefL}Axlxj(bY3C1jZOMuB+EQ&=@Q@Zr*${CRkorvrW8%ri-0m4>fa3C0E_e4du zhSF@QJ)A7fY`Zz6aUix#5=u=}iug0h<@#AAF_kf+$1DKRVcnfC-KE(oF?Bwh>fR~M zHg@k+)xGUpnpm1%uJh{{A!7Ad;YO?X;jG&F8GW13zZi2y;6)HlX8M^|vGF%>TN(vP zh)w4ANc6DE6;bi*>(_ty(sci~&<+&7?RLBnex`L!!<@}?I6(~Gbsfu(e-^h2Tjjp^ z!}xvMD1@Y?VZ@Ztu(&e94I|^qFd5Qz7=xPqeeD!6fv0UKM06n+MWG=JrXx0IVNM|r zdLH#YOh6(=sS(Rh>-Y`W7pxn%gK@M`As50X_(pb6^lCc8GcwvhBT5Riiyo?m5=Nh` zL(Uy1kY-98Mw(KD6QlT*#7Os2^+t>%Y!hzQ>g(IjKTf3^X>=qQ63p`dfSjLbQUYh< zAwacc>a9Tx`MbTR4l!=R=>*E|EdN*Z>DM9w{IaE6w}6gY5Xva;r+74(1*h!KBXse& zvm)rM7)cB{@9kgzJA39tX8x(X=ktaQ!yTc_#r+#5G7E=R4$U9lHhg%vdvs19z+Q^uwK(la`GZOadZWpS2E_ji#M{@Umk)MO=`vVTLHLksyqMi`TGM4w*^`|gDLw0_I)2EqS=+C zWgmawV4zb^&*01+^t@o|U(fs&=MEhjdLZCfG*UliubRj&9$9u+M+nLa@}rh%n%G9^N+0lCe-YqC z->i>ieFor%6+wr5FZ8?_08e!XLZnegERJ|riUgPH^c%_D*a7uF0d~{w0;vQ}0pe`?%u192&LScem2Di02_T>vy9n6heI}0yfSsLH zfIUGlWfek(O7XZrm5ZR81r?j>KXLt~;q9Qa3GRiB6bsAGlWhhITLVQZfgL!GGZ*a+ z2o(@=Gh(gNdSj|wJ2KCaqDPBGNs>lgBfpb6BJK%FX5u^5;s{Gscia9w9c^pF^9e42 z-Q)nWIwY7y5X-M9)}_i>xF0jWIieZbHgxDag4B?l=pOJMBL4u2l;dS&rCP=*+TO9J zmB~s%0(A@vh`~`M?64U_ZFDCK@V^MP6v=bY$imwFOst)oV{}*!jP4M#TwvnxlJ z^lu2-?*)Ds_6Ady1nf)xJelYu%cp2muxMoE$ozm~?Rhf{3THtDv}zw)v`(-2`wAo% zN~*JPdPi?tw^IL3e8IXU`gh8;lwV>*8bNB4iXOC%=2ifc`q@bBSOoU20vU=D%#Idn|?gF`G*%rnw9*U)HRF9DQvL54=n&eXtsHaCreszo%5|2b3kx-sK z`J;Ti2r$#S)5zHbm^ovx#T1!bPL?{Cp(-S_!Vv*gWhVi|R1x8)G@k^g5l2NR#d9%o z*&;$!c8-9mMD&vdzW_j4><~3IT*nliH~!|^(?5Prs3R&>2&(ikS18s})|9dc84BUs zxLu+`3veSrJ*mEla9asf$%L#B#6*H^P+O8ZCW<(PS2($h3s7q{dROTzp{Q2Ts1b08 zK_3Q;6!MeCHY*%TC4O55=75Ea1a%S^igl%cpwMs7DM6uR6%l$T6^S5=sX5~*bAu_M z4aZX!22&PJ6qN*)t`BBx2sk!ejRm%Fxh|N2m=*O`5%6C{fo5c%IP~nHp`>p-GGJij zCwg}k4{r~qEDG2cePos>OCgy5o#14_@O{G@1CAA=^TzDevw)LoJlbO(eRYNAf~kbk zia1I^W#)m(v?G5p&sM#_e6hq#`3hUj9R0CWtk#oouH0E?x)1{peS0d^qVs}PBUV1{XScObCm>7wz30sGZP ze^Q@d#FafSiy=^|-t#P!D`!{Q5UWqYXu+Nvxx`P*o|9QKJ4AS6Rv7VSvrNTVRQGl? zTj(xalIq?N2#BRABD}Hr%pNnSICfUSo7BE0+|G8V!tlSyr1N!HH3)D`CI5o1ZI0Z?!>xKUya3(NB1B>GoC=FJAo(VW)wdj z1>sZxZ5xKe$6~*&0(C_;@B)d&_-%#6iej$`(MlwiNMku@)`-ZG;9EGA;97L)a)N0$ z(3}|SREj^8Q5niuJTqfGk(oDmXz+pIv_MA1Uy6TTFuZFRU~&0q`WT~+J`w+np+@k} z(NHe1xc-v${9gqsxnRonD--|-3^WA*5*X;b>cyH1u8itJ{e|VF$X_h9RWCGOTwtdB zVq1+{e{oGgO|kyxd0OOuUTj1f!Bl{35kT8Myrf9b7DE+Gwb>y_GO_iE2xEstxU^w$ zDW$QPKOO>HC?nYs3P5O0SB0)(lZ%WF{0Me=8WqXwpU-3(SG##~cX< z!j}k^Z7QKZfTmPx5kZaBr}bz7YS>u?YHZA2nvpNi-%R(vCb(}1F=&KM&SZHk?x`5Q zMoovntO`SEgnX+8K?GPhSVZAJzFA6$}#vF65!qrHOITM?c7$_gg9EzimHl1VkWwT&@V*4Re z@~qZeSZ%AW)L%3fR4>q9oU5h$0wdDcOP@X%HQUmU|k689_F=iN6Rw{r(FmY0; zSzPuOaoMx8YS~)_f}QzYMTBFb{THzjSQWMrRQo_^7i%8~A%ADz0{lVR8$=y4`)~eX z{ES@mW^&k~6(2_{Ui{HT%l#-?(QeP8oj- zUX@fsRz#_k=~!v4`SzT&!Z#S!nxSO%+a-A2zrjviQOZE~3X+e$aDim^uzNTgU%4AO zIA*`+t{83&=HsHqR=rGr(N;jXZN8TBl}4lyJ6|U5?uOw$!Oma8NU=}7^JBx4*@;30 zKFDx~NJ<52$g(iJQ}Iykb;jw6dtKIi$QVx6b13?m2n=TyceVVHZw5WAVyQ{yA%(qe zqdl+M>v3$aN5$8nSZc!hm-afdRAigGZu=dZOK7cZuIS#>2#{?q5s--G^ywr<6)L-n zZKCU#DPfDT%{J?vBKyVYJr!B@ZQ4^q=#usnNs%J&_sK8GMr?oC zJ;Of=s1kcuypQ1%-G?=bT=XHYJs1VVt}%PWCSsoi*)5a3Fexr*E_nyZD^k`!MyyC# z>I|a|f#x!+q#6cjtb|fYiaH~!!9Hg<{o)arK2AOTEg6c#Rdwp|bND8gXi3ICPAb@w z!CO^Wm4cBZECJ-^Y*p5MNc5WAlPq3ks6ehNHjY@WwE5%;5;kar#_Zy(f{C`iQaq4o{vmu&k$FQ1 z8quqxZ(t<}A~r#YU&q3Hs0IDPXP9n$^W^ojXW0te&P;mMRb+D&CdOBr`-{>$^*ZD? z0VXuUaQ7Yz3?FiWo>7orDL|3d9o(9kc5nysDhWL8U=mXaoU%i0=CO{_iOxNmnD@m~bGlpWu9_a?Rrs zsYaIzGk0SbCWdtV&k;#`9-SUG7wuh2e^_;U@S73g0>w>WPf$@i_Q0mIlgG!hI$GO% zx_fy3akOV^T|HC+$Sh`#KqG)B6JzZ}R8lW^I6R19Sry0VY8u5lB;qm;Q(z7DXrh0^ zj7vV5JkcdnR1p9(i>Euwe~o#Nx#uM7Znd!JYTlgjyv3LE7Ed@zCLAj!(laMAvL>?f zCNhgA3Q8w3vj3Q5%}?mxax)crqC2(%y1I+tbn~UIOS=P=_XSfvcg6lW29|dNEYoy7 zHosi+s?AfZA1(0Y>n~(#k-d;_L>e0+@4{<{+y@f93EGi%3Of{t3tJTJJd~-Ru?$&V zL`}%6UTlh!1kc-6FnS|KjaXDOr(@qCg|GbE>ciwVXdg#O8b!Z zqmToILh=H3i1#j~LA#@NmPL=E&WHLHcO)#L*^b0HVn>pmS6I6;QE~0u z>FIC2HGTG*SKoSyC##pkIdCE9LtvQR@;yBrhsyoEhq}GxhkJVVyVkho9oEg6r`o*g z)<7`$(9|4(mF^?!QuS5rCF#y@xCH8kf0{NOEfaA+sfHraEQ(p9P)90{}8d!X4UH&D9RR_)ebSW{44tiPD2MgC&35ox=;@z8*XX!?E2CT&&x5Ds}`m19F< zo`fSd!fGZnGDf(QZSroJG>3?jzV9i>_YKjTN!U87+SpDKnFivEY@OLQ!{)L`_m?-9 zyjdeQmvU!m8zn~c=n%J@w7HVV-2j`5omHDFSwMFz;HwblN%+<)ufaUsAeQ>HA#CjJ z>P4t}9>2mCI2te)AAE!wMpQk0y(%74Xp7HG>VY1`9Yw-9@%ypBOYBAhUUDlJ*oS3_ zPhek_{r7Xv-|-Pz7LKisp*r=)XyrD-7cW`w;&nBC6J$)nM{LD%kauBggNJL~y6L{! zCjKFMHWT|8?x7hr;>)y0X%1%Id(5$~QT0V#k=O&ftS-GpyYV~diFPB&jQ=^&Nt%$H zaYxZ*N6|!9{zOjEMDBu#oV-6-jY-KL*fmM11HPf=p?U<9KRDbKNG0cKhllYdwZV+K zfTQjzc4O*2=QGC~8yUH{DVVW2;MhFVZu_K$xP=(zV=Fv%&6^G+7woobo&G|4foBag zidxFAF(QpXi?nLkURwsx#29F;w8tXNWH~K9_MrzWfteK!8Eulsf_ndo*TJaB;k*hJ*9qEHga&IOW_L>zIJrZeNexwKVK^|xVjbLlgTB|~`vz)a^1wK5}WWi2*t(Q-+u z(PNeXJF|Upa7#5v>nE1l3`TD97cYJGU6<%nSibn?_}P?YoQ-_~EP=~uPVnp2=vG0Z z(T4TYY@!c^wY{m)Y|+E~7_xIj4UZvioZ*>rrCFNcdLEZAt+nR3W}BkeXikt?a;|dO z$rTt#R?$i8pzVH)DS<0kbq?c%cr#WXnDxTNI?QL0FHuoe$OE54W%N_7G7EA<+@nId2fT9T_#7Sw`VV8}vb=&NftS^HSitT; zL@7I(H%Na0UK+c)&NiT?>zDG3`b70%HjvxU;ELuzx@i-r?4SsJ~d|mZW$uG!EiG* z;=h8j8xJGbH3XpTHuhPNuUtCS|Fs*>o?>_CBa)*MQbPD6viu@3Pn)lD>gCg6YkSZB z1Kr+UZ%19&44drjpbx^DaN<7E`K1P*OCzmO!T}hE<3kzJZ$s~)1KvIoXkL5st5;ti zn)=$)eS|~gU#_r~nM-a3PaW2lvCb*qN4Iv}c*^p^q&@c|EK46T zEclu|zF>dtmE(x+0LuDxnu0fwurIN(QCT{?Z5`y29JXU(nZI$jcQ3v`jn=GxVJ?L1 zGe3rhCez5NUk(VD#v6;MJ`Ep1WL>C*pt9`QrUt$~-T!yfKllzKt@!WI2)>JiQ7-WA zym90!>b_vD<)xk&pYh6fr@uWQv=%;QEAbPI&tP???^r5QoexRLAzYS<7y$+|E{@Vg{+G}ZA&oY^8v@_Z(20DpbPUz zv6!7V;mo1WvLy63Ao8cHgcf(C1W*aay(=8 zgtK7WSrT-XOca*Bvf!l!!@Z%xB@^>1UU}rDM@H6yUwUbMpk&8{d&NXa zDJc2eJW%qvu0Y|kk%wP< z$o#ybV3tL45yCRF$hoT(%y428OBH2`z|UKWuU9A)w@uJ|Fi%@i^wd8h=}kbyiZZJ^ zG$ss}i47zVHIacQvQSW%M9Q)l?jdb5X-W8dDPj1;)OP@Gv|d5F#n3_x-l+;Vh=MH! z_7mZU)#>f{1ad7qsZ1oPa@axOSY6UWK=`{DgdpL9-7FL+`_35P{}fCqi*QTGTNuh) z1c+!q|xCyhFZ; z#*sTjUhN2TflwC`RSb+I9OBy3#S%Az`5wC=neBWR)__3jFc+E_OGz?XH3$+?nU-Lrg`{hvMWe#xi~s}{7jUQGXGd?ZY*~8E-hjJ zvb(g9E$uLF@laS$iMf)_WTz*8``d7ro=MCdbtoRl5_8MMM=WqBF@7o|Y$+_K9{wvv zC0u8d%(f|ALmVJ8&^{A1kP+Xdgg+lq+);-j)QRb594|ew&kU^wyw|uyc;`U;(?mM{ zs6*bYBoZo!x`?0Bb%HK(3yIpWsZ_IO z<5E~K)96{7duMNjnw=(i=a}gW;w&sj)V%F>OKl+b5rSZ3C`glYa+zYOpieS$S(3@Jn=E`qwa-DOfqbDaEk?5a^_H`^I|o}(J2nadSRC9%2A7w3rs zd(O@(P?yKp^Qp7v@C8hfYh#f*828zTm~S@n-|px_Q1VWAToah$>0R@Skq8D1(?5D? z`o*KuN8h5bKFr`jutZ?IS(GQD;3DLZn4ebC1LF&7%vWka8xedcZW9JfB|MXLZ=wNjrlb*x zU^t-~C{$y@1gKn(1Tnl2v?Ss(Lk)rn_tdd(P5-q2>Kk7ZS_?>l&14Ky3?{i3ZzD?X z-5LDnXi(!sRj`?*T_QM!(>{p#0d^%A$XcpG%p~*w<_RD0El9#iZT<&{qG~2`9A`)!<$$H{4&m^DC zIGu4e|8)Mju|w$?E~@Tz*z->WsQP&Bd%ZO6O$M($`I^4fye{##tAyf7h8u`Q@yn%2sDHzh^R|l5j!P zI7IdA>~uwwED%=4aF-S_VwT*cMGSgkw@bR8gy*C)Q!e41(*d=WGK*$V5ER5bdKL?X zy6P0(zFuJcB%(AxjYeaAneO0Hh(YC2VKI^>%_8cja~aJh&UqJ~-d{*~d`3*aRI0IC zrcFdlZ&7f1vqgGxgz8d8mf37&m_AE2E;E-cmMWOu;;}GHkFyHX=g64;aY+!b!t^|S zrH=nCC1E76=$fQFp-(|s9RuY({=HZzUJ%=#7@B^rpT0cE?jdmL3gI9GPggK9(adVn zfs7pA{JM)t=Us#}aVszC2-dnHc4ftaM=+fHm_mR@9d;KP7eD^J*>LgW-;)H%?lcuH zR#0goP$?OE$vCf-?hu88nb_bsO;-_JEvAItS~UKSg@0|)__vK3zE7PI{td@>?I)B8 z!2(6};u&IYpelbr;wS06f@cNZFDUpz28!R6QEQuAuO(c~NGfBGURy_&yS$$A}45 zg>K^=f#f+uT|>J^%oJ_`pSAZe;mEp6`-3Sh0ej0w*_z}PSCW?BhDenm&W|DXq)3W) z$|IvCCNmkRGJNUDsLj`0$cm$MPR2S|AXF6~e=*NfiqkumjM@bKJNdbkUt_C{Grwy# zBmZuKtv1K}u9M~SZFOn-cijbb4*h#JE%NU>2ZFr_bxWCo_feK0-~OC^6?yJ#H7&MMp&FDRi$AHQ+(<>{kGh+P$M zUjc5*SXsUtIRSO4u9E>WYb0PF!Q6|)$U>A-&6ZfvAeA^iF?bJIPTQ0tj8~n)aEaiYu|iMgwG;tT3ErzV zx+C+VExYgXbg|y4EJK#wX&XS{?z@l4!N!y%j!Aj1F7@bzyqbfJ7=N;2H0aW7`5)LT zDa`2ZKQw|4A<1XuQW2O~>HWWD)!gO&cUd!J*cV%}jncVy&Q`Mx$}ek^^g2`(mWuP> zv#WOULhN#D(|lDmG24uQ&8#Pwc|{+PZO36PXTY06q+uf7i^`Z2?t(T(50>PUfBQ%)$Zs0dSc1wmA?_hj&rt&P16J*U;4eV$HOviT3^!sM+Swo$#^s(tmt>?D8&__i@ZDXOUa9?llfmIb12M-=(9{}){V?0#RU$K=3 z1J3sz+JteGp+R{^Z$}|+BlI3I9F%r+wfC;I5m8iy=&frvwe9hCS5;6}D1`kKhP!r# zr>&|GuPZ&^_MHeI;o&=(PmW*RSsRa!KMNhRJBp1f`uu#wo-SX-fkVCfdVGr`dsWvw zY^Lwylpi>Bn2)SfRiWyeJxNoa&(pgJYGL&Ndig$Yh3auPdc6lc-CYlP)s^$3We=%ZP>`H!=!J}V;MpAGZnR}yr>&C_4!CGEvSJ& zD+|_{QB}ic!E?@kxQy%B+j|fJH(EA#wevl&%HmD#F zeZ9{Q?~plEN+h}PO+EtA{5(nsBn!fyFH`OpNWyx2^;=M?{3~u7Her+T-p;Uj3#3LH z5kEMoVPoxmtu@U}O|^~NTbsGsrf@u!aJAc8cX&26*M@CS^zXnoC%m-}hfPhj^{w3J z!g1>mv830_io;2D(73?uqK^C}Yk9th4_j)wd;DJXuVNGunJ9^DO36sELwU?_lJJgO zVQGrJP66S=Hns+s-bV0*q2Tu6c%c*)l4MCLXn_8M9?OCqBv$r5>=o2KEi}yyG!cJ5 z;-|T$wXHftwgj$Q9*S;9fkH)l7gfnW1AzN~2q;BDs0K1R{GB~}0#6EOi6c_m9 z@>6S1uEmA%?5bdP)g-<~825Z!pm^z+bJ{o@&x!HmjDXW^-Y=M#qGMs|%J4$RvSXxe$%xr@ececn1$I_4~s zdbpCh`u(Jg!Ihz;;@>%7QMqxz!a^eB(eF8V&zVJMm!DpK_MX%CoLdo^zb=$rt6V07 zN6UyilwCDofiqpwcI`x#>s0y4^5OJxwL6cEM>ikh5&mbj5kkO@kq` zU?Qh*Jf}REQ$Fk;*)o>1ZZao7;950WHvl2fpWqI;9IyAprmA6r&`IR#X`CqvWxbUlT+lDpfU#+kp{|_45 z#(4cd*mSu14|Xl(U2~ci1+Y^uNw3*sRt6 zdbJk$f7BY0Mo9mNQpVQO+zMlFMQO`)8%2jDxckwEQs5u!Gl0s|Hp!GWT8c+ZvqPDu zW9~*Vqt>CU>aV(}Lf;CZxqaN1R8Ko8oKkG~+g z7PLB?5Yg43xC((PPHwg}TNKAAa;M@_;Oqj+m2$(ZpML3-DPft9lkG`lU0sAdiTp7d za0Y@sNwn3AP?{yvzA^1dK>L3BJ4zNTk$21y$8ncCg7&pzo&~o60mbu-y6}KG~JP>c2-|MgFhUurj_qMGWBtaRY zk`;dke6#9r3HuQz0mJ;ZPUc8@*z^$5rwhZ{hYlMn%NLg~>?@2K+SIF0Lt_Q^iYXQ! zD(&gX81|E(V z9zdk1n464X)WZBLgqezL+)5 zB68+38mHiQ_PL~KbHi4|xpbssWJh52#z6X}{`#AS%y>@1#p78EgINnlGD2A^>3d%p z;`a}$M)Kh{`#$jI7mj~nJZ*k3ZT@imXj6c4_nvQ<$euf%y&#y4P`zhAfBN&^ZyhhI z36|BIuMd@N3jKfWU3+j;RhqxIA4xw+C;d!!C+W@u(jg%PLK5EOK?sioAw&=gSRR=q ztOUFXQE_!MOsPRBi8>BJttFJsOgl4b9NjUC&KLxh-C3+|I+#x5DDDtoYG-Rh%M6zM zv%l}$?%SQvAY*OqUwfvPXN_uTh6-}%1Z`F@W2-WmdsRPs1}_SB!-j@!Hi1FZq~ z;t_YP-(5S@5pb{Uof6FU^roOhhI_o~*!6Eg)ae0z0FWTRr4^<@c>ov-X`KF|}J*C=cQfU!vLeYK=eYf(iYLJe7 zYYj1Ac8os(TtUFJwkK~`Mh2)2>q*XgkmK0&U-a+s`BYe9((DZTr*u@sJZg$i7PJ+UU~f>HN3CC z0$+}p>FO$REpm0uqT=ryn~=Q#*|+Feq&0j!2=M~NJCU9gG9n8mdPj2G!K;MH-k3~X z2M0vE92%D_(PZj4c)L8hZ}cCNbzM=eAm&SxETZV)p`qY;C9D&k)W})Ty{fO`xY*rl z2=OymrwM4KvIAql2bgcqSCyKGCTK5%wyb0Nqx$|G0b4<@GMMh@O}gN8_tuQdv|JlP3H zWxDdDjfWI(fifiDz3n04F}n671~AG0}kueet`rm%UC_I1S5v_N3M30Rr(jrTjs0?2|^3$;oj);Q?L5(+3(2&rQ zAd1tN`Y-5dl_~!UiqRzMiP0hDemer`Kg&arArjp>r|PN((~n%UZf$uGL^fgSbo~WQ@U6QIiY!1;drch ze-1L%ysyB_FmX;;OwXBvqO@QVpQO*+?cA^!Vu9B z@4+diMvxuJ)6Xng5{gMYD#g21|>&e7F%3diHiwk(@~baTW;EE& z*KDBlr$*<~mlM@J{T zh1NX8Zxnuw|38tz^iSk&Yg=5Ef*_qCMeCk@LMTZLnh{D8BX)$eyP4CFh3{alOH8w+ zY8xO}m=Y1Jccu3U(VphIH24eg)WvgS9j;hV0iNqPBt3I<)^b>Q=h{@wOdA#|bnPy*0M`Vk?W^x7>&DJXN8V4RcXI?y15}mEp zKG=Mw@|>xGOl?be7|eQiy=wI3%?Y^6e6Dg|`7)oW?hJz8tpRa@cgvZA;ie6~`&t4` zt-f__zP1iuU8mo;&!^jWV{(#9P#WRiY1{Es2>~qGy-L(-ddVkvGbi|4igCj2%DGbx2s|=#gRLisvwtyiY(B zLT|xLGn&;DH>WGHK5$14L&>W%0n;w^?}RI4J&5Gv!az~tFa(1kLu=o*@0uy~T~cox z87?NrdfOQC5rfm%k?+i4h$FL}>vjx=#(jjxNWlHqGZ3GN5LsiteLvB!JO+_EFbAc?89&s6iGUG$`f(F;^Jq2J7qHfz zOk0x&RS^W2*lFdHSF9Gjlgzr26Gx7lYc zoya^`AFwW9jD?sK6vvyIV3tlfD%WSRDDeaYPrE(ypx?OGr(27`R$BS_)Uw#T*2$4g zCXbbF8`*?ZmIGC`>OvT?tkn*%&UlbM1fy5Ie4w86=#B(H$ zk~l`s~`5?Ur9n*^C1g?S{Fkyu57Mhc8kT`0gAv-d|Rr#uot z{5sx1{uA7&A}_3vDRaXrwX%*6TO0^v8J=e&S!yC#m=1(_xXeuH8EGW*vq)C);%1F< zEg!aND9dJqQzKkppmeTS6eRCh*(EbjCAlh+CJ$xZ%jeNw18=0if)%XX23bC>t>c|a zb9g}pr56>U&cAHot=Lt(6uN+rYAb@VA@KZG;V*P~50e-t@e#y6vRIPu_@AG~ zh;)XRy~oGiKMarW)py^9Iy(01i-eD3M`UynSVPRbtKE-}z5CO#$KSnEaT;$%PJ}@_ zBZeeud_}9#o9`hcfe8Cd5SGRxYx@Qra@$^f=q(Q{)Qr5c_rDgmoc(uN{=9e}wapPK zlYtXsZw_94~j?8n7y>)o(UtU-!cyMtn{$HU>EVghT4b#@|dC+y~{iB!Of9Jp7 z|KWZOMRI(I-4c*+9iaQM9fe{f0K$(b?{SDw63DA|Hn)p7L-B%+{6->85pTo}>?|!b zcZz8fGqC#$ak>PXCx%y<7WD0r6voI(F)v=ma|1#jp*q-OZ@JLB4ly>rji1j#gaRJIj78|#(=Y$8i=T z_$T(Nl(j_ufs z0vWNRbB~Zejp!eyd1kw>M+M?mYld?%eZW`?NyW4}dQ21X4J^+2eX7 zVfAz*z$Hr?7|w*#{@SN%R??_%WcX;-?^v?bvG; zpFLr02k%Zb9)RGc8Y@XGr&VStT|%o>v|0m!A?{jAS5YqY#2xfv;(EmHR}B~f4U~Yw zT!Xi92HwMt=TV@WBmcH3-wU|w9n(jAE585~Twlw=T18vvpv$b89%gh$^lx7=)6ao5jQ$}7x?3WGHHT6klOVjU@F+w`7Xh3wpCL0tBV1-HK}Z1Z z7N1uGJsGrg;>bB9zi0U>flP}6eenWMTc4(?0GI7*6A(crwPtB;Wz(v4TkF=XyJzb? ztJkg*?x&(|Dw<8g1rgG)i)MF2B%x(}eI=DFBtcUXL~8(hcW9I)Qts@e6un*{B?}5K zh8(5fqap2bHp!bodqr-~UMl=DiT6nSk%XGgA(e!Ugq?(wL?#K=W0R^8Qfw1ix)#+kdSW`;y0f{fbI+dpJ3<7w*qgf!a$o+6$`poz6C-P+vN(%Ko)b?gwDo7*Fs z^pQ1jBO{RrsmWz3?x~?o2^xlj^@0ZrQBtf#U9YI}E5P_FJ-ACQ|ay7IcKo?P+66+Mso6&WaF&+JwV za~7mhQoFVEXYAI3q^nyWv}O1>GjgUFyEXXJr*^CHmuBszx~G_ehP0qAC76;HG}?k` zmY~s!7^dKj30g9Psg|Gx34724WUl*KmcbRXjg8A~bw<*e>T6hIMkj ziVy2_y#2aU&u`_&T}r;1|AQi%PGxymPAH4R7O3;W8P&nGb7Z{C56^2JABYHIC$siX}R}2`lVxyvD{UbUpir<>?ob zt$nkPRUEAdBcPcF8~crfN_uH?KwA)WW-&j@ zrh$^P8>AFbn?oKuT&Ys zs;p~CoZ3FDa-fXG-CzCly5n_zb77C>ub|_z|E$PgQ9n|#+F!AHl<1vj4tU@uzo;{g z=-lUYZeKP6_SBx);oG>`*VcYcwMF=yCFt%c7S6}hsA#)&m8NgN=Zd!?7m(#PN zPdi#L7eQP7x@kSD`ie%u0JO?m>US0gY%_b7kET2OHhb6kGfD#KvwBvHIx_qAd*=om zb9z>d0!XFxZe#;DoJKIM$a}wc%V5di=E2qD9Nq~K4^8dM?0>+U;Z30~VD3eO<$AJ0 zKLw#{9oZxH;&b-mpdl-0a1uk2B|B)%B^n2-lQ@g4Q^EY{n9{TIx}CG*vJS1Fo0!Ee zUG#i?PMunWQxrE|?|gaXur5EGWYpL3f3w5;15z zJr)*D=`G{HnztHsZPFGUSOgW`zR~FsllK&6IJD$z} diff --git a/__pycache__/main.cpython-38.pyc b/__pycache__/main.cpython-38.pyc deleted file mode 100644 index bf9ada9b888957fe762b2b53beeba7f15201046e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41248 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 L?7cq(v)g|K>gKf? diff --git a/htmls/settingspage/settingswindow.html b/htmls/settingspage/settingswindow.html index ed1a6c74..5fc7eef8 100644 --- a/htmls/settingspage/settingswindow.html +++ b/htmls/settingspage/settingswindow.html @@ -396,15 +396,47 @@ let activeTab = 'mainset'; const contentFrame = document.getElementById('contentFrame'); + const fallbackStorage = Object.create(null); const contentBody = document.getElementById('contentBody'); const searchInput = document.getElementById('settingsSearch'); const searchHint = document.getElementById('searchHint'); const sidebarToggleBtn = document.getElementById('sidebarToggleBtn'); + function storageGet(key) { + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + return localStorage.getItem(key); + } + } catch (e) { + console.warn('localStorage.getItem 不可用,使用内存存储:', e); + } + return Object.prototype.hasOwnProperty.call(fallbackStorage, key) ? fallbackStorage[key] : null; + } + + function storageSet(key, value) { + const normalizedValue = String(value); + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + localStorage.setItem(key, normalizedValue); + return; + } + } catch (e) { + console.warn('localStorage.setItem 不可用,使用内存存储:', e); + } + fallbackStorage[key] = normalizedValue; + } + function markLoading(loading) { contentBody.classList.toggle('loading', loading); } + function bindFrameLoad(frame) { + frame.addEventListener('load', () => { + markLoading(false); + applySearchToFrame(searchInput.value.trim()); + }); + } + function switchTab(tabName) { const tab = TABS[tabName] ? tabName : 'mainset'; activeTab = tab; @@ -414,7 +446,7 @@ }); document.getElementById('pageTitle').textContent = TABS[tab].title; - localStorage.setItem(TAB_STORAGE_KEY, tab); + storageSet(TAB_STORAGE_KEY, tab); window.location.hash = tab; if (!contentFrame.src.includes(TABS[tab].src)) { @@ -463,7 +495,7 @@ function setSidebarCollapsed(collapsed) { document.body.classList.toggle('sidebar-collapsed', collapsed); sidebarToggleBtn.title = collapsed ? '展开导航栏' : '收缩导航栏'; - localStorage.setItem(SIDEBAR_COLLAPSED_KEY, collapsed ? '1' : '0'); + storageSet(SIDEBAR_COLLAPSED_KEY, collapsed ? '1' : '0'); } function toggleSidebar() { @@ -472,21 +504,18 @@ } function initSidebarState() { - const collapsed = localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === '1'; + const collapsed = storageGet(SIDEBAR_COLLAPSED_KEY) === '1'; setSidebarCollapsed(collapsed); } function initTab() { const hashTab = (window.location.hash || '').replace('#', '').trim(); - const storedTab = localStorage.getItem(TAB_STORAGE_KEY); + const storedTab = storageGet(TAB_STORAGE_KEY); const initialTab = TABS[hashTab] ? hashTab : (TABS[storedTab] ? storedTab : 'mainset'); switchTab(initialTab); } - contentFrame.addEventListener('load', () => { - markLoading(false); - applySearchToFrame(searchInput.value.trim()); - }); + bindFrameLoad(contentFrame); searchInput.addEventListener('input', handleSearchInput); sidebarToggleBtn.addEventListener('click', toggleSidebar); diff --git a/index.html b/index.html index 5c4bc74a..f845ebe8 100644 --- a/index.html +++ b/index.html @@ -832,6 +832,31 @@

Wow 伙伴!

} // 作业数据管理 + const fallbackStorage = Object.create(null); + function storageGet(key) { + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + return localStorage.getItem(key); + } + } catch (e) { + console.warn('localStorage.getItem 不可用,使用内存存储:', e); + } + return Object.prototype.hasOwnProperty.call(fallbackStorage, key) ? fallbackStorage[key] : null; + } + + function storageSet(key, value) { + const normalizedValue = String(value); + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + localStorage.setItem(key, normalizedValue); + return; + } + } catch (e) { + console.warn('localStorage.setItem 不可用,使用内存存储:', e); + } + fallbackStorage[key] = normalizedValue; + } + // 作业数据缓存 let homeworkCache = null; @@ -843,7 +868,7 @@

Wow 伙伴!

if (result.success) { homeworkCache = result.data || []; // 同步到 localStorage - localStorage.setItem('homeworkList', JSON.stringify(homeworkCache)); + storageSet('homeworkList', JSON.stringify(homeworkCache)); return homeworkCache; } } @@ -851,7 +876,7 @@

Wow 伙伴!

console.error('从服务器加载作业失败:', error); } // 如果服务器加载失败,从 localStorage 加载 - const data = localStorage.getItem('homeworkList'); + const data = storageGet('homeworkList'); homeworkCache = data ? JSON.parse(data) : []; return homeworkCache; } @@ -862,7 +887,7 @@

Wow 伙伴!

return homeworkCache; } // 否则从 localStorage 加载 - const data = localStorage.getItem('homeworkList'); + const data = storageGet('homeworkList'); return data ? JSON.parse(data) : []; } @@ -889,14 +914,14 @@

Wow 伙伴!

list.push(homework); } - localStorage.setItem('homeworkList', JSON.stringify(list)); + storageSet('homeworkList', JSON.stringify(list)); homeworkCache = list; } function deleteHomework(id) { const list = getHomeworkList(); const newList = list.filter(h => h.id !== id); - localStorage.setItem('homeworkList', JSON.stringify(newList)); + storageSet('homeworkList', JSON.stringify(newList)); homeworkCache = newList; syncHomeworkDataToServer(); renderHomeworkCards(); diff --git a/main.py b/main.py index 0ba7e89f..552333ae 100644 --- a/main.py +++ b/main.py @@ -60,6 +60,16 @@ } +def is_wsl_environment(): + """检测是否运行在 WSL 环境""" + release = platform.release().lower() + return ( + 'WSL_DISTRO_NAME' in os.environ + or 'microsoft' in release + or 'wsl' in release + ) + + def get_data_dir(): return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data') @@ -1521,6 +1531,15 @@ def saveHomeworkToAutoSave(self, homework_list): except Exception as e: log(f"读取启动设置失败: {str(e)}", "error") + # WSLg 下通常没有可用托盘,禁用托盘相关逻辑以避免初始化异常导致崩溃窗口 + tray_enabled = True + if system == 'Linux' and is_wsl_environment(): + tray_enabled = False + log("检测到 WSL 环境:禁用系统托盘", "warning") + if start_minimized: + log("WSL 环境下托盘不可用:忽略启动最小化设置", "warning") + start_minimized = False + # 根据屏幕大小自适应主窗口尺寸,避免高缩放下超出可视区域被系统强制铺满 base_width, base_height = 2296, 1136 screen_width, screen_height = get_screen_size() @@ -1556,7 +1575,12 @@ def saveHomeworkToAutoSave(self, homework_list): main_window.hide() # 在主线程设置托盘图标(必须在start之前) - setup_tray_icon(main_window) + if tray_enabled: + try: + setup_tray_icon(main_window) + except Exception as e: + tray_icon = None + log(f"系统托盘初始化失败,继续无托盘运行: {str(e)}", "warning") # 注册拖拽区域 def on_loaded(): @@ -1564,7 +1588,9 @@ def on_loaded(): # 仅允许在html标签内拖拽 main_window.evaluate_js(""" document.querySelector('html').addEventListener('mousedown', function(e) { - window.dragStart(); + if (typeof window.dragStart === 'function') { + window.dragStart(); + } }); """) From 2f77cf6b152d2f5e4f6b75bc29898fc311de32e8 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 28 Mar 2026 13:19:57 +0800 Subject: [PATCH 9/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Ddeb=E6=B2=A1?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09e4b7fb..ad35bc89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,7 +187,9 @@ jobs: CONTROLEOF dpkg-deb --root-owner-group --build "${PKG}" - mv "build/deb/${APP_NAME}.deb" "AssignSticker-${APP_VERSION}-amd64.deb" + mkdir -p release + mv "build/deb/${APP_NAME}.deb" "release/AssignSticker-${APP_VERSION}-amd64.deb" + ls -lah release - name: Upload Linux artifact uses: actions/upload-artifact@v4 @@ -195,7 +197,7 @@ jobs: name: AssignSticker-linux-x64 path: | release/AssignSticker-linux-x64-*.zip - AssignSticker-*-amd64.deb + release/AssignSticker-*-amd64.deb if-no-files-found: error release: @@ -244,8 +246,8 @@ jobs: with: tag_name: ${{ steps.rel.outputs.tag }} files: | - artifacts/windows/* - artifacts/linux/* + artifacts/windows/**/* + artifacts/linux/**/* draft: false prerelease: false generate_release_notes: true