Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/Cards/UserCard.vala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class Greeter.UserCard : Greeter.BaseCard {
public LightDM.User lightdm_user { get; construct; }
public bool show_input { get; set; default = false; }
public bool is_24h { get; set; default = true; }
// TODO: In Gtk4 remove this gesture and move it to MainWindow
// TODO: In Gtk4 remove this gesture and move it to MainWindow
public Gtk.GestureMultiPress click_gesture { get; private set; }

private Pantheon.AccountsService greeter_act;
Expand All @@ -23,8 +23,14 @@ public class Greeter.UserCard : Greeter.BaseCard {

private SelectionCheck logged_in;

public UserCard (LightDM.User lightdm_user) {
Object (lightdm_user: lightdm_user);
public FPrintUtil fprint_util { get; construct; }
public signal void do_fprint_login (string username);

public UserCard (LightDM.User lightdm_user, FPrintUtil fprint_util) {
Object (
lightdm_user: lightdm_user,
fprint_util: fprint_util
);
}

construct {
Expand Down Expand Up @@ -160,6 +166,12 @@ public class Greeter.UserCard : Greeter.BaseCard {
password_entry.activate.connect (on_login);
login_button.clicked.connect (on_login);

fprint_util.verify_passed.connect (() => {
do_fprint_login (lightdm_user.name);
});

fprint_util.verify_failed.connect (wrong_credentials);

grab_focus.connect (password_entry.grab_focus_without_selecting);
}

Expand Down Expand Up @@ -319,7 +331,7 @@ public class Greeter.UserCard : Greeter.BaseCard {
settings.set_value ("xkb-options", options);
}

/*
/*
* When we get string typed settings from our settings daemon account service we might get a null value.
* In this case we reset the value to avoid criticals and unwanted behaviour.
*/
Expand Down Expand Up @@ -426,6 +438,12 @@ public class Greeter.UserCard : Greeter.BaseCard {
SettingsPortal.get_default ().prefers_color_scheme = greeter_act.prefers_color_scheme;
}

public override bool focus (Gtk.DirectionType direction) {
use_fingerprint = false;
fprint_util.stop ();
return base.focus (direction);
}

public override void wrong_credentials () {
password_entry.get_style_context ().add_class (Gtk.STYLE_CLASS_ERROR);
main_box.get_style_context ().add_class ("shake");
Expand Down
168 changes: 89 additions & 79 deletions src/FPrintUtils.vala
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,106 @@
* Authors: Corentin Noël <corentin@elementary.io>
*/

namespace Greeter.FPrintUtils {
public enum MessageText {
FPRINT_SWIPE,
FPRINT_SWIPE_AGAIN,
FPRINT_SWIPE_TOO_SHORT,
FPRINT_NOT_CENTERED,
FPRINT_REMOVE,
FPRINT_PLACE,
FPRINT_PLACE_AGAIN,
FPRINT_NO_MATCH,
FPRINT_TIMEOUT,
FPRINT_ERROR,
FAILED,
OTHER
}
[DBus (name = "net.reactivated.Fprint.Manager")]
public interface FPrintManager : GLib.Object {
public abstract ObjectPath get_default_device () throws GLib.Error;
public abstract ObjectPath[] get_devices () throws GLib.Error;
}

private inline string fprintd_message (string message) {
// TODO: Translate message using GLib.dgettext
return message;
}
[DBus (name = "net.reactivated.Fprint.Device")]
public interface FPrintDevice : GLib.Object {
public abstract string name { owned get; }
public abstract void claim (string username) throws GLib.Error;
public abstract void release () throws GLib.Error;
public abstract void verify_start (string finger) throws GLib.Error;
public abstract void verify_stop () throws GLib.Error;
public signal void verify_status (string result, bool done);
}

public class FPrintUtil : GLib.Object {
private FPrintManager manager;
private FPrintDevice[] devices = {};

public MessageText string_to_messagetext (string text) {
// Ideally this would query PAM and ask which module is currently active,
// but since we're running through LightDM we don't have that ability.
// There should at be a state machine to transition to and from the
// active module depending on the messages received. But, this is can go
// wrong quickly.
// The reason why this is needed is, for example, we can get the "An
// unknown error occurred" message from pam_fprintd, but we can get it
// from some other random module as well. You never know.
// Maybe it's worth adding some LightDM/PAM functionality for this?
// The PAM "feature" which makes it all tricky is that modules can send
// arbitrary messages to the stream and it's hard to analyze or keep track
// of them programmatically.
// Also, there doesn't seem to be a way to give the user a choice over
// which module he wants to use to authenticate (ie. maybe today I have
// a bandaid over my finger and I can't scan it so I have to wait for it
// time out, if I didn't disable that in the settings)
//
// These messages are taken from here:
// - https://gitlab.freedesktop.org/libfprint/fprintd/blob/master/pam/fingerprint-strings.h
// - https://gitlab.freedesktop.org/libfprint/fprintd/blob/master/pam/pam_fprintd.c
public signal void verify_passed ();
public signal void verify_failed ();

if (text == fprintd_message ("An unknown error occurred")) {
return MessageText.FPRINT_ERROR;
} else if (text == fprintd_message ("An unknown error occurred")) {
return MessageText.FPRINT_ERROR;
} else if (check_fprintd_string (text, "Swipe", "across")) {
return MessageText.FPRINT_SWIPE;
} else if (text == fprintd_message ("Swipe your finger again")) {
return MessageText.FPRINT_SWIPE_AGAIN;
} else if (text == fprintd_message ("Swipe was too short, try again")) {
return MessageText.FPRINT_SWIPE_TOO_SHORT;
} else if (text == fprintd_message ("Your finger was not centered, try swiping your finger again")) {
return MessageText.FPRINT_NOT_CENTERED;
} else if (text == fprintd_message ("Remove your finger, and try swiping your finger again")) {
return MessageText.FPRINT_REMOVE;
} else if (check_fprintd_string (text, "Place", "on")) {
return MessageText.FPRINT_PLACE;
} else if (text == fprintd_message ("Place your finger on the reader again")) {
return MessageText.FPRINT_PLACE_AGAIN;
} else if (text == fprintd_message ("Failed to match fingerprint")) {
return MessageText.FPRINT_NO_MATCH;
} else if (text == fprintd_message ("Verification timed out")) {
return MessageText.FPRINT_TIMEOUT;
} else if (text == "Login failed") {
return MessageText.FAILED;
public bool start (string username) {

bool any_started = false;

try {
manager = Bus.get_proxy_sync (
BusType.SYSTEM,
"net.reactivated.Fprint",
"/net/reactivated/Fprint/Manager",
DBusProxyFlags.NONE
);
} catch (Error e) {
debug (e.message);
return false;
}

return MessageText.OTHER;
}
ObjectPath[]? device_paths = null;

private bool check_fprintd_string (string text, string action, string position) {
const string[] FINGERS = {
"finger",
"left thumb", "left index finger", "left middle finger", "left ring finger", "left little finger",
"right thumb", "right index finger", "right middle finger", "right ring finger", "right little finger"
};
try {
device_paths = manager.get_devices ();
} catch (Error e) {
debug (e.message);
return false;
}

foreach (unowned string finger in FINGERS) {
// Place your finger on %s
var english_string = action.concat (" your ", finger, " ", position, " %s");
foreach (var device_path in device_paths) {
FPrintDevice device;
try {
device = GLib.Bus.get_proxy_sync (
GLib.BusType.SYSTEM,
"net.reactivated.Fprint",
device_path
);
} catch (Error e) {
debug (e.message);
continue;
}
devices += device;
}

if (text.has_prefix (fprintd_message (english_string)) ||
text.has_prefix (fprintd_message (english_string.printf ("the fingerprint reader")))) {
return true;
foreach (var device in devices) {
try {
device.claim (username);
device.verify_status.connect(status);
device.verify_start ("any");
any_started = true;
} catch (Error e) {
debug ("%s: %s", e.message, device.name);
}
}

return any_started;
}

public void stop () {
foreach (var device in devices) {
try {
device.release ();
} catch (Error e) {
debug ("%s: %s", e.message, device.name);
}
}
}

return false;
private void status (string result, bool done) {
if (result == "verify-match") {
foreach (var device in devices) {
try {
device.verify_stop ();
} catch (GLib.Error e) {
debug ("%s: %s", device.name, e.message);
}
}
stop ();
verify_passed ();
} else {
verify_failed ();
}
}
}
26 changes: 10 additions & 16 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
private int current_user_card_index = -1;
private unowned Greeter.BaseCard? current_card = null;

private FPrintUtil fprint_util;

private bool installer_mode = false;

private Gtk.EventControllerKey key_controller;
Expand All @@ -37,10 +39,11 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
gsettings = new GLib.Settings ("io.elementary.greeter");
settings = new Greeter.Settings ();

lightdm_greeter.show_message.connect (show_message);
lightdm_greeter.show_prompt.connect (show_prompt);
lightdm_greeter.authentication_complete.connect (authentication_complete);

fprint_util = new FPrintUtil ();

var guest_login_button = new Gtk.Button.with_label (_("Log in as Guest"));
var manual_login_button = new Gtk.ToggleButton.with_label (_("Manual Login…"));

Expand Down Expand Up @@ -264,20 +267,6 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
}
}

private void show_message (string text, LightDM.MessageType type) {
var messagetext = Greeter.FPrintUtils.string_to_messagetext (text);
switch (messagetext) {
case Greeter.FPrintUtils.MessageText.FPRINT_TIMEOUT:
case Greeter.FPrintUtils.MessageText.FPRINT_ERROR:
case Greeter.FPrintUtils.MessageText.OTHER:
current_card.use_fingerprint = false;
break;
default:
current_card.use_fingerprint = true;
break;
}
}

private void show_prompt (string text, LightDM.PromptType type = LightDM.PromptType.QUESTION) {
if (current_card is ManualCard) {
if (type == LightDM.PromptType.SECRET) {
Expand Down Expand Up @@ -362,6 +351,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
user_cards.head.foreach ((card) => {
if (card.lightdm_user.name == user_to_select) {
carousel.scroll_to (card);
card.use_fingerprint = fprint_util.start (card.lightdm_user.name);
user_selected = true;
}
});
Expand All @@ -370,6 +360,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
unowned var user_card = user_cards.peek_head ();
user_card.show_input = true;
carousel.scroll_to (user_card);
user_card.use_fingerprint = fprint_util.start (user_card.lightdm_user.name);
}
} else {
datetime_revealer.reveal_child = false;
Expand Down Expand Up @@ -400,9 +391,10 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
}

private void add_card (LightDM.User lightdm_user) {
var user_card = new Greeter.UserCard (lightdm_user);
var user_card = new Greeter.UserCard (lightdm_user, fprint_util);
user_card.show_all ();
user_card.do_connect.connect (do_connect);
user_card.do_fprint_login.connect (do_connect_username);
user_card.click_gesture.pressed.connect ((gesture, n_press, x, y) => {
assert (gesture.widget is UserCard);

Expand Down Expand Up @@ -512,6 +504,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
unowned Greeter.UserCard? next_card = user_cards.peek_nth (current_user_card_index - 1);
if (next_card != null) {
carousel.scroll_to (next_card);
next_card.use_fingerprint = fprint_util.start (next_card.lightdm_user.name);
}
}

Expand All @@ -523,6 +516,7 @@ public class Greeter.MainWindow : Gtk.ApplicationWindow {
unowned Greeter.UserCard? next_card = user_cards.peek_nth (current_user_card_index + 1);
if (next_card != null) {
carousel.scroll_to (next_card);
next_card.use_fingerprint = fprint_util.start (next_card.lightdm_user.name);
}
}
}
Loading