ObjectiveC
Helper Class (#4286)
* `NativeMacOS` Helper Class * Corrections * Make CFString IDisposable * Fix `openURL:` * `dealloc` metal layer * Remove releases * Use NSString * Update Ryujinx.Ui.Common/Helper/NativeMacOS.cs Co-authored-by: merry <git@mary.rs> * Programatically select updates in Finder * Address feedback * Feedback * Ptr * Fix whoopsie * Ack suggestions * Update Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * GDK Suggestions --------- Co-authored-by: merry <git@mary.rs> Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
parent
f1943fd0b6
commit
7bae440d3a
4 changed files with 141 additions and 132 deletions
|
@ -1,127 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Avalonia;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
static partial class MetalHelper
|
|
||||||
{
|
|
||||||
private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
|
|
||||||
|
|
||||||
private struct Selector
|
|
||||||
{
|
|
||||||
public readonly IntPtr NativePtr;
|
|
||||||
|
|
||||||
public unsafe Selector(string value)
|
|
||||||
{
|
|
||||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
|
||||||
byte* data = stackalloc byte[size];
|
|
||||||
|
|
||||||
fixed (char* pValue = value)
|
|
||||||
{
|
|
||||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativePtr = sel_registerName(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Selector(string value) => new Selector(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe IntPtr GetClass(string value)
|
|
||||||
{
|
|
||||||
int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
|
|
||||||
byte* data = stackalloc byte[size];
|
|
||||||
|
|
||||||
fixed (char* pValue = value)
|
|
||||||
{
|
|
||||||
System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return objc_getClass(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct NSPoint
|
|
||||||
{
|
|
||||||
public double X;
|
|
||||||
public double Y;
|
|
||||||
|
|
||||||
public NSPoint(double x, double y)
|
|
||||||
{
|
|
||||||
X = x;
|
|
||||||
Y = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct NSRect
|
|
||||||
{
|
|
||||||
public NSPoint Pos;
|
|
||||||
public NSPoint Size;
|
|
||||||
|
|
||||||
public NSRect(double x, double y, double width, double height)
|
|
||||||
{
|
|
||||||
Pos = new NSPoint(x, y);
|
|
||||||
Size = new NSPoint(width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
|
|
||||||
{
|
|
||||||
// Create a new CAMetalLayer.
|
|
||||||
IntPtr layerClass = GetClass("CAMetalLayer");
|
|
||||||
IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
|
|
||||||
objc_msgSend(metalLayer, "init");
|
|
||||||
|
|
||||||
// Create a child NSView to render into.
|
|
||||||
IntPtr nsViewClass = GetClass("NSView");
|
|
||||||
IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
|
|
||||||
objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
|
|
||||||
|
|
||||||
// Make its renderer our metal layer.
|
|
||||||
objc_msgSend(child, "setWantsLayer:", (byte)1);
|
|
||||||
objc_msgSend(child, "setLayer:", metalLayer);
|
|
||||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
|
||||||
|
|
||||||
// Ensure the scale factor is up to date.
|
|
||||||
updateBounds = (Rect rect) => {
|
|
||||||
objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
|
||||||
};
|
|
||||||
|
|
||||||
nsView = child;
|
|
||||||
return metalLayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static unsafe partial IntPtr sel_registerName(byte* data);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static unsafe partial IntPtr objc_getClass(byte* data);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport)]
|
|
||||||
private static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
|
||||||
|
|
||||||
[LibraryImport(LibObjCImport, EntryPoint = "objc_msgSend")]
|
|
||||||
private static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,9 +2,9 @@ using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
|
@ -30,6 +30,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||||
protected IntPtr NsView { get; set; }
|
protected IntPtr NsView { get; set; }
|
||||||
protected IntPtr MetalLayer { get; set; }
|
protected IntPtr MetalLayer { get; set; }
|
||||||
|
|
||||||
|
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
|
||||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
public event EventHandler<IntPtr> WindowCreated;
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
|
@ -237,8 +238,29 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
IPlatformHandle CreateMacOS()
|
IPlatformHandle CreateMacOS()
|
||||||
{
|
{
|
||||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
// Create a new CAMetalLayer.
|
||||||
|
IntPtr layerClass = ObjectiveC.objc_getClass("CAMetalLayer");
|
||||||
|
IntPtr metalLayer = ObjectiveC.IntPtr_objc_msgSend(layerClass, "alloc");
|
||||||
|
ObjectiveC.objc_msgSend(metalLayer, "init");
|
||||||
|
|
||||||
|
// Create a child NSView to render into.
|
||||||
|
IntPtr nsViewClass = ObjectiveC.objc_getClass("NSView");
|
||||||
|
IntPtr child = ObjectiveC.IntPtr_objc_msgSend(nsViewClass, "alloc");
|
||||||
|
ObjectiveC.objc_msgSend(child, "init", new ObjectiveC.NSRect(0, 0, 0, 0));
|
||||||
|
|
||||||
|
// Make its renderer our metal layer.
|
||||||
|
ObjectiveC.objc_msgSend(child, "setWantsLayer:", 1);
|
||||||
|
ObjectiveC.objc_msgSend(child, "setLayer:", metalLayer);
|
||||||
|
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||||
|
|
||||||
|
// Ensure the scale factor is up to date.
|
||||||
|
_updateBoundsCallback = rect =>
|
||||||
|
{
|
||||||
|
ObjectiveC.objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
IntPtr nsView = child;
|
||||||
|
MetalLayer = metalLayer;
|
||||||
NsView = nsView;
|
NsView = nsView;
|
||||||
|
|
||||||
return new PlatformHandle(nsView, "NSView");
|
return new PlatformHandle(nsView, "NSView");
|
||||||
|
@ -260,7 +282,7 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
void DestroyMacOS()
|
void DestroyMacOS()
|
||||||
{
|
{
|
||||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
97
Ryujinx.Ui.Common/Helper/ObjectiveC.cs
Normal file
97
Ryujinx.Ui.Common/Helper/ObjectiveC.cs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Helper
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
public static partial class ObjectiveC
|
||||||
|
{
|
||||||
|
private const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
|
||||||
|
private static unsafe partial IntPtr sel_getUid(string name);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, StringMarshalling = StringMarshalling.Utf8)]
|
||||||
|
public static partial IntPtr objc_getClass(string name);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime)]
|
||||||
|
public static partial void objc_msgSend(IntPtr receiver, Selector selector);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime)]
|
||||||
|
public static partial void objc_msgSend(IntPtr receiver, Selector selector, byte value);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime)]
|
||||||
|
public static partial void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime)]
|
||||||
|
public static partial void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime)]
|
||||||
|
public static partial void objc_msgSend(IntPtr receiver, Selector selector, double value);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||||
|
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||||
|
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend", StringMarshalling = StringMarshalling.Utf8)]
|
||||||
|
public static partial IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector, string param);
|
||||||
|
|
||||||
|
[LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static partial bool bool_objc_msgSend(IntPtr receiver, Selector selector, IntPtr param);
|
||||||
|
|
||||||
|
public struct Selector
|
||||||
|
{
|
||||||
|
public readonly IntPtr SelPtr;
|
||||||
|
|
||||||
|
public unsafe Selector(string name)
|
||||||
|
{
|
||||||
|
SelPtr = sel_getUid(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator Selector(string value) => new(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NSString
|
||||||
|
{
|
||||||
|
public readonly IntPtr StrPtr;
|
||||||
|
|
||||||
|
public NSString(string aString)
|
||||||
|
{
|
||||||
|
IntPtr nsString = objc_getClass("NSString");
|
||||||
|
StrPtr = IntPtr_objc_msgSend(nsString, "stringWithUTF8String:", aString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator IntPtr(NSString nsString) => nsString.StrPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct NSPoint
|
||||||
|
{
|
||||||
|
public readonly double X;
|
||||||
|
public readonly double Y;
|
||||||
|
|
||||||
|
public NSPoint(double x, double y)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct NSRect
|
||||||
|
{
|
||||||
|
public readonly NSPoint Pos;
|
||||||
|
public readonly NSPoint Size;
|
||||||
|
|
||||||
|
public NSRect(double x, double y, double width, double height)
|
||||||
|
{
|
||||||
|
Pos = new NSPoint(x, y);
|
||||||
|
Size = new NSPoint(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,17 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS())
|
else if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
Process.Start("open", $"-R \"{path}\"");
|
ObjectiveC.NSString nsStringPath = new(path);
|
||||||
|
IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
|
||||||
|
var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "fileURLWithPath:", nsStringPath);
|
||||||
|
|
||||||
|
IntPtr nsArray = ObjectiveC.objc_getClass("NSArray");
|
||||||
|
IntPtr urlArray = ObjectiveC.IntPtr_objc_msgSend(nsArray, "arrayWithObject:", urlPtr);
|
||||||
|
|
||||||
|
IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
|
||||||
|
IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
|
||||||
|
|
||||||
|
ObjectiveC.objc_msgSend(sharedWorkspace, "activateFileViewerSelectingURLs:", urlArray);
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsLinux())
|
else if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
|
@ -84,7 +94,14 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS())
|
else if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
Process.Start("open", url);
|
ObjectiveC.NSString nsStringPath = new(url);
|
||||||
|
IntPtr nsUrl = ObjectiveC.objc_getClass("NSURL");
|
||||||
|
var urlPtr = ObjectiveC.IntPtr_objc_msgSend(nsUrl, "URLWithString:", nsStringPath);
|
||||||
|
|
||||||
|
IntPtr nsWorkspace = ObjectiveC.objc_getClass("NSWorkspace");
|
||||||
|
IntPtr sharedWorkspace = ObjectiveC.IntPtr_objc_msgSend(nsWorkspace, "sharedWorkspace");
|
||||||
|
|
||||||
|
ObjectiveC.bool_objc_msgSend(sharedWorkspace, "openURL:", urlPtr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
Reference in a new issue