From a56014ce38f1e7be9f3a42c53c313bfe9719bb9d Mon Sep 17 00:00:00 2001 From: Nate Lillich Date: Tue, 24 Apr 2012 18:28:45 -0700 Subject: [PATCH] Closes #4633. Geary will now handle mailto links and can be set up as the default email client. --- CMakeLists.txt | 16 +++++++ geary.desktop | 13 +++++ icons/CMakeLists.txt | 1 + src/client/geary-application.vala | 23 ++++++--- src/client/geary-controller.vala | 9 ++-- src/common/common-yorba-application.vala | 11 +++-- src/engine/api/geary-composed-email.vala | 60 +++++++++++++++++++++++- vapi/unique-3.0.vapi | 5 +- 8 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 geary.desktop diff --git a/CMakeLists.txt b/CMakeLists.txt index 8246dee7..c04cd4c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,20 @@ add_subdirectory(icons) add_subdirectory(sql) add_subdirectory(ui) +# +# Install geary.desktop +# +install( + FILES + geary.desktop + DESTINATION + share/applications +) +install( + CODE + "execute_process (COMMAND update-desktop-database)" +) + # # Uninstall target # @@ -38,5 +52,7 @@ add_custom_target( ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake COMMAND ${glib_schema_compiler} ${GSETTINGS_DIR} + COMMAND + update-desktop-database ) diff --git a/geary.desktop b/geary.desktop new file mode 100644 index 00000000..cae00af8 --- /dev/null +++ b/geary.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=geary +Name=Geary +GenericName=Email Client +Comment=Send and receive email +TryExec=geary +Exec=geary %U +Type=Application +Terminal=false +Categories=GNOME;GTK;Network;Office;Email +MimeType=x-scheme-handler/mailto + diff --git a/icons/CMakeLists.txt b/icons/CMakeLists.txt index e5b0162b..7ba6e5cb 100644 --- a/icons/CMakeLists.txt +++ b/icons/CMakeLists.txt @@ -2,6 +2,7 @@ set(ICONS_DEST share/geary/icons) install(FILES archive-icon.png DESTINATION ${ICONS_DEST}) install(FILES geary.png DESTINATION ${ICONS_DEST}) +install(FILES geary.png DESTINATION share/icons/hicolor/scalable/apps) install(FILES multiple-tags.png DESTINATION ${ICONS_DEST}) install(FILES non-starred-grey.png DESTINATION ${ICONS_DEST}) install(FILES one-tag.png DESTINATION ${ICONS_DEST}) diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index ac0f9edb..315ae46b 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -102,7 +102,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc., // i18n: Command line arguments are invalid GLib.error (_("Failed to parse command line: %s"), error.message); } - + if (version) { stdout.printf("%s %s\n\n%s\n\n%s\n\t%s\n", PRGNAME, VERSION, COPYRIGHT, @@ -122,6 +122,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc., } else { Log.set_handler(null, LogLevelFlags.LEVEL_DEBUG, log_ignore); } + return 0; } @@ -134,24 +135,24 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return result; } - public override void activate() { + public override void activate(string[] args) { // If Geary is already running, show the main window and return. if (controller != null && controller.main_window != null) { controller.main_window.present(); + handle_args(args); return; } - + // Start Geary. Geary.Engine.init(get_user_data_directory(), get_resource_directory()); config = new Configuration(); - controller = new GearyController(); - login(); - + handle_args(args); + return; } - + private void login(bool query_keyring = true) { // Get saved credentials. If not present, ask user. string username = get_username(); @@ -324,5 +325,13 @@ along with Geary; if not, write to the Free Software Foundation, Inc., public Gtk.Window get_main_window() { return controller.main_window; } + + private void handle_args(string[] args) { + foreach(string arg in args) { + if (arg.has_prefix("mailto:")) { + controller.compose_mailto(arg); + } + } + } } diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala index 11b5fee1..9efe33c1 100644 --- a/src/client/geary-controller.vala +++ b/src/client/geary-controller.vala @@ -917,10 +917,7 @@ public class GearyController { public void on_link_selected(string link) { const string MAILTO = "mailto:"; if (link.down().has_prefix(MAILTO)) { - // TODO: handle more complex mailto links (subject, body, etc.) - create_compose_window(new Geary.ComposedEmail(new DateTime.now_local(), - get_from(), new Geary.RFC822.MailboxAddresses.single( - new Geary.RFC822.MailboxAddress(null, link.substring(MAILTO.length))))); + compose_mailto(link); } else { open_uri(link); } @@ -946,5 +943,9 @@ public class GearyController { GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE).sensitive = sensitive; GearyApplication.instance.actions.get_action(ACTION_MARK_AS_MENU).sensitive = sensitive; } + + public void compose_mailto(string mailto) { + create_compose_window(new Geary.ComposedEmail.from_mailto(mailto, get_from())); + } } diff --git a/src/common/common-yorba-application.vala b/src/common/common-yorba-application.vala index 8fccb152..67952c1e 100644 --- a/src/common/common-yorba-application.vala +++ b/src/common/common-yorba-application.vala @@ -37,7 +37,7 @@ public abstract class YorbaApplication { return 0; } - public virtual signal void activate() { + public virtual signal void activate(string[] args) { } public virtual signal void exiting(bool panicked) { @@ -65,7 +65,10 @@ public abstract class YorbaApplication { // If app already running, activate it and exit if (unique_app.is_running()) { - unique_app.send_message((int) Unique.Command.ACTIVATE, null); + Unique.MessageData data = new Unique.MessageData(); + string argstr = string.joinv(", ", args); + data.set_text(argstr, argstr.length); + unique_app.send_message((int) Unique.Command.ACTIVATE, data); return false; } @@ -79,7 +82,7 @@ public abstract class YorbaApplication { Unique.MessageData data, uint timestamp) { switch (command) { case Unique.Command.ACTIVATE: - activate(); + activate(data.get_text().split(", ")); break; default: @@ -112,7 +115,7 @@ public abstract class YorbaApplication { error("Unable to register application: %s", e.message); } - activate(); + activate(args); // enter the main loop if (exitcode == 0) diff --git a/src/engine/api/geary-composed-email.vala b/src/engine/api/geary-composed-email.vala index 112126bd..0dc9eccc 100644 --- a/src/engine/api/geary-composed-email.vala +++ b/src/engine/api/geary-composed-email.vala @@ -29,10 +29,14 @@ public class Geary.ComposedEmail : Object { public string? mailer { get; set; default = null; } public ComposedEmail(DateTime date, RFC822.MailboxAddresses from, - RFC822.MailboxAddresses? to = null) { + RFC822.MailboxAddresses? to = null, RFC822.MailboxAddresses? cc = null, + RFC822.MailboxAddresses? bcc = null, RFC822.Subject? subject = null) { this.date = date; this.from = from; this.to = to; + this.cc = cc; + this.bcc = bcc; + this.subject = subject; } public ComposedEmail.as_reply(DateTime date, RFC822.MailboxAddresses from, Geary.Email source) { @@ -76,7 +80,59 @@ public class Geary.ComposedEmail : Object { body_html = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" + Geary.RFC822.Utils.quote_email_for_forward(source, true))); } - + + public ComposedEmail.from_mailto(string mailto, RFC822.MailboxAddresses default_from) { + DateTime date = new DateTime.now_local(); + RFC822.MailboxAddresses from = default_from; + RFC822.MailboxAddresses? to = null; + RFC822.MailboxAddresses? cc = null; + RFC822.MailboxAddresses? bcc = null; + RFC822.Subject? subject = null; + + if (mailto.length > "mailto:".length) { + // Parse the mailto link. + string[] parts = mailto.substring("mailto:".length).split("?", 2); + string email = Uri.unescape_string(parts[0]); + string[] params = parts.length == 2 ? parts[1].split("&") : new string[0]; + Gee.HashMap headers = new Gee.HashMap(); + foreach (string param in params) { + string[] param_parts = param.split("=", 2); + if (param_parts.length == 2) { + headers.set(Uri.unescape_string(param_parts[0]).down(), + Uri.unescape_string(param_parts[1])); + } + } + + // Assemble the headers. + if (headers.has_key("from")) { + from = new RFC822.MailboxAddresses.from_rfc822_string(headers.get("from")); + } + + if (email.length > 0 && headers.has_key("to")) { + to = new RFC822.MailboxAddresses.from_rfc822_string("%s,%s".printf(email, headers.get("to"))); + } else if (email.length > 0) { + to = new RFC822.MailboxAddresses.from_rfc822_string(email); + } else if (headers.has_key("to")) { + to = new RFC822.MailboxAddresses.from_rfc822_string(headers.get("to")); + } + + if (headers.has_key("cc")) { + cc = new RFC822.MailboxAddresses.from_rfc822_string(headers.get("cc")); + } + + if (headers.has_key("bcc")) { + bcc = new RFC822.MailboxAddresses.from_rfc822_string(headers.get("bcc")); + } + + if (headers.has_key("subject")) { + subject = new RFC822.Subject(headers.get("subject")); + } + } + + // And construct! + this(date, from, to, cc, bcc, subject); + } + private void set_reply_references(Geary.Email source) { in_reply_to = source.message_id; reply_to_email = source; diff --git a/vapi/unique-3.0.vapi b/vapi/unique-3.0.vapi index 79f67c5f..1ae4d919 100644 --- a/vapi/unique-3.0.vapi +++ b/vapi/unique-3.0.vapi @@ -52,12 +52,13 @@ namespace Unique { public unowned Gdk.Screen get_screen (); public unowned string get_startup_id (); public unowned string get_text (); - public unowned string get_uris (); + [CCode (array_length = false)] + public unowned string[] get_uris (); public uint get_workspace (); public void @set (uchar[] data, ssize_t length); public void set_filename (string filename); public bool set_text (string str, ssize_t length); - public bool set_uris (string uris); + public bool set_uris ([CCode (array_length = false)] string[] uris); } [CCode (cprefix = "UNIQUE_", cheader_filename = "unique/uniqueenumtypes.h")] public enum Command {