diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7ecb58a8..ad35bc89 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 @@ -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: @@ -103,103 +103,23 @@ jobs: 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 + uv pip install nuitka + uv run python build.py --backend nuitka - name: Build DEB package shell: bash @@ -207,19 +127,35 @@ jobs: 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" + 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 - cp -r dist/assignsticker/* "${PKG}/opt/${APP_NAME}/" + 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 + chmod +x "${PKG}/opt/${APP_NAME}/${BIN_NAME}" - cat > "${PKG}/usr/bin/${APP_NAME}" <<'LAUNCHEOF' + cat > "${PKG}/usr/bin/${APP_NAME}" < Homepage: https://github.com/SECTL/AssignSticker Description: Homework Showboard Application @@ -251,13 +187,17 @@ 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 with: - name: AssignSticker-linux-amd64 - path: AssignSticker-*-amd64.deb + name: AssignSticker-linux-x64 + path: | + release/AssignSticker-linux-x64-*.zip + release/AssignSticker-*-amd64.deb if-no-files-found: error release: @@ -295,7 +235,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 @@ -306,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 diff --git a/.gitignore b/.gitignore index 30fe27b2..1ab0513c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,41 @@ logs/* -.DS_Store \ No newline at end of file +.DS_Store +dist +release +.venv +logs +*.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 diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc deleted file mode 100644 index f9416d1c..00000000 Binary files a/__pycache__/main.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/main.cpython-38.pyc b/__pycache__/main.cpython-38.pyc deleted file mode 100644 index bf9ada9b..00000000 Binary files a/__pycache__/main.cpython-38.pyc and /dev/null differ diff --git a/build.py b/build.py index 23f14c9e..5605342a 100644 --- a/build.py +++ b/build.py @@ -2,232 +2,396 @@ # -*- coding: utf-8 -*- """ AssignSticker 打包脚本 -使用 PyInstaller 打包成 Windows x64 可执行文件 -打包项目根目录下的所有内容(排除特定文件和目录) + +默认使用 Nuitka(standalone)打包,支持 Windows/Linux: + 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': + +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') + + 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_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(): - """获取项目根目录""" +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 文件 - } + print("\n扫描并收集资源(Nuitka)...") + 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 + + if item.is_dir(): + args.append(f"--include-data-dir={name}={name}") + print(f" 包含目录: {name}") + continue + + if item.suffix.lower() in EXCLUDE_SUFFIXES: + print(f" 排除文件: {name}") + continue - datas = [] + args.append(f"--include-data-file={name}={name}") + print(f" 包含文件: {name}") - print("\n扫描项目根目录...") + return args - # 遍历项目根目录下的所有项目 - for item in project_root.iterdir(): - item_name = item.name - # 检查是否在排除列表中 - if item_name in exclude_items: - print(f" 排除: {item_name}") +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()): + name = item.name + + if name in EXCLUDE_FILES or name in EXCLUDE_DIRS: + print(f" 排除: {name}") + continue + if name.startswith("."): + print(f" 排除隐藏项: {name}") continue - # 跳过隐藏文件(以.开头) - if item_name.startswith('.') and item_name != '.': - print(f" 排除隐藏项: {item_name}") + if item.is_dir(): + args.append(f"--add-data={name}{separator}{name}") + print(f" 包含目录: {name}") continue - # 跳过 Python 缓存文件 - if item_name.endswith(('.pyc', '.pyo', '.spec')): - print(f" 排除缓存: {item_name}") + if item.suffix.lower() in EXCLUDE_SUFFIXES: + 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"--add-data={name}{separator}.") + print(f" 包含文件: {name}") - return datas + return args -def build_exe(): - """构建可执行文件""" - print("\n" + "="*60) - print("开始构建 AssignSticker Windows 可执行文件") - print("="*60 + "\n") +def ensure_backend_installed(backend: str) -> bool: + try: + if backend == "nuitka": + import nuitka # noqa: F401 - project_root = get_project_root() - os.chdir(project_root) + print(f"Nuitka 已安装") + else: + import PyInstaller # noqa: F401 - # 清理旧的构建文件 - clean_build_dirs() + print(f"PyInstaller 已安装") + return True + except ImportError: + if backend == "nuitka": + print("错误: 未安装 Nuitka,请先执行: uv pip install nuitka") + else: + print("错误: 未安装 PyInstaller,请先执行: uv pip install pyinstaller") + return False - # 获取数据文件参数 - print("\n收集数据文件...") - data_args = get_all_data_files() - # 构建 PyInstaller 命令 +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', 'PyInstaller', - '--onefile', # 单文件模式 - '--windowed', # 窗口模式(无控制台) - '--name=AssignSticker', # 输出文件名 - '--icon=icon.ico', # 图标文件 - '--clean', # 清理临时文件 - '--noconfirm', # 不确认覆盖 + sys.executable, + "-m", + "nuitka", + mode_arg, + f"--output-filename={APP_NAME}", + "--enable-plugin=tk-inter", + "--assume-yes-for-downloads", + "--output-dir=dist", ] - # 添加数据文件参数 - cmd.extend(data_args) + 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.append('main.py') + cmd.extend(data_args) + cmd.append(MAIN_SCRIPT) print("\n执行命令:") - print(' '.join(cmd)) - print() + print(" ".join(cmd)) + subprocess.run(cmd, check=True, capture_output=False) + + dist_root = get_project_root() / "dist" + if onefile: + 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 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] + 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) - # 执行构建 - try: - result = subprocess.run(cmd, check=True, capture_output=False) - print("\n" + "="*60) - print("构建成功!") - print("="*60) - return True - except subprocess.CalledProcessError as e: - print("\n" + "="*60) - print("构建失败!") - print("="*60) - print(f"错误代码: {e.returncode}") - return False + print("\n执行命令:") + print(" ".join(cmd)) + subprocess.run(cmd, check=True, capture_output=False) + 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_distribution(): - """创建发布包(复制dist目录下的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' + 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) 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(): + 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}") else: - print(f" 错误: 找不到 {exe_source}") - return False - - # 创建 ZIP 压缩包 - print("\n创建 ZIP 压缩包...") - 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 + raise FileNotFoundError(f"找不到构建产物: {build_output}") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + 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") + 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() + platform_key = get_platform_key() - -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 platform_key not in {"windows", "linux"}: + print(f"警告: 当前平台 {platform_key} 未正式支持,可能打包失败") 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} {platform_key} 可执行文件") + 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/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 a16d562a..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') @@ -1271,13 +1281,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: @@ -1518,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() @@ -1553,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(): @@ -1561,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(); + } }); """) 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 = [