From c126a8a4f2ee90839e4ef48c58c07050184305ab Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 20 Mar 2024 23:03:26 +0800 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- requirements.txt | 1 + source/control/runner.py | 123 ++++++++++++----------- source/webpage/index.py | 211 ++++++++++++++++++++++++++++++++------- 4 files changed, 242 insertions(+), 95 deletions(-) diff --git a/main.py b/main.py index 709dca3..82156fe 100644 --- a/main.py +++ b/main.py @@ -2,4 +2,4 @@ from source.webpage import index -ui.run(title="Maa Debugger", storage_secret="maadbg") +ui.run(title="Maa Debugger", storage_secret="maadbg")#, root_path="/proxy/8080") diff --git a/requirements.txt b/requirements.txt index 64a2d14..884b71c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ numpy Pillow nicegui +asyncer \ No newline at end of file diff --git a/source/control/runner.py b/source/control/runner.py index e6ae55e..3f46958 100644 --- a/source/control/runner.py +++ b/source/control/runner.py @@ -1,91 +1,94 @@ from pathlib import Path +from typing import List +from asyncer import asyncify import sys -specified_binding_dir = None -specified_bin_dir = None -if len(sys.argv) > 2: - specified_binding_dir = Path(sys.argv[1]) - specified_bin_dir = Path(sys.argv[2]) - print( - f"specified binding_dir: {specified_binding_dir}, bin_dir: {specified_bin_dir}" - ) - -latest_install_dir = None -latest_adb_path = None -latest_adb_address = None -latest_resource_dir = None - -resource = None -controller = None -instance = None - - -async def run_task( - install_dir: Path, adb_path: Path, adb_address: str, resource_dir: Path, task: str -): - binding_dir = ( - specified_binding_dir - and specified_binding_dir - or (install_dir / "binding" / "Python") - ) +async def import_maa(binding_dir: Path, bin_dir: Path) -> bool: if not binding_dir.exists(): - return "Binding directory does not exist" + print("Binding directory does not exist") + return False + + if not bin_dir.exists(): + print("Bin dir does not exist") + return False binding_dir = str(binding_dir) if binding_dir not in sys.path: sys.path.insert(0, binding_dir) - from maa.library import Library - from maa.resource import Resource - from maa.controller import AdbController - from maa.instance import Instance + try: + from maa.library import Library + except ModuleNotFoundError as err: + print(err) + return False + + version = await asyncify(Library.open)(bin_dir) + if not version: + print("Failed to open MaaFramework") + return False + + print(f"Import MAA successfully, version: {version}") + + return True + + +async def detect_adb() -> List["AdbDevice"]: from maa.toolkit import Toolkit - global latest_install_dir, latest_adb_path, latest_adb_address + return await Toolkit.adb_devices() - if latest_install_dir != install_dir: - bin_dir = specified_bin_dir and specified_bin_dir or (install_dir / "bin") - version = Library.open(bin_dir) - if not version: - return "Failed to open MaaFramework" - print(f"MaaFw Version: {version}") - latest_install_dir = install_dir +resource = None +controller = None +instance = None + + +async def connect_adb(path: Path, address: str) -> bool: + global controller + + from maa.controller import AdbController + + controller = AdbController(path, address) + connected = await controller.connect() + if not connected: + print("Failed to connect {path} {address}") + return False - Toolkit.init_option("./") + return True - global resource, controller, instance - if latest_adb_path != adb_path or latest_adb_address != adb_address: - controller = AdbController(adb_path, adb_address) - connected = await controller.connect() - if not connected: - return "Failed to connect to ADB" +async def load_resource(dir: Path) -> bool: + global resource - latest_adb_path = adb_path - latest_adb_address = adb_address + from maa.resource import Resource if not resource: resource = Resource() - # reload every time - loaded = resource.clear() and await resource.load(resource_dir) - if not loaded: - return "Failed to load resource" + return resource.clear() and await resource.load(dir) + + +async def run_task(entry: str, param: dict = {}) -> bool: + global controller, resource, instance + + from maa.instance import Instance if not instance: instance = Instance() instance.bind(resource, controller) - inited = instance.inited - if not inited: - return "Failed to init MaaFramework instance" + if not instance.inited: + print("Failed to init MaaFramework instance") + return False - ret = await instance.run_task(task, {}) - return f"Task returned: {ret}" + return await instance.run_task(entry, param) async def stop_task(): - if instance: - await instance.stop() + global instance + + if not instance: + return + + await instance.stop() diff --git a/source/webpage/index.py b/source/webpage/index.py index 3a98e8b..f6aec7f 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -1,66 +1,209 @@ from nicegui import app, ui from pathlib import Path -from source.control.runner import run_task, stop_task +from source.control.runner import * @ui.page("/") async def index(): - maafw_install_dir_input = ( + with ui.row(): + await import_maa_control() + with ui.row(): + await connect_adb_control() + with ui.row(): + await load_resource_control() + with ui.row(): + await run_task_control() + + +class ControlStatus: + def __init__(self): + self.label = ui.label() + self.pending() + + def pending(self): + self.label.text = "🟡" + + def success(self): + self.label.text = "✅" + + def failure(self): + self.label.text = "❌" + + def running(self): + self.label.text = "⏳" + + +async def import_maa_control(): + status = ControlStatus() + + pybinding_input = ( ui.input( - "MaaFramework Release Directory", - placeholder="eg: C:/Downloads/MAA-win-x86_64", + "MaaFramework Python Binding Directory", + placeholder="eg: C:/Downloads/MAA-win-x86_64/binding/Python", + on_change=lambda: status.pending(), ) .props("size=60") - .bind_value(app.storage.general, "maafw_install_dir") + .bind_value(app.storage.general, "maa_pybinding") ) + bin_input = ( + ui.input( + "MaaFramework Binary Directory", + placeholder="eg: C:/Downloads/MAA-win-x86_64/bin", + on_change=lambda: status.pending(), + ) + .props("size=60") + .bind_value(app.storage.general, "maa_bin") + ) + + import_button = ui.button("Import", on_click=lambda: on_click_import()) + + async def on_click_import(): + status.running() + + if not pybinding_input.value or not bin_input.value: + status.failure() + return + + imported = await import_maa(Path(pybinding_input.value), Path(bin_input.value)) + if not imported: + status.failure() + + status.success() + + pybinding_input.disable() + bin_input.disable() + import_button.disable() + + +async def connect_adb_control(): + status = ControlStatus() adb_path_input = ( - ui.input("ADB Path", placeholder="eg: C:/adb.exe") + ui.input( + "ADB Path", + placeholder="eg: C:/adb.exe", + on_change=lambda: status.pending(), + ) .props("size=60") .bind_value(app.storage.general, "adb_path") ) - adb_address_input = ( - ui.input("ADB Address", placeholder="eg: 127.0.0.1:5555") - .props("size=60") + ui.input( + "ADB Address", + placeholder="eg: 127.0.0.1:5555", + on_change=lambda: status.pending(), + ) + .props("size=30") .bind_value(app.storage.general, "adb_address") ) + ui.button( + "Connect", + on_click=lambda: on_click_connect(), + ) + ui.button( + "Detect", + on_click=lambda: on_click_detect(), + ) + devices_select = ui.select({}, on_change=lambda e: on_change_devices_select(e)) + + async def on_click_connect(): + status.running() + + if not adb_path_input.value or not adb_address_input.value: + status.failure() + return + + connected = await connect_adb( + Path(adb_path_input.value), adb_address_input.value + ) + if not connected: + status.failure() - resource_dir_input = ( - ui.input("Resource Directory", placeholder="eg: C:/M9A/assets/resource") + status.success() + + async def on_click_detect(): + devices = await detect_adb() + options = {} + for d in devices: + v = (d.adb_path, d.address) + l = d.name + " " + d.address + options[v] = l + + devices_select.options = options + devices_select.update() + if options: + devices_select.value = next(iter(options)) + + def on_change_devices_select(e): + adb_path_input.value = str(e.value[0]) + adb_address_input.value = e.value[1] + + +async def load_resource_control(): + status = ControlStatus() + + dir_input = ( + ui.input( + "Resource Directory", + placeholder="eg: C:/M9A/assets/resource/base", + on_change=lambda: status.pending(), + ) .props("size=60") .bind_value(app.storage.general, "resource_dir") ) - with ui.row(): - task_input = ( - ui.input("Task", placeholder="Enter the task") - .props("size=30") - .bind_value(app.storage.general, "task") - ) + ui.button( + "Load", + on_click=lambda: on_click_load(), + ) - run_button = ui.button("Run", on_click=on_run_button_click) - stop_button = ui.button("Stop", on_click=on_stop_button_click) + async def on_click_load(): + status.running() + if not dir_input.value: + status.failure() + return -async def on_run_button_click(): - maafw_install_dir = Path(app.storage.general.get("maafw_install_dir")) - adb_path = Path(app.storage.general.get("adb_path")) - adb_address = app.storage.general.get("adb_address") - resource_dir = Path(app.storage.general.get("resource_dir")) - task = app.storage.general.get("task") + loaded = await load_resource(Path(dir_input.value)) + if not loaded: + status.failure() - print( - f"on_run_button_click: maafw_install_dir: {maafw_install_dir}, adb_path: {adb_path}, adb_address: {adb_address}, resource_dir: {resource_dir}, task: {task}" - ) + status.success() + + +async def run_task_control(): + status = ControlStatus() - message = await run_task( - maafw_install_dir, adb_path, adb_address, resource_dir, task + entry_input = ( + ui.input( + "Task Entry", + placeholder="eg: StartUp", + on_change=lambda: status.pending(), + ) + .props("size=30") + .bind_value(app.storage.general, "task_entry") ) - ui.notify(message) + ui.button("Start", on_click=lambda: on_click_start()) + ui.button("Stop", on_click=lambda: on_click_stop()) + + async def on_click_start(): + status.running() + + if not entry_input.value: + status.failure() + return + + run = await run_task(entry_input.value) + if not run: + status.failure() + + status.success() + + async def on_click_stop(): + stopped = await stop_task() + if not stopped: + status.failure() -async def on_stop_button_click(): - await stop_task() - ui.notify("Task stopped") + status.pending() From d787afa0f45dd617c0e9ba18b08942cdc3789b48 Mon Sep 17 00:00:00 2001 From: MistEO Date: Thu, 21 Mar 2024 16:16:28 +0800 Subject: [PATCH 2/9] chore: rename --- source/webpage/index.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/webpage/index.py b/source/webpage/index.py index f6aec7f..97ce9cb 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -16,7 +16,7 @@ async def index(): await run_task_control() -class ControlStatus: +class StatusIndicator: def __init__(self): self.label = ui.label() self.pending() @@ -35,7 +35,7 @@ def running(self): async def import_maa_control(): - status = ControlStatus() + status = StatusIndicator() pybinding_input = ( ui.input( @@ -77,7 +77,7 @@ async def on_click_import(): async def connect_adb_control(): - status = ControlStatus() + status = StatusIndicator() adb_path_input = ( ui.input( @@ -141,7 +141,7 @@ def on_change_devices_select(e): async def load_resource_control(): - status = ControlStatus() + status = StatusIndicator() dir_input = ( ui.input( @@ -173,7 +173,7 @@ async def on_click_load(): async def run_task_control(): - status = ControlStatus() + status = StatusIndicator() entry_input = ( ui.input( From c19a048f6b319d03a591a462535dfccca72aba33 Mon Sep 17 00:00:00 2001 From: MistEO Date: Thu, 21 Mar 2024 17:59:46 +0800 Subject: [PATCH 3/9] =?UTF-8?q?perf:=20=E5=B8=83=E5=B1=80=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/webpage/index.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/source/webpage/index.py b/source/webpage/index.py index 97ce9cb..c51ccd1 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -6,32 +6,47 @@ @ui.page("/") async def index(): - with ui.row(): + ui.dark_mode() # auto dark mode + + with ui.row().style("align-items: center;"): await import_maa_control() - with ui.row(): + + ui.separator() + + with ui.row().style("align-items: center;"): await connect_adb_control() - with ui.row(): + with ui.row().style("align-items: center;"): await load_resource_control() - with ui.row(): + with ui.row().style("align-items: center;"): await run_task_control() + ui.separator() + class StatusIndicator: def __init__(self): - self.label = ui.label() self.pending() + ui.label().bind_text(self, "text") def pending(self): - self.label.text = "🟡" + self.text = "🟡" + return self def success(self): - self.label.text = "✅" + self.text = "✅" + return self def failure(self): - self.label.text = "❌" + self.text = "❌" + return self def running(self): - self.label.text = "⏳" + self.text = "👀" + return self + + def hide(self): + self.text = " " + return self async def import_maa_control(): @@ -106,6 +121,7 @@ async def connect_adb_control(): on_click=lambda: on_click_detect(), ) devices_select = ui.select({}, on_change=lambda e: on_change_devices_select(e)) + detect_status = StatusIndicator().hide() async def on_click_connect(): status.running() @@ -123,6 +139,8 @@ async def on_click_connect(): status.success() async def on_click_detect(): + detect_status.running() + devices = await detect_adb() options = {} for d in devices: @@ -135,6 +153,8 @@ async def on_click_detect(): if options: devices_select.value = next(iter(options)) + detect_status.hide() + def on_change_devices_select(e): adb_path_input.value = str(e.value[0]) adb_address_input.value = e.value[1] From 3f2e43ab559f353fa323ac1ba5ac0c8e06b5fbdb Mon Sep 17 00:00:00 2001 From: MistEO Date: Thu, 21 Mar 2024 22:57:44 +0800 Subject: [PATCH 4/9] =?UTF-8?q?fix:=20=E4=B8=80=E4=BA=9B=E5=B0=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/control/runner.py | 2 +- source/webpage/index.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/source/control/runner.py b/source/control/runner.py index 3f46958..acc1be8 100644 --- a/source/control/runner.py +++ b/source/control/runner.py @@ -52,7 +52,7 @@ async def connect_adb(path: Path, address: str) -> bool: controller = AdbController(path, address) connected = await controller.connect() if not connected: - print("Failed to connect {path} {address}") + print(f"Failed to connect {path} {address}") return False return True diff --git a/source/webpage/index.py b/source/webpage/index.py index c51ccd1..19ee44e 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -83,6 +83,7 @@ async def on_click_import(): imported = await import_maa(Path(pybinding_input.value), Path(bin_input.value)) if not imported: status.failure() + return status.success() @@ -135,6 +136,7 @@ async def on_click_connect(): ) if not connected: status.failure() + return status.success() @@ -188,6 +190,7 @@ async def on_click_load(): loaded = await load_resource(Path(dir_input.value)) if not loaded: status.failure() + return status.success() @@ -218,6 +221,7 @@ async def on_click_start(): run = await run_task(entry_input.value) if not run: status.failure() + return status.success() @@ -225,5 +229,6 @@ async def on_click_stop(): stopped = await stop_task() if not stopped: status.failure() + return status.pending() From 5a5b969d941e921650e67a9e02a77613fb1adfb7 Mon Sep 17 00:00:00 2001 From: MistEO Date: Fri, 22 Mar 2024 16:49:15 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=8C=87=E7=A4=BA=E5=99=A8=EF=BC=8C=E6=94=B9=E7=94=A8?= =?UTF-8?q?bind=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/control/runner.py | 4 + source/webpage/components/status_indicator.py | 33 ++++ source/webpage/index.py | 147 ++++++++++-------- 3 files changed, 119 insertions(+), 65 deletions(-) create mode 100644 source/webpage/components/status_indicator.py diff --git a/source/control/runner.py b/source/control/runner.py index acc1be8..0d53f86 100644 --- a/source/control/runner.py +++ b/source/control/runner.py @@ -19,6 +19,7 @@ async def import_maa(binding_dir: Path, bin_dir: Path) -> bool: try: from maa.library import Library + from maa.toolkit import Toolkit except ModuleNotFoundError as err: print(err) return False @@ -30,6 +31,8 @@ async def import_maa(binding_dir: Path, bin_dir: Path) -> bool: print(f"Import MAA successfully, version: {version}") + Toolkit.init_option("./") + return True @@ -92,3 +95,4 @@ async def stop_task(): return await instance.stop() + diff --git a/source/webpage/components/status_indicator.py b/source/webpage/components/status_indicator.py new file mode 100644 index 0000000..765766b --- /dev/null +++ b/source/webpage/components/status_indicator.py @@ -0,0 +1,33 @@ +from nicegui import ui +from enum import Enum, auto + + +class Status(Enum): + PENDING = auto() + RUNNING = auto() + SUCCESS = auto() + FAILURE = auto() + + +class StatusIndicator: + def __init__(self, target_object, target_name): + self._label = ui.label().bind_text_from( + target_object, + target_name, + backward=lambda s: StatusIndicator._text_backward(s), + ) + + def label(self): + return self._label + + @staticmethod + def _text_backward(status: Status) -> str: + match status: + case Status.PENDING: + return "🟡" + case Status.RUNNING: + return "👀" + case Status.SUCCESS: + return "✅" + case Status.FAILURE: + return "❌" diff --git a/source/webpage/index.py b/source/webpage/index.py index 19ee44e..b8d8377 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -2,6 +2,7 @@ from pathlib import Path from source.control.runner import * +from .components.status_indicator import Status, StatusIndicator @ui.page("/") @@ -23,125 +24,130 @@ async def index(): ui.separator() -class StatusIndicator: - def __init__(self): - self.pending() - ui.label().bind_text(self, "text") - - def pending(self): - self.text = "🟡" - return self - - def success(self): - self.text = "✅" - return self - - def failure(self): - self.text = "❌" - return self - - def running(self): - self.text = "👀" - return self - - def hide(self): - self.text = " " - return self +class GlobalStatus: + maa_imported: Status = Status.PENDING + adb_connected: Status = Status.PENDING + adb_detected: Status = Status.PENDING # not required + res_loaded: Status = Status.PENDING + task_started: Status = Status.PENDING async def import_maa_control(): - status = StatusIndicator() + + StatusIndicator(GlobalStatus, "maa_imported") pybinding_input = ( ui.input( "MaaFramework Python Binding Directory", placeholder="eg: C:/Downloads/MAA-win-x86_64/binding/Python", - on_change=lambda: status.pending(), ) .props("size=60") .bind_value(app.storage.general, "maa_pybinding") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + ) ) bin_input = ( ui.input( "MaaFramework Binary Directory", placeholder="eg: C:/Downloads/MAA-win-x86_64/bin", - on_change=lambda: status.pending(), ) .props("size=60") .bind_value(app.storage.general, "maa_bin") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + ) ) - import_button = ui.button("Import", on_click=lambda: on_click_import()) + import_button = ui.button( + "Import", on_click=lambda: on_click_import() + ).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + ) async def on_click_import(): - status.running() + GlobalStatus.maa_imported = Status.RUNNING if not pybinding_input.value or not bin_input.value: - status.failure() + GlobalStatus.maa_imported = Status.FAILURE return imported = await import_maa(Path(pybinding_input.value), Path(bin_input.value)) if not imported: - status.failure() + GlobalStatus.maa_imported = Status.FAILURE return - status.success() - - pybinding_input.disable() - bin_input.disable() - import_button.disable() + GlobalStatus.maa_imported = Status.SUCCESS async def connect_adb_control(): - status = StatusIndicator() + StatusIndicator(GlobalStatus, "adb_connected") adb_path_input = ( ui.input( "ADB Path", placeholder="eg: C:/adb.exe", - on_change=lambda: status.pending(), ) .props("size=60") .bind_value(app.storage.general, "adb_path") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) ) adb_address_input = ( ui.input( "ADB Address", placeholder="eg: 127.0.0.1:5555", - on_change=lambda: status.pending(), ) .props("size=30") .bind_value(app.storage.general, "adb_address") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) ) ui.button( "Connect", on_click=lambda: on_click_connect(), + ).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS ) + ui.button( "Detect", on_click=lambda: on_click_detect(), + ).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) + + devices_select = ui.select( + {}, on_change=lambda e: on_change_devices_select(e) + ).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) + StatusIndicator(GlobalStatus, "adb_detected").label().bind_visibility_from( + GlobalStatus, + "adb_detected", + backward=lambda s: s == Status.RUNNING or s == Status.FAILURE, ) - devices_select = ui.select({}, on_change=lambda e: on_change_devices_select(e)) - detect_status = StatusIndicator().hide() async def on_click_connect(): - status.running() + GlobalStatus.adb_connected = Status.RUNNING if not adb_path_input.value or not adb_address_input.value: - status.failure() + GlobalStatus.adb_connected = Status.FAILURE return connected = await connect_adb( Path(adb_path_input.value), adb_address_input.value ) if not connected: - status.failure() + GlobalStatus.adb_connected = Status.FAILURE return - status.success() + GlobalStatus.adb_connected = Status.SUCCESS async def on_click_detect(): - detect_status.running() + GlobalStatus.adb_detected = Status.RUNNING devices = await detect_adb() options = {} @@ -152,10 +158,11 @@ async def on_click_detect(): devices_select.options = options devices_select.update() - if options: - devices_select.value = next(iter(options)) + if not options: + GlobalStatus.adb_detected = Status.FAILURE - detect_status.hide() + devices_select.value = next(iter(options)) + GlobalStatus.adb_detected = Status.SUCCESS def on_change_devices_select(e): adb_path_input.value = str(e.value[0]) @@ -163,72 +170,82 @@ def on_change_devices_select(e): async def load_resource_control(): - status = StatusIndicator() + StatusIndicator(GlobalStatus, "res_loaded") dir_input = ( ui.input( "Resource Directory", placeholder="eg: C:/M9A/assets/resource/base", - on_change=lambda: status.pending(), ) .props("size=60") .bind_value(app.storage.general, "resource_dir") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) ) ui.button( "Load", on_click=lambda: on_click_load(), + ).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS ) async def on_click_load(): - status.running() + GlobalStatus.res_loaded = Status.RUNNING if not dir_input.value: - status.failure() + GlobalStatus.res_loaded = Status.FAILURE return loaded = await load_resource(Path(dir_input.value)) if not loaded: - status.failure() + GlobalStatus.res_loaded = Status.FAILURE return - status.success() + GlobalStatus.res_loaded = Status.SUCCESS async def run_task_control(): - status = StatusIndicator() + StatusIndicator(GlobalStatus, "task_started") entry_input = ( ui.input( "Task Entry", placeholder="eg: StartUp", - on_change=lambda: status.pending(), ) .props("size=30") .bind_value(app.storage.general, "task_entry") + .bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) ) - ui.button("Start", on_click=lambda: on_click_start()) - ui.button("Stop", on_click=lambda: on_click_stop()) + ui.button("Start", on_click=lambda: on_click_start()).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) + ui.button("Stop", on_click=lambda: on_click_stop()).bind_enabled_from( + GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + ) async def on_click_start(): - status.running() + GlobalStatus.task_started = Status.RUNNING if not entry_input.value: - status.failure() + GlobalStatus.task_started = Status.FAILURE return run = await run_task(entry_input.value) if not run: - status.failure() + GlobalStatus.task_started = Status.FAILURE return - status.success() + GlobalStatus.task_started = Status.SUCCESS async def on_click_stop(): stopped = await stop_task() if not stopped: - status.failure() + GlobalStatus.task_started = Status.FAILURE return - status.pending() + GlobalStatus.task_started = Status.PENDING From aae5f4eca6578e45bce6e96e1bc5a606a29f4590 Mon Sep 17 00:00:00 2001 From: MistEO Date: Fri, 22 Mar 2024 16:51:28 +0800 Subject: [PATCH 6/9] chore: rename --- source/webpage/index.py | 90 ++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/source/webpage/index.py b/source/webpage/index.py index b8d8377..6ffedf7 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -25,16 +25,16 @@ async def index(): class GlobalStatus: - maa_imported: Status = Status.PENDING - adb_connected: Status = Status.PENDING - adb_detected: Status = Status.PENDING # not required - res_loaded: Status = Status.PENDING - task_started: Status = Status.PENDING + maa_importing: Status = Status.PENDING + adb_connecting: Status = Status.PENDING + adb_detecting: Status = Status.PENDING # not required + res_loading: Status = Status.PENDING + task_running: Status = Status.PENDING async def import_maa_control(): - StatusIndicator(GlobalStatus, "maa_imported") + StatusIndicator(GlobalStatus, "maa_importing") pybinding_input = ( ui.input( @@ -44,7 +44,7 @@ async def import_maa_control(): .props("size=60") .bind_value(app.storage.general, "maa_pybinding") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s != Status.SUCCESS ) ) bin_input = ( @@ -55,33 +55,33 @@ async def import_maa_control(): .props("size=60") .bind_value(app.storage.general, "maa_bin") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s != Status.SUCCESS ) ) import_button = ui.button( "Import", on_click=lambda: on_click_import() ).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s != Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s != Status.SUCCESS ) async def on_click_import(): - GlobalStatus.maa_imported = Status.RUNNING + GlobalStatus.maa_importing = Status.RUNNING if not pybinding_input.value or not bin_input.value: - GlobalStatus.maa_imported = Status.FAILURE + GlobalStatus.maa_importing = Status.FAILURE return imported = await import_maa(Path(pybinding_input.value), Path(bin_input.value)) if not imported: - GlobalStatus.maa_imported = Status.FAILURE + GlobalStatus.maa_importing = Status.FAILURE return - GlobalStatus.maa_imported = Status.SUCCESS + GlobalStatus.maa_importing = Status.SUCCESS async def connect_adb_control(): - StatusIndicator(GlobalStatus, "adb_connected") + StatusIndicator(GlobalStatus, "adb_connecting") adb_path_input = ( ui.input( @@ -91,7 +91,7 @@ async def connect_adb_control(): .props("size=60") .bind_value(app.storage.general, "adb_path") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ) adb_address_input = ( @@ -102,52 +102,52 @@ async def connect_adb_control(): .props("size=30") .bind_value(app.storage.general, "adb_address") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ) ui.button( "Connect", on_click=lambda: on_click_connect(), ).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ui.button( "Detect", on_click=lambda: on_click_detect(), ).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) devices_select = ui.select( {}, on_change=lambda e: on_change_devices_select(e) ).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) - StatusIndicator(GlobalStatus, "adb_detected").label().bind_visibility_from( + StatusIndicator(GlobalStatus, "adb_detecting").label().bind_visibility_from( GlobalStatus, - "adb_detected", + "adb_detecting", backward=lambda s: s == Status.RUNNING or s == Status.FAILURE, ) async def on_click_connect(): - GlobalStatus.adb_connected = Status.RUNNING + GlobalStatus.adb_connecting = Status.RUNNING if not adb_path_input.value or not adb_address_input.value: - GlobalStatus.adb_connected = Status.FAILURE + GlobalStatus.adb_connecting = Status.FAILURE return connected = await connect_adb( Path(adb_path_input.value), adb_address_input.value ) if not connected: - GlobalStatus.adb_connected = Status.FAILURE + GlobalStatus.adb_connecting = Status.FAILURE return - GlobalStatus.adb_connected = Status.SUCCESS + GlobalStatus.adb_connecting = Status.SUCCESS async def on_click_detect(): - GlobalStatus.adb_detected = Status.RUNNING + GlobalStatus.adb_detecting = Status.RUNNING devices = await detect_adb() options = {} @@ -159,10 +159,10 @@ async def on_click_detect(): devices_select.options = options devices_select.update() if not options: - GlobalStatus.adb_detected = Status.FAILURE + GlobalStatus.adb_detecting = Status.FAILURE devices_select.value = next(iter(options)) - GlobalStatus.adb_detected = Status.SUCCESS + GlobalStatus.adb_detecting = Status.SUCCESS def on_change_devices_select(e): adb_path_input.value = str(e.value[0]) @@ -170,7 +170,7 @@ def on_change_devices_select(e): async def load_resource_control(): - StatusIndicator(GlobalStatus, "res_loaded") + StatusIndicator(GlobalStatus, "res_loading") dir_input = ( ui.input( @@ -180,7 +180,7 @@ async def load_resource_control(): .props("size=60") .bind_value(app.storage.general, "resource_dir") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ) @@ -188,26 +188,26 @@ async def load_resource_control(): "Load", on_click=lambda: on_click_load(), ).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) async def on_click_load(): - GlobalStatus.res_loaded = Status.RUNNING + GlobalStatus.res_loading = Status.RUNNING if not dir_input.value: - GlobalStatus.res_loaded = Status.FAILURE + GlobalStatus.res_loading = Status.FAILURE return loaded = await load_resource(Path(dir_input.value)) if not loaded: - GlobalStatus.res_loaded = Status.FAILURE + GlobalStatus.res_loading = Status.FAILURE return - GlobalStatus.res_loaded = Status.SUCCESS + GlobalStatus.res_loading = Status.SUCCESS async def run_task_control(): - StatusIndicator(GlobalStatus, "task_started") + StatusIndicator(GlobalStatus, "task_running") entry_input = ( ui.input( @@ -217,35 +217,35 @@ async def run_task_control(): .props("size=30") .bind_value(app.storage.general, "task_entry") .bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ) ui.button("Start", on_click=lambda: on_click_start()).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) ui.button("Stop", on_click=lambda: on_click_stop()).bind_enabled_from( - GlobalStatus, "maa_imported", backward=lambda s: s == Status.SUCCESS + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) async def on_click_start(): - GlobalStatus.task_started = Status.RUNNING + GlobalStatus.task_running = Status.RUNNING if not entry_input.value: - GlobalStatus.task_started = Status.FAILURE + GlobalStatus.task_running = Status.FAILURE return run = await run_task(entry_input.value) if not run: - GlobalStatus.task_started = Status.FAILURE + GlobalStatus.task_running = Status.FAILURE return - GlobalStatus.task_started = Status.SUCCESS + GlobalStatus.task_running = Status.SUCCESS async def on_click_stop(): stopped = await stop_task() if not stopped: - GlobalStatus.task_started = Status.FAILURE + GlobalStatus.task_running = Status.FAILURE return - GlobalStatus.task_started = Status.PENDING + GlobalStatus.task_running = Status.PENDING From 9fa04591313bf5973ecd0146967ff14b2a0cf5dd Mon Sep 17 00:00:00 2001 From: MistEO Date: Sat, 23 Mar 2024 22:58:27 +0800 Subject: [PATCH 7/9] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ requirements.txt | 2 + .../runner.py => interaction/interaction.py} | 24 ++++++- source/webpage/components/screenshotter.py | 32 +++++++++ source/webpage/index.py | 66 +++++++++++++++---- 5 files changed, 114 insertions(+), 14 deletions(-) rename source/{control/runner.py => interaction/interaction.py} (81%) create mode 100644 source/webpage/components/screenshotter.py diff --git a/.gitignore b/.gitignore index 470d849..7b20a98 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,7 @@ cython_debug/ # nicegui .nicegui/ + +# maafw +config +debug diff --git a/requirements.txt b/requirements.txt index 884b71c..cd793d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ +### from MaaFw numpy Pillow +### from MaaDebugger nicegui asyncer \ No newline at end of file diff --git a/source/control/runner.py b/source/interaction/interaction.py similarity index 81% rename from source/control/runner.py rename to source/interaction/interaction.py index 0d53f86..902ba06 100644 --- a/source/control/runner.py +++ b/source/interaction/interaction.py @@ -1,7 +1,8 @@ from pathlib import Path -from typing import List +from typing import List, Optional from asyncer import asyncify import sys +from PIL import Image async def import_maa(binding_dir: Path, bin_dir: Path) -> bool: @@ -96,3 +97,24 @@ async def stop_task(): await instance.stop() + +async def screencap() -> Optional[Image]: + global controller + if not controller: + return None + + im = await controller.screencap() + if im is None: + return None + + pil = Image.fromarray(im) + b, g, r = pil.split() + return Image.merge("RGB", (r, g, b)) + + +async def click(x, y) -> None: + global controller + if not controller: + return None + + await controller.click(x, y) diff --git a/source/webpage/components/screenshotter.py b/source/webpage/components/screenshotter.py new file mode 100644 index 0000000..963b92c --- /dev/null +++ b/source/webpage/components/screenshotter.py @@ -0,0 +1,32 @@ +from nicegui import ui +import asyncio +import threading + +from source.interaction.interaction import screencap + + +class Screenshotter(threading.Thread): + def __init__(self): + super().__init__() + self.source = None + self.active = False + + def __del__(self): + self.active = False + self.source = None + super().__del__() + + def run(self): + while self.active: + im = asyncio.run(screencap()) + if not im: + continue + + self.source = im + + def start(self): + self.active = True + super().start() + + def stop(self): + self.active = False diff --git a/source/webpage/index.py b/source/webpage/index.py index 6ffedf7..6790616 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -1,25 +1,33 @@ -from nicegui import app, ui +from nicegui import app, ui, binding from pathlib import Path -from source.control.runner import * +from source.interaction.interaction import * from .components.status_indicator import Status, StatusIndicator +from .components.screenshotter import Screenshotter +binding.MAX_PROPAGATION_TIME = 1 +ui.dark_mode() # auto dark mode + @ui.page("/") async def index(): - ui.dark_mode() # auto dark mode with ui.row().style("align-items: center;"): await import_maa_control() ui.separator() - with ui.row().style("align-items: center;"): - await connect_adb_control() - with ui.row().style("align-items: center;"): - await load_resource_control() - with ui.row().style("align-items: center;"): - await run_task_control() + with ui.row(): + with ui.column(): + with ui.row().style("align-items: center;"): + await connect_adb_control() + with ui.row().style("align-items: center;"): + await load_resource_control() + with ui.row().style("align-items: center;"): + await run_task_control() + + with ui.column(): + await screenshot_control() ui.separator() @@ -32,6 +40,9 @@ class GlobalStatus: task_running: Status = Status.PENDING +screenshotter = Screenshotter() + + async def import_maa_control(): StatusIndicator(GlobalStatus, "maa_importing") @@ -119,11 +130,18 @@ async def connect_adb_control(): GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS ) - devices_select = ui.select( - {}, on_change=lambda e: on_change_devices_select(e) - ).bind_enabled_from( - GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS + devices_select = ( + ui.select({}, on_change=lambda e: on_change_devices_select(e)) + .bind_enabled_from( + GlobalStatus, "maa_importing", backward=lambda s: s == Status.SUCCESS + ) + .bind_visibility_from( + GlobalStatus, + "adb_detecting", + backward=lambda s: s == Status.SUCCESS, + ) ) + StatusIndicator(GlobalStatus, "adb_detecting").label().bind_visibility_from( GlobalStatus, "adb_detecting", @@ -145,6 +163,9 @@ async def on_click_connect(): return GlobalStatus.adb_connecting = Status.SUCCESS + GlobalStatus.adb_detecting = Status.PENDING + + screenshotter.start() async def on_click_detect(): GlobalStatus.adb_detecting = Status.RUNNING @@ -169,6 +190,22 @@ def on_change_devices_select(e): adb_address_input.value = e.value[1] +async def screenshot_control(): + with ui.card().tight(): + ui.interactive_image( + cross="green", + on_mouse=lambda e: on_click_image(int(e.image_x), int(e.image_y)), + ).bind_source_from(screenshotter, "source").style( + "height: 200px; align-items: center;" + ).bind_visibility_from( + GlobalStatus, "adb_connecting", backward=lambda s: s == Status.SUCCESS + ) + + async def on_click_image(x, y): + print(f"on_click_image: {x}, {y}") + await click(x, y) + + async def load_resource_control(): StatusIndicator(GlobalStatus, "res_loading") @@ -229,6 +266,8 @@ async def run_task_control(): ) async def on_click_start(): + screenshotter.stop() + GlobalStatus.task_running = Status.RUNNING if not entry_input.value: @@ -249,3 +288,4 @@ async def on_click_stop(): return GlobalStatus.task_running = Status.PENDING + screenshotter.start() From a3f92d987904ff2b6c733f775331bbb8aba7668d Mon Sep 17 00:00:00 2001 From: MistEO Date: Sat, 23 Mar 2024 23:12:24 +0800 Subject: [PATCH 8/9] =?UTF-8?q?perf:=20=E4=B8=80=E4=BA=9B=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interaction/{interaction.py => maafw.py} | 0 source/webpage/components/screenshotter.py | 5 ++--- source/webpage/index.py | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) rename source/interaction/{interaction.py => maafw.py} (100%) diff --git a/source/interaction/interaction.py b/source/interaction/maafw.py similarity index 100% rename from source/interaction/interaction.py rename to source/interaction/maafw.py diff --git a/source/webpage/components/screenshotter.py b/source/webpage/components/screenshotter.py index 963b92c..3607604 100644 --- a/source/webpage/components/screenshotter.py +++ b/source/webpage/components/screenshotter.py @@ -2,7 +2,7 @@ import asyncio import threading -from source.interaction.interaction import screencap +import source.interaction.maafw as maafw class Screenshotter(threading.Thread): @@ -14,11 +14,10 @@ def __init__(self): def __del__(self): self.active = False self.source = None - super().__del__() def run(self): while self.active: - im = asyncio.run(screencap()) + im = asyncio.run(maafw.screencap()) if not im: continue diff --git a/source/webpage/index.py b/source/webpage/index.py index 6790616..453f926 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -1,7 +1,8 @@ from nicegui import app, ui, binding from pathlib import Path -from source.interaction.interaction import * +import source.interaction.maafw as maafw + from .components.status_indicator import Status, StatusIndicator from .components.screenshotter import Screenshotter @@ -9,6 +10,7 @@ binding.MAX_PROPAGATION_TIME = 1 ui.dark_mode() # auto dark mode + @ui.page("/") async def index(): @@ -83,7 +85,7 @@ async def on_click_import(): GlobalStatus.maa_importing = Status.FAILURE return - imported = await import_maa(Path(pybinding_input.value), Path(bin_input.value)) + imported = await maafw.import_maa(Path(pybinding_input.value), Path(bin_input.value)) if not imported: GlobalStatus.maa_importing = Status.FAILURE return @@ -155,7 +157,7 @@ async def on_click_connect(): GlobalStatus.adb_connecting = Status.FAILURE return - connected = await connect_adb( + connected = await maafw.connect_adb( Path(adb_path_input.value), adb_address_input.value ) if not connected: @@ -170,7 +172,7 @@ async def on_click_connect(): async def on_click_detect(): GlobalStatus.adb_detecting = Status.RUNNING - devices = await detect_adb() + devices = await maafw.detect_adb() options = {} for d in devices: v = (d.adb_path, d.address) @@ -203,7 +205,7 @@ async def screenshot_control(): async def on_click_image(x, y): print(f"on_click_image: {x}, {y}") - await click(x, y) + await maafw.click(x, y) async def load_resource_control(): @@ -235,7 +237,7 @@ async def on_click_load(): GlobalStatus.res_loading = Status.FAILURE return - loaded = await load_resource(Path(dir_input.value)) + loaded = await maafw.load_resource(Path(dir_input.value)) if not loaded: GlobalStatus.res_loading = Status.FAILURE return @@ -274,7 +276,7 @@ async def on_click_start(): GlobalStatus.task_running = Status.FAILURE return - run = await run_task(entry_input.value) + run = await maafw.run_task(entry_input.value) if not run: GlobalStatus.task_running = Status.FAILURE return @@ -282,7 +284,7 @@ async def on_click_start(): GlobalStatus.task_running = Status.SUCCESS async def on_click_stop(): - stopped = await stop_task() + stopped = await maafw.stop_task() if not stopped: GlobalStatus.task_running = Status.FAILURE return From e1180dd5c778f79c12267ced17a717eeaf546e51 Mon Sep 17 00:00:00 2001 From: MistEO Date: Mon, 25 Mar 2024 14:15:17 +0800 Subject: [PATCH 9/9] =?UTF-8?q?chore:=20=E5=B0=8F=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/interaction/maafw.py | 2 +- source/webpage/components/screenshotter.py | 1 - source/webpage/index.py | 6 ++++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/interaction/maafw.py b/source/interaction/maafw.py index 902ba06..f9f63b3 100644 --- a/source/interaction/maafw.py +++ b/source/interaction/maafw.py @@ -1,9 +1,9 @@ from pathlib import Path from typing import List, Optional from asyncer import asyncify -import sys from PIL import Image +import sys async def import_maa(binding_dir: Path, bin_dir: Path) -> bool: if not binding_dir.exists(): diff --git a/source/webpage/components/screenshotter.py b/source/webpage/components/screenshotter.py index 3607604..fa10990 100644 --- a/source/webpage/components/screenshotter.py +++ b/source/webpage/components/screenshotter.py @@ -1,4 +1,3 @@ -from nicegui import ui import asyncio import threading diff --git a/source/webpage/index.py b/source/webpage/index.py index 453f926..c738f02 100644 --- a/source/webpage/index.py +++ b/source/webpage/index.py @@ -85,7 +85,9 @@ async def on_click_import(): GlobalStatus.maa_importing = Status.FAILURE return - imported = await maafw.import_maa(Path(pybinding_input.value), Path(bin_input.value)) + imported = await maafw.import_maa( + Path(pybinding_input.value), Path(bin_input.value) + ) if not imported: GlobalStatus.maa_importing = Status.FAILURE return @@ -198,7 +200,7 @@ async def screenshot_control(): cross="green", on_mouse=lambda e: on_click_image(int(e.image_x), int(e.image_y)), ).bind_source_from(screenshotter, "source").style( - "height: 200px; align-items: center;" + "height: 200px;" ).bind_visibility_from( GlobalStatus, "adb_connecting", backward=lambda s: s == Status.SUCCESS )