From d5a8468469b9cb64c03303426fbe9be7e2c1f222 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 13 Apr 2026 12:15:15 +0000
Subject: [PATCH 01/15] Add missing C# bindings for MaaFramework C API
- Add MaaMacOSControllerCreate, MaaAndroidNativeControllerCreate,
MaaReplayControllerCreate, MaaRecordControllerCreate P/Invoke
- Add MaaControllerPostRelativeMove P/Invoke
- Add MaaTaskerGetWaitFreezesDetail P/Invoke
- Add MaaToolkitMacOS P/Invoke (CheckPermission, RequestPermission,
RevealPermissionSettings)
- Update MaaDbgControllerCreate to new 1-param signature
- Add MaaMacOSController, MaaAndroidNativeController,
MaaReplayController, MaaRecordController wrapper classes
- Update MaaDbgController wrapper for new API
- Add RelativeMove to IMaaController/MaaController
- Add GetWaitFreezesDetail to IMaaTasker/MaaTasker
- Add MacOSScreencapMethod, MacOSInputMethod, MacOSPermission enums
- Add MaaWfId type alias
- Add IMaaToolkitMacOS interface and implementation
- Add Node.WaitFreezes messages to MaaMsg
Agent-Logs-Url: https://github.com/MaaXYZ/MaaFramework.Binding.CSharp/sessions/201d767e-cec0-4888-b09f-8b7a74837c9a
Co-authored-by: MistEO <18511905+MistEO@users.noreply.github.com>
---
.../Framework/Instance/MaaController.cs | 45 +++++++++++++++-
.../Interop/Framework/Instance/MaaTasker.cs | 4 ++
.../Interop/Framework/MaaDef.cs | 5 ++
.../Interop/Toolkit/MaaToolkitDef.cs | 1 +
.../Interop/Toolkit/MacOS/MaaToolkitMacOS.cs | 30 +++++++++++
.../MaaController.cs | 7 +++
.../MaaAndroidNativeController.cs | 46 ++++++++++++++++
.../MaaController/MaaDbgController.cs | 24 +++------
.../MaaController/MaaMacOSController.cs | 53 +++++++++++++++++++
.../MaaController/MaaRecordController.cs | 51 ++++++++++++++++++
.../MaaController/MaaReplayController.cs | 46 ++++++++++++++++
src/MaaFramework.Binding.Native/MaaTasker.cs | 30 +++++++++++
src/MaaFramework.Binding.Native/MaaToolkit.cs | 28 ++++++++++
.../Enums/Controllers/MacOSInputMethod.cs | 24 +++++++++
.../Enums/Controllers/MacOSPermission.cs | 22 ++++++++
.../Enums/Controllers/MacOSScreencapMethod.cs | 22 ++++++++
src/MaaFramework.Binding/IMaaController.cs | 8 +++
src/MaaFramework.Binding/IMaaTasker.cs | 13 +++++
src/MaaFramework.Binding/IMaaToolkit.cs | 32 +++++++++++
src/MaaFramework.Binding/MaaDef.cs | 1 +
src/MaaFramework.Binding/MaaMsg.cs | 35 ++++++++++++
21 files changed, 509 insertions(+), 18 deletions(-)
create mode 100644 src/MaaFramework.Binding.Native/Interop/Toolkit/MacOS/MaaToolkitMacOS.cs
create mode 100644 src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
create mode 100644 src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
create mode 100644 src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
create mode 100644 src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
create mode 100644 src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
create mode 100644 src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
create mode 100644 src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index e128ac7..23090fd 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -28,8 +28,48 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaCustomControllerCreate([MarshalUsing(typeof(MaaMarshaller))] Custom.IMaaCustomController controller, nint controllerArg);
+ ///
+ /// Create a debug controller that serves images from a directory.
+ ///
+ /// Path to a directory of images (or a single image file).
+ /// The controller handle, or nint.Zero on failure.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaDbgControllerCreate(string readPath);
+
+ ///
+ /// Create a macOS controller for native macOS applications.
+ ///
+ /// The CGWindowID of the target window (0 for desktop).
+ /// macOS screencap method to use.
+ /// macOS input method to use.
+ /// The controller handle, or nint.Zero on failure.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaMacOSControllerCreate(uint windowId, MaaMacOSScreencapMethod screencapMethod, MaaMacOSInputMethod inputMethod);
+
+ ///
+ /// Create an Android native controller.
+ ///
+ /// JSON config for the control unit.
+ /// The controller handle, or nint.Zero on failure.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaAndroidNativeControllerCreate(string configJson);
+
+ ///
+ /// Create a replay controller that replays recorded operations.
+ ///
+ /// Path to the recording JSONL file.
+ /// The controller handle, or nint.Zero on failure.
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
- public static partial MaaControllerHandle MaaDbgControllerCreate(string readPath, string writePath, MaaDbgControllerType type, string config);
+ public static partial MaaControllerHandle MaaReplayControllerCreate(string recordingPath);
+
+ ///
+ /// Create a record controller that wraps an existing controller and records all operations.
+ ///
+ /// The inner controller to forward all operations to.
+ /// Path to the recording JSONL file to write.
+ /// The record controller handle, or nint.Zero on failure.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaRecordControllerCreate(MaaControllerHandle inner, string recordingPath);
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial void MaaControllerDestroy(MaaControllerHandle ctrl);
@@ -87,6 +127,9 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostTouchUp(MaaControllerHandle ctrl, int contact);
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaCtrlId MaaControllerPostRelativeMove(MaaControllerHandle ctrl, int dx, int dy);
+
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostKeyDown(MaaControllerHandle ctrl, int keycode);
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
index 0046b3e..90ae9b2 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
@@ -109,6 +109,10 @@ public static partial class MaaTasker
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool MaaTaskerGetNodeDetail(MaaTaskerHandle tasker, MaaNodeId nodeId, MaaStringBufferHandle nodeName, out MaaRecoId recoId, out MaaActId actionId, [MarshalAs(UnmanagedType.U1)] out bool completed);
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool MaaTaskerGetWaitFreezesDetail(MaaTaskerHandle tasker, MaaWfId wfId, MaaStringBufferHandle nodeName, MaaStringBufferHandle phase, [MarshalAs(UnmanagedType.U1)] out bool success, out MaaSize elapsedMs, MaaRecoId[]? recoIdList, ref MaaSize recoIdListSize, MaaRectHandle roi);
+
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool MaaTaskerGetTaskDetail(MaaTaskerHandle tasker, MaaTaskId taskId, MaaStringBufferHandle entry, MaaNodeId[] nodeIdList, ref MaaSize nodeIdListSize, out MaaStatus status);
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
index 34a5784..0d8fadf 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
@@ -50,10 +50,15 @@
// No bitwise OR, just set it
global using MaaDbgControllerType = System.UInt64;
global using MaaControllerFeature = System.UInt64;
+// macOS controller types
+global using MaaMacOSScreencapMethod = System.UInt64;
+global using MaaMacOSInputMethod = System.UInt64;
// Gamepad types
global using MaaGamepadType = System.UInt64;
global using MaaGamepadButton = System.UInt64;
global using MaaGamepadTouch = System.UInt64;
+// WaitFreezes id
+global using MaaWfId = System.Int64;
global using MaaRectHandle = nint;
using System.Runtime.InteropServices;
diff --git a/src/MaaFramework.Binding.Native/Interop/Toolkit/MaaToolkitDef.cs b/src/MaaFramework.Binding.Native/Interop/Toolkit/MaaToolkitDef.cs
index bc13a1a..0261a5c 100644
--- a/src/MaaFramework.Binding.Native/Interop/Toolkit/MaaToolkitDef.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Toolkit/MaaToolkitDef.cs
@@ -14,6 +14,7 @@
global using MaaToolkitAdbDeviceListHandle = nint;
global using MaaToolkitDesktopWindowHandle = nint;
global using MaaToolkitDesktopWindowListHandle = nint;
+global using MaaMacOSPermission = System.Int32;
namespace MaaFramework.Binding.Interop.Native;
diff --git a/src/MaaFramework.Binding.Native/Interop/Toolkit/MacOS/MaaToolkitMacOS.cs b/src/MaaFramework.Binding.Native/Interop/Toolkit/MacOS/MaaToolkitMacOS.cs
new file mode 100644
index 0000000..ffc34e1
--- /dev/null
+++ b/src/MaaFramework.Binding.Native/Interop/Toolkit/MacOS/MaaToolkitMacOS.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable CS1573 // 参数在 XML 注释中没有匹配的 param 标记
+#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
+
+using System.Runtime.InteropServices;
+
+namespace MaaFramework.Binding.Interop.Native;
+
+public static partial class MaaToolkitMacOS
+{
+ [LibraryImport("MaaToolkit")]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool MaaToolkitMacOSCheckPermission(MaaMacOSPermission perm);
+
+ [LibraryImport("MaaToolkit")]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool MaaToolkitMacOSRequestPermission(MaaMacOSPermission perm);
+
+ [LibraryImport("MaaToolkit")]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool MaaToolkitMacOSRevealPermissionSettings(MaaMacOSPermission perm);
+}
diff --git a/src/MaaFramework.Binding.Native/MaaController.cs b/src/MaaFramework.Binding.Native/MaaController.cs
index 47766e2..752e6ce 100644
--- a/src/MaaFramework.Binding.Native/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController.cs
@@ -168,6 +168,13 @@ public MaaJob TouchMove(int contact, int x, int y, int pressure = 1)
public MaaJob TouchUp(int contact)
=> CreateJob(MaaControllerPostTouchUp(Handle, contact));
+ ///
+ ///
+ /// Wrapper of .
+ ///
+ public MaaJob RelativeMove(int dx, int dy)
+ => CreateJob(MaaControllerPostRelativeMove(Handle, dx, dy));
+
///
///
/// Wrapper of .
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
new file mode 100644
index 0000000..728d087
--- /dev/null
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
@@ -0,0 +1,46 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using static MaaFramework.Binding.Interop.Native.MaaController;
+
+namespace MaaFramework.Binding;
+
+///
+/// A wrapper class providing a reference implementation for .
+///
+[DebuggerDisplay("{DebuggerDisplay,nq}")]
+public class MaaAndroidNativeController : MaaController
+{
+ private readonly string _debugConfigJson;
+
+ [ExcludeFromCodeCoverage(Justification = "Debugger display.")]
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string DebuggerDisplay => IsInvalid
+ ? $"Invalid {GetType().Name}"
+ : $"{GetType().Name} {{ ConfigJson = {_debugConfigJson} }}";
+
+ ///
+ /// Creates a instance.
+ ///
+ /// JSON config for the control unit.
+ /// Executes if ; otherwise, not link.
+ /// Checks LinkStart().Wait() status if ; otherwise, not check.
+ ///
+ /// Wrapper of .
+ /// This controller is only available on Android.
+ ///
+ ///
+ ///
+ public MaaAndroidNativeController([StringSyntax("Json")] string configJson, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(configJson);
+
+ var handle = MaaAndroidNativeControllerCreate(configJson);
+ _ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
+ SetHandle(handle, needReleased: true);
+
+ _debugConfigJson = configJson;
+
+ if (link == LinkOption.Start)
+ LinkStartOnConstructed(check, configJson);
+ }
+}
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
index 274910a..41e91a2 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
@@ -11,47 +11,37 @@ namespace MaaFramework.Binding;
public class MaaDbgController : MaaController
{
private readonly string _debugReadPath;
- private readonly string _debugWritePath;
- private readonly DbgControllerType _debugType;
- private readonly string _debugConfig;
[ExcludeFromCodeCoverage(Justification = "Debugger display.")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => IsInvalid
? $"Invalid {GetType().Name}"
- : $"{GetType().Name} {{ ReadFrom = {_debugReadPath}, WriteTo = {_debugWritePath}, Type = {_debugType}, Config = {_debugConfig} }}";
+ : $"{GetType().Name} {{ ReadFrom = {_debugReadPath} }}";
///
/// Creates a instance.
///
- /// The read path.
- /// The write path.
- /// The DebugControllerType.
- /// The config.
+ /// Path to a directory of images (or a single image file).
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
/// Wrapper of .
+ /// Images are loaded on connect and cycled through on each screencap request.
+ /// All input operations (click, swipe, etc.) are no-ops that return success.
///
///
///
- public MaaDbgController(string readPath, string writePath, DbgControllerType type, [StringSyntax("Json")] string config = "{}", LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ public MaaDbgController(string readPath, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
{
ArgumentException.ThrowIfNullOrEmpty(readPath);
- ArgumentException.ThrowIfNullOrEmpty(writePath);
- if (type == DbgControllerType.None) throw new ArgumentException($"Value cannot be {DbgControllerType.None}.", nameof(type));
- ArgumentException.ThrowIfNullOrEmpty(config);
- var handle = MaaDbgControllerCreate(readPath, writePath, (MaaDbgControllerType)type, config);
+ var handle = MaaDbgControllerCreate(readPath);
_ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
SetHandle(handle, needReleased: true);
_debugReadPath = readPath;
- _debugWritePath = writePath;
- _debugType = type;
- _debugConfig = config;
if (link == LinkOption.Start)
- LinkStartOnConstructed(check, readPath, writePath, type, config);
+ LinkStartOnConstructed(check, readPath);
}
}
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
new file mode 100644
index 0000000..0ecc125
--- /dev/null
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
@@ -0,0 +1,53 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using static MaaFramework.Binding.Interop.Native.MaaController;
+
+namespace MaaFramework.Binding;
+
+///
+/// A wrapper class providing a reference implementation for .
+///
+[DebuggerDisplay("{DebuggerDisplay,nq}")]
+public class MaaMacOSController : MaaController
+{
+ private readonly uint _debugWindowId;
+ private readonly MacOSScreencapMethod _debugScreencapMethod;
+ private readonly MacOSInputMethod _debugInputMethod;
+
+ [ExcludeFromCodeCoverage(Justification = "Debugger display.")]
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string DebuggerDisplay => IsInvalid
+ ? $"Invalid {GetType().Name}"
+ : $"{GetType().Name} {{ WindowId = {_debugWindowId}, ScreencapMethod = {_debugScreencapMethod}, InputMethod = {_debugInputMethod} }}";
+
+ ///
+ /// Creates a instance.
+ ///
+ /// The CGWindowID of the target window (0 for desktop).
+ /// macOS screencap method to use.
+ /// macOS input method to use.
+ /// Executes if ; otherwise, not link.
+ /// Checks LinkStart().Wait() status if ; otherwise, not check.
+ ///
+ /// Wrapper of .
+ /// This controller is designed for native macOS applications.
+ /// Requires Screen Recording permission for screencap.
+ /// Input simulation requires Accessibility permission.
+ /// Some features are not supported: start_app, stop_app, scroll.
+ /// Only single touch is supported (contact must be 0).
+ ///
+ ///
+ public MaaMacOSController(uint windowId, MacOSScreencapMethod screencapMethod, MacOSInputMethod inputMethod, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ {
+ var handle = MaaMacOSControllerCreate(windowId, (MaaMacOSScreencapMethod)screencapMethod, (MaaMacOSInputMethod)inputMethod);
+ _ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
+ SetHandle(handle, needReleased: true);
+
+ _debugWindowId = windowId;
+ _debugScreencapMethod = screencapMethod;
+ _debugInputMethod = inputMethod;
+
+ if (link == LinkOption.Start)
+ LinkStartOnConstructed(check, windowId, screencapMethod, inputMethod);
+ }
+}
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
new file mode 100644
index 0000000..9e38ecd
--- /dev/null
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
@@ -0,0 +1,51 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using static MaaFramework.Binding.Interop.Native.MaaController;
+
+namespace MaaFramework.Binding;
+
+///
+/// A wrapper class providing a reference implementation for .
+///
+[DebuggerDisplay("{DebuggerDisplay,nq}")]
+public class MaaRecordController : MaaController
+{
+ private readonly string _debugRecordingPath;
+
+ [ExcludeFromCodeCoverage(Justification = "Debugger display.")]
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string DebuggerDisplay => IsInvalid
+ ? $"Invalid {GetType().Name}"
+ : $"{GetType().Name} {{ RecordingPath = {_debugRecordingPath} }}";
+
+ ///
+ /// Creates a instance.
+ ///
+ /// The inner controller to forward all operations to. Must not be null.
+ /// Path to the recording JSONL file to write.
+ /// Executes if ; otherwise, not link.
+ /// Checks LinkStart().Wait() status if ; otherwise, not check.
+ ///
+ /// Wrapper of .
+ /// The record controller does NOT take ownership of the inner controller.
+ /// Screenshot images will be saved to a "{stem}-Screenshot" folder in the same directory as this file.
+ /// The recorded file can be replayed using .
+ ///
+ ///
+ ///
+ ///
+ public MaaRecordController(MaaController inner, string recordingPath, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ {
+ ArgumentNullException.ThrowIfNull(inner);
+ ArgumentException.ThrowIfNullOrEmpty(recordingPath);
+
+ var handle = MaaRecordControllerCreate(inner.Handle, recordingPath);
+ _ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
+ SetHandle(handle, needReleased: true);
+
+ _debugRecordingPath = recordingPath;
+
+ if (link == LinkOption.Start)
+ LinkStartOnConstructed(check, inner, recordingPath);
+ }
+}
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
new file mode 100644
index 0000000..4755ed7
--- /dev/null
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
@@ -0,0 +1,46 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using static MaaFramework.Binding.Interop.Native.MaaController;
+
+namespace MaaFramework.Binding;
+
+///
+/// A wrapper class providing a reference implementation for .
+///
+[DebuggerDisplay("{DebuggerDisplay,nq}")]
+public class MaaReplayController : MaaController
+{
+ private readonly string _debugRecordingPath;
+
+ [ExcludeFromCodeCoverage(Justification = "Debugger display.")]
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string DebuggerDisplay => IsInvalid
+ ? $"Invalid {GetType().Name}"
+ : $"{GetType().Name} {{ RecordingPath = {_debugRecordingPath} }}";
+
+ ///
+ /// Creates a instance.
+ ///
+ /// Path to the recording JSONL file written by .
+ /// Executes if ; otherwise, not link.
+ /// Checks LinkStart().Wait() status if ; otherwise, not check.
+ ///
+ /// Wrapper of .
+ /// Screenshot image paths in the file are resolved relative to this file's parent directory.
+ ///
+ ///
+ ///
+ public MaaReplayController(string recordingPath, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(recordingPath);
+
+ var handle = MaaReplayControllerCreate(recordingPath);
+ _ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
+ SetHandle(handle, needReleased: true);
+
+ _debugRecordingPath = recordingPath;
+
+ if (link == LinkOption.Start)
+ LinkStartOnConstructed(check, recordingPath);
+ }
+}
diff --git a/src/MaaFramework.Binding.Native/MaaTasker.cs b/src/MaaFramework.Binding.Native/MaaTasker.cs
index b6e11e9..532851e 100644
--- a/src/MaaFramework.Binding.Native/MaaTasker.cs
+++ b/src/MaaFramework.Binding.Native/MaaTasker.cs
@@ -407,6 +407,36 @@ public bool GetActionDetail(MaaActId actionId, out string nodeName, out string a
return ret;
}
+ ///
+ public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi)
+ => GetWaitFreezesDetail(wfId, out nodeName, out phase, out isSucceeded, out elapsedMs, out recoIdList, (MaaRectBuffer?)roi);
+
+ ///
+ ///
+ /// Wrapper of .
+ ///
+ public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, MaaRectBuffer? roi)
+ {
+ var roiHandle = roi?.Handle ?? MaaRectHandle.Zero;
+ MaaSize recoIdListSize = 0;
+
+ using var nodeNameBuffer = new MaaStringBuffer();
+ using var phaseBuffer = new MaaStringBuffer();
+
+ if (!MaaTaskerGetWaitFreezesDetail(Handle, wfId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out elapsedMs, null, ref recoIdListSize, roiHandle))
+ {
+ nodeName = string.Empty;
+ phase = string.Empty;
+ recoIdList = [];
+ return false;
+ }
+
+ nodeName = nodeNameBuffer.ToString();
+ phase = phaseBuffer.ToString();
+ recoIdList = recoIdListSize == 0 ? [] : new MaaRecoId[recoIdListSize];
+ return MaaTaskerGetWaitFreezesDetail(Handle, wfId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out elapsedMs, recoIdList, ref recoIdListSize, roiHandle);
+ }
+
///
///
/// Wrapper of .
diff --git a/src/MaaFramework.Binding.Native/MaaToolkit.cs b/src/MaaFramework.Binding.Native/MaaToolkit.cs
index 74c0c80..734c452 100644
--- a/src/MaaFramework.Binding.Native/MaaToolkit.cs
+++ b/src/MaaFramework.Binding.Native/MaaToolkit.cs
@@ -38,6 +38,9 @@ public MaaToolkit(bool init = false, string userPath = nameof(Environment.Curren
///
public IMaaToolkitDesktop Desktop { get; } = new DesktopClass();
+ ///
+ public IMaaToolkitMacOS MacOS { get; } = new MacOSClass();
+
///
protected internal class ConfigClass : IMaaToolkitConfig
{
@@ -101,4 +104,29 @@ public IMaaListBuffer Find()
return list;
}
}
+
+ ///
+ protected internal class MacOSClass : IMaaToolkitMacOS
+ {
+ ///
+ ///
+ /// Wrapper of .
+ ///
+ public bool CheckPermission(MacOSPermission permission)
+ => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSCheckPermission((MaaMacOSPermission)permission);
+
+ ///
+ ///
+ /// Wrapper of .
+ ///
+ public bool RequestPermission(MacOSPermission permission)
+ => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSRequestPermission((MaaMacOSPermission)permission);
+
+ ///
+ ///
+ /// Wrapper of .
+ ///
+ public bool RevealPermissionSettings(MacOSPermission permission)
+ => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSRevealPermissionSettings((MaaMacOSPermission)permission);
+ }
}
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
new file mode 100644
index 0000000..6687e16
--- /dev/null
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
@@ -0,0 +1,24 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable CS1573 // 参数在 XML 注释中没有匹配的 param 标记
+#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
+
+namespace MaaFramework.Binding;
+
+///
+/// macOS input method.
+///
+[System.Flags]
+public enum MacOSInputMethod : System.UInt64
+{
+ None = 0,
+ GlobalEvent = 1,
+ PostToPid = (1 << 1),
+}
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
new file mode 100644
index 0000000..6c6010c
--- /dev/null
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
@@ -0,0 +1,22 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable CS1573 // 参数在 XML 注释中没有匹配的 param 标记
+#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
+
+namespace MaaFramework.Binding;
+
+///
+/// macOS permission types.
+///
+public enum MacOSPermission : System.Int32
+{
+ ScreenCapture = 1,
+ Accessibility = 2,
+}
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
new file mode 100644
index 0000000..9764168
--- /dev/null
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
@@ -0,0 +1,22 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable CS1573 // 参数在 XML 注释中没有匹配的 param 标记
+#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
+
+namespace MaaFramework.Binding;
+
+///
+/// macOS screencap method.
+///
+public enum MacOSScreencapMethod : System.UInt64
+{
+ None = 0,
+ ScreenCaptureKit = 1,
+}
diff --git a/src/MaaFramework.Binding/IMaaController.cs b/src/MaaFramework.Binding/IMaaController.cs
index 0f11c6b..57ca357 100644
--- a/src/MaaFramework.Binding/IMaaController.cs
+++ b/src/MaaFramework.Binding/IMaaController.cs
@@ -120,6 +120,14 @@ public interface IMaaController : IMaaCommon, IMaaOption, IMaa
///
MaaJob TouchUp(int contact);
+ ///
+ /// Moves the mouse/pointer relative to its current position.
+ ///
+ /// The relative horizontal offset.
+ /// The relative vertical offset.
+ /// A relative move .
+ MaaJob RelativeMove(int dx, int dy);
+
///
/// Usage: KeyDown -> KeyUp.
/// For adb controller, is from android key event.
diff --git a/src/MaaFramework.Binding/IMaaTasker.cs b/src/MaaFramework.Binding/IMaaTasker.cs
index 4b282b7..fd03497 100644
--- a/src/MaaFramework.Binding/IMaaTasker.cs
+++ b/src/MaaFramework.Binding/IMaaTasker.cs
@@ -154,6 +154,19 @@ bool GetRecognitionDetail(MaaRecoId recognitionId, out string nodeName, out s
/// if query was successful; otherwise, .
bool GetActionDetail(MaaActId actionId, out string nodeName, out string action, IMaaRectBuffer? box, out bool isSucceeded, out string detailJson);
+ ///
+ /// Gets the wait freezes detail.
+ ///
+ /// The wait freezes id.
+ /// The node name.
+ /// The phase (e.g. "pre", "post", "repeat", "context").
+ /// A value indicating whether the wait freezes is succeeded.
+ /// The elapsed time in milliseconds.
+ /// The recognition id list.
+ /// The region of interest.
+ /// if query was successful; otherwise, .
+ bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi);
+
///
/// Gets the node detail.
///
diff --git a/src/MaaFramework.Binding/IMaaToolkit.cs b/src/MaaFramework.Binding/IMaaToolkit.cs
index 4ba0ecd..6130b85 100644
--- a/src/MaaFramework.Binding/IMaaToolkit.cs
+++ b/src/MaaFramework.Binding/IMaaToolkit.cs
@@ -22,6 +22,11 @@ public interface IMaaToolkit
/// Gets the MaaToolkit Desktop.
///
IMaaToolkitDesktop Desktop { get; }
+
+ ///
+ /// Gets the MaaToolkit macOS helper.
+ ///
+ IMaaToolkitMacOS MacOS { get; }
}
///
@@ -89,3 +94,30 @@ public interface IMaaToolkitDesktopWindow
///
IMaaListBuffer Find();
}
+
+///
+/// An interface defining wrapped members for MaaToolkit macOS.
+///
+public interface IMaaToolkitMacOS
+{
+ ///
+ /// Checks if the specified macOS permission is granted.
+ ///
+ /// The permission to check.
+ /// if the permission is granted; otherwise, .
+ bool CheckPermission(MacOSPermission permission);
+
+ ///
+ /// Requests the specified macOS permission.
+ ///
+ /// The permission to request.
+ /// if the permission was granted; otherwise, .
+ bool RequestPermission(MacOSPermission permission);
+
+ ///
+ /// Reveals the system permission settings for the specified macOS permission.
+ ///
+ /// The permission to reveal settings for.
+ /// if the settings were revealed successfully; otherwise, .
+ bool RevealPermissionSettings(MacOSPermission permission);
+}
diff --git a/src/MaaFramework.Binding/MaaDef.cs b/src/MaaFramework.Binding/MaaDef.cs
index 3883e8b..f6e38b4 100644
--- a/src/MaaFramework.Binding/MaaDef.cs
+++ b/src/MaaFramework.Binding/MaaDef.cs
@@ -4,6 +4,7 @@
global using MaaNodeId = System.Int64;
global using MaaRecoId = System.Int64;
global using MaaActId = System.Int64;
+global using MaaWfId = System.Int64;
global using MaaSize = System.UInt64;
using System.Diagnostics.CodeAnalysis;
diff --git a/src/MaaFramework.Binding/MaaMsg.cs b/src/MaaFramework.Binding/MaaMsg.cs
index d1af735..824bbe1 100644
--- a/src/MaaFramework.Binding/MaaMsg.cs
+++ b/src/MaaFramework.Binding/MaaMsg.cs
@@ -346,5 +346,40 @@ public static class Action
public const string Failed = "Node.Action.Failed";
}
+ public static class WaitFreezes
+ {
+ ///
+ /// Message for wait freezes.
+ ///
+ ///
+ /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
+ ///
+ public const string Starting = "Node.WaitFreezes.Starting";
+
+ ///
+ /// Message for wait freezes.
+ ///
+ ///
+ /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
+ ///
+ public const string Prefix = "Node.WaitFreezes";
+
+ ///
+ /// Message for wait freezes.
+ ///
+ ///
+ /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
+ ///
+ public const string Succeeded = "Node.WaitFreezes.Succeeded";
+
+ ///
+ /// Message for wait freezes.
+ ///
+ ///
+ /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
+ ///
+ public const string Failed = "Node.WaitFreezes.Failed";
+
+ }
}
}
From 30cba442086603adccb956d24174d2804f4fc110 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Sat, 4 Jul 2026 17:33:26 +0800
Subject: [PATCH 02/15] feat(controller): add
MaaControllerPostMouseMoveRelative (#1189)
---
docs/articles/overview-of-wrapper-and-api.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/articles/overview-of-wrapper-and-api.md b/docs/articles/overview-of-wrapper-and-api.md
index bb65979..6ece007 100644
--- a/docs/articles/overview-of-wrapper-and-api.md
+++ b/docs/articles/overview-of-wrapper-and-api.md
@@ -104,6 +104,7 @@ IMaaDisposable Derived:
| IMaaController.TouchDown() | `MaaControllerPostTouchDown` |
| IMaaController.TouchMove() | `MaaControllerPostTouchMove` |
| IMaaController.TouchUp() | `MaaControllerPostTouchUp` |
+| IMaaController.RelativeMove() | `MaaControllerPostRelativeMove` |
| IMaaController.Screencap() | `MaaControllerPostScreencap` |
| IMaaController.Scroll() | `MaaControllerPostScroll` |
| IMaaController.Inactive() | `MaaControllerPostInactive` |
From cfb6610b6e1fd1223356b57a60c9d8768e8da2cf Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Sat, 4 Jul 2026 18:36:57 +0800
Subject: [PATCH 03/15] =?UTF-8?q?feat(win32):=20=E5=BA=9F=E5=BC=83=20`Post?=
=?UTF-8?q?ThreadMessage`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
index 33cbfb5..22b8571 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
@@ -43,6 +43,7 @@ public enum Win32InputMethod : System.UInt64
SendMessage = (1 << 1),
PostMessage = (1 << 2),
LegacyEvent = (1 << 3),
+ [Obsolete("Deprecated from MaaFramework v5.9.0.")]
PostThreadMessage = (1 << 4),
SendMessageWithCursorPos = (1 << 5),
PostMessageWithCursorPos = (1 << 6),
From dd056005d21d610020b35c1b44462ca3b0e828ce Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Sat, 4 Jul 2026 18:44:33 +0800
Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=E5=AE=8C=E6=95=B4=E7=9A=84=20mac?=
=?UTF-8?q?OS=20=E5=8E=9F=E7=94=9F=E6=88=AA=E5=9B=BE=E5=92=8C=E6=8E=A7?=
=?UTF-8?q?=E5=88=B6=E4=BB=A5=E5=8F=8A=20MaaToolkit=20=E7=9A=84=20DesktopW?=
=?UTF-8?q?indow=20=E6=94=AF=E6=8C=81=20(#1116)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/articles/overview-of-wrapper-and-api.md | 3 +++
.../Interop/Framework/Instance/MaaController.cs | 7 +++++++
.../MaaController/MaaMacOSController.cs | 4 ++--
.../Enums/Controllers/MacOSInputMethod.cs | 12 ++++++++++--
.../Enums/Controllers/MacOSScreencapMethod.cs | 10 +++++++++-
5 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/docs/articles/overview-of-wrapper-and-api.md b/docs/articles/overview-of-wrapper-and-api.md
index 6ece007..e317de4 100644
--- a/docs/articles/overview-of-wrapper-and-api.md
+++ b/docs/articles/overview-of-wrapper-and-api.md
@@ -26,6 +26,8 @@
| AdbInputMethods | `MaaAdbInputMethod` |
| Win32ScreencapMethod | `MaaWin32ScreencapMethod` |
| Win32InputMethod | `MaaWin32InputMethod` |
+| MacOSScreencapMethod | `MaaMacOSScreencapMethod` |
+| MacOSInputMethod | `MaaMacOSInputMethod` |
| DbgControllerType | `MaaDbgControllerType` |
| GamepadType | `MaaGamepadType` |
| GamepadButton | `MaaGamepadButton` |
@@ -87,6 +89,7 @@ IMaaDisposable Derived:
| --- | --- |
| MaaAdbController.ctor() | `MaaAdbControllerCreate`
`MaaControllerAddSink` |
| MaaWin32Controller.ctor() | `MaaWin32ControllerCreate`
`MaaControllerAddSink` |
+| MaaMacOSController.ctor() | `MaaMacOSControllerCreate`
`MaaControllerAddSink` |
| MaaCustomController.ctor() | `MaaCustomControllerCreate`
`MaaControllerAddSink` |
| MaaDbgController.ctor() | `MaaDbgControllerCreate`
`MaaControllerAddSink` |
| MaaPlayCoverController.ctor() | `MaaPlayCoverControllerCreate`
`MaaControllerAddSink` |
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index 23090fd..21aa2f5 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -43,6 +43,13 @@ public static partial class MaaController
/// macOS screencap method to use.
/// macOS input method to use.
/// The controller handle, or nint.Zero on failure.
+ ///
+ ///
This controller is designed for native macOS applications.
+ ///
Requires Screen Recording permission for screencap.
+ ///
Input simulation requires Accessibility permission.
+ ///
Some features are not supported: start_app, stop_app, scroll.
+ ///
Only single touch is supported (contact must be 0).
+ ///
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaMacOSControllerCreate(uint windowId, MaaMacOSScreencapMethod screencapMethod, MaaMacOSInputMethod inputMethod);
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
index 0ecc125..09e147c 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaMacOSController.cs
@@ -24,8 +24,8 @@ public class MaaMacOSController : MaaController
/// Creates a instance.
///
/// The CGWindowID of the target window (0 for desktop).
- /// macOS screencap method to use.
- /// macOS input method to use.
+ /// The macOS screencap method.
+ /// The macOS input method.
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
index 6687e16..7e419aa 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSInputMethod.cs
@@ -13,9 +13,17 @@
namespace MaaFramework.Binding;
///
-/// macOS input method.
+/// MacOS input method.
+///
+/// Select ONE method only.
+///
+/// | Method | Description |
+///
|-----------------|--------------------------------------------------------------------------------------------------------------------|
+///
| GlobalEvent | Injects into the global HID event stream via CGEventPost(kCGHIDEventTap), dispatched by the OS to the front window |
+///
| PostToPid | Directly send to target process using CGEventPostToPid |
+///
+///
///
-[System.Flags]
public enum MacOSInputMethod : System.UInt64
{
None = 0,
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
index 9764168..ff02e26 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSScreencapMethod.cs
@@ -13,7 +13,15 @@
namespace MaaFramework.Binding;
///
-/// macOS screencap method.
+/// MacOS screencap method.
+///
+/// Select ONE method only.
+///
+/// | Method | Description |
+///
|-----------------|------------------------------------------------|
+///
| ScreenCaptureKit| Modern macOS screencap using ScreenCaptureKit |
+///
+///
///
public enum MacOSScreencapMethod : System.UInt64
{
From 335c2c8693160a4d52d0ba667665bbcb9dcb5df4 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Sat, 4 Jul 2026 22:23:12 +0800
Subject: [PATCH 05/15] feat: macos utils (#1214)
---
docs/articles/overview-of-wrapper-and-api.md | 4 ++++
src/MaaFramework.Binding.Native/MaaToolkit.cs | 13 +++++++------
.../Enums/Controllers/MacOSPermission.cs | 2 +-
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/docs/articles/overview-of-wrapper-and-api.md b/docs/articles/overview-of-wrapper-and-api.md
index e317de4..30f6282 100644
--- a/docs/articles/overview-of-wrapper-and-api.md
+++ b/docs/articles/overview-of-wrapper-and-api.md
@@ -28,6 +28,7 @@
| Win32InputMethod | `MaaWin32InputMethod` |
| MacOSScreencapMethod | `MaaMacOSScreencapMethod` |
| MacOSInputMethod | `MaaMacOSInputMethod` |
+| MacOSPermission | `MaaMacOSPermissionEnum` |
| DbgControllerType | `MaaDbgControllerType` |
| GamepadType | `MaaGamepadType` |
| GamepadButton | `MaaGamepadButton` |
@@ -316,6 +317,9 @@ IMaaDisposable Derived:
| MaaToolkit.ctor()
IMaaToolkit.Config.InitOption() | `MaaToolkitConfigInitOption` |
| IMaaToolkit.AdbDevice.Find()
IMaaToolkit.AdbDevice.FindAsync() | `MaaToolkitAdbDeviceFind`
`MaaToolkitAdbDeviceFindSpecified` |
| IMaaToolkit.Desktop.Window.Find() | `MaaToolkitDesktopWindowFindAll` |
+| IMaaToolkit.MacOS.CheckPermission() | `MaaToolkitMacOSCheckPermission` |
+| IMaaToolkit.MacOS.RequestPermission() | `MaaToolkitMacOSRequestPermission` |
+| IMaaToolkit.MacOS.RevealPermissionSettings() | `MaaToolkitMacOSRevealPermissionSettings` |
## Buffers.AdbDeviceListBuffer : Buffers.MaaListBuffer : Buffers.IMaaListBuffer
diff --git a/src/MaaFramework.Binding.Native/MaaToolkit.cs b/src/MaaFramework.Binding.Native/MaaToolkit.cs
index 734c452..65c0448 100644
--- a/src/MaaFramework.Binding.Native/MaaToolkit.cs
+++ b/src/MaaFramework.Binding.Native/MaaToolkit.cs
@@ -2,6 +2,7 @@
using MaaFramework.Binding.Interop.Native;
using System.Diagnostics.CodeAnalysis;
using static MaaFramework.Binding.Interop.Native.MaaToolkit;
+using static MaaFramework.Binding.Interop.Native.MaaToolkitMacOS;
namespace MaaFramework.Binding;
@@ -110,23 +111,23 @@ protected internal class MacOSClass : IMaaToolkitMacOS
{
///
///
- /// Wrapper of .
+ /// Wrapper of .
///
public bool CheckPermission(MacOSPermission permission)
- => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSCheckPermission((MaaMacOSPermission)permission);
+ => MaaToolkitMacOSCheckPermission((MaaMacOSPermission)permission);
///
///
- /// Wrapper of .
+ /// Wrapper of .
///
public bool RequestPermission(MacOSPermission permission)
- => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSRequestPermission((MaaMacOSPermission)permission);
+ => MaaToolkitMacOSRequestPermission((MaaMacOSPermission)permission);
///
///
- /// Wrapper of .
+ /// Wrapper of .
///
public bool RevealPermissionSettings(MacOSPermission permission)
- => Interop.Native.MaaToolkitMacOS.MaaToolkitMacOSRevealPermissionSettings((MaaMacOSPermission)permission);
+ => MaaToolkitMacOSRevealPermissionSettings((MaaMacOSPermission)permission);
}
}
diff --git a/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs b/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
index 6c6010c..7d48bf1 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/MacOSPermission.cs
@@ -13,7 +13,7 @@
namespace MaaFramework.Binding;
///
-/// macOS permission types.
+/// MacOS permission types.
///
public enum MacOSPermission : System.Int32
{
From b07824d070d4793ec9267693fa5ffd087ab9ea4d Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Sat, 4 Jul 2026 22:35:05 +0800
Subject: [PATCH 06/15] feat: Record & Replay Controller (#1146)
---
docs/articles/overview-of-wrapper-and-api.md | 3 +-
.../Framework/Instance/MaaController.cs | 43 +++++++++++++------
.../Framework/Instance/MaaCustomController.cs | 15 +++++++
.../Interop/Framework/MaaDef.cs | 2 -
.../MaaAgentClient.cs | 12 +++---
.../MaaController.cs | 4 +-
.../MaaController/MaaDbgController.cs | 10 +++--
.../MaaController/MaaRecordController.cs | 18 +++++---
.../MaaController/MaaReplayController.cs | 8 ++--
.../Test_Custom.cs | 7 +++
.../Test_IMaaController.cs | 4 +-
.../Custom/IMaaCustomController.cs | 6 +++
src/MaaFramework.Binding/IMaaController.cs | 6 +--
13 files changed, 96 insertions(+), 42 deletions(-)
diff --git a/docs/articles/overview-of-wrapper-and-api.md b/docs/articles/overview-of-wrapper-and-api.md
index 30f6282..a11201b 100644
--- a/docs/articles/overview-of-wrapper-and-api.md
+++ b/docs/articles/overview-of-wrapper-and-api.md
@@ -29,7 +29,6 @@
| MacOSScreencapMethod | `MaaMacOSScreencapMethod` |
| MacOSInputMethod | `MaaMacOSInputMethod` |
| MacOSPermission | `MaaMacOSPermissionEnum` |
-| DbgControllerType | `MaaDbgControllerType` |
| GamepadType | `MaaGamepadType` |
| GamepadButton | `MaaGamepadButton` |
| GamepadTouch | `MaaGamepadTouch` |
@@ -93,6 +92,8 @@ IMaaDisposable Derived:
| MaaMacOSController.ctor() | `MaaMacOSControllerCreate`
`MaaControllerAddSink` |
| MaaCustomController.ctor() | `MaaCustomControllerCreate`
`MaaControllerAddSink` |
| MaaDbgController.ctor() | `MaaDbgControllerCreate`
`MaaControllerAddSink` |
+| MaaReplayController.ctor() | `MaaReplayControllerCreate`
`MaaControllerAddSink` |
+| MaaRecordController.ctor() | `MaaRecordControllerCreate`
`MaaControllerAddSink` |
| MaaPlayCoverController.ctor() | `MaaPlayCoverControllerCreate`
`MaaControllerAddSink` |
| MaaGamepadController.ctor() | `MaaGamepadControllerCreate`
`MaaControllerAddSink` |
| MaaWlRootsController.ctor() | `MaaWlRootsControllerCreate`
`MaaControllerAddSink` |
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index 21aa2f5..1d268f7 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -25,17 +25,6 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaWin32ControllerCreate(nint hWnd, MaaWin32ScreencapMethod screencapMethod, MaaWin32InputMethod mouseMethod, MaaWin32InputMethod keyboardMethod);
- [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
- public static partial MaaControllerHandle MaaCustomControllerCreate([MarshalUsing(typeof(MaaMarshaller))] Custom.IMaaCustomController controller, nint controllerArg);
-
- ///
- /// Create a debug controller that serves images from a directory.
- ///
- /// Path to a directory of images (or a single image file).
- /// The controller handle, or nint.Zero on failure.
- [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
- public static partial MaaControllerHandle MaaDbgControllerCreate(string readPath);
-
///
/// Create a macOS controller for native macOS applications.
///
@@ -61,10 +50,28 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaAndroidNativeControllerCreate(string configJson);
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaCustomControllerCreate([MarshalUsing(typeof(MaaMarshaller))] Custom.IMaaCustomController controller, nint controllerArg);
+
+ ///
+ /// Create a debug controller that serves images from a directory.
+ ///
+ ///
+ /// Path to a directory of images (or a single image file).
+ /// Images are loaded on connect and cycled through on each screencap request.
+ /// All input operations (click, swipe, etc.) are no-ops that return success.
+ ///
+ /// The controller handle, or nint.Zero on failure.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaDbgControllerCreate(string readPath);
+
///
/// Create a replay controller that replays recorded operations.
///
- /// Path to the recording JSONL file.
+ ///
+ /// Path to the recording JSONL file written by MaaRecordControllerCreate.
+ /// Screenshot image paths in the file are resolved relative to this file's parent directory.
+ ///
/// The controller handle, or nint.Zero on failure.
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaReplayControllerCreate(string recordingPath);
@@ -72,8 +79,16 @@ public static partial class MaaController
///
/// Create a record controller that wraps an existing controller and records all operations.
///
- /// The inner controller to forward all operations to.
- /// Path to the recording JSONL file to write.
+ ///
+ /// The inner controller to forward all operations to. Must not be null.
+ /// The record controller does NOT take ownership of the inner controller.
+ ///
+ ///
+ /// Path to the recording JSONL file to write.
+ /// Screenshot images will be saved to a "{stem}-Screenshot" folder
+ /// in the same directory as this file.
+ /// The recorded file can be replayed using MaaReplayControllerCreate.
+ ///
/// The record controller handle, or nint.Zero on failure.
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaRecordControllerCreate(MaaControllerHandle inner, string recordingPath);
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaCustomController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaCustomController.cs
index db9981e..8b4c3c9 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaCustomController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaCustomController.cs
@@ -110,6 +110,8 @@ private sealed class Delegates(IMaaCustomController managed)
public KeyDownDelegate KeyDown = (int keycode, nint transArg) => managed.KeyDown(keycode);
public KeyUpDelegate KeyUp = (int keycode, nint transArg) => managed.KeyUp(keycode);
public ScrollDelegate Scroll = (int dx, int dy, nint transArg) => managed.Scroll(dx, dy);
+ public RelativeMoveDelegate RelativeMove = (int dx, int dy, nint transArg) => managed.RelativeMove(dx, dy);
+ public ShellDelegate Shell = (string cmd, long timeout, nint transArg, MaaStringBufferHandle buffer) => managed.Shell(cmd, timeout, new MaaStringBuffer(buffer));
public InactiveDelegate Inactive = (nint transArg) => managed.Inactive();
public GetInfoDelegate GetInfo = (nint transArg, MaaStringBufferHandle buffer) => managed.GetInfo(new MaaStringBuffer(buffer));
};
@@ -141,6 +143,8 @@ private sealed class Unmanaged(Delegates delegates)
public nint KeyDown = Marshal.GetFunctionPointerForDelegate(delegates.KeyDown);
public nint KeyUp = Marshal.GetFunctionPointerForDelegate(delegates.KeyUp);
public nint Scroll = Marshal.GetFunctionPointerForDelegate(delegates.Scroll);
+ public nint RelativeMove = Marshal.GetFunctionPointerForDelegate(delegates.RelativeMove);
+ public nint Shell = Marshal.GetFunctionPointerForDelegate(delegates.Shell);
public nint Inactive = Marshal.GetFunctionPointerForDelegate(delegates.Inactive);
public nint GetInfo = Marshal.GetFunctionPointerForDelegate(delegates.GetInfo);
}
@@ -218,6 +222,17 @@ private sealed class Unmanaged(Delegates delegates)
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool ScrollDelegate(int dx, int dy, nint transArg);
+ [return: MarshalAs(UnmanagedType.U1)]
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate bool RelativeMoveDelegate(int dx, int dy, nint transArg);
+
+ ///
+ /// Write result to buffer.
+ ///
+ [return: MarshalAs(UnmanagedType.U1)]
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate bool ShellDelegate(string cmd, long timeout, nint transArg, MaaStringBufferHandle buffer);
+
[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool InactiveDelegate(nint transArg);
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
index 0d8fadf..54818ba 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
@@ -47,8 +47,6 @@
global using MaaWin32ScreencapMethod = System.UInt64;
// No bitwise OR, just set it
global using MaaWin32InputMethod = System.UInt64;
-// No bitwise OR, just set it
-global using MaaDbgControllerType = System.UInt64;
global using MaaControllerFeature = System.UInt64;
// macOS controller types
global using MaaMacOSScreencapMethod = System.UInt64;
diff --git a/src/MaaFramework.Binding.Native/MaaAgentClient.cs b/src/MaaFramework.Binding.Native/MaaAgentClient.cs
index 878272e..c6ab526 100644
--- a/src/MaaFramework.Binding.Native/MaaAgentClient.cs
+++ b/src/MaaFramework.Binding.Native/MaaAgentClient.cs
@@ -13,14 +13,14 @@ namespace MaaFramework.Binding;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class MaaAgentClient : MaaDisposableHandle, IMaaAgentClient
{
- private long _timeout = -1;
+ private long _millisecondsTimeout = -1;
private Process? _agentServerProcess;
[ExcludeFromCodeCoverage(Justification = "Debugger display.")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => IsInvalid
? $"Invalid {GetType().Name}"
- : $"{GetType().Name} {{ {nameof(Id)} = {Id ?? ""}, {nameof(IsConnected)} = {IsConnected}, {nameof(IsAlive)} = {IsAlive}, Timeout = {_timeout} }}";
+ : $"{GetType().Name} {{ {nameof(Id)} = {Id ?? ""}, {nameof(IsConnected)} = {IsConnected}, {nameof(IsAlive)} = {IsAlive}, Timeout = {_millisecondsTimeout} }}";
internal sealed class NullAgentClient : MaaAgentClient { internal NullAgentClient() : base(MaaAgentClientHandle.Zero) { } }
@@ -387,8 +387,8 @@ public bool LinkStop()
///
public bool SetTimeout(long millisecondsDelay)
{
- _timeout = millisecondsDelay;
- return MaaAgentClientSetTimeout(Handle, _timeout);
+ _millisecondsTimeout = millisecondsDelay;
+ return MaaAgentClientSetTimeout(Handle, _millisecondsTimeout);
}
///
@@ -439,7 +439,7 @@ public bool CancelWith(CancellationToken cancellationToken, Func? waitFunc
finally
{
ctr.Dispose();
- _ = SetTimeout(_timeout).ThrowIfFalse();
+ _ = SetTimeout(_millisecondsTimeout).ThrowIfFalse();
}
}
@@ -459,7 +459,7 @@ public bool Cancel(Func? waitFunc = null, Task? waitTask = null, Maa
}
finally
{
- _ = SetTimeout(_timeout).ThrowIfFalse();
+ _ = SetTimeout(_millisecondsTimeout).ThrowIfFalse();
}
}
diff --git a/src/MaaFramework.Binding.Native/MaaController.cs b/src/MaaFramework.Binding.Native/MaaController.cs
index 752e6ce..8a6abe9 100644
--- a/src/MaaFramework.Binding.Native/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController.cs
@@ -214,8 +214,8 @@ public MaaJob Inactive()
///
/// Wrapper of .
///
- public MaaJob Shell(string cmd, long timeout = 20000)
- => CreateJob(MaaControllerPostShell(Handle, cmd, timeout));
+ public MaaJob Shell(string cmd, long millisecondsTimeout = 20000)
+ => CreateJob(MaaControllerPostShell(Handle, cmd, millisecondsTimeout));
///
///
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
index 41e91a2..1a074df 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaDbgController.cs
@@ -19,15 +19,17 @@ public class MaaDbgController : MaaController
: $"{GetType().Name} {{ ReadFrom = {_debugReadPath} }}";
///
- /// Creates a instance.
+ /// Creates a instance that serves images from a directory.
///
- /// Path to a directory of images (or a single image file).
+ ///
+ /// Path to a directory of images (or a single image file).
+ /// Images are loaded on connect and cycled through on each screencap request.
+ /// All input operations (click, swipe, etc.) are no-ops that return success.
+ ///
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
/// Wrapper of .
- /// Images are loaded on connect and cycled through on each screencap request.
- /// All input operations (click, swipe, etc.) are no-ops that return success.
///
///
///
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
index 9e38ecd..d6eac6d 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaRecordController.cs
@@ -10,7 +10,10 @@ namespace MaaFramework.Binding;
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class MaaRecordController : MaaController
{
+#pragma warning disable // 删除未读的私有成员
+ private readonly MaaController _debugInner;
private readonly string _debugRecordingPath;
+#pragma warning restore // 删除未读的私有成员
[ExcludeFromCodeCoverage(Justification = "Debugger display.")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
@@ -21,15 +24,19 @@ public class MaaRecordController : MaaController
///
/// Creates a instance.
///
- /// The inner controller to forward all operations to. Must not be null.
- /// Path to the recording JSONL file to write.
+ ///
+ /// The inner controller to forward all operations to. Must not be null.
+ /// The record controller does NOT take ownership of the inner controller.
+ ///
+ ///
+ /// Path to the recording JSONL file to write.
+ /// Screenshot images will be saved to a "{stem}-Screenshot" folder in the same directory as this file.
+ /// The recorded file can be replayed using .
+ ///
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
/// Wrapper of .
- /// The record controller does NOT take ownership of the inner controller.
- /// Screenshot images will be saved to a "{stem}-Screenshot" folder in the same directory as this file.
- /// The recorded file can be replayed using .
///
///
///
@@ -43,6 +50,7 @@ public MaaRecordController(MaaController inner, string recordingPath, LinkOption
_ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
SetHandle(handle, needReleased: true);
+ _debugInner = inner;
_debugRecordingPath = recordingPath;
if (link == LinkOption.Start)
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
index 4755ed7..86aa213 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaReplayController.cs
@@ -19,14 +19,16 @@ public class MaaReplayController : MaaController
: $"{GetType().Name} {{ RecordingPath = {_debugRecordingPath} }}";
///
- /// Creates a instance.
+ /// Creates a instance that replays recorded operations.
///
- /// Path to the recording JSONL file written by .
+ ///
+ /// Path to the recording JSONL file written by .
+ /// Screenshot image paths in the file are resolved relative to this file's parent directory.
+ ///
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
/// Wrapper of .
- /// Screenshot image paths in the file are resolved relative to this file's parent directory.
///
///
///
diff --git a/src/MaaFramework.Binding.UnitTests/Test_Custom.cs b/src/MaaFramework.Binding.UnitTests/Test_Custom.cs
index fa9569e..2cc9296 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_Custom.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_Custom.cs
@@ -301,6 +301,13 @@ public bool KeyUp(int keycode)
=> c.KeyUp(keycode).Wait().IsSucceeded();
public bool Scroll(int dx, int dy)
=> c.Scroll(dx, dy).Wait().IsSucceeded();
+
+ public bool RelativeMove(int dx, int dy)
+ => c.RelativeMove(dx, dy).Wait().IsSucceeded();
+
+ public bool Shell(string cmd, long millisecondsTimeout, IMaaStringBuffer buffer)
+ => c.Shell(cmd, millisecondsTimeout).Wait().IsSucceeded() && c.GetShellOutput(out _);
+
public bool Inactive()
=> c.Inactive().Wait().IsSucceeded();
public bool GetInfo(IMaaStringBuffer buffer)
diff --git a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
index 32171cd..fcd972d 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
@@ -365,11 +365,11 @@ public void Interface_Uuid(MaaTypes type, IMaaController maaController)
[TestMethod]
[MaaData(MaaTypes.All, nameof(Data), "echo hello", 20000)]
- public void Interface_Shell_ShellOutput(MaaTypes type, IMaaController maaController, string cmd, long timeout)
+ public void Interface_Shell_ShellOutput(MaaTypes type, IMaaController maaController, string cmd, long millisecondsTimeout)
{
Assert.IsNotNull(maaController);
- var job = maaController.Shell(cmd, timeout);
+ var job = maaController.Shell(cmd, millisecondsTimeout);
if (maaController is MaaAdbController)
{
Interface_IMaaPost_Success(job);
diff --git a/src/MaaFramework.Binding/Custom/IMaaCustomController.cs b/src/MaaFramework.Binding/Custom/IMaaCustomController.cs
index 1948fee..c58e27a 100644
--- a/src/MaaFramework.Binding/Custom/IMaaCustomController.cs
+++ b/src/MaaFramework.Binding/Custom/IMaaCustomController.cs
@@ -35,6 +35,12 @@ public interface IMaaCustomController : IMaaCustom, IDisposable
bool KeyDown(int keycode);
bool KeyUp(int keycode);
bool Scroll(int dx, int dy);
+ bool RelativeMove(int dx, int dy);
+
+ ///
+ /// Write result to buffer.
+ ///
+ bool Shell(string cmd, long millisecondsTimeout, IMaaStringBuffer buffer);
bool Inactive();
///
diff --git a/src/MaaFramework.Binding/IMaaController.cs b/src/MaaFramework.Binding/IMaaController.cs
index 57ca357..3cbed50 100644
--- a/src/MaaFramework.Binding/IMaaController.cs
+++ b/src/MaaFramework.Binding/IMaaController.cs
@@ -177,13 +177,13 @@ public interface IMaaController : IMaaCommon, IMaaOption, IMaa
/// Appends a job for shell command action.
///
/// The shell command to execute.
- /// The timeout in milliseconds. Default is 20000 (20 seconds).
+ /// The timeout in milliseconds. Default is 20000 (20 seconds).
/// A shell .
///
- /// This is only valid for ADB controllers. If the controller is not an ADB controller, the action will fail.
+ /// Supported by ADB controllers and custom controllers that implement the shell callback.
/// See also .
///
- MaaJob Shell(string cmd, long timeout = 20000);
+ MaaJob Shell(string cmd, long millisecondsTimeout = 20000);
///
/// Gets the cached shell command output.
From 2d85bf7899843afa888efe8f80905fc979ee8f76 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Mon, 6 Jul 2026 16:38:39 +0800
Subject: [PATCH 07/15] feat: android native support (#1222)
---
docs/articles/overview-of-wrapper-and-api.md | 1 +
.../Interop/Framework/Instance/MaaController.cs | 13 ++++++++++++-
.../MaaController/MaaAndroidNativeController.cs | 11 ++++++++++-
3 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/docs/articles/overview-of-wrapper-and-api.md b/docs/articles/overview-of-wrapper-and-api.md
index a11201b..c1954aa 100644
--- a/docs/articles/overview-of-wrapper-and-api.md
+++ b/docs/articles/overview-of-wrapper-and-api.md
@@ -90,6 +90,7 @@ IMaaDisposable Derived:
| MaaAdbController.ctor() | `MaaAdbControllerCreate`
`MaaControllerAddSink` |
| MaaWin32Controller.ctor() | `MaaWin32ControllerCreate`
`MaaControllerAddSink` |
| MaaMacOSController.ctor() | `MaaMacOSControllerCreate`
`MaaControllerAddSink` |
+| MaaAndroidNativeController.ctor() | `MaaAndroidNativeControllerCreate`
`MaaControllerAddSink` |
| MaaCustomController.ctor() | `MaaCustomControllerCreate`
`MaaControllerAddSink` |
| MaaDbgController.ctor() | `MaaDbgControllerCreate`
`MaaControllerAddSink` |
| MaaReplayController.ctor() | `MaaReplayControllerCreate`
`MaaControllerAddSink` |
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index 1d268f7..f1454d4 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -45,8 +45,19 @@ public static partial class MaaController
///
/// Create an Android native controller.
///
- /// JSON config for the control unit.
+ ///
+ /// JSON config for the control unit. Required fields:
+ /// - library_path: path to the Android native control unit library
+ /// - screen_resolution.width / screen_resolution.height: raw screenshot and touch resolution
+ /// Optional fields:
+ /// - display_id: target display id, defaults to 0
+ /// - force_stop: whether to force stop before start_app, defaults to false
+ ///
/// The controller handle, or nint.Zero on failure.
+ ///
+ /// This controller is only available on Android.
+ /// The configured screen_resolution must match the control unit's raw screenshot/touch coordinate space.
+ ///
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaAndroidNativeControllerCreate(string configJson);
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
index 728d087..e437ecd 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaAndroidNativeController.cs
@@ -21,12 +21,21 @@ public class MaaAndroidNativeController : MaaController
///
/// Creates a instance.
///
- /// JSON config for the control unit.
+ ///
+ /// JSON config for the control unit.
+ /// Required fields:
+ ///
- library_path: path to the Android native control unit library
+ ///
- screen_resolution.width / screen_resolution.height: raw screenshot and touch resolution
+ /// Optional fields:
+ ///
- display_id: target display id, defaults to 0
+ ///
- force_stop: whether to force stop before start_app, defaults to false
+ ///
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
/// Wrapper of .
/// This controller is only available on Android.
+ /// The configured screen_resolution must match the control unit's raw screenshot/touch coordinate space.
///
///
///
From 2503d2cae93f048c795c4c695797cb7691e1d737 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Mon, 6 Jul 2026 17:03:21 +0800
Subject: [PATCH 08/15] =?UTF-8?q?feat(win32):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E9=BC=A0=E6=A0=87=E9=94=81=E5=AE=9A=E8=B7=9F=E9=9A=8F=E6=A8=A1?=
=?UTF-8?q?=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81=E5=90=8E=E5=8F=B0=20TPS/FP?=
=?UTF-8?q?S=20=E8=A7=86=E8=A7=92=E6=8E=A7=E5=88=B6=20(#1232)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/MaaFramework.Binding.Native/MaaController.cs | 3 ++-
.../Test_IMaaController.cs | 2 ++
.../Enums/Options/ControllerOption.cs | 10 ++++++++++
.../Extensions/MaaOptionExtensions.cs | 5 +++++
4 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/MaaFramework.Binding.Native/MaaController.cs b/src/MaaFramework.Binding.Native/MaaController.cs
index 8a6abe9..804fd75 100644
--- a/src/MaaFramework.Binding.Native/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController.cs
@@ -82,7 +82,8 @@ public bool SetOption(ControllerOption opt, T value)
{
(int vvvv, ControllerOption.ScreenshotTargetLongSide
or ControllerOption.ScreenshotTargetShortSide) => vvvv.ToMaaOptionValue(),
- (bool vvv, ControllerOption.ScreenshotUseRawSize) => vvv.ToMaaOptionValue(),
+ (bool vvv, ControllerOption.ScreenshotUseRawSize
+ or ControllerOption.MouseLockFollow) => vvv.ToMaaOptionValue(),
_ => throw new NotSupportedException($"'{nameof(ControllerOption)}.{opt}' or type '{typeof(T)}' is not supported."),
};
diff --git a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
index fcd972d..4743a56 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
@@ -144,6 +144,7 @@ public void CreateInstances()
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetLongSide, 1280)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetShortSide, 720)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, false)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, false)]
public void Interface_SetOption(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
@@ -400,6 +401,7 @@ public void Interface_Shell_ShellOutput(MaaTypes type, IMaaController maaControl
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetLongSide, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetShortSide, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, 0.0)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, 0.0)]
public void Interface_SetOption_InvalidData(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
diff --git a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
index 20a0fea..2232c5c 100644
--- a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
+++ b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
@@ -39,5 +39,15 @@ public enum ControllerOption : System.Int32
/// value: bool, eg: true; val_size: sizeof(bool)
///
ScreenshotUseRawSize = 3,
+
+ ///
+ /// Enable or disable mouse-lock-follow mode for Win32 controllers.
+ ///
+ ///
+ /// This is designed for TPS/FPS games that lock the mouse to their window in the background.
+ /// Only valid for Win32 controllers using message-based input methods.
+ /// value: bool, eg: true; val_size: sizeof(bool)
+ ///
+ MouseLockFollow = 4,
}
diff --git a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
index 28e4d28..b7d1aa6 100644
--- a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
+++ b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
@@ -25,6 +25,11 @@ public static bool SetOption_ScreenshotTargetShortSide(this IMaaOption? opt, bool value)
=> opt?.SetOption(ControllerOption.ScreenshotUseRawSize, value) ?? throw new ArgumentNullException(nameof(opt));
+ ///
+ ///
+ public static bool SetOption_MouseLockFollow(this IMaaOption? opt, bool value)
+ => opt?.SetOption(ControllerOption.MouseLockFollow, value) ?? throw new ArgumentNullException(nameof(opt));
+
///
///
public static bool SetOption_LogDir(this IMaaOption? opt, string value)
From 89d64c7225626404242d03c7791c9562a2445abc Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Mon, 6 Jul 2026 17:17:10 +0800
Subject: [PATCH 09/15] feat: add optional anchor info for reco sink (#1187)
---
.../Notification/NotificationDetail.cs | 13 +++++++------
src/MaaFramework.Binding/MaaMsg.cs | 8 ++++----
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
index eee2e60..5b2b491 100644
--- a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
+++ b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
@@ -33,7 +33,7 @@ public record PipelineNodeDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("node_id")] int NodeId,
[property: JsonPropertyName("name")] string Name,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus
);
///
@@ -41,7 +41,7 @@ public record RecognitionNodeDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("node_id")] int NodeId,
[property: JsonPropertyName("name")] string Name,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus
);
///
@@ -49,7 +49,7 @@ public record ActionNodeDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("node_id")] int NodeId,
[property: JsonPropertyName("name")] string Name,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus
);
///
@@ -66,7 +66,7 @@ public record NodeNextListDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("list")] IReadOnlyList NextList,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus
);
///
@@ -74,7 +74,8 @@ public record NodeRecognitionDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("reco_id")] int RecognitionId,
[property: JsonPropertyName("name")] string Name,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus,
+ [property: JsonPropertyName("anchor")] string? Anchor
);
///
@@ -82,5 +83,5 @@ public record NodeActionDetail(
[property: JsonPropertyName("task_id")] int TaskId,
[property: JsonPropertyName("action_id")] int ActionId,
[property: JsonPropertyName("name")] string Name,
- [property: JsonPropertyName("focus")] JsonElement? Focus
+ [property: JsonPropertyName("focus")] JsonElement Focus
);
diff --git a/src/MaaFramework.Binding/MaaMsg.cs b/src/MaaFramework.Binding/MaaMsg.cs
index 824bbe1..2d877c5 100644
--- a/src/MaaFramework.Binding/MaaMsg.cs
+++ b/src/MaaFramework.Binding/MaaMsg.cs
@@ -282,7 +282,7 @@ public static class Recognition
/// Message for the recognition list of node.
///
///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, }
+ /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
///
public const string Starting = "Node.Recognition.Starting";
@@ -290,7 +290,7 @@ public static class Recognition
/// Message for the recognition list of node.
///
///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, }
+ /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
///
public const string Prefix = "Node.Recognition";
@@ -298,7 +298,7 @@ public static class Recognition
/// Message for the recognition list of node.
///
///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, }
+ /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
///
public const string Succeeded = "Node.Recognition.Succeeded";
@@ -306,7 +306,7 @@ public static class Recognition
/// Message for the recognition list of node.
///
///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, }
+ /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
///
public const string Failed = "Node.Recognition.Failed";
From 068d9141506160f3db2aeefd57df219002826cca Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Mon, 6 Jul 2026 19:53:10 +0800
Subject: [PATCH 10/15] feat: controller ScreenshotResizeMethod (#1243)
---
src/MaaFramework.Binding.Native/MaaController.cs | 3 ++-
.../Test_IMaaController.cs | 2 ++
.../Enums/Options/ControllerOption.cs | 11 +++++++++++
.../Extensions/MaaOptionExtensions.cs | 5 +++++
4 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/MaaFramework.Binding.Native/MaaController.cs b/src/MaaFramework.Binding.Native/MaaController.cs
index 804fd75..4a7ea9b 100644
--- a/src/MaaFramework.Binding.Native/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController.cs
@@ -81,7 +81,8 @@ public bool SetOption(ControllerOption opt, T value)
var optValue = (value, opt) switch
{
(int vvvv, ControllerOption.ScreenshotTargetLongSide
- or ControllerOption.ScreenshotTargetShortSide) => vvvv.ToMaaOptionValue(),
+ or ControllerOption.ScreenshotTargetShortSide
+ or ControllerOption.ScreenshotResizeMethod) => vvvv.ToMaaOptionValue(),
(bool vvv, ControllerOption.ScreenshotUseRawSize
or ControllerOption.MouseLockFollow) => vvv.ToMaaOptionValue(),
diff --git a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
index 4743a56..42471b8 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
@@ -145,6 +145,7 @@ public void CreateInstances()
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetShortSide, 720)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, false)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, false)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotResizeMethod, 0)]
public void Interface_SetOption(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
@@ -402,6 +403,7 @@ public void Interface_Shell_ShellOutput(MaaTypes type, IMaaController maaControl
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotTargetShortSide, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, 0.0)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotResizeMethod, 0.0)]
public void Interface_SetOption_InvalidData(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
diff --git a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
index 2232c5c..950ee48 100644
--- a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
+++ b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
@@ -49,5 +49,16 @@ public enum ControllerOption : System.Int32
/// value: bool, eg: true; val_size: sizeof(bool)
///
MouseLockFollow = 4,
+
+ ///
+ /// Set the interpolation method used when resizing screenshots.
+ ///
+ ///
+ /// Value corresponds to cv::InterpolationFlags:
+ /// INTER_NEAREST=0, INTER_LINEAR=1, INTER_CUBIC=2, INTER_AREA=3, INTER_LANCZOS4=4
+ /// Default is INTER_AREA (3).
+ /// value: int, eg: 3; val_size: sizeof(int)
+ ///
+ ScreenshotResizeMethod = 6,
}
diff --git a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
index b7d1aa6..1f81570 100644
--- a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
+++ b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
@@ -30,6 +30,11 @@ public static bool SetOption_ScreenshotUseRawSize(this IMaaOption? opt, bool value)
=> opt?.SetOption(ControllerOption.MouseLockFollow, value) ?? throw new ArgumentNullException(nameof(opt));
+ ///
+ ///
+ public static bool SetOption_ScreenshotResizeMethod(this IMaaOption? opt, int value)
+ => opt?.SetOption(ControllerOption.ScreenshotResizeMethod, value) ?? throw new ArgumentNullException(nameof(opt));
+
///
///
public static bool SetOption_LogDir(this IMaaOption? opt, string value)
From 984211804be602f365febdedaef8736d8e6ace7c Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Mon, 6 Jul 2026 21:23:09 +0800
Subject: [PATCH 11/15] feat: wait_freezes msg (#1178)
---
.../Notification/NotificationDetail.cs | 96 +++-
.../Notification/NotificationDetailContext.cs | 3 +-
.../Notification/NotificationHandler.cs | 24 +-
.../NotificationHandlerRegistry.cs | 16 +-
src/MaaFramework.Binding.Extensions/README.md | 6 +-
.../README.zh_cn.md | 4 +
.../Interop/Framework/MaaDef.cs | 3 +-
src/MaaFramework.Binding.Native/MaaTasker.cs | 12 +-
.../Test_Common.cs | 1 +
src/MaaFramework.Binding/IMaaTasker.cs | 14 +-
src/MaaFramework.Binding/MaaMsg.cs | 446 ++++++++----------
11 files changed, 325 insertions(+), 300 deletions(-)
diff --git a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
index 5b2b491..32753bf 100644
--- a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
+++ b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetail.cs
@@ -3,51 +3,69 @@
namespace MaaFramework.Binding.Notification;
-///
+#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
+
+///
+public enum ResourcePathType
+{
+ Bundle,
+ OcrModel,
+ Pipeline,
+ Image,
+
+ Unknown = -1
+}
+
+///
public record ResourceLoadingDetail(
- [property: JsonPropertyName("res_id")] int ResourceId,
+ [property: JsonPropertyName("res_id")] long ResourceId,
[property: JsonPropertyName("path")] string Path,
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("hash")] string Hash
-);
+)
+{
+ // 1. .NET 7 没有支持 aot 的 JsonStringEnumConverter
+ // 2. Unknown 向前兼容,使用 JsonStringEnumConverter 还需要设置 option 并且将 Unknown 设置为默认值 0
+ [JsonIgnore] public ResourcePathType ParsedType => Enum.TryParse(Type, true, out var result) ? result : ResourcePathType.Unknown;
+};
-///
+///
public record ControllerActionDetail(
- [property: JsonPropertyName("ctrl_id")] int ControllerId,
+ [property: JsonPropertyName("ctrl_id")] long ControllerId,
[property: JsonPropertyName("uuid")] string Uuid,
[property: JsonPropertyName("action")] string Action,
[property: JsonPropertyName("param")] JsonElement Param,
[property: JsonPropertyName("info")] JsonElement Info
);
-///
+///
public record TaskerTaskDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
+ [property: JsonPropertyName("task_id")] long TaskId,
[property: JsonPropertyName("entry")] string Entry,
[property: JsonPropertyName("uuid")] string Uuid,
[property: JsonPropertyName("hash")] string Hash
);
-///
+///
public record PipelineNodeDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
- [property: JsonPropertyName("node_id")] int NodeId,
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("node_id")] long NodeId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("focus")] JsonElement Focus
);
-///
+///
public record RecognitionNodeDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
- [property: JsonPropertyName("node_id")] int NodeId,
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("node_id")] long NodeId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("focus")] JsonElement Focus
);
-///
+///
public record ActionNodeDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
- [property: JsonPropertyName("node_id")] int NodeId,
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("node_id")] long NodeId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("focus")] JsonElement Focus
);
@@ -61,27 +79,57 @@ public record NodeAttr(
[property: JsonPropertyName("anchor")] bool Anchor
);
-///
+///
public record NodeNextListDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
+ [property: JsonPropertyName("task_id")] long TaskId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("list")] IReadOnlyList NextList,
[property: JsonPropertyName("focus")] JsonElement Focus
);
-///
+///
public record NodeRecognitionDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
- [property: JsonPropertyName("reco_id")] int RecognitionId,
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("reco_id")] long RecognitionId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("focus")] JsonElement Focus,
[property: JsonPropertyName("anchor")] string? Anchor
);
-///
+///
public record NodeActionDetail(
- [property: JsonPropertyName("task_id")] int TaskId,
- [property: JsonPropertyName("action_id")] int ActionId,
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("action_id")] long ActionId,
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("focus")] JsonElement Focus
);
+
+public record WaitFreezesParam(
+ [property: JsonPropertyName("time")] long MillisecondsTime,
+ [property: JsonPropertyName("threshold")] double Threshold,
+ [property: JsonPropertyName("method")] int Method,
+ [property: JsonPropertyName("rate_limit")] long MillisecondsRateLimit,
+ [property: JsonPropertyName("timeout")] long MillisecondsTimeout
+)
+{
+ [JsonIgnore] public TimeSpan Time => TimeSpan.FromMilliseconds(MillisecondsTime);
+ [JsonIgnore] public TimeSpan RateLimit => TimeSpan.FromMilliseconds(MillisecondsRateLimit);
+ [JsonIgnore] public TimeSpan Timeout => TimeSpan.FromMilliseconds(MillisecondsTimeout);
+}
+
+///
+public record NodeWaitFreezesDetail(
+ [property: JsonPropertyName("task_id")] long TaskId,
+ [property: JsonPropertyName("wf_id")] long WaitFreezesId,
+ [property: JsonPropertyName("name")] string Name,
+ [property: JsonPropertyName("phase")] string Phase,
+ [property: JsonPropertyName("roi")] IReadOnlyList Roi,
+ [property: JsonPropertyName("param")] WaitFreezesParam Param,
+ [property: JsonPropertyName("reco_ids")] IReadOnlyList? RecognitionIds,
+ [property: JsonPropertyName("elapsed")] long? MillisecondsElapsed,
+ [property: JsonPropertyName("focus")] JsonElement Focus
+// https://github.com/MaaXYZ/MaaFramework/blob/v5.10.5/source/MaaFramework/Task/Component/ActionHelper.cpp#L46
+)
+{
+ [JsonIgnore] public TimeSpan? Elapsed => MillisecondsElapsed.HasValue ? TimeSpan.FromMilliseconds(MillisecondsElapsed.Value) : null;
+}
diff --git a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetailContext.cs b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetailContext.cs
index 6708a23..694e3d2 100644
--- a/src/MaaFramework.Binding.Extensions/Notification/NotificationDetailContext.cs
+++ b/src/MaaFramework.Binding.Extensions/Notification/NotificationDetailContext.cs
@@ -9,8 +9,9 @@ namespace MaaFramework.Binding.Notification;
[JsonSerializable(typeof(PipelineNodeDetail))]
[JsonSerializable(typeof(RecognitionNodeDetail))]
[JsonSerializable(typeof(ActionNodeDetail))]
-[JsonSerializable(typeof(NodeAttr))]
[JsonSerializable(typeof(NodeNextListDetail))]
[JsonSerializable(typeof(NodeRecognitionDetail))]
[JsonSerializable(typeof(NodeActionDetail))]
+[JsonSerializable(typeof(NodeWaitFreezesDetail))]
+[JsonSerializable(typeof(string))]
public partial class NotificationDetailContext : JsonSerializerContext;
diff --git a/src/MaaFramework.Binding.Extensions/Notification/NotificationHandler.cs b/src/MaaFramework.Binding.Extensions/Notification/NotificationHandler.cs
index fb5135f..6073d80 100644
--- a/src/MaaFramework.Binding.Extensions/Notification/NotificationHandler.cs
+++ b/src/MaaFramework.Binding.Extensions/Notification/NotificationHandler.cs
@@ -100,6 +100,18 @@ public static EventHandler ToCallback(this Notif
public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaTasker
=> notify.ToCallback(MaaMsg.Tasker.Task.Prefix);
+ ///
+ public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
+ => notify.ToCallback(MaaMsg.Node.PipelineNode.Prefix);
+
+ ///
+ public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
+ => notify.ToCallback(MaaMsg.Node.RecognitionNode.Prefix);
+
+ ///
+ public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
+ => notify.ToCallback(MaaMsg.Node.ActionNode.Prefix);
+
///
public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
=> notify.ToCallback(MaaMsg.Node.NextList.Prefix);
@@ -113,15 +125,7 @@ public static EventHandler ToCallback(this Notif
=> notify.ToCallback(MaaMsg.Node.Action.Prefix);
///
- public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
- => notify.ToCallback(MaaMsg.Node.PipelineNode.Prefix);
-
- ///
- public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
- => notify.ToCallback(MaaMsg.Node.RecognitionNode.Prefix);
-
- ///
- public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
- => notify.ToCallback(MaaMsg.Node.ActionNode.Prefix);
+ public static EventHandler ToCallback(this NotificationHandler notify) where TContext : IMaaContext
+ => notify.ToCallback(MaaMsg.Node.WaitFreezes.Prefix);
}
diff --git a/src/MaaFramework.Binding.Extensions/Notification/NotificationHandlerRegistry.cs b/src/MaaFramework.Binding.Extensions/Notification/NotificationHandlerRegistry.cs
index 055329a..e5ccd0f 100644
--- a/src/MaaFramework.Binding.Extensions/Notification/NotificationHandlerRegistry.cs
+++ b/src/MaaFramework.Binding.Extensions/Notification/NotificationHandlerRegistry.cs
@@ -232,6 +232,20 @@ internal void OnFailed(object? sender, string details) => Failed?.Invoke(sender,
NotificationDetailContext.Default.NodeActionDetail) ?? throw new InvalidCastException());
}
+ public WaitFreezesRegistry WaitFreezes { get; } = new();
+ public sealed class WaitFreezesRegistry
+ {
+ public event EventHandler? Starting;
+ internal void OnStarting(object? sender, string details) => Starting?.Invoke(sender, JsonSerializer.Deserialize(details,
+ NotificationDetailContext.Default.NodeWaitFreezesDetail) ?? throw new InvalidCastException());
+ public event EventHandler? Succeeded;
+ internal void OnSucceeded(object? sender, string details) => Succeeded?.Invoke(sender, JsonSerializer.Deserialize(details,
+ NotificationDetailContext.Default.NodeWaitFreezesDetail) ?? throw new InvalidCastException());
+ public event EventHandler? Failed;
+ internal void OnFailed(object? sender, string details) => Failed?.Invoke(sender, JsonSerializer.Deserialize(details,
+ NotificationDetailContext.Default.NodeWaitFreezesDetail) ?? throw new InvalidCastException());
+ }
+
}
-}
\ No newline at end of file
+}
diff --git a/src/MaaFramework.Binding.Extensions/README.md b/src/MaaFramework.Binding.Extensions/README.md
index 3d48478..5f705c6 100644
--- a/src/MaaFramework.Binding.Extensions/README.md
+++ b/src/MaaFramework.Binding.Extensions/README.md
@@ -56,9 +56,13 @@ Supported TDetail types:
- ResourceLoadingDetail
- ControllerActionDetail
- TaskerTaskDetail
+- PipelineNodeDetail
+- RecognitionNodeDetail
+- ActionNodeDetail
- NodeNextListDetail
- NodeRecognitionDetail
- NodeActionDetail
+- NodeWaitFreezesDetail
- string
Usage example:
@@ -82,4 +86,4 @@ NotificationHandler OnResourceLoading = (type, detail) =>
}
};
maa.Callback += OnResourceLoading.ToCallback();
-```
\ No newline at end of file
+```
diff --git a/src/MaaFramework.Binding.Extensions/README.zh_cn.md b/src/MaaFramework.Binding.Extensions/README.zh_cn.md
index 22a71b6..c151721 100644
--- a/src/MaaFramework.Binding.Extensions/README.zh_cn.md
+++ b/src/MaaFramework.Binding.Extensions/README.zh_cn.md
@@ -56,9 +56,13 @@ TDetail 为以下类型:
- ResourceLoadingDetail
- ControllerActionDetail
- TaskerTaskDetail
+- PipelineNodeDetail
+- RecognitionNodeDetail
+- ActionNodeDetail
- NodeNextListDetail
- NodeRecognitionDetail
- NodeActionDetail
+- NodeWaitFreezesDetail
- string
使用示例:
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
index 54818ba..8e9075e 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/MaaDef.cs
@@ -19,6 +19,7 @@
global using MaaRecoId = System.Int64;
global using MaaActId = System.Int64;
global using MaaNodeId = System.Int64;
+global using MaaWfId = System.Int64;
global using MaaSinkId = System.Int64;
global using MaaStringBufferHandle = nint;
global using MaaImageBufferHandle = nint;
@@ -55,8 +56,6 @@
global using MaaGamepadType = System.UInt64;
global using MaaGamepadButton = System.UInt64;
global using MaaGamepadTouch = System.UInt64;
-// WaitFreezes id
-global using MaaWfId = System.Int64;
global using MaaRectHandle = nint;
using System.Runtime.InteropServices;
diff --git a/src/MaaFramework.Binding.Native/MaaTasker.cs b/src/MaaFramework.Binding.Native/MaaTasker.cs
index 532851e..c5be649 100644
--- a/src/MaaFramework.Binding.Native/MaaTasker.cs
+++ b/src/MaaFramework.Binding.Native/MaaTasker.cs
@@ -408,14 +408,14 @@ public bool GetActionDetail(MaaActId actionId, out string nodeName, out string a
}
///
- public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi)
- => GetWaitFreezesDetail(wfId, out nodeName, out phase, out isSucceeded, out elapsedMs, out recoIdList, (MaaRectBuffer?)roi);
+ public bool GetWaitFreezesDetail(MaaWfId waitFreezesId, out string nodeName, out string phase, out bool isSucceeded, out MaaSize millisecondsElapsed, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi)
+ => GetWaitFreezesDetail(waitFreezesId, out nodeName, out phase, out isSucceeded, out millisecondsElapsed, out recoIdList, (MaaRectBuffer?)roi);
- ///
+ ///
///
/// Wrapper of .
///
- public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, MaaRectBuffer? roi)
+ public bool GetWaitFreezesDetail(MaaWfId waitFreezesId, out string nodeName, out string phase, out bool isSucceeded, out MaaSize millisecondsElapsed, out MaaRecoId[] recoIdList, MaaRectBuffer? roi)
{
var roiHandle = roi?.Handle ?? MaaRectHandle.Zero;
MaaSize recoIdListSize = 0;
@@ -423,7 +423,7 @@ public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string p
using var nodeNameBuffer = new MaaStringBuffer();
using var phaseBuffer = new MaaStringBuffer();
- if (!MaaTaskerGetWaitFreezesDetail(Handle, wfId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out elapsedMs, null, ref recoIdListSize, roiHandle))
+ if (!MaaTaskerGetWaitFreezesDetail(Handle, waitFreezesId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out millisecondsElapsed, null, ref recoIdListSize, roiHandle))
{
nodeName = string.Empty;
phase = string.Empty;
@@ -434,7 +434,7 @@ public bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string p
nodeName = nodeNameBuffer.ToString();
phase = phaseBuffer.ToString();
recoIdList = recoIdListSize == 0 ? [] : new MaaRecoId[recoIdListSize];
- return MaaTaskerGetWaitFreezesDetail(Handle, wfId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out elapsedMs, recoIdList, ref recoIdListSize, roiHandle);
+ return MaaTaskerGetWaitFreezesDetail(Handle, waitFreezesId, nodeNameBuffer.Handle, phaseBuffer.Handle, out isSucceeded, out millisecondsElapsed, recoIdList, ref recoIdListSize, roiHandle);
}
///
diff --git a/src/MaaFramework.Binding.UnitTests/Test_Common.cs b/src/MaaFramework.Binding.UnitTests/Test_Common.cs
index d0cbbd2..33b243a 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_Common.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_Common.cs
@@ -95,4 +95,5 @@ internal static void OnCallback(object? sender, MaaCallbackEventArgs e)
internal static NotificationHandler OnNodeNextList = (type, detail, context) => Assert.IsNotNull(context);
internal static NotificationHandler OnNodeRecognition = (type, detail, context) => Assert.IsNotNull(context);
internal static NotificationHandler OnNodeAction = (type, detail, context) => Assert.IsNotNull(context);
+ internal static NotificationHandler OnNodeWaitFreezes = (type, detail, context) => Assert.IsNotNull(context);
}
diff --git a/src/MaaFramework.Binding/IMaaTasker.cs b/src/MaaFramework.Binding/IMaaTasker.cs
index fd03497..4cbd7bb 100644
--- a/src/MaaFramework.Binding/IMaaTasker.cs
+++ b/src/MaaFramework.Binding/IMaaTasker.cs
@@ -157,15 +157,19 @@ bool GetRecognitionDetail(MaaRecoId recognitionId, out string nodeName, out s
///
/// Gets the wait freezes detail.
///
- /// The wait freezes id.
+ /// The wait freezes id.
/// The node name.
/// The phase (e.g. "pre", "post", "repeat", "context").
/// A value indicating whether the wait freezes is succeeded.
- /// The elapsed time in milliseconds.
- /// The recognition id list.
- /// The region of interest.
+ /// The total elapsed time in milliseconds.
+ /// The recognition id array for each frame-to-frame comparison.
+ /// The actual region of interest used for comparison.
/// if query was successful; otherwise, .
- bool GetWaitFreezesDetail(MaaWfId wfId, out string nodeName, out string phase, out bool isSucceeded, out ulong elapsedMs, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi);
+ ///
+ /// Obtained via `wf_id` from the callback.
+ /// Each `reco_id` can be queried via `MaaTaskerGetRecognitionDetail` to get the drawn images.
+ ///
+ bool GetWaitFreezesDetail(MaaWfId waitFreezesId, out string nodeName, out string phase, out bool isSucceeded, out MaaSize millisecondsElapsed, out MaaRecoId[] recoIdList, IMaaRectBuffer? roi);
///
/// Gets the node detail.
diff --git a/src/MaaFramework.Binding/MaaMsg.cs b/src/MaaFramework.Binding/MaaMsg.cs
index 2d877c5..debb5f8 100644
--- a/src/MaaFramework.Binding/MaaMsg.cs
+++ b/src/MaaFramework.Binding/MaaMsg.cs
@@ -22,364 +22,310 @@ public static class MaaMsg
{
public static class Resource
{
+ ///
+ /// The message for the resource loading.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// res_id: number,
+ /// path: string,
+ /// type: string, // "Bundle" | "OcrModel" | "Pipeline" | "Image"
+ /// hash: string,
+ /// }
+ ///
+ ///
public static class Loading
{
- ///
- /// The message for the resource loading.
- ///
- ///
- /// details_json: { res_id: number, path: string, type: string // "Bundle" | "OcrModel" | "Pipeline" | "Image", hash: string, }
- ///
+ ///
public const string Starting = "Resource.Loading.Starting";
- ///
- /// The message for the resource loading.
- ///
- ///
- /// details_json: { res_id: number, path: string, type: string // "Bundle" | "OcrModel" | "Pipeline" | "Image", hash: string, }
- ///
+ ///
public const string Prefix = "Resource.Loading";
- ///
- /// The message for the resource loading.
- ///
- ///
- /// details_json: { res_id: number, path: string, type: string // "Bundle" | "OcrModel" | "Pipeline" | "Image", hash: string, }
- ///
+ ///
public const string Succeeded = "Resource.Loading.Succeeded";
- ///
- /// The message for the resource loading.
- ///
- ///
- /// details_json: { res_id: number, path: string, type: string // "Bundle" | "OcrModel" | "Pipeline" | "Image", hash: string, }
- ///
+ ///
public const string Failed = "Resource.Loading.Failed";
-
}
}
public static class Controller
{
+ ///
+ /// Message for the controller actions.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// ctrl_id: number,
+ /// uuid: string,
+ /// action: string,
+ /// param: object,
+ /// info: object,
+ /// }
+ ///
+ ///
public static class Action
{
- ///
- /// Message for the controller actions.
- ///
- ///
- /// details_json: { ctrl_id: number, uuid: string, action: string, param: object, info: object, }
- ///
+ ///
public const string Starting = "Controller.Action.Starting";
- ///
- /// Message for the controller actions.
- ///
- ///
- /// details_json: { ctrl_id: number, uuid: string, action: string, param: object, info: object, }
- ///
+ ///
public const string Prefix = "Controller.Action";
- ///
- /// Message for the controller actions.
- ///
- ///
- /// details_json: { ctrl_id: number, uuid: string, action: string, param: object, info: object, }
- ///
+ ///
public const string Succeeded = "Controller.Action.Succeeded";
- ///
- /// Message for the controller actions.
- ///
- ///
- /// details_json: { ctrl_id: number, uuid: string, action: string, param: object, info: object, }
- ///
+ ///
public const string Failed = "Controller.Action.Failed";
-
}
}
public static class Tasker
{
+ ///
+ /// Message for the task.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// entry: string,
+ /// uuid: string,
+ /// hash: string
+ /// }
+ ///
+ ///
public static class Task
{
- ///
- /// Message for the task.
- ///
- ///
- /// details_json: { task_id: number, entry: string, uuid: string, hash: string }
- ///
+ ///
public const string Starting = "Tasker.Task.Starting";
- ///
- /// Message for the task.
- ///
- ///
- /// details_json: { task_id: number, entry: string, uuid: string, hash: string }
- ///
+ ///
public const string Prefix = "Tasker.Task";
- ///
- /// Message for the task.
- ///
- ///
- /// details_json: { task_id: number, entry: string, uuid: string, hash: string }
- ///
+ ///
public const string Succeeded = "Tasker.Task.Succeeded";
- ///
- /// Message for the task.
- ///
- ///
- /// details_json: { task_id: number, entry: string, uuid: string, hash: string }
- ///
+ ///
public const string Failed = "Tasker.Task.Failed";
-
}
}
public static class Node
{
+ ///
+ /// Message for the node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// node_id: number,
+ /// name: string,
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class PipelineNode
{
- ///
- /// Message for the pipeline node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Starting = "Node.PipelineNode.Starting";
- ///
- /// Message for the pipeline node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Prefix = "Node.PipelineNode";
- ///
- /// Message for the pipeline node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Succeeded = "Node.PipelineNode.Succeeded";
- ///
- /// Message for the pipeline node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Failed = "Node.PipelineNode.Failed";
-
}
+
+ ///
+ /// Message for the node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// node_id: number,
+ /// name: string,
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class RecognitionNode
{
- ///
- /// Message for the recognition node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Starting = "Node.RecognitionNode.Starting";
- ///
- /// Message for the recognition node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Prefix = "Node.RecognitionNode";
- ///
- /// Message for the recognition node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Succeeded = "Node.RecognitionNode.Succeeded";
- ///
- /// Message for the recognition node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Failed = "Node.RecognitionNode.Failed";
-
}
+
+ ///
+ /// Message for the node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// node_id: number,
+ /// name: string,
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class ActionNode
{
- ///
- /// Message for the action node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Starting = "Node.ActionNode.Starting";
- ///
- /// Message for the action node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Prefix = "Node.ActionNode";
- ///
- /// Message for the action node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Succeeded = "Node.ActionNode.Succeeded";
- ///
- /// Message for the action node.
- ///
- ///
- /// details_json: { task_id: number, node_id: number, name: string, focus: any, }
- ///
+ ///
public const string Failed = "Node.ActionNode.Failed";
-
}
+
+ ///
+ /// Message for the next list of node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// name: string,
+ /// list: [
+ /// {
+ /// "name": string,
+ /// "jump_back": boolean,
+ /// "anchor": boolean,
+ /// },
+ /// ...
+ /// ],
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class NextList
{
- ///
- /// Message for the next list of node.
- ///
- ///
- /// details_json: { task_id: number, name: string, list: [{ name: string, jump_back: boolean, anchor: boolean }, ...], focus: any, }
- ///
+ ///
public const string Starting = "Node.NextList.Starting";
- ///
- /// Message for the next list of node.
- ///
- ///
- /// details_json: { task_id: number, name: string, list: [{ name: string, jump_back: boolean, anchor: boolean }, ...], focus: any, }
- ///
+ ///
public const string Prefix = "Node.NextList";
- ///
- /// Message for the next list of node.
- ///
- ///
- /// details_json: { task_id: number, name: string, list: [{ name: string, jump_back: boolean, anchor: boolean }, ...], focus: any, }
- ///
+ ///
public const string Succeeded = "Node.NextList.Succeeded";
- ///
- /// Message for the next list of node.
- ///
- ///
- /// details_json: { task_id: number, name: string, list: [{ name: string, jump_back: boolean, anchor: boolean }, ...], focus: any, }
- ///
+ ///
public const string Failed = "Node.NextList.Failed";
-
}
+
+ ///
+ /// Message for the recognition list of node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// reco_id: number,
+ /// name: string,
+ /// focus: any,
+ /// anchor?: string,
+ /// }
+ ///
+ ///
public static class Recognition
{
- ///
- /// Message for the recognition list of node.
- ///
- ///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
- ///
+ ///
public const string Starting = "Node.Recognition.Starting";
- ///
- /// Message for the recognition list of node.
- ///
- ///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
- ///
+ ///
public const string Prefix = "Node.Recognition";
- ///
- /// Message for the recognition list of node.
- ///
- ///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
- ///
+ ///
public const string Succeeded = "Node.Recognition.Succeeded";
- ///
- /// Message for the recognition list of node.
- ///
- ///
- /// details_json: { task_id: number, reco_id: number, name: string, focus: any, anchor?: string, }
- ///
+ ///
public const string Failed = "Node.Recognition.Failed";
-
}
+
+ ///
+ /// Message for the action of node.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// action_id: number,
+ /// name: string,
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class Action
{
- ///
- /// Message for the action of node.
- ///
- ///
- /// details_json: { task_id: number, action_id: number, name: string, focus: any, }
- ///
+ ///
public const string Starting = "Node.Action.Starting";
- ///
- /// Message for the action of node.
- ///
- ///
- /// details_json: { task_id: number, action_id: number, name: string, focus: any, }
- ///
+ ///
public const string Prefix = "Node.Action";
- ///
- /// Message for the action of node.
- ///
- ///
- /// details_json: { task_id: number, action_id: number, name: string, focus: any, }
- ///
+ ///
public const string Succeeded = "Node.Action.Succeeded";
- ///
- /// Message for the action of node.
- ///
- ///
- /// details_json: { task_id: number, action_id: number, name: string, focus: any, }
- ///
+ ///
public const string Failed = "Node.Action.Failed";
-
}
+
+ ///
+ /// Message for wait freezes.
+ ///
+ ///
+ ///
+ /// details_json: {
+ /// task_id: number,
+ /// wf_id: number,
+ /// name: string,
+ /// phase: string, // "pre" | "post" | "repeat" | "context"
+ /// roi: [number, number, number, number],
+ /// param: {
+ /// time: number,
+ /// threshold: number,
+ /// method: number,
+ /// rate_limit: number,
+ /// timeout: number,
+ /// },
+ /// reco_ids?: number[], // Succeeded / Failed only
+ /// elapsed?: number, // Succeeded / Failed only
+ /// focus: any,
+ /// }
+ ///
+ ///
public static class WaitFreezes
{
- ///
- /// Message for wait freezes.
- ///
- ///
- /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
- ///
+ ///
public const string Starting = "Node.WaitFreezes.Starting";
- ///
- /// Message for wait freezes.
- ///
- ///
- /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
- ///
+ ///
public const string Prefix = "Node.WaitFreezes";
- ///
- /// Message for wait freezes.
- ///
- ///
- /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
- ///
+ ///
public const string Succeeded = "Node.WaitFreezes.Succeeded";
- ///
- /// Message for wait freezes.
- ///
- ///
- /// details_json: { task_id: number, wf_id: number, name: string, phase: string, roi: [number, number, number, number], param: { time: number, threshold: number, method: number, rate_limit: number, timeout: number }, reco_ids?: number[], elapsed?: number, focus: any, }
- ///
+ ///
public const string Failed = "Node.WaitFreezes.Failed";
-
}
}
}
From 5900a8cb5144b305ed15d08b1ecc44364892f1d1 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Tue, 7 Jul 2026 16:48:35 +0800
Subject: [PATCH 12/15] =?UTF-8?q?feat(wlroots):=20=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E5=B0=86=E6=8C=89=E9=94=AE=E8=A7=86=E4=B8=BA=20Win32=20VK=20?=
=?UTF-8?q?=E9=94=AE=E7=A0=81=20(#1283)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Framework/Instance/MaaController.cs | 34 +++++++++++++++----
.../MaaController/MaaWlRootsController.cs | 9 +++--
2 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index f1454d4..79b8af6 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -104,6 +104,34 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaControllerHandle MaaRecordControllerCreate(MaaControllerHandle inner, string recordingPath);
+ ///
+ /// Create a PlayCover controller for macOS.
+ ///
+ /// The PlayTools service address in "host:port" format.
+ /// The application bundle identifier (e.g., "com.hypergryph.arknights").
+ /// The controller handle, or nullptr on failure.
+ ///
+ /// This controller is designed for PlayCover on macOS.
+ /// Some features are not supported: start_app, input_text, click_key, key_down, key_up, scroll.
+ /// Only single touch is supported (contact must be 0).
+ ///
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaPlayCoverControllerCreate(string address, string uuid);
+
+ ///
+ /// Create a wlroots controller for Linux.
+ ///
+ /// The wayland socket path (e.g., "/run/user/1000/wayland-0").
+ ///
+ ///
If true, key codes passed to click_key / key_down / key_up are
+ ///
interpreted as Win32 Virtual-Key codes (VK_*) and translated to Linux evdev codes
+ ///
internally. If false, key codes are passed through as raw evdev codes.
+ ///
+ /// The controller handle, or nullptr on failure.
+ /// This controller is designed for wlroots on Linux.
+ [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial MaaControllerHandle MaaWlRootsControllerCreate(string wlrSocketPath, [MarshalAs(UnmanagedType.U1)] bool useWin32VkCode);
+
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial void MaaControllerDestroy(MaaControllerHandle ctrl);
@@ -185,12 +213,6 @@ public static partial class MaaController
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool MaaControllerGetShellOutput(MaaControllerHandle ctrl, MaaStringBufferHandle buffer);
- [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
- public static partial MaaControllerHandle MaaPlayCoverControllerCreate(string address, string uuid);
-
- [LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
- public static partial MaaControllerHandle MaaWlRootsControllerCreate(string wlr_socket_path);
-
///
/// Create a virtual gamepad controller for Windows.
///
diff --git a/src/MaaFramework.Binding.Native/MaaController/MaaWlRootsController.cs b/src/MaaFramework.Binding.Native/MaaController/MaaWlRootsController.cs
index 881234b..d3d3ad0 100644
--- a/src/MaaFramework.Binding.Native/MaaController/MaaWlRootsController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController/MaaWlRootsController.cs
@@ -24,6 +24,11 @@ public class MaaWlRootsController : MaaController
/// Creates a instance.
///
/// The wayland socket path (e.g., "/run/user/1000/wayland-0").
+ ///
+ /// If true, key codes passed to / /
+ ///
interpreted as Win32 Virtual-Key codes (VK_*) and translated to Linux evdev codes internally.
+ /// If false, key codes are passed through as raw evdev codes.
+ ///
/// Executes if ; otherwise, not link.
/// Checks LinkStart().Wait() status if ; otherwise, not check.
///
@@ -32,11 +37,11 @@ public class MaaWlRootsController : MaaController
///
///
///
- public MaaWlRootsController(string wlrSocketPath, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
+ public MaaWlRootsController(string wlrSocketPath, bool useWin32VirtualKeyCodes = false, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
{
ArgumentException.ThrowIfNullOrEmpty(wlrSocketPath);
- var handle = MaaWlRootsControllerCreate(wlrSocketPath);
+ var handle = MaaWlRootsControllerCreate(wlrSocketPath, useWin32VirtualKeyCodes);
_ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
SetHandle(handle, needReleased: true);
From 050be48303892d89f645af3878c189398f33ea25 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Wed, 8 Jul 2026 01:40:41 +0800
Subject: [PATCH 13/15] =?UTF-8?q?feat(win32):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E5=90=8E=E5=8F=B0=E6=89=98=E7=AE=A1=E6=8C=89=E9=94=AE=E5=AE=88?=
=?UTF-8?q?=E6=8A=A4=EF=BC=8C=E6=94=AF=E6=8C=81=E5=90=8E=E5=8F=B0=E9=95=BF?=
=?UTF-8?q?=E6=8C=89=E7=A7=BB=E5=8A=A8=20(#1231)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Interop/MaaMarshaller.cs | 43 ++++++++++++++++++-
.../Interop/MaaMarshallingExtensions.cs | 4 ++
.../MaaController.cs | 2 +
.../Test_IMaaController.cs | 2 +
.../Enums/Options/ControllerOption.cs | 11 +++++
.../Extensions/MaaOptionExtensions.cs | 5 +++
6 files changed, 65 insertions(+), 2 deletions(-)
diff --git a/src/MaaFramework.Binding.Native/Interop/MaaMarshaller.cs b/src/MaaFramework.Binding.Native/Interop/MaaMarshaller.cs
index 8c9fbbe..50c788e 100644
--- a/src/MaaFramework.Binding.Native/Interop/MaaMarshaller.cs
+++ b/src/MaaFramework.Binding.Native/Interop/MaaMarshaller.cs
@@ -1,7 +1,8 @@
-using System.Runtime.InteropServices;
+using MaaFramework.Binding.Custom;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Text;
-using MaaFramework.Binding.Custom;
namespace MaaFramework.Binding.Interop.Native;
@@ -46,6 +47,44 @@ public static byte[] ConvertToMaaOptionValue(bool value)
///
public static byte[] ConvertToMaaOptionValue(string value)
=> Encoding.UTF8.GetBytes(value);
+
+ ///
+ /// Converts a to a MaaOptionValue ([]).
+ ///
+ public static byte[] ConvertToMaaOptionValue(IEnumerable value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ if (value is int[] array)
+ {
+ var result = new byte[array.Length * 4];
+ Span byteSpan = result;
+ MemoryMarshal.Cast(array).CopyTo(byteSpan);
+ return result;
+ }
+
+ if (value is List list)
+ {
+ var result = new byte[list.Count * 4];
+ Span byteSpan = result;
+ MemoryMarshal.Cast(CollectionsMarshal.AsSpan(list)).CopyTo(byteSpan);
+ return result;
+ }
+
+ if (value.TryGetNonEnumeratedCount(out var count))
+ {
+ var result = new byte[count * 4];
+ var i = 0;
+ foreach (var item in value)
+ {
+ // 边界检查 MemoryMarshal.Cast(new ReadOnlySpan(in item)).CopyTo(new Span(result, i++ * 4, 4));
+ Unsafe.WriteUnaligned(ref result[i++ * 4], item);
+ }
+ return result;
+ }
+
+ return [.. value.SelectMany(BitConverter.GetBytes)];
+ }
}
///
diff --git a/src/MaaFramework.Binding.Native/Interop/MaaMarshallingExtensions.cs b/src/MaaFramework.Binding.Native/Interop/MaaMarshallingExtensions.cs
index 65f602c..d659ae5 100644
--- a/src/MaaFramework.Binding.Native/Interop/MaaMarshallingExtensions.cs
+++ b/src/MaaFramework.Binding.Native/Interop/MaaMarshallingExtensions.cs
@@ -67,4 +67,8 @@ internal static byte[] ToMaaOptionValue(this string value)
///
internal static byte[] ToMaaOptionValue(this nuint value)
=> MaaMarshaller.ConvertToMaaOptionValue(value);
+
+ ///
+ internal static byte[] ToMaaOptionValue(this IEnumerable value)
+ => MaaMarshaller.ConvertToMaaOptionValue(value);
}
diff --git a/src/MaaFramework.Binding.Native/MaaController.cs b/src/MaaFramework.Binding.Native/MaaController.cs
index 4a7ea9b..6036d4c 100644
--- a/src/MaaFramework.Binding.Native/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/MaaController.cs
@@ -86,6 +86,8 @@ or ControllerOption.ScreenshotTargetShortSide
(bool vvv, ControllerOption.ScreenshotUseRawSize
or ControllerOption.MouseLockFollow) => vvv.ToMaaOptionValue(),
+ (IEnumerable ints, ControllerOption.BackgroundManagedKeys) => ints.ToMaaOptionValue(),
+
_ => throw new NotSupportedException($"'{nameof(ControllerOption)}.{opt}' or type '{typeof(T)}' is not supported."),
};
diff --git a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
index 42471b8..0c755a4 100644
--- a/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
+++ b/src/MaaFramework.Binding.UnitTests/Test_IMaaController.cs
@@ -146,6 +146,7 @@ public void CreateInstances()
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, false)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, false)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotResizeMethod, 0)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.BackgroundManagedKeys, new int[] { 0x57, 0x41, 0x53, 0x44 })]
public void Interface_SetOption(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
@@ -404,6 +405,7 @@ public void Interface_Shell_ShellOutput(MaaTypes type, IMaaController maaControl
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotUseRawSize, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.MouseLockFollow, 0.0)]
[MaaData(MaaTypes.All, nameof(Data), ControllerOption.ScreenshotResizeMethod, 0.0)]
+ [MaaData(MaaTypes.All, nameof(Data), ControllerOption.BackgroundManagedKeys, 0.0)]
public void Interface_SetOption_InvalidData(MaaTypes type, IMaaController maaController, ControllerOption opt, object arg)
{
Assert.IsNotNull(maaController);
diff --git a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
index 950ee48..6185ad0 100644
--- a/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
+++ b/src/MaaFramework.Binding/Enums/Options/ControllerOption.cs
@@ -60,5 +60,16 @@ public enum ControllerOption : System.Int32
/// value: int, eg: 3; val_size: sizeof(int)
///
ScreenshotResizeMethod = 6,
+
+ ///
+ /// Configure background managed key domain for Win32 controllers.
+ ///
+ ///
+ /// Must be set before connection. After setting, matching ClickKey / LongPressKey / KeyDown / KeyUp
+ ///
operations automatically route through the background guardian path.
+ /// Only supported by Win32 controllers; other controllers will fail.
+ /// value: int32_t array of virtual key codes; val_size: sizeof(int32_t) * count
+ ///
+ BackgroundManagedKeys = 7,
}
diff --git a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
index 1f81570..56f40eb 100644
--- a/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
+++ b/src/MaaFramework.Binding/Extensions/MaaOptionExtensions.cs
@@ -35,6 +35,11 @@ public static bool SetOption_MouseLockFollow(this IMaaOption?
public static bool SetOption_ScreenshotResizeMethod(this IMaaOption? opt, int value)
=> opt?.SetOption(ControllerOption.ScreenshotResizeMethod, value) ?? throw new ArgumentNullException(nameof(opt));
+ ///
+ ///
+ public static bool SetOption_BackgroundManagedKeys(this IMaaOption? opt, IEnumerable value)
+ => opt?.SetOption(ControllerOption.BackgroundManagedKeys, value) ?? throw new ArgumentNullException(nameof(opt));
+
///
///
public static bool SetOption_LogDir(this IMaaOption? opt, string value)
From c25d3147512080086a2f8bd1e6d57021085187a7 Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Wed, 8 Jul 2026 15:03:09 +0800
Subject: [PATCH 14/15] fix: resolve compilation errors
---
.../Interop/Framework/Instance/MaaTasker.cs | 4 +++-
src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
index 90ae9b2..c076fe8 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaTasker.cs
@@ -7,6 +7,8 @@
//
//------------------------------------------------------------------------------
+#nullable enable
+
#pragma warning disable CS1573 // 参数在 XML 注释中没有匹配的 param 标记
#pragma warning disable CS1591 // 缺少对公共可见类型或成员的 XML 注释
@@ -115,7 +117,7 @@ public static partial class MaaTasker
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
- public static partial bool MaaTaskerGetTaskDetail(MaaTaskerHandle tasker, MaaTaskId taskId, MaaStringBufferHandle entry, MaaNodeId[] nodeIdList, ref MaaSize nodeIdListSize, out MaaStatus status);
+ public static partial bool MaaTaskerGetTaskDetail(MaaTaskerHandle tasker, MaaTaskId taskId, MaaStringBufferHandle entry, MaaNodeId[]? nodeIdList, ref MaaSize nodeIdListSize, out MaaStatus status);
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
diff --git a/src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs b/src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs
index 8f646df..f666420 100644
--- a/src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs
+++ b/src/MaaFramework.Binding.Native/Interop/NativeLibrary.cs
@@ -91,7 +91,7 @@ private static IEnumerable GetLibraryPaths(string libraryFullName)
// to AppContext.BaseDirectory which is the directory containing the single-file executable.
var assemblyDirectory = string.IsNullOrEmpty(s_assembly.Location)
? AppContext.BaseDirectory
- : Path.GetDirectoryName(s_assembly.Location)!;
+ : Path.GetDirectoryName(s_assembly.Location);
string?[] basePaths =
[
From d5096ca1855c0c1512255780294e0e7cd918065e Mon Sep 17 00:00:00 2001
From: moomiji <35213527+moomiji@users.noreply.github.com>
Date: Wed, 8 Jul 2026 15:07:26 +0800
Subject: [PATCH 15/15] chore: update documentation comments
---
.../Framework/Instance/MaaController.cs | 24 ++++++
.../Enums/Controllers/AdbInputMethods.cs | 22 +++--
.../Enums/Controllers/AdbScreencapMethods.cs | 32 +++++---
.../Enums/Controllers/GamepadButton.cs | 80 ++++++++++---------
.../Enums/Controllers/GamepadTouch.cs | 36 +++++----
.../Enums/Controllers/GamepadType.cs | 14 ++--
.../Enums/Controllers/Win32InputMethod.cs | 43 +++++-----
7 files changed, 156 insertions(+), 95 deletions(-)
diff --git a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
index 79b8af6..7ddff03 100644
--- a/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
+++ b/src/MaaFramework.Binding.Native/Interop/Framework/Instance/MaaController.cs
@@ -200,12 +200,36 @@ public static partial class MaaController
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostScreencap(MaaControllerHandle ctrl);
+ ///
+ /// Post a scroll action to the controller.
+ ///
+ /// The controller handle.
+ /// The horizontal scroll delta. Positive values scroll right, negative values scroll left.
+ /// The vertical scroll delta. Positive values scroll up, negative values scroll down.
+ /// The control id of the scroll action.
+ ///
+ /// Not all controllers support scroll. If not supported, the action will fail.
+ /// Scroll is supported by Win32 controllers and custom controllers that implement scroll.
+ /// If the controller does not support scroll, the action will fail. Use MaaControllerStatus or
+ /// MaaControllerWait to check the result.
+ /// The dx/dy values are sent directly as scroll increments. Using multiples of 120 (WHEEL_DELTA) is
+ /// recommended for best compatibility.
+ ///
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostScroll(MaaControllerHandle ctrl, int dx, int dy);
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostInactive(MaaControllerHandle ctrl);
+ /// Post a shell command to the controller.
+ /// The controller handle.
+ /// The shell command to execute.
+ /// Timeout in milliseconds. Default is 20000 (20 seconds).
+ /// The control id of the shell action.
+ ///
+ /// This is only valid for ADB controllers. If the controller is not an ADB controller, the action will fail.
+ /// Supported by ADB controllers and custom controllers that implement the shell callback.
+ ///
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostShell(MaaControllerHandle ctrl, string cmd, long timeout);
diff --git a/src/MaaFramework.Binding/Enums/Controllers/AdbInputMethods.cs b/src/MaaFramework.Binding/Enums/Controllers/AdbInputMethods.cs
index 06a2c51..50b6a8f 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/AdbInputMethods.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/AdbInputMethods.cs
@@ -14,16 +14,22 @@ namespace MaaFramework.Binding;
///
/// Adb input method flags
-/// Use bitwise OR to set the methods you need.
-/// MaaFramework will select the first available method according to priority.
+///
+/// Use bitwise OR to set the methods you need.
+///
MaaFramework will select the first available method according to priority.
+///
/// Priority (high to low): EmulatorExtras > Maatouch > MinitouchAndAdbKey > AdbShell
+///
/// Default: All methods except EmulatorExtras
-/// | Method | Speed | Compatibility | Notes |
-/// |----------------------|-------|---------------|---------------------------------------|
-/// | AdbShell | Slow | High | |
-/// | MinitouchAndAdbKey | Fast | Medium | Key press still uses AdbShell |
-/// | Maatouch | Fast | Medium | |
-/// | EmulatorExtras | Fast | Low | Emulators only: MuMu 12 |
+///
+/// | Method | Speed | Compatibility | Notes |
+///
|----------------------|-------|---------------|---------------------------------------|
+///
| AdbShell | Slow | High | |
+///
| MinitouchAndAdbKey | Fast | Medium | Key press still uses AdbShell |
+///
| Maatouch | Fast | Medium | |
+///
| EmulatorExtras | Fast | Low | Emulators only: MuMu 12 |
+///
+///
///
[Flags]
public enum AdbInputMethods : System.UInt64
diff --git a/src/MaaFramework.Binding/Enums/Controllers/AdbScreencapMethods.cs b/src/MaaFramework.Binding/Enums/Controllers/AdbScreencapMethods.cs
index 2a5fc68..6b8aaad 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/AdbScreencapMethods.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/AdbScreencapMethods.cs
@@ -14,20 +14,26 @@ namespace MaaFramework.Binding;
///
/// Adb screencap method flags.
-/// Use bitwise OR to set the methods you need.
-/// MaaFramework will test all provided methods and use the fastest available one.
+///
+/// Use bitwise OR to set the methods you need.
+///
MaaFramework will test all provided methods and use the fastest available one.
+///
/// Default: All methods except RawByNetcat, MinicapDirect, MinicapStream
-/// Note: MinicapDirect and MinicapStream use lossy JPEG encoding, which may
-/// significantly reduce template matching accuracy. Not recommended.
-/// | Method | Speed | Compatibility | Encoding | Notes |
-/// |-----------------------|------------|---------------|----------|-------------------------------------|
-/// | EncodeToFileAndPull | Slow | High | Lossless | |
-/// | Encode | Slow | High | Lossless | |
-/// | RawWithGzip | Medium | High | Lossless | |
-/// | RawByNetcat | Fast | Low | Lossless | |
-/// | MinicapDirect | Fast | Low | Lossy | |
-/// | MinicapStream | Very Fast | Low | Lossy | |
-/// | EmulatorExtras | Very Fast | Low | Lossless | Emulators only: MuMu 12, LDPlayer 9 |
+///
+/// Note: MinicapDirect and MinicapStream use lossy JPEG encoding, which may
+///
significantly reduce template matching accuracy. Not recommended.
+///
+/// | Method | Speed | Compatibility | Encoding | Notes |
+///
|-----------------------|------------|---------------|----------|-------------------------------------|
+///
| EncodeToFileAndPull | Slow | High | Lossless | |
+///
| Encode | Slow | High | Lossless | |
+///
| RawWithGzip | Medium | High | Lossless | |
+///
| RawByNetcat | Fast | Low | Lossless | |
+///
| MinicapDirect | Fast | Low | Lossy | |
+///
| MinicapStream | Very Fast | Low | Lossy | |
+///
| EmulatorExtras | Very Fast | Low | Lossless | Emulators only: MuMu 12, LDPlayer 9 |
+///
+///
///
[Flags]
public enum AdbScreencapMethods : System.UInt64
diff --git a/src/MaaFramework.Binding/Enums/Controllers/GamepadButton.cs b/src/MaaFramework.Binding/Enums/Controllers/GamepadButton.cs
index 4a39fb3..8125f7b 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/GamepadButton.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/GamepadButton.cs
@@ -13,42 +13,50 @@
namespace MaaFramework.Binding;
///
-/// Virtual gamepad button codes for click_key/key_down/key_up
-/// Use these values with MaaControllerPostClickKey, MaaControllerPostKeyDown, MaaControllerPostKeyUp.
-/// Values are based on XUSB (Xbox 360) button flags. DS4 face buttons are mapped to Xbox equivalents.
-/// Xbox 360 buttons:
-/// | Value | Button | Description |
-/// |---------|---------------------|------------------------|
-/// | 0x1000 | A | A button |
-/// | 0x2000 | B | B button |
-/// | 0x4000 | X | X button |
-/// | 0x8000 | Y | Y button |
-/// | 0x0100 | LB (Left Shoulder) | Left bumper |
-/// | 0x0200 | RB (Right Shoulder) | Right bumper |
-/// | 0x0040 | L_THUMB | Left stick click |
-/// | 0x0080 | R_THUMB | Right stick click |
-/// | 0x0010 | START | Start button |
-/// | 0x0020 | BACK | Back button |
-/// | 0x0400 | GUIDE | Guide/Home button |
-/// | 0x0001 | DPAD_UP | D-pad up |
-/// | 0x0002 | DPAD_DOWN | D-pad down |
-/// | 0x0004 | DPAD_LEFT | D-pad left |
-/// | 0x0008 | DPAD_RIGHT | D-pad right |
-/// DualShock 4 buttons (aliases to Xbox buttons):
-/// | Value | Button | Xbox Equivalent | Description |
-/// |---------|-----------|-----------------|---------------------------|
-/// | 0x1000 | CROSS | A | Cross (X) button |
-/// | 0x2000 | CIRCLE | B | Circle button |
-/// | 0x4000 | SQUARE | X | Square button |
-/// | 0x8000 | TRIANGLE | Y | Triangle button |
-/// | 0x0100 | L1 | LB | L1 button |
-/// | 0x0200 | R1 | RB | R1 button |
-/// | 0x0040 | L3 | L_THUMB | Left stick click |
-/// | 0x0080 | R3 | R_THUMB | Right stick click |
-/// | 0x0010 | OPTIONS | START | Options button |
-/// | 0x0020 | SHARE | BACK | Share button |
-/// | 0x10000 | PS | - | PS button (DS4 special) |
-/// | 0x20000 | TOUCHPAD | - | Touchpad click (DS4 only) |
+/// Virtual gamepad button codes for / /
+///
+/// Use these values with MaaControllerPostClickKey, MaaControllerPostKeyDown, MaaControllerPostKeyUp.
+///
Values are based on XUSB (Xbox 360) button flags. DS4 face buttons are mapped to Xbox equivalents.
+///
+/// Xbox 360 buttons:
+///
+/// | Value | Button | Description |
+///
|---------|---------------------|------------------------|
+///
| 0x1000 | A | A button |
+///
| 0x2000 | B | B button |
+///
| 0x4000 | X | X button |
+///
| 0x8000 | Y | Y button |
+///
| 0x0100 | LB (Left Shoulder) | Left bumper |
+///
| 0x0200 | RB (Right Shoulder) | Right bumper |
+///
| 0x0040 | L_THUMB | Left stick click |
+///
| 0x0080 | R_THUMB | Right stick click |
+///
| 0x0010 | START | Start button |
+///
| 0x0020 | BACK | Back button |
+///
| 0x0400 | GUIDE | Guide/Home button |
+///
| 0x0001 | DPAD_UP | D-pad up |
+///
| 0x0002 | DPAD_DOWN | D-pad down |
+///
| 0x0004 | DPAD_LEFT | D-pad left |
+///
| 0x0008 | DPAD_RIGHT | D-pad right |
+///
+///
+/// DualShock 4 buttons (aliases to Xbox buttons):
+///
+/// | Value | Button | Xbox Equivalent | Description |
+///
|---------|-----------|-----------------|---------------------------|
+///
| 0x1000 | CROSS | A | Cross (X) button |
+///
| 0x2000 | CIRCLE | B | Circle button |
+///
| 0x4000 | SQUARE | X | Square button |
+///
| 0x8000 | TRIANGLE | Y | Triangle button |
+///
| 0x0100 | L1 | LB | L1 button |
+///
| 0x0200 | R1 | RB | R1 button |
+///
| 0x0040 | L3 | L_THUMB | Left stick click |
+///
| 0x0080 | R3 | R_THUMB | Right stick click |
+///
| 0x0010 | OPTIONS | START | Options button |
+///
| 0x0020 | SHARE | BACK | Share button |
+///
| 0x10000 | PS | - | PS button (DS4 special) |
+///
| 0x20000 | TOUCHPAD | - | Touchpad click (DS4 only) |
+///
+///
///
[Flags]
public enum GamepadButton : System.UInt64
diff --git a/src/MaaFramework.Binding/Enums/Controllers/GamepadTouch.cs b/src/MaaFramework.Binding/Enums/Controllers/GamepadTouch.cs
index af7722b..46af7b8 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/GamepadTouch.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/GamepadTouch.cs
@@ -13,21 +13,27 @@
namespace MaaFramework.Binding;
///
-/// Virtual gamepad touch contact definitions for touch_down/touch_move/touch_up
-/// For gamepad controller, the touch functions are repurposed for analog inputs:
-/// - x, y: Analog stick position
-/// - pressure: Trigger value (0~255)
-/// Contact mapping:
-/// | Contact | Input | x range | y range | pressure | Description |
-/// |---------------|-----------------|---------------|---------------|------------|----------------------------------|
-/// | LeftStick | Left Stick | -32768~32767 | -32768~32767 | ignored | Left analog stick X/Y position |
-/// | RightStick | Right Stick | -32768~32767 | -32768~32767 | ignored | Right analog stick X/Y position |
-/// | LeftTrigger | Left Trigger | ignored | ignored | 0~255 | Left trigger (LT/L2) value |
-/// | RightTrigger | Right Trigger | ignored | ignored | 0~255 | Right trigger (RT/R2) value |
-/// Usage:
-/// - touch_down(contact, x, y, pressure): Start analog input
-/// - touch_move(contact, x, y, pressure): Update analog input position/value
-/// - touch_up(contact): Release/reset analog input to center/zero
+/// Virtual gamepad touch contact definitions for / /
+///
+/// For gamepad controller, the touch functions are repurposed for analog inputs:
+///
- x, y: Analog stick position
+///
- pressure: Trigger value (0~255)
+///
+/// Contact mapping:
+///
+/// | Contact | Input | x range | y range | pressure | Description |
+///
|---------------|-----------------|---------------|---------------|------------|----------------------------------|
+///
| LeftStick | Left Stick | -32768~32767 | -32768~32767 | ignored | Left analog stick X/Y position |
+///
| RightStick | Right Stick | -32768~32767 | -32768~32767 | ignored | Right analog stick X/Y position |
+///
| LeftTrigger | Left Trigger | ignored | ignored | 0~255 | Left trigger (LT/L2) value |
+///
| RightTrigger | Right Trigger | ignored | ignored | 0~255 | Right trigger (RT/R2) value |
+///
+///
+/// Usage:
+///
- touch_down(contact, x, y, pressure): Start analog input
+///
- touch_move(contact, x, y, pressure): Update analog input position/value
+///
- touch_up(contact): Release/reset analog input to center/zero
+///
///
public enum GamepadTouch : System.UInt64
{
diff --git a/src/MaaFramework.Binding/Enums/Controllers/GamepadType.cs b/src/MaaFramework.Binding/Enums/Controllers/GamepadType.cs
index 267482f..e219fe3 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/GamepadType.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/GamepadType.cs
@@ -14,11 +14,15 @@ namespace MaaFramework.Binding;
///
/// Virtual gamepad type.
-/// Select ONE type only.
-/// | Type | Description |
-/// |---------------|------------------------------------------------|
-/// | Xbox360 | Microsoft Xbox 360 Controller (wired) |
-/// | DualShock4 | Sony DualShock 4 Controller (wired) |
+///
+/// Select ONE type only.
+///
+/// | Type | Description |
+///
|---------------|------------------------------------------------|
+///
| Xbox360 | Microsoft Xbox 360 Controller (wired) |
+///
| DualShock4 | Sony DualShock 4 Controller (wired) |
+///
+///
///
public enum GamepadType : System.UInt64
{
diff --git a/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs b/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
index 22b8571..182e239 100644
--- a/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
+++ b/src/MaaFramework.Binding/Enums/Controllers/Win32InputMethod.cs
@@ -14,27 +14,34 @@ namespace MaaFramework.Binding;
///
/// Win32 input method
+///
/// No bitwise OR, select ONE method only.
+///
/// No default value. Client should choose one as default.
+///
/// Different applications process input differently, there is no universal solution.
-/// | Method | Compatibility | Require Admin | Seize Mouse | Background Support | Notes |
-/// |--------------------------|---------------|---------------|--------------|--------------------|------------------------------------------|
-/// | Seize | High | No | Yes | No | |
-/// | SendMessage | Medium | Maybe | No | Yes | |
-/// | PostMessage | Medium | Maybe | No | Yes | |
-/// | LegacyEvent | Low | No | Yes | No | |
-/// | PostThreadMessage | Low | Maybe | No | Yes | |
-/// | SendMessageWithCursorPos | Medium | Maybe | Briefly | Yes | Moves cursor to target position, then restores |
-/// | PostMessageWithCursorPos | Medium | Maybe | Briefly | Yes | Moves cursor to target position, then restores |
-/// | SendMessageWithWindowPos | Medium | Maybe | No | Yes | Moves window to align target with cursor, then restores |
-/// | PostMessageWithWindowPos | Medium | Maybe | No | Yes | Moves window to align target with cursor, then restores |
-/// Note:
-/// - Admin rights mainly depend on the target application's privilege level.
-/// If the target runs as admin, MaaFramework should also run as admin for compatibility.
-/// - "WithCursorPos" methods briefly move the cursor to target position, send message,
-/// then restore cursor position. This "briefly" seizes the mouse but won't block user operations.
-/// - "WithWindowPos" methods briefly move the window so the target aligns with the current cursor
-/// position, send message, then restore the window position. The cursor is not moved.
+///
+/// | Method | Compatibility | Require Admin | Seize Mouse | Background Support | Notes |
+///
|--------------------------|---------------|---------------|--------------|--------------------|------------------------------------------|
+///
| Seize | High | No | Yes | No | |
+///
| SendMessage | Medium | Maybe | No | Yes | |
+///
| PostMessage | Medium | Maybe | No | Yes | |
+///
| LegacyEvent | Low | No | Yes | No | |
+///
| PostThreadMessage | Low | Maybe | No | Yes | |
+///
| SendMessageWithCursorPos | Medium | Maybe | Briefly | Yes | Moves cursor to target position, then restores |
+///
| PostMessageWithCursorPos | Medium | Maybe | Briefly | Yes | Moves cursor to target position, then restores |
+///
| SendMessageWithWindowPos | Medium | Maybe | No | Yes | Moves window to align target with cursor, then restores |
+///
| PostMessageWithWindowPos | Medium | Maybe | No | Yes | Moves window to align target with cursor, then restores |
+///
+///
+/// Note:
+///
- Admin rights mainly depend on the target application's privilege level.
+///
If the target runs as admin, MaaFramework should also run as admin for compatibility.
+///
- "WithCursorPos" methods briefly move the cursor to target position, send message,
+///
then restore cursor position. This "briefly" seizes the mouse but won't block user operations.
+///
- "WithWindowPos" methods briefly move the window so the target aligns with the current cursor
+///
position, send message, then restore the window position. The cursor is not moved.
+///
///
public enum Win32InputMethod : System.UInt64
{