From 8d5c385c766781adb249460bf0c1ccd035841934 Mon Sep 17 00:00:00 2001 From: llama Date: Wed, 3 Sep 2025 04:54:17 +0800 Subject: [PATCH 1/6] use existing translation instead of adding new one (#4310) (#4316) Co-authored-by: Abdo --- ftl/core/adding.ftl | 5 ----- qt/aqt/addcards.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/ftl/core/adding.ftl b/ftl/core/adding.ftl index a090576aa..e94333008 100644 --- a/ftl/core/adding.ftl +++ b/ftl/core/adding.ftl @@ -1,10 +1,5 @@ adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter) adding-added = Added -adding-added-cards = - Added { $count -> - [one] { $count } card - *[other] { $count } cards - } adding-discard-current-input = Discard current input? adding-keep-editing = Keep Editing adding-edit = Edit "{ $val }" diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 68f5c4259..a27d86234 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -300,7 +300,7 @@ class AddCards(QMainWindow): self.addHistory(note) - tooltip(tr.adding_added_cards(count=changes.count), period=500) + tooltip(tr.importing_cards_added(count=changes.count), period=500) av_player.stop_and_clear_queue() self._load_new_note(sticky_fields_from=note) gui_hooks.add_cards_did_add_note(note) From 06f9d41a96cdaabef781e266ba168d78b5c8e1c0 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 3 Sep 2025 14:46:17 +1000 Subject: [PATCH 2/6] Bypass install_name_tool invocation on macOS Not sure when https://github.com/astral-sh/uv/issues/14893 will be ready, and this seems to solve the problem for now. Closes #4227 --- qt/launcher/mac/build.sh | 8 ++++++- qt/launcher/mac/stub.c | 6 ++++++ qt/launcher/src/main.rs | 45 ++++++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 qt/launcher/mac/stub.c diff --git a/qt/launcher/mac/build.sh b/qt/launcher/mac/build.sh index 6143451b4..b861bc006 100755 --- a/qt/launcher/mac/build.sh +++ b/qt/launcher/mac/build.sh @@ -30,6 +30,12 @@ lipo -create \ -output "$APP_LAUNCHER/Contents/MacOS/launcher" cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/" +# Build install_name_tool stub +clang -arch arm64 -o "$OUTPUT_DIR/stub_arm64" stub.c +clang -arch x86_64 -o "$OUTPUT_DIR/stub_x86_64" stub.c +lipo -create "$OUTPUT_DIR/stub_arm64" "$OUTPUT_DIR/stub_x86_64" -output "$APP_LAUNCHER/Contents/MacOS/install_name_tool" +rm "$OUTPUT_DIR/stub_arm64" "$OUTPUT_DIR/stub_x86_64" + # Copy support files ANKI_VERSION=$(cat ../../../.version | tr -d '\n') sed "s/ANKI_VERSION/$ANKI_VERSION/g" Info.plist > "$APP_LAUNCHER/Contents/Info.plist" @@ -40,7 +46,7 @@ cp ../versions.py "$APP_LAUNCHER/Contents/Resources/" # Codesign/bundle if [ -z "$NODMG" ]; then - for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do + for i in "$APP_LAUNCHER/Contents/MacOS/uv" "$APP_LAUNCHER/Contents/MacOS/install_name_tool" "$APP_LAUNCHER/Contents/MacOS/launcher" "$APP_LAUNCHER"; do codesign --force -vvvv -o runtime -s "Developer ID Application:" \ --entitlements entitlements.python.xml \ "$i" diff --git a/qt/launcher/mac/stub.c b/qt/launcher/mac/stub.c new file mode 100644 index 000000000..09f1479a7 --- /dev/null +++ b/qt/launcher/mac/stub.c @@ -0,0 +1,6 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +int main(void) { + return 0; +} \ No newline at end of file diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index ccc4022b7..c4aba4509 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -261,11 +261,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re None }; - let have_venv = state.venv_folder.exists(); - if cfg!(target_os = "macos") && !have_developer_tools() && !have_venv { - println!("If you see a pop-up about 'install_name_tool', you can cancel it, and ignore the warning below.\n"); - } - // Prepare to sync the venv let mut command = Command::new(&state.uv_path); command.current_dir(&state.uv_install_root); @@ -277,17 +272,29 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re } } - // remove CONDA_PREFIX/bin from PATH to avoid conda interference - #[cfg(target_os = "macos")] - if let Ok(conda_prefix) = std::env::var("CONDA_PREFIX") { + if cfg!(target_os = "macos") { + // remove CONDA_PREFIX/bin from PATH to avoid conda interference + if let Ok(conda_prefix) = std::env::var("CONDA_PREFIX") { + if let Ok(current_path) = std::env::var("PATH") { + let conda_bin = format!("{conda_prefix}/bin"); + let filtered_paths: Vec<&str> = current_path + .split(':') + .filter(|&path| path != conda_bin) + .collect(); + let new_path = filtered_paths.join(":"); + command.env("PATH", new_path); + } + } + // put our fake install_name_tool at the top of the path to override + // potential conflicts if let Ok(current_path) = std::env::var("PATH") { - let conda_bin = format!("{conda_prefix}/bin"); - let filtered_paths: Vec<&str> = current_path - .split(':') - .filter(|&path| path != conda_bin) - .collect(); - let new_path = filtered_paths.join(":"); - command.env("PATH", new_path); + let exe_dir = std::env::current_exe() + .ok() + .and_then(|exe| exe.parent().map(|p| p.to_path_buf())); + if let Some(exe_dir) = exe_dir { + let new_path = format!("{}:{}", exe_dir.display(), current_path); + command.env("PATH", new_path); + } } } @@ -930,14 +937,6 @@ fn handle_uninstall(state: &State) -> Result { Ok(true) } -fn have_developer_tools() -> bool { - Command::new("xcode-select") - .args(["-p"]) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) -} - fn build_python_command(state: &State, args: &[String]) -> Result { let python_exe = if cfg!(target_os = "windows") { let show_console = std::env::var("ANKI_CONSOLE").is_ok(); From 2491eb0316283abe010a0e908b4dab17c5dba37f Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 3 Sep 2025 17:20:32 +1000 Subject: [PATCH 3/6] Don't reuse existing terminal process May possibly help with #4304 --- qt/launcher/src/platform/mac.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/launcher/src/platform/mac.rs b/qt/launcher/src/platform/mac.rs index f97d7fd07..8662ba9f5 100644 --- a/qt/launcher/src/platform/mac.rs +++ b/qt/launcher/src/platform/mac.rs @@ -62,7 +62,7 @@ pub fn prepare_for_launch_after_update(mut cmd: Command, root: &Path) -> Result< pub fn relaunch_in_terminal() -> Result<()> { let current_exe = std::env::current_exe().context("Failed to get current executable path")?; Command::new("open") - .args(["-a", "Terminal"]) + .args(["-na", "Terminal"]) .arg(current_exe) .ensure_spawn()?; std::process::exit(0); From db1d04f622dda95a5ee8b529591392dd12e4c613 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 3 Sep 2025 19:58:45 +1000 Subject: [PATCH 4/6] Centralize uv command setup Closes #4306 --- qt/launcher/src/main.rs | 53 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index c4aba4509..a178f05e5 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -193,8 +193,8 @@ fn extract_aqt_version(state: &State) -> Option { return None; } - let output = Command::new(&state.uv_path) - .current_dir(&state.uv_install_root) + let output = uv_command(state) + .ok()? .env("VIRTUAL_ENV", &state.venv_folder) .args(["pip", "show", "aqt"]) .output() @@ -262,15 +262,7 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re }; // Prepare to sync the venv - let mut command = Command::new(&state.uv_path); - command.current_dir(&state.uv_install_root); - - // remove UV_* environment variables to avoid interference - for (key, _) in std::env::vars() { - if key.starts_with("UV_") || key == "VIRTUAL_ENV" { - command.env_remove(key); - } - } + let mut command = uv_command(state)?; if cfg!(target_os = "macos") { // remove CONDA_PREFIX/bin from PATH to avoid conda interference @@ -316,13 +308,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re command.env("UV_NO_CACHE", "1"); } - // Add mirror environment variable if enabled - if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? { - command - .env("UV_PYTHON_INSTALL_MIRROR", &python_mirror) - .env("UV_DEFAULT_INDEX", &pypi_mirror); - } - match command.ensure_success() { Ok(_) => { // Sync succeeded @@ -672,9 +657,8 @@ fn filter_and_normalize_versions( fn fetch_versions(state: &State) -> Result> { let versions_script = state.resources_dir.join("versions.py"); - let mut cmd = Command::new(&state.uv_path); - cmd.current_dir(&state.uv_install_root) - .args(["run", "--no-project", "--no-config", "--managed-python"]) + let mut cmd = uv_command(state)?; + cmd.args(["run", "--no-project", "--no-config", "--managed-python"]) .args(["--with", "pip-system-certs,requests[socks]"]); let python_version = read_file(&state.dist_python_version_path)?; @@ -687,12 +671,6 @@ fn fetch_versions(state: &State) -> Result> { cmd.arg(&versions_script); - // Add mirror environment variable if enabled - if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? { - cmd.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror) - .env("UV_DEFAULT_INDEX", &pypi_mirror); - } - let output = match cmd.utf8_output() { Ok(output) => output, Err(e) => { @@ -937,6 +915,27 @@ fn handle_uninstall(state: &State) -> Result { Ok(true) } +fn uv_command(state: &State) -> Result { + let mut command = Command::new(&state.uv_path); + command.current_dir(&state.uv_install_root); + + // remove UV_* environment variables to avoid interference + for (key, _) in std::env::vars() { + if key.starts_with("UV_") || key == "VIRTUAL_ENV" { + command.env_remove(key); + } + } + + // Add mirror environment variable if enabled + if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? { + command + .env("UV_PYTHON_INSTALL_MIRROR", &python_mirror) + .env("UV_DEFAULT_INDEX", &pypi_mirror); + } + + Ok(command) +} + fn build_python_command(state: &State, args: &[String]) -> Result { let python_exe = if cfg!(target_os = "windows") { let show_console = std::env::var("ANKI_CONSOLE").is_ok(); From 6a985c9fb0b7a3df25f0c4163d1917452f52b82a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 3 Sep 2025 20:54:16 +1000 Subject: [PATCH 5/6] Add support for custom launcher venv locations Closes #4305 when https://github.com/ankitects/anki-manual/pull/444 is merged, and makes it easier to maintain multiple Anki versions at once. --- qt/launcher/src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index a178f05e5..aaa443aa0 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -88,9 +88,13 @@ fn main() { } fn run() -> Result<()> { - let uv_install_root = dirs::data_local_dir() - .context("Unable to determine data_dir")? - .join("AnkiProgramFiles"); + let uv_install_root = if let Ok(custom_root) = std::env::var("ANKI_LAUNCHER_VENV_ROOT") { + std::path::PathBuf::from(custom_root) + } else { + dirs::data_local_dir() + .context("Unable to determine data_dir")? + .join("AnkiProgramFiles") + }; let (exe_dir, resources_dir) = get_exe_and_resources_dirs()?; From b2ab0c08303a61d345d06e1607d85ead423f4996 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 3 Sep 2025 23:50:26 +1000 Subject: [PATCH 6/6] Add an experimental new system Qt mode to the launcher Goal is to allow users to use their system Qt libraries that have things like fcitx support available. For #4313 --- qt/launcher/src/main.rs | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index aaa443aa0..2bbb9cc0f 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -51,6 +51,8 @@ struct State { previous_version: Option, resources_dir: std::path::PathBuf, venv_folder: std::path::PathBuf, + /// system Python + PyQt6 library mode + system_qt: bool, } #[derive(Debug, Clone)] @@ -117,6 +119,8 @@ fn run() -> Result<()> { mirror_path: uv_install_root.join("mirror"), pyproject_modified_by_user: false, // calculated later previous_version: None, + system_qt: (cfg!(unix) && !cfg!(target_os = "macos")) + && resources_dir.join("system_qt").exists(), resources_dir, venv_folder: uv_install_root.join(".venv"), }; @@ -294,18 +298,36 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re } } + // Create venv with system site packages if system Qt is enabled + if state.system_qt { + let mut venv_command = uv_command(state)?; + venv_command.args([ + "venv", + "--no-managed-python", + "--system-site-packages", + "--no-config", + ]); + venv_command.ensure_success()?; + } + command .env("UV_CACHE_DIR", &state.uv_cache_dir) .env("UV_PYTHON_INSTALL_DIR", &state.uv_python_install_dir) .env( "UV_HTTP_TIMEOUT", std::env::var("UV_HTTP_TIMEOUT").unwrap_or_else(|_| "180".to_string()), - ) - .args(["sync", "--upgrade", "--managed-python", "--no-config"]); + ); - // Add python version if .python-version file exists + command.args(["sync", "--upgrade", "--no-config"]); + if !state.system_qt { + command.arg("--managed-python"); + } + + // Add python version if .python-version file exists (but not for system Qt) if let Some(version) = &python_version_trimmed { - command.args(["--python", version]); + if !state.system_qt { + command.args(["--python", version]); + } } if state.no_cache_marker.exists() { @@ -727,7 +749,26 @@ fn apply_version_kind(version_kind: &VersionKind, state: &State) -> Result<()> { &format!("anki-release=={version}\",\n \"anki=={version}\",\n \"aqt=={version}"), ), }; - write_file(&state.user_pyproject_path, &updated_content)?; + + let final_content = if state.system_qt { + format!( + concat!( + "{}\n\n[tool.uv]\n", + "override-dependencies = [\n", + " \"pyqt6; sys_platform=='never'\",\n", + " \"pyqt6-qt6; sys_platform=='never'\",\n", + " \"pyqt6-webengine; sys_platform=='never'\",\n", + " \"pyqt6-webengine-qt6; sys_platform=='never'\",\n", + " \"pyqt6_sip; sys_platform=='never'\"\n", + "]\n" + ), + updated_content + ) + } else { + updated_content + }; + + write_file(&state.user_pyproject_path, &final_content)?; // Update .python-version based on version kind match version_kind {