From 9efee50d405b8ca990d30114fb6ec6a57e2e92f1 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 22 Oct 2026 02:02:27 +0000 Subject: feat(bundle): open independent native windows with CtrlCmd+Shift+N macOS only activates a running app, so a second document needs another window in the same process. Each window label gets its own Runner, which is stopped when its window is destroyed or the app exits, and the capability now covers every window. Co-Authored-By: Claude Opus 5.5 --- bundle/src-tauri/src/main.rs | 79 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 10 deletions(-) (limited to 'bundle/src-tauri/src/main.rs') diff --git a/bundle/src-tauri/src/main.rs b/bundle/src-tauri/src/main.rs index 07f25b3..80b28c5 100644 --- a/bundle/src-tauri/src/main.rs +++ b/bundle/src-tauri/src/main.rs @@ -4,7 +4,29 @@ mod compiler; mod runner; use compiler::Compiler; use runner::{OutputEvent, Runner}; -use tauri::{ipc::Channel, Manager, State}; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicUsize, Ordering}, + Mutex, + }, +}; +use tauri::{ipc::Channel, AppHandle, Manager, State, WebviewWindow, WebviewWindowBuilder}; + +// Each window runs its own program, keyed by window label. +#[derive(Default)] +struct Runners(Mutex>); + +impl Runners { + fn get(&self, label: &str) -> Runner { + self.0 + .lock() + .unwrap() + .entry(label.into()) + .or_default() + .clone() + } +} #[tauri::command] fn read_file(path: String) -> Result { @@ -21,11 +43,13 @@ async fn run_file( path: String, input: String, output: Channel, - runner: State<'_, Runner>, + window: WebviewWindow, + runners: State<'_, Runners>, compiler: State<'_, Compiler>, ) -> Result<(), String> { let executable = compiler.inner().0.clone()?; - let result = runner + let result = runners + .get(window.label()) .run(executable, path.into(), input, |stream, bytes| { let _ = output.send(OutputEvent::Data { stream: stream.into(), @@ -42,25 +66,60 @@ async fn run_file( } #[tauri::command] -async fn stop_run(runner: State<'_, Runner>) -> Result<(), String> { - runner.stop().await; +async fn stop_run(window: WebviewWindow, runners: State<'_, Runners>) -> Result<(), String> { + runners.get(window.label()).stop().await; Ok(()) } +// Async because creating a window from a synchronous command deadlocks on Windows. +#[tauri::command] +async fn new_window(app: AppHandle) -> Result<(), String> { + static NEXT: AtomicUsize = AtomicUsize::new(1); + let mut config = app.config().app.windows[0].clone(); + config.label = format!("window-{}", NEXT.fetch_add(1, Ordering::Relaxed)); + WebviewWindowBuilder::from_config(&app, &config) + .and_then(|builder| builder.build()) + .map(drop) + .map_err(|error| error.to_string()) +} + fn main() { let compiler = Compiler::load(); tauri::Builder::default() .plugin(tauri_plugin_dialog::init()) - .manage(Runner::default()) + .manage(Runners::default()) .manage(compiler) .invoke_handler(tauri::generate_handler![ - read_file, write_file, run_file, stop_run + read_file, write_file, run_file, stop_run, new_window ]) .build(tauri::generate_context!()) .expect("Could not start CPSC Bundle") - .run(|app, event| { - if matches!(event, tauri::RunEvent::ExitRequested { .. }) { - tauri::async_runtime::block_on(app.state::().stop()); + .run(|app, event| match event { + tauri::RunEvent::ExitRequested { .. } => { + let runners: Vec = app + .state::() + .0 + .lock() + .unwrap() + .values() + .cloned() + .collect(); + tauri::async_runtime::block_on(async { + for runner in runners { + runner.stop().await; + } + }); + } + tauri::RunEvent::WindowEvent { + label, + event: tauri::WindowEvent::Destroyed, + .. + } => { + let runner = app.state::().0.lock().unwrap().remove(&label); + if let Some(runner) = runner { + tauri::async_runtime::block_on(runner.stop()); + } } + _ => {} }); } -- cgit v1.3.1-4-g156e