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.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- 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 +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -6,69 +6,43 @@ 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
|
-
from pymobiledevice3.tunneld import TUNNELD_DEFAULT_ADDRESS, async_get_tunneld_devices
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
UDID_ENV_VAR = "PYMOBILEDEVICE3_UDID"
|
|
31
|
+
TUNNEL_ENV_VAR = "PYMOBILEDEVICE3_TUNNEL"
|
|
32
|
+
USBMUX_ENV_VAR = "PYMOBILEDEVICE3_USBMUX"
|
|
33
|
+
USBMUX_OPTION_HELP = (
|
|
34
|
+
"Address of the usbmuxd daemon (unix socket path or HOST:PORT). Defaults to the platform usbmuxd if omitted."
|
|
35
|
+
)
|
|
36
|
+
DEVICE_OPTIONS_PANEL_TITLE = "Device Options"
|
|
30
37
|
OSUTILS = get_os_utils()
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class RSDOption(Option):
|
|
37
|
-
def __init__(self, *args, **kwargs):
|
|
38
|
-
self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
|
|
39
|
-
help = kwargs.get('help', '')
|
|
40
|
-
if self.mutually_exclusive:
|
|
41
|
-
ex_str = ', '.join(self.mutually_exclusive)
|
|
42
|
-
kwargs['help'] = help + (
|
|
43
|
-
'\nNOTE: This argument is mutually exclusive with '
|
|
44
|
-
' arguments: [' + ex_str + '].'
|
|
45
|
-
)
|
|
46
|
-
super().__init__(*args, **kwargs)
|
|
47
|
-
|
|
48
|
-
def handle_parse_result(self, ctx, opts, args):
|
|
49
|
-
if (isinstance(ctx.command, RSDCommand) and not (isinstance(ctx.command, Command)) and
|
|
50
|
-
('rsd_service_provider_using_tunneld' not in opts) and ('rsd_service_provider_manually' not in opts)):
|
|
51
|
-
# defaulting to `--tunnel ''` if no remote option was specified
|
|
52
|
-
opts['rsd_service_provider_using_tunneld'] = ''
|
|
53
|
-
if self.mutually_exclusive.intersection(opts) and self.name in opts:
|
|
54
|
-
raise UsageError(
|
|
55
|
-
'Illegal usage: `{}` is mutually exclusive with '
|
|
56
|
-
'arguments `{}`.'.format(
|
|
57
|
-
self.name,
|
|
58
|
-
', '.join(self.mutually_exclusive)
|
|
59
|
-
)
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return super().handle_parse_result(
|
|
63
|
-
ctx,
|
|
64
|
-
opts,
|
|
65
|
-
args
|
|
66
|
-
)
|
|
39
|
+
# Global options
|
|
40
|
+
COLORED_OUTPUT: bool = True
|
|
67
41
|
|
|
68
42
|
|
|
69
43
|
def default_json_encoder(obj):
|
|
70
44
|
if isinstance(obj, bytes):
|
|
71
|
-
return f
|
|
45
|
+
return f"<{obj.hex()}>"
|
|
72
46
|
if isinstance(obj, datetime.datetime):
|
|
73
47
|
return str(obj)
|
|
74
48
|
if isinstance(obj, uuid.UUID):
|
|
@@ -76,13 +50,14 @@ def default_json_encoder(obj):
|
|
|
76
50
|
raise TypeError()
|
|
77
51
|
|
|
78
52
|
|
|
79
|
-
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:
|
|
80
54
|
if colored is None:
|
|
81
55
|
colored = user_requested_colored_output()
|
|
82
56
|
formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default)
|
|
83
57
|
if colored and os.isatty(sys.stdout.fileno()):
|
|
84
|
-
colorful_json = highlight(
|
|
85
|
-
|
|
58
|
+
colorful_json = highlight(
|
|
59
|
+
formatted_json, lexers.JsonLexer(), formatters.Terminal256Formatter(style="stata-dark")
|
|
60
|
+
)
|
|
86
61
|
print(colorful_json)
|
|
87
62
|
return colorful_json
|
|
88
63
|
else:
|
|
@@ -90,19 +65,19 @@ 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):
|
|
94
|
-
hex_dump = hexdump.hexdump(data, result=
|
|
68
|
+
def print_hex(data, colored=True) -> None:
|
|
69
|
+
hex_dump = hexdump.hexdump(data, result="return")
|
|
95
70
|
if colored:
|
|
96
|
-
print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style=
|
|
71
|
+
print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style="native")))
|
|
97
72
|
else:
|
|
98
|
-
print(hex_dump, end=
|
|
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
|
|
|
@@ -116,7 +91,7 @@ def user_requested_colored_output() -> bool:
|
|
|
116
91
|
|
|
117
92
|
|
|
118
93
|
def get_last_used_terminal_formatting(buf: str) -> str:
|
|
119
|
-
return
|
|
94
|
+
return "\x1b" + buf.rsplit("\x1b", 1)[1].split("m")[0] + "m"
|
|
120
95
|
|
|
121
96
|
|
|
122
97
|
def sudo_required(func):
|
|
@@ -131,173 +106,204 @@ def sudo_required(func):
|
|
|
131
106
|
|
|
132
107
|
|
|
133
108
|
def prompt_selection(choices: list[Any], message: str, idx: bool = False) -> Any:
|
|
134
|
-
question = [inquirer3.List(
|
|
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
112
|
except KeyboardInterrupt:
|
|
138
|
-
raise click.ClickException(
|
|
139
|
-
return result[
|
|
113
|
+
raise click.ClickException("No selection was made") from None
|
|
114
|
+
return result["selection"] if not idx else choices.index(result["selection"])
|
|
140
115
|
|
|
141
116
|
|
|
142
117
|
def prompt_device_list(device_list: list):
|
|
143
|
-
return prompt_selection(device_list,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
118
|
+
return prompt_selection(device_list, "Choose device")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_invoked_for_completion() -> bool:
|
|
122
|
+
"""Returns True if the command is invoked for autocompletion."""
|
|
123
|
+
return any(env.startswith("_") and env.endswith("_COMPLETE") for env in os.environ)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def get_mobdev2_devices(udid: Optional[str] = None) -> list[TcpLockdownClient]:
|
|
127
|
+
return [lockdown async for _, lockdown in get_mobdev2_lockdowns(udid=udid)]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def _tunneld(udid: Optional[str] = None) -> Optional[RemoteServiceDiscoveryService]:
|
|
131
|
+
if udid is None:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
udid = udid.strip()
|
|
135
|
+
port = TUNNELD_DEFAULT_ADDRESS[1]
|
|
136
|
+
if ":" in udid:
|
|
137
|
+
udid, port = udid.split(":")
|
|
138
|
+
|
|
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,
|
|
169
|
+
),
|
|
170
|
+
] = None,
|
|
171
|
+
tunnel: Annotated[
|
|
172
|
+
Optional[str],
|
|
173
|
+
typer.Option(
|
|
174
|
+
envvar=TUNNEL_ENV_VAR,
|
|
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,
|
|
180
|
+
),
|
|
181
|
+
] = None,
|
|
182
|
+
) -> Optional[RemoteServiceDiscoveryService]:
|
|
183
|
+
if is_invoked_for_completion():
|
|
202
184
|
# prevent lockdown connection establishment when in autocomplete mode
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
if
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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:
|
|
254
240
|
raise NoDeviceConnectedError()
|
|
255
241
|
|
|
256
|
-
if
|
|
257
|
-
|
|
258
|
-
# Connect to the specified device
|
|
259
|
-
self.service_provider = [rsd for rsd in rsds if rsd.udid == udid][0]
|
|
260
|
-
except IndexError:
|
|
261
|
-
raise DeviceNotFoundError(udid)
|
|
262
|
-
else:
|
|
263
|
-
if len(rsds) == 1:
|
|
264
|
-
self.service_provider = rsds[0]
|
|
265
|
-
else:
|
|
266
|
-
self.service_provider = prompt_device_list(rsds)
|
|
242
|
+
if len(devices) == 1:
|
|
243
|
+
return devices[0]
|
|
267
244
|
|
|
268
|
-
|
|
269
|
-
if rsd == self.service_provider:
|
|
270
|
-
continue
|
|
271
|
-
await rsd.close()
|
|
245
|
+
return prompt_device_list(devices)
|
|
272
246
|
|
|
273
|
-
|
|
247
|
+
if udid is not None:
|
|
248
|
+
return create_using_usbmux(serial=udid, usbmux_address=usbmux)
|
|
274
249
|
|
|
275
|
-
|
|
276
|
-
|
|
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)
|
|
277
253
|
|
|
254
|
+
return prompt_device_list([create_using_usbmux(serial=device.serial, usbmux_address=usbmux) for device in devices])
|
|
278
255
|
|
|
279
|
-
class Command(RSDCommand, LockdownCommand):
|
|
280
|
-
def __init__(self, *args, **kwargs):
|
|
281
|
-
super().__init__(*args, **kwargs)
|
|
282
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]
|
|
283
274
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
]
|
|
291
297
|
|
|
292
298
|
|
|
293
299
|
class BasedIntParamType(click.ParamType):
|
|
294
|
-
name =
|
|
300
|
+
name = "based int"
|
|
295
301
|
|
|
296
302
|
def convert(self, value, param, ctx):
|
|
297
303
|
try:
|
|
298
304
|
return int(value, 0)
|
|
299
305
|
except ValueError:
|
|
300
|
-
self.fail(f
|
|
306
|
+
self.fail(f"{value!r} is not a valid int.", param, ctx)
|
|
301
307
|
|
|
302
308
|
|
|
303
309
|
BASED_INT = BasedIntParamType()
|
|
@@ -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:
|
|
21
|
-
"""
|
|
22
|
-
print_json(CompanionProxyService(service_provider).list(), default=lambda x:
|
|
19
|
+
@cli.command("list")
|
|
20
|
+
def companion_list(service_provider: ServiceProviderDep) -> None:
|
|
21
|
+
"""list all paired companion devices"""
|
|
22
|
+
print_json(CompanionProxyService(service_provider).list(), default=lambda x: "<non-serializable>")
|