Browse Source

add initial implementation for osx tray icon support.

pull/6610/head
Dan Walmsley 4 years ago
parent
commit
6ae59214a4
  1. 6
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 1
      native/Avalonia.Native/src/OSX/common.h
  3. 11
      native/Avalonia.Native/src/OSX/main.mm
  4. 30
      native/Avalonia.Native/src/OSX/trayicon.h
  5. 59
      native/Avalonia.Native/src/OSX/trayicon.mm
  6. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  7. 75
      src/Avalonia.Native/TrayIconImpl.cs
  8. 15
      src/Avalonia.Native/avn.idl
  9. 2
      src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

6
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -22,6 +22,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 522D5958258159C1006F7F7A /* Carbon.framework */; };
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -51,6 +52,8 @@
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
522D5958258159C1006F7F7A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
523484C926EA688F00EA0C2C /* trayicon.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trayicon.mm; sourceTree = "<group>"; };
523484CB26EA68AA00EA0C2C /* trayicon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trayicon.h; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -114,6 +117,8 @@
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
523484C926EA688F00EA0C2C /* trayicon.mm */,
523484CB26EA68AA00EA0C2C /* trayicon.h */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
@ -204,6 +209,7 @@
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,

1
native/Avalonia.Native/src/OSX/common.h

@ -22,6 +22,7 @@ extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon(IAvnTrayIconEvents* events);
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);

11
native/Avalonia.Native/src/OSX/main.mm

@ -303,6 +303,17 @@ public:
}
}
virtual HRESULT CreateTrayIcon (IAvnTrayIconEvents*cb, IAvnTrayIcon** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreateTrayIcon(cb);
return S_OK;
}
}
virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{
START_COM_CALL;

30
native/Avalonia.Native/src/OSX/trayicon.h

@ -0,0 +1,30 @@
//
// trayicon.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 09/09/2021.
// Copyright © 2021 Avalonia. All rights reserved.
//
#ifndef trayicon_h
#define trayicon_h
#include "common.h"
class AvnTrayIcon : public ComSingleObject<IAvnTrayIcon, &IID_IAvnTrayIcon>
{
private:
NSStatusItem* _native;
ComPtr<IAvnTrayIconEvents> _events;
public:
FORWARD_IUNKNOWN()
AvnTrayIcon(IAvnTrayIconEvents* events);
virtual HRESULT SetIcon (void* data, size_t length) override;
virtual HRESULT SetMenu (IAvnMenu* menu) override;
};
#endif /* trayicon_h */

59
native/Avalonia.Native/src/OSX/trayicon.mm

@ -0,0 +1,59 @@
#include "common.h"
#include "trayicon.h"
extern IAvnTrayIcon* CreateTrayIcon(IAvnTrayIconEvents* cb)
{
@autoreleasepool
{
return new AvnTrayIcon(cb);
}
}
AvnTrayIcon::AvnTrayIcon(IAvnTrayIconEvents* events)
{
_events = events;
_native = [[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength];
}
HRESULT AvnTrayIcon::SetIcon (void* data, size_t length)
{
START_COM_CALL;
@autoreleasepool
{
if(data != nullptr)
{
NSData *imageData = [NSData dataWithBytes:data length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];
NSSize originalSize = [image size];
NSSize size;
size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;
auto scaleFactor = size.height / originalSize.height;
size.width = originalSize.width * scaleFactor;
[image setSize: size];
[_native setImage:image];
}
else
{
[_native setImage:nullptr];
}
return S_OK;
}
}
HRESULT AvnTrayIcon::SetMenu (IAvnMenu* menu)
{
START_COM_CALL;
@autoreleasepool
{
}
return S_OK;
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -136,7 +136,7 @@ namespace Avalonia.Native
public ITrayIconImpl CreateTrayIcon ()
{
throw new NotImplementedException();
return new TrayIconImpl(_factory);
}
public IWindowImpl CreateWindow()

75
src/Avalonia.Native/TrayIconImpl.cs

@ -0,0 +1,75 @@
using System;
using System.IO;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform;
namespace Avalonia.Native
{
class TrayIconEvents : CallbackBase, IAvnTrayIconEvents
{
private TrayIconImpl _parent;
public TrayIconEvents (TrayIconImpl parent)
{
_parent = parent;
}
public void Clicked()
{
}
public void DoubleClicked()
{
}
}
internal class TrayIconImpl : ITrayIconImpl
{
private readonly IAvnTrayIcon _native;
public TrayIconImpl(IAvaloniaNativeFactory factory)
{
_native = factory.CreateTrayIcon(new TrayIconEvents(this));
}
public void Dispose()
{
}
public unsafe void SetIcon(IWindowIconImpl? icon)
{
if(icon is null)
{
_native.SetIcon(null, IntPtr.Zero);
}
else
{
using (var ms = new MemoryStream())
{
icon.Save(ms);
var imageData = ms.ToArray();
fixed(void* ptr = imageData)
{
_native.SetIcon(ptr, new IntPtr(imageData.Length));
}
}
}
}
public void SetToolTipText(string? text)
{
// NOP
}
public void SetIsVisible(bool visible)
{
}
public INativeMenuExporter? MenuExporter { get; }
}
}

15
src/Avalonia.Native/avn.idl

@ -427,6 +427,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
HRESULT CreateMenuItem(IAvnMenuItem** ppv);
HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv);
HRESULT CreateTrayIcon(IAvnTrayIconEvents* cb, IAvnTrayIcon** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -665,6 +666,20 @@ interface IAvnGlSurfaceRenderingSession : IUnknown
HRESULT GetScaling(double* ret);
}
[uuid(60992d19-38f0-4141-a0a9-76ac303801f3)]
interface IAvnTrayIcon : IUnknown
{
HRESULT SetIcon(void* data, size_t length);
HRESULT SetMenu(IAvnMenu* menu);
}
[uuid(a687a6d9-73aa-4fef-9b4a-61587d7285d3)]
interface IAvnTrayIconEvents : IUnknown
{
void Clicked ();
void DoubleClicked ();
}
[uuid(a7724dc1-cf6b-4fa8-9d23-228bf2593edc)]
interface IAvnMenu : IUnknown
{

2
src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs

@ -35,7 +35,7 @@ namespace Avalonia.Win32
}
else if (item.HasClickHandlers && item is INativeMenuItemExporterEventsImplBridge bridge)
{
newItem.Click += (_,_) => bridge.RaiseClicked();
newItem.Click += (s, e) => bridge.RaiseClicked();
}
items.Add(newItem);

Loading…
Cancel
Save