diff --git a/Readme.md b/Readme.md index 33a2922..25628f3 100644 --- a/Readme.md +++ b/Readme.md @@ -9,10 +9,10 @@ A Polkit authentication agent for Wayland desktops, built with `iced_layershell` * Lightweight and fast * Built with Rust and the Iced toolkit * Wayland native +* Retry mechanism (up to 3 attempts) if authentication fails ### Planned Features -* [ ] Retry if authentication fails * [ ] Use system theme for a more integrated look and feel * [ ] CSS-based styling for customization diff --git a/src/main.rs b/src/main.rs index c7b75cc..8778ff5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use iced_layershell::to_layer_message; use polkit_agent_rs::polkit::UnixUser; use std::collections::BTreeMap; +use futures::channel::mpsc::Sender; use iced::widget::{Space, button, column, pick_list, row, text, text_input}; use iced::{Bottom, Center, Fill}; use polkit_agent_rs::RegisterFlags; @@ -23,25 +24,57 @@ mod mypolkit; use mypolkit::MyPolkit; const OBJECT_PATH: &str = "/org/waycrate/PolicyKit1/AuthenticationAgent"; +const MAX_RETRIES: u32 = 3; -fn start_session(session: &AgentSession, password: String, task: gio::Task) { +fn start_session( + username: String, + cookie: String, + password: String, + task: gio::Task, + window_id: Id, + sender: Arc>>, +) { + let user: UnixUser = UnixUser::new_for_name(&username).unwrap(); + let session = AgentSession::new(&user, &cookie); let sub_loop = glib::MainLoop::new(None, true); let sub_loop_2 = sub_loop.clone(); + let sender_clone = sender.clone(); - session.connect_completed(move |session, _success| { + session.connect_completed(move |session, success| { unsafe { - task.clone().return_result(Ok("success".to_string())); + if success { + task.clone().return_result(Ok("success".to_string())); + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::AuthenticationSuccess(window_id)); + } else { + task.clone().return_result(Err(glib::Error::new( + glib::FileError::Failed, + "Authentication failed", + ))); + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::AuthenticationFailed( + window_id, + "Authentication failed".to_string(), + )); + } } session.cancel(); sub_loop_2.quit(); }); - session.connect_show_info(|_session, info| { + + session.connect_show_info(move |_session, info| { println!("info: {info}"); }); - session.connect_show_error(|_session, error| { + + session.connect_show_error(move |_session, error| { eprintln!("error: {error}"); }); + session.connect_request(move |session, request, _echo_on| { println!("{}", request); if !request.starts_with("Password:") { @@ -83,11 +116,14 @@ struct AuthSession { error: Option, task: gio::Task, message: String, + retry_count: u32, + max_retries: u32, } -#[derive(Debug, Default)] +#[derive(Debug)] struct PolkitApp { sessions: BTreeMap, + sender: Option>>>, } #[to_layer_message(multi)] @@ -103,6 +139,7 @@ pub enum Message { AuthenticationSuccess(Id), AuthenticationFailed(Id, String), IcedEvent(Event), + SetSender(Arc>>), } impl PolkitApp { @@ -116,6 +153,7 @@ impl PolkitApp { ( Self { sessions: BTreeMap::new(), + sender: None, }, Command::none(), ) @@ -130,6 +168,7 @@ impl PolkitApp { iced::Subscription::run(|| { iced::stream::channel(100, |sender| { let sender = Arc::new(Mutex::new(sender)); + let sender_clone = sender.clone(); std::thread::spawn(move || { let main_loop = glib::MainLoop::new(None, true); @@ -154,7 +193,13 @@ impl PolkitApp { main_loop.run(); }); - futures::future::ready(()) + async move { + let _ = sender_clone + .lock() + .unwrap() + .try_send(Message::SetSender(sender_clone.clone())); + futures::future::pending::<()>().await; + } }) }), iced::window::close_events().map(Message::WindowClosed), @@ -190,6 +235,8 @@ impl PolkitApp { error: None, task, message: messg, + retry_count: 0, + max_retries: MAX_RETRIES, }, ); @@ -220,15 +267,21 @@ impl PolkitApp { } Message::Authenticate(id) => { - if let Some(session) = self.sessions.get(&id) { - let user: UnixUser = UnixUser::new_for_name(&session.selected_user).unwrap(); - let ass = AgentSession::new(&user, &session.cookie); + if let Some(session) = self.sessions.get_mut(&id) { + let username = session.selected_user.clone(); + let cookie = session.cookie.clone(); + let password = session.password.clone(); + let task = session.task.clone(); + + session.error = None; - start_session(&ass, session.password.clone(), session.task.clone()); - } else { - return Command::none(); + if let Some(sender) = self.sender.clone() { + std::thread::spawn(move || { + start_session(username, cookie, password, task, id, sender); + }); + } } - task::effect(Action::Window(WindowAction::Close(id))) + Command::none() } Message::AuthenticationSuccess(id) => { @@ -237,7 +290,20 @@ impl PolkitApp { Message::AuthenticationFailed(id, error) => { if let Some(session) = self.sessions.get_mut(&id) { - session.error = Some(error); + session.retry_count += 1; + + if session.retry_count >= session.max_retries { + return task::effect(Action::Window(WindowAction::Close(id))); + } + + let remaining = session.max_retries - session.retry_count; + session.error = Some(format!( + "{}. {} attempt{} remaining.", + error, + remaining, + if remaining == 1 { "" } else { "s" } + )); + session.password.clear(); } Command::none() } @@ -246,6 +312,11 @@ impl PolkitApp { task::effect(Action::Window(WindowAction::Close(id))) } + Message::SetSender(sender) => { + self.sender = Some(sender); + Command::none() + } + _ => Command::none(), } } @@ -289,9 +360,12 @@ impl PolkitApp { .spacing(20) .padding(25) ]; - if let Some(_error) = &session.error { - // content = content - // .push(text(error).style(|theme| iced::theme::Text::Color(theme.palette().danger))); + if let Some(error) = &session.error { + content = content.push( + text(error) + .size(14) + .color(iced::Color::from_rgb(0.8, 0.0, 0.0)), + ); } content = content.push(Space::with_height(Fill)).push(