diff --git a/.version b/.version index ce73bf7c0..8dec45b51 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -25.08b1 +25.07.3rc1 diff --git a/Cargo.toml b/Cargo.toml index db5753893..2ff29cd1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,7 +141,7 @@ walkdir = "2.5.0" which = "8.0.0" widestring = "1.1.0" winapi = { version = "0.3", features = ["wincon", "winreg"] } -windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_Foundation", "Win32_UI_Shell"] } +windows = { version = "0.61.3", features = ["Media_SpeechSynthesis", "Media_Core", "Foundation_Collections", "Storage_Streams", "Win32_System_Console", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_Foundation", "Win32_UI_Shell", "Wdk_System_SystemServices"] } wiremock = "0.6.3" xz2 = "0.1.7" zip = { version = "4.1.0", default-features = false, features = ["deflate", "time"] } diff --git a/build/runner/src/pyenv.rs b/build/runner/src/pyenv.rs index 9d65626ca..efd58fd91 100644 --- a/build/runner/src/pyenv.rs +++ b/build/runner/src/pyenv.rs @@ -36,7 +36,7 @@ pub fn setup_pyenv(args: PyenvArgs) { // remove UV_* environment variables to avoid interference for (key, _) in std::env::vars() { - if key.starts_with("UV_") { + if key.starts_with("UV_") || key == "VIRTUAL_ENV" { command.env_remove(key); } } diff --git a/ftl/core-repo b/ftl/core-repo index 3d04bcbf7..b90ef6f03 160000 --- a/ftl/core-repo +++ b/ftl/core-repo @@ -1 +1 @@ -Subproject commit 3d04bcbf7fefca0007bc9db307409d88210995d8 +Subproject commit b90ef6f03c251eb336029ac7c5f551200d41273f diff --git a/ftl/qt-repo b/ftl/qt-repo index c65a9587b..9aa63c335 160000 --- a/ftl/qt-repo +++ b/ftl/qt-repo @@ -1 +1 @@ -Subproject commit c65a9587b1f18931986bdf145872e8e4c44c5c82 +Subproject commit 9aa63c335c61b30421d39cf43fd8e3975179059c diff --git a/pylib/pyproject.toml b/pylib/pyproject.toml index 23e10077f..fb7422694 100644 --- a/pylib/pyproject.toml +++ b/pylib/pyproject.toml @@ -7,7 +7,7 @@ dependencies = [ "decorator", "markdown", "orjson", - "protobuf>=4.21", + "protobuf>=6.0,<8.0", "requests[socks]", # remove after we update to min python 3.11+ "typing_extensions", diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 228d3cfeb..03e989f2c 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -70,10 +70,10 @@ def show(mw: aqt.AnkiQt) -> QDialog: abouttext += f"

{lede}" abouttext += f"

{tr.about_anki_is_licensed_under_the_agpl3()}" abouttext += f"

{tr.about_version(val=version_with_build())}
" - abouttext += ("Python %s Qt %s PyQt %s
") % ( + abouttext += ("Python %s Qt %s Chromium %s
") % ( platform.python_version(), qVersion(), - PYQT_VERSION_STR, + (qWebEngineChromiumVersion() or "").split(".")[0], ) abouttext += ( without_unicode_isolation(tr.about_visit_website(val=aqt.appWebsite)) @@ -225,6 +225,7 @@ def show(mw: aqt.AnkiQt) -> QDialog: "Adnane Taghi", "Anon_0000", "Bilolbek Normuminov", + "Sagiv Marzini", ) ) diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index 807d4093c..0035e1f42 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -1292,9 +1292,10 @@ daily_backups weekly_backups monthly_backups - tabWidget syncAnkiHubLogout syncAnkiHubLogin + buttonBox + tabWidget diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index bd87ef830..afce6d489 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -82,11 +82,14 @@ class Preferences(QDialog): ) group = self.form.preferences_answer_keys group.setLayout(layout := QFormLayout()) + tab_widget: QWidget = self.form.url_schemes for ease, label in ease_labels: layout.addRow( label, line_edit := QLineEdit(self.mw.pm.get_answer_key(ease) or ""), ) + QWidget.setTabOrder(tab_widget, line_edit) + tab_widget = line_edit qconnect( line_edit.textChanged, functools.partial(self.mw.pm.set_answer_key, ease), diff --git a/qt/launcher/lin/build.sh b/qt/launcher/lin/build.sh index 7bd78c27d..f38f6defe 100755 --- a/qt/launcher/lin/build.sh +++ b/qt/launcher/lin/build.sh @@ -13,7 +13,8 @@ HOST_ARCH=$(uname -m) # Define output paths OUTPUT_DIR="../../../out/launcher" -LAUNCHER_DIR="$OUTPUT_DIR/anki-linux" +ANKI_VERSION=$(cat ../../../.version | tr -d '\n') +LAUNCHER_DIR="$OUTPUT_DIR/anki-launcher-$ANKI_VERSION-linux" # Clean existing output directory rm -rf "$LAUNCHER_DIR" @@ -77,8 +78,8 @@ chmod +x \ chmod -R a+r "$LAUNCHER_DIR" ZSTD="zstd -c --long -T0 -18" -TRANSFORM="s%^.%anki-linux%S" -TARBALL="$OUTPUT_DIR/anki-linux.tar.zst" +TRANSFORM="s%^.%anki-launcher-$ANKI_VERSION-linux%S" +TARBALL="$OUTPUT_DIR/anki-launcher-$ANKI_VERSION-linux.tar.zst" tar -I "$ZSTD" --transform "$TRANSFORM" -cf "$TARBALL" -C "$LAUNCHER_DIR" . diff --git a/qt/launcher/mac/Info.plist b/qt/launcher/mac/Info.plist index ac0ab2f09..a48960208 100644 --- a/qt/launcher/mac/Info.plist +++ b/qt/launcher/mac/Info.plist @@ -5,7 +5,7 @@ CFBundleDisplayName Anki CFBundleShortVersionString - 1.0 + ANKI_VERSION LSMinimumSystemVersion 12 LSApplicationCategoryType diff --git a/qt/launcher/mac/build.sh b/qt/launcher/mac/build.sh index 470b5cd25..d521e155b 100755 --- a/qt/launcher/mac/build.sh +++ b/qt/launcher/mac/build.sh @@ -31,7 +31,8 @@ lipo -create \ cp "$OUTPUT_DIR/uv" "$APP_LAUNCHER/Contents/MacOS/" # Copy support files -cp Info.plist "$APP_LAUNCHER/Contents/" +ANKI_VERSION=$(cat ../../../.version | tr -d '\n') +sed "s/ANKI_VERSION/$ANKI_VERSION/g" Info.plist > "$APP_LAUNCHER/Contents/Info.plist" cp icon/Assets.car "$APP_LAUNCHER/Contents/Resources/" cp ../pyproject.toml "$APP_LAUNCHER/Contents/Resources/" cp ../../../.python-version "$APP_LAUNCHER/Contents/Resources/" diff --git a/qt/launcher/mac/dmg/build.sh b/qt/launcher/mac/dmg/build.sh index 16b48c06a..7eeba9948 100755 --- a/qt/launcher/mac/dmg/build.sh +++ b/qt/launcher/mac/dmg/build.sh @@ -6,7 +6,8 @@ set -e # base folder with Anki.app in it output="$1" dist="$1/tmp" -dmg_path="$output/Anki.dmg" +ANKI_VERSION=$(cat ../../../.version | tr -d '\n') +dmg_path="$output/anki-launcher-$ANKI_VERSION-mac.dmg" if [ -d "/Volumes/Anki" ] then diff --git a/qt/launcher/src/bin/build_win.rs b/qt/launcher/src/bin/build_win.rs index 96688f190..4c2ca4413 100644 --- a/qt/launcher/src/bin/build_win.rs +++ b/qt/launcher/src/bin/build_win.rs @@ -22,6 +22,11 @@ const NSIS_PATH: &str = "C:\\Program Files (x86)\\NSIS\\makensis.exe"; fn main() -> Result<()> { println!("Building Windows launcher..."); + // Read version early so it can be used throughout the build process + let version = std::fs::read_to_string("../../../.version")? + .trim() + .to_string(); + let output_dir = PathBuf::from(OUTPUT_DIR); let launcher_exe_dir = PathBuf::from(LAUNCHER_EXE_DIR); let nsis_dir = PathBuf::from(NSIS_DIR); @@ -31,16 +36,20 @@ fn main() -> Result<()> { extract_nsis_plugins()?; copy_files(&output_dir)?; sign_binaries(&output_dir)?; - copy_nsis_files(&nsis_dir)?; + copy_nsis_files(&nsis_dir, &version)?; build_uninstaller(&output_dir, &nsis_dir)?; sign_file(&output_dir.join("uninstall.exe"))?; generate_install_manifest(&output_dir)?; build_installer(&output_dir, &nsis_dir)?; - sign_file(&PathBuf::from("../../../out/launcher_exe/anki-install.exe"))?; + + let installer_filename = format!("anki-launcher-{version}-windows.exe"); + let installer_path = PathBuf::from("../../../out/launcher_exe").join(&installer_filename); + + sign_file(&installer_path)?; println!("Build completed successfully!"); println!("Output directory: {}", output_dir.display()); - println!("Installer: ../../../out/launcher_exe/anki-install.exe"); + println!("Installer: ../../../out/launcher_exe/{installer_filename}"); Ok(()) } @@ -235,11 +244,13 @@ fn generate_install_manifest(output_dir: &Path) -> Result<()> { Ok(()) } -fn copy_nsis_files(nsis_dir: &Path) -> Result<()> { +fn copy_nsis_files(nsis_dir: &Path, version: &str) -> Result<()> { println!("Copying NSIS support files..."); - // Copy anki.template.nsi as anki.nsi - copy_file("anki.template.nsi", nsis_dir.join("anki.nsi"))?; + // Copy anki.template.nsi as anki.nsi and substitute version placeholders + let template_content = std::fs::read_to_string("anki.template.nsi")?; + let substituted_content = template_content.replace("ANKI_VERSION", version); + write_file(nsis_dir.join("anki.nsi"), substituted_content)?; // Copy fileassoc.nsh copy_file("fileassoc.nsh", nsis_dir.join("fileassoc.nsh"))?; diff --git a/qt/launcher/src/main.rs b/qt/launcher/src/main.rs index d48adaf49..903cc2b60 100644 --- a/qt/launcher/src/main.rs +++ b/qt/launcher/src/main.rs @@ -198,6 +198,7 @@ fn extract_aqt_version( ) -> Option { let output = Command::new(uv_path) .current_dir(uv_install_root) + .env("VIRTUAL_ENV", uv_install_root.join(".venv")) .args(["pip", "show", "aqt"]) .output() .ok()?; @@ -269,7 +270,7 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re // remove UV_* environment variables to avoid interference for (key, _) in std::env::vars() { - if key.starts_with("UV_") { + if key.starts_with("UV_") || key == "VIRTUAL_ENV" { command.env_remove(key); } } diff --git a/qt/launcher/src/platform/windows.rs b/qt/launcher/src/platform/windows.rs index 3c060a9de..ebdff6261 100644 --- a/qt/launcher/src/platform/windows.rs +++ b/qt/launcher/src/platform/windows.rs @@ -8,6 +8,7 @@ use anyhow::Context; use anyhow::Result; use widestring::u16cstr; use windows::core::PCWSTR; +use windows::Wdk::System::SystemServices::RtlGetVersion; use windows::Win32::System::Console::AttachConsole; use windows::Win32::System::Console::GetConsoleWindow; use windows::Win32::System::Console::ATTACH_PARENT_PROCESS; @@ -18,8 +19,25 @@ use windows::Win32::System::Registry::HKEY; use windows::Win32::System::Registry::HKEY_CURRENT_USER; use windows::Win32::System::Registry::KEY_READ; use windows::Win32::System::Registry::REG_SZ; +use windows::Win32::System::SystemInformation::OSVERSIONINFOW; use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID; +/// Returns true if running on Windows 10 (not Windows 11) +fn is_windows_10() -> bool { + unsafe { + let mut info = OSVERSIONINFOW { + dwOSVersionInfoSize: std::mem::size_of::() as u32, + ..Default::default() + }; + if RtlGetVersion(&mut info).is_ok() { + // Windows 10 has build numbers < 22000, Windows 11 >= 22000 + info.dwBuildNumber < 22000 && info.dwMajorVersion == 10 + } else { + false + } + } +} + pub fn ensure_terminal_shown() -> Result<()> { unsafe { if !GetConsoleWindow().is_invalid() { @@ -29,6 +47,14 @@ pub fn ensure_terminal_shown() -> Result<()> { } if std::env::var("ANKI_IMPLICIT_CONSOLE").is_ok() && attach_to_parent_console() { + // This black magic triggers Windows to switch to the new + // ANSI-supporting console host, which is usually only available + // when the app is built with the console subsystem. + // Only needed on Windows 10, not Windows 11. + if is_windows_10() { + let _ = Command::new("cmd").args(["/C", ""]).status(); + } + // Successfully attached to parent console reconnect_stdio_to_console(); return Ok(()); diff --git a/qt/launcher/win/anki.template.nsi b/qt/launcher/win/anki.template.nsi index 84dedf9c8..36b32a893 100644 --- a/qt/launcher/win/anki.template.nsi +++ b/qt/launcher/win/anki.template.nsi @@ -24,7 +24,7 @@ Name "Anki" Unicode true ; The file to write (relative to nsis directory) -OutFile "..\launcher_exe\anki-install.exe" +OutFile "..\launcher_exe\anki-launcher-ANKI_VERSION-windows.exe" ; Non elevated RequestExecutionLevel user @@ -214,7 +214,7 @@ Section "" ; Write the uninstall keys for Windows WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayName" "Anki Launcher" - WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "1.0.0" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "DisplayVersion" "ANKI_VERSION" WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Anki" "NoModify" 1 diff --git a/qt/pyproject.toml b/qt/pyproject.toml index 6a686dde8..1ff34f59d 100644 --- a/qt/pyproject.toml +++ b/qt/pyproject.toml @@ -40,8 +40,8 @@ qt67 = [ qt = [ "pyqt6==6.9.1", "pyqt6-qt6==6.9.1", - "pyqt6-webengine==6.9.0", - "pyqt6-webengine-qt6==6.9.1", + "pyqt6-webengine==6.8.0", + "pyqt6-webengine-qt6==6.8.2", "pyqt6_sip==13.10.2", ] qt68 = [ diff --git a/ts/editor/ClozeButtons.svelte b/ts/editor/ClozeButtons.svelte index e9faae540..dfe4fb7c3 100644 --- a/ts/editor/ClozeButtons.svelte +++ b/ts/editor/ClozeButtons.svelte @@ -4,7 +4,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->