add idle timeout and version on settings screen

This commit is contained in:
2024-01-31 13:14:08 -08:00
parent 69f6a39396
commit 141334f7e2
9 changed files with 149 additions and 17 deletions

View File

@ -1,4 +1,5 @@
use std::error::Error;
use std::time::Duration;
use once_cell::sync::OnceCell;
use sqlx::{
@ -40,6 +41,7 @@ pub fn run() -> tauri::Result<()> {
ipc::unlock,
ipc::respond,
ipc::get_session_status,
ipc::signal_activity,
ipc::save_credentials,
ipc::get_config,
ipc::save_config,
@ -119,10 +121,30 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
let state = AppState::new(conf, session, pool, setup_errors, desktop_is_gnome);
app.manage(state);
// make sure we do this after managing app state, so that it doesn't panic
start_auto_locker(app.app_handle());
Ok(())
}
fn start_auto_locker(app: AppHandle) {
rt::spawn(async move {
let state = app.state::<AppState>();
loop {
// this gives our session-timeout a minimum resolution of 10s, which seems fine?
let delay = Duration::from_secs(10);
tokio::time::sleep(delay).await;
if state.should_auto_lock().await {
state.lock().await.error_popup("Failed to lock Creddy");
}
}
});
}
pub fn show_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
w.show()?;

View File

@ -1,4 +1,5 @@
use std::path::PathBuf;
use std::time::Duration;
use auto_launch::AutoLaunchBuilder;
use is_terminal::IsTerminal;
@ -45,6 +46,10 @@ impl HotkeysConfig {
pub struct AppConfig {
#[serde(default = "default_rehide_ms")]
pub rehide_ms: u64,
#[serde(default = "default_auto_lock")]
pub auto_lock: bool,
#[serde(default = "default_lock_after")]
pub lock_after: Duration,
#[serde(default = "default_start_minimized")]
pub start_minimized: bool,
#[serde(default = "default_start_on_login")]
@ -60,6 +65,8 @@ impl Default for AppConfig {
fn default() -> Self {
AppConfig {
rehide_ms: default_rehide_ms(),
auto_lock: default_auto_lock(),
lock_after: default_lock_after(),
start_minimized: default_start_minimized(),
start_on_login: default_start_on_login(),
terminal: default_term_config(),
@ -187,6 +194,30 @@ fn default_hotkey_config() -> HotkeysConfig {
fn default_rehide_ms() -> u64 { 1000 }
fn default_auto_lock() -> bool { true }
fn default_lock_after() -> Duration { Duration::from_secs(43200) }
// start minimized and on login only in production mode
fn default_start_minimized() -> bool { !cfg!(debug_assertions) }
fn default_start_on_login() -> bool { !cfg!(debug_assertions) }
// struct DurationVisitor;
// impl<'de> Visitor<'de> for DurationVisitor {
// type Value = Duration;
// fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
// write!(formatter, "an integer between 0 and 2^64 - 1")
// }
// fn visit_u64<E: de::Error>(self, v: u64) -> Result<Duration, E> {
// Ok(Duration::from_secs(v))
// }
// }
// fn duration_from_secs<'de, D>(deserializer: D) -> Result<Duration, D::Error>
// where D: Deserializer<'de>
// {
// deserializer.deserialize_u64(DurationVisitor)
// }

View File

@ -247,6 +247,19 @@ pub enum UnlockError {
}
#[derive(Debug, ThisError, AsRefStr)]
pub enum LockError {
#[error("App is not unlocked")]
NotUnlocked,
#[error("Database error: {0}")]
DbError(#[from] SqlxError),
#[error(transparent)]
Setup(#[from] SetupError),
#[error(transparent)]
TauriError(#[from] tauri::Error),
}
#[derive(Debug, ThisError, AsRefStr)]
pub enum CryptoError {
#[error(transparent)]
@ -369,6 +382,7 @@ impl_serialize_basic!(SetupError);
impl_serialize_basic!(GetCredentialsError);
impl_serialize_basic!(ClientInfoError);
impl_serialize_basic!(WindowError);
impl_serialize_basic!(LockError);
impl Serialize for HandlerError {

View File

@ -56,6 +56,13 @@ pub async fn get_session_status(app_state: State<'_, AppState>) -> Result<String
}
#[tauri::command]
pub async fn signal_activity(app_state: State<'_, AppState>) -> Result<(), ()> {
app_state.signal_activity().await;
Ok(())
}
#[tauri::command]
pub async fn save_credentials(
credentials: BaseCredentials,

View File

@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::{
sync::RwLock,
@ -102,6 +103,7 @@ impl VisibilityLease {
pub struct AppState {
pub config: RwLock<AppConfig>,
pub session: RwLock<Session>,
pub last_activity: RwLock<OffsetDateTime>,
pub request_count: RwLock<u64>,
pub waiting_requests: RwLock<HashMap<u64, Sender<RequestResponse>>>,
pub pending_terminal_request: RwLock<bool>,
@ -123,6 +125,7 @@ impl AppState {
AppState {
config: RwLock::new(config),
session: RwLock::new(session),
last_activity: RwLock::new(OffsetDateTime::now_utc()),
request_count: RwLock::new(0),
waiting_requests: RwLock::new(HashMap::new()),
pending_terminal_request: RwLock::new(false),
@ -210,6 +213,38 @@ impl AppState {
Ok(())
}
pub async fn lock(&self) -> Result<(), LockError> {
let mut session = self.session.write().await;
match *session {
Session::Empty => Err(LockError::NotUnlocked),
Session::Locked(_) => Err(LockError::NotUnlocked),
Session::Unlocked{..} => {
*session = Session::load(&self.pool).await?;
let app_handle = app::APP.get().unwrap();
app_handle.emit_all("locked", None::<usize>)?;
Ok(())
}
}
}
pub async fn signal_activity(&self) {
let mut last_activity = self.last_activity.write().await;
*last_activity = OffsetDateTime::now_utc();
}
pub async fn should_auto_lock(&self) -> bool {
let config = self.config.read().await;
if !config.auto_lock || !self.is_unlocked().await {
return false;
}
let last_activity = self.last_activity.read().await;
let elapsed = OffsetDateTime::now_utc() - *last_activity;
elapsed >= config.lock_after
}
pub async fn is_unlocked(&self) -> bool {
let session = self.session.read().await;
matches!(*session, Session::Unlocked{..})
@ -223,7 +258,7 @@ impl AppState {
pub async fn session_creds_cloned(&self) -> Result<SessionCredentials, GetCredentialsError> {
let app_session = self.session.read().await;
let (_bsae, session) = app_session.try_get()?;
let (_base, session) = app_session.try_get()?;
Ok(session.clone())
}

View File

@ -1,12 +1,15 @@
use tauri::{
AppHandle,
CustomMenuItem,
Manager,
SystemTray,
SystemTrayEvent,
SystemTrayMenu,
CustomMenuItem,
async_runtime as rt,
};
use crate::app;
use crate::state::AppState;
pub fn create() -> SystemTray {
@ -21,13 +24,18 @@ pub fn create() -> SystemTray {
}
pub fn handle_event(app: &AppHandle, event: SystemTrayEvent) {
pub fn handle_event(app_handle: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::MenuItemClick{ id, .. } => {
match id.as_str() {
"exit" => app.exit(0),
"exit" => app_handle.exit(0),
"show_hide" => {
let _ = app::toggle_main_window(app);
let _ = app::toggle_main_window(app_handle);
let new_handle = app_handle.app_handle();
rt::spawn(async move {
let state = new_handle.state::<AppState>();
state.signal_activity().await;
});
}
_ => (),
}