304 lines
9.6 KiB
Python
304 lines
9.6 KiB
Python
"""
|
||
iOS Input Event Implementation
|
||
iOS-specific input event classes that implement AbstractInputEvent.
|
||
"""
|
||
from typing import Optional, Dict, Any
|
||
|
||
from ...core.abstract_input_event import (
|
||
AbstractInputEvent,
|
||
BaseTouchEvent,
|
||
BaseLongTouchEvent,
|
||
BaseSwipeEvent,
|
||
BaseScrollEvent,
|
||
BaseSetTextEvent,
|
||
BaseKeyEvent,
|
||
BaseKillAppEvent,
|
||
EventType
|
||
)
|
||
|
||
|
||
class IOSTouchEvent(BaseTouchEvent):
|
||
|
||
"""iOS 触摸/点击事件"""
|
||
|
||
def __init__(self, x: int, y: int, view: Dict[str, Any] = None):
|
||
"""
|
||
初始化触摸事件
|
||
|
||
:param x: 触摸 X 坐标
|
||
:param y: 触摸 Y 坐标
|
||
:param view: 关联的视图字典(可选)
|
||
"""
|
||
super().__init__(x=x, y=y, view=view)
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
device.view_touch(self.x, self.y)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send touch event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSTouchEvent(x={self.x}, y={self.y})"
|
||
|
||
|
||
class IOSLongTouchEvent(BaseLongTouchEvent):
|
||
"""iOS 长按事件"""
|
||
|
||
def __init__(self, x: int, y: int, duration: float = 2.0, view: Dict[str, Any] = None):
|
||
"""
|
||
初始化长按事件
|
||
|
||
:param x: 长按 X 坐标
|
||
:param y: 长按 Y 坐标
|
||
:param duration: 长按持续时间(秒)
|
||
:param view: 关联的视图字典(可选)
|
||
"""
|
||
super().__init__(x=x, y=y, duration=duration, view=view)
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
device.view_long_touch(self.x, self.y, self.duration)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send long touch event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSLongTouchEvent(x={self.x}, y={self.y}, duration={self.duration})"
|
||
|
||
|
||
class IOSSwipeEvent(BaseSwipeEvent):
|
||
"""iOS 滑动事件"""
|
||
|
||
def __init__(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
||
duration: float = 0.5, view: Dict[str, Any] = None):
|
||
"""
|
||
初始化滑动事件
|
||
|
||
:param start_x: 起始 X 坐标
|
||
:param start_y: 起始 Y 坐标
|
||
:param end_x: 结束 X 坐标
|
||
:param end_y: 结束 Y 坐标
|
||
:param duration: 滑动持续时间(秒)
|
||
:param view: 关联的视图字典(可选)
|
||
"""
|
||
super().__init__(
|
||
start_x=start_x, start_y=start_y,
|
||
end_x=end_x, end_y=end_y,
|
||
duration=duration, view=view
|
||
)
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
device.view_drag(
|
||
(self.start_x, self.start_y),
|
||
(self.end_x, self.end_y),
|
||
self.duration
|
||
)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send swipe event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSSwipeEvent(({self.start_x},{self.start_y})->({self.end_x},{self.end_y}))"
|
||
|
||
|
||
class IOSScrollEvent(BaseScrollEvent):
|
||
"""iOS 滚动事件"""
|
||
|
||
def __init__(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
||
direction: str = "down", view: Dict[str, Any] = None):
|
||
"""
|
||
初始化滚动事件
|
||
|
||
:param start_x: 起始 X 坐标
|
||
:param start_y: 起始 Y 坐标
|
||
:param end_x: 结束 X 坐标
|
||
:param end_y: 结束 Y 坐标
|
||
:param direction: 滚动方向 (up/down/left/right)
|
||
:param view: 关联的视图字典(可选)
|
||
"""
|
||
super().__init__(direction=direction, view=view)
|
||
self.start_x = start_x
|
||
self.start_y = start_y
|
||
self.end_x = end_x
|
||
self.end_y = end_y
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
device.view_drag(
|
||
(self.start_x, self.start_y),
|
||
(self.end_x, self.end_y),
|
||
0.3 # 滚动通常比较快
|
||
)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send scroll event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSScrollEvent(direction={self.direction})"
|
||
|
||
|
||
class IOSSetTextEvent(BaseSetTextEvent):
|
||
"""iOS 文本输入事件"""
|
||
|
||
def __init__(self, text: str, view: Dict[str, Any] = None):
|
||
"""
|
||
初始化文本输入事件
|
||
|
||
:param text: 要输入的文本
|
||
:param view: 关联的视图字典(可选)
|
||
"""
|
||
super().__init__(text=text, view=view)
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
# 如果有关联视图,先点击激活
|
||
if self.view:
|
||
bounds = self.view.get("bounds", [[0, 0], [0, 0]])
|
||
center_x = (bounds[0][0] + bounds[1][0]) // 2
|
||
center_y = (bounds[0][1] + bounds[1][1]) // 2
|
||
device.view_touch(center_x, center_y)
|
||
import time
|
||
time.sleep(0.5)
|
||
|
||
device.view_set_text(self.text)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send set text event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
text_preview = self.text[:20] + "..." if len(self.text) > 20 else self.text
|
||
return f"IOSSetTextEvent(text='{text_preview}')"
|
||
|
||
|
||
class IOSKeyEvent(BaseKeyEvent):
|
||
"""iOS 按键事件"""
|
||
|
||
# iOS 支持的按键
|
||
SUPPORTED_KEYS = {
|
||
"HOME": "home",
|
||
"BACK": "home", # iOS 无返回键,用 Home 代替
|
||
"VOLUME_UP": "volumeUp",
|
||
"VOLUME_DOWN": "volumeDown",
|
||
}
|
||
|
||
def __init__(self, key_name: str = None, key_code: str = None):
|
||
"""
|
||
初始化按键事件
|
||
|
||
:param key_name: 按键名称 (兼容参数,与基类一致)
|
||
:param key_code: 按键代码 (HOME, VOLUME_UP, VOLUME_DOWN)
|
||
"""
|
||
# 兼容两种参数名
|
||
key = key_name or key_code or "HOME"
|
||
super().__init__(key_name=key)
|
||
self.key_code = key # 保留 key_code 供 send 方法使用
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
device.key_press(self.key_name)
|
||
return True
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to send key event: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSKeyEvent(key={self.key_name})"
|
||
|
||
|
||
class IOSKillAppEvent(BaseKillAppEvent):
|
||
"""iOS 终止应用事件"""
|
||
|
||
def __init__(self, app: str = None, bundle_id: str = None):
|
||
"""
|
||
初始化终止应用事件
|
||
|
||
:param app: 要终止的应用(兼容参数,与基类一致)
|
||
:param bundle_id: 要终止的应用 Bundle ID(None 表示当前应用)
|
||
"""
|
||
# 兼容两种参数名
|
||
app_id = app or bundle_id
|
||
super().__init__(app=app_id)
|
||
self.bundle_id = app_id # 保留 bundle_id 供 send 方法使用
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
# 等待 WDA 就绪
|
||
if hasattr(device, '_wait_wda_ready'):
|
||
device._wait_wda_ready()
|
||
|
||
bundle_id = self.bundle_id or device.bundle_id
|
||
if bundle_id and device._wda_client:
|
||
device._wda_client.app_terminate(bundle_id)
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to kill app: {e}")
|
||
return False
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSKillAppEvent(bundle_id={self.bundle_id})"
|
||
|
||
|
||
class IOSIntentEvent(AbstractInputEvent):
|
||
"""
|
||
iOS Intent 事件(适配 Android Intent 概念)
|
||
|
||
iOS 没有真正的 Intent,此类用于兼容 input_policy 中的应用启动逻辑。
|
||
"""
|
||
|
||
def __init__(self, intent=None, bundle_id: str = None):
|
||
"""
|
||
初始化 Intent 事件
|
||
|
||
:param intent: 兼容参数(iOS 中忽略)
|
||
:param bundle_id: 要启动的应用 Bundle ID
|
||
"""
|
||
super().__init__(event_type=EventType.INTENT)
|
||
self.intent = intent
|
||
self.bundle_id = bundle_id
|
||
|
||
def send(self, device) -> bool:
|
||
"""发送事件到设备"""
|
||
try:
|
||
# 等待 WDA 就绪
|
||
if hasattr(device, '_wait_wda_ready'):
|
||
device._wait_wda_ready()
|
||
|
||
# 从 intent 或 bundle_id 确定目标应用
|
||
target_bundle_id = self.bundle_id or device.bundle_id
|
||
|
||
if target_bundle_id and device._wda_client:
|
||
# 使用 WDA 启动应用
|
||
device._wda_client.app_launch(target_bundle_id)
|
||
import time
|
||
time.sleep(1)
|
||
return True
|
||
return False
|
||
except Exception as e:
|
||
device.logger.warning(f"Failed to launch app via intent: {e}")
|
||
return False
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
return {
|
||
"event_type": self.event_type.value,
|
||
"bundle_id": self.bundle_id,
|
||
"intent": str(self.intent) if self.intent else None,
|
||
}
|
||
|
||
def get_event_str(self, state=None) -> str:
|
||
return f"IOSIntentEvent(bundle_id={self.bundle_id})"
|