33
44import datetime
55import io
6+ import logging
67import os
7- from typing import Iterator , Optional
8+ import socket
9+ from typing import Any , Dict , Iterator , Optional
810
911import requests
12+ from packaging .version import Version
1013from PIL import Image
1114from pydantic import BaseModel
15+ from pymobiledevice3 .common import get_home_folder
16+ from pymobiledevice3 .exceptions import AlreadyMountedError
1217from pymobiledevice3 .lockdown import LockdownClient , create_using_usbmux , usbmux
1318from pymobiledevice3 .lockdown_service_provider import LockdownServiceProvider
1419from pymobiledevice3 .remote .remote_service_discovery import RemoteServiceDiscoveryService
20+ from pymobiledevice3 .services .amfi import AmfiService
1521from pymobiledevice3 .services .dvt .dvt_secure_socket_proxy import DvtSecureSocketProxyService
1622from pymobiledevice3 .services .dvt .instruments .device_info import DeviceInfo
1723from pymobiledevice3 .services .dvt .instruments .screenshot import Screenshot
1824from pymobiledevice3 .services .installation_proxy import InstallationProxyService
25+ from pymobiledevice3 .services .mobile_image_mounter import auto_mount
1926from pymobiledevice3 .services .screenshot import ScreenshotService
27+ from pymobiledevice3 .utils import get_asyncio_loop
2028
2129from tidevice3 .exceptions import FatalError
2230from tidevice3 .utils .download import download_file , is_hyperlink
2331
32+ logger = logging .getLogger (__name__ )
2433
2534class DeviceShortInfo (BaseModel ):
2635 BuildVersion : str
@@ -78,15 +87,34 @@ def connect_service_provider(udid: Optional[str], force_usbmux: bool = False, us
7887 return lockdown
7988
8089
81- def connect_remote_service_discovery_service (udid : str , tunneld_url : str = 'http://localhost:5555' ) -> RemoteServiceDiscoveryService :
90+ class EnterableRemoteServiceDiscoveryService (RemoteServiceDiscoveryService ):
91+ def __enter__ (self ) -> EnterableRemoteServiceDiscoveryService :
92+ get_asyncio_loop ().run_until_complete (self .connect ())
93+ return self
94+
95+ def __exit__ (self , exc_type , exc_val , exc_tb ) -> None :
96+ get_asyncio_loop ().run_until_complete (self .close ())
97+
98+
99+ def is_port_open (ip : str , port : int ) -> bool :
100+ with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
101+ return s .connect_ex ((ip , port )) == 0
102+
103+
104+ def connect_remote_service_discovery_service (udid : str , tunneld_url : str = None ) -> EnterableRemoteServiceDiscoveryService :
105+ if tunneld_url is None :
106+ if is_port_open ("localhost" , 49151 ):
107+ tunneld_url = "http://localhost:49151"
108+ else :
109+ tunneld_url = "http://localhost:5555" # for backward compatibility
110+
82111 try :
83112 resp = requests .get (tunneld_url , timeout = DEFAULT_TIMEOUT )
84- tunnels = resp .json ()
113+ tunnels : Dict [ str , Any ] = resp .json ()
85114 ipv6_address = tunnels .get (udid )
86115 if ipv6_address is None :
87116 raise FatalError ("tunneld not ready for device" , udid )
88- rsd = RemoteServiceDiscoveryService (ipv6_address )
89- rsd .connect ()
117+ rsd = EnterableRemoteServiceDiscoveryService (ipv6_address )
90118 return rsd
91119 except requests .RequestException :
92120 raise FatalError ("Please run `sudo t3 tunneld` first" )
@@ -136,4 +164,23 @@ def app_install(service_provider: LockdownClient, path_or_url: str):
136164 ipa_path = path_or_url
137165 else :
138166 raise ValueError ("local file not found" , path_or_url )
139- InstallationProxyService (lockdown = service_provider ).install_from_local (ipa_path )
167+ InstallationProxyService (lockdown = service_provider ).install_from_local (ipa_path )
168+
169+
170+ def enable_developer_mode (service_provider : LockdownClient ):
171+ """ enable developer mode """
172+ if Version (service_provider .product_version ) >= Version ("16" ):
173+ if not service_provider .developer_mode_status :
174+ logger .info ('enable developer mode' )
175+ AmfiService (service_provider ).enable_developer_mode ()
176+ else :
177+ logger .info ('developer mode already enabled' )
178+
179+ try :
180+ xcode = get_home_folder () / 'Xcode.app'
181+ xcode .mkdir (parents = True , exist_ok = True )
182+ auto_mount (service_provider , xcode = xcode )
183+ logger .info ('mount developer image' )
184+ except AlreadyMountedError :
185+ logger .info ('developer image already mounted' )
186+
0 commit comments