feat: 新增纯坐标模式开关与文本输入优化
1. 新增WOC_DISABLE_OPENCV环境变量支持纯坐标定位模式 2. 优化xdotool文本输入:替换换行符为空格、添加30ms输入延迟 3. 新增输入框清空逻辑,避免内容残留
This commit is contained in:
parent
be93906635
commit
3effe1338c
@ -31,6 +31,11 @@ _STEP_MIN_TIMEOUT_SEC = 7.0
|
|||||||
_FLOW_TIMEOUT_SEC = 30.0
|
_FLOW_TIMEOUT_SEC = 30.0
|
||||||
_HTTP_TIMEOUT_SEC = 60.0
|
_HTTP_TIMEOUT_SEC = 60.0
|
||||||
|
|
||||||
|
# xdotool type 每字符间隔(毫秒):默认值 12ms 对中文 IME 过快,
|
||||||
|
# 容易导致字符丢失;提高到 30ms 给微信 UI 足够处理时间。
|
||||||
|
# 可通过环境变量 WOC_TYPE_DELAY_MS 覆盖。
|
||||||
|
_TYPE_DELAY_MS = int(os.environ.get("WOC_TYPE_DELAY_MS", "30"))
|
||||||
|
|
||||||
|
|
||||||
class XdotoolBackend(BackendProtocol):
|
class XdotoolBackend(BackendProtocol):
|
||||||
"""xdotool + scrot 后端实现。
|
"""xdotool + scrot 后端实现。
|
||||||
@ -170,15 +175,27 @@ class XdotoolBackend(BackendProtocol):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def type_text(self, text: str) -> None:
|
async def type_text(self, text: str) -> None:
|
||||||
"""xdotool type -- <text>:原样输入文本,不解析修饰键组合。"""
|
"""xdotool type --delay <ms> -- <text>:原样输入文本,不解析修饰键组合。
|
||||||
|
|
||||||
|
使用 --delay 控制每字符间隔,避免中文 IME 处理不及导致字符丢失。
|
||||||
|
"""
|
||||||
text_len = len(text)
|
text_len = len(text)
|
||||||
logger.info(
|
logger.info(
|
||||||
"[ui-backend] type_text start len=%d preview=%r",
|
"[ui-backend] type_text start len=%d delay=%dms preview=%r",
|
||||||
text_len, text[:30],
|
text_len, _TYPE_DELAY_MS, text[:30],
|
||||||
)
|
)
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
async with self._ui_lock:
|
async with self._ui_lock:
|
||||||
await self._run(["xdotool", "type", "--", text])
|
await self._run(
|
||||||
|
[
|
||||||
|
"xdotool",
|
||||||
|
"type",
|
||||||
|
"--delay",
|
||||||
|
str(_TYPE_DELAY_MS),
|
||||||
|
"--",
|
||||||
|
text,
|
||||||
|
]
|
||||||
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"[ui-backend] type_text done len=%d (%.0fms)",
|
"[ui-backend] type_text done len=%d (%.0fms)",
|
||||||
text_len, (time.perf_counter() - t0) * 1000,
|
text_len, (time.perf_counter() - t0) * 1000,
|
||||||
|
|||||||
@ -164,11 +164,20 @@ class ContactDriver(BaseDriver):
|
|||||||
微信 4.x Linux 自绘 UI 不响应 Ctrl+V,改用 xdotool type 逐字符输入。
|
微信 4.x Linux 自绘 UI 不响应 Ctrl+V,改用 xdotool type 逐字符输入。
|
||||||
方法名保留 _paste_via_xclip 以维持调用点稳定。
|
方法名保留 _paste_via_xclip 以维持调用点稳定。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 把 \n / \r 替换为空格,避免 xdotool 把换行解析为 Return 键。
|
||||||
|
- 使用 --delay 30ms 降低输入速度,减少中文 IME 字符丢失。
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
BridgeError(SEND_FAILED): xdotool type 执行失败
|
BridgeError(SEND_FAILED): xdotool type 执行失败
|
||||||
"""
|
"""
|
||||||
|
normalized = text.replace("\r\n", " ").replace("\n", " ").replace("\r", " ")
|
||||||
|
normalized = " ".join(normalized.split())
|
||||||
|
if normalized != text:
|
||||||
|
logger.info("[ui] type text normalized: original_len=%d len=%d", len(text), len(normalized))
|
||||||
|
text = normalized
|
||||||
logger.info("[ui] type text: len=%d", len(text))
|
logger.info("[ui] type text: len=%d", len(text))
|
||||||
rc, _, stderr = await self._run(["xdotool", "type", text])
|
rc, _, stderr = await self._run(["xdotool", "type", "--delay", "30", "--", text])
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
||||||
raise BridgeError(
|
raise BridgeError(
|
||||||
|
|||||||
@ -117,11 +117,21 @@ class MessageDriver(BaseDriver):
|
|||||||
而 xdotool type(逐字符 XSendEvent)能正常输入中英文。因此改用
|
而 xdotool type(逐字符 XSendEvent)能正常输入中英文。因此改用
|
||||||
xdotool type 直接打字。方法名保留 _paste_via_xclip 以维持调用点稳定。
|
xdotool type 直接打字。方法名保留 _paste_via_xclip 以维持调用点稳定。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 把 \n / \r 替换为空格,避免 xdotool 把换行解析为 Return 键触发发送。
|
||||||
|
- 使用 --delay 30ms 降低输入速度,减少中文 IME 字符丢失。
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
BridgeError(SEND_FAILED): xdotool type 执行失败
|
BridgeError(SEND_FAILED): xdotool type 执行失败
|
||||||
"""
|
"""
|
||||||
|
# 规范化:去除换行,避免 Return 误触发
|
||||||
|
normalized = text.replace("\r\n", " ").replace("\n", " ").replace("\r", " ")
|
||||||
|
normalized = " ".join(normalized.split())
|
||||||
|
if normalized != text:
|
||||||
|
logger.info("[ui] type text normalized: original_len=%d len=%d", len(text), len(normalized))
|
||||||
|
text = normalized
|
||||||
logger.info("[ui] type text: len=%d", len(text))
|
logger.info("[ui] type text: len=%d", len(text))
|
||||||
rc, _, stderr = await self._run(["xdotool", "type", text])
|
rc, _, stderr = await self._run(["xdotool", "type", "--delay", "30", "--", text])
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
||||||
raise BridgeError(
|
raise BridgeError(
|
||||||
|
|||||||
@ -92,11 +92,20 @@ class MomentDriver(BaseDriver):
|
|||||||
微信 4.x Linux 自绘 UI 不响应 Ctrl+V,改用 xdotool type 逐字符输入。
|
微信 4.x Linux 自绘 UI 不响应 Ctrl+V,改用 xdotool type 逐字符输入。
|
||||||
方法名保留 _paste_via_xclip 以维持调用点稳定。
|
方法名保留 _paste_via_xclip 以维持调用点稳定。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 把 \n / \r 替换为空格,避免 xdotool 把换行解析为 Return 键。
|
||||||
|
- 使用 --delay 30ms 降低输入速度,减少中文 IME 字符丢失。
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
BridgeError(SEND_FAILED): xdotool type 执行失败
|
BridgeError(SEND_FAILED): xdotool type 执行失败
|
||||||
"""
|
"""
|
||||||
|
normalized = text.replace("\r\n", " ").replace("\n", " ").replace("\r", " ")
|
||||||
|
normalized = " ".join(normalized.split())
|
||||||
|
if normalized != text:
|
||||||
|
logger.info("[ui] type text normalized: original_len=%d len=%d", len(text), len(normalized))
|
||||||
|
text = normalized
|
||||||
logger.info("[ui] type text: len=%d", len(text))
|
logger.info("[ui] type text: len=%d", len(text))
|
||||||
rc, _, stderr = await self._run(["xdotool", "type", text])
|
rc, _, stderr = await self._run(["xdotool", "type", "--delay", "30", "--", text])
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
err = stderr.decode(errors="ignore").strip() if stderr else "unknown"
|
||||||
raise BridgeError(
|
raise BridgeError(
|
||||||
|
|||||||
@ -270,19 +270,29 @@ class SendTextFlow(Flow):
|
|||||||
async def _type_content(self, ctx: FlowContext) -> None:
|
async def _type_content(self, ctx: FlowContext) -> None:
|
||||||
"""输入消息内容。"""
|
"""输入消息内容。"""
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
# 清理开头/结尾的空白和换行,避免输入框视觉为空、发送按钮灰色
|
# 1. 清理首尾空白
|
||||||
cleaned_content = ctx.content.strip()
|
cleaned_content = ctx.content.strip()
|
||||||
if cleaned_content != ctx.content:
|
# 2. 把换行符(含 \r\n / \n / \r)替换为空格,避免 xdotool type 把 \n
|
||||||
|
# 解析为 Return 键,导致消息在输入过程中被提前分段发送。
|
||||||
|
# 微信输入框本身支持 Shift+Enter 换行,但 xdotool type 无法区分。
|
||||||
|
normalized_content = cleaned_content.replace("\r\n", " ").replace("\n", " ").replace("\r", " ")
|
||||||
|
# 3. 合并连续空格为一个,保持内容整洁
|
||||||
|
normalized_content = " ".join(normalized_content.split())
|
||||||
|
if normalized_content != ctx.content:
|
||||||
logger.info(
|
logger.info(
|
||||||
"[flow:send_text] step=type_content stripped leading/trailing whitespace: "
|
"[flow:send_text] step=type_content normalized: "
|
||||||
"original_len=%d cleaned_len=%d",
|
"original_len=%d cleaned_len=%d",
|
||||||
len(ctx.content), len(cleaned_content),
|
len(ctx.content), len(normalized_content),
|
||||||
)
|
)
|
||||||
ctx.content = cleaned_content
|
ctx.content = normalized_content
|
||||||
logger.info(
|
logger.info(
|
||||||
"[flow:send_text] step=type_content start content_len=%d",
|
"[flow:send_text] step=type_content start content_len=%d",
|
||||||
len(ctx.content),
|
len(ctx.content),
|
||||||
)
|
)
|
||||||
|
# 4. 输入前清空输入框,避免上次残留内容与本次内容混合
|
||||||
|
await self.actions.backend.key_press("ctrl+a")
|
||||||
|
await self.actions.backend.key_press("Delete")
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
await self.actions.backend.type_text(ctx.content)
|
await self.actions.backend.type_text(ctx.content)
|
||||||
ctx.current_state = FlowState.TEXT_TYPED
|
ctx.current_state = FlowState.TEXT_TYPED
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -62,6 +62,9 @@ services:
|
|||||||
- WOC_AUTO_ACCEPT_POLL_INTERVAL=${WOC_AUTO_ACCEPT_POLL_INTERVAL:-3}
|
- WOC_AUTO_ACCEPT_POLL_INTERVAL=${WOC_AUTO_ACCEPT_POLL_INTERVAL:-3}
|
||||||
- WOC_UI_BACKEND=${WOC_UI_BACKEND:-flow}
|
- WOC_UI_BACKEND=${WOC_UI_BACKEND:-flow}
|
||||||
- WOC_UI_DEBUG_SHOTS=${WOC_UI_DEBUG_SHOTS:-false}
|
- WOC_UI_DEBUG_SHOTS=${WOC_UI_DEBUG_SHOTS:-false}
|
||||||
|
# 纯坐标模式:=1 时 bridge 跳过 OpenCV 模板匹配,直接用窗口比例坐标定位 UI 元素。
|
||||||
|
# 适用于模板图像不可靠的场景,避免假匹配/假阳性。留空=用 OpenCV(需正确模板)。
|
||||||
|
- WOC_DISABLE_OPENCV=${WOC_DISABLE_OPENCV:-1}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# 面板账号数据(用户、实例元信息、密码哈希)
|
# 面板账号数据(用户、实例元信息、密码哈希)
|
||||||
|
|||||||
@ -201,6 +201,7 @@ function envList(inst: Instance): string[] {
|
|||||||
'WOC_AUTO_ACCEPT_POLL_INTERVAL',
|
'WOC_AUTO_ACCEPT_POLL_INTERVAL',
|
||||||
'WOC_UI_BACKEND',
|
'WOC_UI_BACKEND',
|
||||||
'WOC_UI_DEBUG_SHOTS',
|
'WOC_UI_DEBUG_SHOTS',
|
||||||
|
'WOC_DISABLE_OPENCV',
|
||||||
]) {
|
]) {
|
||||||
const v = process.env[k];
|
const v = process.env[k];
|
||||||
if (v) env.push(`${k}=${v}`);
|
if (v) env.push(`${k}=${v}`);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user