Files
ios-location-track/Location.spec
T
jbjf6666 4a5d676c7b docs: 新增《画轨迹 · 折线绘制 · 开始虚拟轨迹》详细教程
TUTORIAL-DRAW-AND-PLAY.txt,中英双语纯文本,1305 行 / 64 KB。

内容按真实界面元素写(对照 web/index.html 与 web/js/app.js 的实际按钮 / 交互):
- A 开始之前:三件必须做完的事(连接设备 / 挂载镜像 / 手机在手边)
- B 界面总览:四块区域 + 三个最关键按钮
- C 地图操作:平移缩放全览、切换底图、快速定位到坐标
- D 三种造轨迹方式
    D1 折线绘制(点击加点、双击或 Esc 结束、至少 2 点)
    D2 途经点 + OSRM 路线规划(拖拽排序、双击改坐标、改完要重新加载)
    D3 导入 GPX
- E 编辑:撤销 / 删除 / 移动 / 排序
- F 出行模式(驾车/骑行/步行/飞行)与播放倍速(0.1x-50x)的区别
- G 真实感参数逐个讲解:漂移 / 速度浮动 / 节奏抖动 / 位移推算速度 / 粗差跳点
- H 开始播放:前置检查清单 + 逐步现象 + 失败提示对照
- I 播放中控制:暂停 / 继续 / 拖动跳转 / 停止 的区别
- J 结束与恢复真实定位(三种方式 + 恢复不了的例外)
- K 保存复用(localStorage 与 GPX 的区别)
- L 快捷键总表
- M 常见问题(含错误提示原文对照)
- N 参数速查表

编码:UTF-8 with BOM + CRLF(记事本友好),.gitattributes 锁定 eol=crlf。
README.md / README.en.md 加入口,Location.spec 随 Windows 包一起装到安装目录。
2026-10-16 12:49:34 +08:00

230 lines
8.6 KiB
RPMSpec
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- mode: python ; coding: utf-8 -*-
"""PyInstaller 打包配置(macOS / Windows 双平台)。
用法:
macOS : ./.venv/bin/python -m PyInstaller --clean --noconfirm Location.spec
Windows: .venv\\Scripts\\python.exe -m PyInstaller --clean --noconfirm Location.spec
设计要点:
- **onedir**(不是 onefile):启动更快,onefile 每次启动都要解包到临时目录,反而更慢。
- 前端资源(web/)与图标打进包内,运行时通过 sys._MEIPASS 定位。
- 排除 pymobiledevice3 带进来但本工具用不到的重型依赖(IPython / pytest 等),
它们只被 pymobiledevice3 的 CLI 交互模式使用;本工具只用 Python API。
- **Windows 专属**:`uac_admin=True` —— 在 exe 清单里写入 requireAdministrator,
启动时直接弹 UAC 提权。这样首次运行就能自动安装 Apple Mobile Device Support,
不需要用户再手动"以管理员身份运行"。
(注:userspace 隧道本身**不需要**管理员权限,提权只是为了装驱动。)
注意:Windows 与 macOS 的差异都集中在文件末尾的 `if IS_WINDOWS:` 分支里,
保持单一 spec,避免两份配置漂移。
"""
import sys
from pathlib import Path
from PyInstaller.utils.hooks import collect_data_files, copy_metadata
IS_WINDOWS = sys.platform == "win32"
ROOT = Path(SPECPATH).resolve()
# 有些包在运行期用 importlib.metadata 读自己的版本(例如 apple_compress 的 __version__,
# 它被 pyimg4 用于个性化镜像处理)。PyInstaller 默认不会打包 dist-info,
# 漏掉后挂载会报 "No package metadata was found for apple_compress"。
METADATA_PACKAGES = [
"apple_compress", "pymobiledevice3", "pyimg4", "construct",
"cryptography", "bpylist2", "ipsw_parser", "opack2",
# Windows 用户态隧道依赖链
"pytun_pmd3", "pmd_pytcp", "pmd_net_addr", "pmd_net_proto",
]
_metadata_datas: list = []
for _pkg in METADATA_PACKAGES:
try:
_metadata_datas += copy_metadata(_pkg)
except Exception:
pass
# pytun_pmd3 自带各架构的 wintun.dll(数据文件,PyInstaller 不会自动收集)。
# Windows 的 userspace 隧道走纯 Python PyTCP 栈,通常不需要它;但某些路径会用
# pytun_pmd3.TunTapDevice,带上以免运行期缺 dll。整包只有 ~1.5MB。
_extra_datas: list = []
try:
_extra_datas += collect_data_files("pytun_pmd3", includes=["wintun/**"])
except Exception:
pass
# 只排除"构建期工具"和"确定与运行无关的开发工具"。
# 依据(macOS 侧实测):questionary / prompt_toolkit / pygments 被 pymobiledevice3
# 运行期导入,曾因误排导致打包版真机不可用;因此与 pymobiledevice3 相关的一律保留。
EXCLUDES = [
# 注意:**不要**排除 distutils / pkg_resources / setuptools / wheel / pip
# —— PyInstaller 与 setuptools 的 hook 会强制导入/别名它们,排除后构建直接失败:
# ValueError: Target module "distutils" already imported as ExcludedModule('distutils',)
# ValueError: Target module "wheel" already imported as ExcludedModule('wheel',)
# (两个坑都在本项目上实测复现过;wheel 那条在 Windows + setuptools 84 上触发。)
# 它们体积很小,排除掉的收益远小于构建失败的风险。
#
# 开发/测试工具(pymobiledevice3 的交互式 CLI 才会用)
"IPython", "ipython", "ipykernel", "jupyter", "jupyter_client", "jupyter_core",
"ipywidgets", "notebook", "nbformat", "nbconvert",
"pytest", "_pytest", "matplotlib", "matplotlib_inline",
# 注意:**不要**排除 tkinter —— 「环境准备」窗口用它实现(标准库,随 exe 打包)。
]
# 运行期动态 import 的模块,PyInstaller 静态分析看不到
HIDDENIMPORTS = [
"pymobiledevice3.remote.remote_service_discovery",
"pymobiledevice3.remote.tunnel_service",
"pymobiledevice3.services.dvt.instruments.dvt_provider",
"pymobiledevice3.services.dvt.instruments.location_simulation",
"pymobiledevice3.lockdown",
"pymobiledevice3.usbmux",
"pymobiledevice3.service_connection",
"pymobiledevice3.exceptions",
"pymobiledevice3.services.mobile_image_mounter",
"developer_disk_image.repo",
"core.apple_driver",
"core.platform",
"core.setup_window",
"tkinter",
"tkinter.ttk",
"tkinter.font",
]
if IS_WINDOWS:
HIDDENIMPORTS += [
# Windows 走 in-process userspace 隧道(不是 macOS 的 native_tunnel)
"pymobiledevice3.remote.userspace_tunnel",
"pytun_pmd3",
"pytun_pmd3.wintun",
"pmd_pytcp",
"pmd_net_addr",
"pmd_net_proto",
"winreg",
"ctypes.wintypes",
]
else:
HIDDENIMPORTS += ["pymobiledevice3.remote.native_tunnel"]
# 打进包内的资源
_datas = _metadata_datas + _extra_datas + [
("web", "web"), # 前端(index.html / style.css / js/)
# 开发者镜像素材(iOS 17+ 全系共用的个性化镜像;Image.dmg 约 15MB)
("resources/ddi", "resources/ddi"),
("docs/PROTOCOL.md", "docs"),
("README.md", "."),
]
if IS_WINDOWS:
_datas += [
("resources/AppIcon.ico", "resources"),
# 中英双语使用说明,随包一起装到安装目录,用户直接就能看(记事本友好:CRLF + BOM)
("USAGE.txt", "."),
("TUTORIAL-DRAW-AND-PLAY.txt", "."),
("docs/README-WINDOWS.md", "docs") if (ROOT / "docs" / "README-WINDOWS.md").exists() else ("README.md", "docs"),
]
else:
_datas += [("resources/AppIcon.icns", "resources")]
a = Analysis(
["main.py"],
pathex=[str(ROOT)],
binaries=[],
datas=_datas,
hiddenimports=HIDDENIMPORTS,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=EXCLUDES,
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
if IS_WINDOWS:
# ---------------- Windows:onedir + .exe(启动时请求管理员权限)----------------
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="iOSLocationTrack",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False, # 不弹黑窗;日志走界面与控制台无关
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(ROOT / "resources" / "AppIcon.ico"),
# ★ 启动时直接请求管理员权限(exe 清单 requestedExecutionLevel=requireAdministrator)。
# 目的:首次运行即可自动安装 Apple Mobile Device Support,不需要用户手动右键提权。
uac_admin=True,
version=str(ROOT / "installer" / "version_info.txt")
if (ROOT / "installer" / "version_info.txt").exists() else None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="iOSLocationTrack",
)
else:
# ---------------- macOS:onedir + .app bundle ----------------
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="iOSLocationTrack",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(ROOT / "resources" / "AppIcon.icns"),
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="iOSLocationTrack",
)
app = BUNDLE(
coll,
name="虚拟定位轨迹.app",
icon=str(ROOT / "resources" / "AppIcon.icns"),
bundle_identifier="com.local.ios-location-track",
version="1.1.0",
info_plist={
"CFBundleName": "虚拟定位轨迹",
"CFBundleDisplayName": "虚拟定位轨迹",
"CFBundleShortVersionString": "1.1.0",
"CFBundleVersion": "1.1.0",
"CFBundlePackageType": "APPL",
"CFBundleIconFile": "AppIcon",
"CFBundleIconName": "AppIcon",
"LSMinimumSystemVersion": "12.0",
"NSHighResolutionCapable": True,
"LSApplicationCategoryType": "public.app-category.developer-tools",
"NSHumanReadableCopyright": "本地工具,用于自有 App 的定位功能测试",
"NSAppleEventsUsageDescription": "用于以管理员权限建立 iOS 设备隧道。",
"LSUIElement": False,
},
)