From 9acf5d344d7f55be454adc1d7cb192c266843e62 Mon Sep 17 00:00:00 2001 From: Niels De Graef Date: Tue, 20 Dec 2016 18:21:19 +0100 Subject: [PATCH 01/13] Make Geary DBus-activatable. Bug 775956. * org.gnome.Geary.service.in: create, and let it be installed by CMake. * org.gnome.Geary.desktop.in: add `DBusActivatable=true`. * geary-autostart.desktop.in: use `--gapplication-service` instead of hidden. * Change the way arguments are parsed: * Use the _command-line_ and _handle-local-options_ signals instead of _local-command-line_. * Remove the `--hidden` option (replaced by `--gapplication-service`) * Use VariantDict (which is a little cleaner) * Don't use global variables in Arg, but set them in the config instead. * We can no longer set a global summary due to the new option handling in GApplication. On IRC, I got the feedback that info like this should be going into a manpage. * Since the QUIT-action can now be called without ever activating the app, make the necessary changes to the `GearyController`: * `main_window` and `current_conversations` can be null. * use `pending_mailtos()` for the compose action as well. * Don't update the UNDO action if we're closing down. * More instance variables prefixed with this (we're changing the lines anyway, might as well do it properly). * `Environment.set_prgname()` is already executed in GApplication.run(), so no more need for `GearyApplication.PRGNAME`. Signed-off-by: Niels De Graef --- desktop/geary-autostart.desktop.in | 2 +- desktop/meson.build | 15 ++ desktop/org.gnome.Geary.desktop.in | 1 + desktop/org.gnome.Geary.service.in | 3 + meson.build | 1 + src/client/application/geary-application.vala | 104 +++++----- src/client/application/geary-args.vala | 191 +++++++++--------- src/client/application/geary-config.vala | 9 + src/client/application/geary-controller.vala | 80 ++++---- src/client/components/client-web-view.vala | 7 +- src/client/composer/composer-widget.vala | 4 +- .../conversation-message.vala | 4 +- src/client/notification/libnotify.vala | 2 +- .../components/client-web-view-test-case.vala | 4 +- .../components/client-web-view-test.vala | 4 +- 15 files changed, 225 insertions(+), 206 deletions(-) create mode 100644 desktop/org.gnome.Geary.service.in diff --git a/desktop/geary-autostart.desktop.in b/desktop/geary-autostart.desktop.in index b226ce3a..56c2f42c 100644 --- a/desktop/geary-autostart.desktop.in +++ b/desktop/geary-autostart.desktop.in @@ -7,7 +7,7 @@ Keywords=Email;E-mail;Mail; # Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=org.gnome.Geary TryExec=geary -Exec=geary --hidden +Exec=geary --gapplication-service Type=Application Terminal=false Categories=GNOME;GTK;Network;Email; diff --git a/desktop/meson.build b/desktop/meson.build index 12d1644f..96c71893 100644 --- a/desktop/meson.build +++ b/desktop/meson.build @@ -87,3 +87,18 @@ geary_compiled_schema = gnome.compile_schemas( install_data('org.gnome.Geary.gschema.xml', install_dir: join_paths(datadir, 'glib-2.0', 'schemas'), ) + +# +# DBus services +# + +service_conf = configuration_data() +service_conf.set('bindir', bindir) + +configure_file( + input: 'org.gnome.Geary.service.in', + output: 'org.gnome.Geary.service', + configuration: service_conf, + install: true, + install_dir: dbus_services_dir +) diff --git a/desktop/org.gnome.Geary.desktop.in b/desktop/org.gnome.Geary.desktop.in index 8a8f838b..58c36a20 100644 --- a/desktop/org.gnome.Geary.desktop.in +++ b/desktop/org.gnome.Geary.desktop.in @@ -13,6 +13,7 @@ Terminal=false Categories=GNOME;GTK;Network;Email; MimeType=x-scheme-handler/mailto; StartupNotify=true +DBusActivatable=true X-GNOME-UsesNotifications=true Actions=Compose; diff --git a/desktop/org.gnome.Geary.service.in b/desktop/org.gnome.Geary.service.in new file mode 100644 index 00000000..75044a85 --- /dev/null +++ b/desktop/org.gnome.Geary.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.gnome.Geary +Exec=@bindir@/geary" --gapplication-service diff --git a/meson.build b/meson.build index 23bfa02d..80c73f12 100644 --- a/meson.build +++ b/meson.build @@ -27,6 +27,7 @@ locale_dir = join_paths(geary_prefix, get_option('localedir')) po_dir = join_paths(meson.source_root(), 'po') vapi_dir = join_paths(meson.source_root(), 'bindings', 'vapi') metadata_dir = join_paths(meson.source_root(), 'bindings', 'metadata') +dbus_services_dir = join_paths(datadir, 'dbus-1', 'services') web_extensions_dir = join_paths(libdir, 'geary', 'web-extensions') # Make sure Meson can find our custom VAPI's diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 6d04b1cd..ae383d73 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -18,7 +18,6 @@ extern const string GETTEXT_PACKAGE; public class GearyApplication : Gtk.Application { public const string NAME = "Geary"; - public const string PRGNAME = "geary"; public const string APP_ID = "org.gnome.Geary"; public const string DESCRIPTION = _("Send and receive email"); public const string COPYRIGHT_1 = _("Copyright 2016 Software Freedom Conservancy Inc."); @@ -52,14 +51,14 @@ public class GearyApplication : Gtk.Application { public const string ACTION_UNDO = "undo"; // App-wide actions - private const string ACTION_ABOUT = "about"; - private const string ACTION_ACCOUNTS = "accounts"; - private const string ACTION_COMPOSE = "compose"; - private const string ACTION_INSPECT = "inspect"; - private const string ACTION_HELP = "help"; - private const string ACTION_MAILTO = "mailto"; - private const string ACTION_PREFERENCES = "preferences"; - private const string ACTION_QUIT = "quit"; + public const string ACTION_ABOUT = "about"; + public const string ACTION_ACCOUNTS = "accounts"; + public const string ACTION_COMPOSE = "compose"; + public const string ACTION_INSPECT = "inspect"; + public const string ACTION_HELP = "help"; + public const string ACTION_MAILTO = "mailto"; + public const string ACTION_PREFERENCES = "preferences"; + public const string ACTION_QUIT = "quit"; private const ActionEntry[] action_entries = { {ACTION_ABOUT, on_activate_about}, @@ -127,10 +126,10 @@ public class GearyApplication : Gtk.Application { * * If this returns `true`, then the primary application instance * will continue to run in the background after the last window is - * closed, instead of existing as usual. + * closed, instead of exiting as usual. */ public bool is_background_service { - get { return Args.hidden_startup || this.config.startup_notifications; } + get { return (this.flags & ApplicationFlags.IS_SERVICE) != 0; } } private string bin; @@ -238,57 +237,45 @@ public class GearyApplication : Gtk.Application { public GearyApplication() { Object( - application_id: APP_ID + application_id: APP_ID, + flags: ApplicationFlags.HANDLES_COMMAND_LINE ); + this.add_main_option_entries(Args.OPTION_ENTRIES); _instance = this; } - // Application.run() calls this as an entry point. - public override bool local_command_line(ref unowned string[] args, out int exit_status) { - bin = args[0]; - exec_dir = (File.new_for_path(Posix.realpath(Environment.find_program_in_path(bin)))).get_parent(); + public override bool local_command_line(ref unowned string[] args, + out int exit_status) { + this.bin = args[0]; + string current_path = Posix.realpath(Environment.find_program_in_path(this.bin)); + this.exec_dir = File.new_for_path(current_path).get_parent(); - try { - register(); - } catch (Error e) { - error("Error registering GearyApplication: %s", e.message); - } + return base.local_command_line(ref args, out exit_status); + } - if (!Args.parse(args)) { - exit_status = 1; - return true; - } + public override int handle_local_options(VariantDict options) { + return Args.handle_local_options(options); + } - if (!Args.quit) { - // Normal application startup or activation - activate(); - foreach (unowned string arg in args) { - if (arg != null) { - if (arg == Geary.ComposedEmail.MAILTO_SCHEME) - activate_action(ACTION_COMPOSE, null); - else if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) - activate_action(ACTION_MAILTO, new Variant.string(arg)); - } - } - } else { - // User requested quit, only try to if we aren't running - // already. - if (this.is_remote) { - activate_action(ACTION_QUIT, null); - } - } + public override int command_line(ApplicationCommandLine command_line) { + int exit_value = Args.handle_general_options(this.config, command_line.get_options_dict()); + if (exit_value != -1) + return exit_value; - exit_status = 0; - return true; + exit_value = Args.handle_arguments(this, command_line.get_arguments()); + if (exit_value != -1) + return exit_value; + + activate(); + + return -1; } public override void startup() { - Configuration.init(is_installed(), GSETTINGS_DIR); - Environment.set_application_name(NAME); - Environment.set_prgname(PRGNAME); - International.init(GETTEXT_PACKAGE, bin); + International.init(GETTEXT_PACKAGE, this.bin); + Configuration.init(is_installed(), GSETTINGS_DIR); Geary.Logging.init(); Geary.Logging.log_to(stderr); GLib.Log.set_default_handler(Geary.Logging.default_handler); @@ -301,7 +288,12 @@ public class GearyApplication : Gtk.Application { // Ensure all geary windows have an icon Gtk.Window.set_default_icon_name(APP_ID); + this.config = new Configuration(APP_ID); + add_action_entries(action_entries, this); + + // Use a hold() here (if started as a service, we will shutdown after 10s). + hold(); } public override void activate() { @@ -340,8 +332,6 @@ public class GearyApplication : Gtk.Application { message("%s %s prefix=%s exec_dir=%s is_installed=%s", NAME, VERSION, INSTALL_PREFIX, exec_dir.get_path(), is_installed().to_string()); - config = new Configuration(APP_ID); - // Application accels add_app_accelerators(ACTION_COMPOSE, { "N" }); add_app_accelerators(ACTION_HELP, { "F1" }); @@ -358,7 +348,7 @@ public class GearyApplication : Gtk.Application { ComposerWidget.add_window_accelerators(this); Components.Inspector.add_window_accelerators(this); - yield controller.open_async(null); + yield this.controller.open_async(null); release(); } @@ -367,11 +357,11 @@ public class GearyApplication : Gtk.Application { // see create_async() for reasoning hold/release is used hold(); - yield controller.close_async(); + if (this.controller != null) // If we didn't get activated, controller might be null + yield this.controller.close_async(); release(); - - is_destroyed = true; + this.is_destroyed = true; } public void add_window_accelerators(string action, @@ -479,7 +469,7 @@ public class GearyApplication : Gtk.Application { // This call will fire "exiting" only if it's not already been fired. public void exit(int exitcode = 0) { - if (exiting_fired) + if (this.exiting_fired) return; this.exitcode = exitcode; @@ -580,7 +570,7 @@ public class GearyApplication : Gtk.Application { private void on_activate_mailto(SimpleAction action, Variant? param) { if (this.controller != null && param != null) { - this.controller.compose_mailto(param.get_string()); + this.controller.compose(param.get_string()); } } diff --git a/src/client/application/geary-args.vala b/src/client/application/geary-args.vala index 1cb4858b..c1c86dbd 100644 --- a/src/client/application/geary-args.vala +++ b/src/client/application/geary-args.vala @@ -6,117 +6,120 @@ namespace Args { -private const OptionEntry[] options = { - { "hidden", 0, 0, OptionArg.NONE, ref hidden_startup, N_("Start Geary with hidden main window"), null }, - { "debug", 'd', 0, OptionArg.NONE, ref log_debug, N_("Output debugging information"), null }, - { "log-conversations", 0, 0, OptionArg.NONE, ref log_conversations, N_("Log conversation monitoring"), null }, - { "log-deserializer", 0, 0, OptionArg.NONE, ref log_deserializer, N_("Log network deserialization"), null }, - { "log-network", 0, 0, OptionArg.NONE, ref log_network, N_("Log network activity"), null }, +// LOCAL OPTIONS +public const string OPTION_VERSION = "version"; +// GENERAL OPTIONS +public const string OPTION_LOG_DEBUG = "debug"; +public const string OPTION_LOG_NETWORK = "log-conversations"; +public const string OPTION_LOG_SERIALIZER = "log-deserializer"; +public const string OPTION_LOG_DESERIALIZER = "log-network"; +public const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; +public const string OPTION_LOG_CONVERSATIONS = "log-serializer"; +public const string OPTION_LOG_PERIODIC = "log-periodic"; +public const string OPTION_LOG_SQL = "log-sql"; +public const string OPTION_LOG_FOLDER_NORMALIZATION = "log-folder-normalization"; +public const string OPTION_INSPECTOR = "inspector"; +public const string OPTION_REVOKE_CERTS = "revoke-certs"; +public const string OPTION_QUIT = "quit"; + +// This is also the order in which they are presented to the user, so it's probably best to keep +// them alphabetical +public const OptionEntry[] OPTION_ENTRIES = { + { OPTION_LOG_DEBUG, 'd', 0, OptionArg.NONE, null, N_("Output debugging information"), null }, + { OPTION_INSPECTOR, 'i', 0, OptionArg.NONE, null, N_("Allow inspection of WebView"), null }, + { OPTION_LOG_CONVERSATIONS, 0, 0, OptionArg.NONE, null, N_("Log conversation monitoring"), null }, + { OPTION_LOG_DESERIALIZER, 0, 0, OptionArg.NONE, null, N_("Log network deserialization"), null }, + { OPTION_LOG_NETWORK, 0, 0, OptionArg.NONE, null, N_("Log network activity"), null }, /// The IMAP replay queue is how changes on the server are replicated on the client. /// It could also be called the IMAP events queue. - { "log-replay-queue", 0, 0, OptionArg.NONE, ref log_replay_queue, N_("Log IMAP replay queue"), null }, + { OPTION_LOG_REPLAY_QUEUE, 0, 0, OptionArg.NONE, null, N_("Log IMAP replay queue"), null }, /// Serialization is how commands and responses are converted into a stream of bytes for /// network transmission - { "log-serializer", 0, 0, OptionArg.NONE, ref log_serializer, N_("Log network serialization"), null }, - { "log-periodic", 0, 0, OptionArg.NONE, ref log_periodic, N_("Log periodic activity"), null }, - { "log-sql", 0, 0, OptionArg.NONE, ref log_sql, N_("Log database queries (generates lots of messages)"), null }, + { OPTION_LOG_SERIALIZER, 0, 0, OptionArg.NONE, null, N_("Log network serialization"), null }, + { OPTION_LOG_PERIODIC, 0, 0, OptionArg.NONE, null, N_("Log periodic activity"), null }, + { OPTION_LOG_SQL, 0, 0, OptionArg.NONE, null, N_("Log database queries (generates lots of messages)"), null }, /// "Normalization" can also be called "synchronization" - { "log-folder-normalization", 0, 0, OptionArg.NONE, ref log_folder_normalization, N_("Log folder normalization"), null }, - { "inspector", 'i', 0, OptionArg.NONE, ref inspector, N_("Allow inspection of WebView"), null }, - { "revoke-certs", 0, 0, OptionArg.NONE, ref revoke_certs, N_("Revoke all server certificates with TLS warnings"), null }, - { "quit", 'q', 0, OptionArg.NONE, ref quit, N_("Perform a graceful quit"), null }, - { "version", 'V', 0, OptionArg.NONE, ref version, N_("Display program version"), null }, + { OPTION_LOG_FOLDER_NORMALIZATION, 0, 0, OptionArg.NONE, null, N_("Log folder normalization"), null }, + { OPTION_REVOKE_CERTS, 0, 0, OptionArg.NONE, null, N_("Revoke all server certificates with TLS warnings"), null }, + { OPTION_VERSION, 'V', 0, OptionArg.NONE, null, N_("Display program version"), null }, + { OPTION_QUIT, 'q', 0, OptionArg.NONE, null, N_("Perform a graceful quit"), null }, + /// Use this to specify arguments in the help section + { "", 0, 0, OptionArg.NONE, null, null, "[mailto:...]" }, { null } }; -public bool hidden_startup = false; -public bool log_debug = false; -public bool log_network = false; -public bool log_serializer = false; -public bool log_deserializer = false; -public bool log_replay_queue = false; -public bool log_conversations = false; -public bool log_periodic = false; -public bool log_sql = false; -public bool log_folder_normalization = false; -public bool inspector = false; -public bool quit = false; -public bool revoke_certs = false; -public bool version = false; - -public bool parse(string[] args) { - var context = new OptionContext("[%s...]".printf(Geary.ComposedEmail.MAILTO_SCHEME)); - context.set_help_enabled(true); - context.add_main_entries(options, null); - context.set_description("%s\n\n%s\n%s\n\n%s\n\t%s\n".printf( - // This gives a command-line hint on how to open new composer windows with mailto: - _("Use %s to open a new composer window").printf(Geary.ComposedEmail.MAILTO_SCHEME), - GearyApplication.COPYRIGHT_1, - GearyApplication.COPYRIGHT_2, - _("Please report comments, suggestions and bugs to:"), - GearyApplication.BUGREPORT)); - - try { - context.parse(ref args); - } catch (OptionError error) { - // i18n: Command line arguments are invalid - stdout.printf (_("Failed to parse command line options: %s\n"), error.message); - stdout.printf("\n%s", context.get_help(true, null)); - return false; +/** + * Handles options for a locally running instance, i.e. options for which you don't need to make + * a connection to a service instance that is already running. + */ +public int handle_local_options(VariantDict local_options) { + if (local_options.contains(OPTION_VERSION)) { + stdout.printf("%s %s\n", Environment.get_prgname(), GearyApplication.VERSION); + return 0; } - // other than the OptionEntry command-line arguments, the only acceptable arguments are - // mailto:'s - for (int ctr = 1; ctr < args.length; ctr++) { - string arg = args[ctr]; + return -1; +} - if (!arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { - stdout.printf(_("Unrecognized command line option “%s”\n").printf(arg)); - stdout.printf("\n%s", context.get_help(true, null)); +public int handle_general_options(Configuration config, VariantDict options) { + if (options.contains(OPTION_QUIT)) + return 0; - return false; - } - } - - if (version) { - stdout.printf("%s %s\n", GearyApplication.PRGNAME, GearyApplication.VERSION); - Process.exit(0); - } - - if (log_network) - Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); - - if (log_serializer) - Geary.Logging.enable_flags(Geary.Logging.Flag.SERIALIZER); - - if (log_replay_queue) - Geary.Logging.enable_flags(Geary.Logging.Flag.REPLAY); - - if (log_conversations) - Geary.Logging.enable_flags(Geary.Logging.Flag.CONVERSATIONS); - - if (log_periodic) - Geary.Logging.enable_flags(Geary.Logging.Flag.PERIODIC); - - if (log_sql) - Geary.Logging.enable_flags(Geary.Logging.Flag.SQL); - - if (log_folder_normalization) - Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); - - if (log_deserializer) - Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); - - if (log_debug) { + bool enable_debug = options.contains(OPTION_LOG_DEBUG); + // Will be logging to stderr until this point + if (enable_debug) { Geary.Logging.log_to(stdout); } else { - // We'll be logging to stderror until this point, so stop - // that. Geary.Logging.log_to(null); } - return true; + // Logging flags + if (options.contains(OPTION_LOG_NETWORK)) + Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); + if (options.contains(OPTION_LOG_SERIALIZER)) + Geary.Logging.enable_flags(Geary.Logging.Flag.SERIALIZER); + if (options.contains(OPTION_LOG_REPLAY_QUEUE)) + Geary.Logging.enable_flags(Geary.Logging.Flag.REPLAY); + if (options.contains(OPTION_LOG_CONVERSATIONS)) + Geary.Logging.enable_flags(Geary.Logging.Flag.CONVERSATIONS); + if (options.contains(OPTION_LOG_PERIODIC)) + Geary.Logging.enable_flags(Geary.Logging.Flag.PERIODIC); + if (options.contains(OPTION_LOG_SQL)) + Geary.Logging.enable_flags(Geary.Logging.Flag.SQL); + if (options.contains(OPTION_LOG_FOLDER_NORMALIZATION)) + Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); + if (options.contains(OPTION_LOG_DESERIALIZER)) + Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); + + config.enable_debug = enable_debug; + config.enable_inspector = options.contains(OPTION_INSPECTOR); + config.revoke_certs = options.contains(OPTION_REVOKE_CERTS); + + return -1; +} + +/** + * Handles the actual arguments of the application. + */ +public int handle_arguments(GearyApplication app, string[] args) { + for (int ctr = 1; ctr < args.length; ctr++) { + string arg = args[ctr]; + + // the only acceptable arguments are mailto:'s + if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { + if (arg == Geary.ComposedEmail.MAILTO_SCHEME) + app.activate_action(GearyApplication.ACTION_COMPOSE, null); + else + app.activate_action(GearyApplication.ACTION_MAILTO, new Variant.string(arg)); + } else { + stdout.printf(_("Unrecognized argument: “%s”\n").printf(arg)); + stdout.printf(_("Geary only accepts mailto-links as arguments.\n")); + + return 1; + } + } + + return -1; } } - diff --git a/src/client/application/geary-config.vala b/src/client/application/geary-config.vala index 2aef0d2d..17fe8550 100644 --- a/src/client/application/geary-config.vala +++ b/src/client/application/geary-config.vala @@ -52,6 +52,15 @@ public class Configuration { public Settings settings { get; private set; } public Settings gnome_interface { get; private set; } + // Can be set as an arguments + public bool enable_debug { get; set; default = false; } + + // Can be set as an arguments + public bool enable_inspector { get; set; default = false; } + + // Can be set as an arguments + public bool revoke_certs { get; set; default = false; } + public DesktopEnvironment desktop_environment { get { string? xdg_current_desktop = Environment.get_variable("XDG_CURRENT_DESKTOP"); diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 8e456c23..93eb48f8 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -158,7 +158,7 @@ public class GearyController : Geary.BaseObject { private Geary.Nonblocking.Mutex select_folder_mutex = new Geary.Nonblocking.Mutex(); private Geary.Folder? previous_non_search_folder = null; private UpgradeDialog upgrade_dialog; - private Gee.List pending_mailtos = new Gee.ArrayList(); + private Gee.List pending_mailtos = new Gee.ArrayList(); private uint operation_count = 0; private Geary.Revokable? revokable = null; @@ -254,8 +254,7 @@ public class GearyController : Geary.BaseObject { ClientWebView.init_web_context( this.application.config, this.application.get_web_extensions_dir(), - this.application.get_user_cache_directory().get_child("web-resources"), - Args.log_debug + this.application.get_user_cache_directory().get_child("web-resources") ); try { ClientWebView.load_resources( @@ -420,23 +419,26 @@ public class GearyController : Geary.BaseObject { on_conversations_selected(new Gee.HashSet()); on_folder_selected(null); - // Disconnect from various UI signals. - main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected); - main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated); - main_window.conversation_list_view.load_more.disconnect(on_load_more); - main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations); - main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed); - main_window.folder_list.folder_selected.disconnect(on_folder_selected); - main_window.folder_list.copy_conversation.disconnect(on_copy_conversation); - main_window.folder_list.move_conversation.disconnect(on_move_conversation); - main_window.main_toolbar.copy_folder_menu.folder_selected.disconnect(on_copy_conversation); - main_window.main_toolbar.move_folder_menu.folder_selected.disconnect(on_move_conversation); - main_window.conversation_viewer.conversation_added.disconnect( - on_conversation_view_added - ); + if (this.main_window != null) { + // Disconnect from various UI signals. + this.main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected); + this.main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated); + this.main_window.conversation_list_view.load_more.disconnect(on_load_more); + this.main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations); + this.main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed); + this.main_window.folder_list.folder_selected.disconnect(on_folder_selected); + this.main_window.folder_list.copy_conversation.disconnect(on_copy_conversation); + this.main_window.folder_list.move_conversation.disconnect(on_move_conversation); + this.main_window.main_toolbar.copy_folder_menu.folder_selected.disconnect(on_copy_conversation); + this.main_window.main_toolbar.move_folder_menu.folder_selected.disconnect(on_move_conversation); + this.main_window.conversation_viewer.conversation_added.disconnect( + on_conversation_view_added + ); - // hide window while shutting down, as this can take a few seconds under certain conditions - main_window.hide(); + // hide window while shutting down, as this can take a few + // seconds under certain conditions + this.main_window.hide(); + } // Release monitoring early so held resources can be freed up this.libnotify = null; @@ -533,9 +535,11 @@ public class GearyController : Geary.BaseObject { ); this.account_manager = null; - this.application.remove_window(this.main_window); - this.main_window.destroy(); - this.main_window = null; + if (this.main_window != null) { + this.application.remove_window(this.main_window); + this.main_window.destroy(); + this.main_window = null; + } this.upgrade_dialog = null; @@ -560,17 +564,10 @@ public class GearyController : Geary.BaseObject { debug("Closed GearyController"); } - /** - * Opens a new, blank composer. - */ - public void compose() { - create_compose_widget(ComposerWidget.ComposeType.NEW_MESSAGE); - } - /** * Opens or queues a new composer addressed to a specific email address. */ - public void compose_mailto(string mailto) { + public void compose(string? mailto = null) { if (current_account == null) { // Schedule the send for after we have an account open. pending_mailtos.add(mailto); @@ -861,7 +858,7 @@ public class GearyController : Geary.BaseObject { Geary.ServiceInformation service, Geary.Endpoint endpoint, GLib.TlsConnection cx) { - if (Args.revoke_certs) { + if (this.application.config.revoke_certs) { // XXX } @@ -1062,8 +1059,7 @@ public class GearyController : Geary.BaseObject { private void display_main_window_if_ready() { if (did_attempt_open_all_accounts() && !upgrade_dialog.visible && - !cancellable_open_account.is_cancelled() && - !Args.hidden_startup) + !cancellable_open_account.is_cancelled()) main_window.show(); } @@ -1184,8 +1180,8 @@ public class GearyController : Geary.BaseObject { // If we were waiting for an account to be selected before issuing mailtos, do that now. if (pending_mailtos.size > 0) { - foreach(string mailto in pending_mailtos) - compose_mailto(mailto); + foreach(string? mailto in pending_mailtos) + compose(mailto); pending_mailtos.clear(); } @@ -2530,12 +2526,14 @@ public class GearyController : Geary.BaseObject { revokable.committed.connect(on_revokable_committed); } - if (revokable != null && description != null) - this.main_window.main_toolbar.undo_tooltip = description; - else - this.main_window.main_toolbar.undo_tooltip = _("Undo (Ctrl+Z)"); + if (this.main_window != null) { + if (revokable != null && description != null) + this.main_window.main_toolbar.undo_tooltip = description; + else + this.main_window.main_toolbar.undo_tooltip = _("Undo (Ctrl+Z)"); - update_revokable_action(); + update_revokable_action(); + } } private void update_revokable_action() { @@ -3074,7 +3072,7 @@ public class GearyController : Geary.BaseObject { private void on_link_activated(string uri) { if (uri.down().has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { - compose_mailto(uri); + compose(uri); } else { open_uri(uri); } diff --git a/src/client/components/client-web-view.vala b/src/client/components/client-web-view.vala index 11864084..e9be8ef6 100644 --- a/src/client/components/client-web-view.vala +++ b/src/client/components/client-web-view.vala @@ -73,8 +73,7 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface { */ public static void init_web_context(Configuration config, File web_extension_dir, - File cache_dir, - bool enable_logging) { + File cache_dir) { WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path()); WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager); // Use a shared process so we don't spawn N WebProcess instances @@ -101,7 +100,7 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface { web_extension_dir.get_path() ); context.set_web_extensions_initialization_user_data( - new Variant.boolean(enable_logging) + new Variant.boolean(config.enable_debug) ); }); @@ -300,7 +299,7 @@ public abstract class ClientWebView : WebKit.WebView, Geary.BaseInterface { WebKit.Settings setts = new WebKit.Settings(); setts.allow_modal_dialogs = false; setts.default_charset = "UTF-8"; - setts.enable_developer_extras = Args.inspector; + setts.enable_developer_extras = config.enable_inspector; setts.enable_fullscreen = false; setts.enable_html5_database = false; setts.enable_html5_local_storage = false; diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala index e4e6d406..fb7b25fd 100644 --- a/src/client/composer/composer-widget.vala +++ b/src/client/composer/composer-widget.vala @@ -998,7 +998,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface { } // User-Agent - email.mailer = GearyApplication.PRGNAME + "/" + GearyApplication.VERSION; + email.mailer = Environment.get_prgname() + "/" + GearyApplication.VERSION; return email; } @@ -1968,7 +1968,7 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface { if (!this.editor.is_rich_text) append_menu_section(context_menu, section); } else if (section == this.context_menu_inspector) { - if (Args.inspector) + if (this.config.enable_inspector) append_menu_section(context_menu, section); } else { append_menu_section(context_menu, section); diff --git a/src/client/conversation-viewer/conversation-message.vala b/src/client/conversation-viewer/conversation-message.vala index d1a80711..392cbcf9 100644 --- a/src/client/conversation-viewer/conversation-message.vala +++ b/src/client/conversation-viewer/conversation-message.vala @@ -408,7 +408,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface { add_action(ACTION_COPY_SELECTION, false).activate.connect(() => { web_view.copy_clipboard(); }); - add_action(ACTION_OPEN_INSPECTOR, Args.inspector).activate.connect(() => { + add_action(ACTION_OPEN_INSPECTOR, config.enable_inspector).activate.connect(() => { this.web_view.get_inspector().show(); }); add_action(ACTION_OPEN_LINK, true, VariantType.STRING) @@ -429,7 +429,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface { context_menu_email = (MenuModel) builder.get_object("context_menu_email"); context_menu_image = (MenuModel) builder.get_object("context_menu_image"); context_menu_main = (MenuModel) builder.get_object("context_menu_main"); - if (Args.inspector) { + if (config.enable_inspector) { context_menu_inspector = (MenuModel) builder.get_object("context_menu_inspector"); } diff --git a/src/client/notification/libnotify.vala b/src/client/notification/libnotify.vala index dbce30bf..93a2ad99 100644 --- a/src/client/notification/libnotify.vala +++ b/src/client/notification/libnotify.vala @@ -26,7 +26,7 @@ public class Libnotify : Geary.BaseObject { monitor.add_required_fields(REQUIRED_FIELDS); if (!Notify.is_initted()) { - if (!Notify.init(GearyApplication.PRGNAME)) + if (!Notify.init(Environment.get_prgname())) message("Failed to initialize libnotify."); } diff --git a/test/client/components/client-web-view-test-case.vala b/test/client/components/client-web-view-test-case.vala index 9de2995e..50203047 100644 --- a/test/client/components/client-web-view-test-case.vala +++ b/test/client/components/client-web-view-test-case.vala @@ -16,11 +16,11 @@ public abstract class ClientWebViewTestCase : TestCase { protected ClientWebViewTestCase(string name) { base(name); this.config = new Configuration(GearyApplication.APP_ID); + this.config.enable_debug = true; ClientWebView.init_web_context( this.config, File.new_for_path(_BUILD_ROOT_DIR).get_child("src"), - File.new_for_path("/tmp"), // XXX use something better here - true + File.new_for_path("/tmp") // XXX use something better here ); try { ClientWebView.load_resources(GLib.File.new_for_path("/tmp")); diff --git a/test/client/components/client-web-view-test.vala b/test/client/components/client-web-view-test.vala index 661cac9c..8dad252e 100644 --- a/test/client/components/client-web-view-test.vala +++ b/test/client/components/client-web-view-test.vala @@ -15,11 +15,11 @@ public class ClientWebViewTest : TestCase { public void init_web_context() throws Error { Configuration config = new Configuration(GearyApplication.APP_ID); + config.enable_debug = true; ClientWebView.init_web_context( config, File.new_for_path(_BUILD_ROOT_DIR).get_child("src"), - File.new_for_path("/tmp"), // XXX use something better here - true + File.new_for_path("/tmp") // XXX use something better here ); } From 3ae24d75b8e73bae145544409b4435f47a9f4f5d Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 00:50:16 +1000 Subject: [PATCH 02/13] Update main window lifecycle management Ensure main window is not shown when started as service, and is only hidden when closed and the background startup pref is set. --- src/client/application/geary-controller.vala | 3 ++- src/client/components/main-window.vala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 93eb48f8..06bc4e4a 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -1059,7 +1059,8 @@ public class GearyController : Geary.BaseObject { private void display_main_window_if_ready() { if (did_attempt_open_all_accounts() && !upgrade_dialog.visible && - !cancellable_open_account.is_cancelled()) + !cancellable_open_account.is_cancelled() && + !this.application.is_background_service) main_window.show(); } diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala index 39f15911..4b53422e 100644 --- a/src/client/components/main-window.vala +++ b/src/client/components/main-window.vala @@ -637,7 +637,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface { [GtkCallback] private bool on_delete_event() { - if (this.application.is_background_service) { + if (this.application.config.startup_notifications) { if (this.application.controller.close_composition_windows(true)) { hide(); } From 34e4b47396a8448a52251435968450da89a2811a Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 00:51:17 +1000 Subject: [PATCH 03/13] Don't keep loading more conversations when started in background --- src/client/application/geary-controller.vala | 4 +++- src/client/conversation-list/conversation-list-view.vala | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 06bc4e4a..36113f94 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -3028,7 +3028,9 @@ public class GearyController : Geary.BaseObject { private void on_scan_completed(Geary.App.ConversationMonitor monitor) { // Done scanning. Check if we have enough messages to fill // the conversation list; if not, trigger a load_more(); - if (!main_window.conversation_list_has_scrollbar() && + if (this.main_window != null && + this.main_window.is_visible() && + !this.main_window.conversation_list_has_scrollbar() && monitor == this.current_conversations && monitor.can_load_more) { debug("Not enough messages, loading more for folder %s", diff --git a/src/client/conversation-list/conversation-list-view.vala b/src/client/conversation-list/conversation-list-view.vala index 73b74755..dee8a33b 100644 --- a/src/client/conversation-list/conversation-list-view.vala +++ b/src/client/conversation-list/conversation-list-view.vala @@ -141,7 +141,8 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface { Gtk.Adjustment adjustment = ((Gtk.Scrollable) this).get_vadjustment(); double upper = adjustment.get_upper(); double threshold = upper - adjustment.page_size - LOAD_MORE_HEIGHT; - if (this.conversation_monitor.can_load_more && + if (this.is_visible() && + this.conversation_monitor.can_load_more && adjustment.get_value() >= threshold) { load_more(); } From 548babc7bb71a5316d0c99105cdbe8875e22042f Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 00:52:42 +1000 Subject: [PATCH 04/13] Don't (necessarily) quit on Ctrl+W, just close the window --- src/client/application/geary-controller.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 36113f94..2c338da2 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -2281,7 +2281,7 @@ public class GearyController : Geary.BaseObject { } private void on_close() { - this.application.exit(); + this.main_window.close(); } private void on_reply_to_message(ConversationEmail target_view) { From 7b861a224e3087d8439ebbb4ec81460349d94f7c Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 01:03:06 +1000 Subject: [PATCH 05/13] Fix controller not being started when started as DBus service --- src/client/application/geary-application.vala | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index ae383d73..478afd5e 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -292,8 +292,13 @@ public class GearyApplication : Gtk.Application { add_action_entries(action_entries, this); - // Use a hold() here (if started as a service, we will shutdown after 10s). - hold(); + if (this.is_background_service) { + // Since command_line won't be called below if running as + // a DBus service, disable logging spew and start the + // controller running. + Geary.Logging.log_to(null); + this.create_async.begin(); + } } public override void activate() { From d8e4c3125668c2465c35bfd5a855f7239e045871 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 18:04:11 +1000 Subject: [PATCH 06/13] Move command line processing into GearyApplication Remove now-empty args namespace and source file. --- po/POTFILES.in | 1 - src/client/application/geary-application.vala | 156 ++++++++++++++++-- src/client/application/geary-args.vala | 125 -------------- src/client/meson.build | 1 - 4 files changed, 141 insertions(+), 142 deletions(-) delete mode 100644 src/client/application/geary-args.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index d79756e2..9077a63f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -22,7 +22,6 @@ src/client/application/application-contact-store.vala src/client/application/application-contact.vala src/client/application/autostart-manager.vala src/client/application/geary-application.vala -src/client/application/geary-args.vala src/client/application/geary-controller.vala src/client/application/goa-mediator.vala src/client/application/main.vala diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 478afd5e..9c57313e 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -60,6 +60,23 @@ public class GearyApplication : Gtk.Application { public const string ACTION_PREFERENCES = "preferences"; public const string ACTION_QUIT = "quit"; + // Local-only command line options + private const string OPTION_VERSION = "version"; + + // Local command line options + private const string OPTION_LOG_DEBUG = "debug"; + private const string OPTION_LOG_NETWORK = "log-conversations"; + private const string OPTION_LOG_SERIALIZER = "log-deserializer"; + private const string OPTION_LOG_DESERIALIZER = "log-network"; + private const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; + private const string OPTION_LOG_CONVERSATIONS = "log-serializer"; + private const string OPTION_LOG_PERIODIC = "log-periodic"; + private const string OPTION_LOG_SQL = "log-sql"; + private const string OPTION_LOG_FOLDER_NORM = "log-folder-normalization"; + private const string OPTION_INSPECTOR = "inspector"; + private const string OPTION_REVOKE_CERTS = "revoke-certs"; + private const string OPTION_QUIT = "quit"; + private const ActionEntry[] action_entries = { {ACTION_ABOUT, on_activate_about}, {ACTION_ACCOUNTS, on_activate_accounts}, @@ -71,6 +88,46 @@ public class GearyApplication : Gtk.Application { {ACTION_QUIT, on_activate_quit}, }; + // This is also the order in which they are presented to the user, + // so it's probably best to keep them alphabetical + public const GLib.OptionEntry[] OPTION_ENTRIES = { + { OPTION_LOG_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, + N_("Print debug logging"), null }, + { OPTION_INSPECTOR, 'i', 0, GLib.OptionArg.NONE, null, + N_("Enable WebKitGTK Inspector in web views"), null }, + { OPTION_LOG_CONVERSATIONS, 0, 0, GLib.OptionArg.NONE, null, + N_("Log conversation monitoring"), null }, + { OPTION_LOG_DESERIALIZER, 0, 0, GLib.OptionArg.NONE, null, + N_("Log IMAP network deserialization"), null }, + { OPTION_LOG_NETWORK, 0, 0, GLib.OptionArg.NONE, null, + N_("Log network activity"), null }, + /// The IMAP replay queue is how changes on the server are + /// replicated on the client. It could also be called the + /// IMAP events queue. + { OPTION_LOG_REPLAY_QUEUE, 0, 0, GLib.OptionArg.NONE, null, + N_("Log IMAP replay queue"), null }, + /// Serialization is how commands and responses are converted + /// into a stream of bytes for network transmission + { OPTION_LOG_SERIALIZER, 0, 0, GLib.OptionArg.NONE, null, + N_("Log IMAP network serialization"), null }, + { OPTION_LOG_PERIODIC, 0, 0, GLib.OptionArg.NONE, null, + N_("Log periodic activity"), null }, + { OPTION_LOG_SQL, 0, 0, GLib.OptionArg.NONE, null, + N_("Log database queries (generates lots of messages)"), null }, + /// "Normalization" can also be called "synchronization" + { OPTION_LOG_FOLDER_NORM, 0, 0, GLib.OptionArg.NONE, null, + N_("Log folder normalization"), null }, + { OPTION_REVOKE_CERTS, 0, 0, GLib.OptionArg.NONE, null, + N_("Revoke all pinned TLS server certificates"), null }, + { OPTION_VERSION, 'v', 0, GLib.OptionArg.NONE, null, + N_("Display program version"), null }, + { OPTION_QUIT, 'q', 0, GLib.OptionArg.NONE, null, + N_("Perform a graceful quit"), null }, + /// Use this to specify arguments in the help section + { "", 0, 0, GLib.OptionArg.NONE, null, null, "[mailto:[...]]" }, + { null } + }; + private const int64 USEC_PER_SEC = 1000000; private const int64 FORCE_SHUTDOWN_USEC = 5 * USEC_PER_SEC; @@ -240,7 +297,7 @@ public class GearyApplication : Gtk.Application { application_id: APP_ID, flags: ApplicationFlags.HANDLES_COMMAND_LINE ); - this.add_main_option_entries(Args.OPTION_ENTRIES); + this.add_main_option_entries(OPTION_ENTRIES); _instance = this; } @@ -253,20 +310,13 @@ public class GearyApplication : Gtk.Application { return base.local_command_line(ref args, out exit_status); } - public override int handle_local_options(VariantDict options) { - return Args.handle_local_options(options); - } - - public override int command_line(ApplicationCommandLine command_line) { - int exit_value = Args.handle_general_options(this.config, command_line.get_options_dict()); - if (exit_value != -1) - return exit_value; - - exit_value = Args.handle_arguments(this, command_line.get_arguments()); - if (exit_value != -1) - return exit_value; - - activate(); + public override int handle_local_options(GLib.VariantDict options) { + if (options.contains(OPTION_VERSION)) { + GLib.stdout.printf( + "%s: %s\n", this.bin, GearyApplication.VERSION + ); + return 0; + } return -1; } @@ -301,6 +351,20 @@ public class GearyApplication : Gtk.Application { } } + public override int command_line(ApplicationCommandLine command_line) { + int exit_value = handle_general_options(this.config, command_line.get_options_dict()); + if (exit_value != -1) + return exit_value; + + exit_value = handle_arguments(this, command_line.get_arguments()); + if (exit_value != -1) + return exit_value; + + activate(); + + return -1; + } + public override void activate() { base.activate(); @@ -532,6 +596,68 @@ public class GearyApplication : Gtk.Application { set_accels_for_action("app." + action, accelerators); } + public int handle_general_options(Configuration config, + GLib.VariantDict options) { + if (options.contains(OPTION_QUIT)) + return 0; + + bool enable_debug = options.contains(OPTION_LOG_DEBUG); + // Will be logging to stderr until this point + if (enable_debug) { + Geary.Logging.log_to(GLib.stdout); + } else { + Geary.Logging.log_to(null); + } + + // Logging flags + if (options.contains(OPTION_LOG_NETWORK)) + Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); + if (options.contains(OPTION_LOG_SERIALIZER)) + Geary.Logging.enable_flags(Geary.Logging.Flag.SERIALIZER); + if (options.contains(OPTION_LOG_REPLAY_QUEUE)) + Geary.Logging.enable_flags(Geary.Logging.Flag.REPLAY); + if (options.contains(OPTION_LOG_CONVERSATIONS)) + Geary.Logging.enable_flags(Geary.Logging.Flag.CONVERSATIONS); + if (options.contains(OPTION_LOG_PERIODIC)) + Geary.Logging.enable_flags(Geary.Logging.Flag.PERIODIC); + if (options.contains(OPTION_LOG_SQL)) + Geary.Logging.enable_flags(Geary.Logging.Flag.SQL); + if (options.contains(OPTION_LOG_FOLDER_NORM)) + Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); + if (options.contains(OPTION_LOG_DESERIALIZER)) + Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); + + config.enable_debug = enable_debug; + config.enable_inspector = options.contains(OPTION_INSPECTOR); + config.revoke_certs = options.contains(OPTION_REVOKE_CERTS); + + return -1; + } + + /** + * Handles the actual arguments of the application. + */ + public int handle_arguments(GearyApplication app, string[] args) { + for (int ctr = 1; ctr < args.length; ctr++) { + string arg = args[ctr]; + + // the only acceptable arguments are mailto:'s + if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { + if (arg == Geary.ComposedEmail.MAILTO_SCHEME) + app.activate_action(GearyApplication.ACTION_COMPOSE, null); + else + app.activate_action(GearyApplication.ACTION_MAILTO, new Variant.string(arg)); + } else { + stdout.printf(_("Unrecognized argument: “%s”\n").printf(arg)); + stdout.printf(_("Geary only accepts mailto-links as arguments.\n")); + + return 1; + } + } + + return -1; + } + private void on_activate_about() { Gtk.show_about_dialog(get_active_window(), "program-name", NAME, diff --git a/src/client/application/geary-args.vala b/src/client/application/geary-args.vala deleted file mode 100644 index c1c86dbd..00000000 --- a/src/client/application/geary-args.vala +++ /dev/null @@ -1,125 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -namespace Args { - -// LOCAL OPTIONS -public const string OPTION_VERSION = "version"; -// GENERAL OPTIONS -public const string OPTION_LOG_DEBUG = "debug"; -public const string OPTION_LOG_NETWORK = "log-conversations"; -public const string OPTION_LOG_SERIALIZER = "log-deserializer"; -public const string OPTION_LOG_DESERIALIZER = "log-network"; -public const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; -public const string OPTION_LOG_CONVERSATIONS = "log-serializer"; -public const string OPTION_LOG_PERIODIC = "log-periodic"; -public const string OPTION_LOG_SQL = "log-sql"; -public const string OPTION_LOG_FOLDER_NORMALIZATION = "log-folder-normalization"; -public const string OPTION_INSPECTOR = "inspector"; -public const string OPTION_REVOKE_CERTS = "revoke-certs"; -public const string OPTION_QUIT = "quit"; - -// This is also the order in which they are presented to the user, so it's probably best to keep -// them alphabetical -public const OptionEntry[] OPTION_ENTRIES = { - { OPTION_LOG_DEBUG, 'd', 0, OptionArg.NONE, null, N_("Output debugging information"), null }, - { OPTION_INSPECTOR, 'i', 0, OptionArg.NONE, null, N_("Allow inspection of WebView"), null }, - { OPTION_LOG_CONVERSATIONS, 0, 0, OptionArg.NONE, null, N_("Log conversation monitoring"), null }, - { OPTION_LOG_DESERIALIZER, 0, 0, OptionArg.NONE, null, N_("Log network deserialization"), null }, - { OPTION_LOG_NETWORK, 0, 0, OptionArg.NONE, null, N_("Log network activity"), null }, - /// The IMAP replay queue is how changes on the server are replicated on the client. - /// It could also be called the IMAP events queue. - { OPTION_LOG_REPLAY_QUEUE, 0, 0, OptionArg.NONE, null, N_("Log IMAP replay queue"), null }, - /// Serialization is how commands and responses are converted into a stream of bytes for - /// network transmission - { OPTION_LOG_SERIALIZER, 0, 0, OptionArg.NONE, null, N_("Log network serialization"), null }, - { OPTION_LOG_PERIODIC, 0, 0, OptionArg.NONE, null, N_("Log periodic activity"), null }, - { OPTION_LOG_SQL, 0, 0, OptionArg.NONE, null, N_("Log database queries (generates lots of messages)"), null }, - /// "Normalization" can also be called "synchronization" - { OPTION_LOG_FOLDER_NORMALIZATION, 0, 0, OptionArg.NONE, null, N_("Log folder normalization"), null }, - { OPTION_REVOKE_CERTS, 0, 0, OptionArg.NONE, null, N_("Revoke all server certificates with TLS warnings"), null }, - { OPTION_VERSION, 'V', 0, OptionArg.NONE, null, N_("Display program version"), null }, - { OPTION_QUIT, 'q', 0, OptionArg.NONE, null, N_("Perform a graceful quit"), null }, - /// Use this to specify arguments in the help section - { "", 0, 0, OptionArg.NONE, null, null, "[mailto:...]" }, - { null } -}; - -/** - * Handles options for a locally running instance, i.e. options for which you don't need to make - * a connection to a service instance that is already running. - */ -public int handle_local_options(VariantDict local_options) { - if (local_options.contains(OPTION_VERSION)) { - stdout.printf("%s %s\n", Environment.get_prgname(), GearyApplication.VERSION); - return 0; - } - - return -1; -} - -public int handle_general_options(Configuration config, VariantDict options) { - if (options.contains(OPTION_QUIT)) - return 0; - - bool enable_debug = options.contains(OPTION_LOG_DEBUG); - // Will be logging to stderr until this point - if (enable_debug) { - Geary.Logging.log_to(stdout); - } else { - Geary.Logging.log_to(null); - } - - // Logging flags - if (options.contains(OPTION_LOG_NETWORK)) - Geary.Logging.enable_flags(Geary.Logging.Flag.NETWORK); - if (options.contains(OPTION_LOG_SERIALIZER)) - Geary.Logging.enable_flags(Geary.Logging.Flag.SERIALIZER); - if (options.contains(OPTION_LOG_REPLAY_QUEUE)) - Geary.Logging.enable_flags(Geary.Logging.Flag.REPLAY); - if (options.contains(OPTION_LOG_CONVERSATIONS)) - Geary.Logging.enable_flags(Geary.Logging.Flag.CONVERSATIONS); - if (options.contains(OPTION_LOG_PERIODIC)) - Geary.Logging.enable_flags(Geary.Logging.Flag.PERIODIC); - if (options.contains(OPTION_LOG_SQL)) - Geary.Logging.enable_flags(Geary.Logging.Flag.SQL); - if (options.contains(OPTION_LOG_FOLDER_NORMALIZATION)) - Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); - if (options.contains(OPTION_LOG_DESERIALIZER)) - Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); - - config.enable_debug = enable_debug; - config.enable_inspector = options.contains(OPTION_INSPECTOR); - config.revoke_certs = options.contains(OPTION_REVOKE_CERTS); - - return -1; -} - -/** - * Handles the actual arguments of the application. - */ -public int handle_arguments(GearyApplication app, string[] args) { - for (int ctr = 1; ctr < args.length; ctr++) { - string arg = args[ctr]; - - // the only acceptable arguments are mailto:'s - if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { - if (arg == Geary.ComposedEmail.MAILTO_SCHEME) - app.activate_action(GearyApplication.ACTION_COMPOSE, null); - else - app.activate_action(GearyApplication.ACTION_MAILTO, new Variant.string(arg)); - } else { - stdout.printf(_("Unrecognized argument: “%s”\n").printf(arg)); - stdout.printf(_("Geary only accepts mailto-links as arguments.\n")); - - return 1; - } - } - - return -1; -} - -} diff --git a/src/client/meson.build b/src/client/meson.build index 2820629d..f362f7b1 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -7,7 +7,6 @@ geary_client_vala_sources = files( 'application/application-contact.vala', 'application/autostart-manager.vala', 'application/geary-application.vala', - 'application/geary-args.vala', 'application/geary-config.vala', 'application/geary-controller.vala', 'application/goa-mediator.vala', From 6f2187f5e221b0a9d2e665840aa6674124c5b906 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 18:26:22 +1000 Subject: [PATCH 07/13] Fully implement --quit Ensure that -q also shuts down non-local instances. --- src/client/application/geary-application.vala | 7 +++++-- src/client/application/geary-controller.vala | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 9c57313e..a443be3b 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -426,8 +426,9 @@ public class GearyApplication : Gtk.Application { // see create_async() for reasoning hold/release is used hold(); - if (this.controller != null) // If we didn't get activated, controller might be null + if (this.controller != null && this.controller.is_open) { yield this.controller.close_async(); + } release(); this.is_destroyed = true; @@ -598,8 +599,10 @@ public class GearyApplication : Gtk.Application { public int handle_general_options(Configuration config, GLib.VariantDict options) { - if (options.contains(OPTION_QUIT)) + if (options.contains(OPTION_QUIT)) { + exit(); return 0; + } bool enable_debug = options.contains(OPTION_LOG_DEBUG); // Will be logging to stderr until this point diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 2c338da2..f3e524f8 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -109,6 +109,13 @@ public class GearyController : Geary.BaseObject { } + /** Determines if the controller is opening or is open. */ + public bool is_open { + get { + return (this.open_cancellable != null); + } + } + public weak GearyApplication application { get; private set; } // circular ref public Accounts.Manager? account_manager { get; private set; default = null; } @@ -413,13 +420,13 @@ public class GearyController : Geary.BaseObject { this.open_cancellable.cancel(); this.open_cancellable = null; - Geary.Engine.instance.account_available.disconnect(on_account_available); - - // Release folder and conversations in the main window - on_conversations_selected(new Gee.HashSet()); - on_folder_selected(null); + this.application.engine.account_available.disconnect(on_account_available); if (this.main_window != null) { + // Release folder and conversations in the main window + on_conversations_selected(new Gee.HashSet()); + on_folder_selected(null); + // Disconnect from various UI signals. this.main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected); this.main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated); From bad2acec58625b6a187cb4c7dfe865e9c3e27652 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Wed, 10 Apr 2019 20:16:40 +1000 Subject: [PATCH 08/13] Fix some incorrect command line arg values, sort alphabetically --- src/client/application/geary-application.vala | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index a443be3b..bca95f13 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -64,18 +64,18 @@ public class GearyApplication : Gtk.Application { private const string OPTION_VERSION = "version"; // Local command line options - private const string OPTION_LOG_DEBUG = "debug"; - private const string OPTION_LOG_NETWORK = "log-conversations"; - private const string OPTION_LOG_SERIALIZER = "log-deserializer"; - private const string OPTION_LOG_DESERIALIZER = "log-network"; - private const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; - private const string OPTION_LOG_CONVERSATIONS = "log-serializer"; - private const string OPTION_LOG_PERIODIC = "log-periodic"; - private const string OPTION_LOG_SQL = "log-sql"; - private const string OPTION_LOG_FOLDER_NORM = "log-folder-normalization"; + private const string OPTION_DEBUG = "debug"; private const string OPTION_INSPECTOR = "inspector"; - private const string OPTION_REVOKE_CERTS = "revoke-certs"; + private const string OPTION_LOG_CONVERSATIONS = "log-conversations"; + private const string OPTION_LOG_DESERIALIZER = "log-deserializer"; + private const string OPTION_LOG_FOLDER_NORM = "log-folder-normalization"; + private const string OPTION_LOG_NETWORK = "log-network"; + private const string OPTION_LOG_PERIODIC = "log-periodic"; + private const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; + private const string OPTION_LOG_SERIALIZER = "log-serializer"; + private const string OPTION_LOG_SQL = "log-sql"; private const string OPTION_QUIT = "quit"; + private const string OPTION_REVOKE_CERTS = "revoke-certs"; private const ActionEntry[] action_entries = { {ACTION_ABOUT, on_activate_about}, @@ -91,7 +91,7 @@ public class GearyApplication : Gtk.Application { // This is also the order in which they are presented to the user, // so it's probably best to keep them alphabetical public const GLib.OptionEntry[] OPTION_ENTRIES = { - { OPTION_LOG_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, + { OPTION_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, N_("Print debug logging"), null }, { OPTION_INSPECTOR, 'i', 0, GLib.OptionArg.NONE, null, N_("Enable WebKitGTK Inspector in web views"), null }, @@ -99,8 +99,13 @@ public class GearyApplication : Gtk.Application { N_("Log conversation monitoring"), null }, { OPTION_LOG_DESERIALIZER, 0, 0, GLib.OptionArg.NONE, null, N_("Log IMAP network deserialization"), null }, + /// "Normalization" can also be called "synchronization" + { OPTION_LOG_FOLDER_NORM, 0, 0, GLib.OptionArg.NONE, null, + N_("Log folder normalization"), null }, { OPTION_LOG_NETWORK, 0, 0, GLib.OptionArg.NONE, null, N_("Log network activity"), null }, + { OPTION_LOG_PERIODIC, 0, 0, GLib.OptionArg.NONE, null, + N_("Log periodic activity"), null }, /// The IMAP replay queue is how changes on the server are /// replicated on the client. It could also be called the /// IMAP events queue. @@ -110,19 +115,14 @@ public class GearyApplication : Gtk.Application { /// into a stream of bytes for network transmission { OPTION_LOG_SERIALIZER, 0, 0, GLib.OptionArg.NONE, null, N_("Log IMAP network serialization"), null }, - { OPTION_LOG_PERIODIC, 0, 0, GLib.OptionArg.NONE, null, - N_("Log periodic activity"), null }, { OPTION_LOG_SQL, 0, 0, GLib.OptionArg.NONE, null, N_("Log database queries (generates lots of messages)"), null }, - /// "Normalization" can also be called "synchronization" - { OPTION_LOG_FOLDER_NORM, 0, 0, GLib.OptionArg.NONE, null, - N_("Log folder normalization"), null }, + { OPTION_QUIT, 'q', 0, GLib.OptionArg.NONE, null, + N_("Perform a graceful quit"), null }, { OPTION_REVOKE_CERTS, 0, 0, GLib.OptionArg.NONE, null, N_("Revoke all pinned TLS server certificates"), null }, { OPTION_VERSION, 'v', 0, GLib.OptionArg.NONE, null, N_("Display program version"), null }, - { OPTION_QUIT, 'q', 0, GLib.OptionArg.NONE, null, - N_("Perform a graceful quit"), null }, /// Use this to specify arguments in the help section { "", 0, 0, GLib.OptionArg.NONE, null, null, "[mailto:[...]]" }, { null } @@ -604,7 +604,7 @@ public class GearyApplication : Gtk.Application { return 0; } - bool enable_debug = options.contains(OPTION_LOG_DEBUG); + bool enable_debug = options.contains(OPTION_DEBUG); // Will be logging to stderr until this point if (enable_debug) { Geary.Logging.log_to(GLib.stdout); From 5eb8d1452d06dc2f915d1341748ccc2396cee6e9 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 12 Apr 2019 20:35:39 +1000 Subject: [PATCH 09/13] Modernise autostart desktop file manager class a bit Move it into the application namespace, give it a slightly more generic name, move its instance from the controller to the application, and modernise its source code. --- po/POTFILES.in | 2 +- .../application-startup-manager.vala | 110 ++++++++++++++++++ src/client/application/autostart-manager.vala | 94 --------------- src/client/application/geary-application.vala | 8 ++ src/client/application/geary-controller.vala | 7 -- src/client/dialogs/preferences-dialog.vala | 2 +- src/client/meson.build | 2 +- 7 files changed, 121 insertions(+), 104 deletions(-) create mode 100644 src/client/application/application-startup-manager.vala delete mode 100644 src/client/application/autostart-manager.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index 9077a63f..69c71b39 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,7 +20,7 @@ src/client/application/application-certificate-manager.vala src/client/application/application-command.vala src/client/application/application-contact-store.vala src/client/application/application-contact.vala -src/client/application/autostart-manager.vala +src/client/application/application-startup-manager.vala src/client/application/geary-application.vala src/client/application/geary-controller.vala src/client/application/goa-mediator.vala diff --git a/src/client/application/application-startup-manager.vala b/src/client/application/application-startup-manager.vala new file mode 100644 index 00000000..2e62d7a1 --- /dev/null +++ b/src/client/application/application-startup-manager.vala @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/* + * Manages desktop files in the autostart. + */ +public class Application.StartupManager : GLib.Object { + + private const string AUTOSTART_FOLDER = "autostart"; + private const string AUTOSTART_DESKTOP_FILE = "geary-autostart.desktop"; + + private Configuration config; + private GLib.File? install_dir; + private GLib.File startup_file; // Startup '.desktop' file + + public StartupManager(Configuration config, GLib.File? install_dir) { + this.config = config; + this.install_dir = install_dir; + this.startup_file = GLib.File.new_for_path( + GLib.Environment.get_user_config_dir() + ).get_child(AUTOSTART_FOLDER) + .get_child(AUTOSTART_DESKTOP_FILE); + + // Connect startup-notifications option callback + config.settings.changed[Configuration.STARTUP_NOTIFICATIONS_KEY].connect( + on_startup_notification_change + ); + } + + /** + * Returns the system-wide autostart desktop file + */ + public GLib.File? get_autostart_desktop_file() { + GLib.File? parent = null; + if (this.install_dir != null) { + // Running from the installation directory + parent = ( + this.install_dir + .get_child("share") + .get_child("applications") + ); + } else { + // Running from the source build directory + parent = ( + GLib.File.new_for_path(GearyApplication.SOURCE_ROOT_DIR) + .get_child("build") + .get_child("desktop") + ); + } + + GLib.File desktop_file = parent.get_child(AUTOSTART_DESKTOP_FILE); + return desktop_file.query_exists() ? desktop_file : null; + } + + /** + * Copies the autostart desktop file to the autostart directory. + */ + public void install_startup_file() throws GLib.Error { + if (!this.startup_file.query_exists()) { + GLib.File autostart_dir = this.startup_file.get_parent(); + if (!autostart_dir.query_exists()) { + autostart_dir.make_directory_with_parents(); + } + GLib.File? autostart = get_autostart_desktop_file(); + if (autostart == null) { + warning("Autostart file is not installed!"); + } else { + autostart.copy(this.startup_file, 0); + } + } + } + + /** + * Deletes the desktop file from autostart directory. + */ + public void delete_startup_file() throws GLib.Error { + try { + this.startup_file.delete(); + } catch (GLib.IOError.NOT_FOUND err) { + // All good + } + } + + /* + * Synchronises the config with the actual state of the autostart file. + * + * Ensures it's not misleading (i.e. the option is checked while + * the file doesn't exist). + */ + public void sync_with_config() { + this.config.startup_notifications = this.startup_file.query_exists(); + } + + private void on_startup_notification_change() { + try { + if (this.config.startup_notifications) { + install_startup_file(); + } else { + delete_startup_file(); + } + } catch (GLib.Error err) { + warning("Failed to update autostart desktop file: %s", err.message); + } + } + +} diff --git a/src/client/application/autostart-manager.vala b/src/client/application/autostart-manager.vala deleted file mode 100644 index 37d1c447..00000000 --- a/src/client/application/autostart-manager.vala +++ /dev/null @@ -1,94 +0,0 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/* - * A simple class for manipulating autostarting Geary as a hidden application through a simple - * desktop file at $HOME/.config/autostart/geary.desktop - */ -public class AutostartManager : Object { - - private const string AUTOSTART_FOLDER = "autostart"; - private const string AUTOSTART_DESKTOP_FILE = "geary-autostart.desktop"; - - private GearyApplication instance; - private File startup_file; // Startup '.desktop' file - - public AutostartManager(GearyApplication instance) { - this.instance = instance; - this.startup_file = File.new_for_path(Environment.get_user_config_dir()).get_child(AUTOSTART_FOLDER) - .get_child(AUTOSTART_DESKTOP_FILE); - - // Connect startup-notifications option callback - this.instance.config.settings.changed[Configuration.STARTUP_NOTIFICATIONS_KEY].connect( - on_startup_notification_change); - } - - /** - * Returns the system-wide autostart desktop file - */ - public File? get_autostart_desktop_file() { - File? install_dir = this.instance.get_install_dir(); - File desktop_file = (install_dir != null) - ? install_dir.get_child("share").get_child("applications").get_child(AUTOSTART_DESKTOP_FILE) - : File.new_for_path(GearyApplication.SOURCE_ROOT_DIR).get_child("build").get_child("desktop").get_child(AUTOSTART_DESKTOP_FILE); - - return desktop_file.query_exists() ? desktop_file : null; - } - - /** - * Deletes the desktop file from autostart directory. - */ - public void delete_startup_file() { - if (this.startup_file.query_exists()) { - try { - this.startup_file.delete(); - } catch (Error err) { - message("Failed to delete startup file: %s", err.message); - } - } - } - - /** - * Creates .desktop file in autostart directory (usually '$HOME/.config/autostart/') if no one exists. - */ - public void create_startup_file() { - if (this.startup_file.query_exists()) - return; - - try { - File autostart_dir = this.startup_file.get_parent(); - if (!autostart_dir.query_exists()) - autostart_dir.make_directory_with_parents(); - File? autostart = get_autostart_desktop_file(); - if (autostart == null) { - message("Autostart file is not installed!"); - } else { - autostart.copy(this.startup_file, 0); - } - } catch (Error err) { - message("Failed to create startup file: %s", err.message); - } - } - - /** - * Callback for startup notification option changes. - */ - public void on_startup_notification_change() { - if (this.instance.config.startup_notifications) - create_startup_file(); - else - delete_startup_file(); - } - - /* - * A convenience method. The purpose of this method is to synchronize the state of startup notifications setting - * with the actual state of the file, so it's not misleading for the user (the option is checked while the file doesn't exist) - */ - public void sync_with_config() { - this.instance.config.startup_notifications = this.startup_file.query_exists(); - } - -} diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index bca95f13..09bf2ddc 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -178,6 +178,11 @@ public class GearyApplication : Gtk.Application { */ public Configuration config { get; private set; } + /** Manages the autostart desktop file. */ + public Application.StartupManager? autostart { + get; private set; default = null; + } + /** * Determines if Geary configured to run as as a background service. * @@ -339,6 +344,9 @@ public class GearyApplication : Gtk.Application { Gtk.Window.set_default_icon_name(APP_ID); this.config = new Configuration(APP_ID); + this.autostart = new Application.StartupManager( + this.config, get_install_dir() + ); add_action_entries(action_entries, this); diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index f3e524f8..e9104451 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -129,8 +129,6 @@ public class GearyController : Geary.BaseObject { public Geary.App.ConversationMonitor? current_conversations { get; private set; default = null; } - public AutostartManager? autostart_manager { get; private set; default = null; } - public Application.AvatarStore? avatars { get; private set; default = new Application.AvatarStore(); } @@ -335,9 +333,6 @@ public class GearyController : Geary.BaseObject { this.main_window.conversation_list_view.grab_focus(); - // instantiate here to ensure that Config is initialized and ready - this.autostart_manager = new AutostartManager(this.application); - // initialize revokable save_revokable(null, null); @@ -562,8 +557,6 @@ public class GearyController : Geary.BaseObject { this.composer_widgets.clear(); this.waiting_to_close.clear(); - this.autostart_manager = null; - this.avatars.close(); this.avatars = null; diff --git a/src/client/dialogs/preferences-dialog.vala b/src/client/dialogs/preferences-dialog.vala index 1af91a09..41c2a8a8 100644 --- a/src/client/dialogs/preferences-dialog.vala +++ b/src/client/dialogs/preferences-dialog.vala @@ -46,7 +46,7 @@ public class PreferencesDialog : Gtk.Dialog { public new void run() { // Sync startup notification option with file state - this.app.controller.autostart_manager.sync_with_config(); + this.app.autostart.sync_with_config(); base.run(); destroy(); diff --git a/src/client/meson.build b/src/client/meson.build index f362f7b1..0770c20e 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -5,7 +5,7 @@ geary_client_vala_sources = files( 'application/application-command.vala', 'application/application-contact-store.vala', 'application/application-contact.vala', - 'application/autostart-manager.vala', + 'application/application-startup-manager.vala', 'application/geary-application.vala', 'application/geary-config.vala', 'application/geary-controller.vala', From 2578c6c08934ea5ab5cc995228ca9465f94f205b Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 12 Apr 2019 20:37:40 +1000 Subject: [PATCH 10/13] Re-implement --hidden as a deprecated command line option When used, start hidden, but print a warning and update the autostart file if needed so it doesn't get used again automatically. --- src/client/application/geary-application.vala | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 09bf2ddc..0272e89a 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -74,6 +74,7 @@ public class GearyApplication : Gtk.Application { private const string OPTION_LOG_REPLAY_QUEUE = "log-replay-queue"; private const string OPTION_LOG_SERIALIZER = "log-serializer"; private const string OPTION_LOG_SQL = "log-sql"; + private const string OPTION_HIDDEN = "hidden"; private const string OPTION_QUIT = "quit"; private const string OPTION_REVOKE_CERTS = "revoke-certs"; @@ -93,6 +94,8 @@ public class GearyApplication : Gtk.Application { public const GLib.OptionEntry[] OPTION_ENTRIES = { { OPTION_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, N_("Print debug logging"), null }, + { OPTION_HIDDEN, 0, 0, GLib.OptionArg.NONE, null, + N_("Start with the main window hidden (deprecated)"), null }, { OPTION_INSPECTOR, 'i', 0, GLib.OptionArg.NONE, null, N_("Enable WebKitGTK Inspector in web views"), null }, { OPTION_LOG_CONVERSATIONS, 0, 0, GLib.OptionArg.NONE, null, @@ -191,11 +194,17 @@ public class GearyApplication : Gtk.Application { * closed, instead of exiting as usual. */ public bool is_background_service { - get { return (this.flags & ApplicationFlags.IS_SERVICE) != 0; } + get { + return ( + (this.flags & ApplicationFlags.IS_SERVICE) != 0 || + this.start_hidden + ); + } } private string bin; private File exec_dir; + private bool start_hidden = false; private bool exiting_fired = false; private int exitcode = 0; private bool is_destroyed = false; @@ -637,6 +646,17 @@ public class GearyApplication : Gtk.Application { Geary.Logging.enable_flags(Geary.Logging.Flag.FOLDER_NORMALIZATION); if (options.contains(OPTION_LOG_DESERIALIZER)) Geary.Logging.enable_flags(Geary.Logging.Flag.DESERIALIZER); + if (options.contains(OPTION_HIDDEN)) { + warning( + /// Warning printed to the console when a deprecated + /// command line option is used. + _("The `--hidden` option is deprecated and will be removed in the future.") + ); + this.start_hidden = true; + // Update the autostart file so that it stops using the + // --hidden option. + this.update_autostart_file.begin(); + } config.enable_debug = enable_debug; config.enable_inspector = options.contains(OPTION_INSPECTOR); @@ -669,6 +689,18 @@ public class GearyApplication : Gtk.Application { return -1; } + /** Removes and re-adds the austostart file if needed. */ + private async void update_autostart_file() { + try { + this.autostart.delete_startup_file(); + if (this.config.startup_notifications) { + this.autostart.install_startup_file(); + } + } catch (GLib.Error err) { + warning("Could not update autostart file"); + } + } + private void on_activate_about() { Gtk.show_about_dialog(get_active_window(), "program-name", NAME, From 6605682cc422c58ece1ed27258c8cf8e7428a5d6 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 12 Apr 2019 20:39:08 +1000 Subject: [PATCH 11/13] Add translation comments denoting command line option strings --- src/client/application/geary-application.vala | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 0272e89a..1bb95729 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -93,40 +93,53 @@ public class GearyApplication : Gtk.Application { // so it's probably best to keep them alphabetical public const GLib.OptionEntry[] OPTION_ENTRIES = { { OPTION_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Print debug logging"), null }, { OPTION_HIDDEN, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Start with the main window hidden (deprecated)"), null }, { OPTION_INSPECTOR, 'i', 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Enable WebKitGTK Inspector in web views"), null }, { OPTION_LOG_CONVERSATIONS, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Log conversation monitoring"), null }, { OPTION_LOG_DESERIALIZER, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Log IMAP network deserialization"), null }, - /// "Normalization" can also be called "synchronization" { OPTION_LOG_FOLDER_NORM, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option. "Normalization" can also be called + /// "synchronization". N_("Log folder normalization"), null }, { OPTION_LOG_NETWORK, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Log network activity"), null }, { OPTION_LOG_PERIODIC, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Log periodic activity"), null }, - /// The IMAP replay queue is how changes on the server are - /// replicated on the client. It could also be called the - /// IMAP events queue. { OPTION_LOG_REPLAY_QUEUE, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option. The IMAP replay queue is how changes + /// on the server are replicated on the client. It could + /// also be called the IMAP events queue. N_("Log IMAP replay queue"), null }, - /// Serialization is how commands and responses are converted - /// into a stream of bytes for network transmission { OPTION_LOG_SERIALIZER, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option. Serialization is how commands and + /// responses are converted into a stream of bytes for + /// network transmission N_("Log IMAP network serialization"), null }, { OPTION_LOG_SQL, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Log database queries (generates lots of messages)"), null }, { OPTION_QUIT, 'q', 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Perform a graceful quit"), null }, { OPTION_REVOKE_CERTS, 0, 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Revoke all pinned TLS server certificates"), null }, { OPTION_VERSION, 'v', 0, GLib.OptionArg.NONE, null, + /// Command line option N_("Display program version"), null }, - /// Use this to specify arguments in the help section + // Use this to specify arguments in the help section { "", 0, 0, GLib.OptionArg.NONE, null, null, "[mailto:[...]]" }, { null } }; From 3c1ae51e29509ff7f0d028c3beaf87e414ff279e Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 12 Apr 2019 20:42:05 +1000 Subject: [PATCH 12/13] Minor code cleanup --- src/client/application/geary-application.vala | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 1bb95729..67b1bf54 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -1,7 +1,9 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright 2016 Software Freedom Conservancy Inc. + * Copyright 2019 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. + * (version 2.1 or later). See the COPYING file in this distribution. */ // Defined by CMake build script. @@ -78,7 +80,7 @@ public class GearyApplication : Gtk.Application { private const string OPTION_QUIT = "quit"; private const string OPTION_REVOKE_CERTS = "revoke-certs"; - private const ActionEntry[] action_entries = { + private const ActionEntry[] ACTION_ENTRIES = { {ACTION_ABOUT, on_activate_about}, {ACTION_ACCOUNTS, on_activate_accounts}, {ACTION_COMPOSE, on_activate_compose}, @@ -91,7 +93,7 @@ public class GearyApplication : Gtk.Application { // This is also the order in which they are presented to the user, // so it's probably best to keep them alphabetical - public const GLib.OptionEntry[] OPTION_ENTRIES = { + private const GLib.OptionEntry[] OPTION_ENTRIES = { { OPTION_DEBUG, 'd', 0, GLib.OptionArg.NONE, null, /// Command line option N_("Print debug logging"), null }, @@ -190,11 +192,21 @@ public class GearyApplication : Gtk.Application { } /** - * The user's desktop-wide settings for the application. + * The user's desktop settings for the application. + * + * This will be null until {@link startup} has been called, and + * hence will only ever become non-null for the primary instance. */ - public Configuration config { get; private set; } + public Configuration? config { + get; private set; default = null; + } - /** Manages the autostart desktop file. */ + /** + * Manages the autostart desktop file. + * + * This will be null until {@link startup} has been called, and + * hence will only ever become non-null for the primary instance. + */ public Application.StartupManager? autostart { get; private set; default = null; } @@ -215,7 +227,7 @@ public class GearyApplication : Gtk.Application { } } - private string bin; + private string binary; private File exec_dir; private bool start_hidden = false; private bool exiting_fired = false; @@ -322,7 +334,7 @@ public class GearyApplication : Gtk.Application { public GearyApplication() { Object( application_id: APP_ID, - flags: ApplicationFlags.HANDLES_COMMAND_LINE + flags: GLib.ApplicationFlags.HANDLES_COMMAND_LINE ); this.add_main_option_entries(OPTION_ENTRIES); _instance = this; @@ -330,9 +342,11 @@ public class GearyApplication : Gtk.Application { public override bool local_command_line(ref unowned string[] args, out int exit_status) { - this.bin = args[0]; - string current_path = Posix.realpath(Environment.find_program_in_path(this.bin)); - this.exec_dir = File.new_for_path(current_path).get_parent(); + this.binary = args[0]; + string current_path = Posix.realpath( + GLib.Environment.find_program_in_path(this.binary) + ); + this.exec_dir = GLib.File.new_for_path(current_path).get_parent(); return base.local_command_line(ref args, out exit_status); } @@ -340,7 +354,7 @@ public class GearyApplication : Gtk.Application { public override int handle_local_options(GLib.VariantDict options) { if (options.contains(OPTION_VERSION)) { GLib.stdout.printf( - "%s: %s\n", this.bin, GearyApplication.VERSION + "%s: %s\n", this.binary, GearyApplication.VERSION ); return 0; } @@ -350,7 +364,7 @@ public class GearyApplication : Gtk.Application { public override void startup() { Environment.set_application_name(NAME); - International.init(GETTEXT_PACKAGE, this.bin); + International.init(GETTEXT_PACKAGE, this.binary); Configuration.init(is_installed(), GSETTINGS_DIR); Geary.Logging.init(); @@ -370,7 +384,7 @@ public class GearyApplication : Gtk.Application { this.config, get_install_dir() ); - add_action_entries(action_entries, this); + add_action_entries(ACTION_ENTRIES, this); if (this.is_background_service) { // Since command_line won't be called below if running as From 40c98d5eb4e3ef3fb1638d52815ba05232458ba5 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 12 Apr 2019 22:02:32 +1000 Subject: [PATCH 13/13] Fix handling of mailto command line args --- src/client/application/geary-application.vala | 69 +++++++++---------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 67b1bf54..e853738d 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -142,7 +142,8 @@ public class GearyApplication : Gtk.Application { /// Command line option N_("Display program version"), null }, // Use this to specify arguments in the help section - { "", 0, 0, GLib.OptionArg.NONE, null, null, "[mailto:[...]]" }, + { GLib.OPTION_REMAINING, 0, 0, GLib.OptionArg.STRING_ARRAY, null, null, + "[mailto:[...]]" }, { null } }; @@ -395,12 +396,8 @@ public class GearyApplication : Gtk.Application { } } - public override int command_line(ApplicationCommandLine command_line) { - int exit_value = handle_general_options(this.config, command_line.get_options_dict()); - if (exit_value != -1) - return exit_value; - - exit_value = handle_arguments(this, command_line.get_arguments()); + public override int command_line(GLib.ApplicationCommandLine command_line) { + int exit_value = handle_general_options(command_line); if (exit_value != -1) return exit_value; @@ -641,8 +638,8 @@ public class GearyApplication : Gtk.Application { set_accels_for_action("app." + action, accelerators); } - public int handle_general_options(Configuration config, - GLib.VariantDict options) { + public int handle_general_options(GLib.ApplicationCommandLine command_line) { + GLib.VariantDict options = command_line.get_options_dict(); if (options.contains(OPTION_QUIT)) { exit(); return 0; @@ -684,35 +681,37 @@ public class GearyApplication : Gtk.Application { // --hidden option. this.update_autostart_file.begin(); } - - config.enable_debug = enable_debug; - config.enable_inspector = options.contains(OPTION_INSPECTOR); - config.revoke_certs = options.contains(OPTION_REVOKE_CERTS); - - return -1; - } - - /** - * Handles the actual arguments of the application. - */ - public int handle_arguments(GearyApplication app, string[] args) { - for (int ctr = 1; ctr < args.length; ctr++) { - string arg = args[ctr]; - - // the only acceptable arguments are mailto:'s - if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { - if (arg == Geary.ComposedEmail.MAILTO_SCHEME) - app.activate_action(GearyApplication.ACTION_COMPOSE, null); - else - app.activate_action(GearyApplication.ACTION_MAILTO, new Variant.string(arg)); - } else { - stdout.printf(_("Unrecognized argument: “%s”\n").printf(arg)); - stdout.printf(_("Geary only accepts mailto-links as arguments.\n")); - - return 1; + if (options.contains(GLib.OPTION_REMAINING)) { + string[] args = options.lookup_value( + GLib.OPTION_REMAINING, + GLib.VariantType.STRING_ARRAY + ).get_strv(); + foreach (string arg in args) { + // the only acceptable arguments are mailto:'s + if (arg == Geary.ComposedEmail.MAILTO_SCHEME) { + activate_action(GearyApplication.ACTION_COMPOSE, null); + } else if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { + activate_action( + GearyApplication.ACTION_MAILTO, + new GLib.Variant.string(arg) + ); + } else { + command_line.printerr("%s: ", this.binary); + command_line.printerr( + /// Command line warning, string substitution + /// is the given argument + _("Unrecognised program argument: “%s”"), arg + ); + command_line.printerr("\n"); + return 1; + } } } + this.config.enable_debug = enable_debug; + this.config.enable_inspector = options.contains(OPTION_INSPECTOR); + this.config.revoke_certs = options.contains(OPTION_REVOKE_CERTS); + return -1; }