mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
253 lines
9 KiB
Rust
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");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|