kotonebot 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.
Files changed (107) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -312
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +1002 -1002
  6. kotonebot/backend/context/task_action.py +183 -183
  7. kotonebot/backend/core.py +86 -129
  8. kotonebot/backend/debug/entry.py +89 -89
  9. kotonebot/backend/debug/mock.py +78 -78
  10. kotonebot/backend/debug/server.py +222 -222
  11. kotonebot/backend/debug/vars.py +351 -351
  12. kotonebot/backend/dispatch.py +227 -227
  13. kotonebot/backend/flow_controller.py +196 -196
  14. kotonebot/backend/image.py +36 -5
  15. kotonebot/backend/loop.py +222 -208
  16. kotonebot/backend/ocr.py +535 -535
  17. kotonebot/backend/preprocessor.py +103 -103
  18. kotonebot/client/__init__.py +9 -9
  19. kotonebot/client/device.py +369 -529
  20. kotonebot/client/fast_screenshot.py +377 -377
  21. kotonebot/client/host/__init__.py +43 -43
  22. kotonebot/client/host/adb_common.py +101 -107
  23. kotonebot/client/host/custom.py +118 -118
  24. kotonebot/client/host/leidian_host.py +196 -196
  25. kotonebot/client/host/mumu12_host.py +353 -353
  26. kotonebot/client/host/protocol.py +214 -214
  27. kotonebot/client/host/windows_common.py +73 -58
  28. kotonebot/client/implements/__init__.py +65 -70
  29. kotonebot/client/implements/adb.py +89 -89
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -11
  31. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  32. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  33. kotonebot/client/implements/remote_windows.py +188 -188
  34. kotonebot/client/implements/uiautomator2.py +85 -85
  35. kotonebot/client/implements/windows/__init__.py +1 -0
  36. kotonebot/client/implements/windows/print_window.py +133 -0
  37. kotonebot/client/implements/windows/send_message.py +324 -0
  38. kotonebot/client/implements/{windows.py → windows/windows.py} +175 -176
  39. kotonebot/client/protocol.py +69 -69
  40. kotonebot/client/registration.py +24 -24
  41. kotonebot/client/scaler.py +467 -0
  42. kotonebot/config/base_config.py +103 -96
  43. kotonebot/config/config.py +61 -0
  44. kotonebot/config/manager.py +36 -36
  45. kotonebot/core/__init__.py +13 -0
  46. kotonebot/core/entities/base.py +182 -0
  47. kotonebot/core/entities/compound.py +75 -0
  48. kotonebot/core/entities/ocr.py +117 -0
  49. kotonebot/core/entities/template_match.py +198 -0
  50. kotonebot/devtools/__init__.py +42 -0
  51. kotonebot/devtools/cli/__init__.py +6 -0
  52. kotonebot/devtools/cli/main.py +53 -0
  53. kotonebot/{tools → devtools}/mirror.py +354 -354
  54. kotonebot/devtools/project/project.py +41 -0
  55. kotonebot/devtools/project/scanner.py +202 -0
  56. kotonebot/devtools/project/schema.py +99 -0
  57. kotonebot/devtools/resgen/__init__.py +42 -0
  58. kotonebot/devtools/resgen/codegen.py +331 -0
  59. kotonebot/devtools/resgen/core.py +94 -0
  60. kotonebot/devtools/resgen/parsers.py +360 -0
  61. kotonebot/devtools/resgen/utils.py +158 -0
  62. kotonebot/devtools/resgen/validation.py +115 -0
  63. kotonebot/devtools/web/dist/assets/bootstrap-icons-BOrJxbIo.woff +0 -0
  64. kotonebot/devtools/web/dist/assets/bootstrap-icons-BtvjY1KL.woff2 +0 -0
  65. kotonebot/devtools/web/dist/assets/ext-language_tools-CD021WJ2.js +2577 -0
  66. kotonebot/devtools/web/dist/assets/index-B_m5f2LF.js +2836 -0
  67. kotonebot/devtools/web/dist/assets/index-BlEDyGGa.css +9 -0
  68. kotonebot/devtools/web/dist/assets/language-client-C9muzqaq.js +128 -0
  69. kotonebot/devtools/web/dist/assets/mode-python-CtHp76XS.js +476 -0
  70. kotonebot/devtools/web/dist/icons/symbol-class.svg +3 -0
  71. kotonebot/devtools/web/dist/icons/symbol-file.svg +3 -0
  72. kotonebot/devtools/web/dist/icons/symbol-method.svg +3 -0
  73. kotonebot/devtools/web/dist/index.html +25 -0
  74. kotonebot/devtools/web/server/__init__.py +0 -0
  75. kotonebot/devtools/web/server/rest_api.py +217 -0
  76. kotonebot/devtools/web/server/server.py +85 -0
  77. kotonebot/errors.py +76 -76
  78. kotonebot/interop/win/__init__.py +13 -9
  79. kotonebot/interop/win/_mouse.py +310 -310
  80. kotonebot/interop/win/message_box.py +313 -313
  81. kotonebot/interop/win/reg.py +37 -37
  82. kotonebot/interop/win/shake_mouse.py +224 -0
  83. kotonebot/interop/win/shortcut.py +43 -43
  84. kotonebot/interop/win/task_dialog.py +513 -513
  85. kotonebot/interop/win/window.py +89 -0
  86. kotonebot/logging/__init__.py +2 -2
  87. kotonebot/logging/log.py +17 -17
  88. kotonebot/primitives/__init__.py +19 -17
  89. kotonebot/primitives/geometry.py +1067 -862
  90. kotonebot/primitives/visual.py +143 -63
  91. kotonebot/ui/file_host/sensio.py +36 -36
  92. kotonebot/ui/file_host/tmp_send.py +54 -54
  93. kotonebot/ui/pushkit/__init__.py +3 -3
  94. kotonebot/ui/pushkit/image_host.py +88 -88
  95. kotonebot/ui/pushkit/protocol.py +13 -13
  96. kotonebot/ui/pushkit/wxpusher.py +54 -54
  97. kotonebot/ui/user.py +148 -148
  98. kotonebot/util.py +436 -436
  99. {kotonebot-0.5.0.dist-info → kotonebot-0.7.0.dist-info}/METADATA +84 -82
  100. kotonebot-0.7.0.dist-info/RECORD +109 -0
  101. {kotonebot-0.5.0.dist-info → kotonebot-0.7.0.dist-info}/WHEEL +1 -1
  102. kotonebot-0.7.0.dist-info/entry_points.txt +2 -0
  103. {kotonebot-0.5.0.dist-info → kotonebot-0.7.0.dist-info}/licenses/LICENSE +673 -673
  104. kotonebot/client/implements/adb_raw.py +0 -163
  105. kotonebot-0.5.0.dist-info/RECORD +0 -71
  106. /kotonebot/{tools → devtools/project}/__init__.py +0 -0
  107. {kotonebot-0.5.0.dist-info → kotonebot-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,176 +1,175 @@
1
- # ruff: noqa: E402
2
- from kotonebot.util import require_windows
3
- require_windows('"WindowsImpl" implementation')
4
-
5
- from ctypes import windll
6
- from typing import Literal
7
- from importlib import resources
8
- from functools import cached_property
9
- from dataclasses import dataclass
10
-
11
- import cv2
12
- import numpy as np
13
- try:
14
- import win32ui
15
- import win32gui
16
- from ahk import AHK, MsgBoxIcon
17
- except ImportError as _e:
18
- from kotonebot.errors import MissingDependencyError
19
- raise MissingDependencyError(_e, 'windows')
20
- from cv2.typing import MatLike
21
-
22
- from ..device import Device, WindowsDevice
23
- from ..protocol import Commandable, Touchable, Screenshotable
24
- from ..registration import ImplConfig
25
-
26
- # 1. 定义配置模型
27
- @dataclass
28
- class WindowsImplConfig(ImplConfig):
29
- window_title: str
30
- ahk_exe_path: str
31
-
32
- class WindowsImpl(Touchable, Screenshotable):
33
- def __init__(self, device: Device, window_title: str, ahk_exe_path: str):
34
- self.__hwnd: int | None = None
35
- self.window_title = window_title
36
- self.ahk = AHK(executable_path=ahk_exe_path)
37
- self.device = device
38
-
39
- # 设置 DPI aware,否则高缩放显示器上返回的坐标会错误
40
- windll.user32.SetProcessDPIAware()
41
- # TODO: 这个应该移动到其他地方去
42
- def _stop():
43
- from kotonebot.backend.context.context import vars
44
- vars.flow.request_interrupt()
45
- self.ahk.msg_box('任务已停止。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
46
-
47
- def _toggle_pause():
48
- from kotonebot.backend.context.context import vars
49
- if vars.flow.is_paused:
50
- self.ahk.msg_box('任务即将恢复。\n关闭此消息框后将会继续执行', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
51
- vars.flow.request_resume()
52
- else:
53
- vars.flow.request_pause()
54
- self.ahk.msg_box('任务已暂停。\n关闭此消息框后再按一次快捷键恢复执行。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
55
-
56
- self.ahk.add_hotkey('^F4', _toggle_pause) # Ctrl+F4 暂停/恢复
57
- self.ahk.add_hotkey('^F3', _stop) # Ctrl+F3 停止
58
- self.ahk.start_hotkeys()
59
- # 将点击坐标设置为相对 Client
60
- self.ahk.set_coord_mode('Mouse', 'Client')
61
-
62
- @property
63
- def hwnd(self) -> int:
64
- if self.__hwnd is None:
65
- self.__hwnd = win32gui.FindWindow(None, self.window_title)
66
- if self.__hwnd is None or self.__hwnd == 0:
67
- raise RuntimeError(f'Failed to find window: {self.window_title}')
68
- return self.__hwnd
69
-
70
- def __client_rect(self) -> tuple[int, int, int, int]:
71
- """获取 Client 区域屏幕坐标"""
72
- hwnd = self.hwnd
73
- client_left, client_top, client_right, client_bottom = win32gui.GetClientRect(hwnd)
74
- client_left, client_top = win32gui.ClientToScreen(hwnd, (client_left, client_top))
75
- client_right, client_bottom = win32gui.ClientToScreen(hwnd, (client_right, client_bottom))
76
- return client_left, client_top, client_right, client_bottom
77
-
78
- def __client_to_screen(self, hwnd: int, x: int, y: int) -> tuple[int, int]:
79
- """将 Client 区域坐标转换为屏幕坐标"""
80
- return win32gui.ClientToScreen(hwnd, (x, y))
81
-
82
- def screenshot(self) -> MatLike:
83
- if not self.ahk.win_is_active(self.window_title):
84
- self.ahk.win_activate(self.window_title)
85
- hwnd = self.hwnd
86
-
87
- # TODO: 需要检查下面这些 WinAPI 的返回结果
88
- # 获取整个窗口的坐标
89
- left, top, right, bot = win32gui.GetWindowRect(hwnd)
90
- w = right - left
91
- h = bot - top
92
-
93
- # 获取客户区域的坐标
94
- client_left, client_top, client_right, client_bot = self.__client_rect()
95
-
96
- # 获取整个屏幕的截图
97
- hwndDC = win32gui.GetWindowDC(0)
98
- mfcDC = win32ui.CreateDCFromHandle(hwndDC)
99
- saveDC = mfcDC.CreateCompatibleDC()
100
-
101
- saveBitMap = win32ui.CreateBitmap()
102
- saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
103
-
104
- saveDC.SelectObject(saveBitMap)
105
-
106
- # 截图整个屏幕
107
- result = windll.gdi32.BitBlt(saveDC.GetSafeHdc(), 0, 0, w, h, mfcDC.GetSafeHdc(), left, top, 0x00CC0020)
108
-
109
- # 将截图转换为OpenCV格式
110
- bmpinfo = saveBitMap.GetInfo()
111
- bmpstr = saveBitMap.GetBitmapBits(True)
112
- im = np.frombuffer(bmpstr, dtype=np.uint8)
113
- im = im.reshape((bmpinfo['bmHeight'], bmpinfo['bmWidth'], 4))
114
-
115
- # 裁剪出客户区域
116
- cropped_im = im[client_top - top:client_bot - top, client_left - left:client_right - left]
117
-
118
- # 释放资源
119
- win32gui.DeleteObject(saveBitMap.GetHandle())
120
- saveDC.DeleteDC()
121
- mfcDC.DeleteDC()
122
- win32gui.ReleaseDC(hwnd, hwndDC)
123
-
124
- # RGBA 转换为 RGB
125
- cropped_im = cv2.cvtColor(cropped_im, cv2.COLOR_RGBA2RGB)
126
- return cropped_im
127
-
128
- @property
129
- def screen_size(self) -> tuple[int, int]:
130
- left, top, right, bot = self.__client_rect()
131
- w = right - left
132
- h = bot - top
133
- return w, h
134
-
135
- def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
136
- pos = self.ahk.win_get_position(self.window_title)
137
- if pos is None:
138
- return None
139
- w, h = pos.width, pos.height
140
- if w > h:
141
- return 'landscape'
142
- else:
143
- return 'portrait'
144
-
145
- def click(self, x: int, y: int) -> None:
146
- # x, y = self.__client_to_screen(self.hwnd, x, y)
147
- # (0, 0) 很可能会点到窗口边框上
148
- if x == 0:
149
- x = 2
150
- if y == 0:
151
- y = 2
152
- if not self.ahk.win_is_active(self.window_title):
153
- self.ahk.win_activate(self.window_title)
154
- self.ahk.click(x, y)
155
-
156
- def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
157
- if not self.ahk.win_is_active(self.window_title):
158
- self.ahk.win_activate(self.window_title)
159
- # TODO: 这个 speed 的单位是什么?
160
- self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
161
-
162
- if __name__ == '__main__':
163
- from ..device import Device
164
- device = Device()
165
- # 在测试环境中直接使用默认路径
166
- ahk_path = str(resources.files('kaa.res.bin') / 'AutoHotkey.exe')
167
- impl = WindowsImpl(device, window_title='gakumas', ahk_exe_path=ahk_path)
168
- device._screenshot = impl
169
- device._touch = impl
170
- device.swipe_scaled(0.5, 0.8, 0.5, 0.2)
171
- # impl.swipe(0, 100, 0, 0)
172
- # impl.click(100, 100)
173
- # while True:
174
- # im = impl.screenshot()
175
- # cv2.imshow('test', im)
176
- # cv2.waitKey(1)
1
+ # ruff: noqa: E402
2
+ from kotonebot.util import require_windows
3
+ require_windows('"WindowsImpl" implementation')
4
+
5
+ from ctypes import windll
6
+ from typing import Literal
7
+ from importlib import resources
8
+ from dataclasses import dataclass
9
+
10
+ import cv2
11
+ import numpy as np
12
+ try:
13
+ import win32ui
14
+ import win32gui
15
+ from ahk import AHK, MsgBoxIcon
16
+ except ImportError as _e:
17
+ from kotonebot.errors import MissingDependencyError
18
+ raise MissingDependencyError(_e, 'windows')
19
+ from cv2.typing import MatLike
20
+
21
+ from ...device import Device
22
+ from ...protocol import Touchable, Screenshotable
23
+ from ...registration import ImplConfig
24
+
25
+ # 1. 定义配置模型
26
+ @dataclass
27
+ class WindowsImplConfig(ImplConfig):
28
+ window_title: str
29
+ ahk_exe_path: str
30
+
31
+ class WindowsImpl(Touchable, Screenshotable):
32
+ def __init__(self, device: Device, window_title: str, ahk_exe_path: str):
33
+ self.__hwnd: int | None = None
34
+ self.window_title = window_title
35
+ self.ahk = AHK(executable_path=ahk_exe_path)
36
+ self.device = device
37
+
38
+ # 设置 DPI aware,否则高缩放显示器上返回的坐标会错误
39
+ windll.user32.SetProcessDPIAware()
40
+ # TODO: 这个应该移动到其他地方去
41
+ def _stop():
42
+ from kotonebot.backend.context.context import vars
43
+ vars.flow.request_interrupt()
44
+ self.ahk.msg_box('任务已停止。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
45
+
46
+ def _toggle_pause():
47
+ from kotonebot.backend.context.context import vars
48
+ if vars.flow.is_paused:
49
+ self.ahk.msg_box('任务即将恢复。\n关闭此消息框后将会继续执行', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
50
+ vars.flow.request_resume()
51
+ else:
52
+ vars.flow.request_pause()
53
+ self.ahk.msg_box('任务已暂停。\n关闭此消息框后再按一次快捷键恢复执行。', title='琴音小助手', icon=MsgBoxIcon.EXCLAMATION)
54
+
55
+ self.ahk.add_hotkey('^F4', _toggle_pause) # Ctrl+F4 暂停/恢复
56
+ self.ahk.add_hotkey('^F3', _stop) # Ctrl+F3 停止
57
+ self.ahk.start_hotkeys()
58
+ # 将点击坐标设置为相对 Client
59
+ self.ahk.set_coord_mode('Mouse', 'Client')
60
+
61
+ @property
62
+ def hwnd(self) -> int:
63
+ if self.__hwnd is None:
64
+ self.__hwnd = win32gui.FindWindow(None, self.window_title)
65
+ if self.__hwnd is None or self.__hwnd == 0:
66
+ raise RuntimeError(f'Failed to find window: {self.window_title}')
67
+ return self.__hwnd
68
+
69
+ def __client_rect(self) -> tuple[int, int, int, int]:
70
+ """获取 Client 区域屏幕坐标"""
71
+ hwnd = self.hwnd
72
+ client_left, client_top, client_right, client_bottom = win32gui.GetClientRect(hwnd)
73
+ client_left, client_top = win32gui.ClientToScreen(hwnd, (client_left, client_top))
74
+ client_right, client_bottom = win32gui.ClientToScreen(hwnd, (client_right, client_bottom))
75
+ return client_left, client_top, client_right, client_bottom
76
+
77
+ def __client_to_screen(self, hwnd: int, x: int, y: int) -> tuple[int, int]:
78
+ """将 Client 区域坐标转换为屏幕坐标"""
79
+ return win32gui.ClientToScreen(hwnd, (x, y))
80
+
81
+ def screenshot(self) -> MatLike:
82
+ if not self.ahk.win_is_active(self.window_title):
83
+ self.ahk.win_activate(self.window_title)
84
+ hwnd = self.hwnd
85
+
86
+ # TODO: 需要检查下面这些 WinAPI 的返回结果
87
+ # 获取整个窗口的坐标
88
+ left, top, right, bot = win32gui.GetWindowRect(hwnd)
89
+ w = right - left
90
+ h = bot - top
91
+
92
+ # 获取客户区域的坐标
93
+ client_left, client_top, client_right, client_bot = self.__client_rect()
94
+
95
+ # 获取整个屏幕的截图
96
+ hwndDC = win32gui.GetWindowDC(0)
97
+ mfcDC = win32ui.CreateDCFromHandle(hwndDC)
98
+ saveDC = mfcDC.CreateCompatibleDC()
99
+
100
+ saveBitMap = win32ui.CreateBitmap()
101
+ saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
102
+
103
+ saveDC.SelectObject(saveBitMap)
104
+
105
+ # 截图整个屏幕
106
+ result = windll.gdi32.BitBlt(saveDC.GetSafeHdc(), 0, 0, w, h, mfcDC.GetSafeHdc(), left, top, 0x00CC0020)
107
+
108
+ # 将截图转换为OpenCV格式
109
+ bmpinfo = saveBitMap.GetInfo()
110
+ bmpstr = saveBitMap.GetBitmapBits(True)
111
+ im = np.frombuffer(bmpstr, dtype=np.uint8)
112
+ im = im.reshape((bmpinfo['bmHeight'], bmpinfo['bmWidth'], 4))
113
+
114
+ # 裁剪出客户区域
115
+ cropped_im = im[client_top - top:client_bot - top, client_left - left:client_right - left]
116
+
117
+ # 释放资源
118
+ win32gui.DeleteObject(saveBitMap.GetHandle())
119
+ saveDC.DeleteDC()
120
+ mfcDC.DeleteDC()
121
+ win32gui.ReleaseDC(hwnd, hwndDC)
122
+
123
+ # 将 RGBA 转换为 RGB
124
+ cropped_im = cv2.cvtColor(cropped_im, cv2.COLOR_RGBA2RGB)
125
+ return cropped_im
126
+
127
+ @property
128
+ def screen_size(self) -> tuple[int, int]:
129
+ left, top, right, bot = self.__client_rect()
130
+ w = right - left
131
+ h = bot - top
132
+ return w, h
133
+
134
+ def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
135
+ pos = self.ahk.win_get_position(self.window_title)
136
+ if pos is None:
137
+ return None
138
+ w, h = pos.width, pos.height
139
+ if w > h:
140
+ return 'landscape'
141
+ else:
142
+ return 'portrait'
143
+
144
+ def click(self, x: int, y: int) -> None:
145
+ # x, y = self.__client_to_screen(self.hwnd, x, y)
146
+ # (0, 0) 很可能会点到窗口边框上
147
+ if x == 0:
148
+ x = 2
149
+ if y == 0:
150
+ y = 2
151
+ if not self.ahk.win_is_active(self.window_title):
152
+ self.ahk.win_activate(self.window_title)
153
+ self.ahk.click(x, y)
154
+
155
+ def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
156
+ if not self.ahk.win_is_active(self.window_title):
157
+ self.ahk.win_activate(self.window_title)
158
+ # TODO: 这个 speed 的单位是什么?
159
+ self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
160
+
161
+ if __name__ == '__main__':
162
+ from ...device import Device
163
+ device = Device()
164
+ # 在测试环境中直接使用默认路径
165
+ ahk_path = str(resources.files('kaa.res.bin') / 'AutoHotkey.exe')
166
+ impl = WindowsImpl(device, window_title='gakumas', ahk_exe_path=ahk_path)
167
+ device._screenshot = impl
168
+ device._touch = impl
169
+ device.swipe_scaled(0.5, 0.8, 0.5, 0.2)
170
+ # impl.swipe(0, 100, 0, 0)
171
+ # impl.click(100, 100)
172
+ # while True:
173
+ # im = impl.screenshot()
174
+ # cv2.imshow('test', im)
175
+ # cv2.waitKey(1)
@@ -1,69 +1,69 @@
1
- from typing import Protocol, TYPE_CHECKING, runtime_checkable, Literal
2
-
3
- from cv2.typing import MatLike
4
-
5
- from kotonebot.primitives import Rect
6
- if TYPE_CHECKING:
7
- from .device import Device
8
-
9
- @runtime_checkable
10
- class ClickableObjectProtocol(Protocol):
11
- """
12
- 可点击对象的协议
13
- """
14
- @property
15
- def rect(self) -> Rect:
16
- ...
17
-
18
- class DeviceScreenshotProtocol(Protocol):
19
- def screenshot(self) -> MatLike:
20
- """
21
- 截图
22
- """
23
- ...
24
-
25
- @runtime_checkable
26
- class Commandable(Protocol):
27
- def __init__(self, device: 'Device'): ...
28
- def launch_app(self, package_name: str) -> None: ...
29
- def current_package(self) -> str | None: ...
30
-
31
- @runtime_checkable
32
- class AndroidCommandable(Protocol):
33
- """定义 Android 平台的特定命令"""
34
- def launch_app(self, package_name: str) -> None: ...
35
- def current_package(self) -> str | None: ...
36
- def adb_shell(self, cmd: str) -> str: ...
37
-
38
- @runtime_checkable
39
- class WindowsCommandable(Protocol):
40
- """定义 Windows 平台的特定命令"""
41
- def get_foreground_window(self) -> tuple[int, str]: ...
42
- def exec_command(self, command: str) -> tuple[int, str, str]: ...
43
-
44
- @runtime_checkable
45
- class Screenshotable(Protocol):
46
- def __init__(self, device: 'Device'): ...
47
- @property
48
- def screen_size(self) -> tuple[int, int]:
49
- """
50
- 屏幕尺寸。格式为 `(width, height)`。
51
-
52
- **注意**: 此属性返回的分辨率会随设备方向变化。
53
- 如果 `self.orientation` 为 `landscape`,则返回的分辨率是横屏下的分辨率,
54
- 否则返回竖屏下的分辨率。
55
-
56
- `self.orientation` 属性默认为竖屏。如果需要自动检测,
57
- 调用 `self.detect_orientation()` 方法。
58
- 如果已知方向,也可以直接设置 `self.orientation` 属性。
59
- """
60
- ...
61
-
62
- def detect_orientation(self) -> Literal['portrait', 'landscape'] | None: ...
63
- def screenshot(self) -> MatLike: ...
64
-
65
- @runtime_checkable
66
- class Touchable(Protocol):
67
- def __init__(self, device: 'Device'): ...
68
- def click(self, x: int, y: int) -> None: ...
69
- def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float|None = None) -> None: ...
1
+ from typing import Protocol, TYPE_CHECKING, runtime_checkable, Literal
2
+
3
+ from cv2.typing import MatLike
4
+
5
+ from kotonebot.primitives import Rect
6
+ if TYPE_CHECKING:
7
+ from .device import Device
8
+
9
+ @runtime_checkable
10
+ class ClickableObjectProtocol(Protocol):
11
+ """
12
+ 可点击对象的协议
13
+ """
14
+ @property
15
+ def rect(self) -> Rect:
16
+ ...
17
+
18
+ class DeviceScreenshotProtocol(Protocol):
19
+ def screenshot(self) -> MatLike:
20
+ """
21
+ 截图
22
+ """
23
+ ...
24
+
25
+ @runtime_checkable
26
+ class Commandable(Protocol):
27
+ def __init__(self, device: 'Device'): ...
28
+ def launch_app(self, package_name: str) -> None: ...
29
+ def current_package(self) -> str | None: ...
30
+
31
+ @runtime_checkable
32
+ class AndroidCommandable(Protocol):
33
+ """定义 Android 平台的特定命令"""
34
+ def launch_app(self, package_name: str) -> None: ...
35
+ def current_package(self) -> str | None: ...
36
+ def adb_shell(self, cmd: str) -> str: ...
37
+
38
+ @runtime_checkable
39
+ class WindowsCommandable(Protocol):
40
+ """定义 Windows 平台的特定命令"""
41
+ def get_foreground_window(self) -> tuple[int, str]: ...
42
+ def exec_command(self, command: str) -> tuple[int, str, str]: ...
43
+
44
+ @runtime_checkable
45
+ class Screenshotable(Protocol):
46
+ def __init__(self, device: 'Device'): ...
47
+ @property
48
+ def screen_size(self) -> tuple[int, int]:
49
+ """
50
+ 屏幕尺寸。格式为 `(width, height)`。
51
+
52
+ **注意**: 此属性返回的分辨率会随设备方向变化。
53
+ 如果 `self.orientation` 为 `landscape`,则返回的分辨率是横屏下的分辨率,
54
+ 否则返回竖屏下的分辨率。
55
+
56
+ `self.orientation` 属性默认为竖屏。如果需要自动检测,
57
+ 调用 `self.detect_orientation()` 方法。
58
+ 如果已知方向,也可以直接设置 `self.orientation` 属性。
59
+ """
60
+ ...
61
+
62
+ def detect_orientation(self) -> Literal['portrait', 'landscape'] | None: ...
63
+ def screenshot(self) -> MatLike: ...
64
+
65
+ @runtime_checkable
66
+ class Touchable(Protocol):
67
+ def __init__(self, device: 'Device'): ...
68
+ def click(self, x: int, y: int) -> None: ...
69
+ def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float|None = None) -> None: ...
@@ -1,24 +1,24 @@
1
- from dataclasses import dataclass
2
- from typing import TypeVar, Callable, Dict, Type, Any, overload, Literal, cast, TYPE_CHECKING
3
-
4
- from ..errors import KotonebotError
5
- from .device import Device
6
- if TYPE_CHECKING:
7
- from .implements.adb import AdbImplConfig
8
- from .implements.remote_windows import RemoteWindowsImplConfig
9
- from .implements.windows import WindowsImplConfig
10
- from .implements.nemu_ipc import NemuIpcImplConfig
11
-
12
- AdbBasedImpl = Literal['adb', 'adb_raw', 'uiautomator2']
13
- DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows', 'nemu_ipc']
14
-
15
- # --- 核心类型定义 ---
16
-
17
- class ImplRegistrationError(KotonebotError):
18
- """与 impl 注册相关的错误"""
19
- pass
20
-
21
- @dataclass
22
- class ImplConfig:
23
- """所有设备实现配置模型的名义上的基类,便于类型约束。"""
24
- pass
1
+ from dataclasses import dataclass
2
+ from typing import TypeVar, Callable, Dict, Type, Any, overload, Literal, cast, TYPE_CHECKING
3
+
4
+ from ..errors import KotonebotError
5
+ from .device import Device
6
+ if TYPE_CHECKING:
7
+ from .implements.adb import AdbImplConfig
8
+ from .implements.remote_windows import RemoteWindowsImplConfig
9
+ from .implements.windows import WindowsImplConfig
10
+ from .implements.nemu_ipc import NemuIpcImplConfig
11
+
12
+ AdbBasedImpl = Literal['adb', 'uiautomator2']
13
+ DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows', 'nemu_ipc']
14
+
15
+ # --- 核心类型定义 ---
16
+
17
+ class ImplRegistrationError(KotonebotError):
18
+ """与 impl 注册相关的错误"""
19
+ pass
20
+
21
+ @dataclass
22
+ class ImplConfig:
23
+ """所有设备实现配置模型的名义上的基类,便于类型约束。"""
24
+ pass