From e9066b3d52cff0be51eb68cb570797f2ca098a24 Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Wed, 5 Feb 2025 14:00:41 +0700
Subject: [PATCH 01/76] change(ios): relocates the "enter foreground"
kbd-reload trigger
Addresses part of #12216
---
.../Classes/Keyboard/InputViewController.swift | 2 +-
.../Classes/Keyboard/KeymanWebViewController.swift | 8 --------
.../Classes/MainViewController/MainViewController.swift | 8 ++++++++
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
index 16acd10f9e..98f6290276 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
@@ -589,7 +589,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}
// KeymanWebViewController maintenance methods
- func reload() {
+ public func reload() {
keymanWeb.reloadKeyboard()
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
index a0c8c6d5e4..72822bbbfc 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
@@ -72,14 +72,6 @@ class KeymanWebViewController: UIViewController {
super.init(nibName: nil, bundle: nil)
_ = view
-
- NotificationCenter.default.addObserver(
- forName: UIApplication.willEnterForegroundNotification,
- object: nil,
- queue: OperationQueue.main
- ) { _ in
- self.reloadKeyboard()
- }
}
required init?(coder aDecoder: NSCoder) {
diff --git a/ios/keyman/Keyman/Keyman/Classes/MainViewController/MainViewController.swift b/ios/keyman/Keyman/Keyman/Classes/MainViewController/MainViewController.swift
index 35ee78c5db..de6f00da63 100644
--- a/ios/keyman/Keyman/Keyman/Classes/MainViewController/MainViewController.swift
+++ b/ios/keyman/Keyman/Keyman/Classes/MainViewController/MainViewController.swift
@@ -112,6 +112,14 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
forName: Notifications.keyboardRemoved,
observer: self,
function: MainViewController.keyboardRemoved)
+
+ NotificationCenter.default.addObserver(
+ forName: UIApplication.willEnterForegroundNotification,
+ object: nil,
+ queue: OperationQueue.main
+ ) { _ in
+ Manager.shared.inputViewController.reload()
+ }
// Unfortunately, it's the main app with the file definitions.
// We have to gerry-rig this so that the framework-based SettingsViewController
From b8aaab6ea21caafc8cb4784664c93ca6280f737c Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Wed, 5 Feb 2025 14:01:18 +0700
Subject: [PATCH 02/76] change(ios): relocates keyboard hide+show notification
handling
Addresses part of #12216
---
.../Keyboard/InputViewController.swift | 10 +++++++-
.../Keyboard/KeymanWebViewController.swift | 20 ---------------
.../KMEI/KeymanEngine/Classes/Manager.swift | 25 +++++++++++++++++++
3 files changed, 34 insertions(+), 21 deletions(-)
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
index 98f6290276..87794aac3b 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
@@ -206,7 +206,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
keymanWeb = KeymanWebViewController(storage: Storage.active)
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
- var message = self.hasFullAccess ? "hasFullAccess: true" : "hasFullAccess: false"
+ let message = self.hasFullAccess ? "hasFullAccess: true" : "hasFullAccess: false"
os_log("%{public}s", log: KeymanEngineLogger.settings, type: .default, message)
SentryManager.breadcrumb(message)
@@ -623,10 +623,18 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
func showHelpBubble() {
keymanWeb.showHelpBubble()
}
+
+ func dismissHelpBubble() {
+ keymanWeb.dismissHelpBubble()
+ }
func showHelpBubble(afterDelay delay: TimeInterval) {
keymanWeb.showHelpBubble(afterDelay: delay)
}
+
+ internal func enforceKeyboardSize() {
+ keymanWeb.resizeKeyboard()
+ }
func clearText() {
setContextState(text: nil, range: NSRange(location: 0, length: 0))
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
index 72822bbbfc..c0e7cefd0c 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
@@ -146,11 +146,6 @@ class KeymanWebViewController: UIViewController {
view = webView
- NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow),
- name: UIResponder.keyboardWillShowNotification, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide),
- name: UIResponder.keyboardWillHideNotification, object: nil)
-
reloadKeyboard()
}
@@ -982,18 +977,3 @@ extension KeymanWebViewController {
return keyboardMenuView != nil
}
}
-
-// MARK: - Keyboard Notifications
-extension KeymanWebViewController {
- @objc func keyboardWillShow(_ notification: Notification) {
- resizeKeyboard()
-
- if Manager.shared.isKeymanHelpOn {
- showHelpBubble(afterDelay: 1.5)
- }
- }
-
- @objc func keyboardWillHide(_ notification: Notification) {
- dismissHelpBubble()
- }
-}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
index 4ab0da660b..25b1f143cb 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
@@ -239,10 +239,35 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
SentryManager.capture(error, message:message)
}
}
+
+ // Keep these within our singleton Manager instance. As we never remove these,
+ // setting them within something prone to replacement (like the keyboard when in
+ // app-extension mode) can lead to memory leaks. See #12216.
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), //
+ name: UIResponder.keyboardWillShowNotification, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), //
+ name: UIResponder.keyboardWillHideNotification, object: nil)
// We used to preload the old KeymanWebViewController, but now that it's embedded within the
// InputViewController, that's not exactly viable.
}
+
+ // MARK: - Keyboard Notifications
+ // Do NOT place these anywhere within InputViewController or its children.
+ // So far as we can tell, the OS causes the app extension to sometimes throw
+ // instances away, and maintaining references via notification to them can
+ // cause memory leaks. Refer to #12216.
+ @objc func keyboardWillShow(_ notification: Notification) {
+ inputViewController?.enforceKeyboardSize()
+
+ if Manager.shared.isKeymanHelpOn {
+ inputViewController?.showHelpBubble(afterDelay: 1.5)
+ }
+ }
+
+ @objc func keyboardWillHide(_ notification: Notification) {
+ inputViewController?.dismissHelpBubble()
+ }
// MARK: - Keyboard management
From 95dae2079ea56b9e275228b12e063852d7af0436 Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Thu, 6 Feb 2025 14:34:10 +0700
Subject: [PATCH 03/76] change(web): drop 'dom' unit test concurrency when run
in CI mode
---
web/src/test/auto/dom/web-test-runner.CI.config.mjs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/web/src/test/auto/dom/web-test-runner.CI.config.mjs b/web/src/test/auto/dom/web-test-runner.CI.config.mjs
index d18a8a9cb0..2ce3711078 100644
--- a/web/src/test/auto/dom/web-test-runner.CI.config.mjs
+++ b/web/src/test/auto/dom/web-test-runner.CI.config.mjs
@@ -9,5 +9,6 @@ export default {
reporters: [
teamcityReporter(), /* custom-written, for CI-friendly reports */
sessionStabilityReporter({ciMode: true})
- ]
+ ],
+ concurrency: 1
}
\ No newline at end of file
From 3e456bbf3b0637d2abb20cee35e530124a85435e Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Fri, 7 Feb 2025 10:02:31 +0700
Subject: [PATCH 04/76] chore(web): top-level clean affects predictive-text
child projects
Addresses parts of #12735.
---
web/build.sh | 2 +-
web/src/engine/predictive-text/build.sh | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/web/build.sh b/web/build.sh
index e6d9a5feb8..5dd30f338f 100755
--- a/web/build.sh
+++ b/web/build.sh
@@ -35,7 +35,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \
":engine/keyboard-storage Subset used to collate keyboards and request them from the cloud" \
":engine/main Builds all common code used by KMW's app/-level targets" \
":engine/osk Builds the Web OSK module" \
- ":engine/predictive-text=src/engine/predictive-text/worker-main Builds KMW's predictive text module" \
+ ":engine/predictive-text Builds KMW's predictive text module" \
":help Online documentation" \
":samples Builds all needed resources for the KMW sample-page set" \
":tools Builds engine-related development resources" \
diff --git a/web/src/engine/predictive-text/build.sh b/web/src/engine/predictive-text/build.sh
index 677d8b2269..015c0ea3be 100755
--- a/web/src/engine/predictive-text/build.sh
+++ b/web/src/engine/predictive-text/build.sh
@@ -53,7 +53,6 @@ builder_run_child_actions configure
## Build actions
builder_run_child_actions build:wordbreakers
-
builder_run_child_actions build:templates
builder_run_child_actions build:worker-thread
builder_run_child_actions build:worker-main
From 6cad46fcf01db149655bd64c3e18791764b70952 Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Fri, 7 Feb 2025 15:19:54 +0700
Subject: [PATCH 05/76] fix(ios): make KeyboardMenuView ref to ancestor `weak`
---
.../Keyboard/InputViewController.swift | 24 +++++++++++++++----
.../Classes/Keyboard/KeyboardMenuView.swift | 4 +++-
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
index 87794aac3b..7923e3eb93 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
@@ -165,6 +165,8 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
// For now, should be mostly upon keymanWeb.view.heightAnchor.
var portraitConstraint: NSLayoutConstraint?
var landscapeConstraint: NSLayoutConstraint?
+
+ var outerWidthConstraint: NSLayoutConstraint?
private var keymanWeb: KeymanWebViewController
private var swallowBackspaceTextChange: Bool = false
@@ -300,6 +302,16 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
keymanWeb.shouldReload = true
}
}
+
+ open override func viewDidDisappear(_ animated: Bool) {
+ super.viewDidDisappear(animated)
+
+ if outerWidthConstraint != nil {
+ outerWidthConstraint?.isActive = false
+ self.inputView?.removeConstraint(self.outerWidthConstraint!)
+ outerWidthConstraint = nil
+ }
+ }
open override func textDidChange(_ textInput: UITextInput?) {
// Swallows self-triggered calls from emptying the context due to keyboard rules
@@ -527,11 +539,15 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}
// These require the view to appear - parent and our relationship with it must exist!
+ // ... wait, is THIS possibly a leak source?
private func setOuterConstraints() {
- var baseWidthConstraint: NSLayoutConstraint
- baseWidthConstraint = self.inputView!.widthAnchor.constraint(equalTo: parent!.view.safeAreaLayoutGuide.widthAnchor)
- baseWidthConstraint.priority = UILayoutPriority(rawValue: 999)
- baseWidthConstraint.isActive = true
+ guard outerWidthConstraint == nil else {
+ outerWidthConstraint!.isActive = true
+ return
+ }
+ outerWidthConstraint = self.inputView!.widthAnchor.constraint(equalTo: parent!.view.safeAreaLayoutGuide.widthAnchor)
+ outerWidthConstraint!.priority = UILayoutPriority(rawValue: 999)
+ outerWidthConstraint!.isActive = true
}
public var kmwHeight: CGFloat {
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardMenuView.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardMenuView.swift
index 397697222c..1823b143b3 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardMenuView.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardMenuView.swift
@@ -26,7 +26,9 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe
private var tableView: UITableView?
private let closeButtonTitle: String?
- private var _inputViewController: InputViewController?
+ // Is populated during init from a weak ref by KeymanWebViewController,
+ // and instances of this class are owned by the same.
+ private weak var _inputViewController: InputViewController?
override var inputViewController: InputViewController? {
return _inputViewController
}
From f36c54b6c641c539ab233529b6c61ab03ea4f4ca Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Mon, 10 Feb 2025 14:55:04 +0700
Subject: [PATCH 06/76] change(ios): mitigate keyboard input-view memory leak
by force-discarding WebView
Related-to: #12216
---
.../Keyboard/InputViewController.swift | 60 ++++++++++++-------
.../Keyboard/KeymanWebViewController.swift | 12 ++++
.../KMEI/KeymanEngine/Classes/Manager.swift | 1 +
3 files changed, 51 insertions(+), 22 deletions(-)
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
index 7923e3eb93..a51db67141 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift
@@ -41,6 +41,16 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
super.init(coder: coder)
}
+ public func destroy() {
+ // In app-extension mode, there are scenarios in which this class does not properly
+ // deallocate! We need to help that process along. In particular, doing this allows
+ // us to guarantee that the WebView is allowed to be GC'd, even when Apple fails to GC
+ // this (`CustomInputView`) instance - which actually happens. (Refer to #12216.)
+ keymanWeb.removeFromParent()
+ keymanWeb.destroy()
+ keymanWeb = nil
+ }
+
public var enableInputClicksWhenVisible: Bool {
get {
// Implemented as noted by https://developer.apple.com/documentation/uikit/uidevice/1620050-playinputclick.
@@ -98,37 +108,37 @@ private class CustomInputView: UIInputView, UIInputViewAudioFeedback {
*/
func keyboardHeightChanged() {
os_log("CustomInputView keyboardHeightChanged", log: KeymanEngineLogger.ui, type: .info)
-
+
// deactivate constraints for both orientations (though one should already be inactive)
landscapeConstraint?.isActive = false
portraitConstraint?.isActive = false
-
+
// rebuild both portrait and landscape constraints
self.buildKeyboardHeightConstraints(bannerHeight: InputViewController.topBarHeight)
-
+
// activate constraints for the current orientation
if InputViewController.isPortrait {
portraitConstraint?.isActive = true
} else {
landscapeConstraint?.isActive = true
}
-
+
self.setNeedsLayout()
}
-
+
private func buildKeyboardHeightConstraints(bannerHeight: CGFloat) {
os_log("CustomInputView buildKeyboardHeightConstraints", log: KeymanEngineLogger.ui, type: .info)
let innerView = keymanWeb.view!
-
+
// Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard.
let portraitHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: true)!)
portraitHeightConstraint.identifier = "Height constraint for portrait mode"
portraitHeightConstraint.priority = .defaultHigh
-
+
let landscapeHeightConstraint = innerView.heightAnchor.constraint(equalToConstant: bannerHeight + keymanWeb.readKeyboardHeight(isPortrait: false)!)
landscapeHeightConstraint.identifier = "Height constraint for landscape mode"
landscapeHeightConstraint.priority = .defaultHigh
-
+
portraitConstraint = portraitHeightConstraint
landscapeConstraint = landscapeHeightConstraint
// .isActive will be set according to the current portrait/landscape perspective.
@@ -165,7 +175,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
// For now, should be mostly upon keymanWeb.view.heightAnchor.
var portraitConstraint: NSLayoutConstraint?
var landscapeConstraint: NSLayoutConstraint?
-
+
var outerWidthConstraint: NSLayoutConstraint?
private var keymanWeb: KeymanWebViewController
@@ -219,6 +229,12 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
fatalError("init(coder:) has not been implemented")
}
+ deinit {
+ inputView?.removeFromSuperview()
+ (inputView as? CustomInputView)?.destroy()
+ inputView = nil
+ }
+
open override func updateViewConstraints() {
resetKeyboardState()
@@ -302,10 +318,10 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
keymanWeb.shouldReload = true
}
}
-
+
open override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
-
+
if outerWidthConstraint != nil {
outerWidthConstraint?.isActive = false
self.inputView?.removeConstraint(self.outerWidthConstraint!)
@@ -380,7 +396,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}
}
}
-
+
func deleteSelection() -> Bool {
if let selected = textDocumentProxy.selectedText, selected.count > 0 {
/*
@@ -393,23 +409,23 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
/*
We have a problem to resolve here: we cannot simply delete the selection
with either .deleteBackward() or .insertText(""):
-
+
- If there is selected text immediately following a space (U+0020),
.deleteBackward() will delete that space IN ADDITION to the selected text.
- Unlike .insertText("-any-string-here"), .insertText("") does nothing;
it does not replace the selection with the new string.
-
+
Our policy (#9073) on handling the backspace key when there is a
text selection is to just delete the selection. We have to override
the special case of space being deleted by .deleteBackward() ourselves.
Additionally, the internal Web engine cannot anticipate the special
case and requires precise and consistent backspace handling in line
with our policy in order to keep the context on both sides synchronized.
-
+
As .insertText() does not delete the selection if the string to
be inserted is empty, we insert something that won't combine,
like a ZWNJ, and then delete it.
-
+
iOS does not allow users to select text in a way that splits
character clusters. This implies that it's impossible for an
inserted ZWNJ to combine with existing context, making this
@@ -417,14 +433,14 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
*/
textDocumentProxy.insertText("\u{200c}")
textDocumentProxy.deleteBackward()
-
+
let afterManipulation = textDocumentProxy.documentContextBeforeInput ?? ""
-
+
// And now to finish our 'canary' check.
if beforeManipulation != afterManipulation {
os_log(.error, log: KeymanEngineLogger.engine, "Could not cleanly execute backspace for selected text")
}
-
+
return true
}
return false
@@ -576,7 +592,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
customInputView.keyboardHeightChanged()
}
}
-
+
func fixLayout() {
view.setNeedsLayout()
view.layoutIfNeeded()
@@ -639,7 +655,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
func showHelpBubble() {
keymanWeb.showHelpBubble()
}
-
+
func dismissHelpBubble() {
keymanWeb.dismissHelpBubble()
}
@@ -647,7 +663,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
func showHelpBubble(afterDelay delay: TimeInterval) {
keymanWeb.showHelpBubble(afterDelay: delay)
}
-
+
internal func enforceKeyboardSize() {
keymanWeb.resizeKeyboard()
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
index c0e7cefd0c..7e18751440 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift
@@ -173,6 +173,18 @@ class KeymanWebViewController: UIViewController {
// MARK: - JavaScript functions
extension KeymanWebViewController {
+ // In app-extension mode, there are scenarios in which this class does not properly
+ // deallocate! We need to help that process along.
+ func destroy() {
+ // Message handlers can maintain references.
+ if #available(iOSApplicationExtension 14.0, *) {
+ self.userContentController.removeAllScriptMessageHandlers()
+ } else {
+ self.userContentController.removeScriptMessageHandler(forName: keymanWebViewName)
+ }
+ view = nil
+ }
+
func languageMenuPosition(_ completion: @escaping (CGRect) -> Void) {
webView!.evaluateJavaScript("langMenuPos();") { result, _ in
guard let result = result as? String, !result.isEmpty else {
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
index 25b1f143cb..adfdad596c 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
@@ -165,6 +165,7 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
}
set(value) {
+ // The system keyboard will set itself here when its `viewDidAppear` triggers.
_inputViewController = value
}
}
From 342f216291a5608162098c3940f953bb44a6c493 Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Tue, 11 Feb 2025 14:14:15 +0700
Subject: [PATCH 07/76] chore: increment to alpha 19.0
---
VERSION.md | 2 +-
android/docs/engine/guides/in-app/index.md | 6 ++--
android/docs/engine/index.md | 2 +-
android/docs/engine/whatsnew.md | 7 +---
android/docs/help/about/whatsnew.md | 7 ++--
android/docs/help/index.md | 2 +-
developer/docs/help/index.md | 4 +--
developer/docs/help/whats-new.md | 42 ++--------------------
developer/src/kmc/src/kmlmp.ts | 2 +-
docs/minimum-versions.md | 2 +-
docs/minimum-versions.md.in | 2 +-
ios/docs/engine/index.md | 2 +-
ios/docs/help/about/whatsnew.md | 7 ++--
ios/docs/help/index.md | 2 +-
linux/docs/help/about/whatsnew.md | 4 +--
linux/docs/help/index.md | 2 +-
mac/docs/help/about/whatsnew.md | 4 +--
mac/docs/help/index.md | 2 +-
resources/build/minimum-versions.inc.sh | 2 +-
web/docs/engine/guide/index.md | 6 ++--
web/docs/engine/index.md | 12 +++----
web/docs/engine/reference/core/version.md | 2 +-
web/docs/engine/reference/index.md | 2 +-
web/docs/engine/whats-new.md | 2 +-
windows/docs/engine/api/index.md | 6 ++--
windows/docs/engine/index.md | 4 +--
windows/docs/help/about/whatsnew.md | 4 +--
windows/docs/help/index.md | 2 +-
28 files changed, 47 insertions(+), 96 deletions(-)
diff --git a/VERSION.md b/VERSION.md
index b9cdf8a8b6..b2c7e01f85 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.189
\ No newline at end of file
+19.0.1
\ No newline at end of file
diff --git a/android/docs/engine/guides/in-app/index.md b/android/docs/engine/guides/in-app/index.md
index d788e267bb..538094ddbb 100644
--- a/android/docs/engine/guides/in-app/index.md
+++ b/android/docs/engine/guides/in-app/index.md
@@ -6,8 +6,8 @@ Keyman Engine for Android allows you to use any Keyman touch keyboard in your An
system keyboard app for purchase in the Play Store.
This guide will walk you through the steps for creating your first Android app with Keyman Engine for Android.
-If you are not familiar with Android development, you will find the
-[Android Developer online training](https://developer.android.com/training/index.html) an invaluable
+If you are not familiar with Android development, you will find the
+[Android Developer online training](https://developer.android.com/training/index.html) an invaluable
resource, and working through some of their tutorials first will help you with the rest of this guide.
### 1. Install Free Tools
@@ -131,5 +131,5 @@ And there you have it: your first Keyman Engine for Android app!
## See Also
* [Guide: Build a system keyboard app](../system-keyboard/)
* [Keyman Developer Documentation](/developer/17.0/)
-* [Keyman Engine for Android Documentation](/developer/engine/android/18.0/)
+* [Keyman Engine for Android Documentation](/developer/engine/android/19.0/)
* [Android Developer Home](https://developer.android.com/index.html)
diff --git a/android/docs/engine/index.md b/android/docs/engine/index.md
index fa549e4b34..38f255592b 100644
--- a/android/docs/engine/index.md
+++ b/android/docs/engine/index.md
@@ -4,7 +4,7 @@ title: Keyman Engine for Android
## Overview
-Keyman Engine for Android 18.0 is a Java library for Android 5.0 and later versions which enables a fully customisable keyboard layout, both within an app and system-wide.
+Keyman Engine for Android 19.0 is a Java library for Android 5.0 and later versions which enables a fully customisable keyboard layout, both within an app and system-wide.
Keyboard layouts for Keyman Engine can be created with [Keyman Developer](/developer/17.0), and a [library of existing keyboard layouts](http://keyman.com/developer/keymanweb/keyboards)
is also available.
diff --git a/android/docs/engine/whatsnew.md b/android/docs/engine/whatsnew.md
index af3f8932d6..5d6f717f8a 100644
--- a/android/docs/engine/whatsnew.md
+++ b/android/docs/engine/whatsnew.md
@@ -1,11 +1,6 @@
---
-title: What's New in Keyman Engine 18.0 for Android
+title: What's New in Keyman Engine 19.0 for Android
---
-## Keyman Engine for Android Breaking Changes: ##
-
-* Change package name from `com.tavultesoft.kmea` to `com.keyman.engine` #7881
-* Update to Java 11 #8543
-
## See Also
* [Keyman Engine for Android Documentation](index)
\ No newline at end of file
diff --git a/android/docs/help/about/whatsnew.md b/android/docs/help/about/whatsnew.md
index 0f0e891e42..d0ca85e93f 100644
--- a/android/docs/help/about/whatsnew.md
+++ b/android/docs/help/about/whatsnew.md
@@ -1,9 +1,6 @@
---
-title: What's New in Keyman 18.0 for Android
+title: What's New in Keyman 19.0 for Android
---
-Here are some of the new features we have added to Keyman 18.0 for Android:
+Here are some of the new features we have added to Keyman 19.0 for Android:
-* New menu to adjust longpress delay time (#12170, #12185)
-* Support localizations for right-to-left languages (#12215)
-* Handle additional actions for ENTER key (#12125, #12315)
diff --git a/android/docs/help/index.md b/android/docs/help/index.md
index 62df93195b..360af91e6d 100644
--- a/android/docs/help/index.md
+++ b/android/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman for Android 18.0 Help
+title: Keyman for Android 19.0 Help
---
## [About Keyman](about/)
diff --git a/developer/docs/help/index.md b/developer/docs/help/index.md
index 9e9c250abb..d7fbc4098d 100644
--- a/developer/docs/help/index.md
+++ b/developer/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman Developer 18.0 User Guide
+title: Keyman Developer 19.0 User Guide
---
Need help using Keyman Developer to create your keyboard layouts? You'll
@@ -8,7 +8,7 @@ and tutorials, and full reference information.
## Guides and Tutorials
- [What is Keyman Developer?](guides/intro)
-- [What's new in 18.0](whats-new)
+- [What's new in 19.0](whats-new)
- [Developing Keyman keyboard layouts](guides/develop)
- [Testing Keyman keyboards](guides/test)
- [Distributing Keyman keyboards](guides/distribute)
diff --git a/developer/docs/help/whats-new.md b/developer/docs/help/whats-new.md
index d1dce092c3..1005476c3d 100644
--- a/developer/docs/help/whats-new.md
+++ b/developer/docs/help/whats-new.md
@@ -1,43 +1,5 @@
---
-title: What's new in Keyman Developer 18.0
+title: What's new in Keyman Developer 19.0
---
-Keyman Developer 17 has a number of significant changes:
-
-* `kmcomp` has been removed and replaced with `kmc`. Learn more in our [kmcomp migration guide](reference/kmc/cli/kmcomp-migration).
-
-* Windows installer packages should now be built with
- `kmc build windows-package-installer` and can no longer be built within
- the Keyman Developer IDE. More details on how to use this are in the
- [`kmc` reference documentation](reference/kmc/cli/reference#toc-kmc-build-windows-package-installer-additional-options).
-
-* `.keyboard_info` files are now generated entirely from the package and
- keyboard files. Extra fields are available in packages for license file,
- welcome file, typing examples, related packages (including deprecated
- packages), and additional font files. Keyboards in the Keyman Cloud
- keyboard repository have already been updated; if you are an author of
- one of these keyboards, you should pull the changes from the repository
- before submitting future updates.
-
-* Keyboard project files (.kpj) now have a new format which does not list
- individual files, but instead includes all files in the same folder and
- subfolders relative to the project file. This prevents projects from
- including unrelated files, and reduces the number of files that change
- when keyboards are updated.
-
-* Keyman Developer will now open each project in a new window, allowing
- the user to rapidly switch between multiple projects at the same time.
-
-* The Keyman Keyboard and Lexical Model Cloud repositories have new build
- scripts which run `kmc`. These run much, much faster than previously!
-
-* The [`&displayMap`](/developer/language/reference/displaymap) store allows
- keyboard developers to specify a font mapping to PUA for the On Screen
- Keyboard and Touch Layout to resolve diacritic rendering issues.
-
-* Keyman Developer has been updated to support Unicode 15.1.
-
-* Additional non-printing characters have been added to the Touch Layout Editor.
-
-* Virtual keys in output of rules have never worked properly or been officially
- supported; Keyman Developer will now warn if you attempt to use this pattern.
+Keyman Developer 19 has the following significant changes:
diff --git a/developer/src/kmc/src/kmlmp.ts b/developer/src/kmc/src/kmlmp.ts
index bb0cda5e53..a1346ac0d3 100644
--- a/developer/src/kmc/src/kmlmp.ts
+++ b/developer/src/kmc/src/kmlmp.ts
@@ -3,7 +3,7 @@
* kmlmp - Keyman Lexical Model Package Compiler
*/
-// Note: this is a deprecated package and will be removed in Keyman 18.0
+// Note: this is a deprecated package and will be removed in Keyman 19.0
import { Command } from 'commander';
import { KmpCompiler } from '@keymanapp/kmc-package';
diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md
index 174f4b518e..beb542e408 100644
--- a/docs/minimum-versions.md
+++ b/docs/minimum-versions.md
@@ -4,7 +4,7 @@ title: Keyman Minimum Versions
Changes in [minimum-versions.inc.sh](minimum-versions.inc.sh) should manually be propagated to the linked help files below:
-## Keyman 18.0
+## Keyman 19.0
Target Operating System and Platform Versions
diff --git a/docs/minimum-versions.md.in b/docs/minimum-versions.md.in
index ffe508e12f..41a664d454 100644
--- a/docs/minimum-versions.md.in
+++ b/docs/minimum-versions.md.in
@@ -4,7 +4,7 @@ title: Keyman Minimum Versions
Changes in [minimum-versions.inc.sh](minimum-versions.inc.sh) should manually be propagated to the linked help files below:
-## Keyman 18.0
+## Keyman 19.0
Target Operating System and Platform Versions
diff --git a/ios/docs/engine/index.md b/ios/docs/engine/index.md
index b87af4e260..bdba88b741 100644
--- a/ios/docs/engine/index.md
+++ b/ios/docs/engine/index.md
@@ -4,7 +4,7 @@ title: Keyman for iPhone and iPad Developer Support
## Overview
-The Keyman Engine for iPhone and iPad 18.0 SDK is designed to provide
+The Keyman Engine for iPhone and iPad 19.0 SDK is designed to provide
advanced international keyboard support to iOS apps.
As a developer, you simply need to use (or subclass) TextView or
diff --git a/ios/docs/help/about/whatsnew.md b/ios/docs/help/about/whatsnew.md
index aa4cf6b333..b123b1f23c 100644
--- a/ios/docs/help/about/whatsnew.md
+++ b/ios/docs/help/about/whatsnew.md
@@ -1,8 +1,5 @@
---
-title: What's New in Keyman 18.0 for iPhone and iPad
+title: What's New in Keyman 19.0 for iPhone and iPad
---
-Here are some of the new features we have added to Keyman for iPhone and iPad 18.0:
-Additional changes:
-
-* Performance improvements
+Here are some of the new features we have added to Keyman for iPhone and iPad 19.0:
diff --git a/ios/docs/help/index.md b/ios/docs/help/index.md
index 994bbbda9e..cd0f81300e 100644
--- a/ios/docs/help/index.md
+++ b/ios/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman for iPhone and iPad 18.0 Help
+title: Keyman for iPhone and iPad 19.0 Help
---
## [About Keyman](about/)
diff --git a/linux/docs/help/about/whatsnew.md b/linux/docs/help/about/whatsnew.md
index 4a4d5a30e1..5521261b96 100644
--- a/linux/docs/help/about/whatsnew.md
+++ b/linux/docs/help/about/whatsnew.md
@@ -1,5 +1,5 @@
---
-title: What's New in Keyman 18.0 for Linux
+title: What's New in Keyman 19.0 for Linux
---
-Here are some of the new features we have added to Keyman for Linux 18.0:
+Here are some of the new features we have added to Keyman for Linux 19.0:
diff --git a/linux/docs/help/index.md b/linux/docs/help/index.md
index 58d245063a..329c66f69b 100644
--- a/linux/docs/help/index.md
+++ b/linux/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman for Linux 18.0 Help
+title: Keyman for Linux 19.0 Help
---
Need help using Keyman for Linux? In time, this product documentation will grow and explain frequently asked questions.
diff --git a/mac/docs/help/about/whatsnew.md b/mac/docs/help/about/whatsnew.md
index 73ccb4d49b..8cc29bdadb 100644
--- a/mac/docs/help/about/whatsnew.md
+++ b/mac/docs/help/about/whatsnew.md
@@ -1,5 +1,5 @@
---
-title: What's New in Keyman 18.0 for macOS
+title: What's New in Keyman 19.0 for macOS
---
-Here are some of the new features we have added to Keyman 18.0 for macOS:
+Here are some of the new features we have added to Keyman 19.0 for macOS:
diff --git a/mac/docs/help/index.md b/mac/docs/help/index.md
index aa507a531e..12397a903d 100644
--- a/mac/docs/help/index.md
+++ b/mac/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman 18.0 for macOS Help
+title: Keyman 19.0 for macOS Help
---
Need help using Keyman for macOS? You'll find everything you need here, including product documentation,
diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh
index 41a7c3690f..f57ebff419 100644
--- a/resources/build/minimum-versions.inc.sh
+++ b/resources/build/minimum-versions.inc.sh
@@ -1,4 +1,4 @@
-# Required minimum versions as of Keyman 18.0
+# Required minimum versions as of Keyman 19.0
#
# This is a list of minimum, maximum, and specific versions of any external
# components or dependencies found in Keyman.
diff --git a/web/docs/engine/guide/index.md b/web/docs/engine/guide/index.md
index 26e2e42b47..4f59a4ebf0 100644
--- a/web/docs/engine/guide/index.md
+++ b/web/docs/engine/guide/index.md
@@ -1,8 +1,8 @@
---
-title: KeymanWeb 18.0 - The Guide
+title: KeymanWeb - The Guide
---
-Welcome to the guide for using KeymanWeb 18.0.
+Welcome to the guide for using KeymanWeb.
- [Getting Started with Keyman Web](get-started)
- [User Interface Design](user-interface-design)
@@ -10,4 +10,4 @@ Welcome to the guide for using KeymanWeb 18.0.
- [Installing Keyboards](adding-keyboards)
- [Additional Examples](examples/)
-[Return to the main KeymanWeb 18.0 page](../)
\ No newline at end of file
+[Return to the main KeymanWeb page](../)
\ No newline at end of file
diff --git a/web/docs/engine/index.md b/web/docs/engine/index.md
index 63d14e2fd1..5515112f88 100644
--- a/web/docs/engine/index.md
+++ b/web/docs/engine/index.md
@@ -1,27 +1,27 @@
---
-title: Keyman Engine for Web 18.0 Developer Help
+title: Keyman Engine for Web 19.0 Developer Help
---
-Keyman Engine for Web 18.0 is the current version of KeymanWeb and
+Keyman Engine for Web 19.0 is the current version of KeymanWeb and
supports touch devices with custom touch-layouts as well as desktop
computer browsers.
[Download](https://keyman.com/developer/keymanweb/)
-: Downloading Keyman Engine for Web 18.0
+: Downloading Keyman Engine for Web
[Guide](guide/)
-: Keyman Engine for Web 18.0 Guide
+: Keyman Engine for Web Guide
[Reference](reference)
-: Keyman Engine for Web 18.0 Developer Reference
+: Keyman Engine for Web Developer Reference
[What's New](whats-new)
-: What's new in Keyman Engine for Web 18.0
+: What's new in Keyman Engine for Web 19.0
diff --git a/web/docs/engine/reference/core/version.md b/web/docs/engine/reference/core/version.md
index 7df0978ac8..40b9db4529 100644
--- a/web/docs/engine/reference/core/version.md
+++ b/web/docs/engine/reference/core/version.md
@@ -22,7 +22,7 @@ Read only
### Return Value
-`'18.0'` (for KeymanWeb 18.0)
+`'19.0'` (for KeymanWeb 19.0)
## Description
diff --git a/web/docs/engine/reference/index.md b/web/docs/engine/reference/index.md
index 74d83c3931..f6e5a3a8de 100644
--- a/web/docs/engine/reference/index.md
+++ b/web/docs/engine/reference/index.md
@@ -1,5 +1,5 @@
---
-title: KeymanWeb 18.0 Reference
+title: KeymanWeb Reference
---
[KeymanWeb Overview](overview)
diff --git a/web/docs/engine/whats-new.md b/web/docs/engine/whats-new.md
index 49fa337fe0..e220fb9c13 100644
--- a/web/docs/engine/whats-new.md
+++ b/web/docs/engine/whats-new.md
@@ -1,3 +1,3 @@
---
-title: What's New in KeymanWeb 18.0
+title: What's New in KeymanWeb 19.0
---
diff --git a/windows/docs/engine/api/index.md b/windows/docs/engine/api/index.md
index 7032aa0cd2..f95332c45a 100644
--- a/windows/docs/engine/api/index.md
+++ b/windows/docs/engine/api/index.md
@@ -1,13 +1,13 @@
---
-title: Keyman Engine for Windows 18.0 API
+title: Keyman Engine for Windows API
---
## Introduction
-The Keyman Engine for Windows 18.0 API is implemented in COM. It can be
+The Keyman Engine for Windows API is implemented in COM. It can be
instantiated with `CreateObject("keymanapi.Keyman")`.
-> [!Note]
+> [!Note]
> This documentation applies to Keyman Engine for Windows versions 14.0 and up.
## Interface Hierarchy
diff --git a/windows/docs/engine/index.md b/windows/docs/engine/index.md
index acdd77d877..0687433412 100644
--- a/windows/docs/engine/index.md
+++ b/windows/docs/engine/index.md
@@ -1,8 +1,8 @@
---
-title: Keyman Engine for Windows 18.0
+title: Keyman Engine for Windows
---
-Keyman Engine for Windows 18.0 gives you the tools to build a customised
+Keyman Engine for Windows gives you the tools to build a customised
desktop keyboarding product for Windows.
**Note:** This documentation applies to Keyman Engine for Windows
diff --git a/windows/docs/help/about/whatsnew.md b/windows/docs/help/about/whatsnew.md
index 4f11c82ec2..ee917f3870 100644
--- a/windows/docs/help/about/whatsnew.md
+++ b/windows/docs/help/about/whatsnew.md
@@ -1,8 +1,8 @@
---
-title: What's New in Keyman 18.0 for Windows
+title: What's New in Keyman 19.0 for Windows
---
-Here are some of the new features we have added to Keyman 18.0 for Windows:
+Here are some of the new features we have added to Keyman 19.0 for Windows:
## Related Topics
diff --git a/windows/docs/help/index.md b/windows/docs/help/index.md
index c7cccc7695..231ed5fc7d 100644
--- a/windows/docs/help/index.md
+++ b/windows/docs/help/index.md
@@ -1,5 +1,5 @@
---
-title: Keyman for Windows 18.0 Help
+title: Keyman for Windows 19.0 Help
---
Need help using Keyman for Windows? You'll find everything you need here, including product documentation,
From 7ab4c67383dac487e23818b9aebd6ad753ec354b Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Tue, 11 Feb 2025 02:52:18 -0500
Subject: [PATCH 08/76] auto: increment master version to 19.0.2
---
HISTORY.md | 5 +++++
VERSION.md | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/HISTORY.md b/HISTORY.md
index ca5fe2101f..e907cc12dd 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,10 @@
# Keyman Version History
+## 19.0.1 alpha 2025-02-11
+
+* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
+* chore: increment to alpha 19.0 (#13187)
+
## 18.0.188 alpha 2025-02-10
* fix(windows): check the params status flag equals ucrsUpdateReady before attempting to download the keyman setup file (#13154)
diff --git a/VERSION.md b/VERSION.md
index b2c7e01f85..fc1a009252 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.1
\ No newline at end of file
+19.0.2
\ No newline at end of file
From f2747be553bfc587226f847fb4ad193e839315e7 Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Tue, 11 Feb 2025 19:24:22 +0100
Subject: [PATCH 09/76] chore(linux): remove support of Ubuntu 20.04 Focal
Ubuntu 20.04 Focal will reach EOL in April and GitHub removes the
runner images, so we no longer will be able to build packages for Focal.
---
.github/workflows/deb-packaging.yml | 2 +-
linux/scripts/launchpad.sh | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml
index 52c485aa65..bf1bfca2fa 100644
--- a/.github/workflows/deb-packaging.yml
+++ b/.github/workflows/deb-packaging.yml
@@ -116,7 +116,7 @@ jobs:
strategy:
fail-fast: true
matrix:
- dist: [focal, jammy, noble, oracular]
+ dist: [jammy, noble, oracular]
steps:
- name: Checkout
diff --git a/linux/scripts/launchpad.sh b/linux/scripts/launchpad.sh
index 98ab9238f0..4b89980994 100755
--- a/linux/scripts/launchpad.sh
+++ b/linux/scripts/launchpad.sh
@@ -33,7 +33,7 @@ else
fi
echo "ppa: ${ppa}"
-distributions="${DIST:-focal jammy noble oracular plucky}"
+distributions="${DIST:-jammy noble oracular plucky}"
packageversion="${PACKAGEVERSION:-1~sil1}"
BASEDIR=$(pwd)
From 02a3a1a66c121c1e42535ca943ec5a8b6c6d7f17 Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Tue, 11 Feb 2025 19:32:29 +0100
Subject: [PATCH 10/76] chore(linux): cleanup code
Now that we no longer support Focal we can use more modern tools for
packaging.
---
docs/linux/README.md | 2 +-
linux/debian/rules | 5 +----
2 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/docs/linux/README.md b/docs/linux/README.md
index 80c1a78434..64ef6c967f 100644
--- a/docs/linux/README.md
+++ b/docs/linux/README.md
@@ -147,7 +147,7 @@ Run `ibus restart` after installing any of them.
Keyman tries to activate the keyboard automatically. If you want to activate
it for a different language, you can do so by following the steps below.
-#### GNOME3 (focal and bionic default, also newer Ubuntu versions)
+#### GNOME3 (Ubuntu default)
- Click the connection/sound/shutdown section in the top right. Then the tools
icon for Settings.
diff --git a/linux/debian/rules b/linux/debian/rules
index 8da37f40e4..5138b58330 100755
--- a/linux/debian/rules
+++ b/linux/debian/rules
@@ -66,10 +66,7 @@ override_dh_auto_install:
rm $(CURDIR)/debian/keyman/usr/share/locale/*.po*
# Don't call `build.sh install` - dh_auto_install does some extra smarts
dh_auto_install --sourcedir=linux/keyman-config --buildsystem=pybuild $@
- # Unfortunately bash-completion 2.10 (focal) doesn't yet provide dh-sequence-bash-completion,
- # which we could add as build-dependency, so we'll have to explicitly call
- # dh_bash_completion here instead of using `dh $@ --with-python3 --with bash-completion`
- dh_bash-completion -O--buildsystem=pybuild
+ dh $@ --with-python3 --with bash-completion
dh_python3 -O--buildsystem=pybuild
override_dh_missing:
From 0e48e44d2deb33cf3ab7123279ceb46cd5f523e6 Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Wed, 12 Feb 2025 13:14:42 +0700
Subject: [PATCH 11/76] chore(developer): add INFO messages for `kmc copy` and
`kmc generate`
Note: these messages are in `kmc`, not in `kmc-copy` or `kmc-generate`,
matching the structure for existing `INFO_BuildingProject` and related
messages.
Fixes: #13127
---
developer/src/kmc-copy/src/copier-messages.ts | 6 +--
.../kmc-generate/src/generator-messages.ts | 8 +---
developer/src/kmc/src/commands/copy.ts | 12 +++++
developer/src/kmc/src/commands/generate.ts | 11 ++++-
.../src/messages/infrastructureMessages.ts | 45 ++++++++++++++++++-
5 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/developer/src/kmc-copy/src/copier-messages.ts b/developer/src/kmc-copy/src/copier-messages.ts
index d4751474b9..976002a35f 100644
--- a/developer/src/kmc-copy/src/copier-messages.ts
+++ b/developer/src/kmc-copy/src/copier-messages.ts
@@ -25,11 +25,7 @@ export class CopierMessages {
o.e ?? 'unknown error'
);
- static INFO_CopyingProject = SevInfo | 0x0002;
- static Info_CopyingProject = (o:{type: string, id: string}) => m(
- this.INFO_CopyingProject,
- `Copying project of type ${def(o.type)} with id ${def(o.id)}`
- );
+ // unused 0x0002 (was INFO_CopyingProject, now in InfrastructureMessages)
static ERROR_CannotCreateFolder = SevError | 0x0003;
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
diff --git a/developer/src/kmc-generate/src/generator-messages.ts b/developer/src/kmc-generate/src/generator-messages.ts
index 185f68cbe6..fc8283763e 100644
--- a/developer/src/kmc-generate/src/generator-messages.ts
+++ b/developer/src/kmc-generate/src/generator-messages.ts
@@ -7,7 +7,7 @@
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException } from "@keymanapp/developer-utils";
const Namespace = CompilerErrorNamespace.Generator;
-const SevInfo = CompilerErrorSeverity.Info | Namespace;
+// const SevInfo = CompilerErrorSeverity.Info | Namespace;
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
const SevWarn = CompilerErrorSeverity.Warn | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
@@ -23,11 +23,7 @@ export class GeneratorMessages {
o.e ?? 'unknown error',
);
- static INFO_GeneratingProject = SevInfo | 0x0002;
- static Info_GeneratingProject = (o:{type: string, id: string}) => m(
- this.INFO_GeneratingProject,
- `Generating project of type ${def(o.type)} with id ${def(o.id)}`
- );
+ // unused 0x0002 (was INFO_GeneratingProject, now in InfrastructureMessages)
static ERROR_CannotCreateFolder = SevError | 0x0003;
static Error_CannotCreateFolder = (o:{folderName:string, e: any}) => CompilerMessageSpecWithException(
diff --git a/developer/src/kmc/src/commands/copy.ts b/developer/src/kmc/src/commands/copy.ts
index b7d0aef12f..9cfef2b957 100644
--- a/developer/src/kmc/src/commands/copy.ts
+++ b/developer/src/kmc/src/commands/copy.ts
@@ -79,6 +79,18 @@ async function doCopy(callbacks: NodeCompilerCallbacks, sources: string | string
return false;
}
+ const dest = options.outPath;
+ callbacks.reportMessage(InfrastructureMessages.Info_CopyingProject({source, dest}));
+ const result = runCopier(callbacks, source, options);
+ if(result) {
+ callbacks.reportMessage(InfrastructureMessages.Info_ProjectCopiedSuccessfully({source, dest}));
+ } else {
+ callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotCopiedSuccessfully({source, dest}));
+ }
+ return result;
+}
+
+async function runCopier(callbacks: NodeCompilerCallbacks, source: string, options: CopierOptions) {
const copier = new KeymanProjectCopier();
try {
if(!await copier.init(callbacks, options)) {
diff --git a/developer/src/kmc/src/commands/generate.ts b/developer/src/kmc/src/commands/generate.ts
index b362d01125..695ced9d3c 100644
--- a/developer/src/kmc/src/commands/generate.ts
+++ b/developer/src/kmc/src/commands/generate.ts
@@ -113,9 +113,16 @@ async function generate(generator: KeymanCompiler, ids: string | string[], comma
}
const options = commanderOptionsToGeneratorOptions(id, commanderOptions);
-
+ const outPath = options.outPath;
const callbacks = new NodeCompilerCallbacks(options);
- if(!await doGenerate(callbacks, generator, options)) {
+ callbacks.reportMessage(InfrastructureMessages.Info_GeneratingProject({id, outPath}));
+
+ const result = await doGenerate(callbacks, generator, options);
+
+ if(result) {
+ callbacks.reportMessage(InfrastructureMessages.Info_ProjectGeneratedSuccessfully({id}));
+ } else {
+ callbacks.reportMessage(InfrastructureMessages.Info_ProjectNotGeneratedSuccessfully({id}));
return await exitProcess(1);
}
}
diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts
index bacadca545..304e1d5e9f 100644
--- a/developer/src/kmc/src/messages/infrastructureMessages.ts
+++ b/developer/src/kmc/src/messages/infrastructureMessages.ts
@@ -158,5 +158,48 @@ export class InfrastructureMessages {
this.ERROR_CopyRequiresOutPath,
`The copy command requires the --out-path, -o parameter`
);
- }
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_CopyingProject = SevInfo | 0x0023;
+ static Info_CopyingProject = (o:{source:string,dest:string}) => ({filename:o.source, ...m(
+ this.INFO_CopyingProject,
+ `Copying project '${def(o.source)}' to '${def(o.dest)}'`,
+ )});
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_ProjectCopiedSuccessfully = SevInfo | 0x0024;
+ static Info_ProjectCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
+ this.INFO_ProjectCopiedSuccessfully,
+ `'${def(o.source)}' copied to '${def(o.dest)}' successfully.`,
+ )});
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_ProjectNotCopiedSuccessfully = SevInfo | 0x0025;
+ static Info_ProjectNotCopiedSuccessfully = (o:{source:string, dest:string}) => ({filename:o.source, ...m(
+ this.INFO_ProjectNotCopiedSuccessfully,
+ `'${def(o.source)}' was not successfully copied to '${def(o.dest)}'.`,
+ )});
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_GeneratingProject = SevInfo | 0x0026;
+ static Info_GeneratingProject = (o:{id:string, outPath: string}) => ({filename:o.id, ...m(
+ this.INFO_GeneratingProject,
+ `Generating new project '${def(o.id)}' in '${def(o.outPath)}'.`,
+ )});
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_ProjectGeneratedSuccessfully = SevInfo | 0x0027;
+ static Info_ProjectGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
+ this.INFO_ProjectGeneratedSuccessfully,
+ `New project '${def(o.id)}' generated successfully.`,
+ )});
+
+ // For this message, we override the filename with the passed-in file. A bit of a hack but does the job
+ static INFO_ProjectNotGeneratedSuccessfully = SevInfo | 0x0028;
+ static Info_ProjectNotGeneratedSuccessfully = (o:{id:string}) => ({filename:o.id, ...m(
+ this.INFO_ProjectNotGeneratedSuccessfully,
+ `New project '${def(o.id)}' was not successfully generated.`,
+ )});
+
+}
From 37a5248810ea9d058bdc7b52c96f68289698f594 Mon Sep 17 00:00:00 2001
From: Shawn Schantz
Date: Wed, 12 Feb 2025 13:33:59 +0700
Subject: [PATCH 12/76] chore(ios): increase minimum ios version to 13
---
.../KeymanEngine.xcodeproj/project.pbxproj | 24 +++++++++----------
.../Keyman/Keyman.xcodeproj/project.pbxproj | 12 +++++-----
2 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
index 045506b9af..7b21b6bc63 100644
--- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
+++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
@@ -1805,7 +1805,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1849,7 +1849,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1900,7 +1900,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngine/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1955,7 +1955,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngine/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2021,7 +2021,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2058,7 +2058,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2151,7 +2151,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngine/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2194,7 +2194,7 @@
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3YE4W86L3G;
ENABLE_BITCODE = NO;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2292,7 +2292,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = KeymanEngineTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2333,7 +2333,7 @@
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = "KeymanEngine/resources/Keyman.bundle/Contents/Resources/KeymanEngine-Info.plist";
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2449,7 +2449,7 @@
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3YE4W86L3G;
ENABLE_BITCODE = NO;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2482,7 +2482,7 @@
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 3YE4W86L3G;
ENABLE_BITCODE = NO;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
index a8b4f8624b..b69c7f424d 100644
--- a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
+++ b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
@@ -1131,7 +1131,7 @@
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
INFOPLIST_FILE = Keyman/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1166,7 +1166,7 @@
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
INFOPLIST_FILE = Keyman/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1319,7 +1319,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
INFOPLIST_FILE = SWKeyboard/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1362,7 +1362,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
INFOPLIST_FILE = SWKeyboard/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1456,7 +1456,7 @@
GCC_PREFIX_HEADER = "Keyman/Keyman-Prefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
INFOPLIST_FILE = Keyman/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -1500,7 +1500,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
INFOPLIST_FILE = SWKeyboard/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 12.2;
+ IPHONEOS_DEPLOYMENT_TARGET = 13;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
From b2598e76ba283cac1f36c0440d0f8a44c68e9b5f Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Tue, 11 Feb 2025 15:51:26 +0700
Subject: [PATCH 13/76] chore(web): fix coverage-reporting artifact
Fixes: #13195
---
web/.c8rc.json | 12 ++++++++-
web/build.sh | 26 ++++++++++++++-----
web/common.inc.sh | 11 --------
web/src/app/browser/.c8rc.json | 10 +++++++
web/src/engine/attachment/.c8rc.json | 2 +-
web/src/engine/common/web-utils/.c8rc.json | 2 +-
web/src/engine/events/.c8rc.json | 2 +-
web/src/engine/interfaces/.c8rc.json | 2 +-
web/src/engine/keyboard-storage/.c8rc.json | 2 +-
web/src/engine/keyboard/.c8rc.json | 2 +-
.../engine/osk/gesture-processor/.c8rc.json | 2 +-
.../predictive-text/templates/.c8rc.json | 2 +-
.../predictive-text/wordbreakers/.c8rc.json | 2 +-
.../worker-main/src/node/worker.ts | 4 +++
.../predictive-text/worker-thread/.c8rc.json | 2 +-
.../engine/js-processor/basic-engine.tests.js | 5 +++-
.../engine/js-processor/chirality.tests.js | 5 +++-
.../engine/js-processor/deadkeys.tests.js | 5 +++-
.../engine/unmatched_final_group.tests.js | 5 +++-
web/src/test/auto/headless/getKeymanRoot.mjs | 12 +++++++++
20 files changed, 83 insertions(+), 32 deletions(-)
create mode 100644 web/src/app/browser/.c8rc.json
create mode 100644 web/src/test/auto/headless/getKeymanRoot.mjs
diff --git a/web/.c8rc.json b/web/.c8rc.json
index f308b48487..070ef1b1d2 100644
--- a/web/.c8rc.json
+++ b/web/.c8rc.json
@@ -7,7 +7,17 @@
"reporter": ["text", "text-summary"],
"reports-dir": "build/coverage",
"src": [
- "**/src/",
+ "src/**",
"../common/**/src"
+ ],
+ "include": [
+ "src/**"
+ ],
+ "exclude": [
+ "src/test/**",
+ "**/src/tests/**",
+ "**/build/**",
+ "src/tools/**",
+ "../node_modules/*"
]
}
diff --git a/web/build.sh b/web/build.sh
index 5dd30f338f..0dd9b5ef53 100755
--- a/web/build.sh
+++ b/web/build.sh
@@ -141,12 +141,26 @@ test_action() {
coverage_action() {
builder_echo "Creating coverage report..."
- cd "$KEYMAN_ROOT"
- mkdir -p web/build/coverage/tmp
- find . -type f -name coverage-\*.json -print0 | xargs -0 cp -t web/build/coverage/tmp
- c8 report --config web/.c8rc.json ---reporter html --clean=false --reports-dir=web/build/coverage
- rm -rf web/build/coverage/tmp
- cd web
+
+ # Always ensure our collation folder is clean, just in case something went wrong on a prior run.
+ rm -rf build/coverage/tmp
+
+ # Also clean out the sub-report directories + main index.html; don't keep old ones if we fail!
+ rm -rf build/coverage/app
+ rm -rf build/coverage/engine
+ rm -f build/coverage/index.html
+
+ # Collate the results into a single folder
+ mkdir build/coverage/tmp
+ find . -type f -name coverage-\*.json -print0 | xargs -0 cp -t build/coverage/tmp
+
+ # Note for maintainers: the .c8rc.json config needs to be written from the perspective of
+ # the working directory - including its `include` and `exclude` entries. All ignores and
+ # such must be redefined in whatever that top-level .c8rc.json is.
+ c8 report --reporter html
+
+ # No need to keep the collation folder around afterward; we can always redo that work.
+ rm -rf build/coverage/tmp
}
builder_run_child_actions build:engine/dom-utils
diff --git a/web/common.inc.sh b/web/common.inc.sh
index 429c9a9a8d..05c924d6ec 100644
--- a/web/common.inc.sh
+++ b/web/common.inc.sh
@@ -108,22 +108,11 @@ function test-headless() {
TEST_CD_REQD=true
fi
- if [ $TEST_CD_REQD ]; then
- # The mocha config needed to live-compile TS-based tests only applies
- # if the command is started within the appropriate subfolder.
- pushd "${TEST_BASE}" > /dev/null
- TEST_BASE=
- fi
-
if [[ -e .c8rc.json ]]; then
c8 mocha --recursive "${TEST_BASE}${TEST_FOLDER}" $TEST_OPTS
else
mocha --recursive "${TEST_BASE}${TEST_FOLDER}" $TEST_OPTS
fi
-
- if [ $TEST_CD_REQD ]; then
- popd > /dev/null
- fi
}
# Runs all headless tests (written in typescript) corresponding to the
diff --git a/web/src/app/browser/.c8rc.json b/web/src/app/browser/.c8rc.json
new file mode 100644
index 0000000000..3e9bef1df0
--- /dev/null
+++ b/web/src/app/browser/.c8rc.json
@@ -0,0 +1,10 @@
+{
+ "check-coverage": false,
+ "clean": true,
+ "exclude-after-remap": true,
+ "reporter": ["text", "text-summary"],
+ "reports-dir": "../../../build/coverage/raw/app/browser",
+ "src": [
+ "src/"
+ ]
+}
\ No newline at end of file
diff --git a/web/src/engine/attachment/.c8rc.json b/web/src/engine/attachment/.c8rc.json
index c789716cd5..eea63036d2 100644
--- a/web/src/engine/attachment/.c8rc.json
+++ b/web/src/engine/attachment/.c8rc.json
@@ -3,7 +3,7 @@
"clean": true,
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../build/coverage/raw/engine/attachment",
"src": [
"src/"
]
diff --git a/web/src/engine/common/web-utils/.c8rc.json b/web/src/engine/common/web-utils/.c8rc.json
index 16a4e93b9c..4c652ff892 100644
--- a/web/src/engine/common/web-utils/.c8rc.json
+++ b/web/src/engine/common/web-utils/.c8rc.json
@@ -10,7 +10,7 @@
],
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../build/coverage/raw/raw/engine/common/web-utils",
"src": [
"src/"
]
diff --git a/web/src/engine/events/.c8rc.json b/web/src/engine/events/.c8rc.json
index c789716cd5..65dd2858d4 100644
--- a/web/src/engine/events/.c8rc.json
+++ b/web/src/engine/events/.c8rc.json
@@ -3,7 +3,7 @@
"clean": true,
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../build/coverage/raw/engine/events",
"src": [
"src/"
]
diff --git a/web/src/engine/interfaces/.c8rc.json b/web/src/engine/interfaces/.c8rc.json
index c42b096f68..ebcb215dba 100644
--- a/web/src/engine/interfaces/.c8rc.json
+++ b/web/src/engine/interfaces/.c8rc.json
@@ -3,7 +3,7 @@
"clean": true,
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "../../../build/engine/interfaces/coverage",
+ "reports-dir": "../../../build/coverage/raw/engine/interfaces",
"src": [
"src/"
]
diff --git a/web/src/engine/keyboard-storage/.c8rc.json b/web/src/engine/keyboard-storage/.c8rc.json
index c789716cd5..396d0ddcc5 100644
--- a/web/src/engine/keyboard-storage/.c8rc.json
+++ b/web/src/engine/keyboard-storage/.c8rc.json
@@ -3,7 +3,7 @@
"clean": true,
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../build/coverage/raw/engine/keyboard-storage",
"src": [
"src/"
]
diff --git a/web/src/engine/keyboard/.c8rc.json b/web/src/engine/keyboard/.c8rc.json
index ceca963e15..b6bd4a818f 100644
--- a/web/src/engine/keyboard/.c8rc.json
+++ b/web/src/engine/keyboard/.c8rc.json
@@ -8,7 +8,7 @@
],
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../build/coverage/raw/engine/keyboard",
"src": [
"src/"
]
diff --git a/web/src/engine/osk/gesture-processor/.c8rc.json b/web/src/engine/osk/gesture-processor/.c8rc.json
index 719cd11dae..24afdfdddf 100644
--- a/web/src/engine/osk/gesture-processor/.c8rc.json
+++ b/web/src/engine/osk/gesture-processor/.c8rc.json
@@ -11,7 +11,7 @@
"src/engine/headless/*"
],
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../../build/coverage/raw/engine/osk/gesture-processor",
"src": [
"src/engine/headless/*"
]
diff --git a/web/src/engine/predictive-text/templates/.c8rc.json b/web/src/engine/predictive-text/templates/.c8rc.json
index d107a1cacb..b938594fb2 100644
--- a/web/src/engine/predictive-text/templates/.c8rc.json
+++ b/web/src/engine/predictive-text/templates/.c8rc.json
@@ -7,7 +7,7 @@
],
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../../build/coverage/raw/engine/predictive-text/templates",
"src": [
"src/"
]
diff --git a/web/src/engine/predictive-text/wordbreakers/.c8rc.json b/web/src/engine/predictive-text/wordbreakers/.c8rc.json
index 6a0e2589da..c2ea18e238 100644
--- a/web/src/engine/predictive-text/wordbreakers/.c8rc.json
+++ b/web/src/engine/predictive-text/wordbreakers/.c8rc.json
@@ -7,7 +7,7 @@
],
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../../build/coverage/raw/engine/predictive-text/wordbreakers",
"src": [
"src/"
]
diff --git a/web/src/engine/predictive-text/worker-main/src/node/worker.ts b/web/src/engine/predictive-text/worker-main/src/node/worker.ts
index 5ad04e55d0..aafb2c4869 100644
--- a/web/src/engine/predictive-text/worker-main/src/node/worker.ts
+++ b/web/src/engine/predictive-text/worker-main/src/node/worker.ts
@@ -6,6 +6,10 @@ import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-
export default class Worker {
static constructInstance(): Worker {
let scriptStr = unwrap(LMLayerWorkerCode);
+ const srcMapUrlIndex = scriptStr.indexOf("//# sourceMappingURL")
+ if(srcMapUrlIndex > 0) {
+ scriptStr = scriptStr.substring(0, srcMapUrlIndex);
+ }
scriptStr += '\n' + LMLayerWorkerSourcemapComment;
let worker = new MappedWorker(scriptStr);
diff --git a/web/src/engine/predictive-text/worker-thread/.c8rc.json b/web/src/engine/predictive-text/worker-thread/.c8rc.json
index b66bb5b106..f30fb6465e 100644
--- a/web/src/engine/predictive-text/worker-thread/.c8rc.json
+++ b/web/src/engine/predictive-text/worker-thread/.c8rc.json
@@ -7,7 +7,7 @@
],
"exclude-after-remap": true,
"reporter": ["text", "text-summary"],
- "reports-dir": "build/coverage",
+ "reports-dir": "../../../../build/coverage/raw/engine/predictive-text/worker-thread",
"src": [
"src/main"
]
diff --git a/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js b/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
index 68eb5136e3..df63ec3106 100644
--- a/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
@@ -9,6 +9,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
+import { getKeymanRoot } from '../../getKeymanRoot.mjs';
+const KEYMAN_ROOT = getKeymanRoot();
+
describe('Engine - Basic Simulation', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/basic_lao_simulation.json'));
// Common test suite setup.
@@ -25,7 +28,7 @@ describe('Engine - Basic Simulation', function() {
before(async function() {
// -- START: Standard Recorder-based unit test loading boilerplate --
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
- let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
+ let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
keyboardWithHarness = keyboardLoader.harness;
keyboardWithHarness.activeKeyboard = keyboard;
diff --git a/web/src/test/auto/headless/engine/js-processor/chirality.tests.js b/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
index c2220b7f5d..ea068e014d 100644
--- a/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
@@ -10,6 +10,9 @@ import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
import { ModifierKeyConstants } from '@keymanapp/common-types';
+import { getKeymanRoot } from '../../getKeymanRoot.mjs';
+const KEYMAN_ROOT = getKeymanRoot();
+
describe('Engine - Chirality', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/chirality.json'));
// Common test suite setup.
@@ -26,7 +29,7 @@ describe('Engine - Chirality', function() {
before(async function() {
// -- START: Standard Recorder-based unit test loading boilerplate --
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
- let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
+ let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
keyboardWithHarness = keyboardLoader.harness;
keyboardWithHarness.activeKeyboard = keyboard;
diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js b/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
index fd5d1457d3..ae67121dfd 100644
--- a/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
@@ -9,6 +9,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
+import { getKeymanRoot } from '../../getKeymanRoot.mjs';
+const KEYMAN_ROOT = getKeymanRoot();
+
describe('Engine - Deadkeys', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/deadkeys.json'));
// Common test suite setup.
@@ -25,7 +28,7 @@ describe('Engine - Deadkeys', function() {
before(async function() {
// -- START: Standard Recorder-based unit test loading boilerplate --
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
- let keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
+ let keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
keyboardWithHarness = keyboardLoader.harness;
keyboardWithHarness.activeKeyboard = keyboard;
diff --git a/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
index 8bfee041eb..5ceaf40cf6 100644
--- a/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
@@ -9,6 +9,9 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
+import { getKeymanRoot } from '../../../getKeymanRoot.mjs';
+const KEYMAN_ROOT = getKeymanRoot();
+
describe('Engine - Unmatched Final Groups', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/ghp_enter.json'));
// Common test suite setup.
@@ -24,7 +27,7 @@ describe('Engine - Unmatched Final Groups', function() {
before(async function() {
// -- START: Standard Recorder-based unit test loading boilerplate --
let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal));
- const keyboard = await keyboardLoader.loadKeyboardFromPath('../../../../../common/test/' + testSuite.keyboard.filename);
+ const keyboard = await keyboardLoader.loadKeyboardFromPath(KEYMAN_ROOT + '/common/test/' + testSuite.keyboard.filename);
keyboardWithHarness = keyboardLoader.harness;
keyboardWithHarness.activeKeyboard = keyboard;
diff --git a/web/src/test/auto/headless/getKeymanRoot.mjs b/web/src/test/auto/headless/getKeymanRoot.mjs
new file mode 100644
index 0000000000..ce17cecd0f
--- /dev/null
+++ b/web/src/test/auto/headless/getKeymanRoot.mjs
@@ -0,0 +1,12 @@
+import { createRequire } from 'module';
+import * as path from 'path'
+const require = createRequire(import.meta.url);
+
+export function getKeymanRoot() {
+ let anchorPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
+ let root = path.dirname(anchorPath);
+ for(let i=0; i < 3; i++) {
+ root = path.dirname(root);
+ }
+ return root;
+}
\ No newline at end of file
From 39c1727b6fe523c6c1f07e6bb4296f9abad2c527 Mon Sep 17 00:00:00 2001
From: Joshua Horton
Date: Wed, 12 Feb 2025 15:55:23 +0700
Subject: [PATCH 14/76] change(web): use env.KEYMAN_ROOT instead of custom
function
---
.../engine/js-processor/basic-engine.tests.js | 5 +++--
.../headless/engine/js-processor/chirality.tests.js | 4 ++--
.../headless/engine/js-processor/deadkeys.tests.js | 4 ++--
.../engine/unmatched_final_group.tests.js | 4 ++--
web/src/test/auto/headless/getKeymanRoot.mjs | 12 ------------
5 files changed, 9 insertions(+), 20 deletions(-)
delete mode 100644 web/src/test/auto/headless/getKeymanRoot.mjs
diff --git a/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js b/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
index df63ec3106..09780444d9 100644
--- a/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/basic-engine.tests.js
@@ -1,6 +1,7 @@
import { assert } from 'chai';
import fs from 'fs';
+
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
@@ -9,8 +10,8 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
-import { getKeymanRoot } from '../../getKeymanRoot.mjs';
-const KEYMAN_ROOT = getKeymanRoot();
+import { env } from 'node:process';
+const KEYMAN_ROOT = env.KEYMAN_ROOT;
describe('Engine - Basic Simulation', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/basic_lao_simulation.json'));
diff --git a/web/src/test/auto/headless/engine/js-processor/chirality.tests.js b/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
index ea068e014d..e8a3e30f9e 100644
--- a/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/chirality.tests.js
@@ -10,8 +10,8 @@ import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader'
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
import { ModifierKeyConstants } from '@keymanapp/common-types';
-import { getKeymanRoot } from '../../getKeymanRoot.mjs';
-const KEYMAN_ROOT = getKeymanRoot();
+import { env } from 'node:process';
+const KEYMAN_ROOT = env.KEYMAN_ROOT;
describe('Engine - Chirality', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/chirality.json'));
diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js b/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
index ae67121dfd..007a4ec9ec 100644
--- a/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/deadkeys.tests.js
@@ -9,8 +9,8 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
-import { getKeymanRoot } from '../../getKeymanRoot.mjs';
-const KEYMAN_ROOT = getKeymanRoot();
+import { env } from 'node:process';
+const KEYMAN_ROOT = env.KEYMAN_ROOT;
describe('Engine - Deadkeys', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/deadkeys.json'));
diff --git a/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
index 5ceaf40cf6..fd2536ec22 100644
--- a/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
+++ b/web/src/test/auto/headless/engine/js-processor/engine/unmatched_final_group.tests.js
@@ -9,8 +9,8 @@ import { KeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from 'keyman/engine/keyboard/node-keyboard-loader';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
-import { getKeymanRoot } from '../../../getKeymanRoot.mjs';
-const KEYMAN_ROOT = getKeymanRoot();
+import { env } from 'node:process';
+const KEYMAN_ROOT = env.KEYMAN_ROOT;
describe('Engine - Unmatched Final Groups', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/ghp_enter.json'));
diff --git a/web/src/test/auto/headless/getKeymanRoot.mjs b/web/src/test/auto/headless/getKeymanRoot.mjs
deleted file mode 100644
index ce17cecd0f..0000000000
--- a/web/src/test/auto/headless/getKeymanRoot.mjs
+++ /dev/null
@@ -1,12 +0,0 @@
-import { createRequire } from 'module';
-import * as path from 'path'
-const require = createRequire(import.meta.url);
-
-export function getKeymanRoot() {
- let anchorPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
- let root = path.dirname(anchorPath);
- for(let i=0; i < 3; i++) {
- root = path.dirname(root);
- }
- return root;
-}
\ No newline at end of file
From ef824dcfa6dbbe13c18d6fcd7bb4420708eca0a4 Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Wed, 12 Feb 2025 11:32:37 +0100
Subject: [PATCH 15/76] chore(linux): update minimum version
---
docs/build/linux-ubuntu.md | 2 +-
docs/minimum-versions.md | 2 +-
linux/docs/help/common/index.md | 4 ++--
resources/build/minimum-versions.inc.sh | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/build/linux-ubuntu.md b/docs/build/linux-ubuntu.md
index 0f531acb59..9c0b195474 100644
--- a/docs/build/linux-ubuntu.md
+++ b/docs/build/linux-ubuntu.md
@@ -23,7 +23,7 @@ The following projects **cannot** be built on Linux:
### System Requirements
-- Minimum Ubuntu version: Ubuntu 20.04
+- Minimum Ubuntu version: Ubuntu 22.04
Other Linux distributions will also work if appropriate dependencies are installed.
diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md
index beb542e408..4f67d5a626 100644
--- a/docs/minimum-versions.md
+++ b/docs/minimum-versions.md
@@ -53,7 +53,7 @@ https://help.keyman.com/developer/engine/android/latest-version/
| KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME | 53.0 |
| KEYMAN_MIN_TARGET_VERSION_IOS | 12.2 |
| KEYMAN_MIN_TARGET_VERSION_MAC | 10.13 |
-| KEYMAN_MIN_TARGET_VERSION_UBUNTU | 20.04 |
+| KEYMAN_MIN_TARGET_VERSION_UBUNTU | 22.04 |
| KEYMAN_MIN_TARGET_VERSION_WEB_CHROME | 95.0 |
| KEYMAN_MIN_TARGET_VERSION_WEB_FIREFOX | 79.0 |
| KEYMAN_MIN_TARGET_VERSION_WEB_OPERA | 47.0 |
diff --git a/linux/docs/help/common/index.md b/linux/docs/help/common/index.md
index b2d606b38d..a7f9e786bf 100644
--- a/linux/docs/help/common/index.md
+++ b/linux/docs/help/common/index.md
@@ -59,8 +59,8 @@ no on-screen keyboard for Wayland that works with Keyman.
**A.** Keyman runs on Debian, Ubuntu, Wasta Linux and can be compiled to run
from source in most distributions.
-**Note:** Ubuntu versions before Ubuntu 20.04 LTS are no longer supported with
-Keyman 17. If you are still running an older version and require Keyman you'll
+**Note:** Ubuntu versions before Ubuntu 22.04 LTS are no longer supported with
+Keyman 18. If you are still running an older version and require Keyman you'll
have to install an older Keyman version.
## Q. Will my existing Windows Keyman keyboard work with Keyman for Linux?
diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh
index f57ebff419..2e1a2914b1 100644
--- a/resources/build/minimum-versions.inc.sh
+++ b/resources/build/minimum-versions.inc.sh
@@ -14,7 +14,7 @@ KEYMAN_MIN_TARGET_VERSION_ANDROID=5.0 # Lollipop
KEYMAN_MIN_TARGET_VERSION_IOS=12.2 # iOS 12.2
KEYMAN_MIN_TARGET_VERSION_WINDOWS=10 # Windows 10
KEYMAN_MIN_TARGET_VERSION_MAC=10.13 # MacOS 10.13 (High Sierra)
-KEYMAN_MIN_TARGET_VERSION_UBUNTU=20.04 # Ubuntu 20.04 Focal
+KEYMAN_MIN_TARGET_VERSION_UBUNTU=22.04 # Ubuntu 22.04 Jammy
KEYMAN_MIN_TARGET_VERSION_ANDROID_CHROME=53.0 # min version of Chrome for Keyman for Android
# Target web browsers for KeymanWeb -- we do not have polyfills for
From 53bb152bef5497e7029b303a1141963a4533e17a Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Wed, 12 Feb 2025 11:47:21 +0100
Subject: [PATCH 16/76] docs(linux): update `welcome.md`
---
linux/docs/help/about/welcome.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/linux/docs/help/about/welcome.md b/linux/docs/help/about/welcome.md
index eacc8c449c..dfd410b46e 100644
--- a/linux/docs/help/about/welcome.md
+++ b/linux/docs/help/about/welcome.md
@@ -6,6 +6,6 @@ Thank you for installing Keyman for Linux. Whether you're a new or returning use
## What is Keyman for Linux?
-Keyman for Linux makes it possible to type in over 1,000 languages in any Linux application.
+Keyman for Linux makes it possible to type in over 2,000 languages in any Linux application.
[Click here](../start/) to get started.
From f1b32a1439c4943b757c75d2d456cde3523e898a Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Wed, 12 Feb 2025 11:58:45 +0100
Subject: [PATCH 17/76] chore(linux): additional code cleanup after Focal
removal
---
linux/debian/control | 4 +--
linux/ibus-keyman/src/keymanutil.c | 5 ++--
linux/ibus-keyman/src/test/keymanutil.tests.c | 30 +++++--------------
.../keyman_config/downloadkeyboard.py | 7 +----
.../keyman_config/install_window.py | 7 +----
.../keyman_config/keyboard_options_view.py | 7 +----
linux/keyman-config/keyman_config/welcome.py | 6 +---
7 files changed, 15 insertions(+), 51 deletions(-)
diff --git a/linux/debian/control b/linux/debian/control
index fbe7066f5c..e12da18aa3 100644
--- a/linux/debian/control
+++ b/linux/debian/control
@@ -10,7 +10,7 @@ Build-Depends:
debhelper-compat (= 12),
dh-python,
gawk,
- gir1.2-webkit2-4.1 | gir1.2-webkit2-4.0,
+ gir1.2-webkit2-4.1,
ibus,
libevdev-dev,
libgtk-3-dev,
@@ -88,7 +88,7 @@ Architecture: all
Depends:
dbus-x11,
dconf-cli,
- gir1.2-webkit2-4.1 | gir1.2-webkit2-4.0,
+ gir1.2-webkit2-4.1,
keyman-engine,
python3-bs4,
python3-fonttools,
diff --git a/linux/ibus-keyman/src/keymanutil.c b/linux/ibus-keyman/src/keymanutil.c
index 9f48f0ffce..4bb0ac4078 100644
--- a/linux/ibus-keyman/src/keymanutil.c
+++ b/linux/ibus-keyman/src/keymanutil.c
@@ -649,9 +649,8 @@ _ptr_array_new_from_array(
}
// `g_ptr_array_new_from_null_terminated_array` is only available in GLib 2.76, but we're still
-// stuck to 2.64 (Ubuntu 20.04 Focal) and 2.72 (Ubuntu 22.04 Jammy). Therefore we
-// copy the implementation here (slightly simplified). Once we're past 2.76 we can use the GLib method
-// directly.
+// stuck to 2.72 (Ubuntu 22.04 Jammy). Therefore we copy the implementation here (slightly
+// simplified). Once we're past 2.76 we can use the GLib method directly.
GPtrArray *
_g_ptr_array_new_from_null_terminated_array(
gpointer *data,
diff --git a/linux/ibus-keyman/src/test/keymanutil.tests.c b/linux/ibus-keyman/src/test/keymanutil.tests.c
index 13636857d8..56965a41ad 100644
--- a/linux/ibus-keyman/src/test/keymanutil.tests.c
+++ b/linux/ibus-keyman/src/test/keymanutil.tests.c
@@ -123,22 +123,6 @@ _free_tst_kb_data(add_keyboard_data* kb_data) {
G_DEFINE_AUTOPTR_CLEANUP_FUNC(add_keyboard_data, _free_tst_kb_data)
-// Newer glib versions have g_assert_cmpstrv which would allow to do
-// g_assert_cmpstrv(result, keyboards);
-// but unfortunately Ubuntu 20.04 Focal doesn't have that, so we roll
-// our own
-void
-_kmn_assert_cmpstrv(gchar** result, gchar** expected) {
- g_assert_nonnull(result);
- g_assert_nonnull(expected);
- int i = 0;
- for (; result[i] && expected[i]; i++) {
- g_assert_cmpstr(result[i], ==, expected[i]);
- }
- g_assert_null(result[i]);
- g_assert_null(expected[i]);
-}
-
//----------------------------------------------------------------------------------------------
void
test_keyman_put_keyboard_options_todconf__invalid() {
@@ -526,7 +510,7 @@ test_keyman_set_custom_keyboards__new_key() {
// Verify
g_auto(GStrv) result = _get_tst_kbds_key();
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, keyboards);
+ g_assert_cmpstrv(result, keyboards);
// Cleanup
_delete_tst_kbds_key();
@@ -546,7 +530,7 @@ test_keyman_set_custom_keyboards__overwrite_key() {
// Verify
g_auto(GStrv) result = _get_tst_kbds_key();
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, keyboards);
+ g_assert_cmpstrv(result, keyboards);
// Cleanup
_delete_tst_kbds_key();
@@ -565,7 +549,7 @@ test_keyman_set_custom_keyboards__delete_key_NULL() {
g_auto(GStrv) result = _get_tst_kbds_key();
gchar* expected[] = {NULL};
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, expected);
+ g_assert_cmpstrv(result, expected);
// Cleanup
_delete_tst_kbds_key();
@@ -585,7 +569,7 @@ test_keyman_set_custom_keyboards__delete_key_empty_array() {
// Verify
g_auto(GStrv) result = _get_tst_kbds_key();
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, keyboards);
+ g_assert_cmpstrv(result, keyboards);
// Cleanup
_delete_tst_kbds_key();
@@ -604,7 +588,7 @@ test_keyman_set_custom_keyboards__invalid_values() {
g_auto(GStrv) result = _get_tst_kbds_key();
g_assert_nonnull(result);
gchar* expected[] = {"fr:/tmp/test/test.kmx", NULL};
- _kmn_assert_cmpstrv(result, expected);
+ g_assert_cmpstrv(result, expected);
// Cleanup
_delete_tst_kbds_key();
@@ -622,7 +606,7 @@ test_keyman_get_custom_keyboards__value() {
// Verify
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, keyboards);
+ g_assert_cmpstrv(result, keyboards);
// Cleanup
_delete_tst_kbds_key();
@@ -640,7 +624,7 @@ test_keyman_get_custom_keyboards__invalid_values() {
// Verify
gchar* expected[] = {"fr:/tmp/test/test.kmx", NULL};
g_assert_nonnull(result);
- _kmn_assert_cmpstrv(result, expected);
+ g_assert_cmpstrv(result, expected);
// Cleanup
_delete_tst_kbds_key();
diff --git a/linux/keyman-config/keyman_config/downloadkeyboard.py b/linux/keyman-config/keyman_config/downloadkeyboard.py
index 42ff74308d..ca3b091f92 100755
--- a/linux/keyman-config/keyman_config/downloadkeyboard.py
+++ b/linux/keyman-config/keyman_config/downloadkeyboard.py
@@ -7,12 +7,7 @@ import urllib.parse
import gi
gi.require_version('Gtk', '3.0')
-try:
- gi.require_version('WebKit2', '4.1')
-except ValueError:
- # TODO: Remove once we drop support for Ubuntu 20.04 Focal
- gi.require_version('WebKit2', '4.0')
-
+gi.require_version('WebKit2', '4.1')
from gi.repository import Gtk, WebKit2
from keyman_config import KeymanComUrl, _, __releaseversion__, __tier__
diff --git a/linux/keyman-config/keyman_config/install_window.py b/linux/keyman-config/keyman_config/install_window.py
index dda242c7e1..1342dd44f6 100755
--- a/linux/keyman-config/keyman_config/install_window.py
+++ b/linux/keyman-config/keyman_config/install_window.py
@@ -15,12 +15,7 @@ import packaging.version
import gi
gi.require_version('Gtk', '3.0')
-try:
- gi.require_version('WebKit2', '4.1')
-except ValueError:
- # TODO: Remove once we drop support for Ubuntu 20.04 Focal
- gi.require_version('WebKit2', '4.0')
-
+gi.require_version('WebKit2', '4.1')
from gi.repository import Gtk, WebKit2
from keyman_config import _, secure_lookup
diff --git a/linux/keyman-config/keyman_config/keyboard_options_view.py b/linux/keyman-config/keyman_config/keyboard_options_view.py
index 795386d7bf..38499a558a 100644
--- a/linux/keyman-config/keyman_config/keyboard_options_view.py
+++ b/linux/keyman-config/keyman_config/keyboard_options_view.py
@@ -8,12 +8,7 @@ from urllib.parse import parse_qsl, urlencode
import gi
gi.require_version('Gtk', '3.0')
-try:
- gi.require_version('WebKit2', '4.1')
-except ValueError:
- # TODO: Remove once we drop support for Ubuntu 20.04 Focal
- gi.require_version('WebKit2', '4.0')
-
+gi.require_version('WebKit2', '4.1')
from gi.repository import Gtk, WebKit2
from keyman_config import _
diff --git a/linux/keyman-config/keyman_config/welcome.py b/linux/keyman-config/keyman_config/welcome.py
index af001ea464..a14d747c6a 100644
--- a/linux/keyman-config/keyman_config/welcome.py
+++ b/linux/keyman-config/keyman_config/welcome.py
@@ -7,11 +7,7 @@ import webbrowser
import gi
gi.require_version('Gtk', '3.0')
-try:
- gi.require_version('WebKit2', '4.1')
-except ValueError:
- # TODO: Remove once we drop support for Ubuntu 20.04 Focal
- gi.require_version('WebKit2', '4.0')
+gi.require_version('WebKit2', '4.1')
from gi.repository import Gtk, WebKit2
from keyman_config import _
From bbc3142c3c05a7c8c4d0a35f6f74303a2e4fc679 Mon Sep 17 00:00:00 2001
From: Eberhard Beilharz
Date: Wed, 12 Feb 2025 18:01:21 +0100
Subject: [PATCH 18/76] docs(linux): update what's new for 18.0
Fixes: #13183
---
linux/docs/help/about/whatsnew.md | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/linux/docs/help/about/whatsnew.md b/linux/docs/help/about/whatsnew.md
index 4a4d5a30e1..35edaa60ab 100644
--- a/linux/docs/help/about/whatsnew.md
+++ b/linux/docs/help/about/whatsnew.md
@@ -2,4 +2,12 @@
title: What's New in Keyman 18.0 for Linux
---
-Here are some of the new features we have added to Keyman for Linux 18.0:
+Here are some significant changes to Keyman for Linux 18.0:
+
+- Minimum supported Ubuntu version is now 22.04 Jammy LTS.
+- Other supported versions are Ubuntu 24.04 Noble LTS and
+ Ubuntu 24.10 Oracular. Users that are running an older version of
+ Ubuntu will have to continue using an older Keyman version.
+- Keyman no longer requires a patched version of ibus as Keyman now uses
+ a system service to manage keystroke order (#11535).
+- Added support for simulation of AltGr (right Alt) with Ctrl+Alt (#11852)
From 67d26a9d3f9074c11cb4a6b5a504d346c89b9e79 Mon Sep 17 00:00:00 2001
From: Keyman Build Agent
Date: Wed, 12 Feb 2025 13:01:06 -0500
Subject: [PATCH 19/76] auto: increment master version to 19.0.3
---
HISTORY.md | 4 ++++
VERSION.md | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/HISTORY.md b/HISTORY.md
index e907cc12dd..544a275dea 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,9 @@
# Keyman Version History
+## 19.0.2 alpha 2025-02-12
+
+* chore(linux): remove support of Ubuntu 20.04 Focal (#13203)
+
## 19.0.1 alpha 2025-02-11
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
diff --git a/VERSION.md b/VERSION.md
index fc1a009252..dd7d796646 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-19.0.2
\ No newline at end of file
+19.0.3
\ No newline at end of file
From d4f96d66b74359426021b96ad546617ba6f32eb5 Mon Sep 17 00:00:00 2001
From: Darcy Wong
Date: Wed, 5 Feb 2025 10:17:25 +0700
Subject: [PATCH 20/76] fix(android): Check WebView is installed and enabled
Fixes: #11560
Fixes: KEYMAN-ANDROID-5E9
Fixes: KEYMAN-ANDROID-6MA
---
.../com/tavultesoft/kmapro/MainActivity.java | 102 ++++++++++++------
.../src/main/res/layout/activity_main.xml | 4 +-
...ut.xml => check_chrome_webview_layout.xml} | 8 +-
.../java/com/keyman/engine/KMKeyboard.java | 19 +++-
.../java/com/keyman/engine/KMManager.java | 37 +++++--
.../java/com/keyman/engine/KMTextView.java | 6 +-
.../com/keyman/engine/util/WebViewUtils.java | 39 ++++++-
.../KMEA/app/src/main/res/values/strings.xml | 39 +++++--
8 files changed, 190 insertions(+), 64 deletions(-)
rename android/KMAPro/kMAPro/src/main/res/layout/{update_chrome_layout.xml => check_chrome_webview_layout.xml} (86%)
diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index 908d2f4538..98e26959b7 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -43,7 +43,9 @@ import com.keyman.android.DownloadIntentService;
import com.keyman.engine.util.KMLog;
import com.keyman.engine.util.KMPLink;
import com.keyman.engine.util.KMString;
+import com.keyman.engine.util.WebViewUtils;
import com.keyman.engine.util.WebViewUtils.EngineWebViewVersionStatus;
+import com.keyman.engine.util.WebViewUtils.SystemWebViewStatus;
import android.Manifest;
import android.app.ProgressDialog;
@@ -153,6 +155,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.setDebugMode(true);
}
+ // Verify WebView installed and enabled before attempting to initialize KMManager
KMManager.initialize(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_INAPP);
KMManager.executeResourceUpdate(this);
@@ -181,8 +184,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
textView.setTextSize((float) textSize);
textView.setSelection(textView.getText().length());
- // Use in-app keyboard WebView to check the Chrome version
- checkChromeVersion(KMManager.getKMKeyboard(KeyboardType.KEYBOARD_TYPE_INAPP));
+ // Check WebView enabled and Chrome version
+ checkWebViewAndChromeVersion(context);
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
checkGetStarted();
}
@@ -770,39 +773,70 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
KMManager.setHapticFeedback(mayHaveHapticFeedback);
}
- private void checkChromeVersion(WebView webView) {
- if (KMManager.getEngineWebViewVersionStatus() != EngineWebViewVersionStatus.FULL) {
- LinearLayout updateChromeLayout = (LinearLayout) findViewById(R.id.updateChromeLayout);
- updateChromeLayout.setVisibility(View.VISIBLE);
-
- Button updateChromeButton = (Button)findViewById(R.id.updateChromeButton);
- updateChromeButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // Launch PlayStore to update Chrome
- try {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
- }
- }
- } catch (android.content.ActivityNotFoundException e) {
- // Link to Chrome if user is not signed in to Play Store
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
- if (intent.resolveActivity(getPackageManager()) != null) {
- startActivity(intent);
- } else {
- Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
- }
- }
- }
- });
+ /**
+ * Check that WebView is enabled and Chrome version high enough for Keyman.
+ * If they're not, update the view and button to install/enable the missing app.
+ * @param context
+ */
+ private void checkWebViewAndChromeVersion(Context context) {
+ final SystemWebViewStatus webViewStatus = WebViewUtils.getSystemWebViewStatus(context);
+ final boolean updateChrome = KMManager.getEngineWebViewVersionStatus() != EngineWebViewVersionStatus.FULL;
+ if (webViewStatus == SystemWebViewStatus.FULL && !updateChrome) {
+ return;
}
+
+ final TextView textView = (TextView)findViewById((R.id.kmWebViewChromeTextView));
+ String buttonLabel = getString(R.string.button_update_chrome);
+ if (webViewStatus == SystemWebViewStatus.NOT_INSTALLED) {
+ textView.setText(getString(R.string.body_install_webview));
+ buttonLabel = getString(R.string.label_install_webview);
+ } else if (webViewStatus == SystemWebViewStatus.DISABLED) {
+ textView.setText(R.string.body_enable_webview);
+ buttonLabel = getString(R.string.label_enable_webview);
+ }
+
+ LinearLayout webViewChromeLayout = (LinearLayout) findViewById(R.id.checkWebViewChromeLayout);
+ webViewChromeLayout.setVisibility(View.VISIBLE);
+
+ Button button = (Button)findViewById(R.id.webViewChromeButton);
+ button.setText(buttonLabel);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Initialize the intent to something (alternate Play Store link to install/update Chrome)
+ Intent intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
+ if (webViewStatus == SystemWebViewStatus.NOT_INSTALLED) {
+ // Play Store link to install WebView
+ intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.webview"));
+ } else if (webViewStatus == SystemWebViewStatus.DISABLED) {
+ // Application settings to enable WebView
+ intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS",
+ Uri.parse("package:com.google.android.webview"));
+ } else if (updateChrome) {
+ // Primary Play Store link to install/update Chrome
+ intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.android.chrome"));
+ }
+
+ // Launch intent
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent);
+ } else {
+ // (alternate Play Store link to install/update Chrome)
+ intent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://play.google.com/store/apps/details?id=com.android.chrome"));
+
+ intent.resolveActivity(packageManager);
+ startActivity(intent);
+ }
+ } catch (android.content.ActivityNotFoundException e) {
+ Toast.makeText(getApplicationContext(), getString(R.string.unable_to_open_browser), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
}
private Uri requestPermissionIntentUri;
diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
index 87c7fe16f9..c2911272d1 100644
--- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
+++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml
@@ -11,7 +11,7 @@
-
-
+