From 7d220537302a924a6b98b757b6ee2f710be0c04c Mon Sep 17 00:00:00 2001 From: Ren Tatsumoto Date: Fri, 30 Dec 2022 03:10:37 +0000 Subject: [PATCH 01/30] Mention how to fix build crash in the readme. (#2286) * update linux.md mention how to fix libcrypt.so.1-related build crash * Mention Fedora as well (dae) --- docs/linux.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/linux.md b/docs/linux.md index 5fbda5c98..2c0e85cb1 100644 --- a/docs/linux.md +++ b/docs/linux.md @@ -29,7 +29,7 @@ $ sudo apt install bash grep findutils curl gcc g++ git rsync ninja-build ## Missing Libraries -If you get errors during startup, try starting with +If you get errors during build or startup, try starting with QT_DEBUG_PLUGINS=1 ./run @@ -42,6 +42,13 @@ sudo apt install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \ libxcb-randr0 libxcb-render-util0 ``` +On some distros such as Arch Linux and Fedora, you may need to install the +`libxcrypt-compat` package if you get an error like this: + +``` +error while loading shared libraries: libcrypt.so.1: cannot open shared object file: No such file or directory +``` + ## Audio To play and record audio during development, install mpv and lame. From be6d1dfb664a8527bf90ad8913f7fbdc4f7cd813 Mon Sep 17 00:00:00 2001 From: Hikaru Y Date: Fri, 30 Dec 2022 12:32:41 +0900 Subject: [PATCH 02/30] Work around issue with entering text around MathJax via IME (#2288) * Add 'placement' property * Extract logic for moving text node into instance method ... so that it can be used elsewhere. * Add writable store to indicate whether composition session is active * Work around issue with entering text around MathJax via IME * Make get() called only once while composition session is active --- ts/editable/frame-handle.ts | 77 +++++++++++++++---- .../mathjax-overlay/MathjaxOverlay.svelte | 6 ++ ts/sveltelib/composition.ts | 12 +++ 3 files changed, 79 insertions(+), 16 deletions(-) create mode 100644 ts/sveltelib/composition.ts diff --git a/ts/editable/frame-handle.ts b/ts/editable/frame-handle.ts index 64ae17155..b090166bd 100644 --- a/ts/editable/frame-handle.ts +++ b/ts/editable/frame-handle.ts @@ -4,9 +4,12 @@ import { getSelection, isSelectionCollapsed } from "@tslib/cross-browser"; import { elementIsEmpty, nodeIsElement, nodeIsText } from "@tslib/dom"; import { on } from "@tslib/events"; +import type { Unsubscriber } from "svelte/store"; +import { get } from "svelte/store"; import { moveChildOutOfElement } from "../domlib/move-nodes"; import { placeCaretAfter } from "../domlib/place-caret"; +import { isComposing } from "../sveltelib/composition"; import type { FrameElement } from "./frame-element"; /** @@ -53,7 +56,6 @@ function restoreHandleContent(mutations: MutationRecord[]): void { } const handleElement = target; - const placement = handleElement instanceof FrameStart ? "beforebegin" : "afterend"; const frameElement = handleElement.parentElement as FrameElement; for (const node of mutation.addedNodes) { @@ -75,7 +77,7 @@ function restoreHandleContent(mutations: MutationRecord[]): void { referenceNode = moveChildOutOfElement( frameElement, node, - placement, + handleElement.placement, ); } } @@ -84,25 +86,16 @@ function restoreHandleContent(mutations: MutationRecord[]): void { !nodeIsText(target) || !isFrameHandle(target.parentElement) || skippableNode(target.parentElement, target) + || target.parentElement.unsubscribe ) { continue; } - - const handleElement = target.parentElement; - const placement = handleElement instanceof FrameStart ? "beforebegin" : "afterend"; - const frameElement = handleElement.parentElement! as FrameElement; - - const cleaned = target.data.replace(spaceRegex, ""); - const text = new Text(cleaned); - - if (placement === "beforebegin") { - frameElement.before(text); - } else { - frameElement.after(text); + if (get(isComposing)) { + target.parentElement.subscribeToCompositionEvent(); + continue; } - handleElement.refreshSpace(); - referenceNode = text; + referenceNode = target.parentElement.moveTextOutOfFrame(); } } @@ -114,6 +107,8 @@ function restoreHandleContent(mutations: MutationRecord[]): void { const handleObserver = new MutationObserver(restoreHandleContent); const handles: Set = new Set(); +type Placement = Extract; + export abstract class FrameHandle extends HTMLElement { static get observedAttributes(): string[] { return ["data-frames"]; @@ -128,6 +123,8 @@ export abstract class FrameHandle extends HTMLElement { */ partiallySelected = false; frames?: string; + abstract placement: Placement; + unsubscribe: Unsubscriber | null; constructor() { super(); @@ -136,6 +133,7 @@ export abstract class FrameHandle extends HTMLElement { subtree: true, characterData: true, }); + this.unsubscribe = null; } attributeChangedCallback(name: string, old: string, newValue: string): void { @@ -197,13 +195,54 @@ export abstract class FrameHandle extends HTMLElement { this.removeMoveIn?.(); this.removeMoveIn = undefined; + this.unsubscribeToCompositionEvent(); } abstract notifyMoveIn(offset: number): void; + + moveTextOutOfFrame(): Text { + const frameElement = this.parentElement! as FrameElement; + const cleaned = this.innerHTML.replace(spaceRegex, ""); + const text = new Text(cleaned); + + if (this.placement === "beforebegin") { + frameElement.before(text); + } else if (this.placement === "afterend") { + frameElement.after(text); + } + this.refreshSpace(); + return text; + } + + /** + * https://github.com/ankitects/anki/issues/2251 + * + * Work around the issue by not moving the input string while an IME session + * is active, and moving the final output from IME only after the session ends. + */ + subscribeToCompositionEvent(): void { + this.unsubscribe = isComposing.subscribe((composing) => { + if (!composing) { + placeCaretAfter(this.moveTextOutOfFrame()); + this.unsubscribeToCompositionEvent(); + } + }); + } + + unsubscribeToCompositionEvent(): void { + this.unsubscribe?.(); + this.unsubscribe = null; + } } export class FrameStart extends FrameHandle { static tagName = "frame-start"; + placement: Placement; + + constructor() { + super(); + this.placement = "beforebegin"; + } getFrameRange(): Range { const range = new Range(); @@ -245,6 +284,12 @@ export class FrameStart extends FrameHandle { export class FrameEnd extends FrameHandle { static tagName = "frame-end"; + placement: Placement; + + constructor() { + super(); + this.placement = "afterend"; + } getFrameRange(): Range { const range = new Range(); diff --git a/ts/editor/mathjax-overlay/MathjaxOverlay.svelte b/ts/editor/mathjax-overlay/MathjaxOverlay.svelte index 626913915..4bc134be2 100644 --- a/ts/editor/mathjax-overlay/MathjaxOverlay.svelte +++ b/ts/editor/mathjax-overlay/MathjaxOverlay.svelte @@ -11,6 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import type CodeMirrorLib from "codemirror"; import { tick } from "svelte"; import { writable } from "svelte/store"; + import { isComposing } from "sveltelib/composition"; import Popover from "../../components/Popover.svelte"; import Shortcut from "../../components/Shortcut.svelte"; @@ -79,6 +80,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html const code = writable(""); function showOverlay(image: HTMLImageElement, pos?: CodeMirrorLib.Position) { + if ($isComposing) { + // Should be canceled while an IME composition session is active + return; + } + const [promise, allowResolve] = promiseWithResolver(); allowPromise = promise; diff --git a/ts/sveltelib/composition.ts b/ts/sveltelib/composition.ts new file mode 100644 index 000000000..561ac484d --- /dev/null +++ b/ts/sveltelib/composition.ts @@ -0,0 +1,12 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import { writable } from "svelte/store"; + +/** + * Indicates whether an IME composition session is currently active + */ +export const isComposing = writable(false); + +window.addEventListener("compositionstart", () => isComposing.set(true)); +window.addEventListener("compositionend", () => isComposing.set(false)); From 766e28d64df87c8171b68c10f2df5a13b8eeee40 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 30 Dec 2022 15:22:11 +1000 Subject: [PATCH 03/30] Clean up build artifacts between runs --- build/runner/src/bundle/artifacts.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/runner/src/bundle/artifacts.rs b/build/runner/src/bundle/artifacts.rs index 9de0f317e..b9e4dcf0f 100644 --- a/build/runner/src/bundle/artifacts.rs +++ b/build/runner/src/bundle/artifacts.rs @@ -22,6 +22,10 @@ pub fn build_artifacts(args: BuildArtifactsArgs) { fs::remove_dir_all(&artifacts).unwrap(); } let bundle_root = args.bundle_root.canonicalize_utf8().unwrap(); + let build_folder = bundle_root.join("build"); + if build_folder.exists() { + fs::remove_dir_all(&build_folder).unwrap(); + } run_silent( Command::new(&args.pyoxidizer_bin) @@ -34,7 +38,7 @@ pub fn build_artifacts(args: BuildArtifactsArgs) { "out/bundle/pyenv", "--var", "build", - bundle_root.join("build").as_str(), + build_folder.as_str(), ]) .env("CARGO_MANIFEST_DIR", "qt/bundle") .env("CARGO_TARGET_DIR", "out/bundle/rust") From ba68764fcb61d2baba9740baa5a88b09a24f23ac Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 30 Dec 2022 15:30:53 +1000 Subject: [PATCH 04/30] Another attempt at fixing missing cacert.pem A few reports like https://forums.ankiweb.net/t/error-report-check-database-did-not-work/25796 indicate that the previous solution has not helped, and has just made things worse. My guess is that AppNap on macOS is preventing the timer from even running before the file gets cleaned up. --- qt/aqt/main.py | 16 ---------------- qt/aqt/package.py | 20 ++++++++++++++++++++ qt/bundle/mac/src/main.rs | 2 +- qt/bundle/pyoxidizer.bzl | 2 ++ 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index e0a7a688c..296e8264c 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1382,13 +1382,6 @@ title="{}" {}>{}""".format( True, parent=self, ) - self.progress.timer( - 12 * 60 * 1000, - self.refresh_certs, - repeat=True, - requiresCollection=False, - parent=self, - ) def onRefreshTimer(self) -> None: if self.state == "deckBrowser": @@ -1404,15 +1397,6 @@ title="{}" {}>{}""".format( if elap > minutes * 60: self.maybe_auto_sync_media() - def refresh_certs(self) -> None: - # The requests library copies the certs into a temporary folder on startup, - # and chokes when the file is later missing due to temp file cleaners. - # Work around the issue by accessing them once every 12 hours. - from requests.certs import where # type: ignore[attr-defined] - - with open(where(), "rb") as f: - f.read() - # Backups ########################################################################## diff --git a/qt/aqt/package.py b/qt/aqt/package.py index 1cee7d5b4..6bece47d1 100644 --- a/qt/aqt/package.py +++ b/qt/aqt/package.py @@ -7,6 +7,7 @@ from __future__ import annotations import os import sys +from pathlib import Path def _fix_pywin32() -> None: @@ -49,6 +50,24 @@ def _patch_pkgutil() -> None: pkgutil.get_data = get_data_custom +def _patch_certifi() -> None: + """Tell certifi (and thus requests) to use a file in our package folder. + + By default it creates a copy of the data in a temporary folder, which then gets + cleaned up by macOS's temp file cleaner.""" + import certifi + + def where() -> str: + prefix = Path(sys.prefix) + if sys.platform == "darwin": + path = prefix / "../Resources/certifi/cacert.pem" + else: + path = prefix / "lib" / "certifi" / "cacert.pem" + return str(path) + + certifi.where = where + + def packaged_build_setup() -> None: if not getattr(sys, "frozen", False): return @@ -59,6 +78,7 @@ def packaged_build_setup() -> None: _fix_pywin32() _patch_pkgutil() + _patch_certifi() # escape hatch for debugging issues with packaged build startup if os.getenv("ANKI_STARTUP_REPL"): diff --git a/qt/bundle/mac/src/main.rs b/qt/bundle/mac/src/main.rs index 399249b3d..597f10157 100644 --- a/qt/bundle/mac/src/main.rs +++ b/qt/bundle/mac/src/main.rs @@ -132,7 +132,7 @@ fn make_app(kind: DistKind, mut plist: plist::Dictionary, stamp: &Utf8Path) -> R let path_str = relative_path.to_str().unwrap(); if path_str.contains("libankihelper") { builder.add_file_macos("libankihelper.dylib", entry)?; - } else if path_str.contains("aqt/data") { + } else if path_str.contains("aqt/data") || path_str.contains("certifi") { builder.add_file_resources(relative_path.strip_prefix("lib").unwrap(), entry)?; } else { if path_str.contains("__pycache__") { diff --git a/qt/bundle/pyoxidizer.bzl b/qt/bundle/pyoxidizer.bzl index 0fba834b1..57721ef77 100644 --- a/qt/bundle/pyoxidizer.bzl +++ b/qt/bundle/pyoxidizer.bzl @@ -55,6 +55,8 @@ def handle_resource(policy, resource): for prefix in included_resource_packages: if resource.package.startswith(prefix): resource.add_include = True + if resource.package == "certifi": + resource.add_location = "filesystem-relative:lib" for suffix in excluded_resource_suffixes: if resource.name.endswith(suffix): resource.add_include = False From 3357389309057e8172fe5b161eceb4da5de12bbc Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 30 Dec 2022 17:36:49 +1000 Subject: [PATCH 05/30] Fix some regressions with the graphs when printing - page-break avoidance needs to be moved to the wrapping TitledContainer - grid has to be disabled, as it prevents page breaks from working, and shows too many columns (https://forums.ankiweb.net/t/stats-save-as-pdf-problems-2-1-55/25773) - content underflowed the top header --- ts/components/TitledContainer.svelte | 1 + ts/graphs/Graph.svelte | 2 -- ts/graphs/GraphsPage.svelte | 6 ++++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ts/components/TitledContainer.svelte b/ts/components/TitledContainer.svelte index 185f2cc0d..e65f1441f 100644 --- a/ts/components/TitledContainer.svelte +++ b/ts/components/TitledContainer.svelte @@ -64,6 +64,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } transition: box-shadow 0.2s ease-in-out; + page-break-inside: avoid; } h1 { border-bottom: 1px solid var(--border); diff --git a/ts/graphs/Graph.svelte b/ts/graphs/Graph.svelte index 530376c0f..fb8f2b177 100644 --- a/ts/graphs/Graph.svelte +++ b/ts/graphs/Graph.svelte @@ -21,8 +21,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html diff --git a/ts/components/TitledContainer.svelte b/ts/components/TitledContainer.svelte index e65f1441f..e653c1caf 100644 --- a/ts/components/TitledContainer.svelte +++ b/ts/components/TitledContainer.svelte @@ -63,7 +63,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html @include elevation(4); } } - transition: box-shadow 0.2s ease-in-out; + transition: box-shadow var(--transition) ease-in-out; page-break-inside: avoid; } h1 { @@ -73,7 +73,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html right: 0; bottom: 4px; color: var(--fg-faint); - transition: color 0.2s linear; + transition: color var(--transition) linear; &:hover { transition: none; color: var(--fg); diff --git a/ts/deck-options/deck-options-base.scss b/ts/deck-options/deck-options-base.scss index b88d78d93..9710faa22 100644 --- a/ts/deck-options/deck-options-base.scss +++ b/ts/deck-options/deck-options-base.scss @@ -1,7 +1,7 @@ @import "sass/base"; // override Bootstrap transition duration -$carousel-transition: 0.2s; +$carousel-transition: var(--transition); @import "bootstrap/scss/buttons"; @import "bootstrap/scss/button-group"; diff --git a/ts/editor/CollapseBadge.svelte b/ts/editor/CollapseBadge.svelte index 608e1a1cc..3ca97677c 100644 --- a/ts/editor/CollapseBadge.svelte +++ b/ts/editor/CollapseBadge.svelte @@ -18,7 +18,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html .collapse-badge { display: inline-block; opacity: 0.4; - transition: opacity 0.2s ease-in-out, transform 80ms ease-in; + transition: opacity var(--transition) ease-in-out, + transform var(--transition) ease-in; &.highlighted { opacity: 1; } diff --git a/ts/graphs/CardCounts.svelte b/ts/graphs/CardCounts.svelte index 0ffcfe02e..67876129d 100644 --- a/ts/graphs/CardCounts.svelte +++ b/ts/graphs/CardCounts.svelte @@ -87,7 +87,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html