From a60a48516ad9ea43b0fdd83d215ef79b9b82f413 Mon Sep 17 00:00:00 2001 From: trigg Date: Sat, 7 Mar 2026 16:28:39 +0000 Subject: [PATCH 1/8] locker: fingerprint and password shake when failing to auth --- data/css/default.css | 30 ++++++++++++++++++ src/locker/plugin.hpp | 2 ++ src/locker/plugin/fingerprint.cpp | 9 ++++++ src/locker/plugin/fingerprint.hpp | 1 + src/locker/plugin/password.cpp | 52 ++++++++++++++++++++----------- src/locker/plugin/password.hpp | 10 +++--- src/locker/timedrevealer.cpp | 22 +++++++++++++ src/locker/timedrevealer.hpp | 2 ++ 8 files changed, 106 insertions(+), 22 deletions(-) diff --git a/data/css/default.css b/data/css/default.css index 2f0415d6..64af64dd 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -241,6 +241,14 @@ text-shadow: 1px 1px 0 var(--bcol), padding-bottom: 75px; } +.wf-locker .failure { + animation-name: shake; + animation-duration:0.3s; + animation-timing-function: linear; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + .wf-locker.sized-480 { font-size:6px; } @@ -292,3 +300,25 @@ text-shadow: 1px 1px 0 var(--bcol), padding-right: 0rem; } } + +@keyframes shake { + 0% { + transform: translateX(0px); + } + 20% { + transform: translateX(20px); + } + 40% { + transform: translateX(-20px); + } + 60% { + transform: translateX(20px); + } + 80% { + transform: translateX(-20px); + } + 100% { + transform: translateX(0px); + } +} + diff --git a/src/locker/plugin.hpp b/src/locker/plugin.hpp index 1a9687f5..bd158bbe 100644 --- a/src/locker/plugin.hpp +++ b/src/locker/plugin.hpp @@ -26,5 +26,7 @@ class WayfireLockerPlugin virtual void deinit() = 0; /* Called after lockscreen unlocked. */ virtual void lockout_changed(bool lockout) {} /* Called when too many failed logins have occured */ + virtual void failure() + {} virtual ~WayfireLockerPlugin() = default; }; diff --git a/src/locker/plugin/fingerprint.cpp b/src/locker/plugin/fingerprint.cpp index 53876e0c..41bbf6c3 100644 --- a/src/locker/plugin/fingerprint.cpp +++ b/src/locker/plugin/fingerprint.cpp @@ -151,6 +151,7 @@ void WayfireLockerFingerprintPlugin::start_fingerprint_scanning() { std::cout << "No match" << std::endl; show(); + failure(); update("Invalid fingerprint", "dialog-error-symbolic", "bad"); stop_fingerprint_scanning(); WayfireLockerApp::get().recieved_bad_auth(); @@ -411,6 +412,14 @@ void WayfireLockerFingerprintPlugin::update(std::string label, std::string image } } +void WayfireLockerFingerprintPlugin::failure() +{ + for (auto & it : widgets) + { + it.second->failure(); + } +} + void WayfireLockerFingerprintPlugin::hide() { show_state = false; diff --git a/src/locker/plugin/fingerprint.hpp b/src/locker/plugin/fingerprint.hpp index 58245fa1..344bcb4c 100644 --- a/src/locker/plugin/fingerprint.hpp +++ b/src/locker/plugin/fingerprint.hpp @@ -49,6 +49,7 @@ class WayfireLockerFingerprintPlugin : public WayfireLockerPlugin void init() override; void deinit() override; void lockout_changed(bool lockout) override; + void failure() override; void hide(); void show(); void color(std::string color); diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp index c7af4d34..3d38b0ce 100644 --- a/src/locker/plugin/password.cpp +++ b/src/locker/plugin/password.cpp @@ -27,14 +27,12 @@ WayfireLockerPasswordPluginWidget::~WayfireLockerPasswordPluginWidget() } } -void WayfireLockerPasswordPlugin::update_labels(std::string text) +void WayfireLockerPasswordPlugin::add_reply(std::string text) { for (auto& it : widgets) { - it.second->label.set_label(text); + it.second->add_reply(text); } - - label_contents = text; } void WayfireLockerPasswordPlugin::blank_passwords() @@ -47,7 +45,7 @@ void WayfireLockerPasswordPlugin::blank_passwords() void WayfireLockerPasswordPlugin::add_output(int id, std::shared_ptr grid) { - widgets.emplace(id, new WayfireLockerPasswordPluginWidget(label_contents)); + widgets.emplace(id, new WayfireLockerPasswordPluginWidget()); /* Share string to every other entry */ auto widget = widgets[id]; @@ -84,21 +82,33 @@ void WayfireLockerPasswordPlugin::add_output(int id, std::shared_ptr new_label = std::make_shared(message); + replies.append(*new_label); + + Glib::signal_timeout().connect_seconds([this, new_label] () + { + replies.remove(*new_label); + return G_SOURCE_REMOVE; + }, 15); +} + void WayfireLockerPasswordPlugin::remove_output(int id, std::shared_ptr grid) { grid->remove(*widgets[id]); @@ -113,8 +123,6 @@ WayfireLockerPasswordPlugin::WayfireLockerPasswordPlugin() : int pam_conversation(int num_mesg, const struct pam_message **mesg, struct pam_response **resp, void *appdata_ptr) { - std::cout << "PAM convo step ... " << std::endl; - WayfireLockerPasswordPlugin *pass_plugin = (WayfireLockerPasswordPlugin*)appdata_ptr; *resp = (struct pam_response*)calloc(num_mesg, sizeof(struct pam_response)); if (*resp == NULL) @@ -125,7 +133,8 @@ int pam_conversation(int num_mesg, const struct pam_message **mesg, struct pam_r for (int count = 0; count < num_mesg; count++) { - std::cout << "PAM msg : " << mesg[count]->msg << std::endl; + std::string message = mesg[count]->msg; + resp[count]->resp_retcode = 0; /* Echo OFF prompt should be user password. */ if (mesg[count]->msg_style == PAM_PROMPT_ECHO_OFF) @@ -133,10 +142,10 @@ int pam_conversation(int num_mesg, const struct pam_message **mesg, struct pam_r resp[count]->resp = strdup(pass_plugin->submitted_password.c_str()); } else if (mesg[count]->msg_style == PAM_ERROR_MSG) { - pass_plugin->update_labels(mesg[count]->msg); + pass_plugin->add_reply(message); } else if (mesg[count]->msg_style == PAM_TEXT_INFO) { - pass_plugin->update_labels(mesg[count]->msg); + pass_plugin->add_reply(message); } } @@ -162,8 +171,7 @@ void WayfireLockerPasswordPlugin::submit_user_password(std::string password) if (retval != PAM_SUCCESS) { /* We don't expect to be here. No graceful way out of this. */ - std::cout << "PAM start returned " << retval << std::endl; - update_labels("pam_start failure"); + add_reply("pam_start failure"); exit(retval); } @@ -175,8 +183,7 @@ void WayfireLockerPasswordPlugin::submit_user_password(std::string password) { if (retval == PAM_AUTH_ERR) { - std::cout << "Authentication failure." << std::endl; - update_labels("Authentication failure."); + failure(); } } else { @@ -191,6 +198,15 @@ void WayfireLockerPasswordPlugin::submit_user_password(std::string password) } } +void WayfireLockerPasswordPlugin::failure() +{ + std::cout << "Password failure" << std::endl; + for (auto & it : widgets) + { + it.second->failure(); + } +} + void WayfireLockerPasswordPlugin::init() {} diff --git a/src/locker/plugin/password.hpp b/src/locker/plugin/password.hpp index 54b6a698..cf72946c 100644 --- a/src/locker/plugin/password.hpp +++ b/src/locker/plugin/password.hpp @@ -18,10 +18,12 @@ class WayfireLockerPasswordPluginWidget : public WayfireLockerTimedRevealer ~WayfireLockerPasswordPluginWidget(); Gtk::Box box; Gtk::Entry entry; - Gtk::Label label; - WayfireLockerPasswordPluginWidget(std::string label_contents); + Gtk::Box replies; + WayfireLockerPasswordPluginWidget(); sigc::connection entry_updated, entry_submitted; + + void add_reply(std::string message); }; class WayfireLockerPasswordPlugin : public WayfireLockerPlugin @@ -35,11 +37,11 @@ class WayfireLockerPasswordPlugin : public WayfireLockerPlugin void submit_user_password(std::string password); void blank_passwords(); void update_passwords(std::string password); + void failure() override; sigc::connection timeout; - void update_labels(std::string text); + void add_reply(std::string text); std::unordered_map> widgets; - std::string label_contents = ""; std::string submitted_password = ""; }; diff --git a/src/locker/timedrevealer.cpp b/src/locker/timedrevealer.cpp index 9d9b42e6..5712180f 100644 --- a/src/locker/timedrevealer.cpp +++ b/src/locker/timedrevealer.cpp @@ -5,6 +5,7 @@ WayfireLockerTimedRevealer::WayfireLockerTimedRevealer(std::string always_option) : always_show(WfOption{always_option}) { + set_overflow(Gtk::Overflow::VISIBLE); if ((hide_timeout > 0) && !always_show) { set_reveal_child(false); @@ -84,6 +85,11 @@ WayfireLockerTimedRevealer::~WayfireLockerTimedRevealer() { signal.disconnect(); } + + if (signal_failure) + { + signal_failure.disconnect(); + } } void WayfireLockerTimedRevealer::activity() @@ -107,3 +113,19 @@ void WayfireLockerTimedRevealer::activity() }, hide_timeout * 1000); } + +void WayfireLockerTimedRevealer::failure() +{ + if (signal_failure) + { + signal_failure.disconnect(); + } + + add_css_class("failure"); + signal_failure = Glib::signal_timeout().connect_seconds( + [this] () + { + remove_css_class("failure"); + return G_SOURCE_REMOVE; + }, 1); +} diff --git a/src/locker/timedrevealer.hpp b/src/locker/timedrevealer.hpp index 837dca81..e01497bb 100644 --- a/src/locker/timedrevealer.hpp +++ b/src/locker/timedrevealer.hpp @@ -7,6 +7,7 @@ class WayfireLockerTimedRevealer : public Gtk::Revealer { private: sigc::connection signal; + sigc::connection signal_failure; public: WayfireLockerTimedRevealer(std::string always_option); @@ -17,4 +18,5 @@ class WayfireLockerTimedRevealer : public Gtk::Revealer WfOption hide_animation_duration{"locker/hide_anim_dur"}; virtual void activity(); /* Allow plugins to have their own logic if more intricate */ + void failure(); }; From e7c5b50896570042a0f32aa6d971012a14faf47a Mon Sep 17 00:00:00 2001 From: trigg Date: Sat, 7 Mar 2026 16:50:08 +0000 Subject: [PATCH 2/8] locker: clean up reply signals --- src/locker/plugin/password.cpp | 9 +++++++-- src/locker/plugin/password.hpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp index 3d38b0ce..ee9947e9 100644 --- a/src/locker/plugin/password.cpp +++ b/src/locker/plugin/password.cpp @@ -25,6 +25,11 @@ WayfireLockerPasswordPluginWidget::~WayfireLockerPasswordPluginWidget() { entry_submitted.disconnect(); } + + for (auto signal : replies_signals) + { + signal.disconnect(); + } } void WayfireLockerPasswordPlugin::add_reply(std::string text) @@ -102,11 +107,11 @@ void WayfireLockerPasswordPluginWidget::add_reply(std::string message) std::shared_ptr new_label = std::make_shared(message); replies.append(*new_label); - Glib::signal_timeout().connect_seconds([this, new_label] () + replies_signals.push_back(Glib::signal_timeout().connect_seconds([this, new_label] () { replies.remove(*new_label); return G_SOURCE_REMOVE; - }, 15); + }, 15)); } void WayfireLockerPasswordPlugin::remove_output(int id, std::shared_ptr grid) diff --git a/src/locker/plugin/password.hpp b/src/locker/plugin/password.hpp index cf72946c..918b9cbd 100644 --- a/src/locker/plugin/password.hpp +++ b/src/locker/plugin/password.hpp @@ -22,6 +22,7 @@ class WayfireLockerPasswordPluginWidget : public WayfireLockerTimedRevealer WayfireLockerPasswordPluginWidget(); sigc::connection entry_updated, entry_submitted; + std::vector replies_signals; void add_reply(std::string message); }; From a0b77c642a3dfe70324709f0ccb997a77790ab3c Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sat, 7 Mar 2026 12:32:13 -0700 Subject: [PATCH 3/8] locker: Fix failure animation This makes it so the wrong-password animation only restarts if another wrong password is entered before it hsa finished. --- src/locker/timedrevealer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/locker/timedrevealer.cpp b/src/locker/timedrevealer.cpp index 5712180f..dd4f30b3 100644 --- a/src/locker/timedrevealer.cpp +++ b/src/locker/timedrevealer.cpp @@ -121,11 +121,12 @@ void WayfireLockerTimedRevealer::failure() signal_failure.disconnect(); } - add_css_class("failure"); - signal_failure = Glib::signal_timeout().connect_seconds( + remove_css_class("failure"); + + Glib::signal_idle().connect( [this] () { - remove_css_class("failure"); + add_css_class("failure"); return G_SOURCE_REMOVE; - }, 1); + }); } From 6801cc6ad58e3eb9becdd42bd8d11182c380636a Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sat, 7 Mar 2026 12:34:37 -0700 Subject: [PATCH 4/8] locker: Rename pam file wf-locker-password to wf-locker --- data/meson.build | 4 ++-- data/{wf-locker-password => wf-locker} | 0 src/locker/plugin/password.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename data/{wf-locker-password => wf-locker} (100%) diff --git a/data/meson.build b/data/meson.build index e35455ef..ea20a57a 100644 --- a/data/meson.build +++ b/data/meson.build @@ -13,6 +13,6 @@ install_data(join_paths('icons', '256x256', 'wayfire.png'), install_dir: join_pa install_data(join_paths('icons', '512x512', 'wayfire.png'), install_dir: join_paths(get_option('prefix'), 'share', 'icons', 'hicolor', '512x512', 'apps')) install_data(join_paths('icons', 'scalable', 'wayfire.svg'), install_dir: join_paths(get_option('prefix'), 'share', 'icons', 'hicolor', 'scalable', 'apps')) -install_data('wf-locker-password', install_dir:'/etc/pam.d/') +install_data('wf-locker', install_dir:'/etc/pam.d/') -subdir('css') \ No newline at end of file +subdir('css') diff --git a/data/wf-locker-password b/data/wf-locker similarity index 100% rename from data/wf-locker-password rename to data/wf-locker diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp index ee9947e9..f8c4fa58 100644 --- a/src/locker/plugin/password.cpp +++ b/src/locker/plugin/password.cpp @@ -172,7 +172,7 @@ void WayfireLockerPasswordPlugin::submit_user_password(std::string password) int retval; /* Start the password-based conversation */ std::cout << "PAM start ... " << std::endl; - retval = pam_start("wf-locker-password", username, &local_conversation, &local_auth_handle); + retval = pam_start("wf-locker", username, &local_conversation, &local_auth_handle); if (retval != PAM_SUCCESS) { /* We don't expect to be here. No graceful way out of this. */ From 8441d3a3a230c8bf98affb827599041dc6fb31a8 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sat, 7 Mar 2026 12:36:22 -0700 Subject: [PATCH 5/8] locker: Install a more sensible pam configuration for our scenario --- data/wf-locker | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/wf-locker b/data/wf-locker index c1355e38..fd46cb30 100644 --- a/data/wf-locker +++ b/data/wf-locker @@ -1 +1,2 @@ -auth include login +auth required pam_unix.so nodelay unlock_time=60 +account required pam_unix.so nodelay unlock_time=60 From 4fff5c51cb059c82dd8d5346cd64f3403043dfbe Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Sat, 7 Mar 2026 12:37:29 -0700 Subject: [PATCH 6/8] locker: Use a slightly longer animation duration --- data/css/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/css/default.css b/data/css/default.css index 64af64dd..a7384d13 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -243,7 +243,7 @@ text-shadow: 1px 1px 0 var(--bcol), .wf-locker .failure { animation-name: shake; - animation-duration:0.3s; + animation-duration:0.5s; animation-timing-function: linear; animation-iteration-count: 1; animation-fill-mode: forwards; From adf614998beeb69bacfaa2f6be4a8d8bc367ed2a Mon Sep 17 00:00:00 2001 From: trigg Date: Sat, 7 Mar 2026 20:19:34 +0000 Subject: [PATCH 7/8] locker: implement lockout for PAM passwords --- src/locker/plugin/password.cpp | 22 +++++++++++++++++++++- src/locker/plugin/password.hpp | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp index f8c4fa58..7ec70da1 100644 --- a/src/locker/plugin/password.cpp +++ b/src/locker/plugin/password.cpp @@ -48,6 +48,18 @@ void WayfireLockerPasswordPlugin::blank_passwords() } } +void WayfireLockerPasswordPluginWidget::lockout_changed(bool lockout) +{ + if (lockout) + { + entry.set_text(""); + entry.set_sensitive(false); + } else + { + entry.set_sensitive(true); + } +} + void WayfireLockerPasswordPlugin::add_output(int id, std::shared_ptr grid) { widgets.emplace(id, new WayfireLockerPasswordPluginWidget()); @@ -203,9 +215,17 @@ void WayfireLockerPasswordPlugin::submit_user_password(std::string password) } } +void WayfireLockerPasswordPlugin::lockout_changed(bool lockout) +{ + for (auto & it : widgets) + { + it.second->lockout_changed(lockout); + } +} + void WayfireLockerPasswordPlugin::failure() { - std::cout << "Password failure" << std::endl; + WayfireLockerApp::get().recieved_bad_auth(); for (auto & it : widgets) { it.second->failure(); diff --git a/src/locker/plugin/password.hpp b/src/locker/plugin/password.hpp index 918b9cbd..c26b9443 100644 --- a/src/locker/plugin/password.hpp +++ b/src/locker/plugin/password.hpp @@ -25,6 +25,7 @@ class WayfireLockerPasswordPluginWidget : public WayfireLockerTimedRevealer std::vector replies_signals; void add_reply(std::string message); + void lockout_changed(bool lockout); }; class WayfireLockerPasswordPlugin : public WayfireLockerPlugin @@ -38,6 +39,7 @@ class WayfireLockerPasswordPlugin : public WayfireLockerPlugin void submit_user_password(std::string password); void blank_passwords(); void update_passwords(std::string password); + void lockout_changed(bool lockout) override; void failure() override; sigc::connection timeout; From 70ac51219499acd39826bd8d7877ee71c6725499 Mon Sep 17 00:00:00 2001 From: trigg Date: Mon, 9 Mar 2026 18:17:47 +0000 Subject: [PATCH 8/8] locker: change placeholder text on lockout --- src/locker/plugin/password.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/locker/plugin/password.cpp b/src/locker/plugin/password.cpp index 7ec70da1..856ce85d 100644 --- a/src/locker/plugin/password.cpp +++ b/src/locker/plugin/password.cpp @@ -54,9 +54,11 @@ void WayfireLockerPasswordPluginWidget::lockout_changed(bool lockout) { entry.set_text(""); entry.set_sensitive(false); + entry.set_placeholder_text("Too many attempts"); } else { entry.set_sensitive(true); + entry.set_placeholder_text("Password"); } }