""" 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})"