201 lines
6.6 KiB
Python
201 lines
6.6 KiB
Python
"""
|
||
Windows Input Event Implementations
|
||
Concrete implementations of input events for Windows desktop applications.
|
||
"""
|
||
from typing import Optional, Dict, Any, List
|
||
|
||
from ...core.abstract_input_event import (
|
||
AbstractInputEvent, EventType,
|
||
BaseTouchEvent, BaseLongTouchEvent, BaseSwipeEvent,
|
||
BaseScrollEvent, BaseSetTextEvent, BaseKeyEvent, BaseKillAppEvent
|
||
)
|
||
|
||
|
||
|
||
class WindowsTouchEvent(BaseTouchEvent):
|
||
"""Windows 点击事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送点击事件到 Windows 设备"""
|
||
x, y = self.x, self.y
|
||
if self.view is not None:
|
||
from .windows_device_state import WindowsDeviceState
|
||
x, y = WindowsDeviceState.get_view_center(self.view)
|
||
|
||
# 默认左键点击
|
||
device.view_touch(int(x), int(y), button='left')
|
||
return True
|
||
|
||
|
||
class WindowsRightClickEvent(BaseTouchEvent):
|
||
"""Windows 右键点击事件 (新增)"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送右键点击事件到 Windows 设备"""
|
||
x, y = self.x, self.y
|
||
if self.view is not None:
|
||
from .windows_device_state import WindowsDeviceState
|
||
x, y = WindowsDeviceState.get_view_center(self.view)
|
||
|
||
device.view_touch(int(x), int(y), button='right')
|
||
return True
|
||
|
||
|
||
class WindowsLongTouchEvent(BaseLongTouchEvent):
|
||
"""Windows 长按事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送长按事件到 Windows 设备"""
|
||
x, y = self.x, self.y
|
||
if self.view is not None:
|
||
from .windows_device_state import WindowsDeviceState
|
||
x, y = WindowsDeviceState.get_view_center(self.view)
|
||
device.view_long_touch(int(x), int(y), self.duration)
|
||
return True
|
||
|
||
|
||
class WindowsSwipeEvent(BaseSwipeEvent):
|
||
"""Windows 滑动/拖拽事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送滑动事件到 Windows 设备"""
|
||
device.view_drag(
|
||
(self.start_x, self.start_y),
|
||
(self.end_x, self.end_y),
|
||
self.duration
|
||
)
|
||
return True
|
||
|
||
|
||
class WindowsScrollEvent(BaseScrollEvent):
|
||
"""Windows 滚动事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送滚动事件到 Windows 设备"""
|
||
import pyautogui
|
||
|
||
# 1. 确定滚动位置
|
||
start_x, start_y = None, None
|
||
|
||
# 如果指定了目标 View,移动到 View 中心
|
||
if self.view is not None:
|
||
from .windows_device_state import WindowsDeviceState
|
||
start_x, start_y = WindowsDeviceState.get_view_center(self.view)
|
||
# 转换为屏幕坐标 (device.view_drag 会处理,但这里是直接调用 pyautogui)
|
||
# 注意:get_view_center 返回的是相对窗口坐标(如果是基于 device state 的话)
|
||
# 我们需要检查 view_center 的逻辑。
|
||
# 现在的 WindowsDeviceState.get_view_center 是基于 cv_views,bounds 是相对窗口的吗?
|
||
# 即使 Device 实现了 _update_window_rect,CV 识别是基于截图的。
|
||
# 如果截图是全屏截图(view_file 335行: path is None -> path=screen...png -> with mss -> grab monitor),
|
||
# 那么 bounds 就是屏幕绝对坐标!
|
||
pass
|
||
|
||
if start_x is None:
|
||
# 默认:窗口中心
|
||
display_info = device.get_display_info()
|
||
width = display_info.get('width', 1920)
|
||
height = display_info.get('height', 1080)
|
||
target_x = display_info.get('left', 0) + width // 2
|
||
target_y = display_info.get('top', 0) + height // 2
|
||
else:
|
||
|
||
# 必须转换为屏幕坐标
|
||
display_info = device.get_display_info()
|
||
target_x = display_info.get('left', 0) + start_x
|
||
target_y = display_info.get('top', 0) + start_y
|
||
|
||
# 移动到目标位置
|
||
pyautogui.moveTo(target_x, target_y)
|
||
|
||
# 执行滚动
|
||
scroll_amount = 3 # 滚动量
|
||
if self.direction == self.DIRECTION_UP:
|
||
pyautogui.scroll(scroll_amount) # scroll up
|
||
elif self.direction == self.DIRECTION_DOWN:
|
||
pyautogui.scroll(-scroll_amount) # scroll down
|
||
elif self.direction == self.DIRECTION_LEFT:
|
||
pyautogui.hscroll(-scroll_amount)
|
||
elif self.direction == self.DIRECTION_RIGHT:
|
||
pyautogui.hscroll(scroll_amount)
|
||
|
||
return True
|
||
|
||
|
||
class WindowsSetTextEvent(BaseSetTextEvent):
|
||
"""Windows 文本输入事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送文本输入事件到 Windows 设备"""
|
||
# 先点击目标视图
|
||
if self.view is not None:
|
||
from .windows_device_state import WindowsDeviceState
|
||
x, y = WindowsDeviceState.get_view_center(self.view)
|
||
device.view_touch(int(x), int(y))
|
||
import time
|
||
time.sleep(0.3)
|
||
|
||
# 输入文本
|
||
device.view_set_text(self.text)
|
||
|
||
return True
|
||
|
||
|
||
class WindowsKeyEvent(BaseKeyEvent):
|
||
"""Windows 按键事件"""
|
||
|
||
# Windows 按键映射 (通用名称 -> pyautogui 按键名)
|
||
KEY_MAP = {
|
||
'BACK': 'backspace',
|
||
'HOME': 'win',
|
||
'MENU': 'apps',
|
||
'ENTER': 'enter',
|
||
'ESCAPE': 'escape',
|
||
'TAB': 'tab',
|
||
'DELETE': 'delete',
|
||
'UP': 'up',
|
||
'DOWN': 'down',
|
||
'LEFT': 'left',
|
||
'RIGHT': 'right',
|
||
'SPACE': 'space',
|
||
'CTRL': 'ctrl',
|
||
'ALT': 'alt',
|
||
'SHIFT': 'shift',
|
||
}
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送按键事件到 Windows 设备"""
|
||
key_code = self.KEY_MAP.get(self.key_name.upper(), self.key_name.lower())
|
||
device.key_press(key_code)
|
||
return True
|
||
|
||
|
||
class WindowsKillAppEvent(BaseKillAppEvent):
|
||
"""Windows 终止应用事件"""
|
||
|
||
def send(self, device) -> bool:
|
||
"""终止 Windows 应用"""
|
||
import subprocess
|
||
|
||
if self.app is None:
|
||
return False
|
||
|
||
# 获取进程名
|
||
process_name = self.app
|
||
if hasattr(self.app, 'get_process_name'):
|
||
process_name = self.app.get_process_name()
|
||
elif hasattr(self.app, 'exe_path'):
|
||
import os
|
||
process_name = os.path.basename(self.app.exe_path)
|
||
|
||
try:
|
||
# 使用 taskkill 强制终止进程
|
||
subprocess.run(
|
||
['taskkill', '/F', '/IM', process_name],
|
||
capture_output=True,
|
||
timeout=5
|
||
)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to kill app {process_name}: {e}")
|
||
return False
|