Use GtkApplication

This ports the Geary application to use GtkApplication, and removes the
dependency on libunique.

Closes: bgo#714145
This commit is contained in:
Charles Lindsay 2013-12-06 16:43:11 -08:00
parent bed698cc08
commit 3eaa1dcb8f
8 changed files with 152 additions and 247 deletions

2
debian/control vendored
View file

@ -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),

View file

@ -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

View file

@ -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);
}
}

View file

@ -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") {

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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);
}
}