using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Tmds.DBus; namespace DesktopNotifications.FreeDesktop { public class FreeDesktopNotificationManager : INotificationManager, IDisposable { private const string NotificationsService = "org.freedesktop.Notifications"; private static readonly ObjectPath NotificationsPath = new ObjectPath("/org/freedesktop/Notifications"); private Connection? _connection; private IDisposable? _notificationActionSubscription; private IDisposable? _notificationCloseSubscription; private Dictionary _activeNotifications; private IFreeDesktopNotificationsProxy? _proxy; public FreeDesktopNotificationManager() { _activeNotifications = new Dictionary(); } public void Dispose() { _notificationActionSubscription?.Dispose(); _notificationCloseSubscription?.Dispose(); } public event EventHandler? NotificationActivated; public event EventHandler? NotificationDismissed; public async ValueTask Initialize() { _connection = Connection.Session; await _connection.ConnectAsync(); _proxy = _connection.CreateProxy( NotificationsService, NotificationsPath ); _notificationActionSubscription = await _proxy.WatchActionInvokedAsync( OnNotificationActionInvoked, OnNotificationActionInvokedError ); _notificationCloseSubscription = await _proxy.WatchNotificationClosedAsync( OnNotificationClosed, OnNotificationClosedError ); } public async ValueTask ShowNotification(Notification notification, DateTimeOffset? expirationTime = null) { if (_connection == null || _proxy == null) { throw new InvalidOperationException("Not connected. Call Initialize() first."); } var duration = expirationTime - DateTimeOffset.Now; var actions = GenerateActions(notification); var id = await _proxy.NotifyAsync( "MyApp", 0, string.Empty, notification.Title, notification.Body, actions.ToArray(), new Dictionary {{"urgency", 1}}, duration?.Milliseconds ?? 0 ).ConfigureAwait(false); _activeNotifications[id] = notification; } private static IEnumerable GenerateActions(Notification notification) { foreach (var (title, actionId) in notification.Buttons) { yield return actionId; yield return title; } } private void OnNotificationClosedError(Exception obj) { throw obj; } private static NotificationDismissReason GetReason(uint reason) { return reason switch { 1 => NotificationDismissReason.Expired, 2 => NotificationDismissReason.User, 3 => NotificationDismissReason.Application, _ => throw new ArgumentOutOfRangeException() }; } private void OnNotificationClosed((uint id, uint reason) @event) { _activeNotifications.Remove(@event.id, out var notification); Debug.Assert(notification != null); var dismissReason = GetReason(@event.reason); NotificationDismissed?.Invoke(this, new NotificationDismissedEventArgs(notification, dismissReason)); } private void OnNotificationActionInvokedError(Exception obj) { throw obj; } private void OnNotificationActionInvoked((uint id, string actionKey) @event) { var notification = _activeNotifications[@event.id]; NotificationActivated?.Invoke(this, new NotificationActivatedEventArgs(notification, @event.actionKey)); } } }