diff --git a/qt/aqt/package.py b/qt/aqt/package.py index a0642fca0..f1834b594 100644 --- a/qt/aqt/package.py +++ b/qt/aqt/package.py @@ -23,47 +23,45 @@ def first_run_setup() -> None: if not is_mac: return - def _dot(): - print(".", flush=True, end="") - - _dot() - import anki.collection - - _dot() - import PyQt6.sip - - _dot() - import PyQt6.QtCore - - _dot() - import PyQt6.QtGui - - _dot() - import PyQt6.QtNetwork - - _dot() - import PyQt6.QtQuick - - _dot() - import PyQt6.QtWebChannel - - _dot() - import PyQt6.QtWebEngineCore - - _dot() - import PyQt6.QtWebEngineWidgets - - _dot() + # Import anki_audio first and spawn commands import anki_audio - import PyQt6.QtWidgets audio_pkg_path = Path(anki_audio.__file__).parent - # Invoke mpv and lame - cmd = [Path(""), "--version"] + # Start mpv and lame commands concurrently + processes = [] for cmd_name in ["mpv", "lame"]: - _dot() - cmd[0] = audio_pkg_path / cmd_name - subprocess.run([str(cmd[0]), str(cmd[1])], check=True, capture_output=True) + cmd_path = audio_pkg_path / cmd_name + proc = subprocess.Popen( + [str(cmd_path), "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + processes.append(proc) - print() + # Continue with other imports while commands run + import concurrent.futures + + import bs4 + import flask + import flask_cors + import markdown + import PyQt6.QtCore + import PyQt6.QtGui + import PyQt6.QtNetwork + import PyQt6.QtQuick + import PyQt6.QtWebChannel + import PyQt6.QtWebEngineCore + import PyQt6.QtWebEngineWidgets + import PyQt6.QtWidgets + import PyQt6.sip + import requests + import waitress + + import anki.collection + + from . import _macos_helper + + # Wait for both commands to complete + for proc in processes: + proc.wait() diff --git a/qt/launcher/mac/build.sh b/qt/launcher/mac/build.sh index eb4483488..0ec39ad8f 100755 --- a/qt/launcher/mac/build.sh +++ b/qt/launcher/mac/build.sh @@ -47,8 +47,8 @@ done codesign -vvv "$APP_LAUNCHER" spctl -a "$APP_LAUNCHER" -# Notarize -./notarize.sh "$OUTPUT_DIR" - -# Bundle -./dmg/build.sh "$OUTPUT_DIR" \ No newline at end of file +# Notarize and bundle (skip if NODMG is set) +if [ -z "$NODMG" ]; then + ./notarize.sh "$OUTPUT_DIR" + ./dmg/build.sh "$OUTPUT_DIR" +fi \ No newline at end of file diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index 40896caa9..2ad3ac00c 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -172,6 +172,8 @@ fn run() -> Result<()> { command.env("UV_PRERELEASE", "allow"); } + println!("\x1B[1mUpdating Anki...\x1B[0m\n"); + match command.ensure_success() { Ok(_) => { // Sync succeeded, break out of loop diff --git a/qt/launcher/src/platform/mac.rs b/qt/launcher/src/platform/mac.rs index 1e7e4a695..57d6bcc73 100644 --- a/qt/launcher/src/platform/mac.rs +++ b/qt/launcher/src/platform/mac.rs @@ -3,6 +3,11 @@ use std::os::unix::process::CommandExt; use std::process::Command; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; +use std::time::Duration; use anki_process::CommandExt as AnkiCommandExt; use anyhow::Context; @@ -25,6 +30,9 @@ pub fn launch_anki_detached(anki_bin: &std::path::Path, _config: &crate::Config) .process_group(0) .ensure_spawn()?; std::mem::forget(child); + + println!("Anki will start shortly."); + println!("\x1B[1mYou can close this window.\x1B[0m\n"); Ok(()) } @@ -34,6 +42,10 @@ pub fn ensure_terminal_shown() -> Result<()> { // If launched from GUI, relaunch in Terminal.app relaunch_in_terminal()?; } + + // Set terminal title to "Anki Launcher" + print!("\x1b]0;Anki Launcher\x07"); + Ok(()) } @@ -47,12 +59,37 @@ fn relaunch_in_terminal() -> Result<()> { } pub fn handle_first_launch(anki_bin: &std::path::Path) -> Result<()> { + use std::io::Write; + use std::io::{ + self, + }; + // Pre-validate by running --version to trigger any Gatekeeper checks - println!("\n\x1B[1mThis may take a few minutes. Please wait...\x1B[0m"); + print!("\n\x1B[1mThis may take a few minutes. Please wait\x1B[0m"); + io::stdout().flush().unwrap(); + + // Start progress indicator + let running = Arc::new(AtomicBool::new(true)); + let running_clone = running.clone(); + let progress_thread = thread::spawn(move || { + while running_clone.load(Ordering::Relaxed) { + print!("."); + io::stdout().flush().unwrap(); + thread::sleep(Duration::from_secs(1)); + } + }); + let _ = Command::new(anki_bin) .env("ANKI_FIRST_RUN", "1") .arg("--version") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) .ensure_success(); + + // Stop progress indicator + running.store(false, Ordering::Relaxed); + progress_thread.join().unwrap(); + println!(); // New line after dots Ok(()) }