diff --git a/.build.yml b/.build.yml index 59bc722ef..c30451002 100644 --- a/.build.yml +++ b/.build.yml @@ -8,6 +8,9 @@ packages: - gdk-pixbuf-dev - linux-pam-dev - scdoc + - fprintd + - glib + - dbus sources: - https://github.com/swaywm/swaylock tasks: diff --git a/README.md b/README.md index d574ca542..d60be253e 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,13 @@ Install dependencies: * cairo * gdk-pixbuf2 \*\* * pam (optional) +* fprintd +* dbus \* +* glib \* * [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man pages) \* * git \* -_\* Compile-time dep_ +_\* Compile-time dep_ _\*\* Optional: required for background images other than PNG_ Run these commands: diff --git a/completions/bash/swaylock b/completions/bash/swaylock index 4985922af..0a8080ea3 100644 --- a/completions/bash/swaylock +++ b/completions/bash/swaylock @@ -14,6 +14,7 @@ _swaylock() -F -h -i + -p -k -K -L @@ -41,6 +42,7 @@ _swaylock() --hide-keyboard-layout --ignore-empty-password --image + --fingerprint --indicator-caps-lock --indicator-idle-visible --indicator-radius diff --git a/completions/fish/swaylock.fish b/completions/fish/swaylock.fish index 41416a393..337c23eb6 100644 --- a/completions/fish/swaylock.fish +++ b/completions/fish/swaylock.fish @@ -14,6 +14,7 @@ complete -c swaylock -l help -s h --description "Show help mes complete -c swaylock -l hide-keyboard-layout -s K --description "Hide the current xkb layout while typing." complete -c swaylock -l ignore-empty-password -s e --description "When an empty password is provided, do not validate it." complete -c swaylock -l image -s i --description "Display the given image, optionally only on the given output." +complete -c swaylock -l fingerprint p --description "Enable fingerprint scanning. Fprint is required." complete -c swaylock -l indicator-caps-lock -s l --description "Show the current Caps Lock state also on the indicator." complete -c swaylock -l indicator-idle-visible --description "Sets the indicator to show even if idle." complete -c swaylock -l indicator-radius --description "Sets the indicator radius." diff --git a/completions/zsh/_swaylock b/completions/zsh/_swaylock index 9e9f78866..91161d860 100644 --- a/completions/zsh/_swaylock +++ b/completions/zsh/_swaylock @@ -18,6 +18,7 @@ _arguments -s \ '(--hide-keyboard-layout -K)'{--hide-keyboard-layout,-K}'[Hide the current xkb layout while typing]' \ '(--ignore-empty-password -e)'{--ignore-empty-password,-e}'[When an empty password is provided, do not validate it]' \ '(--image -i)'{--image,-i}'[Display the given image, optionally only on the given output]:filename:_files' \ + '(--fingerprint -p)'{--fingerprint,-p}'[Enable fingerprint scanning. Fprint is required]' \ '(--indicator-caps-lock -l)'{--indicator-caps-lock,-l}'[Show the current Caps Lock state also on the indicator]' \ '(--indicator-idle-visible)'--indicator-idle-visible'[Sets the indicator to show even if idle]' \ '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \ diff --git a/fingerprint/fingerprint.c b/fingerprint/fingerprint.c new file mode 100644 index 000000000..ec214e4f9 --- /dev/null +++ b/fingerprint/fingerprint.c @@ -0,0 +1,251 @@ +/* + * Based on fprintd util to verify a fingerprint + * Copyright (C) 2008 Daniel Drake + * Copyright (C) 2020 Marco Trevisan + * Copyright (C) 2023 Alexandr Lutsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "fingerprint.h" +#include "log.h" + +static void display_message(struct FingerprintState *state, const char *fmt, ...) { + va_list(args); + va_start(args, fmt); + vsnprintf(state->status, sizeof(state->status), fmt, args); + va_end(args); + + state->sw_state->auth_state = AUTH_STATE_FINGERPRINT; + state->sw_state->fingerprint_msg = state->status; + damage_state(state->sw_state); + schedule_indicator_clear(state->sw_state); +} + +static void create_manager(struct FingerprintState *state) { + g_autoptr(GError) error = NULL; + state->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); + if (state->connection == NULL) { + swaylock_log(LOG_ERROR, "Failed to connect to session bus: %s", error->message); + display_message(state, "Fingerprint error"); + return; + } + + state->manager = fprint_dbus_manager_proxy_new_sync( + state->connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + "/net/reactivated/Fprint/Manager", + NULL, &error); + if (state->manager == NULL) { + swaylock_log(LOG_ERROR, "Failed to get Fprintd manager: %s", error->message); + display_message(state, "Fingerprint error"); + return; + } + + swaylock_log(LOG_DEBUG, "FPrint manager created"); +} + +static void open_device(struct FingerprintState *state) { + state->device = NULL; + g_autoptr(FprintDBusDevice) dev = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *path = NULL; + if (!fprint_dbus_manager_call_get_default_device_sync(state->manager, &path, NULL, &error)) { + swaylock_log(LOG_ERROR, "Impossible to verify: %s", error->message); + display_message(state, "Fingerprint error"); + return; + } + + swaylock_log(LOG_DEBUG, "Fingerprint: using device %s", path); + dev = fprint_dbus_device_proxy_new_sync( + state->connection, + G_DBUS_PROXY_FLAGS_NONE, + "net.reactivated.Fprint", + path, NULL, &error); + if (error) { + swaylock_log(LOG_ERROR, "failed to connect to device: %s", error->message); + display_message(state, "Fingerprint error"); + return; + } + + if (!fprint_dbus_device_call_claim_sync(dev, "", NULL, &error)) { + // we need to wait while device can be claimed + swaylock_log(LOG_DEBUG, "failed to claim the device: %s(%d)", error->message, error->code); + return; + } + + swaylock_log(LOG_DEBUG, "FPrint device opened %s", path); + state->device = g_steal_pointer (&dev); +} + +static void verify_result(GObject *object, const char *result, gboolean done, void *user_data) { + struct FingerprintState *state = user_data; + swaylock_log(LOG_INFO, "Verify result: %s (%s)", result, done ? "done" : "not done"); + state->match = g_str_equal (result, "verify-match"); + if (g_str_equal (result, "verify-retry-scan")) { + display_message(state, "Retry"); + return; + } else if(g_str_equal (result, "verify-swipe-too-short")) { + display_message(state, "Retry, too short"); + return; + } else if(g_str_equal (result, "verify-finger-not-centered")) { + display_message(state, "Retry, not centered"); + return; + } else if(g_str_equal (result, "verify-remove-and-retry")) { + display_message(state, "Remove and retry"); + return; + } + + if(state->match) { + display_message(state, "Fingerprint OK"); + } else { + display_message(state, "Retry"); + } + + state->completed = TRUE; + g_autoptr(GError) error = NULL; + if (!fprint_dbus_device_call_verify_stop_sync(state->device, NULL, &error)) { + swaylock_log(LOG_ERROR, "VerifyStop failed: %s", error->message); + display_message(state, "Fingerprint error"); + return; + } +} + +static void verify_started_cb(GObject *obj, GAsyncResult *res, gpointer user_data) { + struct FingerprintState *state = user_data; + if (!fprint_dbus_device_call_verify_start_finish(FPRINT_DBUS_DEVICE(obj), res, &state->error)) { + return; + } + + swaylock_log(LOG_DEBUG, "Verify started!"); + state->started = TRUE; +} + +static void proxy_signal_cb(GDBusProxy *proxy, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + struct FingerprintState *state = user_data; + if (!state->started) { + return; + } + + if (!g_str_equal(signal_name, "VerifyStatus")) { + return; + } + + const gchar *result; + gboolean done; + g_variant_get(parameters, "(&sb)", &result, &done); + verify_result(G_OBJECT (proxy), result, done, user_data); +} + +static void start_verify(struct FingerprintState *state) { + /* This one is funny. We connect to the signal immediately to avoid + * race conditions. However, we must ignore any authentication results + * that happen before our start call returns. + * This is because the verify call itself may internally try to verify + * against fprintd (possibly using a separate account). + * + * To do so, we *must* use the async version of the verify call, as the + * sync version would cause the signals to be queued and only processed + * after it returns. + */ + fprint_dbus_device_call_verify_start(state->device, "any", NULL, + verify_started_cb, + state); + + /* Wait for verify start while discarding any VerifyStatus signals */ + while (!state->started && !state->error) { + g_main_context_iteration(NULL, TRUE); + } + + if (state->error) { + swaylock_log(LOG_ERROR, "VerifyStart failed: %s", state->error->message); + display_message(state, "Fingerprint error"); + g_clear_error(&state->error); + return; + } +} + +static void release_callback(GObject *source_object, GAsyncResult *res, + gpointer user_data) { +} + +void fingerprint_init(struct FingerprintState *fingerprint_state, + struct swaylock_state *swaylock_state) { + memset(fingerprint_state, 0, sizeof(struct FingerprintState)); + fingerprint_state->sw_state = swaylock_state; + create_manager(fingerprint_state); + if(fingerprint_state->manager == NULL || fingerprint_state->connection == NULL) { + return; + } +} + +int fingerprint_verify(struct FingerprintState *fingerprint_state) { + /* VerifyStatus signals are processing, do not wait for completion. */ + g_main_context_iteration (NULL, FALSE); + if (fingerprint_state->manager == NULL || + fingerprint_state->connection == NULL) { + return false; + } + + if (fingerprint_state->device == NULL) { + open_device(fingerprint_state); + if (fingerprint_state->device == NULL) { + return false; + } + + g_signal_connect (fingerprint_state->device, "g-signal", G_CALLBACK (proxy_signal_cb), + fingerprint_state); + start_verify(fingerprint_state); + } + + + + if (!fingerprint_state->completed) { + return false; + } + + if (!fingerprint_state->match) { + fingerprint_state->completed = 0; + fingerprint_state->match = 0; + start_verify(fingerprint_state); + return false; + } + + return true; +} + +void fingerprint_deinit(struct FingerprintState *fingerprint_state) { + if (!fingerprint_state->device) { + return; + } + + g_signal_handlers_disconnect_by_func(fingerprint_state->device, proxy_signal_cb, + fingerprint_state); + fprint_dbus_device_call_release(fingerprint_state->device, NULL, release_callback, NULL); + fingerprint_state->device = NULL; +} diff --git a/fingerprint/fingerprint.h b/fingerprint/fingerprint.h new file mode 100644 index 000000000..d030a43cf --- /dev/null +++ b/fingerprint/fingerprint.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Alexandr Lutsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FINGERPRINT_H +#define _FINGERPRINT_H + +#include "swaylock.h" +#include "fingerprint/fprintd-dbus.h" + +struct FingerprintState { + GError *error; + gboolean started; + gboolean completed; + gboolean match; + + char status[128]; + + FprintDBusManager *manager; + GDBusConnection *connection; + FprintDBusDevice *device; + struct swaylock_state *sw_state; +}; + +void fingerprint_init(struct FingerprintState *fingerprint_state, struct swaylock_state *state); +int fingerprint_verify(struct FingerprintState *fingerprint_state); +void fingerprint_deinit(struct FingerprintState *fingerprint_state); + +#endif \ No newline at end of file diff --git a/fingerprint/meson.build b/fingerprint/meson.build new file mode 100644 index 000000000..22399521c --- /dev/null +++ b/fingerprint/meson.build @@ -0,0 +1,33 @@ +gnome = import('gnome') + +dbus = dependency('dbus-1') +glib = dependency('glib-2.0', version: '>=2.64.0') +gio_dep = dependency('gio-2.0') + +fprintd_dbus_interfaces = files( + '/usr/share/dbus-1/interfaces/net.reactivated.Fprint.Manager.xml', + '/usr/share/dbus-1/interfaces/net.reactivated.Fprint.Device.xml', +) + +fprintd_dbus_sources = gnome.gdbus_codegen('fprintd-dbus', + sources: fprintd_dbus_interfaces, + autocleanup: 'all', + interface_prefix: 'net.reactivated.Fprint.', + namespace: 'FprintDBus', + object_manager: true, +) + +fingerprint = declare_dependency( + include_directories: [ + include_directories('..'), + ], + sources: [ + fprintd_dbus_sources, + 'fingerprint.c' , + ], + dependencies: [ + gio_dep, + glib, + dbus + ], +) \ No newline at end of file diff --git a/include/swaylock.h b/include/swaylock.h index 9de8ab688..d1884c89c 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -16,6 +16,7 @@ enum auth_state { AUTH_STATE_BACKSPACE, AUTH_STATE_VALIDATING, AUTH_STATE_INVALID, + AUTH_STATE_FINGERPRINT, }; struct swaylock_colorset { @@ -63,6 +64,7 @@ struct swaylock_args { bool daemonize; int ready_fd; bool indicator_idle_visible; + bool fingerprint; }; struct swaylock_password { @@ -91,6 +93,7 @@ struct swaylock_state { bool run_display, locked; struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; struct ext_session_lock_v1 *ext_session_lock_v1; + char *fingerprint_msg; }; struct swaylock_surface { diff --git a/main.c b/main.c index 26d30a52e..1f95113ba 100644 --- a/main.c +++ b/main.c @@ -26,6 +26,7 @@ #include "seat.h" #include "swaylock.h" #include "ext-session-lock-v1-client-protocol.h" +#include "fingerprint/fingerprint.h" static uint32_t parse_color(const char *color) { if (color[0] == '#') { @@ -529,6 +530,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, {"disable-caps-lock-text", no_argument, NULL, 'L'}, {"indicator-caps-lock", no_argument, NULL, 'l'}, {"line-uses-inside", no_argument, NULL, 'n'}, + {"fingerprint", no_argument, NULL, 'p'}, {"line-uses-ring", no_argument, NULL, 'r'}, {"scaling", required_argument, NULL, 's'}, {"tiling", no_argument, NULL, 't'}, @@ -594,9 +596,9 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, "File descriptor to send readiness notifications to.\n" " -h, --help " "Show help message and quit.\n" - " -i, --image [[]:] " + " -i, --image [[]:] " "Display the given image, optionally only on the given output.\n" - " -k, --show-keyboard-layout " + " -k, --show-keyboard-layout " "Display the current xkb layout while typing.\n" " -K, --hide-keyboard-layout " "Hide the current xkb layout while typing.\n" @@ -604,6 +606,8 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, "Disable the Caps Lock text.\n" " -l, --indicator-caps-lock " "Show the current Caps Lock state also on the indicator.\n" + " -p, --fingerprint " + "Enable fingerprint scanning. Fprint is required.\n" " -s, --scaling " "Image scaling mode: stretch, fill, fit, center, tile, solid_color.\n" " -t, --tiling " @@ -701,7 +705,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, optind = 1; while (1) { int opt_idx = 0; - c = getopt_long(argc, argv, "c:deFfhi:kKLlnrs:tuvC:R:", long_options, + c = getopt_long(argc, argv, "c:deFfhi:kKLlnprs:tuvC:R:", long_options, &opt_idx); if (c == -1) { break; @@ -745,6 +749,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, load_image(optarg, state); } break; + case 'p': + if(state) { + state->args.fingerprint = true; + } + break; case 'k': if (state) { state->args.show_keyboard_layout = true; @@ -1110,6 +1119,17 @@ void log_init(int argc, char **argv) { swaylock_log_init(LOG_ERROR); } +static void check_fingerprint(void *d) { + struct FingerprintState *fingerprint_state = d; + if (fingerprint_verify(fingerprint_state)) { + do_sigusr(1); + } else { + (void)write(sigusr_fds[1], NULL, 0); + } + + loop_add_timer(state.eventloop, 300, check_fingerprint, fingerprint_state); +} + int main(int argc, char **argv) { log_init(argc, argv); initialize_pw_backend(argc, argv); @@ -1135,6 +1155,7 @@ int main(int argc, char **argv) { .hide_keyboard_layout = false, .show_failed_attempts = false, .indicator_idle_visible = false, + .fingerprint = false, .ready_fd = -1, }; wl_list_init(&state.images); @@ -1270,6 +1291,12 @@ int main(int argc, char **argv) { loop_add_fd(state.eventloop, sigusr_fds[0], POLLIN, term_in, NULL); signal(SIGUSR1, do_sigusr); + struct FingerprintState fingerprint_state; + if(state.args.fingerprint) { + fingerprint_init(&fingerprint_state, &state); + loop_add_timer(state.eventloop, 100, check_fingerprint, &fingerprint_state); + } + state.run_display = true; while (state.run_display) { errno = 0; @@ -1282,6 +1309,9 @@ int main(int argc, char **argv) { ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1); wl_display_roundtrip(state.display); + if(state.args.fingerprint) { + fingerprint_deinit(&fingerprint_state); + } free(state.args.font); cairo_destroy(state.test_cairo); cairo_surface_destroy(state.test_surface); diff --git a/meson.build b/meson.build index ad7c4c790..75304d6e7 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ conf_data.set_quoted('SWAYLOCK_VERSION', version) conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) subdir('include') +subdir('fingerprint') dependencies = [ cairo, @@ -91,6 +92,7 @@ dependencies = [ rt, xkbcommon, wayland_client, + fingerprint ] sources = [ diff --git a/render.c b/render.c index 3ce7a8075..540f6c0c0 100644 --- a/render.c +++ b/render.c @@ -101,6 +101,9 @@ void render_frame(struct swaylock_surface *surface) { case AUTH_STATE_VALIDATING: text = "Verifying"; break; + case AUTH_STATE_FINGERPRINT: + text = state->fingerprint_msg; + break; case AUTH_STATE_INVALID: text = "Wrong"; break;