pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  import datetime
2
2
  import logging
3
3
  from abc import abstractmethod
4
- from typing import Optional
4
+ from typing import Any, Optional
5
5
 
6
6
  from pymobiledevice3.exceptions import StartServiceError
7
7
  from pymobiledevice3.service_connection import ServiceConnection
@@ -17,6 +17,11 @@ class LockdownServiceProvider:
17
17
  def product_version(self) -> str:
18
18
  pass
19
19
 
20
+ @property
21
+ @abstractmethod
22
+ def product_build_version(self) -> str:
23
+ pass
24
+
20
25
  @property
21
26
  @abstractmethod
22
27
  def ecid(self) -> int:
@@ -41,17 +46,19 @@ class LockdownServiceProvider:
41
46
  pass
42
47
 
43
48
  @abstractmethod
44
- async def aio_start_lockdown_service(
45
- self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
49
+ async def aio_start_lockdown_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def get_value(self, domain: Optional[str] = None, key: Optional[str] = None) -> Any:
46
54
  pass
47
55
 
48
- def start_lockdown_developer_service(
49
- self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
56
+ def start_lockdown_developer_service(self, name: str, include_escrow_bag: bool = False) -> ServiceConnection:
50
57
  try:
51
58
  return self.start_lockdown_service(name, include_escrow_bag=include_escrow_bag)
52
59
  except StartServiceError:
53
- logging.getLogger(self.__module__).error(
54
- 'Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. '
55
- 'You can do so using: pymobiledevice3 mounter mount'
60
+ logging.getLogger(self.__module__).exception(
61
+ "Failed to connect to required service. Make sure DeveloperDiskImage.dmg has been mounted. "
62
+ "You can do so using: pymobiledevice3 mounter mount"
56
63
  )
57
64
  raise
@@ -11,25 +11,38 @@ DEFAULT_INTERVAL_SEC = 3
11
11
  DEFAULT_MAX_FAILS = 3
12
12
 
13
13
 
14
+ def is_wsl() -> bool:
15
+ try:
16
+ with open("/proc/version") as f:
17
+ version_info = f.read()
18
+ return "Microsoft" in version_info or "WSL" in version_info
19
+ except FileNotFoundError:
20
+ return False
21
+
22
+
14
23
  class OsUtils:
15
24
  _instance = None
16
25
  _os_name = None
17
26
 
18
27
  @classmethod
19
- def create(cls) -> 'OsUtils':
28
+ def create(cls) -> "OsUtils":
20
29
  if cls._instance is None:
21
30
  cls._os_name = sys.platform
22
- if cls._os_name == 'win32':
31
+ if cls._os_name == "win32":
23
32
  from pymobiledevice3.osu.win_util import Win32
33
+
24
34
  cls._instance = Win32()
25
- elif cls._os_name == 'darwin':
35
+ elif cls._os_name == "darwin":
26
36
  from pymobiledevice3.osu.posix_util import Darwin
37
+
27
38
  cls._instance = Darwin()
28
- elif cls._os_name == 'linux':
29
- from pymobiledevice3.osu.posix_util import Linux
30
- cls._instance = Linux()
31
- elif cls._os_name == 'cygwin':
39
+ elif cls._os_name == "linux":
40
+ from pymobiledevice3.osu.posix_util import Linux, Wsl
41
+
42
+ cls._instance = Wsl() if is_wsl() else Linux()
43
+ elif cls._os_name == "cygwin":
32
44
  from pymobiledevice3.osu.posix_util import Cygwin
45
+
33
46
  cls._instance = Cygwin()
34
47
  else:
35
48
  raise OSNotSupportedError(cls._os_name)
@@ -62,8 +75,13 @@ class OsUtils:
62
75
  def get_ipv6_ips(self) -> list[str]:
63
76
  raise FeatureNotSupportedError(self._os_name, inspect.currentframe().f_code.co_name)
64
77
 
65
- def set_keepalive(self, sock: socket.socket, after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
66
- interval_sec: int = DEFAULT_INTERVAL_SEC, max_fails: int = DEFAULT_MAX_FAILS) -> None:
78
+ def set_keepalive(
79
+ self,
80
+ sock: socket.socket,
81
+ after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
82
+ interval_sec: int = DEFAULT_INTERVAL_SEC,
83
+ max_fails: int = DEFAULT_MAX_FAILS,
84
+ ) -> None:
67
85
  raise FeatureNotSupportedError(self._os_name, inspect.currentframe().f_code.co_name)
68
86
 
69
87
  def parse_timestamp(self, time_stamp) -> datetime:
@@ -33,13 +33,16 @@ class Posix(OsUtils):
33
33
  return 'This command requires root privileges. Consider retrying with "sudo".'
34
34
 
35
35
  def get_ipv6_ips(self) -> list[str]:
36
- return [f'{adapter.ips[0].ip[0]}%{adapter.nice_name}' for adapter in get_adapters() if
37
- adapter.ips[0].is_IPv6 and not adapter.nice_name.startswith('tun')]
36
+ return [
37
+ f"{adapter.ips[0].ip[0]}%{adapter.nice_name}"
38
+ for adapter in get_adapters()
39
+ if adapter.ips[0].is_IPv6 and not adapter.nice_name.startswith("tun")
40
+ ]
38
41
 
39
42
  def chown_to_non_sudo_if_needed(self, path: Path) -> None:
40
- if os.getenv('SUDO_UID') is None:
43
+ if os.getenv("SUDO_UID") is None:
41
44
  return
42
- os.chown(path, int(os.getenv('SUDO_UID')), int(os.getenv('SUDO_GID')))
45
+ os.chown(path, int(os.getenv("SUDO_UID")), int(os.getenv("SUDO_GID")))
43
46
 
44
47
  def parse_timestamp(self, time_stamp) -> datetime:
45
48
  return datetime.datetime.fromtimestamp(time_stamp)
@@ -52,14 +55,19 @@ class Posix(OsUtils):
52
55
  class Darwin(Posix):
53
56
  @property
54
57
  def pair_record_path(self) -> Path:
55
- return Path('/var/db/lockdown/')
58
+ return Path("/var/db/lockdown/")
56
59
 
57
60
  @property
58
61
  def loopback_header(self) -> bytes:
59
- return struct.pack('>I', socket.AF_INET6)
60
-
61
- def set_keepalive(self, sock: socket.socket, after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
62
- interval_sec: int = DEFAULT_INTERVAL_SEC, max_fails: int = DEFAULT_MAX_FAILS) -> None:
62
+ return struct.pack(">I", socket.AF_INET6)
63
+
64
+ def set_keepalive(
65
+ self,
66
+ sock: socket.socket,
67
+ after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
68
+ interval_sec: int = DEFAULT_INTERVAL_SEC,
69
+ max_fails: int = DEFAULT_MAX_FAILS,
70
+ ) -> None:
63
71
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
64
72
  sock.setsockopt(socket.IPPROTO_TCP, _DARWIN_TCP_KEEPALIVE, after_idle_sec)
65
73
  sock.setsockopt(socket.IPPROTO_TCP, _DARWIN_TCP_KEEPINTVL, interval_sec)
@@ -69,24 +77,35 @@ class Darwin(Posix):
69
77
  class Linux(Posix):
70
78
  @property
71
79
  def pair_record_path(self) -> Path:
72
- return Path('/var/lib/lockdown/')
80
+ return Path("/var/lib/lockdown/")
73
81
 
74
82
  @property
75
83
  def loopback_header(self) -> bytes:
76
- return b'\x00\x00\x86\xdd'
77
-
78
- def set_keepalive(self, sock: socket.socket, after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
79
- interval_sec: int = DEFAULT_INTERVAL_SEC, max_fails: int = DEFAULT_MAX_FAILS) -> None:
84
+ return b"\x00\x00\x86\xdd"
85
+
86
+ def set_keepalive(
87
+ self,
88
+ sock: socket.socket,
89
+ after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
90
+ interval_sec: int = DEFAULT_INTERVAL_SEC,
91
+ max_fails: int = DEFAULT_MAX_FAILS,
92
+ ) -> None:
80
93
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
81
94
  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec)
82
95
  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec)
83
96
  sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
84
97
 
85
98
  def get_homedir(self) -> Path:
86
- return Path('~' + os.environ.get('SUDO_USER', '')).expanduser()
99
+ return Path("~" + os.environ.get("SUDO_USER", "")).expanduser()
87
100
 
88
101
 
89
102
  class Cygwin(Posix):
90
103
  @property
91
104
  def usbmux_address(self) -> tuple[str, int]:
92
105
  return MuxConnection.ITUNES_HOST, socket.AF_INET
106
+
107
+
108
+ class Wsl(Linux):
109
+ @property
110
+ def usbmux_address(self) -> tuple[str, int]:
111
+ return MuxConnection.ITUNES_HOST, socket.AF_INET
@@ -13,7 +13,7 @@ from pymobiledevice3.usbmux import MuxConnection
13
13
  class Win32(OsUtils):
14
14
  @property
15
15
  def is_admin(self) -> bool:
16
- """ Check if the current OS user is an Administrator or root.
16
+ """Check if the current OS user is an Administrator or root.
17
17
  See: https://github.com/Preston-Landers/pyuac/blob/master/pyuac/admin.py
18
18
  :return: True if the current user is an 'Administrator', otherwise False.
19
19
  """
@@ -33,7 +33,7 @@ class Win32(OsUtils):
33
33
 
34
34
  @property
35
35
  def loopback_header(self) -> bytes:
36
- return b'\x00\x00\x86\xdd'
36
+ return b"\x00\x00\x86\xdd"
37
37
 
38
38
  @property
39
39
  def access_denied_error(self) -> str:
@@ -41,14 +41,20 @@ class Win32(OsUtils):
41
41
 
42
42
  @property
43
43
  def pair_record_path(self) -> Path:
44
- return Path(os.environ.get('ALLUSERSPROFILE', ''), 'Apple', 'Lockdown')
44
+ return Path(os.environ.get("ALLUSERSPROFILE", ""), "Apple", "Lockdown")
45
45
 
46
46
  def get_ipv6_ips(self) -> list[str]:
47
- return [f'{adapter.ips[0].ip[0]}%{adapter.ips[0].ip[2]}' for adapter in get_adapters() if
48
- adapter.ips[0].is_IPv6]
47
+ return [
48
+ f"{adapter.ips[0].ip[0]}%{adapter.ips[0].ip[2]}" for adapter in get_adapters() if adapter.ips[0].is_IPv6
49
+ ]
49
50
 
50
- def set_keepalive(self, sock: socket.socket, after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
51
- interval_sec: int = DEFAULT_INTERVAL_SEC, **kwargs) -> None:
51
+ def set_keepalive(
52
+ self,
53
+ sock: socket.socket,
54
+ after_idle_sec: int = DEFAULT_AFTER_IDLE_SEC,
55
+ interval_sec: int = DEFAULT_INTERVAL_SEC,
56
+ **kwargs,
57
+ ) -> None:
52
58
  sock.ioctl(socket.SIO_KEEPALIVE_VALS, (1, after_idle_sec * 1000, interval_sec * 1000))
53
59
 
54
60
  def parse_timestamp(self, time_stamp) -> datetime:
@@ -58,4 +64,4 @@ class Win32(OsUtils):
58
64
  return
59
65
 
60
66
  def wait_return(self):
61
- input('Press ENTER to exit>')
67
+ input("Press ENTER to exit>")
@@ -15,19 +15,55 @@ from pymobiledevice3.usbmux import PlistMuxConnection
15
15
 
16
16
  logger = logging.getLogger(__name__)
17
17
  OSUTILS = get_os_utils()
18
- PAIRING_RECORD_EXT = 'plist'
18
+ PAIRING_RECORD_EXT = "plist"
19
19
 
20
20
 
21
- def generate_host_id(hostname: str = None) -> str:
21
+ def generate_host_id(hostname: Optional[str] = None) -> str:
22
+ """
23
+ Generate a unique host ID based on the hostname.
24
+
25
+ :param hostname: The hostname to use for generating the host ID.
26
+ If None, the current hostname is used.
27
+ :type hostname: str, optional
28
+ :return: The generated host ID.
29
+ :rtype: str
30
+ """
22
31
  hostname = platform.node() if hostname is None else hostname
23
32
  host_id = uuid.uuid3(uuid.NAMESPACE_DNS, hostname)
24
33
  return str(host_id).upper()
25
34
 
26
35
 
36
+ def get_usbmux_pairing_record(identifier: str, usbmux_address: Optional[str] = None):
37
+ """
38
+ Retrieve the pairing record from usbmuxd.
39
+
40
+ :param identifier: The identifier of the device.
41
+ :type identifier: str
42
+ :param usbmux_address: The address of the usbmuxd server.
43
+ :type usbmux_address: Optional[str], optional
44
+ :return: The pairing record if found, otherwise None.
45
+ :rtype: dict or None
46
+ """
47
+ with suppress(NotPairedError, MuxException), usbmux.create_mux(usbmux_address=usbmux_address) as mux:
48
+ if isinstance(mux, PlistMuxConnection):
49
+ pair_record = mux.get_pair_record(identifier)
50
+ if pair_record is not None:
51
+ return pair_record
52
+ return None
53
+
54
+
27
55
  def get_itunes_pairing_record(identifier: str) -> Optional[dict]:
28
- filename = OSUTILS.pair_record_path / f'{identifier}.plist'
56
+ """
57
+ Retrieve the pairing record from iTunes.
58
+
59
+ :param identifier: The identifier of the device.
60
+ :type identifier: str
61
+ :return: The pairing record if found, otherwise None.
62
+ :rtype: Optional[dict]
63
+ """
64
+ filename = OSUTILS.pair_record_path / f"{identifier}.plist"
29
65
  try:
30
- with open(filename, 'rb') as f:
66
+ with open(filename, "rb") as f:
31
67
  pair_record = plistlib.load(f)
32
68
  except (PermissionError, FileNotFoundError, plistlib.InvalidFileException):
33
69
  return None
@@ -35,30 +71,46 @@ def get_itunes_pairing_record(identifier: str) -> Optional[dict]:
35
71
 
36
72
 
37
73
  def get_local_pairing_record(identifier: str, pairing_records_cache_folder: Path) -> Optional[dict]:
38
- logger.debug('Looking for pymobiledevice3 pairing record')
39
- path = pairing_records_cache_folder / f'{identifier}.{PAIRING_RECORD_EXT}'
74
+ """
75
+ Retrieve the pairing record from local storage.
76
+
77
+ :param identifier: The identifier of the device.
78
+ :type identifier: str
79
+ :param pairing_records_cache_folder: The path to the local pairing records cache folder.
80
+ :type pairing_records_cache_folder: Path
81
+ :return: The pairing record if found, otherwise None.
82
+ :rtype: Optional[dict]
83
+ """
84
+ logger.debug("Looking for pymobiledevice3 pairing record")
85
+ path = pairing_records_cache_folder / f"{identifier}.{PAIRING_RECORD_EXT}"
40
86
  if not path.exists():
41
- logger.debug(f'No pymobiledevice3 pairing record found for device {identifier}')
87
+ logger.debug(f"No pymobiledevice3 pairing record found for device {identifier}")
42
88
  return None
43
89
  return plistlib.loads(path.read_bytes())
44
90
 
45
91
 
46
- def get_preferred_pair_record(identifier: str, pairing_records_cache_folder: Path,
47
- usbmux_address: Optional[str] = None) -> dict:
92
+ def get_preferred_pair_record(
93
+ identifier: str, pairing_records_cache_folder: Path, usbmux_address: Optional[str] = None
94
+ ) -> dict:
48
95
  """
49
- look for an existing pair record to connected device by following order:
96
+ Look for an existing pair record for the connected device in the following order:
50
97
  - usbmuxd
51
98
  - iTunes
52
99
  - local storage
53
- """
54
100
 
101
+ :param identifier: The identifier of the device.
102
+ :type identifier: str
103
+ :param pairing_records_cache_folder: The path to the local pairing records cache folder.
104
+ :type pairing_records_cache_folder: Path
105
+ :param usbmux_address: The address of the usbmuxd server.
106
+ :type usbmux_address: Optional[str], optional
107
+ :return: The preferred pairing record.
108
+ :rtype: dict
109
+ """
55
110
  # usbmuxd
56
- with suppress(NotPairedError, MuxException):
57
- with usbmux.create_mux(usbmux_address=usbmux_address) as mux:
58
- if isinstance(mux, PlistMuxConnection):
59
- pair_record = mux.get_pair_record(identifier)
60
- if pair_record is not None:
61
- return pair_record
111
+ pair_record = get_usbmux_pairing_record(identifier=identifier, usbmux_address=usbmux_address)
112
+ if pair_record is not None:
113
+ return pair_record
62
114
 
63
115
  # iTunes
64
116
  pair_record = get_itunes_pairing_record(identifier)
@@ -69,7 +121,16 @@ def get_preferred_pair_record(identifier: str, pairing_records_cache_folder: Pat
69
121
  return get_local_pairing_record(identifier, pairing_records_cache_folder)
70
122
 
71
123
 
72
- def create_pairing_records_cache_folder(pairing_records_cache_folder: Path = None) -> Path:
124
+ def create_pairing_records_cache_folder(pairing_records_cache_folder: Optional[Path] = None) -> Path:
125
+ """
126
+ Create the pairing records cache folder if it does not exist.
127
+
128
+ :param pairing_records_cache_folder: The path to the local pairing records cache folder.
129
+ If None, the home folder is used.
130
+ :type pairing_records_cache_folder: Path, optional
131
+ :return: The path to the pairing records cache folder.
132
+ :rtype: Path
133
+ """
73
134
  if pairing_records_cache_folder is None:
74
135
  pairing_records_cache_folder = get_home_folder()
75
136
  else:
@@ -79,13 +140,33 @@ def create_pairing_records_cache_folder(pairing_records_cache_folder: Path = Non
79
140
 
80
141
 
81
142
  def get_remote_pairing_record_filename(identifier: str) -> str:
82
- return f'remote_{identifier}'
143
+ """
144
+ Generate the filename for the remote pairing record.
145
+
146
+ :param identifier: The identifier of the device.
147
+ :type identifier: str
148
+ :return: The filename for the remote pairing record.
149
+ :rtype: str
150
+ """
151
+ return f"remote_{identifier}"
83
152
 
84
153
 
85
154
  def iter_remote_pair_records() -> Generator[Path, None, None]:
86
- return get_home_folder().glob('remote_*')
155
+ """
156
+ Iterate over the remote pairing records in the home folder.
157
+
158
+ :return: A generator yielding paths to the remote pairing records.
159
+ :rtype: Generator[Path, None, None]
160
+ """
161
+ return get_home_folder().glob("remote_*")
87
162
 
88
163
 
89
164
  def iter_remote_paired_identifiers() -> Generator[str, None, None]:
165
+ """
166
+ Iterate over the identifiers of the remote paired devices.
167
+
168
+ :return: A generator yielding the identifiers of the remote paired devices.
169
+ :rtype: Generator[str, None, None]
170
+ """
90
171
  for file in iter_remote_pair_records():
91
- yield file.parts[-1].split('remote_', 1)[1].split('.', 1)[0]
172
+ yield file.parts[-1].split("remote_", 1)[1].split(".", 1)[0]
@@ -1,11 +1,15 @@
1
+ import sys
1
2
  from enum import Enum
2
3
 
3
4
 
4
5
  class ConnectionType(Enum):
5
- USB = 'usb'
6
- WIFI = 'wifi'
6
+ USB = "usb"
7
+ WIFI = "wifi"
7
8
 
8
9
 
9
10
  class TunnelProtocol(Enum):
10
- TCP = 'tcp'
11
- QUIC = 'quic'
11
+ TCP = "tcp"
12
+ QUIC = "quic"
13
+
14
+ # TODO: make only TCP the default once 3.12 becomes deprecated
15
+ DEFAULT = TCP if sys.version_info >= (3, 13) else QUIC
@@ -11,45 +11,63 @@ class AppServiceService(CoreDeviceService):
11
11
  Manage applications
12
12
  """
13
13
 
14
- SERVICE_NAME = 'com.apple.coredevice.appservice'
14
+ SERVICE_NAME = "com.apple.coredevice.appservice"
15
15
 
16
16
  def __init__(self, rsd: RemoteServiceDiscoveryService):
17
17
  super().__init__(rsd, self.SERVICE_NAME)
18
18
 
19
- async def list_apps(self, include_app_clips: bool = True, include_removable_apps: bool = True,
20
- include_hidden_apps: bool = True, include_internal_apps: bool = True,
21
- include_default_apps: bool = True) -> list[dict]:
22
- """ List applications """
23
- return await self.invoke('com.apple.coredevice.feature.listapps', {
24
- 'includeAppClips': include_app_clips, 'includeRemovableApps': include_removable_apps,
25
- 'includeHiddenApps': include_hidden_apps, 'includeInternalApps': include_internal_apps,
26
- 'includeDefaultApps': include_default_apps})
19
+ async def list_apps(
20
+ self,
21
+ include_app_clips: bool = True,
22
+ include_removable_apps: bool = True,
23
+ include_hidden_apps: bool = True,
24
+ include_internal_apps: bool = True,
25
+ include_default_apps: bool = True,
26
+ ) -> list[dict]:
27
+ """List applications"""
28
+ return await self.invoke(
29
+ "com.apple.coredevice.feature.listapps",
30
+ {
31
+ "includeAppClips": include_app_clips,
32
+ "includeRemovableApps": include_removable_apps,
33
+ "includeHiddenApps": include_hidden_apps,
34
+ "includeInternalApps": include_internal_apps,
35
+ "includeDefaultApps": include_default_apps,
36
+ },
37
+ )
27
38
 
28
39
  async def launch_application(
29
- self, bundle_id: str, arguments: Optional[list[str]] = None, kill_existing: bool = True,
30
- start_suspended: bool = False, environment: Optional[dict] = None, extra_options: Optional[dict] = None) \
31
- -> list[dict]:
32
- """ launch application """
33
- return await self.invoke('com.apple.coredevice.feature.launchapplication', {
34
- 'applicationSpecifier': {
35
- 'bundleIdentifier': {'_0': bundle_id},
36
- },
37
- 'options': {
38
- 'arguments': arguments if arguments is not None else [],
39
- 'environmentVariables': environment if environment is not None else {},
40
- 'standardIOUsesPseudoterminals': True,
41
- 'startStopped': start_suspended,
42
- 'terminateExisting': kill_existing,
43
- 'user': {'shortName': 'mobile'},
44
- 'platformSpecificOptions': plistlib.dumps(extra_options if extra_options is not None else {}),
45
- },
46
- 'standardIOIdentifiers': {
40
+ self,
41
+ bundle_id: str,
42
+ arguments: Optional[list[str]] = None,
43
+ kill_existing: bool = True,
44
+ start_suspended: bool = False,
45
+ environment: Optional[dict] = None,
46
+ extra_options: Optional[dict] = None,
47
+ ) -> list[dict]:
48
+ """launch application"""
49
+ return await self.invoke(
50
+ "com.apple.coredevice.feature.launchapplication",
51
+ {
52
+ "applicationSpecifier": {
53
+ "bundleIdentifier": {"_0": bundle_id},
54
+ },
55
+ "options": {
56
+ "arguments": arguments if arguments is not None else [],
57
+ "environmentVariables": environment if environment is not None else {},
58
+ "standardIOUsesPseudoterminals": True,
59
+ "startStopped": start_suspended,
60
+ "terminateExisting": kill_existing,
61
+ "user": {"shortName": "mobile"},
62
+ "platformSpecificOptions": plistlib.dumps(extra_options if extra_options is not None else {}),
63
+ },
64
+ "standardIOIdentifiers": {},
47
65
  },
48
- })
66
+ )
49
67
 
50
68
  async def list_processes(self) -> list[dict]:
51
- """ List processes """
52
- return (await self.invoke('com.apple.coredevice.feature.listprocesses'))['processTokens']
69
+ """List processes"""
70
+ return (await self.invoke("com.apple.coredevice.feature.listprocesses"))["processTokens"]
53
71
 
54
72
  async def list_roots(self) -> dict:
55
73
  """
@@ -57,10 +75,7 @@ class AppServiceService(CoreDeviceService):
57
75
 
58
76
  Can only be performed on certain devices
59
77
  """
60
- return await self.invoke('com.apple.coredevice.feature.listroots', {
61
- 'rootPoint': {
62
- 'relative': '/'
63
- }})
78
+ return await self.invoke("com.apple.coredevice.feature.listroots", {"rootPoint": {"relative": "/"}})
64
79
 
65
80
  async def spawn_executable(self, executable: str, arguments: list[str]) -> dict:
66
81
  """
@@ -68,26 +83,29 @@ class AppServiceService(CoreDeviceService):
68
83
 
69
84
  Can only be performed on certain devices
70
85
  """
71
- return await self.invoke('com.apple.coredevice.feature.spawnexecutable', {
72
- 'executableItem': {
73
- 'url': {
74
- '_0': {
75
- 'relative': executable,
86
+ return await self.invoke(
87
+ "com.apple.coredevice.feature.spawnexecutable",
88
+ {
89
+ "executableItem": {
90
+ "url": {
91
+ "_0": {
92
+ "relative": executable,
93
+ },
94
+ }
95
+ },
96
+ "standardIOIdentifiers": {},
97
+ "options": {
98
+ "arguments": arguments,
99
+ "environmentVariables": {},
100
+ "standardIOUsesPseudoterminals": True,
101
+ "startStopped": False,
102
+ "user": {
103
+ "active": True,
76
104
  },
77
- }
78
- },
79
- 'standardIOIdentifiers': {},
80
- 'options': {
81
- 'arguments': arguments,
82
- 'environmentVariables': {},
83
- 'standardIOUsesPseudoterminals': True,
84
- 'startStopped': False,
85
- 'user': {
86
- 'active': True,
105
+ "platformSpecificOptions": plistlib.dumps({}),
87
106
  },
88
- 'platformSpecificOptions': plistlib.dumps({}),
89
107
  },
90
- })
108
+ )
91
109
 
92
110
  async def monitor_process_termination(self, pid: int) -> dict:
93
111
  """
@@ -95,33 +113,42 @@ class AppServiceService(CoreDeviceService):
95
113
 
96
114
  Can only be performed on certain devices
97
115
  """
98
- return await self.invoke('com.apple.coredevice.feature.monitorprocesstermination', {
99
- 'processToken': {'processIdentifier': XpcInt64Type(pid)}})
116
+ return await self.invoke(
117
+ "com.apple.coredevice.feature.monitorprocesstermination",
118
+ {"processToken": {"processIdentifier": XpcInt64Type(pid)}},
119
+ )
100
120
 
101
121
  async def uninstall_app(self, bundle_identifier: str) -> None:
102
122
  """
103
123
  Uninstall given application by its bundle identifier
104
124
  """
105
- await self.invoke('com.apple.coredevice.feature.uninstallapp', {'bundleIdentifier': bundle_identifier})
125
+ await self.invoke("com.apple.coredevice.feature.uninstallapp", {"bundleIdentifier": bundle_identifier})
106
126
 
107
127
  async def send_signal_to_process(self, pid: int, signal: int) -> dict:
108
128
  """
109
129
  Send signal to given process by its pid
110
130
  """
111
- return await self.invoke('com.apple.coredevice.feature.sendsignaltoprocess', {
112
- 'process': {'processIdentifier': XpcInt64Type(pid)},
113
- 'signal': XpcInt64Type(signal),
114
- })
131
+ return await self.invoke(
132
+ "com.apple.coredevice.feature.sendsignaltoprocess",
133
+ {
134
+ "process": {"processIdentifier": XpcInt64Type(pid)},
135
+ "signal": XpcInt64Type(signal),
136
+ },
137
+ )
115
138
 
116
- async def fetch_icons(self, bundle_identifier: str, width: float, height: float, scale: float,
117
- allow_placeholder: bool) -> dict:
139
+ async def fetch_icons(
140
+ self, bundle_identifier: str, width: float, height: float, scale: float, allow_placeholder: bool
141
+ ) -> dict:
118
142
  """
119
143
  Fetch given application's icons
120
144
  """
121
- return await self.invoke('com.apple.coredevice.feature.fetchappicons', {
122
- 'width': width,
123
- 'height': height,
124
- 'scale': scale,
125
- 'allowPlaceholder': allow_placeholder,
126
- 'bundleIdentifier': bundle_identifier
127
- })
145
+ return await self.invoke(
146
+ "com.apple.coredevice.feature.fetchappicons",
147
+ {
148
+ "width": width,
149
+ "height": height,
150
+ "scale": scale,
151
+ "allowPlaceholder": allow_placeholder,
152
+ "bundleIdentifier": bundle_identifier,
153
+ },
154
+ )