From 3eaa1dcb8f78aae7c9b49bdf13a917285667e9f4 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Fri, 6 Dec 2013 16:43:11 -0800 Subject: [PATCH] Use GtkApplication This ports the Geary application to use GtkApplication, and removes the dependency on libunique. Closes: bgo#714145 --- debian/control | 2 - src/CMakeLists.txt | 4 +- src/client/application/geary-application.vala | 186 ++++++++++++----- src/client/application/geary-config.vala | 4 +- src/client/application/geary-controller.vala | 6 +- src/client/components/main-window.vala | 6 +- src/client/notification/libnotify.vala | 2 +- src/client/util/util-yorba-application.vala | 189 ------------------ 8 files changed, 152 insertions(+), 247 deletions(-) delete mode 100644 src/client/util/util-yorba-application.vala diff --git a/debian/control b/debian/control index 441a0482..0a011829 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,6 @@ Build-Depends: debhelper (>= 8), libgee-0.8-dev (>= 0.8.5), libglib2.0-dev (>= 2.32.0), libgtk-3-dev (>= 3.6.0), - libunique-3.0-dev (>= 3.0.0), libnotify-dev (>=0.7.5), libcanberra-dev (>= 0.28), libwebkitgtk-3.0-dev (<< 2.1.0), @@ -30,7 +29,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, libgee-0.8-2 (>= 0.8.5), libglib2.0-0 (>= 2.32.0), libgtk-3-0 (>= 3.6.0), - libunique-3.0-0 (>= 3.0.0), libnotify4 (>= 0.7.5), libcanberra0 (>= 0.28), libwebkitgtk-3.0-0 (<< 2.1.0), diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 204836f3..9f8278a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -370,7 +370,6 @@ client/util/util-gravatar.vala client/util/util-gtk.vala client/util/util-international.vala client/util/util-webkit.vala -client/util/util-yorba-application.vala ) set(CONSOLE_SRC @@ -479,7 +478,6 @@ pkg_check_modules(DEPS REQUIRED gio-2.0>=2.28.0 gtk+-3.0>=3.6.0 gee-0.8>=0.8.5 - unique-3.0>=3.0.0 libnotify>=0.7.5 libcanberra>=0.28 sqlite3>=3.7.4 @@ -490,7 +488,7 @@ pkg_check_modules(DEPS REQUIRED ) set(ENGINE_PACKAGES - glib-2.0 gee-0.8 gio-2.0 gmime-2.6 unique-3.0 posix sqlite3 libxml-2.0 + glib-2.0 gee-0.8 gio-2.0 gmime-2.6 posix sqlite3 libxml-2.0 ) set(CLIENT_PACKAGES diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 54f2e51b..a43e3e0f 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -9,10 +9,12 @@ extern const string _VERSION; extern const string _INSTALL_PREFIX; extern const string _GSETTINGS_DIR; extern const string _SOURCE_ROOT_DIR; +extern const string GETTEXT_PACKAGE; -public class GearyApplication : YorbaApplication { +public class GearyApplication : Gtk.Application { public const string NAME = "Geary"; public const string PRGNAME = "geary"; + public const string APP_ID = "org.yorba.geary"; public const string DESCRIPTION = DESKTOP_GENERIC_NAME; public const string COPYRIGHT = _("Copyright 2011-2013 Yorba Foundation"); public const string WEBSITE = "http://www.yorba.org"; @@ -44,30 +46,49 @@ public class GearyApplication : YorbaApplication { }; public const string LICENSE = """ -Geary is free software; you can redistribute it and/or modify it under the -terms of the GNU Lesser General Public License as published by the Free -Software Foundation; either version 2.1 of the License, or (at your option) +Geary is free software; you can redistribute it and/or modify it under the +terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) any later version. -Geary is distributed in the hope that it will be useful, but WITHOUT +Geary is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. -You should have received a copy of the GNU Lesser General Public License -along with Geary; if not, write to the Free Software Foundation, Inc., +You should have received a copy of the GNU Lesser General Public License +along with Geary; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """; - public static GearyApplication instance { + private static const string ACTION_ENTRY_COMPOSE = "compose"; + + public static const ActionEntry[] action_entries = { + {ACTION_ENTRY_COMPOSE, activate_compose, "s"}, + }; + + public static GearyApplication instance { get { return _instance; } - private set { + private set { // Ensure singleton behavior. assert (_instance == null); _instance = value; } } + /** + * Signal that is activated when 'exit' is called, but before the application actually exits. + * + * To cancel an exit, a callback should return GearyApplication.cancel_exit(). To procede with + * an exit, a callback should return true. + */ + public virtual signal bool exiting(bool panicked) { + controller.close(); + Date.terminate(); + + return true; + } + public GearyController controller { get; private set; default = new GearyController(); } public Gtk.ActionGroup actions { @@ -82,50 +103,84 @@ along with Geary; if not, write to the Free Software Foundation, Inc., private static GearyApplication _instance = null; + private string bin; private File exec_dir; + private bool exiting_fired = false; + private int exitcode = 0; + public GearyApplication() { - base (NAME, PRGNAME, "org.yorba.geary"); + Object(application_id: APP_ID); _instance = this; } - public override int startup() { - exec_dir = (File.new_for_path(Posix.realpath(Environment.find_program_in_path(args[0])))).get_parent(); + // 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(); - Geary.Logging.init(); - Configuration.init(is_installed(), GSETTINGS_DIR); - Date.init(); - WebKit.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER); + try { + register(); + } catch (Error e) { + error("Error registering GearyApplication: %s", e.message); + } - int ec = base.startup(); - if (ec != 0) - return ec; + Args.parse(args); - return Args.parse(args); - } - - public override bool exiting(bool panicked) { - controller.close(); - Date.terminate(); + activate(); + foreach (unowned string arg in args) { + if (arg != null && arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) + activate_action(ACTION_ENTRY_COMPOSE, new Variant.string(arg)); + } + exit_status = 0; return true; } - public override void activate(string[] args) { - do_activate_async.begin(args); + public override void startup() { + Configuration.init(is_installed(), GSETTINGS_DIR); + + Environment.set_application_name(NAME); + Environment.set_prgname(PRGNAME); + International.init(GETTEXT_PACKAGE, bin); + + Geary.Logging.init(); + Date.init(); + WebKit.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER); + + base.startup(); + + add_action_entries(action_entries, this); } - - // Without owned on the args parameter, vala won't bother to keep the array - // around until the open_async() call completes, leading to crashes. This - // way, this method gets its own long-lived copy. - private async void do_activate_async(owned 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); + + public override void activate() { + base.activate(); + + if (!present()) + create_async.begin(); + } + + public void activate_compose(SimpleAction action, Variant? param) { + if (param == null) return; - } + + compose(param.get_string()); + } + + public bool present() { + if (controller == null || controller.main_window == null) + return false; + + controller.main_window.present(); + return true; + } + + private async void create_async() { + // Manually keep the main loop around for the duration of this call. + // Without this, the main loop will exit as soon as we hit the yield + // below, before we create the main window. + hold(); // do *after* parsing args, as they dicate where logging is sent to, if anywhere, and only // after activate (which means this is only logged for the one user-visible instance, not @@ -133,10 +188,18 @@ along with Geary; if not, write to the Free Software Foundation, Inc., 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(); + config = new Configuration(APP_ID); yield controller.open_async(); - handle_args(args); + release(); + } + + public bool compose(string mailto) { + if (controller == null) + return false; + + controller.compose_mailto(mailto); + return true; } // NOTE: This assert()'s if the Gtk.Action is not present in the default action group @@ -241,12 +304,45 @@ along with Geary; if not, write to the Free Software Foundation, Inc., load_ui_file_for_manager(ui_manager, ui_filename); } - private void handle_args(string[] args) { - foreach(string arg in args) { - if (arg.has_prefix(Geary.ComposedEmail.MAILTO_SCHEME)) { - controller.compose_mailto(arg); - } + // This call will fire "exiting" only if it's not already been fired. + public void exit(int exitcode = 0) { + if (exiting_fired) + return; + + this.exitcode = exitcode; + + exiting_fired = true; + if (!exiting(false)) { + exiting_fired = false; + this.exitcode = 0; + + return; } + + if (Gtk.main_level() > 0) + Gtk.main_quit(); + else + Posix.exit(exitcode); + } + + /** + * A callback for GearyApplication.exiting should return cancel_exit() to prevent the + * application from exiting. + */ + public bool cancel_exit() { + Signal.stop_emission_by_name(this, "exiting"); + return false; + } + + // This call will fire "exiting" only if it's not already been fired and halt the application + // in its tracks. + public void panic() { + if (!exiting_fired) { + exiting_fired = true; + exiting(true); + } + + Posix.exit(1); } } diff --git a/src/client/application/geary-config.vala b/src/client/application/geary-config.vala index 5351fa86..b7dc253b 100644 --- a/src/client/application/geary-config.vala +++ b/src/client/application/geary-config.vala @@ -115,9 +115,9 @@ public class Configuration { } // Creates a configuration object. - public Configuration() { + public Configuration(string schema_id) { // Start GSettings. - settings = new Settings("org.yorba.geary"); + settings = new Settings(schema_id); gnome_interface = new Settings("org.gnome.desktop.interface"); foreach(string schema in GLib.Settings.list_schemas()) { if (schema == "com.canonical.indicator.datetime") { diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index b15b4883..af171d63 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -161,7 +161,7 @@ public class GearyController : Geary.BaseObject { upgrade_dialog.notify[UpgradeDialog.PROP_VISIBLE_NAME].connect(display_main_window_if_ready); // Create the main window (must be done after creating actions.) - main_window = new MainWindow(); + main_window = new MainWindow(GearyApplication.instance); main_window.notify["has-toplevel-focus"].connect(on_has_toplevel_focus); enable_message_buttons(false); @@ -1184,8 +1184,8 @@ public class GearyController : Geary.BaseObject { } // We need to include the second parameter, or valac doesn't recognize the function as matching - // YorbaApplication.exiting's signature. - private bool on_application_exiting(YorbaApplication sender, bool panicked) { + // GearyApplication.exiting's signature. + private bool on_application_exiting(GearyApplication sender, bool panicked) { if (close_composition_windows()) return true; diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala index 4f29e9f1..3880c221 100644 --- a/src/client/components/main-window.vala +++ b/src/client/components/main-window.vala @@ -4,7 +4,7 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ -public class MainWindow : Gtk.Window { +public class MainWindow : Gtk.ApplicationWindow { private const int MESSAGE_LIST_WIDTH = 250; private const int FOLDER_LIST_WIDTH = 100; private const int STATUS_BAR_HEIGHT = 18; @@ -28,7 +28,9 @@ public class MainWindow : Gtk.Window { private Geary.AggregateProgressMonitor progress_monitor = new Geary.AggregateProgressMonitor(); private Geary.ProgressMonitor? conversation_monitor_progress = null; - public MainWindow() { + public MainWindow(GearyApplication application) { + Object(application: application); + title = GearyApplication.NAME; conversation_list_view = new ConversationListView(conversation_list_store); diff --git a/src/client/notification/libnotify.vala b/src/client/notification/libnotify.vala index 4da8a5a0..421b8e9b 100644 --- a/src/client/notification/libnotify.vala +++ b/src/client/notification/libnotify.vala @@ -57,7 +57,7 @@ public class Libnotify : Geary.BaseObject { private void on_default_action(Notify.Notification notification, string action) { invoked(folder, email); - GearyApplication.instance.activate(new string[0]); + GearyApplication.instance.activate(); } private void notify_new_mail(Geary.Folder folder, int added) { diff --git a/src/client/util/util-yorba-application.vala b/src/client/util/util-yorba-application.vala deleted file mode 100644 index ffab17f8..00000000 --- a/src/client/util/util-yorba-application.vala +++ /dev/null @@ -1,189 +0,0 @@ -/* Copyright 2011-2013 Yorba Foundation - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * YorbaApplication is a poor man's lookalike of GNOME 3's GApplication, with a couple of additions. - * The idea is to ease a future migration to GTK 3 once some outstanding problems with Gtk.Application - * are resolved. - * - * For more information about why we built this class intead of using Gtk.Application, see the - * following ticket: - * http://redmine.yorba.org/issues/4266 - */ - -extern const string GETTEXT_PACKAGE; - -public abstract class YorbaApplication { - - public bool registered { get; private set; } - public string[]? args { get; private set; } - - private string app_id; - private bool running = false; - private bool exiting_fired = false; - private int exitcode = 0; - private Unique.App? unique_app = null; - - /** - * This signal is fired only when the application is starting the first time, not on - * subsequent activations (i.e. the application is launched while running by the user). - * - * The args[] array will be available when this signal is fired. - */ - public virtual signal int startup() { - unowned string[] a = args; - Gtk.init(ref a); - - // Sanitize the args. Gtk's init function will leave null elements - // in the array, which then causes OptionContext to crash. - // See ticket: https://bugzilla.gnome.org/show_bug.cgi?id=674837 - string[] fixed_args = new string[0]; - for (int i = 0; i < args.length; i++) { - if (args[i] != null) - fixed_args += args[i]; - } - args = fixed_args; - - return 0; - } - - public virtual signal void activate(string[] args) { - } - - /** - * Signal that is activated when 'exit' is called, but before the application actually exits. - * - * To cancel an exit, a callback should return YorbaApplication.cancel_exit(). To procede with - * an exit, a callback should return true. - */ - public virtual signal bool exiting(bool panicked) { - return true; - } - - /** - * application_title is a localized name of the application. program_name is non-localized - * and used by the system. app_id is a CORBA-esque program identifier. - * - * Only one YorbaApplication instance may be created in an program. - */ - public YorbaApplication(string application_title, string program_name, string app_id) { - this.app_id = app_id; - - Environment.set_application_name(application_title); - Environment.set_prgname(program_name); - } - - public bool register(Cancellable? cancellable = null) throws Error { - if (registered) - return false; - - unique_app = new Unique.App(app_id, null); - unique_app.message_received.connect(on_unique_app_message); - - // If app already running, activate it and exit - if (unique_app.is_running()) { - 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; - } - - registered = true; - - return true; - } - - private Unique.Response on_unique_app_message(Unique.App app, int command, - Unique.MessageData data, uint timestamp) { - switch (command) { - case Unique.Command.ACTIVATE: - activate(data.get_text().split(", ")); - break; - - default: - return Unique.Response.PASSTHROUGH; - } - - return Unique.Response.OK; - } - - public void add_window(Gtk.Window window) { - unique_app.watch_window(window); - } - - public int run(string[] args) { - if (running) - error("run() called twice."); - - this.args = args; - International.init(GETTEXT_PACKAGE, args[0]); - - running = true; - exitcode = startup(); - if (exitcode != 0) - return exitcode; - - try { - if (!register()) { - return exitcode; - } - } catch (Error e) { - error("Unable to register application: %s", e.message); - } - - activate(args); - - // enter the main loop - if (exitcode == 0) - Gtk.main(); - - return exitcode; - } - - // This call will fire "exiting" only if it's not already been fired. - public void exit(int exitcode = 0) { - if (exiting_fired || !running) - return; - - this.exitcode = exitcode; - - exiting_fired = true; - if (!exiting(false)) { - exiting_fired = false; - this.exitcode = 0; - - return; - } - - if (Gtk.main_level() > 0) - Gtk.main_quit(); - else - Posix.exit(exitcode); - } - - /** - * A callback for GearyApplication.exiting should return cancel_exit() to prevent the - * application from exiting. - */ - public bool cancel_exit() { - Signal.stop_emission_by_name(this, "exiting"); - return false; - } - - // This call will fire "exiting" only if it's not already been fired and halt the application - // in its tracks. - public void panic() { - if (!exiting_fired) { - exiting_fired = true; - exiting(true); - } - - Posix.exit(1); - } -} -