upgrade to tauri 2.0 beta

This commit is contained in:
2024-06-01 20:17:50 -04:00
parent b165965289
commit 816bd7db00
28 changed files with 6323 additions and 1065 deletions

View File

@ -33,10 +33,9 @@ pub fn run() -> tauri::Result<()> {
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
show_main_window(app)
.error_popup("Failed to show main window");
.blocking_error_popup("Failed to show main window")
}))
.system_tray(tray::create())
.on_system_tray_event(tray::handle_event)
.plugin(tauri_plugin_global_shortcut::Builder::default().build())
.invoke_handler(tauri::generate_handler![
ipc::unlock,
ipc::respond,
@ -77,8 +76,8 @@ pub async fn connect_db() -> Result<SqlitePool, SetupError> {
async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
APP.set(app.handle()).unwrap();
APP.set(app.handle().clone()).unwrap();
tray::setup(app)?;
// get_or_create_db_path doesn't create the actual db file, just the directory
let is_first_launch = !config::get_or_create_db_path()?.exists();
let pool = connect_db().await?;
@ -96,7 +95,7 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
};
let session = Session::load(&pool).await?;
Server::start(app.handle())?;
Server::start(app.handle().clone())?;
config::set_auto_launch(conf.start_on_login)?;
if let Err(_e) = config::set_auto_launch(conf.start_on_login) {
@ -123,7 +122,7 @@ async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
app.manage(state);
// make sure we do this after managing app state, so that it doesn't panic
start_auto_locker(app.app_handle());
start_auto_locker(app.app_handle().clone());
Ok(())
}
@ -138,7 +137,7 @@ fn start_auto_locker(app: AppHandle) {
tokio::time::sleep(delay).await;
if state.should_auto_lock().await {
state.lock().await.error_popup("Failed to lock Creddy");
state.lock().await.error_popup("Failed to lock Creddy").await;
}
}
});
@ -146,27 +145,27 @@ fn start_auto_locker(app: AppHandle) {
pub fn show_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.show()?;
app.tray_handle()
.get_item("show_hide")
.set_title("Hide")?;
// app.tray_handle()
// .get_item("show_hide")
// .set_title("Hide")?;
Ok(())
}
pub fn hide_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.hide()?;
app.tray_handle()
.get_item("show_hide")
.set_title("Show")?;
// app.tray_handle()
// .get_item("show_hide")
// .set_title("Show")?;
Ok(())
}
pub fn toggle_main_window(app: &AppHandle) -> Result<(), WindowError> {
let w = app.get_window("main").ok_or(WindowError::NoMainWindow)?;
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
if w.is_visible()? {
hide_main_window(app)
}

View File

@ -135,9 +135,12 @@ impl LockedCredentials {
}
fn default_credentials_version() -> usize { 1 }
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BaseCredentials {
#[serde(default = "default_credentials_version")]
pub version: usize,
pub access_key_id: String,
pub secret_access_key: String,
@ -167,6 +170,7 @@ impl BaseCredentials {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SessionCredentials {
#[serde(default = "default_credentials_version")]
pub version: usize,
pub access_key_id: String,
pub secret_access_key: String,

View File

@ -1,7 +1,6 @@
use std::error::Error;
use std::convert::AsRef;
use std::ffi::OsString;
use std::sync::mpsc;
use std::string::FromUtf8Error;
use strum_macros::AsRefStr;
@ -10,14 +9,16 @@ use aws_sdk_sts::{
types::SdkError as AwsSdkError,
error::GetSessionTokenError,
};
use rfd::{
AsyncMessageDialog,
MessageDialog,
MessageLevel,
};
use sqlx::{
error::Error as SqlxError,
migrate::MigrateError,
};
use tauri::api::dialog::{
MessageDialogBuilder,
MessageDialogKind,
};
use tauri_plugin_global_shortcut::Error as ShortcutError;
use tokio::sync::oneshot::error::RecvError;
use serde::{
Serialize,
@ -27,33 +28,66 @@ use serde::{
};
pub trait ShowError {
fn error_popup(self, title: &str);
fn error_popup_nowait(self, title: &str);
#[allow(async_fn_in_trait)]
pub trait ShowError<T>
{
async fn error_popup(self, title: &str);
async fn error_popup_passthrough(self, title: &str) -> Result<T, ()>;
fn blocking_error_popup(self, title: &str);
fn error_print(self);
fn error_print_prefix(self, prefix: &str);
}
impl<E: std::fmt::Display> ShowError for Result<(), E> {
fn error_popup(self, title: &str) {
impl<T, E> ShowError<T> for Result<T, E>
where E: std::fmt::Display
{
async fn error_popup(self, title: &str) {
if let Err(e) = self {
let (tx, rx) = mpsc::channel();
MessageDialogBuilder::new(title, format!("{e}"))
.kind(MessageDialogKind::Error)
.show(move |_| tx.send(true).unwrap());
rx.recv().unwrap();
AsyncMessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show()
.await;
}
}
fn error_popup_nowait(self, title: &str) {
fn blocking_error_popup(self, title: &str) {
if let Err(e) = self {
MessageDialogBuilder::new(title, format!("{e}"))
.kind(MessageDialogKind::Error)
.show(|_| {})
MessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show();
}
}
async fn error_popup_passthrough(self, title: &str) -> Result<T, ()> {
match self {
Ok(v) => Ok(v),
Err(e) => {
AsyncMessageDialog::new()
.set_level(MessageLevel::Error)
.set_title(title)
.set_description(format!("{e}"))
.show()
.await;
Err(())
}
}
}
// fn error_popup_nowait(self, title: &str) {
// if let Err(e) = self {
// let app = app::APP.get().expect("Error popup failed, app handle not available");
// app.dialog()
// .message(format!("{e}"))
// .kind(MessageDialogKind::Error)
// .title(title)
// .show(|_| {})
// }
// }
fn error_print(self) {
if let Err(e) = self {
eprintln!("{e}");
@ -144,7 +178,7 @@ pub enum SetupError {
#[error("Failed to resolve data directory: {0}")]
DataDir(#[from] DataDirError),
#[error("Failed to register hotkeys: {0}")]
RegisterHotkeys(#[from] tauri::Error),
RegisterHotkeys(#[from] ShortcutError),
}

View File

@ -13,7 +13,7 @@ use creddy::{
fn main() {
let res = match cli::parser().get_matches().subcommand() {
None | Some(("run", _)) => {
app::run().error_popup("Creddy failed to start");
app::run().blocking_error_popup("Creddy failed to start");
Ok(())
},
Some(("get", m)) => cli::get(m),

View File

@ -126,12 +126,12 @@ async fn get_aws_credentials(
// so we bundle it all up in an async block and return a Result so we can handle errors
let proceed = async {
let notification = AwsRequestNotification {id: request_id, client, base};
app_handle.emit_all("credentials-request", &notification)?;
app_handle.emit("credentials-request", &notification)?;
let response = tokio::select! {
r = chan_recv => r?,
_ = waiter.wait_for_close() => {
app_handle.emit_all("request-cancelled", request_id)?;
app_handle.emit("request-cancelled", request_id)?;
return Err(HandlerError::Abandoned);
},
};

View File

@ -2,7 +2,6 @@ use std::io::ErrorKind;
use tokio::net::{UnixListener, UnixStream};
use tauri::{
AppHandle,
Manager,
async_runtime as rt,
};
@ -41,7 +40,7 @@ impl Server {
async fn try_serve(&self) -> Result<(), HandlerError> {
let (stream, _addr) = self.listener.accept().await?;
let new_handle = self.app_handle.app_handle();
let new_handle = self.app_handle.clone();
let client_pid = get_client_pid(&stream)?;
rt::spawn(async move {
super::handle(stream, new_handle, client_pid)

View File

@ -52,7 +52,7 @@ impl Server {
// create a new pipe instance to listen for the next client, and swap it in
let new_listener = ServerOptions::new().create(r"\\.\pipe\creddy-requests")?;
let stream = std::mem::replace(&mut self.listener, new_listener);
let new_handle = self.app_handle.app_handle();
let new_handle = self.app_handle.clone();
let client_pid = get_client_pid(&stream)?;
rt::spawn(async move {
super::handle(stream, new_handle, client_pid)

View File

@ -1,11 +1,16 @@
use serde::{Serialize, Deserialize};
use tauri::{
GlobalShortcutManager,
AppHandle,
Manager,
async_runtime as rt,
};
use tauri_plugin_global_shortcut::{
GlobalShortcutExt,
Error as ShortcutError,
};
use crate::app::APP;
use crate::config::HotkeysConfig;
use crate::errors::*;
@ -21,38 +26,62 @@ pub enum ShortcutAction {
pub fn exec_shortcut(action: ShortcutAction) {
match action {
ShortcutAction::LaunchTerminal => launch_terminal(),
ShortcutAction::ShowWindow => {
let app = APP.get().unwrap();
app.get_window("main")
.ok_or("Couldn't find application main window")
.map(|w| w.show().error_popup("Failed to show window"))
.error_popup("Failed to show window");
},
ShortcutAction::LaunchTerminal => {
rt::spawn(async {
terminal::launch(false).await.error_popup("Failed to launch terminal");
});
show_window(app);
},
}
}
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> tauri::Result<()> {
fn show_window(app: &AppHandle) {
let handle = app.clone();
rt::spawn(async move {
handle.get_webview_window("main")
.ok_or("Couldn't find application main window")
.error_popup_passthrough("Failed to show window").await?
.show()
.error_popup("Failed to show window").await;
Ok::<(), ()>(())
});
// app.get_webview_window("main") // Option<Window>
// .ok_or("Couldn't find application main window") // Result<Window, &'static str>
// .map(|w| w.show().error_popup("Failed to show window"))
// .error_popup("Failed to show window");
}
fn launch_terminal() {
rt::spawn(async {
terminal::launch(false)
.await
.error_popup("Failed to launch terminal")
.await;
});
}
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> Result<(), ShortcutError> {
let app = APP.get().unwrap();
let mut manager = app.global_shortcut_manager();
manager.unregister_all()?;
let shortcuts = app.global_shortcut();
shortcuts.unregister_all([
hotkeys.show_window.keys.as_str(),
hotkeys.launch_terminal.keys.as_str(),
])?;
if hotkeys.show_window.enabled {
manager.register(
&hotkeys.show_window.keys,
|| exec_shortcut(ShortcutAction::ShowWindow)
shortcuts.on_shortcut(
hotkeys.show_window.keys.as_str(),
|app, _shortcut, _event| show_window(app)
)?;
}
if hotkeys.launch_terminal.enabled {
manager.register(
&hotkeys.launch_terminal.keys,
|| exec_shortcut(ShortcutAction::LaunchTerminal)
shortcuts.on_shortcut(
hotkeys.launch_terminal.keys.as_str(),
|_app, _shortcut, _event| launch_terminal()
)?;
}

View File

@ -37,7 +37,7 @@ impl Visibility {
fn acquire(&mut self, delay_ms: u64) -> Result<VisibilityLease, WindowError> {
let app = crate::app::APP.get().unwrap();
let window = app.get_window("main")
let window = app.get_webview_window("main")
.ok_or(WindowError::NoMainWindow)?;
self.leases += 1;
@ -63,18 +63,17 @@ impl Visibility {
let lease = VisibilityLease { notify: tx };
let delay = Duration::from_millis(delay_ms);
let handle = app.app_handle();
rt::spawn(async move {
// We don't care if it's an error; lease being dropped should be handled identically
let _ = rx.await;
tokio::time::sleep(delay).await;
// we can't use `self` here because we would have to move it into the async block
let state = handle.state::<AppState>();
let state = app.state::<AppState>();
let mut visibility = state.visibility.write().await;
visibility.leases -= 1;
if visibility.leases == 0 {
if let Some(false) = visibility.original {
app::hide_main_window(&handle).error_print();
app::hide_main_window(app).error_print();
}
visibility.original = None;
}
@ -222,7 +221,7 @@ impl AppState {
*session = Session::load(&self.pool).await?;
let app_handle = app::APP.get().unwrap();
app_handle.emit_all("locked", None::<usize>)?;
app_handle.emit("locked", None::<usize>)?;
Ok(())
}

View File

@ -23,16 +23,16 @@ pub async fn launch(use_base: bool) -> Result<(), LaunchTerminalError> {
cmd
};
// if session is unlocked or empty, wait for credentials from frontend
// if session is locked or empty, wait for credentials from frontend
if !state.is_unlocked().await {
app.emit_all("launch-terminal-request", ())?;
app.emit("launch-terminal-request", ())?;
let lease = state.acquire_visibility_lease(0).await
.map_err(|_e| LaunchTerminalError::NoMainWindow)?; // automate conversion eventually?
let (tx, rx) = tokio::sync::oneshot::channel();
app.once_global("credentials-event", move |e| {
app.once("credentials-event", move |e| {
let success = match e.payload() {
Some("\"unlocked\"") | Some("\"entered\"") => true,
"\"unlocked\"" | "\"entered\"" => true,
_ => false,
};
let _ = tx.send(success);

View File

@ -1,45 +1,46 @@
use tauri::{
App,
AppHandle,
CustomMenuItem,
Manager,
SystemTray,
SystemTrayEvent,
SystemTrayMenu,
async_runtime as rt,
};
use tauri::menu::{
MenuBuilder,
MenuEvent,
MenuItemBuilder,
};
use crate::app;
use crate::state::AppState;
pub fn create() -> SystemTray {
let show = CustomMenuItem::new("show_hide".to_string(), "Show");
let quit = CustomMenuItem::new("exit".to_string(), "Exit");
pub fn setup(app: &App) -> tauri::Result<()> {
let show_hide = MenuItemBuilder::with_id("show_hide", "Show/Hide").build(app)?;
let exit = MenuItemBuilder::with_id("exit", "Exit").build(app)?;
let menu = SystemTrayMenu::new()
.add_item(show)
.add_item(quit);
let menu = MenuBuilder::new(app)
.items(&[&show_hide, &exit])
.build()?;
SystemTray::new().with_menu(menu)
let tray = app.tray_by_id("main").unwrap();
tray.set_menu(Some(menu))?;
tray.on_menu_event(handle_event);
Ok(())
}
pub fn handle_event(app_handle: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::MenuItemClick{ id, .. } => {
match id.as_str() {
"exit" => app_handle.exit(0),
"show_hide" => {
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;
});
}
_ => (),
}
}
fn handle_event(app_handle: &AppHandle, event: MenuEvent) {
match event.id.0.as_str() {
"exit" => app_handle.exit(0),
"show_hide" => {
let _ = app::toggle_main_window(app_handle);
let new_handle = app_handle.clone();
rt::spawn(async move {
let state = new_handle.state::<AppState>();
state.signal_activity().await;
});
},
_ => (),
}
}