|
1 | 1 | """ |
2 | 2 | viralens · 打包成桌面 app 时的入口(被 PyInstaller 冻结,见 viralens.spec)。 |
3 | 3 |
|
| 4 | +目标:双击直接弹出一个**原生应用窗口**(pywebview)——没有终端、没有浏览器标签页。 |
| 5 | +缺原生 webview 运行时(如部分 Linux 没装 WebKitGTK)时,自动回退到「开浏览器」。 |
| 6 | +
|
4 | 7 | 两种角色,靠命令行第一个参数区分: |
5 | | - · 正常双击启动 → 起本地网页界面(app.main()),浏览器自动打开。 |
6 | | - · 自己重新拉起 → 形如 `viralens --vl-exec fetch_multi --force`:这是流水线某一步, |
7 | | - 由 runtime.dispatch_if_worker() 接管、当成 __main__ 跑掉再退出。 |
8 | | - (源码模式下这一步等价于 `python scripts/fetch_multi.py --force`。) |
| 8 | + · 正常双击启动 → 起本地服务器 + 开原生窗口(或回退浏览器)。 |
| 9 | + · 自己重新拉起 → 形如 `viralens --vl-exec fetch_multi --force`:流水线某一步, |
| 10 | + 由 runtime.dispatch_if_worker() 接管、当成 __main__ 跑掉再退出(不开窗口)。 |
9 | 11 |
|
10 | | -为什么要这样:整个工具是一串脚本用 subprocess 互相调起来的。打包后 sys.executable |
11 | | -变成 app 自己而不是 Python,所以让 app 自己充当「Python」——带上 --vl-exec 再跑一遍。 |
| 12 | +注意:本 app 以「窗口模式」(console=False)打包,没有控制台。下面 _ensure_std() 负责: |
| 13 | + · 子步骤进程(被父进程用管道收 stdout 显示进度)→ 把标准输出接回那条管道; |
| 14 | + · 主窗口进程(双击,没有任何 std 句柄)→ 接到 devnull,避免 print 崩溃。 |
12 | 15 | """ |
| 16 | +import io |
| 17 | +import os |
| 18 | +import sys |
| 19 | + |
| 20 | + |
| 21 | +def _ensure_std(): |
| 22 | + """窗口模式下 sys.stdout/stderr 可能是 None。子进程的句柄是父进程给的管道(有效)→ |
| 23 | + 接回去让进度能被收集;主窗口进程没有有效句柄 → dup 失败,退到 devnull。""" |
| 24 | + for name, fd in (("stdout", 1), ("stderr", 2)): |
| 25 | + if getattr(sys, name, None) is None: |
| 26 | + try: |
| 27 | + stream = io.TextIOWrapper(os.fdopen(os.dup(fd), "wb"), |
| 28 | + encoding="utf-8", errors="replace", line_buffering=True) |
| 29 | + except Exception: |
| 30 | + stream = open(os.devnull, "w", encoding="utf-8", errors="replace") |
| 31 | + setattr(sys, name, stream) |
| 32 | + |
| 33 | + |
| 34 | +_ensure_std() |
| 35 | + |
13 | 36 | import runtime |
14 | 37 |
|
15 | 38 | runtime.bootstrap() |
16 | | -runtime.dispatch_if_worker() # 若本进程是 --vl-exec 子步骤:跑完即退出,不会往下走 |
| 39 | +runtime.dispatch_if_worker() # 若是 --vl-exec 子步骤:跑完即退出,绝不往下走(不开窗口) |
17 | 40 |
|
18 | 41 | import app |
19 | | -app.main() |
| 42 | + |
| 43 | +# —— 主进程:起服务器,开原生窗口;开不出来就回退浏览器 —— |
| 44 | +srv, url = app.start_server() |
| 45 | + |
| 46 | +_opened = False |
| 47 | +try: |
| 48 | + import webview |
| 49 | + webview.create_window("viralens", url, width=1180, height=820, min_size=(900, 600)) |
| 50 | + webview.start() # 阻塞,直到用户关掉窗口 |
| 51 | + _opened = True |
| 52 | +except Exception: |
| 53 | + _opened = False |
| 54 | + |
| 55 | +if not _opened: |
| 56 | + # 没有可用的原生 webview(如 Linux 缺 WebKitGTK)→ 开浏览器并挂住进程 |
| 57 | + import threading |
| 58 | + import webbrowser |
| 59 | + try: |
| 60 | + webbrowser.open(url) |
| 61 | + except Exception: |
| 62 | + pass |
| 63 | + try: |
| 64 | + threading.Event().wait() |
| 65 | + except KeyboardInterrupt: |
| 66 | + pass |
0 commit comments