Merge branch 'main' of https://git.zhaozuohong.vip/mower-ng/mower-ng
This commit is contained in:
commit
20e8a59b62
9 changed files with 146 additions and 67 deletions
|
@ -346,6 +346,12 @@ class EmulatorPart(ConfModel):
|
|||
rotate: bool = False
|
||||
"将截图旋转180度"
|
||||
|
||||
class DroidCastRawConf(ConfModel):
|
||||
orientation: Literal["portrait", "landscape"] = "landscape"
|
||||
"屏幕方向"
|
||||
rotate: Literal[0, 90, 180, 270] = 0
|
||||
"截图旋转角度"
|
||||
|
||||
adb: str = "127.0.0.1:16384"
|
||||
"ADB连接地址"
|
||||
emulator: EmulatorConf
|
||||
|
@ -356,6 +362,8 @@ class EmulatorPart(ConfModel):
|
|||
"游戏服务器"
|
||||
droidcast: DroidCastConf
|
||||
"DroidCast截图设置"
|
||||
droidcast_raw: DroidCastRawConf
|
||||
"DroidCast截图设置"
|
||||
custom_screenshot: CustomScreenshotConf
|
||||
"自定义截图"
|
||||
tap_to_launch_game: TapToLaunchGameConf
|
||||
|
@ -369,10 +377,11 @@ class EmulatorPart(ConfModel):
|
|||
screencap_strategy: Literal[
|
||||
"adb",
|
||||
"droidcast",
|
||||
"droidcast_raw",
|
||||
"mumuipc",
|
||||
"diy",
|
||||
"scrcpy",
|
||||
] = "droidcast"
|
||||
] = "droidcast_raw"
|
||||
"截图方案"
|
||||
control_strategy: Literal["scrcpy", "mumuipc"] = "scrcpy"
|
||||
"触控方案"
|
||||
|
|
|
@ -12,6 +12,7 @@ from mower.utils.log import logger, save_screenshot
|
|||
method_map = {
|
||||
"adb": "mower.utils.device.method.adb.ADB",
|
||||
"droidcast": "mower.utils.device.method.droidcast.DroidCast",
|
||||
"droidcast_raw": "mower.utils.device.method.droidcast.DroidCast_raw",
|
||||
"scrcpy": "mower.utils.device.method.scrcpy.Scrcpy",
|
||||
"mumuipc": "mower.utils.device.method.mumu_ipc.MuMu12IPC",
|
||||
"diy": "mower.utils.device.method.adb.DIY",
|
||||
|
|
|
@ -285,7 +285,7 @@ class ADB:
|
|||
dpi = (override_dpi or physical_dpi).partition("density:")[2].strip()
|
||||
if (
|
||||
config.conf.emulator.name == "MuMu12"
|
||||
and config.conf.screencap_strategy == "droidcast"
|
||||
and config.conf.screencap_strategy.startswith("droidcast")
|
||||
):
|
||||
from mower.utils.device.method.mumumanager import MuMuManager
|
||||
|
||||
|
|
|
@ -2,15 +2,16 @@ import time
|
|||
from functools import cached_property, wraps
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import requests
|
||||
|
||||
from mower import __rootdir__
|
||||
from mower.utils import config
|
||||
from mower.utils.csleep import MowerExit
|
||||
from mower.utils.device.method.adb import ADB
|
||||
from mower.utils.image import bytes2img
|
||||
from mower.utils.log import logger
|
||||
from mower.utils.network import get_new_port, is_port_in_use
|
||||
from mower.utils.path import get_path
|
||||
|
||||
|
||||
def retry_droidcast(func):
|
||||
|
@ -29,6 +30,10 @@ def retry_droidcast(func):
|
|||
|
||||
|
||||
class DroidCast:
|
||||
display_name = "DroidCast"
|
||||
package_name = "com.rayworks.droidcast"
|
||||
apk_file = "DroidCast-debug-1.2.1.apk"
|
||||
|
||||
def __init__(self):
|
||||
self.session = requests.Session()
|
||||
self.port = 0
|
||||
|
@ -40,7 +45,7 @@ class DroidCast:
|
|||
|
||||
def get_droidcast_classpath(self) -> str | None:
|
||||
# TODO: 退出时(并非结束mower线程时)关闭DroidCast进程、取消ADB转发
|
||||
out = self.adb.adb_shell("pm path com.rayworks.droidcast")
|
||||
out = self.adb.adb_shell(f"pm path {self.package_name}")
|
||||
if out is None:
|
||||
logger.exception("无法获取CLASSPATH")
|
||||
return None
|
||||
|
@ -54,15 +59,18 @@ class DroidCast:
|
|||
return class_path
|
||||
|
||||
def start_droidcast(self) -> bool:
|
||||
class_path = self.get_droidcast_classpath()
|
||||
try:
|
||||
class_path = self.get_droidcast_classpath()
|
||||
except Exception:
|
||||
class_path = None
|
||||
if not class_path:
|
||||
logger.info("安装DroidCast")
|
||||
apk_path = f"{__rootdir__}/vendor/DroidCast-debug-1.2.1.apk"
|
||||
logger.info(f"安装{self.display_name}")
|
||||
try:
|
||||
self.adb.adb.install(apk_path)
|
||||
logger.info("DroidCast安装完成,获取CLASSPATH")
|
||||
apk_path = get_path(f"@install/mower/vendor/{self.apk_file}")
|
||||
self.adb.adb.install(str(apk_path))
|
||||
logger.info(f"{self.display_name}安装完成,获取CLASSPATH")
|
||||
except Exception as e:
|
||||
logger.error(f"DroidCast安装失败: {e}")
|
||||
logger.error(f"{self.display_name}安装失败: {e}")
|
||||
return False
|
||||
class_path = self.get_droidcast_classpath()
|
||||
if not class_path:
|
||||
|
@ -83,24 +91,68 @@ class DroidCast:
|
|||
if port == 0:
|
||||
port = get_new_port()
|
||||
self.port = port
|
||||
logger.info(f"更新DroidCast端口为{port}")
|
||||
logger.info(f"更新{self.display_name}端口为{port}")
|
||||
else:
|
||||
logger.info(f"保持DroidCast端口为{port}")
|
||||
logger.info(f"保持{self.display_name}端口为{port}")
|
||||
self.adb.adb.forward(f"tcp:{port}", f"tcp:{port}")
|
||||
logger.info("ADB端口转发成功,启动DroidCast")
|
||||
cmd = f"{class_path} app_process / com.rayworks.droidcast.Main --port={port}"
|
||||
logger.info("ADB端口转发成功,启动{self.display_name}")
|
||||
cmd = f"{class_path} app_process / {self.package_name}.Main --port={port}"
|
||||
self.socket = self.adb.adb_shell(cmd, True)
|
||||
time.sleep(1) # TODO: 更好地等待DroidCast启动
|
||||
return True
|
||||
|
||||
@retry_droidcast
|
||||
def capture_display(self):
|
||||
if self.port == 0:
|
||||
raise Exception("DroidCast未启动")
|
||||
url = f"http://127.0.0.1:{self.port}/screenshot"
|
||||
logger.debug(f"GET {url}")
|
||||
r = self.session.get(url)
|
||||
img = bytes2img(r.content)
|
||||
def decode(self, data):
|
||||
img = bytes2img(data)
|
||||
if config.conf.droidcast.rotate:
|
||||
img = cv2.rotate(img, cv2.ROTATE_180)
|
||||
return img
|
||||
|
||||
@retry_droidcast
|
||||
def capture_display(self):
|
||||
if self.port == 0:
|
||||
raise Exception(f"{self.display_name}未启动")
|
||||
url = f"http://127.0.0.1:{self.port}/screenshot"
|
||||
logger.debug(f"GET {url}")
|
||||
r = self.session.get(url)
|
||||
return self.decode(r.content)
|
||||
|
||||
|
||||
class DroidCast_raw(DroidCast):
|
||||
display_name = "DroidCast_raw"
|
||||
package_name = "ink.mol.droidcast_raw"
|
||||
apk_file = "DroidCast_raw-release-1.1.apk"
|
||||
|
||||
def decode(self, data):
|
||||
# https://github.com/LmeSzinc/StarRailCopilot/blob/bf62eccaaa6f46ea9796a83175df0ab7882dd137/module/device/method/droidcast.py#L226
|
||||
arr = np.frombuffer(data, dtype=np.uint16)
|
||||
if config.conf.droidcast_raw.orientation == "portrait":
|
||||
shape = (1080, 1920)
|
||||
else:
|
||||
shape = (1920, 1080)
|
||||
arr = arr.reshape(shape)
|
||||
|
||||
r = cv2.bitwise_and(arr, 0b1111100000000000)
|
||||
r = cv2.convertScaleAbs(r, alpha=0.00390625)
|
||||
m = cv2.convertScaleAbs(r, alpha=0.03125)
|
||||
cv2.add(r, m, dst=r)
|
||||
|
||||
g = cv2.bitwise_and(arr, 0b0000011111100000)
|
||||
g = cv2.convertScaleAbs(g, alpha=0.125)
|
||||
m = cv2.convertScaleAbs(g, alpha=0.015625, dst=m)
|
||||
cv2.add(g, m, dst=g)
|
||||
|
||||
b = cv2.bitwise_and(arr, 0b0000000000011111)
|
||||
b = cv2.convertScaleAbs(b, alpha=8)
|
||||
m = cv2.convertScaleAbs(b, alpha=0.03125, dst=m)
|
||||
cv2.add(b, m, dst=b)
|
||||
|
||||
img = cv2.merge([r, g, b])
|
||||
|
||||
if config.conf.droidcast_raw.rotate == 90:
|
||||
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
|
||||
elif config.conf.droidcast_raw.rotate == 180:
|
||||
img = cv2.rotate(img, cv2.ROTATE_180)
|
||||
elif config.conf.droidcast_raw.rotate == 270:
|
||||
img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
|
||||
|
||||
return img
|
||||
|
|
|
@ -145,7 +145,10 @@ class MuMuManager:
|
|||
width = int(float(self.get_field_value(data, "resolution_width.custom")))
|
||||
height = int(float(self.get_field_value(data, "resolution_height.custom")))
|
||||
dpi = int(float(self.get_field_value(data, "resolution_dpi.custom")))
|
||||
if config.conf.screencap_strategy == "droidcast" and self.app_kept_alive():
|
||||
if (
|
||||
config.conf.screencap_strategy.startswith("droidcast")
|
||||
and self.app_kept_alive()
|
||||
):
|
||||
logger.error("MuMu12的droidcast截图策略下,需要关闭应用后台保活")
|
||||
try:
|
||||
self.set_app_kept_alive_false()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue