MaaFw 4.5.5__py3-none-win_arm64.whl → 5.0.0a1__py3-none-win_arm64.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 MaaFw might be problematic. Click here for more details.

maa/pipeline.py ADDED
@@ -0,0 +1,374 @@
1
+ import json
2
+ from dataclasses import dataclass, field
3
+ from typing import Any, List, Tuple, Union, Dict
4
+
5
+ # Type aliases to match C++ std::variant types
6
+ JRect = Tuple[int, int, int, int] # std::array<int, 4>
7
+ JTarget = Union[bool, str, JRect] # std::variant<bool, std::string, JRect>
8
+
9
+
10
+ # Recognition parameter dataclasses (matching C++ JRecognitionParam variants)
11
+ @dataclass
12
+ class JDirectHit:
13
+ pass
14
+
15
+
16
+ @dataclass
17
+ class JTemplateMatch:
18
+ roi: JTarget
19
+ roi_offset: JRect
20
+ template: List[str]
21
+ threshold: List[float]
22
+ order_by: str
23
+ index: int
24
+ method: int
25
+ green_mask: bool
26
+
27
+
28
+ @dataclass
29
+ class JFeatureMatch:
30
+ roi: JTarget
31
+ roi_offset: JRect
32
+ template: List[str]
33
+ detector: str
34
+ order_by: str
35
+ count: int
36
+ index: int
37
+ green_mask: bool
38
+ ratio: float
39
+
40
+
41
+ @dataclass
42
+ class JColorMatch:
43
+ roi: JTarget
44
+ roi_offset: JRect
45
+ lower: List[List[int]]
46
+ upper: List[List[int]]
47
+ order_by: str
48
+ method: int
49
+ count: int
50
+ index: int
51
+ connected: bool
52
+
53
+
54
+ @dataclass
55
+ class JOCR:
56
+ roi: JTarget
57
+ roi_offset: JRect
58
+ expected: List[str]
59
+ threshold: float
60
+ replace: List[List[str]]
61
+ order_by: str
62
+ index: int
63
+ only_rec: bool
64
+ model: str
65
+
66
+
67
+ @dataclass
68
+ class JNeuralNetworkClassify:
69
+ roi: JTarget
70
+ roi_offset: JRect
71
+ labels: List[str]
72
+ model: str
73
+ expected: List[int]
74
+ order_by: str
75
+ index: int
76
+
77
+
78
+ @dataclass
79
+ class JNeuralNetworkDetect:
80
+ roi: JTarget
81
+ roi_offset: JRect
82
+ labels: List[str]
83
+ model: str
84
+ expected: List[int]
85
+ threshold: List[float]
86
+ order_by: str
87
+ index: int
88
+
89
+
90
+ @dataclass
91
+ class JCustomRecognition:
92
+ roi: JTarget
93
+ roi_offset: JRect
94
+ custom_recognition: str
95
+ custom_recognition_param: Any
96
+
97
+
98
+ # Recognition parameter union type
99
+ JRecognitionParam = Union[
100
+ JDirectHit,
101
+ JTemplateMatch,
102
+ JFeatureMatch,
103
+ JColorMatch,
104
+ JOCR,
105
+ JNeuralNetworkClassify,
106
+ JNeuralNetworkDetect,
107
+ JCustomRecognition,
108
+ ]
109
+
110
+
111
+ # Action parameter dataclasses (matching C++ JActionParam variants)
112
+ @dataclass
113
+ class JDoNothing:
114
+ pass
115
+
116
+
117
+ @dataclass
118
+ class JClick:
119
+ target: JTarget
120
+ target_offset: JRect
121
+
122
+
123
+ @dataclass
124
+ class JLongPress:
125
+ target: JTarget
126
+ target_offset: JRect
127
+ duration: int
128
+
129
+
130
+ @dataclass
131
+ class JSwipe:
132
+ starting: int
133
+ begin: JTarget
134
+ begin_offset: JRect
135
+ end: List[JTarget]
136
+ end_offset: List[JRect]
137
+ end_hold: List[int]
138
+ duration: List[int]
139
+ only_hover: bool
140
+
141
+
142
+ @dataclass
143
+ class JMultiSwipe:
144
+ swipes: List[JSwipe]
145
+
146
+
147
+ @dataclass
148
+ class JClickKey:
149
+ key: List[int]
150
+
151
+
152
+ @dataclass
153
+ class JLongPressKey:
154
+ key: List[int]
155
+ duration: int
156
+
157
+
158
+ @dataclass
159
+ class JInputText:
160
+ input_text: str
161
+
162
+
163
+ @dataclass
164
+ class JStartApp:
165
+ package: str
166
+
167
+
168
+ @dataclass
169
+ class JStopApp:
170
+ package: str
171
+
172
+
173
+ @dataclass
174
+ class JStopTask:
175
+ pass
176
+
177
+
178
+ @dataclass
179
+ class JCommand:
180
+ exec: str
181
+ args: List[str]
182
+ detach: bool
183
+
184
+
185
+ @dataclass
186
+ class JCustomAction:
187
+ target: JTarget
188
+ custom_action: str
189
+ custom_action_param: Any
190
+ target_offset: JRect
191
+
192
+
193
+ # Action parameter union type
194
+ JActionParam = Union[
195
+ JDoNothing,
196
+ JClick,
197
+ JLongPress,
198
+ JSwipe,
199
+ JMultiSwipe,
200
+ JClickKey,
201
+ JLongPressKey,
202
+ JInputText,
203
+ JStartApp,
204
+ JStopApp,
205
+ JStopTask,
206
+ JCommand,
207
+ JCustomAction,
208
+ ]
209
+
210
+
211
+ # Main pipeline dataclasses
212
+ @dataclass
213
+ class JRecognition:
214
+ type: str
215
+ param: JRecognitionParam
216
+
217
+
218
+ @dataclass
219
+ class JAction:
220
+ type: str
221
+ param: JActionParam
222
+
223
+
224
+ @dataclass
225
+ class JWaitFreezes:
226
+ time: int
227
+ target: JTarget
228
+ target_offset: JRect
229
+ threshold: float
230
+ method: int
231
+ rate_limit: int
232
+ timeout: int
233
+
234
+
235
+ @dataclass
236
+ class JPipelineData:
237
+ recognition: JRecognition
238
+ action: JAction
239
+ next: List[str]
240
+ interrupt: List[str]
241
+ is_sub: bool
242
+ rate_limit: int
243
+ timeout: int
244
+ on_error: List[str]
245
+ inverse: bool
246
+ enabled: bool
247
+ pre_delay: int
248
+ post_delay: int
249
+ pre_wait_freezes: JWaitFreezes
250
+ post_wait_freezes: JWaitFreezes
251
+ focus: Any
252
+
253
+
254
+ class JPipelineParser:
255
+ @staticmethod
256
+ def _parse_wait_freezes(data: dict) -> JWaitFreezes:
257
+ """Convert wait freezes with proper defaults"""
258
+ return JWaitFreezes(
259
+ time=data.get("time"),
260
+ target=data.get("target"), # type: ignore
261
+ target_offset=data.get("target_offset"), # type: ignore
262
+ threshold=data.get("threshold"),
263
+ method=data.get("method"),
264
+ rate_limit=data.get("rate_limit"),
265
+ timeout=data.get("timeout"),
266
+ )
267
+
268
+ @classmethod
269
+ def _parse_param(
270
+ cls, param_type: str, param_data: dict, param_type_map: dict, default_class
271
+ ):
272
+ """Generic function to parse parameters based on type map."""
273
+ param_class = param_type_map.get(param_type)
274
+ if not param_class:
275
+ raise ValueError(f"Unknown type: {param_type}")
276
+
277
+ if param_class == default_class:
278
+ return param_class()
279
+
280
+ try:
281
+ return param_class(**param_data)
282
+ except TypeError as e:
283
+ print(
284
+ f"Warning: Failed to create {param_class.__name__} with data {param_data}: {e}"
285
+ )
286
+ return default_class()
287
+
288
+ @classmethod
289
+ def _parse_recognition_param(
290
+ cls, param_type: str, param_data: dict
291
+ ) -> JRecognitionParam:
292
+ """Convert dict to appropriate JRecognitionParam variant based on type."""
293
+ param_type_map = {
294
+ "DirectHit": JDirectHit,
295
+ "TemplateMatch": JTemplateMatch,
296
+ "FeatureMatch": JFeatureMatch,
297
+ "ColorMatch": JColorMatch,
298
+ "OCR": JOCR,
299
+ "NeuralNetworkClassify": JNeuralNetworkClassify,
300
+ "NeuralNetworkDetect": JNeuralNetworkDetect,
301
+ "Custom": JCustomRecognition,
302
+ }
303
+ return cls._parse_param(param_type, param_data, param_type_map, JDirectHit)
304
+
305
+ @classmethod
306
+ def _parse_action_param(cls, param_type: str, param_data: dict) -> JActionParam:
307
+ """Convert dict to appropriate JActionParam variant based on type."""
308
+ param_type_map = {
309
+ "DoNothing": JDoNothing,
310
+ "Click": JClick,
311
+ "LongPress": JLongPress,
312
+ "Swipe": JSwipe,
313
+ "MultiSwipe": JMultiSwipe,
314
+ "ClickKey": JClickKey,
315
+ "LongPressKey": JLongPressKey,
316
+ "InputText": JInputText,
317
+ "StartApp": JStartApp,
318
+ "StopApp": JStopApp,
319
+ "StopTask": JStopTask,
320
+ "Command": JCommand,
321
+ "Custom": JCustomAction,
322
+ }
323
+ return cls._parse_param(param_type, param_data, param_type_map, JDoNothing)
324
+
325
+ @classmethod
326
+ def parse_pipeline_data(cls, pipeline_data: Union[str, Dict]) -> JPipelineData:
327
+ """Parse JSON string to JPipelineData dataclass with proper variant types."""
328
+ if isinstance(pipeline_data, dict):
329
+ data = pipeline_data
330
+ elif isinstance(pipeline_data, str):
331
+ try:
332
+ data: dict = json.loads(pipeline_data)
333
+ except json.JSONDecodeError as e:
334
+ raise ValueError(f"Invalid JSON format: {e}")
335
+ else:
336
+ raise TypeError("Input must be a JSON string or a dict.")
337
+
338
+ # Convert recognition
339
+ recognition_data: dict = data.get("recognition")
340
+ recognition_type: str = recognition_data.get("type")
341
+ recognition_param_data: dict = recognition_data.get("param")
342
+ recognition_param = cls._parse_recognition_param(
343
+ recognition_type, recognition_param_data
344
+ )
345
+ recognition = JRecognition(type=recognition_type, param=recognition_param)
346
+
347
+ # Convert action
348
+ action_data: dict = data.get("action")
349
+ action_type: str = action_data.get("type")
350
+ action_param_data = action_data.get("param")
351
+ action_param = cls._parse_action_param(action_type, action_param_data)
352
+ action = JAction(type=action_type, param=action_param)
353
+
354
+ pre_wait_freezes = cls._parse_wait_freezes(data.get("pre_wait_freezes")) # type: ignore
355
+ post_wait_freezes = cls._parse_wait_freezes(data.get("post_wait_freezes")) # type: ignore
356
+
357
+ # Create JPipelineData with converted data
358
+ return JPipelineData(
359
+ recognition=recognition,
360
+ action=action,
361
+ next=data.get("next"),
362
+ interrupt=data.get("interrupt"),
363
+ is_sub=data.get("is_sub"),
364
+ rate_limit=data.get("rate_limit"),
365
+ timeout=data.get("timeout"),
366
+ on_error=data.get("on_error"),
367
+ inverse=data.get("inverse"),
368
+ enabled=data.get("enabled"),
369
+ pre_delay=data.get("pre_delay"),
370
+ post_delay=data.get("post_delay"),
371
+ pre_wait_freezes=pre_wait_freezes, # type: ignore
372
+ post_wait_freezes=post_wait_freezes, # type: ignore
373
+ focus=data.get("focus"),
374
+ )
maa/resource.py CHANGED
@@ -1,17 +1,18 @@
1
1
  import ctypes
2
2
  import pathlib
3
3
  import json
4
- from typing import Any, Optional, Union
4
+ from typing import Any, Optional, Union, List, Dict, Tuple
5
+ from dataclasses import dataclass, field
5
6
 
6
- from .notification_handler import NotificationHandler
7
+ from .event_sink import EventSink, NotificationType
7
8
  from .define import *
8
9
  from .job import Job
9
10
  from .library import Library
10
11
  from .buffer import StringBuffer, StringListBuffer
12
+ from .pipeline import JPipelineData, JPipelineParser
11
13
 
12
14
 
13
15
  class Resource:
14
- _notification_handler: Optional[NotificationHandler]
15
16
  _handle: MaaResourceHandle
16
17
  _own: bool
17
18
 
@@ -19,19 +20,21 @@ class Resource:
19
20
 
20
21
  def __init__(
21
22
  self,
22
- notification_handler: Optional[NotificationHandler] = None,
23
+ notification_handler: None = None,
23
24
  handle: Optional[MaaResourceHandle] = None,
24
25
  ):
26
+ if notification_handler:
27
+ raise NotImplementedError(
28
+ "NotificationHandler is deprecated, use add_sink instead."
29
+ )
30
+
25
31
  self._set_api_properties()
26
32
 
27
33
  if handle:
28
34
  self._handle = handle
29
35
  self._own = False
30
36
  else:
31
- self._notification_handler = notification_handler
32
- self._handle = Library.framework().MaaResourceCreate(
33
- *NotificationHandler._gen_c_param(self._notification_handler)
34
- )
37
+ self._handle = Library.framework().MaaResourceCreate()
35
38
  self._own = True
36
39
 
37
40
  if not self._handle:
@@ -45,16 +48,18 @@ class Resource:
45
48
  Library.framework().MaaResourceDestroy(self._handle)
46
49
 
47
50
  def post_bundle(self, path: Union[pathlib.Path, str]) -> Job:
48
- resid = Library.framework().MaaResourcePostBundle(
51
+ res_id = Library.framework().MaaResourcePostBundle(
49
52
  self._handle, str(path).encode()
50
53
  )
51
- return Job(resid, self._status, self._wait)
54
+ return Job(res_id, self._status, self._wait)
52
55
 
53
56
  def override_pipeline(self, pipeline_override: Dict) -> bool:
57
+ pipeline_json = json.dumps(pipeline_override, ensure_ascii=False)
58
+
54
59
  return bool(
55
60
  Library.framework().MaaResourceOverridePipeline(
56
61
  self._handle,
57
- json.dumps(pipeline_override, ensure_ascii=False).encode(),
62
+ pipeline_json.encode(),
58
63
  )
59
64
  )
60
65
 
@@ -67,14 +72,13 @@ class Resource:
67
72
  self._handle, name.encode(), list_buffer._handle
68
73
  )
69
74
  )
70
-
71
- def get_node_data(self, name: str) -> Optional[dict]:
75
+
76
+ def get_node_data(self, name: str) -> Optional[Dict]:
72
77
  string_buffer = StringBuffer()
73
78
  if not Library.framework().MaaResourceGetNodeData(
74
79
  self._handle, name.encode(), string_buffer._handle
75
80
  ):
76
81
  return None
77
-
78
82
  data = string_buffer.get()
79
83
  if not data:
80
84
  return None
@@ -84,6 +88,14 @@ class Resource:
84
88
  except json.JSONDecodeError:
85
89
  return None
86
90
 
91
+ def get_node_object(self, name: str) -> Optional[JPipelineData]:
92
+ node_data = self.get_node_data(name)
93
+
94
+ if not node_data:
95
+ return None
96
+
97
+ return JPipelineParser.parse_pipeline_data(node_data)
98
+
87
99
  @property
88
100
  def loaded(self) -> bool:
89
101
  return bool(Library.framework().MaaResourceLoaded(self._handle))
@@ -231,6 +243,27 @@ class Resource:
231
243
  raise RuntimeError("Failed to get hash.")
232
244
  return buffer.get()
233
245
 
246
+ _sink_holder: Dict[int, "ResourceEventSink"] = {}
247
+
248
+ def add_sink(self, sink: "ResourceEventSink") -> Optional[int]:
249
+ sink_id = int(
250
+ Library.framework().MaaResourceAddSink(
251
+ self._handle, *EventSink._gen_c_param(sink)
252
+ )
253
+ )
254
+ if sink_id == MaaInvalidId:
255
+ return None
256
+
257
+ self._sink_holder[sink_id] = sink
258
+ return sink_id
259
+
260
+ def remove_sink(self, sink_id: int) -> None:
261
+ Library.framework().MaaResourceRemoveSink(self._handle, sink_id)
262
+ self._sink_holder.pop(sink_id)
263
+
264
+ def clear_sinks(self) -> None:
265
+ Library.framework().MaaResourceClearSinks(self._handle)
266
+
234
267
  ### private ###
235
268
 
236
269
  def set_inference(self, execution_provider: int, device_id: int) -> bool:
@@ -267,10 +300,7 @@ class Resource:
267
300
  Resource._api_properties_initialized = True
268
301
 
269
302
  Library.framework().MaaResourceCreate.restype = MaaResourceHandle
270
- Library.framework().MaaResourceCreate.argtypes = [
271
- MaaNotificationCallback,
272
- ctypes.c_void_p,
273
- ]
303
+ Library.framework().MaaResourceCreate.argtypes = []
274
304
 
275
305
  Library.framework().MaaResourceDestroy.restype = None
276
306
  Library.framework().MaaResourceDestroy.argtypes = [MaaResourceHandle]
@@ -376,3 +406,52 @@ class Resource:
376
406
  MaaResourceHandle,
377
407
  MaaStringListBufferHandle,
378
408
  ]
409
+
410
+ Library.framework().MaaResourceAddSink.restype = MaaSinkId
411
+ Library.framework().MaaResourceAddSink.argtypes = [
412
+ MaaResourceHandle,
413
+ MaaEventCallback,
414
+ ctypes.c_void_p,
415
+ ]
416
+
417
+ Library.framework().MaaResourceRemoveSink.restype = None
418
+ Library.framework().MaaResourceRemoveSink.argtypes = [
419
+ MaaResourceHandle,
420
+ MaaSinkId,
421
+ ]
422
+
423
+ Library.framework().MaaResourceClearSinks.restype = None
424
+ Library.framework().MaaResourceClearSinks.argtypes = [MaaResourceHandle]
425
+
426
+
427
+ class ResourceEventSink(EventSink):
428
+
429
+ @dataclass
430
+ class ResourceLoadingDetail:
431
+ res_id: int
432
+ hash: str
433
+ path: str
434
+
435
+ def on_resource_loading(
436
+ self,
437
+ resource: Resource,
438
+ noti_type: NotificationType,
439
+ detail: ResourceLoadingDetail,
440
+ ):
441
+ pass
442
+
443
+ def on_raw_notification(self, handle: ctypes.c_void_p, msg: str, details: dict):
444
+
445
+ resource = Resource(handle=handle)
446
+ noti_type = EventSink._notification_type(msg)
447
+
448
+ if msg.startswith("Resource.Loading"):
449
+ detail = self.ResourceLoadingDetail(
450
+ res_id=details["res_id"],
451
+ hash=details["hash"],
452
+ path=details["path"],
453
+ )
454
+ self.on_resource_loading(resource, noti_type, detail)
455
+
456
+ else:
457
+ self.on_unknown_notification(resource, msg, details)