automas-maafw-controller-win32 0.1.1__tar.gz

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.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: automas-maafw-controller-win32
3
+ Version: 0.1.1
4
+ Summary: Win32 controller provider for AUTO-MAS MaaFW plugins
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: automas-maafw-interface>=0.1.0
8
+ Requires-Dist: pydantic>=2
9
+
10
+ # automas-maafw-controller-win32
11
+
12
+ Win32 controller provider for the AUTO-MAS MaaFW plugin group.
13
+
14
+ The service exposes window scanning helpers. `maa.toolkit` is imported lazily
15
+ only when Win32 windows are scanned.
@@ -0,0 +1,6 @@
1
+ # automas-maafw-controller-win32
2
+
3
+ Win32 controller provider for the AUTO-MAS MaaFW plugin group.
4
+
5
+ The service exposes window scanning helpers. `maa.toolkit` is imported lazily
6
+ only when Win32 windows are scanned.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "automas-maafw-controller-win32"
7
+ version = "0.1.1"
8
+ description = "Win32 controller provider for AUTO-MAS MaaFW plugins"
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "automas-maafw-interface>=0.1.0",
13
+ "pydantic>=2",
14
+ ]
15
+
16
+ [project.entry-points."auto_mas.plugins"]
17
+ automas_maafw_controller_win32 = "automas_maafw_controller_win32.plugin:Plugin"
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["src"]
21
+
22
+ [tool.setuptools.package-data]
23
+ automas_maafw_controller_win32 = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ __all__: list[str] = []
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .service import MaaFWWin32ControllerService
6
+
7
+ if TYPE_CHECKING:
8
+ from auto_mas_core import PluginContext
9
+
10
+
11
+ DEFAULT_INSTANCE = {
12
+ "name": "MaaFW Win32 Controller",
13
+ "enabled": True,
14
+ "config": {},
15
+ }
16
+
17
+ schema = {
18
+ "__no_plugin_config__": {
19
+ "type": "boolean",
20
+ "default": True,
21
+ "hidden": True,
22
+ "configurable": False,
23
+ "title": "No plugin-level configuration",
24
+ },
25
+ }
26
+
27
+
28
+ class Plugin:
29
+ provides = ["maafw.controller.win32"]
30
+ wants = ["maafw.registry.v1"]
31
+
32
+ def __init__(self, ctx: "PluginContext") -> None:
33
+ self.ctx = ctx
34
+ self.service = MaaFWWin32ControllerService()
35
+
36
+ async def on_start(self) -> None:
37
+ self.ctx.set("maafw.controller.win32", self.service)
38
+ registry = self.ctx.get("maafw.registry.v1")
39
+ if registry is not None and hasattr(registry, "register_controller_provider"):
40
+ registry.register_controller_provider(self.service.get_provider_definition())
41
+ self.ctx.logger.info("maafw.controller.win32 ready")
42
+
43
+ async def on_stop(self, reason: str) -> None:
44
+ registry = self.ctx.get("maafw.registry.v1")
45
+ if registry is not None and hasattr(registry, "unregister_controller_provider"):
46
+ registry.unregister_controller_provider("win32")
47
+ self.ctx.logger.info(f"maafw.controller.win32 stopped, reason={reason}")
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import sys
5
+ from dataclasses import dataclass
6
+ from typing import Any, Iterable
7
+
8
+ from automas_maafw_interface.models import MaaFWController
9
+ from pydantic import BaseModel
10
+
11
+ MAX_REGEX_PATTERN_LENGTH = 256
12
+ MAX_REGEX_VALUE_LENGTH = 4096
13
+ DANGEROUS_NESTED_QUANTIFIER_RE = re.compile(
14
+ r"\([^)]*(?:[*+]|\{\d+(?:,\d*)?\})[^)]*\)(?:[*+]|\{\d+(?:,\d*)?\})"
15
+ )
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class MaaFWWin32Window:
20
+ hWnd: int
21
+ className: str
22
+ windowName: str
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class MaaFWWindowMatch(MaaFWWin32Window):
27
+ controllerName: str
28
+ controllerType: str
29
+
30
+
31
+ class MaaFWWin32ControllerService:
32
+ """maafw.controller.win32 service."""
33
+
34
+ key = "win32"
35
+ display_name = "Win32"
36
+ controller_types = ["Win32"]
37
+
38
+ def get_provider_definition(self) -> dict[str, Any]:
39
+ return {
40
+ "key": self.key,
41
+ "displayName": self.display_name,
42
+ "controllerTypes": list(self.controller_types),
43
+ "capabilities": ["window_scan", "device_spec"],
44
+ }
45
+
46
+ def list_windows(self) -> list[MaaFWWin32Window]:
47
+ if sys.platform != "win32":
48
+ raise RuntimeError("Win32 窗口扫描仅支持 Windows")
49
+
50
+ from maa.toolkit import Toolkit
51
+
52
+ return [
53
+ MaaFWWin32Window(
54
+ hWnd=_normalize_hwnd(window.hwnd),
55
+ className=str(window.class_name or ""),
56
+ windowName=str(window.window_name or ""),
57
+ )
58
+ for window in Toolkit.find_desktop_windows()
59
+ ]
60
+
61
+ def match_controller_windows(
62
+ self,
63
+ controller: BaseModel | dict[str, Any],
64
+ windows: Iterable[MaaFWWin32Window] | None = None,
65
+ ) -> list[MaaFWWindowMatch]:
66
+ if isinstance(controller, MaaFWController):
67
+ controller_model = controller
68
+ else:
69
+ controller_payload = (
70
+ controller.model_dump(mode="json", by_alias=True)
71
+ if hasattr(controller, "model_dump")
72
+ else controller
73
+ )
74
+ controller_model = MaaFWController.model_validate(controller_payload)
75
+ if controller_model.type != "Win32":
76
+ return []
77
+
78
+ class_regex, window_regex = _controller_window_regex(controller_model)
79
+ source_windows = list(windows) if windows is not None else self.list_windows()
80
+ matched: list[MaaFWWindowMatch] = []
81
+ seen_hwnds: set[int] = set()
82
+
83
+ for window in source_windows:
84
+ if window.hWnd in seen_hwnds:
85
+ continue
86
+ if not _matches_regex(class_regex, window.className):
87
+ continue
88
+ if not _matches_regex(window_regex, window.windowName):
89
+ continue
90
+
91
+ seen_hwnds.add(window.hWnd)
92
+ matched.append(
93
+ MaaFWWindowMatch(
94
+ hWnd=window.hWnd,
95
+ className=window.className,
96
+ windowName=window.windowName,
97
+ controllerName=controller_model.name,
98
+ controllerType=controller_model.type,
99
+ )
100
+ )
101
+
102
+ return matched
103
+
104
+ def build_device_spec(
105
+ self,
106
+ *,
107
+ h_wnd: int | None = None,
108
+ screencap_method: int = 0,
109
+ mouse_method: int = 0,
110
+ keyboard_method: int = 0,
111
+ ) -> dict[str, Any]:
112
+ return {
113
+ "type": "Win32",
114
+ "hWnd": h_wnd,
115
+ "screencapMethod": screencap_method,
116
+ "mouseMethod": mouse_method,
117
+ "keyboardMethod": keyboard_method,
118
+ }
119
+
120
+
121
+ def _controller_window_regex(controller: MaaFWController) -> tuple[str | None, str | None]:
122
+ if controller.type == "Win32" and controller.win32 is not None:
123
+ return controller.win32.class_regex, controller.win32.window_regex
124
+ return None, None
125
+
126
+
127
+ def _matches_regex(pattern: str | None, value: str) -> bool:
128
+ if not pattern:
129
+ return True
130
+ try:
131
+ compiled_pattern = _compile_safe_regex(pattern)
132
+ return compiled_pattern.search(value[:MAX_REGEX_VALUE_LENGTH]) is not None
133
+ except re.error as exc:
134
+ raise RuntimeError(f"interface 窗口匹配正则无效: {pattern}") from exc
135
+
136
+
137
+ def _compile_safe_regex(pattern: str) -> re.Pattern[str]:
138
+ if len(pattern) > MAX_REGEX_PATTERN_LENGTH:
139
+ raise RuntimeError("interface window regex is too long")
140
+ if DANGEROUS_NESTED_QUANTIFIER_RE.search(pattern):
141
+ raise RuntimeError("interface window regex contains nested quantifiers")
142
+ return re.compile(pattern)
143
+
144
+
145
+ def _normalize_hwnd(value: Any) -> int:
146
+ if hasattr(value, "value"):
147
+ value = value.value
148
+ if value is None:
149
+ return 0
150
+ return int(value)
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: automas-maafw-controller-win32
3
+ Version: 0.1.1
4
+ Summary: Win32 controller provider for AUTO-MAS MaaFW plugins
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: automas-maafw-interface>=0.1.0
8
+ Requires-Dist: pydantic>=2
9
+
10
+ # automas-maafw-controller-win32
11
+
12
+ Win32 controller provider for the AUTO-MAS MaaFW plugin group.
13
+
14
+ The service exposes window scanning helpers. `maa.toolkit` is imported lazily
15
+ only when Win32 windows are scanned.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/automas_maafw_controller_win32/__init__.py
4
+ src/automas_maafw_controller_win32/plugin.py
5
+ src/automas_maafw_controller_win32/py.typed
6
+ src/automas_maafw_controller_win32/service.py
7
+ src/automas_maafw_controller_win32.egg-info/PKG-INFO
8
+ src/automas_maafw_controller_win32.egg-info/SOURCES.txt
9
+ src/automas_maafw_controller_win32.egg-info/dependency_links.txt
10
+ src/automas_maafw_controller_win32.egg-info/entry_points.txt
11
+ src/automas_maafw_controller_win32.egg-info/requires.txt
12
+ src/automas_maafw_controller_win32.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [auto_mas.plugins]
2
+ automas_maafw_controller_win32 = automas_maafw_controller_win32.plugin:Plugin
@@ -0,0 +1,2 @@
1
+ automas-maafw-interface>=0.1.0
2
+ pydantic>=2