Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
*.png binary
*.PNG binary
*.jpg binary
*.apk binary
113 changes: 112 additions & 1 deletion autowsgr/emulator/controller/scrcpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
_SCRCPY_SERVER_VERSION = '2.7'
_DEVICE_JAR_PATH = '/data/local/tmp/scrcpy-server.jar'

# Yosemite IME (airtest) — 通过 broadcast 接收文本,无需切换输入法
# APK 由 airtest 包提供,确保 `pip install airtest` 已执行
_YOSEMITE_DEVICE_PATH = '/data/local/tmp/Yosemite.apk'
_YOSEMITE_IME = 'com.netease.nie.yosemite/.ime.ImeService'


class ScrcpyController(AndroidController):
"""基于 scrcpy 协议的 Android 设备控制器。
Expand Down Expand Up @@ -96,6 +101,10 @@ def __init__(
self._frame_lock = threading.Lock()
self._reconnect_lock = threading.Lock()

# Yosemite IME 状态
self._ime_before_yosemite: str | None = None
self._yosemite_ime_enabled = False

# ── 连接 ──

def connect(self) -> DeviceInfo:
Expand Down Expand Up @@ -144,6 +153,11 @@ def connect(self) -> DeviceInfo:
self._serial or 'auto',
*self._resolution,
)
# 输入法管理:根据配置决定使用 Yosemite 或非 Yosemite 输入法
if self._config is not None and self._config.ime and self._config.ime.lower() == 'yosemite':
self._ensure_yosemite_ime_ready()
else:
self._ensure_non_yosemite_ime()
return DeviceInfo(
serial=self._serial or 'auto',
resolution=self._resolution,
Expand Down Expand Up @@ -313,6 +327,9 @@ def _stream_loop(self) -> None:

def disconnect(self) -> None:
serial = self._serial or 'auto'
# 恢复 Yosemite IME 切换前的输入法
if self._yosemite_ime_enabled and self._ime_before_yosemite is not None:
self._restore_yosemite_ime()
self._close_video_channel()

self._device = None
Expand Down Expand Up @@ -494,10 +511,104 @@ def key_event(self, key_code: int, *, delay: bool = True) -> None:
)
)

# ── IME / 文本输入 ──

def _ensure_yosemite_ime_ready(self) -> None:
"""确保 Yosemite IME 已安装、启用并设为当前输入法。

首次调用时从 airtest 包中安装 APK 并持久切换到 Yosemite IME。
"""
if self._yosemite_ime_enabled:
return

import airtest

_yosemite_apk = (
Path(airtest.__file__).resolve().parent
/ 'core'
/ 'android'
/ 'static'
/ 'apks'
/ 'Yosemite.apk'
)

dev = self._require_device()
ime_list = dev.shell('ime list -s')

if _YOSEMITE_IME not in ime_list:
if not _yosemite_apk.exists():
raise EmulatorConnectionError(
f'Yosemite.apk 未找到: {_yosemite_apk}\n'
'请确保 airtest 包安装正确: pip install airtest-openwsgr'
)
_log.info('[Emulator] 安装 Yosemite IME(来自 airtest 包)...')
dev.push(str(_yosemite_apk), _YOSEMITE_DEVICE_PATH)
result = dev.shell(f'pm install -r -g {_YOSEMITE_DEVICE_PATH}')
if 'Success' not in result:
_log.error('[Emulator] pm install 输出: {}', result.strip())
raise EmulatorConnectionError(f'Yosemite IME 安装失败: {result.strip()}')

# 保存原输入法并切换到 Yosemite IME(持久,disconnect 时恢复)
self._ime_before_yosemite = dev.shell('settings get secure default_input_method').strip()
dev.shell(f'ime enable {_YOSEMITE_IME}')
dev.shell(f'ime set {_YOSEMITE_IME}')
time.sleep(0.5) # 等待 InputConnection 建立
self._yosemite_ime_enabled = True
_log.debug('[Emulator] Yosemite IME 就绪 (was {})', self._ime_before_yosemite)

def _restore_yosemite_ime(self) -> None:
"""恢复 _ensure_yosemite_ime_ready() 之前的输入法。"""
prev = self._ime_before_yosemite
if prev is None:
return
dev = self._require_device()
dev.shell(f'ime set {prev}')
_log.debug('[Emulator] IME 已恢复为 {}', prev)

def _ensure_non_yosemite_ime(self) -> None:
"""确保当前输入法不是 Yosemite。

检测已启用的输入法列表,若当前为 Yosemite 则切换到第一个非 Yosemite 的输入法。
切换后不会恢复,因为用户明确选择不使用 Yosemite。
"""
dev = self._require_device()
current_ime = dev.shell('settings get secure default_input_method').strip()

# 当前输入法已经不是 Yosemite,无需切换
if _YOSEMITE_IME not in current_ime:
_log.debug('[Emulator] 当前输入法非 Yosemite: {}', current_ime)
return

# 获取已启用的输入法列表,选择第一个非 Yosemite 的
ime_list = dev.shell('ime list -s').strip().splitlines()
target_ime = next(
(ime.strip() for ime in ime_list if ime.strip() and _YOSEMITE_IME not in ime),
None,
)

if target_ime is None:
_log.warning('[Emulator] 未找到可用的非 Yosemite 输入法,跳过切换')
return

dev.shell(f'ime set {target_ime}')
_log.info('[Emulator] 已从 Yosemite 切换到 {}', target_ime)

def text(self, content: str, *, delay: bool = True) -> None:
"""向设备输入文本。

当 ``emulator.ime`` 为 ``"yosemite"`` 时,使用 Yosemite IME broadcast
(支持中文、英文及空格);否则回退到 adbutils send_keys(仅 ASCII)。
"""
dev = self._require_device()
_log.debug("[Emulator] text('{}') {}", content, caller_info())
dev.send_keys(content)
if self._config is not None and self._config.ime and self._config.ime.lower() == 'yosemite':
self._ensure_yosemite_ime_ready()
# 用单引号包裹文本,防止 shell / am 误解析特殊字符(如 -)
# 文本中的单引号用 '\'' 转义:结束单引号 → 转义引号 → 开始单引号
safe = content.replace("'", "'\\''")
dev.shell(f"am broadcast -a ADB_INPUT_TEXT --es msg '{safe}'")
else:
dev.send_keys(content)

# 增加延迟,改动同 click_delay
if delay: # True 才走延迟
Expand Down
2 changes: 2 additions & 0 deletions autowsgr/infra/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class EmulatorConfig(BaseModel):
"""ADB serial 地址。None = 自动检测"""
process_name: str | None = None
"""模拟器进程名。None = 自动推断"""
ime: str | None = None
"""输入法选择,默认为 None = 系统默认输入法,可选值:'Yosemite' """


class AccountConfig(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions examples/usersettings.full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ emulator:
path: null # 模拟器可执行文件路径; null = 自动检测
serial: null # ADB serial 地址; null = 自动检测
process_name: null # 模拟器进程名; null = 自动推断
ime: null # 文本输入法; null = 系统默认, 'Yosemite' = 以支持低版本安卓内核的模拟器中文输入

# ═══════════════════════════════════════════
# 游戏账号
Expand Down
1 change: 1 addition & 0 deletions examples/usersettings.minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
emulator:
type: 雷电 # 雷电 / 蓝叠 / MuMu / 夜神 / 逍遥 / WAYDROID
# serial: "127.0.0.1:5555" # 多开时手动指定, 默认自动检测
# ime: Yosemite # 文本输入法; 设为 Yosemite 以支持低版本安卓内核的模拟器中文输入

# ── 账号 ──
account:
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ dependencies = [
"uvicorn[standard]>=0.23.0",
"websockets>=11.0",
"retry>=0.9.2",
# Android 中文文本输入
"airtest-openwsgr>=1.3.9",
]

[dependency-groups]
Expand Down
Loading
Loading