diff --git a/Avalonia.sln b/Avalonia.sln
index f12af02236..afaee6a907 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -191,6 +191,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -2589,6 +2591,46 @@ Global
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2649,5 +2691,6 @@ Global
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
EndGlobalSection
EndGlobal
diff --git a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
index 1115cf5768..5a346d4e8d 100644
--- a/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
+++ b/samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
@@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WindowsInteropTest"
xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32"
+ xmlns:wpf="clr-namespace:Avalonia.Win32.Interop.Wpf;assembly=Avalonia.Win32.Interop"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
@@ -15,7 +16,7 @@
-
+
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 28e5e274d0..d0405a3af8 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -14,7 +14,7 @@
true
- AnyCPU
+ x86
true
full
false
@@ -164,6 +164,10 @@
{3e908f67-5543-4879-a1dc-08eace79b3cd}
Avalonia.Direct2D1
+
+ {cbc4ff2f-92d4-420b-be21-9fe0b930b04e}
+ Avalonia.Win32.Interop
+
{811a76cf-1cf6-440f-963b-bbe31bd72a82}
Avalonia.Win32
diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
index b8d54fa67b..45b4f8eaa1 100644
--- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
+++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
@@ -39,6 +39,8 @@ namespace Avalonia.Controls.Embedding
}
}
+ public Size MeasureBase(Size availableSize) => base.MeasureOverride(availableSize);
+
protected override Size MeasureOverride(Size availableSize)
{
var cs = PlatformImpl?.ClientSize ?? default(Size);
diff --git a/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
new file mode 100644
index 0000000000..ae61fc697f
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Avalonia.Win32.Interop.csproj
@@ -0,0 +1,101 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}
+ Library
+ Properties
+ Avalonia.Win32.Interop
+ Avalonia.Win32.Interop
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UnmanagedMethods.cs
+
+
+
+
+
+
+
+
+
+
+
+ {d211e587-d8bc-45b9-95a4-f297c8fa5200}
+ Avalonia.Animation
+
+
+ {b09b78d8-9b26-48b0-9149-d64a2f120f3f}
+ Avalonia.Base
+
+
+ {d2221c82-4a25-4583-9b43-d791e3f6820c}
+ Avalonia.Controls
+
+
+ {62024b2d-53eb-4638-b26b-85eeaa54866e}
+ Avalonia.Input
+
+
+ {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}
+ Avalonia.Interactivity
+
+
+ {42472427-4774-4c81-8aff-9f27b8e31721}
+ Avalonia.Layout
+
+
+ {f1baa01a-f176-4c6a-b39d-5b40bb1b148f}
+ Avalonia.Styling
+
+
+ {eb582467-6abb-43a1-b052-e981ba910e3a}
+ Avalonia.Visuals
+
+
+ {fb05ac90-89ba-4f2f-a924-f37875fb547c}
+ Avalonia.Cairo
+
+
+ {811a76cf-1cf6-440f-963b-bbe31bd72a82}
+ Avalonia.Win32
+
+
+
+
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..7c0d638381
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Avalonia.Win32.Interop")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Avalonia.Win32.Interop")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("cbc4ff2f-92d4-420b-be21-9fe0b930b04e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
new file mode 100644
index 0000000000..6ae898ae3d
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/CursorShim.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ static class CursorShim
+ {
+ public static Cursor FromHCursor(IntPtr hcursor)
+ {
+ var field = typeof(Cursor).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+ .FirstOrDefault(f => f.FieldType == typeof(SafeHandle));
+ if (field == null)
+ return null;
+ var rv = (Cursor) FormatterServices.GetUninitializedObject(typeof(Cursor));
+ field.SetValue(rv, new SafeHandleShim(hcursor));
+ return rv;
+ }
+
+ class SafeHandleShim : SafeHandle
+ {
+ public SafeHandleShim(IntPtr hcursor) : base(new IntPtr(-1), false)
+ {
+ this.handle = hcursor;
+ }
+
+ protected override bool ReleaseHandle() => true;
+
+ public override bool IsInvalid => false;
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs
new file mode 100644
index 0000000000..9c1f39e86b
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Helpers.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ static class Helpers
+ {
+ public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y);
+ public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y);
+ public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height);
+ public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height);
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
new file mode 100644
index 0000000000..2d350fbe30
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfAvaloniaHost.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ public class WpfAvaloniaHost : FrameworkElement, IDisposable
+ {
+ private WpfTopLevelImpl _impl = new WpfTopLevelImpl();
+ private readonly SynchronizationContext _sync;
+ public WpfAvaloniaHost()
+ {
+ _sync = SynchronizationContext.Current;
+ _impl.ControlRoot.Prepare();
+ _impl.Visibility = Visibility.Visible;
+ AddLogicalChild(_impl);
+ AddVisualChild(_impl);
+ }
+
+
+ public object Content
+ {
+ get => _impl.ControlRoot.Content;
+ set => _impl.ControlRoot.Content = value;
+ }
+
+ //Separate class is needed to prevent accidential resurrection
+ class Disposer
+ {
+ private readonly WpfTopLevelImpl _impl;
+
+ public Disposer(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public void Callback(object state)
+ {
+ _impl.Dispose();
+ }
+ }
+
+ protected override System.Windows.Size MeasureOverride(System.Windows.Size constraint)
+ => _impl.ControlRoot.MeasureBase(constraint.ToAvaloniaSize()).ToWpfSize();
+
+ protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
+ {
+ _impl.Arrange(new System.Windows.Rect(arrangeSize));
+ return arrangeSize;
+ }
+
+ protected override int VisualChildrenCount => 1;
+ protected override System.Windows.Media.Visual GetVisualChild(int index) => _impl;
+
+ ~WpfAvaloniaHost()
+ {
+ if (_impl != null)
+ _sync.Post(new Disposer(_impl).Callback, null);
+ }
+
+ public void Dispose()
+ {
+ if (_impl != null)
+ {
+ RemoveVisualChild(_impl);
+ RemoveLogicalChild(_impl);
+ _impl.Dispose();
+ _impl = null;
+ GC.SuppressFinalize(this);
+ }
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
new file mode 100644
index 0000000000..4aad80f8a5
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfMouseDevice.cs
@@ -0,0 +1,30 @@
+using System;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class WpfMouseDevice : MouseDevice
+ {
+ private readonly WpfTopLevelImpl _impl;
+
+ public WpfMouseDevice(WpfTopLevelImpl impl)
+ {
+ _impl = impl;
+ }
+
+ public override void Capture(IInputElement control)
+ {
+ if (control == null)
+ {
+ System.Windows.Input.Mouse.Capture(null);
+ }
+ else if ((control.GetVisualRoot() as EmbeddableControlRoot)?.PlatformImpl != _impl)
+ throw new ArgumentException("Visual belongs to unknown toplevel");
+ else
+ System.Windows.Input.Mouse.Capture(_impl);
+ base.Capture(control);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
new file mode 100644
index 0000000000..56dc26992e
--- /dev/null
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Layout;
+using Avalonia.Platform;
+using Key = Avalonia.Input.Key;
+using KeyEventArgs = System.Windows.Input.KeyEventArgs;
+using MouseButton = System.Windows.Input.MouseButton;
+
+namespace Avalonia.Win32.Interop.Wpf
+{
+ class WpfTopLevelImpl : FrameworkElement, IEmbeddableWindowImpl
+ {
+ private HwndSource _currentHwndSource;
+ private readonly HwndSourceHook _hook;
+ private readonly IEmbeddableWindowImpl _ttl;
+ private IInputRoot _inputRoot;
+ private readonly IEnumerable