This repository was archived by the owner on Jan 12, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathFreeDesktopNotificationManager.cs
More file actions
168 lines (138 loc) · 5.97 KB
/
FreeDesktopNotificationManager.cs
File metadata and controls
168 lines (138 loc) · 5.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Tmds.DBus;
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<uint, Notification> _activeNotifications;
private Connection? _connection;
private IDisposable? _notificationActionSubscription;
private IDisposable? _notificationCloseSubscription;
private IFreeDesktopNotificationsProxy? _proxy;
/// <summary>
///
/// </summary>
/// <param name="appContext"></param>
public FreeDesktopNotificationManager(FreeDesktopApplicationContext? appContext = null)
{
_appContext = appContext ?? FreeDesktopApplicationContext.FromCurrentProcess();
_activeNotifications = new Dictionary<uint, Notification>();
}
public void Dispose()
{
_notificationActionSubscription?.Dispose();
_notificationCloseSubscription?.Dispose();
}
public event EventHandler<NotificationActivatedEventArgs>? NotificationActivated;
public event EventHandler<NotificationDismissedEventArgs>? NotificationDismissed;
public string? LaunchActionId { get; }
public async Task Initialize()
{
_connection = Connection.Session;
await _connection.ConnectAsync();
_proxy = _connection.CreateProxy<IFreeDesktopNotificationsProxy>(
NotificationsService,
NotificationsPath
);
_notificationActionSubscription = await _proxy.WatchActionInvokedAsync(
OnNotificationActionInvoked,
OnNotificationActionInvokedError
);
_notificationCloseSubscription = await _proxy.WatchNotificationClosedAsync(
OnNotificationClosed,
OnNotificationClosedError
);
}
public async Task ShowNotification(Notification notification, DateTimeOffset? expirationTime = null)
{
if (_connection == null || _proxy == null)
{
throw new InvalidOperationException("Not connected. Call Initialize() first.");
}
if (expirationTime < DateTimeOffset.Now)
{
throw new ArgumentException(nameof(expirationTime));
}
var duration = expirationTime - DateTimeOffset.Now;
var actions = GenerateActions(notification);
var id = await _proxy.NotifyAsync(
_appContext.Name,
0,
_appContext.AppIcon ?? string.Empty,
notification.Title ?? throw new ArgumentException(),
notification.Body ?? throw new ArgumentException(),
actions.ToArray(),
new Dictionary<string, object> {{"urgency", 1}},
duration?.Milliseconds ?? 0
).ConfigureAwait(false);
_activeNotifications[id] = notification;
}
public async Task ScheduleNotification(
Notification notification,
DateTimeOffset deliveryTime,
DateTimeOffset? expirationTime = null)
{
if (deliveryTime < DateTimeOffset.Now || deliveryTime > expirationTime)
{
throw new ArgumentException(nameof(deliveryTime));
}
//Note: We could consider spawning some daemon that sends the notification at the specified time.
//For now we only allow to schedule notifications while the application is running.
await Task.Delay(deliveryTime - DateTimeOffset.Now);
await ShowNotification(notification, expirationTime);
}
private static IEnumerable<string> 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)
{
if (!_activeNotifications.TryGetValue(@event.id, out var notification)) return;
_activeNotifications.Remove(@event.id);
//TODO: Not sure why but it calls this event twice sometimes
//In this case the notification has already been removed from the dict.
if (notification == null)
{
return;
}
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)
{
if (!_activeNotifications.TryGetValue(@event.id, out var notification) || notification is null) return;
NotificationActivated?.Invoke(this,
new NotificationActivatedEventArgs(notification, @event.actionKey));
_activeNotifications.Remove(@event.id);
}
}
}