EmbeddedWindow.cs
1 using Avalonia; 2 using Avalonia.Controls; 3 using Avalonia.Input; 4 using Avalonia.Platform; 5 using Ryujinx.Common.Configuration; 6 using Ryujinx.UI.Common.Configuration; 7 using Ryujinx.UI.Common.Helper; 8 using SPB.Graphics; 9 using SPB.Platform; 10 using SPB.Platform.GLX; 11 using SPB.Platform.X11; 12 using SPB.Windowing; 13 using System; 14 using System.Runtime.InteropServices; 15 using System.Runtime.Versioning; 16 using System.Threading.Tasks; 17 using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; 18 19 namespace Ryujinx.Ava.UI.Renderer 20 { 21 public class EmbeddedWindow : NativeControlHost 22 { 23 private WindowProc _wndProcDelegate; 24 private string _className; 25 26 protected GLXWindow X11Window { get; set; } 27 28 protected IntPtr WindowHandle { get; set; } 29 protected IntPtr X11Display { get; set; } 30 protected IntPtr NsView { get; set; } 31 protected IntPtr MetalLayer { get; set; } 32 33 public delegate void UpdateBoundsCallbackDelegate(Rect rect); 34 private UpdateBoundsCallbackDelegate _updateBoundsCallback; 35 36 public event EventHandler<IntPtr> WindowCreated; 37 public event EventHandler<Size> BoundsChanged; 38 39 public EmbeddedWindow() 40 { 41 this.GetObservable(BoundsProperty).Subscribe(StateChanged); 42 43 Initialized += OnNativeEmbeddedWindowCreated; 44 } 45 46 public virtual void OnWindowCreated() { } 47 48 protected virtual void OnWindowDestroyed() { } 49 50 protected virtual void OnWindowDestroying() 51 { 52 WindowHandle = IntPtr.Zero; 53 X11Display = IntPtr.Zero; 54 NsView = IntPtr.Zero; 55 MetalLayer = IntPtr.Zero; 56 } 57 58 private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e) 59 { 60 OnWindowCreated(); 61 62 Task.Run(() => 63 { 64 WindowCreated?.Invoke(this, WindowHandle); 65 }); 66 } 67 68 private void StateChanged(Rect rect) 69 { 70 BoundsChanged?.Invoke(this, rect.Size); 71 _updateBoundsCallback?.Invoke(rect); 72 } 73 74 protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control) 75 { 76 if (OperatingSystem.IsLinux()) 77 { 78 return CreateLinux(control); 79 } 80 81 if (OperatingSystem.IsWindows()) 82 { 83 return CreateWin32(control); 84 } 85 86 if (OperatingSystem.IsMacOS()) 87 { 88 return CreateMacOS(); 89 } 90 91 return base.CreateNativeControlCore(control); 92 } 93 94 protected override void DestroyNativeControlCore(IPlatformHandle control) 95 { 96 OnWindowDestroying(); 97 98 if (OperatingSystem.IsLinux()) 99 { 100 DestroyLinux(); 101 } 102 else if (OperatingSystem.IsWindows()) 103 { 104 DestroyWin32(control); 105 } 106 else if (OperatingSystem.IsMacOS()) 107 { 108 DestroyMacOS(); 109 } 110 else 111 { 112 base.DestroyNativeControlCore(control); 113 } 114 115 OnWindowDestroyed(); 116 } 117 118 [SupportedOSPlatform("linux")] 119 private IPlatformHandle CreateLinux(IPlatformHandle control) 120 { 121 if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) 122 { 123 X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle)); 124 X11Window.Hide(); 125 } 126 else 127 { 128 X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow; 129 } 130 131 WindowHandle = X11Window.WindowHandle.RawHandle; 132 X11Display = X11Window.DisplayHandle.RawHandle; 133 134 return new PlatformHandle(WindowHandle, "X11"); 135 } 136 137 [SupportedOSPlatform("windows")] 138 IPlatformHandle CreateWin32(IPlatformHandle control) 139 { 140 _className = "NativeWindow-" + Guid.NewGuid(); 141 142 _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) 143 { 144 switch (msg) 145 { 146 case WindowsMessages.NcHitTest: 147 return -1; 148 } 149 150 return DefWindowProc(hWnd, msg, wParam, lParam); 151 }; 152 153 WndClassEx wndClassEx = new() 154 { 155 cbSize = Marshal.SizeOf<WndClassEx>(), 156 hInstance = GetModuleHandle(null), 157 lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), 158 style = ClassStyles.CsOwndc, 159 lpszClassName = Marshal.StringToHGlobalUni(_className), 160 hCursor = CreateArrowCursor() 161 }; 162 163 RegisterClassEx(ref wndClassEx); 164 165 WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); 166 167 SetWindowLongPtrW(control.Handle, GWLP_WNDPROC, wndClassEx.lpfnWndProc); 168 169 Marshal.FreeHGlobal(wndClassEx.lpszClassName); 170 171 return new PlatformHandle(WindowHandle, "HWND"); 172 } 173 174 [SupportedOSPlatform("macos")] 175 IPlatformHandle CreateMacOS() 176 { 177 // Create a new CAMetalLayer. 178 ObjectiveC.Object layerObject = new("CAMetalLayer"); 179 ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc"); 180 metalLayer.SendMessage("init"); 181 182 // Create a child NSView to render into. 183 ObjectiveC.Object nsViewObject = new("NSView"); 184 ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc"); 185 child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0)); 186 187 // Make its renderer our metal layer. 188 child.SendMessage("setWantsLayer:", 1); 189 child.SendMessage("setLayer:", metalLayer); 190 metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); 191 192 // Ensure the scale factor is up to date. 193 _updateBoundsCallback = rect => 194 { 195 metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); 196 }; 197 198 IntPtr nsView = child.ObjPtr; 199 MetalLayer = metalLayer.ObjPtr; 200 NsView = nsView; 201 202 return new PlatformHandle(nsView, "NSView"); 203 } 204 205 [SupportedOSPlatform("Linux")] 206 void DestroyLinux() 207 { 208 X11Window?.Dispose(); 209 } 210 211 [SupportedOSPlatform("windows")] 212 void DestroyWin32(IPlatformHandle handle) 213 { 214 DestroyWindow(handle.Handle); 215 UnregisterClass(_className, GetModuleHandle(null)); 216 } 217 218 [SupportedOSPlatform("macos")] 219 #pragma warning disable CA1822 // Mark member as static 220 void DestroyMacOS() 221 { 222 // TODO 223 } 224 #pragma warning restore CA1822 225 } 226 }