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
pymobiledevice3/cli/restore.py
CHANGED
|
@@ -4,18 +4,20 @@ import logging
|
|
|
4
4
|
import plistlib
|
|
5
5
|
import tempfile
|
|
6
6
|
import traceback
|
|
7
|
-
from collections.abc import
|
|
7
|
+
from collections.abc import Iterator
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import IO,
|
|
9
|
+
from typing import IO, Annotated, Optional
|
|
10
10
|
from zipfile import ZipFile
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
import IPython
|
|
14
14
|
import requests
|
|
15
|
+
import typer
|
|
15
16
|
from pygments import formatters, highlight, lexers
|
|
17
|
+
from typer_injector import Depends, InjectingTyper
|
|
16
18
|
|
|
17
19
|
from pymobiledevice3 import usbmux
|
|
18
|
-
from pymobiledevice3.cli.cli_common import is_invoked_for_completion, print_json, prompt_selection
|
|
20
|
+
from pymobiledevice3.cli.cli_common import is_invoked_for_completion, print_json, prompt_selection
|
|
19
21
|
from pymobiledevice3.exceptions import ConnectionFailedError, ConnectionFailedToUsbmuxdError, IncorrectModeError
|
|
20
22
|
from pymobiledevice3.irecv import IRecv
|
|
21
23
|
from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
|
|
@@ -25,55 +27,66 @@ from pymobiledevice3.restore.restore import Restore
|
|
|
25
27
|
from pymobiledevice3.services.diagnostics import DiagnosticsService
|
|
26
28
|
from pymobiledevice3.utils import file_download
|
|
27
29
|
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
28
33
|
SHELL_USAGE = """
|
|
29
34
|
# use `irecv` variable to access Restore mode API
|
|
30
35
|
# for example:
|
|
31
36
|
print(irecv.getenv('build-version'))
|
|
32
37
|
"""
|
|
33
|
-
|
|
34
|
-
logger = logging.getLogger(__name__)
|
|
35
38
|
IPSWME_API = "https://api.ipsw.me/v4/device/"
|
|
36
39
|
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
41
|
+
cli = InjectingTyper(
|
|
42
|
+
name="restore",
|
|
43
|
+
help="Restore/erase IPSWs, fetch blobs, and manage devices in Recovery/DFU.",
|
|
44
|
+
no_args_is_help=True,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def device_dependency(
|
|
49
|
+
ecid: Annotated[
|
|
50
|
+
Optional[str],
|
|
51
|
+
typer.Option(
|
|
52
|
+
help="Target device ECID; defaults to the first connected USB device or waits for Recovery/DFU.",
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
) -> Optional[Device]:
|
|
56
|
+
if is_invoked_for_completion():
|
|
57
|
+
# prevent lockdown connection establishment when in autocomplete mode
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
logger.debug("searching among connected devices via lockdownd")
|
|
61
|
+
devices = [dev for dev in usbmux.list_devices() if dev.connection_type == "USB"]
|
|
62
|
+
if len(devices) > 1:
|
|
63
|
+
raise click.ClickException("Multiple device detected")
|
|
64
|
+
try:
|
|
65
|
+
for device in devices:
|
|
66
|
+
try:
|
|
67
|
+
lockdown = create_using_usbmux(serial=device.serial, connection_type="USB")
|
|
68
|
+
except (ConnectionFailedError, IncorrectModeError):
|
|
69
|
+
continue
|
|
70
|
+
if (ecid is None) or (lockdown.ecid == ecid):
|
|
71
|
+
logger.debug("found device")
|
|
72
|
+
return Device(lockdown=lockdown)
|
|
73
|
+
else:
|
|
74
|
+
continue
|
|
75
|
+
except ConnectionFailedToUsbmuxdError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
logger.debug("waiting for device to be available in Recovery mode")
|
|
79
|
+
return Device(irecv=IRecv(ecid=ecid))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
DeviceDep = Annotated[
|
|
83
|
+
Device,
|
|
84
|
+
Depends(device_dependency),
|
|
85
|
+
]
|
|
73
86
|
|
|
74
87
|
|
|
75
88
|
@contextlib.contextmanager
|
|
76
|
-
def tempzip_download_ctx(url: str) ->
|
|
89
|
+
def tempzip_download_ctx(url: str) -> Iterator[ZipFile]:
|
|
77
90
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
78
91
|
tmpzip = Path(tmpdir) / url.split("/")[-1]
|
|
79
92
|
file_download(url, tmpzip)
|
|
@@ -81,33 +94,52 @@ def tempzip_download_ctx(url: str) -> Generator[ZipFile, None, None]:
|
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
@contextlib.contextmanager
|
|
84
|
-
def zipfile_ctx(path: str) ->
|
|
97
|
+
def zipfile_ctx(path: str) -> Iterator[ZipFile]:
|
|
85
98
|
yield ZipFile(path)
|
|
86
99
|
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
def ipsw_ctx_dependency(
|
|
102
|
+
device: DeviceDep,
|
|
103
|
+
ipsw: Annotated[
|
|
104
|
+
Optional[str],
|
|
105
|
+
typer.Option(
|
|
106
|
+
"--ipsw",
|
|
107
|
+
"-i",
|
|
108
|
+
help="Path or URL to an IPSW. If omitted, choose a signed build interactively.",
|
|
109
|
+
),
|
|
110
|
+
] = None,
|
|
111
|
+
) -> contextlib.AbstractContextManager[ZipFile]:
|
|
112
|
+
if ipsw and not ipsw.startswith(("http://", "https://")):
|
|
113
|
+
return zipfile_ctx(ipsw)
|
|
114
|
+
|
|
115
|
+
url = ipsw
|
|
116
|
+
if url is None:
|
|
117
|
+
url = query_ipswme(device.product_type)
|
|
118
|
+
return tempzip_download_ctx(url)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
IPSWCtxDep = Annotated[
|
|
122
|
+
contextlib.AbstractContextManager[ZipFile],
|
|
123
|
+
Depends(ipsw_ctx_dependency),
|
|
124
|
+
]
|
|
95
125
|
|
|
96
|
-
@staticmethod
|
|
97
|
-
def ipsw_ctx(ctx, param, value) -> Generator[ZipFile, None, None]:
|
|
98
|
-
if value and not value.startswith(("http://", "https://")):
|
|
99
|
-
return zipfile_ctx(value)
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
def tss_dependency(
|
|
128
|
+
tss: Annotated[
|
|
129
|
+
Optional[Path],
|
|
130
|
+
typer.Option(help="Path to SHSH blob plist to use for signing requests."),
|
|
131
|
+
] = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
if tss is None:
|
|
134
|
+
return
|
|
135
|
+
with tss.open("rb") as tss_file:
|
|
136
|
+
return plistlib.load(tss_file)
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
138
|
+
|
|
139
|
+
TSSDep = Annotated[
|
|
140
|
+
Optional[dict],
|
|
141
|
+
Depends(tss_dependency),
|
|
142
|
+
]
|
|
111
143
|
|
|
112
144
|
|
|
113
145
|
def query_ipswme(identifier: str) -> str:
|
|
@@ -118,15 +150,9 @@ def query_ipswme(identifier: str) -> str:
|
|
|
118
150
|
return firmwares[idx]["url"]
|
|
119
151
|
|
|
120
152
|
|
|
121
|
-
async def restore_update_task(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if isinstance(device, LockdownClient):
|
|
125
|
-
lockdown = device
|
|
126
|
-
elif isinstance(device, IRecv):
|
|
127
|
-
irecv = device
|
|
128
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
129
|
-
|
|
153
|
+
async def restore_update_task(
|
|
154
|
+
device: Device, ipsw: ZipFile, tss: Optional[dict], erase: bool, ignore_fdr: bool
|
|
155
|
+
) -> None:
|
|
130
156
|
behavior = Behavior.Update
|
|
131
157
|
if erase:
|
|
132
158
|
behavior = Behavior.Erase
|
|
@@ -139,19 +165,8 @@ async def restore_update_task(device: Device, ipsw: ZipFile, tss: Optional[IO],
|
|
|
139
165
|
raise
|
|
140
166
|
|
|
141
167
|
|
|
142
|
-
@
|
|
143
|
-
def
|
|
144
|
-
pass
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@cli.group()
|
|
148
|
-
def restore() -> None:
|
|
149
|
-
"""Restore an IPSW or access device in recovery mode"""
|
|
150
|
-
pass
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@restore.command("shell", cls=Command)
|
|
154
|
-
def restore_shell(device):
|
|
168
|
+
@cli.command("shell")
|
|
169
|
+
def restore_shell(device: DeviceDep) -> None:
|
|
155
170
|
"""create an IPython shell for interacting with iBoot"""
|
|
156
171
|
IPython.embed(
|
|
157
172
|
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
@@ -161,40 +176,34 @@ def restore_shell(device):
|
|
|
161
176
|
)
|
|
162
177
|
|
|
163
178
|
|
|
164
|
-
@
|
|
165
|
-
def restore_enter(device):
|
|
179
|
+
@cli.command("enter")
|
|
180
|
+
def restore_enter(device: DeviceDep) -> None:
|
|
166
181
|
"""enter Recovery mode"""
|
|
167
182
|
if isinstance(device, LockdownClient):
|
|
168
183
|
device.enter_recovery()
|
|
169
184
|
|
|
170
185
|
|
|
171
|
-
@
|
|
172
|
-
def restore_exit():
|
|
186
|
+
@cli.command("exit")
|
|
187
|
+
def restore_exit() -> None:
|
|
173
188
|
"""exit Recovery mode"""
|
|
174
189
|
irecv = IRecv()
|
|
175
190
|
irecv.set_autoboot(True)
|
|
176
191
|
irecv.reboot()
|
|
177
192
|
|
|
178
193
|
|
|
179
|
-
@
|
|
180
|
-
def restore_restart(device):
|
|
194
|
+
@cli.command("restart")
|
|
195
|
+
def restore_restart(device: DeviceDep) -> None:
|
|
181
196
|
"""restarts device"""
|
|
182
|
-
if
|
|
183
|
-
with DiagnosticsService(device) as diagnostics:
|
|
197
|
+
if device.is_lockdown:
|
|
198
|
+
with DiagnosticsService(device.lockdown) as diagnostics:
|
|
184
199
|
diagnostics.restart()
|
|
185
200
|
else:
|
|
186
|
-
device.reboot()
|
|
201
|
+
device.irecv.reboot()
|
|
187
202
|
|
|
188
203
|
|
|
189
|
-
async def restore_tss_task(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if isinstance(device, LockdownClient):
|
|
193
|
-
lockdown = device
|
|
194
|
-
elif isinstance(device, IRecv):
|
|
195
|
-
irecv = device
|
|
196
|
-
|
|
197
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
204
|
+
async def restore_tss_task(
|
|
205
|
+
device: Device, ipsw_ctx: contextlib.AbstractContextManager[ZipFile], out: Optional[IO]
|
|
206
|
+
) -> None:
|
|
198
207
|
with ipsw_ctx as ipsw:
|
|
199
208
|
tss = await Recovery(ipsw, device).fetch_tss_record()
|
|
200
209
|
if out:
|
|
@@ -202,46 +211,42 @@ async def restore_tss_task(device: Device, ipsw_ctx: Generator, tss: IO, out: Op
|
|
|
202
211
|
print_json(tss)
|
|
203
212
|
|
|
204
213
|
|
|
205
|
-
@
|
|
206
|
-
|
|
207
|
-
def restore_tss(device: Device, ipsw_ctx: Generator, tss: IO, out: Optional[IO]) -> None:
|
|
214
|
+
@cli.command("tss")
|
|
215
|
+
def restore_tss(device: DeviceDep, ipsw_ctx: IPSWCtxDep, out: Optional[Path] = None) -> None:
|
|
208
216
|
"""query SHSH blobs"""
|
|
209
|
-
|
|
217
|
+
with out.open("wb") if out else contextlib.nullcontext() as out_file:
|
|
218
|
+
asyncio.run(restore_tss_task(device, ipsw_ctx, out_file), debug=True)
|
|
210
219
|
|
|
211
220
|
|
|
212
|
-
async def restore_ramdisk_task(device: Device, ipsw_ctx:
|
|
213
|
-
lockdown = None
|
|
214
|
-
irecv = None
|
|
215
|
-
if isinstance(device, LockdownClient):
|
|
216
|
-
lockdown = device
|
|
217
|
-
elif isinstance(device, IRecv):
|
|
218
|
-
irecv = device
|
|
219
|
-
device = Device(lockdown=lockdown, irecv=irecv)
|
|
220
|
-
|
|
221
|
+
async def restore_ramdisk_task(device: Device, ipsw_ctx: contextlib.AbstractContextManager[ZipFile]) -> None:
|
|
221
222
|
with ipsw_ctx as ipsw:
|
|
222
223
|
await Recovery(ipsw, device).boot_ramdisk()
|
|
223
224
|
|
|
224
225
|
|
|
225
|
-
@
|
|
226
|
-
def restore_ramdisk(device:
|
|
226
|
+
@cli.command("ramdisk")
|
|
227
|
+
def restore_ramdisk(device: DeviceDep, ipsw_ctx: IPSWCtxDep) -> None:
|
|
227
228
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
ipsw can be either a filename or an url
|
|
229
|
+
Boot only the update ramdisk without performing a restore (IPSW path or URL accepted).
|
|
231
230
|
"""
|
|
232
231
|
asyncio.run(restore_ramdisk_task(device, ipsw_ctx), debug=True)
|
|
233
232
|
|
|
234
233
|
|
|
235
|
-
@
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
@cli.command("update")
|
|
235
|
+
def restore_update(
|
|
236
|
+
device: DeviceDep,
|
|
237
|
+
ipsw_ctx: IPSWCtxDep,
|
|
238
|
+
tss: TSSDep,
|
|
239
|
+
erase: Annotated[
|
|
240
|
+
bool,
|
|
241
|
+
typer.Option(help="Erase and restore (factory reset) instead of updating in place."),
|
|
242
|
+
] = False,
|
|
243
|
+
ignore_fdr: Annotated[
|
|
244
|
+
bool,
|
|
245
|
+
typer.Option(help="Connect to the FDR service only (debug mode; no traffic proxying)."),
|
|
246
|
+
] = False,
|
|
247
|
+
) -> None:
|
|
241
248
|
"""
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ipsw can be either a filename or an url
|
|
249
|
+
Update or restore the device using an IPSW (local path or URL).
|
|
245
250
|
"""
|
|
246
251
|
with ipsw_ctx as ipsw:
|
|
247
252
|
asyncio.run(restore_update_task(device, ipsw, tss, erase, ignore_fdr), debug=True)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Annotated, Literal
|
|
2
3
|
|
|
3
|
-
import click
|
|
4
4
|
import IPython
|
|
5
|
+
import typer
|
|
6
|
+
from typer_injector import InjectingTyper
|
|
5
7
|
|
|
6
|
-
from pymobiledevice3.cli.cli_common import
|
|
7
|
-
from pymobiledevice3.lockdown import LockdownClient
|
|
8
|
-
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
8
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
|
|
9
9
|
from pymobiledevice3.services.springboard import SpringBoardServicesService
|
|
10
10
|
|
|
11
11
|
SHELL_USAGE = """
|
|
@@ -13,32 +13,28 @@ Use `service` to access the service features
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
cli = InjectingTyper(
|
|
17
|
+
name="springboard",
|
|
18
|
+
help="Interact with SpringBoard UI (icons, wallpapers, orientation, shell).",
|
|
19
|
+
no_args_is_help=True,
|
|
20
|
+
)
|
|
21
|
+
state_cli = InjectingTyper(
|
|
22
|
+
name="state",
|
|
23
|
+
help="Icon state operations.",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
cli.add_typer(state_cli)
|
|
19
27
|
|
|
20
28
|
|
|
21
|
-
@
|
|
22
|
-
def
|
|
23
|
-
"""
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@springboard.group()
|
|
28
|
-
def state():
|
|
29
|
-
"""icons state options"""
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@state.command("get", cls=Command)
|
|
34
|
-
def state_get(service_provider: LockdownClient):
|
|
35
|
-
"""get icon state"""
|
|
29
|
+
@state_cli.command("get")
|
|
30
|
+
def state_get(service_provider: ServiceProviderDep) -> None:
|
|
31
|
+
"""Fetch the current icon layout/state."""
|
|
36
32
|
print_json(SpringBoardServicesService(lockdown=service_provider).get_icon_state())
|
|
37
33
|
|
|
38
34
|
|
|
39
|
-
@
|
|
40
|
-
def springboard_shell(service_provider:
|
|
41
|
-
"""
|
|
35
|
+
@cli.command("shell")
|
|
36
|
+
def springboard_shell(service_provider: ServiceProviderDep) -> None:
|
|
37
|
+
"""Open an IPython shell bound to SpringBoardServicesService."""
|
|
42
38
|
service = SpringBoardServicesService(lockdown=service_provider)
|
|
43
39
|
IPython.embed(
|
|
44
40
|
header=SHELL_USAGE,
|
|
@@ -48,43 +44,47 @@ def springboard_shell(service_provider: LockdownClient):
|
|
|
48
44
|
)
|
|
49
45
|
|
|
50
46
|
|
|
51
|
-
@
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"""get application's icon"""
|
|
56
|
-
out.write(SpringBoardServicesService(lockdown=service_provider).get_icon_pngdata(bundle_id))
|
|
47
|
+
@cli.command("icon")
|
|
48
|
+
def springboard_icon(service_provider: ServiceProviderDep, bundle_id: str, out: Path) -> None:
|
|
49
|
+
"""Save an app's icon PNG to the given path."""
|
|
50
|
+
out.write_bytes(SpringBoardServicesService(lockdown=service_provider).get_icon_pngdata(bundle_id))
|
|
57
51
|
|
|
58
52
|
|
|
59
|
-
@
|
|
60
|
-
def springboard_orientation(service_provider:
|
|
61
|
-
"""
|
|
53
|
+
@cli.command("orientation")
|
|
54
|
+
def springboard_orientation(service_provider: ServiceProviderDep) -> None:
|
|
55
|
+
"""Print current screen orientation."""
|
|
62
56
|
print(SpringBoardServicesService(lockdown=service_provider).get_interface_orientation())
|
|
63
57
|
|
|
64
58
|
|
|
65
|
-
@
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
out.write(SpringBoardServicesService(lockdown=service_provider).get_wallpaper_pngdata())
|
|
59
|
+
@cli.command("wallpaper-home-screen")
|
|
60
|
+
def springboard_wallpaper_home_screen(service_provider: ServiceProviderDep, out: Path) -> None:
|
|
61
|
+
"""Save the homescreen wallpaper PNG to the given path."""
|
|
62
|
+
out.write_bytes(SpringBoardServicesService(lockdown=service_provider).get_wallpaper_pngdata())
|
|
70
63
|
|
|
71
64
|
|
|
72
|
-
@
|
|
73
|
-
@click.argument("wallpaper-name", type=click.Choice(["homescreen", "lockscreen"]))
|
|
74
|
-
@click.argument("out", type=click.File("wb"))
|
|
75
|
-
@click.option("-r", "--reload", is_flag=True, help="reload icon state before fetching image")
|
|
65
|
+
@cli.command("wallpaper-preview-image")
|
|
76
66
|
def springboard_wallpaper_preview_image(
|
|
77
|
-
service_provider:
|
|
67
|
+
service_provider: ServiceProviderDep,
|
|
68
|
+
wallpaper_name: Literal["homescreen", "lockscreen"],
|
|
69
|
+
out: Path,
|
|
70
|
+
reload: Annotated[
|
|
71
|
+
bool,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--reload",
|
|
74
|
+
"-r",
|
|
75
|
+
help="reload icon state before fetching image",
|
|
76
|
+
),
|
|
77
|
+
] = False,
|
|
78
78
|
) -> None:
|
|
79
|
-
"""
|
|
79
|
+
"""Save the preview image for the homescreen or lockscreen wallpaper (optionally reload state first)."""
|
|
80
80
|
with SpringBoardServicesService(lockdown=service_provider) as springboard_service:
|
|
81
81
|
if reload:
|
|
82
82
|
springboard_service.reload_icon_state()
|
|
83
|
-
out.
|
|
83
|
+
out.write_bytes(springboard_service.get_wallpaper_preview_image(wallpaper_name))
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
@
|
|
87
|
-
def springboard_homescreen_icon_metrics(service_provider:
|
|
88
|
-
"""
|
|
86
|
+
@cli.command("homescreen-icon-metrics")
|
|
87
|
+
def springboard_homescreen_icon_metrics(service_provider: ServiceProviderDep) -> None:
|
|
88
|
+
"""Print homescreen icon spacing/metrics."""
|
|
89
89
|
with SpringBoardServicesService(lockdown=service_provider) as springboard_service:
|
|
90
90
|
print_json(springboard_service.get_homescreen_icon_metrics())
|