[BUG] 开启"启动后关闭启动器"时任务栏图标滞留 — 原因已定位(kill_pyappify 硬杀进程), 附修复补丁

Author: sakurairsoraCreated Sep 22, 2026Updated Sep 22, 2026
Labelsbugarea: accountpriority: normal

问题

开启设置里的 "启动后关闭启动器" (Kill Launcher After Start, 默认开启) 后, 启动器进程被强杀, 任务栏/托盘会残留无法点击的幽灵图标, 鼠标悬停后才消失。同 #1405, 该 issue 无回复, 这里重新整理原因和修复方案。

根因

MainWindow 的对应分支调用 pyappify.kill_pyappify(), 其实现是:

python
os.kill(pid, signal.SIGTERM)

在 Windows 上这等价于 TerminateProcess 硬杀: Tauri 启动器进程瞬间消失, 没有机会走自己的退出清理流程, 托盘注册的图标成了幽灵图标; 窗口按钮 + 托盘图标都没被正常注销, 所以会同时看到两个残留。

修复(已在本地实测通过)

杀之前先温和关闭: 先向启动器可见窗口发 WM_CLOSE; 3 秒未退出 (Tauri 拦截了关闭事件时), 再向它的窗口消息线程投 WM_QUIT, 让 tao/Tauri 事件循环正常退出并执行清理 (注销托盘图标); 都失败才回退到原来的 SIGTERM

python
def _graceful_close_launcher(process_pid, exit_event=None):
    """Close the launcher gracefully: WM_CLOSE first, then WM_QUIT to its window
    threads, so the Tauri app exits through its normal shutdown path and
    unregisters its tray icon (a hard kill leaves a ghost tray icon)."""
    import ctypes

    user32 = ctypes.windll.user32
    WM_CLOSE = 0x0010
    WM_QUIT = 0x0012
    windows = []

    EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)

    def _callback(hwnd, _lparam):
        owner_pid = ctypes.c_ulong()
        tid = user32.GetWindowThreadProcessId(hwnd, ctypes.byref(owner_pid))
        if owner_pid.value == process_pid:
            windows.append((hwnd, tid, bool(user32.IsWindowVisible(hwnd))))
        return True

    user32.EnumWindows(EnumWindowsProc(_callback), 0)
    if not windows:
        return False

    for hwnd, _tid, visible in windows:
        if visible:
            user32.PostMessageW(hwnd, WM_CLOSE, 0, 0)
    if _wait_for_process_exit(process_pid, 3, exit_event=exit_event):
        return True

    for tid in {tid for _hwnd, tid, _visible in windows}:
        user32.PostThreadMessageW(tid, WM_QUIT, 0, 0)
    return _wait_for_process_exit(process_pid, 7, exit_event=exit_event)


def kill_pyappify(timeout=30, exit_event=None):
    if pid:
        log = _get_logger()
        log.info(f"Attempting to terminate process with PID: {pid}")
        try:
            if _graceful_close_launcher(pid, exit_event=exit_event):
                log.info(f'graceful close success {pid}')
                return True
            log.warning(f'graceful close did not exit pid {pid}, falling back to terminate')
        except Exception as e:
            log.warning(f'graceful close failed: {e}')
        try:
            os.kill(pid, signal.SIGTERM)
            if not _wait_for_process_exit(pid, timeout, exit_event=exit_event):
                log.warning(f"Timed out waiting for process with PID {pid} to exit.")
                return False
            log.info(f'_wait_for_process_exit success {pid}')
            return True
        except Exception as e:
            log.error(f"Failed to terminate process with PID {pid}: {e}")
            return False
    return False

实测

  • 日志: graceful close success — WM_QUIT 后启动器约 3 秒内正常退出
  • 任务栏/托盘经 UI Automation 全量扫描, 0 个 ok-ww 残留按钮
  • 硬杀兜底逻辑保留, 温和关闭失败时行为与现在一致

备注

master 分支已改为依赖 pip 包 pyappify==1.0.13 (应用仓库里不再内置这个模块), 所以这个修复需要落到 pyappify 包的源码里再发版; PyPI 上的 wheel 没有附源码仓库链接, 只能麻烦 ok-oldking 在包的源码处套用上面的改动。在包更新之前, v3.6.x 用户可以直接按补丁修改 working/pyappify/__init__.py

(本 issue 整合自 #1405, 那边的内容已合并到这里。)

Source: ok-oldking/ok-wuthering-waves