From 9d2b10530c7f0ba57ef5db23f7fe71da8aac0c10 Mon Sep 17 00:00:00 2001 From: Eric Gregory Date: Thu, 15 Sep 2011 12:19:39 -0700 Subject: [PATCH] 4028, 3700 login dialog and password persistence Adds a login dialog box Support for Glade UI files Gnome keyring for password storage Assumption of single Geary account (for now) --- src/client/geary-application.vala | 32 +++++- src/client/main.vala | 6 - src/client/ui/geary-login.vala | 63 +++++++++++ src/client/util/util-keyring.vala | 25 +++++ src/client/wscript_build | 9 +- src/common/common-yorba-application.vala | 21 +++- src/engine/api/geary-engine.vala | 16 +++ ui/login.glade | 136 +++++++++++++++++++++++ wscript | 10 +- 9 files changed, 302 insertions(+), 16 deletions(-) create mode 100644 src/client/ui/geary-login.vala create mode 100644 src/client/util/util-keyring.vala create mode 100644 ui/login.glade diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index bea18e91..c9163d6b 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -55,11 +55,38 @@ along with Geary; if not, write to the Free Software Foundation, Inc., base (NAME, "geary", "org.yorba.geary"); } - public override void startup() { + public override int startup() { config = new Configuration(YorbaApplication.instance.get_install_dir() != null, YorbaApplication.instance.get_exec_dir().get_child("build/src/client").get_path()); - Geary.Credentials cred = new Geary.Credentials("imap.gmail.com", args[1], args[2]); + // Get saved credentials. If not present, ask user. + string username = ""; + string? password = null; + try { + Gee.List accounts = Geary.Engine.get_usernames(); + if (accounts.size > 0) { + username = accounts.get(0); + password = keyring_get_password(username); + } + } catch (Error e) { + debug("Unable to fetch accounts. Error: %s", e.message); + } + + if (password == null) { + LoginDialog login = new LoginDialog(username); + login.show(); + if (login.get_response() == Gtk.ResponseType.OK) { + username = login.username; + password = login.password; + + // TODO: check credentials before saving password in keyring. + keyring_save_password(username, password); + } else { + return 1; + } + } + + Geary.Credentials cred = new Geary.Credentials("imap.gmail.com", username, password); try { account = Geary.Engine.open(cred); @@ -69,6 +96,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc., main_window.show_all(); main_window.start(account); + return 0; } public override void activate() { diff --git a/src/client/main.vala b/src/client/main.vala index c7ad9c5d..04444057 100644 --- a/src/client/main.vala +++ b/src/client/main.vala @@ -8,12 +8,6 @@ int main(string[] args) { // initialize GTK, which modifies the command-line arguments Gtk.init(ref args); - if (args.length != 3) { - stderr.printf("usage: geary \n"); - - return 0; - } - try { // if already registered, silently exit if (!GearyApplication.instance.register()) diff --git a/src/client/ui/geary-login.vala b/src/client/ui/geary-login.vala new file mode 100644 index 00000000..2004aab1 --- /dev/null +++ b/src/client/ui/geary-login.vala @@ -0,0 +1,63 @@ +/* Copyright 2011 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. + */ + +// Displays a dialog for collecting the user's login data. +public class LoginDialog { + private Gtk.Dialog dialog; + private Gtk.Entry entry_username; + private Gtk.Entry entry_password; + private Gtk.ResponseType response; + private Gtk.Button ok_button; + + public string username { get; private set; default = ""; } + public string password { get; private set; default = ""; } + + public LoginDialog(string default_username = "", string default_password = "") { + Gtk.Builder builder = YorbaApplication.instance.create_builder("login.glade"); + + dialog = builder.get_object("LoginDialog") as Gtk.Dialog; + dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG); + dialog.set_default_response(Gtk.ResponseType.OK); + + entry_username = builder.get_object("username") as Gtk.Entry; + entry_password = builder.get_object("password") as Gtk.Entry; + + entry_username.set_text(default_username); + entry_password.set_text(default_password); + + entry_username.changed.connect(on_changed); + entry_password.changed.connect(on_changed); + + dialog.add_action_widget(new Gtk.Button.from_stock(Gtk.Stock.CANCEL), Gtk.ResponseType.CANCEL); + ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK); + ok_button.can_default = true; + ok_button.sensitive = false; + dialog.add_action_widget(ok_button, Gtk.ResponseType.OK); + dialog.set_default_response(Gtk.ResponseType.OK); + } + + // Runs the dialog. + public void show() { + dialog.show_all(); + response = (Gtk.ResponseType) dialog.run(); + username = entry_username.text.strip(); + password = entry_password.text.strip(); + dialog.destroy(); + } + + // Call this after Show to get the response. Will either be OK or cancel. + public Gtk.ResponseType get_response() { + return response; + } + + private void on_changed() { + ok_button.sensitive = is_complete(); + } + + private bool is_complete() { + return entry_username.text.strip() != "" && entry_password.text.strip() != ""; + } +} diff --git a/src/client/util/util-keyring.vala b/src/client/util/util-keyring.vala new file mode 100644 index 00000000..32204d63 --- /dev/null +++ b/src/client/util/util-keyring.vala @@ -0,0 +1,25 @@ +/* Copyright 2011 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. + */ + +const string GEARY_USERNAME_PREFIX = "org.yorba.geary username:"; + +public static void keyring_save_password(string username, string password) { + string name = GEARY_USERNAME_PREFIX + username; + + GnomeKeyring.Result res = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD, null, + name, password, "user", name); + + assert(res == GnomeKeyring.Result.OK); +} + +// Returns the password for the given username, or null if not set. +public static string? keyring_get_password(string username) { + string password; + GnomeKeyring.Result res = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD, out password, + "user", GEARY_USERNAME_PREFIX + username); + + return res == GnomeKeyring.Result.OK ? password : null; +} diff --git a/src/client/wscript_build b/src/client/wscript_build index c988c18f..5eb88e1e 100644 --- a/src/client/wscript_build +++ b/src/client/wscript_build @@ -8,21 +8,24 @@ client_src = [ 'geary-config.vala', 'main.vala', +'ui/geary-login.vala', 'ui/folder-list-store.vala', 'ui/folder-list-view.vala', 'ui/main-window.vala', 'ui/message-buffer.vala', 'ui/message-list-store.vala', 'ui/message-list-view.vala', -'ui/message-viewer.vala' +'ui/message-viewer.vala', + +'util/util-keyring.vala' ] gsettings_schemas = [ 'org.yorba.geary.gschema.xml' ] -client_uselib = 'GLIB GEE GTK' -client_packages = [ 'gtk+-2.0', 'glib-2.0', 'gee-1.0' ] +client_uselib = 'GLIB GEE GTK GNOME-KEYRING' +client_packages = [ 'gtk+-2.0', 'glib-2.0', 'gee-1.0', 'gnome-keyring-1' ] app = bld.program( target = 'geary', diff --git a/src/common/common-yorba-application.vala b/src/common/common-yorba-application.vala index 176b3a76..ae7c8868 100644 --- a/src/common/common-yorba-application.vala +++ b/src/common/common-yorba-application.vala @@ -37,7 +37,8 @@ public abstract class YorbaApplication { * * The args[] array will be available when this signal is fired. */ - public virtual signal void startup() { + public virtual signal int startup() { + return 0; } public virtual signal void activate() { @@ -107,10 +108,11 @@ public abstract class YorbaApplication { exec_dir = (File.new_for_path(Environment.find_program_in_path(args[0]))).get_parent(); running = true; - startup(); + exitcode = startup(); // enter the main loop - Gtk.main(); + if (exitcode == 0) + Gtk.main(); return exitcode; } @@ -167,5 +169,18 @@ public abstract class YorbaApplication { File prefix_dir = File.new_for_path(PREFIX); return exec_dir.has_prefix(prefix_dir) ? prefix_dir : null; } + + // Creates a GTK builder given the filename of a UI file in the ui directory. + public Gtk.Builder create_builder(string ui_filename) { + Gtk.Builder builder = new Gtk.Builder(); + try { + builder.add_from_file(get_resource_directory().get_child("ui").get_child( + ui_filename).get_path()); + } catch(GLib.Error error) { + warning("Unable to create Gtk.Builder: %s".printf(error.message)); + } + + return builder; +} } diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala index a17e0502..5a7e891d 100644 --- a/src/engine/api/geary-engine.vala +++ b/src/engine/api/geary-engine.vala @@ -20,4 +20,20 @@ public class Geary.Engine { new Geary.Imap.Account(cred, Imap.ClientConnection.DEFAULT_PORT_TLS), new Geary.Sqlite.Account(cred)); } + + // Returns a list of usernames associated with Geary. + public static Gee.List get_usernames() throws Error { + Gee.ArrayList list = new Gee.ArrayList(); + + FileEnumerator enumerator = YorbaApplication.instance.get_user_data_directory(). + enumerate_children("standard::*", FileQueryInfoFlags.NONE); + + FileInfo? info = null; + while ((info = enumerator.next_file()) != null) { + if (info.get_file_type() == FileType.DIRECTORY) + list.add(info.get_name()); + } + + return list; + } } diff --git a/ui/login.glade b/ui/login.glade new file mode 100644 index 00000000..9a1e87ac --- /dev/null +++ b/ui/login.glade @@ -0,0 +1,136 @@ + + + + + + False + True + 5 + dialog + + + True + False + 2 + + + True + False + end + + + + + + + + + False + False + 6 + end + 0 + + + + + True + False + 12 + 12 + Welcome to Geary. + True + + + False + False + 1 + + + + + True + False + 2 + 2 + + + True + False + 0 + Username: + + + GTK_FILL + GTK_FILL + 6 + + + + + True + False + 0 + Password: + + + 1 + 2 + GTK_FILL + 6 + + + + + True + True + + True + 0 + False + False + True + True + + + 1 + 2 + GTK_FILL + GTK_FILL + 6 + 4 + + + + + True + True + False + + True + True + False + False + True + True + + + 1 + 2 + 1 + 2 + 6 + 4 + + + + + False + False + 2 + + + + + + diff --git a/wscript b/wscript index 72b8c3c6..49e2f65e 100644 --- a/wscript +++ b/wscript @@ -80,17 +80,23 @@ def configure(conf): atleast_version='2.4.14', mandatory=1, args='--cflags --libs') + + conf.check_cfg( + package='gnome-keyring-1', + uselib_store='GNOME-KEYRING', + atleast_version='2.32.0', + mandatory=1, + args='--cflags --libs') def build(bld): bld.add_post_fun(post_build) bld.env.append_value('CFLAGS', ['-O2', '-g', '-D_PREFIX="' + bld.env.PREFIX + '"']) bld.env.append_value('LINKFLAGS', ['-O2', '-g']) - bld.env.append_value('VALAFLAGS', ['-g', '--enable-checking', '--fatal-warnings']) + bld.env.append_value('VALAFLAGS', ['-g', '--enable-checking', '--fatal-warnings']) bld.recurse('src') - def post_build(bld): # Copy executable to root folder. geary_path = 'build/src/client/geary'