uiautodev 0.3.3__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.

Potentially problematic release.


This version of uiautodev might be problematic. Click here for more details.

@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import json as sysjson
5
+ import platform
6
+ import re
7
+ import socket
8
+ import sys
9
+ import typing
10
+ import uuid
11
+ from http.client import HTTPConnection, HTTPResponse
12
+ from typing import Optional, TypeVar, Union
13
+
14
+ from pydantic import BaseModel
15
+ from pygments import formatters, highlight, lexers
16
+
17
+ from uiautodev.exceptions import RequestError
18
+ from uiautodev.model import Node
19
+
20
+
21
+ def is_output_terminal() -> bool:
22
+ """
23
+ Check if the standard output is attached to a terminal.
24
+ """
25
+ return sys.stdout.isatty()
26
+
27
+
28
+ def enable_windows_ansi_support():
29
+ if platform.system().lower() == "windows" and is_output_terminal():
30
+ import ctypes
31
+
32
+ kernel32 = ctypes.windll.kernel32
33
+ kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
34
+
35
+
36
+ def default_json_encoder(obj):
37
+ if isinstance(obj, bytes):
38
+ return f'<{obj.hex()}>'
39
+ if isinstance(obj, datetime.datetime):
40
+ return str(obj)
41
+ if isinstance(obj, uuid.UUID):
42
+ return str(obj)
43
+ if isinstance(obj, BaseModel):
44
+ return obj.model_dump()
45
+ raise TypeError()
46
+
47
+
48
+ def print_json(buf, colored=None, default=default_json_encoder):
49
+ """ copy from pymobiledevice3 """
50
+ formatted_json = sysjson.dumps(buf, sort_keys=True, indent=4, default=default)
51
+ if colored is None:
52
+ if is_output_terminal():
53
+ colored = True
54
+ enable_windows_ansi_support()
55
+ else:
56
+ colored = False
57
+
58
+ if colored:
59
+ colorful_json = highlight(formatted_json, lexers.JsonLexer(),
60
+ formatters.TerminalTrueColorFormatter(style='stata-dark'))
61
+ print(colorful_json)
62
+ else:
63
+ print(formatted_json)
64
+
65
+
66
+ _T = TypeVar("_T")
67
+
68
+ def convert_to_type(value: str, _type: _T) -> _T:
69
+ """ usage example:
70
+ convert_to_type("123", int)
71
+ """
72
+ if _type in (int, float, str):
73
+ return _type(value)
74
+ if _type == bool:
75
+ return value.lower() in ("true", "1")
76
+ if _type == Union[int, float]:
77
+ return float(value) if "." in value else int(value)
78
+ if _type == re.Pattern:
79
+ return re.compile(value)
80
+ raise NotImplementedError(f"convert {value} to {_type}")
81
+
82
+
83
+ def convert_params_to_model(params: list[str], model: BaseModel) -> BaseModel:
84
+ """ used in cli.py """
85
+ assert len(params) > 0
86
+ if len(params) == 1:
87
+ try:
88
+ return model.model_validate_json(params)
89
+ except Exception as e:
90
+ print("module_parse_error", e)
91
+
92
+ value = {}
93
+ type_hints = typing.get_type_hints(model)
94
+ for p in params:
95
+ if "=" not in p:
96
+ _type = type_hints.get(p)
97
+ if _type == bool:
98
+ value[p] = True
99
+ continue
100
+ elif _type is None:
101
+ print(f"unknown key: {p}")
102
+ continue
103
+ raise ValueError(f"missing value for {p}")
104
+ k, v = p.split("=", 1)
105
+ _type = type_hints.get(k)
106
+ if _type is None:
107
+ print(f"unknown key: {k}")
108
+ continue
109
+ value[k] = convert_to_type(v, _type)
110
+ return model.model_validate(value)
111
+
112
+
113
+ class SocketHTTPConnection(HTTPConnection):
114
+ def __init__(self, conn: socket.socket, timeout: float):
115
+ super().__init__("localhost", timeout=timeout)
116
+ self.__conn = conn
117
+
118
+ def connect(self):
119
+ self.sock = self.__conn
120
+
121
+ def __enter__(self) -> HTTPConnection:
122
+ return self
123
+
124
+ def __exit__(self, exc_type, exc_value, traceback):
125
+ self.close()
126
+
127
+
128
+ class MySocketHTTPConnection(SocketHTTPConnection):
129
+ def connect(self):
130
+ super().connect()
131
+ self.sock.settimeout(self.timeout)
132
+
133
+
134
+ def fetch_through_socket(sock: socket.socket, path: str, method: str = "GET", json: Optional[dict] = None, timeout: float = 60) -> bytearray:
135
+ """ usage example:
136
+ with socket.create_connection((host, port)) as s:
137
+ request_through_socket(s, "GET", "/")
138
+ """
139
+ conn = MySocketHTTPConnection(sock, timeout)
140
+ try:
141
+ if json is None:
142
+ conn.request(method, path)
143
+ else:
144
+ conn.request(method, path, body=sysjson.dumps(json), headers={"Content-Type": "application/json"})
145
+ response = conn.getresponse()
146
+ if response.getcode() != 200:
147
+ raise RequestError(f"request {method} {path}, status: {response.getcode()}")
148
+ content = bytearray()
149
+ while chunk := response.read(40960):
150
+ content.extend(chunk)
151
+ return content
152
+ finally:
153
+ conn.close()
154
+
155
+
156
+ def node_travel(node: Node, dfs: bool = True):
157
+ """ usage example:
158
+ for n in node_travel(node):
159
+ print(n)
160
+ """
161
+ if not dfs:
162
+ yield node
163
+ for child in node.children:
164
+ yield from node_travel(child, dfs)
165
+ if dfs:
166
+ yield node
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Created on Tue Mar 05 2024 10:18:09 by codeskyblue
5
+
6
+ Copy from https://github.com/doronz88/pymobiledevice3
7
+ """
8
+
9
+
10
+ from uiautodev.exceptions import IOSDriverException
11
+
12
+
13
+ class NotPairedError(IOSDriverException):
14
+ pass
15
+
16
+
17
+
18
+ class MuxException(IOSDriverException):
19
+ pass
20
+
21
+
22
+ class MuxVersionError(MuxException):
23
+ pass
24
+
25
+
26
+ class BadCommandError(MuxException):
27
+ pass
28
+
29
+
30
+ class BadDevError(MuxException):
31
+ pass
32
+
33
+
34
+ class ConnectionFailedError(MuxException):
35
+ pass
36
+
37
+
38
+ class ConnectionFailedToUsbmuxdError(ConnectionFailedError):
39
+ pass
40
+
41
+
42
+ class ArgumentError(IOSDriverException):
43
+ pass
@@ -0,0 +1,485 @@
1
+ """
2
+ Copy from https://github.com/doronz88/pymobiledevice3
3
+
4
+ Add http.client.HTTPConnection
5
+ """
6
+ import abc
7
+ import plistlib
8
+ import socket
9
+ import sys
10
+ import time
11
+ from dataclasses import dataclass
12
+ from http.client import HTTPConnection
13
+ from typing import List, Mapping, Optional
14
+
15
+ from construct import Const, CString, Enum, FixedSized, GreedyBytes, Int16ul, Int32ul, Padding, Prefixed, StreamError, \
16
+ Struct, Switch, this
17
+
18
+ from uiautodev.utils.exceptions import BadCommandError, BadDevError, ConnectionFailedError, \
19
+ ConnectionFailedToUsbmuxdError, MuxException, MuxVersionError, NotPairedError
20
+
21
+ usbmuxd_version = Enum(Int32ul,
22
+ BINARY=0,
23
+ PLIST=1,
24
+ )
25
+
26
+ usbmuxd_result = Enum(Int32ul,
27
+ OK=0,
28
+ BADCOMMAND=1,
29
+ BADDEV=2,
30
+ CONNREFUSED=3,
31
+ BADVERSION=6,
32
+ )
33
+
34
+ usbmuxd_msgtype = Enum(Int32ul,
35
+ RESULT=1,
36
+ CONNECT=2,
37
+ LISTEN=3,
38
+ ADD=4,
39
+ REMOVE=5,
40
+ PAIRED=6,
41
+ PLIST=8,
42
+ )
43
+
44
+ usbmuxd_header = Struct(
45
+ 'version' / usbmuxd_version, # protocol version
46
+ 'message' / usbmuxd_msgtype, # message type
47
+ 'tag' / Int32ul, # responses to this query will echo back this tag
48
+ )
49
+
50
+ usbmuxd_request = Prefixed(Int32ul, Struct(
51
+ 'header' / usbmuxd_header,
52
+ 'data' / Switch(this.header.message, {
53
+ usbmuxd_msgtype.CONNECT: Struct(
54
+ 'device_id' / Int32ul,
55
+ 'port' / Int16ul, # TCP port number
56
+ 'reserved' / Const(0, Int16ul),
57
+ ),
58
+ usbmuxd_msgtype.PLIST: GreedyBytes,
59
+ }),
60
+ ), includelength=True)
61
+
62
+ usbmuxd_device_record = Struct(
63
+ 'device_id' / Int32ul,
64
+ 'product_id' / Int16ul,
65
+ 'serial_number' / FixedSized(256, CString('ascii')),
66
+ Padding(2),
67
+ 'location' / Int32ul
68
+ )
69
+
70
+ usbmuxd_response = Prefixed(Int32ul, Struct(
71
+ 'header' / usbmuxd_header,
72
+ 'data' / Switch(this.header.message, {
73
+ usbmuxd_msgtype.RESULT: Struct(
74
+ 'result' / usbmuxd_result,
75
+ ),
76
+ usbmuxd_msgtype.ADD: usbmuxd_device_record,
77
+ usbmuxd_msgtype.REMOVE: Struct(
78
+ 'device_id' / Int32ul,
79
+ ),
80
+ usbmuxd_msgtype.PLIST: GreedyBytes,
81
+ }),
82
+ ), includelength=True)
83
+
84
+
85
+
86
+
87
+ @dataclass
88
+ class MuxDevice:
89
+ devid: int
90
+ serial: str
91
+ connection_type: str
92
+
93
+ def connect(self, port: int, usbmux_address: Optional[str] = None) -> socket.socket:
94
+ mux = create_mux(usbmux_address=usbmux_address)
95
+ try:
96
+ return mux.connect(self, port)
97
+ except: # noqa: E722
98
+ mux.close()
99
+ raise
100
+
101
+ @property
102
+ def is_usb(self) -> bool:
103
+ return self.connection_type == 'USB'
104
+
105
+ @property
106
+ def is_network(self) -> bool:
107
+ return self.connection_type == 'Network'
108
+
109
+ def matches_udid(self, udid: str) -> bool:
110
+ return self.serial.replace('-', '') == udid.replace('-', '')
111
+
112
+ def make_http_connection(self, port: int) -> HTTPConnection:
113
+ return USBMuxHTTPConnection(self, port)
114
+
115
+
116
+ class SafeStreamSocket:
117
+ """ wrapper to native python socket object to be used with construct as a stream """
118
+
119
+ def __init__(self, address, family):
120
+ self._offset = 0
121
+ self.sock = socket.socket(family, socket.SOCK_STREAM)
122
+ self.sock.connect(address)
123
+
124
+ def send(self, msg: bytes) -> int:
125
+ self._offset += len(msg)
126
+ self.sock.sendall(msg)
127
+ return len(msg)
128
+
129
+ def recv(self, size: int) -> bytes:
130
+ msg = b''
131
+ while len(msg) < size:
132
+ chunk = self.sock.recv(size - len(msg))
133
+ self._offset += len(chunk)
134
+ if not chunk:
135
+ raise MuxException('socket connection broken')
136
+ msg += chunk
137
+ return msg
138
+
139
+ def close(self) -> None:
140
+ self.sock.close()
141
+
142
+ def settimeout(self, interval: float) -> None:
143
+ self.sock.settimeout(interval)
144
+
145
+ def setblocking(self, blocking: bool) -> None:
146
+ self.sock.setblocking(blocking)
147
+
148
+ def tell(self) -> int:
149
+ return self._offset
150
+
151
+ read = recv
152
+ write = send
153
+
154
+
155
+ class MuxConnection:
156
+ # used on Windows
157
+ ITUNES_HOST = ('127.0.0.1', 27015)
158
+
159
+ # used for macOS and Linux
160
+ USBMUXD_PIPE = '/var/run/usbmuxd'
161
+
162
+ @staticmethod
163
+ def create_usbmux_socket(usbmux_address: Optional[str] = None) -> SafeStreamSocket:
164
+ try:
165
+ if usbmux_address is not None:
166
+ if ':' in usbmux_address:
167
+ # assume tcp address
168
+ hostname, port = usbmux_address.split(':')
169
+ port = int(port)
170
+ address = (hostname, port)
171
+ family = socket.AF_INET
172
+ else:
173
+ # assume unix domain address
174
+ address = usbmux_address
175
+ family = socket.AF_UNIX
176
+ else:
177
+ if sys.platform in ['win32', 'cygwin']:
178
+ address = MuxConnection.ITUNES_HOST
179
+ family = socket.AF_INET
180
+ else:
181
+ address = MuxConnection.USBMUXD_PIPE
182
+ family = socket.AF_UNIX
183
+ return SafeStreamSocket(address, family)
184
+ except ConnectionRefusedError:
185
+ raise ConnectionFailedToUsbmuxdError()
186
+
187
+ @staticmethod
188
+ def create(usbmux_address: Optional[str] = None):
189
+ # first attempt to connect with possibly the wrong version header (plist protocol)
190
+ sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
191
+
192
+ message = usbmuxd_request.build({
193
+ 'header': {'version': usbmuxd_version.PLIST, 'message': usbmuxd_msgtype.PLIST, 'tag': 1},
194
+ 'data': plistlib.dumps({'MessageType': 'ReadBUID'})
195
+ })
196
+ sock.send(message)
197
+ response = usbmuxd_response.parse_stream(sock)
198
+
199
+ # if we sent a bad request, we should re-create the socket in the correct version this time
200
+ sock.close()
201
+ sock = MuxConnection.create_usbmux_socket(usbmux_address=usbmux_address)
202
+
203
+ if response.header.version == usbmuxd_version.BINARY:
204
+ return BinaryMuxConnection(sock)
205
+ elif response.header.version == usbmuxd_version.PLIST:
206
+ return PlistMuxConnection(sock)
207
+
208
+ raise MuxVersionError(f'usbmuxd returned unsupported version: {response.version}')
209
+
210
+ def __init__(self, sock: SafeStreamSocket):
211
+ self._sock = sock
212
+
213
+ # after initiating the "Connect" packet, this same socket will be used to transfer data into the service
214
+ # residing inside the target device. when this happens, we can no longer send/receive control commands to
215
+ # usbmux on same socket
216
+ self._connected = False
217
+
218
+ # message sequence number. used when verifying the response matched the request
219
+ self._tag = 1
220
+
221
+ self.devices = []
222
+
223
+ @abc.abstractmethod
224
+ def _connect(self, device_id: int, port: int):
225
+ """ initiate a "Connect" request to target port """
226
+ pass
227
+
228
+ @abc.abstractmethod
229
+ def get_device_list(self, timeout: float = None):
230
+ """
231
+ request an update to current device list
232
+ """
233
+ pass
234
+
235
+ def connect(self, device: MuxDevice, port: int) -> socket.socket:
236
+ """ connect to a relay port on target machine and get a raw python socket object for the connection """
237
+ self._connect(device.devid, socket.htons(port))
238
+ self._connected = True
239
+ return self._sock.sock
240
+
241
+ def close(self):
242
+ """ close current socket """
243
+ self._sock.close()
244
+
245
+ def _assert_not_connected(self):
246
+ """ verify active state is in state for control messages """
247
+ if self._connected:
248
+ raise MuxException('Mux is connected, cannot issue control packets')
249
+
250
+ def _raise_mux_exception(self, result: int, message: str = None):
251
+ exceptions = {
252
+ int(usbmuxd_result.BADCOMMAND): BadCommandError,
253
+ int(usbmuxd_result.BADDEV): BadDevError,
254
+ int(usbmuxd_result.CONNREFUSED): ConnectionFailedError,
255
+ int(usbmuxd_result.BADVERSION): MuxVersionError,
256
+ }
257
+ exception = exceptions.get(result, MuxException)
258
+ raise exception(message)
259
+
260
+ def __enter__(self):
261
+ return self
262
+
263
+ def __exit__(self, exc_type, exc_val, exc_tb):
264
+ self.close()
265
+
266
+
267
+ class BinaryMuxConnection(MuxConnection):
268
+ """ old binary protocol """
269
+
270
+ def __init__(self, sock: SafeStreamSocket):
271
+ super().__init__(sock)
272
+ self._version = usbmuxd_version.BINARY
273
+
274
+ def get_device_list(self, timeout: float = None):
275
+ """ use timeout to wait for the device list to be fully populated """
276
+ self._assert_not_connected()
277
+ end = time.time() + timeout
278
+ self.listen()
279
+ while time.time() < end:
280
+ self._sock.settimeout(end - time.time())
281
+ try:
282
+ self._receive_device_state_update()
283
+ except (BlockingIOError, StreamError):
284
+ continue
285
+ except IOError:
286
+ try:
287
+ self._sock.setblocking(True)
288
+ self.close()
289
+ except OSError:
290
+ pass
291
+ raise MuxException('Exception in listener socket')
292
+
293
+ def listen(self):
294
+ """ start listening for events of attached and detached devices """
295
+ self._send_receive(usbmuxd_msgtype.LISTEN)
296
+
297
+ def _connect(self, device_id: int, port: int):
298
+ self._send({'header': {'version': self._version,
299
+ 'message': usbmuxd_msgtype.CONNECT,
300
+ 'tag': self._tag},
301
+ 'data': {'device_id': device_id, 'port': port},
302
+ })
303
+ response = self._receive()
304
+ if response.header.message != usbmuxd_msgtype.RESULT:
305
+ raise MuxException(f'unexpected message type received: {response}')
306
+
307
+ if response.data.result != usbmuxd_result.OK:
308
+ raise self._raise_mux_exception(int(response.data.result),
309
+ f'failed to connect to device: {device_id} at port: {port}. reason: '
310
+ f'{response.data.result}')
311
+
312
+ def _send(self, data: Mapping):
313
+ self._assert_not_connected()
314
+ self._sock.send(usbmuxd_request.build(data))
315
+ self._tag += 1
316
+
317
+ def _receive(self, expected_tag: int = None):
318
+ self._assert_not_connected()
319
+ response = usbmuxd_response.parse_stream(self._sock)
320
+ if expected_tag and response.header.tag != expected_tag:
321
+ raise MuxException(f'Reply tag mismatch: expected {expected_tag}, got {response.header.tag}')
322
+ return response
323
+
324
+ def _send_receive(self, message_type: int):
325
+ self._send({'header': {'version': self._version, 'message': message_type, 'tag': self._tag},
326
+ 'data': b''})
327
+ response = self._receive(self._tag - 1)
328
+ if response.header.message != usbmuxd_msgtype.RESULT:
329
+ raise MuxException(f'unexpected message type received: {response}')
330
+
331
+ result = response.data.result
332
+ if result != usbmuxd_result.OK:
333
+ raise self._raise_mux_exception(int(result), f'{message_type} failed: error {result}')
334
+
335
+ def _add_device(self, device: MuxDevice):
336
+ self.devices.append(device)
337
+
338
+ def _remove_device(self, device_id: int):
339
+ self.devices = [device for device in self.devices if device.devid != device_id]
340
+
341
+ def _receive_device_state_update(self):
342
+ response = self._receive()
343
+ if response.header.message == usbmuxd_msgtype.ADD:
344
+ # old protocol only supported USB devices
345
+ self._add_device(MuxDevice(response.data.device_id, response.data.serial_number, 'USB'))
346
+ elif response.header.message == usbmuxd_msgtype.REMOVE:
347
+ self._remove_device(response.data.device_id)
348
+ else:
349
+ raise MuxException(f'Invalid packet type received: {response}')
350
+
351
+
352
+ class PlistMuxConnection(BinaryMuxConnection):
353
+ def __init__(self, sock: SafeStreamSocket):
354
+ super().__init__(sock)
355
+ self._version = usbmuxd_version.PLIST
356
+
357
+ def listen(self) -> None:
358
+ self._send_receive({'MessageType': 'Listen'})
359
+
360
+ def get_pair_record(self, serial: str) -> Mapping:
361
+ # serials are saved inside usbmuxd without '-'
362
+ self._send({'MessageType': 'ReadPairRecord', 'PairRecordID': serial})
363
+ response = self._receive(self._tag - 1)
364
+ pair_record = response.get('PairRecordData')
365
+ if pair_record is None:
366
+ raise NotPairedError('device should be paired first')
367
+ return plistlib.loads(pair_record)
368
+
369
+ def get_device_list(self, timeout: float = None) -> None:
370
+ """ get device list synchronously without waiting the timeout """
371
+ self.devices = []
372
+ self._send({'MessageType': 'ListDevices'})
373
+ for response in self._receive(self._tag - 1)['DeviceList']:
374
+ if response['MessageType'] == 'Attached':
375
+ super()._add_device(MuxDevice(response['DeviceID'], response['Properties']['SerialNumber'],
376
+ response['Properties']['ConnectionType']))
377
+ elif response['MessageType'] == 'Detached':
378
+ super()._remove_device(response['DeviceID'])
379
+ else:
380
+ raise MuxException(f'Invalid packet type received: {response}')
381
+
382
+ def get_buid(self) -> str:
383
+ """ get SystemBUID """
384
+ self._send({'MessageType': 'ReadBUID'})
385
+ return self._receive(self._tag - 1)['BUID']
386
+
387
+ def save_pair_record(self, serial: str, device_id: int, record_data: bytes):
388
+ # serials are saved inside usbmuxd without '-'
389
+ self._send_receive({'MessageType': 'SavePairRecord',
390
+ 'PairRecordID': serial,
391
+ 'PairRecordData': record_data,
392
+ 'DeviceID': device_id})
393
+
394
+ def _connect(self, device_id: int, port: int):
395
+ self._send_receive({'MessageType': 'Connect', 'DeviceID': device_id, 'PortNumber': port})
396
+
397
+ def _send(self, data: Mapping):
398
+ request = {'ClientVersionString': 'qt4i-usbmuxd', 'ProgName': 'pymobiledevice3', 'kLibUSBMuxVersion': 3}
399
+ request.update(data)
400
+ super()._send({'header': {'version': self._version,
401
+ 'message': usbmuxd_msgtype.PLIST,
402
+ 'tag': self._tag},
403
+ 'data': plistlib.dumps(request),
404
+ })
405
+
406
+ def _receive(self, expected_tag: int = None) -> Mapping:
407
+ response = super()._receive(expected_tag=expected_tag)
408
+ if response.header.message != usbmuxd_msgtype.PLIST:
409
+ raise MuxException(f'Received non-plist type {response}')
410
+ return plistlib.loads(response.data)
411
+
412
+ def _send_receive(self, data: Mapping):
413
+ self._send(data)
414
+ response = self._receive(self._tag - 1)
415
+ if response['MessageType'] != 'Result':
416
+ raise MuxException(f'got an invalid message: {response}')
417
+ if response['Number'] != 0:
418
+ raise self._raise_mux_exception(response['Number'], f'got an error message: {response}')
419
+
420
+
421
+ def create_mux(usbmux_address: Optional[str] = None) -> MuxConnection:
422
+ return MuxConnection.create(usbmux_address=usbmux_address)
423
+
424
+
425
+ def list_devices(usbmux_address: Optional[str] = None) -> List[MuxDevice]:
426
+ mux = create_mux(usbmux_address=usbmux_address)
427
+ mux.get_device_list(0.1)
428
+ devices = mux.devices
429
+ mux.close()
430
+ return devices
431
+
432
+
433
+ def select_device(udid: str = None, connection_type: str = None, usbmux_address: Optional[str] = None) \
434
+ -> Optional[MuxDevice]:
435
+ """
436
+ select a UsbMux device according to given arguments.
437
+ if more than one device could be selected, always prefer the usb one.
438
+ """
439
+ tmp = None
440
+ for device in list_devices(usbmux_address=usbmux_address):
441
+ if connection_type is not None and device.connection_type != connection_type:
442
+ # if a specific connection_type was desired and not of this one then skip
443
+ continue
444
+
445
+ if udid is not None and not device.matches_udid(udid):
446
+ # if a specific udid was desired and not of this one then skip
447
+ continue
448
+
449
+ # save best result as a temporary
450
+ tmp = device
451
+
452
+ if device.is_usb:
453
+ # always prefer usb connection
454
+ return device
455
+
456
+ return tmp
457
+
458
+
459
+ def select_devices_by_connection_type(connection_type: str, usbmux_address: Optional[str] = None) -> List[MuxDevice]:
460
+ """
461
+ select all UsbMux devices by connection type
462
+ """
463
+ tmp = []
464
+ for device in list_devices(usbmux_address=usbmux_address):
465
+ if device.connection_type == connection_type:
466
+ tmp.append(device)
467
+
468
+ return tmp
469
+
470
+
471
+
472
+ class USBMuxHTTPConnection(HTTPConnection):
473
+ def __init__(self, device: MuxDevice, port=8100):
474
+ super().__init__("localhost", port)
475
+ self.__device = device
476
+ self.__port = port
477
+
478
+ def connect(self):
479
+ self.sock = self.__device.connect(self.__port)
480
+
481
+ def __enter__(self) -> HTTPConnection:
482
+ return self
483
+
484
+ def __exit__(self, exc_type, exc_value, traceback):
485
+ self.close()