feat: 新增工具Tab - 剪贴板注入(多选本地adb/STF设备批量注入文字,u2 setClipboard 实现,实测支持中文/引号/换行)
This commit is contained in:
+56
-1
@@ -23,6 +23,7 @@ import shlex
|
||||
import re
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeout
|
||||
|
||||
from flask import (Flask, jsonify, request, redirect, url_for, render_template,
|
||||
Response, make_response, session)
|
||||
@@ -35,7 +36,8 @@ from core.device_worker import clear_worker_error, clear_all_worker_errors
|
||||
from core.logger import get_logger, _LOG_DIR, _MODULE_FILES
|
||||
from core.models import db, init_db, User, DeviceGroup, TaskJob, CustomAction
|
||||
from core.apk_manager import ApkManager
|
||||
from core.adb_helper import screenshot, list_installed_apps, adb_connect_light, _ADB_LOCK, _adb
|
||||
from core.adb_helper import (screenshot, list_installed_apps, adb_connect_light,
|
||||
adb_connect, _ADB_LOCK, _adb)
|
||||
from core import uiauto_helper, tailscale_client
|
||||
from core.tailscale_client import TailscaleError
|
||||
from config import ADB_PATH, STF_SSH_TARGET, STF_DOCKER_CONTAINER
|
||||
@@ -937,6 +939,59 @@ def api_adb_cmd():
|
||||
return jsonify({"ok": False, "error": f"执行失败: {e}"}), 500
|
||||
|
||||
|
||||
@app.route("/api/tools/clipboard/set", methods=["POST"])
|
||||
@admin_required
|
||||
def api_tools_clipboard_set():
|
||||
"""工具-剪贴板注入:把指定文字写入一台或多台设备的剪贴板。
|
||||
|
||||
请求: {"serials": ["100.100.10.11:5555", ...], "text": "要注入的文字"}
|
||||
设备来源与维护终端一致:本地 adb 已连接设备(含 USB)+ STF 在线池。
|
||||
实现:u2 jsonrpc.setClipboard(实测 cmd clipboard 在 MIUI 上不存在)。
|
||||
"""
|
||||
data = request.json or {}
|
||||
serials = data.get("serials") or []
|
||||
text = (data.get("text") or "").strip()
|
||||
if not isinstance(serials, list) or not serials:
|
||||
return jsonify({"ok": False, "error": "未选择设备"}), 400
|
||||
if not text:
|
||||
return jsonify({"ok": False, "error": "注入内容不能为空"}), 400
|
||||
results = {}
|
||||
for serial in serials:
|
||||
ok, msg = _set_device_clipboard(serial, text)
|
||||
results[serial] = {"ok": ok, "msg": msg}
|
||||
ok_count = sum(1 for v in results.values() if v["ok"])
|
||||
fail_count = len(results) - ok_count
|
||||
return jsonify({"ok": True, "results": results,
|
||||
"ok_count": ok_count, "fail_count": fail_count,
|
||||
"error": None if ok_count == len(results) else f"{fail_count} 台设备注入失败"})
|
||||
|
||||
|
||||
def _set_device_clipboard(serial, text):
|
||||
"""向单台设备注入剪贴板文字(u2 jsonrpc.setClipboard)。
|
||||
|
||||
- IP:port 设备:先 adb connect(已连接自动跳过;绝不 disconnect,红线)
|
||||
- USB 设备(无冒号):u2 按 serial 直连,首次会自动推送 atx-agent
|
||||
- 连接/注入都带超时保护,避免 atx-agent 无响应时挂住请求
|
||||
"""
|
||||
import uiautomator2 as u2
|
||||
if ":" in serial:
|
||||
try:
|
||||
adb_connect(serial)
|
||||
except Exception as e:
|
||||
return False, f"adb 连接失败: {e}"
|
||||
d = None
|
||||
try:
|
||||
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||
d = pool.submit(u2.connect, serial).result(timeout=30)
|
||||
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||
pool.submit(d.set_clipboard, text).result(timeout=15)
|
||||
return True, "已注入"
|
||||
except FuturesTimeout:
|
||||
return False, "连接/注入超时(atx-agent 可能无响应)"
|
||||
except Exception as e:
|
||||
return False, f"{type(e).__name__}: {str(e)[:120]}"
|
||||
|
||||
|
||||
@app.route("/api/stf/restart", methods=["POST"])
|
||||
@admin_required
|
||||
def api_stf_restart():
|
||||
|
||||
Reference in New Issue
Block a user