diff --git a/DesktopNotifications.Apple/AppleNotificationManager.cs b/DesktopNotifications.Apple/AppleNotificationManager.cs index 76c2253..c2dbaeb 100644 --- a/DesktopNotifications.Apple/AppleNotificationManager.cs +++ b/DesktopNotifications.Apple/AppleNotificationManager.cs @@ -14,10 +14,12 @@ namespace DesktopNotifications.Apple public ValueTask Initialize() { + return default; } public ValueTask ShowNotification(Notification notification, DateTimeOffset? expirationTime = null) { + return default; } } } diff --git a/DesktopNotifications.FreeDesktop/FreeDesktopApplicationContext.cs b/DesktopNotifications.FreeDesktop/FreeDesktopApplicationContext.cs new file mode 100644 index 0000000..baa868c --- /dev/null +++ b/DesktopNotifications.FreeDesktop/FreeDesktopApplicationContext.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.IO; + +namespace DesktopNotifications.FreeDesktop +{ + /// + /// + public class FreeDesktopApplicationContext : ApplicationContext + { + private FreeDesktopApplicationContext(string name, string? appIcon) : base(name) + { + AppIcon = appIcon; + } + + /// + /// + public string? AppIcon { get; } + + public static FreeDesktopApplicationContext FromCurrentProcess(string? appIcon = null) + { + var mainModule = Process.GetCurrentProcess().MainModule; + return new FreeDesktopApplicationContext( + Path.GetFileNameWithoutExtension(mainModule.FileName), + appIcon + ); + } + } +} \ No newline at end of file diff --git a/DesktopNotifications.FreeDesktop/FreeDesktopNotificationManager.cs b/DesktopNotifications.FreeDesktop/FreeDesktopNotificationManager.cs index 0792675..6f1b860 100644 --- a/DesktopNotifications.FreeDesktop/FreeDesktopNotificationManager.cs +++ b/DesktopNotifications.FreeDesktop/FreeDesktopNotificationManager.cs @@ -9,18 +9,24 @@ namespace DesktopNotifications.FreeDesktop { public class FreeDesktopNotificationManager : INotificationManager, IDisposable { + private readonly FreeDesktopApplicationContext _appContext; private const string NotificationsService = "org.freedesktop.Notifications"; private static readonly ObjectPath NotificationsPath = new ObjectPath("/org/freedesktop/Notifications"); + private readonly Dictionary _activeNotifications; private Connection? _connection; private IDisposable? _notificationActionSubscription; private IDisposable? _notificationCloseSubscription; - private Dictionary _activeNotifications; private IFreeDesktopNotificationsProxy? _proxy; - public FreeDesktopNotificationManager() + /// + /// + /// + /// + public FreeDesktopNotificationManager(FreeDesktopApplicationContext? appContext = null) { + _appContext = appContext ?? FreeDesktopApplicationContext.FromCurrentProcess(); _activeNotifications = new Dictionary(); } @@ -65,11 +71,11 @@ namespace DesktopNotifications.FreeDesktop var actions = GenerateActions(notification); var id = await _proxy.NotifyAsync( - "MyApp", + _appContext.Name, 0, - string.Empty, - notification.Title, - notification.Body, + _appContext.AppIcon ?? string.Empty, + notification.Title ?? throw new ArgumentException(), + notification.Body ?? throw new ArgumentException(), actions.ToArray(), new Dictionary {{"urgency", 1}}, duration?.Milliseconds ?? 0 @@ -110,7 +116,7 @@ namespace DesktopNotifications.FreeDesktop var dismissReason = GetReason(@event.reason); - NotificationDismissed?.Invoke(this, + NotificationDismissed?.Invoke(this, new NotificationDismissedEventArgs(notification, dismissReason)); } @@ -123,7 +129,7 @@ namespace DesktopNotifications.FreeDesktop { var notification = _activeNotifications[@event.id]; - NotificationActivated?.Invoke(this, + NotificationActivated?.Invoke(this, new NotificationActivatedEventArgs(notification, @event.actionKey)); } } diff --git a/Example/ShellLink.cs b/DesktopNotifications.Windows/ShellLink.cs similarity index 99% rename from Example/ShellLink.cs rename to DesktopNotifications.Windows/ShellLink.cs index 8ec1289..4cf6b7a 100644 --- a/Example/ShellLink.cs +++ b/DesktopNotifications.Windows/ShellLink.cs @@ -1,11 +1,10 @@ using System; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; -namespace Example.Win32 +namespace DesktopNotifications.Windows { // Modified from http://smdn.jp/programming/tips/createlnk/ // Originally from http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects diff --git a/DesktopNotifications.Windows/WindowsApplicationContext.cs b/DesktopNotifications.Windows/WindowsApplicationContext.cs new file mode 100644 index 0000000..01e37e6 --- /dev/null +++ b/DesktopNotifications.Windows/WindowsApplicationContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace DesktopNotifications.Windows +{ + public class WindowsApplicationContext : ApplicationContext + { + private WindowsApplicationContext(string name, string appUserModelId) : base(name) + { + AppUserModelId = appUserModelId; + } + + public string AppUserModelId { get; } + + [DllImport("shell32.dll", SetLastError = true)] + private static extern void SetCurrentProcessExplicitAppUserModelID( + [MarshalAs(UnmanagedType.LPWStr)] string appId); + + public static WindowsApplicationContext FromCurrentProcess(string? customName = null, + string? appUserModelId = null) + { + var aumid = appUserModelId ?? Guid.NewGuid().ToString(); + + SetCurrentProcessExplicitAppUserModelID(aumid); + + var mainModule = Process.GetCurrentProcess().MainModule; + + using var shortcut = new ShellLink + { + TargetPath = mainModule.FileName, + Arguments = string.Empty, + AppUserModelID = aumid + }; + + var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var startMenuPath = Path.Combine(appData, @"Microsoft\Windows\Start Menu\Programs"); + var appName = customName ?? Path.GetFileNameWithoutExtension(mainModule.FileName); + var shortcutFile = Path.Combine(startMenuPath, $"{appName}.lnk"); + + shortcut.Save(shortcutFile); + + return new WindowsApplicationContext(appName, aumid); + } + } +} \ No newline at end of file diff --git a/DesktopNotifications.Windows/WindowsNotificationManager.cs b/DesktopNotifications.Windows/WindowsNotificationManager.cs index 8d7d377..1f23cf2 100644 --- a/DesktopNotifications.Windows/WindowsNotificationManager.cs +++ b/DesktopNotifications.Windows/WindowsNotificationManager.cs @@ -10,20 +10,33 @@ namespace DesktopNotifications.Windows { public class WindowsNotificationManager : INotificationManager { + private readonly WindowsApplicationContext _applicationContext; private readonly Dictionary _notifications; private readonly ToastNotifier _toastNotifier; + private string? _launchAction; private TaskCompletionSource? _launchActionPromise; + private EventHandler? _notificationActivatedHandler; /// /// - /// - public WindowsNotificationManager(string appId) + /// + public WindowsNotificationManager(WindowsApplicationContext? applicationContext = null) { - _toastNotifier = ToastNotificationManager.CreateToastNotifier(appId); + _applicationContext = applicationContext ?? WindowsApplicationContext.FromCurrentProcess(); + _toastNotifier = ToastNotificationManager.CreateToastNotifier(_applicationContext.AppUserModelId); _notifications = new Dictionary(); } - public event EventHandler? NotificationActivated; + public event EventHandler? NotificationActivated + { + add + { + _notificationActivatedHandler += value; + ProcessLaunchAction(); + } + remove => _notificationActivatedHandler -= value; + } + public event EventHandler? NotificationDismissed; public async ValueTask Initialize() @@ -33,31 +46,14 @@ namespace DesktopNotifications.Windows _launchActionPromise = new TaskCompletionSource(); ToastNotificationManagerCompat.OnActivated += OnAppActivated; - var launchAction = await _launchActionPromise.Task; + _launchAction = await _launchActionPromise.Task; - Debug.Assert(launchAction != null); + Debug.Assert(_launchAction != null); - //TODO: Lookup notification object from history? - NotificationActivated?.Invoke(this, - new NotificationActivatedEventArgs(null, launchAction)); + ProcessLaunchAction(); } } - private static XmlDocument GenerateXml(Notification notification) - { - var builder = new ToastContentBuilder(); - - builder.AddText(notification.Title); - builder.AddText(notification.Body); - - foreach (var (title, actionId) in notification.Buttons) - { - builder.AddButton(title, ToastActivationType.Foreground, actionId); - } - - return builder.GetXml(); - } - public ValueTask ShowNotification(Notification notification, DateTimeOffset? expirationTime) { if (expirationTime < DateTimeOffset.Now) @@ -81,6 +77,39 @@ namespace DesktopNotifications.Windows return default; } + public void Dispose() + { + } + + private void ProcessLaunchAction() + { + if (_launchAction == null || _notificationActivatedHandler == null) + { + return; + } + + //TODO: Lookup notification object from history? + _notificationActivatedHandler.Invoke(this, + new NotificationActivatedEventArgs(null, _launchAction)); + + _launchAction = null; + } + + private static XmlDocument GenerateXml(Notification notification) + { + var builder = new ToastContentBuilder(); + + builder.AddText(notification.Title); + builder.AddText(notification.Body); + + foreach (var (title, actionId) in notification.Buttons) + { + builder.AddButton(title, ToastActivationType.Foreground, actionId); + } + + return builder.GetXml(); + } + private void OnAppActivated(ToastNotificationActivatedEventArgsCompat e) { Debug.Assert(_launchActionPromise != null); @@ -114,11 +143,7 @@ namespace DesktopNotifications.Windows var notification = _notifications[sender]; var actionId = string.IsNullOrEmpty(activationArgs.Arguments) ? "default" : activationArgs.Arguments; - NotificationActivated?.Invoke(this, new NotificationActivatedEventArgs(notification, actionId)); - } - - public void Dispose() - { + _notificationActivatedHandler?.Invoke(this, new NotificationActivatedEventArgs(notification, actionId)); } } } \ No newline at end of file diff --git a/DesktopNotifications/ApplicationContext.cs b/DesktopNotifications/ApplicationContext.cs new file mode 100644 index 0000000..6afc75e --- /dev/null +++ b/DesktopNotifications/ApplicationContext.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace DesktopNotifications +{ + /// + /// + /// + public class ApplicationContext + { + public ApplicationContext(string name) + { + Name = name; + } + + /// + /// + /// + public string Name { get; } + + } +} diff --git a/DesktopNotifications/Notification.cs b/DesktopNotifications/Notification.cs index ab41fdd..caef52e 100644 --- a/DesktopNotifications/Notification.cs +++ b/DesktopNotifications/Notification.cs @@ -11,9 +11,9 @@ namespace DesktopNotifications Buttons = new List<(string Title, string ActionId)>(); } - public string Title { get; set; } + public string? Title { get; set; } - public string Body { get; set; } + public string? Body { get; set; } public List<(string Title, string ActionId)> Buttons { get; } } diff --git a/Example/Program.cs b/Example/Program.cs index 89f9dbe..88f0bc9 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -1,12 +1,9 @@ using System; -using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using DesktopNotifications; using DesktopNotifications.FreeDesktop; using DesktopNotifications.Windows; -using Example.Win32; namespace Example { @@ -25,24 +22,7 @@ namespace Example if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - const string AppName = "DesktopNotificationsExample"; - - SetCurrentProcessExplicitAppUserModelID(AppName); - - using var shortcut = new ShellLink - { - TargetPath = Process.GetCurrentProcess().MainModule.FileName, - Arguments = string.Empty, - AppUserModelID = AppName - }; - - var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var startMenuPath = Path.Combine(appData, @"Microsoft\Windows\Start Menu\Programs"); - var shortcutFile = Path.Combine(startMenuPath, $"{AppName}.lnk"); - - shortcut.Save(shortcutFile); - - return new WindowsNotificationManager(AppName); + return new WindowsNotificationManager(); } throw new PlatformNotSupportedException(); @@ -51,12 +31,11 @@ namespace Example private static async Task Main(string[] args) { using var manager = CreateManager(); + await manager.Initialize(); manager.NotificationActivated += ManagerOnNotificationActivated; manager.NotificationDismissed += ManagerOnNotificationDismissed; - await manager.Initialize(); - var notification = new Notification { Title = "Hello World!",