ubuntu-24.04.3-wsl-amd64.gz
https://softwares-b243.obs.cn-north-4.myhuaweicloud.com/ubuntu-24.04.3-wsl-amd64.gz
#!/usr/bin/env python3
import os
import sys
import shutil
import subprocess
import re
import plistlib # <--- 新增这个库,用于修改 Info.plist
from pathlib import Path
# ==============================================================================
# 配置区
# ==============================================================================
SCRIPT_DIR = Path(__file__).resolve().parent
def get_version():
init_path = SCRIPT_DIR / "mind_tree/__init__.py"
try:
with open(init_path, "r", encoding="utf-8") as f:
content = f.read()
match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
if match:
return match.group(1)
return "1.0.0"
except:
return "1.0.0"
VERSION = get_version()
class Config:
APP_NAME = "Mind Tree"
APP_SLUG = "mind-tree"
VERSION = VERSION
SCRIPT_DIR = SCRIPT_DIR
PYINSTALLER_ENTRY_POINT = SCRIPT_DIR / "mind_tree/main.py"
DIST_DIR = SCRIPT_DIR / "dist"
WORK_DIR = SCRIPT_DIR / "build"
ICON_PATH = SCRIPT_DIR / "mind_tree/libs/mind-tree-logo-3.ico"
ASSETS_SOURCE = SCRIPT_DIR / "mind_tree/libs"
ASSETS_DEST = "libs"
# ==============================================================================
# 工具函数
# ==============================================================================
def log(msg):
print(f"\033[0;32m[MacBuild] {msg}\033[0m")
def run_cmd(cmd):
print(f"Running: {' '.join([str(c) for c in cmd])}")
subprocess.check_call([str(c) for c in cmd])
def get_snownlp_path():
import snownlp
return Path(snownlp.__file__).parent
def modify_info_plist(app_path):
"""
修改生成的 Info.plist,设置 LSUIElement=True (隐藏 Dock 图标)
"""
plist_path = app_path / "Contents" / "Info.plist"
if not plist_path.exists():
print(f"Error: Info.plist not found at {plist_path}")
sys.exit(1)
log(f"Modifying Info.plist at: {plist_path}")
try:
# 读取 Plist
with open(plist_path, 'rb') as f:
plist_data = plistlib.load(f)
# 修改:设置为后台应用
plist_data['LSUIElement'] = True
# 也可以顺便加上 Bundle Identifier,防止系统混淆
plist_data['CFBundleIdentifier'] = "com.mindtree.app"
# 写回 Plist
with open(plist_path, 'wb') as f:
plistlib.dump(plist_data, f)
log("Info.plist modified successfully (LSUIElement=True).")
except Exception as e:
print(f"Failed to modify Info.plist: {e}")
sys.exit(1)
# ==============================================================================
# 主逻辑
# ==============================================================================
def main():
log("=== Starting macOS Build Process (Plan B) ===")
# 1. PyInstaller 打包
snownlp_path = get_snownlp_path()
cmd = [
"pyinstaller",
"--clean", "--noconfirm",
"--onedir",
"--windowed",
"--name", Config.APP_NAME,
"--workpath", str(Config.WORK_DIR),
"--distpath", str(Config.DIST_DIR),
"--specpath", str(Config.WORK_DIR),
"--add-data", f"{Config.ASSETS_SOURCE}:{Config.ASSETS_DEST}",
"--icon", str(Config.ICON_PATH),
"-p", str(Config.SCRIPT_DIR),
"--hidden-import", "sip",
# 注意:这里去掉了 --runtime-hook 和 --plist-items
# 我们改用 post-build 修改的方式
]
# SnowNLP 数据
for sub in ["normal", "tag", "seg", "sentiment"]:
src = snownlp_path / sub
if src.exists():
cmd.extend(["--add-data", f"{src}:snownlp/{sub}"])
cmd.append(str(Config.PYINSTALLER_ENTRY_POINT))
run_cmd(cmd)
app_path = Config.DIST_DIR / f"{Config.APP_NAME}.app"
if not app_path.exists():
print("Error: .app bundle not found!")
sys.exit(1)
log(f"App bundle created at: {app_path}")
# 2. 【关键步骤】修改 Info.plist 使其默认为后台应用
modify_info_plist(app_path)
# 3. 重新签名 (因为修改了 plist,必须重签,否则 M1 上会崩溃)
log("Applying Ad-hoc signature (Required for Apple Silicon)...")
try:
run_cmd(["codesign", "--force", "--deep", "--sign", "-", str(app_path)])
log("Ad-hoc signing complete.")
except Exception as e:
print(f"Warning: Signing failed. Error: {e}")
# 4. 创建 .dmg
dmg_name = f"{Config.APP_SLUG}-{Config.VERSION}-macos.dmg"
dmg_path = Config.DIST_DIR / dmg_name
if dmg_path.exists():
dmg_path.unlink()
log("Creating .dmg disk image...")
run_cmd([
"hdiutil", "create",
"-volname", Config.APP_NAME,
"-srcfolder", str(app_path),
"-ov",
"-format", "UDZO",
str(dmg_path)
])
log("✅ macOS Build Complete!")
log(f"DMG File: {dmg_path}")
if __name__ == "__main__":
main()
# mind_tree/main.py
import sys
import multiprocessing
# ... 其他 import ...
def promote_to_foreground():
"""
[macOS] 将当前进程从后台(LSUIElement)升级为前台应用。
效果:图标会出现在 Dock 栏,且窗口可以获取焦点。
"""
if sys.platform != 'darwin':
return
try:
from ctypes import cdll, util, c_int, c_void_p, Structure, POINTER, byref
# 加载系统库
app_services = cdll.LoadLibrary(util.find_library('ApplicationServices'))
# 1. 获取当前进程 PSN (Process Serial Number)
class ProcessSerialNumber(Structure):
_fields_ = [("highLongOfPSN", c_int), ("lowLongOfPSN", c_int)]
psn = ProcessSerialNumber()
# GetCurrentProcess(ProcessSerialNumber *PSN)
app_services.GetCurrentProcess.argtypes = [POINTER(ProcessSerialNumber)]
app_services.GetCurrentProcess(byref(psn))
# 2. 变形!(TransformProcessType)
# kProcessTransformToForegroundApplication = 1
# 这会让图标优雅地滑入 Dock 栏
app_services.TransformProcessType.argtypes = [POINTER(ProcessSerialNumber), c_int]
app_services.TransformProcessType(byref(psn), 1)
# 3. (可选) 强制让应用置顶并获取焦点
# 如果发现图标出来了但窗口在后面,可以加这段逻辑
# 为了不引入 pyobjc 依赖,这里简化处理,通常 GUI 框架(PyQt/Tkinter)的 show() 会处理好焦点
except Exception as e:
print(f"设置 Dock 图标失败: {e}")
# rthook_hide_dock.py
import sys
import os
from ctypes import cdll, util, c_int, c_uint, Structure, byref, POINTER
# 定义 macOS 的 ProcessSerialNumber 结构体
class ProcessSerialNumber(Structure):
_fields_ = [("highLongOfPSN", c_uint),
("lowLongOfPSN", c_uint)]
def _log(msg):
try:
with open("/tmp/mit_debug.txt", "a") as f:
pid = os.getpid()
f.write(f"[PID {pid}] {msg}\n")
except:
pass
if sys.platform == 'darwin':
try:
args = sys.argv
should_hide = False
# 1. 检测逻辑 (保持你之前的逻辑,因为检测是成功的)
if '-c' in args:
for arg in args:
if 'resource_tracker' in arg:
should_hide = True
break
for arg in args:
if 'multiprocessing' in arg and 'fork' in arg:
should_hide = True
break
if should_hide:
# 加载 ApplicationServices
core_services_lib = util.find_library('ApplicationServices')
if core_services_lib:
core_services = cdll.LoadLibrary(core_services_lib)
# === 修正部分开始 ===
# 1. 定义 GetCurrentProcess 函数
# OSErr GetCurrentProcess(ProcessSerialNumber *PSN);
core_services.GetCurrentProcess.argtypes = [POINTER(ProcessSerialNumber)]
core_services.GetCurrentProcess.restype = c_int
# 2. 定义 TransformProcessType 函数
# OSStatus TransformProcessType(const ProcessSerialNumber *psn, ProcessApplicationTransformState transformState);
core_services.TransformProcessType.argtypes = [POINTER(ProcessSerialNumber), c_int]
core_services.TransformProcessType.restype = c_int
# 3. 获取当前进程的 PSN
psn = ProcessSerialNumber()
err_get = core_services.GetCurrentProcess(byref(psn))
if err_get == 0:
# 4. 使用获取到的 PSN 进行转换
# kProcessTransformToBackgroundApplication = 2
err_transform = core_services.TransformProcessType(byref(psn), 2)
_log(f"隐藏尝试完成。GetPSN: {err_get}, Transform: {err_transform}")
else:
_log(f"获取进程PSN失败: {err_get}")
# === 修正部分结束 ===
else:
_log("错误:无法加载 ApplicationServices")
except Exception as e:
_log(f"发生异常: {e}")
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install python@3.12
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc
import sys
def make_process_background():
"""
将当前进程转为后台进程(隐藏 Dock 图标)。
仅在 macOS 下打包后的环境有效。
"""
if sys.platform != 'darwin':
return
try:
from ctypes import cdll, util, c_int, c_void_p
# 1. 加载 ApplicationServices 框架
core_services_path = util.find_library('ApplicationServices')
if not core_services_path:
return
core_services = cdll.LoadLibrary(core_services_path)
# 2. 调用 TransformProcessType
# kProcessTransformToBackgroundApplication = 2
# 这会让系统把当前进程标记为后台进程,从而移除 Dock 图标
core_services.TransformProcessType.argtypes = [c_void_p, c_int]
core_services.TransformProcessType(None, 2)
except Exception:
# 如果失败了也不要让程序崩溃,只是图标去不掉而已
pass