pymobiledevice3 4.27.0__py3-none-any.whl → 5.1.2__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
- pymobiledevice3/__main__.py +123 -98
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +351 -117
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +27 -20
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +601 -519
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +82 -72
- pymobiledevice3/cli/mounter.py +84 -67
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +188 -111
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +156 -78
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +400 -251
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -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 +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +64 -42
- pymobiledevice3/remote/tunnel_service.py +383 -297
- 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 +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +535 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +137 -127
- pymobiledevice3/services/afc.py +363 -293
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +61 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +56 -48
- pymobiledevice3/services/diagnostics.py +971 -968
- 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 +11 -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 +25 -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 +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +331 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +128 -74
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- 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 +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +183 -179
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +142 -116
- pymobiledevice3/tcp_forwarder.py +64 -50
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +315 -193
- pymobiledevice3/usbmux.py +197 -148
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +2 -6
- pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
- pymobiledevice3-4.27.0.dist-info/RECORD +0 -172
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
|
@@ -24,51 +24,50 @@ from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS, async_get_tunne
|
|
|
24
24
|
from pymobiledevice3.usbmux import select_devices_by_connection_type
|
|
25
25
|
|
|
26
26
|
COLORED_OUTPUT = True
|
|
27
|
-
UDID_ENV_VAR =
|
|
28
|
-
TUNNEL_ENV_VAR =
|
|
29
|
-
USBMUX_ENV_VAR =
|
|
27
|
+
UDID_ENV_VAR = "PYMOBILEDEVICE3_UDID"
|
|
28
|
+
TUNNEL_ENV_VAR = "PYMOBILEDEVICE3_TUNNEL"
|
|
29
|
+
USBMUX_ENV_VAR = "PYMOBILEDEVICE3_USBMUX"
|
|
30
30
|
OSUTILS = get_os_utils()
|
|
31
31
|
|
|
32
|
-
USBMUX_OPTION_HELP = (
|
|
33
|
-
|
|
32
|
+
USBMUX_OPTION_HELP = (
|
|
33
|
+
f"usbmuxd listener address (in the form of either /path/to/unix/socket OR HOST:PORT). "
|
|
34
|
+
f"Can be specified via {USBMUX_ENV_VAR} envvar"
|
|
35
|
+
)
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
class RSDOption(Option):
|
|
37
39
|
def __init__(self, *args, **kwargs):
|
|
38
|
-
self.mutually_exclusive = set(kwargs.pop(
|
|
39
|
-
|
|
40
|
+
self.mutually_exclusive = set(kwargs.pop("mutually_exclusive", []))
|
|
41
|
+
help_option = kwargs.get("help", "")
|
|
40
42
|
if self.mutually_exclusive:
|
|
41
|
-
ex_str =
|
|
42
|
-
kwargs[
|
|
43
|
-
|
|
44
|
-
' arguments: [' + ex_str + '].'
|
|
43
|
+
ex_str = ", ".join(self.mutually_exclusive)
|
|
44
|
+
kwargs["help"] = help_option + (
|
|
45
|
+
"\nNOTE: This argument is mutually exclusive with arguments: [" + ex_str + "]."
|
|
45
46
|
)
|
|
46
47
|
super().__init__(*args, **kwargs)
|
|
47
48
|
|
|
48
49
|
def handle_parse_result(self, ctx, opts, args):
|
|
49
|
-
if (
|
|
50
|
-
|
|
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
|
+
):
|
|
51
56
|
# defaulting to `--tunnel ''` if no remote option was specified
|
|
52
|
-
opts[
|
|
57
|
+
opts["rsd_service_provider_using_tunneld"] = ""
|
|
53
58
|
if self.mutually_exclusive.intersection(opts) and self.name in opts:
|
|
54
59
|
raise UsageError(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
self.name,
|
|
58
|
-
', '.join(self.mutually_exclusive)
|
|
60
|
+
"Illegal usage: `{}` is mutually exclusive with arguments `{}`.".format(
|
|
61
|
+
self.name, ", ".join(self.mutually_exclusive)
|
|
59
62
|
)
|
|
60
63
|
)
|
|
61
64
|
|
|
62
|
-
return super().handle_parse_result(
|
|
63
|
-
ctx,
|
|
64
|
-
opts,
|
|
65
|
-
args
|
|
66
|
-
)
|
|
65
|
+
return super().handle_parse_result(ctx, opts, args)
|
|
67
66
|
|
|
68
67
|
|
|
69
68
|
def default_json_encoder(obj):
|
|
70
69
|
if isinstance(obj, bytes):
|
|
71
|
-
return f
|
|
70
|
+
return f"<{obj.hex()}>"
|
|
72
71
|
if isinstance(obj, datetime.datetime):
|
|
73
72
|
return str(obj)
|
|
74
73
|
if isinstance(obj, uuid.UUID):
|
|
@@ -81,8 +80,9 @@ def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder
|
|
|
81
80
|
colored = user_requested_colored_output()
|
|
82
81
|
formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default)
|
|
83
82
|
if colored and os.isatty(sys.stdout.fileno()):
|
|
84
|
-
colorful_json = highlight(
|
|
85
|
-
|
|
83
|
+
colorful_json = highlight(
|
|
84
|
+
formatted_json, lexers.JsonLexer(), formatters.Terminal256Formatter(style="stata-dark")
|
|
85
|
+
)
|
|
86
86
|
print(colorful_json)
|
|
87
87
|
return colorful_json
|
|
88
88
|
else:
|
|
@@ -91,11 +91,11 @@ def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def print_hex(data, colored=True):
|
|
94
|
-
hex_dump = hexdump.hexdump(data, result=
|
|
94
|
+
hex_dump = hexdump.hexdump(data, result="return")
|
|
95
95
|
if colored:
|
|
96
|
-
print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style=
|
|
96
|
+
print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style="native")))
|
|
97
97
|
else:
|
|
98
|
-
print(hex_dump, end=
|
|
98
|
+
print(hex_dump, end="\n\n")
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def set_verbosity(ctx, param, value):
|
|
@@ -116,7 +116,7 @@ def user_requested_colored_output() -> bool:
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def get_last_used_terminal_formatting(buf: str) -> str:
|
|
119
|
-
return
|
|
119
|
+
return "\x1b" + buf.rsplit("\x1b", 1)[1].split("m")[0] + "m"
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def sudo_required(func):
|
|
@@ -131,24 +131,24 @@ def sudo_required(func):
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
def prompt_selection(choices: list[Any], message: str, idx: bool = False) -> Any:
|
|
134
|
-
question = [inquirer3.List(
|
|
134
|
+
question = [inquirer3.List("selection", message=message, choices=choices, carousel=True)]
|
|
135
135
|
try:
|
|
136
136
|
result = inquirer3.prompt(question, theme=GreenPassion(), raise_keyboard_interrupt=True)
|
|
137
|
-
except KeyboardInterrupt:
|
|
138
|
-
raise click.ClickException(
|
|
139
|
-
return result[
|
|
137
|
+
except KeyboardInterrupt as e:
|
|
138
|
+
raise click.ClickException("No selection was made") from e
|
|
139
|
+
return result["selection"] if not idx else choices.index(result["selection"])
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
def prompt_device_list(device_list: list):
|
|
143
|
-
return prompt_selection(device_list,
|
|
143
|
+
return prompt_selection(device_list, "Choose device")
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
def choose_service_provider(callback: Callable):
|
|
147
147
|
def wrap_callback_calling(**kwargs: dict) -> None:
|
|
148
148
|
service_provider = None
|
|
149
|
-
lockdown_service_provider = kwargs.pop(
|
|
150
|
-
rsd_service_provider_manually = kwargs.pop(
|
|
151
|
-
rsd_service_provider_using_tunneld = kwargs.pop(
|
|
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
152
|
if lockdown_service_provider is not None:
|
|
153
153
|
service_provider = lockdown_service_provider
|
|
154
154
|
if rsd_service_provider_manually is not None:
|
|
@@ -161,20 +161,23 @@ def choose_service_provider(callback: Callable):
|
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
def is_invoked_for_completion() -> bool:
|
|
164
|
-
"""
|
|
165
|
-
for env in os.environ
|
|
166
|
-
if env.startswith('_') and env.endswith('_COMPLETE'):
|
|
167
|
-
return True
|
|
168
|
-
return False
|
|
164
|
+
"""Returns True if the command is ivoked for autocompletion."""
|
|
165
|
+
return any(env.startswith("_") and env.endswith("_COMPLETE") for env in os.environ)
|
|
169
166
|
|
|
170
167
|
|
|
171
168
|
class BaseCommand(click.Command):
|
|
172
169
|
def __init__(self, *args, **kwargs):
|
|
173
170
|
super().__init__(*args, **kwargs)
|
|
174
171
|
self.params[:0] = [
|
|
175
|
-
click.Option((
|
|
176
|
-
click.Option(
|
|
177
|
-
|
|
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
|
+
),
|
|
178
181
|
]
|
|
179
182
|
|
|
180
183
|
|
|
@@ -191,20 +194,35 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
191
194
|
self.usbmux_address = None
|
|
192
195
|
self.mobdev2_option = None
|
|
193
196
|
self.params[:0] = [
|
|
194
|
-
click.Option(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
),
|
|
202
219
|
]
|
|
203
220
|
|
|
204
|
-
async def get_mobdev2_devices(
|
|
205
|
-
|
|
221
|
+
async def get_mobdev2_devices(
|
|
222
|
+
self, udid: Optional[str] = None, ips: Optional[list[str]] = None
|
|
223
|
+
) -> list[TcpLockdownClient]:
|
|
206
224
|
result = []
|
|
207
|
-
async for
|
|
225
|
+
async for _ip, lockdown in get_mobdev2_lockdowns(udid=udid, ips=ips):
|
|
208
226
|
result.append(lockdown)
|
|
209
227
|
return result
|
|
210
228
|
|
|
@@ -225,8 +243,11 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
225
243
|
return self.service_provider
|
|
226
244
|
|
|
227
245
|
if self.mobdev2_option is not None:
|
|
228
|
-
devices = asyncio.run(
|
|
229
|
-
|
|
246
|
+
devices = asyncio.run(
|
|
247
|
+
self.get_mobdev2_devices(
|
|
248
|
+
udid=value if value else None, ips=[self.mobdev2_option] if self.mobdev2_option else None
|
|
249
|
+
)
|
|
250
|
+
)
|
|
230
251
|
if not devices:
|
|
231
252
|
raise NoDeviceConnectedError()
|
|
232
253
|
|
|
@@ -240,29 +261,37 @@ class LockdownCommand(BaseServiceProviderCommand):
|
|
|
240
261
|
if value is not None:
|
|
241
262
|
return create_using_usbmux(serial=value)
|
|
242
263
|
|
|
243
|
-
devices = select_devices_by_connection_type(connection_type=
|
|
264
|
+
devices = select_devices_by_connection_type(connection_type="USB", usbmux_address=self.usbmux_address)
|
|
244
265
|
if len(devices) <= 1:
|
|
245
266
|
return create_using_usbmux(usbmux_address=self.usbmux_address)
|
|
246
267
|
|
|
247
|
-
return prompt_device_list(
|
|
248
|
-
|
|
268
|
+
return prompt_device_list([
|
|
269
|
+
create_using_usbmux(serial=device.serial, usbmux_address=self.usbmux_address) for device in devices
|
|
270
|
+
])
|
|
249
271
|
|
|
250
272
|
|
|
251
273
|
class RSDCommand(BaseServiceProviderCommand):
|
|
252
274
|
def __init__(self, *args, **kwargs):
|
|
253
275
|
super().__init__(*args, **kwargs)
|
|
254
276
|
self.params[:0] = [
|
|
255
|
-
RSDOption(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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).",
|
|
283
|
+
),
|
|
284
|
+
RSDOption(
|
|
285
|
+
("rsd_service_provider_using_tunneld", "--tunnel"),
|
|
286
|
+
callback=self.tunneld,
|
|
287
|
+
mutually_exclusive=["rsd_service_provider_manually"],
|
|
288
|
+
envvar=TUNNEL_ENV_VAR,
|
|
289
|
+
help="\b\n"
|
|
290
|
+
"Either an empty string to force tunneld device selection, or a UDID of a tunneld "
|
|
291
|
+
"discovered device.\n"
|
|
292
|
+
"The string may be suffixed with :PORT in case tunneld is not serving at the default port.\n"
|
|
293
|
+
f"This option may also be transferred as an environment variable: {TUNNEL_ENV_VAR}",
|
|
294
|
+
),
|
|
266
295
|
]
|
|
267
296
|
|
|
268
297
|
def rsd(self, ctx, param: str, value: Optional[tuple[str, int]]) -> Optional[RemoteServiceDiscoveryService]:
|
|
@@ -278,19 +307,21 @@ class RSDCommand(BaseServiceProviderCommand):
|
|
|
278
307
|
|
|
279
308
|
udid = udid.strip()
|
|
280
309
|
port = TUNNELD_DEFAULT_ADDRESS[1]
|
|
281
|
-
if
|
|
282
|
-
udid, port = udid.split(
|
|
310
|
+
if ":" in udid:
|
|
311
|
+
udid, port = udid.split(":")
|
|
283
312
|
|
|
284
313
|
rsds = await async_get_tunneld_devices((TUNNELD_DEFAULT_ADDRESS[0], port))
|
|
285
314
|
if len(rsds) == 0:
|
|
286
315
|
raise NoDeviceConnectedError()
|
|
287
316
|
|
|
288
|
-
if udid !=
|
|
317
|
+
if udid != "":
|
|
289
318
|
try:
|
|
290
319
|
# Connect to the specified device
|
|
291
|
-
self.service_provider =
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
294
325
|
else:
|
|
295
326
|
if len(rsds) == 1:
|
|
296
327
|
self.service_provider = rsds[0]
|
|
@@ -323,13 +354,13 @@ class CommandWithoutAutopair(Command):
|
|
|
323
354
|
|
|
324
355
|
|
|
325
356
|
class BasedIntParamType(click.ParamType):
|
|
326
|
-
name =
|
|
357
|
+
name = "based int"
|
|
327
358
|
|
|
328
359
|
def convert(self, value, param, ctx):
|
|
329
360
|
try:
|
|
330
361
|
return int(value, 0)
|
|
331
362
|
except ValueError:
|
|
332
|
-
self.fail(f
|
|
363
|
+
self.fail(f"{value!r} is not a valid int.", param, ctx)
|
|
333
364
|
|
|
334
365
|
|
|
335
366
|
BASED_INT = BasedIntParamType()
|
|
@@ -12,11 +12,11 @@ def cli() -> None:
|
|
|
12
12
|
|
|
13
13
|
@cli.group()
|
|
14
14
|
def companion() -> None:
|
|
15
|
-
"""
|
|
15
|
+
"""List paired "companion" devices"""
|
|
16
16
|
pass
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@companion.command(
|
|
19
|
+
@companion.command("list", cls=Command)
|
|
20
20
|
def companion_list(service_provider: LockdownClient):
|
|
21
|
-
"""
|
|
22
|
-
print_json(CompanionProxyService(service_provider).list(), default=lambda x:
|
|
21
|
+
"""list all paired companion devices"""
|
|
22
|
+
print_json(CompanionProxyService(service_provider).list(), default=lambda x: "<non-serializable>")
|
|
@@ -7,12 +7,12 @@ from plumbum import CommandNotFound, local
|
|
|
7
7
|
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
|
-
ShellCompletion = namedtuple(
|
|
10
|
+
ShellCompletion = namedtuple("ShellCompletion", ["source", "rc", "path"])
|
|
11
11
|
|
|
12
12
|
COMPLETIONS = [
|
|
13
|
-
ShellCompletion(
|
|
14
|
-
ShellCompletion(
|
|
15
|
-
ShellCompletion(
|
|
13
|
+
ShellCompletion("zsh_source", Path("~/.zshrc").expanduser(), Path("~/.pymobiledevice3.zsh").expanduser()),
|
|
14
|
+
ShellCompletion("bash_source", Path("~/.bashrc").expanduser(), Path("~/.pymobiledevice3.bash").expanduser()),
|
|
15
|
+
ShellCompletion("fish_source", None, Path("~/.config/fish/completions/pymobiledevice3.fish").expanduser()),
|
|
16
16
|
]
|
|
17
17
|
|
|
18
18
|
|
|
@@ -29,9 +29,9 @@ def install_completions() -> None:
|
|
|
29
29
|
If supplying an explicit shell script to write, install it there, otherwise install globally.
|
|
30
30
|
"""
|
|
31
31
|
try:
|
|
32
|
-
pymobiledevice3 = local[
|
|
32
|
+
pymobiledevice3 = local["pymobiledevice3"]
|
|
33
33
|
except CommandNotFound:
|
|
34
|
-
logger.error(
|
|
34
|
+
logger.error("pymobiledevice3 main binary could not be found in your path.")
|
|
35
35
|
return
|
|
36
36
|
|
|
37
37
|
for completion in COMPLETIONS:
|
|
@@ -39,12 +39,12 @@ def install_completions() -> None:
|
|
|
39
39
|
if not completion.path.parent.exists():
|
|
40
40
|
# fish is not installed, skip
|
|
41
41
|
continue
|
|
42
|
-
logger.info(f
|
|
42
|
+
logger.info(f"Writing shell completions to: {completion.path}")
|
|
43
43
|
completion.path.write_text(pymobiledevice3())
|
|
44
|
-
line = f
|
|
44
|
+
line = f"source {completion.path}"
|
|
45
45
|
|
|
46
46
|
if not completion.rc.exists() or line in completion.rc.read_text():
|
|
47
47
|
continue
|
|
48
48
|
|
|
49
|
-
logger.info(f
|
|
50
|
-
completion.rc.write_text(f
|
|
49
|
+
logger.info(f"Adding source line to {completion.rc}")
|
|
50
|
+
completion.rc.write_text(f"{completion.rc.read_text()}\n{line}")
|
pymobiledevice3/cli/crash.py
CHANGED
|
@@ -13,70 +13,76 @@ def cli() -> None:
|
|
|
13
13
|
|
|
14
14
|
@cli.group()
|
|
15
15
|
def crash() -> None:
|
|
16
|
-
"""
|
|
16
|
+
"""Manage crash reports"""
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
@crash.command(
|
|
21
|
-
@click.option(
|
|
20
|
+
@crash.command("clear", cls=Command)
|
|
21
|
+
@click.option("-f", "--flush", is_flag=True, default=False, help="flush before clear")
|
|
22
22
|
def crash_clear(service_provider: LockdownClient, flush):
|
|
23
|
-
"""
|
|
23
|
+
"""clear(/remove) all crash reports"""
|
|
24
24
|
crash_manager = CrashReportsManager(service_provider)
|
|
25
25
|
if flush:
|
|
26
26
|
crash_manager.flush()
|
|
27
27
|
crash_manager.clear()
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
@crash.command(
|
|
31
|
-
@click.argument(
|
|
32
|
-
@click.argument(
|
|
33
|
-
@click.option(
|
|
34
|
-
@click.option(
|
|
30
|
+
@crash.command("pull", cls=Command)
|
|
31
|
+
@click.argument("out", type=click.Path(file_okay=False))
|
|
32
|
+
@click.argument("remote_file", type=click.Path(), required=False)
|
|
33
|
+
@click.option("-e", "--erase", is_flag=True)
|
|
34
|
+
@click.option("-m", "--match", help="Match given regex over enumerated basenames")
|
|
35
35
|
def crash_pull(service_provider: LockdownServiceProvider, out, remote_file, erase, match) -> None:
|
|
36
|
-
"""
|
|
36
|
+
"""pull all crash reports"""
|
|
37
37
|
if remote_file is None:
|
|
38
|
-
remote_file =
|
|
38
|
+
remote_file = "/"
|
|
39
39
|
CrashReportsManager(service_provider).pull(out, remote_file, erase, match)
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
@crash.command(
|
|
42
|
+
@crash.command("shell", cls=Command)
|
|
43
43
|
def crash_shell(service_provider: LockdownClient):
|
|
44
|
-
"""
|
|
44
|
+
"""start an afc shell"""
|
|
45
45
|
CrashReportsShell.create(service_provider)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
@crash.command(
|
|
49
|
-
@click.argument(
|
|
50
|
-
@click.option(
|
|
48
|
+
@crash.command("ls", cls=Command)
|
|
49
|
+
@click.argument("remote_file", type=click.Path(), required=False)
|
|
50
|
+
@click.option("-d", "--depth", type=click.INT, default=1)
|
|
51
51
|
def crash_ls(service_provider: LockdownClient, remote_file, depth):
|
|
52
|
-
"""
|
|
52
|
+
"""List"""
|
|
53
53
|
if remote_file is None:
|
|
54
|
-
remote_file =
|
|
54
|
+
remote_file = "/"
|
|
55
55
|
for path in CrashReportsManager(service_provider).ls(remote_file, depth):
|
|
56
56
|
print(path)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
@crash.command(
|
|
59
|
+
@crash.command("flush", cls=Command)
|
|
60
60
|
def crash_mover_flush(service_provider: LockdownClient):
|
|
61
|
-
"""
|
|
61
|
+
"""trigger com.apple.crashreportmover to flush all products into CrashReports directory"""
|
|
62
62
|
CrashReportsManager(service_provider).flush()
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
@crash.command(
|
|
66
|
-
@click.argument(
|
|
67
|
-
@click.option(
|
|
65
|
+
@crash.command("watch", cls=Command)
|
|
66
|
+
@click.argument("name", required=False)
|
|
67
|
+
@click.option("-r", "--raw", is_flag=True)
|
|
68
68
|
def crash_mover_watch(service_provider: LockdownClient, name, raw):
|
|
69
|
-
"""
|
|
69
|
+
"""watch for crash report generation"""
|
|
70
70
|
for crash_report in CrashReportsManager(service_provider).watch(name=name, raw=raw):
|
|
71
71
|
print(crash_report)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
@crash.command(
|
|
75
|
-
@click.argument(
|
|
76
|
-
@click.option(
|
|
77
|
-
@click.option(
|
|
78
|
-
|
|
74
|
+
@crash.command("sysdiagnose", cls=Command)
|
|
75
|
+
@click.argument("out", type=click.Path(exists=False, dir_okay=True, file_okay=True))
|
|
76
|
+
@click.option("-e", "--erase", is_flag=True, help="erase file after pulling")
|
|
77
|
+
@click.option(
|
|
78
|
+
"-t",
|
|
79
|
+
"--timeout",
|
|
80
|
+
default=None,
|
|
81
|
+
show_default=True,
|
|
82
|
+
type=click.FLOAT,
|
|
83
|
+
help="Maximum time in seconds to wait for the completion of sysdiagnose archive",
|
|
84
|
+
)
|
|
79
85
|
def crash_sysdiagnose(service_provider: LockdownClient, out, erase, timeout):
|
|
80
|
-
"""
|
|
81
|
-
print(
|
|
86
|
+
"""get a sysdiagnose archive from device (requires user interaction)"""
|
|
87
|
+
print("Press Power+VolUp+VolDown for 0.215 seconds")
|
|
82
88
|
CrashReportsManager(service_provider).get_new_sysdiagnose(out, erase=erase, timeout=timeout)
|