pymobiledevice3 5.0.4__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.
- misc/understanding_idevice_protocol_layers.md +10 -5
- pymobiledevice3/__main__.py +171 -46
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +22 -21
- pymobiledevice3/cli/activation.py +24 -22
- pymobiledevice3/cli/afc.py +49 -41
- pymobiledevice3/cli/amfi.py +13 -18
- pymobiledevice3/cli/apps.py +71 -65
- pymobiledevice3/cli/backup.py +134 -93
- pymobiledevice3/cli/bonjour.py +31 -29
- pymobiledevice3/cli/cli_common.py +175 -232
- pymobiledevice3/cli/companion_proxy.py +12 -12
- pymobiledevice3/cli/crash.py +95 -52
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +70 -75
- pymobiledevice3/cli/mounter.py +99 -57
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +36 -20
- pymobiledevice3/cli/power_assertion.py +15 -16
- pymobiledevice3/cli/processes.py +11 -17
- pymobiledevice3/cli/profile.py +120 -75
- pymobiledevice3/cli/provision.py +27 -26
- pymobiledevice3/cli/remote.py +109 -100
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +145 -65
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +232 -156
- pymobiledevice3/exceptions.py +6 -2
- pymobiledevice3/lockdown.py +5 -1
- pymobiledevice3/lockdown_service_provider.py +5 -0
- pymobiledevice3/remote/remote_service_discovery.py +18 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/restore/restore.py +2 -2
- pymobiledevice3/service_connection.py +15 -12
- pymobiledevice3/services/afc.py +731 -220
- pymobiledevice3/services/device_link.py +45 -31
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/lockdown_service.py +12 -9
- pymobiledevice3/services/mobile_config.py +1 -0
- pymobiledevice3/services/mobilebackup2.py +6 -3
- pymobiledevice3/services/os_trace.py +97 -55
- pymobiledevice3/services/remote_fetch_symbols.py +13 -8
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/alert.py +8 -8
- pymobiledevice3/services/web_protocol/automation_session.py +87 -79
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/driver.py +71 -70
- pymobiledevice3/services/web_protocol/element.py +58 -56
- pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
- pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
- pymobiledevice3/services/web_protocol/switch_to.py +23 -19
- pymobiledevice3/services/webinspector.py +42 -67
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1539
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -6,63 +6,38 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
import uuid
|
|
8
8
|
from functools import wraps
|
|
9
|
-
from
|
|
9
|
+
from textwrap import dedent
|
|
10
|
+
from typing import Annotated, Any, Callable, Optional
|
|
10
11
|
|
|
11
12
|
import click
|
|
12
13
|
import coloredlogs
|
|
13
14
|
import hexdump
|
|
14
15
|
import inquirer3
|
|
15
|
-
|
|
16
|
+
import typer
|
|
17
|
+
from click import UsageError
|
|
16
18
|
from inquirer3.themes import GreenPassion
|
|
17
19
|
from pygments import formatters, highlight, lexers
|
|
20
|
+
from typer_injector import Depends
|
|
18
21
|
|
|
19
22
|
from pymobiledevice3.exceptions import AccessDeniedError, DeviceNotFoundError, NoDeviceConnectedError
|
|
20
|
-
from pymobiledevice3.lockdown import
|
|
23
|
+
from pymobiledevice3.lockdown import TcpLockdownClient, create_using_usbmux, get_mobdev2_lockdowns
|
|
24
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
21
25
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
22
26
|
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
23
27
|
from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS, async_get_tunneld_devices
|
|
24
28
|
from pymobiledevice3.usbmux import select_devices_by_connection_type
|
|
25
29
|
|
|
26
|
-
COLORED_OUTPUT = True
|
|
27
30
|
UDID_ENV_VAR = "PYMOBILEDEVICE3_UDID"
|
|
28
31
|
TUNNEL_ENV_VAR = "PYMOBILEDEVICE3_TUNNEL"
|
|
29
32
|
USBMUX_ENV_VAR = "PYMOBILEDEVICE3_USBMUX"
|
|
30
|
-
OSUTILS = get_os_utils()
|
|
31
|
-
|
|
32
33
|
USBMUX_OPTION_HELP = (
|
|
33
|
-
|
|
34
|
-
f"Can be specified via {USBMUX_ENV_VAR} envvar"
|
|
34
|
+
"Address of the usbmuxd daemon (unix socket path or HOST:PORT). Defaults to the platform usbmuxd if omitted."
|
|
35
35
|
)
|
|
36
|
+
DEVICE_OPTIONS_PANEL_TITLE = "Device Options"
|
|
37
|
+
OSUTILS = get_os_utils()
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def __init__(self, *args, **kwargs):
|
|
40
|
-
self.mutually_exclusive = set(kwargs.pop("mutually_exclusive", []))
|
|
41
|
-
help_option = kwargs.get("help", "")
|
|
42
|
-
if self.mutually_exclusive:
|
|
43
|
-
ex_str = ", ".join(self.mutually_exclusive)
|
|
44
|
-
kwargs["help"] = help_option + (
|
|
45
|
-
"\nNOTE: This argument is mutually exclusive with arguments: [" + ex_str + "]."
|
|
46
|
-
)
|
|
47
|
-
super().__init__(*args, **kwargs)
|
|
48
|
-
|
|
49
|
-
def handle_parse_result(self, ctx, opts, args):
|
|
50
|
-
if (
|
|
51
|
-
isinstance(ctx.command, RSDCommand)
|
|
52
|
-
and not (isinstance(ctx.command, Command))
|
|
53
|
-
and ("rsd_service_provider_using_tunneld" not in opts)
|
|
54
|
-
and ("rsd_service_provider_manually" not in opts)
|
|
55
|
-
):
|
|
56
|
-
# defaulting to `--tunnel ''` if no remote option was specified
|
|
57
|
-
opts["rsd_service_provider_using_tunneld"] = ""
|
|
58
|
-
if self.mutually_exclusive.intersection(opts) and self.name in opts:
|
|
59
|
-
raise UsageError(
|
|
60
|
-
"Illegal usage: `{}` is mutually exclusive with arguments `{}`.".format(
|
|
61
|
-
self.name, ", ".join(self.mutually_exclusive)
|
|
62
|
-
)
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
return super().handle_parse_result(ctx, opts, args)
|
|
39
|
+
# Global options
|
|
40
|
+
COLORED_OUTPUT: bool = True
|
|
66
41
|
|
|
67
42
|
|
|
68
43
|
def default_json_encoder(obj):
|
|
@@ -75,7 +50,7 @@ def default_json_encoder(obj):
|
|
|
75
50
|
raise TypeError()
|
|
76
51
|
|
|
77
52
|
|
|
78
|
-
def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder):
|
|
53
|
+
def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder) -> str:
|
|
79
54
|
if colored is None:
|
|
80
55
|
colored = user_requested_colored_output()
|
|
81
56
|
formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default)
|
|
@@ -90,7 +65,7 @@ def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder
|
|
|
90
65
|
return formatted_json
|
|
91
66
|
|
|
92
67
|
|
|
93
|
-
def print_hex(data, colored=True):
|
|
68
|
+
def print_hex(data, colored=True) -> None:
|
|
94
69
|
hex_dump = hexdump.hexdump(data, result="return")
|
|
95
70
|
if colored:
|
|
96
71
|
print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style="native")))
|
|
@@ -98,11 +73,11 @@ def print_hex(data, colored=True):
|
|
|
98
73
|
print(hex_dump, end="\n\n")
|
|
99
74
|
|
|
100
75
|
|
|
101
|
-
def set_verbosity(
|
|
102
|
-
coloredlogs.set_level(logging.INFO - (
|
|
76
|
+
def set_verbosity(level: int) -> None:
|
|
77
|
+
coloredlogs.set_level(logging.INFO - (level * 10))
|
|
103
78
|
|
|
104
79
|
|
|
105
|
-
def set_color_flag(
|
|
80
|
+
def set_color_flag(value: bool) -> None:
|
|
106
81
|
global COLORED_OUTPUT
|
|
107
82
|
COLORED_OUTPUT = value
|
|
108
83
|
|
|
@@ -134,8 +109,8 @@ def prompt_selection(choices: list[Any], message: str, idx: bool = False) -> Any
|
|
|
134
109
|
question = [inquirer3.List("selection", message=message, choices=choices, carousel=True)]
|
|
135
110
|
try:
|
|
136
111
|
result = inquirer3.prompt(question, theme=GreenPassion(), raise_keyboard_interrupt=True)
|
|
137
|
-
except KeyboardInterrupt
|
|
138
|
-
raise click.ClickException("No selection was made") from
|
|
112
|
+
except KeyboardInterrupt:
|
|
113
|
+
raise click.ClickException("No selection was made") from None
|
|
139
114
|
return result["selection"] if not idx else choices.index(result["selection"])
|
|
140
115
|
|
|
141
116
|
|
|
@@ -143,214 +118,182 @@ def prompt_device_list(device_list: list):
|
|
|
143
118
|
return prompt_selection(device_list, "Choose device")
|
|
144
119
|
|
|
145
120
|
|
|
146
|
-
def choose_service_provider(callback: Callable):
|
|
147
|
-
def wrap_callback_calling(**kwargs: dict) -> None:
|
|
148
|
-
service_provider = None
|
|
149
|
-
lockdown_service_provider = kwargs.pop("lockdown_service_provider", None)
|
|
150
|
-
rsd_service_provider_manually = kwargs.pop("rsd_service_provider_manually", None)
|
|
151
|
-
rsd_service_provider_using_tunneld = kwargs.pop("rsd_service_provider_using_tunneld", None)
|
|
152
|
-
if lockdown_service_provider is not None:
|
|
153
|
-
service_provider = lockdown_service_provider
|
|
154
|
-
if rsd_service_provider_manually is not None:
|
|
155
|
-
service_provider = rsd_service_provider_manually
|
|
156
|
-
if rsd_service_provider_using_tunneld is not None:
|
|
157
|
-
service_provider = rsd_service_provider_using_tunneld
|
|
158
|
-
callback(service_provider=service_provider, **kwargs)
|
|
159
|
-
|
|
160
|
-
return wrap_callback_calling
|
|
161
|
-
|
|
162
|
-
|
|
163
121
|
def is_invoked_for_completion() -> bool:
|
|
164
|
-
"""Returns True if the command is
|
|
122
|
+
"""Returns True if the command is invoked for autocompletion."""
|
|
165
123
|
return any(env.startswith("_") and env.endswith("_COMPLETE") for env in os.environ)
|
|
166
124
|
|
|
167
125
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
super().__init__(*args, **kwargs)
|
|
171
|
-
self.params[:0] = [
|
|
172
|
-
click.Option(("verbosity", "-v", "--verbose"), count=True, callback=set_verbosity, expose_value=False),
|
|
173
|
-
click.Option(
|
|
174
|
-
("color", "--color/--no-color"),
|
|
175
|
-
default=True,
|
|
176
|
-
callback=set_color_flag,
|
|
177
|
-
is_flag=True,
|
|
178
|
-
expose_value=False,
|
|
179
|
-
help="colorize output",
|
|
180
|
-
),
|
|
181
|
-
]
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
class BaseServiceProviderCommand(BaseCommand):
|
|
185
|
-
def __init__(self, *args, **kwargs):
|
|
186
|
-
super().__init__(*args, **kwargs)
|
|
187
|
-
self.service_provider = None
|
|
188
|
-
self.callback = choose_service_provider(self.callback)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
class LockdownCommand(BaseServiceProviderCommand):
|
|
192
|
-
def __init__(self, *args, **kwargs):
|
|
193
|
-
super().__init__(*args, **kwargs)
|
|
194
|
-
self.usbmux_address = None
|
|
195
|
-
self.mobdev2_option = None
|
|
196
|
-
self.params[:0] = [
|
|
197
|
-
click.Option(
|
|
198
|
-
("mobdev2", "--mobdev2"),
|
|
199
|
-
callback=self.mobdev2,
|
|
200
|
-
expose_value=False,
|
|
201
|
-
default=None,
|
|
202
|
-
help="Use bonjour browse for mobdev2 devices. Expected value IP address of the interface to "
|
|
203
|
-
"use. Leave empty to browse through all interfaces",
|
|
204
|
-
),
|
|
205
|
-
click.Option(
|
|
206
|
-
("usbmux", "--usbmux"),
|
|
207
|
-
callback=self.usbmux,
|
|
208
|
-
expose_value=False,
|
|
209
|
-
envvar=USBMUX_ENV_VAR,
|
|
210
|
-
help=USBMUX_OPTION_HELP,
|
|
211
|
-
),
|
|
212
|
-
click.Option(
|
|
213
|
-
("lockdown_service_provider", "--udid"),
|
|
214
|
-
envvar=UDID_ENV_VAR,
|
|
215
|
-
callback=self.udid,
|
|
216
|
-
help=f"Device unique identifier. You may pass {UDID_ENV_VAR} environment variable to pass this"
|
|
217
|
-
f" option as well",
|
|
218
|
-
),
|
|
219
|
-
]
|
|
126
|
+
async def get_mobdev2_devices(udid: Optional[str] = None) -> list[TcpLockdownClient]:
|
|
127
|
+
return [lockdown async for _, lockdown in get_mobdev2_lockdowns(udid=udid)]
|
|
220
128
|
|
|
221
|
-
async def get_mobdev2_devices(
|
|
222
|
-
self, udid: Optional[str] = None, ips: Optional[list[str]] = None
|
|
223
|
-
) -> list[TcpLockdownClient]:
|
|
224
|
-
result = []
|
|
225
|
-
async for _ip, lockdown in get_mobdev2_lockdowns(udid=udid, ips=ips):
|
|
226
|
-
result.append(lockdown)
|
|
227
|
-
return result
|
|
228
129
|
|
|
229
|
-
|
|
230
|
-
|
|
130
|
+
async def _tunneld(udid: Optional[str] = None) -> Optional[RemoteServiceDiscoveryService]:
|
|
131
|
+
if udid is None:
|
|
132
|
+
return
|
|
231
133
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
134
|
+
udid = udid.strip()
|
|
135
|
+
port = TUNNELD_DEFAULT_ADDRESS[1]
|
|
136
|
+
if ":" in udid:
|
|
137
|
+
udid, port = udid.split(":")
|
|
236
138
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return prompt_device_list([
|
|
269
|
-
create_using_usbmux(serial=device.serial, usbmux_address=self.usbmux_address) for device in devices
|
|
270
|
-
])
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
class RSDCommand(BaseServiceProviderCommand):
|
|
274
|
-
def __init__(self, *args, **kwargs):
|
|
275
|
-
super().__init__(*args, **kwargs)
|
|
276
|
-
self.params[:0] = [
|
|
277
|
-
RSDOption(
|
|
278
|
-
("rsd_service_provider_manually", "--rsd"),
|
|
279
|
-
type=(str, int),
|
|
280
|
-
callback=self.rsd,
|
|
281
|
-
mutually_exclusive=["rsd_service_provider_using_tunneld"],
|
|
282
|
-
help="\b\nRSD hostname and port number (as provided by a `start-tunnel` subcommand).",
|
|
139
|
+
rsds = await async_get_tunneld_devices((TUNNELD_DEFAULT_ADDRESS[0], int(port)))
|
|
140
|
+
if len(rsds) == 0:
|
|
141
|
+
raise NoDeviceConnectedError()
|
|
142
|
+
|
|
143
|
+
if udid != "":
|
|
144
|
+
service_provider = next((rsd for rsd in rsds if rsd.udid == udid), None)
|
|
145
|
+
if service_provider is None:
|
|
146
|
+
raise DeviceNotFoundError(udid) from None
|
|
147
|
+
else:
|
|
148
|
+
service_provider = rsds[0] if len(rsds) == 1 else prompt_device_list(rsds)
|
|
149
|
+
|
|
150
|
+
for rsd in rsds:
|
|
151
|
+
if rsd == service_provider:
|
|
152
|
+
continue
|
|
153
|
+
await rsd.close()
|
|
154
|
+
|
|
155
|
+
return service_provider
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def make_rsd_dependency(*, allow_none: bool) -> Callable[..., Optional[RemoteServiceDiscoveryService]]:
|
|
159
|
+
def rsd_dependency(
|
|
160
|
+
rsd: Annotated[
|
|
161
|
+
Optional[tuple[str, int]],
|
|
162
|
+
typer.Option(
|
|
163
|
+
metavar="HOST PORT",
|
|
164
|
+
help=dedent("""\
|
|
165
|
+
Hostname and port of a RemoteServiceDiscovery (from any of the `start-tunnel` subcommands).
|
|
166
|
+
Mutually exclusive with --tunnel.
|
|
167
|
+
"""),
|
|
168
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
283
169
|
),
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
170
|
+
] = None,
|
|
171
|
+
tunnel: Annotated[
|
|
172
|
+
Optional[str],
|
|
173
|
+
typer.Option(
|
|
288
174
|
envvar=TUNNEL_ENV_VAR,
|
|
289
|
-
help="\
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
"
|
|
293
|
-
|
|
175
|
+
help=dedent("""\
|
|
176
|
+
Use a device discovered via tunneld. Provide a UDID (optionally with :PORT) or leave empty to pick
|
|
177
|
+
interactively. Mutually exclusive with --rsd.
|
|
178
|
+
"""),
|
|
179
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
294
180
|
),
|
|
295
|
-
]
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
181
|
+
] = None,
|
|
182
|
+
) -> Optional[RemoteServiceDiscoveryService]:
|
|
183
|
+
if is_invoked_for_completion():
|
|
184
|
+
# prevent lockdown connection establishment when in autocomplete mode
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
if rsd is not None and tunnel is not None:
|
|
188
|
+
raise UsageError("Illegal usage: --rsd is mutually exclusive with --tunnel.")
|
|
189
|
+
|
|
190
|
+
if rsd is not None:
|
|
191
|
+
rsd_service = RemoteServiceDiscoveryService(rsd)
|
|
192
|
+
asyncio.run(rsd_service.connect(), debug=True)
|
|
193
|
+
return rsd_service
|
|
194
|
+
|
|
195
|
+
if tunnel is not None or not allow_none:
|
|
196
|
+
return asyncio.run(_tunneld(tunnel or ""), debug=True)
|
|
197
|
+
|
|
198
|
+
return rsd_dependency
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def any_service_provider_dependency(
|
|
202
|
+
rsd_service_provider: Annotated[
|
|
203
|
+
Optional[RemoteServiceDiscoveryService],
|
|
204
|
+
Depends(make_rsd_dependency(allow_none=True)),
|
|
205
|
+
] = None,
|
|
206
|
+
mobdev2: Annotated[
|
|
207
|
+
bool,
|
|
208
|
+
typer.Option(
|
|
209
|
+
help="Discover devices over bonjour/mobdev2 instead of usbmux.",
|
|
210
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
211
|
+
),
|
|
212
|
+
] = False,
|
|
213
|
+
usbmux: Annotated[
|
|
214
|
+
Optional[str],
|
|
215
|
+
typer.Option(
|
|
216
|
+
envvar=USBMUX_ENV_VAR,
|
|
217
|
+
help=USBMUX_OPTION_HELP,
|
|
218
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
219
|
+
),
|
|
220
|
+
] = None,
|
|
221
|
+
udid: Annotated[
|
|
222
|
+
Optional[str],
|
|
223
|
+
typer.Option(
|
|
224
|
+
envvar=UDID_ENV_VAR,
|
|
225
|
+
help="Target device UDID (defaults to the first USB device).",
|
|
226
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
227
|
+
),
|
|
228
|
+
] = None,
|
|
229
|
+
) -> LockdownServiceProvider:
|
|
230
|
+
if is_invoked_for_completion():
|
|
231
|
+
# prevent lockdown connection establishment when in autocomplete mode
|
|
232
|
+
return # type: ignore[return-value]
|
|
233
|
+
|
|
234
|
+
if rsd_service_provider is not None:
|
|
235
|
+
return rsd_service_provider
|
|
236
|
+
|
|
237
|
+
if mobdev2:
|
|
238
|
+
devices = asyncio.run(get_mobdev2_devices(udid=udid))
|
|
239
|
+
if not devices:
|
|
315
240
|
raise NoDeviceConnectedError()
|
|
316
241
|
|
|
317
|
-
if
|
|
318
|
-
|
|
319
|
-
# Connect to the specified device
|
|
320
|
-
self.service_provider = next(
|
|
321
|
-
rsd for rsd in rsds if rsd.udid == udid or rsd.udid.replace("-", "") == udid
|
|
322
|
-
)
|
|
323
|
-
except IndexError as e:
|
|
324
|
-
raise DeviceNotFoundError(udid) from e
|
|
325
|
-
else:
|
|
326
|
-
if len(rsds) == 1:
|
|
327
|
-
self.service_provider = rsds[0]
|
|
328
|
-
else:
|
|
329
|
-
self.service_provider = prompt_device_list(rsds)
|
|
242
|
+
if len(devices) == 1:
|
|
243
|
+
return devices[0]
|
|
330
244
|
|
|
331
|
-
|
|
332
|
-
if rsd == self.service_provider:
|
|
333
|
-
continue
|
|
334
|
-
await rsd.close()
|
|
245
|
+
return prompt_device_list(devices)
|
|
335
246
|
|
|
336
|
-
|
|
247
|
+
if udid is not None:
|
|
248
|
+
return create_using_usbmux(serial=udid, usbmux_address=usbmux)
|
|
337
249
|
|
|
338
|
-
|
|
339
|
-
|
|
250
|
+
devices = select_devices_by_connection_type(connection_type="USB", usbmux_address=usbmux)
|
|
251
|
+
if len(devices) <= 1:
|
|
252
|
+
return create_using_usbmux(usbmux_address=usbmux)
|
|
340
253
|
|
|
254
|
+
return prompt_device_list([create_using_usbmux(serial=device.serial, usbmux_address=usbmux) for device in devices])
|
|
341
255
|
|
|
342
|
-
class Command(RSDCommand, LockdownCommand):
|
|
343
|
-
def __init__(self, *args, **kwargs):
|
|
344
|
-
super().__init__(*args, **kwargs)
|
|
345
256
|
|
|
257
|
+
def no_autopair_service_provider_dependency(
|
|
258
|
+
rsd_service_provider: Annotated[
|
|
259
|
+
Optional[RemoteServiceDiscoveryService],
|
|
260
|
+
Depends(make_rsd_dependency(allow_none=True)),
|
|
261
|
+
] = None,
|
|
262
|
+
udid: Annotated[
|
|
263
|
+
Optional[str],
|
|
264
|
+
typer.Option(
|
|
265
|
+
envvar=UDID_ENV_VAR,
|
|
266
|
+
help="Target device UDID (defaults to the first USB device).",
|
|
267
|
+
rich_help_panel=DEVICE_OPTIONS_PANEL_TITLE,
|
|
268
|
+
),
|
|
269
|
+
] = None,
|
|
270
|
+
) -> LockdownServiceProvider:
|
|
271
|
+
if is_invoked_for_completion():
|
|
272
|
+
# prevent lockdown connection establishment when in autocomplete mode
|
|
273
|
+
return # type: ignore[return-value]
|
|
346
274
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
275
|
+
if rsd_service_provider is not None:
|
|
276
|
+
return rsd_service_provider
|
|
277
|
+
|
|
278
|
+
return create_using_usbmux(serial=udid, autopair=False)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
RSDServiceProviderDep = Annotated[
|
|
282
|
+
RemoteServiceDiscoveryService,
|
|
283
|
+
Depends(make_rsd_dependency(allow_none=False)),
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
ServiceProviderDep = Annotated[
|
|
288
|
+
LockdownServiceProvider,
|
|
289
|
+
Depends(any_service_provider_dependency),
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
NoAutoPairServiceProviderDep = Annotated[
|
|
294
|
+
LockdownServiceProvider,
|
|
295
|
+
Depends(no_autopair_service_provider_dependency),
|
|
296
|
+
]
|
|
354
297
|
|
|
355
298
|
|
|
356
299
|
class BasedIntParamType(click.ParamType):
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
from typer_injector import InjectingTyper
|
|
2
2
|
|
|
3
|
-
from pymobiledevice3.cli.cli_common import
|
|
4
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
3
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
5
4
|
from pymobiledevice3.services.companion import CompanionProxyService
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
cli = InjectingTyper(
|
|
7
|
+
name="companion",
|
|
8
|
+
help='List paired "companion" devices',
|
|
9
|
+
no_args_is_help=True,
|
|
10
|
+
)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@cli.
|
|
14
|
-
def
|
|
15
|
-
|
|
13
|
+
@cli.callback()
|
|
14
|
+
def callback() -> None:
|
|
15
|
+
# Force subgroup
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@
|
|
20
|
-
def companion_list(service_provider:
|
|
19
|
+
@cli.command("list")
|
|
20
|
+
def companion_list(service_provider: ServiceProviderDep) -> None:
|
|
21
21
|
"""list all paired companion devices"""
|
|
22
22
|
print_json(CompanionProxyService(service_provider).list(), default=lambda x: "<non-serializable>")
|