Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native notifications for Windows, Linux and OSX #3389

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
22d8d98
!F Porting FreeDesktop NotificationManager from PR
pr8x Dec 22, 2019
4e58a51
!T Cleaning up the notification page
pr8x Dec 23, 2019
39ea180
!B Fixing About and Exit commands
pr8x Dec 23, 2019
964d28e
!F Adding support for Win10NotificationManager
pr8x Dec 23, 2019
fee7617
!F OSX Notification COM implementation
pr8x Dec 23, 2019
ba3f4b6
!F Adding CreateNotificationManager hook to IAvaloniaNativeFactory
pr8x Dec 24, 2019
9efd362
!F Implementing NativeNotificationManager
pr8x Dec 25, 2019
51b53a2
!T Reverting to .NET Core 2
pr8x Dec 27, 2019
291a274
Merge branch 'master' into native_notifications_win_linux_osx
pr8x Jan 12, 2020
d7463f1
Merge branch 'master' into native_notifications_win_linux_osx
Jan 22, 2020
da4422c
!T Reverting to ReactiveCommand
pr8x Jan 25, 2020
a4e3230
Merge branch 'master' into native_notifications_win_linux_osx
pr8x Jan 25, 2020
05ea283
!R Installing shortcut and appUserModlId in AfterSetup.
pr8x Jan 25, 2020
4c4e4ab
Merge branch 'native_notifications_win_linux_osx' of https://github.c…
pr8x Jan 25, 2020
70978d7
!R Making INotificationManager.Show async
pr8x Feb 1, 2020
e171730
Merge branch 'master' into native_notifications_win_linux_osx
pr8x Feb 1, 2020
8db0b4d
!R Using ValueTask
pr8x Feb 1, 2020
a937b2c
!T Don't create notifier every time notification is requested
pr8x Feb 1, 2020
7c76b26
!T Removing duplicate package
pr8x Feb 1, 2020
c9913df
!R Fallback to null when FreeDesktopNotificationManager fails to init…
pr8x Feb 1, 2020
625faf9
!R Moving AfterSetup() into Win32Platform
pr8x Feb 1, 2020
7323816
!T Removing service check
pr8x Feb 16, 2020
62d3953
Merge branch 'master' into native_notifications_win_linux_osx
pr8x Feb 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions native/Avalonia.Native/inc/avalonia-native.h
Expand Up @@ -24,6 +24,7 @@ struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
struct IAvnNotificationManager;

struct AvnSize
{
Expand Down Expand Up @@ -172,6 +173,17 @@ enum AvnWindowEdge
WindowEdgeSouthEast
};


typedef void (*AvnNotificationActionCallback)(int);
typedef void (*AvnNotificationCloseCallback)(int);

struct AvnNotification {
int identifier;
const char* TitleUtf8;
const char* TextUtf8;
int durationMs;
};

AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
Expand All @@ -190,6 +202,7 @@ AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0;
virtual HRESULT CreateNotificationManager(IAvnNotificationManager** ppv) = 0;
};

AVNCOM(IAvnString, 17) : IUnknown
Expand Down Expand Up @@ -406,4 +419,11 @@ AVNCOM(IAvnAppMenuItem, 19) : IUnknown
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};

AVNCOM(IAvnNotificationManager, 20) : IUnknown
{
virtual void SetCloseCallback(AvnNotificationCloseCallback callback) = 0;
virtual void SetActionCallback(AvnNotificationActionCallback callback) = 0;
virtual bool ShowNotification(AvnNotification* notification) = 0;
};

extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();
Expand Up @@ -13,10 +13,10 @@
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
37A517B32159597E00FBA241 /* screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* screens.mm */; };
37C09D8821580FE4006A6758 /* system_dialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* system_dialogs.mm */; };
37DDA9B0219330F8002E132B /* avnstring.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* avnstring.mm */; };
37E2330F21583241000CB7E2 /* key_transform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* key_transform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
Expand All @@ -25,6 +25,8 @@
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
FA60D81023B2D993006DCFA0 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA60D80F23B2D992006DCFA0 /* Cocoa.framework */; };
FA7E93B823B10CE0004F99B3 /* notification_manager.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA7E93B723B10CE0004F99B3 /* notification_manager.mm */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -34,14 +36,14 @@
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* key_transform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = key_transform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* system_dialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = system_dialogs.mm; sourceTree = "<group>"; };
37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = "<group>"; };
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
37DDA9AF219330F8002E132B /* avnstring.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = avnstring.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* avnstring.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = avnstring.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* key_transform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = key_transform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; 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>"; };
Expand All @@ -53,13 +55,16 @@
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
FA60D80F23B2D992006DCFA0 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
FA7E93B723B10CE0004F99B3 /* notification_manager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = notification_manager.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
AB7A61EC2147C814003C5833 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FA60D81023B2D993006DCFA0 /* Cocoa.framework in Frameworks */,
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */,
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */,
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
Expand All @@ -73,6 +78,7 @@
AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup;
children = (
FA60D80F23B2D992006DCFA0 /* Cocoa.framework */,
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */,
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
AB1E522B217613570091CD71 /* OpenGL.framework */,
Expand All @@ -84,26 +90,27 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
FA7E93B723B10CE0004F99B3 /* notification_manager.mm */,
1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37DDA9B121933371002E132B /* avnstring.h */,
37DDA9AF219330F8002E132B /* avnstring.mm */,
37A4E71A2178846A00EACBCD /* headers */,
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
37E2330E21583241000CB7E2 /* KeyTransform.mm */,
379860FE214DA0C000CD0246 /* key_transform.h */,
37E2330E21583241000CB7E2 /* key_transform.mm */,
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
37A517B22159597E00FBA241 /* screens.mm */,
37C09D8721580FE4006A6758 /* system_dialogs.mm */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
);
Expand Down Expand Up @@ -189,15 +196,17 @@
1A002B9E232135EE00021753 /* app.mm in Sources */,
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
37DDA9B0219330F8002E132B /* avnstring.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* key_transform.mm in Sources */,
FA7E93B823B10CE0004F99B3 /* notification_manager.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
37A517B32159597E00FBA241 /* screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
37C09D8821580FE4006A6758 /* system_dialogs.mm in Sources */,
AB573DC4217605E400D389A2 /* gl.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -254,9 +263,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
"INFOPLIST_FILE[sdk=*]" = "";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.avalonia.native.osx;
SDKROOT = macosx;
};
name = Debug;
Expand Down Expand Up @@ -306,6 +317,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.avalonia.native.osx;
SDKROOT = macosx;
};
name = Release;
Expand All @@ -317,6 +329,8 @@
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = ../../inc;
"INFOPLIST_FILE[sdk=*]" = "";
PRODUCT_BUNDLE_IDENTIFIER = com.avalonia.native.osx;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
Expand All @@ -328,6 +342,7 @@
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
HEADER_SEARCH_PATHS = ../../inc;
PRODUCT_BUNDLE_IDENTIFIER = com.avalonia.native.osx;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
Expand Down
35 changes: 35 additions & 0 deletions native/Avalonia.Native/src/OSX/app.mm
@@ -1,8 +1,39 @@
#include "common.h"
#include <Cocoa/Cocoa.h>
#import <objc/runtime.h>

@implementation NSBundle (FakeBundleIdentifier)

- (NSString *)__bundleIdentifier;
{
if (self == [NSBundle mainBundle]) {
return @"com.avalonia.native.osx";
Copy link
Member

@kekekeks kekekeks Feb 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add an option to configure that with AvaloniaNativeMacOptions and fall back to "com.avaloniaui.apps."+Assembly.GetEntryAssembly().Name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should also somehow interact with bundle identifier from Info.plist if the app is running from an actual app bundle.

Copy link
Contributor Author

@pr8x pr8x Feb 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should check the [[NSBundle mainBundle] bundleIdentifier] and only install the fake bundle identifier when there is no mainBundle identifier.

} else {
return [self __bundleIdentifier];
}
}

@end

//It is required to install a bundle identifier in order to use the notification center.
//Otherwise [NSUserNotificationCenter defaultUserNotificationCenter] will be nil.
//https://github.com/munki/munki/blob/master/code/apps/munki-notifier/munki-notifier/AppDelegate.m
static BOOL InstallFakeBundleIdentifierHook()
{
Class classImpl = objc_getClass("NSBundle");
if (classImpl) {
method_exchangeImplementations(class_getInstanceMethod(classImpl, @selector(bundleIdentifier)),
class_getInstanceMethod(classImpl, @selector(__bundleIdentifier)));
return YES;
}
return NO;
}

@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@end

extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;

@implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
Expand All @@ -20,6 +51,10 @@ - (void)applicationWillFinishLaunching:(NSNotification *)notification
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];

@autoreleasepool {
InstallFakeBundleIdentifierHook();
}
}

@end
Expand Down
2 changes: 1 addition & 1 deletion native/Avalonia.Native/src/OSX/clipboard.mm
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.

#include "common.h"
#include "AvnString.h"
#include "avnstring.h"

class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{
Expand Down
1 change: 1 addition & 0 deletions native/Avalonia.Native/src/OSX/common.h
Expand Up @@ -21,6 +21,7 @@ extern IAvnGlDisplay* GetGlDisplay();
extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem();
extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
extern IAvnNotificationManager* CreateNotificationManager();
extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu);
extern IAvnAppMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
Expand Down
@@ -1,7 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.

#include "KeyTransform.h"
#include "key_transform.h"

const int kVK_ANSI_A = 0x00;
const int kVK_ANSI_S = 0x01;
Expand Down
6 changes: 6 additions & 0 deletions native/Avalonia.Native/src/OSX/main.mm
Expand Up @@ -266,6 +266,12 @@ virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override

return S_OK;
}

virtual HRESULT CreateNotificationManager(IAvnNotificationManager** ppv) override
{
*ppv = ::CreateNotificationManager();
return S_OK;
}
};

extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
Expand Down
83 changes: 83 additions & 0 deletions native/Avalonia.Native/src/OSX/notification_manager.mm
@@ -0,0 +1,83 @@
#include "common.h"
#import <Cocoa/Cocoa.h>

@interface NotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate>
@property AvnNotificationActionCallback ActionCallback;
@property AvnNotificationCloseCallback CloseCallback;
@end

@implementation NotificationCenterDelegate

- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
{
// Force notifications to be always displayed even when running in foreground
return YES;
}

-(void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
{
if (notification.activationType != NSUserNotificationActivationTypeContentsClicked &&
notification.activationType != NSUserNotificationActivationTypeActionButtonClicked) {
return;
}

[self ActionCallback]([notification.identifier intValue]);
}
@end

class NotificationManager : public ComSingleObject<IAvnNotificationManager, &IID_IAvnNotificationManager>
{
NotificationCenterDelegate* _notificationCenterDelegate;

public:
FORWARD_IUNKNOWN()

NotificationManager() {
_notificationCenterDelegate = [NotificationCenterDelegate new];
}

virtual bool ShowNotification(AvnNotification* notification) override {

NSUserNotificationCenter* notificationCenter = [NSUserNotificationCenter defaultUserNotificationCenter];
notificationCenter.delegate = _notificationCenterDelegate;

NSUserNotification* cocoaNotification = [NSUserNotification new];

int notificationId = notification->identifier;
cocoaNotification.identifier = [NSString stringWithFormat:@"%i", notificationId];
cocoaNotification.title = [NSString stringWithUTF8String:notification->TitleUtf8];
cocoaNotification.informativeText = [NSString stringWithUTF8String:notification->TextUtf8];

[notificationCenter deliverNotification:cocoaNotification];

if (notification->durationMs != 0) {
unsigned long long durationNs = notification->durationMs * NSEC_PER_MSEC;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, durationNs), dispatch_get_main_queue(), ^{
[notificationCenter removeDeliveredNotification:cocoaNotification];

//TODO: HACK: I can't find a global close event... Might be missing something.
_notificationCenterDelegate.CloseCallback(notificationId);
});
}

return YES;
}

virtual void SetCloseCallback(AvnNotificationCloseCallback callback) override {
_notificationCenterDelegate.CloseCallback = callback;
}

virtual void SetActionCallback(AvnNotificationActionCallback callback) override {
_notificationCenterDelegate.ActionCallback = callback;
}

};

extern IAvnNotificationManager* CreateNotificationManager()
{
@autoreleasepool
{
return new NotificationManager();
}
}
2 changes: 1 addition & 1 deletion native/Avalonia.Native/src/OSX/platformthreading.mm
Expand Up @@ -59,7 +59,7 @@ - (void) action
FORWARD_IUNKNOWN()
bool Running = false;
bool Cancelled = false;
virtual void Cancel()
virtual void Cancel() override
{
Cancelled = true;
if(Running)
Expand Down