Skip to content

Commit

Permalink
[GeneratedComInterface] based Windows automation (#16543)
Browse files Browse the repository at this point in the history
* Move automation interfaces to the Avalonia.Win32.Automation with DisableRuntimeMarshalling and use [GeneratedComInterface] marshalling

* Various fixes for the new windows accessibility

* Numerge Avalonia.Win32.Automation into Avalonia.Win32

* Suppress Avalonia.Win32 API warnings, these interfaces never were part of the public API

* Fix IRawElementProviderSimple2 definition on legacy COM interop

* Some changes after review

* More consistent COM method names

* Fix folder hierarchy

* Rewrite SafeArrayMarshaller to use arrays as managed type

* Add ManagedObjectWrapper where's possible

* Throw an exception for unsupported SafeArrayRef scenario

---------

Co-authored-by: Julien Lebosquain <[email protected]>
  • Loading branch information
maxkatz6 and MrJul authored Oct 31, 2024
1 parent 923f3c5 commit af0f73d
Show file tree
Hide file tree
Showing 77 changed files with 2,134 additions and 977 deletions.
7 changes: 7 additions & 0 deletions Avalonia.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
214 changes: 214 additions & 0 deletions api/Avalonia.Win32.nupkg.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.DockPosition</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IDockProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IExpandCollapseProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IGridItemProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IGridProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IInvokeProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IMultipleViewProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRangeValueProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderAdviseEvents</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragment</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderFragmentRoot</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IRawElementProviderSimple2</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IScrollItemProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IScrollProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ISelectionItemProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ISelectionProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ISynchronizedInputProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ITableItemProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ITableProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ITextProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ITextRangeProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IToggleProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ITransformProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IValueProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.IWindowProvider</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.NavigateDirection</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.ProviderOptions</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.RowOrColumnMajor</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.SupportedTextSelection</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.SynchronizedInputType</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.TextPatternRangeEndpoint</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.TextUnit</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.WindowInteractionState</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Win32.Interop.Automation.WindowVisualState</Target>
<Left>baseline/netstandard2.0/Avalonia.Win32.dll</Left>
<Right>target/netstandard2.0/Avalonia.Win32.dll</Right>
</Suppression>
</Suppressions>
12 changes: 12 additions & 0 deletions nukebuild/numerge.config
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
"DoNotMergeDependencies": true
}
]
},
{
"Id": "Avalonia.Win32",
"MergeAll": false,
"IncomingIncludeAssetsOverride": "",
"Merge": [
{
"Id": "Avalonia.Win32.Automation",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
}
]
}
]
}
2 changes: 1 addition & 1 deletion packages/Avalonia/AvaloniaSingleProject.targets
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<PropertyGroup Condition=" '$(_AvaloniaWindowsTarget)' == 'true' ">
<OutputType Condition="'$(OutputType)' == 'Exe'">WinExe</OutputType>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<BuiltInComInteropSupport Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</BuiltInComInteropSupport>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>

Expand Down
1 change: 0 additions & 1 deletion samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<OutputType>WinExe</OutputType>
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
</PropertyGroup>

<PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Avalonia.Controls/Avalonia.Controls.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<InternalsVisibleTo Include="Avalonia.Headless, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Native, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32.Automation, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LinuxFramebuffer, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport.Remote, PublicKey=$(AvaloniaPublicKey)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IExpandCollapseProvider, ExpandCollapseState>(x => x.ExpandCollapseState);
return InvokeSync<IExpandCollapseProvider, ExpandCollapseState>(x => x.ExpandCollapseState);
}

void UIA.IExpandCollapseProvider.Expand() => InvokeSync<IExpandCollapseProvider>(x => x.Expand());
Expand Down
17 changes: 17 additions & 0 deletions src/Windows/Avalonia.Win32.Automation/AutomationNode.RangeValue.cs
Original file line number Diff line number Diff line change
@@ -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<IRangeValueProvider, double>(x => x.Value);
bool UIA.IRangeValueProvider.GetIsReadOnly() => InvokeSync<IRangeValueProvider, bool>(x => x.IsReadOnly);
double UIA.IRangeValueProvider.GetMaximum() => InvokeSync<IRangeValueProvider, double>(x => x.Maximum);
double UIA.IRangeValueProvider.GetMinimum() => InvokeSync<IRangeValueProvider, double>(x => x.Minimum);
double UIA.IRangeValueProvider.GetLargeChange() => 1;
double UIA.IRangeValueProvider.GetSmallChange() => 1;

public void SetValue(double value) => InvokeSync<IRangeValueProvider>(x => x.SetValue(value));
}
}
30 changes: 30 additions & 0 deletions src/Windows/Avalonia.Win32.Automation/AutomationNode.Scroll.cs
Original file line number Diff line number Diff line change
@@ -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<IScrollProvider, bool>(x => x.HorizontallyScrollable);
double UIA.IScrollProvider.GetHorizontalScrollPercent() => InvokeSync<IScrollProvider, double>(x => x.HorizontalScrollPercent);
double UIA.IScrollProvider.GetHorizontalViewSize() => InvokeSync<IScrollProvider, double>(x => x.HorizontalViewSize);
bool UIA.IScrollProvider.GetVerticallyScrollable() => InvokeSync<IScrollProvider, bool>(x => x.VerticallyScrollable);
double UIA.IScrollProvider.GetVerticalScrollPercent() => InvokeSync<IScrollProvider, double>(x => x.VerticalScrollPercent);
double UIA.IScrollProvider.GetVerticalViewSize() => InvokeSync<IScrollProvider, double>(x => x.VerticalViewSize);

void UIA.IScrollProvider.Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount)
{
InvokeSync<IScrollProvider>(x => x.Scroll(horizontalAmount, verticalAmount));
}

void UIA.IScrollProvider.SetScrollPercent(double horizontalPercent, double verticalPercent)
{
InvokeSync<IScrollProvider>(x => x.SetScrollPercent(horizontalPercent, verticalPercent));
}

void UIA.IScrollItemProvider.ScrollIntoView()
{
InvokeSync(() => Peer.BringIntoView());
}
}
}
Loading

0 comments on commit af0f73d

Please sign in to comment.