basic system tray functionality

This commit is contained in:
Joseph Montanaro 2022-12-21 14:49:01 -08:00
parent 50f0985f4f
commit 5ffa55c03c
9 changed files with 136 additions and 14 deletions

36
src-tauri/Cargo.lock generated
View File

@ -1879,6 +1879,30 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libappindicator"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8"
dependencies = [
"glib",
"gtk",
"gtk-sys",
"libappindicator-sys",
"log",
]
[[package]]
name = "libappindicator-sys"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa"
dependencies = [
"gtk-sys",
"libloading",
"once_cell",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.126" version = "0.2.126"
@ -1894,6 +1918,16 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]] [[package]]
name = "libsodium-sys" name = "libsodium-sys"
version = "0.2.7" version = "0.2.7"
@ -3622,6 +3656,7 @@ dependencies = [
"core-foundation", "core-foundation",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
"dirs-next",
"dispatch", "dispatch",
"gdk", "gdk",
"gdk-pixbuf", "gdk-pixbuf",
@ -3635,6 +3670,7 @@ dependencies = [
"instant", "instant",
"jni 0.19.0", "jni 0.19.0",
"lazy_static", "lazy_static",
"libappindicator",
"libc", "libc",
"log", "log",
"ndk", "ndk",

View File

@ -17,7 +17,7 @@ tauri-build = { version = "1.0.4", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.0.5", features = ["api-all"] } tauri = { version = "1.0.5", features = ["api-all", "system-tray"] }
sodiumoxide = "0.2.7" sodiumoxide = "0.2.7"
tokio = { version = ">=1.19", features = ["full"] } tokio = { version = ">=1.19", features = ["full"] }
sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] } sqlx = { version = "0.6.2", features = ["sqlite", "runtime-tokio-rustls"] }

View File

@ -1,5 +1,8 @@
use core::time::Duration;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use tauri::State; use tauri::State;
use tokio::time::sleep;
use crate::clientinfo::Client; use crate::clientinfo::Client;
use crate::state::{AppState, Session, Credentials}; use crate::state::{AppState, Session, Credentials};
@ -27,9 +30,20 @@ pub enum Approval {
#[tauri::command] #[tauri::command]
pub fn respond(response: RequestResponse, app_state: State<'_, AppState>) -> Result<(), String> { pub fn respond(
response: RequestResponse,
window: tauri::Window,
app_state: State<'_, AppState>
) -> Result<(), String> {
app_state.send_response(response) app_state.send_response(response)
.map_err(|e| format!("Error responding to request: {e}")) .map_err(|e| format!("Error responding to request: {e}"))?;
tauri::async_runtime::spawn(async move {
sleep(Duration::from_secs(3)).await;
let _ = window.hide();
});
Ok(())
} }

View File

@ -11,6 +11,7 @@ mod clientinfo;
mod ipc; mod ipc;
mod state; mod state;
mod server; mod server;
mod tray;
use state::AppState; use state::AppState;
@ -23,6 +24,8 @@ fn main() {
tauri::Builder::default() tauri::Builder::default()
.manage(initial_state) .manage(initial_state)
.system_tray(tray::create())
.on_system_tray_event(tray::handle_event)
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
ipc::unlock, ipc::unlock,
ipc::respond, ipc::respond,
@ -36,6 +39,16 @@ fn main() {
tauri::async_runtime::spawn(server::serve(addr, app.handle())); tauri::async_runtime::spawn(server::serve(addr, app.handle()));
Ok(()) Ok(())
}) })
.run(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application")
.run(|app, run_event| match run_event {
tauri::RunEvent::WindowEvent { label, event, .. } => match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
let _ = app.get_window(&label).map(|w| w.hide());
api.prevent_close();
}
_ => ()
}
_ => ()
})
} }

View File

@ -93,7 +93,10 @@ impl Handler {
async fn notify_frontend(&self, req: &Request) -> Result<(), RequestError> { async fn notify_frontend(&self, req: &Request) -> Result<(), RequestError> {
self.app.emit_all("credentials-request", req)?; self.app.emit_all("credentials-request", req)?;
let window = self.app.get_window("main").ok_or(RequestError::NoMainWindow)?; let window = self.app.get_window("main").ok_or(RequestError::NoMainWindow)?;
if !window.is_visible()? {
window.unminimize()?; window.unminimize()?;
window.show()?;
}
window.set_focus()?; window.set_focus()?;
Ok(()) Ok(())
} }

36
src-tauri/src/tray.rs Normal file
View File

@ -0,0 +1,36 @@
use tauri::{
AppHandle,
Manager,
SystemTray,
SystemTrayEvent,
SystemTrayMenu,
CustomMenuItem,
};
pub fn create() -> SystemTray {
let show = CustomMenuItem::new("show".to_string(), "Show");
let quit = CustomMenuItem::new("exit".to_string(), "Exit");
let menu = SystemTrayMenu::new()
.add_item(show)
.add_item(quit);
SystemTray::new().with_menu(menu)
}
pub fn handle_event(app: &AppHandle, event: SystemTrayEvent) {
match event {
SystemTrayEvent::MenuItemClick{ id, .. } => {
match id.as_str() {
"exit" => app.exit(0),
"show" => {
let _ = app.get_window("main").map(|w| w.show());
}
_ => (),
}
}
_ => (),
}
}

View File

@ -62,6 +62,10 @@
"title": "Creddy", "title": "Creddy",
"width": 800 "width": 800
} }
] ],
"systemTray": {
"iconPath": "icons/icon.png",
"iconAsTemplate": true
}
} }
} }

View File

@ -27,6 +27,9 @@
if (event.shiftKey && (event.code === 'Enter' || event.code === 'NumpadEnter')) { if (event.shiftKey && (event.code === 'Enter' || event.code === 'NumpadEnter')) {
approve(); approve();
} }
else if (event.code === 'Escape') {
deny();
}
} }
</script> </script>

View File

@ -4,18 +4,31 @@
import { invoke } from '@tauri-apps/api/tauri'; import { invoke } from '@tauri-apps/api/tauri';
export let appState; export let appState;
let error = null;
onMount(async () => { const dispatch = createEventDispatcher();
async function respond() {
let response = { let response = {
id: appState.currentRequest.id, id: appState.currentRequest.id,
approval: 'Denied', approval: 'Denied',
} }
try {
await invoke('respond', {response}); await invoke('respond', {response});
appState.currentRequest = null; appState.currentRequest = null;
}); }
catch (e) {
error = e;
}
const dispatch = createEventDispatcher();
window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000); window.setTimeout(() => dispatch('navigate', {target: 'Home'}), 3000);
}
onMount(respond);
</script> </script>
{#if error}
<div class="text-red-400">{error}</div>
{:else}
<h1 class="text-4xl text-gray-300">Denied!</h1> <h1 class="text-4xl text-gray-300">Denied!</h1>
{/if}