safing-portmaster/desktop/tauri/src-tauri/src/main.rs
2024-12-10 14:21:34 +02:00

253 lines
9 KiB
Rust

// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::{env, path::Path, time::Duration};
use tauri::{AppHandle, Emitter, Listener, Manager, RunEvent, WindowEvent};
// Library crates
mod portapi;
mod service;
#[cfg(target_os = "linux")]
mod xdg;
// App modules
mod cli;
mod config;
mod portmaster;
mod traymenu;
mod window;
use log::{debug, error, info};
use portmaster::PortmasterExt;
use tauri_plugin_log::RotationStrategy;
use traymenu::setup_tray_menu;
use window::{close_splash_window, create_main_window, hide_splash_window};
#[macro_use]
extern crate lazy_static;
const FALLBACK_TO_OLD_UI_EXIT_CODE: i32 = 77;
#[derive(Clone, serde::Serialize)]
struct Payload {
args: Vec<String>,
cwd: String,
}
struct WsHandler {
handle: AppHandle,
background: bool,
is_first_connect: bool,
}
impl portmaster::Handler for WsHandler {
fn name(&self) -> String {
"main-handler".to_string()
}
fn on_connect(&mut self, cli: portapi::client::PortAPI) {
info!("connection established, creating main window");
// we successfully connected to Portmaster. Set is_first_connect to false
// so we don't show the splash-screen when we loose connection.
self.is_first_connect = false;
// The order is important. If all current windows are destroyed tauri will exit.
// First create the main ui window then destroy the splash screen.
// Hide splash screen. Will be closed after main window is created.
if let Err(err) = hide_splash_window(&self.handle) {
error!("failed to close splash window: {}", err.to_string());
}
// create the main window now. It's not automatically visible by default.
// Rather, the angular application will show the window itself when it finished
// bootstrapping.
if let Err(err) = create_main_window(&self.handle) {
error!("failed to create main window: {}", err.to_string());
} else {
debug!("created main window")
}
// Now it is safe to destroy the splash window.
if let Err(err) = close_splash_window(&self.handle) {
error!("failed to close splash window: {}", err.to_string());
}
let handle = self.handle.clone();
tauri::async_runtime::spawn(async move {
traymenu::tray_handler(cli, handle).await;
});
}
fn on_disconnect(&mut self) {
// if we're not running in background and this was the first connection attempt
// then display the splash-screen.
//
// Once we had a successful connection the splash-screen will not be shown anymore
// since there's already a main window with the angular application.
if !self.background && self.is_first_connect {
let _ = window::create_splash_window(&self.handle.clone());
self.is_first_connect = false
}
}
}
fn show_webview_not_installed_dialog() -> i32 {
use rfd::MessageDialog;
let result = MessageDialog::new()
.set_title("Portmaster")
.set_description("Webkit is not installed. Please install it and run portmaster again")
.set_buttons(rfd::MessageButtons::OkCancelCustom(
"Go to install page".to_owned(),
"Use old UI".to_owned(),
))
.show();
println!("{:?}", result);
if let rfd::MessageDialogResult::Custom(result) = result {
if result.eq("Go to install page") {
_ = open::that("https://wiki.safing.io/en/Portmaster/Install/Webview");
std::thread::sleep(Duration::from_secs(2));
return 0;
}
}
FALLBACK_TO_OLD_UI_EXIT_CODE
}
fn main() {
if tauri::webview_version().is_err() {
std::process::exit(show_webview_not_installed_dialog());
}
let cli_args = cli::parse(std::env::args());
// TODO(vladimir): Support for other log targets?
#[cfg(target_os = "linux")]
let log_target = tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout);
// let log_target = if let Some(data_dir) = cli_args.data {
// tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Folder {
// path: Path::new(&format!("{}/logs/app2", data_dir)).into(),
// file_name: None,
// })
// } else {
// };
// TODO(vladimir): Permission for logs/app2 folder are not guaranteed. Use the default location for now.
#[cfg(target_os = "windows")]
let log_target = if let Some(data_dir) = cli_args.data {
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir { file_name: None })
} else {
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout)
};
let app = tauri::Builder::default()
// Shell plugin for open_external support
.plugin(tauri_plugin_shell::init())
// Initialize Logging plugin.
.plugin(
tauri_plugin_log::Builder::default()
.level(cli_args.log_level)
.rotation_strategy(RotationStrategy::KeepAll)
.clear_targets()
.target(log_target)
.build(),
)
// Clipboard support
.plugin(tauri_plugin_clipboard_manager::init())
// Dialog (Save/Open) support
.plugin(tauri_plugin_dialog::init())
// OS Version and Architecture support
.plugin(tauri_plugin_os::init())
// Initialize save windows state plugin.
.plugin(tauri_plugin_window_state::Builder::default().build())
// Single instance guard
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
// Send info to already dunning instance.
let _ = app.emit("single-instance", Payload { args: argv, cwd });
}))
// Notification support
.plugin(tauri_plugin_notification::init())
.invoke_handler(tauri::generate_handler![
portmaster::commands::get_app_info,
portmaster::commands::get_service_manager_status,
portmaster::commands::start_service,
portmaster::commands::get_state,
portmaster::commands::set_state,
portmaster::commands::should_show,
portmaster::commands::should_handle_prompts
])
// Setup the app an any listeners
.setup(move |app| {
setup_tray_menu(app)?;
portmaster::setup(app.handle().clone());
// Setup the single-instance event listener that will create/focus the main window
// or the splash-screen.
let handle = app.handle().clone();
app.listen_any("single-instance", move |_event| {
let _ = window::open_window(&handle);
});
// Handle cli flags:
app.portmaster()
.set_show_after_bootstrap(!cli_args.background);
app.portmaster()
.with_notification_support(cli_args.with_notifications);
app.portmaster()
.with_connection_prompts(cli_args.with_prompts);
// prepare a custom portmaster plugin handler that will show the splash-screen
// (if not in --background) and launch the tray-icon handler.
let handler = WsHandler {
handle: app.handle().clone(),
background: cli_args.background,
is_first_connect: true,
};
// register the custom handler
app.portmaster().register_handler(handler);
Ok(())
})
.any_thread()
.build(tauri::generate_context!())
.expect("error while running tauri application");
app.run(|handle, e| {
if let RunEvent::WindowEvent { label, event, .. } = e {
if label != "main" {
// We only have one window at most so any other label is unexpected
return;
}
// Do not let the user close the window, instead send an event to the main
// window so we can show the "will not stop portmaster" dialog and let the window
// close itself using
//
// window.__TAURI__.window.getCurrent().close()
//
// Note: the above javascript does NOT trigger the CloseRequested event so
// there's no need to handle that case here.
if let WindowEvent::CloseRequested { api, .. } = event {
debug!(
"window (label={}) close request received, forwarding to user-interface.",
label
);
api.prevent_close();
if let Some(window) = handle.get_webview_window(label.as_str()) {
let result = window.emit("exit-requested", "");
if let Err(err) = result {
error!("failed to emit event: {}", err.to_string());
}
} else {
error!("window was None");
}
}
}
});
}