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
|
@@ -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,131 +17,123 @@ from pymobiledevice3.services.heartbeat import HeartbeatService
|
|
|
17
17
|
logger = logging.getLogger(__name__)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@cli.group("lockdown")
|
|
26
|
-
def lockdown_group() -> None:
|
|
27
|
-
"""Pair/Unpair device or access other lockdown services"""
|
|
28
|
-
pass
|
|
20
|
+
cli = InjectingTyper(
|
|
21
|
+
name="lockdown",
|
|
22
|
+
help="Pair/Unpair device or access other lockdown services",
|
|
23
|
+
no_args_is_help=True,
|
|
24
|
+
)
|
|
29
25
|
|
|
30
26
|
|
|
31
|
-
@
|
|
32
|
-
def lockdown_recovery(service_provider:
|
|
27
|
+
@cli.command("recovery")
|
|
28
|
+
def lockdown_recovery(service_provider: ServiceProviderDep) -> None:
|
|
33
29
|
"""enter recovery"""
|
|
34
30
|
print_json(service_provider.enter_recovery())
|
|
35
31
|
|
|
36
32
|
|
|
37
|
-
@
|
|
38
|
-
|
|
39
|
-
def lockdown_service(service_provider: LockdownServiceProvider, service_name):
|
|
33
|
+
@cli.command("service")
|
|
34
|
+
def lockdown_service(service_provider: ServiceProviderDep, service_name: str) -> None:
|
|
40
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
|
-
def lockdown_developer_service(service_provider: LockdownServiceProvider, service_name):
|
|
39
|
+
@cli.command("developer-service")
|
|
40
|
+
def lockdown_developer_service(service_provider: ServiceProviderDep, service_name: str) -> None:
|
|
47
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:
|
|
45
|
+
@cli.command("info")
|
|
46
|
+
def lockdown_info(service_provider: ServiceProviderDep) -> None:
|
|
53
47
|
"""query all lockdown values"""
|
|
54
48
|
print_json(service_provider.all_values)
|
|
55
49
|
|
|
56
50
|
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
-
@click.argument("key", required=False)
|
|
60
|
-
def lockdown_get(service_provider: LockdownClient, domain, key):
|
|
51
|
+
@cli.command("get")
|
|
52
|
+
def lockdown_get(service_provider: ServiceProviderDep, domain: Optional[str] = None, key: Optional[str] = None) -> None:
|
|
61
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
|
-
|
|
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:
|
|
70
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
|
-
@click.argument("key")
|
|
77
|
-
def lockdown_remove(service_provider: LockdownClient, domain, key):
|
|
68
|
+
@cli.command("remove")
|
|
69
|
+
def lockdown_remove(service_provider: ServiceProviderDep, domain: str, key: str) -> None:
|
|
78
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
|
-
def lockdown_unpair(service_provider: LockdownClient, host_id: Optional[str] = None):
|
|
74
|
+
@cli.command("unpair")
|
|
75
|
+
def lockdown_unpair(service_provider: NoAutoPairServiceProviderDep, host_id: Optional[str] = None) -> None:
|
|
85
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:
|
|
80
|
+
@cli.command("pair")
|
|
81
|
+
def lockdown_pair(service_provider: NoAutoPairServiceProviderDep) -> None:
|
|
91
82
|
"""pair device"""
|
|
92
83
|
service_provider.pair()
|
|
93
84
|
|
|
94
85
|
|
|
95
|
-
@
|
|
96
|
-
|
|
97
|
-
|
|
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:
|
|
98
94
|
"""pair supervised device"""
|
|
99
|
-
service_provider.pair_supervised(
|
|
95
|
+
service_provider.pair_supervised(keybag)
|
|
100
96
|
|
|
101
97
|
|
|
102
|
-
@
|
|
103
|
-
|
|
104
|
-
def lockdown_save_pair_record(service_provider: LockdownClient, output):
|
|
98
|
+
@cli.command("save-pair-record")
|
|
99
|
+
def lockdown_save_pair_record(service_provider: NoAutoPairServiceProviderDep, output: Path) -> None:
|
|
105
100
|
"""save pair record to specified location"""
|
|
106
101
|
if service_provider.pair_record is None:
|
|
107
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:
|
|
107
|
+
@cli.command("date")
|
|
108
|
+
def lockdown_date(service_provider: ServiceProviderDep) -> None:
|
|
114
109
|
"""get device date"""
|
|
115
110
|
print(service_provider.date)
|
|
116
111
|
|
|
117
112
|
|
|
118
|
-
@
|
|
119
|
-
def lockdown_heartbeat(service_provider:
|
|
113
|
+
@cli.command("heartbeat")
|
|
114
|
+
def lockdown_heartbeat(service_provider: ServiceProviderDep) -> None:
|
|
120
115
|
"""start heartbeat service"""
|
|
121
116
|
HeartbeatService(service_provider).start()
|
|
122
117
|
|
|
123
118
|
|
|
124
|
-
@
|
|
125
|
-
|
|
126
|
-
def lockdown_language(service_provider: LockdownClient, language: Optional[str]) -> None:
|
|
119
|
+
@cli.command("language")
|
|
120
|
+
def lockdown_language(service_provider: ServiceProviderDep, language: Optional[str] = None) -> None:
|
|
127
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
|
-
def lockdown_locale(service_provider: LockdownClient, locale: Optional[str]) -> None:
|
|
127
|
+
@cli.command("locale")
|
|
128
|
+
def lockdown_locale(service_provider: ServiceProviderDep, locale: Optional[str] = None) -> None:
|
|
136
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
|
-
def lockdown_device_name(service_provider: LockdownClient, new_name):
|
|
135
|
+
@cli.command("device-name")
|
|
136
|
+
def lockdown_device_name(service_provider: ServiceProviderDep, new_name: Optional[str] = None) -> None:
|
|
145
137
|
"""get/set current device name"""
|
|
146
138
|
if new_name:
|
|
147
139
|
service_provider.set_value(new_name, key="DeviceName")
|
|
@@ -149,9 +141,10 @@ def lockdown_device_name(service_provider: LockdownClient, new_name):
|
|
|
149
141
|
print(f"{service_provider.get_value(key='DeviceName')}")
|
|
150
142
|
|
|
151
143
|
|
|
152
|
-
@
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
@cli.command("wifi-connections")
|
|
145
|
+
def lockdown_wifi_connections(
|
|
146
|
+
service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
|
|
147
|
+
) -> None:
|
|
155
148
|
"""get/set wifi connections state"""
|
|
156
149
|
if not state:
|
|
157
150
|
# show current state
|
|
@@ -170,21 +163,23 @@ async def async_cli_start_tunnel(service_provider: LockdownServiceProvider, scri
|
|
|
170
163
|
)
|
|
171
164
|
|
|
172
165
|
|
|
173
|
-
@
|
|
174
|
-
@click.option(
|
|
175
|
-
"--script-mode",
|
|
176
|
-
is_flag=True,
|
|
177
|
-
help="Show only HOST and port number to allow easy parsing from external shell scripts",
|
|
178
|
-
)
|
|
166
|
+
@cli.command("start-tunnel")
|
|
179
167
|
@sudo_required
|
|
180
|
-
def cli_start_tunnel(
|
|
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:
|
|
181
175
|
"""start tunnel"""
|
|
182
176
|
asyncio.run(async_cli_start_tunnel(service_provider, script_mode), debug=True)
|
|
183
177
|
|
|
184
178
|
|
|
185
|
-
@
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
@cli.command("assistive-touch")
|
|
180
|
+
def lockdown_assistive_touch(
|
|
181
|
+
service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
|
|
182
|
+
) -> None:
|
|
188
183
|
"""get/set assistive touch icon state (visibility)"""
|
|
189
184
|
if not state:
|
|
190
185
|
key = "AssistiveTouchEnabledByiTunes"
|