-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Conversation
@@ -4,6 +4,9 @@ | |||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
<PackageId>Avalonia.Win32</PackageId> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.18362.2005" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that work with Win 8.1 or is it Win10+ only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is Win10+ only from what I can tell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Windows 10 WinRT API Pack enables you to add the latest Windows Runtime APIs support to your .NET Framework 4.5+ and .NET Core 3.0+ libraries and apps.
https://www.nuget.org/packages/Microsoft.Windows.SDK.Contracts/10.0.17763.1000-preview
But AFAIK the toast notifications only work in Windows 10 anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Windows 8.1 also has them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah Windows 8 and 8.1 both have support for the ToastNotification API. Not sure if we really care about Windows 8 though 😄 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want a managed fallback that displays notifications in separate windows manually. Like IM clients of the past did. That's not in the scope of this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's more of "do people who use Avalonia for their software care about Windows 8 and 8.1", not "if we care about it".
!F Close and action callbacks !F Handling notification center events in seperate delegate !T Renaming some files to be more consistent
@danwalmsley So how do we solve the problem with registering the application shortcut in the startmenu on Windows? Should I just register a temporary and uninstall it when the application terminates? |
its not required, but will be used, if registered (not check, only google) |
In my experience deleting the shortcut (even when the AppUserModelID has been assigned to the current process via
Also it seems like once the shortcut was installed they use the name and the icon of it to display the application in the taskbar. Without a shortcut the taskbar icon will default to the main window icon AFAICT. So it might actually be a good idea to have some sort of |
# Conflicts: # src/Avalonia.Native/AvaloniaNativePlatform.cs
@danwalmsley this is ready for review now, when you have a moment. |
…om/pr8x/Avalonia into native_notifications_win_linux_osx
src/Avalonia.FreeDesktop/Notifications/FreeDesktopNotificationManager.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the comments: please, avoid doing formatting changes for the sake of doing formatted changes. It confuses git blame
and makes it way harder to track the origin of a particular change. Same goes for renaming files. You'll probably have to squash your changes now.
|
||
private async ValueTask Connect() | ||
{ | ||
_isConnected = await |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the app might be started without a session bus or without org.freedesktop.Notifications
service on the session bus. That's the case for VNC-powered sessions, WSL, embedded devices and other scenarios without a "full" desktop environment. We should gracefully fall back to some managed implementation in that case.
Use DBusHelper.Connection
which will be just null
if no session DBus is available like we are doing in our dbusmenu implementation. Then try to create a proxy and see if that throws. That check should be done before registering FreeDesktopNotificationManager
in the service locator, so the rest of the code base won't be confused by dysfunctional notification manager. It would also be a good place to call GetCapabilitiesAsync
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be a good place to call GetCapabilitiesAsync.
Which capabilties would you check for? At first glance the features we rely on should be working in all server implementations.
We should gracefully fall back to some managed implementation in that case.
You mean the WindowNotificationManager
? Which Window should it be installed on though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we're only invoking DBusHelper.TryInitialize()
when UseDBusMenu
is enabled. We should probably be doing this unconditionally now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which Window should it be installed on though
The initial idea was that user code is supposed to get the notification manager from the window itself rather than using service locator. A single app might be using toplevels represented by different windowing systems. I. e. it's planned to have a "remote" mode for devtools when they are using our previewer remote widget infrastructure to render themselves to a remote machine (i. e. you will be able to connect to devtools of an application running on your android phone). So the general policy is to remove global variables when possible.
So there shouldn't be a globally accessible instance of the notification manager, only one accessed via ITopLevelImpl in some manner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there shouldn't be a globally accessible instance of the notification manager, only one accessed via ITopLevelImpl in some manner.
Allright, but that seems vastly out of scope for this PR. For now the service will just return null
when it fails to initialize. I guess the users can account for that in their code and fire up the WindowNotificationManager
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait... So instead of registering the notification manager (on all platforms) through AvaloniaLocator
I should just make it a property on the TopLevel
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, probably an attached one. The general idea is that services should be resolved via toplevels (somewhat like in Android where every damn thing needs a Context
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, note how it works with global menu - toplevel impl implements an extra interface where it announces that it could potentially provide some service.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toplevel impl implements an extra interface
I can't seem to find the interface you're referring to. What is it called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl |
_notifications = new Dictionary<int, INotification>(); | ||
_notificationCloseCallback = NotificationOnClose; | ||
_notificationActionCallback = NotificationOnAction; | ||
_native.CloseCallback = Marshal.GetFunctionPointerForDelegate(_notificationCloseCallback); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use function pointers with Avalonia.Native. Create a COM interface and implement it on the C# side instead. We are automatically generating native-to-managed call proxies for interfaces with Events
suffix.
The preferred way would be to not use an int
identifier field in AvnNotification but introduce
AVNCOM(IAvnNotificationEvents, 21) : IUnknown
{
virtual void OnAction() = 0;
virtual void OnClose() = 0;
};
and create a new instance of a class implementing that interface for each ShowNotification
call and pass it along with other notification info, then keep it in ComPtr<IAvnNotificationEvents>
on the native side. That way the callback life time would be managed automatically and we won't need to keep the GC-rooted dictionary with all of the notifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preferred way would be to not use an int identifier field in AvnNotification
I guess once we implement replacing of notifications it still makes sense to have some id field to identify the OSX notification we want to replace.
then keep it in ComPtr on the native side
That means I have to store a vector of all the ComPtr<IAvnNotificationEvents>
and release them when the notification closes? AFAIU you want the interface of IAvnNotificationManager
to be ShowNotification(AvnNotification* notification, IAvnNotificationCallbacks* callbacks)
, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need a vector?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user can show multiple notifications. I need to store their corresponding callbacks somewhere in order to call them from the global NotificationCenterDelegate
. I could plug them into the NSUserNotification.userInfo
somehow, I suppose. userInfo
expects a map of id
, though (which is essentially a pointer to objc_object
). So I guess I need to find some ugly workaround of putting a ComPtr
in there...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userInfo expects a map of id
it seems that OSX expects the contents of userInfo to be serializable. I'm afraid that might need to change the API design to accomodate OSX notifications properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What API design are you thinking of? For now, we could also keep it with the Dictionary
on C# and the function pointers. And revisit it in the future once we figured out a good/better way of designing this API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, we could also keep it with the Dictionary on C# and the function pointers.
That can and will lead to memory leaks.
- (NSString *)__bundleIdentifier; | ||
{ | ||
if (self == [NSBundle mainBundle]) { | ||
return @"com.avalonia.native.osx"; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@pr8x it seems that OSX notifications kinda "live forever" and one is expected to track them using a string With the current API we might want to disallow using arbitrary huge expiration times, so we could assume that notification will always expire in a reasonable time. |
@kekekeks What do you mean by "live forever"? They just stay in the notification center until the user manually removes them? I guess we can default to some reasonable expiration time when the user doens't specify it. But what is the problem with them just staying in the notfication center? |
Do they stay in the notification center after application is closed? |
ad46e01
to
7323816
Compare
@kekekeks Yes, the notifications indeed stay when the application exits. I should probably listen to some application shutdown event and call |
We should also call |
BTW, same happens on KDE. |
closing due to inactivity... @pr8x if you would like to resolve the conflicts we can resurrect this PR. |
What does the pull request do?
Implementing native notification support for Windows (10), Linux (DBUS/FreeDesktop) and OSX.
Discussion
In Windows it is required to register an application shortcut in the StartMenu in order to send toast notifications (See https://docs.microsoft.com/en-us/windows/win32/shell/enable-desktop-toast-with-appusermodelid). I am not exactlly sure how we want to handle this. I could add a
RegisterApplication
function in theWin32Platform
to do this registration and also create a uniqueAppUserModelID
based on theApplication.Name
. It's probably a good idea to uninstall the shortcut once the application exits.This PR provides a basic implementation that works across the three major desktop platforms. Advanced features (such as replacing notifications, custom buttons, images, etc) can be rolled in future updates.
What is the current behavior?
We only have managed notifications hosted inside avalonia windows.
What is the updated/expected behavior with this PR?
How was the solution implemented (if it's not obvious)?
ToastNotification
APIorg.freedesktop.Notifications
service interface. (Thanks to [WIP] Native notifications #2884)NSUserNotificationCenter
)Fixed issues
#2155