From 11d9855a3994bc24ff67f5c2c4933c1d9559f6c5 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 20 Nov 2020 15:21:34 +0100 Subject: Refactor Notifications, add freedesktop backend fixes #707 --- libdino/CMakeLists.txt | 2 + libdino/src/dbus/notifications.vala | 29 ++++++++ libdino/src/service/notification_events.vala | 100 ++++++++++++++++++++------- libdino/src/util/display_name.vala | 97 ++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 26 deletions(-) create mode 100644 libdino/src/dbus/notifications.vala create mode 100644 libdino/src/util/display_name.vala (limited to 'libdino') diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt index 9c2145e3..b3293a68 100644 --- a/libdino/CMakeLists.txt +++ b/libdino/CMakeLists.txt @@ -11,6 +11,7 @@ SOURCES src/application.vala src/dbus/login1.vala + src/dbus/notifications.vala src/dbus/upower.vala src/entity/account.vala @@ -49,6 +50,7 @@ SOURCES src/service/stream_interactor.vala src/service/util.vala + src/util/display_name.vala src/util/util.vala src/util/weak_map.vala CUSTOM_VAPIS diff --git a/libdino/src/dbus/notifications.vala b/libdino/src/dbus/notifications.vala new file mode 100644 index 00000000..68401440 --- /dev/null +++ b/libdino/src/dbus/notifications.vala @@ -0,0 +1,29 @@ +namespace Dino { + + [DBus (name = "org.freedesktop.Notifications")] + public interface DBusNotifications : GLib.Object { + + public signal void action_invoked(uint32 key, string action_key); + + public signal void notification_closed (uint32 id, uint32 reason); + + public abstract uint32 notify(string app_name, uint32 replaces_id, string app_icon, string summary, + string body, string[] actions, HashTable hints, int32 expire_timeout) throws DBusError, IOError; + + public abstract void get_capabilities(out string[] capabilities) throws Error; + + public abstract void close_notification(uint id) throws DBusError, IOError; + + public abstract void get_server_information(out string name, out string vendor, out string version, out string spec_version) throws DBusError, IOError; + } + + public static DBusNotifications? get_notifications_dbus() { + DBusNotifications? upower = null; + try { + upower = Bus.get_proxy_sync(BusType.SESSION, "org.freedesktop.Notifications", "/org/freedesktop/Notifications"); + } catch (IOError e) { + warning("Couldn't get org.freedesktop.Notifications DBus instance: %s\n", e.message); + } + return upower; + } +} \ No newline at end of file diff --git a/libdino/src/service/notification_events.vala b/libdino/src/service/notification_events.vala index f6ef7019..d1e55113 100644 --- a/libdino/src/service/notification_events.vala +++ b/libdino/src/service/notification_events.vala @@ -16,6 +16,7 @@ public class NotificationEvents : StreamInteractionModule, Object { public signal void notify_voice_request(Account account, Jid room_jid, Jid from_jid, string nick); private StreamInteractor stream_interactor; + private NotificationProvider? notifier; public static void start(StreamInteractor stream_interactor) { NotificationEvents m = new NotificationEvents(stream_interactor); @@ -27,55 +28,102 @@ public class NotificationEvents : StreamInteractionModule, Object { stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(on_content_item_received); stream_interactor.get_module(PresenceManager.IDENTITY).received_subscription_request.connect(on_received_subscription_request); - stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect((account, room_jid, from_jid, password, reason) => notify_muc_invite(account, room_jid, from_jid, password, reason)); - stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick) => notify_voice_request(account, room_jid, from_jid, nick)); - stream_interactor.connection_manager.connection_error.connect((account, error) => notify_connection_error(account, error)); + stream_interactor.get_module(MucManager.IDENTITY).invite_received.connect(on_invite_received); + stream_interactor.get_module(MucManager.IDENTITY).voice_request_received.connect((account, room_jid, from_jid, nick) => { + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(room_jid, account, Conversation.Type.GROUPCHAT); + if (conversation == null) return; + notifier.notify_voice_request.begin(conversation, from_jid); + }); + stream_interactor.connection_manager.connection_error.connect((account, error) => notifier.notify_connection_error.begin(account, error)); + stream_interactor.get_module(ChatInteraction.IDENTITY).focused_in.connect((conversation) => { + notifier.retract_content_item_notifications.begin(); + notifier.retract_conversation_notifications.begin(conversation); + }); + } + + public void register_notification_provider(NotificationProvider notification_provider) { + if (notifier == null || notifier.get_priority() < notification_provider.get_priority()) { + notifier = notification_provider; + } } private void on_content_item_received(ContentItem item, Conversation conversation) { ContentItem last_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); - if (item.id != last_item.id && last_item.id != conversation.read_up_to_item) return; - - if (!should_notify(item, conversation)) return; + if (item.id != last_item.id) return; + if (item.id == conversation.read_up_to_item) return; if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus()) return; - notify_content_item(item, conversation); - } - private bool should_notify(ContentItem content_item, Conversation conversation) { Conversation.NotifySetting notify = conversation.get_notification_setting(stream_interactor); + if (notify == Conversation.NotifySetting.OFF) return; - if (notify == Conversation.NotifySetting.OFF) return false; + string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null); + string? participant_display_name = null; + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + participant_display_name = get_participant_display_name(stream_interactor, conversation, item.jid); + } - switch (content_item.type_) { + switch (item.type_) { case MessageItem.TYPE: - Message message = ((MessageItem) content_item).message; - if (message.direction == Message.DIRECTION_SENT) return false; + Message message = ((MessageItem) item).message; + + if (message.direction == Message.DIRECTION_SENT) return; + + if (notify == Conversation.NotifySetting.HIGHLIGHT) { + Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); + if (nick == null) return; + + bool highlight = Regex.match_simple("\\b" + Regex.escape_string(nick.resourcepart) + "\\b", message.body, RegexCompileFlags.CASELESS); + if (!highlight) return; + } + + notifier.notify_message.begin(message, conversation, conversation_display_name, participant_display_name); break; case FileItem.TYPE: - FileTransfer file_transfer = ((FileItem) content_item).file_transfer; - // Don't notify on file transfers in a groupchat set to "mention only" - if (notify == Conversation.NotifySetting.HIGHLIGHT) return false; - if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return false; - break; - } + FileTransfer file_transfer = ((FileItem) item).file_transfer; + bool is_image = file_transfer.mime_type != null && file_transfer.mime_type.has_prefix("image"); - if (content_item.type_ == MessageItem.TYPE && notify == Conversation.NotifySetting.HIGHLIGHT) { - Jid? nick = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account); - if (nick == null) return false; + // Don't notify on file transfers in a groupchat set to "mention only" + if (notify == Conversation.NotifySetting.HIGHLIGHT) return; + if (file_transfer.direction == FileTransfer.DIRECTION_SENT) return; - Entities.Message message = ((MessageItem) content_item).message; - return Regex.match_simple("\\b" + Regex.escape_string(nick.resourcepart) + "\\b", message.body, RegexCompileFlags.CASELESS); + notifier.notify_file.begin(file_transfer, conversation, is_image, conversation_display_name, participant_display_name); + break; } - return true; } private void on_received_subscription_request(Jid jid, Account account) { Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(jid, account, Conversation.Type.CHAT); if (stream_interactor.get_module(ChatInteraction.IDENTITY).is_active_focus(conversation)) return; - notify_subscription_request(conversation); + notifier.notify_subscription_request.begin(conversation); } + + private void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { + string inviter_display_name; + if (room_jid.equals_bare(from_jid)) { + Conversation conversation = new Conversation(room_jid, account, Conversation.Type.GROUPCHAT); + inviter_display_name = get_participant_display_name(stream_interactor, conversation, from_jid); + } else { + Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); + inviter_display_name = get_participant_display_name(stream_interactor, direct_conversation, from_jid); + } + notifier.notify_muc_invite.begin(account, room_jid, from_jid, inviter_display_name); + } +} + +public interface NotificationProvider : Object { + public abstract double get_priority(); + + public abstract async void notify_message(Message message, Conversation conversation, string conversation_display_name, string? participant_display_name); + public abstract async void notify_file(FileTransfer file_transfer, Conversation conversation, bool is_image, string conversation_display_name, string? participant_display_name); + public abstract async void notify_subscription_request(Conversation conversation); + public abstract async void notify_connection_error(Account account, ConnectionManager.ConnectionError error); + public abstract async void notify_muc_invite(Account account, Jid room_jid, Jid from_jid, string inviter_display_name); + public abstract async void notify_voice_request(Conversation conversation, Jid from_jid); + + public abstract async void retract_content_item_notifications(); + public abstract async void retract_conversation_notifications(Conversation conversation); } } diff --git a/libdino/src/util/display_name.vala b/libdino/src/util/display_name.vala new file mode 100644 index 00000000..9296fbf3 --- /dev/null +++ b/libdino/src/util/display_name.vala @@ -0,0 +1,97 @@ +using Gee; + +using Dino.Entities; +using Xmpp; + +namespace Dino { + public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation, string? muc_pm_format) { + if (conversation.type_ == Conversation.Type.CHAT) { + string? display_name = get_real_display_name(stream_interactor, conversation.account, conversation.counterpart); + if (display_name != null) return display_name; + return conversation.counterpart.to_string(); + } + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + return get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart); + } + if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + return (muc_pm_format ?? "%s / %s").printf(get_occupant_display_name(stream_interactor, conversation, conversation.counterpart), get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart.bare_jid)); + } + return conversation.counterpart.to_string(); + } + + public static string get_participant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid participant, string? self_word = null) { + if (self_word != null) { + if (conversation.account.bare_jid.equals_bare(participant) || + (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && + conversation.nickname != null && participant.equals_bare(conversation.counterpart) && conversation.nickname == participant.resourcepart) { + return self_word; + } + } + if (conversation.type_ == Conversation.Type.CHAT) { + return get_real_display_name(stream_interactor, conversation.account, participant, self_word) ?? participant.bare_jid.to_string(); + } + if ((conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.counterpart.equals_bare(participant)) { + return get_occupant_display_name(stream_interactor, conversation, participant); + } + return participant.bare_jid.to_string(); + } + + private static string? get_real_display_name(StreamInteractor stream_interactor, Account account, Jid jid, string? self_word = null) { + if (jid.equals_bare(account.bare_jid)) { + if (self_word != null || account.alias == null || account.alias.length == 0) { + return self_word; + } + return account.alias; + } + Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); + if (roster_item != null && roster_item.name != null && roster_item.name != "") { + return roster_item.name; + } + return null; + } + + private static string get_groupchat_display_name(StreamInteractor stream_interactor, Account account, Jid jid) { + MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); + string? room_name = muc_manager.get_room_name(account, jid); + if (room_name != null && room_name != jid.localpart) { + return room_name; + } + if (muc_manager.is_private_room(account, jid)) { + Gee.List? other_occupants = muc_manager.get_other_offline_members(jid, account); + if (other_occupants != null && other_occupants.size > 0) { + var builder = new StringBuilder (); + foreach(Jid occupant in other_occupants) { + if (builder.len != 0) { + builder.append(", "); + } + builder.append((get_real_display_name(stream_interactor, account, occupant) ?? occupant.localpart ?? occupant.domainpart).split(" ")[0]); + } + return builder.str; + } + } + return jid.to_string(); + } + + private static string get_occupant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid jid, string? self_word = null, bool muc_real_name = false) { + if (muc_real_name) { + MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); + if (muc_manager.is_private_room(conversation.account, jid.bare_jid)) { + Jid? real_jid = muc_manager.get_real_jid(jid, conversation.account); + if (real_jid != null) { + string? display_name = get_real_display_name(stream_interactor, conversation.account, real_jid, self_word); + if (display_name != null) return display_name; + } + } + } + + // If it's us (jid=our real full JID), display our nick + if (conversation.type_ == Conversation.Type.GROUPCHAT_PM && conversation.account.bare_jid.equals_bare(jid)) { + var muc_conv = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(conversation.counterpart.bare_jid, conversation.account, Conversation.Type.GROUPCHAT); + if (muc_conv != null && muc_conv.nickname != null) { + return muc_conv.nickname; + } + } + + return jid.resourcepart ?? jid.to_string(); + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf