From 81ae315c1f85f45a374444ed8a35a58ce1b7d50b Mon Sep 17 00:00:00 2001 From: "Katja Ramona Sophie Kwast (zaphyra)" Date: Tue, 26 Aug 2025 11:14:52 +0200 Subject: [PATCH] `https://github.com/swaywm/swaylock/compare/master...SL-RU:swaylock-fprintd:fprintd adapted` for swaylock-plugin --- completions/bash/swaylock | 2 + completions/fish/swaylock.fish | 1 + completions/zsh/_swaylock | 1 + fingerprint/fingerprint.c | 251 +++++++++++++++++++++++++++++++++ fingerprint/fingerprint.h | 43 ++++++ fingerprint/meson.build | 33 +++++ include/swaylock.h | 3 + main.c | 32 ++++- meson.build | 2 + render.c | 2 + 10 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 fingerprint/fingerprint.c create mode 100644 fingerprint/fingerprint.h create mode 100644 fingerprint/meson.build diff --git a/completions/bash/swaylock b/completions/bash/swaylock index f8f411f..d13c679 100644 --- a/completions/bash/swaylock +++ b/completions/bash/swaylock @@ -14,6 +14,7 @@ _swaylock-plugin() -F -h -i + -p -k -K -L @@ -41,6 +42,7 @@ _swaylock-plugin() --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 320ed6b..4ed4e62 100644 --- a/completions/fish/swaylock.fish +++ b/completions/fish/swaylock.fish @@ -14,6 +14,7 @@ complete -c swaylock-plugin -l help -s h --description "Show h complete -c swaylock-plugin -l hide-keyboard-layout -s K --description "Hide the current xkb layout while typing." complete -c swaylock-plugin -l ignore-empty-password -s e --description "When an empty password is provided, do not validate it." complete -c swaylock-plugin -l image -s i --description "Display the given image, optionally only on the given output." +complete -c swaylock-plugin -l fingerprint -s p --description "Enable fingerprint scanning. Fprint is required." complete -c swaylock-plugin -l indicator-caps-lock -s l --description "Show the current Caps Lock state also on the indicator." complete -c swaylock-plugin -l indicator-idle-visible --description "Sets the indicator to show even if idle." complete -c swaylock-plugin -l indicator-radius --description "Sets the indicator radius." diff --git a/completions/zsh/_swaylock b/completions/zsh/_swaylock index 3854ced..1a659a0 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 0000000..4e9477a --- /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_auth_idle(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 0000000..d030a43 --- /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 0000000..2239952 --- /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 233c663..6d7bf6d 100644 --- a/include/swaylock.h +++ b/include/swaylock.h @@ -18,6 +18,7 @@ enum auth_state { AUTH_STATE_IDLE, // nothing happening AUTH_STATE_VALIDATING, // currently validating password AUTH_STATE_INVALID, // displaying message: password was wrong + AUTH_STATE_FINGERPRINT, }; // Indicator state: status of password buffer / typing letters @@ -74,6 +75,7 @@ struct swaylock_args { bool daemonize; int ready_fd; bool indicator_idle_visible; + bool fingerprint; char *plugin_command; bool plugin_per_output; /* negative values = no grace; unit: seconds */ @@ -304,6 +306,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 zxdg_output_manager_v1 *zxdg_output_manager; struct forward_state forward; struct swaylock_bg_server server; diff --git a/main.c b/main.c index 1300fc8..6cc33a5 100644 --- a/main.c +++ b/main.c @@ -29,6 +29,7 @@ #include "seat.h" #include "swaylock.h" #include "ext-session-lock-v1-client-protocol.h" +#include "fingerprint/fingerprint.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-server-protocol.h" #include "fullscreen-shell-unstable-v1-client-protocol.h" @@ -876,6 +877,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'}, @@ -955,6 +957,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 " @@ -1060,7 +1064,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; @@ -1104,6 +1108,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; @@ -2257,6 +2266,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) { /* Initially ignore SIGUSR1 + SIGUSR2 to prevent stray signals (like * `killall -SIGUSR2`) from affecting the pw backend subprocess */ @@ -2291,6 +2311,7 @@ int main(int argc, char **argv) { .hide_keyboard_layout = false, .show_failed_attempts = false, .indicator_idle_visible = false, + .fingerprint = false, .ready_fd = -1, .plugin_command = NULL, .grace_time = 0.0f, @@ -2608,6 +2629,12 @@ int main(int argc, char **argv) { sa2.sa_flags = 0; sigaction(SIGCHLD, &sa2, NULL); + 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; @@ -2624,6 +2651,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 e72aff7..6babe5c 100644 --- a/meson.build +++ b/meson.build @@ -116,6 +116,7 @@ if logind.found() endif subdir('include') +subdir('fingerprint') dependencies = [ cairo, @@ -125,6 +126,7 @@ dependencies = [ xkbcommon, wayland_client, wayland_server, + fingerprint, ] sources = [ diff --git a/render.c b/render.c index 5d48017..123847b 100644 --- a/render.c +++ b/render.c @@ -109,6 +109,8 @@ static bool render_frame(struct swaylock_surface *surface) { if (state->input_state == INPUT_STATE_CLEAR) { // This message has highest priority text = "Cleared"; + } else if (state->auth_state == AUTH_STATE_FINGERPRINT) { + text = state->fingerprint_msg; } else if (state->auth_state == AUTH_STATE_VALIDATING) { text = "Verifying"; } else if (state->auth_state == AUTH_STATE_INVALID) { -- 2.50.1