Prepare public release v0.1.0

This commit is contained in:
2026-01-30 09:44:12 +01:00
parent 75be95fdf7
commit 2034281ad7
41 changed files with 2137 additions and 1028 deletions

View File

@@ -86,8 +86,13 @@ trait LinuxHelloDaemon {
/// Signal emitted when enrollment progress updates
#[zbus(signal)]
fn enrollment_progress(&self, session_id: &str, step: u32, total: u32, message: &str)
-> ZbusResult<()>;
fn enrollment_progress(
&self,
session_id: &str,
step: u32,
total: u32,
message: &str,
) -> ZbusResult<()>;
/// Signal emitted when enrollment completes
#[zbus(signal)]
@@ -128,10 +133,7 @@ impl DaemonClient {
/// Get the D-Bus proxy for the daemon
async fn get_proxy(&self) -> Result<LinuxHelloDaemonProxy<'static>, DaemonError> {
let guard = self.connection.lock().await;
let conn = guard
.as_ref()
.ok_or(DaemonError::NotConnected)?
.clone();
let conn = guard.as_ref().ok_or(DaemonError::NotConnected)?.clone();
LinuxHelloDaemonProxy::new(&conn)
.await
@@ -144,13 +146,14 @@ impl DaemonClient {
match proxy.get_status().await {
Ok(json) => {
serde_json::from_str(&json)
.map_err(|e| DaemonError::ParseError(e.to_string()))
serde_json::from_str(&json).map_err(|e| DaemonError::ParseError(e.to_string()))
}
Err(e) => {
// If daemon is not running, return a default status
if e.to_string().contains("org.freedesktop.DBus.Error.ServiceUnknown")
|| e.to_string().contains("org.freedesktop.DBus.Error.NameHasNoOwner")
if e.to_string()
.contains("org.freedesktop.DBus.Error.ServiceUnknown")
|| e.to_string()
.contains("org.freedesktop.DBus.Error.NameHasNoOwner")
{
Ok(SystemStatus {
daemon_running: false,
@@ -169,12 +172,13 @@ impl DaemonClient {
match proxy.list_templates().await {
Ok(json) => {
serde_json::from_str(&json)
.map_err(|e| DaemonError::ParseError(e.to_string()))
serde_json::from_str(&json).map_err(|e| DaemonError::ParseError(e.to_string()))
}
Err(e) => {
if e.to_string().contains("org.freedesktop.DBus.Error.ServiceUnknown")
|| e.to_string().contains("org.freedesktop.DBus.Error.NameHasNoOwner")
if e.to_string()
.contains("org.freedesktop.DBus.Error.ServiceUnknown")
|| e.to_string()
.contains("org.freedesktop.DBus.Error.NameHasNoOwner")
{
Ok(Vec::new())
} else {

View File

@@ -8,8 +8,8 @@ use std::rc::Rc;
use std::sync::Arc;
use glib::clone;
use gtk4::prelude::*;
use gtk4::glib;
use gtk4::prelude::*;
use libadwaita as adw;
use libadwaita::prelude::*;
use tokio::sync::Mutex;
@@ -63,9 +63,7 @@ impl EnrollmentDialog {
.show_end_title_buttons(false)
.build();
let cancel_button = gtk4::Button::builder()
.label("Cancel")
.build();
let cancel_button = gtk4::Button::builder().label("Cancel").build();
header.pack_start(&cancel_button);
let start_button = gtk4::Button::builder()
@@ -155,9 +153,21 @@ impl EnrollmentDialog {
.build();
let tips = [
("face-smile-symbolic", "Good lighting", "Ensure your face is well-lit"),
("view-reveal-symbolic", "Clear view", "Remove glasses if possible"),
("object-rotate-right-symbolic", "Multiple angles", "Slowly turn your head when prompted"),
(
"face-smile-symbolic",
"Good lighting",
"Ensure your face is well-lit",
),
(
"view-reveal-symbolic",
"Clear view",
"Remove glasses if possible",
),
(
"object-rotate-right-symbolic",
"Multiple angles",
"Slowly turn your head when prompted",
),
];
for (icon, title, subtitle) in tips {
@@ -238,7 +248,8 @@ impl EnrollmentDialog {
fn validate_input(&self) {
let label = self.label_entry.text();
let valid = !label.is_empty() && label.len() <= 64;
self.start_button.set_sensitive(valid && *self.state.borrow() == EnrollmentState::Ready);
self.start_button
.set_sensitive(valid && *self.state.borrow() == EnrollmentState::Ready);
}
/// Create a weak reference for callbacks
@@ -273,9 +284,11 @@ impl EnrollmentDialog {
self.progress_bar.set_text(Some("Starting..."));
self.instruction_label.set_visible(true);
self.status_page.set_icon_name(Some("camera-video-symbolic"));
self.status_page
.set_icon_name(Some("camera-video-symbolic"));
self.status_page.set_title("Enrolling...");
self.status_page.set_description(Some("Please look at the camera"));
self.status_page
.set_description(Some("Please look at the camera"));
let client = self.client.clone();
let state = self.state.clone();
@@ -315,11 +328,14 @@ impl EnrollmentDialog {
let instruction = instruction.to_string();
glib::idle_add_local_once(clone!(
#[strong] progress_bar,
#[strong] instruction_label,
#[strong]
progress_bar,
#[strong]
instruction_label,
move || {
progress_bar.set_fraction(progress);
progress_bar.set_text(Some(&format!("{}%", (progress * 100.0) as u32)));
progress_bar
.set_text(Some(&format!("{}%", (progress * 100.0) as u32)));
instruction_label.set_label(&instruction);
}
));
@@ -332,19 +348,29 @@ impl EnrollmentDialog {
if *state.borrow() == EnrollmentState::InProgress {
match client_guard.finish_enrollment(&sid).await {
Ok(template_id) => {
tracing::info!("Enrollment completed, template ID: {}", template_id);
tracing::info!(
"Enrollment completed, template ID: {}",
template_id
);
*state.borrow_mut() = EnrollmentState::Completed;
glib::idle_add_local_once(clone!(
#[strong] status_page,
#[strong] progress_bar,
#[strong] instruction_label,
#[strong] start_button,
#[strong] on_completed,
#[strong]
status_page,
#[strong]
progress_bar,
#[strong]
instruction_label,
#[strong]
start_button,
#[strong]
on_completed,
move || {
status_page.set_icon_name(Some("emblem-ok-symbolic"));
status_page.set_title("Enrollment Complete");
status_page.set_description(Some("Your face has been enrolled successfully"));
status_page.set_description(Some(
"Your face has been enrolled successfully",
));
progress_bar.set_visible(false);
instruction_label.set_visible(false);
start_button.set_label("Done");
@@ -360,13 +386,31 @@ impl EnrollmentDialog {
));
}
Err(e) => {
handle_enrollment_error(&state, &status_page, &progress_bar, &instruction_label, &start_button, &label_entry, &on_completed, &e.to_string());
handle_enrollment_error(
&state,
&status_page,
&progress_bar,
&instruction_label,
&start_button,
&label_entry,
&on_completed,
&e.to_string(),
);
}
}
}
}
Err(e) => {
handle_enrollment_error(&state, &status_page, &progress_bar, &instruction_label, &start_button, &label_entry, &on_completed, &e.to_string());
handle_enrollment_error(
&state,
&status_page,
&progress_bar,
&instruction_label,
&start_button,
&label_entry,
&on_completed,
&e.to_string(),
);
}
}
});
@@ -419,13 +463,20 @@ fn handle_enrollment_error(
*state.borrow_mut() = EnrollmentState::Failed;
glib::idle_add_local_once(clone!(
#[strong] status_page,
#[strong] progress_bar,
#[strong] instruction_label,
#[strong] start_button,
#[strong] label_entry,
#[strong] on_completed,
#[strong] error,
#[strong]
status_page,
#[strong]
progress_bar,
#[strong]
instruction_label,
#[strong]
start_button,
#[strong]
label_entry,
#[strong]
on_completed,
#[strong]
error,
move || {
status_page.set_icon_name(Some("dialog-error-symbolic"));
status_page.set_title("Enrollment Failed");

View File

@@ -83,8 +83,7 @@ impl TemplateDisplayModel {
.unwrap_or_else(|| "Unknown".to_string());
let (last_used_date, recently_used) = if let Some(ts) = template.last_used {
let dt = DateTime::<Utc>::from_timestamp(ts, 0)
.map(|dt| dt.with_timezone(&Local));
let dt = DateTime::<Utc>::from_timestamp(ts, 0).map(|dt| dt.with_timezone(&Local));
let date_str = dt
.as_ref()
@@ -135,7 +134,7 @@ mod tests {
id: id.to_string(),
label: label.to_string(),
username: "testuser".to_string(),
created_at: 1704067200, // 2024-01-01 00:00:00 UTC
created_at: 1704067200, // 2024-01-01 00:00:00 UTC
last_used: Some(1704153600), // 2024-01-02 00:00:00 UTC
}
}

View File

@@ -268,11 +268,12 @@ impl SettingsWindow {
// Anti-spoofing switch
let this = self.clone();
self.anti_spoofing_switch.connect_active_notify(move |switch| {
let enabled = switch.is_active();
tracing::info!("Anti-spoofing toggled: {}", enabled);
this.save_settings();
});
self.anti_spoofing_switch
.connect_active_notify(move |switch| {
let enabled = switch.is_active();
tracing::info!("Anti-spoofing toggled: {}", enabled);
this.save_settings();
});
// Confidence threshold
let this = self.clone();
@@ -312,8 +313,10 @@ impl SettingsWindow {
// Update daemon status
glib::idle_add_local_once(clone!(
#[strong] daemon_row,
#[strong] status,
#[strong]
daemon_row,
#[strong]
status,
move || {
if status.daemon_running {
daemon_row.set_subtitle("Running");
@@ -327,8 +330,10 @@ impl SettingsWindow {
// Update camera status
glib::idle_add_local_once(clone!(
#[strong] camera_row,
#[strong] status,
#[strong]
camera_row,
#[strong]
status,
move || {
if status.camera_available {
let device = status.camera_device.as_deref().unwrap_or("Available");
@@ -343,8 +348,10 @@ impl SettingsWindow {
// Update TPM status
glib::idle_add_local_once(clone!(
#[strong] tpm_row,
#[strong] status,
#[strong]
tpm_row,
#[strong]
status,
move || {
if status.tpm_available {
tpm_row.set_subtitle("Available - Secure storage enabled");
@@ -358,8 +365,10 @@ impl SettingsWindow {
// Update enroll button sensitivity
glib::idle_add_local_once(clone!(
#[strong] enroll_button,
#[strong] status,
#[strong]
enroll_button,
#[strong]
status,
move || {
enroll_button.set_sensitive(status.daemon_running && status.camera_available);
}
@@ -371,8 +380,10 @@ impl SettingsWindow {
// Update templates list
glib::idle_add_local_once(clone!(
#[strong] templates_group,
#[strong] template_list,
#[strong]
templates_group,
#[strong]
template_list,
move || {
update_templates_list(&templates_group, &template_list, templates);
}
@@ -505,7 +516,10 @@ fn create_template_row(template: &TemplateInfo) -> adw::ActionRow {
let subtitle = format!(
"Created: {} | Last used: {}",
format_timestamp(template.created_at),
template.last_used.map(format_timestamp).unwrap_or_else(|| "Never".to_string())
template
.last_used
.map(format_timestamp)
.unwrap_or_else(|| "Never".to_string())
);
let row = adw::ActionRow::builder()
@@ -525,7 +539,10 @@ fn create_template_row(template: &TemplateInfo) -> adw::ActionRow {
let template_id = template.id.clone();
delete_button.connect_clicked(move |button| {
// Show confirmation dialog
if let Some(window) = button.root().and_then(|r| r.downcast::<gtk4::Window>().ok()) {
if let Some(window) = button
.root()
.and_then(|r| r.downcast::<gtk4::Window>().ok())
{
show_delete_confirmation(&window, &template_id);
}
});
@@ -554,10 +571,7 @@ fn show_delete_confirmation(window: &gtk4::Window, template_id: &str) {
.body("This will remove the enrolled face template. You will need to enroll again to use facial authentication.")
.build();
dialog.add_responses(&[
("cancel", "Cancel"),
("delete", "Remove"),
]);
dialog.add_responses(&[("cancel", "Cancel"), ("delete", "Remove")]);
dialog.set_response_appearance("delete", adw::ResponseAppearance::Destructive);
dialog.set_default_response(Some("cancel"));
dialog.set_close_response("cancel");
@@ -589,9 +603,7 @@ fn show_about_dialog(window: &adw::ApplicationWindow) {
.comments("Facial authentication for Linux, inspired by Windows Hello")
.build();
about.add_credit_section(Some("Contributors"), &[
"Linux Hello Team",
]);
about.add_credit_section(Some("Contributors"), &["Linux Hello Team"]);
about.present(Some(window));
}