diff --git a/.changeset/windows-hotfix.md b/.changeset/windows-hotfix.md new file mode 100644 index 00000000..6c14f166 --- /dev/null +++ b/.changeset/windows-hotfix.md @@ -0,0 +1,3 @@ +### Bug Fixes +- On Windows, File > New window no longer freezes Stroke. The window opens, and the app closes normally +- On Windows, psql tables in the Terminal tab no longer show garbled borders like `│`, and NULL shows as `(null)` diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 8935f1fd..5ea34bc9 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -270,8 +270,13 @@ pub async fn read_file(path: String) -> Result { /// The chrome is built to match the main window exactly: frameless everywhere, /// macOS keeping its native traffic lights, and the dark base colour painted /// before the first frame so no white flash escapes while the frontend boots. +/// +/// Async on purpose. A synchronous command runs on the main thread, and +/// building a WebView2 window there deadlocks on Windows (tauri-apps/wry#583): +/// the window never appeared, and every other window froze with it, so the +/// app could not even be closed. #[tauri::command] -pub fn open_new_window(app: tauri::AppHandle) -> Result<(), String> { +pub async fn open_new_window(app: tauri::AppHandle) -> Result<(), String> { // Labels must be unique and stable-ish; the counter restarts with the app, // and a closed label is free to reuse, so probe for the first gap. let label = (2..64) @@ -290,7 +295,12 @@ pub fn open_new_window(app: tauri::AppHandle) -> Result<(), String> { .resizable(true) // Hidden until its page reveals itself, same as the main window. .visible(false) - .background_color(tauri::window::Color(8, 8, 8, 255)); + .background_color(tauri::window::Color(8, 8, 8, 255)) + // The rest of the main window's settings (lib.rs): inspector in debug + // builds only, no injected zoom polyfill, no navigating away from the app. + .devtools(cfg!(debug_assertions)) + .zoom_hotkeys_enabled(false) + .on_navigation(crate::navigation_allowed); #[cfg(target_os = "macos")] { @@ -304,6 +314,10 @@ pub fn open_new_window(app: tauri::AppHandle) -> Result<(), String> { } let window = builder.build().map_err(|e| e.to_string())?; + // The surface on the OS's side of light and dark before the first frame. + let surface = crate::surface_for_theme(window.theme().unwrap_or(tauri::Theme::Dark)); + let _ = window.set_background_color(Some(surface)); + crate::set_webview_backdrop(&window, surface); crate::arm_reveal_failsafe(&window); Ok(()) } diff --git a/src-tauri/src/db/terminal.rs b/src-tauri/src/db/terminal.rs index 388713f2..ea8145d0 100644 --- a/src-tauri/src/db/terminal.rs +++ b/src-tauri/src/db/terminal.rs @@ -317,6 +317,21 @@ const PSQL_PROMPT1: &str = "%[%033]133;A%007%]%[%033[1;34m%]%/%[%033[0m%]%R%[%033[33m%]%x%[%033[0m%]%# %[%033]133;B%007%]"; const PSQL_PROMPT2: &str = "%[%033]133;A;k=s%007%]%[%033[2m%]%/%R%x%#%[%033[0m%] %[%033]133;B%007%]"; +/// Table borders and the NULL marker. Unicode borders and `∅` everywhere but +/// Windows: psql writes those borders as UTF-8 whatever its client encoding, +/// and the Windows console reads its output in the ANSI code page (1252), so +/// every `│` came out as `│`; `∅` reached it through a 1252 argv as `?`. There +/// psql keeps its own ASCII borders, and NULL shows as `(null)`, which read +/// the same in any code page. +fn psql_display_args(windows: bool) -> Vec { + if windows { + vec!["--pset=null=(null)".to_string()] + } else { + // NULL apart from the empty string. + vec!["--pset=linestyle=unicode".to_string(), "--pset=null=\u{2205}".to_string()] + } +} + async fn pg_launch(config: PgConfig) -> Result { let (c, tunnel) = resolve_pg_ssh(config).await?; let mut env = vec![ @@ -355,10 +370,10 @@ async fn pg_launch(config: PgConfig) -> Result { // and narrow results stay plain tables. (`format=wrapped` was tried // and dropped: it squeezes columns and breaks names mid-word.) "--pset=expanded=auto".to_string(), - "--pset=linestyle=unicode".to_string(), - // NULL apart from the empty string. - "--pset=null=\u{2205}".to_string(), - ], + ] + .into_iter() + .chain(psql_display_args(cfg!(windows))) + .collect(), env, unset: &["PGSERVICE", "PGPASSWORD", "PGSSLMODE", "PGSSLROOTCERT", "PGTZ", "PGOPTIONS"], tunnel, @@ -985,6 +1000,15 @@ mod tests { assert!(!env.contains_key("PGTZ")); } + #[test] + fn psql_keeps_ascii_borders_on_windows() { + let unix = psql_display_args(false); + assert!(unix.contains(&"--pset=linestyle=unicode".to_string())); + let windows = psql_display_args(true); + assert!(windows.iter().all(|a| a.is_ascii() && !a.contains("linestyle")), "{windows:?}"); + assert!(windows.contains(&"--pset=null=(null)".to_string())); + } + #[test] fn sqlite_gets_box_mode_by_version() { let c = SqliteConfig { name: "t".into(), file_path: "/tmp/x.db".into() }; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 37c4e9fe..8d47abbf 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -43,7 +43,7 @@ use tauri::Manager; const DARK_SURFACE: tauri::window::Color = tauri::window::Color(8, 8, 8, 255); const LIGHT_SURFACE: tauri::window::Color = tauri::window::Color(247, 247, 247, 255); -fn surface_for_theme(theme: tauri::Theme) -> tauri::window::Color { +pub(crate) fn surface_for_theme(theme: tauri::Theme) -> tauri::window::Color { match theme { tauri::Theme::Light => LIGHT_SURFACE, _ => DARK_SURFACE, @@ -58,6 +58,16 @@ fn surface_for_theme(theme: tauri::Theme) -> tauri::window::Color { /// never loads), which would otherwise leave a live process with no window. const REVEAL_FAILSAFE: std::time::Duration = std::time::Duration::from_secs(4); +/// Whether a window may navigate to `url`: only the app's own pages. A link +/// to anywhere else opens in the browser instead of replacing the app. +pub(crate) fn navigation_allowed(url: &tauri::Url) -> bool { + if matches!(url.scheme(), "tauri" | "ipc") { + return true; + } + let host = url.host_str().unwrap_or(""); + host == "localhost" || host == "tauri.localhost" || host == "127.0.0.1" +} + pub(crate) fn arm_reveal_failsafe(window: &tauri::WebviewWindow) { let window = window.clone(); std::thread::spawn(move || { @@ -82,7 +92,7 @@ pub(crate) fn arm_reveal_failsafe(window: &tauri::WebviewWindow) { /// - WKWebView (macOS): `underPageBackgroundColor` /// - WebView2 (Windows): `ICoreWebView2Controller2::DefaultBackgroundColor` /// - WebKitGTK (Linux): `webkit_web_view_set_background_color` -fn set_webview_backdrop(window: &tauri::WebviewWindow, color: tauri::window::Color) { +pub(crate) fn set_webview_backdrop(window: &tauri::WebviewWindow, color: tauri::window::Color) { #[cfg(target_os = "macos")] set_macos_webview_backdrop(window, color); #[cfg(target_os = "windows")] @@ -397,14 +407,7 @@ pub fn run() { // a stray trackpad pinch near a column resize handle would then page-zoom // the whole webview (devicePixelRatio jumps, canvas renders blurry). .zoom_hotkeys_enabled(false) - .on_navigation(|url| { - let scheme = url.scheme(); - if matches!(scheme, "tauri" | "ipc") { - return true; - } - let host = url.host_str().unwrap_or(""); - host == "localhost" || host == "tauri.localhost" || host == "127.0.0.1" - }) + .on_navigation(navigation_allowed) .build()?; // Match the surface to the OS appearance before the first frame. Both