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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from ipsw_parser.dsc import create_device_support_layout, get_device_support_path
|
|
8
|
+
from packaging.version import Version
|
|
9
|
+
from typer_injector import InjectingTyper
|
|
10
|
+
|
|
11
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
12
|
+
from pymobiledevice3.exceptions import RSDRequiredError
|
|
13
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
14
|
+
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
15
|
+
from pymobiledevice3.services.dtfetchsymbols import DtFetchSymbols
|
|
16
|
+
from pymobiledevice3.services.remote_fetch_symbols import RemoteFetchSymbolsService
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
cli = InjectingTyper(
|
|
22
|
+
name="fetch-symbols",
|
|
23
|
+
help="Download the DSC (and dyld) from the device",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) -> None:
|
|
29
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
30
|
+
print_json(DtFetchSymbols(service_provider).list_files())
|
|
31
|
+
else:
|
|
32
|
+
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
33
|
+
raise RSDRequiredError(service_provider.identifier)
|
|
34
|
+
|
|
35
|
+
async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
|
|
36
|
+
print_json([f.file_path for f in await fetch_symbols.get_dsc_file_list()])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@cli.command("list")
|
|
40
|
+
def fetch_symbols_list(service_provider: ServiceProviderDep) -> None:
|
|
41
|
+
"""list of files to be downloaded"""
|
|
42
|
+
asyncio.run(fetch_symbols_list_task(service_provider), debug=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def fetch_symbols_download_task(service_provider: LockdownServiceProvider, out: Optional[Path] = None) -> None:
|
|
46
|
+
should_create_device_support_layout = False
|
|
47
|
+
if out is None:
|
|
48
|
+
assert service_provider.product_type is not None # for type checker
|
|
49
|
+
out = get_device_support_path(
|
|
50
|
+
service_provider.product_type, service_provider.product_version, service_provider.product_build_version
|
|
51
|
+
)
|
|
52
|
+
should_create_device_support_layout = True
|
|
53
|
+
|
|
54
|
+
logger.info(f"Downloading DSC into: {out}")
|
|
55
|
+
|
|
56
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
|
|
58
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
59
|
+
fetch_symbols = DtFetchSymbols(service_provider)
|
|
60
|
+
files = fetch_symbols.list_files()
|
|
61
|
+
|
|
62
|
+
downloaded_files = set()
|
|
63
|
+
|
|
64
|
+
for i, file in enumerate(files):
|
|
65
|
+
if file.startswith("/"):
|
|
66
|
+
# trim root to allow relative download
|
|
67
|
+
file = file[1:]
|
|
68
|
+
file = out / file
|
|
69
|
+
|
|
70
|
+
if file not in downloaded_files:
|
|
71
|
+
# first time the file was seen in list, means we can safely remove any old copy if any
|
|
72
|
+
file.unlink(missing_ok=True)
|
|
73
|
+
|
|
74
|
+
downloaded_files.add(file)
|
|
75
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
with open(file, "ab") as f:
|
|
77
|
+
# same file may appear twice, so we'll need to append data into it
|
|
78
|
+
logger.info(f"writing to: {file}")
|
|
79
|
+
fetch_symbols.get_file(i, f)
|
|
80
|
+
else:
|
|
81
|
+
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
82
|
+
raise RSDRequiredError(service_provider.identifier)
|
|
83
|
+
async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
|
|
84
|
+
await fetch_symbols.download(out)
|
|
85
|
+
|
|
86
|
+
if should_create_device_support_layout:
|
|
87
|
+
assert service_provider.product_type is not None # for type checker
|
|
88
|
+
create_device_support_layout(
|
|
89
|
+
service_provider.product_type, service_provider.product_version, service_provider.product_build_version, out
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@cli.command("download")
|
|
94
|
+
def fetch_symbols_download(
|
|
95
|
+
service_provider: ServiceProviderDep,
|
|
96
|
+
out: Annotated[
|
|
97
|
+
Optional[Path],
|
|
98
|
+
typer.Argument(dir_okay=True, file_okay=False),
|
|
99
|
+
] = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Fetches symbols from the given device and saves them into Xcode DeviceSupport directory.
|
|
103
|
+
|
|
104
|
+
This command downloads symbol data. Optionally, the user can specify an output directory where the data will
|
|
105
|
+
be stored. If no output directory is provided, the symbols will be downloaded into the Xcode directory directly
|
|
106
|
+
(DeviceSupport).
|
|
107
|
+
"""
|
|
108
|
+
asyncio.run(fetch_symbols_download_task(service_provider, out), debug=True)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from typer_injector import InjectingTyper
|
|
7
|
+
|
|
8
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep
|
|
9
|
+
from pymobiledevice3.services.simulate_location import DtSimulateLocation
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
cli = InjectingTyper(
|
|
15
|
+
name="simulate-location",
|
|
16
|
+
help="Simulate GPS location (set, clear, or replay GPX routes).",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@cli.command("clear")
|
|
22
|
+
def simulate_location_clear(service_provider: ServiceProviderDep) -> None:
|
|
23
|
+
"""Stop location simulation and resume real GPS."""
|
|
24
|
+
DtSimulateLocation(service_provider).clear()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@cli.command("set")
|
|
28
|
+
def simulate_location_set(service_provider: ServiceProviderDep, latitude: float, longitude: float) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Set a fixed simulated location (latitude, longitude).
|
|
31
|
+
Example: `set 40.690008 -74.045843` (Liberty Island).
|
|
32
|
+
"""
|
|
33
|
+
DtSimulateLocation(service_provider).set(latitude, longitude)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@cli.command("play")
|
|
37
|
+
def simulate_location_play(
|
|
38
|
+
service_provider: ServiceProviderDep,
|
|
39
|
+
filename: Annotated[
|
|
40
|
+
Path,
|
|
41
|
+
typer.Argument(exists=True, file_okay=True, dir_okay=False),
|
|
42
|
+
],
|
|
43
|
+
timing_randomness_range: int,
|
|
44
|
+
disable_sleep: Annotated[bool, typer.Option()] = False,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Replay a GPX route; optionally disable sleeps and add timing jitter."""
|
|
47
|
+
DtSimulateLocation(service_provider).play_gpx_file(
|
|
48
|
+
str(filename),
|
|
49
|
+
disable_sleep=disable_sleep,
|
|
50
|
+
timing_randomness_range=timing_randomness_range,
|
|
51
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Annotated, Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from typer_injector import InjectingTyper
|
|
6
|
+
|
|
7
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
8
|
+
from pymobiledevice3.cli.diagnostics import battery
|
|
9
|
+
from pymobiledevice3.lockdown import retry_create_using_usbmux
|
|
10
|
+
from pymobiledevice3.services.diagnostics import DiagnosticsService
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
cli = InjectingTyper(
|
|
16
|
+
name="diagnostics",
|
|
17
|
+
help="Reboot/Shutdown device or access other diagnostics services",
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
)
|
|
20
|
+
cli.add_typer(battery.cli)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@cli.command("restart")
|
|
24
|
+
def diagnostics_restart(
|
|
25
|
+
service_provider: ServiceProviderDep,
|
|
26
|
+
reconnect: Annotated[
|
|
27
|
+
bool,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--reconnect",
|
|
30
|
+
"-r",
|
|
31
|
+
help="Wait until the device reconnects before finishing the operation.",
|
|
32
|
+
),
|
|
33
|
+
] = False,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Restart device"""
|
|
36
|
+
DiagnosticsService(lockdown=service_provider).restart()
|
|
37
|
+
if reconnect:
|
|
38
|
+
# Wait for the device to be available again
|
|
39
|
+
with retry_create_using_usbmux(None, serial=service_provider.udid):
|
|
40
|
+
print(f"Device Reconnected ({service_provider.udid}).")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@cli.command("shutdown")
|
|
44
|
+
def diagnostics_shutdown(service_provider: ServiceProviderDep) -> None:
|
|
45
|
+
"""Shutdown device"""
|
|
46
|
+
DiagnosticsService(lockdown=service_provider).shutdown()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@cli.command("sleep")
|
|
50
|
+
def diagnostics_sleep(service_provider: ServiceProviderDep) -> None:
|
|
51
|
+
"""Put device into sleep"""
|
|
52
|
+
DiagnosticsService(lockdown=service_provider).sleep()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@cli.command("info")
|
|
56
|
+
def diagnostics_info(service_provider: ServiceProviderDep) -> None:
|
|
57
|
+
"""Get diagnostics info"""
|
|
58
|
+
print_json(DiagnosticsService(lockdown=service_provider).info())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@cli.command("ioregistry")
|
|
62
|
+
def diagnostics_ioregistry(
|
|
63
|
+
service_provider: ServiceProviderDep,
|
|
64
|
+
plane: Annotated[Optional[str], typer.Option()] = None,
|
|
65
|
+
name: Annotated[Optional[str], typer.Option()] = None,
|
|
66
|
+
ioclass: Annotated[Optional[str], typer.Option()] = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Get ioregistry info"""
|
|
69
|
+
print_json(DiagnosticsService(lockdown=service_provider).ioregistry(plane=plane, name=name, ioclass=ioclass))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@cli.command("mg")
|
|
73
|
+
def diagnostics_mg(service_provider: ServiceProviderDep, keys: Optional[list[str]] = None) -> None:
|
|
74
|
+
"""Get MobileGestalt key values from given list. If empty, return all known."""
|
|
75
|
+
print_json(DiagnosticsService(lockdown=service_provider).mobilegestalt(keys=keys))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from typer_injector import InjectingTyper
|
|
5
|
+
|
|
6
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
7
|
+
from pymobiledevice3.services.diagnostics import DiagnosticsService
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
cli = InjectingTyper(
|
|
13
|
+
name="battery",
|
|
14
|
+
help="Battery options",
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@cli.command("single")
|
|
20
|
+
def diagnostics_battery_single(service_provider: ServiceProviderDep) -> None:
|
|
21
|
+
"""get single snapshot of battery data"""
|
|
22
|
+
raw_info = DiagnosticsService(lockdown=service_provider).get_battery()
|
|
23
|
+
print_json(raw_info)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@cli.command("monitor")
|
|
27
|
+
def diagnostics_battery_monitor(service_provider: ServiceProviderDep) -> None:
|
|
28
|
+
"""monitor battery usage"""
|
|
29
|
+
diagnostics = DiagnosticsService(lockdown=service_provider)
|
|
30
|
+
while True:
|
|
31
|
+
raw_info = diagnostics.get_battery()
|
|
32
|
+
info = {
|
|
33
|
+
"InstantAmperage": raw_info.get("InstantAmperage"),
|
|
34
|
+
"Temperature": raw_info.get("Temperature"),
|
|
35
|
+
"Voltage": raw_info.get("Voltage"),
|
|
36
|
+
"IsCharging": raw_info.get("IsCharging"),
|
|
37
|
+
"CurrentCapacity": raw_info.get("CurrentCapacity"),
|
|
38
|
+
}
|
|
39
|
+
logger.info(info)
|
|
40
|
+
time.sleep(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@cli.command("wifi")
|
|
44
|
+
def diagnostics_wifi(service_provider: ServiceProviderDep) -> None:
|
|
45
|
+
"""Query WiFi info from IORegistry"""
|
|
46
|
+
raw_info = DiagnosticsService(lockdown=service_provider).get_wifi()
|
|
47
|
+
print_json(raw_info)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
|
|
4
|
+
from typer_injector import InjectingTyper
|
|
5
|
+
|
|
6
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
7
|
+
from pymobiledevice3.services.idam import IDAMService
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
cli = InjectingTyper(
|
|
13
|
+
name="idam",
|
|
14
|
+
help=dedent("""\
|
|
15
|
+
Access IDAM (Inter-Device Audio and MIDI) configuration
|
|
16
|
+
|
|
17
|
+
For more info refer to:
|
|
18
|
+
<https://www.youtube.com/watch?v=IXmP938brnc>
|
|
19
|
+
"""),
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cli.command()
|
|
25
|
+
def configuration_inquiry(service_provider: ServiceProviderDep) -> None:
|
|
26
|
+
"""Inquiry IDAM configuration"""
|
|
27
|
+
with IDAMService(service_provider) as idam:
|
|
28
|
+
print_json(idam.configuration_inquiry())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@cli.command()
|
|
32
|
+
def enable(service_provider: ServiceProviderDep) -> None:
|
|
33
|
+
"""Enable IDAM"""
|
|
34
|
+
with IDAMService(service_provider) as idam:
|
|
35
|
+
idam.set_idam_configuration(True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@cli.command()
|
|
39
|
+
def disable(service_provider: ServiceProviderDep) -> None:
|
|
40
|
+
"""Disable IDAM"""
|
|
41
|
+
with IDAMService(service_provider) as idam:
|
|
42
|
+
idam.set_idam_configuration(False)
|
pymobiledevice3/cli/lockdown.py
CHANGED
|
@@ -2,13 +2,13 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
import plistlib
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Annotated, Literal, Optional
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import typer
|
|
8
|
+
from typer_injector import InjectingTyper
|
|
8
9
|
|
|
9
|
-
from pymobiledevice3.cli.cli_common import
|
|
10
|
+
from pymobiledevice3.cli.cli_common import NoAutoPairServiceProviderDep, ServiceProviderDep, print_json, sudo_required
|
|
10
11
|
from pymobiledevice3.cli.remote import tunnel_task
|
|
11
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
12
12
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
13
13
|
from pymobiledevice3.remote.common import TunnelProtocol
|
|
14
14
|
from pymobiledevice3.remote.tunnel_service import CoreDeviceTunnelProxy
|
|
@@ -17,169 +17,174 @@ from pymobiledevice3.services.heartbeat import HeartbeatService
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
cli = InjectingTyper(
|
|
21
|
+
name="lockdown",
|
|
22
|
+
help="Pair/Unpair device or access other lockdown services",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
@cli.
|
|
26
|
-
def
|
|
27
|
-
"""
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@lockdown_group.command('recovery', cls=Command)
|
|
32
|
-
def lockdown_recovery(service_provider: LockdownClient):
|
|
33
|
-
""" enter recovery """
|
|
27
|
+
@cli.command("recovery")
|
|
28
|
+
def lockdown_recovery(service_provider: ServiceProviderDep) -> None:
|
|
29
|
+
"""enter recovery"""
|
|
34
30
|
print_json(service_provider.enter_recovery())
|
|
35
31
|
|
|
36
32
|
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
""" send-receive raw service messages with a given service name"""
|
|
33
|
+
@cli.command("service")
|
|
34
|
+
def lockdown_service(service_provider: ServiceProviderDep, service_name: str) -> None:
|
|
35
|
+
"""send-receive raw service messages with a given service name"""
|
|
41
36
|
service_provider.start_lockdown_service(service_name).shell()
|
|
42
37
|
|
|
43
38
|
|
|
44
|
-
@
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
""" send-receive raw service messages with a given developer service name """
|
|
39
|
+
@cli.command("developer-service")
|
|
40
|
+
def lockdown_developer_service(service_provider: ServiceProviderDep, service_name: str) -> None:
|
|
41
|
+
"""send-receive raw service messages with a given developer service name"""
|
|
48
42
|
service_provider.start_lockdown_developer_service(service_name).shell()
|
|
49
43
|
|
|
50
44
|
|
|
51
|
-
@
|
|
52
|
-
def lockdown_info(service_provider:
|
|
53
|
-
"""
|
|
45
|
+
@cli.command("info")
|
|
46
|
+
def lockdown_info(service_provider: ServiceProviderDep) -> None:
|
|
47
|
+
"""query all lockdown values"""
|
|
54
48
|
print_json(service_provider.all_values)
|
|
55
49
|
|
|
56
50
|
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def lockdown_get(service_provider: LockdownClient, domain, key):
|
|
61
|
-
""" query lockdown values by their domain and key names """
|
|
51
|
+
@cli.command("get")
|
|
52
|
+
def lockdown_get(service_provider: ServiceProviderDep, domain: Optional[str] = None, key: Optional[str] = None) -> None:
|
|
53
|
+
"""query lockdown values by their domain and key names"""
|
|
62
54
|
print_json(service_provider.get_value(domain=domain, key=key))
|
|
63
55
|
|
|
64
56
|
|
|
65
|
-
@
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
@cli.command("set")
|
|
58
|
+
def lockdown_set(
|
|
59
|
+
service_provider: ServiceProviderDep,
|
|
60
|
+
value: str,
|
|
61
|
+
domain: Optional[str] = None,
|
|
62
|
+
key: Optional[str] = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""set a lockdown value using python's eval()"""
|
|
71
65
|
print_json(service_provider.set_value(value=eval(value), domain=domain, key=key))
|
|
72
66
|
|
|
73
67
|
|
|
74
|
-
@
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def lockdown_remove(service_provider: LockdownClient, domain, key):
|
|
78
|
-
""" remove a domain/key pair """
|
|
68
|
+
@cli.command("remove")
|
|
69
|
+
def lockdown_remove(service_provider: ServiceProviderDep, domain: str, key: str) -> None:
|
|
70
|
+
"""remove a domain/key pair"""
|
|
79
71
|
print_json(service_provider.remove_value(domain=domain, key=key))
|
|
80
72
|
|
|
81
73
|
|
|
82
|
-
@
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
""" unpair from connected device """
|
|
74
|
+
@cli.command("unpair")
|
|
75
|
+
def lockdown_unpair(service_provider: NoAutoPairServiceProviderDep, host_id: Optional[str] = None) -> None:
|
|
76
|
+
"""unpair from connected device"""
|
|
86
77
|
service_provider.unpair(host_id=host_id)
|
|
87
78
|
|
|
88
79
|
|
|
89
|
-
@
|
|
90
|
-
def lockdown_pair(service_provider:
|
|
91
|
-
"""
|
|
80
|
+
@cli.command("pair")
|
|
81
|
+
def lockdown_pair(service_provider: NoAutoPairServiceProviderDep) -> None:
|
|
82
|
+
"""pair device"""
|
|
92
83
|
service_provider.pair()
|
|
93
84
|
|
|
94
85
|
|
|
95
|
-
@
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
86
|
+
@cli.command("pair-supervised")
|
|
87
|
+
def lockdown_pair_supervised(
|
|
88
|
+
service_provider: NoAutoPairServiceProviderDep,
|
|
89
|
+
keybag: Annotated[
|
|
90
|
+
Path,
|
|
91
|
+
typer.Argument(file_okay=True, dir_okay=False, exists=True),
|
|
92
|
+
],
|
|
93
|
+
) -> None:
|
|
94
|
+
"""pair supervised device"""
|
|
95
|
+
service_provider.pair_supervised(keybag)
|
|
100
96
|
|
|
101
97
|
|
|
102
|
-
@
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
""" save pair record to specified location """
|
|
98
|
+
@cli.command("save-pair-record")
|
|
99
|
+
def lockdown_save_pair_record(service_provider: NoAutoPairServiceProviderDep, output: Path) -> None:
|
|
100
|
+
"""save pair record to specified location"""
|
|
106
101
|
if service_provider.pair_record is None:
|
|
107
|
-
logger.error(
|
|
102
|
+
logger.error("no pairing record was found")
|
|
108
103
|
return
|
|
109
|
-
plistlib.
|
|
104
|
+
output.write_bytes(plistlib.dumps(service_provider.pair_record))
|
|
110
105
|
|
|
111
106
|
|
|
112
|
-
@
|
|
113
|
-
def lockdown_date(service_provider:
|
|
114
|
-
"""
|
|
107
|
+
@cli.command("date")
|
|
108
|
+
def lockdown_date(service_provider: ServiceProviderDep) -> None:
|
|
109
|
+
"""get device date"""
|
|
115
110
|
print(service_provider.date)
|
|
116
111
|
|
|
117
112
|
|
|
118
|
-
@
|
|
119
|
-
def lockdown_heartbeat(service_provider:
|
|
120
|
-
"""
|
|
113
|
+
@cli.command("heartbeat")
|
|
114
|
+
def lockdown_heartbeat(service_provider: ServiceProviderDep) -> None:
|
|
115
|
+
"""start heartbeat service"""
|
|
121
116
|
HeartbeatService(service_provider).start()
|
|
122
117
|
|
|
123
118
|
|
|
124
|
-
@
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
""" Get/Set current language settings """
|
|
119
|
+
@cli.command("language")
|
|
120
|
+
def lockdown_language(service_provider: ServiceProviderDep, language: Optional[str] = None) -> None:
|
|
121
|
+
"""Get/Set current language settings"""
|
|
128
122
|
if language is not None:
|
|
129
123
|
service_provider.set_language(language)
|
|
130
124
|
print_json(service_provider.language)
|
|
131
125
|
|
|
132
126
|
|
|
133
|
-
@
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
""" Get/Set current language settings """
|
|
127
|
+
@cli.command("locale")
|
|
128
|
+
def lockdown_locale(service_provider: ServiceProviderDep, locale: Optional[str] = None) -> None:
|
|
129
|
+
"""Get/Set current language settings"""
|
|
137
130
|
if locale is not None:
|
|
138
131
|
service_provider.set_locale(locale)
|
|
139
132
|
print_json(service_provider.locale)
|
|
140
133
|
|
|
141
134
|
|
|
142
|
-
@
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
""" get/set current device name """
|
|
135
|
+
@cli.command("device-name")
|
|
136
|
+
def lockdown_device_name(service_provider: ServiceProviderDep, new_name: Optional[str] = None) -> None:
|
|
137
|
+
"""get/set current device name"""
|
|
146
138
|
if new_name:
|
|
147
|
-
service_provider.set_value(new_name, key=
|
|
139
|
+
service_provider.set_value(new_name, key="DeviceName")
|
|
148
140
|
else:
|
|
149
|
-
print(f
|
|
141
|
+
print(f"{service_provider.get_value(key='DeviceName')}")
|
|
150
142
|
|
|
151
143
|
|
|
152
|
-
@
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
@cli.command("wifi-connections")
|
|
145
|
+
def lockdown_wifi_connections(
|
|
146
|
+
service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
|
|
147
|
+
) -> None:
|
|
148
|
+
"""get/set wifi connections state"""
|
|
156
149
|
if not state:
|
|
157
150
|
# show current state
|
|
158
|
-
print_json(service_provider.get_value(domain=
|
|
151
|
+
print_json(service_provider.get_value(domain="com.apple.mobile.wireless_lockdown"))
|
|
159
152
|
else:
|
|
160
153
|
# enable/disable
|
|
161
|
-
service_provider.enable_wifi_connections = state ==
|
|
154
|
+
service_provider.enable_wifi_connections = state == "on"
|
|
162
155
|
|
|
163
156
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
service = CoreDeviceTunnelProxy(service_provider)
|
|
172
|
-
asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None, protocol=TunnelProtocol.TCP), debug=True)
|
|
157
|
+
async def async_cli_start_tunnel(service_provider: LockdownServiceProvider, script_mode: bool) -> None:
|
|
158
|
+
await tunnel_task(
|
|
159
|
+
await CoreDeviceTunnelProxy.create(service_provider),
|
|
160
|
+
script_mode=script_mode,
|
|
161
|
+
secrets=None,
|
|
162
|
+
protocol=TunnelProtocol.TCP,
|
|
163
|
+
)
|
|
173
164
|
|
|
174
165
|
|
|
175
|
-
@
|
|
176
|
-
@
|
|
177
|
-
def
|
|
178
|
-
|
|
166
|
+
@cli.command("start-tunnel")
|
|
167
|
+
@sudo_required
|
|
168
|
+
def cli_start_tunnel(
|
|
169
|
+
service_provider: ServiceProviderDep,
|
|
170
|
+
script_mode: Annotated[
|
|
171
|
+
bool,
|
|
172
|
+
typer.Option(help="Show only HOST and port number to allow easy parsing from external shell scripts"),
|
|
173
|
+
] = False,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""start tunnel"""
|
|
176
|
+
asyncio.run(async_cli_start_tunnel(service_provider, script_mode), debug=True)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@cli.command("assistive-touch")
|
|
180
|
+
def lockdown_assistive_touch(
|
|
181
|
+
service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
|
|
182
|
+
) -> None:
|
|
183
|
+
"""get/set assistive touch icon state (visibility)"""
|
|
179
184
|
if not state:
|
|
180
|
-
key =
|
|
181
|
-
accessibility_values = service_provider.get_value(
|
|
185
|
+
key = "AssistiveTouchEnabledByiTunes"
|
|
186
|
+
accessibility_values = service_provider.get_value("com.apple.Accessibility")
|
|
182
187
|
print_json({key: bool(accessibility_values[key])})
|
|
183
188
|
else:
|
|
184
189
|
# enable/disable
|
|
185
|
-
service_provider.assistive_touch = state ==
|
|
190
|
+
service_provider.assistive_touch = state == "on"
|