2020-11-25 23:12:21 +00:00
|
|
|
using Dino;
|
|
|
|
using Dino.Entities;
|
2021-02-27 17:42:07 +00:00
|
|
|
using winrt.Windows.UI.Notifications;
|
2020-11-25 23:12:21 +00:00
|
|
|
using Xmpp;
|
2020-11-26 21:50:03 +00:00
|
|
|
using Gee;
|
2020-11-25 23:12:21 +00:00
|
|
|
|
|
|
|
namespace Dino.Plugins.WindowsNotification {
|
|
|
|
public class WindowsNotificationProvider : NotificationProvider, Object {
|
|
|
|
|
2021-02-27 18:53:56 +00:00
|
|
|
private delegate void DelegateToUi();
|
|
|
|
|
2021-05-09 17:25:13 +00:00
|
|
|
private static uint32 notification_counter = 0;
|
2021-02-27 17:42:07 +00:00
|
|
|
private ToastNotifier notifier;
|
2020-11-25 23:12:21 +00:00
|
|
|
private StreamInteractor stream_interactor;
|
|
|
|
private Dino.Application app;
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-06-10 23:03:30 +00:00
|
|
|
private Gee.List<uint32> marked_for_removal;
|
2021-02-27 17:42:07 +00:00
|
|
|
|
|
|
|
// we must keep a reference to the notification itself or else their actions are disabled
|
|
|
|
private HashMap<uint, ToastNotification> notifications;
|
2021-05-09 17:25:13 +00:00
|
|
|
private Gee.List<uint32> content_notifications;
|
|
|
|
private HashMap<Conversation, Gee.List<uint32>> conversation_notifications;
|
|
|
|
private HashMap<Call, uint32> call_notifications;
|
2020-11-25 23:12:21 +00:00
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
public WindowsNotificationProvider(Dino.Application app, ToastNotifier notifier) {
|
|
|
|
this.notifier = notifier;
|
2020-11-25 23:12:21 +00:00
|
|
|
this.stream_interactor = app.stream_interactor;
|
|
|
|
this.app = app;
|
2021-06-10 23:03:30 +00:00
|
|
|
this.marked_for_removal = new Gee.ArrayList<uint32>();
|
|
|
|
this.content_notifications = new Gee.ArrayList<uint32>();
|
2021-05-09 17:25:13 +00:00
|
|
|
this.conversation_notifications = new HashMap<Conversation, Gee.List<uint32>>(Conversation.hash_func, Conversation.equals_func);
|
|
|
|
this.call_notifications = new HashMap<Call, uint32>(Call.hash_func, Call.equals_func);
|
2021-02-27 17:42:07 +00:00
|
|
|
this.notifications = new HashMap<uint, ToastNotification>();
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public double get_priority() {
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name) {
|
|
|
|
yield notify_content_item(conversation, conversation_display_name, participant_display_name, message.body);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name) {
|
|
|
|
string text = "";
|
|
|
|
if (file_transfer.direction == Message.DIRECTION_SENT) {
|
|
|
|
text = is_image ? _("Image sent") : _("File sent");
|
|
|
|
} else {
|
|
|
|
text = is_image ? _("Image received") : _("File received");
|
|
|
|
}
|
2021-03-23 10:34:56 +00:00
|
|
|
|
|
|
|
string? inlineImagePath = null;
|
|
|
|
if (file_transfer.state == FileTransfer.State.COMPLETE) {
|
|
|
|
inlineImagePath = file_transfer.get_file().get_path();
|
|
|
|
}
|
2020-11-25 23:12:21 +00:00
|
|
|
|
2021-03-23 10:34:56 +00:00
|
|
|
yield notify_content_item(conversation, conversation_display_name, participant_display_name, text, inlineImagePath);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_subscription_request(Conversation conversation) {
|
|
|
|
string summary = _("Subscription request");
|
|
|
|
string body = Markup.escape_text(conversation.counterpart.to_string());
|
|
|
|
|
2020-11-26 21:50:03 +00:00
|
|
|
var image_path = get_avatar(conversation);
|
2021-02-27 21:20:34 +00:00
|
|
|
var notification = yield new ToastNotificationBuilder()
|
2021-02-27 17:42:07 +00:00
|
|
|
.SetHeader(summary)
|
|
|
|
.SetBody(body)
|
2021-03-23 09:52:31 +00:00
|
|
|
.SetAppLogo(image_path)
|
2021-02-27 17:42:07 +00:00
|
|
|
.AddButton(_("Accept"), "accept-subscription")
|
|
|
|
.AddButton(_("Deny"), "deny-subscription")
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
notification.Activated((argument, user_input) => {
|
2021-02-27 18:53:56 +00:00
|
|
|
run_on_ui(() => {
|
|
|
|
if (argument != null) {
|
|
|
|
app.activate_action(argument, conversation.id);
|
|
|
|
} else {
|
|
|
|
app.activate_action("open-conversation", conversation.id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
marked_for_removal.add(notification_id);
|
|
|
|
});
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-02-27 18:53:56 +00:00
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
2020-11-26 21:50:03 +00:00
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
notifications[notification_id] = notification;
|
|
|
|
|
|
|
|
if (!conversation_notifications.has_key(conversation)) {
|
2021-05-09 17:25:13 +00:00
|
|
|
conversation_notifications[conversation] = new ArrayList<uint32>();
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
2021-02-27 17:42:07 +00:00
|
|
|
conversation_notifications[conversation].add(notification_id);
|
|
|
|
|
|
|
|
notifier.Show(notification);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_connection_error(Account account, ConnectionManager.ConnectionError error) {
|
|
|
|
string summary = _("Could not connect to %s").printf(account.bare_jid.domainpart);
|
|
|
|
string body = "";
|
|
|
|
switch (error.source) {
|
|
|
|
case ConnectionManager.ConnectionError.Source.SASL:
|
|
|
|
body = _("Wrong password");
|
|
|
|
break;
|
|
|
|
case ConnectionManager.ConnectionError.Source.TLS:
|
|
|
|
body = _("Invalid TLS certificate");
|
|
|
|
break;
|
|
|
|
case ConnectionManager.ConnectionError.Source.STREAM_ERROR:
|
|
|
|
body = "Stream Error";
|
|
|
|
break;
|
|
|
|
case ConnectionManager.ConnectionError.Source.CONNECTION:
|
|
|
|
body = "Connection";
|
|
|
|
break;
|
|
|
|
}
|
2021-02-27 17:42:07 +00:00
|
|
|
|
2021-02-27 21:20:34 +00:00
|
|
|
var notification = yield new ToastNotificationBuilder()
|
2021-02-27 17:42:07 +00:00
|
|
|
.SetHeader(summary)
|
|
|
|
.SetBody(body)
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
notification.Activated((argument, user_input) => marked_for_removal.add(notification_id));
|
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notifications[notification_id] = notification;
|
|
|
|
notifier.Show(notification);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name) {
|
|
|
|
Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT);
|
|
|
|
|
|
|
|
string display_room = room_jid.bare_jid.to_string();
|
|
|
|
string summary = _("Invitation to %s").printf(display_room);
|
|
|
|
string body = _("%s invited you to %s").printf(inviter_display_name, display_room);
|
|
|
|
|
2020-11-26 23:48:59 +00:00
|
|
|
var image_path = get_avatar(direct_conversation);
|
2021-02-27 21:20:34 +00:00
|
|
|
var notification = yield new ToastNotificationBuilder()
|
2021-02-27 17:42:07 +00:00
|
|
|
.SetHeader(summary)
|
|
|
|
.SetBody(body)
|
2021-03-23 09:52:31 +00:00
|
|
|
.SetAppLogo(image_path)
|
2021-02-27 17:42:07 +00:00
|
|
|
.AddButton(_("Accept"), "open-muc-join")
|
|
|
|
.AddButton(_("Deny"), "deny-invite")
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
var group_conversation_id = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT).id;
|
|
|
|
notification.Activated((argument, user_input) => {
|
2021-02-27 18:53:56 +00:00
|
|
|
run_on_ui(() => {
|
|
|
|
if (argument != null) {
|
|
|
|
app.activate_action(argument, group_conversation_id);
|
|
|
|
} else {
|
|
|
|
app.activate_action("open-muc-join", group_conversation_id);
|
|
|
|
}
|
|
|
|
});
|
2020-11-26 21:50:03 +00:00
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
marked_for_removal.add(notification_id);
|
|
|
|
});
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-02-27 18:53:56 +00:00
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
2021-02-27 17:42:07 +00:00
|
|
|
|
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notifications[notification_id] = notification;
|
|
|
|
notifier.Show(notification);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async void notify_voice_request(Conversation conversation, Jid from_jid) {
|
|
|
|
string display_name = Dino.get_participant_display_name(stream_interactor, conversation, from_jid);
|
|
|
|
string display_room = Dino.get_conversation_display_name(stream_interactor, conversation, _("%s from %s"));
|
|
|
|
string summary = _("Permission request");
|
|
|
|
string body = _("%s requests the permission to write in %s").printf(display_name, display_room);
|
|
|
|
|
2020-11-26 21:50:03 +00:00
|
|
|
var image_path = get_avatar(conversation);
|
2021-02-27 21:20:34 +00:00
|
|
|
var notification = yield new ToastNotificationBuilder()
|
2021-02-27 17:42:07 +00:00
|
|
|
.SetHeader(summary)
|
|
|
|
.SetBody(body)
|
2021-03-23 09:52:31 +00:00
|
|
|
.SetAppLogo(image_path)
|
2021-02-27 17:42:07 +00:00
|
|
|
.AddButton(_("Accept"), "accept-voice-request")
|
|
|
|
.AddButton(_("Deny"), "deny-voice-request")
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
notification.Activated((argument, user_input) => {
|
|
|
|
if (argument != null) {
|
2021-02-27 18:53:56 +00:00
|
|
|
run_on_ui(() => app.activate_action(argument, conversation.id));
|
2020-11-26 21:50:03 +00:00
|
|
|
}
|
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
marked_for_removal.add(notification_id);
|
|
|
|
});
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-02-27 18:53:56 +00:00
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
2021-02-27 17:42:07 +00:00
|
|
|
|
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notifications[notification_id] = notification;
|
|
|
|
notifier.Show(notification);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
2021-03-23 10:34:56 +00:00
|
|
|
private async void notify_content_item(Conversation conversation, string conversation_display_name, string? participant_display_name, string body_, string? inlineImagePath = null) {
|
2020-11-28 01:25:19 +00:00
|
|
|
clear_marked();
|
2020-11-26 23:48:59 +00:00
|
|
|
|
|
|
|
string body = body_;
|
|
|
|
if (participant_display_name != null) {
|
|
|
|
body = @"$participant_display_name: $body";
|
|
|
|
}
|
|
|
|
|
|
|
|
var image_path = get_avatar(conversation);
|
2021-03-23 23:19:04 +00:00
|
|
|
var notification = yield new ToastNotificationBuilder()
|
2021-02-27 17:42:07 +00:00
|
|
|
.SetHeader(conversation_display_name)
|
|
|
|
.SetBody(body)
|
2021-03-23 23:19:04 +00:00
|
|
|
.SetAppLogo(image_path)
|
|
|
|
.SetInlineImage(inlineImagePath)
|
|
|
|
.Build();
|
2021-02-27 17:42:07 +00:00
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
notification.Activated((argument, user_input) => {
|
2021-02-27 18:53:56 +00:00
|
|
|
run_on_ui(() => app.activate_action("open-conversation", conversation.id));
|
2021-02-27 17:42:07 +00:00
|
|
|
marked_for_removal.add(notification_id);
|
|
|
|
});
|
|
|
|
|
2021-02-27 19:30:36 +00:00
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
2021-02-27 17:42:07 +00:00
|
|
|
|
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notifications[notification_id] = notification;
|
|
|
|
notifier.Show(notification);
|
2021-03-15 11:38:03 +00:00
|
|
|
|
|
|
|
content_notifications.add(notification_id);
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private string? get_avatar(Conversation conversation) {
|
|
|
|
var avatar_manager = app.stream_interactor.get_module(AvatarManager.IDENTITY);
|
|
|
|
return avatar_manager.get_avatar_filepath(conversation.account, conversation.counterpart);
|
|
|
|
}
|
2020-11-26 23:48:59 +00:00
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
public async void retract_content_item_notifications() {
|
|
|
|
foreach (uint id in content_notifications) {
|
|
|
|
remove_notification(id);
|
|
|
|
}
|
|
|
|
content_notifications.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void retract_conversation_notifications(Conversation conversation) {
|
|
|
|
if (conversation_notifications.has_key(conversation)) {
|
|
|
|
var conversation_items = conversation_notifications[conversation];
|
|
|
|
foreach (uint id in conversation_items) {
|
|
|
|
remove_notification(id);
|
|
|
|
}
|
|
|
|
conversation_items.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 23:32:22 +00:00
|
|
|
public async void notify_call(Call call, Conversation conversation, bool video, bool multiparty, string conversation_display_name) {
|
2021-05-09 17:25:13 +00:00
|
|
|
string summary = Markup.escape_text(conversation_display_name);
|
|
|
|
string body = video ? _("Incoming video call") : _("Incoming call");
|
2022-02-08 23:32:22 +00:00
|
|
|
if (multiparty) {
|
|
|
|
body = video ? _("Incoming video group call") : _("Incoming group call");
|
|
|
|
}
|
2021-05-09 17:25:13 +00:00
|
|
|
|
|
|
|
var image_path = get_avatar(conversation);
|
|
|
|
var notification = yield new ToastNotificationBuilder()
|
|
|
|
.SetHeader(summary)
|
|
|
|
.SetBody(body)
|
|
|
|
.SetAppLogo(image_path)
|
|
|
|
.AddButton(_("Accept"), "accept-call")
|
2022-02-10 00:01:26 +00:00
|
|
|
.AddButton(_("Deny"), "reject-call", null, ActivationType.Background)
|
2021-05-09 17:25:13 +00:00
|
|
|
.SetScenario(Scenario.IncomingCall)
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
var notification_id = generate_id();
|
|
|
|
notification.Activated((argument, user_input) => {
|
|
|
|
run_on_ui(() => {
|
|
|
|
if (argument != null) {
|
2022-02-10 00:01:26 +00:00
|
|
|
app.activate_action(argument, new Variant.tuple(new Variant[] { new Variant.int32(conversation.id), new Variant.int32(call.id) }));
|
2021-05-09 17:25:13 +00:00
|
|
|
} else {
|
|
|
|
app.activate_action("open-conversation", conversation.id);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
marked_for_removal.add(notification_id);
|
|
|
|
});
|
|
|
|
|
|
|
|
notification.Dismissed((reason) => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notification.Failed(() => marked_for_removal.add(notification_id));
|
|
|
|
|
|
|
|
notifications[notification_id] = notification;
|
|
|
|
|
|
|
|
call_notifications[call] = notification_id;
|
|
|
|
|
|
|
|
notifier.Show(notification);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async void retract_call_notification(Call call, Conversation conversation) {
|
|
|
|
if (call_notifications.has_key(call)) {
|
|
|
|
var notification_id = call_notifications[call];
|
|
|
|
remove_notification(notification_id);
|
|
|
|
call_notifications.unset(call);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-28 01:25:19 +00:00
|
|
|
private void clear_marked() {
|
|
|
|
foreach (var id in marked_for_removal) {
|
2021-02-27 17:42:07 +00:00
|
|
|
remove_notification(id);
|
2020-11-26 23:48:59 +00:00
|
|
|
}
|
2020-11-28 01:25:19 +00:00
|
|
|
marked_for_removal.clear();
|
2020-11-26 23:48:59 +00:00
|
|
|
}
|
|
|
|
|
2021-02-27 17:42:07 +00:00
|
|
|
private void remove_notification(uint id) {
|
|
|
|
ToastNotification notification = null;
|
|
|
|
notifications.unset(id, out notification);
|
|
|
|
if (notification != null) {
|
|
|
|
notifier.Hide(notification);
|
2020-11-26 23:48:59 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-27 17:42:07 +00:00
|
|
|
|
2021-05-09 17:25:13 +00:00
|
|
|
private uint32 generate_id() {
|
2021-02-27 17:42:07 +00:00
|
|
|
return AtomicUint.add(ref notification_counter, 1);
|
|
|
|
}
|
2021-02-27 18:53:56 +00:00
|
|
|
|
2021-05-09 17:25:46 +00:00
|
|
|
private void run_on_ui(owned DelegateToUi func) {
|
2021-02-27 18:53:56 +00:00
|
|
|
Idle.add(() => { func(); return false; }, GLib.Priority.HIGH);
|
|
|
|
}
|
2020-11-25 23:12:21 +00:00
|
|
|
}
|
|
|
|
}
|