183 lines
6.3 KiB
Python
183 lines
6.3 KiB
Python
# 运行在 Windows 虚拟机内部
|
||
from flask import Flask, request, send_file
|
||
import pyautogui
|
||
import io
|
||
import os
|
||
import subprocess
|
||
import ctypes
|
||
import time
|
||
|
||
app = Flask(__name__)
|
||
|
||
# 获取Windows DPI缩放比例
|
||
def get_dpi_scale():
|
||
"""获取Windows的DPI缩放比例"""
|
||
try:
|
||
# 获取主显示器的DPI缩放比例
|
||
scale_factor = ctypes.windll.shcore.GetScaleFactorForDevice(0) / 100.0
|
||
return scale_factor
|
||
except:
|
||
# 如果获取失败,默认返回1.0(无缩放)
|
||
return 1.0
|
||
|
||
# 获取实际屏幕分辨率
|
||
def get_screen_size():
|
||
"""获取实际屏幕分辨率(物理像素)"""
|
||
try:
|
||
user32 = ctypes.windll.user32
|
||
width = user32.GetSystemMetrics(0) # SM_CXSCREEN
|
||
height = user32.GetSystemMetrics(1) # SM_CYSCREEN
|
||
return width, height
|
||
except:
|
||
# 如果获取失败,使用 pyautogui 的方法
|
||
return pyautogui.size()
|
||
|
||
DPI_SCALE = get_dpi_scale()
|
||
SCREEN_WIDTH, SCREEN_HEIGHT = get_screen_size()
|
||
print(f"检测到DPI缩放比例: {DPI_SCALE}")
|
||
print(f"实际屏幕分辨率: {SCREEN_WIDTH} x {SCREEN_HEIGHT}")
|
||
|
||
# 获取截图分辨率(用于坐标转换)
|
||
def get_screenshot_size():
|
||
"""获取截图的实际分辨率"""
|
||
img = pyautogui.screenshot()
|
||
return img.size[0], img.size[1]
|
||
|
||
SCREENSHOT_WIDTH, SCREENSHOT_HEIGHT = get_screenshot_size()
|
||
print(f"截图分辨率: {SCREENSHOT_WIDTH} x {SCREENSHOT_HEIGHT}")
|
||
|
||
# 1. 获取屏幕截图
|
||
@app.route('/screenshot', methods=['GET'])
|
||
def screenshot():
|
||
img = pyautogui.screenshot()
|
||
img_io = io.BytesIO()
|
||
img.save(img_io, 'PNG')
|
||
img_io.seek(0)
|
||
return send_file(img_io, mimetype='image/png')
|
||
|
||
# 获取分辨率信息(用于调试)
|
||
@app.route('/screen_info', methods=['GET'])
|
||
def screen_info():
|
||
"""返回屏幕和截图的分辨率信息,用于调试坐标转换"""
|
||
screenshot_w, screenshot_h = get_screenshot_size()
|
||
return {
|
||
"screen_width": SCREEN_WIDTH,
|
||
"screen_height": SCREEN_HEIGHT,
|
||
"screenshot_width": screenshot_w,
|
||
"screenshot_height": screenshot_h,
|
||
"dpi_scale": DPI_SCALE,
|
||
"scale_ratio_x": SCREEN_WIDTH / screenshot_w if screenshot_w > 0 else 1.0,
|
||
"scale_ratio_y": SCREEN_HEIGHT / screenshot_h if screenshot_h > 0 else 1.0
|
||
}
|
||
|
||
# 2. 执行动作
|
||
@app.route('/action', methods=['POST'])
|
||
def action():
|
||
data = request.json
|
||
try:
|
||
if data['type'] == 'click':
|
||
# 获取当前截图分辨率(可能每次不同)
|
||
screenshot_w, screenshot_h = get_screenshot_size()
|
||
|
||
# 从截图坐标转换为实际屏幕坐标
|
||
# 如果截图分辨率和屏幕分辨率不同,需要按比例缩放
|
||
x = data['x']
|
||
y = data['y']
|
||
|
||
# 计算缩放比例
|
||
scale_x = SCREEN_WIDTH / screenshot_w if screenshot_w > 0 else 1.0
|
||
scale_y = SCREEN_HEIGHT / screenshot_h if screenshot_h > 0 else 1.0
|
||
|
||
# 应用缩放
|
||
actual_x = int(x * scale_x)
|
||
actual_y = int(y * scale_y)
|
||
|
||
print(f"收到坐标: ({x}, {y}) -> 转换后: ({actual_x}, {actual_y}) [缩放比例: {scale_x:.2f}, {scale_y:.2f}]")
|
||
|
||
pyautogui.click(x=actual_x, y=actual_y)
|
||
elif data['type'] == 'type':
|
||
pyautogui.write(data['text'])
|
||
elif data['type'] == 'hotkey':
|
||
pyautogui.hotkey(*data['keys']) # 例如 ['ctrl', 's']
|
||
return {"status": "success"}
|
||
except Exception as e:
|
||
return {"status": "error", "msg": str(e)}
|
||
|
||
# 获取当前鼠标位置 (用于Host录制辅助)
|
||
@app.route('/mouse_pos', methods=['GET'])
|
||
def mouse_pos():
|
||
"""获取虚拟机当前鼠标位置"""
|
||
try:
|
||
x, y = pyautogui.position()
|
||
return {
|
||
"status": "success",
|
||
"x": int(x),
|
||
"y": int(y),
|
||
"timestamp": time.time()
|
||
}
|
||
except Exception as e:
|
||
return {"status": "error", "msg": str(e)}, 500
|
||
|
||
# 3. [关键!] 初始化环境
|
||
@app.route('/reset', methods=['POST'])
|
||
def reset():
|
||
# 这里可以写简单的逻辑:
|
||
# 1. 杀死 Jade 进程
|
||
os.system("taskkill /f /im jade.exe")
|
||
# 2. 这里的"重置"比快照弱,但对于 M1 调试更方便
|
||
# 如果必须用快照,需要在 Step 3 的 Mac 端调用 vmrun
|
||
return {"status": "reset_done"}
|
||
|
||
# 4. 列出桌面文件(用于调试)
|
||
@app.route('/list_desktop', methods=['GET'])
|
||
def list_desktop():
|
||
"""列出桌面上的文件"""
|
||
try:
|
||
desktop = os.path.expanduser(r"~\Desktop")
|
||
if os.path.exists(desktop):
|
||
files = os.listdir(desktop)
|
||
return {"status": "success", "files": files, "desktop_path": desktop}
|
||
else:
|
||
return {"status": "error", "msg": "Desktop path not found"}
|
||
except Exception as e:
|
||
return {"status": "error", "msg": str(e)}
|
||
|
||
# 5. 下载桌面文件(备用文件收集方式)
|
||
@app.route('/download/<filename>', methods=['GET'])
|
||
def download_file(filename):
|
||
"""
|
||
从桌面下载文件
|
||
用作vmrun文件传输的备用方案
|
||
"""
|
||
try:
|
||
desktop = os.path.expanduser(r"~\Desktop")
|
||
filepath = os.path.join(desktop, filename)
|
||
|
||
if not os.path.exists(filepath):
|
||
return {"status": "error", "msg": f"File not found: {filename}"}, 404
|
||
|
||
return send_file(filepath, as_attachment=True, download_name=filename)
|
||
except Exception as e:
|
||
return {"status": "error", "msg": str(e)}, 500
|
||
|
||
if __name__ == '__main__':
|
||
# 监听 0.0.0.0 允许外部访问
|
||
print("\n" + "=" * 60)
|
||
print("JADE Agent Server 启动")
|
||
print("=" * 60)
|
||
print(f"监听地址: 0.0.0.0:5000")
|
||
print(f"屏幕分辨率: {SCREEN_WIDTH}x{SCREEN_HEIGHT}")
|
||
print(f"截图分辨率: {SCREENSHOT_WIDTH}x{SCREENSHOT_HEIGHT}")
|
||
print(f"DPI缩放: {DPI_SCALE}")
|
||
print("=" * 60)
|
||
print("\n可用接口:")
|
||
print(" GET /screenshot - 获取屏幕截图")
|
||
print(" GET /screen_info - 获取屏幕信息")
|
||
print(" POST /action - 执行动作")
|
||
print(" POST /reset - 重置环境")
|
||
print(" GET /list_desktop - 列出桌面文件")
|
||
print(" GET /download/<file> - 下载桌面文件")
|
||
print("=" * 60 + "\n")
|
||
|
||
app.run(host='0.0.0.0', port=5000)
|