1 Commits

Author SHA1 Message Date
nevaforget 85cf039506 refactor: power-confirm via PowerAction table (v0.6.14)
Align the power-confirm flow to moonset's ActionDef pattern, in lockstep
with moongreet: a PowerAction table + create_power_button factory replace
the two hand-wired reboot/shutdown handlers and the loose-param
show_power_confirm. Add an in-flight re-trigger guard
(power_box.set_sensitive(false)) and clear a stale error_label when
showing a new prompt.
2026-06-02 14:31:50 +02:00
4 changed files with 111 additions and 63 deletions
Generated
+1 -1
View File
@@ -575,7 +575,7 @@ dependencies = [
[[package]] [[package]]
name = "moonlock" name = "moonlock"
version = "0.6.13" version = "0.6.14"
dependencies = [ dependencies = [
"gdk-pixbuf", "gdk-pixbuf",
"gdk4", "gdk4",
+4 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "moonlock" name = "moonlock"
version = "0.6.13" version = "0.6.14"
edition = "2024" edition = "2024"
description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support" description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support"
license = "MIT" license = "MIT"
@@ -28,6 +28,7 @@ tempfile = "3"
glib-build-tools = "0.22" glib-build-tools = "0.22"
[profile.release] [profile.release]
lto = "fat" lto = "thin"
codegen-units = 1 codegen-units = 1
strip = true strip = false
debug = true
+7
View File
@@ -2,6 +2,13 @@
Architectural and design decisions for Moonlock, in reverse chronological order. Architectural and design decisions for Moonlock, in reverse chronological order.
## 2026-06-02 Align power-confirm to moonset's ActionDef pattern (v0.6.14)
- **Who**: ClaudeCode, Dom
- **Why**: A code review of moongreet's power-confirm (ported from this file) flagged the shared pattern as lower-altitude than moonset's: two near-identical reboot/shutdown handlers and a `show_power_confirm` taking loose `message`/`action_fn`/`error_message` params that can drift apart. moonset already solved this with an `ActionDef` table + button factory. Changed here in lockstep with moongreet to keep the three projects symmetric.
- **Tradeoffs**: A `PowerAction` struct + `power_actions()` table + `create_power_button` factory is slightly more machinery for two actions, but couples icon/prompt/error/action into one value (a mismatched prompt/action becomes unrepresentable) and makes a third action a one-line table entry. Did NOT touch `confirm_box: Rc<RefCell<Option<gtk::Box>>>` — moonset uses the same, it is the shared convention.
- **How**: Replaced the two hand-wired handlers with a loop over `power_actions()`; `show_power_confirm`/`execute_power_action` now take `PowerAction` (Copy) instead of three loose strings. Added an in-flight re-trigger guard via `power_box.set_sensitive(false)` (re-enabled on failure), matching moonset; also clear a stale `error_label` when showing a new prompt.
## 2026-05-04 Drop PAM account/session stack, remove `check_account`, drop `pam_acct_mgmt` from password path (v0.6.13) ## 2026-05-04 Drop PAM account/session stack, remove `check_account`, drop `pam_acct_mgmt` from password path (v0.6.13)
- **Who**: ClaudeCode, Dom - **Who**: ClaudeCode, Dom
+99 -59
View File
@@ -170,55 +170,17 @@ pub fn create_lockscreen_window(
power_box.set_margin_end(16); power_box.set_margin_end(16);
power_box.set_margin_bottom(16); power_box.set_margin_bottom(16);
let reboot_btn = gtk::Button::new(); for action in power_actions() {
reboot_btn.set_icon_name("system-reboot-symbolic"); let button = create_power_button(
reboot_btn.add_css_class("power-button"); action,
reboot_btn.set_tooltip_text(Some(strings.reboot_tooltip)); strings,
reboot_btn.connect_clicked(clone!( &power_box,
#[weak] &confirm_area,
confirm_area, &confirm_box,
#[strong] &error_label,
confirm_box, );
#[weak] power_box.append(&button);
error_label, }
move |_| {
show_power_confirm(
strings.reboot_confirm,
power::reboot,
strings.reboot_failed,
strings,
&confirm_area,
&confirm_box,
&error_label,
);
}
));
power_box.append(&reboot_btn);
let shutdown_btn = gtk::Button::new();
shutdown_btn.set_icon_name("system-shutdown-symbolic");
shutdown_btn.add_css_class("power-button");
shutdown_btn.set_tooltip_text(Some(strings.shutdown_tooltip));
shutdown_btn.connect_clicked(clone!(
#[weak]
confirm_area,
#[strong]
confirm_box,
#[weak]
error_label,
move |_| {
show_power_confirm(
strings.shutdown_confirm,
power::shutdown,
strings.shutdown_failed,
strings,
&confirm_area,
&confirm_box,
&error_label,
);
}
));
power_box.append(&shutdown_btn);
overlay.add_overlay(&power_box); overlay.add_overlay(&power_box);
@@ -688,23 +650,84 @@ fn set_default_avatar(
image.set_icon_name(Some("avatar-default-symbolic")); image.set_icon_name(Some("avatar-default-symbolic"));
} }
/// Definition for a single power-action button (reboot, shutdown).
/// Couples icon, prompt, error text and action so a button cannot be wired
/// with a mismatched prompt/action pair. Mirrors moonset's `ActionDef`.
#[derive(Clone, Copy)]
struct PowerAction {
icon_name: &'static str,
tooltip_attr: fn(&Strings) -> &'static str,
confirm_attr: fn(&Strings) -> &'static str,
error_attr: fn(&Strings) -> &'static str,
action_fn: fn() -> Result<(), PowerError>,
}
/// The power actions offered on the lockscreen.
fn power_actions() -> [PowerAction; 2] {
[
PowerAction {
icon_name: "system-reboot-symbolic",
tooltip_attr: |s| s.reboot_tooltip,
confirm_attr: |s| s.reboot_confirm,
error_attr: |s| s.reboot_failed,
action_fn: power::reboot,
},
PowerAction {
icon_name: "system-shutdown-symbolic",
tooltip_attr: |s| s.shutdown_tooltip,
confirm_attr: |s| s.shutdown_confirm,
error_attr: |s| s.shutdown_failed,
action_fn: power::shutdown,
},
]
}
/// Build a power-action icon button wired to the confirmation flow.
fn create_power_button(
action: PowerAction,
strings: &'static Strings,
power_box: &gtk::Box,
confirm_area: &gtk::Box,
confirm_box: &Rc<RefCell<Option<gtk::Box>>>,
error_label: &gtk::Label,
) -> gtk::Button {
let button = gtk::Button::new();
button.set_icon_name(action.icon_name);
button.add_css_class("power-button");
button.set_tooltip_text(Some((action.tooltip_attr)(strings)));
button.connect_clicked(clone!(
#[weak]
power_box,
#[weak]
confirm_area,
#[strong]
confirm_box,
#[weak]
error_label,
move |_| {
show_power_confirm(action, strings, &power_box, &confirm_area, &confirm_box, &error_label);
}
));
button
}
/// Show inline power confirmation. /// Show inline power confirmation.
fn show_power_confirm( fn show_power_confirm(
message: &'static str, action: PowerAction,
action_fn: fn() -> Result<(), PowerError>,
error_message: &'static str,
strings: &'static Strings, strings: &'static Strings,
power_box: &gtk::Box,
confirm_area: &gtk::Box, confirm_area: &gtk::Box,
confirm_box: &Rc<RefCell<Option<gtk::Box>>>, confirm_box: &Rc<RefCell<Option<gtk::Box>>>,
error_label: &gtk::Label, error_label: &gtk::Label,
) { ) {
dismiss_power_confirm(confirm_area, confirm_box); dismiss_power_confirm(confirm_area, confirm_box);
error_label.set_visible(false);
let new_box = gtk::Box::new(gtk::Orientation::Vertical, 8); let new_box = gtk::Box::new(gtk::Orientation::Vertical, 8);
new_box.set_halign(gtk::Align::Center); new_box.set_halign(gtk::Align::Center);
new_box.set_margin_top(16); new_box.set_margin_top(16);
let confirm_label = gtk::Label::new(Some(message)); let confirm_label = gtk::Label::new(Some((action.confirm_attr)(strings)));
confirm_label.add_css_class("confirm-label"); confirm_label.add_css_class("confirm-label");
new_box.append(&confirm_label); new_box.append(&confirm_label);
@@ -714,6 +737,8 @@ fn show_power_confirm(
let yes_btn = gtk::Button::with_label(strings.confirm_yes); let yes_btn = gtk::Button::with_label(strings.confirm_yes);
yes_btn.add_css_class("confirm-yes"); yes_btn.add_css_class("confirm-yes");
yes_btn.connect_clicked(clone!( yes_btn.connect_clicked(clone!(
#[weak]
power_box,
#[weak] #[weak]
confirm_area, confirm_area,
#[strong] #[strong]
@@ -721,8 +746,7 @@ fn show_power_confirm(
#[weak] #[weak]
error_label, error_label,
move |_| { move |_| {
dismiss_power_confirm(&confirm_area, &confirm_box); execute_power_action(action, strings, &power_box, &confirm_area, &confirm_box, &error_label);
execute_power_action(action_fn, error_message, &error_label);
} }
)); ));
button_row.append(&yes_btn); button_row.append(&yes_btn);
@@ -753,28 +777,44 @@ fn dismiss_power_confirm(confirm_area: &gtk::Box, confirm_box: &Rc<RefCell<Optio
} }
} }
/// Execute a power action in a background thread. /// Execute a power action in a background thread, guarding against re-trigger.
fn execute_power_action( fn execute_power_action(
action_fn: fn() -> Result<(), PowerError>, action: PowerAction,
error_message: &'static str, strings: &'static Strings,
power_box: &gtk::Box,
confirm_area: &gtk::Box,
confirm_box: &Rc<RefCell<Option<gtk::Box>>>,
error_label: &gtk::Label, error_label: &gtk::Label,
) { ) {
dismiss_power_confirm(confirm_area, confirm_box);
let action_fn = action.action_fn;
let error_message = (action.error_attr)(strings);
// Desensitize the power buttons so a double-click or keyboard repeat cannot
// fire the same action twice while it is in flight.
power_box.set_sensitive(false);
glib::spawn_future_local(clone!( glib::spawn_future_local(clone!(
#[weak]
power_box,
#[weak] #[weak]
error_label, error_label,
async move { async move {
let result = gio::spawn_blocking(move || action_fn()).await; let result = gio::spawn_blocking(action_fn).await;
match result { match result {
Ok(Ok(())) => {} Ok(Ok(())) => {}
Ok(Err(e)) => { Ok(Err(e)) => {
log::error!("Power action failed: {e}"); log::error!("Power action failed: {e}");
error_label.set_text(error_message); error_label.set_text(error_message);
error_label.set_visible(true); error_label.set_visible(true);
power_box.set_sensitive(true);
} }
Err(_) => { Err(_) => {
log::error!("Power action panicked"); log::error!("Power action panicked");
error_label.set_text(error_message); error_label.set_text(error_message);
error_label.set_visible(true); error_label.set_visible(true);
power_box.set_sensitive(true);
} }
} }
} }