diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index a47552f..cc1eb4b 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,14 @@
-
+
+
+
+
+
+
+
+
diff --git a/Entity/Variables.py b/Entity/Variables.py
index 069994e..a24f720 100644
--- a/Entity/Variables.py
+++ b/Entity/Variables.py
@@ -3,8 +3,11 @@ from typing import Dict, Any
from Entity.AnchorModel import AnchorModel
# wda apple bundle id
-WdaAppBundleId = "com.yolozsAgent.wda.xctrunner"
-
+WdaAppBundleId = "com.yolojtAgent.wda.xctrunner"
+# wda投屏端口
+wdaScreenPort = 9567
+# wda功能端口
+wdaFunctionPort = 8567
# 全局主播列表
anchorList: list[AnchorModel] = []
# 线程锁
diff --git a/Entity/__pycache__/Variables.cpython-312.pyc b/Entity/__pycache__/Variables.cpython-312.pyc
index eff3e48..33bcf4c 100644
Binary files a/Entity/__pycache__/Variables.cpython-312.pyc and b/Entity/__pycache__/Variables.cpython-312.pyc differ
diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py
index 6867093..92ad39d 100644
--- a/Module/DeviceInfo.py
+++ b/Module/DeviceInfo.py
@@ -1,17 +1,21 @@
import os
import signal
import subprocess
+import sys
+import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Dict, Optional, List
import tidevice
+import usb
import wda
from tidevice import Usbmux, ConnectionType
from tidevice._device import BaseDevice
from Entity.DeviceModel import DeviceModel
-from Entity.Variables import WdaAppBundleId
+from Entity.Variables import WdaAppBundleId, wdaFunctionPort, wdaScreenPort
from Module.FlaskSubprocessManager import FlaskSubprocessManager
+from Module.IOSActivator import IOSActivator
from Utils.LogManager import LogManager
import socket
@@ -36,7 +40,6 @@ class DeviceInfo:
orphan_gc_tick = 0
while True:
online = {d.udid for d in Usbmux().device_list() if d.conn_type == ConnectionType.USB}
-
# 拔掉——同步
for udid in list(self._models):
if udid not in online:
@@ -107,8 +110,16 @@ class DeviceInfo:
print("进入启动wda方法")
try:
dev = tidevice.Device(udid)
- print("获取tidevice对象成功,准备启动wda")
- dev.app_start(WdaAppBundleId)
+ systemVersion = int(dev.product_version.split(".")[0])
+ # 判断运行wda的逻辑
+ if systemVersion > 17:
+ ios = IOSActivator()
+ threading.Thread(
+ target=ios.activate,
+ args=(udid,)
+ ).start()
+ else:
+ dev.app_start(WdaAppBundleId)
print("启动wda成功")
time.sleep(3)
return True
@@ -118,7 +129,7 @@ class DeviceInfo:
def _screen_info(self, udid: str):
try:
- c = wda.USBClient(udid, 8100)
+ c = wda.USBClient(udid, wdaFunctionPort)
c.home()
size = c.window_size()
scale = c.scale
@@ -139,7 +150,7 @@ class DeviceInfo:
flags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
return subprocess.Popen(
- [self._iproxy_path, "-u", udid, str(port), "9100"],
+ [self._iproxy_path, "-u", udid, str(port), wdaScreenPort],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=flags
diff --git a/Module/IOSActivator.py b/Module/IOSActivator.py
new file mode 100644
index 0000000..90f6812
--- /dev/null
+++ b/Module/IOSActivator.py
@@ -0,0 +1,300 @@
+
+import os
+import random
+import re
+import socket
+import sys
+import subprocess
+from typing import Optional
+
+from Entity.Variables import WdaAppBundleId
+
+
+class IOSActivator:
+ """
+ 轻量 iOS 激活器(仅代码调用):
+ 1) 启动 `pymobiledevice3 remote tunneld`(基于传入 UDID)
+ 2) 自动挂载 Developer Disk Image
+ 3) 设备隧道就绪后启动 WDA:
+ - 优先使用 `--rsd ` 直连(支持 IPv6)
+ - 失败再用 `PYMOBILEDEVICE3_TUNNEL=127.0.0.1:` 作为退路(仅 IPv4)
+ """
+
+ def __init__(self, python_executable: Optional[str] = None):
+ self.python = python_executable or sys.executable
+
+ # --------------------------
+ # 内部工具
+ # --------------------------
+ def _auto_mount_developer_disk(self, udid: str, retries: int = 3, backoff_seconds: float = 2.0) -> None:
+ """使用 `pymobiledevice3 mounter auto-mount` 为指定 UDID 挂载开发者镜像(带重试)。"""
+ env = os.environ.copy()
+ env["PYMOBILEDEVICE3_UDID"] = udid
+
+ max_attempts = max(1, int(retries))
+ last_err = None
+ for i in range(max_attempts):
+ try:
+ out = subprocess.check_output(
+ [self.python, "-m", "pymobiledevice3", "mounter", "auto-mount"],
+ text=True,
+ stderr=subprocess.STDOUT,
+ env=env,
+ )
+ if out:
+ for line in out.splitlines():
+ print(f"[mounter] {line}")
+ print("[mounter] Developer disk image mounted.")
+ return
+ except subprocess.CalledProcessError as exc:
+ lowered = (exc.output or "").lower()
+ if "already mounted" in lowered:
+ print("[mounter] Developer disk image already mounted.")
+ return
+ last_err = exc
+ if i < max_attempts - 1:
+ print(f"[mounter] attempt {i+1}/{max_attempts} failed, retrying in {backoff_seconds}s ...")
+ try:
+ import time as _t
+ _t.sleep(backoff_seconds)
+ except Exception:
+ pass
+
+ msg = last_err.output if isinstance(last_err, subprocess.CalledProcessError) else str(last_err)
+ raise RuntimeError(f"Auto-mount failed after {max_attempts} attempts: {msg}")
+
+ def _is_ipv4_host(self, host: str) -> bool:
+ return bool(re.match(r"^\d{1,3}(\.\d{1,3}){3}$", host))
+
+ def _wait_for_rsd_ready(self, rsd_host: str, rsd_port: str, retries: int = 5, delay: float = 3.0) -> bool:
+ """
+ 探测 RSD 通道是否就绪:直接尝试 TCP 连接。
+ """
+ port_int = int(rsd_port)
+ for i in range(1, retries + 1):
+ print(f"[rsd] Probing RSD {rsd_host}:{rsd_port} (attempt {i}/{retries}) ...")
+ try:
+ with socket.create_connection((rsd_host, port_int), timeout=2):
+ print("[rsd] ✅ RSD is reachable and ready.")
+ return True
+ except (socket.timeout, ConnectionRefusedError, OSError) as e:
+ print(f"[rsd] Not ready yet ({e}). Retrying...")
+ import time as _t
+ _t.sleep(delay)
+ print("[rsd] ❌ RSD did not become ready after retries.")
+ return False
+
+ def _launch_wda_via_rsd(self, bundle_id: str, rsd_host: str, rsd_port: str, udid: str) -> None:
+ """
+ 使用 `--rsd ` 直连设备隧道来启动 WDA(推荐路径,IPv4/IPv6 都 OK)。
+ 不设置 PYMOBILEDEVICE3_TUNNEL,避免 IPv6 解析问题。
+ """
+ print(f"[wda] Launch via RSD {rsd_host}:{rsd_port}, bundle: {bundle_id}")
+ env = os.environ.copy()
+ env["PYMOBILEDEVICE3_UDID"] = udid
+
+ args = [
+ self.python, "-m", "pymobiledevice3",
+ "developer", "dvt", "launch", bundle_id,
+ "--rsd", rsd_host, rsd_port,
+ ]
+ try:
+ out = subprocess.check_output(args, text=True, stderr=subprocess.STDOUT, env=env)
+ except subprocess.CalledProcessError as exc:
+ raise RuntimeError(f"WDA launch via RSD failed: {exc.output}") from exc
+
+ if out:
+ for line in out.splitlines():
+ print(f"[wda] {line}")
+ print("[wda] Launch via RSD completed.")
+
+ def _launch_wda_via_http_tunnel(self, bundle_id: str, http_host: str, http_port: str, udid: str) -> None:
+ """
+ 退路:通过 HTTP 网关端口设置 PYMOBILEDEVICE3_TUNNEL(仅 IPv4)。
+ """
+ if not self._is_ipv4_host(http_host):
+ raise RuntimeError(f"HTTP tunnel host must be IPv4, got {http_host}")
+
+ tunnel_endpoint = f"{http_host}:{http_port}"
+ print(f"[wda] Launch via HTTP tunnel {tunnel_endpoint}, bundle: {bundle_id}")
+ env = os.environ.copy()
+ env["PYMOBILEDEVICE3_TUNNEL"] = tunnel_endpoint
+ env["PYMOBILEDEVICE3_UDID"] = udid
+
+ args = [self.python, "-m", "pymobiledevice3", "developer", "dvt", "launch", bundle_id]
+ try:
+ out = subprocess.check_output(args, text=True, stderr=subprocess.STDOUT, env=env)
+ except subprocess.CalledProcessError as exc:
+ raise RuntimeError(f"WDA launch via HTTP tunnel failed: {exc.output}") from exc
+
+ if out:
+ for line in out.splitlines():
+ print(f"[wda] {line}")
+ print("[wda] Launch via HTTP tunnel completed.")
+
+ def _pick_available_port(self, base=49151, step=10) -> int:
+ """挑选一个可用端口(避免重复)"""
+ for i in range(0, 1000, step):
+ port = base + i
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ if s.connect_ex(("127.0.0.1", port)) != 0:
+ return port
+ raise RuntimeError("No free port found for tunneld")
+
+ # --------------------------
+ # 对外方法
+ # --------------------------
+ def activate(
+ self,
+ udid: str,
+ wda_bundle_id: Optional[str] = WdaAppBundleId,
+ ready_timeout_sec: float = 120.0,
+ mount_retries: int = 3,
+ backoff_seconds: float = 2.0,
+ rsd_probe_retries: int = 5,
+ rsd_probe_delay_sec: float = 3.0,
+ ) -> str:
+ """
+ 执行:开隧道 -> (等待 RSD 就绪)-> 挂载镜像 -> 启动 WDA
+ - 优先用 `--rsd` 启动(先做 dvt list 探测)
+ - 失败再用 HTTP 端口作为退路
+ """
+ if not udid or not isinstance(udid, str):
+ raise ValueError("udid is required and must be a non-empty string")
+
+ print(f"[activate] UDID = {udid}")
+ env = os.environ.copy()
+ env["PYMOBILEDEVICE3_UDID"] = udid
+
+ # 1) 开隧道(子进程常驻)
+ port = self._pick_available_port()
+ cmd = [self.python, "-m", "pymobiledevice3", "remote", "tunneld", "--port", str(port)]
+
+ proc = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ env=env,
+ )
+
+ captured: list[str] = []
+ http_host: Optional[str] = None
+ http_port: Optional[str] = None
+ rsd_host: Optional[str] = None
+ rsd_port: Optional[str] = None
+ device_tunnel_ready = False
+ wda_started = False
+ mount_done = False
+
+ import time as _t
+ start_ts = _t.time()
+
+ HTTP_RE = re.compile(r"http://([0-9.]+):(\d+)")
+ RSD_CREATED_RE = re.compile(r"Created tunnel\s+--rsd\s+([^\s]+)\s+(\d+)")
+
+ try:
+ assert proc.stdout is not None
+ for line in proc.stdout:
+ captured.append(line)
+ print(f"[tunneld] {line}", end="")
+
+ if proc.poll() is not None:
+ break
+
+ # 捕获 HTTP 网关端口
+ if http_port is None:
+ m = HTTP_RE.search(line)
+ if m:
+ http_host, http_port = m.group(1), m.group(2)
+ print(f"[tunneld] Tunnel API: {http_host}:{http_port}")
+
+ # 捕获设备 RSD(可能 IPv6)
+ m = RSD_CREATED_RE.search(line)
+ if m:
+ rsd_host, rsd_port = m.group(1), m.group(2)
+ device_tunnel_ready = True
+ print(f"[tunneld] Device-level tunnel ready (RSD {rsd_host}:{rsd_port}).")
+
+ # 条件满足后推进
+ if (not wda_started) and wda_bundle_id and device_tunnel_ready:
+ try:
+ if not mount_done:
+ self._auto_mount_developer_disk(
+ udid, retries=mount_retries, backoff_seconds=backoff_seconds
+ )
+ mount_done = True
+
+ # 先做 RSD 就绪探测,再走 RSD 启动
+ rsd_ok = False
+ if rsd_host and rsd_port:
+ rsd_ok = self._wait_for_rsd_ready(
+ rsd_host, rsd_port,
+ retries=rsd_probe_retries,
+ delay=rsd_probe_delay_sec,
+ )
+ if rsd_ok:
+ self._launch_wda_via_rsd(
+ bundle_id=wda_bundle_id,
+ rsd_host=rsd_host, # type: ignore[arg-type]
+ rsd_port=rsd_port, # type: ignore[arg-type]
+ udid=udid,
+ )
+ else:
+ # RSD 不就绪或失败,回退到 HTTP 网关(必须是 IPv4)
+ if http_host and http_port:
+ self._launch_wda_via_http_tunnel(
+ bundle_id=wda_bundle_id,
+ http_host=http_host,
+ http_port=http_port,
+ udid=udid,
+ )
+ else:
+ raise RuntimeError("No valid tunnel endpoint for fallback.")
+
+ wda_started = True
+
+ except RuntimeError as exc:
+ print(str(exc), file=sys.stderr)
+ proc.terminate()
+ try:
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ raise
+
+ # 超时保护(仍未启动 WDA)
+ if (not wda_started) and ready_timeout_sec > 0 and (_t.time() - start_ts > ready_timeout_sec):
+ print(f"[tunneld] Timeout waiting for device tunnel ({ready_timeout_sec}s). Aborting.")
+ proc.terminate()
+ try:
+ proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ break
+
+ # 等待子进程结束(若已结束)
+ try:
+ return_code = proc.wait(timeout=5)
+ except subprocess.TimeoutExpired:
+ proc.kill()
+ return_code = proc.returncode or -9
+
+ output = "".join(captured)
+ if return_code != 0 and not wda_started:
+ raise RuntimeError(f"tunneld exited with code {return_code}.\n{output}")
+
+ print("[activate] Done.")
+ return output
+
+ except KeyboardInterrupt:
+ print("\n[activate] Interrupted, cleaning up ...", file=sys.stderr)
+ try:
+ proc.terminate()
+ proc.wait(timeout=5)
+ except Exception:
+ try:
+ proc.kill()
+ except Exception:
+ pass
+ raise
diff --git a/Module/Main.py b/Module/Main.py
index 8b8a9f3..a964353 100644
--- a/Module/Main.py
+++ b/Module/Main.py
@@ -36,7 +36,7 @@ def main(arg):
if __name__ == "__main__":
# 获取启动时候传递的参数
- main(sys.argv)
+ # main(sys.argv)
# 添加iOS开发包到电脑上
deployer = DevDiskImageDeployer(verbose=True)
diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc
index f2ddf59..a12092b 100644
Binary files a/Module/__pycache__/DeviceInfo.cpython-312.pyc and b/Module/__pycache__/DeviceInfo.cpython-312.pyc differ
diff --git a/Module/__pycache__/Main.cpython-312.pyc b/Module/__pycache__/Main.cpython-312.pyc
index d403ad4..cdb3f18 100644
Binary files a/Module/__pycache__/Main.cpython-312.pyc and b/Module/__pycache__/Main.cpython-312.pyc differ
diff --git a/Utils/LogManager.py b/Utils/LogManager.py
index 3981133..0c8bb94 100644
--- a/Utils/LogManager.py
+++ b/Utils/LogManager.py
@@ -26,7 +26,7 @@ def _force_utf8_everywhere():
except Exception:
pass
-_force_utf8_everywhere()
+# _force_utf8_everywhere()
class LogManager:
"""
diff --git a/Utils/__pycache__/LogManager.cpython-312.pyc b/Utils/__pycache__/LogManager.cpython-312.pyc
index f1c24fa..3e4d8a6 100644
Binary files a/Utils/__pycache__/LogManager.cpython-312.pyc and b/Utils/__pycache__/LogManager.cpython-312.pyc differ