瀏覽代碼

Ported native embedding PR to master

Nikita Tsukanov 5 年之前
父節點
當前提交
8f73baf507
共有 41 個文件被更改,包括 1647 次插入94 次删除
  1. 27 0
      Avalonia.sln
  2. 22 1
      native/Avalonia.Native/inc/avalonia-native.h
  3. 8 0
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  4. 35 1
      native/Avalonia.Native/src/OSX/app.mm
  5. 9 0
      native/Avalonia.Native/src/OSX/common.h
  6. 145 0
      native/Avalonia.Native/src/OSX/controlhost.mm
  7. 17 0
      native/Avalonia.Native/src/OSX/deadlock.mm
  8. 26 3
      native/Avalonia.Native/src/OSX/window.mm
  9. 2 2
      nukebuild/Build.cs
  10. 8 0
      samples/interop/NativeEmbedSample/App.xaml
  11. 22 0
      samples/interop/NativeEmbedSample/App.xaml.cs
  12. 121 0
      samples/interop/NativeEmbedSample/EmbedSample.cs
  13. 58 0
      samples/interop/NativeEmbedSample/GtkHelper.cs
  14. 39 0
      samples/interop/NativeEmbedSample/MacHelper.cs
  15. 43 0
      samples/interop/NativeEmbedSample/MainWindow.xaml
  16. 36 0
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  17. 30 0
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  18. 17 0
      samples/interop/NativeEmbedSample/Program.cs
  19. 74 0
      samples/interop/NativeEmbedSample/WinApi.cs
  20. 1 0
      samples/interop/NativeEmbedSample/nodes-license.md
  21. 二進制
      samples/interop/NativeEmbedSample/nodes.mp4
  22. 141 0
      src/Avalonia.Controls/NativeControlHost.cs
  23. 12 0
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  24. 32 0
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  25. 12 0
      src/Avalonia.Controls/Primitives/Popup.cs
  26. 16 0
      src/Avalonia.Controls/TopLevel.cs
  27. 136 0
      src/Avalonia.Native/NativeControlHostImpl.cs
  28. 17 1
      src/Avalonia.Native/WindowImplBase.cs
  29. 2 0
      src/Avalonia.Visuals/VisualTree/TransformedBounds.cs
  30. 10 0
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  31. 190 0
      src/Avalonia.X11/X11NativeControlHost.cs
  32. 3 0
      src/Avalonia.X11/X11Platform.cs
  33. 5 1
      src/Avalonia.X11/X11Window.cs
  34. 1 20
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  35. 2 59
      src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs
  36. 7 1
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  37. 58 0
      src/Windows/Avalonia.Win32/OffscreenParentWindow.cs
  38. 38 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  39. 201 0
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  40. 15 1
      src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs
  41. 9 2
      src/Windows/Avalonia.Win32/WindowImpl.cs

+ 27 - 0
Avalonia.sln

@@ -205,6 +205,8 @@ EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
 EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
@@ -1921,6 +1923,30 @@ Global
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.Build.0 = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -1978,6 +2004,7 @@ Global
 		{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 22 - 1
native/Avalonia.Native/inc/avalonia-native.h

@@ -23,7 +23,8 @@ struct IAvnGlSurfaceRenderingSession;
 struct IAvnMenu;
 struct IAvnMenuItem;
 struct IAvnMenuEvents;
-
+struct IAvnNativeControlHost;
+struct IAvnNativeControlHostTopLevelAttachment;
 enum SystemDecorations {
     SystemDecorationsNone = 0,
     SystemDecorationsBorderOnly = 1,
@@ -235,6 +236,7 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
     virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
     virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
     virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
+    virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) = 0;
 };
 
 AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@@ -270,6 +272,7 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
     virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0;
     virtual void ScalingChanged(double scaling) = 0;
     virtual void RunRenderPriorityJobs() = 0;
+    virtual void LostFocus() = 0;
 };
 
 
@@ -427,4 +430,22 @@ AVNCOM(IAvnMenuEvents, 1A) : IUnknown
     virtual void NeedsUpdate () = 0;
 };
 
+AVNCOM(IAvnNativeControlHost, 20) : IUnknown
+{
+    virtual HRESULT CreateDefaultChild(void* parent, void** retOut) = 0;
+    virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() = 0;
+    virtual void DestroyDefaultChild(void* child) = 0;
+};
+
+AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
+{
+    virtual void* GetParentHandle() = 0;
+    virtual HRESULT InitializeWithChildHandle(void* child) = 0;
+    virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
+    virtual void MoveTo(float x, float y, float width, float height) = 0;
+    virtual void Hide() = 0;
+    virtual void ReleaseChild() = 0;
+};
+
+
 extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

+ 8 - 0
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@@ -10,6 +10,8 @@
 		1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
 		1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
 		1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
+		1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
+		1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
 		1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
 		1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
 		37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
@@ -31,6 +33,8 @@
 		1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
 		1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; };
 		1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
+		1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; };
+		1AFD334023E03C4F0042899B /* controlhost.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = controlhost.mm; sourceTree = "<group>"; };
 		1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
 		1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 		37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
@@ -84,11 +88,13 @@
 		AB7A61E62147C814003C5833 = {
 			isa = PBXGroup;
 			children = (
+				1A1852DB23E05814008F0DED /* deadlock.mm */,
 				1A002B9D232135EE00021753 /* app.mm */,
 				37DDA9B121933371002E132B /* AvnString.h */,
 				37DDA9AF219330F8002E132B /* AvnString.mm */,
 				37A4E71A2178846A00EACBCD /* headers */,
 				1A3E5EAD23E9FB1300EDE661 /* cgl.mm */,
+				1AFD334023E03C4F0042899B /* controlhost.mm */,
 				5BF943652167AD1D009CAE35 /* cursor.h */,
 				5B21A981216530F500CEE36E /* cursor.mm */,
 				5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
@@ -188,6 +194,7 @@
 			files = (
 				1A002B9E232135EE00021753 /* app.mm in Sources */,
 				5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
+				1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
 				5B21A982216530F500CEE36E /* cursor.mm in Sources */,
 				37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
 				AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
@@ -196,6 +203,7 @@
 				37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
 				520624B322973F4100C4DCEF /* menu.mm in Sources */,
 				37A517B32159597E00FBA241 /* Screens.mm in Sources */,
+				1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
 				AB00E4F72147CA920032A60A /* main.mm in Sources */,
 				37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
 				AB661C202148286E00291242 /* window.mm in Sources */,

+ 35 - 1
native/Avalonia.Native/src/OSX/app.mm

@@ -25,9 +25,43 @@ NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivati
 
 @end
 
+@interface AvnApplication : NSApplication
+
+
+@end
+
+@implementation AvnApplication
+{
+    BOOL _isHandlingSendEvent;
+}
+
+- (void)sendEvent:(NSEvent *)event
+{
+    bool oldHandling = _isHandlingSendEvent;
+    _isHandlingSendEvent = true;
+    @try {
+        [super sendEvent: event];
+    } @finally {
+        _isHandlingSendEvent = oldHandling;
+    }
+}
+
+// This is needed for certain embedded controls
+- (BOOL) isHandlingSendEvent
+{
+    return _isHandlingSendEvent;
+}
+
+- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
+{
+    _isHandlingSendEvent = handlingSendEvent;
+}
+
+@end
+
 extern void InitializeAvnApp()
 {
-    NSApplication* app = [NSApplication sharedApplication];
+    NSApplication* app = [AvnApplication sharedApplication];
     id delegate = [AvnAppDelegate new];
     [app setDelegate:delegate];
 }

+ 9 - 0
native/Avalonia.Native/src/OSX/common.h

@@ -18,6 +18,7 @@ extern IAvnGlDisplay* GetGlDisplay();
 extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
 extern IAvnMenuItem* CreateAppMenuItem();
 extern IAvnMenuItem* CreateAppMenuItemSeperator();
+extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
 extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
 extern IAvnMenu* GetAppMenu ();
 extern NSMenuItem* GetAppMenuItem ();
@@ -48,4 +49,12 @@ template<typename T> inline T* objc_cast(id from) {
 - (void) action;
 @end
 
+class AvnInsidePotentialDeadlock
+{
+public:
+    static bool IsInside();
+    AvnInsidePotentialDeadlock();
+    ~AvnInsidePotentialDeadlock();
+};
+
 #endif

+ 145 - 0
native/Avalonia.Native/src/OSX/controlhost.mm

@@ -0,0 +1,145 @@
+#include "common.h"
+
+
+IAvnNativeControlHostTopLevelAttachment* CreateAttachment();
+
+class AvnNativeControlHost :
+    public ComSingleObject<IAvnNativeControlHost, &IID_IAvnNativeControlHost>
+{
+public:
+    FORWARD_IUNKNOWN();
+    NSView* View;
+    AvnNativeControlHost(NSView* view)
+    {
+        View = view;
+    }
+    
+    virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override
+    {
+        NSView* view = [NSView new];
+        [view setWantsLayer: true];
+        
+        *retOut = (__bridge_retained void*)view;
+        return S_OK;
+    };
+    
+    virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override
+    {
+        return ::CreateAttachment();
+    };
+    
+    virtual void DestroyDefaultChild(void* child) override
+    {
+        // ARC will release the object for us
+        (__bridge_transfer NSView*) child;
+    }
+};
+
+class AvnNativeControlHostTopLevelAttachment :
+public ComSingleObject<IAvnNativeControlHostTopLevelAttachment, &IID_IAvnNativeControlHostTopLevelAttachment>
+{
+    NSView* _holder;
+    NSView* _child;
+public:
+    FORWARD_IUNKNOWN();
+    
+    AvnNativeControlHostTopLevelAttachment()
+    {
+        _holder = [NSView new];
+        [_holder setWantsLayer:true];
+    }
+    
+    virtual ~AvnNativeControlHostTopLevelAttachment()
+    {
+        if(_child != nil && [_child superview] == _holder)
+        {
+            [_child removeFromSuperview];
+        }
+        
+        if([_holder superview] != nil)
+        {
+            [_holder removeFromSuperview];
+        }
+    }
+    
+    virtual void* GetParentHandle() override
+    {
+        return (__bridge void*)_holder;
+    };
+    
+    virtual HRESULT InitializeWithChildHandle(void* child) override
+    {
+        if(_child != nil)
+            return E_FAIL;
+        _child = (__bridge NSView*)child;
+        if(_child == nil)
+            return E_FAIL;
+        [_holder addSubview:_child];
+        [_child setHidden: false];
+        return S_OK;
+    };
+    
+    virtual HRESULT AttachTo(IAvnNativeControlHost* host) override
+    {
+        if(host == nil)
+        {
+            [_holder removeFromSuperview];
+            [_holder setHidden: true];
+        }
+        else
+        {
+            AvnNativeControlHost* chost = dynamic_cast<AvnNativeControlHost*>(host);
+            if(chost == nil || chost->View == nil)
+                return E_FAIL;
+            [_holder setHidden:true];
+            [chost->View addSubview:_holder];
+        }
+        return S_OK;
+    };
+    
+    virtual void MoveTo(float x, float y, float width, float height) override
+    {
+        if(_child == nil)
+            return;
+        if(AvnInsidePotentialDeadlock::IsInside())
+        {
+            IAvnNativeControlHostTopLevelAttachment* slf = this;
+            slf->AddRef();
+            dispatch_async(dispatch_get_main_queue(), ^{
+                slf->MoveTo(x, y, width, height);
+                slf->Release();
+            });
+            return;
+        }
+        
+        NSRect childFrame = {0, 0, width, height};
+        NSRect holderFrame = {x, y, width, height};
+        
+        [_child setFrame: childFrame];
+        [_holder setFrame: holderFrame];
+        [_holder setHidden: false];
+        if([_holder superview] != nil)
+            [[_holder superview] setNeedsDisplay:true];
+    }
+    
+    virtual void Hide() override
+    {
+        [_holder setHidden: true];
+    }
+    
+    virtual void ReleaseChild() override
+    {
+        [_child removeFromSuperview];
+        _child = nil;
+    }
+};
+
+IAvnNativeControlHostTopLevelAttachment* CreateAttachment()
+{
+    return new AvnNativeControlHostTopLevelAttachment();
+}
+
+extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent)
+{
+    return new AvnNativeControlHost(parent);
+}

+ 17 - 0
native/Avalonia.Native/src/OSX/deadlock.mm

@@ -0,0 +1,17 @@
+#include "common.h"
+
+static int Counter = 0;
+AvnInsidePotentialDeadlock::AvnInsidePotentialDeadlock()
+{
+    Counter++;
+}
+
+AvnInsidePotentialDeadlock::~AvnInsidePotentialDeadlock()
+{
+    Counter--;
+}
+
+bool AvnInsidePotentialDeadlock::IsInside()
+{
+    return Counter!=0;
+}

+ 26 - 3
native/Avalonia.Native/src/OSX/window.mm

@@ -382,6 +382,14 @@ public:
         *ppv = [renderTarget createSurfaceRenderTarget];
         return *ppv == nil ? E_FAIL : S_OK;
     }
+    
+    virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override
+    {
+        if(View == NULL)
+            return E_FAIL;
+        *retOut = ::CreateNativeControlHost(View);
+        return S_OK;
+    }
 
 protected:
     virtual NSWindowStyleMask GetStyle()
@@ -816,9 +824,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
 }
 
-
 - (void)updateLayer
 {
+    AvnInsidePotentialDeadlock deadlock;
     if (_parent == nullptr)
     {
         return;
@@ -881,7 +889,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     if([self ignoreUserInput])
         return;
     
-    [self becomeFirstResponder];
     auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
     auto avnPoint = [self toAvnPoint:localPoint];
     auto point = [self translateLocalPoint:avnPoint];
@@ -908,11 +915,27 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     auto timestamp = [event timestamp] * 1000;
     auto modifiers = [self getModifiers:[event modifierFlags]];
     
-    [self becomeFirstResponder];
+    if(type != AvnRawMouseEventType::Move ||
+       (
+           [self window] != nil &&
+           (
+                [[self window] firstResponder] == nil
+                || ![[[self window] firstResponder] isKindOfClass: [NSView class]]
+           )
+       )
+    )
+        [self becomeFirstResponder];
+    
     _parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
     [super mouseMoved:event];
 }
 
+- (BOOL) resignFirstResponder
+{
+    _parent->BaseEvents->LostFocus();
+    return YES;
+}
+
 - (void)mouseMoved:(NSEvent *)event
 {
     [self mouseEvent:event withType:Move];

+ 2 - 2
nukebuild/Build.cs

@@ -100,7 +100,7 @@ partial class Build : NukeBuild
             .SetProjectFile(projectFile)
             // This is required for VS2019 image on Azure Pipelines
             .When(Parameters.IsRunningOnWindows &&
-                  Parameters.IsRunningOnAzure, c => c
+                  Parameters.IsRunningOnAzure, _ => _
                 .AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_8_X64")))
             .AddProperty("PackageVersion", Parameters.Version)
             .AddProperty("iOSRoslynPathHackRequired", true)
@@ -162,7 +162,7 @@ partial class Build : NukeBuild
                 .SetFramework(fw)
                 .EnableNoBuild()
                 .EnableNoRestore()
-                .When(Parameters.PublishTestResults, c => c
+                .When(Parameters.PublishTestResults, _ => _
                     .SetLogger("trx")
                     .SetResultsDirectory(Parameters.TestResultsRoot)));
         }

+ 8 - 0
samples/interop/NativeEmbedSample/App.xaml

@@ -0,0 +1,8 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="NativeEmbedSample.App">
+  <Application.Styles>
+    <StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
+    <StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
+  </Application.Styles>
+</Application>

+ 22 - 0
samples/interop/NativeEmbedSample/App.xaml.cs

@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace NativeEmbedSample
+{
+    public class App : Application
+    {
+        public override void Initialize()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+
+        public override void OnFrameworkInitializationCompleted()
+        {
+            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
+                desktopLifetime.MainWindow = new MainWindow();
+
+            base.OnFrameworkInitializationCompleted();
+        }
+    }
+}

+ 121 - 0
samples/interop/NativeEmbedSample/EmbedSample.cs

@@ -0,0 +1,121 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using MonoMac.AppKit;
+using MonoMac.Foundation;
+using MonoMac.WebKit;
+using Encoding = SharpDX.Text.Encoding;
+
+namespace NativeEmbedSample
+{
+    public class EmbedSample : NativeControlHost
+    {
+        public bool IsSecond { get; set; }
+        private Process _mplayer;
+
+        IPlatformHandle CreateLinux(IPlatformHandle parent)
+        {
+            if (IsSecond)
+            {
+                var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
+                if (chooser != null)
+                    return chooser;
+            }
+
+            var control = base.CreateNativeControlCore(parent);
+            var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
+                "..",
+                "nodes.mp4"));
+            _mplayer = Process.Start(new ProcessStartInfo("mplayer",
+                $"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
+            {
+                UseShellExecute = false,
+
+            });
+            return control;
+        }
+
+        void DestroyLinux(IPlatformHandle handle)
+        {
+            _mplayer.Kill();
+            _mplayer = null;
+            base.DestroyNativeControlCore(handle);
+        }
+
+        private const string RichText =
+            @"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
+{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
+{\*\generator Riched20 6.3.9600}\viewkind4\uc1 
+\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0  a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
+}";
+
+        IPlatformHandle CreateWin32(IPlatformHandle parent)
+        {
+            WinApi.LoadLibrary("Msftedit.dll");
+            var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
+                @"Rich Edit",
+                0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
+                IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
+            var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
+            var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : "");
+            var bytes = Encoding.UTF8.GetBytes(text);
+            WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
+            return new PlatformHandle(handle, "HWND");
+
+        }
+
+        void DestroyWin32(IPlatformHandle handle)
+        {
+            WinApi.DestroyWindow(handle.Handle);
+        }
+
+        IPlatformHandle CreateOSX(IPlatformHandle parent)
+        {
+            // Note: We are using MonoMac for example purposes
+            // It shouldn't be used in production apps
+            MacHelper.EnsureInitialized();
+
+            var webView = new WebView();
+            Dispatcher.UIThread.Post(() =>
+            {
+                webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
+                    IsSecond ? "https://bing.com": "https://google.com/")));
+            });
+            return new MacOSViewHandle(webView);
+
+        }
+
+        void DestroyOSX(IPlatformHandle handle)
+        {
+            ((MacOSViewHandle)handle).Dispose();
+        }
+        
+        protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                return CreateLinux(parent);
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                return CreateWin32(parent);
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                return CreateOSX(parent);
+            return base.CreateNativeControlCore(parent);
+        }
+
+        protected override void DestroyNativeControlCore(IPlatformHandle control)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                DestroyLinux(control);
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                DestroyWin32(control);
+            else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+                DestroyOSX(control);
+            else
+                base.DestroyNativeControlCore(control);
+        }
+    }
+}

+ 58 - 0
samples/interop/NativeEmbedSample/GtkHelper.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Platform.Interop;
+using Avalonia.X11.NativeDialogs;
+using static Avalonia.X11.NativeDialogs.Gtk;
+using static Avalonia.X11.NativeDialogs.Glib;
+namespace NativeEmbedSample
+{
+    public class GtkHelper
+    {
+        private static  Task<bool> s_gtkTask;
+        class FileChooser : INativeControlHostDestroyableControlHandle
+        {
+            private readonly IntPtr _widget;
+
+            public FileChooser(IntPtr widget, IntPtr xid)
+            {
+                _widget = widget;
+                Handle = xid;
+            }
+
+            public IntPtr Handle { get; }
+            public string HandleDescriptor => "XID";
+            public void Destroy()
+            {
+                RunOnGlibThread(() =>
+                {
+                    gtk_widget_destroy(_widget);
+                    return 0;
+                }).Wait();
+            }
+        }
+
+        
+        
+        public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid)
+        {
+            if (s_gtkTask == null)
+                s_gtkTask = StartGtk();
+            if (!s_gtkTask.Result)
+                return null;
+            return RunOnGlibThread(() =>
+            {
+                using (var title = new Utf8Buffer("Embedded"))
+                {
+                    var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
+                        IntPtr.Zero);
+                    gtk_widget_realize(widget);
+                    var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
+                    gtk_window_present(widget);
+                    return new FileChooser(widget,  xid);
+                }
+            }).Result;
+        }
+    }
+}

+ 39 - 0
samples/interop/NativeEmbedSample/MacHelper.cs

@@ -0,0 +1,39 @@
+using System;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace NativeEmbedSample
+{
+    public class MacHelper
+    {
+        private static bool _isInitialized;
+
+        public static void EnsureInitialized()
+        {
+            if (_isInitialized)
+                return;
+            _isInitialized = true;
+            NSApplication.Init();
+        }
+    }
+
+    class MacOSViewHandle : IPlatformHandle, IDisposable
+    {
+        private NSView _view;
+
+        public MacOSViewHandle(NSView view)
+        {
+            _view = view;
+        }
+
+        public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
+        public string HandleDescriptor => "NSView";
+
+        public void Dispose()
+        {
+            _view.Dispose();
+            _view = null;
+        }
+    }
+
+}

+ 43 - 0
samples/interop/NativeEmbedSample/MainWindow.xaml

@@ -0,0 +1,43 @@
+<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
+        Width="1024" Height="800"
+        Title="Native embedding sample"
+        xmlns:local="clr-namespace:NativeEmbedSample"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="NativeEmbedSample.MainWindow">
+  <DockPanel>
+    <Menu DockPanel.Dock="Top">
+      <MenuItem Header="Test">
+        <MenuItem Header="SubMenu">
+          <MenuItem Header="Item 1"/>
+          <MenuItem Header="Item 2"/>
+          <MenuItem Header="Item 3"/>  
+        </MenuItem>
+        <MenuItem Header="Item 1"/>
+        <MenuItem Header="Item 2"/>
+        <MenuItem Header="Item 3"/>  
+      </MenuItem>
+    </Menu>
+    <DockPanel DockPanel.Dock="Top">
+      <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
+      <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
+      <TextBox Text="Lorem ipsum dolor sit amet"/>
+    </DockPanel>
+    <Grid ColumnDefinitions="*,5,*">
+      <DockPanel>
+        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
+          <CheckBox x:Name="firstVisible" IsChecked="True"/>
+          <TextBlock>Visible</TextBlock>
+        </StackPanel>
+        <local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
+      </DockPanel>
+      <GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
+      <DockPanel Grid.Column="2">
+        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
+          <CheckBox x:Name="secondVisible" IsChecked="True"/>
+          <TextBlock>Visible</TextBlock>
+        </StackPanel>
+        <local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
+      </DockPanel>
+    </Grid>
+  </DockPanel>
+</Window>

+ 36 - 0
samples/interop/NativeEmbedSample/MainWindow.xaml.cs

@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace NativeEmbedSample
+{
+    public class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            AvaloniaXamlLoader.Load(this);
+            this.AttachDevTools();
+        }
+
+        public async void ShowPopupDelay(object sender, RoutedEventArgs args)
+        {
+            await Task.Delay(3000);
+            ShowPopup(sender, args);
+        }
+
+        public void ShowPopup(object sender, RoutedEventArgs args)
+        {
+
+            new ContextMenu()
+            {
+                Items = new List<MenuItem>
+                {
+                    new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
+                }
+            }.Open((Control)sender);
+        }
+    }
+}

+ 30 - 0
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
+    <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
+
+    <AvaloniaResource Include="**\*.xaml">
+      <SubType>Designer</SubType>
+    </AvaloniaResource>
+    <None Remove="nodes.mp4" />
+    <Content Include="nodes.mp4">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
+  </ItemGroup>
+
+  <Import Project="..\..\..\build\SampleApp.props" />
+  <Import Project="..\..\..\build\BuildTargets.targets" />
+  <Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
+</Project>

+ 17 - 0
samples/interop/NativeEmbedSample/Program.cs

@@ -0,0 +1,17 @@
+using Avalonia;
+
+namespace NativeEmbedSample
+{
+    class Program
+    {
+        static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+        public static AppBuilder BuildAvaloniaApp()
+            => AppBuilder.Configure<App>()
+                .With(new AvaloniaNativePlatformOptions()
+                {
+                })
+                .UsePlatformDetect();
+
+    }
+}

+ 74 - 0
samples/interop/NativeEmbedSample/WinApi.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace NativeEmbedSample
+{
+    public unsafe class WinApi
+    {
+        public enum CommonControls : uint
+        {
+            ICC_LISTVIEW_CLASSES   = 0x00000001, // listview, header
+            ICC_TREEVIEW_CLASSES   = 0x00000002, // treeview, tooltips
+            ICC_BAR_CLASSES    = 0x00000004, // toolbar, statusbar, trackbar, tooltips
+            ICC_TAB_CLASSES    = 0x00000008, // tab, tooltips
+            ICC_UPDOWN_CLASS       = 0x00000010, // updown
+            ICC_PROGRESS_CLASS     = 0x00000020, // progress
+            ICC_HOTKEY_CLASS       = 0x00000040, // hotkey
+            ICC_ANIMATE_CLASS      = 0x00000080, // animate
+            ICC_WIN95_CLASSES      = 0x000000FF,
+            ICC_DATE_CLASSES       = 0x00000100, // month picker, date picker, time picker, updown
+            ICC_USEREX_CLASSES     = 0x00000200, // comboex
+            ICC_COOL_CLASSES       = 0x00000400, // rebar (coolbar) control
+            ICC_INTERNET_CLASSES   = 0x00000800,
+            ICC_PAGESCROLLER_CLASS = 0x00001000,  // page scroller
+            ICC_NATIVEFNTCTL_CLASS = 0x00002000,  // native font control
+            ICC_STANDARD_CLASSES   = 0x00004000,
+            ICC_LINK_CLASS     = 0x00008000
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct INITCOMMONCONTROLSEX
+        {
+            public int dwSize;
+            public uint dwICC;
+        }
+
+        [DllImport("Comctl32.dll")]
+        public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        public static extern bool DestroyWindow(IntPtr hwnd);
+
+        [DllImport("kernel32.dll")]
+        public static extern IntPtr LoadLibrary(string lib);
+
+
+        [DllImport("kernel32.dll")]
+        public static extern IntPtr GetModuleHandle(string lpModuleName);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        public static extern IntPtr CreateWindowEx(
+            int dwExStyle,
+            string lpClassName,
+            string lpWindowName,
+            uint dwStyle,
+            int x,
+            int y,
+            int nWidth,
+            int nHeight,
+            IntPtr hWndParent,
+            IntPtr hMenu,
+            IntPtr hInstance,
+            IntPtr lpParam);
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct SETTEXTEX
+        {
+            public uint Flags;
+            public uint Codepage;
+        }
+
+        [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
+        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
+    }
+}

+ 1 - 0
samples/interop/NativeEmbedSample/nodes-license.md

@@ -0,0 +1 @@
+nodes.mp4 by beeple is licensed under the creative commons license, downloaded from https://vimeo.com/9936271

二進制
samples/interop/NativeEmbedSample/nodes.mp4


+ 141 - 0
src/Avalonia.Controls/NativeControlHost.cs

@@ -0,0 +1,141 @@
+using Avalonia.Controls.Platform;
+using Avalonia.LogicalTree;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Controls
+{
+    public class NativeControlHost : Control
+    {
+        private TopLevel _currentRoot;
+        private INativeControlHostImpl _currentHost;
+        private INativeControlHostControlTopLevelAttachment _attachment;
+        private IPlatformHandle _nativeControlHandle;
+        private bool _queuedForDestruction;
+        static NativeControlHost()
+        {
+            IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
+            TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
+        }
+
+        private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) 
+            => host.UpdateHost();
+
+        private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
+            => host.UpdateHost();
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            _currentRoot = e.Root as TopLevel;
+            UpdateHost();
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            _currentRoot = null;
+            UpdateHost();
+        }
+
+
+        void UpdateHost()
+        {
+            _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
+            var needsAttachment = _currentHost != null;
+            var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
+            
+            if (needsAttachment)
+            {
+                // If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment
+                if (_attachment != null && _attachment.AttachedTo != _currentHost)
+                {
+                    if (_attachment != null)
+                    {
+                        if (_attachment.IsCompatibleWith(_currentHost))
+                        {
+                            _attachment.AttachedTo = _currentHost;
+                        }
+                        else
+                        {
+                            _attachment.Dispose();
+                            _attachment = null;
+                        }
+                    }
+                }
+
+                // If there is no attachment, but the control exists,
+                // attempt to attach to to the current toplevel or destroy the control if it's incompatible
+                if (_attachment == null && _nativeControlHandle != null)
+                {
+                    if (_currentHost.IsCompatibleWith(_nativeControlHandle))
+                        _attachment = _currentHost.CreateNewAttachment(_nativeControlHandle);
+                    else
+                        DestroyNativeControl();
+                }
+
+                // There is no control handle an no attachment, create both
+                if (_nativeControlHandle == null)
+                {
+                    _attachment = _currentHost.CreateNewAttachment(parent =>
+                        _nativeControlHandle = CreateNativeControlCore(parent));
+                }
+            }
+            else
+            {
+                // Immediately detach the control from the current toplevel if there is an existing attachment
+                if (_attachment != null)
+                    _attachment.AttachedTo = null;
+                
+                // Don't destroy the control immediately, it might be just being reparented to another TopLevel
+                if (_nativeControlHandle != null && !_queuedForDestruction)
+                {
+                    _queuedForDestruction = true;
+                    Dispatcher.UIThread.Post(CheckDestruction, DispatcherPriority.Background);
+                }
+            }
+
+            if (needsShow)
+                _attachment?.ShowInBounds(TransformedBounds.Value);
+            else if (needsAttachment)
+                _attachment?.Hide();
+        }
+
+        public bool TryUpdateNativeControlPosition()
+        {
+            var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
+
+            if(needsShow)
+                _attachment?.ShowInBounds(TransformedBounds.Value);
+            return needsShow;
+        }
+
+        void CheckDestruction()
+        {
+            _queuedForDestruction = false;
+            if (_currentRoot == null)
+                DestroyNativeControl();
+        }
+        
+        protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
+        {
+            return _currentHost.CreateDefaultChild(parent);
+        }
+
+        void DestroyNativeControl()
+        {
+            if (_nativeControlHandle != null)
+            {
+                _attachment?.Dispose();
+                _attachment = null;
+                
+                DestroyNativeControlCore(_nativeControlHandle);
+                _nativeControlHandle = null;
+            }
+        }
+
+        protected virtual void DestroyNativeControlCore(IPlatformHandle control)
+        {
+            ((INativeControlHostDestroyableControlHandle)control).Destroy();
+        }
+        
+    }
+}

+ 12 - 0
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -3,6 +3,7 @@ using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
+using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Threading;
 
@@ -66,6 +67,9 @@ namespace Avalonia.Controls.Platform
                 window.Deactivated += WindowDeactivated;
             }
 
+            if (_root is TopLevel tl && tl.PlatformImpl is IEmbeddableWindowImpl eimpl)
+                eimpl.LostFocus += TopLevelLostPlatformFocus;
+
             _inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
         }
 
@@ -95,6 +99,9 @@ namespace Avalonia.Controls.Platform
             {
                 root.Deactivated -= WindowDeactivated;
             }
+            
+            if (_root is TopLevel tl && tl.PlatformImpl is IEmbeddableWindowImpl eimpl)
+                eimpl.LostFocus -= TopLevelLostPlatformFocus;
 
             _inputManagerSubscription?.Dispose();
 
@@ -403,6 +410,11 @@ namespace Avalonia.Controls.Platform
         {
             Menu?.Close();
         }
+        
+        private void TopLevelLostPlatformFocus()
+        {
+            Menu?.Close();
+        }
 
         protected void Click(IMenuItem item)
         {

+ 32 - 0
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@@ -0,0 +1,32 @@
+using System;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Platform
+{
+    public interface INativeControlHostImpl
+    {
+        INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent);
+        INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create);
+        INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle);
+        bool IsCompatibleWith(IPlatformHandle handle);
+    }
+
+    public interface INativeControlHostDestroyableControlHandle : IPlatformHandle
+    {
+        void Destroy();
+    }
+
+    public interface INativeControlHostControlTopLevelAttachment : IDisposable
+    {
+        INativeControlHostImpl AttachedTo { get; set; }
+        bool IsCompatibleWith(INativeControlHostImpl host);
+        void Hide();
+        void ShowInBounds(TransformedBounds transformedBounds);
+    }
+
+    public interface ITopLevelImplWithNativeControlHost
+    {
+        INativeControlHostImpl NativeControlHost { get; }
+    }
+}

+ 12 - 0
src/Avalonia.Controls/Primitives/Popup.cs

@@ -8,6 +8,7 @@ using Avalonia.Input.Raw;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
 using Avalonia.Metadata;
+using Avalonia.Platform;
 using Avalonia.VisualTree;
 
 #nullable enable
@@ -263,6 +264,11 @@ namespace Avalonia.Controls.Primitives
                 DeferCleanup(SubscribeToEventHandler<Window, EventHandler>(window, WindowDeactivated,
                     (x, handler) => x.Deactivated += handler,
                     (x, handler) => x.Deactivated -= handler));
+                
+                if (window.PlatformImpl is IEmbeddableWindowImpl reportsFocus)
+                    DeferCleanup(SubscribeToEventHandler<IEmbeddableWindowImpl, Action>(reportsFocus, WindowLostFocus,
+                        (x, handler) => x.LostFocus += handler,
+                        (x, handler) => x.LostFocus -= handler));
             }
             else
             {
@@ -512,6 +518,12 @@ namespace Avalonia.Controls.Primitives
                 Close();
             }
         }
+        
+        private void WindowLostFocus()
+        {
+            if(!StaysOpen)
+                Close();
+        }
 
         private IgnoreIsOpenScope BeginIgnoringIsOpen()
         {

+ 16 - 0
src/Avalonia.Controls/TopLevel.cs

@@ -10,6 +10,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.Utilities;
+using Avalonia.VisualTree;
 using JetBrains.Annotations;
 
 namespace Avalonia.Controls
@@ -134,6 +135,9 @@ namespace Avalonia.Controls
                     nameof(IResourceProvider.ResourcesChanged),
                     this);
             }
+
+            if (impl is IEmbeddableWindowImpl embeddableWindowImpl)
+                embeddableWindowImpl.LostFocus += PlatformImpl_LostFocus;
         }
 
         /// <summary>
@@ -369,5 +373,17 @@ namespace Avalonia.Controls
         {
             (this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect);
         }
+
+        void PlatformImpl_LostFocus()
+        {
+            var focused = (IVisual)FocusManager.Instance.Current;
+            if (focused == null)
+                return;
+            while (focused.VisualParent != null)
+                focused = focused.VisualParent;
+
+            if (focused == this)
+                KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
+        }
     }
 }

+ 136 - 0
src/Avalonia.Native/NativeControlHostImpl.cs

@@ -0,0 +1,136 @@
+using System;
+using Avalonia.Controls.Platform;
+using Avalonia.Native.Interop;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Native
+{
+    class NativeControlHostImpl : IDisposable, INativeControlHostImpl
+    {
+        private IAvnNativeControlHost _host;
+
+        public NativeControlHostImpl(IAvnNativeControlHost host)
+        {
+            _host = host;
+        }
+
+        public void Dispose()
+        {
+            _host?.Dispose();
+            _host = null;
+        }
+
+        class DestroyableNSView : INativeControlHostDestroyableControlHandle
+        {
+            private IAvnNativeControlHost _impl;
+            private IntPtr _nsView;
+
+            public DestroyableNSView(IAvnNativeControlHost impl)
+            {
+                _impl = new IAvnNativeControlHost(impl.NativePointer);
+                _impl.AddRef();
+                _nsView = _impl.CreateDefaultChild(IntPtr.Zero);
+            }
+
+            public IntPtr Handle => _nsView;
+            public string HandleDescriptor => "NSView";
+            public void Destroy()
+            {
+                if (_impl != null)
+                {
+                    _impl.DestroyDefaultChild(_nsView);
+                    _impl.Dispose();
+                    _impl = null;
+                    _nsView = IntPtr.Zero;
+                }
+            }
+        }
+        
+        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) 
+            => new DestroyableNSView(_host);
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(
+            Func<IPlatformHandle, IPlatformHandle> create)
+        {
+            var a = new Attachment(_host.CreateAttachment());
+            try
+            {
+                var child = create(a.GetParentHandle());
+                a.InitWithChild(child);
+                a.AttachedTo = this;
+                return a;
+            }
+            catch
+            {
+                a.Dispose();
+                throw;
+            }
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
+        {
+            var a = new Attachment(_host.CreateAttachment());
+            a.InitWithChild(handle);
+            a.AttachedTo = this;
+            return a;
+        }
+
+        public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "NSView";
+
+        class Attachment : INativeControlHostControlTopLevelAttachment
+        {
+            private IAvnNativeControlHostTopLevelAttachment _native;
+            private NativeControlHostImpl _attachedTo;
+            public IPlatformHandle GetParentHandle() => new PlatformHandle(_native.ParentHandle, "NSView");
+            public Attachment(IAvnNativeControlHostTopLevelAttachment native)
+            {
+                _native = native;
+            }
+            
+            public void Dispose()
+            {
+                if (_native != null)
+                {
+                    _native.ReleaseChild();
+                    _native.Dispose();
+                    _native = null;
+                }
+            }
+
+            public INativeControlHostImpl AttachedTo
+            {
+                get => _attachedTo;
+                set
+                {
+                    var host = (NativeControlHostImpl)value;
+                    if(host == null)
+                        _native.AttachTo(null);
+                    else
+                        _native.AttachTo(host._host);
+                    _attachedTo = host;
+                }
+            }
+
+            public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
+
+            public void Hide()
+            {
+                _native?.Hide();
+            }
+            
+            public void ShowInBounds(TransformedBounds transformedBounds)
+            {
+                if (_attachedTo == null)
+                    throw new InvalidOperationException("Native control isn't attached to a toplevel");
+                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
+                bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
+                    Math.Max(1, bounds.Height));
+                _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
+            }
+
+            public void InitWithChild(IPlatformHandle handle) 
+                => _native.InitializeWithChildHandle(handle.Handle);
+        }
+    }
+}

+ 17 - 1
src/Avalonia.Native/WindowImplBase.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Controls;
+using Avalonia.Controls.Platform;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
@@ -42,7 +43,8 @@ namespace Avalonia.Native
     }
 
     public abstract class WindowBaseImpl : IWindowBaseImpl,
-        IFramebufferPlatformSurface
+        IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost,
+        IEmbeddableWindowImpl
     {
         IInputRoot _inputRoot;
         IAvnWindowBase _native;
@@ -56,6 +58,7 @@ namespace Avalonia.Native
         private Size _lastRenderedLogicalSize;
         private double _savedScaling;
         private GlPlatformSurface _glSurface;
+        private NativeControlHostImpl _nativeControlHost;
 
         internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
         {
@@ -77,6 +80,7 @@ namespace Avalonia.Native
             Screen = new ScreenImpl(screens);
             _savedLogicalSize = ClientSize;
             _savedScaling = Scaling;
+            _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
 
             var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
                     .FirstOrDefault(m => m.Bounds.Contains(Position));
@@ -98,6 +102,8 @@ namespace Avalonia.Native
             this 
         };
 
+        public INativeControlHostImpl NativeControlHost => _nativeControlHost;
+
         public ILockedFramebuffer Lock()
         {
             var w = _savedLogicalSize.Width * _savedScaling;
@@ -116,6 +122,8 @@ namespace Avalonia.Native
             }, (int)w, (int)h, new Vector(dpi, dpi));
         }
 
+        public event Action LostFocus;
+        
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }
         public Action Closed { get; set; }
@@ -198,6 +206,11 @@ namespace Avalonia.Native
             {
                 Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
             }
+            
+            void IAvnWindowBaseEvents.LostFocus()
+            {
+                _parent.LostFocus?.Invoke();
+            }
         }
 
         public void Activate()
@@ -261,6 +274,9 @@ namespace Avalonia.Native
             _native?.Dispose();
             _native = null;
 
+            _nativeControlHost?.Dispose();
+            _nativeControlHost = null;
+            
             (Screen as ScreenImpl)?.Dispose();
         }
 

+ 2 - 0
src/Avalonia.Visuals/VisualTree/TransformedBounds.cs

@@ -76,5 +76,7 @@ namespace Avalonia.VisualTree
         {
             return !left.Equals(right);
         }
+
+        public override string ToString() => $"Bounds: {Bounds} Clip: {Clip} Transform {Transform}";
     }
 }

+ 10 - 0
src/Avalonia.X11/NativeDialogs/Gtk.cs

@@ -207,6 +207,9 @@ namespace Avalonia.X11.NativeDialogs
         
         [DllImport(GtkName)]
         public static extern void gtk_widget_realize(IntPtr gtkWidget);
+        
+        [DllImport(GtkName)]
+        public static extern void gtk_widget_destroy(IntPtr gtkWidget);
 
         [DllImport(GtkName)]
         public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
@@ -219,6 +222,13 @@ namespace Avalonia.X11.NativeDialogs
 
         [DllImport(GdkName)]
         static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
+        
+        [DllImport(GdkName)]
+        public static extern IntPtr gdk_x11_window_get_xid(IntPtr window);
+
+
+        [DllImport(GtkName)]
+        public static extern IntPtr gtk_container_add(IntPtr container, IntPtr widget);
 
         [DllImport(GdkName)]
         static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);

+ 190 - 0
src/Avalonia.X11/X11NativeControlHost.cs

@@ -0,0 +1,190 @@
+using System;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+using static Avalonia.X11.XLib;
+namespace Avalonia.X11
+{
+    // TODO: Actually implement XEmbed instead of simply using XReparentWindow
+    class X11NativeControlHost : INativeControlHostImpl
+    {
+        private readonly AvaloniaX11Platform _platform;
+        public X11Window Window { get; }
+
+        public X11NativeControlHost(AvaloniaX11Platform platform, X11Window window)
+        {
+            _platform = platform;
+            Window = window;
+        }
+
+        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
+        {
+            var ch = new DumbWindow(_platform.Info);
+            XSync(_platform.Display, false);
+            return ch;
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
+        {
+            var holder = new DumbWindow(_platform.Info, Window.Handle.Handle);
+            Attachment attachment = null;
+            try
+            {
+                var child = create(holder);
+                // ReSharper disable once UseObjectOrCollectionInitializer
+                // It has to be assigned to the variable before property setter is called so we dispose it on exception
+                attachment = new Attachment(_platform.Display, holder, _platform.OrphanedWindow, child);
+                attachment.AttachedTo = this;
+                return attachment;
+            }
+            catch
+            {
+                attachment?.Dispose();
+                holder?.Destroy();
+                throw;
+            }
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
+        {
+            if (!IsCompatibleWith(handle))
+                throw new ArgumentException(handle.HandleDescriptor + " is not compatible with the current window",
+                    nameof(handle));
+            var attachment = new Attachment(_platform.Display, new DumbWindow(_platform.Info, Window.Handle.Handle),
+                _platform.OrphanedWindow, handle) { AttachedTo = this };
+            return attachment;
+        }
+
+        public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "XID";
+
+        class DumbWindow : INativeControlHostDestroyableControlHandle
+        {
+            private readonly IntPtr _display;
+
+            public DumbWindow(X11Info x11, IntPtr? parent = null)
+            {
+                _display = x11.Display;
+                /*Handle = XCreateSimpleWindow(x11.Display, XLib.XDefaultRootWindow(_display),
+                    0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);*/
+                var attr = new XSetWindowAttributes
+                {
+                    backing_store = 1,
+                    bit_gravity = Gravity.NorthWestGravity,
+                    win_gravity = Gravity.NorthWestGravity,
+                    
+                };
+
+                parent = parent ?? XDefaultRootWindow(x11.Display);
+
+                Handle = XCreateWindow(_display, parent.Value, 0, 0,
+                    1,1, 0, 0,
+                    (int)CreateWindowArgs.InputOutput,
+                    IntPtr.Zero, 
+                    new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
+                                       SetWindowValuemask.BackPixel |
+                                       SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
+            }
+
+            public IntPtr Handle { get; private set; }
+            public string HandleDescriptor => "XID";
+            public void Destroy()
+            {
+                if (Handle != IntPtr.Zero)
+                {
+                    XDestroyWindow(_display, Handle);
+                    Handle = IntPtr.Zero;
+                }
+            }
+        }
+        
+        class Attachment : INativeControlHostControlTopLevelAttachment
+        {
+            private readonly IntPtr _display;
+            private readonly IntPtr _orphanedWindow;
+            private DumbWindow _holder;
+            private IPlatformHandle _child;
+            private X11NativeControlHost _attachedTo;
+            private bool _mapped;
+            
+            public Attachment(IntPtr display, DumbWindow holder, IntPtr orphanedWindow, IPlatformHandle child)
+            {
+                _display = display;
+                _orphanedWindow = orphanedWindow;
+                _holder = holder;
+                _child = child;
+                XReparentWindow(_display, child.Handle, holder.Handle, 0, 0);
+                XMapWindow(_display, child.Handle);
+            }
+            
+            public void Dispose()
+            {
+                if (_child != null)
+                {
+                    XReparentWindow(_display, _child.Handle, _orphanedWindow, 0, 0);
+                    _child = null;
+                }
+
+                _holder?.Destroy();
+                _holder = null;
+                _attachedTo = null;
+            }
+
+            void CheckDisposed()
+            {
+                if (_child == null)
+                    throw new ObjectDisposedException("X11 INativeControlHostControlTopLevelAttachment");
+            }
+
+            public INativeControlHostImpl AttachedTo
+            {
+                get => _attachedTo;
+                set
+                {
+                    CheckDisposed();
+                    _attachedTo = (X11NativeControlHost)value;
+                    if (value == null)
+                    {
+                        _mapped = false;
+                        XUnmapWindow(_display, _holder.Handle);
+                        XReparentWindow(_display, _holder.Handle, _orphanedWindow, 0, 0);
+                    }
+                    else
+                    {
+                        XReparentWindow(_display, _holder.Handle, _attachedTo.Window.Handle.Handle, 0, 0);
+                    }
+                }
+            }
+
+            public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
+
+            public void Hide()
+            {
+                if(_attachedTo == null || _child == null)
+                    return;
+                _mapped = false;
+                XUnmapWindow(_display, _holder.Handle);
+            }
+            
+            public void ShowInBounds(TransformedBounds transformedBounds)
+            {
+                CheckDisposed();
+                if (_attachedTo == null)
+                    throw new InvalidOperationException("The control isn't currently attached to a toplevel");
+                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
+                             new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
+                var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
+                    Math.Max(1, (int)bounds.Height));
+                XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
+                XMoveResizeWindow(_display, _holder.Handle, pixelRect.X, pixelRect.Y, pixelRect.Width,
+                    pixelRect.Height);
+                if (!_mapped)
+                {
+                    XMapWindow(_display, _holder.Handle);
+                    XRaiseWindow(_display, _holder.Handle);
+                    _mapped = true;
+                }
+                Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
+            }
+        }
+    }
+}

+ 3 - 0
src/Avalonia.X11/X11Platform.cs

@@ -26,12 +26,15 @@ namespace Avalonia.X11
         public IX11Screens X11Screens { get; private set; }
         public IScreenImpl Screens { get; private set; }
         public X11PlatformOptions Options { get; private set; }
+        public IntPtr OrphanedWindow { get; private set; }
         public void Initialize(X11PlatformOptions options)
         {
             Options = options;
             XInitThreads();
             Display = XOpenDisplay(IntPtr.Zero);
             DeferredDisplay = XOpenDisplay(IntPtr.Zero);
+            OrphanedWindow = XCreateSimpleWindow(Display, XDefaultRootWindow(Display), 0, 0, 1, 1, 0, IntPtr.Zero,
+                IntPtr.Zero);
             if (Display == IntPtr.Zero)
                 throw new Exception("XOpenDisplay failed");
             XError.Init();

+ 5 - 1
src/Avalonia.X11/X11Window.cs

@@ -21,7 +21,9 @@ using static Avalonia.X11.XLib;
 // ReSharper disable StringLiteralTypo
 namespace Avalonia.X11
 {
-    unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client, ITopLevelImplWithNativeMenuExporter
+    unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
+        ITopLevelImplWithNativeMenuExporter,
+        ITopLevelImplWithNativeControlHost
     {
         private readonly AvaloniaX11Platform _platform;
         private readonly IWindowImpl _popupParent;
@@ -181,6 +183,7 @@ namespace Avalonia.X11
                 PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
             if (platform.Options.UseDBusMenu)
                 NativeMenuExporter = DBusMenuExporter.TryCreate(_handle);
+            NativeControlHost = new X11NativeControlHost(_platform, this);
         }
 
         class SurfaceInfo  : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
@@ -1086,5 +1089,6 @@ namespace Avalonia.X11
 
         public IPopupPositioner PopupPositioner { get; }
         public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
+        public INativeControlHostImpl NativeControlHost { get; }
     }
 }

+ 1 - 20
src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs

@@ -24,9 +24,7 @@ namespace Avalonia.Win32.Embedding
             if (_root.IsFocused)
                 FocusManager.Instance.Focus(null);
             _root.GotFocus += RootGotFocus;
-            // ReSharper disable once PossibleNullReferenceException
-            // Always non-null at this point
-            _root.PlatformImpl.LostFocus += PlatformImpl_LostFocus;
+
             FixPosition();
         }
 
@@ -36,23 +34,6 @@ namespace Avalonia.Win32.Embedding
             set { _root.Content = value; }
         }
 
-        void Unfocus()
-        {
-            var focused = (IVisual)FocusManager.Instance.Current;
-            if (focused == null)
-                return;
-            while (focused.VisualParent != null)
-                focused = focused.VisualParent;
-
-            if (focused == _root)
-                KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
-        }
-
-        private void PlatformImpl_LostFocus()
-        {
-            Unfocus();
-        }
-
         protected override void Dispose(bool disposing)
         {
             if (disposing)

+ 2 - 59
src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs

@@ -6,11 +6,8 @@ using Avalonia.Win32.Interop;
 
 namespace Avalonia.Win32
 {
-    class EmbeddedWindowImpl : WindowImpl, IEmbeddableWindowImpl
+    class EmbeddedWindowImpl : WindowImpl
     {
-        private static IntPtr DefaultParentWindow = CreateParentWindow();
-        private static UnmanagedMethods.WndProc _wndProcDelegate;
-
         protected override IntPtr CreateWindowOverride(ushort atom)
         {
             var hWnd = UnmanagedMethods.CreateWindowEx(
@@ -22,66 +19,12 @@ namespace Avalonia.Win32
                 0,
                 640,
                 480,
-                DefaultParentWindow,
+                OffscreenParentWindow.Handle,
                 IntPtr.Zero,
                 IntPtr.Zero,
                 IntPtr.Zero);
             return hWnd;
         }
 
-        protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
-        {
-            if (msg == (uint)UnmanagedMethods.WindowsMessage.WM_KILLFOCUS)
-                LostFocus?.Invoke();
-            return base.WndProc(hWnd, msg, wParam, lParam);
-        }
-
-        public event Action LostFocus;
-
-        private static IntPtr CreateParentWindow()
-        {
-            _wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc);
-
-            var wndClassEx = new UnmanagedMethods.WNDCLASSEX
-            {
-                cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
-                hInstance = UnmanagedMethods.GetModuleHandle(null),
-                lpfnWndProc = _wndProcDelegate,
-                lpszClassName = "AvaloniaEmbeddedWindow-" + Guid.NewGuid(),
-            };
-
-            var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
-
-            if (atom == 0)
-            {
-                throw new Win32Exception();
-            }
-
-            var hwnd = UnmanagedMethods.CreateWindowEx(
-                0,
-                atom,
-                null,
-                (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
-                UnmanagedMethods.CW_USEDEFAULT,
-                UnmanagedMethods.CW_USEDEFAULT,
-                UnmanagedMethods.CW_USEDEFAULT,
-                UnmanagedMethods.CW_USEDEFAULT,
-                IntPtr.Zero,
-                IntPtr.Zero,
-                IntPtr.Zero,
-                IntPtr.Zero);
-
-            if (hwnd == IntPtr.Zero)
-            {
-                throw new Win32Exception();
-            }
-
-            return hwnd;
-        }
-
-        private static IntPtr ParentWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
-        {
-            return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
-        }
     }
 }

+ 7 - 1
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Win32.Interop
     [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Using Win32 naming for consistency.")]
     [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Look in Win32 docs.")]
     [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items must be documented", Justification = "Look in Win32 docs.")]
-    internal static class UnmanagedMethods
+    internal unsafe static class UnmanagedMethods
     {
         public const int CW_USEDEFAULT = unchecked((int)0x80000000);
 
@@ -1000,6 +1000,10 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll")]
         public static extern bool InvalidateRect(IntPtr hWnd, ref RECT lpRect, bool bErase);
 
+
+        [DllImport("user32.dll")]
+        public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase);
+
         [DllImport("user32.dll")]
         public static extern bool IsWindowEnabled(IntPtr hWnd);
 
@@ -1050,6 +1054,8 @@ namespace Avalonia.Win32.Interop
         [DllImport("user32.dll")]
         public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);
         [DllImport("user32.dll")]
+        public static extern IntPtr GetParent(IntPtr hWnd);
+        [DllImport("user32.dll")]
         public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow);
 
         [DllImport("kernel32.dll", SetLastError = true)]

+ 58 - 0
src/Windows/Avalonia.Win32/OffscreenParentWindow.cs

@@ -0,0 +1,58 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+using Avalonia.Win32.Interop;
+namespace Avalonia.Win32
+{
+    class OffscreenParentWindow
+    {
+        public static IntPtr Handle { get; } = CreateParentWindow();
+        private static UnmanagedMethods.WndProc s_wndProcDelegate;
+        private static IntPtr CreateParentWindow()
+        {
+            s_wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc);
+
+            var wndClassEx = new UnmanagedMethods.WNDCLASSEX
+            {
+                cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
+                hInstance = UnmanagedMethods.GetModuleHandle(null),
+                lpfnWndProc = s_wndProcDelegate,
+                lpszClassName = "AvaloniaEmbeddedWindow-" + Guid.NewGuid(),
+            };
+
+            var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
+
+            if (atom == 0)
+            {
+                throw new Win32Exception();
+            }
+
+            var hwnd = UnmanagedMethods.CreateWindowEx(
+                0,
+                atom,
+                null,
+                (int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
+                UnmanagedMethods.CW_USEDEFAULT,
+                UnmanagedMethods.CW_USEDEFAULT,
+                UnmanagedMethods.CW_USEDEFAULT,
+                UnmanagedMethods.CW_USEDEFAULT,
+                IntPtr.Zero,
+                IntPtr.Zero,
+                IntPtr.Zero,
+                IntPtr.Zero);
+
+            if (hwnd == IntPtr.Zero)
+            {
+                throw new Win32Exception();
+            }
+
+            return hwnd;
+        }
+
+        private static IntPtr ParentWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+        {
+            return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
+        }
+    }
+}

+ 38 - 2
src/Windows/Avalonia.Win32/PopupImpl.cs

@@ -7,11 +7,31 @@ namespace Avalonia.Win32
 {
     class PopupImpl : WindowImpl, IPopupImpl
     {
+
+        // This is needed because we are calling virtual methods from constructors
+        // One fabulous design decision leads to another, I guess
+        [ThreadStatic]
+        private static IntPtr s_parentHandle;
+
         public override void Show()
         {
             UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate);
+            var parent = UnmanagedMethods.GetParent(Handle.Handle);
+            if (parent != IntPtr.Zero)
+            {
+                IntPtr nextParent = parent;
+                while (nextParent != IntPtr.Zero)
+                {
+                    parent = nextParent;
+                    nextParent = UnmanagedMethods.GetParent(parent);
+                }
+
+                UnmanagedMethods.SetFocus(parent);
+            }
         }
 
+        protected override bool ShouldTakeFocusOnClick => false;
+
         protected override IntPtr CreateWindowOverride(ushort atom)
         {
             UnmanagedMethods.WindowStyles style =
@@ -31,10 +51,11 @@ namespace Avalonia.Win32
                 UnmanagedMethods.CW_USEDEFAULT,
                 UnmanagedMethods.CW_USEDEFAULT,
                 UnmanagedMethods.CW_USEDEFAULT,
-                IntPtr.Zero,
+                s_parentHandle,
                 IntPtr.Zero,
                 IntPtr.Zero,
                 IntPtr.Zero);
+            s_parentHandle = IntPtr.Zero;
 
             var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE);
 
@@ -56,7 +77,22 @@ namespace Avalonia.Win32
             }
         }
 
-        public PopupImpl(IWindowBaseImpl parent)
+        // This is needed because we are calling virtual methods from constructors
+        // One fabulous design decision leads to another, I guess
+        static IWindowBaseImpl SaveParentHandle(IWindowBaseImpl parent)
+        {
+            s_parentHandle = parent.Handle.Handle;
+            return parent;
+        }
+
+        // This is needed because we are calling virtual methods from constructors
+        // One fabulous design decision leads to another, I guess
+        public PopupImpl(IWindowBaseImpl parent) : this(SaveParentHandle(parent), false)
+        {
+
+        }
+
+        private PopupImpl(IWindowBaseImpl parent, bool dummy) : base()
         {
             PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
         }

+ 201 - 0
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@@ -0,0 +1,201 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    class Win32NativeControlHost : INativeControlHostImpl
+    {
+        public WindowImpl Window { get; }
+
+        public Win32NativeControlHost(WindowImpl window)
+        {
+            Window = window;
+        }
+
+        void AssertCompatible(IPlatformHandle handle)
+        {
+            if (!IsCompatibleWith(handle))
+                throw new ArgumentException($"Don't know what to do with {handle.HandleDescriptor}");
+        }
+
+        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
+        {
+            AssertCompatible(parent);
+            return new DumbWindow(parent.Handle);
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
+        {
+            var holder = new DumbWindow(Window.Handle.Handle);
+            Win32NativeControlAttachment attachment = null;
+            try
+            {
+                var child = create(holder);
+                // ReSharper disable once UseObjectOrCollectionInitializer
+                // It has to be assigned to the variable before property setter is called so we dispose it on exception
+                attachment = new Win32NativeControlAttachment(holder, child);
+                attachment.AttachedTo = this;
+                return attachment;
+            }
+            catch
+            {
+                attachment?.Dispose();
+                holder?.Destroy();
+                throw;
+            }
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
+        {
+            AssertCompatible(handle);
+            return new Win32NativeControlAttachment(new DumbWindow(Window.Handle.Handle),
+                handle) { AttachedTo = this };
+        }
+
+        public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "HWND";
+
+        class DumbWindow : IDisposable, INativeControlHostDestroyableControlHandle
+        {
+            public IntPtr Handle { get;}
+            public string HandleDescriptor => "HWND";
+            public void Destroy() => Dispose();
+
+            UnmanagedMethods.WndProc _wndProcDelegate;
+            private readonly string _className;
+
+            public DumbWindow(IntPtr? parent = null)
+            {
+                _wndProcDelegate = WndProc;
+                var wndClassEx = new UnmanagedMethods.WNDCLASSEX
+                {
+                    cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
+                    hInstance = UnmanagedMethods.GetModuleHandle(null),
+                    lpfnWndProc = _wndProcDelegate,
+                    lpszClassName = _className = "AvaloniaDumbWindow-" + Guid.NewGuid(),
+                };
+
+                var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
+                Handle = UnmanagedMethods.CreateWindowEx(
+                    0,
+                    atom,
+                    null,
+                    (int)UnmanagedMethods.WindowStyles.WS_CHILD,
+                    0,
+                    0,
+                    640,
+                    480,
+                    parent ?? OffscreenParentWindow.Handle,
+                    IntPtr.Zero,
+                    IntPtr.Zero,
+                    IntPtr.Zero);
+            }
+
+
+
+            protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
+            {
+                return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
+            }
+
+            private void ReleaseUnmanagedResources()
+            {
+                UnmanagedMethods.DestroyWindow(Handle);
+                UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null));
+            }
+
+            public void Dispose()
+            {
+                ReleaseUnmanagedResources();
+                GC.SuppressFinalize(this);
+            }
+
+            ~DumbWindow()
+            {
+                ReleaseUnmanagedResources();
+            }
+        }
+
+        class Win32NativeControlAttachment : INativeControlHostControlTopLevelAttachment
+        {
+            private DumbWindow _holder;
+            private IPlatformHandle _child;
+            private Win32NativeControlHost _attachedTo;
+
+            public Win32NativeControlAttachment(DumbWindow holder, IPlatformHandle child)
+            {
+                _holder = holder;
+                _child = child;
+                UnmanagedMethods.SetParent(child.Handle, _holder.Handle);
+                UnmanagedMethods.ShowWindow(child.Handle, UnmanagedMethods.ShowWindowCommand.Show);
+            }
+
+            void CheckDisposed()
+            {
+                if (_holder == null)
+                    throw new ObjectDisposedException(nameof(Win32NativeControlAttachment));
+            }
+
+            public void Dispose()
+            {
+                if (_child != null)
+                    UnmanagedMethods.SetParent(_child.Handle, OffscreenParentWindow.Handle);
+                _holder?.Dispose();
+                _holder = null;
+                _child = null;
+                _attachedTo = null;
+            }
+
+            public INativeControlHostImpl AttachedTo
+            {
+                get => _attachedTo;
+                set
+                {
+                    CheckDisposed();
+                    _attachedTo = (Win32NativeControlHost) value;
+                    if (_attachedTo == null)
+                    {
+                        UnmanagedMethods.ShowWindow(_holder.Handle, UnmanagedMethods.ShowWindowCommand.Hide);
+                        UnmanagedMethods.SetParent(_holder.Handle, OffscreenParentWindow.Handle);
+                    }
+                    else
+                        UnmanagedMethods.SetParent(_holder.Handle, _attachedTo.Window.Handle.Handle);
+                }
+            }
+
+            public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
+
+            public void Hide()
+            {
+                UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
+                    -100, -100, 1, 1,
+                    UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
+                    UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
+            }
+            
+            public unsafe void ShowInBounds(TransformedBounds transformedBounds)
+            {
+                CheckDisposed();
+                if (_attachedTo == null)
+                    throw new InvalidOperationException("The control isn't currently attached to a toplevel");
+                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
+                             new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
+                var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
+                    Math.Max(1, (int)bounds.Height));
+                
+                UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, pixelRect.Width, pixelRect.Height, true);
+                UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, pixelRect.X, pixelRect.Y, pixelRect.Width,
+                    pixelRect.Height,
+                    UnmanagedMethods.SetWindowPosFlags.SWP_SHOWWINDOW
+                    | UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER
+                    | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
+                
+                UnmanagedMethods.InvalidateRect(_attachedTo.Window.Handle.Handle, null, false);
+            }
+        }
+
+    }
+}

+ 15 - 1
src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs

@@ -5,6 +5,7 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
 using Avalonia.Controls;
+using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Win32.Input;
@@ -22,7 +23,8 @@ namespace Avalonia.Win32
             uint timestamp = unchecked((uint)GetMessageTime());
 
             RawInputEventArgs e = null;
-
+            var shouldTakeFocus = false;
+            
             switch ((WindowsMessage)msg)
             {
                 case WindowsMessage.WM_ACTIVATE:
@@ -155,6 +157,7 @@ namespace Avalonia.Win32
                 case WindowsMessage.WM_MBUTTONDOWN:
                 case WindowsMessage.WM_XBUTTONDOWN:
                 {
+                    shouldTakeFocus = ShouldTakeFocusOnClick;
                     if (ShouldIgnoreTouchEmulatedMessage())
                     {
                         break;
@@ -183,6 +186,7 @@ namespace Avalonia.Win32
                 case WindowsMessage.WM_MBUTTONUP:
                 case WindowsMessage.WM_XBUTTONUP:
                 {
+                    shouldTakeFocus = ShouldTakeFocusOnClick;
                     if (ShouldIgnoreTouchEmulatedMessage())
                     {
                         break;
@@ -433,12 +437,18 @@ namespace Avalonia.Win32
                     (Screen as ScreenImpl)?.InvalidateScreensCache();
                     return IntPtr.Zero;
                 }
+                case WindowsMessage.WM_KILLFOCUS:
+                    LostFocus?.Invoke();
+                    break;
             }
 
 #if USE_MANAGED_DRAG
             if (_managedDrag.PreprocessInputEvent(ref e))
                 return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
 #endif
+            
+            if (shouldTakeFocus)
+                SetFocus(_hwnd);
 
             if (e != null && Input != null)
             {
@@ -523,5 +533,9 @@ namespace Avalonia.Win32
 
             return modifiers;
         }
+        
+        public INativeControlHostImpl NativeControlHost => _nativeControlHost;
+
+        protected virtual bool ShouldTakeFocusOnClick => true;
     }
 }

+ 9 - 2
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel;
 using System.Runtime.InteropServices;
 using Avalonia.Controls;
+using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.OpenGL;
@@ -17,7 +18,9 @@ namespace Avalonia.Win32
     /// <summary>
     /// Window implementation for Win32 platform.
     /// </summary>
-    public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
+    public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
+        ITopLevelImplWithNativeControlHost,
+        IEmbeddableWindowImpl
     {
         private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
 
@@ -48,6 +51,7 @@ namespace Avalonia.Win32
         private readonly FramebufferManager _framebuffer;
         private readonly IGlPlatformSurface _gl;
 
+        private Win32NativeControlHost _nativeControlHost;
         private WndProc _wndProcDelegate;
         private string _className;
         private IntPtr _hwnd;
@@ -98,6 +102,7 @@ namespace Avalonia.Win32
 
             Screen = new ScreenImpl();
 
+            _nativeControlHost = new Win32NativeControlHost(this);
             s_instances.Add(this);
         }
 
@@ -120,6 +125,8 @@ namespace Avalonia.Win32
         public Action<PixelPoint> PositionChanged { get; set; }
 
         public Action<WindowState> WindowStateChanged { get; set; }
+        
+        public event Action LostFocus;
 
         public Thickness BorderThickness
         {
@@ -456,7 +463,7 @@ namespace Avalonia.Win32
                 0,
                 atom,
                 null,
-                (int)WindowStyles.WS_OVERLAPPEDWINDOW,
+                (int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN,
                 CW_USEDEFAULT,
                 CW_USEDEFAULT,
                 CW_USEDEFAULT,