Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Import Project="..\Shared\SpiderEye.Playground.Shared.proj" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net6.0-macos</TargetFramework>
<DefineConstants>$(DefineConstants);MAC</DefineConstants>
</PropertyGroup>

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| ----- | ----- | ----- |
Expand Down Expand Up @@ -170,7 +170,7 @@ First you need an `Info.plist` file like you'd have for any other macOS app. Her
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<string>10.14</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
Expand Down
65 changes: 19 additions & 46 deletions Source/SpiderEye.Mac/CocoaApplication.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Threading;
using SpiderEye.Mac.Interop;
using SpiderEye.Mac.Native;
using AppKit;
using Foundation;

namespace SpiderEye.Mac
{
Expand All @@ -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<ShouldTerminateDelegate>(
"applicationShouldTerminateAfterLastWindowClosed:",
"c@:@",
(self, op, notification) => (byte)(Application.ExitWithLastWindow ? 1 : 0));

definition.AddMethod<NotificationDelegate>(
"applicationDidFinishLaunching:",
"v@:@",
(self, op, notification) =>
{
var instance = definition.GetParent<CocoaApplication>(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;
}
}
}
}
74 changes: 27 additions & 47 deletions Source/SpiderEye.Mac/CocoaStatusIcon.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
using System;
using SpiderEye.Mac.Interop;
using SpiderEye.Mac.Native;
using SpiderEye.Tools;
using AppKit;
using Foundation;

namespace SpiderEye.Mac
{
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
Expand All @@ -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;
}
}

Expand All @@ -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<CocoaMenu>(menu?.NativeMenu);
ObjC.Call(statusItem, "setMenu:", nativeMenu?.Handle ?? IntPtr.Zero);
statusItem.StatusBar.RemoveStatusItem(statusItem);
statusItem.Dispose();
}
}
}
75 changes: 0 additions & 75 deletions Source/SpiderEye.Mac/CocoaSynchronizationContext.cs

This file was deleted.

Loading