Use GtkApplication
This ports the Geary application to use GtkApplication, and removes the dependency on libunique. Closes: bgo#714145
This commit is contained in:
parent
bed698cc08
commit
3eaa1dcb8f
8 changed files with 152 additions and 247 deletions
2
debian/control
vendored
2
debian/control
vendored
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue