uiautodev 0.5.0__py3-none-any.whl → 0.7.0__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.
- uiautodev/__init__.py +1 -1
- uiautodev/app.py +70 -7
- uiautodev/binaries/scrcpy_server.jar +0 -0
- uiautodev/cli.py +6 -22
- uiautodev/command_proxy.py +1 -3
- uiautodev/common.py +30 -1
- uiautodev/driver/android.py +2 -3
- uiautodev/driver/base_driver.py +1 -1
- uiautodev/driver/harmony.py +224 -0
- uiautodev/driver/testdata/layout.json +1 -0
- uiautodev/model.py +6 -2
- uiautodev/provider.py +18 -3
- uiautodev/remote/android_input.py +74 -0
- uiautodev/remote/keycode.py +350 -0
- uiautodev/remote/scrcpy.py +177 -0
- uiautodev/remote/touch_controller.py +123 -0
- uiautodev/router/device.py +3 -2
- uiautodev/utils/common.py +11 -7
- uiautodev/utils/envutils.py +9 -0
- {uiautodev-0.5.0.dist-info → uiautodev-0.7.0.dist-info}/METADATA +17 -4
- uiautodev-0.7.0.dist-info/RECORD +38 -0
- {uiautodev-0.5.0.dist-info → uiautodev-0.7.0.dist-info}/WHEEL +1 -1
- uiautodev-0.5.0.dist-info/RECORD +0 -30
- {uiautodev-0.5.0.dist-info → uiautodev-0.7.0.dist-info}/LICENSE +0 -0
- {uiautodev-0.5.0.dist-info → uiautodev-0.7.0.dist-info}/entry_points.txt +0 -0
uiautodev/model.py
CHANGED
|
@@ -33,13 +33,17 @@ class Rect(BaseModel):
|
|
|
33
33
|
|
|
34
34
|
class Node(BaseModel):
|
|
35
35
|
key: str
|
|
36
|
-
name: str
|
|
36
|
+
name: str # can be seen as description
|
|
37
37
|
bounds: Optional[Tuple[float, float, float, float]] = None
|
|
38
38
|
rect: Optional[Rect] = None
|
|
39
|
-
properties: Dict[str, Union[str, bool]] =
|
|
39
|
+
properties: Dict[str, Union[str, bool]] = {}
|
|
40
40
|
children: List[Node] = []
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
class OCRNode(Node):
|
|
44
|
+
confidence: float
|
|
45
|
+
|
|
46
|
+
|
|
43
47
|
class WindowSize(typing.NamedTuple):
|
|
44
48
|
width: int
|
|
45
49
|
height: int
|
uiautodev/provider.py
CHANGED
|
@@ -12,6 +12,7 @@ import adbutils
|
|
|
12
12
|
|
|
13
13
|
from uiautodev.driver.android import AndroidDriver
|
|
14
14
|
from uiautodev.driver.base_driver import BaseDriver
|
|
15
|
+
from uiautodev.driver.harmony import HDC, HarmonyDriver
|
|
15
16
|
from uiautodev.driver.ios import IOSDriver
|
|
16
17
|
from uiautodev.driver.mock import MockDriver
|
|
17
18
|
from uiautodev.exceptions import UiautoException
|
|
@@ -27,7 +28,7 @@ class BaseProvider(abc.ABC):
|
|
|
27
28
|
@abc.abstractmethod
|
|
28
29
|
def get_device_driver(self, serial: str) -> BaseDriver:
|
|
29
30
|
raise NotImplementedError()
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
def get_single_device_driver(self) -> BaseDriver:
|
|
32
33
|
""" debug use """
|
|
33
34
|
devs = self.list_devices()
|
|
@@ -66,11 +67,25 @@ class IOSProvider(BaseProvider):
|
|
|
66
67
|
@lru_cache
|
|
67
68
|
def get_device_driver(self, serial: str) -> BaseDriver:
|
|
68
69
|
return IOSDriver(serial)
|
|
69
|
-
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class HarmonyProvider(BaseProvider):
|
|
73
|
+
def __init__(self):
|
|
74
|
+
super().__init__()
|
|
75
|
+
self.hdc = HDC()
|
|
76
|
+
|
|
77
|
+
def list_devices(self) -> list[DeviceInfo]:
|
|
78
|
+
devices = self.hdc.list_device()
|
|
79
|
+
return [DeviceInfo(serial=d, model=self.hdc.get_model(d), name=self.hdc.get_model(d)) for d in devices]
|
|
80
|
+
|
|
81
|
+
@lru_cache
|
|
82
|
+
def get_device_driver(self, serial: str) -> HarmonyDriver:
|
|
83
|
+
return HarmonyDriver(self.hdc, serial)
|
|
84
|
+
|
|
70
85
|
|
|
71
86
|
class MockProvider(BaseProvider):
|
|
72
87
|
def list_devices(self) -> list[DeviceInfo]:
|
|
73
88
|
return [DeviceInfo(serial="mock-serial", model="mock-model", name="mock-name")]
|
|
74
89
|
|
|
75
90
|
def get_device_driver(self, serial: str) -> BaseDriver:
|
|
76
|
-
return MockDriver(serial)
|
|
91
|
+
return MockDriver(serial)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Ref
|
|
2
|
+
# https://github.com/Genymobile/scrcpy/blob/master/app/src/android/input.h
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MetaState(IntEnum):
|
|
7
|
+
"""Android meta state flags ported from Android's KeyEvent class
|
|
8
|
+
|
|
9
|
+
These flags represent the state of meta keys such as ALT, SHIFT, CTRL, etc.
|
|
10
|
+
They can be combined using bitwise OR operations to represent multiple
|
|
11
|
+
meta keys being pressed simultaneously.
|
|
12
|
+
|
|
13
|
+
The values and comments are taken directly from the Android source code
|
|
14
|
+
to maintain compatibility and provide accurate descriptions.
|
|
15
|
+
"""
|
|
16
|
+
# No meta keys are pressed
|
|
17
|
+
NONE = 0x0
|
|
18
|
+
|
|
19
|
+
# This mask is used to check whether one of the SHIFT meta keys is pressed
|
|
20
|
+
SHIFT_ON = 0x1
|
|
21
|
+
|
|
22
|
+
# This mask is used to check whether one of the ALT meta keys is pressed
|
|
23
|
+
ALT_ON = 0x2
|
|
24
|
+
|
|
25
|
+
# This mask is used to check whether the SYM meta key is pressed
|
|
26
|
+
SYM_ON = 0x4
|
|
27
|
+
|
|
28
|
+
# This mask is used to check whether the FUNCTION meta key is pressed
|
|
29
|
+
FUNCTION_ON = 0x8
|
|
30
|
+
|
|
31
|
+
# This mask is used to check whether the left ALT meta key is pressed
|
|
32
|
+
ALT_LEFT_ON = 0x10
|
|
33
|
+
|
|
34
|
+
# This mask is used to check whether the right ALT meta key is pressed
|
|
35
|
+
ALT_RIGHT_ON = 0x20
|
|
36
|
+
|
|
37
|
+
# This mask is used to check whether the left SHIFT meta key is pressed
|
|
38
|
+
SHIFT_LEFT_ON = 0x40
|
|
39
|
+
|
|
40
|
+
# This mask is used to check whether the right SHIFT meta key is pressed
|
|
41
|
+
SHIFT_RIGHT_ON = 0x80
|
|
42
|
+
|
|
43
|
+
# This mask is used to check whether the CAPS LOCK meta key is on
|
|
44
|
+
CAPS_LOCK_ON = 0x100000
|
|
45
|
+
|
|
46
|
+
# This mask is used to check whether the NUM LOCK meta key is on
|
|
47
|
+
NUM_LOCK_ON = 0x200000
|
|
48
|
+
|
|
49
|
+
# This mask is used to check whether the SCROLL LOCK meta key is on
|
|
50
|
+
SCROLL_LOCK_ON = 0x400000
|
|
51
|
+
|
|
52
|
+
# This mask is used to check whether one of the CTRL meta keys is pressed
|
|
53
|
+
CTRL_ON = 0x1000
|
|
54
|
+
|
|
55
|
+
# This mask is used to check whether the left CTRL meta key is pressed
|
|
56
|
+
CTRL_LEFT_ON = 0x2000
|
|
57
|
+
|
|
58
|
+
# This mask is used to check whether the right CTRL meta key is pressed
|
|
59
|
+
CTRL_RIGHT_ON = 0x4000
|
|
60
|
+
|
|
61
|
+
# This mask is used to check whether one of the META meta keys is pressed
|
|
62
|
+
META_ON = 0x10000
|
|
63
|
+
|
|
64
|
+
# This mask is used to check whether the left META meta key is pressed
|
|
65
|
+
META_LEFT_ON = 0x20000
|
|
66
|
+
|
|
67
|
+
# This mask is used to check whether the right META meta key is pressed
|
|
68
|
+
META_RIGHT_ON = 0x40000
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class KeyeventAction(IntEnum):
|
|
72
|
+
DOWN = 0
|
|
73
|
+
UP = 1
|
|
74
|
+
MULTIPLE = 2
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class KeyCode(IntEnum):
|
|
5
|
+
"""Android key codes ported from Android's KeyEvent class
|
|
6
|
+
|
|
7
|
+
This enum contains all the key codes defined in Android's KeyEvent class,
|
|
8
|
+
which are used for sending key events to Android devices through scrcpy.
|
|
9
|
+
|
|
10
|
+
The comments for each key code are taken directly from the Android source code
|
|
11
|
+
to maintain compatibility and provide accurate descriptions.
|
|
12
|
+
"""
|
|
13
|
+
# Unknown key code
|
|
14
|
+
UNKNOWN = 0
|
|
15
|
+
# Soft Left key - Usually situated below the display on phones
|
|
16
|
+
SOFT_LEFT = 1
|
|
17
|
+
# Soft Right key - Usually situated below the display on phones
|
|
18
|
+
SOFT_RIGHT = 2
|
|
19
|
+
# Home key - This key is handled by the framework and is never delivered to applications
|
|
20
|
+
HOME = 3
|
|
21
|
+
# Back key
|
|
22
|
+
BACK = 4
|
|
23
|
+
# Call key
|
|
24
|
+
CALL = 5
|
|
25
|
+
# End Call key
|
|
26
|
+
ENDCALL = 6
|
|
27
|
+
# '0' key
|
|
28
|
+
KEY_0 = 7
|
|
29
|
+
# '1' key
|
|
30
|
+
KEY_1 = 8
|
|
31
|
+
# '2' key
|
|
32
|
+
KEY_2 = 9
|
|
33
|
+
# '3' key
|
|
34
|
+
KEY_3 = 10
|
|
35
|
+
# '4' key
|
|
36
|
+
KEY_4 = 11
|
|
37
|
+
# '5' key
|
|
38
|
+
KEY_5 = 12
|
|
39
|
+
# '6' key
|
|
40
|
+
KEY_6 = 13
|
|
41
|
+
# '7' key
|
|
42
|
+
KEY_7 = 14
|
|
43
|
+
# '8' key
|
|
44
|
+
KEY_8 = 15
|
|
45
|
+
# '9' key
|
|
46
|
+
KEY_9 = 16
|
|
47
|
+
# '*' key
|
|
48
|
+
STAR = 17
|
|
49
|
+
# '#' key
|
|
50
|
+
POUND = 18
|
|
51
|
+
# Directional Pad Up key - May also be synthesized from trackball motions
|
|
52
|
+
DPAD_UP = 19
|
|
53
|
+
# Directional Pad Down key - May also be synthesized from trackball motions
|
|
54
|
+
DPAD_DOWN = 20
|
|
55
|
+
# Directional Pad Left key - May also be synthesized from trackball motions
|
|
56
|
+
DPAD_LEFT = 21
|
|
57
|
+
# Directional Pad Right key - May also be synthesized from trackball motions
|
|
58
|
+
DPAD_RIGHT = 22
|
|
59
|
+
# Directional Pad Center key - May also be synthesized from trackball motions
|
|
60
|
+
DPAD_CENTER = 23
|
|
61
|
+
# Volume Up key - Adjusts the speaker volume up
|
|
62
|
+
VOLUME_UP = 24
|
|
63
|
+
# Volume Down key - Adjusts the speaker volume down
|
|
64
|
+
VOLUME_DOWN = 25
|
|
65
|
+
# Power key
|
|
66
|
+
POWER = 26
|
|
67
|
+
# Camera key - Used to launch a camera application or take pictures
|
|
68
|
+
CAMERA = 27
|
|
69
|
+
# Clear key
|
|
70
|
+
CLEAR = 28
|
|
71
|
+
A = 29
|
|
72
|
+
B = 30
|
|
73
|
+
C = 31
|
|
74
|
+
D = 32
|
|
75
|
+
E = 33
|
|
76
|
+
F = 34
|
|
77
|
+
G = 35
|
|
78
|
+
H = 36
|
|
79
|
+
I = 37
|
|
80
|
+
J = 38
|
|
81
|
+
K = 39
|
|
82
|
+
L = 40
|
|
83
|
+
M = 41
|
|
84
|
+
N = 42
|
|
85
|
+
O = 43
|
|
86
|
+
P = 44
|
|
87
|
+
Q = 45
|
|
88
|
+
R = 46
|
|
89
|
+
S = 47
|
|
90
|
+
T = 48
|
|
91
|
+
U = 49
|
|
92
|
+
V = 50
|
|
93
|
+
W = 51
|
|
94
|
+
X = 52
|
|
95
|
+
Y = 53
|
|
96
|
+
Z = 54
|
|
97
|
+
COMMA = 55
|
|
98
|
+
PERIOD = 56
|
|
99
|
+
ALT_LEFT = 57
|
|
100
|
+
ALT_RIGHT = 58
|
|
101
|
+
SHIFT_LEFT = 59
|
|
102
|
+
SHIFT_RIGHT = 60
|
|
103
|
+
TAB = 61
|
|
104
|
+
SPACE = 62
|
|
105
|
+
SYM = 63
|
|
106
|
+
EXPLORER = 64
|
|
107
|
+
ENVELOPE = 65
|
|
108
|
+
# Enter key
|
|
109
|
+
ENTER = 66
|
|
110
|
+
# Backspace key - Deletes characters before the insertion point
|
|
111
|
+
DEL = 67
|
|
112
|
+
GRAVE = 68
|
|
113
|
+
MINUS = 69
|
|
114
|
+
EQUALS = 70
|
|
115
|
+
LEFT_BRACKET = 71
|
|
116
|
+
RIGHT_BRACKET = 72
|
|
117
|
+
BACKSLASH = 73
|
|
118
|
+
SEMICOLON = 74
|
|
119
|
+
APOSTROPHE = 75
|
|
120
|
+
SLASH = 76
|
|
121
|
+
AT = 77
|
|
122
|
+
NUM = 78
|
|
123
|
+
HEADSETHOOK = 79
|
|
124
|
+
FOCUS = 80
|
|
125
|
+
PLUS = 81
|
|
126
|
+
# Menu key
|
|
127
|
+
MENU = 82
|
|
128
|
+
NOTIFICATION = 83
|
|
129
|
+
SEARCH = 84
|
|
130
|
+
MEDIA_PLAY_PAUSE = 85
|
|
131
|
+
MEDIA_STOP = 86
|
|
132
|
+
MEDIA_NEXT = 87
|
|
133
|
+
MEDIA_PREVIOUS = 88
|
|
134
|
+
MEDIA_REWIND = 89
|
|
135
|
+
MEDIA_FAST_FORWARD = 90
|
|
136
|
+
MUTE = 91
|
|
137
|
+
PAGE_UP = 92
|
|
138
|
+
PAGE_DOWN = 93
|
|
139
|
+
PICTSYMBOLS = 94
|
|
140
|
+
SWITCH_CHARSET = 95
|
|
141
|
+
BUTTON_A = 96
|
|
142
|
+
BUTTON_B = 97
|
|
143
|
+
BUTTON_C = 98
|
|
144
|
+
BUTTON_X = 99
|
|
145
|
+
BUTTON_Y = 100
|
|
146
|
+
BUTTON_Z = 101
|
|
147
|
+
BUTTON_L1 = 102
|
|
148
|
+
BUTTON_R1 = 103
|
|
149
|
+
BUTTON_L2 = 104
|
|
150
|
+
BUTTON_R2 = 105
|
|
151
|
+
BUTTON_THUMBL = 106
|
|
152
|
+
BUTTON_THUMBR = 107
|
|
153
|
+
BUTTON_START = 108
|
|
154
|
+
BUTTON_SELECT = 109
|
|
155
|
+
BUTTON_MODE = 110
|
|
156
|
+
ESCAPE = 111
|
|
157
|
+
FORWARD_DEL = 112
|
|
158
|
+
CTRL_LEFT = 113
|
|
159
|
+
CTRL_RIGHT = 114
|
|
160
|
+
CAPS_LOCK = 115
|
|
161
|
+
SCROLL_LOCK = 116
|
|
162
|
+
META_LEFT = 117
|
|
163
|
+
META_RIGHT = 118
|
|
164
|
+
FUNCTION = 119
|
|
165
|
+
SYSRQ = 120
|
|
166
|
+
BREAK = 121
|
|
167
|
+
MOVE_HOME = 122
|
|
168
|
+
MOVE_END = 123
|
|
169
|
+
INSERT = 124
|
|
170
|
+
FORWARD = 125
|
|
171
|
+
MEDIA_PLAY = 126
|
|
172
|
+
MEDIA_PAUSE = 127
|
|
173
|
+
MEDIA_CLOSE = 128
|
|
174
|
+
MEDIA_EJECT = 129
|
|
175
|
+
MEDIA_RECORD = 130
|
|
176
|
+
F1 = 131
|
|
177
|
+
F2 = 132
|
|
178
|
+
F3 = 133
|
|
179
|
+
F4 = 134
|
|
180
|
+
F5 = 135
|
|
181
|
+
F6 = 136
|
|
182
|
+
F7 = 137
|
|
183
|
+
F8 = 138
|
|
184
|
+
F9 = 139
|
|
185
|
+
F10 = 140
|
|
186
|
+
F11 = 141
|
|
187
|
+
F12 = 142
|
|
188
|
+
NUM_LOCK = 143
|
|
189
|
+
NUMPAD_0 = 144
|
|
190
|
+
NUMPAD_1 = 145
|
|
191
|
+
NUMPAD_2 = 146
|
|
192
|
+
NUMPAD_3 = 147
|
|
193
|
+
NUMPAD_4 = 148
|
|
194
|
+
NUMPAD_5 = 149
|
|
195
|
+
NUMPAD_6 = 150
|
|
196
|
+
NUMPAD_7 = 151
|
|
197
|
+
NUMPAD_8 = 152
|
|
198
|
+
NUMPAD_9 = 153
|
|
199
|
+
NUMPAD_DIVIDE = 154
|
|
200
|
+
NUMPAD_MULTIPLY = 155
|
|
201
|
+
NUMPAD_SUBTRACT = 156
|
|
202
|
+
NUMPAD_ADD = 157
|
|
203
|
+
NUMPAD_DOT = 158
|
|
204
|
+
NUMPAD_COMMA = 159
|
|
205
|
+
NUMPAD_ENTER = 160
|
|
206
|
+
NUMPAD_EQUALS = 161
|
|
207
|
+
NUMPAD_LEFT_PAREN = 162
|
|
208
|
+
NUMPAD_RIGHT_PAREN = 163
|
|
209
|
+
VOLUME_MUTE = 164
|
|
210
|
+
INFO = 165
|
|
211
|
+
CHANNEL_UP = 166
|
|
212
|
+
CHANNEL_DOWN = 167
|
|
213
|
+
ZOOM_IN = 168
|
|
214
|
+
ZOOM_OUT = 169
|
|
215
|
+
TV = 170
|
|
216
|
+
WINDOW = 171
|
|
217
|
+
GUIDE = 172
|
|
218
|
+
DVR = 173
|
|
219
|
+
BOOKMARK = 174
|
|
220
|
+
CAPTIONS = 175
|
|
221
|
+
SETTINGS = 176
|
|
222
|
+
TV_POWER = 177
|
|
223
|
+
TV_INPUT = 178
|
|
224
|
+
STB_POWER = 179
|
|
225
|
+
STB_INPUT = 180
|
|
226
|
+
AVR_POWER = 181
|
|
227
|
+
AVR_INPUT = 182
|
|
228
|
+
PROG_RED = 183
|
|
229
|
+
PROG_GREEN = 184
|
|
230
|
+
PROG_YELLOW = 185
|
|
231
|
+
PROG_BLUE = 186
|
|
232
|
+
APP_SWITCH = 187
|
|
233
|
+
BUTTON_1 = 188
|
|
234
|
+
BUTTON_2 = 189
|
|
235
|
+
BUTTON_3 = 190
|
|
236
|
+
BUTTON_4 = 191
|
|
237
|
+
BUTTON_5 = 192
|
|
238
|
+
BUTTON_6 = 193
|
|
239
|
+
BUTTON_7 = 194
|
|
240
|
+
BUTTON_8 = 195
|
|
241
|
+
BUTTON_9 = 196
|
|
242
|
+
BUTTON_10 = 197
|
|
243
|
+
BUTTON_11 = 198
|
|
244
|
+
BUTTON_12 = 199
|
|
245
|
+
BUTTON_13 = 200
|
|
246
|
+
BUTTON_14 = 201
|
|
247
|
+
BUTTON_15 = 202
|
|
248
|
+
BUTTON_16 = 203
|
|
249
|
+
LANGUAGE_SWITCH = 204
|
|
250
|
+
MANNER_MODE = 205
|
|
251
|
+
MODE_3D = 206
|
|
252
|
+
CONTACTS = 207
|
|
253
|
+
CALENDAR = 208
|
|
254
|
+
MUSIC = 209
|
|
255
|
+
CALCULATOR = 210
|
|
256
|
+
ZENKAKU_HANKAKU = 211
|
|
257
|
+
EISU = 212
|
|
258
|
+
MUHENKAN = 213
|
|
259
|
+
HENKAN = 214
|
|
260
|
+
KATAKANA_HIRAGANA = 215
|
|
261
|
+
YEN = 216
|
|
262
|
+
RO = 217
|
|
263
|
+
KANA = 218
|
|
264
|
+
ASSIST = 219
|
|
265
|
+
BRIGHTNESS_DOWN = 220
|
|
266
|
+
BRIGHTNESS_UP = 221
|
|
267
|
+
MEDIA_AUDIO_TRACK = 222
|
|
268
|
+
SLEEP = 223
|
|
269
|
+
WAKEUP = 224
|
|
270
|
+
PAIRING = 225
|
|
271
|
+
MEDIA_TOP_MENU = 226
|
|
272
|
+
KEY_11 = 227
|
|
273
|
+
KEY_12 = 228
|
|
274
|
+
LAST_CHANNEL = 229
|
|
275
|
+
TV_DATA_SERVICE = 230
|
|
276
|
+
VOICE_ASSIST = 231
|
|
277
|
+
TV_RADIO_SERVICE = 232
|
|
278
|
+
TV_TELETEXT = 233
|
|
279
|
+
TV_NUMBER_ENTRY = 234
|
|
280
|
+
TV_TERRESTRIAL_ANALOG = 235
|
|
281
|
+
TV_TERRESTRIAL_DIGITAL = 236
|
|
282
|
+
TV_SATELLITE = 237
|
|
283
|
+
TV_SATELLITE_BS = 238
|
|
284
|
+
TV_SATELLITE_CS = 239
|
|
285
|
+
TV_SATELLITE_SERVICE = 240
|
|
286
|
+
TV_NETWORK = 241
|
|
287
|
+
TV_ANTENNA_CABLE = 242
|
|
288
|
+
TV_INPUT_HDMI_1 = 243
|
|
289
|
+
TV_INPUT_HDMI_2 = 244
|
|
290
|
+
TV_INPUT_HDMI_3 = 245
|
|
291
|
+
TV_INPUT_HDMI_4 = 246
|
|
292
|
+
TV_INPUT_COMPOSITE_1 = 247
|
|
293
|
+
TV_INPUT_COMPOSITE_2 = 248
|
|
294
|
+
TV_INPUT_COMPONENT_1 = 249
|
|
295
|
+
TV_INPUT_COMPONENT_2 = 250
|
|
296
|
+
TV_INPUT_VGA_1 = 251
|
|
297
|
+
TV_AUDIO_DESCRIPTION = 252
|
|
298
|
+
TV_AUDIO_DESCRIPTION_MIX_UP = 253
|
|
299
|
+
TV_AUDIO_DESCRIPTION_MIX_DOWN = 254
|
|
300
|
+
TV_ZOOM_MODE = 255
|
|
301
|
+
TV_CONTENTS_MENU = 256
|
|
302
|
+
TV_MEDIA_CONTEXT_MENU = 257
|
|
303
|
+
TV_TIMER_PROGRAMMING = 258
|
|
304
|
+
HELP = 259
|
|
305
|
+
NAVIGATE_PREVIOUS = 260
|
|
306
|
+
NAVIGATE_NEXT = 261
|
|
307
|
+
NAVIGATE_IN = 262
|
|
308
|
+
NAVIGATE_OUT = 263
|
|
309
|
+
STEM_PRIMARY = 264
|
|
310
|
+
STEM_1 = 265
|
|
311
|
+
STEM_2 = 266
|
|
312
|
+
STEM_3 = 267
|
|
313
|
+
DPAD_UP_LEFT = 268
|
|
314
|
+
DPAD_DOWN_LEFT = 269
|
|
315
|
+
DPAD_UP_RIGHT = 270
|
|
316
|
+
DPAD_DOWN_RIGHT = 271
|
|
317
|
+
MEDIA_SKIP_FORWARD = 272
|
|
318
|
+
MEDIA_SKIP_BACKWARD = 273
|
|
319
|
+
MEDIA_STEP_FORWARD = 274
|
|
320
|
+
MEDIA_STEP_BACKWARD = 275
|
|
321
|
+
SOFT_SLEEP = 276
|
|
322
|
+
CUT = 277
|
|
323
|
+
COPY = 278
|
|
324
|
+
PASTE = 279
|
|
325
|
+
SYSTEM_NAVIGATION_UP = 280
|
|
326
|
+
SYSTEM_NAVIGATION_DOWN = 281
|
|
327
|
+
SYSTEM_NAVIGATION_LEFT = 282
|
|
328
|
+
SYSTEM_NAVIGATION_RIGHT = 283
|
|
329
|
+
ALL_APPS = 284
|
|
330
|
+
|
|
331
|
+
# =========================================================================
|
|
332
|
+
# Aliases for original Android KeyEvent names
|
|
333
|
+
# =========================================================================
|
|
334
|
+
# These aliases are provided to maintain compatibility with the original
|
|
335
|
+
# Android KeyEvent naming convention (AKEYCODE_*). This makes it easier
|
|
336
|
+
# to reference keys using the same names as in Android documentation.
|
|
337
|
+
|
|
338
|
+
# Numeric key aliases
|
|
339
|
+
KEYCODE_0 = KEY_0 # '0' key
|
|
340
|
+
KEYCODE_1 = KEY_1 # '1' key
|
|
341
|
+
KEYCODE_2 = KEY_2 # '2' key
|
|
342
|
+
KEYCODE_3 = KEY_3 # '3' key
|
|
343
|
+
KEYCODE_4 = KEY_4 # '4' key
|
|
344
|
+
KEYCODE_5 = KEY_5 # '5' key
|
|
345
|
+
KEYCODE_6 = KEY_6 # '6' key
|
|
346
|
+
KEYCODE_7 = KEY_7 # '7' key
|
|
347
|
+
KEYCODE_8 = KEY_8 # '8' key
|
|
348
|
+
KEYCODE_9 = KEY_9 # '9' key
|
|
349
|
+
KEYCODE_11 = KEY_11 # '11' key
|
|
350
|
+
KEYCODE_12 = KEY_12 # '12' key
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
import struct
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import retry
|
|
10
|
+
from adbutils import AdbError, Network, adb
|
|
11
|
+
from adbutils._adb import AdbConnection
|
|
12
|
+
from adbutils._device import AdbDevice
|
|
13
|
+
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
14
|
+
|
|
15
|
+
from uiautodev.remote.touch_controller import ScrcpyTouchController
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
class ScrcpyServer:
|
|
20
|
+
"""
|
|
21
|
+
ScrcpyServer class is responsible for managing the scrcpy server on Android devices.
|
|
22
|
+
It handles the initialization, communication, and control of the scrcpy server,
|
|
23
|
+
including video streaming and touch control.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, device: AdbDevice, scrcpy_jar_path: Optional[str] = None):
|
|
27
|
+
"""
|
|
28
|
+
Initializes the ScrcpyServer instance.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
scrcpy_jar_path (str, optional): Path to the scrcpy server JAR file. Defaults to None.
|
|
32
|
+
"""
|
|
33
|
+
self.scrcpy_jar_path = scrcpy_jar_path or os.path.join(os.path.dirname(__file__), '../binaries/scrcpy_server.jar')
|
|
34
|
+
self.device = device
|
|
35
|
+
self.resolution_width = 0 # scrcpy 投屏转换宽度
|
|
36
|
+
self.resolution_height = 0 # scrcpy 投屏转换高度
|
|
37
|
+
|
|
38
|
+
self._shell_conn: AdbConnection
|
|
39
|
+
self._video_conn: socket.socket
|
|
40
|
+
self._control_conn: socket.socket
|
|
41
|
+
|
|
42
|
+
self._setup_connection()
|
|
43
|
+
|
|
44
|
+
def _setup_connection(self):
|
|
45
|
+
self._shell_conn = self._start_scrcpy_server(control=True)
|
|
46
|
+
self._video_conn = self._connect_scrcpy(self.device)
|
|
47
|
+
self._control_conn = self._connect_scrcpy(self.device)
|
|
48
|
+
self._parse_scrcpy_info(self._video_conn)
|
|
49
|
+
self.controller = ScrcpyTouchController(self._control_conn)
|
|
50
|
+
|
|
51
|
+
@retry.retry(exceptions=AdbError, tries=20, delay=0.1)
|
|
52
|
+
def _connect_scrcpy(self, device: AdbDevice) -> socket.socket:
|
|
53
|
+
return device.create_connection(Network.LOCAL_ABSTRACT, 'scrcpy')
|
|
54
|
+
|
|
55
|
+
def _parse_scrcpy_info(self, conn: socket.socket):
|
|
56
|
+
dummy_byte = conn.recv(1)
|
|
57
|
+
if not dummy_byte or dummy_byte != b"\x00":
|
|
58
|
+
raise ConnectionError("Did not receive Dummy Byte!")
|
|
59
|
+
logger.debug('Received Dummy Byte!')
|
|
60
|
+
device_name = conn.recv(64).decode('utf-8').rstrip('\x00')
|
|
61
|
+
logger.debug(f'Device name: {device_name}')
|
|
62
|
+
codec = conn.recv(4)
|
|
63
|
+
logger.debug(f'resolution_data: {codec}')
|
|
64
|
+
resolution_data = conn.recv(8)
|
|
65
|
+
logger.debug(f'resolution_data: {resolution_data}')
|
|
66
|
+
self.resolution_width, self.resolution_height = struct.unpack(">II", resolution_data)
|
|
67
|
+
logger.debug(f'Resolution: {self.resolution_width}x{self.resolution_height}')
|
|
68
|
+
|
|
69
|
+
def close(self):
|
|
70
|
+
try:
|
|
71
|
+
self._control_conn.close()
|
|
72
|
+
self._video_conn.close()
|
|
73
|
+
self._shell_conn.close()
|
|
74
|
+
except:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def __del__(self):
|
|
78
|
+
self.close()
|
|
79
|
+
|
|
80
|
+
def _start_scrcpy_server(self, control: bool = True) -> AdbConnection:
|
|
81
|
+
"""
|
|
82
|
+
Pushes the scrcpy server JAR file to the Android device and starts the scrcpy server.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
control (bool, optional): Whether to enable touch control. Defaults to True.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
AdbConnection
|
|
89
|
+
"""
|
|
90
|
+
# 获取设备对象
|
|
91
|
+
device = self.device
|
|
92
|
+
|
|
93
|
+
# 推送 scrcpy 服务器到设备
|
|
94
|
+
device.sync.push(self.scrcpy_jar_path, '/data/local/tmp/scrcpy_server.jar', check=True)
|
|
95
|
+
logger.info('scrcpy server JAR pushed to device')
|
|
96
|
+
|
|
97
|
+
# 构建启动 scrcpy 服务器的命令
|
|
98
|
+
start_command = (
|
|
99
|
+
'CLASSPATH=/data/local/tmp/scrcpy_server.jar '
|
|
100
|
+
'app_process / '
|
|
101
|
+
'com.genymobile.scrcpy.Server 2.7 '
|
|
102
|
+
'log_level=info max_size=1024 max_fps=30 '
|
|
103
|
+
'video_bit_rate=8000000 tunnel_forward=true '
|
|
104
|
+
'send_frame_meta=false '
|
|
105
|
+
f'control={"true" if control else "false"} '
|
|
106
|
+
'audio=false show_touches=false stay_awake=false '
|
|
107
|
+
'power_off_on_close=false clipboard_autosync=false'
|
|
108
|
+
)
|
|
109
|
+
conn = device.shell(start_command, stream=True)
|
|
110
|
+
logger.debug("scrcpy output: %s", conn.conn.recv(100))
|
|
111
|
+
return conn # type: ignore
|
|
112
|
+
|
|
113
|
+
async def handle_unified_websocket(self, websocket: WebSocket, serial=''):
|
|
114
|
+
logger.info(f"[Unified] WebSocket connection from {websocket} for serial: {serial}")
|
|
115
|
+
|
|
116
|
+
video_task = asyncio.create_task(self._stream_video_to_websocket(self._video_conn, websocket))
|
|
117
|
+
control_task = asyncio.create_task(self._handle_control_websocket(websocket))
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
# 不使用 return_exceptions=True,让异常能够正确传播
|
|
121
|
+
await asyncio.gather(video_task, control_task)
|
|
122
|
+
finally:
|
|
123
|
+
# 取消任务
|
|
124
|
+
for task in (video_task, control_task):
|
|
125
|
+
if not task.done():
|
|
126
|
+
task.cancel()
|
|
127
|
+
logger.info(f"[Unified] WebSocket closed for serial={serial}")
|
|
128
|
+
|
|
129
|
+
async def _stream_video_to_websocket(self, conn: socket.socket, ws: WebSocket):
|
|
130
|
+
# Set socket to non-blocking mode
|
|
131
|
+
conn.setblocking(False)
|
|
132
|
+
|
|
133
|
+
while True:
|
|
134
|
+
# check if ws closed
|
|
135
|
+
if ws.client_state.name != "CONNECTED":
|
|
136
|
+
logger.info('WebSocket no longer connected. Exiting video stream.')
|
|
137
|
+
break
|
|
138
|
+
# Use asyncio to read data asynchronously
|
|
139
|
+
data = await asyncio.get_event_loop().sock_recv(conn, 1024*1024)
|
|
140
|
+
if not data:
|
|
141
|
+
logger.warning('No data received, connection may be closed.')
|
|
142
|
+
raise ConnectionError("Video stream ended unexpectedly")
|
|
143
|
+
# send data to ws
|
|
144
|
+
await ws.send_bytes(data)
|
|
145
|
+
|
|
146
|
+
async def _handle_control_websocket(self, ws: WebSocket):
|
|
147
|
+
while True:
|
|
148
|
+
try:
|
|
149
|
+
message = await ws.receive_text()
|
|
150
|
+
logger.debug(f"[Unified] Received message: {message}")
|
|
151
|
+
message = json.loads(message)
|
|
152
|
+
|
|
153
|
+
width, height = self.resolution_width, self.resolution_height
|
|
154
|
+
message_type = message.get('type')
|
|
155
|
+
if message_type == 'touchMove':
|
|
156
|
+
xP = message['xP']
|
|
157
|
+
yP = message['yP']
|
|
158
|
+
self.controller.move(int(xP * width), int(yP * height), width, height)
|
|
159
|
+
elif message_type == 'touchDown':
|
|
160
|
+
xP = message['xP']
|
|
161
|
+
yP = message['yP']
|
|
162
|
+
self.controller.down(int(xP * width), int(yP * height), width, height)
|
|
163
|
+
elif message_type == 'touchUp':
|
|
164
|
+
xP = message['xP']
|
|
165
|
+
yP = message['yP']
|
|
166
|
+
self.controller.up(int(xP * width), int(yP * height), width, height)
|
|
167
|
+
elif message_type == 'keyEvent':
|
|
168
|
+
event_number = message['data']['eventNumber']
|
|
169
|
+
self.device.shell(f'input keyevent {event_number}')
|
|
170
|
+
elif message_type == 'text':
|
|
171
|
+
text = message['detail']
|
|
172
|
+
self.device.shell(f'am broadcast -a SONIC_KEYBOARD --es msg \'{text}\'')
|
|
173
|
+
elif message_type == 'ping':
|
|
174
|
+
await ws.send_text(json.dumps({"type": "pong"}))
|
|
175
|
+
except json.JSONDecodeError as e:
|
|
176
|
+
logger.error(f"Invalid JSON message: {e}")
|
|
177
|
+
continue
|