answer42 0.2.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.
- answer42-0.2.0.dist-info/METADATA +388 -0
- answer42-0.2.0.dist-info/RECORD +28 -0
- answer42-0.2.0.dist-info/WHEEL +4 -0
- answer42-0.2.0.dist-info/entry_points.txt +2 -0
- answer42-0.2.0.dist-info/licenses/LICENSE +21 -0
- mcp_1c/__init__.py +4 -0
- mcp_1c/assets/MCPTestClient.cf +0 -0
- mcp_1c/assets/MCPTestManager.cf +0 -0
- mcp_1c/assets/__init__.py +1 -0
- mcp_1c/assets/skills/answer42/SKILL.md +170 -0
- mcp_1c/assets/skills/answer42-rag/SKILL.md +58 -0
- mcp_1c/bridge.py +136 -0
- mcp_1c/credentials.py +147 -0
- mcp_1c/os_support.py +224 -0
- mcp_1c/platform.py +187 -0
- mcp_1c/protocol.py +35 -0
- mcp_1c/rag/__init__.py +5 -0
- mcp_1c/rag/detect.py +23 -0
- mcp_1c/rag/model.py +114 -0
- mcp_1c/rag/parsers.py +387 -0
- mcp_1c/rag/service.py +375 -0
- mcp_1c/rag/store.py +228 -0
- mcp_1c/recorder.py +239 -0
- mcp_1c/release_helper.py +83 -0
- mcp_1c/runtime.py +636 -0
- mcp_1c/server.py +3285 -0
- mcp_1c/skill_installer.py +127 -0
- mcp_1c/window_control.py +276 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Install packaged Answer42 skills into popular agent skill directories."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import importlib.resources
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
PACKAGED_SKILLS = ("answer42", "answer42-rag")
|
|
11
|
+
|
|
12
|
+
AGENT_SKILL_DIRS = {
|
|
13
|
+
# OpenClaw workspace skills.
|
|
14
|
+
"openclaw": Path("~/.openclaw/workspace/skills"),
|
|
15
|
+
# Claude Code / Claude Desktop style user skills directory.
|
|
16
|
+
"claude": Path("~/.claude/skills"),
|
|
17
|
+
# Best-effort conventional locations for agents that do not expose a single
|
|
18
|
+
# portable skill standard yet. Users can always pass --target-dir explicitly.
|
|
19
|
+
"codex": Path("~/.codex/skills"),
|
|
20
|
+
"opencode": Path("~/.config/opencode/skills"),
|
|
21
|
+
"pi": Path("~/.pi/skills"),
|
|
22
|
+
"hermes": Path("~/.hermes/skills"),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _copy_tree(src: Path, dst: Path, *, force: bool, dry_run: bool) -> str:
|
|
27
|
+
existed = dst.exists()
|
|
28
|
+
if existed:
|
|
29
|
+
if not force:
|
|
30
|
+
return "skipped-exists"
|
|
31
|
+
if not dry_run:
|
|
32
|
+
shutil.rmtree(dst)
|
|
33
|
+
if not dry_run:
|
|
34
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
shutil.copytree(src, dst)
|
|
36
|
+
return "updated" if existed else "installed"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def install_skills(
|
|
40
|
+
*,
|
|
41
|
+
agents: list[str],
|
|
42
|
+
target_dir: Path | None,
|
|
43
|
+
skills: list[str],
|
|
44
|
+
force: bool,
|
|
45
|
+
dry_run: bool,
|
|
46
|
+
) -> list[dict[str, str]]:
|
|
47
|
+
selected_skills = skills or list(PACKAGED_SKILLS)
|
|
48
|
+
unknown_skills = sorted(set(selected_skills) - set(PACKAGED_SKILLS))
|
|
49
|
+
if unknown_skills:
|
|
50
|
+
raise ValueError(f"Unknown packaged skill(s): {', '.join(unknown_skills)}")
|
|
51
|
+
|
|
52
|
+
targets: list[tuple[str, Path]] = []
|
|
53
|
+
if target_dir is not None:
|
|
54
|
+
targets.append(("custom", target_dir.expanduser()))
|
|
55
|
+
for agent in agents:
|
|
56
|
+
if agent == "all":
|
|
57
|
+
targets.extend((name, path.expanduser()) for name, path in AGENT_SKILL_DIRS.items())
|
|
58
|
+
continue
|
|
59
|
+
path = AGENT_SKILL_DIRS.get(agent)
|
|
60
|
+
if path is None:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"Unknown agent {agent!r}. Known: {', '.join(sorted(AGENT_SKILL_DIRS))}, all"
|
|
63
|
+
)
|
|
64
|
+
targets.append((agent, path.expanduser()))
|
|
65
|
+
if not targets:
|
|
66
|
+
targets.append(("openclaw", AGENT_SKILL_DIRS["openclaw"].expanduser()))
|
|
67
|
+
|
|
68
|
+
results: list[dict[str, str]] = []
|
|
69
|
+
root = importlib.resources.files("mcp_1c.assets").joinpath("skills")
|
|
70
|
+
with importlib.resources.as_file(root) as root_path:
|
|
71
|
+
for skill in selected_skills:
|
|
72
|
+
src = root_path / skill
|
|
73
|
+
if not src.exists():
|
|
74
|
+
raise FileNotFoundError(f"Packaged skill not found: {skill}")
|
|
75
|
+
for agent, base_dir in targets:
|
|
76
|
+
dst = base_dir / skill
|
|
77
|
+
status = _copy_tree(src, dst, force=force, dry_run=dry_run)
|
|
78
|
+
results.append(
|
|
79
|
+
{
|
|
80
|
+
"agent": agent,
|
|
81
|
+
"skill": skill,
|
|
82
|
+
"path": str(dst),
|
|
83
|
+
"status": "would-" + status if dry_run else status,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
return results
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main(argv: list[str] | None = None) -> int:
|
|
90
|
+
parser = argparse.ArgumentParser(description="Install packaged Answer42 skills")
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--agent",
|
|
93
|
+
action="append",
|
|
94
|
+
choices=[*sorted(AGENT_SKILL_DIRS), "all"],
|
|
95
|
+
help="Agent preset to install into. Can be passed multiple times. Default: openclaw.",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--target-dir",
|
|
99
|
+
type=Path,
|
|
100
|
+
help="Custom skills directory. Use this for agents with non-standard or project-local paths.",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--skill",
|
|
104
|
+
action="append",
|
|
105
|
+
choices=list(PACKAGED_SKILLS),
|
|
106
|
+
help="Packaged skill to install. Can be passed multiple times. Default: all packaged skills.",
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument("--no-overwrite", action="store_true", help="Skip skills that already exist.")
|
|
109
|
+
parser.add_argument("--dry-run", action="store_true", help="Print planned changes without writing files.")
|
|
110
|
+
parser.add_argument("--list-agents", action="store_true", help="Print known agent presets and exit.")
|
|
111
|
+
args = parser.parse_args(argv)
|
|
112
|
+
|
|
113
|
+
if args.list_agents:
|
|
114
|
+
for name, path in sorted(AGENT_SKILL_DIRS.items()):
|
|
115
|
+
print(f"{name}: {path.expanduser()}")
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
results = install_skills(
|
|
119
|
+
agents=args.agent or [],
|
|
120
|
+
target_dir=args.target_dir,
|
|
121
|
+
skills=args.skill or [],
|
|
122
|
+
force=not args.no_overwrite,
|
|
123
|
+
dry_run=args.dry_run,
|
|
124
|
+
)
|
|
125
|
+
for item in results:
|
|
126
|
+
print(f"{item['status']}: {item['agent']}:{item['skill']} -> {item['path']}")
|
|
127
|
+
return 0
|
mcp_1c/window_control.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import ctypes.wintypes
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class WindowControlResult:
|
|
15
|
+
ok: bool
|
|
16
|
+
method: str
|
|
17
|
+
title: str = ""
|
|
18
|
+
geometry: dict[str, int] | None = None
|
|
19
|
+
candidates: list[dict[str, Any]] | None = None
|
|
20
|
+
error: str = ""
|
|
21
|
+
|
|
22
|
+
def as_dict(self) -> dict[str, Any]:
|
|
23
|
+
return {
|
|
24
|
+
"ok": self.ok,
|
|
25
|
+
"method": self.method,
|
|
26
|
+
"title": self.title,
|
|
27
|
+
"geometry": self.geometry or {},
|
|
28
|
+
"candidates": self.candidates or [],
|
|
29
|
+
"error": self.error,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def maximize_test_client_window(
|
|
34
|
+
title_contains: str = "",
|
|
35
|
+
display: str | None = None,
|
|
36
|
+
width: int | None = None,
|
|
37
|
+
height: int | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
"""Maximize/resize a 1C test-client window.
|
|
40
|
+
|
|
41
|
+
Priority: Windows WinAPI on Windows; wmctrl -> xdotool -> python-xlib on X11.
|
|
42
|
+
This function controls only window geometry; it doesn't click or type in the application.
|
|
43
|
+
"""
|
|
44
|
+
if os.name == "nt":
|
|
45
|
+
return _maximize_with_winapi(title_contains, width, height).as_dict()
|
|
46
|
+
|
|
47
|
+
env = os.environ.copy()
|
|
48
|
+
if display:
|
|
49
|
+
env["DISPLAY"] = display
|
|
50
|
+
display = env.get("DISPLAY", "")
|
|
51
|
+
|
|
52
|
+
for fn in (_maximize_with_wmctrl, _maximize_with_xdotool, _maximize_with_xlib):
|
|
53
|
+
result = fn(title_contains=title_contains, display=display, width=width, height=height, env=env)
|
|
54
|
+
if result.ok:
|
|
55
|
+
return result.as_dict()
|
|
56
|
+
last = result
|
|
57
|
+
return last.as_dict()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def active_1c_window_geometry(title_contains: str = "") -> dict[str, int] | None:
|
|
61
|
+
"""Return best visible 1C window geometry on Windows, or None elsewhere."""
|
|
62
|
+
if os.name != "nt":
|
|
63
|
+
return None
|
|
64
|
+
chosen = _find_windows_1c_window(title_contains)
|
|
65
|
+
if not chosen:
|
|
66
|
+
return None
|
|
67
|
+
return chosen.get("geometry")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _screen_size(display: str, env: dict[str, str]) -> tuple[int, int]:
|
|
71
|
+
try:
|
|
72
|
+
out = subprocess.check_output(["xdpyinfo", "-display", display], env=env, text=True, stderr=subprocess.DEVNULL)
|
|
73
|
+
for line in out.splitlines():
|
|
74
|
+
if "dimensions:" in line:
|
|
75
|
+
part = line.split("dimensions:", 1)[1].strip().split()[0]
|
|
76
|
+
w, h = part.split("x")
|
|
77
|
+
return int(w), int(h)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return 1920, 1080
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _maximize_with_wmctrl(title_contains: str, display: str, width: int | None, height: int | None, env: dict[str, str]) -> WindowControlResult:
|
|
84
|
+
if not shutil.which("wmctrl"):
|
|
85
|
+
return WindowControlResult(False, "wmctrl", error="wmctrl not installed")
|
|
86
|
+
try:
|
|
87
|
+
sw, sh = (width, height) if width and height else _screen_size(display, env)
|
|
88
|
+
out = subprocess.check_output(["wmctrl", "-l"], env=env, text=True)
|
|
89
|
+
candidates=[]
|
|
90
|
+
chosen=None
|
|
91
|
+
for line in out.splitlines():
|
|
92
|
+
parts=line.split(None, 3)
|
|
93
|
+
if len(parts) < 4:
|
|
94
|
+
continue
|
|
95
|
+
wid, _, _, title = parts
|
|
96
|
+
item={"id": wid, "title": title}
|
|
97
|
+
candidates.append(item)
|
|
98
|
+
if _title_matches(title, title_contains):
|
|
99
|
+
chosen=item
|
|
100
|
+
if chosen is None:
|
|
101
|
+
return WindowControlResult(False, "wmctrl", candidates=candidates, error="window not found")
|
|
102
|
+
wid=chosen["id"]
|
|
103
|
+
subprocess.check_call(["wmctrl", "-i", "-r", wid, "-b", "remove,maximized_vert,maximized_horz"], env=env)
|
|
104
|
+
subprocess.check_call(["wmctrl", "-i", "-r", wid, "-e", f"0,0,0,{sw},{sh}"], env=env)
|
|
105
|
+
subprocess.check_call(["wmctrl", "-i", "-r", wid, "-b", "add,maximized_vert,maximized_horz"], env=env)
|
|
106
|
+
return WindowControlResult(True, "wmctrl", title=chosen["title"], geometry={"x":0,"y":0,"width":sw,"height":sh}, candidates=candidates)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
return WindowControlResult(False, "wmctrl", error=str(e))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _maximize_with_xdotool(title_contains: str, display: str, width: int | None, height: int | None, env: dict[str, str]) -> WindowControlResult:
|
|
112
|
+
if not shutil.which("xdotool"):
|
|
113
|
+
return WindowControlResult(False, "xdotool", error="xdotool not installed")
|
|
114
|
+
try:
|
|
115
|
+
sw, sh = (width, height) if width and height else _screen_size(display, env)
|
|
116
|
+
pattern = title_contains or ".*"
|
|
117
|
+
ids = subprocess.check_output(["xdotool", "search", "--name", pattern], env=env, text=True).split()
|
|
118
|
+
if not ids:
|
|
119
|
+
return WindowControlResult(False, "xdotool", error="window not found")
|
|
120
|
+
wid=ids[-1]
|
|
121
|
+
title = subprocess.check_output(["xdotool", "getwindowname", wid], env=env, text=True).strip()
|
|
122
|
+
subprocess.check_call(["xdotool", "windowsize", wid, str(sw), str(sh)], env=env)
|
|
123
|
+
subprocess.check_call(["xdotool", "windowmove", wid, "0", "0"], env=env)
|
|
124
|
+
subprocess.check_call(["xdotool", "windowactivate", wid], env=env)
|
|
125
|
+
return WindowControlResult(True, "xdotool", title=title, geometry={"x":0,"y":0,"width":sw,"height":sh})
|
|
126
|
+
except Exception as e:
|
|
127
|
+
return WindowControlResult(False, "xdotool", error=str(e))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _title_matches(title: str, title_contains: str) -> bool:
|
|
131
|
+
if title_contains and title_contains.lower() not in title.lower():
|
|
132
|
+
return False
|
|
133
|
+
if "Конфигурация" in title:
|
|
134
|
+
return False
|
|
135
|
+
return any(s in title for s in ["DEV-AREA", "Начальная", "Приложения", "1С:Предприятие", "ЭмФешн", "Время создания"])
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _maximize_with_xlib(title_contains: str, display: str, width: int | None, height: int | None, env: dict[str, str]) -> WindowControlResult:
|
|
139
|
+
try:
|
|
140
|
+
from Xlib import X, display as xdisplay
|
|
141
|
+
disp = xdisplay.Display(display or None)
|
|
142
|
+
root = disp.screen().root
|
|
143
|
+
sw = width or disp.screen().width_in_pixels
|
|
144
|
+
sh = height or disp.screen().height_in_pixels
|
|
145
|
+
candidates=[]
|
|
146
|
+
def walk(win):
|
|
147
|
+
yield win
|
|
148
|
+
try:
|
|
149
|
+
children = win.query_tree().children
|
|
150
|
+
except Exception:
|
|
151
|
+
return
|
|
152
|
+
for child in children:
|
|
153
|
+
yield from walk(child)
|
|
154
|
+
def get_name(win):
|
|
155
|
+
for atom_name in ("_NET_WM_NAME", "WM_NAME"):
|
|
156
|
+
try:
|
|
157
|
+
prop=win.get_full_property(disp.intern_atom(atom_name), X.AnyPropertyType)
|
|
158
|
+
if prop and prop.value is not None:
|
|
159
|
+
value=prop.value
|
|
160
|
+
if isinstance(value, bytes):
|
|
161
|
+
return value.decode("utf-8", "ignore")
|
|
162
|
+
try:
|
|
163
|
+
return bytes(value).decode("utf-8", "ignore")
|
|
164
|
+
except Exception:
|
|
165
|
+
return str(value)
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
return ""
|
|
169
|
+
chosen=None
|
|
170
|
+
for win in walk(root):
|
|
171
|
+
title=get_name(win)
|
|
172
|
+
if not title:
|
|
173
|
+
continue
|
|
174
|
+
try:
|
|
175
|
+
geo=win.get_geometry()
|
|
176
|
+
except Exception:
|
|
177
|
+
continue
|
|
178
|
+
if geo.width < 300 or geo.height < 200:
|
|
179
|
+
continue
|
|
180
|
+
item={"title": title, "width": geo.width, "height": geo.height}
|
|
181
|
+
candidates.append(item)
|
|
182
|
+
if _title_matches(title, title_contains):
|
|
183
|
+
chosen=(win, item)
|
|
184
|
+
if chosen is None:
|
|
185
|
+
return WindowControlResult(False, "xlib", candidates=candidates, error="window not found")
|
|
186
|
+
win,item=chosen
|
|
187
|
+
win.configure(x=0, y=0, width=sw, height=sh, border_width=0, stack_mode=X.Above)
|
|
188
|
+
try:
|
|
189
|
+
win.set_input_focus(X.RevertToParent, X.CurrentTime)
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
win.map()
|
|
193
|
+
disp.sync()
|
|
194
|
+
time.sleep(0.2)
|
|
195
|
+
return WindowControlResult(True, "xlib", title=item["title"], geometry={"x":0,"y":0,"width":sw,"height":sh}, candidates=candidates)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
return WindowControlResult(False, "xlib", error=str(e))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _find_windows_1c_window(title_contains: str = "") -> dict[str, Any] | None:
|
|
201
|
+
user32 = ctypes.windll.user32
|
|
202
|
+
candidates: list[dict[str, Any]] = []
|
|
203
|
+
|
|
204
|
+
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
|
|
205
|
+
|
|
206
|
+
def callback(hwnd: int, _lparam: int) -> bool:
|
|
207
|
+
try:
|
|
208
|
+
if not user32.IsWindowVisible(hwnd):
|
|
209
|
+
return True
|
|
210
|
+
length = user32.GetWindowTextLengthW(hwnd)
|
|
211
|
+
if length <= 0:
|
|
212
|
+
return True
|
|
213
|
+
buf = ctypes.create_unicode_buffer(length + 1)
|
|
214
|
+
user32.GetWindowTextW(hwnd, buf, length + 1)
|
|
215
|
+
title = buf.value
|
|
216
|
+
if not _title_matches(title, title_contains):
|
|
217
|
+
return True
|
|
218
|
+
rect = ctypes.wintypes.RECT() # type: ignore[attr-defined]
|
|
219
|
+
if not user32.GetWindowRect(hwnd, ctypes.byref(rect)):
|
|
220
|
+
return True
|
|
221
|
+
width = int(rect.right - rect.left)
|
|
222
|
+
height = int(rect.bottom - rect.top)
|
|
223
|
+
if width < 300 or height < 150:
|
|
224
|
+
return True
|
|
225
|
+
priority = min(width * height // 10000, 50)
|
|
226
|
+
if "1С:Предприятие" in title:
|
|
227
|
+
priority += 50
|
|
228
|
+
if title and title not in {"1cv8c", "1cv8", "Конфигурация"}:
|
|
229
|
+
priority += 100
|
|
230
|
+
candidates.append({
|
|
231
|
+
"hwnd": hwnd,
|
|
232
|
+
"title": title,
|
|
233
|
+
"priority": priority,
|
|
234
|
+
"geometry": {"left": int(rect.left), "top": int(rect.top), "width": width, "height": height, "title": title},
|
|
235
|
+
})
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
user32.EnumWindows(EnumWindowsProc(callback), 0)
|
|
242
|
+
except Exception:
|
|
243
|
+
return None
|
|
244
|
+
if not candidates:
|
|
245
|
+
return None
|
|
246
|
+
candidates.sort(key=lambda item: int(item.get("priority") or 0), reverse=True)
|
|
247
|
+
return candidates[0]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _maximize_with_winapi(title_contains: str, width: int | None, height: int | None) -> WindowControlResult:
|
|
251
|
+
try:
|
|
252
|
+
import ctypes.wintypes # type: ignore # noqa: F401
|
|
253
|
+
|
|
254
|
+
user32 = ctypes.windll.user32
|
|
255
|
+
chosen = _find_windows_1c_window(title_contains)
|
|
256
|
+
if chosen is None:
|
|
257
|
+
return WindowControlResult(False, "winapi", error="window not found")
|
|
258
|
+
hwnd = int(chosen["hwnd"])
|
|
259
|
+
screen_w = width or user32.GetSystemMetrics(0) or 1920
|
|
260
|
+
screen_h = height or user32.GetSystemMetrics(1) or 1080
|
|
261
|
+
user32.ShowWindow(hwnd, 9) # SW_RESTORE
|
|
262
|
+
user32.MoveWindow(hwnd, 0, 0, int(screen_w), int(screen_h), True)
|
|
263
|
+
user32.ShowWindow(hwnd, 3) # SW_MAXIMIZE
|
|
264
|
+
try:
|
|
265
|
+
user32.SetForegroundWindow(hwnd)
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
return WindowControlResult(
|
|
269
|
+
True,
|
|
270
|
+
"winapi",
|
|
271
|
+
title=str(chosen.get("title") or ""),
|
|
272
|
+
geometry={"x": 0, "y": 0, "width": int(screen_w), "height": int(screen_h)},
|
|
273
|
+
candidates=[{"title": str(chosen.get("title") or ""), "hwnd": hwnd}],
|
|
274
|
+
)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
return WindowControlResult(False, "winapi", error=str(e))
|