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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/articles/overview-of-wrapper-and-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ IMaaDisposable Derived:
| MaaDbgController.ctor() | `MaaDbgControllerCreate` <br> `MaaControllerAddSink` |
| MaaPlayCoverController.ctor() | `MaaPlayCoverControllerCreate` <br> `MaaControllerAddSink` |
| MaaGamepadController.ctor() | `MaaGamepadControllerCreate` <br> `MaaControllerAddSink` |
| MaaWlRootsController.ctor() | `MaaWlRootsControllerCreate` <br> `MaaControllerAddSink` |
| IDisposable.Dispose() | `MaaControllerDestroy` |
| IMaaOption.SetOption() | `MaaControllerSetOption` |
| IMaaController.LinkStart() | `MaaControllerPostConnection` |
Expand All @@ -105,6 +106,7 @@ IMaaDisposable Derived:
| IMaaController.TouchUp() | `MaaControllerPostTouchUp` |
| IMaaController.Screencap() | `MaaControllerPostScreencap` |
| IMaaController.Scroll() | `MaaControllerPostScroll` |
| IMaaController.Inactive() | `MaaControllerPostInactive` |
| IMaaController.Shell() | `MaaControllerPostShell` |
| IMaaController.GetShellOutput() | `MaaControllerGetShellOutput` |
| IMaaPost.GetStatus() | `MaaControllerStatus` |
Expand All @@ -113,6 +115,7 @@ IMaaDisposable Derived:
| IMaaController.GetCachedImage() | `MaaControllerCachedImage` |
| IMaaController.Uuid | `MaaControllerGetUuid` |
| IMaaController.GetResolution() | `MaaControllerGetResolution` |
| IMaaController.Info | `MaaControllerGetInfo` |
| IMaaDisposableHandle.Handle | *The MaaControllerHandle.* |

## MaaTasker : IMaaTasker
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Maa.AgentBinary" Version="1.2.0" />
<PackageVersion Include="Maa.Framework.Runtimes" Version="5.6.0" />
<PackageVersion Include="Maa.Framework.Runtimes" Version="5.8.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" Condition="'$(TargetFramework)' != 'net7.0'" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" Condition="'$(TargetFramework)' == 'net7.0'" />
<PackageVersion Include="MSTest.TestAdapter" Version="3.10.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public record ControllerActionDetail(
[property: JsonPropertyName("ctrl_id")] int ControllerId,
[property: JsonPropertyName("uuid")] string Uuid,
[property: JsonPropertyName("action")] string Action,
[property: JsonPropertyName("param")] JsonElement Param
[property: JsonPropertyName("param")] JsonElement Param,
[property: JsonPropertyName("info")] JsonElement Info
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ControllerActionDetail now requires an info JSON field, but MaaMsg.Controller.Action's documented payload (auto-generated in MaaMsg.cs) still lists only { ctrl_id, uuid, action, param }. If info may be absent (e.g., older runtimes), deserialization into a non-nullable JsonElement can be ambiguous (default ValueKind.Undefined). Consider making Info nullable (JsonElement?) and/or updating the MaaMsg payload documentation/generator to match the new schema.

Suggested change
[property: JsonPropertyName("info")] JsonElement Info
[property: JsonPropertyName("info")] JsonElement? Info

Copilot uses AI. Check for mistakes.
);

/// <inheritdoc cref="MaaMsg.Tasker.Task.Prefix"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ public static partial class MaaController
[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);

[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostShell(MaaControllerHandle ctrl, string cmd, long timeout);

Expand All @@ -109,6 +112,9 @@ public static partial class MaaController
[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);

/// <summary>
/// Create a virtual gamepad controller for Windows.
/// </summary>
Expand Down Expand Up @@ -154,6 +160,10 @@ public static partial class MaaController
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool MaaControllerGetResolution(MaaControllerHandle ctrl, out int width, out int height);

[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
public static partial bool MaaControllerGetInfo(MaaControllerHandle ctrl, MaaStringBufferHandle buffer);

[Obsolete]
[LibraryImport("MaaFramework", StringMarshalling = StringMarshalling.Utf8)]
public static partial MaaCtrlId MaaControllerPostPressKey(MaaControllerHandle ctrl, int keycode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 InactiveDelegate Inactive = (nint transArg) => managed.Inactive();
public GetInfoDelegate GetInfo = (nint transArg, MaaStringBufferHandle buffer) => managed.GetInfo(new MaaStringBuffer(buffer));
};

/// <summary>
Expand Down Expand Up @@ -139,6 +141,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 Inactive = Marshal.GetFunctionPointerForDelegate(delegates.Inactive);
public nint GetInfo = Marshal.GetFunctionPointerForDelegate(delegates.GetInfo);
}

[return: MarshalAs(UnmanagedType.U1)]
Expand Down Expand Up @@ -213,4 +217,12 @@ private sealed class Unmanaged(Delegates delegates)
[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool ScrollDelegate(int dx, int dy, nint transArg);

[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool InactiveDelegate(nint transArg);

[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool GetInfoDelegate(nint transArg, MaaStringBufferHandle buffer);
}
20 changes: 20 additions & 0 deletions src/MaaFramework.Binding.Native/MaaController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
/// <remarks>
/// Wrapper of <see cref="MaaControllerPostPressKey"/>.
/// </remarks>
[Obsolete($"Use {nameof(ClickKey)}() instead.")]

Check warning on line 118 in src/MaaFramework.Binding.Native/MaaController.cs

View workflow job for this annotation

GitHub Actions / build

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)

Check warning on line 118 in src/MaaFramework.Binding.Native/MaaController.cs

View workflow job for this annotation

GitHub Actions / build

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)
public MaaJob PressKey(int keyCode)
=> CreateJob(MaaControllerPostPressKey(Handle, keyCode));

Expand Down Expand Up @@ -196,6 +196,13 @@
public MaaJob Scroll(int dx, int dy)
=> CreateJob(MaaControllerPostScroll(Handle, dx, dy));

/// <inheritdoc/>
/// <remarks>
/// Wrapper of <see cref="MaaControllerPostInactive"/>.
/// </remarks>
public MaaJob Inactive()
=> CreateJob(MaaControllerPostInactive(Handle));

/// <inheritdoc/>
/// <remarks>
/// Wrapper of <see cref="MaaControllerPostShell"/>.
Expand All @@ -214,7 +221,7 @@
/// <remarks>
/// Wrapper of <see cref="MaaControllerStatus"/>.
/// </remarks>
[Obsolete("Deprecated from v4.5.0.")]

Check warning on line 224 in src/MaaFramework.Binding.Native/MaaController.cs

View workflow job for this annotation

GitHub Actions / build

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)

Check warning on line 224 in src/MaaFramework.Binding.Native/MaaController.cs

View workflow job for this annotation

GitHub Actions / build

Do not forget to remove this deprecated code someday. (https://rules.sonarsource.com/csharp/RSPEC-1133)
public MaaJobStatus GetStatus(MaaJob job)
{
ArgumentNullException.ThrowIfNull(job);
Expand Down Expand Up @@ -287,4 +294,17 @@
/// </remarks>
public bool GetResolution(out int width, out int height)
=> MaaControllerGetResolution(Handle, out width, out height);

/// <inheritdoc/>
/// <remarks>
/// Wrapper of <see cref="MaaControllerGetInfo"/>.
/// </remarks>
public string? Info
{
get
{
_ = MaaStringBuffer.TryGetValue(out var info, h => MaaControllerGetInfo(Handle, h));
return info;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static MaaFramework.Binding.Interop.Native.MaaController;

namespace MaaFramework.Binding;

/// <summary>
/// A wrapper class providing a reference implementation for <see cref="MaaWlRootsControllerCreate"/>.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class MaaWlRootsController : MaaController
{
#pragma warning disable IDE0032 // 使用自动属性
private readonly string _debugWlrSocketPath;
#pragma warning restore IDE0032 // 使用自动属性

[ExcludeFromCodeCoverage(Justification = "Debugger display.")]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => IsInvalid
? $"Invalid {GetType().Name}"
: $"{GetType().Name} {{ WlrSocketPath = {_debugWlrSocketPath} }}";

/// <summary>
/// Creates a <see cref="MaaWlRootsController"/> instance.
/// </summary>
/// <param name="wlrSocketPath">The wayland socket path (e.g., "/run/user/1000/wayland-0").</param>
/// <param name="link">Executes <see cref="IMaaController.LinkStart"/> if <see cref="LinkOption.Start"/>; otherwise, not link.</param>
/// <param name="check">Checks LinkStart().Wait() status if <see cref="CheckStatusOption.ThrowIfNotSucceeded"/>; otherwise, not check.</param>
/// <remarks>
/// Wrapper of <see cref="MaaWlRootsControllerCreate"/>.
/// <para>This controller is designed for wlroots on Linux.</para>
/// </remarks>
/// <exception cref="ArgumentException"/>
/// <exception cref="MaaJobStatusException"/>
public MaaWlRootsController(string wlrSocketPath, LinkOption link = LinkOption.Start, CheckStatusOption check = CheckStatusOption.ThrowIfNotSucceeded)
{
ArgumentException.ThrowIfNullOrEmpty(wlrSocketPath);

var handle = MaaWlRootsControllerCreate(wlrSocketPath);
_ = MaaControllerAddSink(handle, MaaEventCallback, (nint)MaaHandleType.Controller);
SetHandle(handle, needReleased: true);

_debugWlrSocketPath = wlrSocketPath;

if (link == LinkOption.Start)
LinkStartOnConstructed(check, wlrSocketPath);
}
}

This file was deleted.

4 changes: 2 additions & 2 deletions src/MaaFramework.Binding.UnitTests/Test_Buffers.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using MaaFramework.Binding.Abstractions;
using MaaFramework.Binding.Buffers;
Expand Down Expand Up @@ -281,8 +281,8 @@
CollectionAssert.AreEqual(
encodedDataArray, funcArray);

Assert.IsTrue(img.Buffer.TryGetEncodedData(out encodedDataStream)
&& img.Buffer.TryGetEncodedData(out encodedDataSpan));
Assert.IsTrue(buffer.TryGetEncodedData(out encodedDataStream)
&& buffer.TryGetEncodedData(out encodedDataSpan));
Assert.IsTrue(
MaaImageBuffer.TrySetEncodedData(encodedDataStream, handle
=> MaaImageBuffer.TryGetEncodedData(handle, out funcArray)));
Expand Down
6 changes: 3 additions & 3 deletions src/MaaFramework.Binding.UnitTests/Test_Common.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
using MaaFramework.Binding.Abstractions;
using MaaFramework.Binding.Notification;

Expand All @@ -17,10 +17,10 @@
internal static string Address { get; set; } = string.Empty;

internal static string DebugPath { get; set; } = Path.GetFullPath("./debug");
internal static string BundlePath { get; set; } = Path.GetFullPath("./SampleResource");
internal static string BundlePath { get; set; } = Path.GetFullPath("./SampleResource/a_bundle");
internal static string AgentPath { get; set; } = Path.GetFullPath($"./MaaAgentBinary");
internal static string AdbConfig { get; set; } = File.ReadAllText(Path.GetFullPath($"{BundlePath}/controller_config.json"));
internal static string ImagePath { get; set; } = Path.Join(BundlePath, "empty_1920x1080.png");
internal static string AdbConfig { get; set; } = File.ReadAllText(Path.GetFullPath($"./SampleResource/controller_config.json"));
internal static string ImagePath { get; set; } = Path.GetFullPath("./SampleResource/empty_1920x1080.png");

private static void InitializeInfo(TestContext testContext)
{
Expand Down
86 changes: 81 additions & 5 deletions src/MaaFramework.Binding.UnitTests/Test_Custom.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using MaaFramework.Binding.Abstractions;
using MaaFramework.Binding.Buffers;
using MaaFramework.Binding.Custom;
using System.Text.Json;

namespace MaaFramework.Binding.UnitTests;

Expand Down Expand Up @@ -45,11 +46,41 @@
Assert.AreEqual(RecognitionParam, args.RecognitionParam);

var cloneContext = (context as ICloneable).Clone() as IMaaContext;
cloneContext = cloneContext?.Clone();
cloneContext = cloneContext!.Clone();
#if MAA_NATIVE
cloneContext = (cloneContext as MaaContext)?.Clone();
cloneContext = (cloneContext as MaaContext)!.Clone();
#endif
Assert.IsNotNull(cloneContext);

Assert.IsFalse(
context.IsCancellationRequested);
Assert.IsTrue(
context.WaitFreezes(TimeSpan.FromSeconds(1)));

Assert.IsFalse(
context.GetAnchor(DefaultAnchorName, out var nodeName));
Assert.IsNull(
nodeName);
Assert.IsTrue(
context.SetAnchor(DefaultAnchorName, DefaultAnchorNodeName));
Assert.IsTrue(
context.GetAnchor(DefaultAnchorName, out nodeName));
Assert.AreEqual(
DefaultAnchorNodeName, nodeName);

Assert.IsNotNull(
context.RunTask(DefaultAnchorNodeName));
Assert.IsTrue(
context.GetHitCount(DefaultAnchorNodeName, out var hitCount));
Assert.AreNotEqual<ulong>(
0, hitCount);
Assert.IsTrue(
context.ClearHitCount(DefaultAnchorNodeName));
Assert.IsTrue(
context.GetHitCount(DefaultAnchorNodeName, out hitCount));
Assert.AreEqual<ulong>(
0, hitCount);

Assert.IsNull(
cloneContext.RunRecognition(DiffEntry, args.Image));
if (!context.Tasker.IsStateless)
Expand All @@ -73,6 +104,11 @@
recognitionDetail?.HitBox);


using var recoDirectDetail =
context.RunRecognitionDirect(RecoDirectType, RecoDirectParam, args.Image);
Assert.IsNotNull(
recoDirectDetail?.HitBox);

Assert.IsTrue(
cloneContext.OverridePipeline(DiffParam));
Assert.AreEqual(
Expand All @@ -90,8 +126,21 @@
Assert.IsTrue(
cloneContext.GetNodeData(DiffEntry, out data));
Assert.IsNotNull(data);
Assert.IsTrue(
data.Contains($"\"next\":[\"{DiffEntry}\"]"));

using var document = JsonDocument.Parse(data);
var root = document.RootElement;
Assert.IsTrue(root.TryGetProperty("next", out var nextElement),
"Expected JSON to contain a 'next' property.");
Assert.AreEqual(JsonValueKind.Array, nextElement.ValueKind,
"Expected 'next' to be a JSON array.");
var containsDiffEntry = nextElement
.EnumerateArray()
.Any(element =>
element.ValueKind == JsonValueKind.Object &&
element.TryGetProperty("name", out var nameProperty) &&
nameProperty.GetString() == DiffEntry);
Assert.IsTrue(containsDiffEntry,
$"Expected 'next' array to contain an element with name '{DiffEntry}'.");


Assert.IsTrue(
Expand All @@ -100,7 +149,7 @@
results.Detail.TrySetValue(recognitionDetail.Detail));
// return ret;

// Using in assert
// Assert in other testings
Detail = recognitionDetail.Detail;
Box = $"{results.Box.X}{results.Box.Y}{results.Box.Width}{results.Box.Height}";

Expand All @@ -120,6 +169,21 @@
}
""";

internal const string RecoDirectType = "ColorMatch";
internal const string RecoDirectParam = """
{
"recognition": "ColorMatch",
"lower": [100, 100, 100],
"upper": [255, 255, 255]
}
""";

internal const string ActionDirectType = "Click";
internal const string ActionDirectParam = "{}";

internal const string DefaultAnchorName = "DefaultAnchorName";
internal const string DefaultAnchorNodeName = "EmptyNode";

internal sealed class TestAction : IMaaCustomAction
{
public string Name { get; set; } = nameof(TestAction);
Expand All @@ -136,6 +200,11 @@
context.RunAction(DiffEntry, args.RecognitionBox, args.RecognitionDetail.Detail, DiffParam);
Assert.IsNotNull(
actionDetail);

using var actionDirectDetail =
context.RunActionDirect(ActionDirectType, ActionDirectParam, args.RecognitionBox, args.RecognitionDetail.Detail);
Assert.IsNotNull(
actionDirectDetail);
return true;
}
}
Expand Down Expand Up @@ -232,6 +301,13 @@
=> c.KeyUp(keycode).Wait().IsSucceeded();
public bool Scroll(int dx, int dy)
=> c.Scroll(dx, dy).Wait().IsSucceeded();
public bool Inactive()
=> c.Inactive().Wait().IsSucceeded();
public bool GetInfo(IMaaStringBuffer buffer)
{
var info = c.Info;
return info is not null && buffer.TrySetValue(info);
}
}

internal sealed class TestInvalidResource : IMaaCustom
Expand Down
Loading
Loading