aboutsummaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-08-26 15:55:22 -0400
committerfrosty <gabriel@bwaaa.monster>2026-08-26 15:55:22 -0400
commitc61e889f209fd07cbff94147df38609de13e3766 (patch)
tree0ebc5beb48bacdaab1362f3926a0a1f4081ff8b5 /main/src
parent2ba1fe874070fc372c1a522c5c5b97173fbe1459 (diff)
feat: add launcher badge
Diffstat (limited to 'main/src')
-rw-r--r--main/src/ui/application.vala11
-rw-r--r--main/src/ui/launcher_badge.vala118
-rw-r--r--main/src/view_model/preferences_dialog.vala1
-rw-r--r--main/src/windows/preferences_window/general_preferences_page.vala3
4 files changed, 133 insertions, 0 deletions
diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala
index 9ac512d9..b72a8d0e 100644
--- a/main/src/ui/application.vala
+++ b/main/src/ui/application.vala
@@ -14,6 +14,7 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
public MainWindow window;
public MainWindowController controller;
+ private LauncherBadge? launcher_badge;
private StatusNotifierItem? status_notifier_item;
public Database db { get; set; }
@@ -50,6 +51,12 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
warning("Could not create Dino's tray icon: %s", e.message);
}
+ try {
+ launcher_badge = new LauncherBadge(stream_interactor);
+ } catch (IOError e) {
+ warning("Could not create Dino's launcher badge: %s", e.message);
+ }
+
NotificationEvents notification_events = stream_interactor.get_module(NotificationEvents.IDENTITY);
get_notifications_dbus.begin((_, res) => {
// It might take a bit to get the interface. NotificationEvents will queue any notifications in the meantime.
@@ -76,6 +83,10 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application {
});
shutdown.connect(() => {
+ if (launcher_badge != null) {
+ launcher_badge.shutdown();
+ launcher_badge = null;
+ }
if (status_notifier_item != null) {
status_notifier_item.shutdown();
status_notifier_item = null;
diff --git a/main/src/ui/launcher_badge.vala b/main/src/ui/launcher_badge.vala
new file mode 100644
index 00000000..aa07fbaa
--- /dev/null
+++ b/main/src/ui/launcher_badge.vala
@@ -0,0 +1,118 @@
+using Gee;
+using GLib;
+
+using Dino.Entities;
+
+namespace Dino.Ui {
+
+[DBus (name = "com.canonical.Unity.LauncherEntry")]
+public class LauncherBadge : Object {
+ private const string APP_URI = "application://im.dino.Dino.desktop";
+ private const string BUS_NAME = "com.canonical.Unity";
+ private const string OBJECT_PATH = "/im/dino/Dino/LauncherEntry";
+
+ private StreamInteractor stream_interactor;
+ private DBusConnection connection;
+ private HashMap<Conversation, ulong> read_handler_ids = new HashMap<Conversation, ulong>(Conversation.hash_func, Conversation.equals_func);
+ private int64 unread_count = -1;
+ private uint object_registration_id;
+ private uint watcher_id;
+ private ulong conversation_activated_handler_id;
+ private ulong conversation_deactivated_handler_id;
+ private ulong new_item_handler_id;
+ private ulong setting_handler_id;
+
+ public signal void update(string app_uri, HashTable<string, Variant> properties);
+
+ public LauncherBadge(StreamInteractor stream_interactor) throws IOError {
+ this.stream_interactor = stream_interactor;
+ connection = Bus.get_sync(BusType.SESSION);
+ object_registration_id = connection.register_object(OBJECT_PATH, this);
+
+ ConversationManager conversation_manager = stream_interactor.get_module(ConversationManager.IDENTITY);
+ conversation_activated_handler_id = conversation_manager.conversation_activated.connect(add_conversation);
+ conversation_deactivated_handler_id = conversation_manager.conversation_deactivated.connect(remove_conversation);
+ new_item_handler_id = stream_interactor.get_module(ContentItemStore.IDENTITY).new_item.connect(() => update_count());
+ setting_handler_id = Dino.Application.get_default().settings.notify["notifications-badge"].connect(() => update_count(true));
+
+ foreach (Conversation conversation in conversation_manager.get_active_conversations()) {
+ connect_conversation(conversation);
+ }
+
+ watcher_id = Bus.watch_name_on_connection(
+ connection,
+ BUS_NAME,
+ BusNameWatcherFlags.NONE,
+ () => update_count(true));
+ update_count(true);
+ }
+
+ public void query(out string app_uri, out HashTable<string, Variant> properties) throws Error {
+ app_uri = APP_URI;
+ properties = get_properties();
+ }
+
+ internal void shutdown() {
+ unread_count = 0;
+ update(APP_URI, get_properties());
+
+ ConversationManager conversation_manager = stream_interactor.get_module(ConversationManager.IDENTITY);
+ conversation_manager.disconnect(conversation_activated_handler_id);
+ conversation_manager.disconnect(conversation_deactivated_handler_id);
+ stream_interactor.get_module(ContentItemStore.IDENTITY).disconnect(new_item_handler_id);
+ Dino.Application.get_default().settings.disconnect(setting_handler_id);
+
+ foreach (Conversation conversation in read_handler_ids.keys) {
+ conversation.disconnect(read_handler_ids[conversation]);
+ }
+ read_handler_ids.clear();
+
+ if (watcher_id != 0) {
+ Bus.unwatch_name(watcher_id);
+ watcher_id = 0;
+ }
+ if (object_registration_id != 0) {
+ connection.unregister_object(object_registration_id);
+ object_registration_id = 0;
+ }
+ }
+
+ private void add_conversation(Conversation conversation) {
+ connect_conversation(conversation);
+ update_count();
+ }
+
+ private void remove_conversation(Conversation conversation) {
+ if (read_handler_ids.has_key(conversation)) {
+ conversation.disconnect(read_handler_ids[conversation]);
+ read_handler_ids.unset(conversation);
+ }
+ update_count();
+ }
+
+ private void connect_conversation(Conversation conversation) {
+ if (read_handler_ids.has_key(conversation)) return;
+ read_handler_ids[conversation] = conversation.notify["read-up-to-item"].connect(() => update_count());
+ }
+
+ private void update_count(bool force = false) {
+ int64 count = 0;
+ ChatInteraction chat_interaction = stream_interactor.get_module(ChatInteraction.IDENTITY);
+ foreach (Conversation conversation in stream_interactor.get_module(ConversationManager.IDENTITY).get_active_conversations()) {
+ count += chat_interaction.get_num_unread(conversation);
+ }
+
+ if (!force && count == unread_count) return;
+ unread_count = count;
+ update(APP_URI, get_properties());
+ }
+
+ private HashTable<string, Variant> get_properties() {
+ HashTable<string, Variant> properties = new HashTable<string, Variant>(str_hash, str_equal);
+ properties["count"] = new Variant.int64(unread_count);
+ properties["count-visible"] = new Variant.boolean(Dino.Application.get_default().settings.notifications_badge && unread_count > 0);
+ return properties;
+ }
+}
+
+}
diff --git a/main/src/view_model/preferences_dialog.vala b/main/src/view_model/preferences_dialog.vala
index d7bad93a..09e22187 100644
--- a/main/src/view_model/preferences_dialog.vala
+++ b/main/src/view_model/preferences_dialog.vala
@@ -119,6 +119,7 @@ public class Dino.Ui.ViewModel.PreferencesDialog : Object {
settings.bind_property("notifications-group-chat-invitations", general_page, "notifications-group-chat-invitations", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
settings.bind_property("notifications-permission-requests", general_page, "notifications-permission-requests", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
settings.bind_property("notifications-connection-errors", general_page, "notifications-connection-errors", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+ settings.bind_property("notifications-badge", general_page, "notifications-badge", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
settings.bind_property("convert-utf8-smileys", general_page, "convert-emojis", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
}
diff --git a/main/src/windows/preferences_window/general_preferences_page.vala b/main/src/windows/preferences_window/general_preferences_page.vala
index 81906643..733de889 100644
--- a/main/src/windows/preferences_window/general_preferences_page.vala
+++ b/main/src/windows/preferences_window/general_preferences_page.vala
@@ -11,6 +11,7 @@ public class Dino.Ui.ViewModel.GeneralPreferencesPage : Object {
public bool notifications_group_chat_invitations { get; set; }
public bool notifications_permission_requests { get; set; }
public bool notifications_connection_errors { get; set; }
+ public bool notifications_badge { get; set; }
public bool convert_emojis { get; set; }
}
@@ -26,6 +27,7 @@ public class Dino.Ui.GeneralPreferencesPage : Adw.PreferencesPage {
[GtkChild] private unowned Adw.SwitchRow notification_group_chat_invitations_row;
[GtkChild] private unowned Adw.SwitchRow notification_permission_requests_row;
[GtkChild] private unowned Adw.SwitchRow notification_connection_errors_row;
+ [GtkChild] private unowned Adw.SwitchRow notification_badge_row;
[GtkChild] private unowned Adw.SwitchRow emoji_row;
public ViewModel.GeneralPreferencesPage model { get; set; default = new ViewModel.GeneralPreferencesPage(); }
@@ -51,6 +53,7 @@ public class Dino.Ui.GeneralPreferencesPage : Adw.PreferencesPage {
model.bind_property("notifications-group-chat-invitations", notification_group_chat_invitations_row, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL),
model.bind_property("notifications-permission-requests", notification_permission_requests_row, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL),
model.bind_property("notifications-connection-errors", notification_connection_errors_row, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL),
+ model.bind_property("notifications-badge", notification_badge_row, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL),
model.bind_property("convert-emojis", emoji_row, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL)
};
} else {