Prepare public release v0.1.0
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: >k4::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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user