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 5 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
danwalmsley 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions native/Avalonia.Native/inc/avalonia-native.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ enum AvnWindowEdge
WindowEdgeSouthEast
};


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

struct AvnNotification {
const char* TitleUtf8;
const char* TextUtf8;
int durationMs;
AvnNotificationActionCallback ActionCallback;
AvnNotificationCloseCallback CloseCallback;
};

AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
Expand Down Expand Up @@ -412,4 +424,9 @@ AVNCOM(IAvnAppMenuItem, 19) : IUnknown
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};

AVNCOM(IAvnNotificationManager, 20) : IUnknown
{
virtual bool ShowNotification(AvnNotification* notification) = 0;
};

extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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 */; };
FA7E93B823B10CE0004F99B3 /* NotificationManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = FA7E93B723B10CE0004F99B3 /* NotificationManager.mm */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -47,6 +48,7 @@
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>"; };
FA7E93B723B10CE0004F99B3 /* NotificationManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NotificationManager.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -74,6 +76,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
FA7E93B723B10CE0004F99B3 /* NotificationManager.mm */,
1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
Expand Down Expand Up @@ -181,6 +184,7 @@
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
FA7E93B823B10CE0004F99B3 /* NotificationManager.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
Expand Down
38 changes: 38 additions & 0 deletions native/Avalonia.Native/src/OSX/NotificationManager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "common.h"
#import <Cocoa/Cocoa.h>

class NotificationManager : public ComSingleObject<IAvnNotificationManager, &IID_IAvnNotificationManager>
{
public:
FORWARD_IUNKNOWN()

virtual bool ShowNotification(AvnNotification* notification) override {
NSUserNotification* cocoaNotification = [NSUserNotification new];

//cocoaNotification.identifier = @"unique-id";
cocoaNotification.title = [NSString stringWithUTF8String:notification->TitleUtf8];
cocoaNotification.informativeText = [NSString stringWithUTF8String:notification->TextUtf8];

NSMutableDictionary* userData = [NSMutableDictionary dictionary];
[userData setValue:[NSValue valueWithPointer:(const void*) notification->ActionCallback]
forKey:@"actionCallback"];
[userData setValue:[NSValue valueWithPointer:(const void*) notification->CloseCallback]
forKey:@"closeCallback"];
cocoaNotification.userInfo = userData;

[[NSUserNotificationCenter defaultUserNotificationCenter]
deliverNotification:cocoaNotification];

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

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

return YES;
}

};
29 changes: 29 additions & 0 deletions native/Avalonia.Native/src/OSX/app.mm
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#include "common.h"
#include <Cocoa/Cocoa.h>

@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@end

extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;

@implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
Expand All @@ -22,6 +25,32 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}

- (void)userNotificationCenter:(NSUserNotificationCenter *)center
didActivateNotification:(NSUserNotification *)notification{

if (notification.activationType != NSUserNotificationActivationTypeContentsClicked &&
notification.activationType != NSUserNotificationActivationTypeActionButtonClicked) {
return;
}

NSValue* actionCallbackValue = (NSValue*) notification.userInfo[@"actionCallback"];

if (actionCallbackValue.pointerValue) {
AvnNotificationActionCallback actionCallback =
(AvnNotificationActionCallback) actionCallbackValue.pointerValue;
actionCallback();
}

//TODO: I can't really find a proper event for closing.
NSValue* closeCallbackValue = (NSValue*) notification.userInfo[@"closeCallback"];

if (closeCallbackValue.pointerValue) {
AvnNotificationActionCallback closeCallback =
(AvnNotificationCloseCallback) closeCallbackValue.pointerValue;
closeCallback();
}
}

@end

extern void InitializeAvnApp()
Expand Down
4 changes: 2 additions & 2 deletions samples/ControlCatalog/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
<DockPanel LastChildFill="True">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
<MenuItem Header="Exit" Command="{Binding Exit}" />
pr8x marked this conversation as resolved.
Show resolved Hide resolved
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
<MenuItem Header="About" Command="{Binding About}" />
</MenuItem>
</Menu>
<local:MainView />
Expand Down
6 changes: 3 additions & 3 deletions samples/ControlCatalog/Pages/NotificationsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
x:Class="ControlCatalog.Pages.NotificationsPage">
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left">
<TextBlock Classes="h1">Notifications</TextBlock>
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" />
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" />
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotificationCommand}" />
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotification}" />
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotification}" />
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotification}" />
pr8x marked this conversation as resolved.
Show resolved Hide resolved
</StackPanel>
</UserControl>
96 changes: 63 additions & 33 deletions samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,93 @@
using System.Reactive;
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
using JetBrains.Annotations;
using ReactiveUI;

namespace ControlCatalog.ViewModels
{
class MainWindowViewModel : ReactiveObject
public class MainWindowViewModel : ReactiveObject
{
private readonly INotificationManager _nativeNotificationManager;
private IManagedNotificationManager _notificationManager;

public MainWindowViewModel(IManagedNotificationManager notificationManager)
{
_notificationManager = notificationManager;
_nativeNotificationManager = AvaloniaLocator.Current.GetService<INotificationManager>();
}

ShowCustomManagedNotificationCommand = ReactiveCommand.Create(() =>
{
NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" });
});
public IManagedNotificationManager NotificationManager
{
get { return _notificationManager; }
set { this.RaiseAndSetIfChanged(ref _notificationManager, value); }
}

ShowManagedNotificationCommand = ReactiveCommand.Create(() =>
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
});
[UsedImplicitly]
public void ShowManagedNotification()
{
NotificationManager.Show(new Notification(
"Welcome",
"Avalonia now supports Notifications."));
}

ShowNativeNotificationCommand = ReactiveCommand.Create(() =>
[UsedImplicitly]
public void ShowNativeNotification()
{
if (_nativeNotificationManager != null)
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
});

AboutCommand = ReactiveCommand.CreateFromTask(async () =>
_nativeNotificationManager.Show(new Notification(
"Native",
"Native Notifications are finally here!",
NotificationType.Success,
onClick: NativeNotficationClicked,
onClose: NativeNotificationClosed));
}
else
{
var dialog = new AboutAvaloniaDialog();

var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;

await dialog.ShowDialog(mainWindow);
});
NotificationManager.Show(new Notification(
"Native",
"Native Notifications are not supported on this platform!",
NotificationType.Error));
}
}

ExitCommand = ReactiveCommand.Create(() =>
{
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
});
private static void NativeNotificationClosed()
{
Console.WriteLine("Native notification closed.");
}

public IManagedNotificationManager NotificationManager
private static void NativeNotficationClicked()
{
get { return _notificationManager; }
set { this.RaiseAndSetIfChanged(ref _notificationManager, value); }
Console.WriteLine("Native notification clicked.");
}

public ReactiveCommand<Unit, Unit> ShowCustomManagedNotificationCommand { get; }
public async void About()
{
var dialog = new AboutAvaloniaDialog();

public ReactiveCommand<Unit, Unit> ShowManagedNotificationCommand { get; }
var mainWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)
?.MainWindow;

public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; }
await dialog.ShowDialog(mainWindow);
}

public ReactiveCommand<Unit, Unit> AboutCommand { get; }
public void Exit()
{
(Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
}

public ReactiveCommand<Unit, Unit> ExitCommand { get; }
[UsedImplicitly]
public void ShowCustomManagedNotification()
{
NotificationManager.Show(
new NotificationViewModel(NotificationManager)
{
Title = "Hey There!",
Message = "Did you know that Avalonia now supports Custom In-Window Notifications?"
});
}
}
}
8 changes: 8 additions & 0 deletions src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@
<PackageReference Include="Tmds.DBus" Version="0.7.0" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Tmds.DBus.Tool" Version="0.7.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Tmds.DBus" Version="0.7.0" />
pr8x marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

</Project>
Loading