diff --git a/Cargo.lock b/Cargo.lock index 9fc5b8d7..d1f1aeed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10297,6 +10297,7 @@ dependencies = [ "yaak-tauri-utils", "yaak-templates", "yaak-tls", + "yaak-window", "yaak-ws", ] @@ -10304,10 +10305,12 @@ dependencies = [ name = "yaak-app-proxy" version = "0.0.0" dependencies = [ + "log 0.4.29", "serde", "tauri", "tauri-build", "yaak-proxy", + "yaak-window", ] [[package]] @@ -10632,6 +10635,17 @@ dependencies = [ "yaak-models", ] +[[package]] +name = "yaak-window" +version = "0.1.0" +dependencies = [ + "log 0.4.29", + "md5 0.8.0", + "rand 0.9.1", + "tauri", + "tokio", +] + [[package]] name = "yaak-ws" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 473cd12f..df5cb002 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "crates-tauri/yaak-license", "crates-tauri/yaak-mac-window", "crates-tauri/yaak-tauri-utils", + "crates-tauri/yaak-window", ] [workspace.dependencies] @@ -72,6 +73,7 @@ yaak-fonts = { path = "crates-tauri/yaak-fonts" } yaak-license = { path = "crates-tauri/yaak-license" } yaak-mac-window = { path = "crates-tauri/yaak-mac-window" } yaak-tauri-utils = { path = "crates-tauri/yaak-tauri-utils" } +yaak-window = { path = "crates-tauri/yaak-window" } [profile.release] strip = false diff --git a/crates-tauri/yaak-app-client/Cargo.toml b/crates-tauri/yaak-app-client/Cargo.toml index df757a68..f79a1082 100644 --- a/crates-tauri/yaak-app-client/Cargo.toml +++ b/crates-tauri/yaak-app-client/Cargo.toml @@ -75,4 +75,5 @@ yaak-sse = { workspace = true } yaak-sync = { workspace = true } yaak-templates = { workspace = true } yaak-tls = { workspace = true } +yaak-window = { workspace = true } yaak-ws = { workspace = true } diff --git a/crates-tauri/yaak-app-client/src/lib.rs b/crates-tauri/yaak-app-client/src/lib.rs index 480995dd..8afc0d4c 100644 --- a/crates-tauri/yaak-app-client/src/lib.rs +++ b/crates-tauri/yaak-app-client/src/lib.rs @@ -78,10 +78,79 @@ mod render; mod sync_ext; mod updates; mod uri_scheme; -mod window; mod window_menu; mod ws_ext; +fn setup_window_menu(win: &WebviewWindow) -> Result<()> { + #[allow(unused_variables)] + let menu = window_menu::app_menu(win.app_handle())?; + + // This causes the window to not be clickable (in AppImage), so disable on Linux + #[cfg(not(target_os = "linux"))] + win.app_handle().set_menu(menu).expect("Failed to set app menu"); + + let webview_window = win.clone(); + win.on_menu_event(move |w, event| { + use tauri::{Emitter, LogicalSize, PhysicalSize}; + use tauri_plugin_opener::OpenerExt; + + if !w.is_focused().unwrap() { + return; + } + + let event_id = event.id().0.as_str(); + match event_id { + "hacked_quit" => { + w.webview_windows().iter().for_each(|(_, w)| { + info!("Closing window {}", w.label()); + let _ = w.close(); + }); + } + "close" => w.close().unwrap(), + "zoom_reset" => w.emit("zoom_reset", true).unwrap(), + "zoom_in" => w.emit("zoom_in", true).unwrap(), + "zoom_out" => w.emit("zoom_out", true).unwrap(), + "settings" => w.emit("settings", true).unwrap(), + "open_feedback" => { + if let Err(e) = + w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>) + { + warn!("Failed to open feedback {e:?}") + } + } + + // Commands for development + "dev.reset_size" => webview_window + .set_size(LogicalSize::new(1100.0, 600.0)) + .unwrap(), + "dev.reset_size_16x9" => { + let width = webview_window.outer_size().unwrap().width; + let height = width * 9 / 16; + webview_window.set_size(PhysicalSize::new(width, height)).unwrap() + } + "dev.reset_size_16x10" => { + let width = webview_window.outer_size().unwrap().width; + let height = width * 10 / 16; + webview_window.set_size(PhysicalSize::new(width, height)).unwrap() + } + "dev.refresh" => webview_window.eval("location.reload()").unwrap(), + "dev.generate_theme_css" => { + w.emit("generate_theme_css", true).unwrap(); + } + "dev.toggle_devtools" => { + if webview_window.is_devtools_open() { + webview_window.close_devtools(); + } else { + webview_window.open_devtools(); + } + } + _ => {} + } + }); + + Ok(()) +} + /// Extension trait for easily creating a PluginContext from a WebviewWindow pub trait PluginContextExt { fn plugin_context(&self) -> PluginContext; @@ -1452,13 +1521,17 @@ async fn cmd_new_child_window( title: &str, inner_size: (f64, f64), ) -> YaakResult<()> { - window::create_child_window(&parent_window, url, label, title, inner_size)?; + let use_native_titlebar = parent_window.app_handle().db().get_settings().use_native_titlebar; + let win = yaak_window::window::create_child_window(&parent_window, url, label, title, inner_size, use_native_titlebar)?; + setup_window_menu(&win)?; Ok(()) } #[tauri::command] async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> YaakResult<()> { - window::create_main_window(&app_handle, url)?; + let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar; + let win = yaak_window::window::create_main_window(&app_handle, url, use_native_titlebar)?; + setup_window_menu(&win)?; Ok(()) } @@ -1737,7 +1810,10 @@ pub fn run() { .run(|app_handle, event| { match event { RunEvent::Ready => { - let _ = window::create_main_window(app_handle, "/"); + let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar; + if let Ok(win) = yaak_window::window::create_main_window(app_handle, "/", use_native_titlebar) { + let _ = setup_window_menu(&win); + } let h = app_handle.clone(); tauri::async_runtime::spawn(async move { let info = history::get_or_upsert_launch_info(&h); diff --git a/crates-tauri/yaak-app-client/src/plugin_events.rs b/crates-tauri/yaak-app-client/src/plugin_events.rs index 3c84da6d..1066a60e 100644 --- a/crates-tauri/yaak-app-client/src/plugin_events.rs +++ b/crates-tauri/yaak-app-client/src/plugin_events.rs @@ -3,7 +3,7 @@ use crate::http_request::send_http_request_with_context; use crate::models_ext::BlobManagerExt; use crate::models_ext::QueryManagerExt; use crate::render::{render_grpc_request, render_http_request, render_json_value}; -use crate::window::{CreateWindowConfig, create_window}; +use yaak_window::window::{CreateWindowConfig, create_window}; use crate::{ call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_plugin_context, workspace_from_window, @@ -320,6 +320,7 @@ async fn handle_host_plugin_request( HostRequest::OpenWindow(req) => { let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128); let (close_tx, mut close_rx) = tokio::sync::mpsc::channel(128); + let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar; let win_config = CreateWindowConfig { url: &req.url, label: &req.label, @@ -328,6 +329,7 @@ async fn handle_host_plugin_request( close_tx: Some(close_tx), inner_size: req.size.clone().map(|s| (s.width, s.height)), data_dir_key: req.data_dir_key.clone(), + use_native_titlebar, ..Default::default() }; if let Err(e) = create_window(app_handle, win_config) { diff --git a/crates-tauri/yaak-app-proxy/Cargo.toml b/crates-tauri/yaak-app-proxy/Cargo.toml index 9cc4c880..53c01d50 100644 --- a/crates-tauri/yaak-app-proxy/Cargo.toml +++ b/crates-tauri/yaak-app-proxy/Cargo.toml @@ -13,6 +13,8 @@ crate-type = ["staticlib", "cdylib", "lib"] tauri-build = { version = "2.5.3", features = [] } [dependencies] +log = { workspace = true } serde = { workspace = true, features = ["derive"] } tauri = { workspace = true } yaak-proxy = { workspace = true } +yaak-window = { workspace = true } diff --git a/crates-tauri/yaak-app-proxy/src/lib.rs b/crates-tauri/yaak-app-proxy/src/lib.rs index 201ab28d..4d9adaec 100644 --- a/crates-tauri/yaak-app-proxy/src/lib.rs +++ b/crates-tauri/yaak-app-proxy/src/lib.rs @@ -1,7 +1,9 @@ +use log::error; use serde::Serialize; use std::sync::Mutex; -use tauri::State; +use tauri::{RunEvent, State}; use yaak_proxy::ProxyHandle; +use yaak_window::window::CreateWindowConfig; #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -58,6 +60,22 @@ pub fn run() { tauri::Builder::default() .manage(ProxyState::default()) .invoke_handler(tauri::generate_handler![proxy_metadata, proxy_start, proxy_stop]) - .run(tauri::generate_context!()) - .expect("error while running yaak proxy tauri application"); + .build(tauri::generate_context!()) + .expect("error while building yaak proxy tauri application") + .run(|app_handle, event| { + if let RunEvent::Ready = event { + let config = CreateWindowConfig { + url: "/", + label: "main", + title: "Yaak Proxy", + inner_size: Some((1000.0, 700.0)), + visible: true, + hide_titlebar: true, + ..Default::default() + }; + if let Err(e) = yaak_window::window::create_window(app_handle, config) { + error!("Failed to create proxy window: {e:?}"); + } + } + }); } diff --git a/crates-tauri/yaak-app-proxy/tauri.conf.json b/crates-tauri/yaak-app-proxy/tauri.conf.json index 83acb27c..846f8bf1 100644 --- a/crates-tauri/yaak-app-proxy/tauri.conf.json +++ b/crates-tauri/yaak-app-proxy/tauri.conf.json @@ -10,16 +10,7 @@ }, "app": { "withGlobalTauri": false, - "windows": [ - { - "label": "main", - "title": "Yaak Proxy", - "width": 1000, - "height": 700, - "minWidth": 720, - "minHeight": 480 - } - ] + "windows": [] }, "bundle": { "icon": [ diff --git a/crates-tauri/yaak-window/Cargo.toml b/crates-tauri/yaak-window/Cargo.toml new file mode 100644 index 00000000..9dee5062 --- /dev/null +++ b/crates-tauri/yaak-window/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "yaak-window" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +log = { workspace = true } +md5 = "0.8.0" +rand = "0.9.0" +tauri = { workspace = true } +tokio = { workspace = true, features = ["sync"] } diff --git a/crates-tauri/yaak-window/src/lib.rs b/crates-tauri/yaak-window/src/lib.rs new file mode 100644 index 00000000..61b63f17 --- /dev/null +++ b/crates-tauri/yaak-window/src/lib.rs @@ -0,0 +1 @@ +pub mod window; diff --git a/crates-tauri/yaak-app-client/src/window.rs b/crates-tauri/yaak-window/src/window.rs similarity index 65% rename from crates-tauri/yaak-app-client/src/window.rs rename to crates-tauri/yaak-window/src/window.rs index 541c6d16..950ed3c7 100644 --- a/crates-tauri/yaak-app-client/src/window.rs +++ b/crates-tauri/yaak-window/src/window.rs @@ -1,13 +1,6 @@ -use crate::error::Result; -use crate::models_ext::QueryManagerExt; -use crate::window_menu::app_menu; -use log::{info, warn}; +use log::info; use rand::random; -use tauri::{ - AppHandle, Emitter, LogicalSize, Manager, PhysicalSize, Runtime, WebviewUrl, WebviewWindow, - WindowEvent, -}; -use tauri_plugin_opener::OpenerExt; +use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WindowEvent}; use tokio::sync::mpsc; const DEFAULT_WINDOW_WIDTH: f64 = 1100.0; @@ -16,11 +9,11 @@ const DEFAULT_WINDOW_HEIGHT: f64 = 600.0; const MIN_WINDOW_WIDTH: f64 = 300.0; const MIN_WINDOW_HEIGHT: f64 = 300.0; -pub(crate) const MAIN_WINDOW_PREFIX: &str = "main_"; +pub const MAIN_WINDOW_PREFIX: &str = "main_"; const OTHER_WINDOW_PREFIX: &str = "other_"; #[derive(Default, Debug)] -pub(crate) struct CreateWindowConfig<'s> { +pub struct CreateWindowConfig<'s> { pub url: &'s str, pub label: &'s str, pub title: &'s str, @@ -29,27 +22,22 @@ pub(crate) struct CreateWindowConfig<'s> { pub navigation_tx: Option>, pub close_tx: Option>, pub data_dir_key: Option, + pub visible: bool, pub hide_titlebar: bool, + pub use_native_titlebar: bool, } -pub(crate) fn create_window( +pub fn create_window( handle: &AppHandle, config: CreateWindowConfig, -) -> Result> { - #[allow(unused_variables)] - let menu = app_menu(handle)?; - - // This causes the window to not be clickable (in AppImage), so disable on Linux - #[cfg(not(target_os = "linux"))] - handle.set_menu(menu).expect("Failed to set app menu"); - +) -> tauri::Result> { info!("Create new window label={}", config.label); let mut win_builder = tauri::WebviewWindowBuilder::new(handle, config.label, WebviewUrl::App(config.url.into())) .title(config.title) .resizable(true) - .visible(false) // To prevent theme flashing, the frontend code calls show() immediately after configuring the theme + .visible(config.visible) .fullscreen(false) .min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT); @@ -96,8 +84,7 @@ pub(crate) fn create_window( }); } - let settings = handle.db().get_settings(); - if config.hide_titlebar && !settings.use_native_titlebar { + if config.hide_titlebar && !config.use_native_titlebar { #[cfg(target_os = "macos")] { use tauri::TitleBarStyle; @@ -129,68 +116,14 @@ pub(crate) fn create_window( }); } - let webview_window = win.clone(); - win.on_menu_event(move |w, event| { - if !w.is_focused().unwrap() { - return; - } - - let event_id = event.id().0.as_str(); - match event_id { - "hacked_quit" => { - // Cmd+Q on macOS doesn't trigger `CloseRequested` so we use a custom Quit menu - // and trigger close() for each window. - w.webview_windows().iter().for_each(|(_, w)| { - info!("Closing window {}", w.label()); - let _ = w.close(); - }); - } - "close" => w.close().unwrap(), - "zoom_reset" => w.emit("zoom_reset", true).unwrap(), - "zoom_in" => w.emit("zoom_in", true).unwrap(), - "zoom_out" => w.emit("zoom_out", true).unwrap(), - "settings" => w.emit("settings", true).unwrap(), - "open_feedback" => { - if let Err(e) = - w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>) - { - warn!("Failed to open feedback {e:?}") - } - } - - // Commands for development - "dev.reset_size" => webview_window - .set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)) - .unwrap(), - "dev.reset_size_16x9" => { - let width = webview_window.outer_size().unwrap().width; - let height = width * 9 / 16; - webview_window.set_size(PhysicalSize::new(width, height)).unwrap() - } - "dev.reset_size_16x10" => { - let width = webview_window.outer_size().unwrap().width; - let height = width * 10 / 16; - webview_window.set_size(PhysicalSize::new(width, height)).unwrap() - } - "dev.refresh" => webview_window.eval("location.reload()").unwrap(), - "dev.generate_theme_css" => { - w.emit("generate_theme_css", true).unwrap(); - } - "dev.toggle_devtools" => { - if webview_window.is_devtools_open() { - webview_window.close_devtools(); - } else { - webview_window.open_devtools(); - } - } - _ => {} - } - }); - Ok(win) } -pub(crate) fn create_main_window(handle: &AppHandle, url: &str) -> Result { +pub fn create_main_window( + handle: &AppHandle, + url: &str, + use_native_titlebar: bool, +) -> tauri::Result { let mut counter = 0; let label = loop { let label = format!("{MAIN_WINDOW_PREFIX}{counter}"); @@ -212,19 +145,21 @@ pub(crate) fn create_main_window(handle: &AppHandle, url: &str) -> Result() * 20.0, )), hide_titlebar: true, + use_native_titlebar, ..Default::default() }; create_window(handle, config) } -pub(crate) fn create_child_window( +pub fn create_child_window( parent_window: &WebviewWindow, url: &str, label: &str, title: &str, inner_size: (f64, f64), -) -> Result { + use_native_titlebar: bool, +) -> tauri::Result { let app_handle = parent_window.app_handle(); let label = format!("{OTHER_WINDOW_PREFIX}_{label}"); let scale_factor = parent_window.scale_factor()?; @@ -245,6 +180,7 @@ pub(crate) fn create_child_window( inner_size: Some(inner_size), position: Some(position), hide_titlebar: true, + use_native_titlebar, ..Default::default() };