diff --git a/HISTORY.md b/HISTORY.md
index 759b782f9a..dbace5ac2e 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,24 @@
# Keyman Version History
+## 18.0.69 alpha 2024-07-05
+
+* fix(core): allow to successfully build on Ubuntu 24.04 (#11926)
+* chore(windows): correct output file for 64-bit build of keyman32 in build.sh (#11930)
+* chore(android,ios): Add Crowdin localization for Polytonic Greek (#11877)
+
+## 18.0.68 alpha 2024-07-04
+
+* refactor(windows): merge keyman64 build into keyman32 (#11906)
+* refactor(windows): remove wm_keyman_keydown and wm_keyman_keyup (#11920)
+
+## 18.0.67 alpha 2024-07-03
+
+* refactor(common/models): move TS priority-queue implementation to web-utils (#11867)
+
+## 18.0.66 alpha 2024-07-02
+
+* fix(developer): handle second parameter of index correctly in kmcmplib compiler (#11815)
+
## 18.0.65 alpha 2024-07-01
* fix(developer): prevent non-BMP characters in key part of rule (#11806)
diff --git a/VERSION.md b/VERSION.md
index fbc6447e6d..0427a9f66d 100644
--- a/VERSION.md
+++ b/VERSION.md
@@ -1 +1 @@
-18.0.66
\ No newline at end of file
+18.0.70
\ No newline at end of file
diff --git a/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml
new file mode 100644
index 0000000000..43e43eb4ca
--- /dev/null
+++ b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml
@@ -0,0 +1,164 @@
+
+
+
+
+ Μοιρασθῆτε
+
+ Φυλλομετρητής
+
+ Μέγεθος κειμένου
+
+ Περισσότερα
+
+ Διαγραφὴ κειμένου
+
+ Πληροφορίες
+
+ Ρυθμίσεις
+
+ Εγκατάσταση Ενημερώσεων
+
+ Ἔκδοση %1$s
+
+ Τὸ Keyman ἀπαιτεῖ ἔκδοση Chrome 57 ἢ νεώτερη.
+
+ Ἐνημερῶστε τὸ Chrome
+
+ Ἀρχίστε νὰ γράφετε ἐδῶ…
+
+
+
+ Μέγεθος κειμένου: %1$d
+
+ Μεγαλῶστε τὸ κείμενο
+
+ Μεγαλῶστε τὸ κείμενο
+
+ Ρύθμιση μεγέθους κειμένου
+
+ \nΤὸ κείμενο θὰ ἐκκαθαρισθεῖ πλήρως\n
+
+ Μικρύνετε τὸ κείμενο
+
+ Προσθέστε πληκτρολόγιο γιὰ τὴν γλῶσσα σας
+
+ Ὁρίστε τὸ Κῆμαν ὡς παν-συστημικὸ πληκτρολόγιο
+
+ Ὁρίστε τὸ Κῆμαν ὡς προεπιλεγμένο πληκτρολόγιο
+
+ Περισσότερες πληροφορίες
+
+ Κατὰ τὴν ἔναρξη νὰ προβάλλεται τὸ \"%1$s
+
+ Γιὰ νὰ ἐγκαταστήσετε πακέτα πληκτρολογίων, ἐπιτρέψτε στὸ Κῆμαν νὰ διαβάζει ἐξωτερικὸ χῶρο ἀποθήκευσης.
+
+ Ἀπερρίφθη αἴτημα χώρου ἀποθήκευσης. Πιθανὴ ἀποτυχία ἐγκατάστασης πακέτου πληκτρολογίου
+ Ἀπερρίφθη αἴτημα χώρου ἀποθηκεύσεως. Δοκιμάστε τὶς ρυθμίσεις Κῆμαν - Ἐγκατάσταση ἀπὸ τοπικὸ ἀρχεῖο
+
+ Ρυθμίσεις
+
+
+ - Ἐγκατεστημένες γλῶσσες (%1$d)
+ - Ἐγκατεστημένες γλῶσσες (%1$d)
+
+
+ Ἐγκαταστῆστε πληκτρολόγιο ἢ λεξικό
+
+ Γλῶσσα προβολῆς
+
+ Μεταβολὴ ὕψους πληκτρολογίου
+
+ Spacebar caption
+
+ Πληκτρολόγιο
+
+ Γλῶσσα
+
+ Γλῶσσα + Πληκτρολόγιο
+
+ Κενό
+
+ \'Ονομα πληκτρολογίου στὸ πλῆκτρο διαστήματος
+
+ \'Ονομα γλώσσας στὸ πλῆκτρο διαστήματος
+
+ \'Ονομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος
+
+ Καμμία λεζάντα στὸ πλῆκτρο διαστήματος
+
+ Δὸνηση κατὰ τὴν πληκτρολόγηση
+
+ Νὰ ἐμφανίζεται πάντα banner
+
+ Πρὸς ὑλοποίησιν
+
+ Ὅταν εἶναι off, ἐμφανίζεται μόνο ὅταν ἔχει ἐνεργοποιηθεῖ τὸ προγνωστικὸ κείμενο
+
+ Ἐπιτρέψτε τὴν ἀποστολὴ ἀναφορῶν κατάρρευσης μέσῳ δικτύου
+
+ Ὅταν εἶναι ΟΝ, θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης
+
+ Ὅταν εἶναι off, δὲν θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης
+
+ Ἐγκατάσταση ἀπὸ τὸ keyman.com
+
+ Ἐγκατάσταση ἀπὸ τοπικὸ ἀρχεῖο
+
+ Ἐγκατάσταση ἀπὸ ἄλλη συσκευή
+
+ Προσθέστε γλῶσσες σὲ ἐγκατεστημένο πληκτρολόγιο
+
+ (ἀπὸ πακέτο πληκτρολογίου)
+
+ Ἐπιλέξτε Πακέτο Πληκτρολογίου
+
+ Ἐπιλέξτε γλῶσσες γιὰ τὸ %1$s
+
+ Προσετέθη ἡ γλῶσσα %1$s στὸ %2$s
+
+ Ὅλες οἱ γλῶσσες ἔχουν ἤδη ἐγκατασταθεῖ
+
+ Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος
+
+ Περιστρέψτε τὴν συσκευὴ γιὰ λειτουργία πορτραίτου καὶ τοπίου
+
+ Ἐπαναφέρετε τὶς προεπιλεγμένες ρυθμίσεις
+
+ Ἀναζητῆστε ἢ πληκτρολογῆστε URL
+
+ Σελιδοδεῖκτες
+
+ Δὲν ὑπάρχουν σελιδοδεῖκτες
+
+ Προσθέστε σελιδοδείκτη
+
+ Τίτλος
+
+ URL
+
+ Τὸ πακέτο %1$s ἀπέτυχε νὰ ἐγκατασταθεῖ
+
+ Λήψη πακέτου πληκτρολογίου\n%1$s…
+
+ Ἀποτυχία ἐξαγωγῆς
+
+ Ἐγκαταστῆστε πληκτρολόγιο
+
+ Ἐγκαταστῆστε Λεξικό
+
+ Τὸ %1$s δὲν εἶναι ἔγκυρο ἀρχεῖο πακέτου Κῆμαν.\n%2$s\"
+
+ Τὸ πακέτο πληκτρολογίου δὲν ἔχει βελτιστοποιημένα πληκτρολόγια ἀφῆς πρὸς ἐγκατάστασιν
+
+ Δὲν ὑπάρχει νέο προγνωστικό κείμενο πρὸς ἐγκατάστασιν
+
+ Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνωστικό κείμενο πρὸς ἐγκατάστασιν
+
+ Τὸ πακέτο πληκτρολογίου δὲν ἔχει σχετικὲς μὲ αὐτὸ γλῶσσες πρὸς ἐγκατάστασιν
+
+ Ἄκυρα/ἐλλιπῆ μεταδεδομένα στὸ πακέτο
+
+ Τὸ πληκτρολόγιο ἀπαιτεῖ νεώτερη ἔκδοση τοῦ Κῆμαν
+
+ Ἀδυναμία ἐκκινήσεως φυλλομετρητῆ
+
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
index 27c462e95a..6a159523a2 100644
--- a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
+++ b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java
@@ -62,6 +62,7 @@ public class DisplayLanguages {
new DisplayLanguageType("nl-NL", "Nederlands (Dutch)"),
new DisplayLanguageType("ann", "Obolo"),
new DisplayLanguageType("pl-PL", "Polski (Polish)"),
+ new DisplayLanguageType("el", "Polytonic Greek"),
new DisplayLanguageType("pt-PT", "Português do Portugal"),
new DisplayLanguageType("ff-ZA", "Pulaar-Fulfulde"), // or Fulah
new DisplayLanguageType("ru-RU", "Pyccĸий (Russian)"),
diff --git a/android/KMEA/app/src/main/res/values-b+el/strings.xml b/android/KMEA/app/src/main/res/values-b+el/strings.xml
new file mode 100644
index 0000000000..871171d63f
--- /dev/null
+++ b/android/KMEA/app/src/main/res/values-b+el/strings.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ - Πληκτρολόγιο
+ - Πληκτρολόγια
+
+
+
+ - Ἄλλη μέθοδος εἰσαγωγῆς
+ - Ἄλλες Μέθοδοι Εἰσαγωγῆς
+
+
+ Προσθέστε νέο Πληκτρολόγιο
+
+ Ἐγκατεστημένες γλῶσσες
+
+ Ρυθμίσεις %1$s
+
+ Προσθέστε
+
+ Πίσω
+
+ Ἀκυρῶστε
+
+ Κλεῖστε
+
+ Κλεῖστε τὸ Κῆμαν
+
+ Προχωρῆστε
+
+ Ἑπόμενη Μέθοδος Εἰσαγωγῆς
+
+ Λήψη
+
+ Ἐγκαταστῆστε
+
+ Ἀργότερα
+
+ Ἑπόμενο
+
+ OK
+
+ Ἐνημερῶστε
+
+ Δὲν ὑπάρχει σύνδεση μὲ τὸ Διαδίκτυο
+
+ Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ τοῦ Κῆμαν!
+
+ Θέλετε νὰ διαγράψετε αὐτὸ τὸ πληκτρολόγιο;
+
+ Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ πληκτρολογίου;
+
+ Θά θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά;
+
+ Ἐνημερώσεις Πόρων
+
+ Διαθέσιμες Ἐνημερώσεις Πόρων
+
+ %1$s (Διαθέσιμη Ἐνημέρωση)
+
+ Διαθέσιμες ἐνημερώσεις γιὰ τὸ πληκτρολόγιο %1$s: %2$s
+
+ Διαθέσιμες ἐνημερώσεις γιὰ τὸ λεξικὸ %1$s: %2$s
+
+ Ἔκδοση πληκτρολογίου
+
+ Σύνδεσμος βοηθείας
+
+ Ἀπεγκαταστῆστε πληκτρολόγιο
+
+ [νέο] %1$s
+
+ Σαρῶστε αὐτὸν τὸν κωδικό γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή
+
+ Καλωσορίσατε στὸ %1$s
+
+ Ἀπαιτεῖται βιβλιοθήκη FileProvider γιὰ νὰ δεῖτε ἀρχεῖο βοηθείας: %1$s
+
+ Μοιραῖο σφάλμα πληκτρολογίου στὸ %1$s:%2$s γιὰ τὴν %3$s γλῶσσα. Φορτώνεται προεπιλεγμένο πληκτρολόγιο.
+
+ Error in keyboard %1$s:%2$s for %3$s language.
+
+ Ἔλεγχος συσχετισμένου λεξικοῦ πρὸς λῆψιν
+ Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Κῆμαν γιὰ τὸν ἔλεγχο συσχετισμένου λεξικοῦ πρὸς λῆψιν
+
+ Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ λεξικοῦ;
+
+ Δὲν ὑπάρχει λεξικὸ πρὸς λῆψιν
+
+ Μὴ διαθέσιμος κατάλογος πόρων
+
+ Ἔχει ξεκινήσει ἐνημέρωση καταλόγου στὸ παρασκήνιο
+
+ Ἡ λήψη τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!
+
+ Ἔλεγχος πόρου σὲ ἐξέλιξη
+
+ Ἡ λήψη τοῦ πληκτρολογίου ἔχει ξεκινήσει στὸ παρασκήνιο
+
+ Ἡ λήψη τοῦ ἐπιλεγέντος πληκτρολογίου βρίσκεται σὲ ἐξέλιξη· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!
+
+ Ἡ λήψη τοῦ πληκτρολογίου ὁλοκληρώθηκε!
+
+ Ἡ λήψη τοῦ λεξικοῦ ἔχει ξεκινήσει στὸ παρασκήνιο
+
+ Ἡ λήψη τοῦ ἐπιλεγέντος λεξικοῦ βρίσκεται σὲ ἐξέλιξη· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο!
+
+ Ἡ λήψη τοῦ λεξικοῦ ὁλοκληρώθηκε.
+
+ Ἡ λήψη ἀπέτυχε
+
+ Ἀποτυχία ἀνακτήσεως ληφθέντος ἀρχείου
+
+ Ἀποτυχία προσβάσεως στὸν διακομιστή!
+
+ "Ὅλοι οἱ πόροι ἔχουν ἐνημερωθεῖ!"
+
+ Ἕνας ἢ περισσότεροι πόροι ἀπέτυχαν νὰ ἐνημερωθοῦν!
+
+ Οἱ πόροι ἐνημερώθηκαν ἐπιτυχῶς!
+
+ Ἔκδοση λεξικοῦ
+
+ Ἀπεγκαταστῆτε λεξικό
+
+ Θὰ θέλατε νὰ διαγράψετε αὐτὸ τὸ λεξικό;
+
+ Τὸ λεξικὸ διεγράφη
+
+ Τὸ πληκτρολόγιο %1$s ἐγκατεστάθη
+
+ Τὸ πληκτρολόγιο διεγράφη
+
+ Ἐνεργοποιῆστε τὶς διορθώσεις
+
+ Ἐνεργοποιῆστε προβλέψεις
+
+ Λεξικά
+
+ - Λεξικό
+ - Λεξικά
+
+
+ Ἔλεγχος διαθεσίμου λεξικοῦ
+ Ἔλεγχος λεξικῶν ὀνλάϊν
+
+ Λεξικό: %1$s
+
+ %1$s λεξικά
+
+
+ Τὸ λεξικὸ ἐγκατεστάθη
+
+
+ - (%1$d πληκτρολόγιο)
+ - (%1$d πληκτρολόγια)
+
+
+ Προεπιλεγμένη Γλῶσσα
+
+
+
+ Διαγράψτε
+
+
+ Κτυπῆστε ἐδῶ γιὰ νὰ ἀλλάξετε πληκτρολόγιο
+
+ Ἀδυναμία ἐκκινήσεως φυλλομετρητῆ
+
diff --git a/common/models/templates/src/index.ts b/common/models/templates/src/index.ts
index 52650ba9bb..8563004fef 100644
--- a/common/models/templates/src/index.ts
+++ b/common/models/templates/src/index.ts
@@ -2,7 +2,6 @@ export {
SENTINEL_CODE_UNIT, applyTransform, buildMergedTransform, isHighSurrogate, isLowSurrogate, isSentinel,
transformToSuggestion, defaultApplyCasing
} from "./common.js";
-export { default as PriorityQueue, Comparator } from "./priority-queue.js";
export { default as QuoteBehavior } from "./quote-behavior.js";
export { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js";
export { default as TrieModel, TrieModelOptions } from "./trie-model.js";
\ No newline at end of file
diff --git a/common/models/templates/src/trie-model.ts b/common/models/templates/src/trie-model.ts
index 0a571517c7..2cac0429c0 100644
--- a/common/models/templates/src/trie-model.ts
+++ b/common/models/templates/src/trie-model.ts
@@ -26,12 +26,11 @@
// Should probably make a 'lm-utils' submodule.
// Allows the kmwstring bindings to resolve.
-import { extendString } from "@keymanapp/web-utils";
+import { extendString, PriorityQueue } from "@keymanapp/web-utils";
import { default as defaultWordBreaker } from "@keymanapp/models-wordbreakers";
import { applyTransform, isHighSurrogate, isSentinel, SENTINEL_CODE_UNIT, transformToSuggestion } from "./common.js";
import { getLastPreCaretToken } from "./tokenization.js";
-import PriorityQueue from "./priority-queue.js";
extendString();
@@ -74,15 +73,6 @@ export interface TrieModelOptions {
punctuation?: LexicalModelPunctuation;
}
-/**
- * Used to determine the probability of an entry from the trie.
- */
-type TextWithProbability = {
- text: string;
- // TODO: use negative-log scaling instead?
- p: number; // real-number weight, from 0 to 1
-}
-
class Traversal implements LexiconTraversal {
/**
* The lexical prefix corresponding to the current traversal state.
@@ -95,14 +85,75 @@ class Traversal implements LexiconTraversal {
*/
root: Node;
- constructor(root: Node, prefix: string) {
+ /**
+ * The max weight for the Trie being 'traversed'. Needed for probability
+ * calculations.
+ */
+ totalWeight: number;
+
+ constructor(root: Node, prefix: string, totalWeight: number) {
this.root = root;
this.prefix = prefix;
+ this.totalWeight = totalWeight;
}
- *children(): Generator<{char: string, traversal: () => LexiconTraversal}> {
+ child(char: USVString): LexiconTraversal | undefined {
+ /*
+ Note: would otherwise return the current instance if `char == ''`. If
+ such a call is happening, it's probably indicative of an implementation
+ issue elsewhere - let's signal now in order to catch such stuff early.
+ */
+ if(char == '') {
+ return undefined;
+ }
+
+ // Split into individual code units.
+ let steps = char.split('');
+ let traversal: Traversal | undefined = this;
+
+ while(steps.length > 0 && traversal) {
+ const step: string = steps.shift()!;
+ traversal = traversal._child(step);
+ }
+
+ return traversal;
+ }
+
+ // Handles one code unit at a time.
+ private _child(char: USVString): Traversal | undefined {
+ const root = this.root;
+ const totalWeight = this.totalWeight;
+ const nextPrefix = this.prefix + char;
+
+ if(root.type == 'internal') {
+ let childNode = root.children[char];
+ if(!childNode) {
+ return undefined;
+ }
+
+ return new Traversal(childNode, nextPrefix, totalWeight);
+ } else {
+ // root.type == 'leaf';
+ const legalChildren = root.entries.filter(function(entry) {
+ return entry.key.indexOf(nextPrefix) == 0;
+ });
+
+ if(!legalChildren.length) {
+ return undefined;
+ }
+
+ return new Traversal(root, nextPrefix, totalWeight);
+ }
+ }
+
+ *children(): Generator<{char: USVString, traversal: () => LexiconTraversal}> {
let root = this.root;
+ // We refer to the field multiple times in this method, and it doesn't change.
+ // This also assists minification a bit, since we can't minify when re-accessing
+ // through `this.`.
+ const totalWeight = this.totalWeight;
+
if(root.type == 'internal') {
for(let entry of root.values) {
let entryNode = root.children[entry];
@@ -120,7 +171,7 @@ class Traversal implements LexiconTraversal {
let prefix = this.prefix + entry + lowSurrogate;
yield {
char: entry + lowSurrogate,
- traversal: function() { return new Traversal(internalNode.children[lowSurrogate], prefix) }
+ traversal: function() { return new Traversal(internalNode.children[lowSurrogate], prefix, totalWeight) }
}
}
} else {
@@ -131,7 +182,7 @@ class Traversal implements LexiconTraversal {
yield {
char: entry,
- traversal: function () {return new Traversal(entryNode, prefix)}
+ traversal: function () {return new Traversal(entryNode, prefix, totalWeight)}
}
}
} else if(isSentinel(entry)) {
@@ -143,7 +194,7 @@ class Traversal implements LexiconTraversal {
let prefix = this.prefix + entry;
yield {
char: entry,
- traversal: function() { return new Traversal(entryNode, prefix)}
+ traversal: function() { return new Traversal(entryNode, prefix, totalWeight)}
}
}
}
@@ -165,30 +216,41 @@ class Traversal implements LexiconTraversal {
}
yield {
char: nodeKey,
- traversal: function() { return new Traversal(root, prefix + nodeKey)}
+ traversal: function() { return new Traversal(root, prefix + nodeKey, totalWeight)}
}
};
return;
}
}
- get entries(): string[] {
+ get entries() {
+ const entryMapper = (value: Entry) => {
+ return {
+ text: value.content,
+ p: value.weight / this.totalWeight
+ }
+ }
+
if(this.root.type == 'leaf') {
let prefix = this.prefix;
let matches = this.root.entries.filter(function(entry) {
return entry.key == prefix;
});
- return matches.map(function(value) { return value.content });
+ return matches.map(entryMapper);
} else {
let matchingLeaf = this.root.children[SENTINEL_CODE_UNIT];
if(matchingLeaf && matchingLeaf.type == 'leaf') {
- return matchingLeaf.entries.map(function(value) { return value.content });
+ return matchingLeaf.entries.map(entryMapper);
} else {
return [];
}
}
}
+
+ get p(): number {
+ return this.root.weight / this.totalWeight;
+ }
}
/**
@@ -286,7 +348,7 @@ export default class TrieModel implements LexicalModel {
}
public traverseFromRoot(): LexiconTraversal {
- return new Traversal(this._trie['root'], '');
+ return this._trie.traverseFromRoot();
}
};
@@ -307,11 +369,10 @@ export default class TrieModel implements LexicalModel {
type SearchKey = string & { _: 'SearchKey'};
/**
- * The priority queue will always pop the most weighted item. There can only
- * be two kinds of items right now: nodes, and entries; both having a weight
- * attribute.
+ * The priority queue will always pop the most probable item - be it a Traversal
+ * state or a lexical entry reached via Traversal.
*/
-type Weighted = Node | Entry;
+type TraversableWithProb = TextWithProbability | LexiconTraversal;
/**
* A function that converts a string (word form or query) into a search key
@@ -367,9 +428,9 @@ interface Entry {
* Wrapper class for the trie and its nodes.
*/
class Trie {
- private root: Node;
+ public readonly root: Node;
/** The total weight of the entire trie. */
- private totalWeight: number;
+ readonly totalWeight: number;
/**
* Converts arbitrary strings to a search key. The trie is built up of
* search keys; not each entry's word form!
@@ -382,6 +443,10 @@ class Trie {
this.totalWeight = totalWeight;
}
+ public traverseFromRoot(): LexiconTraversal {
+ return new Traversal(this.root, '', this.totalWeight);
+ }
+
/**
* Lookups an arbitrary prefix (a query) in the trie. Returns the top 3
* results in sorted order.
@@ -390,12 +455,8 @@ class Trie {
*/
lookup(prefix: string): TextWithProbability[] {
let searchKey = this.toKey(prefix);
- let lowestCommonNode = findPrefix(this.root, searchKey);
- if (lowestCommonNode === null) {
- return [];
- }
-
- return getSortedResults(lowestCommonNode, searchKey, this.totalWeight);
+ let rootTraversal = this.traverseFromRoot().child(searchKey);
+ return rootTraversal ? getSortedResults(rootTraversal) : [];
}
/**
@@ -403,36 +464,10 @@ class Trie {
* @param n How many suggestions, maximum, to return.
*/
firstN(n: number): TextWithProbability[] {
- return getSortedResults(this.root, '' as SearchKey, this.totalWeight, n);
+ return getSortedResults(this.traverseFromRoot(), n);
}
}
-/**
- * Finds the deepest descendent in the trie with the given prefix key.
- *
- * This means that a search in the trie for a given prefix has a best-case
- * complexity of O(m) where m is the length of the prefix.
- *
- * @param key The prefix to search for.
- * @param index The index in the prefix. Initially 0.
- */
-function findPrefix(node: Node, key: SearchKey, index: number = 0): Node | null {
- // An important note - the Trie itself is built on a per-JS-character basis,
- // not on a UTF-8 character-code basis.
- if (node.type === 'leaf' || index === key.length) {
- return node;
- }
-
- // So, for SMP models, we need to match each char of the supplementary pair
- // in sequence. Each has its own node in the Trie.
- let char = key[index];
- if (node.children[char]) {
- return findPrefix(node.children[char], key, index + 1);
- }
-
- return null;
-}
-
/**
* Returns all entries matching the given prefix, in descending order of
* weight.
@@ -441,72 +476,36 @@ function findPrefix(node: Node, key: SearchKey, index: number = 0): Node | null
* @param results the current results
* @param queue
*/
-function getSortedResults(node: Node, prefix: SearchKey, N: number, limit = MAX_SUGGESTIONS): TextWithProbability[] {
- let queue = new PriorityQueue(function(a: Weighted, b: Weighted) {
+function getSortedResults(traversal: LexiconTraversal, limit = MAX_SUGGESTIONS): TextWithProbability[] {
+ let queue = new PriorityQueue(function(a: TraversableWithProb, b: TraversableWithProb) {
// In case of Trie compilation issues that emit `null` or `undefined`
- return (b ? b.weight : 0) - (a ? a.weight : 0);
+ return (b ? b.p : 0) - (a ? a.p : 0);
});
let results: TextWithProbability[] = [];
- if (node.type === 'leaf') {
- // Assuming the values are sorted, we can just add all of the values in the
- // leaf, until we reach the limit.
- for (let item of node.entries) {
- // String.startsWith is not supported on certain Android (5.0) devices we wish to support.
- // Requires a minimum of Chrome 36, as opposed to 5.0's default of 35.
- if (item.key.indexOf(prefix) == 0) {
- let { content, weight } = item;
- results.push({
- text: content,
- p: weight / N
- });
+ queue.enqueue(traversal);
- if (results.length >= limit) {
- return results;
- }
- }
- }
- } else {
- queue.enqueue(node);
- let next: Weighted | undefined;
+ while(queue.count > 0) {
+ const entry = queue.dequeue();
- while (next = queue.dequeue()) {
- if (isNode(next)) {
- // When a node is next up in the queue, that means that next least
- // likely suggestion is among its decsendants.
- // So we search all of its descendants!
- if (next.type === 'leaf') {
- queue.enqueueAll(next.entries);
- } else {
- // XXX: alias `next` so that TypeScript can be SURE that internal is
- // in fact an internal node. Because of the callback binding to the
- // original definition of node (i.e., a Node | Entry), this will not
- // type-check otherwise.
- let internal = next;
- queue.enqueueAll(next.values.map(char => {
- return internal.children[char];
- }));
- }
- } else {
- // When an entry is up next in the queue, we just add its contents to
- // the results!
- results.push({
- text: next.content,
- p: next.weight / N
- });
- if (results.length >= limit) {
- return results;
- }
+ if((entry as TextWithProbability)!.text !== undefined) {
+ const lexicalEntry = entry as TextWithProbability;
+ results.push(lexicalEntry);
+ if(results.length >= limit) {
+ return results;
}
+ } else {
+ const traversal = entry as LexiconTraversal;
+ queue.enqueueAll(traversal.entries);
+ let children: LexiconTraversal[] = []
+ for(let child of traversal.children()) {
+ children.push(child.traversal());
+ }
+ queue.enqueueAll(children);
}
}
+
return results;
-
-}
-
-/** TypeScript type guard that returns whether the thing is a Node. */
-function isNode(x: Entry | Node): x is Node {
- return 'type' in x;
}
/**
diff --git a/common/models/templates/test/test-trie-traversal.js b/common/models/templates/test/test-trie-traversal.js
index d3d0496316..09237ee9a0 100644
--- a/common/models/templates/test/test-trie-traversal.js
+++ b/common/models/templates/test/test-trie-traversal.js
@@ -13,6 +13,12 @@ var smpForUnicode = function(code){
return String.fromCharCode(H, L);
}
+// Prob: entry weight / total weight
+// "the" is the highest-weighted word in the fixture.
+const PROB_OF_THE = 1000 / 500500;
+const PROB_OF_TRUE = 607 / 500500;
+const PROB_OF_TROUBLE = 267 / 500500;
+
describe('Trie traversal abstractions', function() {
it('root-level iteration over child nodes', function() {
var model = new TrieModel(jsonFixture('tries/english-1000'));
@@ -21,7 +27,11 @@ describe('Trie traversal abstractions', function() {
assert.isDefined(rootTraversal);
let rootKeys = ['t', 'o', 'a', 'i', 'w', 'h', 'f', 'b', 'n', 'y', 's', 'm',
- 'u', 'c', 'd', 'l', 'e', 'j', 'p', 'g', 'v', 'k', 'r', 'q']
+ 'u', 'c', 'd', 'l', 'e', 'j', 'p', 'g', 'v', 'k', 'r', 'q'];
+
+ rootKeys.forEach((entry) => assert.isOk(rootTraversal.child(entry)));
+ assert.isNotOk(rootTraversal.child('x'));
+ assert.isNotOk(rootTraversal.child('z'));
for(let child of rootTraversal.children()) {
let keyIndex = rootKeys.indexOf(child.char);
@@ -50,6 +60,7 @@ describe('Trie traversal abstractions', function() {
assert.isDefined(traversalInner1);
assert.isArray(child.traversal().entries);
assert.isEmpty(child.traversal().entries);
+ assert.equal(traversalInner1.p, PROB_OF_THE);
for(let tChild of traversalInner1.children()) {
if(tChild.char == 'h') {
@@ -58,19 +69,28 @@ describe('Trie traversal abstractions', function() {
assert.isDefined(traversalInner2);
assert.isEmpty(tChild.traversal().entries);
assert.isArray(tChild.traversal().entries);
+ assert.equal(traversalInner2.p, PROB_OF_THE);
for(let hChild of traversalInner2.children()) {
if(hChild.char == 'e') {
eSuccess = true;
let traversalInner3 = hChild.traversal();
assert.isDefined(traversalInner3);
-
assert.isDefined(traversalInner3.entries);
- assert.equal(traversalInner3.entries[0], "the");
+ assert.deepEqual(traversalInner3.entries, [
+ {
+ text: "the",
+ p: PROB_OF_THE
+ }
+ ]);
+ assert.equal(traversalInner3.p, PROB_OF_THE);
for(let eChild of traversalInner3.children()) {
let keyIndex = eKeys.indexOf(eChild.char);
assert.notEqual(keyIndex, -1, "Did not find char '" + eChild.char + "' in array!");
+
+ // THE is not accessible if any of the sub-tries of our 'e' node (traversalInner3).
+ assert.isBelow(eChild.traversal().p, PROB_OF_THE);
eKeys.splice(keyIndex, 1);
}
}
@@ -87,6 +107,38 @@ describe('Trie traversal abstractions', function() {
assert.isEmpty(eKeys);
});
+ it('direct traversal with simple internal nodes', function() {
+ var model = new TrieModel(jsonFixture('tries/english-1000'));
+
+ let rootTraversal = model.traverseFromRoot();
+ assert.isDefined(rootTraversal);
+
+ let eKeys = ['y', 'r', 'i', 'm', 's', 'n', 'o'];
+
+ const tNode = rootTraversal.child('t');
+ assert.isOk(tNode);
+ assert.isDefined(tNode);
+ assert.isArray(tNode.entries);
+ assert.isEmpty(tNode.entries);
+
+ const hNode = tNode.child('h');
+ assert.isOk(hNode);
+ assert.isDefined(hNode);
+ assert.isArray(hNode.entries);
+ assert.isEmpty(hNode.entries);
+
+ const eNode = hNode.child('e');
+ assert.isOk(eNode);
+ assert.isDefined(eNode);
+ assert.isArray(eNode.entries);
+ assert.isNotEmpty(eNode.entries);
+ assert.equal(eNode.entries[0].text, "the");
+
+ for(let key of eKeys) {
+ assert.isOk(eNode.child(key));
+ }
+ });
+
it('traversal over compact leaf node', function() {
var model = new TrieModel(jsonFixture('tries/english-1000'));
@@ -102,6 +154,7 @@ describe('Trie traversal abstractions', function() {
assert.isDefined(traversalInner1);
assert.isArray(child.traversal().entries);
assert.isEmpty(child.traversal().entries);
+ assert.equal(traversalInner1.p, PROB_OF_THE);
for(let tChild of traversalInner1.children()) {
if(tChild.char == 'r') {
@@ -109,6 +162,7 @@ describe('Trie traversal abstractions', function() {
assert.isDefined(traversalInner2);
assert.isArray(tChild.traversal().entries);
assert.isEmpty(tChild.traversal().entries);
+ assert.equal(traversalInner2.p, PROB_OF_TRUE);
for(let rChild of traversalInner2.children()) {
if(rChild.char == 'o') {
@@ -137,10 +191,17 @@ describe('Trie traversal abstractions', function() {
if(leafChildSequence.length > 0) {
assert.isArray(curChild.traversal().entries);
assert.isEmpty(curChild.traversal().entries);
+ assert.equal(curChild.traversal().p, PROB_OF_TROUBLE);
} else {
let finalTraversal = curChild.traversal();
+ assert.equal(finalTraversal.p, PROB_OF_TROUBLE);
assert.isDefined(finalTraversal.entries);
- assert.equal(finalTraversal.entries[0], 'trouble');
+ assert.deepEqual(finalTraversal.entries, [
+ {
+ text: 'trouble',
+ p: PROB_OF_TROUBLE
+ }
+ ]);
eSuccess = true;
}
} while (leafChildSequence.length > 0);
@@ -154,7 +215,6 @@ describe('Trie traversal abstractions', function() {
assert.isTrue(eSuccess);
});
-
it('traversal with SMP entries', function() {
// Two entries, both of which read "apple" to native English speakers.
// One solely uses SMP characters, the other of which uses a mix of SMP and standard.
@@ -179,18 +239,20 @@ describe('Trie traversal abstractions', function() {
for(let child of rootTraversal.children()) {
if(child.char == smpA) {
aSuccess = true;
- let traversalInner1 = child.traversal();
+ const traversalInner1 = child.traversal();
assert.isDefined(traversalInner1);
- assert.isArray(child.traversal().entries);
- assert.isEmpty(child.traversal().entries);
+ assert.isArray(traversalInner1.entries);
+ assert.isEmpty(traversalInner1.entries);
+ assert.equal(traversalInner1.p, 0.5); // The two entries are equally weighted.
for(let aChild of traversalInner1.children()) {
if(aChild.char == smpP) {
pSuccess = true;
- let traversalInner2 = aChild.traversal();
+ const traversalInner2 = aChild.traversal();
assert.isDefined(traversalInner2);
- assert.isArray(aChild.traversal().entries);
- assert.isEmpty(aChild.traversal().entries);
+ assert.isArray(traversalInner2.entries);
+ assert.isEmpty(traversalInner2.entries);
+ assert.equal(traversalInner2.p, 0.5);
for(let pChild of traversalInner2.children()) {
let keyIndex = pKeys.indexOf(pChild.char);
@@ -198,10 +260,11 @@ describe('Trie traversal abstractions', function() {
pKeys.splice(keyIndex, 1);
if(pChild.char == 'p') { // We'll test traversal with the 'mixed' entry from here.
- let traversalInner3 = pChild.traversal();
+ const traversalInner3 = pChild.traversal();
assert.isDefined(traversalInner3);
- assert.isArray(pChild.traversal().entries);
- assert.isEmpty(pChild.traversal().entries);
+ assert.isArray(traversalInner3.entries);
+ assert.isEmpty(traversalInner3.entries);
+ assert.equal(traversalInner3.p, 0.5);
// Now to handle the rest, knowing it's backed by a leaf node.
let curChild = pChild;
@@ -227,12 +290,20 @@ describe('Trie traversal abstractions', function() {
// Conditional test - if that was not the final character, entries should be undefined.
if(leafChildSequence.length > 0) {
- assert.isArray(curChild.traversal().entries);
- assert.isEmpty(curChild.traversal().entries);
+ const nextTraversal = curChild.traversal()
+ assert.isArray(nextTraversal.entries);
+ assert.isEmpty(nextTraversal.entries);
+ assert.equal(nextTraversal.p, 0.5);
} else {
let finalTraversal = curChild.traversal();
assert.isDefined(finalTraversal.entries);
- assert.equal(finalTraversal.entries[0], smpA + smpP + 'pl' + smpE);
+ assert.deepEqual(finalTraversal.entries, [
+ {
+ text: smpA + smpP + 'pl' + smpE,
+ p: 1/2
+ }
+ ]);
+ assert.equal(finalTraversal.p, 0.5);
eSuccess = true;
}
} while (leafChildSequence.length > 0);
@@ -249,4 +320,52 @@ describe('Trie traversal abstractions', function() {
assert.isEmpty(pKeys);
});
+
+ it('direct traversal with SMP entries', function() {
+ // Two entries, both of which read "apple" to native English speakers.
+ // One solely uses SMP characters, the other of which uses a mix of SMP and standard.
+ var model = new TrieModel(jsonFixture('tries/smp-apple'));
+
+ let rootTraversal = model.traverseFromRoot();
+ assert.isDefined(rootTraversal);
+
+ let smpA = smpForUnicode(0x1d5ba);
+ let smpP = smpForUnicode(0x1d5c9);
+ let smpL = smpForUnicode(0x1d5c5);
+ let smpE = smpForUnicode(0x1d5be);
+
+ // Just to be sure our utility function is working right.
+ assert.equal(smpA + smpP + 'pl' + smpE, "𝖺𝗉pl𝖾");
+
+ let pKeys = ['p', smpP];
+ let leafChildSequence = ['l', smpE];
+
+ const aNode = rootTraversal.child(smpA);
+ assert.isOk(aNode);
+ assert.isNotOk(rootTraversal.child('a'));
+
+ const pNode1 = aNode.child(smpP);
+ assert.isOk(pNode1);
+ assert.isNotOk(aNode.child('p'));
+
+ const pNode2 = pNode1.child('p');
+ assert.isOk(pNode2);
+ assert.isOk(pNode1.child(smpP)); // Both exist for this step.
+
+ const lNode = pNode2.child('l');
+ assert.isOk(lNode);
+ assert.isNotOk(pNode2.child(smpL));
+
+ const eNode = lNode.child(smpE);
+ assert.isOk(eNode);
+ assert.isNotOk(lNode.child('e'));
+
+ assert.deepEqual(eNode.entries, [
+ {
+ text: smpA + smpP + 'pl' + smpE,
+ p: 1/2
+ }
+ ]);
+ assert.equal(eNode.p, 0.5);
+ });
});
diff --git a/common/models/types/index.d.ts b/common/models/types/index.d.ts
index 30aba3ba3d..0d6223bc64 100644
--- a/common/models/types/index.d.ts
+++ b/common/models/types/index.d.ts
@@ -19,13 +19,32 @@ declare type USVString = string;
declare type CasingForm = 'lower' | 'initial' | 'upper';
+/**
+ * Represents one lexical entry and its probability..
+ */
+type TextWithProbability = {
+ /**
+ * A lexical entry (word) offered by the model.
+ *
+ * Note: not the search-term keyed part. This will match the actual, unkeyed form.
+ */
+ text: string;
+
+ /**
+ * The probability of the lexical entry, directly based upon its frequency.
+ *
+ * A real-number weight, from 0 to 1.
+ */
+ p: number;
+}
+
/**
* Used to facilitate edit-distance calculations by allowing the LMLayer to
* efficiently search the model's lexicon in a Trie-like manner.
*/
declare interface LexiconTraversal {
/**
- * Provides an iterable pattern used to search for words with a prefix matching
+ * Provides an iterable pattern used to search for words with a 'keyed' prefix matching
* the current traversal state's prefix when a new character is appended. Iterating
* across `children` provides 'breadth' to a lexical search.
*
@@ -50,6 +69,20 @@ declare interface LexiconTraversal {
*/
children(): Generator<{char: USVString, traversal: () => LexiconTraversal}>;
+ /**
+ * Allows direct access to the traversal state that results when appending one
+ * or more codepoints encoded in UTF-16 to the current traversal state's prefix.
+ * This allows bypassing iteration among all legal child Traversals.
+ *
+ * If such a traversal state is not supported, returns `undefined`.
+ *
+ * Note: traversals navigate and represent the lexicon in its "keyed" state,
+ * as produced by use of the search-term keying function defined for the model.
+ * That is, if a model "keys" `è` to `e`, there will be no `è` child.
+ * @param char
+ */
+ child(char: USVString): LexiconTraversal | undefined;
+
/**
* Any entries directly keyed by the currently-represented lookup prefix. Entries and
* children may exist simultaneously, but `entries` must always exist when no children are
@@ -70,7 +103,14 @@ declare interface LexiconTraversal {
* - prefix of 'crepe': ['crêpe', 'crêpé']
* - other examples: https://www.thoughtco.com/french-accent-homographs-1371072
*/
- entries: USVString[];
+ entries: TextWithProbability[];
+
+ // Note: `p`, not `maxP` - we want to see the same name for `this.entries.p` and `this.p`
+ /**
+ * Gives the probability of the highest-frequency lexical entry that is either a member or
+ * descendent of the represented trie `Node`.
+ */
+ p: number;
}
/**
@@ -294,6 +334,11 @@ declare interface Suggestion {
* to the input text. Ex: 'keep', 'emoji', 'correction', etc.
*/
tag?: SuggestionTag;
+
+ /**
+ * Set to true if this suggestion is a valid auto-accept target.
+ */
+ autoAccept?: boolean
}
interface Reversion extends Suggestion {
diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts
index a0f7d77290..2ac5f62d00 100644
--- a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts
+++ b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts
@@ -36,6 +36,18 @@ describe('LMLayer using dummy model', function () {
// Since Firefox can't do JSON imports quite yet.
const hazelFixture = await fetch(new URL(`${domain}/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json`));
hazelModel = await hazelFixture.json();
+ hazelModel = hazelModel.map((set) => set.map((entry) => {
+ return {
+ ...entry,
+ // Dummy-model predictions all claim probability 1; there's no actual probability stuff
+ // used here.
+ 'lexical-p': 1,
+ // We're predicting from a single transform, not a distribution, so probability 1.
+ 'correction-p': 1,
+ // Multiply 'em together.
+ p: 1,
+ }
+ }));
});
describe('Prediction', function () {
diff --git a/common/test/resources/model-helpers.mjs b/common/test/resources/model-helpers.mjs
index 1de10bdb15..b2ad083f96 100644
--- a/common/test/resources/model-helpers.mjs
+++ b/common/test/resources/model-helpers.mjs
@@ -113,7 +113,18 @@ export function randomToken() {
}
export function iGotDistractedByHazel() {
- return jsonFixture('models/future_suggestions/i_got_distracted_by_hazel');
+ return jsonFixture('models/future_suggestions/i_got_distracted_by_hazel').map((set) => set.map((entry) => {
+ return {
+ ...entry,
+ // Dummy-model predictions all claim probability 1; there's no actual probability stuff
+ // used here.
+ 'lexical-p': 1,
+ // We're predicting from a single transform, not a distribution, so probability 1.
+ 'correction-p': 1,
+ // Multiply 'em together.
+ p: 1,
+ }
+ }));
}
export function jsonFixture(name, root, import_root) {
diff --git a/common/web/input-processor/src/text/prediction/languageProcessor.ts b/common/web/input-processor/src/text/prediction/languageProcessor.ts
index a24938fe27..921f4abf3c 100644
--- a/common/web/input-processor/src/text/prediction/languageProcessor.ts
+++ b/common/web/input-processor/src/text/prediction/languageProcessor.ts
@@ -19,7 +19,7 @@ export type StateChangeHandler = (state: StateChangeEnum) => any;
/**
* Covers 'tryaccept' events.
*/
-export type TryUIHandler = (source: string) => boolean;
+export type TryUIHandler = (source: string, returnObj: {shouldSwallow: boolean}) => boolean;
export type InvalidateSourceEnum = 'new'|'context';
@@ -168,9 +168,9 @@ export default class LanguageProcessor extends EventEmitter;
private swallowPrediction: boolean = false;
@@ -191,7 +192,7 @@ export default class PredictionContext extends EventEmitter {
- //let keyman = com.keyman.singleton;
+ private doTryAccept = (source: string, returnObj: {shouldSwallow: boolean}): void => {
+ const recentAcceptCause = this.recentAcceptCause;
- if(!this.recentAccept && this.selected) {
+ if(!recentAcceptCause && this.selected) {
this.accept(this.selected);
- // returnObj.shouldSwallow = true;
- } else if(this.recentAccept && source == 'space') {
- this.recentAccept = false;
- // // If the model doesn't insert wordbreaks, don't swallow the space. If it does,
- // // we consider that insertion to be the results of the first post-accept space.
- // returnObj.shouldSwallow = !!keyman.core.languageProcessor.wordbreaksAfterSuggestions; // can be handed outside
+ returnObj.shouldSwallow = true;
+
+ // doTryAccept is the path for keystroke-based auto-acceptance.
+ // Overwrite the cause to reflect this.
+ this.recentAcceptCause = 'key';
+ } else if(recentAcceptCause && source == 'space') {
+ this.recentAcceptCause = null;
+ if(recentAcceptCause == 'key') {
+ // No need to swallow the keystroke's whitespace; we triggered the prior acceptance
+ // FROM a space, so we've already aliased the suggestion's built-in space.
+ returnObj.shouldSwallow = false;
+ return;
+ }
+
+ // Standard whitespace applications from the banner, those we DO want to
+ // swallow the first time.
+ //
+ // If the model doesn't insert wordbreaks, there's no space to alias, so
+ // don't swallow the space. If it does, we consider that insertion to be
+ // the results of the first post-accept space.
+ returnObj.shouldSwallow = !!this.langProcessor.wordbreaksAfterSuggestions; // can be handed outside
} else {
- // returnObj.shouldSwallow = false;
+ returnObj.shouldSwallow = false;
}
}
@@ -250,9 +268,9 @@ export default class PredictionContext extends EventEmitter {
// By default, we assume that the context is the same until we notice otherwise.
this.initNewContext = false;
+ this.selected = null;
if(!this.swallowPrediction || source == 'context') {
- this.recentAccept = false;
+ this.recentAcceptCause = null;
this.doRevert = false;
this.recentRevert = false;
@@ -299,7 +318,7 @@ export default class PredictionContext extends EventEmitter,
+ correction: ProbabilityMass,
+ totalProb: number;
+};
+
export default class ModelCompositor {
private lexicalModel: LexicalModel;
private contextTracker?: correction.ContextTracker;
@@ -39,9 +45,13 @@ export default class ModelCompositor {
private SUGGESTION_ID_SEED = 0;
- private testMode: boolean = false
+ private testMode: boolean = false;
+ private verbose: boolean = true;
- constructor(lexicalModel: LexicalModel, testMode?: boolean) {
+ constructor(
+ lexicalModel: LexicalModel,
+ testMode?: boolean
+ ) {
this.lexicalModel = lexicalModel;
if(lexicalModel.traverseFromRoot) {
this.contextTracker = new correction.ContextTracker();
@@ -50,23 +60,31 @@ export default class ModelCompositor {
this.testMode = !!testMode;
}
- private predictFromCorrections(corrections: ProbabilityMass[], context: Context): Distribution {
- let returnedPredictions: Distribution = [];
+ private predictFromCorrections(corrections: ProbabilityMass[], context: Context): CorrectionPredictionTuple[] {
+ let returnedPredictions: CorrectionPredictionTuple[] = [];
for(let correction of corrections) {
let predictions = this.lexicalModel.predict(correction.sample, context);
- let predictionSet = predictions.map(function(pair: ProbabilityMass) {
- let transform = correction.sample;
- let inputProb = correction.p;
+ const { sample: correctionTransform, p: correctionProb } = correction;
+ const correctionRoot = this.wordbreak(models.applyTransform(correction.sample, context));
+
+ let predictionSet = predictions.map((pair: ProbabilityMass) => {
// Let's not rely on the model to copy transform IDs.
// Only bother is there IS an ID to copy.
- if(transform.id !== undefined) {
- pair.sample.transformId = transform.id;
+ if(correctionTransform.id !== undefined) {
+ pair.sample.transformId = correctionTransform.id;
}
- let prediction = {sample: pair.sample, p: pair.p * inputProb};
- return prediction;
+ let tuple: CorrectionPredictionTuple = {
+ prediction: pair,
+ correction: {
+ sample: correctionRoot,
+ p: correctionProb
+ },
+ totalProb: pair.p * correctionProb
+ };
+ return tuple;
}, this);
returnedPredictions = returnedPredictions.concat(predictionSet);
@@ -76,7 +94,7 @@ export default class ModelCompositor {
}
async predict(transformDistribution: Transform | Distribution, context: Context): Promise {
- let suggestionDistribution: Distribution = [];
+ let suggestionDistribution: CorrectionPredictionTuple[] = [];
let lexicalModel = this.lexicalModel;
let punctuation = this.punctuation;
@@ -119,7 +137,7 @@ export default class ModelCompositor {
let keepOptionText = this.wordbreak(postContext);
let keepOption: Outcome = null;
- let rawPredictions: Distribution = [];
+ let rawPredictions: CorrectionPredictionTuple[] = [];
// Used to restore whitespaces if operations would remove them.
let prefixTransform: Transform;
@@ -318,10 +336,10 @@ export default class ModelCompositor {
// If we're getting the same prediction again, it's lower-cost. Update!
let oldPredictionSet = correctionPredictionMap[match.matchString];
if(oldPredictionSet) {
- rawPredictions = rawPredictions.filter((entry) => !oldPredictionSet.find((match) => entry == match))
+ rawPredictions = rawPredictions.filter((entry) => !oldPredictionSet.find((match) => entry.prediction.sample == match.sample));
}
- correctionPredictionMap[match.matchString] = predictions;
+ correctionPredictionMap[match.matchString] = predictions.map((entry) => entry.prediction);
rawPredictions = rawPredictions.concat(predictions);
@@ -337,13 +355,13 @@ export default class ModelCompositor {
} else {
// Sort the prediction list; we need them in descending order for the next check.
rawPredictions.sort(function(a, b) {
- return b.p - a.p;
+ return b.totalProb - a.totalProb;
});
// If the best suggestion from the search's current tier fails to beat the worst
// pending suggestion from previous tiers, assume all further corrections will
// similarly fail to win; terminate the search-loop.
- if(rawPredictions[ModelCompositor.MAX_SUGGESTIONS-1].p > Math.exp(-correctionCost)) {
+ if(rawPredictions[ModelCompositor.MAX_SUGGESTIONS-1].totalProb > Math.exp(-correctionCost)) {
break;
}
}
@@ -361,7 +379,7 @@ export default class ModelCompositor {
// Section 2 - post-analysis for our generated predictions, managing 'keep'.
// Assumption: Duplicated 'displayAs' properties indicate duplicated Suggestions.
// When true, we can use an 'associative array' to de-duplicate everything.
- let suggestionDistribMap: {[key: string]: ProbabilityMass} = {};
+ let suggestionDistribMap: {[key: string]: CorrectionPredictionTuple} = {};
let currentCasing: CasingForm = null;
if(lexicalModel.languageUsesCasing) {
currentCasing = this.detectCurrentCasing(postContext);
@@ -370,9 +388,12 @@ export default class ModelCompositor {
let baseWord = this.wordbreak(context);
// Deduplicator + annotator of 'keep' suggestions.
- for(let prediction of rawPredictions) {
+ for(let tuple of rawPredictions) {
+ const prediction = tuple.prediction.sample;
+ const prob = tuple.totalProb;
+
// Combine duplicate samples.
- let displayText = prediction.sample.displayAs;
+ let displayText = prediction.displayAs;
let preserveAsKeep = displayText == keepOptionText;
// De-duplication should be case-insensitive, but NOT
@@ -384,7 +405,7 @@ export default class ModelCompositor {
if(preserveAsKeep) {
// Preserve the original, pre-keyed version of the text.
if(!keepOption) {
- let baseTransform = prediction.sample.transform;
+ let baseTransform = prediction.transform;
let keepTransform = {
insert: keepOptionText,
@@ -393,15 +414,15 @@ export default class ModelCompositor {
id: baseTransform.id
}
- let intermediateKeep = models.transformToSuggestion(keepTransform, prediction.p);
+ let intermediateKeep = models.transformToSuggestion(keepTransform, prob);
keepOption = this.toAnnotatedSuggestion(intermediateKeep, 'keep', models.QuoteBehavior.noQuotes);
keepOption.matchesModel = true;
// Since we replaced the original Suggestion with a keep-annotated one,
// we must manually preserve the transform ID.
- keepOption.transformId = prediction.sample.transformId;
- } else if(keepOption.p && prediction.p) {
- keepOption.p += prediction.p;
+ keepOption.transformId = prediction.transformId;
+ } else if(keepOption.p && prob) {
+ keepOption.p += prob;
}
} else {
// Apply capitalization rules now; facilitates de-duplication of suggestions
@@ -409,16 +430,16 @@ export default class ModelCompositor {
//
// Example: "apple" and "Apple" are separate when 'lower', but identical for 'initial' and 'upper'.
if(currentCasing && currentCasing != 'lower') {
- this.applySuggestionCasing(prediction.sample, baseWord, currentCasing);
+ this.applySuggestionCasing(prediction, baseWord, currentCasing);
// update the mapping string, too.
- displayText = prediction.sample.displayAs;
+ displayText = prediction.displayAs;
}
let existingSuggestion = suggestionDistribMap[displayText];
if(existingSuggestion) {
- existingSuggestion.p += prediction.p;
+ existingSuggestion.totalProb += prob;
} else {
- suggestionDistribMap[displayText] = prediction;
+ suggestionDistribMap[displayText] = tuple;
}
}
}
@@ -448,28 +469,41 @@ export default class ModelCompositor {
}
suggestionDistribution = suggestionDistribution.sort(function(a, b) {
- return b.p - a.p; // Use descending order - we want the largest probabilty suggestions first!
+ return b.totalProb - a.totalProb; // Use descending order - we want the largest probabilty suggestions first!
});
- let suggestions = suggestionDistribution.splice(0, ModelCompositor.MAX_SUGGESTIONS).map(function(value) {
- let sample: Suggestion & {
- p?: number,
- "lexical-p"?: number,
- "correction-p"?: number
- } = value.sample;
+ // Section 4: Auto-correction + finalization.
+ if(keepOption && keepOption.matchesModel) {
+ // Auto-select it for auto-acceptance; we don't correct away from perfectly-valid
+ // lexical entries, even if they are comparatively low-frequency.
+ keepOption.autoAccept = true;
+ } else {
+ this.predictionAutoSelect(suggestionDistribution);
+ }
- if(sample['p']) {
- // For analysis / debugging
- sample['lexical-p'] = sample['p'];
- sample['correction-p'] = value.p / sample['p'];
- // Use of the Trie model always exposed the lexical model's probability for a word to KMW.
- // It's useful for debugging right now, so may as well repurpose it as the posterior.
- //
- // We still condition on 'p' existing so that test cases aren't broken.
- sample['p'] = value.p;
+ // Now that we've marked the suggestion to auto-select, we can finalize the suggestions.
+ let suggestions = suggestionDistribution.splice(0, ModelCompositor.MAX_SUGGESTIONS).map((tuple) => {
+ const prediction = tuple.prediction;
+
+ if(!this.verbose) {
+ return {
+ ...prediction.sample,
+ p: tuple.totalProb
+ };
+ } else {
+ const sample: Suggestion & {
+ p?: number,
+ "lexical-p"?: number,
+ "correction-p"?: number
+ } = {
+ ...prediction.sample,
+ p: tuple.totalProb,
+ "lexical-p": prediction.p,
+ "correction-p": tuple.correction.p
+ }
+
+ return sample;
}
- //
- return sample;
});
if(keepOption) {
@@ -534,6 +568,53 @@ export default class ModelCompositor {
return suggestions;
}
+ private predictionAutoSelect(suggestionDistribution: CorrectionPredictionTuple[]) {
+ if(suggestionDistribution.length == 0) {
+ return;
+ }
+
+ if(suggestionDistribution.length == 1) {
+ // Mark for auto-acceptance; there are no alternatives.
+ suggestionDistribution[0].prediction.sample.autoAccept = true;
+ return;
+ }
+
+ // Is it reasonable to auto-accept any of our suggestions?
+ const bestSuggestion = suggestionDistribution[0];
+
+ const baseCorrection = bestSuggestion.correction.sample;
+ if(baseCorrection.length == 0) {
+ // If the correction is rooted on an empty root, there's no basis for
+ // auto-correcting to this suggestion.
+ return;
+ }
+
+ // Find the highest probability for any correction that led to a valid prediction.
+ // No need to full-on re-sort everything, though.
+ const bestCorrection = suggestionDistribution.reduce((prev, current) => prev?.correction.p > current.correction.p ? prev : current, null).correction;
+ if(bestCorrection.p > bestSuggestion.correction.p) {
+ // Here, the best suggestion didn't come from the best correction.
+ // Is it actually reasonable to auto-correct? We're probably just very
+ // biased toward its frequency. (Maybe a threshold should be considered?)
+ return;
+ }
+
+ // compare best vs other probabilities.
+ const probSum = suggestionDistribution.reduce((accum, current) => accum + current.totalProb, 0);
+ const proportionOfBest = bestSuggestion.totalProb / probSum;
+ if(proportionOfBest < .66) {
+ return;
+ }
+
+ // compare correction-cost aspects? We disable if the base correction is lower than best,
+ // but should we do other comparisons too?
+
+ // const nextSuggestion = suggestionDistribution[1];
+ // baseCorrection
+
+ bestSuggestion.prediction.sample.autoAccept = true;
+ }
+
// Responsible for applying casing rules to suggestions.
private applySuggestionCasing(suggestion: Suggestion, baseWord: USVString, casingForm: CasingForm) {
// Step 1: does the suggestion replace the whole word? If not, we should extend the suggestion to do so.
diff --git a/common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js b/common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js
index daa3fa5fcf..b49b511e37 100644
--- a/common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js
+++ b/common/web/lm-worker/src/test/mocha/cases/edit-distance/distance-modeler.js
@@ -2,6 +2,7 @@ import { assert } from 'chai';
import * as models from '#./models/index.js';
import * as correction from '#./correction/index.js';
+import { PriorityQueue } from '@keymanapp/web-utils';
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
function buildTestTimer() {
@@ -142,7 +143,7 @@ describe('Correction Distance Modeler', function() {
assert.equal(edges.length, expectedChildCount);
// One final bit, which is a bit of integration - we know the top two nodes that should result.
- let queue = new models.PriorityQueue(correction.QUEUE_NODE_COMPARATOR, edges);
+ let queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, edges);
let firstEdge = queue.dequeue();
assert.equal(firstEdge.priorInput[0].sample.insert, 't');
@@ -185,13 +186,13 @@ describe('Correction Distance Modeler', function() {
];
let layer1Edges = rootNode.buildSubstitutionEdges(synthDistribution1);
- let layer1Queue = new models.PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer1Edges);
+ let layer1Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer1Edges);
let tEdge = layer1Queue.dequeue();
assertEdgeChars(tEdge, 't', 't');
let layer2Edges = tEdge.buildSubstitutionEdges(synthDistribution2);
- let layer2Queue = new models.PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer2Edges);
+ let layer2Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer2Edges);
let eEdge = layer2Queue.dequeue();
assertEdgeChars(eEdge, 'e', 'e');
@@ -208,7 +209,7 @@ describe('Correction Distance Modeler', function() {
let layer3eEdges = eEdge.buildSubstitutionEdges(synthDistribution3);
let layer3hEdges = hEdge.buildSubstitutionEdges(synthDistribution3);
let layer3ehEdges = ehEdge.buildSubstitutionEdges(synthDistribution3);
- let layer3Queue = new models.PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer3eEdges.concat(layer3hEdges).concat(layer3ehEdges));
+ let layer3Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer3eEdges.concat(layer3hEdges).concat(layer3ehEdges));
// Find the first result with an actual word directly represented.
let bestEdge;
diff --git a/common/web/utils/src/index.ts b/common/web/utils/src/index.ts
index 1d1e540a80..ca7bb2ba55 100644
--- a/common/web/utils/src/index.ts
+++ b/common/web/utils/src/index.ts
@@ -23,6 +23,8 @@ export { default as TimeoutPromise, timedPromise } from "./timeoutPromise.js";
export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from "./surrogates.js";
+export { default as PriorityQueue, QueueComparator } from "./priority-queue.js"
+
// // Uncomment the following line and run the bundled output to verify successful
// // esbuild bundling of this submodule:
// console.log(Version.CURRENT.toString());
\ No newline at end of file
diff --git a/common/models/templates/src/priority-queue.ts b/common/web/utils/src/priority-queue.ts
similarity index 96%
rename from common/models/templates/src/priority-queue.ts
rename to common/web/utils/src/priority-queue.ts
index 6a1344fd89..f0b16f0e25 100644
--- a/common/models/templates/src/priority-queue.ts
+++ b/common/web/utils/src/priority-queue.ts
@@ -11,11 +11,11 @@
* - value > 0 if `b` should come before `a`
* - 0 if they should be treated equally.
*/
-export type Comparator = (a: Type, b: Type) => number;
+export type QueueComparator = (a: Type, b: Type) => number;
export default class PriorityQueue {
- private comparator: Comparator;
+ private comparator: QueueComparator;
private heap: Type[];
/**
@@ -29,8 +29,8 @@ export default class PriorityQueue {
* the first parameter should precede the second parameter.
* @param initialEntries
*/
- constructor(comparator: Comparator, initialEntries?: Type[]);
- constructor(arg1: Comparator | PriorityQueue, initialEntries?: Type[]) {
+ constructor(comparator: QueueComparator, initialEntries?: Type[]);
+ constructor(arg1: QueueComparator | PriorityQueue, initialEntries?: Type[]) {
if(typeof arg1 != 'function') {
this.comparator = arg1.comparator;
// Shallow-copies are fine.
diff --git a/common/models/templates/test/test-priority-queue.js b/common/web/utils/src/test/priorityQueue.js
similarity index 97%
rename from common/models/templates/test/test-priority-queue.js
rename to common/web/utils/src/test/priorityQueue.js
index 0887a19d22..0aa4c50b96 100644
--- a/common/models/templates/test/test-priority-queue.js
+++ b/common/web/utils/src/test/priorityQueue.js
@@ -3,7 +3,7 @@
*/
import { assert } from 'chai';
-import { PriorityQueue } from '@keymanapp/models-templates';
+import { PriorityQueue } from '@keymanapp/web-utils';
describe('Priority queue', function() {
it('can act as a min-heap', function () {
diff --git a/common/windows/build.sh b/common/windows/build.sh
index 714d633544..06f5dd0201 100755
--- a/common/windows/build.sh
+++ b/common/windows/build.sh
@@ -18,7 +18,7 @@ builder_describe "Keyman common Windows modules" \
builder_parse "$@"
-if [[ $BUILDER_OS != windows ]]; then
+if [[ $BUILDER_OS != win ]]; then
builder_echo grey "Platform is not Windows; skipping common/windows"
exit 0
fi
diff --git a/core/tests/unit/ldml/test_kmx_plus.cpp b/core/tests/unit/ldml/test_kmx_plus.cpp
index fc3f7e1ffe..5b14043b74 100644
--- a/core/tests/unit/ldml/test_kmx_plus.cpp
+++ b/core/tests/unit/ldml/test_kmx_plus.cpp
@@ -1,3 +1,4 @@
+#include
#include
#include "kmx/kmx_plus.h"
#include "kmx/kmx_xstring.h"
diff --git a/crowdin.yml b/crowdin.yml
index f8b9785bc3..3c95e40186 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -34,6 +34,7 @@ files:
languages_mapping:
# Prevent invalid region pap-rPAP. Leaving "in" for Indonesian
android_code:
+ el-polyton: b+el # TODO: figure out polyton variant
es-419: b+es+419
pap: pap
shu-latn-n: b+shu+latn
@@ -44,6 +45,7 @@ files:
languages_mapping:
# Prevent invalid region pap-rPAP
android_code:
+ el-polyton: b+el # TODO: figure out polyton variant
es-419: b+es+419
pap: pap
shu-latn-n: b+shu+latn
@@ -79,6 +81,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings
dest: /ios/engine/Localizable.strings
@@ -86,6 +89,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.stringsdict
dest: /ios/engine/Localizable.stringsdict
@@ -93,6 +97,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
- source: /ios/keyman/Keyman/Keyman/en.lproj/Localizable.strings
dest: /ios/app/Localizable.strings
@@ -100,6 +105,7 @@ files:
languages_mapping:
osx_code:
pt-PT: pt-PT.lproj
+ el-polyton: el.lproj
# Linux files
@@ -136,7 +142,7 @@ files:
osx_code:
pt-PT: pt-PT.lproj
- - source: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings
+ - source: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/en.lproj/KMKeyboardHelpWindowController.strings
dest: /mac/app/KMKeyboardHelpWindowController.strings
translation: /mac/Keyman4MacIM/Keyman4MacIM/KMKeyboardHelpWindow/%osx_code%/%original_file_name%
languages_mapping:
diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
index eaacec0439..d1c711368f 100644
--- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
+++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
@@ -493,6 +493,9 @@
CEA9670A24BEC8030035AACF /* EngineStateBundler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EngineStateBundler.swift; sourceTree = ""; };
CEA9670C24BEEFF80035AACF /* khmer_angkor update-base.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "khmer_angkor update-base.bundle"; sourceTree = ""; };
CEA9670E24BEF05A0035AACF /* Updates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Updates.swift; sourceTree = ""; };
+ CEAC2A222C2A503600C99ABD /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/ResourceInfoView.strings; sourceTree = ""; };
+ CEAC2A232C2A505700C99ABD /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = el; path = el.lproj/Localizable.stringsdict; sourceTree = ""; };
+ CEAC2A242C2A508000C99ABD /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; };
CEACC90125F07C81006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ResourceInfoView.strings; sourceTree = ""; };
CEACC90325F07CAF006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; };
CEACC90425F07CB6006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = ""; };
@@ -1205,6 +1208,7 @@
uk,
ru,
es,
+ el,
);
mainGroup = F243887314BBD43000A3E055;
productRefGroup = F243887F14BBD43000A3E055 /* Products */;
@@ -1602,6 +1606,7 @@
298566D32980D39F004ACA95 /* uk */,
298566DF2980E48C004ACA95 /* ru */,
CEFFECD72A417F2E00D58C36 /* es */,
+ CEAC2A232C2A505700C99ABD /* el */,
);
name = Localizable.stringsdict;
sourceTree = "";
@@ -1630,6 +1635,7 @@
298566D22980D39A004ACA95 /* uk */,
298566DE2980E486004ACA95 /* ru */,
CEFFECD62A417F2900D58C36 /* es */,
+ CEAC2A242C2A508000C99ABD /* el */,
);
name = Localizable.strings;
sourceTree = "";
@@ -1659,6 +1665,7 @@
298566D12980D390004ACA95 /* uk */,
298566DD2980E477004ACA95 /* ru */,
CEFFECD52A417F1300D58C36 /* es */,
+ CEAC2A222C2A503600C99ABD /* el */,
);
name = ResourceInfoView.xib;
sourceTree = "";
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/el.lproj/ResourceInfoView.strings b/ios/engine/KMEI/KeymanEngine/Classes/el.lproj/ResourceInfoView.strings
new file mode 100644
index 0000000000..6288e1237c
--- /dev/null
+++ b/ios/engine/KMEI/KeymanEngine/Classes/el.lproj/ResourceInfoView.strings
@@ -0,0 +1,2 @@
+/* Class = "UILabel"; text = "Scan this code to load this keyboard on another device"; ObjectID = "z2O-MT-IoV"; */
+"z2O-MT-IoV.text" = "Σαρῶστε αὐτὸν τὸν κωδικό γιὰ νὰ φορτώσετε αὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή";
diff --git a/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.strings b/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.strings
new file mode 100644
index 0000000000..47b3aa9de0
--- /dev/null
+++ b/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.strings
@@ -0,0 +1,257 @@
+/* A descriptive message used for errors when the app is already busy downloading */
+"alert-download-error-busy" = "Λήψη ἤδη σὲ ἐξέλιξη.";
+
+/* A descriptive message used when a download fails */
+"alert-download-error-detail" = "Παρουσιάσθηκε σφάλμα κατὰ τὴν λήψη ἢ ἐγκατάσταση.";
+
+/* Title for a "download failed" alert */
+"alert-download-error-title" = "Σφάλμα κατὰ τὴν λήψη";
+
+/* Title for a general alert about errors */
+"alert-error-title" = "Σφάλμα";
+
+/* A descriptive message used when no internet connection is detected */
+"alert-no-connection-detail" = "Ἀδυναμία προσβάσεως στὸν διακομιστὴ Κῆμαν. Παρακαλοῦμε ξαναδοκιμάστε ἀργότερα.";
+
+/* Title for a "no connection" alert */
+"alert-no-connection-title" = "Σφάλμα συνδέσεως";
+
+/* Short text for a 'back' navigational command. Used to return to the previous screen */
+"command-back" = "Πίσω";
+
+/* Short text for a 'cancel' command. Used to back out of a menu or install process without making changes */
+"command-cancel" = "Ἀκυρῶστε";
+
+/* Short text for a 'done' command. Used to back out of settings menus after changes have been made */
+"command-done" = "Ἕτοιμοι";
+
+/* Short text for an 'install' command. Used to confirm installation of a package. */
+"command-install" = "Ἐγκαταστῆστε";
+
+/* Short text for a 'next' navigational command. Used to advance to the next screen */
+"command-next" = "Ἑπόμενο";
+
+/* Short text for an 'OK' command. Used for some error alerts */
+"command-ok" = "OK";
+
+/* Short text for confirmining 'uninstall' commands. */
+"command-uninstall" = "Ἀπεγκαταστῆστε";
+
+/* Text for the command to uninstall a keyboard */
+"command-uninstall-keyboard" = "Ἀπεγκαταστῆστε πληκτρολόγιο";
+
+/* Confirmation text to display before uninstalling a keyboard */
+"command-uninstall-keyboard-confirm" = "Θὰ θέλατε νὰ ἀπεγκαταστήσετε αὐτὸ τὸ πληκτρολόγιο;";
+
+/* Text for the command to uninstall a lexical model */
+"command-uninstall-lexical-model" = "Ἀπεγκαταστῆτε λεξικό";
+
+/* Confirmation text to display before uninstalling a lexical model */
+"command-uninstall-lexical-model-confirm" = "Θὰ θέλατε νὰ ἀπεγκαταστήσετε αὐτὸ τὸ λεξικό;";
+
+/* Text for error when a keyboard cannot load properly */
+"error-loading-keyboard" = "Ἀδυνατοῦμε νὰ φορτώσομε τὸ πληκτρολόγιο ποὺ ζητήσατε";
+
+/* Text for error when a lexical model cannot load properly */
+"error-loading-lexical-model" = "Ἀδυνατοῦμε νὰ φορτώσομε τὸ λεξικὸ ποὺ ζητήσατε";
+
+/* Text for error when an installed file is unexpectedly missing */
+"error-missing-file" = "Δὲν βρέθηκε κάποιο ἀπαραίτητο ἀρχεῖο.";
+
+/* Text for error when an installed file is unexpectedly missing */
+"error-missing-file-critical" = "Κάποιο σημαντικὸ ἀρχεῖο δὲν εὑρέθη. Παρακαλοῦμε προσπαθῆστε νὰ ἐγκαταστήσετε ἐκ νέου τὴν ἐφαρμογή.";
+
+/* Text for errors in data received from search queries */
+"error-query-decoding" = "Αὐτὴν τὴν στιγμὴ ὑπάρχουν τεχνικὰ προβλήματα στὸν διακομιστή";
+
+/* A descriptive message used when a download fails */
+"error-query-general" = "Σημειώθηκε σφάλμα κατὰ τὴν ἐπικοινωνία μὲ τὸν διακομιστή";
+
+/* Text for error when a search query is unexpectedly empty */
+"error-query-no-data" = "Ὁ διακομιστὴς δὲν ἀποκρίθηκε";
+
+/* Text for general errors where not much information is known */
+"error-unknown" = "Σημειώθηκε ἀπροσδόκητο σφάλμα";
+
+/* Text for error when updating a resource: cannot download because no source is available */
+"error-update-no-link" = "Δὲν ὑπάρχει πηγαῖος κώδικας πρὸς ἐνημέρωσιν αὐτοῦ τοῦ πακέτου";
+
+/* Text for error when updating a resource: Keyman does not know how to update the resource */
+"error-update-not-managed" = "Ἀδυνατοῦμε νὰ ἐνημερώσομε πόρο μὴ διαχειριζόμενο ἀπὸ τὴν μηχανὴ Κῆμαν";
+
+/* Text for the command to open the help page of a keyboard, as shown on resource information views */
+"info-command-help-keyboard" = "Βοήθεια γιὰ τὸ πληκτρολόγιο";
+
+/* Text for the command to open the help page of a lexical model, as shown on resource information views */
+"info-command-help-lexical-model" = "Βοήθεια γιὰ τὸ λεξικό";
+
+/* Text label for displaying a keyboard's version, as shown on resource information views */
+"info-label-version-keyboard" = "Ἔκδοση πληκτρολογίου";
+
+/* Text label for displaying a lexical model's version, as shown on resource information views */
+"info-label-version-lexical-model" = "Ἔκδοση λεξικοῦ";
+
+/* Label used for the "package info" / readme tab with the package installation prompt on phones. */
+"installer-label-package-info" = "Πληροφορίες πακέτου";
+
+/* Label used for the language-selection tab with the package installation prompt on phones. */
+"installer-label-select-languages" = "Ἐπιλέξτε γλῶσσα(-ες)";
+
+/* Label used with a package's version, as seen within the package installation prompt. Example: "Version: 14.0.0" */
+"installer-label-version" = "Ἔκδοση: %@";
+
+/* Section header for languages supported by a package, as seen within the package installation prompt */
+"installer-section-available-languages" = "Διαθέσιμες γλῶσσες";
+
+/* Text for the help popup for changing keyboards with the globe key */
+"keyboard-help-change" = "Κτυπῆστε ἐδῶ γιὰ νὰ ἀλλάξετε πληκτρολόγιο";
+
+/* Text for the command to exit the Keyman keyboard in favor of other keyboards installed on the system */
+"keyboard-menu-exit" = "Κλεῖστε τὸ %@";
+
+/* Error installing a Keyman package - could not allocate a location to install it */
+"kmp-error-file-system" = "Σφάλμα κατά τὴν ἐγκατάσταση πακέτου - σφάλμα συστήματος ἀρχείων";
+
+/* Error installing a Keyman package - could not copy package files */
+"kmp-error-file-copying" = "Σφάλμα κατά τὴν ἐγκατάσταση πακέτου - ἀδυναμία ἀντιγραφῆς ἀπαραιτήτων ἀρχείων";
+
+/* Error opening a Keyman package - package is not valid */
+"kmp-error-invalid" = "Κατεστραμμένο ἀρχεῖο πακέτου.";
+
+/* Error opening a Keyman package - it does not exist / the specified location is wrong */
+"kmp-error-missing" = "Τὸ πακέτο ποὺ ὁρίσατε δὲν ὑπάρχει.";
+
+/* Error installing a Keyman package - expected resource (keyboard or dictionary) is missing */
+"kmp-error-missing-resource" = "Αὐτὸ τὸ πακέτο δὲν περιέχει τὸ πληκτρολόγιο ἢ λεξικὸ ποὺ ζητήσατε.";
+
+/* Error installing a Keyman package with a version of Keyman that does not support it */
+"kmp-error-unsupported-keyman-version" = "Αὐτὸ τὸ πακέτο ἀπαιτεῖ νεώτερη ἔκδοση τοῦ Κῆμαν.";
+
+/* Error opening a Keyman package - cannot parse contents */
+"kmp-error-no-metadata" = "Αὐτὸ τὸ πακέτο δὲν ἔχει κατασκευασθεῖ σωστά - ὑπάρχουν ἄγνωστα περιεχόμενα.";
+
+/* Error opening a Keyman package - package's contents are for desktop platforms only */
+"kmp-error-unsupported" = "Αὐτὸ τὸ πακέτο δὲν συμπεριλαμβάνει ὑποστήριξη γιὰ τὴν συσκευή σας.";
+
+/* Error opening a Keyman package - package contains unexpected resource (keyboard or dictionary) type */
+"kmp-error-wrong-type" = "Αὐτὸ τὸ πακέτο δὲν περιέχει τὸν ἀναμενόμενο τύπο πόρου.";
+
+/* Title for the Installed Languages menu */
+"menu-installed-languages-title" = "Ἐγκατεστημένες γλῶσσες";
+
+/* Section header for lexical models within a language-specific settings menu */
+"menu-langsettings-label-lexical-models" = "Λεξικά";
+
+/* Section header for keyboards within a language-specific settings menu */
+"menu-langsettings-section-keyboards" = "Πληκτρολόγια";
+
+/* Section header for the settings toggles within a language-specific settings menu */
+"menu-langsettings-section-settings" = "Ρυθμίσεις γλώσσας";
+
+/* Title for the language-specific settings menus */
+"menu-langsettings-title" = "Ρυθμίσεις %@";
+
+/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */
+"menu-langsettings-toggle-correct" = "Ἐνεργοποιῆστε τὶς διορθώσεις";
+
+/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */
+"menu-langsettings-toggle-predict" = "Ἐνεργοποιῆστε προβλέψεις";
+
+/* Help message for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */
+"menu-lexical-model-install-message" = "Θὰ θέλατε νὰ ἐγκαταστήσετε αὐτὸ τὸ λεξικό;";
+
+/* Title for a prompt that appears for confirming a lexical model download: language (1): lexical model (dictionary) name (2) */
+"menu-lexical-model-install-title" = "%1$@: %2$@";
+
+/* Text for an info alert indicating that no lexical models are available */
+"menu-lexical-model-none-message" = "Δὲν ὑπάρχουν διαθέσιμα λεξικά";
+
+/* Title for the lexical model menu, a submenu of the language-specific settings menus */
+"menu-lexical-model-title" = "%@ Λεξικά";
+
+/* Title for the keyboard picker */
+"menu-picker-title" = "Πληκτρολόγια";
+
+/* Primary text for the Settings menu option to report keyboard crashes */
+"menu-settings-error-report" = "Ἐπιτρέψτε ἀναφορὲς σφαλμάτων";
+
+/* Secondary text for the Settings menu option to report keyboard crashes */
+"menu-settings-error-report-description" = "Μπορεῖ νὰ ἀπαιτηθεῖ \"πλήρης πρόσβαση\"";
+
+/* Primary text for the Settings menu local-file package installation option */
+"menu-settings-install-from-file" = "Ἐγκαταστῆστε ἀπὸ ἀρχεῖο";
+
+/* Secondary text for the Settings menu local-file package installation option */
+"menu-settings-install-from-file-description" = "Ψάξτε ἀρχεῖα .kmp";
+
+/* Primary text for the Settings menu option that displays the iOS system menu options for the app's keyboard */
+"menu-settings-system-keyboard-menu" = "Ρυθμίσεις πληκτρολογίου συστήματος";
+
+/* Label for the "Show Banner" toggle on the main settings screen */
+"menu-settings-show-banner" = "Νὰ ἐμφανίζεται Banner";
+
+/* Label for the "Get Started" automatic display toggle seen in the Settings menu */
+"menu-settings-startup-get-started" = "Νὰ ἐμφανίζεται ἡ ἐναρκτήρια ὀθόνη κατὰ τὴν ἐκκίνηση";
+
+/* Title for the main Settings menu */
+"menu-settings-title" = "Ρυθμίσεις Κῆμαν";
+
+/* Secondary text showing current setting for spacebar caption - blank */
+"menu-settings-spacebar-hint-blank" = "Καμμία λεζάντα στὸ πλῆκτρο διαστήματος";
+
+/* Secondary text showing current setting for spacebar caption - keyboard */
+"menu-settings-spacebar-hint-keyboard" = "Νὰ ἐμφανίζεται στὸ στὸ πλῆκτρο διαστήματος τὸ ὄνομα τοῦ πληκτρολογίου";
+
+/* Secondary text showing current setting for spacebar caption - language */
+"menu-settings-spacebar-hint-language" = "Νὰ ἐμφανίζεται στὸ στὸ πλῆκτρο διαστήματος τὸ ὄνομα τῆς γλώσσας";
+
+/* Secondary text showing current setting for spacebar caption - language + keyboard */
+"menu-settings-spacebar-hint-languageKeyboard" = "Νὰ ἐμφανίζεται στὸ στὸ πλῆκτρο διαστήματος τὸ ὄνομα τοῦ πληκτρολογίου καὶ τῆς γλώσσας";
+
+/* Label for the "Spacebar Caption" item on the main settings screen */
+"menu-settings-spacebar-text" = "Λεζάντα Πλήκτρου Διαστήματος";
+
+/* Title for the "Spacebar Caption" settings screen */
+"menu-settings-spacebar-title" = "Λεζάντα Πλήκτρου Διαστήματος";
+
+/* Text showing name of spacebar caption - blank */
+"menu-settings-spacebar-item-blank" = "Κενό";
+
+/* Text showing name of spacebar caption - keyboard */
+"menu-settings-spacebar-item-keyboard" = "Πληκτρολόγιο";
+
+/* Text showing name of spacebar caption - language */
+"menu-settings-spacebar-item-language" = "Γλῶσσα";
+
+/* Text showing name of spacebar caption - language + keyboard */
+"menu-settings-spacebar-item-languageKeyboard" = "Γλῶσσα καὶ πληκτρολόγιο";
+
+/* Short text for notification: download failure for keyboard */
+"notification-download-failure-keyboard" = "Ἡ μεταφόρτωση τοῦ πληκτρολογίου ἀπέτυχε";
+
+/* Short text for notification: download failure for lexical model */
+"notification-download-failure-lexical-model" = "Ἡ μεταφόρτωση τοῦ λεξικοῦ ἀπέτυχε";
+
+/* Short text for notification: download success for keyboard */
+"notification-download-success-keyboard" = "Τὸ πληκτρολόγιο μεταφορτώθηκε ἐπιτυχῶς";
+
+/* Short text for notification: download success for lexical model */
+"notification-download-success-lexical-model" = "Τὸ λεξιλογικὸ μοντέλλο μεταφορτώθηκε ἐπιτυχῶς";
+
+/* Short text for notification: downloading a keyboard */
+"notification-downloading-keyboard" = "Μεταφορτώνεται πληκτρολόγιο\U2026";
+
+/* Short text for notification: downloading a lexical model */
+"notification-downloading-lexical-model" = "Μεταφορτώνεται λεξικό\U2026";
+
+/* Short text for notification: an update is available */
+"notification-update-available" = "Διαθέσιμη Ἐνημέρωση";
+
+/* Short text for notification: currently updating */
+"notification-update-processing" = "Ἐνημέρωση σὲ ἐξέλιξη\U2026";
+
+/* Text indicating success at installing new keyboards or dictionaries */
+"success-install" = "Ἐπιτυχὴς ἐγκατάσταση.";
+
+/* A title to use in alerts indicating 'success' at whatever task the user requested */
+"success-title" = "Ἐπιτυχία";
diff --git a/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.stringsdict b/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.stringsdict
new file mode 100644
index 0000000000..afaf5ae03d
--- /dev/null
+++ b/ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.stringsdict
@@ -0,0 +1,118 @@
+
+
+
+
+ menu-langsettings-lexical-model-count
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ Τὸ λεξικὸ %u ἐγκατεστάθη
+ other
+ %u λεξικὰ ἐγκατεστάθησαν
+
+
+ notification-update-failed
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ %u ἐνημέρωση ἀπέτυχε
+ other
+ %u ἐνημερώσεις ἀπέτυχαν
+
+
+ notification-update-success
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ %u ἐπιτυχὴς ἐνημέρωση
+ other
+ %u ἐπιτυχεῖς ἐνημερώσεις
+
+
+ settings-keyboards-installed-count
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ %u ἐγκατεστημένο πληκτρολόγιο
+ other
+ %u ἐγκατεστημένα πληκτρολόγια
+
+
+ settings-languages-installed-count
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ %u ἐγκατεστημένη γλῶσσα
+ other
+ %u ἐγκατεστημένες γλῶσσες
+
+
+ package-default-found-keyboards
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ Εὑρέθη %u πληκτρολόγιο στὸ πακέτο:
+ other
+ Εὑρέθησαν %u πληκτρολόγια στὸ πακέτο:
+
+
+ package-default-found-lexical-models
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ u
+ one
+ Εὑρέθη %u λεξικὸ στὸ πακέτο:
+ other
+ Εὑρέθησαν %u λεξικὰ στὸ πακέτο:
+
+
+
+
diff --git a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
index fdeae450f0..6211f26573 100644
--- a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
+++ b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 52;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -332,6 +332,7 @@
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageBrowserViewController.swift; sourceTree = ""; };
CE80AD32257F2B4A008D2150 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE98E6BD2615593300F3F2C0 /* ff */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ff; path = ff.lproj/Localizable.strings; sourceTree = ""; };
+ CEAC2A272C2A50E100C99ABD /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; };
CEACC90E25F07D58006EAB45 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; };
CEACC90F25F07D5A006EAB45 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/Localizable.strings; sourceTree = ""; };
CEACC91225F07D77006EAB45 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; };
@@ -793,6 +794,7 @@
sv,
uk,
ru,
+ el,
);
mainGroup = 98ABADA7176935E400B62590;
productRefGroup = 98ABADB1176935E400B62590 /* Products */;
@@ -1106,6 +1108,7 @@
298566C42980C493004ACA95 /* sv */,
298566D02980D354004ACA95 /* uk */,
298566DC2980E416004ACA95 /* ru */,
+ CEAC2A272C2A50E100C99ABD /* el */,
);
name = Localizable.strings;
sourceTree = "";
diff --git a/ios/keyman/Keyman/Keyman/el.lproj/Localizable.strings b/ios/keyman/Keyman/Keyman/el.lproj/Localizable.strings
new file mode 100644
index 0000000000..2df136fccc
--- /dev/null
+++ b/ios/keyman/Keyman/Keyman/el.lproj/Localizable.strings
@@ -0,0 +1,81 @@
+/* Title for the bookmarks menu within the embedded browser */
+"browser-bookmarks-add-title" = "Προσθέστε σελιδοδείκτη";
+
+/* Title for the bookmarks menu within the embedded browser */
+"browser-bookmarks-none" = "Δὲν ὑπάρχουν σελιδοδεῖκτες";
+
+/* Title for the bookmarks menu within the embedded browser */
+"browser-bookmarks-title" = "Σελιδοδεῖκτες";
+
+/* Used to confirm a user's desire to install a package */
+"confirm-install" = "Ἐγκαταστῆστε";
+
+/* The label for the toggle to stop automatically showing the "Get Started" tutorial popup. */
+"disable-get-started" = "Νὰ μὴν ξαναεμφανισθεῖ";
+
+/* Indicates an error opening a web page requested by a user */
+"error-opening-page" = "Ἀδυναμία ἀνοίγματος σελίδας";
+
+/* Long-form used when installing a font in order to display a language properly. */
+"font-install-description" = "Πατῆστε τὸ πλῆκτρο ἐγκατάστασης γιὰ νὰ ἐμφανίζεται ἡ %@ σωστὰ σὲ ὅλες τὶς ἐφαρμογές σας";
+
+/* The name of the iOS Settings menu option for keyboards */
+"ios-settings-keyboards" = "Πληκτρολόγια";
+
+/* The name of the iOS Settings menu option for giving the keyboard full access. (The menu entry
+underneath the app's name within the app-specific keyboard menu.) */
+"ios-settings-allow-full-access" = "Ἐπιστρέψτε πλήρη πρόσβαση";
+
+/* Short-form used when installing a font in order to display a language properly. */
+"language-for-font" = "Γραμματοσειρά %@";
+
+/* Used for 'add' options within menus */
+"menu-add" = "Προσθήκη";
+
+/* Used to indicate a sequence of menu options that a user needs to copy. May be chained. Example: "Keyboards (1) > Enable Keyman (2)" */
+"menu-breadcrumbing" = "%1$@ > %2$@";
+
+/* Used to exit a menu without choosing an option */
+"menu-cancel" = "Ἀκύρωση";
+
+/* Menu option that erases all previously-typed text. */
+"menu-clear-text" = "Διαγραφὴ κειμένου";
+
+/* Menu option that displays a list designed to help users start using the app */
+"menu-get-started" = "Ξεκινῆστε";
+
+/* Menu option that displays help for the app */
+"menu-help" = "Πληροφορίες";
+
+/* Menu option that displays the main screen's drop-down menu */
+"menu-more" = "Περισσότερα";
+
+/* Menu option used to edit keyboard and dictionary settings */
+"menu-settings" = "Ρυθμίσεις";
+
+/* Menu option that displays the iOS Share menu */
+"menu-share" = "Μοιρασθῆτε";
+
+/* Menu option that displays an embedded web browser. */
+"menu-show-browser" = "Φυλλομετρητής";
+
+/* Menu option used to control in-app font size. */
+"menu-text-size" = "Μέγεθος κειμένου";
+
+/* Used to describe the current font size */
+"text-size-label" = "Μέγεθος κειμένου: %i";
+
+/* Text to indicate that a user should set a toggle within iOS Settings to 'active'. */
+"toggle-to-enable" = "Ἐνεργοποιῆστε %@";
+
+/* First option on the Get Started tutorial - Add a keyboard for your language */
+"tutorial-add-keyboard" = "Προσθέστε πληκτρολόγιο γιὰ τὴν γλῶσσα σας";
+
+/* Third option on the Get Started tutorial - Displays app help. */
+"tutorial-show-help" = "Περισσότερες πληροφορίες";
+
+/* Second option on the Get Started tutorial - Set up Keyman as system-wide keyboard */
+"tutorial-system-keyboard" = "Ὁρίστε τὸ Κῆμαν ὡς παν-συστημικὸ πληκτρολόγιο";
+
+/* Used to display app version (as in \"Version: 1.0.2\" */
+"version-label" = "Ἔκδοση: %@";
diff --git a/web/src/engine/osk/src/banner/suggestionBanner.ts b/web/src/engine/osk/src/banner/suggestionBanner.ts
index d35504e5e2..f81936b1ad 100644
--- a/web/src/engine/osk/src/banner/suggestionBanner.ts
+++ b/web/src/engine/osk/src/banner/suggestionBanner.ts
@@ -13,7 +13,7 @@ import {
import { BANNER_GESTURE_SET } from './bannerGestureSet.js';
-import { DeviceSpec, Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor';
+import { DeviceSpec, Keyboard, KeyboardProperties, timedPromise } from '@keymanapp/keyboard-processor';
import { Banner } from './banner.js';
import { ParsedLengthStyle } from '../lengthStyle.js';
import { getFontSizeStyle } from '../fontSizeUtils.js';
@@ -175,6 +175,7 @@ export class BannerSuggestion {
}
this.currentWidth = this.collapsedWidth;
+ this.highlight(suggestion?.autoAccept);
this.updateLayout();
}
@@ -553,6 +554,16 @@ export class SuggestionBanner extends Banner {
return;
}
+ const autoselection = this._predictionContext.selected;
+ this._predictionContext.selected = null;
+ if(autoselection) {
+ this.options.forEach((entry) => {
+ if(entry.suggestion == autoselection) {
+ entry.highlight(false);
+ };
+ });
+ }
+
this.scrollState = new BannerScrollState(source.currentSample, this.container.scrollLeft);
const suggestion = source.baseItem;
@@ -585,6 +596,31 @@ export class SuggestionBanner extends Banner {
}
const terminationHandler = () => {
+ const currentSuggestions = this.currentSuggestions;
+ // First, schedule reselection of the autoselected suggestion.
+ // We shouldn't do it synchronously, as suggestion acceptance triggers
+ // _after_ this handler is called.
+ // Delaying via the task queue is enough to get the desired order of events.
+ timedPromise(0).then(async () => {
+ // If the suggestion list instance has changed, our state has changed; do
+ // not reselect.
+ if(currentSuggestions != this.currentSuggestions) {
+ return;
+ }
+
+ // The suggestions are still current? Then restore the original
+ // auto-correct suggestion and its highlighting.
+ this._predictionContext.selected = autoselection;
+ if(autoselection) {
+ for(let entry of this.options) {
+ if(entry.suggestion == autoselection) {
+ entry.highlight(true);
+ break;
+ };
+ }
+ }
+ });
+
if(sourceTracker.suggestion) {
clearSelection(sourceTracker.suggestion);
sourceTracker.suggestion = null;
@@ -603,7 +639,15 @@ export class SuggestionBanner extends Banner {
// The actual result comes in via the sequence's `stage` event.
sequence.once('stage', (result) => {
const suggestion = result.item; // Should also == sourceTracker.suggestion.
- if(suggestion && !this.scrollState.hasScrolled) {
+ // 1. A valid suggestion has been selected
+ // 2. The user wasn't scrolling the banner. (If they were, they likely
+ // need to lift their finger to select a newly-visible suggestion!)
+ // 3. The suggestions themselves are still valid; avoid suggestion
+ // double-application or similar.
+ if(suggestion && !this.scrollState.hasScrolled && this.currentSuggestions.length > 0) {
+ // Invalidate the suggestions internally, but don't visually update;
+ // this will avoid banner-flicker.
+ this.currentSuggestions = [];
this.predictionContext.accept(suggestion.suggestion).then(() => {
// Reset the scroll state
this.container.scrollLeft = this.isRTL ? this.container.scrollWidth : 0;
diff --git a/windows/src/.gitignore b/windows/src/.gitignore
index 3ac3da4ea5..350b7e4bdb 100644
--- a/windows/src/.gitignore
+++ b/windows/src/.gitignore
@@ -16,7 +16,7 @@
# These files are generated during build
engine/keyman32/KEYMAN32.res
-engine/keyman64/Keyman64.res
+engine/keyman32/Keyman64.res
engine/keymanx64/keymanx64.res
engine/mcompile/Release/
tds_file.txt
diff --git a/windows/src/engine/.gitignore b/windows/src/engine/.gitignore
index 13c59ae0a5..e17dcb3de7 100644
--- a/windows/src/engine/.gitignore
+++ b/windows/src/engine/.gitignore
@@ -3,11 +3,6 @@ keyman32/keyman-debug-etw.h
keyman32/keyman-debug-etw.rc
keyman32/keyman-debug-etw.res
keyman32/keyman-debug-etwTEMP.BIN
-keyman64/MSG00001.bin
-keyman64/keyman-debug-etw.h
-keyman64/keyman-debug-etw.rc
-keyman64/keyman-debug-etw.res
-keyman64/keyman-debug-etwTEMP.BIN
mcompile/Debug
# Visual Studio packages for test
diff --git a/windows/src/engine/README.md b/windows/src/engine/README.md
index 38851aa11e..5a4f1ab2b1 100644
--- a/windows/src/engine/README.md
+++ b/windows/src/engine/README.md
@@ -11,13 +11,9 @@ The installation merge module database for the Keyman Engine.
Host application for the Keyman Engine. Shows the Keyman icon, Keyman menu, OSK, and handles language switching etc, as well as running the windows hooks to capture and translate keystrokes.
-## keyman32 - keyman32.dll
+## keyman32 - keyman32.dll, keyman64.dll
-Main keyboard processing library and hook library (32 bit).
-
-## keyman64 - keyman64.dll
-
-Main keyboard processing library and hook library (64 bit).
+Main keyboard processing library and hook library (x86, x64).
## keymanmc - keymanmc.dll
diff --git a/windows/src/engine/build.sh b/windows/src/engine/build.sh
index 6cc993758a..9c9a6d2d0f 100755
--- a/windows/src/engine/build.sh
+++ b/windows/src/engine/build.sh
@@ -17,8 +17,7 @@ builder_describe \
\
":insthelper Installation helper module" \
":keyman Main host process (32 bit)" \
- ":keyman32 Keystroke processing engine (32 bit)" \
- ":keyman64 Keystroke processing engine (64 bit)" \
+ ":keyman32 Keystroke processing engine (all archs)" \
":keymanmc Message library" \
":keymanx64 Host process (64 bit)" \
":kmcomapi COM API library" \
diff --git a/windows/src/engine/engine.sln b/windows/src/engine/engine.sln
index 81da027de0..5c641463c8 100644
--- a/windows/src/engine/engine.sln
+++ b/windows/src/engine/engine.sln
@@ -3,9 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31313.79
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Keyman32", "keyman32\Keyman32.vcxproj", "{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman64", "keyman64\keyman64.vcxproj", "{D1115FB2-230D-4FD8-90B9-982E49953D18}"
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman32", "keyman32\Keyman32.vcxproj", "{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keymanx64", "keymanx64\keymanx64.vcxproj", "{A34650EA-D6E8-4229-8091-6BF1443565B5}"
EndProject
@@ -29,18 +27,12 @@ Global
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.ActiveCfg = Debug|Win32
- {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.Build.0 = Debug|Win32
+ {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.ActiveCfg = Release|x64
+ {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.Build.0 = Release|x64
{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|x64.ActiveCfg = Debug|Win32
{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.ActiveCfg = Release|Win32
{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.Build.0 = Release|Win32
{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|x64.ActiveCfg = Release|Win32
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|Win32.ActiveCfg = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|x64.ActiveCfg = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|x64.Build.0 = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|Win32.ActiveCfg = Release|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|x64.ActiveCfg = Release|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|x64.Build.0 = Release|x64
{A34650EA-D6E8-4229-8091-6BF1443565B5}.Debug|Win32.ActiveCfg = Debug|x64
{A34650EA-D6E8-4229-8091-6BF1443565B5}.Debug|x64.ActiveCfg = Debug|x64
{A34650EA-D6E8-4229-8091-6BF1443565B5}.Debug|x64.Build.0 = Debug|x64
diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp
index 93ffcb445d..9f4062a59e 100644
--- a/windows/src/engine/keyman32/appint/aiTIP.cpp
+++ b/windows/src/engine/keyman32/appint/aiTIP.cpp
@@ -191,9 +191,6 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM
_td->TIPFUpdateable = Updateable;
_td->TIPFPreserved = Preserved; // I4290
- _td->state.msg.hwnd = GetFocus();
- _td->state.msg.lParam = 0;
- _td->state.msg.message = wm_keymankeydown;
_td->state.vkey = (WORD) wParam;
_td->state.isDown = !isUp;
diff --git a/windows/src/engine/keyman32/build.sh b/windows/src/engine/keyman32/build.sh
index b171dd9cc8..e21b45ae26 100755
--- a/windows/src/engine/keyman32/build.sh
+++ b/windows/src/engine/keyman32/build.sh
@@ -5,10 +5,11 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
-builder_describe "Keystroke processing engine (32 bit)" \
+builder_describe "Keystroke processing engine" \
@/common/include \
- @/core:x86 \
- clean configure build test publish install
+ @/core \
+ clean configure build test publish install \
+ :x86 :x64
builder_parse "$@"
@@ -16,19 +17,26 @@ builder_parse "$@"
source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
WIN32_TARGET="$WIN32_TARGET_PATH/keyman32.dll"
+X64_TARGET="$X64_TARGET_PATH/keyman64.dll"
builder_describe_outputs \
- configure:project /resources/build/win/delphi_environment_generated.inc.sh \
- build:project /windows/src/engine/keyman32/$WIN32_TARGET
+ configure /resources/build/win/delphi_environment_generated.inc.sh \
+ build:x86 /windows/src/engine/keyman32/$WIN32_TARGET \
+ build:x64 /windows/src/engine/keyman32/$X64_TARGET \
#-------------------------------------------------------------------------------------------------------------------
-function do_clean() {
- vs_msbuild keyman32.vcxproj //t:Clean
+function do_clean_x86() {
+ vs_msbuild keyman32.vcxproj //t:Clean //p:Platform=Win32
clean_windows_project_files
}
-function do_build() {
+function do_clean_x64() {
+ vs_msbuild keyman32.vcxproj //t:Clean //p:Platform=x64
+ clean_windows_project_files
+}
+
+function do_build_x86() {
create-windows-output-folders
build_version.res
vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=Win32"
@@ -36,21 +44,43 @@ function do_build() {
cp "$WIN32_TARGET_PATH/keyman32.pdb" "$WINDOWS_DEBUGPATH_ENGINE"
}
-function do_publish() {
+function do_build_x64() {
+ create-windows-output-folders
+ run_in_vs_env rc version64.rc
+ vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=x64"
+ cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE"
+ cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE"
+}
+
+function do_publish_x86() {
wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/keyman32.dll"
wrap-symstore "$WINDOWS_PROGRAM_ENGINE/keyman32.dll" //t keyman-engine-windows
wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/keyman32.pdb" //t keyman-engine-windows
}
-function do_install() {
+function do_publish_x64() {
+ wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/keyman64.dll"
+ wrap-symstore "$WINDOWS_PROGRAM_ENGINE/keyman64.dll" //t keyman-engine-windows
+ wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/keyman64.pdb" //t keyman-engine-windows
+}
+
+function do_install_x86() {
cp "$WINDOWS_PROGRAM_ENGINE/keyman32.dll" "$INSTALLPATH_KEYMANENGINE/keyman32.dll"
echo "You may want to manually tweak keyman-debug-etw.man and fill in $INSTALLPATH_KEYMANENGINE/keyman32.dll"
echo "and then run wevtutil im keyman-debug-etw.man to get the latest event tracing"
}
-builder_run_action clean:project do_clean
-builder_run_action configure:project configure_windows_build_environment
-builder_run_action build:project do_build
+function do_install_x64() {
+ cp "$WINDOWS_PROGRAM_ENGINE/keyman64.dll" "$INSTALLPATH_KEYMANENGINE/keyman64.dll"
+}
+
+builder_run_action clean:x86 do_clean_x86
+builder_run_action clean:x64 do_clean_x64
+builder_run_action configure configure_windows_build_environment
+builder_run_action build:x86 do_build_x86
+builder_run_action build:x64 do_build_x64
# builder_run_action test:project do_test
-builder_run_action publish:project do_publish
-builder_run_action install:project do_install
+builder_run_action publish:x86 do_publish_x86
+builder_run_action publish:x64 do_publish_x64
+builder_run_action install:x86 do_install_x86
+builder_run_action install:x64 do_install_x64
diff --git a/windows/src/engine/keyman32/calldll.cpp b/windows/src/engine/keyman32/calldll.cpp
index 942988a5c6..c91fab64a9 100644
--- a/windows/src/engine/keyman32/calldll.cpp
+++ b/windows/src/engine/keyman32/calldll.cpp
@@ -270,7 +270,7 @@ extern "C" uint8_t IM_CallBackCore(km_core_state *km_state, uint32_t UniqueStore
SendDebugMessageFormat(0, sdmKeyboard, 0, "IM_CallBackCore: td TIPFUpdatable about to call function [%s]", imdh->name);
LogContext(_td->lpActiveKeyboard->lpCoreKeyboardState, CONTEXT_CORE);
LogContext(_td->lpActiveKeyboard->lpCoreKeyboardState, CONTEXT_INT);
- (*imdh->function)(_td->state.msg.hwnd, _td->state.vkey, _td->state.charCode, Globals::get_ShiftState());
+ (*imdh->function)(GetFocus(), _td->state.vkey, _td->state.charCode, Globals::get_ShiftState());
//SendDebugMessageFormat(0, sdmKeyboard, 0, "IM_CallBackCore: Exit");
return TRUE;
diff --git a/windows/src/engine/keyman32/globals.h b/windows/src/engine/keyman32/globals.h
index 8f1dfc4c90..90e172312b 100644
--- a/windows/src/engine/keyman32/globals.h
+++ b/windows/src/engine/keyman32/globals.h
@@ -252,8 +252,6 @@ extern UINT
wm_keyman, // user message - ignore msg
wm_keyman_control, // messages to main Keyman window - replaces WM_USER+*
wm_keyman_control_internal, // messages to all windows to notify of changes to Keyman // I4412
- wm_keymankeydown,
- wm_keymankeyup,
wm_keyman_grabwindowproc,
wm_keyman_refresh,
wm_kmgetactivekeymanid,
diff --git a/windows/src/engine/keyman32/hotkeys.cpp b/windows/src/engine/keyman32/hotkeys.cpp
index cead296f8d..ac16a3698e 100644
--- a/windows/src/engine/keyman32/hotkeys.cpp
+++ b/windows/src/engine/keyman32/hotkeys.cpp
@@ -26,9 +26,7 @@
// I5136
#include "pch.h"
-#ifdef _WIN64
-#error hotkeys.cpp is not required for win64.
-#endif
+#ifndef _WIN64
Hotkeys *g_Hotkeys = NULL; // I4326
@@ -141,3 +139,5 @@ Hotkey *Hotkeys::GetHotkey(DWORD hotkey)
return &m_hotkeys[i];
return NULL;
}
+
+#endif
diff --git a/windows/src/engine/keyman32/k32_dbg.cpp b/windows/src/engine/keyman32/k32_dbg.cpp
index 9492ee2f0e..e5865c8e8d 100644
--- a/windows/src/engine/keyman32/k32_dbg.cpp
+++ b/windows/src/engine/keyman32/k32_dbg.cpp
@@ -225,13 +225,7 @@ void DebugMessage(LPMSG msg, WPARAM wParam) // I2908
{
char ds[256];
- if(msg->message == wm_keymankeydown)
- wsprintf(ds, "DebugMessage(%x, wm_keymankeydown: %s lParam: %X) [message flags: %x time: %d]", PtrToInt(msg->hwnd),
- Debug_VirtualKey((WORD) msg->wParam), (unsigned int) msg->lParam, wParam, (int) msg->time);
- else if(msg->message == wm_keymankeyup)
- wsprintf(ds, "DebugMessage(%x, wm_keymankeyup: %s lParam: %X) [message flags: %x time: %d]", PtrToInt(msg->hwnd),
- Debug_VirtualKey((WORD) msg->wParam), (unsigned int) msg->lParam, wParam, (int) msg->time);
- else if (msg->message == WM_KEYMAN_KEY_EVENT)
+ if (msg->message == WM_KEYMAN_KEY_EVENT)
wsprintf(ds, "DebugMessage(%x, WM_KEYMAN_KEY_EVENT: %s lParam: %X) [message flags: %x time: %d]", PtrToInt(msg->hwnd),
Debug_VirtualKey((WORD)msg->wParam), (unsigned int) msg->lParam, wParam, (int) msg->time);
else if(msg->message == WM_KEYDOWN || msg->message == WM_KEYUP || msg->message == WM_SYSKEYDOWN || msg->message == WM_SYSKEYUP)
diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp
index fb82735377..e44758183e 100644
--- a/windows/src/engine/keyman32/k32_globals.cpp
+++ b/windows/src/engine/keyman32/k32_globals.cpp
@@ -73,8 +73,6 @@ UINT
//TODO: consolidate these messages -- they are probably not all required now
wm_keyman = 0, // user message - ignore msg // I3594
- wm_keymankeydown = 0,
- wm_keymankeyup = 0,
wm_keyman_grabwindowproc = 0,
wm_keyman_refresh = 0,
wm_kmgetactivekeymanid = 0,
diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp
index cc5152cf58..79c46ebc59 100644
--- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp
+++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp
@@ -26,9 +26,7 @@
#include "serialkeyeventserver.h"
#include "kbd.h" /* DDK kbdlayout */
-#ifdef _WIN64
-#error k32_lowlevelkeyboardhook.cpp is not required for win64.
-#endif
+#ifndef _WIN64
BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState);
@@ -329,3 +327,5 @@ BOOL ProcessHotkey(UINT vkCode, BOOL isUp, DWORD ShiftState) {
return TRUE;
}
+
+#endif
diff --git a/windows/src/engine/keyman32/keyman32.cpp b/windows/src/engine/keyman32/keyman32.cpp
index 9dbd692525..e51a9b8617 100644
--- a/windows/src/engine/keyman32/keyman32.cpp
+++ b/windows/src/engine/keyman32/keyman32.cpp
@@ -190,8 +190,6 @@ void DoChangeWindowMessageFilter()
}
DoCWMF(wm_keyman); // I3594
- DoCWMF(wm_keymankeydown);
- DoCWMF(wm_keymankeyup);
DoCWMF(wm_keyman_grabwindowproc);
DoCWMF(wm_keyman_refresh);
DoCWMF(wm_kmgetactivekeymanid);
@@ -255,8 +253,6 @@ BOOL InitialiseProcess(HWND hwnd)
Initialise_Flag_ShouldSerializeInput();
wm_keyman = RegisterWindowMessage(RWM_KEYMAN);
- wm_keymankeydown = RegisterWindowMessage("WM_KEYMANKEYDOWN");
- wm_keymankeyup = RegisterWindowMessage("WM_KEYMANKEYUP");
wm_keyman_grabwindowproc = RegisterWindowMessage("WM_KEYMAN_GRABWINDOWPROC");
wm_keyman_refresh = RegisterWindowMessage("WM_KEYMANREFRESH");
wm_kmgetactivekeymanid = RegisterWindowMessage("WM_KEYMAN_GETACTIVEKEYMANID");
diff --git a/windows/src/engine/keyman32/keyman32.rc b/windows/src/engine/keyman32/keyman32.rc
index 06a3db3fef..624b24e9f2 100644
--- a/windows/src/engine/keyman32/keyman32.rc
+++ b/windows/src/engine/keyman32/keyman32.rc
@@ -1,4 +1,3 @@
-
#include "version.rc"
/////////////////////////////////////////////////////////////////////////////
diff --git a/windows/src/engine/keyman32/keyman32.vcxproj b/windows/src/engine/keyman32/keyman32.vcxproj
index 021aa55d65..8f75aeb126 100644
--- a/windows/src/engine/keyman32/keyman32.vcxproj
+++ b/windows/src/engine/keyman32/keyman32.vcxproj
@@ -5,10 +5,18 @@
Debug
Win32
+
+ Debug
+ x64
+
Release
Win32
+
+ Release
+ x64
+
{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}
@@ -22,11 +30,21 @@
false
v142
+
+ DynamicLibrary
+ false
+ v142
+
DynamicLibrary
false
v142
+
+ DynamicLibrary
+ false
+ v142
+
@@ -34,33 +52,65 @@
+
+
+
+
+
+
+
+
<_ProjectFileVersion>10.0.30319.1
$(ProjectDir)bin\$(Platform)\$(Configuration)\
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
$(ProjectDir)obj\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
false
+ false
$(ProjectDir)bin\$(Platform)\$(Configuration)\
+ $(ProjectDir)bin\$(Platform)\$(Configuration)\
$(ProjectDir)obj\$(Platform)\$(Configuration)\
+ $(ProjectDir)obj\$(Platform)\$(Configuration)\
true
+ true
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
+ AllRules.ruleset
+
+
$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath)
$(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath)
+ keyman32
+
+
+ $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
+ $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath)
+ keyman64
$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath)
$(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath)
+ keyman32
+
+
+ $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
+ $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath)
+ keyman64
@@ -120,6 +170,62 @@
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ ./Keyman32.tlb
+
+
+
+
+ MaxSpeed
+ OnlyExplicitInline
+ .\;..\..\global\inc;%(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
+ true
+
+
+ MultiThreaded
+ true
+ Use
+ pch.h
+ All
+ Level4
+ true
+ true
+ ProgramDatabase
+
+
+ NDEBUG;%(PreprocessorDefinitions)
+ 0x0409
+
+
+ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions)
+ libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies)
+ true
+ keyman64.def
+ true
+ true
+ false
+ Windows
+ true
+ true
+ 0x1c100000
+ true
+ true
+ ..\..\..\lib\keyman64.lib
+
+
+ true
+ ./Keyman64.bsc
+
+
+
+
+
+
_DEBUG;%(PreprocessorDefinitions)
@@ -173,124 +279,92 @@
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ ./Keyman32.tlb
+
+
+
+
+ Disabled
+ .\;..\..\global\inc;.;%(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
+
+
+ MultiThreadedDebug
+ Use
+ pch.h
+ All
+ true
+ Level4
+ true
+ true
+ ProgramDatabase
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ 0x0409
+
+
+ /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions)
+ libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies)
+ true
+ keyman64.def
+ true
+ true
+ Windows
+ 0x1C100000
+ true
+ true
+ ..\..\..\lib\keyman64.lib
+
+
+ true
+ ./Keyman32.bsc
+
+
+
+
+
+
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
+
+
+
+
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
+
+
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
+
+
+
+
+
+
+
Create
+ Create
Create
+ Create
@@ -298,60 +372,58 @@
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
+
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
-
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
- %(AdditionalIncludeDirectories)
- %(PreprocessorDefinitions)
-
+
+
+
Document
false
+ false
false
+ false
mc %(FullPath)
+ mc %(FullPath)
Compiling keyman-debug-etw
+ Compiling keyman-debug-etw
%(Filename).rc;%(Filename.h);MSG00001.bin
+ %(Filename).rc;%(Filename.h);MSG00001.bin
mc %(FullPath)
+ mc %(FullPath)
Compiling keyman-debug-etw
+ Compiling keyman-debug-etw
%(Filename).rc;%(Filename.h);MSG00001.bin
+ %(Filename).rc;%(Filename.h);MSG00001.bin
Designer
-
+
+ true
+ true
+
+
+ true
+ true
+
%(PreprocessorDefinitions)
+ %(PreprocessorDefinitions)
%(PreprocessorDefinitions)
+ %(PreprocessorDefinitions)
+ true
+ true
+
+
+ true
+ true
diff --git a/windows/src/engine/keyman32/keyman32.vcxproj.filters b/windows/src/engine/keyman32/keyman32.vcxproj.filters
index 0f16750b7c..5627c72b04 100644
--- a/windows/src/engine/keyman32/keyman32.vcxproj.filters
+++ b/windows/src/engine/keyman32/keyman32.vcxproj.filters
@@ -152,6 +152,9 @@
Resource Files
+
+ Source Files
+
@@ -160,6 +163,9 @@
Source Files
+
+ Source Files
+
diff --git a/windows/src/engine/keyman32/keyman64.def b/windows/src/engine/keyman32/keyman64.def
new file mode 100644
index 0000000000..9f46c08cb8
--- /dev/null
+++ b/windows/src/engine/keyman32/keyman64.def
@@ -0,0 +1,45 @@
+LIBRARY KEYMAN64
+
+EXPORTS
+ Keyman_GetInitialised
+ Keyman_Initialise
+ Keyman_Exit
+ Keyman_GetLastActiveWindow
+ Keyman_GetLastFocusWindow
+ KMSetOutput
+ KMGetContext
+ KMQueueAction
+ KMDisplayIM
+ KMHideIM
+ KMGetActiveKeyboard
+ KMGetKeyboardPath
+
+ TIPActivateEx
+ TIPActivateKeyboard
+ TIPProcessKey
+ TIPIsKeymanRunning
+ GetKeyboardPreservedKeys
+
+ GetActiveKeymanID
+ Keyman_RegisterControllerWindow
+ Keyman_UnregisterControllerWindow
+ Keyman_RestartEngine
+
+ Keyman_SendMasterController
+ Keyman_PostControllers
+ Keyman_PostMasterController
+
+ Keyman_StartExit
+ Keyman_ResetInitialisation
+
+ Keyman_WriteDebugEvent
+ Keyman_WriteDebugEventW
+
+ Keyman_Diagnostic
+
+ Keyman_RegisterMasterController
+ Keyman_UnregisterMasterController
+ Keyman_RegisterControllerThread
+ Keyman_UnregisterControllerThread
+
+ SetCustomPostKeyCallback
diff --git a/windows/src/engine/keyman32/keyman64.rc b/windows/src/engine/keyman32/keyman64.rc
new file mode 100644
index 0000000000..c4fe9ce92e
--- /dev/null
+++ b/windows/src/engine/keyman32/keyman64.rc
@@ -0,0 +1 @@
+#include "version64.rc"
diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h
index 14179f977a..f72b059702 100644
--- a/windows/src/engine/keyman32/keymanengine.h
+++ b/windows/src/engine/keyman32/keymanengine.h
@@ -97,7 +97,6 @@ typedef struct tagINTKEYBOARDINFO
typedef struct tagKMSTATE
{
BOOL NoMatches;
- MSG msg;
WORD vkey; // I934
WCHAR charCode; // I4582
BOOL windowunicode; // I4287
diff --git a/windows/src/engine/keyman32/kmhook_getmessage.cpp b/windows/src/engine/keyman32/kmhook_getmessage.cpp
index d4d1177ced..dccd6eba91 100644
--- a/windows/src/engine/keyman32/kmhook_getmessage.cpp
+++ b/windows/src/engine/keyman32/kmhook_getmessage.cpp
@@ -73,21 +73,21 @@ void GetCapsAndNumlockState();
/*
BOOL SysTabCtrl(HWND hwnd)
{
- char buf[64];
- GetClassName(hwnd, buf, 64);
- return !strcmpi(buf, "SysTabControl32");
+ char buf[64];
+ GetClassName(hwnd, buf, 64);
+ return !strcmpi(buf, "SysTabControl32");
}
*/
BOOL IsMessageEquivalent(LPMSG m1, LPMSG m2)
{
- return m1->hwnd == m2->hwnd &&
- m1->lParam == m2->lParam &&
- m1->message == m2->message &&
- m1->time == m2->time &&
- m1->pt.x == m2->pt.x &&
- m1->pt.y == m2->pt.y &&
- m1->wParam == m2->wParam;
+ return m1->hwnd == m2->hwnd &&
+ m1->lParam == m2->lParam &&
+ m1->message == m2->message &&
+ m1->time == m2->time &&
+ m1->pt.x == m2->pt.x &&
+ m1->pt.y == m2->pt.y &&
+ m1->wParam == m2->wParam;
}
LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam);
@@ -108,16 +108,16 @@ LRESULT CALLBACK kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
}
#endif
- return res;
+ return res;
}
LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
- LPMSG mp;
+ LPMSG mp;
- mp = (LPMSG)lParam;
+ mp = (LPMSG)lParam;
- // If the message sent should be ignored, just return
+ // If the message sent should be ignored, just return
if(nCode < 0)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
@@ -131,19 +131,18 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
_td = ThreadGlobals();
- if(!_td)
+ if(!_td)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(!_td->FInitialised) // Keyman keyboard is being selected for first time in active process
{
if(!Globals::CheckControllers()) return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam); // I2435 - Prevent Keyman from re-initialising when it is shutting down // I3253 // I3532
- if(!InitialiseProcess(mp->hwnd)) return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ if(!InitialiseProcess(mp->hwnd)) return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
- if (((mp->message >= WM_KEYFIRST && mp->message <= WM_KEYLAST) || mp->message == wm_keymankeydown || mp->message == wm_keymankeyup ||
- mp->message == WM_KEYMAN_KEY_EVENT)
- && ShouldDebug(sdmMessage)) {
+ if (((mp->message >= WM_KEYFIRST && mp->message <= WM_KEYLAST) ||
+ mp->message == WM_KEYMAN_KEY_EVENT) && ShouldDebug(sdmMessage)) {
DebugMessage(mp, wParam);
}
@@ -173,67 +172,52 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
// I1337 - move mousewheel message checking before the PM_REMOVE handling - otherwise we can't pre-modify the message
// TODO: Move to WH_MOUSE_LL proc in main process
- if(mp->message == WM_MOUSEWHEEL)
- {
- HWND hwndMouseWheel = FindWindow("TfrmVisualKeyboard", NULL);
- if(hwndMouseWheel && GetWindowThreadProcessId(mp->hwnd, NULL) != GetWindowThreadProcessId(hwndMouseWheel, NULL))
- {
- RECT rcMouseWheel;
- POINT pt;
- GetWindowRect(hwndMouseWheel, &rcMouseWheel);
- pt.x = LOWORD(mp->lParam);
- pt.y = HIWORD(mp->lParam);
- if(PtInRect(&rcMouseWheel, pt))
- {
+ if(mp->message == WM_MOUSEWHEEL)
+ {
+ HWND hwndMouseWheel = FindWindow("TfrmVisualKeyboard", NULL);
+ if(hwndMouseWheel && GetWindowThreadProcessId(mp->hwnd, NULL) != GetWindowThreadProcessId(hwndMouseWheel, NULL))
+ {
+ RECT rcMouseWheel;
+ POINT pt;
+ GetWindowRect(hwndMouseWheel, &rcMouseWheel);
+ pt.x = LOWORD(mp->lParam);
+ pt.y = HIWORD(mp->lParam);
+ if(PtInRect(&rcMouseWheel, pt))
+ {
// if(!_td->state.PreviousNoRemove) // I2908
- PostMessage(WindowFromPoint(pt), WM_MOUSEWHEEL, mp->wParam, mp->lParam);
- mp->message = WM_NULL;
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
- }
- }
+ PostMessage(WindowFromPoint(pt), WM_MOUSEWHEEL, mp->wParam, mp->lParam);
+ mp->message = WM_NULL;
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
+ }
+ }
if((wParam & PM_REMOVE) == 0) // I2908
{
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
-#if 0 // I2908
- if(_td->state.PreviousNoRemove && IsMessageEquivalent(&_td->state.msg, mp))
- {
- /*
- Was the previous message the same as this one. If so, don't process it again.
- */
- _td->state.PreviousNoRemove = (wParam & PM_REMOVE) == 0;
- _td->state.msg = *mp;
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
-#endif
-
- _td->state.msg = *mp;
- /*_td->state.PreviousNoRemove = (wParam & PM_REMOVE) == 0; I2908 */
-
// 16 April 2005, 7 May 2007 - mcdurdin - Check if Keyman is receiving messages (reimplement from v6.2) */
- if(mp->message == wm_test_keyman_functioning && mp->wParam == TKF_PING)
- {
- PostMessage(mp->hwnd, wm_test_keyman_functioning, TKF_RESPONSE, mp->lParam);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ if(mp->message == wm_test_keyman_functioning && mp->wParam == TKF_PING)
+ {
+ PostMessage(mp->hwnd, wm_test_keyman_functioning, TKF_RESPONSE, mp->lParam);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
// I3594
- if(mp->message == wm_keyman_refresh)
- {
- SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_refresh %d tag=%d", mp->wParam, mp->lParam);
- HandleRefresh((int) mp->wParam, (LONG) mp->lParam);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ if(mp->message == wm_keyman_refresh)
+ {
+ SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_refresh %d tag=%d", mp->wParam, mp->lParam);
+ HandleRefresh((int) mp->wParam, (LONG) mp->lParam);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- if(mp->message == wm_keyman_control_internal) // I3933
- {
+ if(mp->message == wm_keyman_control_internal) // I3933
+ {
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_control_internal hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam); // I4674
- ProcessWMKeymanControlInternal(mp->hwnd, mp->wParam, mp->lParam);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ ProcessWMKeymanControlInternal(mp->hwnd, mp->wParam, mp->lParam);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
if (mp->message == wm_keyman_control) {
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_control hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam);
@@ -241,43 +225,43 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
- if(mp->message == wm_keyman) // I3933
- {
+ if(mp->message == wm_keyman) // I3933
+ {
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam); // I4674
- ProcessWMKeyman(mp->hwnd, mp->wParam, mp->lParam);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ ProcessWMKeyman(mp->hwnd, mp->wParam, mp->lParam);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- /*
- Handle wm_keyman_control_internal messages
- */
+ /*
+ Handle wm_keyman_control_internal messages
+ */
- /*
- If the keyboard needs to be activated, select it: messaging from the main application
- */
+ /*
+ If the keyboard needs to be activated, select it: messaging from the main application
+ */
- if(_td->lpActiveKeyboard)
- {
+ if(_td->lpActiveKeyboard)
+ {
_td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard;
- }
+ }
// I4412
- if(mp->message == wm_keymanshift)
- {
- SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keymanshift %x %x", mp->wParam, mp->lParam);
+ if(mp->message == wm_keymanshift)
+ {
+ SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keymanshift %x %x", mp->wParam, mp->lParam);
SelectApplicationIntegration();
- if(!_td->app->IsWindowHandled(mp->hwnd)) _td->app->HandleWindow(mp->hwnd);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ if(!_td->app->IsWindowHandled(mp->hwnd)) _td->app->HandleWindow(mp->hwnd);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- if(*Globals::hwndIM() != 0)
- {
- if(mp->message == wm_keymanim_close)
- {
- if(mp->lParam) KMHideIM();
- if(mp->wParam)
- {
- if(_td->app)
- {
+ if(*Globals::hwndIM() != 0)
+ {
+ if(mp->message == wm_keymanim_close)
+ {
+ if(mp->lParam) KMHideIM();
+ if(mp->wParam)
+ {
+ if(_td->app)
+ {
// Call the core keyboard processor to process the queued actions
if (!_td->lpActiveKeyboard) {
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
@@ -291,43 +275,45 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
_td->app->SetCurrentShiftState(Globals::get_ShiftState());
_td->app->SendActions();
- }
- }
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ }
+ }
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- if(!*Globals::hwndIMAlways())
- {
- if(mp->message == wm_keymankeydown)
- PostMessage(*Globals::hwndIM(), WM_KEYDOWN, mp->wParam, mp->lParam);
- else if(mp->message == wm_keymankeyup)
- PostMessage(*Globals::hwndIM(), WM_KEYUP, mp->wParam, mp->lParam);
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
- }
+ if(!*Globals::hwndIMAlways())
+ {
+/* TODO: IM windows no longer receive these messages, what is going on? #11919?
+ if(mp->message == wxm_keymankeydown)
+ PostMessage(*Globals::hwndIM(), WM_KEYDOWN, mp->wParam, mp->lParam);
+ else if(mp->message == wxm_keymankeyup)
+ PostMessage(*Globals::hwndIM(), WM_KEYUP, mp->wParam, mp->lParam);
+*/
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
+ }
- /*
- If no keyboard is active, return
- */
+ /*
+ If no keyboard is active, return
+ */
- if(!_td->lpActiveKeyboard || _td->FInRefreshKeyboards)
- {
- if(_td->app) _td->app->ResetContext();
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ if(!_td->lpActiveKeyboard || _td->FInRefreshKeyboards)
+ {
+ if(_td->app) _td->app->ResetContext();
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- if(mp->message > WM_MOUSEFIRST && mp->message <= WM_MOUSELAST && !IsIMWindow(mp->hwnd))
- {
- // > MOUSEFIRST ignores mouse movement
- if(_td->app) _td->app->ResetContext();
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
- }
+ if(mp->message > WM_MOUSEFIRST && mp->message <= WM_MOUSELAST && !IsIMWindow(mp->hwnd))
+ {
+ // > MOUSEFIRST ignores mouse movement
+ if(_td->app) _td->app->ResetContext();
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ }
- /* // I3617
- Text Services Framework tells us we don't need to continue here
- */
+ /* // I3617
+ Text Services Framework tells us we don't need to continue here
+ */
- return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
+ return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam)
@@ -335,38 +321,38 @@ void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam)
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return;
- SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ProcessWMKeyman wm_keyman: %d %d", wParam, lParam);
- switch(wParam)
- {
- case KM_DISABLEUI:
- _td->KeymanUIDisabled = TRUE;
- break;
- case KM_ENABLEUI:
- _td->KeymanUIDisabled = FALSE;
- break;
- case KM_FOCUSCHANGED:
+ SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ProcessWMKeyman wm_keyman: %d %d", wParam, lParam);
+ switch(wParam)
+ {
+ case KM_DISABLEUI:
+ _td->KeymanUIDisabled = TRUE;
+ break;
+ case KM_ENABLEUI:
+ _td->KeymanUIDisabled = FALSE;
+ break;
+ case KM_FOCUSCHANGED:
if(lParam & KMF_WINDOWCHANGED)
{
hwnd = GetFocus();
- if(_td->lpActiveKeyboard) {
+ if(_td->lpActiveKeyboard) {
_td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard;
- }
+ }
SelectApplicationIntegration();
if(_td->app && !_td->app->IsWindowHandled(hwnd)) _td->app->HandleWindow(hwnd);
_td->state.windowunicode = !_td->app || _td->app->IsUnicode();
if(IsFocusedThread())
- {
+ {
if(_td->app) _td->app->ResetQueue();
- GetCapsAndNumlockState();
- UpdateActiveWindows();
+ GetCapsAndNumlockState();
+ UpdateActiveWindows();
}
}
- break;
- }
+ break;
+ }
}
@@ -374,8 +360,8 @@ void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(hwnd);
- switch(wParam)
- {
+ switch(wParam)
+ {
case KMCI_GETACTIVEKEYBOARD: // I3933
ReportActiveKeyboard(LOWORD(lParam)); // I3949
break;
@@ -393,7 +379,7 @@ void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam)
case KMCI_SETFOREGROUND: // I3933
SetForegroundWindow(hwnd);
break;
- }
+ }
}
void
@@ -422,30 +408,30 @@ ProcessWMKeymanControl(WPARAM wParam, LPARAM lParam) {
may have been reset while Keyman was not aware of it
*/
void GetCapsAndNumlockState() { // I4793
- DWORD n = Globals::get_ShiftState();
+ DWORD n = Globals::get_ShiftState();
- if(GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG;
- else *Globals::ShiftState() &= ~NUMLOCKFLAG;
+ if(GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG;
+ else *Globals::ShiftState() &= ~NUMLOCKFLAG;
- if(GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG;
- else *Globals::ShiftState() &= ~CAPITALFLAG;
+ if(GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG;
+ else *Globals::ShiftState() &= ~CAPITALFLAG;
- if(GetKeyState(VK_SHIFT) < 0) *Globals::ShiftState() |= K_SHIFTFLAG;
- else *Globals::ShiftState() &= ~K_SHIFTFLAG;
+ if(GetKeyState(VK_SHIFT) < 0) *Globals::ShiftState() |= K_SHIFTFLAG;
+ else *Globals::ShiftState() &= ~K_SHIFTFLAG;
- if(GetKeyState(VK_RCONTROL) < 0) *Globals::ShiftState() |= RCTRLFLAG;
- else *Globals::ShiftState() &= ~RCTRLFLAG;
+ if(GetKeyState(VK_RCONTROL) < 0) *Globals::ShiftState() |= RCTRLFLAG;
+ else *Globals::ShiftState() &= ~RCTRLFLAG;
- if(GetKeyState(VK_LCONTROL) < 0) *Globals::ShiftState() |= LCTRLFLAG;
- else *Globals::ShiftState() &= ~LCTRLFLAG;
+ if(GetKeyState(VK_LCONTROL) < 0) *Globals::ShiftState() |= LCTRLFLAG;
+ else *Globals::ShiftState() &= ~LCTRLFLAG;
- if(GetKeyState(VK_LMENU) < 0) *Globals::ShiftState() |= LALTFLAG;
- else *Globals::ShiftState() &= ~LALTFLAG;
+ if(GetKeyState(VK_LMENU) < 0) *Globals::ShiftState() |= LALTFLAG;
+ else *Globals::ShiftState() &= ~LALTFLAG;
- if(GetKeyState(VK_RMENU) < 0) *Globals::ShiftState() |= RALTFLAG;
- else *Globals::ShiftState() &= ~RALTFLAG;
+ if(GetKeyState(VK_RMENU) < 0) *Globals::ShiftState() |= RALTFLAG;
+ else *Globals::ShiftState() &= ~RALTFLAG;
- SendDebugMessageFormat(0, sdmInternat, 0, "GetCapsAndNumlockState (Enter:%x Exit:%x)", n, Globals::get_ShiftState());
+ SendDebugMessageFormat(0, sdmInternat, 0, "GetCapsAndNumlockState (Enter:%x Exit:%x)", n, Globals::get_ShiftState());
}
/*
@@ -489,5 +475,5 @@ BOOL IsFocusedThread()
return FALSE;
}
return gti.hwndFocus == GetFocus();
- //return GetCurrentThreadId() == GetWindowThreadProcessId(GetForegroundWindow(), NULL);
+ //return GetCurrentThreadId() == GetWindowThreadProcessId(GetForegroundWindow(), NULL);
}
diff --git a/windows/src/engine/keyman32/kmhook_keyboard.cpp b/windows/src/engine/keyman32/kmhook_keyboard.cpp
index 711eb68322..c5859bccc0 100644
--- a/windows/src/engine/keyman32/kmhook_keyboard.cpp
+++ b/windows/src/engine/keyman32/kmhook_keyboard.cpp
@@ -62,9 +62,8 @@
#include "pch.h"
#include
-#ifdef _WIN64
-#error kmhook_keyboard.cpp is not required for win64.
-#endif
+#ifndef _WIN64
+
// I3617
BOOL IsLanguageSwitchWindowVisible();
void SendToLanguageSwitchWindow(WPARAM wParam, LPARAM lParam);
@@ -192,3 +191,5 @@ int ProcessLanguageSwitchShiftKey(WPARAM wParam, BOOL isUp)
PostDummyKeyEvent(); // I3301 - this is imperfect because we don't deal with HC_NOREMOVE. But good enough? // I3534 // I4844
return 0;
}
+
+#endif
diff --git a/windows/src/engine/keyman32/kmprocess.cpp b/windows/src/engine/keyman32/kmprocess.cpp
index dd43038f5e..442b01b991 100644
--- a/windows/src/engine/keyman32/kmprocess.cpp
+++ b/windows/src/engine/keyman32/kmprocess.cpp
@@ -121,23 +121,22 @@ BOOL ProcessHook()
fOutputKeystroke = FALSE; // TODO: 5442 no longer needs to be global once we use core processor
- if(_td->state.msg.message == wm_keymankeydown) { // I4827
- if (ShouldDebug(sdmKeyboard)) {
- if(!_td->lpActiveKeyboard || !_td->lpActiveKeyboard->lpCoreKeyboardState) {
- SendDebugMessageFormat(_td->state.msg.hwnd, sdmKeyboard, 0, "Key pressed: %s Context ",
- Debug_VirtualKey(_td->state.vkey));
- } else {
- km_core_cu* debug_context = km_core_state_context_debug(
- _td->lpActiveKeyboard->lpCoreKeyboardState,
- KM_CORE_DEBUG_CONTEXT_CACHED
- );
- SendDebugMessageFormat(_td->state.msg.hwnd, sdmKeyboard, 0, "Key pressed: %s Context '%ls'",
- Debug_VirtualKey(_td->state.vkey), debug_context);
- km_core_cu_dispose(debug_context);
- }
+ if (ShouldDebug(sdmKeyboard)) {
+ if(!_td->lpActiveKeyboard || !_td->lpActiveKeyboard->lpCoreKeyboardState) {
+ SendDebugMessageFormat(0, sdmKeyboard, 0, "Key %s: %s Context ",
+ _td->state.isDown ? "pressed" : "released",
+ Debug_VirtualKey(_td->state.vkey));
+ } else {
+ km_core_cu* debug_context = km_core_state_context_debug(
+ _td->lpActiveKeyboard->lpCoreKeyboardState,
+ KM_CORE_DEBUG_CONTEXT_CACHED
+ );
+ SendDebugMessageFormat(0, sdmKeyboard, 0, "Key %s: %s Context '%ls'",
+ _td->state.isDown ? "pressed" : "released",
+ Debug_VirtualKey(_td->state.vkey), debug_context);
+ km_core_cu_dispose(debug_context);
}
-
- }
+ }
// For applications not using the TSF kmtip calls this function twice for each keystroke,
// first to determine if we are doing processing work (TIPFUpdateable == FALSE),
diff --git a/windows/src/engine/keyman32/syskbdnt64.cpp b/windows/src/engine/keyman32/syskbdnt64.cpp
index 37342ac7d5..882bb4e030 100644
--- a/windows/src/engine/keyman32/syskbdnt64.cpp
+++ b/windows/src/engine/keyman32/syskbdnt64.cpp
@@ -31,6 +31,9 @@
////////////////////////////////////////////////////////////////////////////
#include "pch.h"
+
+#ifndef _WIN64
+
#include "kbd.h" /* DDK kbdlayout */
//#pragma warning ( disable : 4200 )
@@ -221,3 +224,5 @@ BOOL IsWow64()
fStored = TRUE;
return bIsWow64;
}
+
+#endif
diff --git a/windows/src/engine/keyman64/version.rc b/windows/src/engine/keyman32/version64.rc
similarity index 100%
rename from windows/src/engine/keyman64/version.rc
rename to windows/src/engine/keyman32/version64.rc
diff --git a/windows/src/engine/keyman64/build.sh b/windows/src/engine/keyman64/build.sh
deleted file mode 100755
index 4b278e3c3e..0000000000
--- a/windows/src/engine/keyman64/build.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env bash
-## START STANDARD BUILD SCRIPT INCLUDE
-# adjust relative paths as necessary
-THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
-. "${THIS_SCRIPT%/*}/../../../../resources/build/builder.inc.sh"
-## END STANDARD BUILD SCRIPT INCLUDE
-
-builder_describe "Keystroke processing engine (64 bit)" \
- @/common/include \
- @/core:x64 \
- clean configure build test publish install
-
-builder_parse "$@"
-
-#-------------------------------------------------------------------------------------------------------------------
-
-source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh"
-X64_TARGET="$X64_TARGET_PATH/keyman64.dll"
-
-builder_describe_outputs \
- configure:project /resources/build/win/delphi_environment_generated.inc.sh \
- build:project /windows/src/engine/keyman64/$X64_TARGET
-
-#-------------------------------------------------------------------------------------------------------------------
-
-function do_clean() {
- vs_msbuild keyman64.vcxproj //t:Clean
- clean_windows_project_files
-}
-
-function do_build() {
- create-windows-output-folders
- build_version.res
- vs_msbuild keyman64.vcxproj //t:Build "//p:Platform=x64"
- cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE"
- cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE"
-}
-
-function do_publish() {
- wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/keyman64.dll"
- wrap-symstore "$WINDOWS_PROGRAM_ENGINE/keyman64.dll" //t keyman-engine-windows
- wrap-symstore "$WINDOWS_DEBUGPATH_ENGINE/keyman64.pdb" //t keyman-engine-windows
-}
-
-builder_run_action clean:project do_clean
-builder_run_action configure:project configure_windows_build_environment
-builder_run_action build:project do_build
-# builder_run_action test:project do_test
-builder_run_action publish:project do_publish
-builder_run_action install:project cp "$WINDOWS_PROGRAM_ENGINE/keyman64.dll" "$INSTALLPATH_KEYMANENGINE/keyman64.dll"
diff --git a/windows/src/engine/keyman64/keyman64.rc b/windows/src/engine/keyman64/keyman64.rc
deleted file mode 100644
index 176ac08147..0000000000
--- a/windows/src/engine/keyman64/keyman64.rc
+++ /dev/null
@@ -1,3 +0,0 @@
-
-#include "version.rc"
-
diff --git a/windows/src/engine/keyman64/keyman64.sln b/windows/src/engine/keyman64/keyman64.sln
deleted file mode 100644
index cbab00dba9..0000000000
--- a/windows/src/engine/keyman64/keyman64.sln
+++ /dev/null
@@ -1,24 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman64", "keyman64.vcxproj", "{D1115FB2-230D-4FD8-90B9-982E49953D18}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Win32 = Debug|Win32
- Debug|x64 = Debug|x64
- Release|Win32 = Release|Win32
- Release|x64 = Release|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|Win32.ActiveCfg = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|x64.ActiveCfg = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Debug|x64.Build.0 = Debug|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|Win32.ActiveCfg = Release|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|x64.ActiveCfg = Release|x64
- {D1115FB2-230D-4FD8-90B9-982E49953D18}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/windows/src/engine/keyman64/keyman64.vcxproj b/windows/src/engine/keyman64/keyman64.vcxproj
deleted file mode 100644
index 9e9b5f7e71..0000000000
--- a/windows/src/engine/keyman64/keyman64.vcxproj
+++ /dev/null
@@ -1,332 +0,0 @@
-
-
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- {D1115FB2-230D-4FD8-90B9-982E49953D18}
- Keyman32
- 10.0
-
-
-
- DynamicLibrary
- false
- v142
-
-
- DynamicLibrary
- false
- v142
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_ProjectFileVersion>10.0.30319.1
- $(ProjectDir)bin\$(Platform)\$(Configuration)\
- $(ProjectDir)obj\$(Platform)\$(Configuration)\
- false
- $(ProjectDir)bin\$(Platform)\$(Configuration)\
- $(ProjectDir)obj\$(Platform)\$(Configuration)\
- true
- AllRules.ruleset
-
-
- AllRules.ruleset
-
-
-
-
- $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
- $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath)
-
-
- $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)
- $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath)
-
-
-
- NDEBUG;%(PreprocessorDefinitions)
- true
- true
- X64
- ./Keyman32.tlb
-
-
-
-
- MaxSpeed
- OnlyExplicitInline
- ..\keyman32;..\..\global\inc;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)
- false
- true
-
-
- MultiThreaded
- true
- Use
- pch.h
- All
- Level4
- true
- true
- ProgramDatabase
-
-
- NDEBUG;%(PreprocessorDefinitions)
- 0x0409
-
-
- /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions)
- libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmt.lib;%(AdditionalDependencies)
- true
- C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories)
- false
-
-
- true
- true
- true
- Windows
- 0x1C800000
- ..\..\..\lib\Keyman64.lib
- MachineX64
- true
- true
-
-
- true
- ./Keyman32.bsc
-
-
-
-
- _DEBUG;%(PreprocessorDefinitions)
- true
- true
- X64
- ./Keyman32.tlb
-
-
-
-
- Disabled
- ..\keyman32;..\..\global\inc;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)
- false
- true
-
-
- MultiThreadedDebug
- Use
- pch.h
- All
- true
- Level4
- true
- true
- ProgramDatabase
- true
-
-
- _DEBUG;%(PreprocessorDefinitions)
- 0x0409
-
-
- /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions)
- libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;libcmtd.lib;%(AdditionalDependencies)
- true
- C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64;$(VCInstallDir)lib\amd64;$(VCInstallDir)lib;%(AdditionalLibraryDirectories)
- false
-
-
-
-
- true
- true
- Windows
- 0x1C800000
- ..\..\..\lib\Keyman64.lib
- MachineX64
- true
- true
-
-
- true
- ./Keyman32.bsc
-
-
-
-
-
-
-
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
-
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
-
- Create
- Create
-
-
-
-
-
-
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
- $(IntDir)%(Filename)1.obj
- $(IntDir)%(Filename)1.xdc
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Document
- false
- false
- mc %(FullPath)
- %(Filename).rc;%(Filename).h;MSG00001.bin
- mc %(FullPath)
- %(Filename).rc;%(Filename).h;MSG00001.bin
- Compiling %(Filename).man
- Compiling %(Filename).man
-
-
-
-
-
-
\ No newline at end of file
diff --git a/windows/src/engine/keyman64/keyman64.vcxproj.filters b/windows/src/engine/keyman64/keyman64.vcxproj.filters
deleted file mode 100644
index 06ba0e951e..0000000000
--- a/windows/src/engine/keyman64/keyman64.vcxproj.filters
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/windows/src/engine/testhost/testhost.cpp b/windows/src/engine/testhost/testhost.cpp
index 4369db42b0..d74c3c1cc6 100644
--- a/windows/src/engine/testhost/testhost.cpp
+++ b/windows/src/engine/testhost/testhost.cpp
@@ -312,8 +312,8 @@ Fail(PCWSTR message) {
#ifdef _WIN64
#define KEYMAN32 "keyman64.dll"
-#define KEYMAN32_DEBUG "keyman64" / "bin" / "x64" / "Debug" / "keyman64.dll"
-#define KEYMAN32_RELEASE "keyman64" / "bin" / "x64" / "Release" / "keyman64.dll"
+#define KEYMAN32_DEBUG "keyman32" / "bin" / "x64" / "Debug" / "keyman64.dll"
+#define KEYMAN32_RELEASE "keyman32" / "bin" / "x64" / "Release" / "keyman64.dll"
#else
#define KEYMAN32 "keyman32.dll"
#define KEYMAN32_DEBUG "keyman32" / "bin" / "Win32" / "Debug" / "keyman32.dll"