kotonebot 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 (70) hide show
  1. kotonebot/__init__.py +40 -0
  2. kotonebot/backend/__init__.py +0 -0
  3. kotonebot/backend/bot.py +302 -0
  4. kotonebot/backend/color.py +525 -0
  5. kotonebot/backend/context/__init__.py +3 -0
  6. kotonebot/backend/context/context.py +1001 -0
  7. kotonebot/backend/context/task_action.py +176 -0
  8. kotonebot/backend/core.py +126 -0
  9. kotonebot/backend/debug/__init__.py +1 -0
  10. kotonebot/backend/debug/entry.py +89 -0
  11. kotonebot/backend/debug/mock.py +79 -0
  12. kotonebot/backend/debug/server.py +223 -0
  13. kotonebot/backend/debug/vars.py +346 -0
  14. kotonebot/backend/dispatch.py +228 -0
  15. kotonebot/backend/flow_controller.py +197 -0
  16. kotonebot/backend/image.py +748 -0
  17. kotonebot/backend/loop.py +277 -0
  18. kotonebot/backend/ocr.py +511 -0
  19. kotonebot/backend/preprocessor.py +103 -0
  20. kotonebot/client/__init__.py +10 -0
  21. kotonebot/client/device.py +500 -0
  22. kotonebot/client/fast_screenshot.py +378 -0
  23. kotonebot/client/host/__init__.py +12 -0
  24. kotonebot/client/host/adb_common.py +94 -0
  25. kotonebot/client/host/custom.py +114 -0
  26. kotonebot/client/host/leidian_host.py +202 -0
  27. kotonebot/client/host/mumu12_host.py +245 -0
  28. kotonebot/client/host/protocol.py +213 -0
  29. kotonebot/client/host/windows_common.py +55 -0
  30. kotonebot/client/implements/__init__.py +7 -0
  31. kotonebot/client/implements/adb.py +85 -0
  32. kotonebot/client/implements/adb_raw.py +159 -0
  33. kotonebot/client/implements/nemu_ipc/__init__.py +8 -0
  34. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +280 -0
  35. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -0
  36. kotonebot/client/implements/remote_windows.py +193 -0
  37. kotonebot/client/implements/uiautomator2.py +82 -0
  38. kotonebot/client/implements/windows.py +168 -0
  39. kotonebot/client/protocol.py +69 -0
  40. kotonebot/client/registration.py +24 -0
  41. kotonebot/config/__init__.py +1 -0
  42. kotonebot/config/base_config.py +96 -0
  43. kotonebot/config/manager.py +36 -0
  44. kotonebot/errors.py +72 -0
  45. kotonebot/interop/win/__init__.py +0 -0
  46. kotonebot/interop/win/message_box.py +314 -0
  47. kotonebot/interop/win/reg.py +37 -0
  48. kotonebot/interop/win/shortcut.py +43 -0
  49. kotonebot/interop/win/task_dialog.py +469 -0
  50. kotonebot/logging/__init__.py +2 -0
  51. kotonebot/logging/log.py +18 -0
  52. kotonebot/primitives/__init__.py +17 -0
  53. kotonebot/primitives/geometry.py +290 -0
  54. kotonebot/primitives/visual.py +63 -0
  55. kotonebot/tools/__init__.py +0 -0
  56. kotonebot/tools/mirror.py +354 -0
  57. kotonebot/ui/__init__.py +0 -0
  58. kotonebot/ui/file_host/sensio.py +36 -0
  59. kotonebot/ui/file_host/tmp_send.py +54 -0
  60. kotonebot/ui/pushkit/__init__.py +3 -0
  61. kotonebot/ui/pushkit/image_host.py +87 -0
  62. kotonebot/ui/pushkit/protocol.py +13 -0
  63. kotonebot/ui/pushkit/wxpusher.py +53 -0
  64. kotonebot/ui/user.py +144 -0
  65. kotonebot/util.py +409 -0
  66. kotonebot-0.1.0.dist-info/METADATA +204 -0
  67. kotonebot-0.1.0.dist-info/RECORD +70 -0
  68. kotonebot-0.1.0.dist-info/WHEEL +5 -0
  69. kotonebot-0.1.0.dist-info/licenses/LICENSE +674 -0
  70. kotonebot-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,469 @@
1
+ import ctypes
2
+ from ctypes import wintypes
3
+ import time
4
+ from typing import List, Tuple, Optional
5
+ from typing import Literal
6
+
7
+ __all__ = [
8
+ "TaskDialog",
9
+ "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON",
10
+ "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON",
11
+ "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE",
12
+ "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON"
13
+ ]
14
+
15
+ # --- Windows API 常量定义 ---
16
+
17
+ # 常用按钮
18
+ TDCBF_OK_BUTTON = 0x0001
19
+ TDCBF_YES_BUTTON = 0x0002
20
+ TDCBF_NO_BUTTON = 0x0004
21
+ TDCBF_CANCEL_BUTTON = 0x0008
22
+ TDCBF_RETRY_BUTTON = 0x0010
23
+ TDCBF_CLOSE_BUTTON = 0x0020
24
+
25
+ # 对话框返回值
26
+ IDOK = 1
27
+ IDCANCEL = 2
28
+ IDABORT = 3
29
+ IDRETRY = 4
30
+ IDIGNORE = 5
31
+ IDYES = 6
32
+ IDNO = 7
33
+ IDCLOSE = 8
34
+
35
+
36
+ # 标准图标 (使用 MAKEINTRESOURCE 宏)
37
+ def MAKEINTRESOURCE(i: int) -> wintypes.LPWSTR:
38
+ return wintypes.LPWSTR(i)
39
+
40
+
41
+ TD_WARNING_ICON = MAKEINTRESOURCE(65535)
42
+ TD_ERROR_ICON = MAKEINTRESOURCE(65534)
43
+ TD_INFORMATION_ICON = MAKEINTRESOURCE(65533)
44
+ TD_SHIELD_ICON = MAKEINTRESOURCE(65532)
45
+
46
+ # Task Dialog 标志
47
+ TDF_ENABLE_HYPERLINKS = 0x0001
48
+ TDF_USE_HICON_MAIN = 0x0002
49
+ TDF_USE_HICON_FOOTER = 0x0004
50
+ TDF_ALLOW_DIALOG_CANCELLATION = 0x0008
51
+ TDF_USE_COMMAND_LINKS = 0x0010
52
+ TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020
53
+ TDF_EXPAND_FOOTER_AREA = 0x0040
54
+ TDF_EXPANDED_BY_DEFAULT = 0x0080
55
+ TDF_VERIFICATION_FLAG_CHECKED = 0x0100
56
+ TDF_SHOW_PROGRESS_BAR = 0x0200
57
+ TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400
58
+ TDF_CALLBACK_TIMER = 0x0800
59
+ TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000
60
+ TDF_RTL_LAYOUT = 0x2000
61
+ TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000
62
+ TDF_CAN_BE_MINIMIZED = 0x8000
63
+
64
+ # Task Dialog 通知
65
+ TDN_CREATED = 0
66
+ TDN_NAVIGATED = 1
67
+ TDN_BUTTON_CLICKED = 2
68
+ TDN_HYPERLINK_CLICKED = 3
69
+ TDN_TIMER = 4
70
+ TDN_DESTROYED = 5
71
+ TDN_RADIO_BUTTON_CLICKED = 6
72
+ TDN_DIALOG_CONSTRUCTED = 7
73
+ TDN_VERIFICATION_CLICKED = 8
74
+ TDN_HELP = 9
75
+ TDN_EXPANDO_BUTTON_CLICKED = 10
76
+
77
+ # Windows 消息
78
+ WM_USER = 0x0400
79
+ TDM_SET_PROGRESS_BAR_POS = WM_USER + 114
80
+
81
+ CommonButtonLiteral = Literal["ok", "yes", "no", "cancel", "retry", "close"]
82
+ IconLiteral = Literal["warning", "error", "information", "shield"]
83
+
84
+
85
+ # --- C 结构体定义 (使用 ctypes) ---
86
+
87
+ class TASKDIALOG_BUTTON(ctypes.Structure):
88
+ _pack_ = 1
89
+ _fields_ = [("nButtonID", ctypes.c_int),
90
+ ("pszButtonText", wintypes.LPCWSTR)]
91
+
92
+
93
+ # 定义回调函数指针原型
94
+ PFTASKDIALOGCALLBACK = ctypes.WINFUNCTYPE(
95
+ ctypes.HRESULT, # 返回值
96
+ wintypes.HWND, # hwnd
97
+ ctypes.c_uint, # msg
98
+ ctypes.c_size_t, # wParam
99
+ ctypes.c_size_t, # lParam
100
+ ctypes.c_ssize_t # lpRefData
101
+ )
102
+
103
+
104
+ class TASKDIALOGCONFIG(ctypes.Structure):
105
+ _pack_ = 1
106
+ _fields_ = [
107
+ ("cbSize", ctypes.c_uint),
108
+ ("hwndParent", wintypes.HWND),
109
+ ("hInstance", wintypes.HINSTANCE),
110
+ ("dwFlags", ctypes.c_uint),
111
+ ("dwCommonButtons", ctypes.c_uint),
112
+ ("pszWindowTitle", wintypes.LPCWSTR),
113
+ ("pszMainIcon", wintypes.LPCWSTR),
114
+ ("pszMainInstruction", wintypes.LPCWSTR),
115
+ ("pszContent", wintypes.LPCWSTR),
116
+ ("cButtons", ctypes.c_uint),
117
+ ("pButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
118
+ ("nDefaultButton", ctypes.c_int),
119
+ ("cRadioButtons", ctypes.c_uint),
120
+ ("pRadioButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
121
+ ("nDefaultRadioButton", ctypes.c_int),
122
+ ("pszVerificationText", wintypes.LPCWSTR),
123
+ ("pszExpandedInformation", wintypes.LPCWSTR),
124
+ ("pszExpandedControlText", wintypes.LPCWSTR),
125
+ ("pszCollapsedControlText", wintypes.LPCWSTR),
126
+ ("pszFooterIcon", wintypes.LPCWSTR),
127
+ ("pszFooter", wintypes.LPCWSTR),
128
+ ("pfCallback", PFTASKDIALOGCALLBACK), # 使用定义好的原型
129
+ ("lpCallbackData", ctypes.c_ssize_t),
130
+ ("cxWidth", ctypes.c_uint)
131
+ ]
132
+
133
+
134
+ # --- 加载 comctl32.dll 并定义函数原型 ---
135
+
136
+ comctl32 = ctypes.WinDLL('comctl32')
137
+ user32 = ctypes.WinDLL('user32')
138
+
139
+ TaskDialogIndirect = comctl32.TaskDialogIndirect
140
+ TaskDialogIndirect.restype = ctypes.HRESULT
141
+ TaskDialogIndirect.argtypes = [
142
+ ctypes.POINTER(TASKDIALOGCONFIG),
143
+ ctypes.POINTER(ctypes.c_int),
144
+ ctypes.POINTER(ctypes.c_int),
145
+ ctypes.POINTER(wintypes.BOOL)
146
+ ]
147
+
148
+
149
+ # --- Python 封装类 ---
150
+
151
+ class TaskDialog:
152
+ """
153
+ 一个用于显示 Windows TaskDialog 的 Python 封装类。
154
+ 支持自定义按钮、单选按钮、进度条、验证框等。
155
+ """
156
+
157
+ def __init__(self,
158
+ parent_hwnd: Optional[int] = None,
159
+ title: str = "Task Dialog",
160
+ main_instruction: str = "",
161
+ content: str = "",
162
+ common_buttons: int | List[CommonButtonLiteral] = TDCBF_OK_BUTTON,
163
+ main_icon: Optional[wintypes.LPWSTR | int | IconLiteral] = None,
164
+ footer: str = "",
165
+ custom_buttons: Optional[List[Tuple[int, str]]] = None,
166
+ default_button: int = 0,
167
+ radio_buttons: Optional[List[Tuple[int, str]]] = None,
168
+ default_radio_button: int = 0,
169
+ verification_text: Optional[str] = None,
170
+ verification_checked_by_default: bool = False,
171
+ show_progress_bar: bool = False,
172
+ show_marquee_progress_bar: bool = False
173
+ ):
174
+ """初始化 TaskDialog 实例。
175
+
176
+ :param parent_hwnd: 父窗口的句柄。
177
+ :param title: 对话框窗口的标题。
178
+ :param main_instruction: 对话框的主要指令文本。
179
+ :param content: 对话框的详细内容文本。
180
+ :param common_buttons: 要显示的通用按钮。可以是以下两种形式之一:
181
+ 1. TDCBF_* 常量的按位或组合 (例如 TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON)
182
+ 2. 字符串列表,支持 "ok", "yes", "no", "cancel", "retry", "close"
183
+ :param main_icon: 主图标。可以是以下几种形式之一:
184
+ 1. TD_*_ICON 常量之一
185
+ 2. HICON 句柄
186
+ 3. 字符串:"warning", "error", "information", "shield"
187
+ :param footer: 页脚区域显示的文本。
188
+ :param custom_buttons: 自定义按钮列表。每个元组包含 (按钮ID, 按钮文本)。
189
+ :param default_button: 默认按钮的ID。可以是通用按钮ID (例如 IDOK) 或自定义按钮ID。
190
+ :param radio_buttons: 单选按钮列表。每个元组包含 (按钮ID, 按钮文本)。
191
+ :param default_radio_button: 默认选中的单选按钮的ID。
192
+ :param verification_text: 验证复选框的文本。如果为 None,则不显示复选框。
193
+ :param verification_checked_by_default: 验证复选框是否默认勾选。
194
+ :param show_progress_bar: 是否显示标准进度条。
195
+ :param show_marquee_progress_bar: 是否显示跑马灯式进度条。
196
+ """
197
+ self.config = TASKDIALOGCONFIG()
198
+ self.config.cbSize = ctypes.sizeof(TASKDIALOGCONFIG)
199
+ self.config.hwndParent = parent_hwnd
200
+ self.config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW
201
+ self.config.dwCommonButtons = self._process_common_buttons(common_buttons)
202
+ self.config.pszWindowTitle = title
203
+ self.config.pszMainInstruction = main_instruction
204
+ self.config.pszContent = content
205
+ self.config.pszFooter = footer
206
+
207
+ self.progress: int = 0
208
+ if show_progress_bar or show_marquee_progress_bar:
209
+ # 进度条暂时还没实现
210
+ raise NotImplementedError("Progress bar is not implemented yet.")
211
+ self.config.dwFlags |= TDF_CALLBACK_TIMER
212
+ if show_progress_bar:
213
+ self.config.dwFlags |= TDF_SHOW_PROGRESS_BAR
214
+ else:
215
+ self.config.dwFlags |= TDF_SHOW_MARQUEE_PROGRESS_BAR
216
+
217
+ # 将实例方法转为 C 回调函数指针。
218
+ # 必须将其保存为实例成员,否则会被垃圾回收!
219
+ self._callback_func_ptr = PFTASKDIALOGCALLBACK(self._callback)
220
+ self.config.pfCallback = self._callback_func_ptr
221
+ # 将本实例的id作为lpCallbackData传递,以便在回调中识别
222
+ self.config.lpCallbackData = id(self)
223
+
224
+ # --- 图标设置 ---
225
+ processed_icon = self._process_main_icon(main_icon)
226
+ if processed_icon is not None:
227
+ if isinstance(processed_icon, wintypes.LPWSTR):
228
+ self.config.pszMainIcon = processed_icon
229
+ else:
230
+ self.config.dwFlags |= TDF_USE_HICON_MAIN
231
+ self.config.hMainIcon = processed_icon
232
+
233
+ # --- 自定义按钮设置 ---
234
+ self.custom_buttons_list = []
235
+ if custom_buttons:
236
+ self.config.cButtons = len(custom_buttons)
237
+ button_array_type = TASKDIALOG_BUTTON * len(custom_buttons)
238
+ self.custom_buttons_list = button_array_type()
239
+ for i, (btn_id, btn_text) in enumerate(custom_buttons):
240
+ self.custom_buttons_list[i].nButtonID = btn_id
241
+ self.custom_buttons_list[i].pszButtonText = btn_text
242
+ self.config.pButtons = self.custom_buttons_list
243
+
244
+ if default_button:
245
+ self.config.nDefaultButton = default_button
246
+
247
+ # --- 单选按钮设置 ---
248
+ self.radio_buttons_list = []
249
+ if radio_buttons:
250
+ self.config.cRadioButtons = len(radio_buttons)
251
+ radio_array_type = TASKDIALOG_BUTTON * len(radio_buttons)
252
+ self.radio_buttons_list = radio_array_type()
253
+ for i, (btn_id, btn_text) in enumerate(radio_buttons):
254
+ self.radio_buttons_list[i].nButtonID = btn_id
255
+ self.radio_buttons_list[i].pszButtonText = btn_text
256
+ self.config.pRadioButtons = self.radio_buttons_list
257
+
258
+ if default_radio_button:
259
+ self.config.nDefaultRadioButton = default_radio_button
260
+
261
+ # --- 验证复选框设置 ---
262
+ if verification_text:
263
+ self.config.pszVerificationText = verification_text
264
+ if verification_checked_by_default:
265
+ self.config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED
266
+
267
+ def _process_common_buttons(self, common_buttons: int | List[CommonButtonLiteral]) -> int:
268
+ """处理 common_buttons 参数,支持常量和字符串列表两种形式"""
269
+ if isinstance(common_buttons, int):
270
+ # 直接使用 Win32 常量
271
+ return common_buttons
272
+ elif isinstance(common_buttons, list):
273
+ # 处理字符串列表
274
+ result = 0
275
+ for button in common_buttons:
276
+ # 使用 match 和 assert_never 进行类型检查
277
+ match button:
278
+ case "ok":
279
+ result |= TDCBF_OK_BUTTON
280
+ case "yes":
281
+ result |= TDCBF_YES_BUTTON
282
+ case "no":
283
+ result |= TDCBF_NO_BUTTON
284
+ case "cancel":
285
+ result |= TDCBF_CANCEL_BUTTON
286
+ case "retry":
287
+ result |= TDCBF_RETRY_BUTTON
288
+ case "close":
289
+ result |= TDCBF_CLOSE_BUTTON
290
+ case _:
291
+ # 这在实际中不会发生,因为类型检查会阻止它
292
+ from typing import assert_never
293
+ assert_never(button)
294
+ return result
295
+ else:
296
+ raise TypeError("common_buttons must be either an int or a list of strings")
297
+
298
+ def _process_main_icon(self, main_icon: Optional[wintypes.LPWSTR | int | IconLiteral]) -> Optional[wintypes.LPWSTR | int]:
299
+ """处理 main_icon 参数,支持常量和字符串两种形式"""
300
+ if main_icon is None:
301
+ return None
302
+ elif isinstance(main_icon, (wintypes.LPWSTR, int)):
303
+ # 直接使用 Win32 常量或 HICON 句柄
304
+ return main_icon
305
+ elif isinstance(main_icon, str):
306
+ # 处理字符串
307
+ match main_icon:
308
+ case "warning":
309
+ return TD_WARNING_ICON
310
+ case "error":
311
+ return TD_ERROR_ICON
312
+ case "information":
313
+ return TD_INFORMATION_ICON
314
+ case "shield":
315
+ return TD_SHIELD_ICON
316
+ case _:
317
+ # 这在实际中不会发生,因为类型检查会阻止它
318
+ from typing import assert_never
319
+ assert_never(main_icon)
320
+ else:
321
+ raise TypeError("main_icon must be None, a Windows constant, or a string")
322
+
323
+ def _callback(self, hwnd: wintypes.HWND, msg: int, wParam: int, lParam: int, lpRefData: int) -> int:
324
+ # 仅当 lpRefData 指向的是当前这个对象实例时才处理
325
+ if lpRefData != id(self):
326
+ return 0 # S_OK
327
+
328
+ if msg == TDN_TIMER:
329
+ # 更新进度条
330
+ if self.progress < 100:
331
+ self.progress += 5
332
+ # 发送消息给对话框来更新进度条位置
333
+ user32.SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, self.progress, 0)
334
+ else:
335
+ # 示例:进度达到100%后,可以模拟点击OK按钮关闭对话框
336
+ # from ctypes import wintypes
337
+ # user32.PostMessageW(hwnd, wintypes.UINT(1125), IDOK, 0) # TDM_CLICK_BUTTON
338
+ pass
339
+
340
+ elif msg == TDN_DESTROYED:
341
+ # 对话框已销毁
342
+ pass
343
+
344
+ return 0 # S_OK
345
+
346
+ def show(self) -> Tuple[int, int, bool]:
347
+ """
348
+ 显示对话框并返回用户交互的结果。
349
+
350
+ :return: 一个元组 (button_id, radio_button_id, verification_checked)
351
+ - button_id: 用户点击的按钮ID (例如 IDOK, IDCANCEL)。
352
+ - radio_button_id: 用户选择的单选按钮的ID。
353
+ - verification_checked: 验证复选框是否被勾选 (True/False)。
354
+ """
355
+ pnButton = ctypes.c_int(0)
356
+ pnRadioButton = ctypes.c_int(0)
357
+ pfVerificationFlagChecked = wintypes.BOOL(False)
358
+
359
+ hr = TaskDialogIndirect(
360
+ ctypes.byref(self.config),
361
+ ctypes.byref(pnButton),
362
+ ctypes.byref(pnRadioButton),
363
+ ctypes.byref(pfVerificationFlagChecked)
364
+ )
365
+
366
+ if hr == 0: # S_OK
367
+ return pnButton.value, pnRadioButton.value, bool(pfVerificationFlagChecked.value)
368
+ else:
369
+ raise ctypes.WinError(hr)
370
+
371
+
372
+ # --- 示例用法 ---
373
+ if __name__ == '__main__':
374
+
375
+ print("--- 示例 1: 简单信息框 ---")
376
+ dlg_simple = TaskDialog(
377
+ title="操作成功",
378
+ main_instruction="您的操作已成功完成。",
379
+ content="文件已保存到您的文档目录。",
380
+ common_buttons=["ok"],
381
+ main_icon="information"
382
+ )
383
+ result_simple, _, _ = dlg_simple.show()
384
+ print(f"用户点击了按钮: {result_simple} (1=OK)\n")
385
+
386
+ print("--- 示例 2: 确认框 ---")
387
+ dlg_confirm = TaskDialog(
388
+ title="确认删除",
389
+ main_instruction="您确定要永久删除这个文件吗?",
390
+ content="这个操作无法撤销。文件将被立即删除。",
391
+ common_buttons=["yes", "no", "cancel"],
392
+ main_icon="warning",
393
+ default_button=IDNO
394
+ )
395
+ result_confirm, _, _ = dlg_confirm.show()
396
+ if result_confirm == IDYES:
397
+ print("用户选择了“是”。")
398
+ elif result_confirm == IDNO:
399
+ print("用户选择了“否”。")
400
+ elif result_confirm == IDCANCEL:
401
+ print("用户选择了“取消”。")
402
+ print(f"返回的按钮ID: {result_confirm}\n")
403
+
404
+ # 示例 3
405
+ print("--- 示例 3: 自定义按钮 ---")
406
+ CUSTOM_BUTTON_SAVE_ID = 101
407
+ CUSTOM_BUTTON_DONT_SAVE_ID = 102
408
+ my_buttons = [
409
+ (CUSTOM_BUTTON_SAVE_ID, "保存并退出"),
410
+ (CUSTOM_BUTTON_DONT_SAVE_ID, "不保存直接退出")
411
+ ]
412
+ dlg_custom = TaskDialog(
413
+ title="未保存的更改",
414
+ main_instruction="文档中有未保存的更改,您想如何处理?",
415
+ custom_buttons=my_buttons,
416
+ common_buttons=["cancel"],
417
+ main_icon="warning",
418
+ footer="这是一个重要的提醒!"
419
+ )
420
+ result_custom, _, _ = dlg_custom.show()
421
+ if result_custom == CUSTOM_BUTTON_SAVE_ID:
422
+ print("用户选择了“保存并退出”。")
423
+ elif result_custom == CUSTOM_BUTTON_DONT_SAVE_ID:
424
+ print("用户选择了“不保存直接退出”。")
425
+ elif result_custom == IDCANCEL:
426
+ print("用户选择了“取消”。")
427
+ print(f"返回的按钮ID: {result_custom}\n")
428
+
429
+ # 示例 4: 带单选按钮和验证框的对话框
430
+ print("--- 示例 4: 单选按钮和验证框 ---")
431
+ RADIO_BTN_WORD_ID = 201
432
+ RADIO_BTN_EXCEL_ID = 202
433
+ RADIO_BTN_PDF_ID = 203
434
+
435
+ radio_buttons = [
436
+ (RADIO_BTN_WORD_ID, "保存为 Word 文档 (.docx)"),
437
+ (RADIO_BTN_EXCEL_ID, "保存为 Excel 表格 (.xlsx)"),
438
+ (RADIO_BTN_PDF_ID, "导出为 PDF 文档 (.pdf)")
439
+ ]
440
+
441
+ dlg_radio = TaskDialog(
442
+ title="选择导出格式",
443
+ main_instruction="请选择您想要导出的文件格式。",
444
+ content="选择一个格式后,点击“确定”继续。",
445
+ common_buttons=["ok", "cancel"],
446
+ main_icon="information",
447
+ radio_buttons=radio_buttons,
448
+ default_radio_button=RADIO_BTN_PDF_ID, # 默认选中PDF
449
+ verification_text="设为我的默认导出选项",
450
+ verification_checked_by_default=True
451
+ )
452
+ btn_id, radio_id, checked = dlg_radio.show()
453
+
454
+ if btn_id == IDOK:
455
+ print(f"用户点击了“确定”。")
456
+ if radio_id == RADIO_BTN_WORD_ID:
457
+ print("选择了导出为 Word。")
458
+ elif radio_id == RADIO_BTN_EXCEL_ID:
459
+ print("选择了导出为 Excel。")
460
+ elif radio_id == RADIO_BTN_PDF_ID:
461
+ print("选择了导出为 PDF。")
462
+
463
+ if checked:
464
+ print("用户勾选了“设为我的默认导出选项”。")
465
+ else:
466
+ print("用户未勾选“设为我的默认导出选项”。")
467
+ else:
468
+ print("用户点击了“取消”。")
469
+ print(f"返回的按钮ID: {btn_id}, 单选按钮ID: {radio_id}, 验证框状态: {checked}\n")
@@ -0,0 +1,2 @@
1
+ from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL, basicConfig
2
+ from .log import getLogger, enable_verbose, LEVEL_VERBOSE as VERBOSE
@@ -0,0 +1,18 @@
1
+ import logging
2
+ from typing import cast
3
+
4
+ LEVEL_VERBOSE = 5
5
+
6
+ def enable_verbose():
7
+ logging.addLevelName(LEVEL_VERBOSE, 'VERBOSE')
8
+
9
+ class VerboseLogger(logging.Logger):
10
+ def verbose(self, msg, *args, **kwargs):
11
+ if self.isEnabledFor(LEVEL_VERBOSE):
12
+ self._log(LEVEL_VERBOSE, msg, args, **kwargs)
13
+
14
+ def getLogger(name: str | None = None) -> VerboseLogger:
15
+ logger = logging.getLogger(name)
16
+ if not isinstance(logger, VerboseLogger):
17
+ logger.__class__ = VerboseLogger
18
+ return cast(VerboseLogger, logger)
@@ -0,0 +1,17 @@
1
+ from .visual import Image, Template
2
+ from .geometry import Point, Rect, Size, Vector2D, Vector3D, is_point, is_rect
3
+ from .geometry import RectTuple, PointTuple
4
+
5
+ __all__ = [
6
+ 'Image',
7
+ 'Template',
8
+ 'Point',
9
+ 'Rect',
10
+ 'Size',
11
+ 'Vector2D',
12
+ 'Vector3D',
13
+ 'is_point',
14
+ 'is_rect',
15
+ 'RectTuple',
16
+ 'PointTuple'
17
+ ]