Refactor: Make MaaTouch inputs synchronous

This commit is contained in:
LmeSzinc 2024-04-28 23:27:23 +08:00
parent 03d99313db
commit 0fb6a96691
4 changed files with 170 additions and 32 deletions

BIN
bin/MaaTouch/maatouchsync Normal file

Binary file not shown.

View File

@ -91,8 +91,8 @@ class ManualConfig:
SCRCPY_FILEPATH_LOCAL = './bin/scrcpy/scrcpy-server-v1.20.jar' SCRCPY_FILEPATH_LOCAL = './bin/scrcpy/scrcpy-server-v1.20.jar'
SCRCPY_FILEPATH_REMOTE = '/data/local/tmp/scrcpy-server-v1.20.jar' SCRCPY_FILEPATH_REMOTE = '/data/local/tmp/scrcpy-server-v1.20.jar'
MAATOUCH_FILEPATH_LOCAL = './bin/MaaTouch/maatouch' MAATOUCH_FILEPATH_LOCAL = './bin/MaaTouch/maatouchsync'
MAATOUCH_FILEPATH_REMOTE = '/data/local/tmp/maatouch' MAATOUCH_FILEPATH_REMOTE = '/data/local/tmp/maatouchsync'
""" """
module.campaign.gems_farming module.campaign.gems_farming

View File

@ -1,5 +1,6 @@
import socket import socket
import threading import threading
import time
from functools import wraps from functools import wraps
from adbutils.errors import AdbError from adbutils.errors import AdbError
@ -8,7 +9,7 @@ from module.base.decorator import cached_property, del_cached_property, has_cach
from module.base.timer import Timer from module.base.timer import Timer
from module.base.utils import * from module.base.utils import *
from module.device.connection import Connection from module.device.connection import Connection
from module.device.method.minitouch import CommandBuilder, insert_swipe from module.device.method.minitouch import CommandBuilder, insert_swipe, Command
from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep from module.device.method.utils import RETRY_TRIES, handle_adb_error, retry_sleep
from module.exception import RequestHumanTakeover from module.exception import RequestHumanTakeover
from module.logger import logger from module.logger import logger
@ -38,6 +39,15 @@ def retry(func):
def init(): def init():
self.adb_reconnect() self.adb_reconnect()
del_cached_property(self, '_maatouch_builder') del_cached_property(self, '_maatouch_builder')
# MaaTouchSyncTimeout
# Probably because adb server was killed
except MaaTouchSyncTimeout as e:
logger.error(e)
def init():
self.adb_reconnect()
del_cached_property(self, '_maatouch_builder')
self.reset_maatouch()
# Emulator closed # Emulator closed
except ConnectionAbortedError as e: except ConnectionAbortedError as e:
logger.error(e) logger.error(e)
@ -79,7 +89,12 @@ def retry(func):
class MaatouchBuilder(CommandBuilder): class MaatouchBuilder(CommandBuilder):
def __init__(self, device, contact=0, handle_orientation=False): def __init__(
self,
device,
contact=0,
handle_orientation=False,
):
""" """
Args: Args:
device (MaaTouch): device (MaaTouch):
@ -90,11 +105,21 @@ class MaatouchBuilder(CommandBuilder):
def send(self): def send(self):
return self.device.maatouch_send(builder=self) return self.device.maatouch_send(builder=self)
def send_sync(self, mode=2):
return self.device.maatouch_send_sync(builder=self, mode=mode)
def end(self):
self.device.sleep(self.DEFAULT_DELAY)
class MaaTouchNotInstalledError(Exception): class MaaTouchNotInstalledError(Exception):
pass pass
class MaaTouchSyncTimeout(Exception):
pass
class MaaTouch(Connection): class MaaTouch(Connection):
""" """
Control method that implements the same as scrcpy and has an interface similar to minitouch. Control method that implements the same as scrcpy and has an interface similar to minitouch.
@ -121,6 +146,8 @@ class MaaTouch(Connection):
del self._maatouch_init_thread del self._maatouch_init_thread
self._maatouch_init_thread = None self._maatouch_init_thread = None
# Return an empty builder
self._maatouch_builder.clear()
return self._maatouch_builder return self._maatouch_builder
def early_maatouch_init(self): def early_maatouch_init(self):
@ -174,7 +201,7 @@ class MaaTouch(Connection):
# CLASSPATH=/data/local/tmp/maatouch app_process / com.shxyke.MaaTouch.App # CLASSPATH=/data/local/tmp/maatouch app_process / com.shxyke.MaaTouch.App
stream = self.adb_shell( stream = self.adb_shell(
['CLASSPATH=/data/local/tmp/maatouch', 'app_process', '/', 'com.shxyke.MaaTouch.App'], [f'CLASSPATH={self.config.MAATOUCH_FILEPATH_REMOTE}', 'app_process', '/', 'com.shxyke.MaaTouch.App'],
stream=True, stream=True,
recvall=False recvall=False
) )
@ -226,6 +253,8 @@ class MaaTouch(Connection):
# _, pid = out.split(" ") # _, pid = out.split(" ")
# self._maatouch_pid = pid # self._maatouch_pid = pid
# Timeout 2s for sync
stream.settimeout(2)
logger.info( logger.info(
"MaaTouch stream connected" "MaaTouch stream connected"
) )
@ -237,11 +266,56 @@ class MaaTouch(Connection):
def maatouch_send(self, builder: MaatouchBuilder): def maatouch_send(self, builder: MaatouchBuilder):
content = builder.to_minitouch() content = builder.to_minitouch()
# logger.info("send operation: {}".format(content.replace("\n", "\\n"))) logger.info("send operation: {}".format(content.replace("\n", "\\n")))
byte_content = content.encode('utf-8') byte_content = content.encode('utf-8')
self._maatouch_stream.sendall(byte_content) self._maatouch_stream.sendall(byte_content)
self._maatouch_stream.recv(0) self._maatouch_stream.recv(0)
self.sleep(self.maatouch_builder.delay / 1000 + builder.DEFAULT_DELAY) self.sleep(builder.delay / 1000 + builder.DEFAULT_DELAY)
builder.clear()
def maatouch_send_sync(self, builder: MaatouchBuilder, mode=2):
# Set inject mode to the last command
for command in builder.commands[::-1]:
if command.operation in ['r', 'd', 'm', 'u']:
command.mode = mode
break
# add maatouch sync command: 's <timestamp>\n'
timestamp = str(int(time.time() * 1000))
builder.commands.insert(0, Command(
's', text=timestamp
))
# Send
content = builder.to_maatouch_sync()
logger.info("send operation: {}".format(content.replace("\n", "\\n")))
byte_content = content.encode('utf-8')
self._maatouch_stream.sendall(byte_content)
self._maatouch_stream.recv(0)
# Wait until operations finished
start = time.time()
socket_out = self._maatouch_stream.makefile()
max_trial = 3
for n in range(3):
try:
out = socket_out.readline()
except socket.timeout as e:
raise MaaTouchSyncTimeout(str(e))
out = out.strip()
logger.info(out)
if out == timestamp:
break
if out == 'Killed':
raise MaaTouchNotInstalledError('MaaTouch died, probably because version incompatible')
if n == max_trial - 1:
raise MaaTouchSyncTimeout('Too many incorrect sync response')
time.sleep(0.001)
logger.info(f'Delay: {builder.delay}')
logger.info(f'Waiting control {time.time() - start}')
self.sleep(builder.DEFAULT_DELAY)
builder.clear() builder.clear()
def maatouch_install(self): def maatouch_install(self):
@ -257,30 +331,31 @@ class MaaTouch(Connection):
builder = self.maatouch_builder builder = self.maatouch_builder
builder.down(x, y).commit() builder.down(x, y).commit()
builder.up().commit() builder.up().commit()
builder.send() builder.send_sync()
@retry @retry
def long_click_maatouch(self, x, y, duration=1.0): def long_click_maatouch(self, x, y, duration=1.0):
duration = int(duration * 1000) duration = int(duration * 1000)
builder = self.maatouch_builder builder = self.maatouch_builder
builder.down(x, y).commit().wait(duration) builder.down(x, y).wait(duration).commit()
builder.up().commit() builder.up().commit()
builder.send() builder.send_sync()
@retry @retry
def swipe_maatouch(self, p1, p2): def swipe_maatouch(self, p1, p2):
points = insert_swipe(p0=p1, p3=p2) points = insert_swipe(p0=p1, p3=p2)
builder = self.maatouch_builder builder = self.maatouch_builder
builder.down(*points[0]).commit() builder.down(*points[0]).wait(10).commit()
builder.send() builder.send_sync()
for point in points[1:]: for point in points[1:]:
builder.move(*point).commit().wait(10) builder.move(*point).wait(10)
builder.send() builder.commit()
builder.send_sync()
builder.up().commit() builder.up().commit()
builder.send() builder.send_sync()
@retry @retry
def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)): def drag_maatouch(self, p1, p2, point_random=(-10, -10, 10, 10)):
@ -289,16 +364,22 @@ class MaaTouch(Connection):
points = insert_swipe(p0=p1, p3=p2, speed=20) points = insert_swipe(p0=p1, p3=p2, speed=20)
builder = self.maatouch_builder builder = self.maatouch_builder
builder.down(*points[0]).commit() builder.down(*points[0]).commit().wait(10)
builder.send() builder.send_sync()
for point in points[1:]: for point in points[1:]:
builder.move(*point).commit().wait(10) builder.move(*point).commit().wait(10)
builder.send() builder.send_sync()
builder.move(*p2).commit().wait(140) builder.move(*p2).commit().wait(140)
builder.move(*p2).commit().wait(140) builder.move(*p2).commit().wait(140)
builder.send() builder.send_sync()
builder.up().commit() builder.up().commit()
builder.send() builder.send_sync()
@retry
def reset_maatouch(self):
builder = self.maatouch_builder
builder.reset().commit()
builder.send_sync()

View File

@ -100,7 +100,9 @@ class Command:
x: int = 0, x: int = 0,
y: int = 0, y: int = 0,
ms: int = 10, ms: int = 10,
pressure: int = 100 pressure: int = 100,
mode: int = 0,
text: str = ''
): ):
""" """
See https://github.com/openstf/minitouch#writable-to-the-socket See https://github.com/openstf/minitouch#writable-to-the-socket
@ -112,6 +114,8 @@ class Command:
y: y:
ms: ms:
pressure: pressure:
mode:
text:
""" """
self.operation = operation self.operation = operation
self.contact = contact self.contact = contact
@ -119,6 +123,8 @@ class Command:
self.y = y self.y = y
self.ms = ms self.ms = ms
self.pressure = pressure self.pressure = pressure
self.mode = mode
self.text = text
def to_minitouch(self) -> str: def to_minitouch(self) -> str:
""" """
@ -139,6 +145,36 @@ class Command:
else: else:
return '' return ''
def to_maatouch_sync(self):
if self.operation == 'c':
return f'{self.operation}\n'
elif self.operation == 'r':
if self.mode:
return f'{self.operation} {self.mode}\n'
else:
return f'{self.operation}\n'
elif self.operation == 'd':
if self.mode:
return f'{self.operation} {self.contact} {self.x} {self.y} {self.pressure} {self.mode}\n'
else:
return f'{self.operation} {self.contact} {self.x} {self.y} {self.pressure}\n'
elif self.operation == 'm':
if self.mode:
return f'{self.operation} {self.contact} {self.x} {self.y} {self.pressure} {self.mode}\n'
else:
return f'{self.operation} {self.contact} {self.x} {self.y} {self.pressure}\n'
elif self.operation == 'u':
if self.mode:
return f'{self.operation} {self.contact} {self.mode}\n'
else:
return f'{self.operation} {self.ms}\n'
elif self.operation == 'w':
return f'{self.operation} {self.ms}\n'
elif self.operation == 's':
return f'{self.operation} {self.text}\n'
else:
return ''
def to_atx_agent(self, max_x=1280, max_y=720) -> str: def to_atx_agent(self, max_x=1280, max_y=720) -> str:
""" """
Dict that send to atx-agent, $DEVICE_URL/minitouch Dict that send to atx-agent, $DEVICE_URL/minitouch
@ -184,7 +220,12 @@ class CommandBuilder:
max_x = 1280 max_x = 1280
max_y = 720 max_y = 720
def __init__(self, device, contact=0, handle_orientation=True): def __init__(
self,
device,
contact=0,
handle_orientation=True,
):
""" """
Args: Args:
device: device:
@ -230,45 +271,61 @@ class CommandBuilder:
def commit(self): def commit(self):
""" add minitouch command: 'c\n' """ """ add minitouch command: 'c\n' """
self.commands.append(Command('c')) self.commands.append(Command(
'c'
))
return self return self
def reset(self): def reset(self, mode=0):
""" add minitouch command: 'r\n' """ """ add minitouch command: 'r\n' """
self.commands.append(Command('r')) self.commands.append(Command(
'r', mode=mode
))
return self return self
def wait(self, ms=10): def wait(self, ms=10):
""" add minitouch command: 'w <ms>\n' """ """ add minitouch command: 'w <ms>\n' """
self.commands.append(Command('w', ms=ms)) self.commands.append(Command(
'w', ms=ms
))
self.delay += ms self.delay += ms
return self return self
def up(self): def up(self, mode=0):
""" add minitouch command: 'u <contact>\n' """ """ add minitouch command: 'u <contact>\n' """
self.commands.append(Command('u', contact=self.contact)) self.commands.append(Command(
'u', contact=self.contact, mode=mode
))
return self return self
def down(self, x, y, pressure=100): def down(self, x, y, pressure=100, mode=0):
""" add minitouch command: 'd <contact> <x> <y> <pressure>\n' """ """ add minitouch command: 'd <contact> <x> <y> <pressure>\n' """
x, y = self.convert(x, y) x, y = self.convert(x, y)
self.commands.append(Command('d', x=x, y=y, contact=self.contact, pressure=pressure)) self.commands.append(Command(
'd', x=x, y=y, contact=self.contact, pressure=pressure, mode=mode
))
return self return self
def move(self, x, y, pressure=100): def move(self, x, y, pressure=100, mode=0):
""" add minitouch command: 'm <contact> <x> <y> <pressure>\n' """ """ add minitouch command: 'm <contact> <x> <y> <pressure>\n' """
x, y = self.convert(x, y) x, y = self.convert(x, y)
self.commands.append(Command('m', x=x, y=y, contact=self.contact, pressure=pressure)) self.commands.append(Command(
'm', x=x, y=y, contact=self.contact, pressure=pressure, mode=mode
))
return self return self
def clear(self): def clear(self):
""" clear current commands """ """ clear current commands """
self.commands = [] self.commands = []
self.delay = 0 self.delay = 0
return self
def to_minitouch(self) -> str: def to_minitouch(self) -> str:
return ''.join([command.to_minitouch() for command in self.commands]) return ''.join([command.to_minitouch() for command in self.commands])
def to_maatouch_sync(self) -> str:
return ''.join([command.to_maatouch_sync() for command in self.commands])
def to_atx_agent(self) -> List[str]: def to_atx_agent(self) -> List[str]:
return [command.to_atx_agent(self.max_x, self.max_y) for command in self.commands] return [command.to_atx_agent(self.max_x, self.max_y) for command in self.commands]