diff --git a/Playground/SpiderEye.Playground.Mac/SpiderEye.Playground.Mac.csproj b/Playground/SpiderEye.Playground.Mac/SpiderEye.Playground.Mac.csproj
index e90c0549..12832575 100644
--- a/Playground/SpiderEye.Playground.Mac/SpiderEye.Playground.Mac.csproj
+++ b/Playground/SpiderEye.Playground.Mac/SpiderEye.Playground.Mac.csproj
@@ -2,7 +2,7 @@
- net6.0
+ net6.0-macos
$(DefineConstants);MAC
diff --git a/README.md b/README.md
index 623559b0..9318db63 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ What's the name supposed to mean? Simple: what kind of view does a spiders eye h
| Windows | 7, 8.x, 10, 11 | .NET 6.0 | WinForms WebBrowser control | IE 9-11 (depending on OS and installed version) |
| Windows | 7, 8.1, 10, 11 | .NET 6.0 | WebView2 | Edge Chromium |
| Linux | any 64bit distro where .NET 6.0 runs | .NET 6.0 | WebKit2GTK | WebKit |
-| macOS | x64 10.13 or newer | .NET 6.0 | WKWebView | WebKit |
+| macOS | x64 10.14 or newer | .NET 6.0 | WKWebView | WebKit |
| Linux Dependencies | Used for | Optional |
| ----- | ----- | ----- |
@@ -170,7 +170,7 @@ First you need an `Info.plist` file like you'd have for any other macOS app. Her
CFBundleShortVersionString
1.0.0
LSMinimumSystemVersion
- 10.13
+ 10.14
CFBundleInfoDictionaryVersion
6.0
CFBundlePackageType
diff --git a/Source/SpiderEye.Mac/CocoaApplication.cs b/Source/SpiderEye.Mac/CocoaApplication.cs
index 369f5930..8c9693e7 100644
--- a/Source/SpiderEye.Mac/CocoaApplication.cs
+++ b/Source/SpiderEye.Mac/CocoaApplication.cs
@@ -1,7 +1,7 @@
using System;
using System.Threading;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
+using AppKit;
+using Foundation;
namespace SpiderEye.Mac
{
@@ -11,65 +11,38 @@ internal class CocoaApplication : IApplication
public SynchronizationContext SynchronizationContext { get; }
- public IntPtr Handle { get; }
-
- private static readonly NativeClassDefinition AppDelegateDefinition;
- private readonly NativeClassInstance appDelegate;
-
- static CocoaApplication()
- {
- AppDelegateDefinition = CreateAppDelegate();
- }
-
public CocoaApplication()
{
- Factory = new CocoaUiFactory();
- SynchronizationContext = new CocoaSynchronizationContext();
+ NSApplication.Init();
- Handle = AppKit.Call("NSApplication", "sharedApplication");
- appDelegate = AppDelegateDefinition.CreateInstance(this);
+ Factory = new CocoaUiFactory();
+ SynchronizationContext = SynchronizationContext.Current!;
- ObjC.Call(Handle, "setActivationPolicy:", IntPtr.Zero);
- ObjC.Call(Handle, "setDelegate:", appDelegate.Handle);
+ NSApplication.SharedApplication.Delegate = new CocoaAppDelegate();
+ NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.Regular;
}
public void Run()
{
- ObjC.Call(Handle, "run");
+ NSApplication.SharedApplication.Run();
}
public void Exit()
{
- ObjC.Call(Handle, "terminate:", Handle);
- appDelegate.Dispose();
+ NSApplication.SharedApplication.Terminate(NSApplication.SharedApplication);
}
- private static NativeClassDefinition CreateAppDelegate()
+ private class CocoaAppDelegate : NSApplicationDelegate
{
- // note: NSApplicationDelegate is not available at runtime and returns null, it's kept for completeness
- var definition = NativeClassDefinition.FromClass(
- "SpiderEyeAppDelegate",
- AppKit.GetClass("NSResponder"),
- AppKit.GetProtocol("NSApplicationDelegate"),
- AppKit.GetProtocol("NSTouchBarProvider"));
-
- definition.AddMethod(
- "applicationShouldTerminateAfterLastWindowClosed:",
- "c@:@",
- (self, op, notification) => (byte)(Application.ExitWithLastWindow ? 1 : 0));
-
- definition.AddMethod(
- "applicationDidFinishLaunching:",
- "v@:@",
- (self, op, notification) =>
- {
- var instance = definition.GetParent(self);
- ObjC.Call(instance.Handle, "activateIgnoringOtherApps:", true);
- });
-
- definition.FinishDeclaration();
-
- return definition;
+ public override void DidFinishLaunching(NSNotification notification)
+ {
+ NSApplication.SharedApplication.ActivateIgnoringOtherApps(true);
+ }
+
+ public override bool ApplicationShouldTerminateAfterLastWindowClosed(NSApplication sender)
+ {
+ return Application.ExitWithLastWindow;
+ }
}
}
}
diff --git a/Source/SpiderEye.Mac/CocoaStatusIcon.cs b/Source/SpiderEye.Mac/CocoaStatusIcon.cs
index 623cd1bc..292ef644 100644
--- a/Source/SpiderEye.Mac/CocoaStatusIcon.cs
+++ b/Source/SpiderEye.Mac/CocoaStatusIcon.cs
@@ -1,7 +1,5 @@
-using System;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
+using AppKit;
+using Foundation;
namespace SpiderEye.Mac
{
@@ -9,8 +7,11 @@ internal class CocoaStatusIcon : IStatusIcon
{
public string? Title
{
- get; // TODO: see if setting title is useful on macOS StatusBar
- set;
+ get { return statusItem.Title; }
+ set
+ {
+ statusItem.Title = value ?? string.Empty;
+ }
}
public AppIcon? Icon
@@ -19,7 +20,16 @@ public AppIcon? Icon
set
{
icon = value;
- UpdateIcon(value);
+
+ NSImage? image = null;
+ if (value != null && value.Icons.Length > 0)
+ {
+ byte[] data = value.GetIconData(value.DefaultIcon);
+ using var nsData = NSData.FromArray(data);
+ image = new NSImage(nsData);
+ }
+
+ statusItem.Button.Image = image;
}
}
@@ -29,59 +39,29 @@ public Menu? Menu
set
{
menu = value;
- UpdateMenu(value);
+ statusItem.Menu = (NSMenu?)value?.NativeMenu;
}
}
- private readonly IntPtr statusItem;
- private readonly IntPtr statusBarButton;
+ private readonly NSStatusItem statusItem;
private AppIcon? icon;
private Menu? menu;
public CocoaStatusIcon(string title)
{
- var statusBar = AppKit.Call("NSStatusBar", "systemStatusBar");
- statusItem = ObjC.Call(statusBar, "statusItemWithLength:", -2.0); // -1 = variable size; -2 = square size
- ObjC.Call(statusItem, "setHighlightMode:", true);
- statusBarButton = ObjC.Call(statusItem, "button");
- ObjC.Call(statusBarButton, "setImageScaling:", new UIntPtr((uint)NSImageScaling.ProportionallyUpOrDown));
- Title = title;
- }
-
- public void Dispose()
- {
- // don't think anything needs to be done here
- }
-
- private unsafe void UpdateIcon(AppIcon? icon)
- {
- var image = IntPtr.Zero;
- if (icon != null && icon.Icons.Length > 0)
- {
- byte[] data = icon.GetIconData(icon.DefaultIcon);
- fixed (byte* dataPtr = data)
- {
- IntPtr nsData = Foundation.Call(
- "NSData",
- "dataWithBytesNoCopy:length:freeWhenDone:",
- (IntPtr)dataPtr,
- new IntPtr(data.Length),
- IntPtr.Zero);
+ var statusBar = NSStatusBar.SystemStatusBar;
- image = AppKit.Call("NSImage", "alloc");
- ObjC.Call(image, "initWithData:", nsData);
- ObjC.Call(statusBarButton, "setImage:", image);
- }
- }
-
- ObjC.Call(statusBarButton, "setImage:", image);
+ statusItem = statusBar.CreateStatusItem(NSStatusItemLength.Square);
+ statusItem.HighlightMode = true;
+ statusItem.Title = title;
+ statusItem.Button.ImageScaling = NSImageScale.ProportionallyUpOrDown;
}
- private void UpdateMenu(Menu? menu)
+ public void Dispose()
{
- var nativeMenu = NativeCast.To(menu?.NativeMenu);
- ObjC.Call(statusItem, "setMenu:", nativeMenu?.Handle ?? IntPtr.Zero);
+ statusItem.StatusBar.RemoveStatusItem(statusItem);
+ statusItem.Dispose();
}
}
}
diff --git a/Source/SpiderEye.Mac/CocoaSynchronizationContext.cs b/Source/SpiderEye.Mac/CocoaSynchronizationContext.cs
deleted file mode 100644
index 5ea9d4e3..00000000
--- a/Source/SpiderEye.Mac/CocoaSynchronizationContext.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using System.Threading;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac
-{
- internal sealed class CocoaSynchronizationContext : SynchronizationContext
- {
- public bool IsMainThread
- {
- get { return Environment.CurrentManagedThreadId == mainThreadId; }
- }
-
- private readonly int mainThreadId;
-
- public CocoaSynchronizationContext()
- : this(Environment.CurrentManagedThreadId)
- {
- }
-
- private CocoaSynchronizationContext(int mainThreadId)
- {
- this.mainThreadId = mainThreadId;
- }
-
- public override SynchronizationContext CreateCopy()
- {
- return new CocoaSynchronizationContext(mainThreadId);
- }
-
- public override void Post(SendOrPostCallback d, object? state)
- {
- if (d == null) { throw new ArgumentNullException(nameof(d)); }
-
- var data = new InvokeState(d, state);
- var handle = GCHandle.Alloc(data, GCHandleType.Normal);
- Dispatch.AsyncFunction(Dispatch.MainQueue, GCHandle.ToIntPtr(handle), InvokeCallback);
- }
-
- public override void Send(SendOrPostCallback d, object? state)
- {
- if (d == null) { throw new ArgumentNullException(nameof(d)); }
-
- if (IsMainThread) { d(state); }
- else
- {
- var data = new InvokeState(d, state);
- var handle = GCHandle.Alloc(data, GCHandleType.Normal);
- Dispatch.SyncFunction(Dispatch.MainQueue, GCHandle.ToIntPtr(handle), InvokeCallback);
- }
- }
-
- private static void InvokeCallback(IntPtr data)
- {
- var handle = GCHandle.FromIntPtr(data);
- var state = (InvokeState)handle.Target!;
-
- try { state.Callback(state.State); }
- finally { handle.Free(); }
- }
-
- private sealed class InvokeState
- {
- public readonly SendOrPostCallback Callback;
- public readonly object? State;
-
- public InvokeState(SendOrPostCallback callback, object? state)
- {
- Callback = callback;
- State = state;
- }
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/CocoaWebview.cs b/Source/SpiderEye.Mac/CocoaWebview.cs
index 39f1ddc8..1fbf6237 100644
--- a/Source/SpiderEye.Mac/CocoaWebview.cs
+++ b/Source/SpiderEye.Mac/CocoaWebview.cs
@@ -1,15 +1,14 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
+using System;
using System.Threading.Tasks;
+using CoreGraphics;
+using Foundation;
using SpiderEye.Bridge;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
using SpiderEye.Tools;
+using WebKit;
namespace SpiderEye.Mac
{
- internal class CocoaWebview : IWebview
+ internal class CocoaWebview : WKWebView, IWebview
{
public event NavigatingEventHandler? Navigating;
public event PageLoadEventHandler? PageLoaded;
@@ -20,78 +19,59 @@ internal class CocoaWebview : IWebview
public bool UseBrowserTitle { get; set; }
public bool EnableDevTools
{
- get { return enableDevToolsField; }
+ get
+ {
+ using var key = new NSString("developerExtrasEnabled");
+ using var value = (NSNumber)preferences.ValueForKey(key);
+ return value.BoolValue;
+ }
set
{
- enableDevToolsField = value;
- IntPtr boolValue = Foundation.Call("NSNumber", "numberWithBool:", value);
- ObjC.Call(preferences, "setValue:forKey:", boolValue, NSString.Create("developerExtrasEnabled"));
+ using var boolValue = new NSNumber(value);
+ using var key = new NSString("developerExtrasEnabled");
+ preferences.SetValueForKey(boolValue, key);
}
}
private Uri Uri
{
- get { return URL.GetAsUri(ObjC.Call(Handle, "URL"))!; }
+ get { return this.Url; }
}
- public readonly IntPtr Handle;
-
- private static readonly NativeClassDefinition CallbackClassDefinition;
- private static readonly NativeClassDefinition SchemeHandlerDefinition;
-
- private readonly NativeClassInstance callbackClass;
- private readonly NativeClassInstance schemeHandler;
-
+ private const string SCHEME = "spidereye";
private readonly WebviewBridge bridge;
private readonly Uri customHost;
- private readonly IntPtr preferences;
-
- private bool enableDevToolsField;
-
- static CocoaWebview()
- {
- CallbackClassDefinition = CreateCallbackClass();
- SchemeHandlerDefinition = CreateSchemeHandler();
- }
+ private readonly WKPreferences preferences;
+ private readonly IDisposable titleObserver;
public CocoaWebview(WebviewBridge bridge)
+ : base(CGRect.Empty, CreateConfiguration())
{
this.bridge = bridge ?? throw new ArgumentNullException(nameof(bridge));
- IntPtr configuration = WebKit.Call("WKWebViewConfiguration", "new");
- IntPtr manager = ObjC.Call(configuration, "userContentController");
+ customHost = new Uri(UriTools.GetRandomResourceUrl(SCHEME));
- callbackClass = CallbackClassDefinition.CreateInstance(this);
- schemeHandler = SchemeHandlerDefinition.CreateInstance(this);
+ WKUserContentController manager = Configuration.UserContentController;
+ manager.AddScriptMessageHandler(new CocoaScriptMessageHandler(this), "external");
+ using var initScriptSource = new NSString(Resources.GetInitScript("Mac"));
+ var script = new WKUserScript(initScriptSource, WKUserScriptInjectionTime.AtDocumentStart, false);
+ manager.AddUserScript(script);
- const string scheme = "spidereye";
- customHost = new Uri(UriTools.GetRandomResourceUrl(scheme));
- ObjC.Call(configuration, "setURLSchemeHandler:forURLScheme:", schemeHandler.Handle, NSString.Create(scheme));
+ NavigationDelegate = new CocoaNavigationDelegate();
- ObjC.Call(manager, "addScriptMessageHandler:name:", callbackClass.Handle, NSString.Create("external"));
- IntPtr script = WebKit.Call("WKUserScript", "alloc");
- ObjC.Call(
- script,
- "initWithSource:injectionTime:forMainFrameOnly:",
- NSString.Create(Resources.GetInitScript("Mac")),
- IntPtr.Zero,
- IntPtr.Zero);
- ObjC.Call(manager, "addUserScript:", script);
+ using var boolValue = NSNumber.FromBoolean(false);
+ using var key = new NSString("drawsBackground");
+ SetValueForKey(boolValue, key);
- Handle = WebKit.Call("WKWebView", "alloc");
- ObjC.Call(Handle, "initWithFrame:configuration:", CGRect.Zero, configuration);
- ObjC.Call(Handle, "setNavigationDelegate:", callbackClass.Handle);
-
- IntPtr boolValue = Foundation.Call("NSNumber", "numberWithBool:", false);
- ObjC.Call(Handle, "setValue:forKey:", boolValue, NSString.Create("drawsBackground"));
- ObjC.Call(Handle, "addObserver:forKeyPath:options:context:", callbackClass.Handle, NSString.Create("title"), IntPtr.Zero, IntPtr.Zero);
-
- preferences = ObjC.Call(configuration, "preferences");
- }
+ titleObserver = AddObserver("title", NSKeyValueObservingOptions.New, observedChange =>
+ {
+ if (UseBrowserTitle)
+ {
+ TitleChanged?.Invoke(this, (NSString?)observedChange.NewValue ?? string.Empty);
+ }
+ });
- public void UpdateBackgroundColor(IntPtr color)
- {
- ObjC.Call(Handle, "setBackgroundColor:", color);
+ preferences = Configuration.Preferences;
}
public void LoadUri(Uri uri)
@@ -100,256 +80,162 @@ public void LoadUri(Uri uri)
if (!uri.IsAbsoluteUri) { uri = new Uri(customHost, uri); }
- IntPtr nsUrl = Foundation.Call("NSURL", "URLWithString:", NSString.Create(uri.ToString()));
- IntPtr request = Foundation.Call("NSURLRequest", "requestWithURL:", nsUrl);
- ObjC.Call(Handle, "loadRequest:", request);
+ using var request = NSUrlRequest.FromUrl(uri);
+ LoadRequest(request);
}
public Task ExecuteScriptAsync(string script)
{
var taskResult = new TaskCompletionSource();
- NSBlock? block = null;
-
- ScriptEvalCallbackDelegate callback = (IntPtr self, IntPtr result, IntPtr error) =>
+ EvaluateJavaScript(script, (result, error) =>
{
- try
+ if (error != null)
{
- if (error != IntPtr.Zero)
- {
- string? message = NSString.GetString(ObjC.Call(error, "localizedDescription"));
- taskResult.TrySetException(new ScriptException($"Script execution failed with: \"{message}\""));
- }
- else
- {
- string? content = NSString.GetString(result);
- taskResult.TrySetResult(content);
- }
+ taskResult.SetException(new ScriptException("Script execution failed.", new NSErrorException(error)));
}
- catch (Exception ex) { taskResult.TrySetException(ex); }
- finally { block!.Dispose(); }
- };
-
- block = new NSBlock(callback);
- ObjC.Call(
- Handle,
- "evaluateJavaScript:completionHandler:",
- NSString.Create(script),
- block.Handle);
-
+ else
+ {
+ taskResult.SetResult((NSString)result);
+ }
+ });
return taskResult.Task;
}
- public void Dispose()
+ protected override void Dispose(bool disposing)
{
- // webview will be released automatically
- callbackClass.Dispose();
- schemeHandler.Dispose();
+ if (disposing)
+ {
+ titleObserver.Dispose();
+ }
+
+ base.Dispose(disposing);
}
- private static NativeClassDefinition CreateCallbackClass()
+ private static WKWebViewConfiguration CreateConfiguration()
{
- // note: WKScriptMessageHandler is not available at runtime and returns null, it's kept for completeness
- var definition = NativeClassDefinition.FromObject(
- "SpiderEyeWebviewCallbacks",
- WebKit.GetProtocol("WKNavigationDelegate"),
- WebKit.GetProtocol("WKScriptMessageHandler"));
-
- definition.AddMethod(
- "webView:decidePolicyForNavigationAction:decisionHandler:",
- "v@:@@@",
- (self, op, view, navigationAction, decisionHandler) =>
- {
- var instance = definition.GetParent(self);
- var args = new NavigatingEventArgs(instance.Uri);
- instance.Navigating?.Invoke(instance, args);
-
- var block = Marshal.PtrToStructure(decisionHandler);
- var callback = Marshal.GetDelegateForFunctionPointer(block.Invoke);
- callback(decisionHandler, args.Cancel ? IntPtr.Zero : new IntPtr(1));
- });
-
- definition.AddMethod(
- "webView:didFinishNavigation:",
- "v@:@@",
- (self, op, view, navigation) =>
- {
- var instance = definition.GetParent(self);
- instance.PageLoaded?.Invoke(instance, new PageLoadEventArgs(instance.Uri, true));
- });
-
- definition.AddMethod(
- "webView:didFailNavigation:withError:",
- "v@:@@@",
- (self, op, view, navigation, error) =>
- {
- var instance = definition.GetParent(self);
- instance.PageLoaded?.Invoke(instance, new PageLoadEventArgs(instance.Uri, false));
- });
-
- definition.AddMethod(
- "observeValueForKeyPath:ofObject:change:context:",
- "v@:@@@@",
- (self, op, keyPath, obj, change, context) =>
- {
- var instance = definition.GetParent(self);
- ObservedValueChanged(instance, keyPath);
- });
-
- definition.AddMethod(
- "userContentController:didReceiveScriptMessage:",
- "v@:@@",
- (self, op, notification, message) =>
- {
- var instance = definition.GetParent(self);
- ScriptCallback(instance, message);
- });
-
- definition.FinishDeclaration();
-
- return definition;
+ var configuration = new WKWebViewConfiguration();
+ configuration.SetUrlSchemeHandler(new CocoaUrlSchemeHandler(), SCHEME);
+ return configuration;
}
- private static NativeClassDefinition CreateSchemeHandler()
+ private class CocoaNavigationDelegate : WKNavigationDelegate
{
- // note: WKURLSchemeHandler is not available at runtime and returns null, it's kept for completeness
- var definition = NativeClassDefinition.FromObject(
- "SpiderEyeSchemeHandler",
- WebKit.GetProtocol("WKURLSchemeHandler"));
-
- definition.AddMethod(
- "webView:startURLSchemeTask:",
- "v@:@@",
- (self, op, view, schemeTask) =>
- {
- var instance = definition.GetParent(self);
- UriSchemeStartCallback(instance, schemeTask);
- });
+ public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
+ {
+ var cocoaWebView = (CocoaWebview)webView;
+ var args = new NavigatingEventArgs(cocoaWebView.Uri);
+ cocoaWebView.Navigating?.Invoke(cocoaWebView, args);
+ decisionHandler.Invoke(args.Cancel ? WKNavigationActionPolicy.Cancel : WKNavigationActionPolicy.Allow);
+ }
- definition.AddMethod(
- "webView:stopURLSchemeTask:",
- "v@:@@",
- (self, op, view, schemeTask) => { /* don't think anything needs to be done here */ });
+ public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
+ {
+ var cocoaWebView = (CocoaWebview)webView;
+ cocoaWebView.PageLoaded?.Invoke(cocoaWebView, new PageLoadEventArgs(cocoaWebView.Uri, true));
+ }
- definition.FinishDeclaration();
+ public override void DidFailNavigation(WKWebView webView, WKNavigation navigation, NSError error)
+ {
+ var cocoaWebView = (CocoaWebview)webView;
+ cocoaWebView.PageLoaded?.Invoke(cocoaWebView, new PageLoadEventArgs(cocoaWebView.Uri, false));
+ }
- return definition;
+ public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
+ {
+ // `WKWebView` doesn't allow redirects from HTTP(S) to non-HTTP(S) schemes. That's because it's
+ // disallowed in the Fetch standard. See https://developer.apple.com/forums/thread/681530 for more
+ // information. Additionally, WebKit doesn't offer any API to handle the redirect properly ourselves.
+ // Instead, the page breaks - the document's location points to the redirect URL even though that page
+ // wasn't loaded. And worst of all, the `WKUrlSchemeHandler` isn't called. See
+ // https://bugs.webkit.org/show_bug.cgi?id=173730, especially comment 21.
+ if (error.LocalizedDescription.Equals("Redirection to URL with a scheme that is not HTTP(S)", StringComparison.OrdinalIgnoreCase)
+ && error.UserInfo["NSErrorFailingURLKey"] is NSUrl url
+ && url.Scheme == SCHEME)
+ {
+ // Simply loading the URL works - sometimes. Sometimes, the web view updates the document location
+ // but doesn't load the content, never calling the `WKUrlSchemeHandler`. To work around that issue,
+ // we first switch to another page (`about:blank`), then back to the URL we wanted to get to.
+ var cocoaWebView = (CocoaWebview)webView;
+
+ // Ensure the window title doesn't change to a blank string (for `about:blank`) for a split second,
+ // then back to the actual page's title.
+ var usedBrowserTitle = cocoaWebView.UseBrowserTitle;
+ cocoaWebView.UseBrowserTitle = false;
+
+ webView.EvaluateJavaScript(
+ "window.location.href=\"about:blank\"",
+ (_, _) =>
+ // Since loading is async, we need to load the actual URL afterward, which would require
+ // state management to keep track of when to do that. So instead, we can request the loading
+ // of that URL after kicking off the other page load, simply via `Task.Run`.
+ Task.Run(() =>
+ {
+ cocoaWebView.UseBrowserTitle = usedBrowserTitle;
+ cocoaWebView.LoadUri(url);
+ }));
+ }
+ }
}
- private static void ObservedValueChanged(CocoaWebview instance, IntPtr keyPath)
+ private class CocoaScriptMessageHandler : WKScriptMessageHandler
{
- string? key = NSString.GetString(keyPath);
- if (key == "title" && instance.UseBrowserTitle)
+ private readonly CocoaWebview cocoaWebView;
+
+ public CocoaScriptMessageHandler(CocoaWebview cocoaWebView)
{
- string? title = NSString.GetString(ObjC.Call(instance.Handle, "title"));
- instance.TitleChanged?.Invoke(instance, title ?? string.Empty);
+ this.cocoaWebView = cocoaWebView;
}
- }
- private static async void ScriptCallback(CocoaWebview instance, IntPtr message)
- {
- if (instance.EnableScriptInterface)
+ public override async void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
- IntPtr body = ObjC.Call(message, "body");
- IntPtr isString = ObjC.Call(body, "isKindOfClass:", Foundation.GetClass("NSString"));
- if (isString != IntPtr.Zero)
+ if (cocoaWebView.EnableScriptInterface && message.Body is NSString body)
{
- string data = NSString.GetString(body)!;
- await instance.bridge.HandleScriptCall(data);
+ await cocoaWebView.bridge.HandleScriptCall(body);
}
}
}
- private static void UriSchemeStartCallback(CocoaWebview instance, IntPtr schemeTask)
+ private class CocoaUrlSchemeHandler : NSObject, IWKUrlSchemeHandler
{
- try
+ public void StartUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
{
- IntPtr request = ObjC.Call(schemeTask, "request");
- IntPtr url = ObjC.Call(request, "URL");
-
- var uri = URL.GetAsUri(url)!;
- var host = new Uri(uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped));
- if (host == instance.customHost)
+ try
{
- using var contentStream = Application.ContentProvider.GetStreamAsync(uri).GetAwaiter().GetResult();
- if (contentStream != null)
+ var cocoaWebView = (CocoaWebview)webView;
+ var request = urlSchemeTask.Request;
+ Uri uri = request.Url;
+
+ var host = new Uri(uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped));
+ if (host == cocoaWebView.customHost)
{
- if (contentStream is UnmanagedMemoryStream unmanagedMemoryStream)
+ using var contentStream = Application.ContentProvider.GetStreamAsync(uri).GetAwaiter().GetResult();
+ if (contentStream != null)
{
- unsafe
- {
- long length = unmanagedMemoryStream.Length - unmanagedMemoryStream.Position;
- var data = (IntPtr)unmanagedMemoryStream.PositionPointer;
- FinishUriSchemeCallback(url, schemeTask, data, length, uri);
- return;
- }
- }
- else
- {
- byte[] data;
- long length;
- if (contentStream is MemoryStream memoryStream)
- {
- data = memoryStream.GetBuffer();
- length = memoryStream.Length;
- }
- else
- {
- using var copyStream = new MemoryStream();
- contentStream.CopyTo(copyStream);
- data = copyStream.GetBuffer();
- length = copyStream.Length;
- }
-
- unsafe
- {
- fixed (byte* dataPtr = data)
- {
- FinishUriSchemeCallback(url, schemeTask, (IntPtr)dataPtr, length, uri);
- return;
- }
- }
+ using var data = NSData.FromStream(contentStream)!;
+ using var response = new NSUrlResponse(uri, MimeTypes.FindForUri(uri), (nint)contentStream.Length, null);
+ urlSchemeTask.DidReceiveResponse(response);
+ urlSchemeTask.DidReceiveData(data);
+ urlSchemeTask.DidFinish();
+ return;
}
}
- }
- FinishUriSchemeCallbackWithError(schemeTask, 404);
+ FinishUriSchemeCallbackWithError(urlSchemeTask, 404);
+ }
+ catch { FinishUriSchemeCallbackWithError(urlSchemeTask, 500); }
}
- catch { FinishUriSchemeCallbackWithError(schemeTask, 500); }
- }
- private static void FinishUriSchemeCallback(IntPtr url, IntPtr schemeTask, IntPtr data, long contentLength, Uri uri)
- {
- IntPtr response = Foundation.Call("NSURLResponse", "alloc");
- ObjC.Call(
- response,
- "initWithURL:MIMEType:expectedContentLength:textEncodingName:",
- url,
- NSString.Create(MimeTypes.FindForUri(uri)),
- new IntPtr(contentLength),
- IntPtr.Zero);
-
- ObjC.Call(schemeTask, "didReceiveResponse:", response);
-
- IntPtr nsData = Foundation.Call(
- "NSData",
- "dataWithBytesNoCopy:length:freeWhenDone:",
- data,
- new IntPtr(contentLength),
- IntPtr.Zero);
- ObjC.Call(schemeTask, "didReceiveData:", nsData);
-
- ObjC.Call(schemeTask, "didFinish");
- }
+ public void StopUrlSchemeTask(WKWebView webView, IWKUrlSchemeTask urlSchemeTask)
+ {
+ // don't think anything needs to be done here
+ }
- private static void FinishUriSchemeCallbackWithError(IntPtr schemeTask, int errorCode)
- {
- var error = Foundation.Call(
- "NSError",
- "errorWithDomain:code:userInfo:",
- NSString.Create("com.bildstein.spidereye"),
- new IntPtr(errorCode),
- IntPtr.Zero);
- ObjC.Call(schemeTask, "didFailWithError:", error);
+ private static void FinishUriSchemeCallbackWithError(IWKUrlSchemeTask urlSchemeTask, int errorCode)
+ {
+ using var domain = new NSString("com.bildstein.spidereye");
+ using var error = new NSError(domain, errorCode);
+ urlSchemeTask.DidFailWithError(error);
+ }
}
}
}
diff --git a/Source/SpiderEye.Mac/CocoaWindow.cs b/Source/SpiderEye.Mac/CocoaWindow.cs
index a11b348e..aa292857 100644
--- a/Source/SpiderEye.Mac/CocoaWindow.cs
+++ b/Source/SpiderEye.Mac/CocoaWindow.cs
@@ -1,61 +1,61 @@
-using System;
-using System.Runtime.InteropServices;
+using System;
+using AppKit;
+using CoreGraphics;
+using Foundation;
using SpiderEye.Bridge;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
+using SpiderEye.Tools;
namespace SpiderEye.Mac
{
- internal class CocoaWindow : IWindow
+ internal class CocoaWindow : NSWindow, IWindow
{
public event CancelableEventHandler? Closing;
public event EventHandler? Closed;
public event EventHandler? Shown;
- public string? Title
+ string? IWindow.Title
{
- get { return NSString.GetString(ObjC.Call(Handle, "title")); }
- set { ObjC.Call(Handle, "setTitle:", NSString.Create(value ?? string.Empty)); }
+ get { return this.Title; }
+ set { this.Title = value ?? string.Empty; }
}
public Size Size
{
get
{
- var frame = Marshal.PtrToStructure(ObjC.Call(Handle, "frame"));
+ var frame = this.Frame;
return new Size((int)frame.Size.Width, (int)frame.Size.Height);
}
set
{
- ObjC.Call(Handle, "setContentSize:", new CGSize(value.Width, value.Height));
+ this.SetContentSize(new CGSize(value.Width, value.Height));
}
}
- public Size MinSize
+ Size IWindow.MinSize
{
get
{
- var size = Marshal.PtrToStructure(ObjC.Call(Handle, "contentMinSize"));
+ var size = this.ContentMinSize;
return new Size(size.Width, size.Height);
}
set
{
- ObjC.Call(Handle, "setContentMinSize:", new CGSize(value.Width, value.Height));
+ this.ContentMinSize = new CGSize(value.Width, value.Height);
}
}
- public Size MaxSize
+ Size IWindow.MaxSize
{
get
{
- var size = Marshal.PtrToStructure(ObjC.Call(Handle, "contentMaxSize"));
+ var size = this.ContentMaxSize;
return new Size(size.Width, size.Height);
}
set
{
if (value == Size.Zero) { value = new Size(float.MaxValue, float.MaxValue); }
-
- ObjC.Call(Handle, "setContentMaxSize:", new CGSize(value.Width, value.Height));
+ this.ContentMaxSize = new CGSize(value.Width, value.Height);
}
}
@@ -65,7 +65,7 @@ public bool CanResize
set
{
canResizeField = value;
- StyleMask = GetWantedStyleMask();
+ StyleMask = GetWantedStyleMask(StyleMask, borderStyleField, value);
}
}
@@ -79,20 +79,24 @@ public WindowBorderStyle BorderStyle
// the title gets reset when setting it to borderless
// so we just store the title, set the border and set the title back again
string? title = Title;
- StyleMask = GetWantedStyleMask();
+ StyleMask = GetWantedStyleMask(StyleMask, value, canResizeField);
Title = title;
}
}
- public string? BackgroundColor
+ string? IWindow.BackgroundColor
{
get { return backgroundColorField; }
set
{
backgroundColorField = value;
- IntPtr bgColor = NSColor.FromHex(value);
- ObjC.Call(Handle, "setBackgroundColor:", bgColor);
- webview.UpdateBackgroundColor(bgColor);
+
+ ColorTools.ParseHex(value, out byte r, out byte g, out byte b);
+ using var color = NSColor.FromRgba(r, g, b, (byte)255);
+ BackgroundColor = color;
+
+ using var key = new NSString("backgroundColor");
+ webview.SetValueForKey(color, key);
}
}
@@ -122,157 +126,117 @@ public IWebview Webview
get { return webview; }
}
- private NSWindowStyleMask StyleMask
- {
- get { return (NSWindowStyleMask)ObjC.Call(Handle, "styleMask"); }
- set { ObjC.Call(Handle, "setStyleMask:", new IntPtr((int)value)); }
- }
-
- public readonly IntPtr Handle;
-
- private static readonly NativeClassDefinition WindowDelegateDefinition;
-
- private readonly NativeClassInstance windowDelegate;
private readonly CocoaWebview webview;
private bool canResizeField;
private WindowBorderStyle borderStyleField;
private string? backgroundColorField;
- static CocoaWindow()
- {
- WindowDelegateDefinition = CreateWindowDelegate();
- }
-
public CocoaWindow(WindowConfiguration config, WebviewBridge bridge)
+ : base(new CGRect(0, 0, config.Size.Width, config.Size.Height), GetWantedStyleMask(0ul, WindowBorderStyle.Default, config.CanResize), NSBackingStore.Buffered, false)
{
if (config == null) { throw new ArgumentNullException(nameof(config)); }
if (bridge == null) { throw new ArgumentNullException(nameof(bridge)); }
- Handle = AppKit.Call("NSWindow", "alloc");
-
canResizeField = config.CanResize;
- var style = GetWantedStyleMask();
- ObjC.SendMessage(
- Handle,
- ObjC.RegisterName("initWithContentRect:styleMask:backing:defer:"),
- new CGRect(0, 0, config.Size.Width, config.Size.Height),
- new UIntPtr((uint)style),
- new UIntPtr(2),
- false);
+ StyleMask = GetWantedStyleMask(StyleMask, borderStyleField, canResizeField);
webview = new CocoaWebview(bridge);
- ObjC.Call(Handle, "setContentView:", webview.Handle);
+
+ // The dev tools view will be added as a subview of the content view. That leads to a warning at runtime
+ // when opening the dev tools if we don't set a parent view as the content view.
+ using var parentView = new NSView() { AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.HeightSizable };
+ parentView.AddSubview(webview);
+ webview.AutoresizingMask = NSViewResizingMask.WidthSizable | NSViewResizingMask.HeightSizable;
+ ContentView = parentView;
+ MakeFirstResponder(webview);
webview.TitleChanged += Webview_TitleChanged;
- windowDelegate = WindowDelegateDefinition.CreateInstance(this);
- ObjC.Call(Handle, "setDelegate:", windowDelegate.Handle);
+ Delegate = new CocoaWindowDelegate(this);
}
public void Show()
{
- ObjC.Call(Handle, "center");
- ObjC.Call(Handle, "makeKeyAndOrderFront:", IntPtr.Zero);
+ Center();
+ MakeKeyAndOrderFront(null);
MacApplication.SynchronizationContext.Post(s => Shown?.Invoke(this, EventArgs.Empty), null);
}
- public void Close()
- {
- ObjC.Call(Handle, "close", IntPtr.Zero);
- }
-
public void EnterFullscreen()
{
- if (!StyleMask.HasFlag(NSWindowStyleMask.FullScreen))
+ if (!StyleMask.HasFlag(NSWindowStyle.FullScreenWindow))
{
- ObjC.Call(Handle, "toggleFullScreen:", Handle);
+ ToggleFullScreen(this);
}
}
public void ExitFullscreen()
{
- if (StyleMask.HasFlag(NSWindowStyleMask.FullScreen))
+ if (StyleMask.HasFlag(NSWindowStyle.FullScreenWindow))
{
- ObjC.Call(Handle, "toggleFullScreen:", Handle);
+ ToggleFullScreen(this);
}
}
public void Maximize()
{
- if (ObjC.Call(Handle, "isZoomed") == IntPtr.Zero) { ObjC.Call(Handle, "zoom:", Handle); }
+ if (!IsZoomed)
+ {
+ Zoom(this);
+ }
}
public void Unmaximize()
{
- if (ObjC.Call(Handle, "isZoomed") != IntPtr.Zero) { ObjC.Call(Handle, "zoom:", Handle); }
+ if (IsZoomed)
+ {
+ Zoom(this);
+ }
}
public void Minimize()
{
- ObjC.Call(Handle, "miniaturize:", IntPtr.Zero);
+ Miniaturize(null);
}
public void Unminimize()
{
- ObjC.Call(Handle, "deminiaturize:", IntPtr.Zero);
+ Deminiaturize(null);
}
- public void Dispose()
+ protected override void Dispose(bool disposing)
{
- // window will be released automatically
- webview.Dispose();
- windowDelegate.Dispose();
- }
+ if (disposing)
+ {
+ webview.Dispose();
+ }
- private static NativeClassDefinition CreateWindowDelegate()
- {
- var definition = NativeClassDefinition.FromObject(
- "SpiderEyeWindowDelegate",
- AppKit.GetProtocol("NSWindowDelegate"));
-
- definition.AddMethod(
- "windowShouldClose:",
- "c@:@",
- (self, op, window) =>
- {
- var instance = definition.GetParent(self);
- var args = new CancelableEventArgs();
- instance.Closing?.Invoke(instance, args);
-
- return args.Cancel ? (byte)0 : (byte)1;
- });
-
- definition.AddMethod(
- "windowWillClose:",
- "v@:@",
- (self, op, notification) =>
- {
- var instance = definition.GetParent(self);
- instance.webview.TitleChanged -= instance.Webview_TitleChanged;
- instance.Closed?.Invoke(instance, EventArgs.Empty);
- });
-
- definition.FinishDeclaration();
-
- return definition;
+ base.Dispose(disposing);
}
- private NSWindowStyleMask GetWantedStyleMask()
+ private class CocoaWindowDelegate : NSWindowDelegate
{
- bool isFullscreen = StyleMask.HasFlag(NSWindowStyleMask.FullScreen);
- NSWindowStyleMask style = NSWindowStyleMask.Closable | NSWindowStyleMask.Miniaturizable;
- style |= borderStyleField switch
+ private readonly CocoaWindow cocoaWindow;
+
+ public CocoaWindowDelegate(CocoaWindow cocoaWindow)
{
- WindowBorderStyle.Default => NSWindowStyleMask.Titled,
- WindowBorderStyle.None => NSWindowStyleMask.Borderless,
- _ => throw new ArgumentException($"Invalid border style value of {borderStyleField}", nameof(BorderStyle)),
- };
- if (canResizeField) { style |= NSWindowStyleMask.Resizable; }
- if (isFullscreen) { style |= NSWindowStyleMask.FullScreen; }
+ this.cocoaWindow = cocoaWindow;
+ }
- return style;
+ public override bool WindowShouldClose(NSObject sender)
+ {
+ var args = new CancelableEventArgs();
+ cocoaWindow.Closing?.Invoke(cocoaWindow, args);
+ return !args.Cancel;
+ }
+
+ public override void WillClose(NSNotification notification)
+ {
+ cocoaWindow.webview.TitleChanged -= cocoaWindow.Webview_TitleChanged;
+ cocoaWindow.Closed?.Invoke(cocoaWindow, EventArgs.Empty);
+ }
}
private void Webview_TitleChanged(object? sender, string title)
@@ -282,5 +246,21 @@ private void Webview_TitleChanged(object? sender, string title)
Application.Invoke(() => Title = title ?? string.Empty);
}
}
+
+ private static NSWindowStyle GetWantedStyleMask(NSWindowStyle styleMask, WindowBorderStyle borderStyle, bool canResize)
+ {
+ bool isFullscreen = styleMask.HasFlag(NSWindowStyle.FullScreenWindow);
+ NSWindowStyle style = NSWindowStyle.Closable | NSWindowStyle.Miniaturizable;
+ style |= borderStyle switch
+ {
+ WindowBorderStyle.Default => NSWindowStyle.Titled,
+ WindowBorderStyle.None => NSWindowStyle.Borderless,
+ _ => throw new ArgumentException($"Invalid border style value of {borderStyle}", nameof(BorderStyle)),
+ };
+ if (canResize) { style |= NSWindowStyle.Resizable; }
+ if (isFullscreen) { style |= NSWindowStyle.FullScreenWindow; }
+
+ return style;
+ }
}
}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaDialog.cs b/Source/SpiderEye.Mac/Dialogs/CocoaDialog.cs
deleted file mode 100644
index 32e2a998..00000000
--- a/Source/SpiderEye.Mac/Dialogs/CocoaDialog.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
-
-namespace SpiderEye.Mac
-{
- internal abstract class CocoaDialog : IDialog
- {
- public string? Title { get; set; }
-
- public DialogResult Show()
- {
- return Show(null);
- }
-
- public DialogResult Show(IWindow? parent)
- {
- var window = NativeCast.To(parent);
- var dialog = CreateDialog();
-
- ObjC.Call(dialog.Handle, "setTitle:", NSString.Create(Title));
- ObjC.Call(dialog.Handle, "setCanCreateDirectories:", true);
-
- int result = dialog.Run(window);
- var mappedResult = MapResult(result);
- BeforeReturn(dialog, mappedResult);
-
- return mappedResult;
- }
-
- protected abstract NSDialog CreateDialog();
-
- protected virtual void BeforeShow(NSDialog dialog)
- {
- }
-
- protected virtual void BeforeReturn(NSDialog dialog, DialogResult result)
- {
- }
-
- private static DialogResult MapResult(int result)
- {
- return result switch
- {
- 1 => DialogResult.Ok,
- 0 => DialogResult.Cancel,
- _ => DialogResult.None,
- };
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaFileDialog.cs b/Source/SpiderEye.Mac/Dialogs/CocoaFileDialog.cs
deleted file mode 100644
index 2bb306e8..00000000
--- a/Source/SpiderEye.Mac/Dialogs/CocoaFileDialog.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac
-{
- internal abstract class CocoaFileDialog : CocoaDialog, IFileDialog
- {
- public string? InitialDirectory { get; set; }
- public string? FileName { get; set; }
- public ICollection FileFilters { get; }
-
- protected CocoaFileDialog()
- {
- FileFilters = new List();
- }
-
- protected override void BeforeShow(NSDialog dialog)
- {
- if (!string.IsNullOrWhiteSpace(InitialDirectory))
- {
- var url = Foundation.Call("NSURL", "fileURLWithPath:", NSString.Create(InitialDirectory));
- ObjC.Call(dialog.Handle, "setDirectoryURL:", url);
- }
-
- if (!string.IsNullOrWhiteSpace(FileName))
- {
- ObjC.Call(dialog.Handle, "setNameFieldStringValue:", NSString.Create(FileName));
- }
-
- SetFileFilters(dialog.Handle, FileFilters);
- }
-
- protected override void BeforeReturn(NSDialog dialog, DialogResult result)
- {
- if (result == DialogResult.Ok)
- {
- var selection = ObjC.Call(dialog.Handle, "URL");
- FileName = NSString.GetString(ObjC.Call(selection, "path"));
- }
- else { FileName = null; }
- }
-
- private static void SetFileFilters(IntPtr dialog, IEnumerable filters)
- {
- var fileTypes = filters
- .SelectMany(t => t.Filters.Select(u => u.TrimStart('*', '.')))
- .Distinct()
- .Select(t => NSString.Create(t));
-
- if (fileTypes.Any())
- {
- var data = fileTypes.ToArray();
- var array = Foundation.Call("NSArray", "arrayWithObjects:count:", data, new IntPtr(data.Length));
- ObjC.Call(dialog, "setAllowedFileTypes:", array);
- }
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaFolderSelectDialog.cs b/Source/SpiderEye.Mac/Dialogs/CocoaFolderSelectDialog.cs
index 6e116dfb..66d83dc2 100644
--- a/Source/SpiderEye.Mac/Dialogs/CocoaFolderSelectDialog.cs
+++ b/Source/SpiderEye.Mac/Dialogs/CocoaFolderSelectDialog.cs
@@ -1,37 +1,57 @@
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
+using System;
+using AppKit;
namespace SpiderEye.Mac
{
- internal class CocoaFolderSelectDialog : CocoaDialog, IFolderSelectDialog
+ internal class CocoaFolderSelectDialog : IFolderSelectDialog
{
+ public string? Title { get; set; }
public string? SelectedPath { get; set; }
- protected override NSDialog CreateDialog()
+ public DialogResult Show()
{
- var panel = NSDialog.CreateOpenPanel();
+ return Show(null);
+ }
- ObjC.Call(panel.Handle, "setCanChooseFiles:", false);
- ObjC.Call(panel.Handle, "setCanChooseDirectories:", true);
- ObjC.Call(panel.Handle, "setAllowsMultipleSelection:", false);
+ public DialogResult Show(IWindow? parent)
+ {
+ using var panel = NSOpenPanel.OpenPanel;
+ panel.Title = Title ?? string.Empty;
+ panel.CanCreateDirectories = true;
+ panel.CanChooseFiles = false;
+ panel.CanChooseDirectories = true;
+ panel.AllowsMultipleSelection = false;
if (!string.IsNullOrWhiteSpace(SelectedPath))
{
- var url = Foundation.Call("NSURL", "fileURLWithPath:", NSString.Create(SelectedPath));
- ObjC.Call(panel.Handle, "setDirectoryURL:", url);
+ panel.DirectoryUrl = new Uri(SelectedPath);
}
- return panel;
+ nint result;
+ if (parent == null)
+ {
+ result = panel.RunModal();
+ }
+ else
+ {
+ panel.BeginSheet((CocoaWindow)parent, result => NSApplication.SharedApplication.StopModalWithCode(result));
+ result = NSApplication.SharedApplication.RunModalForWindow(panel);
+ }
+
+ var mappedResult = MapResult(result);
+ SelectedPath = mappedResult == DialogResult.Ok ? panel.Url.Path : null;
+
+ return mappedResult;
}
- protected override void BeforeReturn(NSDialog dialog, DialogResult result)
+ private static DialogResult MapResult(nint result)
{
- if (result == DialogResult.Ok)
+ return result switch
{
- var selection = ObjC.Call(dialog.Handle, "URL");
- SelectedPath = NSString.GetString(ObjC.Call(selection, "path"));
- }
- else { SelectedPath = null; }
+ 1 => DialogResult.Ok,
+ 0 => DialogResult.Cancel,
+ _ => DialogResult.None,
+ };
}
}
}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaMessageBox.cs b/Source/SpiderEye.Mac/Dialogs/CocoaMessageBox.cs
index f93854b7..88ce07f2 100644
--- a/Source/SpiderEye.Mac/Dialogs/CocoaMessageBox.cs
+++ b/Source/SpiderEye.Mac/Dialogs/CocoaMessageBox.cs
@@ -1,7 +1,4 @@
-using System;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
+using AppKit;
namespace SpiderEye.Mac
{
@@ -18,42 +15,37 @@ public DialogResult Show()
public DialogResult Show(IWindow? parent)
{
- var window = NativeCast.To(parent);
- using var alert = NSDialog.CreateAlert();
- ObjC.Call(alert.Handle, "setShowsHelp:", IntPtr.Zero);
- ObjC.Call(alert.Handle, "setAlertStyle:", new UIntPtr((uint)NSAlertStyle.Informational));
- ObjC.Call(alert.Handle, "setMessageText:", NSString.Create(Title ?? string.Empty));
- ObjC.Call(alert.Handle, "setInformativeText:", NSString.Create(Message ?? string.Empty));
- AddButtons(alert.Handle, Buttons);
+ using var alert = new NSAlert();
+ alert.ShowsHelp = false;
+ alert.AlertStyle = NSAlertStyle.Informational;
+ alert.MessageText = Title ?? string.Empty;
+ alert.InformativeText = Message ?? string.Empty;
- return (DialogResult)alert.Run(window);
- }
-
- private static void AddButtons(IntPtr alert, MessageBoxButtons buttons)
- {
- switch (buttons)
+ var buttonDetails = Buttons switch
{
- case MessageBoxButtons.OkCancel:
- AddButton(alert, "Ok", DialogResult.Ok);
- AddButton(alert, "Cancel", DialogResult.Cancel);
- break;
+ MessageBoxButtons.OkCancel => new[] { ("Ok", DialogResult.Ok), ("Cancel", DialogResult.Cancel) },
+ MessageBoxButtons.YesNo => new[] { ("Yes", DialogResult.Yes), ("No", DialogResult.No) },
+ _ => new[] { ("Ok", DialogResult.Ok) },
+ };
- case MessageBoxButtons.YesNo:
- AddButton(alert, "Yes", DialogResult.Yes);
- AddButton(alert, "No", DialogResult.No);
- break;
+ foreach (var (title, dialogResult) in buttonDetails)
+ {
+ using var button = alert.AddButton(title);
+ button.Tag = (int)dialogResult;
+ }
- case MessageBoxButtons.Ok:
- default:
- AddButton(alert, "Ok", DialogResult.Ok);
- break;
+ nint result;
+ if (parent == null)
+ {
+ result = alert.RunModal();
+ }
+ else
+ {
+ alert.BeginSheet((CocoaWindow)parent, response => NSApplication.SharedApplication.StopModalWithCode((nint)response));
+ result = NSApplication.SharedApplication.RunModalForWindow(alert.Window);
}
- }
- private static void AddButton(IntPtr alert, string title, DialogResult result)
- {
- IntPtr button = ObjC.Call(alert, "addButtonWithTitle:", NSString.Create(title));
- ObjC.Call(button, "setTag:", new IntPtr((int)result));
+ return (DialogResult)result;
}
}
}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaOpenFileDialog.cs b/Source/SpiderEye.Mac/Dialogs/CocoaOpenFileDialog.cs
index f8fc62d4..ea5f7bc2 100644
--- a/Source/SpiderEye.Mac/Dialogs/CocoaOpenFileDialog.cs
+++ b/Source/SpiderEye.Mac/Dialogs/CocoaOpenFileDialog.cs
@@ -1,44 +1,79 @@
using System;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
+using System.Collections.Generic;
+using System.Linq;
+using AppKit;
namespace SpiderEye.Mac
{
- internal class CocoaOpenFileDialog : CocoaFileDialog, IOpenFileDialog
+ internal class CocoaOpenFileDialog : IOpenFileDialog
{
+ public string? Title { get; set; }
+ public string? InitialDirectory { get; set; }
+ public string? FileName { get; set; }
+ public ICollection FileFilters { get; } = new List();
public bool Multiselect { get; set; }
+ public string[]? SelectedFiles { get; private set; }
- public string[]? SelectedFiles
+ public DialogResult Show()
{
- get;
- private set;
+ return Show(null);
}
- protected override NSDialog CreateDialog()
+ public DialogResult Show(IWindow? parent)
{
- var panel = NSDialog.CreateOpenPanel();
+ using var panel = NSOpenPanel.OpenPanel;
+ panel.Title = Title ?? string.Empty;
+ panel.CanCreateDirectories = true;
+ panel.CanChooseFiles = true;
+ panel.CanChooseDirectories = false;
+ panel.AllowsMultipleSelection = Multiselect;
- ObjC.Call(panel.Handle, "setCanChooseFiles:", true);
- ObjC.Call(panel.Handle, "setCanChooseDirectories:", false);
- ObjC.Call(panel.Handle, "setAllowsMultipleSelection:", Multiselect);
+ if (!string.IsNullOrWhiteSpace(InitialDirectory))
+ {
+ panel.DirectoryUrl = new Uri(InitialDirectory);
+ }
- return panel;
- }
+ if (!string.IsNullOrWhiteSpace(FileName))
+ {
+ panel.NameFieldStringValue = FileName;
+ }
- protected override void BeforeReturn(NSDialog dialog, DialogResult result)
- {
- base.BeforeReturn(dialog, result);
+ var fileTypes = FileFilters
+ .SelectMany(t => t.Filters.Select(u => u.TrimStart('*', '.')))
+ .Distinct()
+ .ToArray();
+
+ if (fileTypes.Length > 0)
+ {
+ panel.AllowedFileTypes = fileTypes;
+ }
- var urls = ObjC.Call(dialog.Handle, "URLs");
- int count = ObjC.Call(urls, "count").ToInt32();
- string[] files = new string[count];
- for (int i = 0; i < count; i++)
+ nint result;
+ if (parent == null)
+ {
+ result = panel.RunModal();
+ }
+ else
{
- var url = ObjC.Call(urls, "objectAtIndex:", new IntPtr(i));
- files[i] = NSString.GetString(ObjC.Call(url, "path"))!;
+ panel.BeginSheet((CocoaWindow)parent, result => NSApplication.SharedApplication.StopModalWithCode(result));
+ result = NSApplication.SharedApplication.RunModalForWindow(panel);
}
- SelectedFiles = files;
+ var mappedResult = MapResult(result);
+ FileName = mappedResult == DialogResult.Ok ? panel.Url.Path : null;
+ SelectedFiles = panel.Urls.Select(u => u.Path).ToArray();
+
+ return mappedResult;
+ }
+
+ private static DialogResult MapResult(nint result)
+ {
+ return result switch
+ {
+ 1 => DialogResult.Ok,
+ 0 => DialogResult.Cancel,
+ _ => DialogResult.None,
+ };
}
}
}
diff --git a/Source/SpiderEye.Mac/Dialogs/CocoaSaveFileDialog.cs b/Source/SpiderEye.Mac/Dialogs/CocoaSaveFileDialog.cs
index fd9e3949..04d92aaf 100644
--- a/Source/SpiderEye.Mac/Dialogs/CocoaSaveFileDialog.cs
+++ b/Source/SpiderEye.Mac/Dialogs/CocoaSaveFileDialog.cs
@@ -1,15 +1,75 @@
-using SpiderEye.Mac.Interop;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AppKit;
namespace SpiderEye.Mac
{
- internal class CocoaSaveFileDialog : CocoaFileDialog, ISaveFileDialog
+ internal class CocoaSaveFileDialog : ISaveFileDialog
{
+ public string? Title { get; set; }
+ public string? InitialDirectory { get; set; }
+ public string? FileName { get; set; }
+ public ICollection FileFilters { get; } = new List();
public bool OverwritePrompt { get; set; }
- protected override NSDialog CreateDialog()
+ public DialogResult Show()
+ {
+ return Show(null);
+ }
+
+ public DialogResult Show(IWindow? parent)
{
// TODO: can't disable overwrite prompt on macOS
- return NSDialog.CreateSavePanel();
+ using var panel = NSSavePanel.SavePanel;
+ panel.Title = Title ?? string.Empty;
+ panel.CanCreateDirectories = true;
+
+ if (!string.IsNullOrWhiteSpace(InitialDirectory))
+ {
+ panel.DirectoryUrl = new Uri(InitialDirectory);
+ }
+
+ if (!string.IsNullOrWhiteSpace(FileName))
+ {
+ panel.NameFieldStringValue = FileName;
+ }
+
+ var fileTypes = FileFilters
+ .SelectMany(t => t.Filters.Select(u => u.TrimStart('*', '.')))
+ .Distinct()
+ .ToArray();
+
+ if (fileTypes.Length > 0)
+ {
+ panel.AllowedFileTypes = fileTypes;
+ }
+
+ nint result;
+ if (parent == null)
+ {
+ result = panel.RunModal();
+ }
+ else
+ {
+ panel.BeginSheet((CocoaWindow)parent, result => NSApplication.SharedApplication.StopModalWithCode(result));
+ result = NSApplication.SharedApplication.RunModalForWindow(panel);
+ }
+
+ var mappedResult = MapResult(result);
+ FileName = mappedResult == DialogResult.Ok ? panel.Url.Path : null;
+
+ return mappedResult;
+ }
+
+ private static DialogResult MapResult(nint result)
+ {
+ return result switch
+ {
+ 1 => DialogResult.Ok,
+ 0 => DialogResult.Cancel,
+ _ => DialogResult.None,
+ };
}
}
}
diff --git a/Source/SpiderEye.Mac/Interop/CGPoint.cs b/Source/SpiderEye.Mac/Interop/CGPoint.cs
deleted file mode 100644
index 831ddb20..00000000
--- a/Source/SpiderEye.Mac/Interop/CGPoint.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Interop
-{
- [StructLayout(LayoutKind.Sequential)]
- internal struct CGPoint
- {
- public double X;
- public double Y;
-
- public CGPoint(double x, double y)
- {
- X = x;
- Y = y;
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/CGRect.cs b/Source/SpiderEye.Mac/Interop/CGRect.cs
deleted file mode 100644
index ebe80549..00000000
--- a/Source/SpiderEye.Mac/Interop/CGRect.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Interop
-{
- [StructLayout(LayoutKind.Sequential)]
- internal struct CGRect
- {
- public static readonly CGRect Zero = default;
-
- public CGPoint Origin;
- public CGSize Size;
-
- public CGRect(double x, double y, double width, double height)
- {
- Origin = new CGPoint(x, y);
- Size = new CGSize(width, height);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/CGSize.cs b/Source/SpiderEye.Mac/Interop/CGSize.cs
deleted file mode 100644
index 308b5c62..00000000
--- a/Source/SpiderEye.Mac/Interop/CGSize.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Interop
-{
- [StructLayout(LayoutKind.Sequential)]
- internal struct CGSize
- {
- public double Width;
- public double Height;
-
- public CGSize(double width, double height)
- {
- Width = width;
- Height = height;
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/Delegates.cs b/Source/SpiderEye.Mac/Interop/Delegates.cs
deleted file mode 100644
index a061d18f..00000000
--- a/Source/SpiderEye.Mac/Interop/Delegates.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-
-namespace SpiderEye.Mac.Interop
-{
- internal delegate byte ShouldTerminateDelegate(IntPtr self, IntPtr op, IntPtr notification);
- internal delegate void ScriptCallbackDelegate(IntPtr self, IntPtr op, IntPtr notification, IntPtr msg);
- internal delegate void ScriptEvalCallbackDelegate(IntPtr self, IntPtr result, IntPtr error);
- internal delegate void NavigationDecideDelegate(IntPtr self, IntPtr op, IntPtr view, IntPtr navigationAction, IntPtr decisionHandler);
- internal delegate void LoadFinishedDelegate(IntPtr self, IntPtr op, IntPtr view, IntPtr navigation);
- internal delegate void LoadFailedDelegate(IntPtr self, IntPtr op, IntPtr view, IntPtr navigation, IntPtr error);
- internal delegate void SchemeHandlerDelegate(IntPtr self, IntPtr op, IntPtr view, IntPtr schemeTask);
- internal delegate void ObserveValueDelegate(IntPtr self, IntPtr op, IntPtr keyPath, IntPtr obj, IntPtr change, IntPtr context);
- internal delegate byte WindowShouldCloseDelegate(IntPtr self, IntPtr op, IntPtr window);
- internal delegate void NotificationDelegate(IntPtr self, IntPtr op, IntPtr notification);
- internal delegate void MenuCallbackDelegate(IntPtr self, IntPtr op, IntPtr menu);
- internal delegate void DispatchDelegate(IntPtr context);
- internal delegate void NavigationDecisionDelegate(IntPtr block, IntPtr result);
-}
diff --git a/Source/SpiderEye.Mac/Interop/KeyMapper.cs b/Source/SpiderEye.Mac/Interop/KeyMapper.cs
deleted file mode 100644
index a8587eb6..00000000
--- a/Source/SpiderEye.Mac/Interop/KeyMapper.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using System;
-using System.Collections.Generic;
-using SpiderEye.Tools;
-
-namespace SpiderEye.Mac.Interop
-{
- internal static class KeyMapper
- {
- public static NSEventModifierFlags GetModifier(ModifierKey key)
- {
- NSEventModifierFlags result = NSEventModifierFlags.None;
-
- foreach (var flag in EnumTools.GetFlags(key))
- {
- switch (flag)
- {
- case ModifierKey.None:
- continue;
-
- case ModifierKey.Shift:
- result |= NSEventModifierFlags.Shift;
- break;
-
- case ModifierKey.Control:
- result |= NSEventModifierFlags.Control;
- break;
-
- case ModifierKey.Alt:
- result |= NSEventModifierFlags.Option;
- break;
-
- case ModifierKey.Super:
- case ModifierKey.Primary:
- result |= NSEventModifierFlags.Command;
- break;
-
- default:
- throw new NotSupportedException($"Unsupported modifier key: \"{flag}\"");
- }
- }
-
- return result;
- }
-
- public static string? GetKey(Key key)
- {
- if (Keymap.TryGetValue(key, out string? value)) { return value; }
- else { throw new NotSupportedException($"Unsupported key: \"{key}\""); }
- }
-
- private static readonly Dictionary Keymap = new()
- {
- { Key.None, null },
- { Key.F1, NSKey.F1 },
- { Key.F2, NSKey.F2 },
- { Key.F3, NSKey.F3 },
- { Key.F4, NSKey.F4 },
- { Key.F5, NSKey.F5 },
- { Key.F6, NSKey.F6 },
- { Key.F7, NSKey.F7 },
- { Key.F8, NSKey.F8 },
- { Key.F9, NSKey.F9 },
- { Key.F10, NSKey.F10 },
- { Key.F11, NSKey.F11 },
- { Key.F12, NSKey.F12 },
- { Key.Number1, "1" },
- { Key.Number2, "2" },
- { Key.Number3, "3" },
- { Key.Number4, "4" },
- { Key.Number5, "5" },
- { Key.Number6, "6" },
- { Key.Number7, "7" },
- { Key.Number8, "8" },
- { Key.Number9, "9" },
- { Key.Number0, "0" },
- { Key.A, "a" },
- { Key.B, "b" },
- { Key.C, "c" },
- { Key.D, "d" },
- { Key.E, "e" },
- { Key.F, "f" },
- { Key.G, "g" },
- { Key.H, "h" },
- { Key.I, "i" },
- { Key.J, "j" },
- { Key.K, "k" },
- { Key.L, "l" },
- { Key.M, "m" },
- { Key.N, "n" },
- { Key.O, "o" },
- { Key.P, "p" },
- { Key.Q, "q" },
- { Key.R, "r" },
- { Key.S, "s" },
- { Key.T, "t" },
- { Key.U, "u" },
- { Key.V, "v" },
- { Key.W, "w" },
- { Key.X, "x" },
- { Key.Y, "y" },
- { Key.Z, "z" },
- { Key.Insert, NSKey.Insert },
- { Key.Delete, "\0x007f" }, // Note: different than NSKey.Delete
- };
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSAlertStyle.cs b/Source/SpiderEye.Mac/Interop/NSAlertStyle.cs
deleted file mode 100644
index abc6389e..00000000
--- a/Source/SpiderEye.Mac/Interop/NSAlertStyle.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace SpiderEye.Mac.Interop
-{
- internal enum NSAlertStyle
- {
- Warning = 0,
- Informational = 1,
- Critical = 2,
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSBlock.cs b/Source/SpiderEye.Mac/Interop/NSBlock.cs
deleted file mode 100644
index 5997f252..00000000
--- a/Source/SpiderEye.Mac/Interop/NSBlock.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal sealed class NSBlock : IDisposable
- {
- public readonly IntPtr Handle;
- private readonly Delegate callback;
-
- public unsafe NSBlock(Delegate callback)
- {
- this.callback = callback ?? throw new ArgumentNullException(nameof(callback));
-
- var blp = (BlockLiteral*)Marshal.AllocHGlobal(sizeof(BlockLiteral));
- var bdp = (BlockDescriptor*)Marshal.AllocHGlobal(sizeof(BlockDescriptor));
-
- blp->Isa = ObjC.GetClass("__NSStackBlock");
- blp->Flags = 0;
- blp->Reserved = 0;
- blp->Invoke = Marshal.GetFunctionPointerForDelegate(callback);
- blp->Descriptor = bdp;
-
- bdp->Reserved = IntPtr.Zero;
- bdp->Size = new IntPtr(sizeof(BlockLiteral));
- bdp->CopyHelper = IntPtr.Zero;
- bdp->DisposeHelper = IntPtr.Zero;
-
- Handle = (IntPtr)blp;
- }
-
- public unsafe void Dispose()
- {
- var blp = (BlockLiteral*)Handle;
-
- Marshal.FreeHGlobal((IntPtr)blp->Descriptor);
- Marshal.FreeHGlobal(Handle);
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public unsafe struct BlockLiteral
- {
- public IntPtr Isa;
- public BlockFlags Flags;
- public int Reserved;
- public IntPtr Invoke;
- public BlockDescriptor* Descriptor;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct BlockDescriptor
- {
- public IntPtr Reserved;
- public IntPtr Size;
- public IntPtr CopyHelper;
- public IntPtr DisposeHelper;
- }
-
- [Flags]
- public enum BlockFlags : int
- {
- RefcountMask = 0xFFFF,
- NeedsFree = 1 << 24,
- HasCopyDispose = 1 << 25,
- HasCxxObj = 1 << 26,
- IsGC = 1 << 27,
- IsGlobal = 1 << 28,
- HasDescriptor = 1 << 29,
- HasSignature = 1 << 30,
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSColor.cs b/Source/SpiderEye.Mac/Interop/NSColor.cs
deleted file mode 100644
index 9c846852..00000000
--- a/Source/SpiderEye.Mac/Interop/NSColor.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
-
-namespace SpiderEye.Mac.Interop
-{
- internal static class NSColor
- {
- public static IntPtr FromHex(string? hex)
- {
- ColorTools.ParseHex(hex, out byte r, out byte g, out byte b);
-
- return AppKit.Call("NSColor", "colorWithRed:green:blue:alpha:", r / 255d, g / 255d, b / 255d, 1d);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSDialog.cs b/Source/SpiderEye.Mac/Interop/NSDialog.cs
deleted file mode 100644
index 70e0f687..00000000
--- a/Source/SpiderEye.Mac/Interop/NSDialog.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal class NSDialog : IDisposable
- {
- public readonly IntPtr Handle;
- private readonly IntPtr window;
- private readonly bool release;
-
- private delegate void Callback(IntPtr self, IntPtr result);
-
- private NSDialog(IntPtr handle, IntPtr window, bool release)
- {
- Handle = handle;
- this.window = window;
- this.release = release;
- }
-
- public static NSDialog CreateAlert()
- {
- var alert = AppKit.Call("NSAlert", "new");
- return new NSDialog(alert, ObjC.Call(alert, "window"), true);
- }
-
- public static NSDialog CreateSavePanel()
- {
- var panel = AppKit.Call("NSSavePanel", "savePanel");
- return new NSDialog(panel, panel, false);
- }
-
- public static NSDialog CreateOpenPanel()
- {
- var panel = AppKit.Call("NSOpenPanel", "openPanel");
- return new NSDialog(panel, panel, false);
- }
-
- public int Run(CocoaWindow? parent)
- {
- if (Handle == IntPtr.Zero) { throw new InvalidOperationException("Dialog is null"); }
-
- if (parent == null) { return ObjC.Call(Handle, "runModal").ToInt32(); }
-
- NSBlock? block = null;
- block = new NSBlock((Callback)((s, r) =>
- {
- ObjC.Call(MacApplication.Handle, "stopModalWithCode:", r);
- block!.Dispose();
- }));
-
- ObjC.Call(Handle, "beginSheetModalForWindow:completionHandler:", parent.Handle, block.Handle);
- return ObjC.Call(MacApplication.Handle, "runModalForWindow:", window).ToInt32();
- }
-
- public void Dispose()
- {
- if (release && Handle != IntPtr.Zero) { ObjC.Call(Handle, "release"); }
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSEventModifierFlags.cs b/Source/SpiderEye.Mac/Interop/NSEventModifierFlags.cs
deleted file mode 100644
index eeaf2b17..00000000
--- a/Source/SpiderEye.Mac/Interop/NSEventModifierFlags.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-
-namespace SpiderEye.Mac.Interop
-{
- [Flags]
- internal enum NSEventModifierFlags : ulong
- {
- None = 0,
- Shift = 1 << 17,
- Control = 1 << 18,
- Option = 1 << 19,
- Command = 1 << 20,
- Function = 1 << 23,
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSImageScaling.cs b/Source/SpiderEye.Mac/Interop/NSImageScaling.cs
deleted file mode 100644
index bba86218..00000000
--- a/Source/SpiderEye.Mac/Interop/NSImageScaling.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-namespace SpiderEye.Mac.Interop
-{
- ///
- /// Image scaling types.
- ///
- internal enum NSImageScaling
- {
- ///
- /// If it is too large for the destination, scale the image
- /// down while preserving the aspect ratio.
- ///
- ProportionallyDown = 0,
-
- ///
- /// Scale each dimension to exactly fit destination.
- ///
- AxesIndependently = 1,
-
- ///
- /// Do not scale the image.
- ///
- None = 2,
-
- ///
- /// Scale the image to its maximum possible dimensions while both
- /// staying within the destination area and preserving its aspect ratio.
- ///
- ProportionallyUpOrDown = 3,
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSKey.cs b/Source/SpiderEye.Mac/Interop/NSKey.cs
deleted file mode 100644
index 9deea089..00000000
--- a/Source/SpiderEye.Mac/Interop/NSKey.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-namespace SpiderEye.Mac.Interop
-{
- internal static class NSKey
- {
- public const string UpArrow = "\0xF700";
- public const string DownArrow = "\0xF701";
- public const string LeftArrow = "\0xF702";
- public const string RightArrow = "\0xF703";
- public const string F1 = "\0xF704";
- public const string F2 = "\0xF705";
- public const string F3 = "\0xF706";
- public const string F4 = "\0xF707";
- public const string F5 = "\0xF708";
- public const string F6 = "\0xF709";
- public const string F7 = "\0xF70A";
- public const string F8 = "\0xF70B";
- public const string F9 = "\0xF70C";
- public const string F10 = "\0xF70D";
- public const string F11 = "\0xF70E";
- public const string F12 = "\0xF70F";
- public const string F13 = "\0xF710";
- public const string F14 = "\0xF711";
- public const string F15 = "\0xF712";
- public const string F16 = "\0xF713";
- public const string F17 = "\0xF714";
- public const string F18 = "\0xF715";
- public const string F19 = "\0xF716";
- public const string F20 = "\0xF717";
- public const string F21 = "\0xF718";
- public const string F22 = "\0xF719";
- public const string F23 = "\0xF71A";
- public const string F24 = "\0xF71B";
- public const string F25 = "\0xF71C";
- public const string F26 = "\0xF71D";
- public const string F27 = "\0xF71E";
- public const string F28 = "\0xF71F";
- public const string F29 = "\0xF720";
- public const string F30 = "\0xF721";
- public const string F31 = "\0xF722";
- public const string F32 = "\0xF723";
- public const string F33 = "\0xF724";
- public const string F34 = "\0xF725";
- public const string F35 = "\0xF726";
- public const string Insert = "\0xF727";
- public const string Delete = "\0xF728";
- public const string Home = "\0xF729";
- public const string Begin = "\0xF72A";
- public const string End = "\0xF72B";
- public const string PageUp = "\0xF72C";
- public const string PageDown = "\0xF72D";
- public const string PrintScreen = "\0xF72E";
- public const string ScrollLock = "\0xF72F";
- public const string Pause = "\0xF730";
- public const string SysReq = "\0xF731";
- public const string Break = "\0xF732";
- public const string Reset = "\0xF733";
- public const string Stop = "\0xF734";
- public const string Menu = "\0xF735";
- public const string User = "\0xF736";
- public const string System = "\0xF737";
- public const string Print = "\0xF738";
- public const string ClearLine = "\0xF739";
- public const string ClearDisplay = "\0xF73A";
- public const string InsertLine = "\0xF73B";
- public const string DeleteLine = "\0xF73C";
- public const string InsertChar = "\0xF73D";
- public const string DeleteChar = "\0xF73E";
- public const string Prev = "\0xF73F";
- public const string Next = "\0xF740";
- public const string Select = "\0xF741";
- public const string Execute = "\0xF742";
- public const string Undo = "\0xF743";
- public const string Redo = "\0xF744";
- public const string Find = "\0xF745";
- public const string Help = "\0xF746";
- public const string ModeSwitch = "\0xF747";
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSRunLoop.cs b/Source/SpiderEye.Mac/Interop/NSRunLoop.cs
deleted file mode 100644
index cff84c73..00000000
--- a/Source/SpiderEye.Mac/Interop/NSRunLoop.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal sealed class NSRunLoop
- {
- private readonly IntPtr loop;
- private readonly IntPtr mode;
- private readonly IntPtr date;
- private readonly IntPtr method;
-
- private volatile bool isCompleted;
-
- public NSRunLoop()
- {
- loop = Foundation.Call("NSRunLoop", "currentRunLoop");
- mode = ObjC.Call(loop, "currentMode");
- date = Foundation.Call("NSDate", "distantFuture");
- method = ObjC.RegisterName("runMode:beforeDate:");
- }
-
- public static void WaitForTask(Task task)
- {
- var loop = new NSRunLoop();
- while (!task.IsCompleted) { loop.Iterate(); }
- }
-
- public static T WaitForTask(Task task)
- {
- WaitForTask((Task)task);
- return task.Result;
- }
-
- public void WaitForCompletion()
- {
- while (!isCompleted) { Iterate(); }
- }
-
- public void Complete()
- {
- isCompleted = true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void Iterate()
- {
- ObjC.SendMessage(loop, method, mode, date);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSString.cs b/Source/SpiderEye.Mac/Interop/NSString.cs
deleted file mode 100644
index 09596050..00000000
--- a/Source/SpiderEye.Mac/Interop/NSString.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Text;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal static class NSString
- {
- public static unsafe IntPtr Create(string? value)
- {
- if (value == null) { return IntPtr.Zero; }
-
- fixed (char* ptr = value)
- {
- return ObjC.SendMessage(
- ObjC.GetClass("NSString"),
- ObjC.RegisterName("stringWithCharacters:length:"),
- (IntPtr)ptr,
- new UIntPtr((uint)value.Length));
- }
- }
-
- public static string? GetString(IntPtr handle)
- {
- if (handle == IntPtr.Zero) { return null; }
-
- IntPtr utf8 = ObjC.Call(handle, "UTF8String");
- return Utf8PointerToString(utf8);
- }
-
- public static unsafe string? Utf8PointerToString(IntPtr utf8)
- {
- if (utf8 == IntPtr.Zero) { return null; }
-
- int count = 0;
- byte* ptr = (byte*)utf8;
- while (*ptr != 0)
- {
- count++;
- ptr++;
- }
-
- return Encoding.UTF8.GetString((byte*)utf8, count);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NSWindowStyleMask.cs b/Source/SpiderEye.Mac/Interop/NSWindowStyleMask.cs
deleted file mode 100644
index d40f4f4e..00000000
--- a/Source/SpiderEye.Mac/Interop/NSWindowStyleMask.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-
-namespace SpiderEye.Mac.Interop
-{
- [Flags]
- internal enum NSWindowStyleMask
- {
- Borderless = 0,
- Titled = 1 << 0,
- Closable = 1 << 1,
- Miniaturizable = 1 << 2,
- Resizable = 1 << 3,
- UnifiedTitleAndToolbar = 1 << 12,
- FullScreen = 1 << 14,
- FullSizeContentView = 1 << 15,
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NativeClassDefinition.cs b/Source/SpiderEye.Mac/Interop/NativeClassDefinition.cs
deleted file mode 100644
index 1aa100d7..00000000
--- a/Source/SpiderEye.Mac/Interop/NativeClassDefinition.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal class NativeClassDefinition
- {
- public IntPtr Handle { get; private set; }
-
- private readonly List callbacks;
- private readonly IntPtr[] protocols;
-
- private bool registered;
- private IntPtr ivar;
-
- private NativeClassDefinition(string name, IntPtr parent, IntPtr[] protocols)
- {
- if (name == null) { throw new ArgumentNullException(nameof(name)); }
-
- this.protocols = protocols ?? throw new ArgumentNullException(nameof(protocols));
- callbacks = new List();
- Handle = ObjC.AllocateClassPair(parent, name, IntPtr.Zero);
- }
-
- public static NativeClassDefinition FromObject(string name, params IntPtr[] protocols)
- {
- return FromClass(name, ObjC.GetClass("NSObject"), protocols);
- }
-
- public static NativeClassDefinition FromClass(string name, IntPtr parent, params IntPtr[] protocols)
- {
- return new NativeClassDefinition(name, parent, protocols);
- }
-
- public void AddMethod(string name, string signature, T callback)
- where T : Delegate
- {
- if (registered) { throw new InvalidOperationException("Native class is already declared and registered"); }
-
- // keep reference to callback or it will get garbage collected
- callbacks.Add(callback);
-
- ObjC.AddMethod(
- Handle,
- ObjC.RegisterName(name),
- callback,
- signature);
- }
-
- public void FinishDeclaration()
- {
- if (registered) { throw new InvalidOperationException("Native class is already declared and registered"); }
- registered = true;
-
- // variable to hold reference to .NET object that creates an instance
- const string variableName = "_SEInstance";
- ObjC.AddVariable(Handle, variableName, new IntPtr(IntPtr.Size), (byte)Math.Log(IntPtr.Size, 2), "@");
- ivar = ObjC.GetVariable(Handle, variableName);
-
- foreach (IntPtr protocol in protocols)
- {
- if (protocol == IntPtr.Zero)
- {
- // must not add null protocol, can cause runtime exception with conformsToProtocol check
- continue;
- }
-
- ObjC.AddProtocol(Handle, protocol);
- }
-
- ObjC.RegisterClassPair(Handle);
- }
-
- public NativeClassInstance CreateInstance(object parent)
- {
- if (!registered) { throw new InvalidOperationException("Native class is not yet fully declared and registered"); }
-
- IntPtr instance = ObjC.Call(Handle, "new");
-
- var parentHandle = GCHandle.Alloc(parent, GCHandleType.Normal);
- ObjC.SetVariableValue(instance, ivar, GCHandle.ToIntPtr(parentHandle));
-
- return new NativeClassInstance(instance, parentHandle);
- }
-
- public T GetParent(IntPtr self)
- {
- IntPtr handle = ObjC.GetVariableValue(self, ivar);
- return (T)GCHandle.FromIntPtr(handle).Target!;
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/NativeClassInstance.cs b/Source/SpiderEye.Mac/Interop/NativeClassInstance.cs
deleted file mode 100644
index 741dbe34..00000000
--- a/Source/SpiderEye.Mac/Interop/NativeClassInstance.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Interop
-{
- internal sealed class NativeClassInstance : IDisposable
- {
- public IntPtr Handle { get; }
-
- private readonly GCHandle parentHandle;
-
- internal NativeClassInstance(IntPtr instance, GCHandle parentHandle)
- {
- Handle = instance;
- this.parentHandle = parentHandle;
- }
-
- ~NativeClassInstance()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private void Dispose(bool disposing)
- {
- parentHandle.Free();
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Interop/URL.cs b/Source/SpiderEye.Mac/Interop/URL.cs
deleted file mode 100644
index fd0c04fc..00000000
--- a/Source/SpiderEye.Mac/Interop/URL.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using SpiderEye.Mac.Native;
-
-namespace SpiderEye.Mac.Interop
-{
- internal static class URL
- {
- public static string? GetAsString(IntPtr handle)
- {
- if (handle == IntPtr.Zero) { return null; }
-
- return NSString.GetString(ObjC.Call(handle, "absoluteString"));
- }
-
- public static Uri? GetAsUri(IntPtr handle)
- {
- if (handle == IntPtr.Zero) { return null; }
-
- return new Uri(GetAsString(handle)!);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/MacApplication.cs b/Source/SpiderEye.Mac/MacApplication.cs
index afdfbdf2..59647ec1 100644
--- a/Source/SpiderEye.Mac/MacApplication.cs
+++ b/Source/SpiderEye.Mac/MacApplication.cs
@@ -1,7 +1,5 @@
-using System;
-using System.Threading;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
+using System.Threading;
+using AppKit;
namespace SpiderEye.Mac
{
@@ -19,15 +17,10 @@ public static Menu? AppMenu
set
{
appMenu = value;
- SetAppMenu(value);
+ NSApplication.SharedApplication.MainMenu = (CocoaMenu?)value?.NativeMenu!;
}
}
- internal static IntPtr Handle
- {
- get { return app?.Handle ?? IntPtr.Zero; }
- }
-
internal static SynchronizationContext SynchronizationContext
{
get { return app!.SynchronizationContext; }
@@ -46,12 +39,6 @@ public static void Init()
AppMenu = CreateDefaultMenu();
}
- private static void SetAppMenu(Menu? menu)
- {
- var nativeMenu = NativeCast.To(menu?.NativeMenu);
- ObjC.Call(Handle, "setMainMenu:", nativeMenu?.Handle ?? IntPtr.Zero);
- }
-
private static Menu CreateDefaultMenu()
{
var menu = new Menu();
diff --git a/Source/SpiderEye.Mac/Menu/CocoaLabelMenuItem.cs b/Source/SpiderEye.Mac/Menu/CocoaLabelMenuItem.cs
index 37075794..0bfd564d 100644
--- a/Source/SpiderEye.Mac/Menu/CocoaLabelMenuItem.cs
+++ b/Source/SpiderEye.Mac/Menu/CocoaLabelMenuItem.cs
@@ -1,6 +1,8 @@
using System;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
+using System.Collections.Generic;
+using AppKit;
+using ObjCRuntime;
+using SpiderEye.Tools;
namespace SpiderEye.Mac
{
@@ -12,77 +14,58 @@ public string? Label
{
get
{
- IntPtr title = ObjC.Call(Handle, "title");
- return NSString.GetString(title);
+ return Title;
}
set
{
- IntPtr title = NSString.Create(value);
- ObjC.Call(Handle, "setTitle:", title);
+ Title = value ?? string.Empty;
if (subMenu != null) { subMenu.Title = value; }
}
}
- public bool Enabled
- {
- get { return ObjC.Call(Handle, "enabled") != IntPtr.Zero; }
- set { ObjC.Call(Handle, "setEnabled:", value); }
- }
-
- private static readonly NativeClassDefinition CallbackClassDefinition;
-
- private readonly NativeClassInstance? callbackClass;
private CocoaSubMenu? subMenu;
- static CocoaLabelMenuItem()
- {
- CallbackClassDefinition = CreateCallbackClass();
- }
-
public CocoaLabelMenuItem(string label)
- : this(label, "menuCallback:")
+ : base()
{
- callbackClass = CallbackClassDefinition.CreateInstance(this);
- SetTarget(callbackClass.Handle);
+ Title = label;
+ Activated += (sender, args) => Click?.Invoke(this, EventArgs.Empty);
}
public CocoaLabelMenuItem(string label, string action, string target)
: this(label, action)
{
- SetTarget(ObjC.RegisterName(target));
+ Target = Runtime.GetNSObject(Selector.GetHandle(target));
}
public CocoaLabelMenuItem(string label, string action, string? target, long tag)
: this(label, action)
{
- SetTarget(ObjC.RegisterName(target));
- SetTag(tag);
+ Target = target == null ? null : Runtime.GetNSObject(Selector.GetHandle(target));
+ Tag = (nint)tag;
}
private CocoaLabelMenuItem(string label, string action)
- : base(AppKit.Call("NSMenuItem", "alloc"))
+ : base()
{
- ObjC.Call(
- Handle,
- "initWithTitle:action:keyEquivalent:",
- NSString.Create(label),
- ObjC.RegisterName(action),
- NSString.Create(string.Empty));
+ Title = label;
+ Action = new Selector(action);
+ KeyEquivalent = string.Empty;
}
public void SetShortcut(ModifierKey modifier, Key key)
{
- NSEventModifierFlags nsModifier = KeyMapper.GetModifier(modifier);
+ NSEventModifierMask nsModifier = KeyMapper.GetModifier(modifier);
string? mappedKey = KeyMapper.GetKey(key);
if (mappedKey == null) { return; }
- ObjC.Call(Handle, "setKeyEquivalentModifierMask:", new UIntPtr((ulong)nsModifier));
- ObjC.Call(Handle, "setKeyEquivalent:", NSString.Create(mappedKey));
+ KeyEquivalentModifierMask = nsModifier;
+ KeyEquivalent = mappedKey;
}
public override IMenu CreateSubMenu()
{
- return subMenu = new CocoaSubMenu(Handle, Label);
+ return subMenu = new CocoaSubMenu(this, Label);
}
public CocoaMenu SetSubMenu(string label)
@@ -90,37 +73,108 @@ public CocoaMenu SetSubMenu(string label)
if (label == null) { throw new ArgumentNullException(nameof(label)); }
if (subMenu != null) { throw new InvalidOperationException("Submenu is already created"); }
- subMenu = new CocoaSubMenu(Handle, label, true);
-
+ subMenu = new CocoaSubMenu(this, label, true);
return subMenu.NativeMenu!;
}
- private static NativeClassDefinition CreateCallbackClass()
+ private static class KeyMapper
{
- var definition = NativeClassDefinition.FromObject("SpiderEyeMenuCallback");
+ public static NSEventModifierMask GetModifier(ModifierKey key)
+ {
+ NSEventModifierMask result = 0ul;
- definition.AddMethod(
- "menuCallback:",
- "v@:@",
- (self, op, menu) =>
+ foreach (var flag in EnumTools.GetFlags(key))
{
- var instance = definition.GetParent(self);
- instance.Click?.Invoke(instance, EventArgs.Empty);
- });
-
- definition.FinishDeclaration();
-
- return definition;
- }
+ switch (flag)
+ {
+ case ModifierKey.None:
+ continue;
+
+ case ModifierKey.Shift:
+ result |= NSEventModifierMask.ShiftKeyMask;
+ break;
+
+ case ModifierKey.Control:
+ result |= NSEventModifierMask.ControlKeyMask;
+ break;
+
+ case ModifierKey.Alt:
+ result |= NSEventModifierMask.AlternateKeyMask;
+ break;
+
+ case ModifierKey.Super:
+ case ModifierKey.Primary:
+ result |= NSEventModifierMask.CommandKeyMask;
+ break;
+
+ default:
+ throw new NotSupportedException($"Unsupported modifier key: \"{flag}\"");
+ }
+ }
+
+ return result;
+ }
- private void SetTarget(IntPtr target)
- {
- ObjC.Call(Handle, "setTarget:", target);
- }
+ public static string? GetKey(Key key)
+ {
+ if (Keymap.TryGetValue(key, out string? value)) { return value; }
+ else { throw new NotSupportedException($"Unsupported key: \"{key}\""); }
+ }
- private void SetTag(long tag)
- {
- ObjC.Call(Handle, "setTag:", new IntPtr(tag));
+ private static readonly Dictionary Keymap = new()
+ {
+ { Key.None, null },
+ { Key.F1, "\0xF704" },
+ { Key.F2, "\0xF705" },
+ { Key.F3, "\0xF706" },
+ { Key.F4, "\0xF707" },
+ { Key.F5, "\0xF708" },
+ { Key.F6, "\0xF709" },
+ { Key.F7, "\0xF70A" },
+ { Key.F8, "\0xF70B" },
+ { Key.F9, "\0xF70C" },
+ { Key.F10, "\0xF70D" },
+ { Key.F11, "\0xF70E" },
+ { Key.F12, "\0xF70F" },
+ { Key.Number1, "1" },
+ { Key.Number2, "2" },
+ { Key.Number3, "3" },
+ { Key.Number4, "4" },
+ { Key.Number5, "5" },
+ { Key.Number6, "6" },
+ { Key.Number7, "7" },
+ { Key.Number8, "8" },
+ { Key.Number9, "9" },
+ { Key.Number0, "0" },
+ { Key.A, "a" },
+ { Key.B, "b" },
+ { Key.C, "c" },
+ { Key.D, "d" },
+ { Key.E, "e" },
+ { Key.F, "f" },
+ { Key.G, "g" },
+ { Key.H, "h" },
+ { Key.I, "i" },
+ { Key.J, "j" },
+ { Key.K, "k" },
+ { Key.L, "l" },
+ { Key.M, "m" },
+ { Key.N, "n" },
+ { Key.O, "o" },
+ { Key.P, "p" },
+ { Key.Q, "q" },
+ { Key.R, "r" },
+ { Key.S, "s" },
+ { Key.T, "t" },
+ { Key.U, "u" },
+ { Key.V, "v" },
+ { Key.W, "w" },
+ { Key.X, "x" },
+ { Key.Y, "y" },
+ { Key.Z, "z" },
+ { Key.Insert, "\0xF727" },
+ { Key.Delete, "\0x007f" }, // Note: different than NSKey.Delete
+ };
}
}
}
diff --git a/Source/SpiderEye.Mac/Menu/CocoaMenu.cs b/Source/SpiderEye.Mac/Menu/CocoaMenu.cs
index 54680bd1..b4337fc1 100644
--- a/Source/SpiderEye.Mac/Menu/CocoaMenu.cs
+++ b/Source/SpiderEye.Mac/Menu/CocoaMenu.cs
@@ -1,54 +1,25 @@
using System;
-using SpiderEye.Mac.Interop;
-using SpiderEye.Mac.Native;
-using SpiderEye.Tools;
+using AppKit;
namespace SpiderEye.Mac
{
- internal class CocoaMenu : IMenu
+ internal class CocoaMenu : NSMenu, IMenu
{
- public string? Title
- {
- get
- {
- IntPtr title = ObjC.Call(Handle, "title");
- return NSString.GetString(title);
- }
- set
- {
- IntPtr title = NSString.Create(value);
- ObjC.Call(Handle, "setTitle:", title);
- }
- }
-
- public readonly IntPtr Handle;
-
public CocoaMenu()
: this(null)
{
}
public CocoaMenu(string? title)
+ : base(title ?? string.Empty)
{
- if (title != null)
- {
- Handle = AppKit.Call("NSMenu", "alloc");
- ObjC.Call(Handle, "initWithTitle:", NSString.Create(title));
- }
- else { Handle = AppKit.Call("NSMenu", "new"); }
}
public void AddItem(IMenuItem item)
{
if (item == null) { throw new ArgumentNullException(nameof(item)); }
- var nativeItem = NativeCast.To(item);
- ObjC.Call(Handle, "addItem:", nativeItem.Handle);
- }
-
- public void Dispose()
- {
- // don't think anything needs to be done here
+ AddItem((NSMenuItem)item);
}
}
}
diff --git a/Source/SpiderEye.Mac/Menu/CocoaMenuItem.cs b/Source/SpiderEye.Mac/Menu/CocoaMenuItem.cs
index 2da85a59..9664cba4 100644
--- a/Source/SpiderEye.Mac/Menu/CocoaMenuItem.cs
+++ b/Source/SpiderEye.Mac/Menu/CocoaMenuItem.cs
@@ -1,90 +1,12 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using SpiderEye.Mac.Native;
+using AppKit;
namespace SpiderEye.Mac
{
- internal abstract class CocoaMenuItem : IMenuItem
+ internal abstract class CocoaMenuItem : NSMenuItem, IMenuItem
{
- public readonly IntPtr Handle;
-
- protected CocoaMenuItem(IntPtr handle)
- {
- Handle = handle;
- }
-
public virtual IMenu CreateSubMenu()
{
- return new CocoaSubMenu(Handle);
- }
-
- public void Dispose()
- {
- // don't think anything needs to be done here
- }
-
- protected sealed class CocoaSubMenu : IMenu
- {
- public string? Title
- {
- get
- {
- if (menu == null) { return title; }
- else { return menu.Title; }
- }
- set
- {
- if (menu == null) { title = value; }
- else { menu.Title = value; }
- }
- }
-
- public CocoaMenu? NativeMenu
- {
- get { return menu; }
- }
-
- private readonly IntPtr menuItem;
- private string? title;
- private CocoaMenu? menu;
-
- public CocoaSubMenu(IntPtr menuItem)
- : this(menuItem, null)
- {
- }
-
- public CocoaSubMenu(IntPtr menuItem, string? title)
- : this(menuItem, title, false)
- {
- }
-
- public CocoaSubMenu(IntPtr menuItem, string? title, bool createImmediately)
- {
- this.menuItem = menuItem;
- this.title = title;
- if (createImmediately) { SetNativeMenu(); }
- }
-
- public void AddItem(IMenuItem item)
- {
- if (item == null) { throw new ArgumentNullException(nameof(item)); }
-
- if (menu == null) { SetNativeMenu(); }
-
- menu.AddItem(item);
- }
-
- public void Dispose()
- {
- menu?.Dispose();
- }
-
- [MemberNotNull(nameof(menu))]
- private void SetNativeMenu()
- {
- menu = new CocoaMenu(title);
- ObjC.Call(menuItem, "setSubmenu:", menu.Handle);
- }
+ return new CocoaSubMenu(this);
}
}
}
diff --git a/Source/SpiderEye.Mac/Menu/CocoaSeparatorMenuItem.cs b/Source/SpiderEye.Mac/Menu/CocoaSeparatorMenuItem.cs
index 26653dd2..d914a551 100644
--- a/Source/SpiderEye.Mac/Menu/CocoaSeparatorMenuItem.cs
+++ b/Source/SpiderEye.Mac/Menu/CocoaSeparatorMenuItem.cs
@@ -1,12 +1,17 @@
-using SpiderEye.Mac.Native;
+using AppKit;
namespace SpiderEye.Mac
{
- internal class CocoaSeparatorMenuItem : CocoaMenuItem
+ internal class CocoaSeparatorMenuItem : NSMenuItem, IMenuItem
{
public CocoaSeparatorMenuItem()
- : base(AppKit.Call("NSMenuItem", "separatorItem"))
+ : base(NSMenuItem.SeparatorItem.Handle)
{
}
+
+ public IMenu CreateSubMenu()
+ {
+ return new CocoaSubMenu(this);
+ }
}
}
diff --git a/Source/SpiderEye.Mac/Menu/CocoaSubMenu.cs b/Source/SpiderEye.Mac/Menu/CocoaSubMenu.cs
new file mode 100644
index 00000000..6185ccf8
--- /dev/null
+++ b/Source/SpiderEye.Mac/Menu/CocoaSubMenu.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using AppKit;
+
+namespace SpiderEye.Mac
+{
+ internal sealed class CocoaSubMenu : IMenu
+ {
+ public string? Title
+ {
+ get
+ {
+ if (menu == null) { return title; }
+ else { return menu.Title; }
+ }
+ set
+ {
+ if (menu == null) { title = value; }
+ else { menu.Title = value ?? string.Empty; }
+ }
+ }
+
+ public CocoaMenu? NativeMenu
+ {
+ get { return menu; }
+ }
+
+ private readonly NSMenuItem menuItem;
+ private string? title;
+ private CocoaMenu? menu;
+
+ public CocoaSubMenu(NSMenuItem menuItem)
+ : this(menuItem, null)
+ {
+ }
+
+ public CocoaSubMenu(NSMenuItem menuItem, string? title)
+ : this(menuItem, title, false)
+ {
+ }
+
+ public CocoaSubMenu(NSMenuItem menuItem, string? title, bool createImmediately)
+ {
+ this.menuItem = menuItem;
+ this.title = title;
+ if (createImmediately) { SetNativeMenu(); }
+ }
+
+ public void AddItem(IMenuItem item)
+ {
+ if (item == null) { throw new ArgumentNullException(nameof(item)); }
+
+ if (menu == null) { SetNativeMenu(); }
+
+ menu.AddItem(item);
+ }
+
+ public void Dispose()
+ {
+ menu?.Dispose();
+ }
+
+ [MemberNotNull(nameof(menu))]
+ private void SetNativeMenu()
+ {
+ menu = new CocoaMenu(title);
+ menuItem.Submenu = menu;
+ }
+ }
+}
diff --git a/Source/SpiderEye.Mac/Menu/MenuExtensions.App.cs b/Source/SpiderEye.Mac/Menu/MenuExtensions.App.cs
index 3f6053d0..eed5dfe4 100644
--- a/Source/SpiderEye.Mac/Menu/MenuExtensions.App.cs
+++ b/Source/SpiderEye.Mac/Menu/MenuExtensions.App.cs
@@ -1,5 +1,5 @@
using System;
-using SpiderEye.Mac.Native;
+using AppKit;
namespace SpiderEye.Mac
{
@@ -32,7 +32,7 @@ public static MenuItem AddServicesMenu(this MenuItemCollection menuItems, string
var menu = new LabelMenuItem(nativeMenu);
menuItems.Add(menu);
- ObjC.Call(MacApplication.Handle, "setServicesMenu:", submenu.Handle);
+ NSApplication.SharedApplication.ServicesMenu = submenu;
return menu;
}
diff --git a/Source/SpiderEye.Mac/Native/AppKit.cs b/Source/SpiderEye.Mac/Native/AppKit.cs
deleted file mode 100644
index c1f7537a..00000000
--- a/Source/SpiderEye.Mac/Native/AppKit.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Native
-{
- internal static class AppKit
- {
- private const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
-
- [DllImport(AppKitFramework, EntryPoint = "objc_getClass", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetClass(string name);
-
- [DllImport(AppKitFramework, EntryPoint = "objc_getProtocol", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetProtocol(string name);
-
- public static IntPtr Call(string id, string sel)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel));
- }
-
- public static IntPtr Call(string id, string sel, double a, double b, double c, double d)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a, b, c, d);
- }
-
- public static IntPtr Call(string id, string sel, IntPtr a)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Native/Dispatch.cs b/Source/SpiderEye.Mac/Native/Dispatch.cs
deleted file mode 100644
index cf9e18b4..00000000
--- a/Source/SpiderEye.Mac/Native/Dispatch.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using SpiderEye.Mac.Interop;
-
-namespace SpiderEye.Mac.Native
-{
- internal static class Dispatch
- {
- public static IntPtr MainQueue
- {
- get { return MainQueueHandle; }
- }
-
- private const string DispatchLib = "/usr/lib/system/libdispatch.dylib";
- private const string SystemLib = "/usr/lib/libSystem.dylib";
-
- private static readonly IntPtr MainQueueHandle = IntPtr.Zero;
- private static readonly IntPtr DispatchLibHandle = IntPtr.Zero;
-
- static Dispatch()
- {
- // dispatch_get_main_queue is not exposed (macro?) so we need to access the main queue field instead
- DispatchLibHandle = LoadLibrary(DispatchLib, 0);
- MainQueueHandle = GetSymbol(DispatchLibHandle, "_dispatch_main_q");
- }
-
- [DllImport(DispatchLib, EntryPoint = "dispatch_sync_f")]
- public static extern void SyncFunction(IntPtr queue, IntPtr context, DispatchDelegate work);
-
- [DllImport(DispatchLib, EntryPoint = "dispatch_async_f")]
- public static extern void AsyncFunction(IntPtr queue, IntPtr context, DispatchDelegate work);
-
- [DllImport(SystemLib, EntryPoint = "dlopen", CharSet = CharSet.Ansi)]
- private static extern IntPtr LoadLibrary(string path, int mode);
-
- [DllImport(SystemLib, EntryPoint = "dlclose")]
- private static extern int ReleaseLibrary(IntPtr handle);
-
- [DllImport(SystemLib, EntryPoint = "dlsym", CharSet = CharSet.Ansi)]
- private static extern IntPtr GetSymbol(IntPtr handle, string symbol);
- }
-}
diff --git a/Source/SpiderEye.Mac/Native/Foundation.cs b/Source/SpiderEye.Mac/Native/Foundation.cs
deleted file mode 100644
index 41e5581f..00000000
--- a/Source/SpiderEye.Mac/Native/Foundation.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Native
-{
- internal static class Foundation
- {
- private const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
-
- [DllImport(FoundationFramework, EntryPoint = "objc_getClass", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetClass(string name);
-
- [DllImport(FoundationFramework, EntryPoint = "objc_getProtocol", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetProtocol(string name);
-
- public static IntPtr Call(string id, string sel)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel));
- }
-
- public static IntPtr Call(string id, string sel, IntPtr a)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a);
- }
-
- public static IntPtr Call(string id, string sel, IntPtr a, IntPtr b)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a, b);
- }
-
- public static IntPtr Call(string id, string sel, IntPtr a, IntPtr b, IntPtr c)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a, b, c);
- }
-
- public static IntPtr Call(string id, string sel, IntPtr[] a, IntPtr b)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a, b);
- }
-
- public static IntPtr Call(string id, string sel, bool a)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel), a);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Native/ObjC.cs b/Source/SpiderEye.Mac/Native/ObjC.cs
deleted file mode 100644
index 65a7a9e5..00000000
--- a/Source/SpiderEye.Mac/Native/ObjC.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using SpiderEye.Mac.Interop;
-
-namespace SpiderEye.Mac.Native
-{
- internal static class ObjC
- {
- private const string ObjCLib = "/usr/lib/libobjc.dylib";
-
- [DllImport(ObjCLib, EntryPoint = "objc_getClass", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetClass(string name);
-
- [DllImport(ObjCLib, EntryPoint = "objc_allocateClassPair", CharSet = CharSet.Ansi)]
- public static extern IntPtr AllocateClassPair(IntPtr superclass, string name, IntPtr extraBytes);
-
- [DllImport(ObjCLib, EntryPoint = "objc_registerClassPair")]
- public static extern void RegisterClassPair(IntPtr cls);
-
- [DllImport(ObjCLib, EntryPoint = "class_addProtocol")]
- public static extern bool AddProtocol(IntPtr cls, IntPtr protocol);
-
- [DllImport(ObjCLib, EntryPoint = "objc_getProtocol", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetProtocol(string name);
-
- [DllImport(ObjCLib, EntryPoint = "class_addMethod", CharSet = CharSet.Ansi)]
- public static extern bool AddMethod(IntPtr cls, IntPtr name, Delegate imp, string types);
-
- [DllImport(ObjCLib, EntryPoint = "class_addIvar", CharSet = CharSet.Ansi)]
- public static extern bool AddVariable(IntPtr cls, string name, IntPtr size, byte alignment, string types);
-
- [DllImport(ObjCLib, EntryPoint = "class_getInstanceVariable", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetVariable(IntPtr cls, string name);
-
- [DllImport(ObjCLib, EntryPoint = "object_getIvar")]
- public static extern IntPtr GetVariableValue(IntPtr obj, IntPtr ivar);
-
- [DllImport(ObjCLib, EntryPoint = "object_setIvar")]
- public static extern IntPtr SetVariableValue(IntPtr obj, IntPtr ivar, IntPtr value);
-
- [DllImport(ObjCLib, EntryPoint = "sel_registerName", CharSet = CharSet.Ansi)]
- public static extern IntPtr RegisterName(string? name);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr a);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, UIntPtr a);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr a, IntPtr b);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr a, IntPtr b, IntPtr c);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr a, IntPtr b, IntPtr c, IntPtr d);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr a, UIntPtr b);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, IntPtr[] a, IntPtr b);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, [MarshalAs(UnmanagedType.I1)]bool value);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, double value);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, double a, double b, double c, double d);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, CGRect rect, IntPtr a);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, CGRect rect, UIntPtr a, UIntPtr b, [MarshalAs(UnmanagedType.I1)]bool c);
-
- [DllImport(ObjCLib, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendMessage(IntPtr self, IntPtr op, CGSize size);
-
- public static IntPtr Call(IntPtr id, string sel)
- {
- return SendMessage(id, RegisterName(sel));
- }
-
- public static IntPtr Call(IntPtr id, string sel, IntPtr a)
- {
- return SendMessage(id, RegisterName(sel), a);
- }
-
- public static IntPtr Call(IntPtr id, string sel, UIntPtr a)
- {
- return SendMessage(id, RegisterName(sel), a);
- }
-
- public static IntPtr Call(IntPtr id, string sel, double a)
- {
- return SendMessage(id, RegisterName(sel), a);
- }
-
- public static IntPtr Call(IntPtr id, string sel, bool a)
- {
- return SendMessage(id, RegisterName(sel), a);
- }
-
- public static IntPtr Call(IntPtr id, string sel, IntPtr a, IntPtr b)
- {
- return SendMessage(id, RegisterName(sel), a, b);
- }
-
- public static IntPtr Call(IntPtr id, string sel, IntPtr a, IntPtr b, IntPtr c)
- {
- return SendMessage(id, RegisterName(sel), a, b, c);
- }
-
- public static IntPtr Call(IntPtr id, string sel, IntPtr a, IntPtr b, IntPtr c, IntPtr d)
- {
- return SendMessage(id, RegisterName(sel), a, b, c, d);
- }
-
- public static IntPtr Call(IntPtr id, string sel, CGRect rect, IntPtr a)
- {
- return SendMessage(id, RegisterName(sel), rect, a);
- }
-
- public static IntPtr Call(IntPtr id, string sel, CGSize size)
- {
- return SendMessage(id, RegisterName(sel), size);
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/Native/Webkit.cs b/Source/SpiderEye.Mac/Native/Webkit.cs
deleted file mode 100644
index 5b2334de..00000000
--- a/Source/SpiderEye.Mac/Native/Webkit.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace SpiderEye.Mac.Native
-{
- internal static class WebKit
- {
- private const string WebKitFramework = "/System/Library/Frameworks/WebKit.framework/WebKit";
-
- [DllImport(WebKitFramework, EntryPoint = "objc_getClass", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetClass(string name);
-
- [DllImport(WebKitFramework, EntryPoint = "objc_getProtocol", CharSet = CharSet.Ansi)]
- public static extern IntPtr GetProtocol(string name);
-
- public static IntPtr Call(string id, string sel)
- {
- return ObjC.SendMessage(GetClass(id), ObjC.RegisterName(sel));
- }
- }
-}
diff --git a/Source/SpiderEye.Mac/SpiderEye.Mac.csproj b/Source/SpiderEye.Mac/SpiderEye.Mac.csproj
index a2127dcf..f84ce8b0 100644
--- a/Source/SpiderEye.Mac/SpiderEye.Mac.csproj
+++ b/Source/SpiderEye.Mac/SpiderEye.Mac.csproj
@@ -1,7 +1,7 @@
-
+
- net6.0
+ net6.0-macos10.14