2 Commits
Author SHA1 Message Date
MCLauncher a44d2afca5 feat: v1.3.0-Beta PCL风格UI重构
Build Installers / Build Linux (push) Has been cancelled
Build Installers / Build Windows (push) Has been cancelled
Build Installers / Build macOS (push) Has been cancelled
Build Installers / 发布 Release (push) Has been cancelled
- 固定窗口尺寸 980x620, 不可缩放, 启动按钮不再被截出屏幕
- 横向布局: 左侧深蓝导航栏 + 右侧内容区
- 启动页改为左右两列: 设置/版本 + 日志/启动按钮
- 导航按钮高亮切换, 模拟 PCL 风格
2026-08-06 22:04:36 +08:00
MCLauncher 393aa91302 feat: v1.3.0-Alpha 蓝色主题UI重构
Build Installers / Build Linux (push) Has been cancelled
Build Installers / Build Windows (push) Has been cancelled
Build Installers / Build macOS (push) Has been cancelled
Build Installers / 发布 Release (push) Has been cancelled
- 黑色深色主题改为明亮蓝色主题
- 新增窗口淡入动画与启动按钮忙碌动画
- 标签页选中蓝色高亮, 表格/日志区浅色化
- 版本号升级 1.3.0-Alpha
2026-08-06 12:44:29 +08:00
+142 -61
View File
@@ -39,7 +39,7 @@ from pathlib import Path
VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json" VERSION_MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"
AUTHLIB_INJECTOR_API = "https://api.github.com/repos/yushijinhun/authlib-injector/releases/latest" AUTHLIB_INJECTOR_API = "https://api.github.com/repos/yushijinhun/authlib-injector/releases/latest"
VERSION = "1.2.926" VERSION = "1.3.0-Alpha"
USER_AGENT = "Mozilla/5.0 (MeowLauncher/%s)" % VERSION USER_AGENT = "Mozilla/5.0 (MeowLauncher/%s)" % VERSION
OS_NAME = {"Windows": "windows", "Linux": "linux", "Darwin": "osx"}.get(platform.system(), "linux") OS_NAME = {"Windows": "windows", "Linux": "linux", "Darwin": "osx"}.get(platform.system(), "linux")
@@ -553,17 +553,19 @@ except Exception:
class MeowLauncherApp: class MeowLauncherApp:
BG = "#1e1e2e" BG = "#eef2f9"
CARD = "#2a2a3d" CARD = "#ffffff"
ACCENT = "#4ade80" ACCENT = "#2563eb"
TEXT = "#e2e8f0" ACCENT_LIGHT = "#60a5fa"
DIM = "#94a3b8" ACCENT_DARK = "#1d4ed8"
TEXT = "#1e293b"
DIM = "#64748b"
def __init__(self): def __init__(self):
self.root = tk.Tk() self.root = tk.Tk()
self.root.title("MeowLauncher %s - Minecraft 启动器" % VERSION) self.root.title("MeowLauncher %s - Minecraft 启动器" % VERSION)
self.root.geometry("600x780") self.root.geometry("980x620")
self.root.minsize(540, 680) self.root.resizable(False, False)
self.root.configure(bg=self.BG) self.root.configure(bg=self.BG)
self.msg_queue = queue.Queue() self.msg_queue = queue.Queue()
@@ -577,6 +579,7 @@ class MeowLauncherApp:
self._build_ui() self._build_ui()
self.root.after(100, self._poll_queue) self.root.after(100, self._poll_queue)
self.refresh_versions() self.refresh_versions()
self._fade_in()
@@ -596,19 +599,41 @@ class MeowLauncherApp:
style.configure("Title.TLabel", background=self.BG, foreground=self.ACCENT, style.configure("Title.TLabel", background=self.BG, foreground=self.ACCENT,
font=("Microsoft YaHei UI", 18, "bold")) font=("Microsoft YaHei UI", 18, "bold"))
style.configure("Dim.TLabel", background=self.BG, foreground=self.DIM) style.configure("Dim.TLabel", background=self.BG, foreground=self.DIM)
style.configure("Accent.TButton", background=self.ACCENT, foreground="#0f172a", style.configure("Accent.TButton", background=self.ACCENT, foreground="#ffffff",
font=("Microsoft YaHei UI", 11, "bold")) font=("Microsoft YaHei UI", 11, "bold"), padding=(16, 10))
style.map("Accent.TButton", style.map("Accent.TButton",
background=[("active", "#22c55e"), ("pressed", "#16a34a")]) background=[("active", self.ACCENT_LIGHT), ("pressed", self.ACCENT_DARK)])
style.configure("TCombobox", fieldbackground=self.CARD, background=self.CARD, style.configure("TCombobox", fieldbackground=self.CARD, background=self.CARD,
foreground=self.TEXT, arrowcolor=self.TEXT) foreground=self.TEXT, arrowcolor=self.TEXT)
style.configure("TEntry", fieldbackground=self.CARD, foreground=self.TEXT) style.configure("TEntry", fieldbackground=self.CARD, foreground=self.TEXT)
style.configure("TCheckbutton", background=self.BG, foreground=self.TEXT) style.configure("TCheckbutton", background=self.BG, foreground=self.TEXT)
style.configure("Horizontal.TProgressbar", background=self.ACCENT, troughcolor=self.CARD) style.configure("Horizontal.TProgressbar", background=self.ACCENT, troughcolor="#e2e8f0")
style.configure("TRadiobutton", background=self.BG, foreground=self.TEXT) style.configure("TRadiobutton", background=self.BG, foreground=self.TEXT)
style.configure("TNotebook", background=self.BG, borderwidth=0)
style.configure("TNotebook.Tab", background=self.CARD, foreground=self.DIM,
padding=(18, 8), font=("Microsoft YaHei UI", 10))
style.map("TNotebook.Tab",
background=[("selected", self.ACCENT)],
foreground=[("selected", "#ffffff")])
style.configure("Content.TFrame", background=self.BG)
style.configure("Nav.TFrame", background="#1e3a8a")
style.configure("NavLogo.TLabel", background="#1e3a8a", foreground="#ffffff",
font=("Microsoft YaHei UI", 30))
style.configure("NavTitle.TLabel", background="#1e3a8a", foreground="#ffffff",
font=("Microsoft YaHei UI", 15, "bold"))
style.configure("NavSub.TLabel", background="#1e3a8a", foreground="#bfdbfe",
font=("Microsoft YaHei UI", 9))
style.configure("NavButton.TButton", background="#1e3a8a", foreground="#e0e7ff",
font=("Microsoft YaHei UI", 11), borderwidth=0, padding=(14, 10))
style.map("NavButton.TButton",
background=[("active", "#2563eb"), ("pressed", "#1d4ed8")])
style.configure("NavButtonActive.TButton", background="#2563eb", foreground="#ffffff",
font=("Microsoft YaHei UI", 11, "bold"), borderwidth=0, padding=(14, 10))
style.configure("Treeview", background=self.CARD, fieldbackground=self.CARD, style.configure("Treeview", background=self.CARD, fieldbackground=self.CARD,
foreground=self.TEXT) foreground=self.TEXT)
style.configure("Treeview.Heading", background=self.CARD, foreground=self.TEXT) style.configure("Treeview.Heading", background="#eef2f9", foreground=self.TEXT)
style.map("Treeview", background=[("selected", self.ACCENT_LIGHT)],
foreground=[("selected", "#ffffff")])
@@ -621,106 +646,141 @@ class MeowLauncherApp:
return frame return frame
def _build_ui(self): def _build_ui(self):
"""构建主窗口: 标题 + 标签页(启动器/资源中心)。""" """构建主窗口: 左侧导航 + 右侧内容页。"""
ttk.Label(self.root, text="MeowLauncher", style="Title.TLabel").pack(pady=(14, 0)) self.nav = ttk.Frame(self.root, style="Nav.TFrame", width=200)
ttk.Label(self.root, text="跨平台 Minecraft 启动器 v%s" % VERSION, style="Dim.TLabel").pack() self.nav.pack(side="left", fill="y")
self.nav.pack_propagate(False)
self.notebook = ttk.Notebook(self.root) ttk.Label(self.nav, text="🐱", style="NavLogo.TLabel").pack(pady=(26, 0))
self.notebook.pack(fill="both", expand=True, padx=10, pady=8) ttk.Label(self.nav, text="MeowLauncher", style="NavTitle.TLabel").pack(pady=(4, 2))
ttk.Label(self.nav, text="跨平台 MC 启动器", style="NavSub.TLabel").pack(pady=(0, 26))
self.tab_launch = ttk.Frame(self.notebook) self.nav_launch_btn = ttk.Button(self.nav, text=" 🚀 启动游戏",
self.tab_res = ttk.Frame(self.notebook) style="NavButton.TButton",
self.notebook.add(self.tab_launch, text=" 启动器 ") command=lambda: self._switch_page("launch"))
self.notebook.add(self.tab_res, text=" 资源中心 ") self.nav_launch_btn.pack(fill="x", padx=16, pady=3)
self.nav_res_btn = ttk.Button(self.nav, text=" 📦 资源中心",
style="NavButton.TButton",
command=lambda: self._switch_page("res"))
self.nav_res_btn.pack(fill="x", padx=16, pady=3)
ttk.Label(self.nav, text="v%s" % VERSION, style="NavSub.TLabel").pack(side="bottom", pady=16)
self.content = ttk.Frame(self.root, style="Content.TFrame")
self.content.pack(side="left", fill="both", expand=True)
self.page_launch = ttk.Frame(self.content, style="Content.TFrame")
self.page_res = ttk.Frame(self.content, style="Content.TFrame")
self._build_launch_tab() self._build_launch_tab()
self._build_res_tab() self._build_res_tab()
self.page_launch.pack(fill="both", expand=True)
self._set_nav_active("launch")
def _set_nav_active(self, page):
"""导航按钮高亮切换。"""
if page == "launch":
self.nav_launch_btn.configure(style="NavButtonActive.TButton")
self.nav_res_btn.configure(style="NavButton.TButton")
else:
self.nav_launch_btn.configure(style="NavButton.TButton")
self.nav_res_btn.configure(style="NavButtonActive.TButton")
def _switch_page(self, page):
"""切换页面 (左侧导航)。"""
if page == "launch":
self.page_res.pack_forget()
self.page_launch.pack(fill="both", expand=True)
else:
self.page_launch.pack_forget()
self.page_res.pack(fill="both", expand=True)
self._set_nav_active(page)
def _build_launch_tab(self): def _build_launch_tab(self):
"""构建[启动器]页: 登录、版本、设置、日志、启动按钮。""" """构建[启动游戏]页: 左列设置 + 右列日志与启动按钮 (PCL 风格)。"""
login = self._card(self.tab_launch, "账号登录") body = ttk.Frame(self.page_launch, style="Content.TFrame")
body.pack(fill="both", expand=True, padx=16, pady=14)
left = ttk.Frame(body, style="Content.TFrame")
left.pack(side="left", fill="both", expand=True)
right = ttk.Frame(body, style="Content.TFrame")
right.pack(side="left", fill="both", expand=True, padx=(14, 0))
login = self._card(left, "账号登录")
self.login_mode = tk.StringVar(value="offline") self.login_mode = tk.StringVar(value="offline")
row = ttk.Frame(login, style="Card.TFrame") row = ttk.Frame(login, style="Card.TFrame")
row.pack(fill="x", pady=4) row.pack(fill="x", pady=4)
ttk.Radiobutton(row, text="离线模式", variable=self.login_mode, value="offline", ttk.Radiobutton(row, text="离线模式", variable=self.login_mode, value="offline",
command=self._toggle_login).pack(side="left", padx=(0, 16)) command=self._toggle_login).pack(side="left", padx=(0, 16))
ttk.Radiobutton(row, text="第三方登录 (Yggdrasil)", ttk.Radiobutton(row, text="第三方登录", variable=self.login_mode, value="yggdrasil",
variable=self.login_mode, value="yggdrasil",
command=self._toggle_login).pack(side="left") command=self._toggle_login).pack(side="left")
self.offline_frame = ttk.Frame(login, style="Card.TFrame") self.offline_frame = ttk.Frame(login, style="Card.TFrame")
self.offline_frame.pack(fill="x") self.offline_frame.pack(fill="x")
ttk.Label(self.offline_frame, text="玩家名:", style="Card.TLabel").pack(side="left") ttk.Label(self.offline_frame, text="玩家名:", style="Card.TLabel").pack(side="left")
self.offline_name = ttk.Entry(self.offline_frame, width=24) self.offline_name = ttk.Entry(self.offline_frame, width=22)
self.offline_name.pack(side="left", padx=8) self.offline_name.pack(side="left", padx=8)
self.ygg_frame = ttk.Frame(login, style="Card.TFrame") self.ygg_frame = ttk.Frame(login, style="Card.TFrame")
self.ygg_frame.pack(fill="x") self.ygg_frame.pack(fill="x")
self.ygg_frame.pack_forget() self.ygg_frame.pack_forget()
ttk.Label(self.ygg_frame, text="皮肤站:", style="Card.TLabel").grid(row=0, column=0, sticky="w", pady=2) ttk.Label(self.ygg_frame, text="皮肤站:", style="Card.TLabel").grid(row=0, column=0, sticky="w", pady=2)
self.server_var = tk.StringVar(value="LittleSkin") self.server_var = tk.StringVar(value="LittleSkin")
self.server_box = ttk.Combobox(self.ygg_frame, textvariable=self.server_var, self.server_box = ttk.Combobox(self.ygg_frame, textvariable=self.server_var,
values=list(YggdrasilAuth.PRESETS.keys()), state="readonly", width=22) values=list(YggdrasilAuth.PRESETS.keys()), state="readonly", width=18)
self.server_box.grid(row=0, column=1, sticky="w", pady=2) self.server_box.grid(row=0, column=1, sticky="w", pady=2)
ttk.Label(self.ygg_frame, text="账号:", style="Card.TLabel").grid(row=1, column=0, sticky="w", pady=2)
ttk.Label(self.ygg_frame, text="邮箱/用户名:", style="Card.TLabel").grid(row=1, column=0, sticky="w", pady=2) self.ygg_user = ttk.Entry(self.ygg_frame, width=18)
self.ygg_user = ttk.Entry(self.ygg_frame, width=24)
self.ygg_user.grid(row=1, column=1, sticky="w", pady=2) self.ygg_user.grid(row=1, column=1, sticky="w", pady=2)
ttk.Label(self.ygg_frame, text="密码:", style="Card.TLabel").grid(row=2, column=0, sticky="w", pady=2) ttk.Label(self.ygg_frame, text="密码:", style="Card.TLabel").grid(row=2, column=0, sticky="w", pady=2)
self.ygg_pass = ttk.Entry(self.ygg_frame, width=24, show="*") self.ygg_pass = ttk.Entry(self.ygg_frame, width=18, show="*")
self.ygg_pass.grid(row=2, column=1, sticky="w", pady=2) self.ygg_pass.grid(row=2, column=1, sticky="w", pady=2)
ttk.Label(self.ygg_frame, text="支持 LittleSkin 等 authlib-injector 皮肤站", ver = self._card(left, "游戏版本")
style="Card.TLabel").grid(row=3, column=0, columnspan=2, sticky="w", pady=2)
ver = self._card(self.tab_launch, "游戏版本")
row = ttk.Frame(ver, style="Card.TFrame") row = ttk.Frame(ver, style="Card.TFrame")
row.pack(fill="x") row.pack(fill="x")
self.version_var = tk.StringVar() self.version_var = tk.StringVar()
self.version_box = ttk.Combobox(row, textvariable=self.version_var, state="readonly", width=28) self.version_box = ttk.Combobox(row, textvariable=self.version_var, state="readonly", width=22)
self.version_box.pack(side="left") self.version_box.pack(side="left")
ttk.Button(row, text="刷新", command=self.refresh_versions).pack(side="left", padx=8) ttk.Button(row, text="刷新", command=self.refresh_versions).pack(side="left", padx=8)
self.show_snapshot = tk.BooleanVar(value=False) self.show_snapshot = tk.BooleanVar(value=False)
ttk.Checkbutton(ver, text="显示快照版", variable=self.show_snapshot, ttk.Checkbutton(ver, text="显示快照版", variable=self.show_snapshot,
command=self.refresh_versions).pack(anchor="w", pady=(6, 0)) command=self.refresh_versions).pack(anchor="w", pady=(6, 0))
cfg = self._card(self.tab_launch, "启动设置") cfg = self._card(left, "启动设置")
row = ttk.Frame(cfg, style="Card.TFrame") row = ttk.Frame(cfg, style="Card.TFrame")
row.pack(fill="x", pady=3) row.pack(fill="x", pady=3)
ttk.Label(row, text="最大内存 (MB):", style="Card.TLabel").pack(side="left") ttk.Label(row, text="内存 (MB):", style="Card.TLabel").pack(side="left")
self.mem_var = tk.StringVar(value="2048") self.mem_var = tk.StringVar(value="2048")
ttk.Entry(row, textvariable=self.mem_var, width=10).pack(side="left", padx=8) ttk.Entry(row, textvariable=self.mem_var, width=8).pack(side="left", padx=8)
row = ttk.Frame(cfg, style="Card.TFrame") row = ttk.Frame(cfg, style="Card.TFrame")
row.pack(fill="x", pady=3) row.pack(fill="x", pady=3)
ttk.Label(row, text="Java 路径 (留空自动检测):", style="Card.TLabel").pack(side="left") ttk.Label(row, text="Java 路径:", style="Card.TLabel").pack(side="left")
self.java_var = tk.StringVar() self.java_var = tk.StringVar()
ttk.Entry(row, textvariable=self.java_var, width=16).pack(side="left", padx=8) ttk.Entry(row, textvariable=self.java_var, width=14).pack(side="left", padx=8)
ttk.Button(row, text="浏览", command=self._pick_java).pack(side="left", padx=4) ttk.Button(row, text="浏览", command=self._pick_java).pack(side="left", padx=4)
row = ttk.Frame(cfg, style="Card.TFrame") row = ttk.Frame(cfg, style="Card.TFrame")
row.pack(fill="x", pady=3) row.pack(fill="x", pady=3)
ttk.Label(row, text="游戏目录:", style="Card.TLabel").pack(side="left") ttk.Label(row, text="游戏目录:", style="Card.TLabel").pack(side="left")
self.dir_var = tk.StringVar(value=str(Path.home() / ".minecraft")) self.dir_var = tk.StringVar(value=str(Path.home() / ".minecraft"))
ttk.Entry(row, textvariable=self.dir_var, width=16).pack(side="left", padx=8) ttk.Entry(row, textvariable=self.dir_var, width=14).pack(side="left", padx=8)
ttk.Button(row, text="浏览", command=self._pick_dir).pack(side="left", padx=4) ttk.Button(row, text="浏览", command=self._pick_dir).pack(side="left", padx=4)
log_card = self._card(self.tab_launch, "日志") log_card = self._card(right, "日志")
self.log_box = scrolledtext.ScrolledText(log_card, height=8, bg="#151521", self.log_box = scrolledtext.ScrolledText(log_card, height=10, bg="#f8fafc",
fg=self.TEXT, font=("Consolas", 9), fg=self.TEXT, font=("Consolas", 9),
relief="flat", state="disabled") relief="flat", state="disabled")
self.log_box.pack(fill="x") self.log_box.pack(fill="both", expand=True)
self.progress = ttk.Progressbar(log_card, mode="determinate", maximum=100) self.progress = ttk.Progressbar(log_card, mode="determinate", maximum=100)
self.progress.pack(fill="x", pady=(8, 0)) self.progress.pack(fill="x", pady=(8, 0))
self.launch_btn = ttk.Button(self.tab_launch, text="启动游戏", style="Accent.TButton", self.launch_btn = ttk.Button(right, text="启动游戏", style="Accent.TButton",
command=self._on_launch) command=self._on_launch)
self.launch_btn.pack(fill="x", padx=14, pady=14) self.launch_btn.pack(fill="x", pady=(12, 0))
def _build_res_tab(self): def _build_res_tab(self):
"""构建[资源中心]页: 版本一键下载 + Mod/插件搜索下载。""" """构建[资源中心]页: 版本一键下载 + Mod/插件搜索下载。"""
dver = self._card(self.tab_res, "游戏版本下载") body = ttk.Frame(self.page_res, style="Content.TFrame")
body.pack(fill="both", expand=True, padx=16, pady=14)
dver = self._card(body, "游戏版本下载")
row = ttk.Frame(dver, style="Card.TFrame") row = ttk.Frame(dver, style="Card.TFrame")
row.pack(fill="x") row.pack(fill="x")
self.res_version_var = tk.StringVar() self.res_version_var = tk.StringVar()
@@ -729,7 +789,7 @@ class MeowLauncherApp:
ttk.Button(row, text="刷新列表", command=self.refresh_versions).pack(side="left", padx=6) ttk.Button(row, text="刷新列表", command=self.refresh_versions).pack(side="left", padx=6)
ttk.Button(row, text="一键下载", command=self._download_version_only).pack(side="left", padx=6) ttk.Button(row, text="一键下载", command=self._download_version_only).pack(side="left", padx=6)
dmod = self._card(self.tab_res, "Mod / 插件下载 (Modrinth)") dmod = self._card(body, "Mod / 插件下载 (Modrinth)")
row = ttk.Frame(dmod, style="Card.TFrame") row = ttk.Frame(dmod, style="Card.TFrame")
row.pack(fill="x") row.pack(fill="x")
self.res_query = ttk.Entry(row, width=14) self.res_query = ttk.Entry(row, width=14)
@@ -752,18 +812,18 @@ class MeowLauncherApp:
self.res_tree.heading("title", text="名称") self.res_tree.heading("title", text="名称")
self.res_tree.heading("author", text="作者") self.res_tree.heading("author", text="作者")
self.res_tree.heading("downloads", text="下载量") self.res_tree.heading("downloads", text="下载量")
self.res_tree.column("title", width=190) self.res_tree.column("title", width=280)
self.res_tree.column("author", width=90) self.res_tree.column("author", width=130)
self.res_tree.column("downloads", width=80) self.res_tree.column("downloads", width=100)
self.res_tree.pack(fill="x", pady=6) self.res_tree.pack(fill="x", pady=6)
ttk.Button(dmod, text="下载选中项", command=self._download_selected).pack(anchor="w") ttk.Button(dmod, text="下载选中项", command=self._download_selected).pack(anchor="w")
self.res_log = scrolledtext.ScrolledText(self.tab_res, height=5, bg="#151521", self.res_log = scrolledtext.ScrolledText(body, height=5, bg="#f8fafc",
fg=self.TEXT, font=("Consolas", 9), fg=self.TEXT, font=("Consolas", 9),
relief="flat", state="disabled") relief="flat", state="disabled")
self.res_log.pack(fill="x", padx=14, pady=(4, 6)) self.res_log.pack(fill="x", pady=(6, 0))
self.res_progress = ttk.Progressbar(self.tab_res, mode="determinate", maximum=100) self.res_progress = ttk.Progressbar(body, mode="determinate", maximum=100)
self.res_progress.pack(fill="x", padx=14, pady=(0, 10)) self.res_progress.pack(fill="x", pady=(8, 0))
def _res_append(self, msg): def _res_append(self, msg):
"""向资源中心日志框追加一行。""" """向资源中心日志框追加一行。"""
@@ -871,6 +931,26 @@ class MeowLauncherApp:
self.msg_queue.put(("log", "[版本] 获取失败: %s" % e)) self.msg_queue.put(("log", "[版本] 获取失败: %s" % e))
threading.Thread(target=worker, daemon=True).start() threading.Thread(target=worker, daemon=True).start()
def _fade_in(self, step=0):
"""窗口淡入动画 (alpha 0 -> 1)。"""
try:
self.root.attributes("-alpha", min(step * 0.1, 1.0))
except Exception:
return
if step < 10:
self.root.after(18, lambda: self._fade_in(step + 1))
def _busy_button(self, text):
"""启动按钮忙碌状态 (文字闪烁)。"""
state = [0]
def tick():
if str(self.launch_btn["state"]) != "disabled":
return
state[0] += 1
self.launch_btn.configure(text=text + "." * (state[0] % 4))
self.root.after(400, tick)
tick()
def _poll_queue(self): def _poll_queue(self):
"""定时轮询消息队列, 把后台线程的日志/结果刷到界面。""" """定时轮询消息队列, 把后台线程的日志/结果刷到界面。"""
try: try:
@@ -922,6 +1002,7 @@ class MeowLauncherApp:
def _on_launch(self): def _on_launch(self):
"""点击启动游戏: 禁用按钮并开后台线程。""" """点击启动游戏: 禁用按钮并开后台线程。"""
self.launch_btn.config(state="disabled") self.launch_btn.config(state="disabled")
self._busy_button("启动中")
threading.Thread(target=self._launch_worker, daemon=True).start() threading.Thread(target=self._launch_worker, daemon=True).start()
def _launch_worker(self): def _launch_worker(self):