devhelmkit 0.1.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.
Files changed (58) hide show
  1. devhelmkit/__init__.py +44 -0
  2. devhelmkit/android/__init__.py +7 -0
  3. devhelmkit/android/driver.py +18 -0
  4. devhelmkit/assets/so/arm64-v8a/agent_v1.so +0 -0
  5. devhelmkit/assets/so/arm64-v8a/agent_v2.so +0 -0
  6. devhelmkit/assets/so/x86_64/agent.so +0 -0
  7. devhelmkit/core/__init__.py +3 -0
  8. devhelmkit/core/base_component.py +214 -0
  9. devhelmkit/core/base_driver.py +414 -0
  10. devhelmkit/core/base_window.py +25 -0
  11. devhelmkit/core/selector_spec.py +102 -0
  12. devhelmkit/entry.py +90 -0
  13. devhelmkit/exceptions.py +56 -0
  14. devhelmkit/harmony/__init__.py +3 -0
  15. devhelmkit/harmony/agent/__init__.py +7 -0
  16. devhelmkit/harmony/agent/so_manager.py +300 -0
  17. devhelmkit/harmony/config.py +124 -0
  18. devhelmkit/harmony/device/__init__.py +6 -0
  19. devhelmkit/harmony/device/hdc.py +430 -0
  20. devhelmkit/harmony/driver.py +1416 -0
  21. devhelmkit/harmony/finder/__init__.py +12 -0
  22. devhelmkit/harmony/finder/component_finder.py +336 -0
  23. devhelmkit/harmony/finder/popup_handler.py +81 -0
  24. devhelmkit/harmony/finder/selector_adapter.py +101 -0
  25. devhelmkit/harmony/finder/xpath_query.py +110 -0
  26. devhelmkit/harmony/rpc/__init__.py +12 -0
  27. devhelmkit/harmony/rpc/client.py +126 -0
  28. devhelmkit/harmony/rpc/proxy_v2.py +106 -0
  29. devhelmkit/harmony/rpc/remote_object.py +48 -0
  30. devhelmkit/harmony/uiobject.py +246 -0
  31. devhelmkit/harmony/uiwindow.py +43 -0
  32. devhelmkit/harmony/webview/__init__.py +17 -0
  33. devhelmkit/harmony/webview/chromedriver_manager.py +251 -0
  34. devhelmkit/harmony/webview/devtools_finder.py +131 -0
  35. devhelmkit/harmony/webview/webview_driver.py +326 -0
  36. devhelmkit/model/__init__.py +3 -0
  37. devhelmkit/model/action.py +147 -0
  38. devhelmkit/model/app_state.py +50 -0
  39. devhelmkit/model/constants.py +22 -0
  40. devhelmkit/model/display.py +32 -0
  41. devhelmkit/model/format_string.py +24 -0
  42. devhelmkit/model/input.py +50 -0
  43. devhelmkit/model/json_base.py +42 -0
  44. devhelmkit/model/keys.py +375 -0
  45. devhelmkit/model/match_pattern.py +15 -0
  46. devhelmkit/model/page.py +13 -0
  47. devhelmkit/model/params.py +42 -0
  48. devhelmkit/model/rect.py +58 -0
  49. devhelmkit/model/runnable.py +18 -0
  50. devhelmkit/utils/__init__.py +3 -0
  51. devhelmkit/utils/logger.py +72 -0
  52. devhelmkit/utils/retry.py +46 -0
  53. devhelmkit/utils/timeout.py +64 -0
  54. devhelmkit-0.1.0.dist-info/METADATA +411 -0
  55. devhelmkit-0.1.0.dist-info/RECORD +58 -0
  56. devhelmkit-0.1.0.dist-info/WHEEL +5 -0
  57. devhelmkit-0.1.0.dist-info/licenses/LICENSE +201 -0
  58. devhelmkit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,326 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """WebViewDriver:webview 自动化驱动。
4
+
5
+ 连接设备端 webview,通过 selenium webdriver 提供页面级自动化能力。
6
+
7
+ 完整流程:
8
+ 1. 探测 devtools 端口(domain socket 或 tcp)
9
+ 2. 建立 hdc 端口转发(本地端口 -> 设备端 devtools)
10
+ 3. 查询 webview 内核版本
11
+ 4. 启动匹配版本的 chromedriver
12
+ 5. 通过 selenium Remote 连接
13
+ 6. 资源释放:移除端口转发 + quit webdriver + 停止 chromedriver
14
+
15
+ 使用方式:
16
+ from devhelmkit.harmony.webview import WebViewDriver
17
+
18
+ wv = WebViewDriver(device)
19
+ wv.connect("com.huawei.hmos.browser")
20
+ wv.driver.get("https://www.baidu.com")
21
+ wv.close()
22
+
23
+ 或通过 HarmonyDriver 入口:
24
+ d.webview("com.huawei.hmos.browser")
25
+ """
26
+ import json
27
+ import re
28
+ import time
29
+ import urllib.request
30
+ from typing import Optional, TYPE_CHECKING
31
+
32
+ from devhelmkit.exceptions import DevhelmError
33
+ from devhelmkit.harmony.webview.chromedriver_manager import ChromedriverManager
34
+ from devhelmkit.harmony.webview.devtools_finder import DevtoolsFinder
35
+ from devhelmkit.utils import logger
36
+
37
+ if TYPE_CHECKING:
38
+ from selenium.webdriver.remote.webdriver import WebDriver
39
+ from devhelmkit.harmony.device.hdc import HdcDevice
40
+
41
+ # 默认本地 devtools 端口(自动分配空闲端口)
42
+ DEFAULT_DEVTOOL_PORT = 9222
43
+
44
+ # 默认连接超时(秒)
45
+ DEFAULT_CONNECTION_TIMEOUT = 60
46
+
47
+ # 页面加载超时(秒)
48
+ DEFAULT_PAGE_LOAD_TIMEOUT = 50
49
+
50
+ # 脚本执行超时(秒)
51
+ DEFAULT_SCRIPT_TIMEOUT = 50
52
+
53
+ # 隐式等待(秒)
54
+ IMPLICIT_WAIT_TIMEOUT = 20
55
+
56
+
57
+ class WebViewDriver:
58
+ """webview 自动化驱动,封装 selenium webdriver 连接与资源管理。"""
59
+
60
+ def __init__(self, device: "HdcDevice",
61
+ chromedriver_search_path: str = "",
62
+ chromedriver_exe_path: str = "",
63
+ chromedriver_port: int = 9515):
64
+ """
65
+ Args:
66
+ device: HdcDevice 实例
67
+ chromedriver_search_path: chromedriver 存放目录(多版本结构)
68
+ chromedriver_exe_path: 直接指定 chromedriver 路径(优先)
69
+ chromedriver_port: chromedriver 监听端口
70
+ """
71
+ self._device = device
72
+ self._bundle_name: Optional[str] = None
73
+ self._driver: Optional["WebDriver"] = None
74
+ self._devtool_port = DEFAULT_DEVTOOL_PORT
75
+ self._remote_devtool_port = DEFAULT_DEVTOOL_PORT
76
+ self._fport_established = False
77
+
78
+ self._finder = DevtoolsFinder(device)
79
+ self._chromedriver = ChromedriverManager(
80
+ search_path=chromedriver_search_path,
81
+ exe_path=chromedriver_exe_path,
82
+ port=chromedriver_port,
83
+ )
84
+
85
+ @property
86
+ def driver(self) -> "WebDriver":
87
+ """selenium webdriver 实例。
88
+
89
+ 未连接时抛异常,确保调用前已 connect。
90
+ """
91
+ if self._driver is None:
92
+ raise DevhelmError("webview 未连接,请先调用 connect()")
93
+ return self._driver
94
+
95
+ @property
96
+ def devtools_url(self) -> str:
97
+ """本地 devtools 访问地址。"""
98
+ return "http://127.0.0.1:%d" % self._devtool_port
99
+
100
+ def connect(self, bundle_name: str,
101
+ remote_devtools_port: Optional[int] = None,
102
+ connection_timeout: int = DEFAULT_CONNECTION_TIMEOUT,
103
+ options=None) -> "WebDriver":
104
+ """连接指定应用的 webview。
105
+
106
+ Args:
107
+ bundle_name: 目标应用包名
108
+ remote_devtools_port: 自定义 webview 内核的 devtools 端口;
109
+ 系统 web 内核无需指定,自动探测
110
+ connection_timeout: 连接超时(秒)
111
+ options: 传递给 selenium webdriver 的 options
112
+
113
+ Returns:
114
+ selenium WebDriver 实例
115
+
116
+ Raises:
117
+ DevhelmError: 连接失败
118
+ """
119
+ self._bundle_name = bundle_name
120
+ self.close()
121
+ self._setup_port_forward(bundle_name, remote_devtools_port)
122
+ webview_version = self._get_webview_version()
123
+ self._chromedriver.start(webview_version)
124
+ self._driver = self._connect_selenium(options, connection_timeout)
125
+ return self._driver
126
+
127
+ def close(self) -> None:
128
+ """释放资源:移除端口转发 + quit webdriver + 停止 chromedriver。"""
129
+ if self._driver is not None:
130
+ try:
131
+ self._driver.quit()
132
+ except Exception as e:
133
+ logger.warn("quit webdriver 异常: %s", e)
134
+ finally:
135
+ self._driver = None
136
+
137
+ self._remove_port_forward()
138
+
139
+ if self._chromedriver is not None:
140
+ self._chromedriver.stop()
141
+
142
+ def switch_to_visible_window(self, index: int = 0) -> None:
143
+ """切换到 visible 状态的窗口。
144
+
145
+ Args:
146
+ index: 第几个可见窗口(0 表示第一个)
147
+ """
148
+ driver = self.driver
149
+ handles = driver.window_handles
150
+ if index < 0:
151
+ handles = list(reversed(handles))
152
+ index = abs(index) - 1
153
+ if index >= len(handles):
154
+ raise ValueError("窗口索引越界: %d (共 %d 个)" % (index, len(handles)))
155
+
156
+ count = index
157
+ for handle in handles:
158
+ driver.switch_to.window(handle)
159
+ visible = driver.execute_script("return document.visibilityState")
160
+ if visible == "visible":
161
+ if count == 0:
162
+ return
163
+ count -= 1
164
+
165
+ def get_all_windows(self, with_url: bool = False) -> list:
166
+ """获取所有窗口信息。
167
+
168
+ Args:
169
+ with_url: True 返回包含 url/title/visible 的字典列表
170
+ """
171
+ if not with_url:
172
+ return self.driver.window_handles
173
+
174
+ driver = self.driver
175
+ current = driver.current_window_handle
176
+ result = []
177
+ for handle in driver.window_handles:
178
+ driver.switch_to.window(handle)
179
+ result.append({
180
+ "handle": handle,
181
+ "url": driver.current_url,
182
+ "title": driver.title,
183
+ "visible": driver.execute_script("return document.visibilityState"),
184
+ })
185
+ driver.switch_to.window(current)
186
+ return result
187
+
188
+ def __getattr__(self, name):
189
+ """代理未找到的属性到 selenium webdriver。"""
190
+ return getattr(self._driver, name)
191
+
192
+ def __enter__(self):
193
+ return self
194
+
195
+ def __exit__(self, exc_type, exc_val, exc_tb):
196
+ self.close()
197
+
198
+ # ============================================================
199
+ # 内部实现
200
+ # ============================================================
201
+
202
+ def _setup_port_forward(self, bundle_name: str,
203
+ remote_devtools_port: Optional[int]) -> None:
204
+ """建立 hdc 端口转发。"""
205
+ self._devtool_port = _get_unused_port()
206
+
207
+ if remote_devtools_port is not None:
208
+ # 自定义 webview 内核,指定 tcp 端口
209
+ self._remote_devtool_port = remote_devtools_port
210
+ if not self._finder.check_tcp_port(remote_devtools_port):
211
+ raise DevhelmError(
212
+ "设备端 devtools 端口 %d 未开放,请检查应用是否已开启 web 调试"
213
+ % remote_devtools_port
214
+ )
215
+ self._device.shell(
216
+ "hdc fport tcp:%d tcp:%d" % (self._devtool_port, remote_devtools_port)
217
+ )
218
+ elif self._finder.is_using_domain_socket():
219
+ # 系统 web 内核,通过 domain socket 探测
220
+ socket_name = self._finder.find_devtools_socket(bundle_name)
221
+ if socket_name is None:
222
+ raise DevhelmError(
223
+ "未找到 %s 的 devtools socket,请检查应用是否已开启 web 调试"
224
+ % bundle_name
225
+ )
226
+ self._device.shell(
227
+ "hdc fport tcp:%d localabstract:%s"
228
+ % (self._devtool_port, socket_name)
229
+ )
230
+ else:
231
+ # 未指定端口且非 domain socket,尝试默认 tcp 端口
232
+ if not self._finder.check_tcp_port(self._remote_devtool_port):
233
+ raise DevhelmError(
234
+ "设备端 devtools 端口 %d 未开放" % self._remote_devtool_port
235
+ )
236
+ self._device.shell(
237
+ "hdc fport tcp:%d tcp:%d"
238
+ % (self._devtool_port, self._remote_devtool_port)
239
+ )
240
+
241
+ self._fport_established = True
242
+ logger.info(
243
+ "devtools 端口转发已建立: tcp:%d -> 设备 (bundle=%s)",
244
+ self._devtool_port, bundle_name
245
+ )
246
+
247
+ def _remove_port_forward(self) -> None:
248
+ """移除 hdc 端口转发。"""
249
+ if not self._fport_established:
250
+ return
251
+ try:
252
+ self._device.shell("hdc fport rm tcp:%d" % self._devtool_port)
253
+ except Exception as e:
254
+ logger.warn("移除端口转发异常: %s", e)
255
+ finally:
256
+ self._fport_established = False
257
+
258
+ def _get_webview_version(self, retry: int = 3) -> int:
259
+ """查询设备端 webview 内核版本。
260
+
261
+ 通过 devtools /json/version 接口获取。
262
+ """
263
+ for _ in range(retry):
264
+ try:
265
+ resp = urllib.request.urlopen(
266
+ self.devtools_url + "/json/version", timeout=5
267
+ ).read().decode("utf-8", errors="ignore")
268
+ info = json.loads(resp)
269
+ browser = info.get("Browser", "")
270
+ match = re.search(r"/(\d+)\.", browser)
271
+ if match:
272
+ version = int(match.group(1))
273
+ logger.info("webview 内核版本: %d", version)
274
+ return version
275
+ logger.warn("未知 webview 版本信息: %s", resp)
276
+ except Exception as e:
277
+ logger.warn("查询 webview 版本失败: %s", e)
278
+ logger.warn("无法获取 webview 版本,回退到 114")
279
+ return 114
280
+
281
+ def _connect_selenium(self, options, timeout: int) -> "WebDriver":
282
+ """通过 selenium Remote 连接 chromedriver。"""
283
+ try:
284
+ from selenium import webdriver
285
+ from selenium.webdriver.remote.remote_connection import RemoteConnection
286
+ except ImportError as e:
287
+ raise DevhelmError(
288
+ "selenium 未安装,请执行 pip install selenium 或 pip install devhelmkit[webview]"
289
+ ) from e
290
+
291
+ RemoteConnection.set_timeout(timeout)
292
+
293
+ if options is None:
294
+ options = webdriver.ChromeOptions()
295
+ options.add_experimental_option(
296
+ "debuggerAddress", "127.0.0.1:%d" % self._devtool_port
297
+ )
298
+
299
+ try:
300
+ driver = webdriver.Remote(
301
+ command_executor=self._chromedriver.host,
302
+ options=options
303
+ )
304
+ except Exception as e:
305
+ logger.error("连接 chromedriver 失败: %s", e)
306
+ self._chromedriver.stop()
307
+ raise DevhelmError("selenium webdriver 连接失败: %s" % e) from e
308
+
309
+ driver.set_page_load_timeout(DEFAULT_PAGE_LOAD_TIMEOUT)
310
+ driver.set_script_timeout(DEFAULT_SCRIPT_TIMEOUT)
311
+ driver.implicitly_wait(IMPLICIT_WAIT_TIMEOUT)
312
+
313
+ logger.info("webview 已连接, 窗口数: %d", len(driver.window_handles))
314
+ logger.info("当前页面: %s", driver.current_url)
315
+ return driver
316
+
317
+
318
+ def _get_unused_port() -> int:
319
+ """获取一个本地空闲端口。"""
320
+ import socket
321
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
322
+ try:
323
+ sock.bind(("127.0.0.1", 0))
324
+ return sock.getsockname()[1]
325
+ finally:
326
+ sock.close()
@@ -0,0 +1,3 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """纯数据类型层:不依赖 core / harmony / android / utils。"""
@@ -0,0 +1,147 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """操作类型与上下文:ActionType / ActionTrackGroup / ActionContext / ActionContextStack。
4
+
5
+ ActionContextStack 持有 driver 的 weakref.proxy,避免循环引用。
6
+ """
7
+ import weakref
8
+ from typing import List, Optional, Tuple
9
+
10
+ from devhelmkit.exceptions import DevhelmError
11
+
12
+
13
+ class ActionType:
14
+ """操作类型常量。"""
15
+ CLICK = "click"
16
+ DOUBLE_CLICK = "double_click"
17
+ LONG_CLICK = "long_click"
18
+ SWIPE = "swipe"
19
+ DRAG = "drag"
20
+ PINCH_IN = "pinch_in"
21
+ PINCH_OUT = "pinch_out"
22
+ INPUT_TEXT = "input_text"
23
+ PRESS_KEY = "press_key"
24
+ MOUSE_SCROLL = "mouse_scroll"
25
+
26
+
27
+ class ActionTrackGroup:
28
+ """操作路径信息。
29
+
30
+ 使用 __init__ 接收可变参数 *points_group,每个参数是 list[(x, y)]。
31
+ """
32
+
33
+ def __init__(self, *points_group):
34
+ if len(points_group) <= 0:
35
+ raise DevhelmError("No data for ActionTrackGroup")
36
+ for points in points_group:
37
+ if not isinstance(points, list):
38
+ raise TypeError("Invalid point_list: %s" % points)
39
+ for point in points:
40
+ x, y = point # 解包校验
41
+ self._trace_group: List[List[Tuple[int, int]]] = list(points_group)
42
+
43
+ def __getitem__(self, item):
44
+ return self._trace_group[item]
45
+
46
+ @property
47
+ def first_track(self):
48
+ if len(self._trace_group) < 1:
49
+ raise DevhelmError("No data in this ActionTraceGroup")
50
+ return self[0]
51
+
52
+
53
+ class ActionContext:
54
+ """单次操作上下文。"""
55
+
56
+ def __init__(self, level: int = 0):
57
+ self.level = level
58
+ self.reset()
59
+
60
+ def reset(self):
61
+ self.action_desc: Optional[str] = None
62
+ self.action_type: Optional[str] = None
63
+ self.pre_page = None
64
+ self.next_page = None
65
+ self.target_selector = None
66
+ self.target_component = None
67
+ self.target_recognize_page = None
68
+ self.action_runnable = None
69
+ self.allow_skip = False
70
+ self.metrics = None
71
+
72
+ def __repr__(self):
73
+ return str(self.action_desc)
74
+
75
+
76
+ class ActionContextStack:
77
+ """操作上下文栈。
78
+
79
+ 用于嵌套操作时管理上下文层级,向栈底传递页面/控件信息。
80
+ 持有 driver 的 weakref.proxy 避免循环引用,支持 with 语法,
81
+ 仅最顶层 action 通知 HookExtensionManager。
82
+ """
83
+
84
+ def __init__(self, driver):
85
+ self.stack: List[ActionContext] = []
86
+ self.default_context = ActionContext()
87
+ self.driver = weakref.proxy(driver)
88
+ self.last_action: Optional[ActionContext] = None
89
+
90
+ def push(self) -> ActionContext:
91
+ new_context = ActionContext(len(self.stack))
92
+ self.stack.append(new_context)
93
+ return new_context
94
+
95
+ def pop(self, success: bool = True) -> Optional[ActionContext]:
96
+ if len(self.stack) <= 0:
97
+ return None
98
+ current_action_ctx = self.stack.pop()
99
+ self._set_upper_level_info()
100
+ if len(self.stack) <= 0 and success and (not current_action_ctx.allow_skip):
101
+ self.last_action = current_action_ctx
102
+ return current_action_ctx
103
+
104
+ def _set_upper_level_info(self):
105
+ """向栈底传递页面/控件信息。"""
106
+ if len(self.stack) < 1:
107
+ return
108
+ top_item = self.top_item
109
+ for item in reversed(self.stack[:-1]):
110
+ if item.pre_page is None:
111
+ item.pre_page = top_item.pre_page
112
+ if item.next_page is None:
113
+ item.next_page = top_item.next_page
114
+ item.target_recognize_page = top_item.target_recognize_page
115
+ item.target_component = top_item.target_component
116
+ item.target_selector = top_item.target_selector
117
+
118
+ def __len__(self):
119
+ return len(self.stack)
120
+
121
+ @property
122
+ def top_item(self) -> ActionContext:
123
+ if len(self.stack) == 0:
124
+ return self.default_context
125
+ return self.stack[-1]
126
+
127
+ @property
128
+ def bottom_item(self) -> ActionContext:
129
+ if len(self.stack) == 0:
130
+ return self.default_context
131
+ return self.stack[0]
132
+
133
+ def enter_new_context(self, action_type: str, action_desc: str,
134
+ action_runnable=None, metrics=None):
135
+ ctx = self.push()
136
+ ctx.action_type = action_type
137
+ ctx.action_desc = action_desc
138
+ ctx.action_runnable = action_runnable
139
+ ctx.metrics = metrics
140
+ return self
141
+
142
+ def __enter__(self) -> ActionContext:
143
+ return self.top_item
144
+
145
+ def __exit__(self, exc_type, exc_val, exc_tb):
146
+ self.pop(exc_val is None)
147
+ return False
@@ -0,0 +1,50 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """应用与窗口状态类型:AppState / WindowMode / ResizeDirection /
4
+ WindowState / OSType。"""
5
+ from dataclasses import dataclass
6
+ from enum import IntEnum
7
+ from typing import Optional, Tuple
8
+
9
+
10
+ class AppState(IntEnum):
11
+ """应用状态。"""
12
+ FOREGROUND = 0
13
+ BACKGROUND = 1
14
+
15
+
16
+ class WindowMode(IntEnum):
17
+ """窗口模式。"""
18
+ FULLSCREEN = 0
19
+ PRIMARY = 1
20
+ SECONDARY = 2
21
+ FLOATING = 3
22
+
23
+
24
+ class ResizeDirection(IntEnum):
25
+ """窗口调整大小方向。"""
26
+ LEFT = 0
27
+ RIGHT = 1
28
+ TOP = 2
29
+ BOTTOM = 3
30
+ TOP_LEFT = 4
31
+ TOP_RIGHT = 5
32
+ BOTTOM_LEFT = 6
33
+ BOTTOM_RIGHT = 7
34
+
35
+
36
+ @dataclass
37
+ class WindowState:
38
+ """窗口状态。"""
39
+ window_id: str
40
+ mode: WindowMode
41
+ bounds: Optional[Tuple[int, int, int, int]] = None
42
+
43
+ def is_fullscreen(self) -> bool:
44
+ return self.mode == WindowMode.FULLSCREEN
45
+
46
+
47
+ class OSType:
48
+ """操作系统类型(类常量风格)。"""
49
+ OHOS = "OHOS"
50
+ HMOS = "HMOS"
@@ -0,0 +1,22 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """常量定义。"""
4
+
5
+ # 超时与等待
6
+ INPUT_TEXT_WAIT_TIME = 1
7
+ DEFAULT_IDLE_TIME = 0.5
8
+ DEFAULT_SLIDE_TIME = 0.3
9
+ MAX_TIMEOUT = 60
10
+ DEFAULT_TIMEOUT = 5
11
+ WAIT_FOR_INSTALL_TIME = 3
12
+ FAST_SWIPE_TIME = 0.25
13
+ SWIPE_INTERVAL = 0.5
14
+
15
+ # 手势参数
16
+ NAV_GESTURE_SPEED = 8000
17
+ DEFAULT_SWIPE_DURATION = 0.5
18
+ DEFAULT_DOUBLE_CLICK_INTERVAL = 0.15
19
+ DEFAULT_LONG_CLICK_INTERVAL = 1
20
+
21
+ # 内部常量
22
+ FULL_DRIVER_TMP_KEY = "_tmp_full_uidriver"
@@ -0,0 +1,32 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """显示器相关类型:DisplayRotation / DisplayInfo。"""
4
+ from dataclasses import dataclass
5
+ from enum import IntEnum
6
+ from typing import Tuple
7
+
8
+
9
+ class DisplayRotation(IntEnum):
10
+ """屏幕旋转状态。"""
11
+ PORTRAIT = 0
12
+ LANDSCAPE = 1
13
+ REVERSE_PORTRAIT = 2
14
+ REVERSE_LANDSCAPE = 3
15
+
16
+
17
+ @dataclass
18
+ class DisplayInfo:
19
+ """显示器信息。"""
20
+ display_id: int
21
+ width: int
22
+ height: int
23
+ density: int
24
+ rotation: DisplayRotation = DisplayRotation.PORTRAIT
25
+
26
+ @property
27
+ def size(self) -> Tuple[int, int]:
28
+ return (self.width, self.height)
29
+
30
+ def is_portrait(self) -> bool:
31
+ return self.rotation in (DisplayRotation.PORTRAIT,
32
+ DisplayRotation.REVERSE_PORTRAIT)
@@ -0,0 +1,24 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """FormatString:带变量的模板字符串。
4
+
5
+ 通过 __getattr__ 转发 template 字符串的方法,使其可像 str 一样使用。
6
+ """
7
+
8
+
9
+ class FormatString:
10
+ """格式化字符串(带变量的模板字符串)。"""
11
+
12
+ def __init__(self, template: str, **kwargs):
13
+ self.template = template
14
+ self.variables = kwargs
15
+
16
+ def __getattr__(self, item):
17
+ # 转发 template 字符串的方法
18
+ return getattr(self.template, item)
19
+
20
+ def __str__(self):
21
+ return self.template
22
+
23
+ def __repr__(self):
24
+ return self.template
@@ -0,0 +1,50 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """输入设备与手势类型:InputDevice / MouseButton / GestureStep /
4
+ TouchPadSwipeOptions / GestureAction。"""
5
+ from dataclasses import dataclass, field
6
+ from enum import IntEnum
7
+ from typing import List, Optional
8
+
9
+
10
+ class InputDevice(IntEnum):
11
+ """输入设备类型。"""
12
+ TOUCH = 0
13
+ MOUSE = 1
14
+ PEN = 2
15
+ KNUCKLE = 3
16
+ TOUCHPAD = 4
17
+
18
+
19
+ class MouseButton(IntEnum):
20
+ """鼠标按键。"""
21
+ LEFT = 0
22
+ RIGHT = 1
23
+ MIDDLE = 2
24
+
25
+
26
+ @dataclass
27
+ class GestureStep:
28
+ """手势步骤。"""
29
+ action: str
30
+ x: int
31
+ y: int
32
+ duration: float = 0.0
33
+
34
+
35
+ @dataclass
36
+ class TouchPadSwipeOptions:
37
+ """触控板滑动选项。"""
38
+ speed: Optional[int] = None
39
+ step_length: Optional[int] = None
40
+
41
+
42
+ @dataclass
43
+ class GestureAction:
44
+ """手势动作。"""
45
+ steps: List[GestureStep] = field(default_factory=list)
46
+ input_device: InputDevice = InputDevice.TOUCH
47
+
48
+ def add_step(self, action: str, x: int, y: int, duration: float = 0.0):
49
+ """添加手势步骤。"""
50
+ self.steps.append(GestureStep(action=action, x=x, y=y, duration=duration))
@@ -0,0 +1,42 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """JsonBase:JSON 序列化基类。
4
+
5
+ 过滤 None 值、支持 api_level 参数、不使用 @dataclass,确保 RPC 协议兼容性。
6
+ """
7
+ import json
8
+ from typing import Any, Dict
9
+
10
+
11
+ class JsonBase:
12
+ """JSON 序列化基类。"""
13
+
14
+ def to_dict(self, api_level: int = 9) -> Dict[str, Any]:
15
+ """转换为字典(过滤 None 值)。
16
+
17
+ Args:
18
+ api_level: API 版本级别,用于版本兼容。
19
+ """
20
+ result = {}
21
+ for key, value in self.__dict__.items():
22
+ if value is not None:
23
+ result[key] = value
24
+ return result
25
+
26
+ def to_json(self) -> str:
27
+ """转换为 JSON 字符串。"""
28
+ return json.dumps(self.to_dict(), ensure_ascii=False,
29
+ separators=(',', ':'))
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: Dict[str, Any]):
33
+ """从字典创建实例。"""
34
+ obj = cls.__new__(cls)
35
+ for key, value in data.items():
36
+ setattr(obj, key, value)
37
+ return obj
38
+
39
+ @classmethod
40
+ def from_json(cls, json_str: str):
41
+ """从 JSON 字符串创建实例。"""
42
+ return cls.from_dict(json.loads(json_str))