diff --git a/Avalonia.sln b/Avalonia.sln
index c995888350..98436f6896 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -301,6 +301,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Vulkan", "src\Aval
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.RenderTests.WpfCompare", "tests\Avalonia.RenderTests.WpfCompare\Avalonia.RenderTests.WpfCompare.csproj", "{9AE1B827-21AC-4063-AB22-C8804B7F931E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Automation", "src\Windows\Avalonia.Win32.Automation\Avalonia.Win32.Automation.csproj", "{0097673D-DBCE-476E-82FE-E78A56E58AA2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -701,6 +703,10 @@ Global
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0097673D-DBCE-476E-82FE-E78A56E58AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0097673D-DBCE-476E-82FE-E78A56E58AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0097673D-DBCE-476E-82FE-E78A56E58AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0097673D-DBCE-476E-82FE-E78A56E58AA2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -788,6 +794,7 @@ Global
{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {0097673D-DBCE-476E-82FE-E78A56E58AA2} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/api/Avalonia.Win32.nupkg.xml b/api/Avalonia.Win32.nupkg.xml
new file mode 100644
index 0000000000..3ce897deda
--- /dev/null
+++ b/api/Avalonia.Win32.nupkg.xml
@@ -0,0 +1,214 @@
+
+
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.DockPosition
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IDockProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IExpandCollapseProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IGridItemProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IGridProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IInvokeProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IMultipleViewProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRangeValueProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRawElementProviderAdviseEvents
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragment
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragmentRoot
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple2
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IScrollItemProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IScrollProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ISelectionItemProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ISelectionProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ISynchronizedInputProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ITableItemProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ITableProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ITextProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ITextRangeProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IToggleProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ITransformProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IValueProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.IWindowProvider
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.NavigateDirection
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.ProviderOptions
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.RowOrColumnMajor
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.SupportedTextSelection
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.SynchronizedInputType
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.TextPatternRangeEndpoint
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.TextUnit
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.WindowInteractionState
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
+ CP0001
+ T:Avalonia.Win32.Interop.Automation.WindowVisualState
+ baseline/netstandard2.0/Avalonia.Win32.dll
+ target/netstandard2.0/Avalonia.Win32.dll
+
+
\ No newline at end of file
diff --git a/nukebuild/numerge.config b/nukebuild/numerge.config
index 71b77bee93..8af0af7ff0 100644
--- a/nukebuild/numerge.config
+++ b/nukebuild/numerge.config
@@ -23,6 +23,18 @@
"DoNotMergeDependencies": true
}
]
+ },
+ {
+ "Id": "Avalonia.Win32",
+ "MergeAll": false,
+ "IncomingIncludeAssetsOverride": "",
+ "Merge": [
+ {
+ "Id": "Avalonia.Win32.Automation",
+ "IgnoreMissingFrameworkBinaries": true,
+ "DoNotMergeDependencies": true
+ }
+ ]
}
]
}
diff --git a/packages/Avalonia/AvaloniaSingleProject.targets b/packages/Avalonia/AvaloniaSingleProject.targets
index 793f0e36eb..17b456f145 100644
--- a/packages/Avalonia/AvaloniaSingleProject.targets
+++ b/packages/Avalonia/AvaloniaSingleProject.targets
@@ -29,7 +29,7 @@
WinExe
- true
+ true
true
diff --git a/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj b/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj
index b3b48afcb9..fe8e9d4506 100644
--- a/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj
+++ b/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj
@@ -3,7 +3,6 @@
WinExe
$(AvsCurrentTargetFramework)
enable
- true
diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj
index 0021d5bb75..09e9455082 100644
--- a/src/Avalonia.Controls/Avalonia.Controls.csproj
+++ b/src/Avalonia.Controls/Avalonia.Controls.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.ExpandCollapse.cs
similarity index 71%
rename from src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs
rename to src/Windows/Avalonia.Win32.Automation/AutomationNode.ExpandCollapse.cs
index aaad9cb3ba..c28d000f56 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.ExpandCollapse.cs
@@ -1,14 +1,14 @@
using Avalonia.Automation;
using Avalonia.Automation.Provider;
-using UIA = Avalonia.Win32.Interop.Automation;
+using UIA = Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IExpandCollapseProvider
{
- ExpandCollapseState UIA.IExpandCollapseProvider.ExpandCollapseState
+ ExpandCollapseState UIA.IExpandCollapseProvider.GetExpandCollapseState()
{
- get => InvokeSync(x => x.ExpandCollapseState);
+ return InvokeSync(x => x.ExpandCollapseState);
}
void UIA.IExpandCollapseProvider.Expand() => InvokeSync(x => x.Expand());
diff --git a/src/Windows/Avalonia.Win32.Automation/AutomationNode.RangeValue.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.RangeValue.cs
new file mode 100644
index 0000000000..47e80a85a2
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.RangeValue.cs
@@ -0,0 +1,17 @@
+using Avalonia.Automation.Provider;
+using UIA = Avalonia.Win32.Automation.Interop;
+
+namespace Avalonia.Win32.Automation
+{
+ internal partial class AutomationNode : UIA.IRangeValueProvider
+ {
+ double UIA.IRangeValueProvider.GetValue() => InvokeSync(x => x.Value);
+ bool UIA.IRangeValueProvider.GetIsReadOnly() => InvokeSync(x => x.IsReadOnly);
+ double UIA.IRangeValueProvider.GetMaximum() => InvokeSync(x => x.Maximum);
+ double UIA.IRangeValueProvider.GetMinimum() => InvokeSync(x => x.Minimum);
+ double UIA.IRangeValueProvider.GetLargeChange() => 1;
+ double UIA.IRangeValueProvider.GetSmallChange() => 1;
+
+ public void SetValue(double value) => InvokeSync(x => x.SetValue(value));
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/AutomationNode.Scroll.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Scroll.cs
new file mode 100644
index 0000000000..3b54c4bdff
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Scroll.cs
@@ -0,0 +1,30 @@
+using Avalonia.Automation.Provider;
+using UIA = Avalonia.Win32.Automation.Interop;
+
+namespace Avalonia.Win32.Automation
+{
+ internal partial class AutomationNode : UIA.IScrollProvider, UIA.IScrollItemProvider
+ {
+ bool UIA.IScrollProvider.GetHorizontallyScrollable() => InvokeSync(x => x.HorizontallyScrollable);
+ double UIA.IScrollProvider.GetHorizontalScrollPercent() => InvokeSync(x => x.HorizontalScrollPercent);
+ double UIA.IScrollProvider.GetHorizontalViewSize() => InvokeSync(x => x.HorizontalViewSize);
+ bool UIA.IScrollProvider.GetVerticallyScrollable() => InvokeSync(x => x.VerticallyScrollable);
+ double UIA.IScrollProvider.GetVerticalScrollPercent() => InvokeSync(x => x.VerticalScrollPercent);
+ double UIA.IScrollProvider.GetVerticalViewSize() => InvokeSync(x => x.VerticalViewSize);
+
+ void UIA.IScrollProvider.Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount)
+ {
+ InvokeSync(x => x.Scroll(horizontalAmount, verticalAmount));
+ }
+
+ void UIA.IScrollProvider.SetScrollPercent(double horizontalPercent, double verticalPercent)
+ {
+ InvokeSync(x => x.SetScrollPercent(horizontalPercent, verticalPercent));
+ }
+
+ void UIA.IScrollItemProvider.ScrollIntoView()
+ {
+ InvokeSync(() => Peer.BringIntoView());
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Selection.cs
similarity index 56%
rename from src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs
rename to src/Windows/Avalonia.Win32.Automation/AutomationNode.Selection.cs
index 3751bb6476..0f40bea5b9 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Selection.cs
@@ -2,23 +2,23 @@
using System.Linq;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
-using UIA = Avalonia.Win32.Interop.Automation;
+using UIA = Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.ISelectionProvider, UIA.ISelectionItemProvider
{
- bool UIA.ISelectionProvider.CanSelectMultiple => InvokeSync(x => x.CanSelectMultiple);
- bool UIA.ISelectionProvider.IsSelectionRequired => InvokeSync(x => x.IsSelectionRequired);
- bool UIA.ISelectionItemProvider.IsSelected => InvokeSync(x => x.IsSelected);
-
- UIA.IRawElementProviderSimple? UIA.ISelectionItemProvider.SelectionContainer
+ bool UIA.ISelectionProvider.CanSelectMultiple() => InvokeSync(x => x.CanSelectMultiple);
+
+ bool UIA.ISelectionProvider.IsSelectionRequired() =>
+ InvokeSync(x => x.IsSelectionRequired);
+
+ bool UIA.ISelectionItemProvider.GetIsSelected() => InvokeSync(x => x.IsSelected);
+
+ UIA.IRawElementProviderSimple? UIA.ISelectionItemProvider.GetSelectionContainer()
{
- get
- {
- var peer = InvokeSync(x => x.SelectionContainer);
- return GetOrCreate(peer as AutomationPeer);
- }
+ var peer = InvokeSync(x => x.SelectionContainer);
+ return GetOrCreate(peer as AutomationPeer);
}
UIA.IRawElementProviderSimple[] UIA.ISelectionProvider.GetSelection()
@@ -28,7 +28,10 @@ namespace Avalonia.Win32.Automation
}
void UIA.ISelectionItemProvider.AddToSelection() => InvokeSync(x => x.AddToSelection());
- void UIA.ISelectionItemProvider.RemoveFromSelection() => InvokeSync(x => x.RemoveFromSelection());
+
+ void UIA.ISelectionItemProvider.RemoveFromSelection() =>
+ InvokeSync(x => x.RemoveFromSelection());
+
void UIA.ISelectionItemProvider.Select() => InvokeSync(x => x.Select());
}
}
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Toggle.cs
similarity index 59%
rename from src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs
rename to src/Windows/Avalonia.Win32.Automation/AutomationNode.Toggle.cs
index 08f4b62d83..ef302fec25 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Toggle.cs
@@ -1,11 +1,11 @@
using Avalonia.Automation.Provider;
-using UIA = Avalonia.Win32.Interop.Automation;
+using UIA = Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IToggleProvider
{
- ToggleState UIA.IToggleProvider.ToggleState => InvokeSync(x => x.ToggleState);
+ ToggleState UIA.IToggleProvider.GetToggleState() => InvokeSync(x => x.ToggleState);
void UIA.IToggleProvider.Toggle() => InvokeSync(x => x.Toggle());
}
}
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Value.cs
similarity index 59%
rename from src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs
rename to src/Windows/Avalonia.Win32.Automation/AutomationNode.Value.cs
index 204e159049..a4fcb03ed9 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.Value.cs
@@ -1,13 +1,13 @@
using System.Runtime.InteropServices;
using Avalonia.Automation.Provider;
-using UIA = Avalonia.Win32.Interop.Automation;
+using UIA = Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation
{
internal partial class AutomationNode : UIA.IValueProvider
{
- bool UIA.IValueProvider.IsReadOnly => InvokeSync(x => x.IsReadOnly);
- string? UIA.IValueProvider.Value => InvokeSync(x => x.Value);
+ bool UIA.IValueProvider.GetIsReadOnly() => InvokeSync(x => x.IsReadOnly);
+ string? UIA.IValueProvider.GetValue() => InvokeSync(x => x.Value);
void UIA.IValueProvider.SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value)
{
diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
similarity index 92%
rename from src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
rename to src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
index 00b4f179c7..e3c58c6673 100644
--- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs
+++ b/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
@@ -6,18 +6,26 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Threading;
-using Avalonia.Win32.Interop.Automation;
+using Avalonia.Win32.Automation.Interop;
using AAP = Avalonia.Automation.Provider;
+using UIA = Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation
{
- [ComVisible(true)]
+#if NET8_0_OR_GREATER
+ [GeneratedComClass]
+ internal partial class AutomationNode :
+#else
+#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Requires .NET COM interop")]
+#endif
internal partial class AutomationNode : MarshalByRefObject,
+#endif
IRawElementProviderSimple,
IRawElementProviderSimple2,
IRawElementProviderFragment,
@@ -44,7 +52,10 @@ namespace Avalonia.Win32.Automation
{ SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired },
{ SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection },
{ SelectionItemPatternIdentifiers.IsSelectedProperty, UiaPropertyId.SelectionItemIsSelected },
- { SelectionItemPatternIdentifiers.SelectionContainerProperty, UiaPropertyId.SelectionItemSelectionContainer }
+ {
+ SelectionItemPatternIdentifiers.SelectionContainerProperty,
+ UiaPropertyId.SelectionItemSelectionContainer
+ }
};
private static ConditionalWeakTable s_nodes = new();
@@ -65,9 +76,9 @@ namespace Avalonia.Win32.Automation
public AutomationPeer Peer { get; protected set; }
- public virtual Rect BoundingRectangle
+ public virtual Rect GetBoundingRectangle()
{
- get => InvokeSync(() =>
+ return InvokeSync(() =>
{
if (GetRoot() is RootAutomationNode root)
return root.ToScreen(Peer.GetBoundingRectangle());
@@ -75,15 +86,14 @@ namespace Avalonia.Win32.Automation
});
}
- public virtual IRawElementProviderFragmentRoot? FragmentRoot
+ public virtual IRawElementProviderFragmentRoot? GetFragmentRoot()
{
- get => InvokeSync(() => GetRoot());
+ return InvokeSync(() => GetRoot());
}
- public virtual IRawElementProviderSimple? HostRawElementProvider => null;
- public virtual ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider;
+ public virtual IRawElementProviderSimple? GetHostRawElementProvider() => null;
+ public virtual ProviderOptions GetProviderOptions() => ProviderOptions.ServerSideProvider;
- [return: MarshalAs(UnmanagedType.IUnknown)]
public virtual object? GetPatternProvider(int patternId)
{
AutomationNode? ThisIfPeerImplementsProvider() => Peer.GetProvider() is object ? this : null;
@@ -105,13 +115,15 @@ namespace Avalonia.Win32.Automation
public virtual object? GetPropertyValue(int propertyId)
{
- return (UiaPropertyId)propertyId switch
+ object? value = (UiaPropertyId)propertyId switch
{
UiaPropertyId.AcceleratorKey => InvokeSync(() => Peer.GetAcceleratorKey()),
UiaPropertyId.AccessKey => InvokeSync(() => Peer.GetAccessKey()),
UiaPropertyId.AutomationId => InvokeSync(() => Peer.GetAutomationId()),
UiaPropertyId.ClassName => InvokeSync(() => Peer.GetClassName()),
- UiaPropertyId.ClickablePoint => new[] { BoundingRectangle.Center.X, BoundingRectangle.Center.Y },
+ UiaPropertyId.ClickablePoint => GetBoundingRectangle() is var rect ?
+ new[] { rect.Center.X, rect.Center.Y } :
+ default,
UiaPropertyId.ControlType => InvokeSync(() => ToUiaControlType(Peer.GetAutomationControlType())),
UiaPropertyId.Culture => CultureInfo.CurrentCulture.LCID,
UiaPropertyId.FrameworkId => "Avalonia",
@@ -128,9 +140,16 @@ namespace Avalonia.Win32.Automation
UiaPropertyId.RuntimeId => _runtimeId,
_ => null,
};
+
+ if (value?.GetType().IsEnum == true)
+ {
+ return Convert.ToInt32(value!);
+ }
+
+ return value;
}
- public int[]? GetRuntimeId() => _runtimeId;
+ public int[] GetRuntimeId() => _runtimeId;
public virtual IRawElementProviderFragment? Navigate(NavigateDirection direction)
{
@@ -167,7 +186,9 @@ namespace Avalonia.Win32.Automation
public void SetFocus() => InvokeSync(() => Peer.SetFocus());
+#if NET6_0_OR_GREATER
[return: NotNullIfNotNull(nameof(peer))]
+#endif
public static AutomationNode? GetOrCreate(AutomationPeer? peer)
{
return peer is null ? null : s_nodes.GetValue(peer, Create);
diff --git a/src/Windows/Avalonia.Win32.Automation/Avalonia.Win32.Automation.csproj b/src/Windows/Avalonia.Win32.Automation/Avalonia.Win32.Automation.csproj
new file mode 100644
index 0000000000..e64233afa5
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Avalonia.Win32.Automation.csproj
@@ -0,0 +1,19 @@
+
+
+
+ $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IDockProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IDockProvider.cs
new file mode 100644
index 0000000000..c65e76366a
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IDockProvider.cs
@@ -0,0 +1,28 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Guid("70d46e77-e3a8-449d-913c-e30eb2afecdb")]
+internal enum DockPosition
+{
+ Top,
+ Left,
+ Bottom,
+ Right,
+ Fill,
+ None
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("159bc72c-4ad3-485e-9637-d7052edf0146")]
+internal partial interface IDockProvider
+{
+ void SetDockPosition(DockPosition dockPosition);
+ DockPosition GetDockPosition();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IExpandCollapseProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IExpandCollapseProvider.cs
new file mode 100644
index 0000000000..ee04a24ce7
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IExpandCollapseProvider.cs
@@ -0,0 +1,19 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Automation;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("d847d3a5-cab0-4a98-8c32-ecb45c59ad24")]
+internal partial interface IExpandCollapseProvider
+{
+ void Expand();
+ void Collapse();
+ ExpandCollapseState GetExpandCollapseState();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IGridItemProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IGridItemProvider.cs
new file mode 100644
index 0000000000..f764427417
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IGridItemProvider.cs
@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("d02541f1-fb81-4d64-ae32-f520f8a6dbd1")]
+internal partial interface IGridItemProvider
+{
+ int GetRow();
+ int GetColumn();
+ int GetRowSpan();
+ int GetColumnSpan();
+ IRawElementProviderSimple GetContainingGrid();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IGridProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IGridProvider.cs
new file mode 100644
index 0000000000..cfc295fa7d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IGridProvider.cs
@@ -0,0 +1,18 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("b17d6187-0907-464b-a168-0ef17a1572b1")]
+internal partial interface IGridProvider
+{
+ IRawElementProviderSimple? GetItem(int row, int column);
+ int GetRowCount();
+ int GetColumnCount();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IInvokeProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IInvokeProvider.cs
new file mode 100644
index 0000000000..7737a1bb74
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IInvokeProvider.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("54fcb24b-e18e-47a2-b4d3-eccbe77599a2")]
+internal partial interface IInvokeProvider
+{
+ void Invoke();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IMultipleViewProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IMultipleViewProvider.cs
new file mode 100644
index 0000000000..dcd0d35e74
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IMultipleViewProvider.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("6278cab1-b556-4a1a-b4e0-418acc523201")]
+internal partial interface IMultipleViewProvider
+{
+ [return: MarshalAs(UnmanagedType.BStr)]
+ string GetViewName(int viewId);
+ void SetCurrentView(int viewId);
+ int GetCurrentView();
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ int[] GetSupportedViews();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRangeValueProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRangeValueProvider.cs
new file mode 100644
index 0000000000..a8f921fa26
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRangeValueProvider.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("36dc7aef-33e6-4691-afe1-2be7274b3d33")]
+internal partial interface IRangeValueProvider
+{
+ void SetValue(double value);
+ double GetValue();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetIsReadOnly();
+
+ double GetMaximum();
+ double GetMinimum();
+ double GetLargeChange();
+ double GetSmallChange();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderAdviseEvents.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderAdviseEvents.cs
new file mode 100644
index 0000000000..9d2e16ab94
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderAdviseEvents.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("a407b27b-0f6d-4427-9292-473c7bf93258")]
+internal partial interface IRawElementProviderAdviseEvents
+{
+ void AdviseEventAdded(int eventId,
+#if NET8_0_OR_GREATER
+ [MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ int[] properties);
+
+ void AdviseEventRemoved(int eventId,
+#if NET8_0_OR_GREATER
+ [MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ int[] properties);
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragment.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragment.cs
new file mode 100644
index 0000000000..0bb56c8b68
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragment.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Guid("670c3006-bf4c-428b-8534-e1848f645122")]
+internal enum NavigateDirection
+{
+ Parent,
+ NextSibling,
+ PreviousSibling,
+ FirstChild,
+ LastChild,
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("f7063da8-8359-439c-9297-bbc5299a7d87")]
+internal partial interface IRawElementProviderFragment
+{
+ IRawElementProviderFragment? Navigate(NavigateDirection direction);
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ int[]? GetRuntimeId();
+ Rect GetBoundingRectangle();
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[]? GetEmbeddedFragmentRoots();
+ void SetFocus();
+ IRawElementProviderFragmentRoot? GetFragmentRoot();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragmentRoot.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragmentRoot.cs
new file mode 100644
index 0000000000..349e58b7b3
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderFragmentRoot.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("620ce2a5-ab8f-40a9-86cb-de3c75599b58")]
+internal partial interface IRawElementProviderFragmentRoot
+{
+ IRawElementProviderFragment? ElementProviderFromPoint(double x, double y);
+ IRawElementProviderFragment? GetFocus();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs
new file mode 100644
index 0000000000..c8c5d1ceee
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple.cs
@@ -0,0 +1,290 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Flags]
+internal enum ProviderOptions
+{
+ ClientSideProvider = 0x0001,
+ ServerSideProvider = 0x0002,
+ NonClientAreaProvider = 0x0004,
+ OverrideProvider = 0x0008,
+ ProviderOwnsSetFocus = 0x0010,
+ UseComThreading = 0x0020
+}
+
+internal enum UiaPropertyId
+{
+ RuntimeId = 30000,
+ BoundingRectangle,
+ ProcessId,
+ ControlType,
+ LocalizedControlType,
+ Name,
+ AcceleratorKey,
+ AccessKey,
+ HasKeyboardFocus,
+ IsKeyboardFocusable,
+ IsEnabled,
+ AutomationId,
+ ClassName,
+ HelpText,
+ ClickablePoint,
+ Culture,
+ IsControlElement,
+ IsContentElement,
+ LabeledBy,
+ IsPassword,
+ NativeWindowHandle,
+ ItemType,
+ IsOffscreen,
+ Orientation,
+ FrameworkId,
+ IsRequiredForForm,
+ ItemStatus,
+ IsDockPatternAvailable,
+ IsExpandCollapsePatternAvailable,
+ IsGridItemPatternAvailable,
+ IsGridPatternAvailable,
+ IsInvokePatternAvailable,
+ IsMultipleViewPatternAvailable,
+ IsRangeValuePatternAvailable,
+ IsScrollPatternAvailable,
+ IsScrollItemPatternAvailable,
+ IsSelectionItemPatternAvailable,
+ IsSelectionPatternAvailable,
+ IsTablePatternAvailable,
+ IsTableItemPatternAvailable,
+ IsTextPatternAvailable,
+ IsTogglePatternAvailable,
+ IsTransformPatternAvailable,
+ IsValuePatternAvailable,
+ IsWindowPatternAvailable,
+ ValueValue,
+ ValueIsReadOnly,
+ RangeValueValue,
+ RangeValueIsReadOnly,
+ RangeValueMinimum,
+ RangeValueMaximum,
+ RangeValueLargeChange,
+ RangeValueSmallChange,
+ ScrollHorizontalScrollPercent,
+ ScrollHorizontalViewSize,
+ ScrollVerticalScrollPercent,
+ ScrollVerticalViewSize,
+ ScrollHorizontallyScrollable,
+ ScrollVerticallyScrollable,
+ SelectionSelection,
+ SelectionCanSelectMultiple,
+ SelectionIsSelectionRequired,
+ GridRowCount,
+ GridColumnCount,
+ GridItemRow,
+ GridItemColumn,
+ GridItemRowSpan,
+ GridItemColumnSpan,
+ GridItemContainingGrid,
+ DockDockPosition,
+ ExpandCollapseExpandCollapseState,
+ MultipleViewCurrentView,
+ MultipleViewSupportedViews,
+ WindowCanMaximize,
+ WindowCanMinimize,
+ WindowWindowVisualState,
+ WindowWindowInteractionState,
+ WindowIsModal,
+ WindowIsTopmost,
+ SelectionItemIsSelected,
+ SelectionItemSelectionContainer,
+ TableRowHeaders,
+ TableColumnHeaders,
+ TableRowOrColumnMajor,
+ TableItemRowHeaderItems,
+ TableItemColumnHeaderItems,
+ ToggleToggleState,
+ TransformCanMove,
+ TransformCanResize,
+ TransformCanRotate,
+ IsLegacyIAccessiblePatternAvailable,
+ LegacyIAccessibleChildId,
+ LegacyIAccessibleName,
+ LegacyIAccessibleValue,
+ LegacyIAccessibleDescription,
+ LegacyIAccessibleRole,
+ LegacyIAccessibleState,
+ LegacyIAccessibleHelp,
+ LegacyIAccessibleKeyboardShortcut,
+ LegacyIAccessibleSelection,
+ LegacyIAccessibleDefaultAction,
+ AriaRole,
+ AriaProperties,
+ IsDataValidForForm,
+ ControllerFor,
+ DescribedBy,
+ FlowsTo,
+ ProviderDescription,
+ IsItemContainerPatternAvailable,
+ IsVirtualizedItemPatternAvailable,
+ IsSynchronizedInputPatternAvailable,
+ OptimizeForVisualContent,
+ IsObjectModelPatternAvailable,
+ AnnotationAnnotationTypeId,
+ AnnotationAnnotationTypeName,
+ AnnotationAuthor,
+ AnnotationDateTime,
+ AnnotationTarget,
+ IsAnnotationPatternAvailable,
+ IsTextPattern2Available,
+ StylesStyleId,
+ StylesStyleName,
+ StylesFillColor,
+ StylesFillPatternStyle,
+ StylesShape,
+ StylesFillPatternColor,
+ StylesExtendedProperties,
+ IsStylesPatternAvailable,
+ IsSpreadsheetPatternAvailable,
+ SpreadsheetItemFormula,
+ SpreadsheetItemAnnotationObjects,
+ SpreadsheetItemAnnotationTypes,
+ IsSpreadsheetItemPatternAvailable,
+ Transform2CanZoom,
+ IsTransformPattern2Available,
+ LiveSetting,
+ IsTextChildPatternAvailable,
+ IsDragPatternAvailable,
+ DragIsGrabbed,
+ DragDropEffect,
+ DragDropEffects,
+ IsDropTargetPatternAvailable,
+ DropTargetDropTargetEffect,
+ DropTargetDropTargetEffects,
+ DragGrabbedItems,
+ Transform2ZoomLevel,
+ Transform2ZoomMinimum,
+ Transform2ZoomMaximum,
+ FlowsFrom,
+ IsTextEditPatternAvailable,
+ IsPeripheral,
+ IsCustomNavigationPatternAvailable,
+ PositionInSet,
+ SizeOfSet,
+ Level,
+ AnnotationTypes,
+ AnnotationObjects,
+ LandmarkType,
+ LocalizedLandmarkType,
+ FullDescription,
+ FillColor,
+ OutlineColor,
+ FillType,
+ VisualEffects,
+ OutlineThickness,
+ CenterPoint,
+ Rotatation,
+ Size
+}
+
+internal enum UiaPatternId
+{
+ Invoke = 10000,
+ Selection,
+ Value,
+ RangeValue,
+ Scroll,
+ ExpandCollapse,
+ Grid,
+ GridItem,
+ MultipleView,
+ Window,
+ SelectionItem,
+ Dock,
+ Table,
+ TableItem,
+ Text,
+ Toggle,
+ Transform,
+ ScrollItem,
+ LegacyIAccessible,
+ ItemContainer,
+ VirtualizedItem,
+ SynchronizedInput,
+ ObjectModel,
+ Annotation,
+ Text2,
+ Styles,
+ Spreadsheet,
+ SpreadsheetItem,
+ Transform2,
+ TextChild,
+ Drag,
+ DropTarget,
+ TextEdit,
+ CustomNavigation
+};
+
+internal enum UiaControlTypeId
+{
+ Button = 50000,
+ Calendar,
+ CheckBox,
+ ComboBox,
+ Edit,
+ Hyperlink,
+ Image,
+ ListItem,
+ List,
+ Menu,
+ MenuBar,
+ MenuItem,
+ ProgressBar,
+ RadioButton,
+ ScrollBar,
+ Slider,
+ Spinner,
+ StatusBar,
+ Tab,
+ TabItem,
+ Text,
+ ToolBar,
+ ToolTip,
+ Tree,
+ TreeItem,
+ Custom,
+ Group,
+ Thumb,
+ DataGrid,
+ DataItem,
+ Document,
+ SplitButton,
+ Window,
+ Pane,
+ Header,
+ HeaderItem,
+ Table,
+ TitleBar,
+ Separator,
+ SemanticZoom,
+ AppBar
+};
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("d6dd68d1-86fd-4332-8666-9abedea2d24c")]
+internal partial interface IRawElementProviderSimple
+{
+ ProviderOptions GetProviderOptions();
+ [return: MarshalAs(UnmanagedType.Interface)]
+ object? GetPatternProvider(int patternId);
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(ComVariantMarshaller))]
+#endif
+ object? GetPropertyValue(int propertyId);
+ IRawElementProviderSimple? GetHostRawElementProvider();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple2.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple2.cs
new file mode 100644
index 0000000000..7bd48f4e78
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IRawElementProviderSimple2.cs
@@ -0,0 +1,26 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("a0a839a9-8da1-4a82-806a-8e0d44e79f56")]
+internal partial interface IRawElementProviderSimple2 : IRawElementProviderSimple
+{
+#if !NET8_0_OR_GREATER
+ // Hack for the legacy COM interop
+ // See https://learn.microsoft.com/en-us/dotnet/standard/native-interop/comwrappers-source-generation#derived-interfaces
+ new ProviderOptions GetProviderOptions();
+ [return: MarshalAs(UnmanagedType.Interface)]
+ new object? GetPatternProvider(int patternId);
+ [return: MarshalAs(UnmanagedType.Struct)]
+ new object? GetPropertyValue(int propertyId);
+ new IRawElementProviderSimple? GetHostRawElementProvider();
+#endif
+ void ShowContextMenu();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IScrollItemProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IScrollItemProvider.cs
new file mode 100644
index 0000000000..8e022c988d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IScrollItemProvider.cs
@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("2360c714-4bf1-4b26-ba65-9b21316127eb")]
+internal partial interface IScrollItemProvider
+{
+ void ScrollIntoView();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IScrollProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IScrollProvider.cs
new file mode 100644
index 0000000000..1113685592
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IScrollProvider.cs
@@ -0,0 +1,28 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Automation.Provider;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("b38b8077-1fc3-42a5-8cae-d40c2215055a")]
+internal partial interface IScrollProvider
+{
+ void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount);
+ void SetScrollPercent(double horizontalPercent, double verticalPercent);
+ double GetHorizontalScrollPercent();
+ double GetVerticalScrollPercent();
+ double GetHorizontalViewSize();
+ double GetVerticalViewSize();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetHorizontallyScrollable();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetVerticallyScrollable();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionItemProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionItemProvider.cs
new file mode 100644
index 0000000000..a4f4d56e54
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionItemProvider.cs
@@ -0,0 +1,23 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("2acad808-b2d4-452d-a407-91ff1ad167b2")]
+internal partial interface ISelectionItemProvider
+{
+ void Select();
+ void AddToSelection();
+ void RemoveFromSelection();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetIsSelected();
+
+ IRawElementProviderSimple? GetSelectionContainer();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionProvider.cs
new file mode 100644
index 0000000000..2a30c97f18
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ISelectionProvider.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("fb8b03af-3bdf-48d4-bd36-1a65793be168")]
+internal partial interface ISelectionProvider
+{
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetSelection();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool CanSelectMultiple();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool IsSelectionRequired();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ISynchronizedInputProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ISynchronizedInputProvider.cs
new file mode 100644
index 0000000000..75850461c3
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ISynchronizedInputProvider.cs
@@ -0,0 +1,27 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Guid("fdc8f176-aed2-477a-8c89-5604c66f278d")]
+internal enum SynchronizedInputType
+{
+ KeyUp = 0x01,
+ KeyDown = 0x02,
+ MouseLeftButtonUp = 0x04,
+ MouseLeftButtonDown = 0x08,
+ MouseRightButtonUp = 0x10,
+ MouseRightButtonDown = 0x20
+}
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("29db1a06-02ce-4cf7-9b42-565d4fab20ee")]
+internal partial interface ISynchronizedInputProvider
+{
+ void StartListening(SynchronizedInputType inputType);
+ void Cancel();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ITableItemProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ITableItemProvider.cs
new file mode 100644
index 0000000000..75bdf48bb8
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ITableItemProvider.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("b9734fa6-771f-4d78-9c90-2517999349cd")]
+internal partial interface ITableItemProvider
+{
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetRowHeaderItems();
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetColumnHeaderItems();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ITableProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ITableProvider.cs
new file mode 100644
index 0000000000..6acacbdf5d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ITableProvider.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Guid("15fdf2e2-9847-41cd-95dd-510612a025ea")]
+internal enum RowOrColumnMajor
+{
+ RowMajor,
+ ColumnMajor,
+ Indeterminate,
+}
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("9c860395-97b3-490a-b52a-858cc22af166")]
+internal partial interface ITableProvider
+{
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetRowHeaders();
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetColumnHeaders();
+ RowOrColumnMajor GetRowOrColumnMajor();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ITextProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ITextProvider.cs
new file mode 100644
index 0000000000..63a92ce547
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ITextProvider.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Flags]
+[Guid("3d9e3d8f-bfb0-484f-84ab-93ff4280cbc4")]
+internal enum SupportedTextSelection
+{
+ None,
+ Single,
+ Multiple,
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("3589c92c-63f3-4367-99bb-ada653b77cf2")]
+internal partial interface ITextProvider
+{
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ ITextRangeProvider[] GetSelection();
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ ITextRangeProvider[] GetVisibleRanges();
+ ITextRangeProvider RangeFromChild(IRawElementProviderSimple childElement);
+
+ ITextRangeProvider RangeFromPoint(double X, double Y);
+
+ ITextRangeProvider GetDocumentRange();
+ SupportedTextSelection GetSupportedTextSelection();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ITextRangeProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ITextRangeProvider.cs
new file mode 100644
index 0000000000..18f167a87a
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ITextRangeProvider.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Win32.Automation.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+internal enum TextPatternRangeEndpoint
+{
+ Start = 0,
+ End = 1,
+}
+
+internal enum TextUnit
+{
+ Character = 0,
+ Format = 1,
+ Word = 2,
+ Line = 3,
+ Paragraph = 4,
+ Page = 5,
+ Document = 6,
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("5347ad7b-c355-46f8-aff5-909033582f63")]
+internal partial interface ITextRangeProvider
+{
+ ITextRangeProvider Clone();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool Compare(ITextRangeProvider range);
+
+ int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange,
+ TextPatternRangeEndpoint targetEndpoint);
+
+ void ExpandToEnclosingUnit(TextUnit unit);
+
+ ITextRangeProvider FindAttribute(int attribute,
+#if NET8_0_OR_GREATER
+ [MarshalUsing(typeof(ComVariantMarshaller))]
+#endif
+ object value, [MarshalAs(UnmanagedType.Bool)] bool backward);
+
+ ITextRangeProvider FindText(
+ [MarshalAs(UnmanagedType.BStr)] string text,
+ [MarshalAs(UnmanagedType.Bool)] bool backward,
+ [MarshalAs(UnmanagedType.Bool)] bool ignoreCase);
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(ComVariantMarshaller))]
+#endif
+ object GetAttributeValue(int attribute);
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ double[] GetBoundingRectangles();
+ IRawElementProviderSimple GetEnclosingElement();
+ [return: MarshalAs(UnmanagedType.BStr)]
+ string GetText(int maxLength);
+ int Move(TextUnit unit, int count);
+ int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count);
+
+ void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange,
+ TextPatternRangeEndpoint targetEndpoint);
+
+ void Select();
+ void AddToSelection();
+ void RemoveFromSelection();
+ void ScrollIntoView([MarshalAs(UnmanagedType.Bool)] bool alignToTop);
+#if NET8_0_OR_GREATER
+ [return: MarshalUsing(typeof(SafeArrayMarshaller))]
+#endif
+ IRawElementProviderSimple[] GetChildren();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IToggleProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IToggleProvider.cs
new file mode 100644
index 0000000000..85dd3c0f97
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IToggleProvider.cs
@@ -0,0 +1,18 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using Avalonia.Automation.Provider;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("56d00bd0-c4f4-433c-a836-1a52a57e0892")]
+internal partial interface IToggleProvider
+{
+ void Toggle();
+ ToggleState GetToggleState();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/ITransformProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/ITransformProvider.cs
new file mode 100644
index 0000000000..baabaf3664
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/ITransformProvider.cs
@@ -0,0 +1,27 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("6829ddc4-4f91-4ffa-b86f-bd3e2987cb4c")]
+internal partial interface ITransformProvider
+{
+ void Move(double x, double y);
+ void Resize(double width, double height);
+ void Rotate(double degrees);
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetCanMove();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetCanResize();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetCanRotate();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IValueProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IValueProvider.cs
new file mode 100644
index 0000000000..6d7526c054
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IValueProvider.cs
@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("c7935180-6fb3-4201-b174-7df73adbf64a")]
+internal partial interface IValueProvider
+{
+ void SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value);
+
+ [return: MarshalAs(UnmanagedType.BStr)]
+ string? GetValue();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetIsReadOnly();
+}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/IWindowProvider.cs b/src/Windows/Avalonia.Win32.Automation/Interop/IWindowProvider.cs
new file mode 100644
index 0000000000..65cec9a1f5
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/IWindowProvider.cs
@@ -0,0 +1,52 @@
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Interop;
+
+[Guid("fdc8f176-aed2-477a-8c89-ea04cc5f278d")]
+internal enum WindowVisualState
+{
+ Normal,
+ Maximized,
+ Minimized
+}
+
+[Guid("65101cc7-7904-408e-87a7-8c6dbd83a18b")]
+internal enum WindowInteractionState
+{
+ Running,
+ Closing,
+ ReadyForUserInteraction,
+ BlockedByModalWindow,
+ NotResponding
+}
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(Options = ComInterfaceOptions.ManagedObjectWrapper)]
+#else
+[ComImport()]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("987df77b-db06-4d77-8f8a-86a9c3bb90b9")]
+internal partial interface IWindowProvider
+{
+ void SetVisualState(WindowVisualState state);
+ void Close();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool WaitForInputIdle(int milliseconds);
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetMaximizable();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetMinimizable();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetIsModal();
+
+ WindowVisualState GetVisualState();
+ WindowInteractionState GetInteractionState();
+
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool GetIsTopmost();
+}
diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs b/src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreProviderApi.cs
similarity index 53%
rename from src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs
rename to src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreProviderApi.cs
index bd939c56b7..36e6902846 100644
--- a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreProviderApi.cs
@@ -1,9 +1,9 @@
using System;
using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
-namespace Avalonia.Win32.Interop.Automation
+namespace Avalonia.Win32.Automation.Interop
{
- [ComVisible(true)]
[Guid("d8e55844-7043-4edc-979d-593cc6b4775e")]
internal enum AsyncContentLoadedState
{
@@ -12,7 +12,6 @@ namespace Avalonia.Win32.Interop.Automation
Completed,
}
- [ComVisible(true)]
[Guid("e4cfef41-071d-472c-a65c-c14f59ea81eb")]
internal enum StructureChangeType
{
@@ -63,29 +62,57 @@ namespace Avalonia.Win32.Interop.Automation
Changes
};
- internal static class UiaCoreProviderApi
+ internal static partial class UiaCoreProviderApi
{
public const int UIA_E_ELEMENTNOTENABLED = unchecked((int)0x80040200);
+#if NET7_0_OR_GREATER
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static partial bool UiaClientsAreListening();
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple? el);
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial int UiaHostProviderFromHwnd(IntPtr hwnd, [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider);
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial int UiaRaiseAutomationEvent(IRawElementProviderSimple? provider, int id);
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple? provider, int id, [MarshalUsing(typeof(ComVariantMarshaller))] object? oldValue, [MarshalUsing(typeof(ComVariantMarshaller))] object? newValue);
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial int UiaRaiseStructureChangedEvent(IRawElementProviderSimple? provider, StructureChangeType structureChangeType, int[]? runtimeId, int runtimeIdLen);
+
+ [LibraryImport("UIAutomationCore.dll", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial int UiaDisconnectProvider(IRawElementProviderSimple? provider);
+#else
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
public static extern bool UiaClientsAreListening();
-
+
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
- public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple? el);
+ public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam,
+ IRawElementProviderSimple? el);
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
- public static extern int UiaHostProviderFromHwnd(IntPtr hwnd, [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider);
+ public static extern int UiaHostProviderFromHwnd(IntPtr hwnd,
+ [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider);
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
public static extern int UiaRaiseAutomationEvent(IRawElementProviderSimple? provider, int id);
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
- public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple? provider, int id, object? oldValue, object? newValue);
+ public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple? provider, int id,
+ object? oldValue, object? newValue);
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
- public static extern int UiaRaiseStructureChangedEvent(IRawElementProviderSimple? provider, StructureChangeType structureChangeType, int[]? runtimeId, int runtimeIdLen);
+ public static extern int UiaRaiseStructureChangedEvent(IRawElementProviderSimple? provider,
+ StructureChangeType structureChangeType, int[]? runtimeId, int runtimeIdLen);
[DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)]
public static extern int UiaDisconnectProvider(IRawElementProviderSimple? provider);
+#endif
}
}
diff --git a/src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreTypesApi.cs b/src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreTypesApi.cs
new file mode 100644
index 0000000000..a8a83dfd57
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Interop/UiaCoreTypesApi.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Runtime.InteropServices;
+
+// Just to keep "netstandard2.0" build happy
+namespace System.Runtime.InteropServices.Marshalling
+{
+}
+
+namespace Avalonia.Win32.Automation.Interop
+{
+ internal static partial class UiaCoreTypesApi
+ {
+ internal enum AutomationIdType
+ {
+ Property,
+ Pattern,
+ Event,
+ ControlType,
+ TextAttribute
+ }
+
+ internal const int UIA_E_ELEMENTNOTENABLED = unchecked((int)0x80040200);
+ internal const int UIA_E_ELEMENTNOTAVAILABLE = unchecked((int)0x80040201);
+ internal const int UIA_E_NOCLICKABLEPOINT = unchecked((int)0x80040202);
+ internal const int UIA_E_PROXYASSEMBLYNOTLOADED = unchecked((int)0x80040203);
+
+ internal static bool IsNetComInteropAvailable
+ {
+ get
+ {
+#if NET8_0_OR_GREATER
+ return true;
+#else
+#if NET6_0_OR_GREATER
+ if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
+ {
+ return false;
+ }
+#endif
+ var comConfig =
+ AppContext.GetData("System.Runtime.InteropServices.BuiltInComInterop.IsSupported") as string;
+ return comConfig == null || bool.Parse(comConfig);
+#endif
+ }
+ }
+
+ internal static int UiaLookupId(AutomationIdType type, ref Guid guid)
+ {
+ return RawUiaLookupId(type, ref guid);
+ }
+
+#if NET7_0_OR_GREATER
+ [LibraryImport("UIAutomationCore.dll", EntryPoint = "UiaLookupId", StringMarshalling = StringMarshalling.Utf8)]
+ private static partial int RawUiaLookupId(AutomationIdType type, ref Guid guid);
+#else
+ [DllImport("UIAutomationCore.dll", EntryPoint = "UiaLookupId", CharSet = CharSet.Unicode)]
+ private static extern int RawUiaLookupId(AutomationIdType type, ref Guid guid);
+#endif
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Automation/InteropAutomationNode.cs b/src/Windows/Avalonia.Win32.Automation/InteropAutomationNode.cs
similarity index 56%
rename from src/Windows/Avalonia.Win32/Automation/InteropAutomationNode.cs
rename to src/Windows/Avalonia.Win32.Automation/InteropAutomationNode.cs
index 9ef8b79cfe..d6f698513c 100644
--- a/src/Windows/Avalonia.Win32/Automation/InteropAutomationNode.cs
+++ b/src/Windows/Avalonia.Win32.Automation/InteropAutomationNode.cs
@@ -1,16 +1,21 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
using Avalonia.Controls.Automation.Peers;
-using Avalonia.Win32.Interop.Automation;
+using Avalonia.Win32.Automation.Interop;
namespace Avalonia.Win32.Automation;
///
/// An automation node which serves as the root of an embedded native control automation tree.
///
-[RequiresUnreferencedCode("Requires .NET COM interop")]
-internal class InteropAutomationNode : AutomationNode, IRawElementProviderFragmentRoot
+#if NET8_0_OR_GREATER
+ [GeneratedComClass]
+#elif NET6_0_OR_GREATER
+ [RequiresUnreferencedCode("Requires .NET COM interop")]
+#endif
+internal partial class InteropAutomationNode : AutomationNode, IRawElementProviderFragmentRoot
{
private readonly IntPtr _handle;
@@ -20,21 +25,18 @@ internal class InteropAutomationNode : AutomationNode, IRawElementProviderFragme
_handle = peer.NativeControlHandle.Handle;
}
- public override Rect BoundingRectangle => default;
- public override IRawElementProviderFragmentRoot? FragmentRoot => null;
- public override ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider | ProviderOptions.OverrideProvider;
+ public override Rect GetBoundingRectangle() => default;
+ public override IRawElementProviderFragmentRoot? GetFragmentRoot() => null;
+ public override ProviderOptions GetProviderOptions() => ProviderOptions.ServerSideProvider | ProviderOptions.OverrideProvider;
public override object? GetPatternProvider(int patternId) => null;
public override object? GetPropertyValue(int propertyId) => null;
- public override IRawElementProviderSimple? HostRawElementProvider
+ public override IRawElementProviderSimple? GetHostRawElementProvider()
{
- get
- {
- var hr = UiaCoreProviderApi.UiaHostProviderFromHwnd(_handle, out var result);
- Marshal.ThrowExceptionForHR(hr);
- return result;
- }
+ var hr = UiaCoreProviderApi.UiaHostProviderFromHwnd(_handle, out var result);
+ Marshal.ThrowExceptionForHR(hr);
+ return result;
}
public override IRawElementProviderFragment? Navigate(NavigateDirection direction)
diff --git a/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariant.cs b/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariant.cs
new file mode 100644
index 0000000000..d5da462f8c
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariant.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.Win32.Automation.Marshalling;
+
+#if NET7_0_OR_GREATER
+// Oversimplified ComVariant implementation based on https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ComVariant.cs
+// Available
+[StructLayout(LayoutKind.Explicit)]
+internal struct ComVariant : IDisposable
+{
+ // VARIANT_BOOL constants.
+ internal const short VARIANT_TRUE = -1;
+ internal const short VARIANT_FALSE = 0;
+
+ // Most of the data types in the Variant are carried in _typeUnion
+ [FieldOffset(0)] private TypeUnion _typeUnion;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct TypeUnion
+ {
+ public ushort _vt;
+ public ushort _wReserved1;
+ public ushort _wReserved2;
+ public ushort _wReserved3;
+
+ public UnionTypes _unionTypes;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ private unsafe struct UnionTypes
+ {
+ [FieldOffset(0)] public sbyte _i1;
+ [FieldOffset(0)] public short _i2;
+ [FieldOffset(0)] public int _i4;
+ [FieldOffset(0)] public long _i8;
+ [FieldOffset(0)] public byte _ui1;
+ [FieldOffset(0)] public ushort _ui2;
+ [FieldOffset(0)] public uint _ui4;
+ [FieldOffset(0)] public ulong _ui8;
+ [FieldOffset(0)] public int _int;
+ [FieldOffset(0)] public uint _uint;
+ [FieldOffset(0)] public short _bool;
+ [FieldOffset(0)] public int _error;
+ [FieldOffset(0)] public float _r4;
+ [FieldOffset(0)] public double _r8;
+ [FieldOffset(0)] public long _cy;
+ [FieldOffset(0)] public double _date;
+ [FieldOffset(0)] public IntPtr _bstr;
+ [FieldOffset(0)] public IntPtr _unknown;
+ [FieldOffset(0)] public IntPtr _dispatch;
+ [FieldOffset(0)] public IntPtr _pvarVal;
+ [FieldOffset(0)] public IntPtr _byref;
+ [FieldOffset(0)] public SafeArrayRef parray;
+ [FieldOffset(0)] public SafeArrayRef*pparray;
+ }
+
+ ///
+ /// Release resources owned by this instance.
+ ///
+ public void Dispose()
+ {
+ // Re-implement the same clearing semantics as PropVariantClear manually for non-Windows platforms.
+ if (VarType == VarEnum.VT_BSTR)
+ {
+ Marshal.FreeBSTR(_typeUnion._unionTypes._bstr);
+ }
+ else if (VarType.HasFlag(VarEnum.VT_ARRAY))
+ {
+ _typeUnion._unionTypes.parray.Destroy();
+ }
+ else if (VarType == VarEnum.VT_UNKNOWN || VarType == VarEnum.VT_DISPATCH)
+ {
+ if (_typeUnion._unionTypes._unknown != IntPtr.Zero)
+ {
+ Marshal.Release(_typeUnion._unionTypes._unknown);
+ }
+ }
+ else if (VarType == VarEnum.VT_LPSTR || VarType == VarEnum.VT_LPWSTR || VarType == VarEnum.VT_CLSID)
+ {
+ Marshal.FreeCoTaskMem(_typeUnion._unionTypes._byref);
+ }
+ else if (VarType == VarEnum.VT_STREAM || VarType == VarEnum.VT_STREAMED_OBJECT ||
+ VarType == VarEnum.VT_STORAGE || VarType == VarEnum.VT_STORED_OBJECT)
+ {
+ if (_typeUnion._unionTypes._unknown != IntPtr.Zero)
+ {
+ Marshal.Release(_typeUnion._unionTypes._unknown);
+ }
+ }
+
+ // Clear out this ComVariant instance.
+ this = default;
+ }
+
+ ///
+ /// Create an instance from the specified value.
+ ///
+ /// The value to wrap in an .
+ /// An that contains the provided value.
+ public static unsafe ComVariant Create(object? value)
+ {
+ if (value is null) return Null;
+
+ Unsafe.SkipInit(out ComVariant variant);
+
+ if (value.GetType().IsEnum)
+ {
+ var underlyingType = Enum.GetUnderlyingType(value.GetType());
+ value = Convert.ChangeType(value, underlyingType);
+ }
+
+ if (value is short)
+ {
+ variant.VarType = VarEnum.VT_I2;
+ variant._typeUnion._unionTypes._i2 = (short)value;
+ }
+ else if (value is int)
+ {
+ variant.VarType = VarEnum.VT_I4;
+ variant._typeUnion._unionTypes._i4 = (int)value;
+ }
+ else if (value is float)
+ {
+ variant.VarType = VarEnum.VT_R4;
+ variant._typeUnion._unionTypes._r4 = (float)value;
+ }
+ else if (value is double)
+ {
+ variant.VarType = VarEnum.VT_R8;
+ variant._typeUnion._unionTypes._r8 = (double)value;
+ }
+ else if (value is DateTime)
+ {
+ variant.VarType = VarEnum.VT_DATE;
+ variant._typeUnion._unionTypes._date = ((DateTime)value).ToOADate();
+ }
+ else if (value is string)
+ {
+ variant.VarType = VarEnum.VT_BSTR;
+ variant._typeUnion._unionTypes._bstr = Marshal.StringToBSTR((string)value);
+ }
+ else if (value is bool)
+ {
+ // bool values in OLE VARIANTs are VARIANT_BOOL values.
+ variant.VarType = VarEnum.VT_BOOL;
+ variant._typeUnion._unionTypes._bool = ((bool)value) ? VARIANT_TRUE : VARIANT_FALSE;
+ }
+ else if (value is sbyte)
+ {
+ variant.VarType = VarEnum.VT_I1;
+ variant._typeUnion._unionTypes._i1 = (sbyte)value;
+ }
+ else if (value is byte)
+ {
+ variant.VarType = VarEnum.VT_UI1;
+ variant._typeUnion._unionTypes._ui1 = (byte)value;
+ }
+ else if (value is ushort)
+ {
+ variant.VarType = VarEnum.VT_UI2;
+ variant._typeUnion._unionTypes._ui2 = (ushort)value;
+ }
+ else if (value is uint)
+ {
+ variant.VarType = VarEnum.VT_UI4;
+ variant._typeUnion._unionTypes._ui4 = (uint)value;
+ }
+ else if (value is long)
+ {
+ variant.VarType = VarEnum.VT_I8;
+ variant._typeUnion._unionTypes._i8 = (long)value;
+ }
+ else if (value is ulong)
+ {
+ variant.VarType = VarEnum.VT_UI8;
+ variant._typeUnion._unionTypes._ui8 = (ulong)value;
+ }
+ else if (value is IEnumerable list && SafeArrayRef.TryCreate(list, out var array, out var arrayEnum))
+ {
+ variant.VarType = arrayEnum | VarEnum.VT_ARRAY;
+ variant._typeUnion._unionTypes.parray = array.Value;
+ }
+ else if (ComWrappers.TryGetComInstance(value, out var unknown))
+ {
+ variant.VarType = VarEnum.VT_UNKNOWN;
+ variant._typeUnion._unionTypes._unknown = unknown;
+ }
+ else
+ {
+ throw new ArgumentException("UnsupportedType", value.GetType().Name);
+ }
+
+ return variant;
+ }
+
+ ///
+ /// A instance that represents a null value with type.
+ ///
+ public static ComVariant Null { get; } = new() { VarType = VarEnum.VT_NULL };
+
+ ///
+ /// Create a managed value based on the value in the instance.
+ ///
+ /// The managed value contained in this variant.
+ public readonly unsafe object? AsObject()
+ {
+ if (VarType == VarEnum.VT_EMPTY)
+ {
+ return null;
+ }
+
+ return VarType switch
+ {
+ VarEnum.VT_NULL => null,
+ // integer
+ VarEnum.VT_I1 => _typeUnion._unionTypes._i1,
+ VarEnum.VT_I2 => _typeUnion._unionTypes._i2,
+ VarEnum.VT_I4 => _typeUnion._unionTypes._i4,
+ VarEnum.VT_I8 => _typeUnion._unionTypes._i8,
+ VarEnum.VT_INT => _typeUnion._unionTypes._i4,
+ VarEnum.VT_ERROR => _typeUnion._unionTypes._i4,
+ // unsigned integer
+ VarEnum.VT_UI1 => _typeUnion._unionTypes._ui1,
+ VarEnum.VT_UI2 => _typeUnion._unionTypes._ui2,
+ VarEnum.VT_UI4 => _typeUnion._unionTypes._ui4,
+ VarEnum.VT_UI8 => _typeUnion._unionTypes._ui8,
+ VarEnum.VT_UINT => _typeUnion._unionTypes._ui4,
+ // floating
+ VarEnum.VT_R4 => _typeUnion._unionTypes._r4,
+ VarEnum.VT_R8 => _typeUnion._unionTypes._r8,
+ // date
+ VarEnum.VT_DATE => DateTime.FromOADate(_typeUnion._unionTypes._date),
+ // string
+ VarEnum.VT_BSTR => Marshal.PtrToStringBSTR(_typeUnion._unionTypes._bstr),
+ // bool
+ VarEnum.VT_BOOL => _typeUnion._unionTypes._bool != VARIANT_FALSE,
+ // unknown
+ VarEnum.VT_UNKNOWN => ComWrappers.TryGetObject(_typeUnion._unionTypes._unknown, out var obj) ? obj : null,
+ // array
+ { } varEnum when varEnum.HasFlag(VarEnum.VT_ARRAY) => (varEnum ^ VarEnum.VT_ARRAY) switch
+ {
+ // integer
+ VarEnum.VT_I1 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_I2 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_I4 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_I8 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_INT => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ // unsigned integer
+ VarEnum.VT_UI1 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_UI2 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_UI4 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_UI8 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_UINT => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ // floating
+ VarEnum.VT_R4 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ VarEnum.VT_R8 => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ // string
+ VarEnum.VT_BSTR => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ // variant
+ VarEnum.VT_UNKNOWN => SafeArrayRef.ToArray(_typeUnion._unionTypes.parray),
+ _ => throw new ArgumentException($"Unknown variant type: {varEnum}")
+ },
+ _ => throw new ArgumentException($"Unknown variant type: {VarType}")
+ };
+ }
+
+ ///
+ /// The type of the data stored in this .
+ ///
+ public VarEnum VarType
+ {
+ readonly get => (VarEnum)_typeUnion._vt;
+ private set => _typeUnion._vt = (ushort)value;
+ }
+}
+#endif
diff --git a/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariantMarshaller.cs b/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariantMarshaller.cs
new file mode 100644
index 0000000000..02ae8eca28
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Marshalling/ComVariantMarshaller.cs
@@ -0,0 +1,16 @@
+#if NET7_0_OR_GREATER
+global using ComVariantMarshaller = Avalonia.Win32.Automation.Marshalling.ComVariantMarshaller;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Marshalling;
+
+[CustomMarshaller(typeof(object), MarshalMode.Default, typeof(ComVariantMarshaller))]
+internal static class ComVariantMarshaller
+{
+ public static ComVariant ConvertToUnmanaged(object? managed) => ComVariant.Create(managed);
+
+ public static object? ConvertToManaged(ComVariant unmanaged) => unmanaged.AsObject();
+
+ public static void Free(ComVariant unmanaged) => unmanaged.Dispose();
+}
+#endif
diff --git a/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayMarshaller.cs b/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayMarshaller.cs
new file mode 100644
index 0000000000..fe7fe54976
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayMarshaller.cs
@@ -0,0 +1,21 @@
+#if NET7_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace Avalonia.Win32.Automation.Marshalling;
+
+[CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.Default, typeof(SafeArrayMarshaller<>))]
+internal static class SafeArrayMarshaller where T : notnull
+{
+ public static SafeArrayRef ConvertToUnmanaged(T[]? managed) =>
+ managed is null ? new SafeArrayRef()
+ : SafeArrayRef.TryCreate(managed, out var result, out _) ? result.Value
+ : throw new NotImplementedException($"SafeArray marshalling for '{managed?.GetType().Name}' is not implemented.");
+
+ public static T[]? ConvertToManaged(SafeArrayRef unmanaged) => SafeArrayRef.ToArray(unmanaged);
+
+ public static void Free(SafeArrayRef unmanaged) => unmanaged.Destroy();
+}
+#endif
diff --git a/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayRef.cs b/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayRef.cs
new file mode 100644
index 0000000000..14158c4ee3
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Automation/Marshalling/SafeArrayRef.cs
@@ -0,0 +1,343 @@
+using System;
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Avalonia.Controls.Documents;
+// ReSharper disable InconsistentNaming
+
+namespace Avalonia.Win32.Automation.Marshalling;
+
+#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
+#if NET7_0_OR_GREATER
+internal unsafe partial struct SafeArrayRef
+{
+ private SAFEARRAY* _ptr;
+
+ internal struct SAFEARRAY
+ {
+ /// The number of dimensions.
+ internal ushort cDims;
+
+ ///
+ /// Flags.
+ /// This doc was truncated.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal ADVANCED_FEATURE_FLAGS fFeatures;
+
+ /// The size of an array element.
+ internal uint cbElements;
+
+ /// The number of times the array has been locked without a corresponding unlock.
+ internal uint cLocks;
+
+ /// The data.
+ internal void* pvData;
+
+ /// One bound for each dimension.
+ internal VariableLengthInlineArray rgsabound;
+ }
+ internal struct SAFEARRAYBOUND
+ {
+ /// The number of elements in the dimension.
+ internal uint cElements;
+
+ /// The lower bound of the dimension.
+ internal int lLbound;
+ }
+
+ internal struct VariableLengthInlineArray
+ where T : unmanaged
+ {
+ internal T e0;
+
+ internal ref T this[int index]
+ {
+ [UnscopedRef]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref Unsafe.Add(ref this.e0, index);
+ }
+
+ [UnscopedRef]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal Span AsSpan(int length)
+ {
+ return MemoryMarshal.CreateSpan(ref this.e0, length);
+ }
+ }
+
+ [Flags]
+ internal enum ADVANCED_FEATURE_FLAGS : ushort
+ {
+ FADF_AUTO = 0x0001,
+ FADF_STATIC = 0x0002,
+ FADF_EMBEDDED = 0x0004,
+ FADF_FIXEDSIZE = 0x0010,
+ FADF_RECORD = 0x0020,
+ FADF_HAVEIID = 0x0040,
+ FADF_HAVEVARTYPE = 0x0080,
+ FADF_BSTR = 0x0100,
+ FADF_UNKNOWN = 0x0200,
+ FADF_DISPATCH = 0x0400,
+ FADF_VARIANT = 0x0800,
+ FADF_RESERVED = 0xF008,
+ }
+
+ public void Destroy()
+ {
+ if (_ptr != default)
+ {
+ SafeArrayDestroy(_ptr);
+ }
+ }
+
+ public static T[]? ToArray(SafeArrayRef? safearray)
+ {
+ if (safearray is null) return null;
+
+ return AccessData(safearray.Value, static (data, length) =>
+ {
+ var array = new T[length];
+ if (typeof(T) == typeof(sbyte))
+ Marshal.Copy(data, (byte[])(object)array, 0, length);
+ else if (typeof(T) == typeof(short))
+ Marshal.Copy(data, (short[])(object)array, 0, length);
+ else if (typeof(T) == typeof(int))
+ Marshal.Copy(data, (int[])(object)array, 0, length);
+ else if (typeof(T) == typeof(long))
+ Marshal.Copy(data, (long[])(object)array, 0, length);
+ else if (typeof(T) == typeof(byte))
+ Marshal.Copy(data, (byte[])(object)array, 0, length);
+ else if (typeof(T) == typeof(ushort))
+ Marshal.Copy(data, (short[])(object)array, 0, length);
+ else if (typeof(T) == typeof(uint))
+ Marshal.Copy(data, (int[])(object)array, 0, length);
+ else if (typeof(T) == typeof(ulong))
+ Marshal.Copy(data, (long[])(object)array, 0, length);
+ else if (typeof(T) == typeof(float))
+ Marshal.Copy(data, (float[])(object)array, 0, length);
+ else if (typeof(T) == typeof(double))
+ Marshal.Copy(data, (double[])(object)array, 0, length);
+ else if (typeof(T) == typeof(nint))
+ Marshal.Copy(data, (nint[])(object)array, 0, length);
+ else if (typeof(T) == typeof(nuint))
+ Marshal.Copy(data, (nint[])(object)array, 0, length);
+ else if (typeof(T) == typeof(string))
+ {
+ var pointers = new IntPtr[length];
+ Marshal.Copy(data, pointers, 0, array.Length);
+ for (var i = 0; i < length; i++)
+ {
+ array[i] = (T)(object)Marshal.PtrToStringBSTR(pointers[i]);
+ }
+ }
+ else if (typeof(T).IsInterface)
+ {
+ var pointers = new IntPtr[length];
+ Marshal.Copy(data, pointers, 0, array.Length);
+ for (int i = 0; i < pointers.Length; i++)
+ {
+ if (ComWrappers.TryGetObject(pointers[i], out var instance))
+ {
+ array[i] = (T)instance;
+ }
+ else
+ {
+ throw new NotImplementedException("COM items not owned by managed code can't be unwrapped from SafeArray.");
+ }
+ }
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+
+ return array;
+ });
+ }
+
+ public static bool TryCreate(IEnumerable? managed, [NotNullWhen(true)] out SafeArrayRef? safearray, out VarEnum varEnum)
+ {
+ safearray = default;
+ varEnum = default;
+
+ if (managed is null)
+ {
+ return false;
+ }
+
+ static SafeArrayRef CreateFromCollection(IReadOnlyCollection collection, VarEnum varEnum)
+ {
+ var collectionSpan = collection switch
+ {
+ T[] array => array,
+ List list => CollectionsMarshal.AsSpan(list),
+ _ => collection.ToArray()
+ };
+ return CreateFromSpan(collectionSpan, varEnum);
+ }
+
+ static SafeArrayRef CreateFromSpan(ReadOnlySpan span, VarEnum varEnum)
+ {
+ var bound = new SAFEARRAYBOUND { cElements = (uint)span.Length, lLbound = 0 };
+ var safearray = SafeArrayCreate(varEnum, 1, bound);
+ if (span.Length == 0)
+ {
+ return new SafeArrayRef
+ {
+ _ptr = safearray
+ };
+ }
+
+ var lockResult = SafeArrayLock(safearray);
+ if (lockResult != 0) throw new Win32Exception(lockResult);
+
+ try
+ {
+ // We assume it has the same length.
+ var output = new Span(safearray->pvData, (int)safearray->rgsabound[0].cElements);
+ span.CopyTo(output);
+ }
+ finally
+ {
+ SafeArrayUnlock(safearray);
+ }
+
+ return new SafeArrayRef
+ {
+ _ptr = safearray
+ };
+ }
+
+ static SafeArrayRef CreateFromStrings(IReadOnlyList strings, VarEnum varEnum)
+ {
+ Debug.Assert(varEnum == VarEnum.VT_BSTR); // other types not supported yet
+ var pointers = ArrayPool.Shared.Rent(strings.Count);
+ try
+ {
+ for (int i = 0; i < strings.Count; i++)
+ {
+ pointers[i] = Marshal.StringToBSTR(strings[i]);
+ }
+
+ return CreateFromSpan(pointers.AsSpan(0, strings.Count), varEnum);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(pointers);
+ }
+ }
+
+ static SafeArrayRef CreateFromBools(IReadOnlyList bools, VarEnum varEnum)
+ {
+ Debug.Assert(varEnum == VarEnum.VT_BOOL); // other types not supported
+ var shorts = ArrayPool.Shared.Rent(bools.Count);
+ try
+ {
+ for (int i = 0; i < bools.Count; i++)
+ {
+ shorts[i] = bools[i] ? ComVariant.VARIANT_TRUE : ComVariant.VARIANT_FALSE;
+ }
+
+ return CreateFromSpan(shorts.AsSpan(0, bools.Count), varEnum);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(shorts);
+ }
+ }
+
+ static SafeArrayRef CreateFromObjects(IReadOnlyList