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)
This commit is contained in:
Eric Gregory 2011-09-15 12:19:39 -07:00
parent 6a5d1fa048
commit 9d2b10530c
9 changed files with 302 additions and 16 deletions

View file

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

View file

@ -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 <user> <pass>\n");
return 0;
}
try {
// if already registered, silently exit
if (!GearyApplication.instance.register())

View file

@ -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() != "";
}
}

View file

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

View file

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

View file

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

View file

@ -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<string> get_usernames() throws Error {
Gee.ArrayList<string> list = new Gee.ArrayList<string>();
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;
}
}

136
ui/login.glade Normal file
View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkDialog" id="LoginDialog">
<property name="can_focus">False</property>
<property name="can_default">True</property>
<property name="border_width">5</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="action area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">6</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="welcome">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xpad">12</property>
<property name="ypad">12</property>
<property name="label" translatable="yes">Welcome to Geary.</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label: username">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Username:</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
<property name="x_padding">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label: password">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Password:</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="x_padding">6</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="username">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="width_chars">0</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
<property name="x_padding">6</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="password">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_padding">6</property>
<property name="y_padding">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

10
wscript
View file

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