diff --git a/.vscode/settings.json b/.vscode/settings.json index 5326e94..edbf1db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "mesonbuild.configureOnOpen": true, "[meson]": { "editor.defaultFormatter": "mesonbuild.mesonbuild" }, diff --git a/data/app.desktop.in.in b/data/app.desktop.in.in index 033330e..5e4e444 100644 --- a/data/app.desktop.in.in +++ b/data/app.desktop.in.in @@ -1,6 +1,9 @@ [Desktop Entry] Type = Application Name = Skiff +Comment = Privacy-first end-to-end encrypted email Icon = @ICON_NAME@ Exec = @COMMAND@ Categories = GTK;GNOME;Office;Email;Calendar;ContactManagement +DBusActivatable=true +X-GNOME-UsesNotifications=true diff --git a/data/app.service.in b/data/app.service.in new file mode 100644 index 0000000..eca93a9 --- /dev/null +++ b/data/app.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=@APP_ID@ +Exec=@BINDIR@/@APP_ID@ --gapplication-service diff --git a/data/desktop.in.in b/data/desktop.in.in deleted file mode 100644 index bcea40a..0000000 --- a/data/desktop.in.in +++ /dev/null @@ -1,7 +0,0 @@ -[Desktop Entry] -Type = Application -Name = Skiff -Comment = Privacy-first end-to-end encrypted email -Icon = @ICON_NAME@ -Exec = @COMMAND@ -Categories = GTK;GNOME;Office;Email;Calendar;ContactManagement diff --git a/data/meson.build b/data/meson.build index 69192d2..d988270 100644 --- a/data/meson.build +++ b/data/meson.build @@ -85,6 +85,17 @@ if compile_schemas.found() ) endif +dbus_service = configure_file( + input: 'app.service.in', + output: meson.project_name() + '.service', + configuration: { + 'APP_ID': meson.project_name(), + 'BINDIR': join_paths(get_option('prefix'), get_option('bindir')), + }, + install: true, + install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services'), +) + # GResources allow you to bundle and reference assets within your application. # Resources are specified using a gresource file. # For more information about GResources, see: https://docs.gtk.org/gio/struct.Resource.html diff --git a/src/Application.vala b/src/Application.vala index aadd303..9004ff0 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -1,6 +1,10 @@ public class SkiffDesktop.Application : He.Application { private const GLib.ActionEntry APP_ENTRIES[] = { { "quit", quit }, + { "open-thread", action_open_thread, "s" }, + { "mark-as-read", action_mark_as_read, "s" }, + { "trash-thread", action_trash_thread, "s" }, + { "archive-thread", action_archive_thread, "s" }, }; public Application () { @@ -33,4 +37,31 @@ public class SkiffDesktop.Application : He.Application { new MainWindow (this); } + + // TODO: if someone wants to refactor this and throw the actions into enums, be my guest + + private void action_open_thread (SimpleAction action, Variant? parameter) { + var thread_id = parameter.get_string (null); + debug ("Got open-thread action with thread id: %s", thread_id); + this.active_window?.present (); + (this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "openThread"); + } + + private void action_mark_as_read (SimpleAction action, Variant? parameter) { + var thread_id = parameter.get_string (null); + debug ("Got mark-as-read action with thread id: %s", thread_id); + (this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "markAsRead"); + } + + private void action_trash_thread (SimpleAction action, Variant? parameter) { + var thread_id = parameter.get_string (null); + debug ("Got trash-thread action with thread id: %s", thread_id); + (this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "sendToTrash"); + } + + private void action_archive_thread (SimpleAction action, Variant? parameter) { + var thread_id = parameter.get_string (null); + debug ("Got archive-thread action with thread id: %s", thread_id); + (this.active_window as MainWindow)?.message_handler.send_notification_action (thread_id, "archive"); + } } diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 800ba7d..7c6552e 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1,6 +1,6 @@ [GtkTemplate (ui = "/com/fyralabs/SkiffDesktop/mainwindow.ui")] public class SkiffDesktop.MainWindow : He.ApplicationWindow { - private const GLib.ActionEntry APP_ENTRIES[] = { + private const GLib.ActionEntry WINDOW_ENTRIES[] = { { "about", action_about }, }; private const string BASE_URL = "https://app.skiff.com/"; @@ -8,22 +8,8 @@ public class SkiffDesktop.MainWindow : He.ApplicationWindow { [GtkChild] private unowned Gtk.Box main_box; private WebKit.WebView webview = new WebKit.WebView (); - private WebKit.UserScript script = new WebKit.UserScript ( - """ - window.IsSkiffWindowsDesktop = true - window.chrome = { - webview: { - postMessage: (v) => window.webkit.messageHandlers.skiffDesktop.postMessage(v), - addEventListener: (_, listener) => window._skiffListener = listener, - removeEventListener: (_, listener) => delete window._skiffListener, - } - } - """, - WebKit.UserContentInjectedFrames.TOP_FRAME, - WebKit.UserScriptInjectionTime.START, - null, - null - ); + + public MessageHandler message_handler; public MainWindow (He.Application application) { Object ( @@ -107,12 +93,7 @@ public class SkiffDesktop.MainWindow : He.ApplicationWindow { } construct { - var message_handler = new MessageHandler (); - - var content_manager = webview.get_user_content_manager (); - content_manager.add_script (script); - content_manager.script_message_received.connect (message_handler.on_script_message); - content_manager.register_script_message_handler ("skiffDesktop", null); + this.message_handler = new MessageHandler (webview); var network_session = webview.get_network_session (); var website_data_manager = network_session.get_website_data_manager (); @@ -141,6 +122,6 @@ public class SkiffDesktop.MainWindow : He.ApplicationWindow { webview.load_uri (BASE_URL); - add_action_entries (APP_ENTRIES, this); + add_action_entries (WINDOW_ENTRIES, this); } } diff --git a/src/MessageHandler.vala b/src/MessageHandler.vala index a357c80..74a35d1 100644 --- a/src/MessageHandler.vala +++ b/src/MessageHandler.vala @@ -1,17 +1,105 @@ public class SkiffDesktop.MessageHandler { - private class NotificationItem : Object { + private WebKit.WebView webview; + private WebKit.UserScript script = new WebKit.UserScript ( + """ + window.IsSkiffWindowsDesktop = true + window.chrome = { + webview: { + postMessage: (v) => window.webkit.messageHandlers.skiffDesktop.postMessage(v), + addEventListener: (_, listener) => window._skiffListener = listener, + removeEventListener: (_, listener) => delete window._skiffListener, + } + } + """, + WebKit.UserContentInjectedFrames.TOP_FRAME, + WebKit.UserScriptInjectionTime.START, + null, + null + ); + + private class NotificationItem : Object, Json.Serializable { public string title { get; set; } public string body { get; set; } - public string threadID { get; set; } - public string emailID { get; set; } + public string thread_id { get; set; } + public string email_id { get; set; } + + public override unowned ParamSpec? find_property (string name) { + switch (name) { + case "threadID": + name = "thread_id"; + break; + case "emailID": + name = "email_id"; + break; + } + + return this.get_class ().find_property (name); + } } - private class NotificationData : Object { - public NotificationItem[] notificationData { get; set; } + private class UnreadData : Object, Json.Serializable { + public int num_unread { get; set; } + + public override unowned ParamSpec? find_property (string name) { + switch (name) { + case "numUnread": + name = "num_unread"; + break; + } + + return this.get_class ().find_property (name); + } + } + + private void display_notification (NotificationItem notification_item) { + var notification = new Notification (notification_item.title); + notification.set_body (notification_item.body); + + notification.set_default_action_and_target ("app.open-thread", "s", notification_item.thread_id); + notification.add_button_with_target(_("Mark As Read"), "app.mark-as-read", "s", notification_item.thread_id); + // notification.add_button_with_target(_("Mark As Spam"), "app.mark-as-spam", "s", notification_item.thread_id); kinda useless IMO (plus, goes over GNOME's limit :/) + notification.add_button_with_target(_("Trash"), "app.trash-thread", "s", notification_item.thread_id); + notification.add_button_with_target(_("Archive"), "app.archive-thread", "s", notification_item.thread_id); + + GLib.Application.get_default ().send_notification ("meowy", notification); } - private class UnreadData : Object { - public int numUnread { get; set; } + public void send_notification_action (string thread_id, string action) { + var root = new Json.Builder () + .begin_object () + .set_member_name ("type") + .add_string_value ("notificationAction") + .set_member_name ("data") + .begin_object () + .set_member_name ("action") + .add_string_value (action) + .set_member_name ("threadId") + .add_string_value (thread_id) + .end_object () + .end_object () + .get_root (); + + var generator = new Json.Generator (); + generator.set_root (root); + + string? json_string = generator.to_data (null); + + var dict = new VariantDict (); + dict.insert ("message", "s", json_string); + var args = dict.end (); + + this.webview.call_async_javascript_function ( + """ + if ('_skiffListener' in window) { + window._skiffListener({ data: message }) + } + """, + -1, + args, + null, + null, + null + ); } public void on_script_message (JSC.Value val) { @@ -19,22 +107,42 @@ public class SkiffDesktop.MessageHandler { parser.load_from_data (val.to_string ()); var root = parser.get_root (); - var message_type = root.get_object ().get_string_member ("type"); + var root_object = root.get_object (); + var message_type = root_object.get_string_member ("type"); + var message_data = root_object.get_member ("data"); switch (message_type) { case "unreadMailCount": - var unread_data = Json.gobject_deserialize (typeof (UnreadData), root) as UnreadData; + var unread_data = Json.gobject_deserialize (typeof (UnreadData), message_data) as UnreadData; assert (unread_data != null); - print ("unread count: %d\n", unread_data.numUnread); + + debug ("Received unread count of %d", unread_data.num_unread); + // TODO: do something with this count break; case "newMessageNotifications": - var notification_data = Json.gobject_deserialize (typeof (NotificationData), root) as NotificationData; - assert (notification_data != null); - print ("notifications: %d\n", notification_data.notificationData.length); + var notification_data = message_data.get_object (); + var notification_array = notification_data.get_array_member ("notificationData"); + + debug ("Received %u new message notifications", notification_array.get_length ()); + + foreach (var element in notification_array.get_elements ()) { + var notification_item = Json.gobject_deserialize (typeof (NotificationItem), element) as NotificationItem; + assert (notification_item != null); + display_notification (notification_item); + } break; default: - print ("Received unknown message %s\n", val.to_string ()); + debug ("Received unknown message %s", val.to_string ()); break; } } + + public MessageHandler (WebKit.WebView webview) { + this.webview = webview; + + var content_manager = webview.get_user_content_manager (); + content_manager.add_script (this.script); + content_manager.script_message_received.connect (this.on_script_message); + content_manager.register_script_message_handler ("skiffDesktop", null); + } }