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.
- automas_maafw_controller_win32-0.1.1/PKG-INFO +15 -0
- automas_maafw_controller_win32-0.1.1/README.md +6 -0
- automas_maafw_controller_win32-0.1.1/pyproject.toml +23 -0
- automas_maafw_controller_win32-0.1.1/setup.cfg +4 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32/__init__.py +3 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32/plugin.py +47 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32/py.typed +1 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32/service.py +150 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/PKG-INFO +15 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/SOURCES.txt +12 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/dependency_links.txt +1 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/entry_points.txt +2 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/requires.txt +2 -0
- automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/top_level.txt +1 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -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 @@
|
|
|
1
|
+
|
automas_maafw_controller_win32-0.1.1/src/automas_maafw_controller_win32.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
automas_maafw_controller_win32
|