mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-31 18:27:49 +00:00
Merge branch 'stable-17.0' into fix/web/cherrypick-longpress-modifier-multitap-warning
This commit is contained in:
commit
b6d95f5efe
112 changed files with 1724 additions and 887 deletions
55
HISTORY.md
55
HISTORY.md
|
|
@ -1,5 +1,60 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 17.0.333 stable 2025-01-16
|
||||
|
||||
* fix(core): permanently disable logging (#12674)
|
||||
* fix(linux): pushing of updated changelog branch (#12819)
|
||||
* fix(linux): work around Lintian errors (#12817)
|
||||
* fix(core): implement ldml_processor::get_key_list() (#12816)
|
||||
* chore(linux): Update debian changelog (#12022)
|
||||
* fix(android): use main looper to dispatch key events when OSK is hidden (#12875)
|
||||
* chore: use GitHub PR titles when writing HISTORY.md (#12908)
|
||||
* fix(developer): filter incorrect fonts out of .keyboard_info (#12913)
|
||||
|
||||
## 17.0.332 stable 2024-11-06
|
||||
|
||||
* fix(developer): create Server config directory before options save (#12609)
|
||||
* fix(developer): handle merge commits when checking git log date (#12628)
|
||||
* fix(linux): set environment variable for rendering of downloads dialog (#12617)
|
||||
|
||||
## 17.0.331 stable 2024-10-30
|
||||
|
||||
* fix(android): Hide suggestion banner on password fields (#12466)
|
||||
* fix(common): declare dep on @keymanapp/ldml-keyboard-constants (#12475)
|
||||
* fix(oem/fv): Update keyboard versions and names for fv_all.kmp (#12504)
|
||||
* chore(ios): renew certificate (#12513)
|
||||
* fix(developer): prevent invalid string ids (#12524)
|
||||
* fix(developer): ignore excess whitespace in `<row keys>` attribute (#12523)
|
||||
|
||||
## 17.0.330 stable 2024-09-16
|
||||
|
||||
* refactor(android): Move Sentry and APK to publish task (#12392)
|
||||
* fix(developer): rewrite ldml visual keyboard compiler (#12406)
|
||||
* fix(developer): check vars string usage before definition (#12407)
|
||||
|
||||
## 17.0.329 stable 2024-09-09
|
||||
|
||||
* chore(android,ios): Add ojibwa ifinal/rdot keyboards to FirstVoices (#12020)
|
||||
* change(web): revert #11174, which loads keyboards before initializing the OSK (#12040)
|
||||
* fix(web): unrevert #11258, leaving OSK hidden before instructed to display (#12058)
|
||||
* chore(common): use `nvm` to select version of node for builds (#12074)
|
||||
* fix(developer): ignore scan code if zero in debugger (#12182)
|
||||
* fix(developer): enforce presence of Version field when FollowKeyboardVersion is not set, in package compiler (#12206)
|
||||
* fix(developer): enforce presence of kps Info.Description field in info compilers (#12207)
|
||||
* fix(web): disable fat-finger data use when mayCorrect = false (#12226)
|
||||
* chore(common): allow build agents to automatically select emsdk version, and enable support for 3.1.60+ (#12245)
|
||||
* fix(web): fix documentation-keyboard spacebar-text scaling (#12240)
|
||||
* fix(core): set mac build version for meson cli build to 10.13 (#12246)
|
||||
* change(ios): defer registration of fonts past initialization (#12241)
|
||||
* chore(android,ios): Update FirstVoices keyboards to 12.15 (#12301)
|
||||
* fix(core): properly support 'other' modifier state with `uint32_t` type (#12285)
|
||||
* fix(developer): find last matching key in LDML key bag when building KVK (#12284)
|
||||
* fix(android): check in material-stepper as internal Maven dependency (#12324)
|
||||
* fix(linux): add `keymanFacename` to .ldml file (#12283)
|
||||
* chore(oem/fv): Update to fv_all 13.0 (#12363)
|
||||
* fix(mac): avoid crash on startup with macOS 10.15 (Catalina) (#12364)
|
||||
* fix(android): skip language counts for lexical-model packages (#12368)
|
||||
|
||||
## 17.0.328 stable 2024-07-27
|
||||
|
||||
* fix(web): add nullish test in setOsk (#12041)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.329
|
||||
17.0.334
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
flatDir {
|
||||
dirs "kMAPro/libs/com/stepstone/stepper/material-stepper/4.3.1/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
|
|
@ -11,16 +13,16 @@ buildscript {
|
|||
classpath 'io.sentry:sentry-android-gradle-plugin:2.1.2'
|
||||
classpath 'name.remal:gradle-plugins:1.5.0'
|
||||
|
||||
// From jcenter() which could be sunset in future
|
||||
// From jcenter() which was deprecated August 2024
|
||||
// https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/
|
||||
classpath 'com.stepstone.stepper:material-stepper:4.3.1'
|
||||
classpath 'com.stepstone.stepper:material-stepper:4.3.1@aar'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url uri("${projectDir}/libs") }
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "$KEYMAN_ROOT/resources/build/build-help.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-download-resources.sh"
|
||||
|
||||
. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh"
|
||||
|
||||
# This script runs from its own folder
|
||||
cd "$THIS_SCRIPT_PATH"
|
||||
|
||||
|
|
@ -32,6 +34,7 @@ builder_describe "Builds Keyman for Android app." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test Runs lint and unit tests." \
|
||||
"publish Publishes symbols to Sentry and the APK to the Play Store." \
|
||||
"--ci Don't start the Gradle daemon. For CI" \
|
||||
"--upload-sentry Upload to sentry"
|
||||
|
||||
|
|
@ -116,3 +119,13 @@ if builder_start_action test; then
|
|||
|
||||
builder_finish_action success test
|
||||
fi
|
||||
|
||||
if builder_start_action publish; then
|
||||
# Copy Release Notes
|
||||
generateReleaseNotes
|
||||
|
||||
# Publish symbols and Keyman for Android to Play Store
|
||||
./gradlew $DAEMON_FLAG publishSentry publishReleaseApk
|
||||
|
||||
builder_finish_action success publish
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -105,12 +105,17 @@ android {
|
|||
}
|
||||
|
||||
// how to configure the sentry android gradle plugin
|
||||
sentry {
|
||||
// Disables or enables the automatic configuration of Native symbols
|
||||
uploadNativeSymbols = true
|
||||
task publishSentry {
|
||||
doLast {
|
||||
println 'Publishing Keyman symbols to Sentry'
|
||||
sentry {
|
||||
// Disables or enables the automatic configuration of Native symbols
|
||||
uploadNativeSymbols = true
|
||||
|
||||
// Does or doesn't include the source code of native code for Sentry
|
||||
includeNativeSources = true
|
||||
// Does or doesn't include the source code of native code for Sentry
|
||||
includeNativeSources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String env_keys_json_file = System.getenv("keys_json_file")
|
||||
|
|
@ -145,7 +150,6 @@ repositories {
|
|||
dirs 'libs'
|
||||
}
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.stepstone.stepper</groupId>
|
||||
<artifactId>material-stepper</artifactId>
|
||||
<version>4.3.1</version>
|
||||
<packaging>aar</packaging>
|
||||
<name>Android Material Stepper</name>
|
||||
<description>This library allows to use Material steppers inside Android applications.</description>
|
||||
<url>https://github.com/stepstone-tech/android-material-stepper</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>zawadz88</id>
|
||||
<name>Piotr Zawadzki</name>
|
||||
<email>piotr.zawadzki@stepstone.com</email>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection>https://github.com/stepstone-tech/android-material-stepper.git</connection>
|
||||
<developerConnection>https://github.com/stepstone-tech/android-material-stepper.git</developerConnection>
|
||||
<url>https://github.com/stepstone-tech/android-material-stepper</url>
|
||||
</scm>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.android.support</groupId>
|
||||
<artifactId>appcompat-v7</artifactId>
|
||||
<version>25.4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>2.7.21</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.assertj</groupId>
|
||||
<artifactId>assertj-android</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.robolectric</groupId>
|
||||
<artifactId>robolectric</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<groupId>commons-logging</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jre7</artifactId>
|
||||
<version>1.1.4-3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<version>1.1.4-3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nhaarman</groupId>
|
||||
<artifactId>mockito-kotlin</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -7,10 +7,6 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- API 33 and above -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<!-- Devices running up to Android 12L
|
||||
Starting in API level 33, this permission has no effect:
|
||||
https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE -->
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ public class CheckPermissions {
|
|||
checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
// API 33+
|
||||
//https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions
|
||||
// Manifest.permission.READ_MEDIA_AUDIO doesn't seem to be needed
|
||||
// We had to remove these MEDIA permissions from AndroidManifest.xml so these will end up failing
|
||||
// https://support.google.com/googleplay/android-developer/answer/14115180?hl=en
|
||||
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_IMAGES);
|
||||
permissionsOK = permissionsOK && checkPermission(activity, Manifest.permission.READ_MEDIA_VIDEO);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ public class PackageActivity extends AppCompatActivity implements
|
|||
|
||||
// Number of languages associated with the first keyboard in a keyboard package.
|
||||
// lexical-model packages will be 0
|
||||
final int languageCount = kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0);
|
||||
final int languageCount = (keyboardCount > 0) ?
|
||||
kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0;
|
||||
|
||||
// Sanity check for keyboard packages
|
||||
if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo;
|
|||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
|
@ -70,6 +71,10 @@ final class KMKeyboard extends WebView {
|
|||
protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED;
|
||||
protected ArrayList<String> javascriptAfterLoad = new ArrayList<>();
|
||||
|
||||
// .getMainLooper() returns the looper associated with the main UI thread.
|
||||
// https://stackoverflow.com/questions/13974661/runonuithread-vs-looper-getmainlooper-post-in-android
|
||||
private Handler jsQueuer = new Handler(Looper.getMainLooper());
|
||||
|
||||
private static String currentKeyboard = null;
|
||||
|
||||
/**
|
||||
|
|
@ -368,7 +373,7 @@ final class KMKeyboard extends WebView {
|
|||
if(this.javascriptAfterLoad.size() > 0) {
|
||||
// Don't call this WebView method on just ANY thread - run it on the main UI thread.
|
||||
// https://stackoverflow.com/a/22611010
|
||||
this.postDelayed(new Runnable() {
|
||||
jsQueuer.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
StringBuilder allCalls = new StringBuilder();
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ public final class KMKeyboardWebViewClient extends WebViewClient {
|
|||
// appContext instead of context?
|
||||
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
boolean modelPredictionPref = false;
|
||||
if (KMManager.currentLexicalModel != null) {
|
||||
if (!KMManager.getMayPredictOverride() && KMManager.currentLexicalModel != null) {
|
||||
modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true);
|
||||
}
|
||||
KMManager.setBannerOptions(modelPredictionPref);
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# CI script to publish specified app APKs to the Play Store.
|
||||
# The APKs should already have been built from a separate script
|
||||
|
||||
# set -x: Debugging use, print each statement
|
||||
# set -x
|
||||
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../resources/build/build-utils.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/android/KMAPro/build-play-store-notes.inc.sh"
|
||||
|
||||
echo Publishing APKs to Play Store
|
||||
|
||||
#
|
||||
# Prevents 'clear' on exit of mingw64 bash shell
|
||||
#
|
||||
SHLVL=0
|
||||
|
||||
display_usage ( ) {
|
||||
echo "build-publish.sh [-no-daemon] [-kmapro] [-fv]"
|
||||
echo
|
||||
echo "Publish app to the Play Store"
|
||||
echo " -no-daemon Don't start the Gradle daemon. Use for CI"
|
||||
echo " -kmapro Keyman for Android"
|
||||
echo " -fv First Voices"
|
||||
exit 1
|
||||
}
|
||||
|
||||
NO_DAEMON=false
|
||||
DO_KMAPRO=false
|
||||
DO_FV=false
|
||||
|
||||
# Parse args
|
||||
while [[ $# -gt 0 ]] ; do
|
||||
key="$1"
|
||||
case $key in
|
||||
-no-daemon)
|
||||
NO_DAEMON=true
|
||||
;;
|
||||
-kmapro)
|
||||
DO_KMAPRO=true
|
||||
;;
|
||||
-fv)
|
||||
DO_FV=true
|
||||
;;
|
||||
-h|-\?)
|
||||
display_usage
|
||||
;;
|
||||
esac
|
||||
shift # past argument
|
||||
done
|
||||
|
||||
# Override JAVA_HOME to OpenJDK 11
|
||||
set_java_home
|
||||
|
||||
echo
|
||||
echo "NO_DAEMON: $NO_DAEMON"
|
||||
echo "DO_KMAPRO: $DO_KMAPRO"
|
||||
echo "DO_FV: $DO_FV"
|
||||
echo
|
||||
|
||||
if [ "$NO_DAEMON" = true ]; then
|
||||
DAEMON_FLAG=--no-daemon
|
||||
else
|
||||
DAEMON_FLAG=
|
||||
fi
|
||||
|
||||
BUILD_FLAGS="publishReleaseApk"
|
||||
echo "BUILD_FLAGS $BUILD_FLAGS"
|
||||
|
||||
# Publish Keyman for Android
|
||||
if [ "$DO_KMAPRO" = true ]; then
|
||||
# Copy Release Notes
|
||||
generateReleaseNotes
|
||||
cd "$KEYMAN_ROOT/android/KMAPro/"
|
||||
./gradlew $DAEMON_FLAG $BUILD_FLAGS
|
||||
fi
|
||||
|
||||
# Publish FV app
|
||||
if [ "$DO_FV" = true ]; then
|
||||
cd "$KEYMAN_ROOT/oem/firstvoices/android/"
|
||||
./gradlew $DAEMON_FLAG $BUILD_FLAGS
|
||||
fi
|
||||
|
|
@ -25,7 +25,7 @@ builder_describe \
|
|||
configure \
|
||||
build \
|
||||
test \
|
||||
"publish Publishes the APKs to the Play Store." \
|
||||
"publish Publishes symbols to Sentry and the APKs to the Play Store." \
|
||||
--ci+ \
|
||||
--upload-sentry+ \
|
||||
":engine=KMEA Keyman Engine for Android" \
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ permission to access storage / "file and media". The screenshot below is from An
|
|||
|
||||
### Android 13.0+ Devices
|
||||
|
||||
Step 1)
|
||||
Go to Android Settings.
|
||||
Due to Google Play Store's "Photo and Video Permissions policy",
|
||||
Keyman for Android must use a system picker to browse for .kmp files from within the Keyman app.
|
||||
|
||||
Step 2)
|
||||
Depending on your device, click "Apps" and grant Keyman permission to the following:
|
||||
* Photos and videos --> Always allow all
|
||||
|
||||
The screenshot below is from Android 14.0.
|
||||
|
||||

|
||||
Follow the steps in [Installing Custom Keyboards/Dictionaries](../basic/installing-custom-packages).
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/ldml-keyboard-constants": "*",
|
||||
"restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc",
|
||||
"semver": "^7.5.2",
|
||||
"xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829"
|
||||
|
|
|
|||
33
common/web/types/src/consts/modifier-key-constants.ts
Normal file
33
common/web/types/src/consts/modifier-key-constants.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Modifier key bit-flags
|
||||
*/
|
||||
|
||||
export const ModifierKeyConstants = {
|
||||
// Define Keyman Developer modifier bit-flags (exposed for use by other modules)
|
||||
// Compare against /common/include/kmx_file.h. CTRL+F "#define LCTRLFLAG" to find the secton.
|
||||
LCTRLFLAG: 0x0001, // Left Control flag
|
||||
RCTRLFLAG: 0x0002, // Right Control flag
|
||||
LALTFLAG: 0x0004, // Left Alt flag
|
||||
RALTFLAG: 0x0008, // Right Alt flag
|
||||
K_SHIFTFLAG: 0x0010, // Either shift flag
|
||||
K_CTRLFLAG: 0x0020, // Either ctrl flag
|
||||
K_ALTFLAG: 0x0040, // Either alt flag
|
||||
K_METAFLAG: 0x0080, // Either Meta-key flag (tentative). Not usable in keyboard rules;
|
||||
// Used internally (currently, only by KMW) to ensure Meta-key
|
||||
// shortcuts safely bypass rules
|
||||
// Meta key = Command key on macOS, Windows key on Windows/Linux
|
||||
CAPITALFLAG: 0x0100, // Caps lock on
|
||||
NOTCAPITALFLAG: 0x0200, // Caps lock NOT on
|
||||
NUMLOCKFLAG: 0x0400, // Num lock on
|
||||
NOTNUMLOCKFLAG: 0x0800, // Num lock NOT on
|
||||
SCROLLFLAG: 0x1000, // Scroll lock on
|
||||
NOTSCROLLFLAG: 0x2000, // Scroll lock NOT on
|
||||
ISVIRTUALKEY: 0x4000, // It is a Virtual Key Sequence
|
||||
VIRTUALCHARKEY: 0x8000, // Keyman 6.0: Virtual Key Cap Sequence NOT YET
|
||||
|
||||
// Note: OTHER_MODIFIER = 0x10000, used by KMX+ for the
|
||||
// other modifier flag in layers, > 16 bit so not available here.
|
||||
// See keys_mod_other in keyman_core_ldml.ts
|
||||
}
|
||||
|
|
@ -285,7 +285,7 @@ export class Vars extends Section {
|
|||
});
|
||||
}
|
||||
findStringVariableValue(id: string): string {
|
||||
return Vars.findVariable(this.strings, id)?.value?.value; // Unwrap: Variable, StrsItem
|
||||
return Vars.findVariable(this.strings, id)?.value?.value ?? null; // Unwrap: Variable, StrsItem
|
||||
}
|
||||
substituteSetRegex(str: string, sections: DependencySections): string {
|
||||
return str.replaceAll(VariableParser.SET_REFERENCE, (_entire, id) => {
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
for (const sub of obj) {
|
||||
// retain the same subtag
|
||||
if (!this.boxImportsAndSpecials(sub, subtag)) {
|
||||
// resolveImports has already logged a message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +126,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
boxXmlArray(obj, key);
|
||||
// Now, resolve the import
|
||||
if (!this.resolveImports(obj, subtag)) {
|
||||
// resolveImports has already logged a message
|
||||
return false;
|
||||
}
|
||||
// now delete the import array we so carefully constructed, the caller does not
|
||||
|
|
@ -132,6 +134,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
delete obj['import'];
|
||||
} else {
|
||||
if (!this.boxImportsAndSpecials(obj[key], key)) {
|
||||
// resolveImports has already logged a message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +154,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
// first, the explicit imports
|
||||
for (const asImport of ([...obj['import'] as LKImport[]].reverse())) {
|
||||
if (!this.resolveOneImport(obj, subtag, asImport)) {
|
||||
// resolveOneImport has already logged a message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -161,6 +165,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
base: constants.cldr_import_base,
|
||||
path: constants.cldr_implied_keys_import
|
||||
}, true)) {
|
||||
// resolveOneImport has already logged a message
|
||||
return false;
|
||||
}
|
||||
} else if (subtag === 'forms') {
|
||||
|
|
@ -169,6 +174,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
base: constants.cldr_import_base,
|
||||
path: constants.cldr_implied_forms_import
|
||||
}, true)) {
|
||||
// resolveOneImport has already logged a message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -266,7 +272,8 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
// An alternative fix would be to pull xml2js directly from github
|
||||
// rather than using the version tagged on npmjs.com.
|
||||
});
|
||||
parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile }); // TODO-LDML: isn't 'e' the error?
|
||||
const data = new TextDecoder().decode(file);
|
||||
parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as LDMLKeyboardXMLSourceFile }); // TODO-LDML: isn't 'e' the error?
|
||||
return a;
|
||||
})();
|
||||
return source;
|
||||
|
|
@ -278,14 +285,23 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
*/
|
||||
public load(file: Uint8Array): LDMLKeyboardXMLSourceFile | null {
|
||||
if (!file) {
|
||||
throw new Error(`file parameter must not be null`);
|
||||
}
|
||||
|
||||
let source: LDMLKeyboardXMLSourceFile = null;
|
||||
try {
|
||||
source = this.loadUnboxed(file);
|
||||
} catch(e) {
|
||||
this.callbacks.reportMessage(CommonTypesMessages.Error_InvalidXml({e}));
|
||||
return null;
|
||||
}
|
||||
const source = this.loadUnboxed(file);
|
||||
if(this.boxArrays(source)) {
|
||||
|
||||
if (this.boxArrays(source)) {
|
||||
return source;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// boxArrays ... resolveImports has already logged a message
|
||||
return null;
|
||||
}
|
||||
|
||||
loadTestDataUnboxed(file: Uint8Array): any {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export { VariableParser, MarkerParser } from './ldml-keyboard/pattern-parser.js'
|
|||
export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './ldml-keyboard/ldml-keyboard-xml-reader.js';
|
||||
|
||||
export * as Constants from './consts/virtual-key-constants.js';
|
||||
export { ModifierKeyConstants } from './consts/modifier-key-constants.js';
|
||||
|
||||
export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
|
||||
CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
|
||||
|
|
@ -43,4 +46,8 @@ export class CommonTypesMessages {
|
|||
static Error_TestDataUnexpectedArray = (o: {subtag: string}) =>
|
||||
m(this.ERROR_TestDataUnexpectedArray,
|
||||
`Problem reading test data: expected single ${def(o.subtag)} element, found multiple`);
|
||||
|
||||
static ERROR_InvalidXml = SevError | 0x0008;
|
||||
static Error_InvalidXml = (o:{e: any}) =>
|
||||
m(this.ERROR_InvalidXml, `The XML file could not be read: ${(o.e ?? '').toString()}`);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ cpu = 'arm64'
|
|||
endian = 'little'
|
||||
|
||||
[binaries]
|
||||
c = ['clang', '-arch', 'arm64']
|
||||
objc = ['clang', '-arch', 'arm64']
|
||||
cpp = ['clang++', '-arch', 'arm64']
|
||||
c = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13']
|
||||
objc = ['clang', '-arch', 'arm64', '-mmacosx-version-min=10.13']
|
||||
cpp = ['clang++', '-arch', 'arm64', '-mmacosx-version-min=10.13']
|
||||
ar = 'ar'
|
||||
ld = 'ld'
|
||||
strip = 'strip'
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ cpu = 'x86_64'
|
|||
endian = 'little'
|
||||
|
||||
[binaries]
|
||||
c = ['clang', '-arch', 'x86_64']
|
||||
objc = ['clang', '-arch', 'x86_64']
|
||||
cpp = ['clang++', '-arch', 'x86_64']
|
||||
c = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
|
||||
objc = ['clang', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
|
||||
cpp = ['clang++', '-arch', 'x86_64', '-mmacosx-version-min=10.13']
|
||||
ar = 'ar'
|
||||
ld = 'ld'
|
||||
strip = 'strip'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#pragma once
|
||||
|
||||
enum km_core_modifier_state {
|
||||
KM_CORE_MODIFIER_NONE = 0,
|
||||
KM_CORE_MODIFIER_LCTRL = 1 << 0,
|
||||
KM_CORE_MODIFIER_RCTRL = 1 << 1,
|
||||
KM_CORE_MODIFIER_LALT = 1 << 2,
|
||||
|
|
|
|||
|
|
@ -455,7 +455,13 @@ public:
|
|||
bool setLayr(const COMP_KMXPLUS_LAYR *newLayr);
|
||||
bool valid() const;
|
||||
|
||||
/**
|
||||
* @param list index from 0 to layr->listCount
|
||||
*/
|
||||
const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const;
|
||||
/**
|
||||
* @param entry index value: COMP_KMXPLUS_LAYR_LIST.layer but less than COMP_KMXPLUS_LAYR_LIST.layer+COMP_KMXPLUS_LAYR_LIST.count
|
||||
*/
|
||||
const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const;
|
||||
const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const;
|
||||
const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using namespace kmx;
|
|||
/* Globals */
|
||||
|
||||
KMX_BOOL km::core::kmx::g_debug_ToConsole = FALSE;
|
||||
KMX_BOOL km::core::kmx::g_debug_KeymanLog = TRUE;
|
||||
KMX_BOOL km::core::kmx::g_debug_KeymanLog = FALSE; // workaround for #12661
|
||||
KMX_BOOL km::core::kmx::g_silent = FALSE;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t>
|
|||
} else {
|
||||
str = keyEntry->get_to_string();
|
||||
}
|
||||
keys.add((km_core_virtual_key)kmapEntry->vkey, (uint16_t)kmapEntry->mod, str);
|
||||
keys.add((km_core_virtual_key)kmapEntry->vkey, kmapEntry->mod, str);
|
||||
}
|
||||
} // else: no keys! but still valid. Just, no keys.
|
||||
|
||||
|
|
@ -348,8 +348,7 @@ km_core_attr const & ldml_processor::attributes() const {
|
|||
}
|
||||
|
||||
km_core_keyboard_key * ldml_processor::get_key_list() const {
|
||||
km_core_keyboard_key* key_list = new km_core_keyboard_key(KM_CORE_KEYBOARD_KEY_LIST_END);
|
||||
return key_list;
|
||||
return keys.get_key_list();
|
||||
}
|
||||
|
||||
km_core_keyboard_imx * ldml_processor::get_imx_list() const {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include "ldml_vkeys.hpp"
|
||||
#include "kmx_file.h"
|
||||
#include <ldml/keyman_core_ldml.h>
|
||||
#include <set>
|
||||
#include <assert.h>
|
||||
|
||||
namespace km {
|
||||
namespace core {
|
||||
|
|
@ -17,11 +19,119 @@ vkeys::vkeys() : vkey_to_string() {
|
|||
}
|
||||
|
||||
void
|
||||
vkeys::add(km_core_virtual_key vk, uint16_t modifier_state, std::u16string output) {
|
||||
vkeys::add(km_core_virtual_key vk, km_core_ldml_modifier_state modifier_state, std::u16string output) {
|
||||
// construct key
|
||||
const vkey_id id(vk, modifier_state);
|
||||
// assign the string
|
||||
vkey_to_string[id] = output;
|
||||
// includes all keys - including gaps.
|
||||
all_vkeys.insert(id);
|
||||
}
|
||||
|
||||
km_core_keyboard_key *
|
||||
vkeys::get_key_list() const {
|
||||
// prescan to find out which modifier flags are used
|
||||
|
||||
std::size_t other_key_count = 0; // number of 'OTHER' keys, which will need to expand to all of the other_state set ( so other_state.size())
|
||||
|
||||
std::set<km::core::ldml::km_core_ldml_modifier_state> all_modifiers;
|
||||
for (const auto &k : all_vkeys) {
|
||||
const auto mod = k.second;
|
||||
if (mod == LDML_KEYS_MOD_OTHER) {
|
||||
other_key_count++;
|
||||
}
|
||||
all_modifiers.insert(mod);
|
||||
}
|
||||
std::set<km_core_modifier_state> other_state;
|
||||
|
||||
// Alt
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_ALT) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0 && all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
|
||||
// no ALT keys were seen, so OTHER includes ALT
|
||||
other_state.insert(KM_CORE_MODIFIER_ALT);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTL) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_RALT);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_ALTR) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_LALT);
|
||||
}
|
||||
|
||||
// ctrl
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_CTRL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0 && all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
|
||||
// no CTRL keys were seen, so OTHER includes CTRL
|
||||
other_state.insert(KM_CORE_MODIFIER_CTRL);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLL) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_RCTRL);
|
||||
} else if(all_modifiers.count(LDML_KEYS_MOD_CTRLR) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_LCTRL);
|
||||
}
|
||||
|
||||
// shift
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_SHIFT) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_SHIFT);
|
||||
}
|
||||
|
||||
// caps
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_CAPS) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_CAPS);
|
||||
}
|
||||
|
||||
// none- it's possible there is no 'none' layer
|
||||
if (all_modifiers.count(LDML_KEYS_MOD_NONE) == 0) {
|
||||
other_state.insert(KM_CORE_MODIFIER_NONE);
|
||||
}
|
||||
|
||||
// We need ALL combinations of the other_state, except for 'all off'.
|
||||
// The number of additions will be (2**(other_state.size())-1
|
||||
// Also, since the LDML_KEYS_MOD_OTHER modifier is excluded, we
|
||||
// will need to subtract 1 when calculating new_list_size
|
||||
const std::size_t other_expanded_count = (1 << other_state.size()) - 1;
|
||||
|
||||
std::vector<uint32_t> other_expanded_mods(other_expanded_count);
|
||||
|
||||
// populate the expanded list.
|
||||
// we start at 1 because 0 is "all bits off" (00000b)
|
||||
for (std::size_t expansion = 1; expansion <= other_expanded_count; expansion++) {
|
||||
uint32_t &expanded_mod = other_expanded_mods.at(expansion-1) = KM_CORE_MODIFIER_NONE;
|
||||
std::size_t bit_mask = 1;
|
||||
for (const auto mod : other_state) {
|
||||
// Check if this modifier is on in this iteration of the expansion
|
||||
// bit_mask will be 2^0 … 2^(other_state().size()-1)
|
||||
if (bit_mask & expansion) {
|
||||
// do we include this entry? check if this bit is on
|
||||
expanded_mod |= mod;
|
||||
}
|
||||
// shift the bitmask over
|
||||
assert(expanded_mod <= KM_CORE_MODIFIER_MASK_CAPS);
|
||||
bit_mask <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
const std::size_t new_list_size = all_vkeys.size() // original size
|
||||
+ (other_key_count * (other_expanded_count - 1))// number of additional entries needed
|
||||
+ 1; // terminator
|
||||
km_core_keyboard_key *list = new km_core_keyboard_key[new_list_size];
|
||||
std::size_t n = 0;
|
||||
for (const auto &k : all_vkeys) {
|
||||
const auto vkey = k.first;
|
||||
const auto mod = k.second;
|
||||
|
||||
if (mod == LDML_KEYS_MOD_OTHER) {
|
||||
// expand to all of other_state
|
||||
for (const auto expanded_mod : other_expanded_mods) {
|
||||
list[n].key = vkey;
|
||||
list[n++].modifier_flag = expanded_mod;
|
||||
assert(n <= new_list_size);
|
||||
}
|
||||
} else {
|
||||
assert(mod <= KM_CORE_MODIFIER_MASK_CAPS); // that no LDMLisms escape
|
||||
list[n].key = vkey;
|
||||
list[n++].modifier_flag = mod;
|
||||
assert(n <= new_list_size);
|
||||
}
|
||||
}
|
||||
// add the list terminator
|
||||
list[n++] = KM_CORE_KEYBOARD_KEY_LIST_END;
|
||||
assert(n == new_list_size);
|
||||
return list;
|
||||
}
|
||||
|
||||
static const uint16_t BOTH_ALT = LALTFLAG | RALTFLAG;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "keyman_core.h"
|
||||
|
||||
|
|
@ -19,10 +20,17 @@ namespace km {
|
|||
namespace core {
|
||||
namespace ldml {
|
||||
|
||||
/**
|
||||
* LDML keyboards have 32-bit modifier flags in order to support
|
||||
* LDML_KEYS_MOD_OTHER (0x10000), unlike the Core APIs which have only 16 bit
|
||||
* modifier flags.
|
||||
*/
|
||||
typedef uint32_t km_core_ldml_modifier_state;
|
||||
|
||||
/**
|
||||
* identifier for keybag lookup
|
||||
*/
|
||||
typedef std::pair<km_core_virtual_key, uint16_t> vkey_id;
|
||||
typedef std::pair<km_core_virtual_key, km_core_ldml_modifier_state> vkey_id;
|
||||
|
||||
/**
|
||||
* LDML Class to manage all things key related: vkey remapping and vkey to string
|
||||
|
|
@ -30,14 +38,15 @@ typedef std::pair<km_core_virtual_key, uint16_t> vkey_id;
|
|||
class vkeys {
|
||||
private:
|
||||
std::map<vkey_id, std::u16string> vkey_to_string;
|
||||
std::set<vkey_id> all_vkeys;
|
||||
|
||||
public:
|
||||
vkeys();
|
||||
|
||||
/**
|
||||
* add a vkey to the bag
|
||||
* add a vkey to the bag.
|
||||
*/
|
||||
void add(km_core_virtual_key vk, uint16_t modifier_state, std::u16string output);
|
||||
void add(km_core_virtual_key vk, km_core_ldml_modifier_state ldml_modifier_state, std::u16string output);
|
||||
|
||||
/**
|
||||
* Lookup a vkey, returns an empty string if not found
|
||||
|
|
@ -46,6 +55,11 @@ public:
|
|||
std::u16string
|
||||
lookup(km_core_virtual_key vk, uint16_t modifier_state, bool &found) const;
|
||||
|
||||
/**
|
||||
* For implementing ldml_processor::get_key_list()
|
||||
*/
|
||||
km_core_keyboard_key* get_key_list() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Non-recursive internal lookup of a specific ID
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
<!--
|
||||
|
||||
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE]
|
||||
@@expected: \u0037\u1790\u17B6\u0127
|
||||
@@keys: [SHIFT K_BKQUOTE][K_1][K_BKQUOTE][LCTRL K_BKQUOTE][RALT K_BKQUOTE]
|
||||
@@expected: \u0037\u1790\u17B6\u0127\u1790\u17B6\u0065
|
||||
@@keylist: [SHIFT K_BKQUOTE][SHIFT K_1][K_BKQUOTE][K_1][CTRL-do-not-use K_1][ALT-do-not-use K_BKQUOTE][CAPS K_BKQUOTE][ALT-do-not-use CAPS K_BKQUOTE]
|
||||
|
||||
-->
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
|
|
@ -23,5 +24,11 @@
|
|||
<layer id="shift" modifiers="shift">
|
||||
<row keys="seven eee" /> <!-- number row -->
|
||||
</layer>
|
||||
<layer id="control" modifiers="ctrl">
|
||||
<row keys="that gap" /> <!-- number row -->
|
||||
</layer>
|
||||
<layer id="catchall" modifiers="other">
|
||||
<row keys="eee gap" /> <!-- number row -->
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <set>
|
||||
|
||||
#include "path.hpp"
|
||||
#include "state.hpp"
|
||||
|
|
@ -266,6 +267,63 @@ verify_context(std::u16string &text_store, km_core_state *&test_state, std::vect
|
|||
delete[] buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param actual the list from get_key_list()
|
||||
* @param expected optional list with keys to check, can be empty - not exhaistive
|
||||
* @returns true if passing
|
||||
*/
|
||||
bool
|
||||
verify_key_list(std::set<km::tests::key_event> &actual, std::set<km::tests::key_event> &expected) {
|
||||
bool equals = true;
|
||||
// error if any bad modifier keys
|
||||
for(const auto &akey : actual) {
|
||||
if (akey.modifier_state > KM_CORE_MODIFIER_MASK_CAPS) {
|
||||
equals = false;
|
||||
std::u16string dump = convert<char, char16_t>(akey.dump()); // akey.dump()
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had key with bad modifier " << akey.modifier_state << ": " << dump << console_color::reset() << std::endl;
|
||||
}
|
||||
}
|
||||
// error if any expected keys missing (note expected may be empty)
|
||||
for(const auto &ekey : expected) {
|
||||
if (actual.count(ekey) == 0) {
|
||||
equals = false;
|
||||
std::u16string dump = convert<char, char16_t>(ekey.dump()); // akey.dump()
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL - key_map had missing key " << dump << console_color::reset() << std::endl;
|
||||
}
|
||||
}
|
||||
if (equals) {
|
||||
std::wcout << console_color::fg(console_color::GREEN) << " " << actual.size() << " vkeys OK, verified " << expected.size() << console_color::reset() << std::endl;
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actual_list the list from get_key_list()
|
||||
* @param keylist optional string with keys to check, can be empty
|
||||
* @param test the LDML test source, for additional data
|
||||
* @returns true if passing
|
||||
*/
|
||||
bool
|
||||
verify_key_list(const km_core_keyboard_key *actual_list, const std::u16string &expected_list, const km::tests::LdmlTestSource &test) {
|
||||
std::set<km::tests::key_event> actual, expected;
|
||||
// convert actual list
|
||||
while (actual_list != nullptr && !(actual_list->key == 0 && actual_list->modifier_flag == 0)) {
|
||||
km::tests::key_event k(actual_list->key, (uint16_t)actual_list->modifier_flag);
|
||||
actual.insert(k);
|
||||
actual_list++; // advance pointer
|
||||
}
|
||||
// parse the expected list
|
||||
std::string keylist = convert<char16_t, char>(expected_list);
|
||||
while (!keylist.empty() && keylist[0] == '[') {
|
||||
const km::tests::key_event k = km::tests::LdmlEmbeddedTestSource::parse_next_key(keylist);
|
||||
if (!k.empty()) {
|
||||
expected.emplace(k);
|
||||
}
|
||||
}
|
||||
return verify_key_list(actual, expected);
|
||||
}
|
||||
|
||||
int
|
||||
run_test(const km::core::path &source, const km::core::path &compiled, km::tests::LdmlTestSource& test_source) {
|
||||
km_core_keyboard * test_kb = nullptr;
|
||||
|
|
@ -371,6 +429,17 @@ run_test(const km::core::path &source, const km::core::path &compiled, km::tests
|
|||
errorLine = __LINE__;
|
||||
}
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_CHECK_KEYLIST: {
|
||||
std::cout << "- checking keylist" << std::endl;
|
||||
// get keylist from kbd
|
||||
const km_core_keyboard_key* actual_list = test_kb->get_key_list();
|
||||
if (!verify_key_list(actual_list, action.string, test_source)) {
|
||||
errorLine = __LINE__;
|
||||
} else {
|
||||
std::cout << " .. passes." << std::endl;
|
||||
}
|
||||
delete [] actual_list;
|
||||
} break;
|
||||
case km::tests::LDML_ACTION_FAIL: {
|
||||
// test requested failure
|
||||
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "- FAIL: " << action.string << console_color::reset()
|
||||
|
|
@ -421,7 +490,7 @@ int run_all_tests(const km::core::path &source, const km::core::path &compiled,
|
|||
|
||||
std::vector<std::string> failures; // track failures for summary
|
||||
|
||||
int embedded_result = embedded_test_source.load_source(source);
|
||||
int embedded_result = embedded_test_source.load_source(source, compiled);
|
||||
|
||||
if (!filter.empty()) {
|
||||
// Always skip the embedded test if there's a filter.
|
||||
|
|
|
|||
|
|
@ -122,6 +122,50 @@ bool LdmlTestSource::get_expected_beep() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
int LdmlTestSource::load_kmx_plus(const km::core::path &compiled) {
|
||||
|
||||
// check and load the KMX (yes, once again)
|
||||
if(!km::core::ldml_processor::is_kmxplus_file(compiled, rawdata)) {
|
||||
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
|
||||
// initialize the kmxplus object with our copy
|
||||
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
|
||||
|
||||
if (!kmxplus->is_valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
if (!kmxplus->key2Helper.valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
bool LdmlTestSource::get_vkey_table(std::set<key_event> &fillin) const {
|
||||
if (!kmxplus || !kmxplus->is_valid()) {
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
// just dump the kmap table
|
||||
for (KMX_DWORD kmapIdx = 0; kmapIdx < kmxplus->key2->kmapCount; kmapIdx++) {
|
||||
const km::core::kmx::COMP_KMXPLUS_KEYS_KMAP *kmap = kmxplus->key2Helper.getKmap(kmapIdx);
|
||||
if (kmap == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (kmap->vkey > 0xFF) {
|
||||
continue; // synthetic key- skip
|
||||
}
|
||||
fillin.insert(key_event(kmap->vkey, kmap->mod));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// String trim functions from https://stackoverflow.com/a/217605/1836776
|
||||
// trim from start (in place)
|
||||
static inline void
|
||||
|
|
@ -259,12 +303,13 @@ LdmlEmbeddedTestSource::is_token(const std::string token, std::string &line) {
|
|||
}
|
||||
|
||||
int
|
||||
LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
||||
LdmlEmbeddedTestSource::load_source( const km::core::path &path, const km::core::path &compiled ) {
|
||||
const std::string s_keys = "@@keys: ";
|
||||
const std::string s_expected = "@@expected: ";
|
||||
const std::string s_context = "@@context: ";
|
||||
const std::string s_capsLock = "@@capsLock: ";
|
||||
const std::string s_expecterror = "@@expect-error: ";
|
||||
const std::string s_keylist = "@@keylist: ";
|
||||
|
||||
// Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected
|
||||
// result
|
||||
|
|
@ -296,6 +341,10 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
|||
context = parse_source_string(line);
|
||||
} else if (is_token(s_capsLock, line)) {
|
||||
set_caps_lock_on(parse_source_string(line).compare(u"1") == 0);
|
||||
} else if (is_token(s_keylist, line)) {
|
||||
set_keylist(line);
|
||||
} else if (line[0] == '@') {
|
||||
std::cerr << path << " warning, unknown @-command " << line << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +360,13 @@ LdmlEmbeddedTestSource::load_source( const km::core::path &path ) {
|
|||
return __LINE__;
|
||||
}
|
||||
|
||||
return 0;
|
||||
// and load KMX+ for keylist
|
||||
if (!expected_error) {
|
||||
// don't attempt to load KMX+ on expected error
|
||||
return load_kmx_plus(compiled);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
km_core_status
|
||||
|
|
@ -352,7 +407,7 @@ LdmlTestSource::char_to_event(char ch) {
|
|||
}
|
||||
|
||||
uint16_t
|
||||
LdmlTestSource::get_modifier(std::string const m) {
|
||||
LdmlTestSource::get_modifier(std::string const &m) {
|
||||
for (int i = 0; km::core::kmx::s_modifier_names[i].name; i++) {
|
||||
if (m == km::core::kmx::s_modifier_names[i].name) {
|
||||
return km::core::kmx::s_modifier_names[i].modifier;
|
||||
|
|
@ -361,6 +416,12 @@ LdmlTestSource::get_modifier(std::string const m) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::string key_event::dump() const {
|
||||
std::stringstream f;
|
||||
f << "Key: {" << km::core::kmx::Debug_VirtualKey(vk) << ", " << km::core::kmx::Debug_ModifierName(modifier_state) << "}";
|
||||
return f.str();
|
||||
}
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
|
||||
// vkey format is MODIFIER MODIFIER K_NAME
|
||||
|
|
@ -396,34 +457,37 @@ LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) {
|
|||
|
||||
void
|
||||
LdmlEmbeddedTestSource::next_action(ldml_action &fillin) {
|
||||
if (is_done || keys.empty()) {
|
||||
// We were already done. return done.
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
return;
|
||||
if (keys.empty()) {
|
||||
// #3 we are almost done, let's run the key check
|
||||
if (check_keylist) {
|
||||
fillin.type = LDML_ACTION_CHECK_KEYLIST;
|
||||
fillin.string = expected_keylist; // could be empty
|
||||
check_keylist = false;
|
||||
} else {
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
}
|
||||
} else if(keys[0].empty()) {
|
||||
// #2. Then, when we finish a key set, we check the 'expected' at the end of it.
|
||||
// Got to the end of a key set. time to check
|
||||
fillin.type = LDML_ACTION_CHECK_EXPECTED;
|
||||
fillin.string = expected[0]; // copy expected
|
||||
expected.pop_front();
|
||||
keys.pop_front();
|
||||
if (keys.empty()) {
|
||||
is_done = true; // so we get DONE next time
|
||||
}
|
||||
} else {
|
||||
// #1 First, we process each key
|
||||
fillin.type = LDML_ACTION_KEY_EVENT;
|
||||
fillin.k = next_key();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::next_key() {
|
||||
// mutate this->keys
|
||||
return next_key(keys[0]);
|
||||
return parse_next_key(keys[0]);
|
||||
}
|
||||
|
||||
key_event
|
||||
LdmlEmbeddedTestSource::next_key(std::string &keys) {
|
||||
LdmlEmbeddedTestSource::parse_next_key(std::string &keys) {
|
||||
// Parse the next element of the string, chop it off, and return it
|
||||
// mutates keys
|
||||
if (keys.length() == 0)
|
||||
|
|
@ -448,10 +512,10 @@ LdmlEmbeddedTestSource::next_key(std::string &keys) {
|
|||
|
||||
class LdmlJsonTestSource : public LdmlTestSource {
|
||||
public:
|
||||
LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
|
||||
LdmlJsonTestSource(const std::string &path);
|
||||
virtual ~LdmlJsonTestSource();
|
||||
virtual const std::u16string &get_context();
|
||||
int load(const nlohmann::json &test);
|
||||
int load(const nlohmann::json &test, const km::core::path &compiled);
|
||||
virtual void next_action(ldml_action &fillin);
|
||||
private:
|
||||
std::string path;
|
||||
|
|
@ -462,14 +526,14 @@ private:
|
|||
* Which action are we on?
|
||||
*/
|
||||
std::size_t action_index = -1;
|
||||
const km::core::kmx::kmx_plus *kmxplus;
|
||||
/** @return false if not found */
|
||||
bool set_key_from_id(key_event& k, const std::u16string& id);
|
||||
bool loaded_context = false;
|
||||
bool check_keys = true;
|
||||
};
|
||||
|
||||
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
|
||||
:path(path), kmxplus(k) {
|
||||
LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path)
|
||||
:path(path) {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -514,6 +578,13 @@ bool LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id)
|
|||
void
|
||||
LdmlJsonTestSource::next_action(ldml_action &fillin) {
|
||||
if ((action_index+1) >= data["/actions"_json_pointer].size()) {
|
||||
// add check keylist
|
||||
if (check_keys) {
|
||||
fillin.type = LDML_ACTION_CHECK_KEYLIST;
|
||||
fillin.string.clear();
|
||||
check_keys = false;
|
||||
return;
|
||||
}
|
||||
// at end, done
|
||||
fillin.type = LDML_ACTION_DONE;
|
||||
return;
|
||||
|
|
@ -581,19 +652,21 @@ LdmlJsonTestSource::get_context() {
|
|||
return context;
|
||||
}
|
||||
|
||||
int LdmlJsonTestSource::load(const nlohmann::json &data) {
|
||||
int LdmlJsonTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
|
||||
this->data = data;
|
||||
// TODO-LDML: validate here?
|
||||
return 0;
|
||||
// TODO-LDML: validate JSON here?
|
||||
|
||||
// load up the kmx_plus
|
||||
return load_kmx_plus(compiled);
|
||||
}
|
||||
|
||||
#if defined(HAVE_ICU4C)
|
||||
class LdmlJsonRepertoireTestSource : public LdmlTestSource {
|
||||
public:
|
||||
LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *kmxplus);
|
||||
LdmlJsonRepertoireTestSource(const std::string &path );
|
||||
virtual ~LdmlJsonRepertoireTestSource();
|
||||
virtual const std::u16string &get_context();
|
||||
int load(const nlohmann::json &test);
|
||||
int load(const nlohmann::json &test, const km::core::path &compiled);
|
||||
virtual void next_action(ldml_action &fillin);
|
||||
private:
|
||||
std::string path;
|
||||
|
|
@ -605,11 +678,10 @@ private:
|
|||
std::unique_ptr<icu::UnicodeSet> uset;
|
||||
std::unique_ptr<icu::UnicodeSetIterator> iterator;
|
||||
bool need_check = false; // set this after each char
|
||||
const km::core::kmx::kmx_plus *kmxplus;
|
||||
};
|
||||
|
||||
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path, km::core::kmx::kmx_plus *k)
|
||||
:path(path), kmxplus(k){
|
||||
LdmlJsonRepertoireTestSource::LdmlJsonRepertoireTestSource(const std::string &path)
|
||||
:path(path) {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -697,7 +769,7 @@ LdmlJsonRepertoireTestSource::get_context() {
|
|||
return context; // no context needed
|
||||
}
|
||||
|
||||
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
|
||||
int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data, const km::core::path &compiled) {
|
||||
this->data = data; // TODO-LDML
|
||||
// Need an update to json.hpp to use contains()
|
||||
// if (data.contains("/type"_json_pointer)) {
|
||||
|
|
@ -728,7 +800,7 @@ int LdmlJsonRepertoireTestSource::load(const nlohmann::json &data) {
|
|||
// }
|
||||
// #endif
|
||||
iterator = std::unique_ptr<icu::UnicodeSetIterator>(new icu::UnicodeSetIterator(*uset));
|
||||
return 0;
|
||||
return load_kmx_plus(compiled);
|
||||
}
|
||||
#endif // HAVE_ICU4C
|
||||
|
||||
|
|
@ -752,25 +824,6 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
return __LINE__; // empty
|
||||
}
|
||||
|
||||
// check and load the KMX (yes, once again)
|
||||
if(!km::core::ldml_processor::is_kmxplus_file(compiled, rawdata)) {
|
||||
std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto comp_keyboard = (const km::core::kmx::COMP_KEYBOARD*)rawdata.data();
|
||||
// initialize the kmxplus object with our copy
|
||||
kmxplus.reset(new km::core::kmx::kmx_plus(comp_keyboard, rawdata.size()));
|
||||
|
||||
if (!kmxplus->is_valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
if (!kmxplus->key2Helper.valid()) {
|
||||
std::cerr << "kmx_plus invalid" << std::endl;
|
||||
return __LINE__;
|
||||
}
|
||||
|
||||
auto conformsTo = data["/keyboardTest3/conformsTo"_json_pointer].get<std::string>();
|
||||
assert_or_return(std::string(LDML_CLDR_TEST_VERSION_LATEST) == conformsTo);
|
||||
|
|
@ -793,8 +846,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
test_path.append(info_name).append("/tests/").append(tests_name).append("/").append(test_name);
|
||||
// std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl;
|
||||
|
||||
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path, kmxplus.get()));
|
||||
assert_or_return(subtest->load(test) == 0);
|
||||
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path));
|
||||
assert_or_return(subtest->load(test, compiled) == 0);
|
||||
test_map[test_path] = std::unique_ptr<LdmlTestSource>(subtest.release());
|
||||
}
|
||||
}
|
||||
|
|
@ -809,8 +862,8 @@ int LdmlJsonTestSourceFactory::load(const km::core::path &compiled, const km::co
|
|||
std::string test_path;
|
||||
test_path.append(info_name).append("/repertoire/").append(rep_name);
|
||||
|
||||
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path, kmxplus.get()));
|
||||
assert_or_return(reptest->load(rep) == 0);
|
||||
std::unique_ptr<LdmlJsonRepertoireTestSource> reptest(new LdmlJsonRepertoireTestSource(test_path));
|
||||
assert_or_return(reptest->load(rep, compiled) == 0);
|
||||
test_map[test_path] = std::unique_ptr<LdmlTestSource>(reptest.release());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "path.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#include "kmx/kmx_plus.h"
|
||||
|
|
@ -14,6 +15,34 @@ namespace tests {
|
|||
struct key_event {
|
||||
km_core_virtual_key vk;
|
||||
uint16_t modifier_state;
|
||||
public:
|
||||
key_event() : vk(0), modifier_state(0) {
|
||||
}
|
||||
key_event(km_core_virtual_key k, uint16_t m) :vk(k), modifier_state(m) {
|
||||
}
|
||||
std::string dump() const;
|
||||
int compare(const key_event &other) const {
|
||||
if (vk < other.vk) return -1;
|
||||
if (vk > other.vk) return 1;
|
||||
if (modifier_state < other.modifier_state) return -1;
|
||||
if (modifier_state > other.modifier_state) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool operator<(const key_event &other) const {
|
||||
return compare(other) < 0;
|
||||
}
|
||||
bool operator>(const key_event &other) const {
|
||||
return compare(other) > 0;
|
||||
}
|
||||
bool operator==(const key_event &other) const {
|
||||
return compare(other) == 0;
|
||||
}
|
||||
/** true if unset (null) key */
|
||||
bool empty() const {
|
||||
return vk == 0 && modifier_state == 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
enum ldml_action_type {
|
||||
|
|
@ -37,6 +66,10 @@ enum ldml_action_type {
|
|||
* expected text
|
||||
*/
|
||||
LDML_ACTION_CHECK_EXPECTED,
|
||||
/**
|
||||
* string - keylist to check
|
||||
*/
|
||||
LDML_ACTION_CHECK_KEYLIST,
|
||||
// TODO-LDML: gestures, etc? Depends on touch.
|
||||
|
||||
/**
|
||||
|
|
@ -77,10 +110,17 @@ public:
|
|||
virtual km_core_status get_expected_load_status();
|
||||
virtual const std::u16string &get_context() = 0;
|
||||
virtual bool get_expected_beep() const;
|
||||
/**
|
||||
* fillin a list
|
||||
* @param fillin key list to be filled in with all vkeys in the hardware map.
|
||||
* @param modifier modifier to load key list for
|
||||
* @returns false on load fail, true on OK
|
||||
*/
|
||||
bool get_vkey_table(std::set<key_event> &fillin) const;
|
||||
|
||||
// helper functions
|
||||
static key_event char_to_event(char ch);
|
||||
static uint16_t get_modifier(std::string const m);
|
||||
static uint16_t get_modifier(std::string const &m);
|
||||
static std::u16string parse_source_string(std::string const &s);
|
||||
static std::u16string parse_u8_source_string(std::string const &s);
|
||||
|
||||
|
|
@ -102,6 +142,12 @@ private:
|
|||
|
||||
private:
|
||||
bool _caps_lock_on = false;
|
||||
protected:
|
||||
/** populate rawdata and kmxplus */
|
||||
int load_kmx_plus(const km::core::path &compiled);
|
||||
// copy of the kbd data, for lookups
|
||||
std::vector<uint8_t> rawdata;
|
||||
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, std::unique_ptr<km::tests::LdmlTestSource>> JsonTestMap;
|
||||
|
|
@ -120,9 +166,6 @@ class LdmlJsonTestSourceFactory {
|
|||
const JsonTestMap& get_tests() const;
|
||||
private:
|
||||
JsonTestMap test_map;
|
||||
// copy of the kbd data, for lookups
|
||||
std::vector<uint8_t> rawdata;
|
||||
std::unique_ptr<km::core::kmx::kmx_plus> kmxplus;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -134,7 +177,7 @@ public:
|
|||
/**
|
||||
* Load the test_source from comments in the .xml source
|
||||
*/
|
||||
int load_source(const km::core::path &path);
|
||||
int load_source(const km::core::path &path, const km::core::path &compiled);
|
||||
|
||||
virtual km_core_status get_expected_load_status();
|
||||
virtual const std::u16string &get_context();
|
||||
|
|
@ -143,10 +186,6 @@ public:
|
|||
virtual void next_action(ldml_action &fillin);
|
||||
|
||||
private:
|
||||
|
||||
bool is_token(const std::string token, std::string &line);
|
||||
key_event vkey_to_event(std::string const &vk_event);
|
||||
key_event next_key(std::string &keys);
|
||||
key_event next_key();
|
||||
|
||||
std::deque<std::string> keys;
|
||||
|
|
@ -155,6 +194,24 @@ private:
|
|||
bool expected_beep = false;
|
||||
bool expected_error = false;
|
||||
bool is_done = false;
|
||||
/** did we check the keylist yet? */
|
||||
bool check_keylist = true;
|
||||
/** set the expected keys in the keylist */
|
||||
void set_keylist(std::string const& s) {
|
||||
expected_keylist = parse_source_string(s);
|
||||
}
|
||||
|
||||
std::u16string expected_keylist;
|
||||
|
||||
/** returns false on fail and updates the message */
|
||||
bool handle_check_keylist(std::string &message) const;
|
||||
|
||||
// utility
|
||||
static key_event vkey_to_event(std::string const &vk_event);
|
||||
static bool is_token(const std::string token, std::string &line);
|
||||
|
||||
public:
|
||||
static key_event parse_next_key(std::string &keys);
|
||||
};
|
||||
|
||||
} // namespace tests
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[binaries]
|
||||
c = ['$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar.py']
|
||||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
[binaries]
|
||||
c = ['$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar.py']
|
||||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
[binaries]
|
||||
c = ['emcc.py']
|
||||
cpp = ['em++.py']
|
||||
ar = ['emar.py']
|
||||
c = ['emcc']
|
||||
cpp = ['em++']
|
||||
ar = ['emar']
|
||||
exe_wrapper = 'node'
|
||||
|
||||
[properties]
|
||||
|
|
|
|||
|
|
@ -493,9 +493,6 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
|
|||
keyboard_info.languages[language] = {};
|
||||
}
|
||||
|
||||
const fontSource = [].concat(...kmpJsonData.keyboards.map(e => e.displayFont ? [e.displayFont] : []), ...kmpJsonData.keyboards.map(e => e.webDisplayFonts ?? []));
|
||||
const oskFontSource = [].concat(...kmpJsonData.keyboards.map(e => e.oskFont ? [e.oskFont] : []), ...kmpJsonData.keyboards.map(e => e.webOskFonts ?? []));
|
||||
|
||||
for(const bcp47 of Object.keys(keyboard_info.languages)) {
|
||||
const language = keyboard_info.languages[bcp47];
|
||||
|
||||
|
|
@ -520,6 +517,20 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
|
|||
// do it right now.
|
||||
//
|
||||
|
||||
// The code below:
|
||||
// 1. Only includes fonts associated with keyboards which support the current bcp47 (filter)
|
||||
// 2. Joins the displayFont and webDisplayFonts data, and removes duplicates (...new Set())
|
||||
|
||||
const supportedKeyboards = kmpJsonData.keyboards.filter(k => k.languages.find(lang => lang.id == bcp47));
|
||||
const fontSource = [...new Set([].concat(
|
||||
...supportedKeyboards.map(e => e.displayFont ? [e.displayFont] : []),
|
||||
...supportedKeyboards.map(e => e.webDisplayFonts ?? [])
|
||||
))];
|
||||
const oskFontSource = [...new Set([].concat(
|
||||
...supportedKeyboards.map(e => e.oskFont ? [e.oskFont] : []),
|
||||
...supportedKeyboards.map(e => e.webOskFonts ?? [])
|
||||
))];
|
||||
|
||||
if(fontSource.length) {
|
||||
language.font = await this.fontSourceToKeyboardInfoFont(kpsFilename, kmpJsonData, fontSource);
|
||||
if(language.font == null) {
|
||||
|
|
|
|||
|
|
@ -35,12 +35,14 @@
|
|||
"@keymanapp/developer-test-helpers": "*",
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/common-tags": "^1.8.4",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/semver": "^7.3.12",
|
||||
"c8": "^7.12.0",
|
||||
"chai": "^4.3.4",
|
||||
"chalk": "^2.4.2",
|
||||
"common-tags": "^1.8.2",
|
||||
"mocha": "^8.4.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.9.5"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboard, KMXPlus, CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, UnicodeSetParser, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts, defaultCompilerOptions, KMXBuilder, KvkFileWriter, KeymanCompilerArtifactOptional } from '@keymanapp/common-types';
|
||||
import { KMX, LDMLKeyboardXMLSourceFileReader, LDMLKeyboard, KMXPlus, CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, UnicodeSetParser, KeymanCompiler, KeymanCompilerResult, KeymanCompilerArtifacts, defaultCompilerOptions, KMXBuilder, KvkFileWriter, KeymanCompilerArtifactOptional } from '@keymanapp/common-types';
|
||||
import { LdmlCompilerOptions } from './ldml-compiler-options.js';
|
||||
import { CompilerMessages } from './messages.js';
|
||||
import { BkspCompiler, TranCompiler } from './tran.js';
|
||||
|
|
@ -125,18 +125,34 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
return null;
|
||||
}
|
||||
|
||||
outputFilename = outputFilename ?? inputFilename.replace(/\.xml$/, '.kmx');
|
||||
|
||||
// In order for the KMX file to be loaded by non-KMXPlus components, it is helpful
|
||||
// to duplicate some of the metadata
|
||||
KMXPlusMetadataCompiler.addKmxMetadata(kmx.kmxplus, kmx.keyboard, compilerOptions);
|
||||
|
||||
// Use the builder to generate the binary output file
|
||||
const builder = new KMXBuilder(kmx, compilerOptions.saveDebug);
|
||||
const kmx_binary = builder.compile();
|
||||
const kmxBuilder = new KMXBuilder(kmx, compilerOptions.saveDebug);
|
||||
const keyboardId = this.callbacks.path.basename(outputFilename, '.kmx');
|
||||
const vkCompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
|
||||
const vkCompilerResult = vkCompiler.compile(kmx.kmxplus, keyboardId);
|
||||
if(vkCompilerResult === null) {
|
||||
return null;
|
||||
}
|
||||
const vkData = typeof vkCompilerResult == 'object' ? vkCompilerResult : null;
|
||||
|
||||
const vkcompiler = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
|
||||
const vk = vkcompiler.compile(source);
|
||||
const writer = new KvkFileWriter();
|
||||
const kvk_binary = writer.write(vk);
|
||||
if(vkData) {
|
||||
kmx.keyboard.stores.push({
|
||||
dpName: '',
|
||||
dpString: keyboardId + '.kvk',
|
||||
dwSystemID: KMX.KMXFile.TSS_VISUALKEYBOARD
|
||||
});
|
||||
}
|
||||
|
||||
const kmxBinary = kmxBuilder.compile();
|
||||
|
||||
const kvkWriter = new KvkFileWriter();
|
||||
const kvkBinary = vkData ? kvkWriter.write(vkData) : null;
|
||||
|
||||
// Note: we could have a step of generating source files here
|
||||
// KvksFileWriter()...
|
||||
|
|
@ -149,12 +165,10 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
//KMW17.0: const encoder = new TextEncoder();
|
||||
//KMW17.0: const kmw_binary = encoder.encode(kmw_string);
|
||||
|
||||
outputFilename = outputFilename ?? inputFilename.replace(/\.xml$/, '.kmx');
|
||||
|
||||
return {
|
||||
artifacts: {
|
||||
kmx: { data: kmx_binary, filename: outputFilename },
|
||||
kvk: { data: kvk_binary, filename: outputFilename.replace(/\.kmx$/, '.kvk') },
|
||||
kmx: { data: kmxBinary, filename: outputFilename },
|
||||
kvk: kvkBinary ? { data: kvkBinary, filename: outputFilename.replace(/\.kmx$/, '.kvk') } : null,
|
||||
//KMW17.0: js: { data: kmw_binary, filename: outputFilename.replace(/\.kmx$/, '.js') },
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { VarsCompiler } from './vars.js';
|
|||
import { CompilerMessages } from './messages.js';
|
||||
|
||||
/**
|
||||
* Compiler for typrs that don't actually consume input XML
|
||||
* Compiler for types that don't actually consume input XML
|
||||
*/
|
||||
export abstract class EmptyCompiler extends SectionCompiler {
|
||||
private _id: SectionIdent;
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
import { CompilerCallbacks, VisualKeyboard, LDMLKeyboard, TouchLayoutFileWriter, KeymanFileTypes } from "@keymanapp/common-types";
|
||||
import { LdmlCompilerOptions } from "./ldml-compiler-options.js";
|
||||
import { TouchLayoutCompiler } from "./touch-layout-compiler.js";
|
||||
import { LdmlKeyboardVisualKeyboardCompiler } from "./visual-keyboard-compiler.js";
|
||||
|
||||
const MINIMUM_KMW_VERSION = '16.0';
|
||||
|
||||
export class LdmlKeyboardKeymanWebCompiler {
|
||||
private readonly options: LdmlCompilerOptions;
|
||||
private readonly nl: string;
|
||||
private readonly tab: string;
|
||||
constructor(private callbacks: CompilerCallbacks, options?: LdmlCompilerOptions) {
|
||||
this.options = { ...options };
|
||||
this.nl = this.options.saveDebug ? "\n" : '';
|
||||
this.tab = this.options.saveDebug ? " " : '';
|
||||
}
|
||||
|
||||
public compileVisualKeyboard(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) {
|
||||
const nl = this.nl, tab = this.tab;
|
||||
const vkc = new LdmlKeyboardVisualKeyboardCompiler(this.callbacks);
|
||||
const vk: VisualKeyboard.VisualKeyboard = vkc.compile(source);
|
||||
|
||||
let result =
|
||||
`{F: ' 1em ${JSON.stringify(vk.header.unicodeFont.name)}', `+
|
||||
`K102: ${vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 ? 1 : 0}};${nl}` + // TODO-LDML: escape ' and " in font name correctly
|
||||
`${tab}this.KV.KLS={${nl}` +
|
||||
`${tab}${tab}TODO_LDML: ${vk.keys.length}${nl}` +
|
||||
// TODO-LDML: fill in KLS
|
||||
`${tab}}`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public compileTouchLayout(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) {
|
||||
const tlcompiler = new TouchLayoutCompiler();
|
||||
const layout = tlcompiler.compileToJavascript(source);
|
||||
const writer = new TouchLayoutFileWriter({formatted: this.options.saveDebug});
|
||||
return writer.compile(layout);
|
||||
}
|
||||
|
||||
private cleanName(name: string): string {
|
||||
let result = this.callbacks.path.basename(name, KeymanFileTypes.Source.LdmlKeyboard);
|
||||
if(!result) {
|
||||
throw new Error(`Invalid file name ${name}`);
|
||||
}
|
||||
|
||||
result = result.replaceAll(/[^a-z0-9]/g, '_');
|
||||
if(result.match(/^[0-9]/)) {
|
||||
// Can't have a digit as initial
|
||||
result = '_' + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public compile(name: string, source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): string {
|
||||
const nl = this.nl, tab = this.tab;
|
||||
|
||||
const sName = 'Keyboard_'+this.cleanName(name);
|
||||
const displayUnderlying = true; // TODO-LDML
|
||||
const modifierBitmask = 0; // TODO-LDML: define the modifiers used by this keyboard
|
||||
const vkDictionary = ''; // TODO-LDML: vk dictionary for touch keys
|
||||
const hasSupplementaryPlaneChars = false; // TODO-LDML
|
||||
const isRTL = false; // TODO-LDML
|
||||
|
||||
let result =
|
||||
`if(typeof keyman === 'undefined') {${nl}` +
|
||||
`${tab}console.error('Keyboard requires KeymanWeb ${MINIMUM_KMW_VERSION} or later');${nl}` +
|
||||
`} else {${nl}` +
|
||||
`${tab}KeymanWeb.KR(new ${sName}());${nl}` +
|
||||
`}${nl}` +
|
||||
`function ${sName}() {${nl}` +
|
||||
// `${tab}${this.setupDebug()}${nl}` + ? we may use this for modifierBitmask in future
|
||||
// `${tab}this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;${nl}` + ? we probably don't need this, it's for back-compat
|
||||
`${tab}this.KI="${sName}";${nl}` +
|
||||
`${tab}this.KN=${JSON.stringify(source.keyboard3.info.name)};${nl}` +
|
||||
`${tab}this.KMINVER=${JSON.stringify(MINIMUM_KMW_VERSION)};${nl}` +
|
||||
`${tab}this.KV=${this.compileVisualKeyboard(source)};${nl}` +
|
||||
`${tab}this.KDU=${displayUnderlying ? '1' : '0'};${nl}` +
|
||||
`${tab}this.KH="";${nl}` + // TODO-LDML: help text not supported
|
||||
`${tab}this.KM=0;${nl}` + // TODO-LDML: mnemonic layout not supported for LDML keyboards
|
||||
`${tab}this.KBVER=${JSON.stringify(source.keyboard3.version?.number || '0.0')};${nl}` +
|
||||
`${tab}this.KMBM=${modifierBitmask};${nl}`;
|
||||
|
||||
if(isRTL) {
|
||||
result += `${tab}this.KRTL=1;${nl}`;
|
||||
}
|
||||
|
||||
if(hasSupplementaryPlaneChars) {
|
||||
result += `${tab}this.KS=1;${nl}`;
|
||||
}
|
||||
|
||||
if(vkDictionary != '') {
|
||||
result += `${tab}this.KVKD=${JSON.stringify(vkDictionary)};${nl}`;
|
||||
}
|
||||
|
||||
let layoutFile = this.compileTouchLayout(source);
|
||||
if(layoutFile != '') {
|
||||
result += `${tab}this.KVKL=${layoutFile};${nl}`;
|
||||
}
|
||||
// TODO-LDML: KCSS not supported
|
||||
|
||||
// TODO-LDML: embed binary keyboard for loading into Core
|
||||
|
||||
// A LDML keyboard has a no-op for its gs() (begin Unicode) function,
|
||||
// because the functionality is embedded in Keyman Core
|
||||
result += `${tab}this.gs=function(t,e){${nl}`+
|
||||
`${tab}${tab}return 0;${nl}`+ // TODO-LDML: we will start by embedding call into Keyman Core here
|
||||
`${tab}};${nl}`;
|
||||
|
||||
result += `}${nl}`;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ export class LayrCompiler extends SectionCompiler {
|
|||
for (const layer of layers.layer) {
|
||||
const rows = layer.row.map((row) => {
|
||||
const erow: LayrRow = {
|
||||
keys: row.keys.split(' ').map((id) => sections.strs.allocString(id)),
|
||||
keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id)),
|
||||
};
|
||||
return erow;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -174,7 +174,13 @@ export class CompilerMessages {
|
|||
static Error_UnparseableReorderSet = (o: { from: string, set: string }) =>
|
||||
m(this.ERROR_UnparseableReorderSet, `Illegal UnicodeSet "${def(o.set)}" in reorder "${def(o.from)}`);
|
||||
|
||||
// Available: 0x029
|
||||
static ERROR_InvalidVariableIdentifer = SevError | 0x0029;
|
||||
static Error_InvalidVariableIdentifer = (o: { id: string }) => m(
|
||||
this.ERROR_InvalidVariableIdentifer,
|
||||
`Invalid variable identifier "\\u${def(o.id)}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`,
|
||||
);
|
||||
|
||||
// Available: 0x02A-0x2F
|
||||
|
||||
static ERROR_InvalidQuadEscape = SevError | 0x0030;
|
||||
static Error_InvalidQuadEscape = (o: { cp: number }) =>
|
||||
|
|
@ -189,9 +195,12 @@ export class CompilerMessages {
|
|||
m(this.ERROR_UnparseableTransformFrom, `Invalid transform from="${def(o.from)}": "${def(o.message)}"`);
|
||||
|
||||
static ERROR_IllegalTransformDollarsign = SevErrorTransform | 0x01;
|
||||
static Error_IllegalTransformDollarsign = (o: { from: string }) =>
|
||||
m(this.ERROR_IllegalTransformDollarsign, `Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`,
|
||||
'**Hint**: Use `\\$` to match a literal dollar-sign.');
|
||||
static Error_IllegalTransformDollarsign = (o: { from: string }) => m(
|
||||
this.ERROR_IllegalTransformDollarsign,
|
||||
`Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`,
|
||||
`**Hint**: Use \`\\$\` to match a literal dollar-sign. If this precedes a variable name, `+
|
||||
`the variable name may not be valid (A-Z, a-z, 0-9, _, 32 character maximum).`
|
||||
);
|
||||
|
||||
static ERROR_TransformFromMatchesNothing = SevErrorTransform | 0x02;
|
||||
static Error_TransformFromMatchesNothing = (o: { from: string }) =>
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
import { TouchLayout, LDMLKeyboard } from "@keymanapp/common-types";
|
||||
|
||||
export class TouchLayoutCompiler {
|
||||
public compileToJavascript(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): TouchLayout.TouchLayoutFile {
|
||||
let result: TouchLayout.TouchLayoutFile = {};
|
||||
|
||||
// start with desktop to mimic vk emit
|
||||
result.desktop = {
|
||||
defaultHint: "none", // TODO-LDML this should be optional
|
||||
layer: []
|
||||
};
|
||||
|
||||
for(let layers of source.keyboard3.layers) {
|
||||
for(let layer of layers.layer) {
|
||||
const resultLayer = this.compileHardwareLayer(source, result, layer);
|
||||
result.desktop.layer.push(resultLayer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private compileHardwareLayer(
|
||||
source: LDMLKeyboard.LDMLKeyboardXMLSourceFile,
|
||||
file: TouchLayout.TouchLayoutFile,
|
||||
layer: LDMLKeyboard.LKLayer
|
||||
) {
|
||||
// TODO-LDML: consider consolidation with keys.ts?
|
||||
|
||||
let fileLayer: TouchLayout.TouchLayoutLayer = {
|
||||
id: this.translateLayerIdToTouchLayoutLayerId(layer.id, layer.modifiers),
|
||||
row: []
|
||||
};
|
||||
|
||||
let y = -1;
|
||||
|
||||
for(let row of layer.row) {
|
||||
y++;
|
||||
|
||||
let fileRow: TouchLayout.TouchLayoutRow = {id: y, key: []};
|
||||
fileLayer.row.push(fileRow);
|
||||
|
||||
const keys = row.keys.split(' ');
|
||||
for(let key of keys) {
|
||||
const keydef = source.keyboard3.keys?.key?.find(x => x.id == key);
|
||||
if(keydef) {
|
||||
const fileKey: TouchLayout.TouchLayoutKey = {
|
||||
id: this.translateKeyIdentifierToTouch(keydef.id) as TouchLayout.TouchLayoutKeyId,
|
||||
text: keydef.output || '',
|
||||
// TODO-LDML: additional properties
|
||||
};
|
||||
fileRow.key.push(fileKey);
|
||||
} else {
|
||||
// TODO-LDML: consider logging missing keys
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileLayer;
|
||||
}
|
||||
|
||||
private translateLayerIdToTouchLayoutLayerId(id: string, modifier: string): string {
|
||||
// Touch layout layers have a set of reserved names that correspond to
|
||||
// hardware modifiers. We want to map these identifiers first before falling
|
||||
// back to the layer ids
|
||||
|
||||
// The set of recognized layer identifiers is:
|
||||
//
|
||||
// touch | LDML
|
||||
// ---------------+-------------
|
||||
// default | none
|
||||
// shift | shift
|
||||
// caps | caps
|
||||
// rightalt | altR
|
||||
// rightalt-shift | altR shift
|
||||
//
|
||||
const map = {
|
||||
none: 'default',
|
||||
shift: 'shift',
|
||||
caps: 'caps',
|
||||
altR: 'rightalt',
|
||||
"altR shift": 'rightalt-shift'
|
||||
};
|
||||
|
||||
// canonicalize modifier string, alphabetical
|
||||
// TODO-LDML: need to support multiple here
|
||||
if (modifier && modifier.indexOf(',') !== -1) {
|
||||
throw Error(`Internal error: TODO-LDML: multiple modifiers ${modifier} not yet supported.`);
|
||||
}
|
||||
modifier = (modifier||'').split(/\b/).sort().join(' ').trim();
|
||||
|
||||
if(Object.hasOwn(map, modifier)) {
|
||||
return (map as any)[modifier];
|
||||
}
|
||||
|
||||
// TODO-LDML: Other layer names will be used unmodified, is this sufficient?
|
||||
return id;
|
||||
}
|
||||
|
||||
private translateKeyIdentifierToTouch(id: string): string {
|
||||
// Note: keys identifiers in kmx were traditionally case-insensitive, but we
|
||||
// are going to use them as case-insensitive for LDML keyboards. The set of
|
||||
// standard key identifiers a-z, A-Z, 0-9 will be used, where possible, and
|
||||
// all other keys will be mapped to `T_key`.
|
||||
|
||||
if(id.match(/^[0-9a-zA-Z]$/)) {
|
||||
return 'K_'+id;
|
||||
}
|
||||
|
||||
// Not a standard key
|
||||
return 'T_'+id;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,14 @@ export class VarsCompiler extends SectionCompiler {
|
|||
return valid;
|
||||
}
|
||||
|
||||
private validateIdentifier(id: string) {
|
||||
if(!id.match(VariableParser.ID)) { // From <string> DTD
|
||||
this.callbacks.reportMessage(CompilerMessages.Error_InvalidVariableIdentifer({id}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private validateVars(st: Substitutions): boolean {
|
||||
let valid = true;
|
||||
const variables = this.keyboard3?.variables;
|
||||
|
|
@ -67,13 +75,28 @@ export class VarsCompiler extends SectionCompiler {
|
|||
if (variables) {
|
||||
// Strings
|
||||
for (const { id, value } of variables.string) {
|
||||
if(!this.validateIdentifier(id)) {
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
addId(id);
|
||||
allStrings.add(id);
|
||||
const stringrefs = VariableParser.allStringReferences(value);
|
||||
for(const ref of stringrefs) {
|
||||
if(!allStrings.has(ref)) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(CompilerMessages.Error_MissingStringVariable({id: ref}));
|
||||
allStrings.add(ref); // avoids multiple reports of same missing variable
|
||||
}
|
||||
}
|
||||
st.string.add(SubstitutionUse.variable, stringrefs);
|
||||
allStrings.add(id);
|
||||
}
|
||||
// Sets
|
||||
for (const { id, value } of variables.set) {
|
||||
if(!this.validateIdentifier(id)) {
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
addId(id);
|
||||
allSets.add(id);
|
||||
// check for illegal references, here.
|
||||
|
|
@ -96,6 +119,10 @@ export class VarsCompiler extends SectionCompiler {
|
|||
}
|
||||
// UnicodeSets
|
||||
for (const { id, value } of variables.uset) {
|
||||
if(!this.validateIdentifier(id)) {
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
addId(id);
|
||||
allUnicodeSets.add(id);
|
||||
const stringrefs = VariableParser.allStringReferences(value);
|
||||
|
|
|
|||
|
|
@ -1,84 +1,164 @@
|
|||
import { VisualKeyboard, LDMLKeyboard, CompilerCallbacks } from "@keymanapp/common-types";
|
||||
import { KeysCompiler } from "./keys.js";
|
||||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Export LDML data (https://www.unicode.org/reports/tr35/tr35-keyboards.html)
|
||||
* to .kvk format. This is an interim solution until Keyman Core supports
|
||||
* interrogation of the KMX+ data for OSK.
|
||||
*/
|
||||
import { ModifierKeyConstants, KMXPlus } from "@keymanapp/common-types";
|
||||
import { VisualKeyboard, CompilerCallbacks } from "@keymanapp/common-types";
|
||||
import { CompilerMessages } from "./messages.js";
|
||||
|
||||
// This is a partial polyfill for findLast, so not polluting Array.prototype
|
||||
//
|
||||
// TODO: remove and replace with Array.prototype.findLast when it is
|
||||
// well-supported
|
||||
function findLast(arr: any, callback: any) {
|
||||
if (!arr) {
|
||||
return undefined;
|
||||
}
|
||||
const len = arr.length >>> 0;
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
if (callback(arr[i], i, arr)) {
|
||||
return arr[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
const LDML_MODIFIER_TO_KVK_MODIFIER = new Map<number, number>();
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LCTRL);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RCTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RCTRL);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.LALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_LALT);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.RALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_SHIFTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_CTRLFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_CTRL);
|
||||
LDML_MODIFIER_TO_KVK_MODIFIER.set(ModifierKeyConstants.K_ALTFLAG, VisualKeyboard.VisualKeyboardShiftState.KVKS_ALT);
|
||||
|
||||
export class LdmlKeyboardVisualKeyboardCompiler {
|
||||
public constructor(private callbacks: CompilerCallbacks) {
|
||||
}
|
||||
|
||||
public compile(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): VisualKeyboard.VisualKeyboard {
|
||||
/**
|
||||
* Generate a visual keyboard
|
||||
* @param source Compiled KMX+ data; note that this is modified to add
|
||||
* &VISUALKEYBOARD system store on success
|
||||
* @param keyboardId Basename of keyboard, without file extension
|
||||
* @returns Visual keyboard data on success, null on failure, or
|
||||
* false if no VK was generated for this keyboard
|
||||
*/
|
||||
public compile(source: KMXPlus.KMXPlusData, keyboardId: string): VisualKeyboard.VisualKeyboard | boolean | null {
|
||||
let result = new VisualKeyboard.VisualKeyboard();
|
||||
|
||||
/* TODO-LDML: consider VisualKeyboardHeaderFlags.kvkhUseUnderlying kvkhDisplayUnderlying kvkhAltGr kvkh102 */
|
||||
result.header.flags = 0;
|
||||
result.header.version = 0x0600;
|
||||
|
||||
/* TODO-LDML: consider associatedKeyboard: this _must_ be set to id (aka basename sans ext) of keyboard .kmx file */
|
||||
result.header.associatedKeyboard = '';
|
||||
result.header.associatedKeyboard = keyboardId;
|
||||
result.header.ansiFont = {...VisualKeyboard.DEFAULT_KVK_FONT};
|
||||
result.header.unicodeFont = {...VisualKeyboard.DEFAULT_KVK_FONT};
|
||||
|
||||
for(let layers of source.keyboard3.layers) {
|
||||
const { formId } = layers;
|
||||
for(let layer of layers.layer) {
|
||||
this.compileHardwareLayer(source, result, layer, formId);
|
||||
let hasVisualKeyboard = false;
|
||||
|
||||
for(let layersList of source.layr.lists) {
|
||||
const formId = layersList.hardware.value;
|
||||
if(formId == 'touch') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(let layer of layersList.layers) {
|
||||
const res = this.compileHardwareLayer(source, result, layer, formId);
|
||||
if(res === false) {
|
||||
// failed to compile the layer
|
||||
return null;
|
||||
}
|
||||
if(res === null) {
|
||||
// not a supported layer type, but not an error
|
||||
continue;
|
||||
}
|
||||
hasVisualKeyboard = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasVisualKeyboard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private compileHardwareLayer(
|
||||
source: KMXPlus.KMXPlusData,
|
||||
vk: VisualKeyboard.VisualKeyboard,
|
||||
layer: KMXPlus.LayrEntry,
|
||||
hardware: string,
|
||||
) {
|
||||
const layerId = layer.id.value;
|
||||
|
||||
hardware = 'us'; // TODO-LDML: US Only. We need to clean this up for other hardware forms
|
||||
|
||||
const shift = this.translateLayerModifiersToVisualKeyboardShift(layer.mod);
|
||||
if(shift === null) {
|
||||
// Caps (num, scroll) is not a supported shift state in .kvk
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
let y = -1;
|
||||
for(let row of layer.rows) {
|
||||
y++;
|
||||
let x = -1;
|
||||
for(let key of row.keys) {
|
||||
x++;
|
||||
|
||||
const keydef: KMXPlus.KeysKeys = findLast(source.keys?.keys, (kd: KMXPlus.KeysKeys) => kd.id.value == key.value);
|
||||
const kmap = source.keys.kmap.find(k => k.key == keydef.id.value && k.mod == layer.mod);
|
||||
const text = this.getDisplayFromKey(keydef, source) ?? null;
|
||||
|
||||
if (!keydef || !kmap || text === null) {
|
||||
this.callbacks.reportMessage(
|
||||
CompilerMessages.Error_KeyNotFoundInKeyBag({ keyId: key.value, layer: layerId, row: y, col: x, form: hardware })
|
||||
);
|
||||
result = false;
|
||||
} else {
|
||||
vk.keys.push({
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
shift,
|
||||
text,
|
||||
vkey: kmap.vkey
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private compileHardwareLayer(
|
||||
source: LDMLKeyboard.LDMLKeyboardXMLSourceFile,
|
||||
vk: VisualKeyboard.VisualKeyboard,
|
||||
layer: LDMLKeyboard.LKLayer,
|
||||
hardware: string,
|
||||
) {
|
||||
const layerId = layer.id;
|
||||
if (hardware === 'touch') {
|
||||
hardware = 'us'; // TODO-LDML: US Only. Do something different here?
|
||||
private getDisplayFromKey(keydef: KMXPlus.KeysKeys, source: KMXPlus.KMXPlusData) {
|
||||
const display = source.disp?.disps?.find(d => d.id.value == keydef.id.value || d.to.value == keydef.to.value);
|
||||
return display?.display.value ?? keydef.to.value;
|
||||
}
|
||||
|
||||
private translateLayerModifiersToVisualKeyboardShift(modifiers: number): VisualKeyboard.VisualKeyboardShiftState {
|
||||
|
||||
if(modifiers == 0) {
|
||||
return VisualKeyboard.VisualKeyboardShiftState.KVKS_NORMAL;
|
||||
}
|
||||
const keymap = KeysCompiler.getKeymapFromForms(source.keyboard3?.forms?.form, hardware);
|
||||
if (!keymap) {
|
||||
this.callbacks.reportMessage(
|
||||
CompilerMessages.Error_InvalidHardware({ formId: hardware })
|
||||
);
|
||||
return;
|
||||
|
||||
if(modifiers &
|
||||
(ModifierKeyConstants.CAPITALFLAG | ModifierKeyConstants.NUMLOCKFLAG | ModifierKeyConstants.SCROLLFLAG)
|
||||
) {
|
||||
// Caps/Num/Scroll are not supported in .kvk, in combination or alone
|
||||
return null;
|
||||
}
|
||||
const shift = this.translateLayerIdToVisualKeyboardShift(layer.id);
|
||||
|
||||
let y = -1;
|
||||
for(let row of layer.row) {
|
||||
y++;
|
||||
let shift: VisualKeyboard.VisualKeyboardShiftState = 0;
|
||||
|
||||
const keys = row.keys.split(' ');
|
||||
let x = -1;
|
||||
for(let key of keys) {
|
||||
const keyId = key;
|
||||
x++;
|
||||
|
||||
let keydef = source.keyboard3.keys?.key?.find(x => x.id == key);
|
||||
|
||||
if (!keydef) {
|
||||
this.callbacks.reportMessage(
|
||||
CompilerMessages.Error_KeyNotFoundInKeyBag({ keyId, layer: layerId, row: y, col: x, form: hardware })
|
||||
);
|
||||
} else {
|
||||
vk.keys.push({
|
||||
flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode,
|
||||
shift: shift,
|
||||
text: keydef.output, // TODO-LDML: displays
|
||||
vkey: keymap[y][x],
|
||||
});
|
||||
}
|
||||
for(const mod of LDML_MODIFIER_TO_KVK_MODIFIER.keys()) {
|
||||
if(modifiers & mod) {
|
||||
shift |= LDML_MODIFIER_TO_KVK_MODIFIER.get(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private translateLayerIdToVisualKeyboardShift(id: string) {
|
||||
if(id == 'base') {
|
||||
return 0;
|
||||
}
|
||||
// TODO-LDML: other modifiers
|
||||
return 0;
|
||||
return shift;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#
|
||||
# Keyman is copyright (C) SIL International. MIT License.
|
||||
#
|
||||
# basic-kvk.txt describes the expected output of running kmc against basic.xml to generate
|
||||
# a .kvk file. It is used in the end-to-end test test-visual-keyboard-compiler-e2e.ts.
|
||||
#
|
||||
|
|
@ -10,8 +12,8 @@ block(kvkheader)
|
|||
00 06 00 00 # version 0x0600
|
||||
00 # flags
|
||||
block(associated_keyboard)
|
||||
01 00 # string len in UTF-16 code units incl zterm
|
||||
00 00 # '\0'
|
||||
06 00 # string len in UTF-16 code units incl zterm
|
||||
62 00 61 00 73 00 69 00 63 00 00 00 # 'basic\0'
|
||||
|
||||
block(ansi_font)
|
||||
06 00 # string len in UTF-16 code units incl zterm
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
if(typeof keyman === 'undefined') {console.error('Keyboard requires KeymanWeb 16.0 or later');} else {KeymanWeb.KR(new Keyboard_basic());}function Keyboard_basic() {this.KI="Keyboard_basic";this.KN="TestKbd";this.KMINVER="16.0";this.KV={F: ' 1em "Arial"', K102: 0};this.KV.KLS={TODO_LDML: 2};this.KDU=1;this.KH="";this.KM=0;this.KBVER="1.0.0";this.KMBM=0;this.KVKL={"desktop":{"defaultHint":"none","layer":[{"id":"base","row":[{"id":"0","key":[{"id":"T_hmaqtugha","text":"ħ"},{"id":"T_that","text":"ថា"}]}]}],"displayUnderlying":false}};this.gs=function(t,e){return 0;};}
|
||||
48
developer/src/kmc-ldml/test/fixtures/basic.js
vendored
48
developer/src/kmc-ldml/test/fixtures/basic.js
vendored
|
|
@ -1,48 +0,0 @@
|
|||
if(typeof keyman === 'undefined') {
|
||||
console.error('Keyboard requires KeymanWeb 16.0 or later');
|
||||
} else {
|
||||
KeymanWeb.KR(new Keyboard_basic());
|
||||
}
|
||||
function Keyboard_basic() {
|
||||
this.KI="Keyboard_basic";
|
||||
this.KN="TestKbd";
|
||||
this.KMINVER="16.0";
|
||||
this.KV={F: ' 1em "Arial"', K102: 0};
|
||||
this.KV.KLS={
|
||||
TODO_LDML: 2
|
||||
};
|
||||
this.KDU=1;
|
||||
this.KH="";
|
||||
this.KM=0;
|
||||
this.KBVER="1.0.0";
|
||||
this.KMBM=0;
|
||||
this.KVKL={
|
||||
"desktop": {
|
||||
"defaultHint": "none",
|
||||
"layer": [
|
||||
{
|
||||
"id": "base",
|
||||
"row": [
|
||||
{
|
||||
"id": "0",
|
||||
"key": [
|
||||
{
|
||||
"id": "T_hmaqtugha",
|
||||
"text": "ħ"
|
||||
},
|
||||
{
|
||||
"id": "T_that",
|
||||
"text": "ថា"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"displayUnderlying": false
|
||||
}
|
||||
};
|
||||
this.gs=function(t,e){
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
#
|
||||
# Keyman is copyright (C) SIL International. MIT License.
|
||||
#
|
||||
# basic.txt describes the expected output of running kmc against basic.xml. It is used in
|
||||
# the end-to-end test test-compiler-e2e.ts.
|
||||
#
|
||||
|
|
@ -402,7 +404,7 @@ block(layr) # struct COMP_KMXPLUS_LAYR {
|
|||
01 00 00 00 # count
|
||||
7B 00 00 00 # KMX_DWORD minDeviceWidth; // 123
|
||||
# layers 0
|
||||
index(strNull,strBase,2) # KMXPLUS_STR id;
|
||||
00 00 00 00 # KMXPLUS_STR id;
|
||||
00 00 00 00 # KMX_DWORD mod
|
||||
00 00 00 00 # KMX_DWORD row index
|
||||
01 00 00 00 # KMX_DWORD count
|
||||
|
|
@ -502,7 +504,6 @@ block(strs) # struct COMP_KMXPLUS_STRS {
|
|||
diff(strs,strSet) sizeof(strSet,2)
|
||||
diff(strs,strSet2) sizeof(strSet2,2)
|
||||
diff(strs,strTranTo) sizeof(strTranTo,2)
|
||||
diff(strs,strBase) sizeof(strBase,2)
|
||||
diff(strs,strElemBkspFrom2) sizeof(strElemBkspFrom2,2)
|
||||
diff(strs,strGapReserved) sizeof(strGapReserved,2)
|
||||
diff(strs,strHmaqtugha) sizeof(strHmaqtugha,2)
|
||||
|
|
@ -538,7 +539,6 @@ block(strs) # struct COMP_KMXPLUS_STRS {
|
|||
#str #0A
|
||||
block(strSet2) 61 00 62 00 63 00 block(x) 00 00 # 'abc'
|
||||
block(strTranTo) 61 00 02 03 block(x) 00 00 # 'â' (U+0061 U+0302)
|
||||
block(strBase) 62 00 61 00 73 00 65 00 block(x) 00 00 # 'base'
|
||||
block(strElemBkspFrom2) 65 00 block(x) 00 00 # 'e'
|
||||
block(strGapReserved) 67 00 61 00 70 00 20 00 28 00 72 00 65 00 73 00 65 00 72 00 76 00 65 00 64 00 29 00 block(x) 00 00 # 'gap (reserved)'
|
||||
block(strHmaqtugha) 68 00 6d 00 61 00 71 00 74 00 75 00 67 00 68 00 61 00 block(x) 00 00 # 'hmaqtugha'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Keyman is copyright (C) SIL International. MIT License. -->
|
||||
|
||||
<!--
|
||||
@@keys: [K_Q][K_W][K_Q]
|
||||
|
|
@ -23,7 +24,8 @@
|
|||
</keys>
|
||||
|
||||
<layers formId="us" minDeviceWidth="123">
|
||||
<layer id="base">
|
||||
<!-- TODO-LDML: unit test for <layers formId="us" with <layer id="base" should be illegal -->
|
||||
<layer modifiers="none">
|
||||
<row keys="hmaqtugha that" />
|
||||
</layer>
|
||||
</layers>
|
||||
|
|
|
|||
18
developer/src/kmc-ldml/test/fixtures/sections/layr/row-keys-whitespace.xml
vendored
Normal file
18
developer/src/kmc-ldml/test/fixtures/sections/layr/row-keys-whitespace.xml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="keys-minimal"/>
|
||||
|
||||
<keys>
|
||||
<key id="grave" output="🪦" />
|
||||
<key id="mistake" output="oops" />
|
||||
</keys>
|
||||
|
||||
<layers formId="us">
|
||||
<layer id="base">
|
||||
<row keys=" grave mistake" />
|
||||
<row keys=" grave mistake " />
|
||||
</layer>
|
||||
</layers>
|
||||
|
||||
</keyboard3>
|
||||
15
developer/src/kmc-ldml/test/fixtures/sections/vars/fail-badref-7.xml
vendored
Normal file
15
developer/src/kmc-ldml/test/fixtures/sections/vars/fail-badref-7.xml
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="vars-fail"/>
|
||||
|
||||
<keys />
|
||||
|
||||
<!-- from spec -->
|
||||
<variables>
|
||||
<string id="y" value="${usedBeforeDefinition}" /> <!-- FAIL: reference before definition -->
|
||||
<string id="usedBeforeDefinition" value="yes" />
|
||||
</variables>
|
||||
|
||||
|
||||
</keyboard3>
|
||||
17
developer/src/kmc-ldml/test/fixtures/sections/vars/fail-invalid-identifiers.xml
vendored
Normal file
17
developer/src/kmc-ldml/test/fixtures/sections/vars/fail-invalid-identifiers.xml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="vars-minimal"/>
|
||||
|
||||
<keys />
|
||||
|
||||
<!-- from spec -->
|
||||
<variables>
|
||||
<string id="invalid-string" value="yes" /> <!-- ERROR_InvalidVariableIdentifer -->
|
||||
<set id="invalid-set" value="A B C D E FF" /> <!-- ERROR_InvalidVariableIdentifer -->
|
||||
<uset id="invalid-uset" value="[कसतनमह]" /> <!-- ERROR_InvalidVariableIdentifer -->
|
||||
<set id="a_marker_name_more_than_32_chars_long" value="Y E S" /> <!-- ERROR_InvalidVariableIdentifer -->
|
||||
<string id="😡" value="UPPERCASE" /> <!-- ERROR_InvalidVariableIdentifer -->
|
||||
</variables>
|
||||
|
||||
</keyboard3>
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
/**
|
||||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Helpers and utilities for the Mocha tests.
|
||||
*/
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
|
||||
import { util, KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, compilerEventFormat, LDMLKeyboard, UnicodeSetParser, CompilerCallbacks } from '@keymanapp/common-types';
|
||||
import { util, KMXPlus, LDMLKeyboardXMLSourceFileReader, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, compilerEventFormat, LDMLKeyboard, UnicodeSetParser, CompilerCallbacks } from '@keymanapp/common-types';
|
||||
import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles
|
||||
import { assert } from 'chai';
|
||||
import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js';
|
||||
import { LdmlCompilerOptions } from '../../src/compiler/ldml-compiler-options.js';
|
||||
import { LdmlKeyboardVisualKeyboardCompiler } from '../../src/compiler/visual-keyboard-compiler.js';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
|
||||
import KMXPlusFile = KMXPlus.KMXPlusFile;
|
||||
|
|
@ -20,7 +21,6 @@ import Section = KMXPlus.Section;
|
|||
import { ElemCompiler, ListCompiler, StrsCompiler } from '../../src/compiler/empty-compiler.js';
|
||||
import { KmnCompiler } from '@keymanapp/kmc-kmn';
|
||||
import { VarsCompiler } from '../../src/compiler/vars.js';
|
||||
// import Vars = KMXPlus.Vars;
|
||||
|
||||
/**
|
||||
* Builds a path to the fixture with the given path components.
|
||||
|
|
@ -47,7 +47,7 @@ beforeEach(function() {
|
|||
|
||||
afterEach(function() {
|
||||
if (this.currentTest.state !== 'passed') {
|
||||
compilerTestCallbacks.messages.forEach(message => console.log(message.message));
|
||||
compilerTestCallbacks.printMessages();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -160,28 +160,7 @@ export async function compileKeyboard(inputFilename: string, options: LdmlCompil
|
|||
return kmx;
|
||||
}
|
||||
|
||||
export async function compileVisualKeyboard(inputFilename: string, options: LdmlCompilerOptions): Promise<VisualKeyboard.VisualKeyboard> {
|
||||
const k = new LdmlKeyboardCompiler();
|
||||
assert.isTrue(await k.init(compilerTestCallbacks, options));
|
||||
const source = k.load(inputFilename);
|
||||
checkMessages();
|
||||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
const valid = await k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
|
||||
const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(source);
|
||||
checkMessages();
|
||||
assert.isNotNull(vk, 'LdmlKeyboardVisualKeyboardCompiler.compile should not have returned null');
|
||||
|
||||
return vk;
|
||||
}
|
||||
|
||||
export function checkMessages() {
|
||||
if(compilerTestCallbacks.messages.length > 0) {
|
||||
console.log(compilerTestCallbacks.messages);
|
||||
}
|
||||
assert.isEmpty(compilerTestCallbacks.messages, compilerEventFormat(compilerTestCallbacks.messages));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js';
|
||||
import { LdmlKeyboardKeymanWebCompiler } from '../src/compiler/keymanweb-compiler.js';
|
||||
import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js';
|
||||
import * as fs from 'fs';
|
||||
|
||||
describe('LdmlKeyboardKeymanWebCompiler', function() {
|
||||
|
||||
it('should build a .js file', async function() {
|
||||
// Let's build basic.xml
|
||||
// It should generate content identical to basic.js
|
||||
const inputFilename = makePathToFixture('basic.xml');
|
||||
const outputFilename = makePathToFixture('basic.js');
|
||||
const outputFilenameNoDebug = makePathToFixture('basic-no-debug.js');
|
||||
|
||||
// Load input data; we'll use the LDML keyboard compiler loader to save us
|
||||
// effort here
|
||||
const k = new LdmlKeyboardCompiler();
|
||||
await k.init(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
|
||||
const source = k.load(inputFilename);
|
||||
checkMessages();
|
||||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
// Sanity check ... this is also checked in other tests
|
||||
const valid = await k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
|
||||
// Actual test: compile to javascript
|
||||
const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true});
|
||||
const output = jsCompiler.compile('basic.xml', source);
|
||||
assert.isNotNull(output);
|
||||
|
||||
// Does the emitted js match?
|
||||
const outputFixture = fs.readFileSync(outputFilename, 'utf-8').replaceAll(/\r\n/g, '\n');
|
||||
assert.strictEqual(output, outputFixture);
|
||||
|
||||
// Second test: compile to javascript without debug formatting
|
||||
const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, saveDebug: false});
|
||||
const outputNoDebug = jsCompilerNoDebug.compile('basic.xml', source);
|
||||
assert.isNotNull(outputNoDebug);
|
||||
|
||||
// Does the emitted js match? The nodebug has no newline at end, but allow one in the fixture
|
||||
const outputFixtureNoDebug = fs.readFileSync(outputFilenameNoDebug, 'utf-8').replaceAll(/\r\n/g, '\n').trim();
|
||||
assert.strictEqual(outputNoDebug, outputFixtureNoDebug);
|
||||
|
||||
// TODO(lowpri): consider using Typescript parser to generate AST for further validation
|
||||
});
|
||||
});
|
||||
|
|
@ -129,5 +129,30 @@ describe('layr', function () {
|
|||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
subpath: 'sections/layr/row-keys-whitespace.xml',
|
||||
callback(sect) {
|
||||
const layr = <Layr> sect;
|
||||
assert.ok(layr);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 0);
|
||||
|
||||
assert.equal(layr.lists?.length, 1);
|
||||
const list0 = layr.lists[0];
|
||||
assert.ok(list0);
|
||||
assert.equal(list0.layers.length, 1);
|
||||
assert.equal(list0.hardware.value, 'us');
|
||||
const layer0 = list0.layers[0];
|
||||
assert.ok(layer0);
|
||||
assert.equal(layer0.rows.length, 2);
|
||||
assert.equal(layer0.id.value, 'base');
|
||||
assert.equal(layer0.mod, constants.keys_mod_none);
|
||||
for(const row of layer0.rows) {
|
||||
assert.ok(row);
|
||||
assert.equal(row.keys.length, 2);
|
||||
assert.equal(row.keys[0]?.value, 'grave');
|
||||
assert.equal(row.keys[1]?.value, 'mistake');
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -121,6 +121,16 @@ describe('vars', function () {
|
|||
CompilerMessages.Error_DuplicateVariable({ids: 'upper, y'})
|
||||
],
|
||||
},
|
||||
{
|
||||
subpath: 'sections/vars/fail-invalid-identifiers.xml',
|
||||
errors: [
|
||||
CompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-string'}),
|
||||
CompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-set'}),
|
||||
CompilerMessages.Error_InvalidVariableIdentifer({id: 'invalid-uset'}),
|
||||
CompilerMessages.Error_InvalidVariableIdentifer({id: 'a_marker_name_more_than_32_chars_long'}),
|
||||
CompilerMessages.Error_InvalidVariableIdentifer({id: '😡'}),
|
||||
],
|
||||
},
|
||||
{
|
||||
subpath: 'sections/vars/fail-uset-props1.xml',
|
||||
errors: [
|
||||
|
|
@ -187,7 +197,14 @@ describe('vars', function () {
|
|||
CompilerMessages.Error_MissingStringVariable({id: 'missingStringInSet'})
|
||||
],
|
||||
},
|
||||
], varsDependencies);
|
||||
{
|
||||
subpath: 'sections/vars/fail-badref-7.xml',
|
||||
errors: [
|
||||
CompilerMessages.Error_MissingStringVariable({id: 'usedBeforeDefinition'})
|
||||
],
|
||||
strictErrors: true
|
||||
},
|
||||
], varsDependencies);
|
||||
describe('should match some marker constants', () => {
|
||||
// neither of these live here, but, common/web/types does not import ldml-keyboard-constants otherwise.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
import 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import hextobin from '@keymanapp/hextobin';
|
||||
import { KvkFileWriter } from '@keymanapp/common-types';
|
||||
import {checkMessages, compilerTestOptions, compileVisualKeyboard, makePathToFixture} from './helpers/index.js';
|
||||
|
||||
describe('visual-keyboard-compiler', function() {
|
||||
this.slow(500); // 0.5 sec -- json schema validation takes a while
|
||||
|
||||
it('should build fixtures', async function() {
|
||||
// Let's build basic.xml
|
||||
// It should match basic.kvk (built from basic-kvk.txt)
|
||||
|
||||
const inputFilename = makePathToFixture('basic.xml');
|
||||
const binaryFilename = makePathToFixture('basic-kvk.txt');
|
||||
|
||||
// Compile the visual keyboard
|
||||
const vk = await compileVisualKeyboard(inputFilename, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
|
||||
assert.isNotNull(vk);
|
||||
|
||||
// Use the builder to generate the binary output file
|
||||
const writer = new KvkFileWriter();
|
||||
const code = writer.write(vk);
|
||||
checkMessages();
|
||||
assert.isNotNull(code);
|
||||
|
||||
// Compare output
|
||||
let expected = await hextobin(binaryFilename, undefined, {silent:true});
|
||||
assert.deepEqual<Uint8Array>(code, expected);
|
||||
});
|
||||
});
|
||||
235
developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts
Normal file
235
developer/src/kmc-ldml/test/test-visual-keyboard-compiler.ts
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import {assert} from 'chai';
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
import { KMX, KvkFileWriter, LDMLKeyboardXMLSourceFileReader, VisualKeyboard } from '@keymanapp/common-types';
|
||||
import hextobin from '@keymanapp/hextobin';
|
||||
|
||||
import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js';
|
||||
|
||||
import { LdmlKeyboardVisualKeyboardCompiler } from '../src/compiler/visual-keyboard-compiler.js';
|
||||
import { LdmlKeyboardCompiler } from '../src/main.js';
|
||||
|
||||
describe('visual-keyboard-compiler', function() {
|
||||
this.slow(500); // 0.5 sec -- json schema validation takes a while
|
||||
|
||||
it('should build fixtures', async function() {
|
||||
// Let's build basic.xml
|
||||
|
||||
// It should match basic.kvk (built from basic-kvk.txt)
|
||||
const inputFilename = makePathToFixture('basic.xml');
|
||||
const binaryFilename = makePathToFixture('basic-kvk.txt');
|
||||
|
||||
// Compile the visual keyboard
|
||||
const k = new LdmlKeyboardCompiler();
|
||||
assert.isTrue(await k.init(compilerTestCallbacks, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false}));
|
||||
const source = k.load(inputFilename);
|
||||
checkMessages();
|
||||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
const valid = await k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
|
||||
let kmx = await k.compile(source);
|
||||
assert(kmx, 'k.compile should not have failed');
|
||||
|
||||
const keyboardId = path.basename(inputFilename, '.xml');
|
||||
|
||||
const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, keyboardId);
|
||||
checkMessages();
|
||||
assert.isNotNull(vk, 'LdmlKeyboardVisualKeyboardCompiler.compile should not have returned null');
|
||||
assert.isNotNull(kmx.keyboard.stores.find(store =>
|
||||
store.dwSystemID == KMX.KMXFile.TSS_VISUALKEYBOARD &&
|
||||
store.dpString == keyboardId + '.kvk'
|
||||
));
|
||||
assert(typeof vk == 'object');
|
||||
|
||||
// Use the builder to generate the binary output file
|
||||
const writer = new KvkFileWriter();
|
||||
const code = writer.write(vk);
|
||||
assert.isEmpty(compilerTestCallbacks.messages);
|
||||
assert.isNotNull(code);
|
||||
|
||||
// Compare output
|
||||
let expected = await hextobin(binaryFilename, undefined, {silent:true});
|
||||
|
||||
assert.deepEqual<Uint8Array>(code, expected);
|
||||
});
|
||||
|
||||
it('should support various modifiers', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="x" output="y" />
|
||||
</keys>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="x" /></layer>
|
||||
<layer modifiers="shift"><row keys="x" /></layer>
|
||||
<layer modifiers="altR"><row keys="x" /></layer>
|
||||
<layer modifiers="ctrlR altR, shift altR"><row keys="x" /></layer>
|
||||
<layer modifiers="caps"><row keys="x" /></layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.keys.length, 5);
|
||||
assert.equal(vk.keys[0].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_NORMAL);
|
||||
assert.equal(vk.keys[1].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT);
|
||||
assert.equal(vk.keys[2].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
|
||||
assert.equal(vk.keys[3].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_RCTRL | VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
|
||||
assert.equal(vk.keys[4].shift, VisualKeyboard.VisualKeyboardShiftState.KVKS_SHIFT | VisualKeyboard.VisualKeyboardShiftState.KVKS_RALT);
|
||||
});
|
||||
|
||||
it('should emit the correct associated keyboard id', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="x" output="y" />
|
||||
</keys>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="x" /></layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.header.associatedKeyboard, 'test');
|
||||
});
|
||||
|
||||
it('should support <display>', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="a" output="X" />
|
||||
<key id="b" output="Y" />
|
||||
</keys>
|
||||
<displays>
|
||||
<display keyId="a" display="C" />
|
||||
<display output="Y" display="D" />
|
||||
</displays>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="a b" /></layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.keys.length, 2);
|
||||
assert.equal(vk.keys[0].text, 'C');
|
||||
assert.equal(vk.keys[1].text, 'D');
|
||||
});
|
||||
|
||||
it('should correctly decode \\u{xxxx}', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="x" output="\\u{0e80}" />
|
||||
<key id="z" output="y" />
|
||||
</keys>
|
||||
<displays>
|
||||
<display output="y" display="\\u{0e81}" />
|
||||
</displays>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="x z" /></layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.keys.length, 2);
|
||||
assert.equal(vk.keys[0].text, '\u{0e80}');
|
||||
assert.equal(vk.keys[1].text, '\u{0e81}');
|
||||
});
|
||||
|
||||
it('should read string variables in key.output', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="x" output="\${one}" />
|
||||
</keys>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="x" /></layer>
|
||||
</layers>
|
||||
<variables>
|
||||
<string id="two" value="2" />
|
||||
<string id="one" value="\${two}" />
|
||||
</variables>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.keys.length, 1);
|
||||
assert.equal(vk.keys[0].text, '2');
|
||||
});
|
||||
|
||||
it('should read string variables in display.display', async function() {
|
||||
const xml = stripIndent`
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="45">
|
||||
<info name="minimal"/>
|
||||
<keys>
|
||||
<key id="x" output="y" />
|
||||
</keys>
|
||||
<displays>
|
||||
<display output="y" display="\${one}" />
|
||||
</displays>
|
||||
<layers formId="us">
|
||||
<layer modifiers="none"><row keys="x" /></layer>
|
||||
</layers>
|
||||
<variables>
|
||||
<string id="two" value="2" />
|
||||
<string id="one" value="\${two}" />
|
||||
</variables>
|
||||
</keyboard3>
|
||||
`;
|
||||
|
||||
const vk = await loadVisualKeyboardFromXml(xml, 'test');
|
||||
|
||||
assert.equal(vk.keys.length, 1);
|
||||
assert.equal(vk.keys[0].text, '2');
|
||||
});
|
||||
});
|
||||
|
||||
async function loadVisualKeyboardFromXml(xml: string, id: string) {
|
||||
const data = new TextEncoder().encode(xml);
|
||||
assert.isOk(data);
|
||||
|
||||
const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, compilerTestCallbacks);
|
||||
const source = reader.load(data);
|
||||
assert.isEmpty(compilerTestCallbacks.messages);
|
||||
assert.isOk(source);
|
||||
|
||||
const k = new LdmlKeyboardCompiler();
|
||||
assert.isTrue(await k.init(compilerTestCallbacks, compilerTestOptions));
|
||||
|
||||
const kmx = await k.compile(source);
|
||||
assert(kmx, 'k.compile should not have failed');
|
||||
|
||||
const vk = (new LdmlKeyboardVisualKeyboardCompiler(compilerTestCallbacks)).compile(kmx.kmxplus, id);
|
||||
assert(typeof vk == 'object');
|
||||
assert.isEmpty(compilerTestCallbacks.messages);
|
||||
assert.isOk(vk);
|
||||
|
||||
return vk;
|
||||
}
|
||||
|
|
@ -10,21 +10,53 @@ export const expectedGitDateFormat = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d\d\
|
|||
*/
|
||||
export function getLastGitCommitDate(path: string): string {
|
||||
// TZ=UTC0 git log -1 --no-merges --date=format:%Y-%m-%dT%H:%M:%SZ --format=%ad
|
||||
let result = null;
|
||||
let result = callGitLog([
|
||||
'log', // git log
|
||||
'-1', // one commit only
|
||||
'--no-merges', // we're only interested in 'real' commits
|
||||
'--format=%at', // emit only the commit date as a UNIX timestamp
|
||||
'--',
|
||||
path
|
||||
]);
|
||||
|
||||
try {
|
||||
result = execFileSync('git', [
|
||||
if(typeof result != 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(result == '') {
|
||||
// #12626: no history was found, but not an error. We may have only a merge
|
||||
// commit in history for that file, allow merges in the log
|
||||
result = callGitLog([
|
||||
'log', // git log
|
||||
'-1', // one commit only
|
||||
'--no-merges', // we're only interested in 'real' commits
|
||||
'--format=%at', // emit only the commit date as a UNIX timestamp
|
||||
'--',
|
||||
path
|
||||
], {
|
||||
]);
|
||||
|
||||
if(typeof result != 'string') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const sec = Number.parseInt(result);
|
||||
if(Number.isNaN(sec)) {
|
||||
// We received invalid data, perhaps a git error string so just ignore
|
||||
return null;
|
||||
}
|
||||
|
||||
// We receive a timestamp in seconds but we need milliseconds
|
||||
return new Date(sec * 1000).toISOString();
|
||||
}
|
||||
|
||||
function callGitLog(params: string[]) {
|
||||
try {
|
||||
const result = execFileSync('git', params, {
|
||||
encoding: 'utf-8', // force a string result rather than Buffer
|
||||
windowsHide: true, // on windows, we may need this to suppress a console window popup
|
||||
stdio: ['pipe', 'pipe', 'pipe'] // all output via pipe, so we don't get git errors on console
|
||||
});
|
||||
return result.trim();
|
||||
} catch (e) {
|
||||
// If git is not available, or the file is not in-repo, then it is probably
|
||||
// fine to just silently return null, as the only machines where this is
|
||||
|
|
@ -34,9 +66,4 @@ export function getLastGitCommitDate(path: string): string {
|
|||
// seems low.
|
||||
return null;
|
||||
}
|
||||
|
||||
// We receive a timestamp in seconds but we need milliseconds
|
||||
const msec = Number.parseInt(result.trim()) * 1000;
|
||||
|
||||
return new Date(msec).toISOString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,9 +90,16 @@ struct BindingType<std::vector<T, Allocator>> {
|
|||
using ValBinding = BindingType<val>;
|
||||
using WireType = ValBinding::WireType;
|
||||
|
||||
#if __EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 60
|
||||
// emscripten-core/emscripten#21692
|
||||
static WireType toWireType(const std::vector<T, Allocator> &vec, rvp::default_tag) {
|
||||
return ValBinding::toWireType(val::array(vec), rvp::default_tag{});
|
||||
}
|
||||
#else
|
||||
static WireType toWireType(const std::vector<T, Allocator> &vec) {
|
||||
return ValBinding::toWireType(val::array(vec));
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::vector<T, Allocator> fromWireType(WireType value) {
|
||||
return vecFromJSArray<T>(ValBinding::fromWireType(value));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[binaries]
|
||||
c = ['$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar.py']
|
||||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[binaries]
|
||||
c = ['$EMSCRIPTEN_BASE/emcc.py']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++.py']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar.py']
|
||||
c = ['$EMSCRIPTEN_BASE/emcc']
|
||||
cpp = ['$EMSCRIPTEN_BASE/em++']
|
||||
ar = ['$EMSCRIPTEN_BASE/emar']
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ type
|
|||
procedure optWriteInt(const nm: string; value: Integer);
|
||||
procedure WriteServerConfigurationJson;
|
||||
class function Default_DefaultProjectPath: string; static;
|
||||
function BackOffAndSaveJson(const Filename: string; const JSON: TJSONObject): Boolean;
|
||||
public
|
||||
procedure Read;
|
||||
procedure Write;
|
||||
|
|
@ -406,8 +407,6 @@ begin
|
|||
end;
|
||||
|
||||
procedure TKeymanDeveloperOptions.Write;
|
||||
var
|
||||
count: Integer;
|
||||
begin
|
||||
// If the output file does not exist, this creates it. Note that we load
|
||||
// from the existing file before writing out our set of options, which is
|
||||
|
|
@ -464,33 +463,7 @@ begin
|
|||
ForceDirectories(TKeymanDeveloperPaths.OptionsPath);
|
||||
|
||||
try
|
||||
count := 0;
|
||||
repeat
|
||||
try
|
||||
SaveJSONToFile(TKeymanDeveloperPaths.OptionsPath + TKeymanDeveloperPaths.S_OptionsJson, json);
|
||||
Break;
|
||||
except
|
||||
on E:EOSError do
|
||||
begin
|
||||
if E.ErrorCode = ERROR_ACCESS_DENIED then
|
||||
begin
|
||||
// Back off and retry 0.5 sec later, it's probably another
|
||||
// instance happening to read or write the settings file
|
||||
Inc(count);
|
||||
Sleep(500);
|
||||
end
|
||||
else
|
||||
raise;
|
||||
end
|
||||
else
|
||||
raise;
|
||||
end;
|
||||
until count >= Max_Retries;
|
||||
if count >= Max_Retries then
|
||||
begin
|
||||
if TKeymanSentryClient.Instance <> nil then
|
||||
TKeymanSentryClient.Instance.ReportMessage('TKeymanDeveloperOptions: Failed to write settings after 5 tries');
|
||||
end;
|
||||
BackOffAndSaveJson(TKeymanDeveloperPaths.OptionsPath + TKeymanDeveloperPaths.S_OptionsJson, json);
|
||||
except
|
||||
on E:Exception do
|
||||
begin
|
||||
|
|
@ -507,6 +480,39 @@ begin
|
|||
WriteServerConfigurationJson;
|
||||
end;
|
||||
|
||||
function TKeymanDeveloperOptions.BackOffAndSaveJson(const Filename: string; const JSON: TJSONObject): Boolean;
|
||||
var
|
||||
count: Integer;
|
||||
begin
|
||||
count := 0;
|
||||
repeat
|
||||
try
|
||||
SaveJSONToFile(Filename, JSON);
|
||||
Exit(True);
|
||||
except
|
||||
on E:EOSError do
|
||||
begin
|
||||
if E.ErrorCode = ERROR_ACCESS_DENIED then
|
||||
begin
|
||||
// Back off and retry 0.5 sec later, it's probably another
|
||||
// instance happening to read or write the settings file
|
||||
Inc(count);
|
||||
Sleep(500);
|
||||
end
|
||||
else
|
||||
raise;
|
||||
end
|
||||
else
|
||||
raise;
|
||||
end;
|
||||
until count >= Max_Retries;
|
||||
|
||||
if TKeymanSentryClient.Instance <> nil then
|
||||
TKeymanSentryClient.Instance.ReportMessage('TKeymanDeveloperOptions: Failed to write '+Filename+' after 5 tries');
|
||||
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
procedure TKeymanDeveloperOptions.WriteServerConfigurationJson;
|
||||
var
|
||||
o: TJSONObject;
|
||||
|
|
@ -517,7 +523,8 @@ begin
|
|||
o.AddPair('ngrokToken', FServerNgrokToken);
|
||||
o.AddPair('useNgrok', TJSONBool.Create(FServerUseNgrok));
|
||||
o.AddPair('ngrokVisible', TJSONBool.Create(FServerServerShowConsoleWindow));
|
||||
SaveJSONToFile(TKeymanDeveloperPaths.ServerDataPath + TKeymanDeveloperPaths.S_ServerConfigJson, o);
|
||||
ForceDirectories(TKeymanDeveloperPaths.ServerDataPath);
|
||||
BackOffAndSaveJSON(TKeymanDeveloperPaths.ServerDataPath + TKeymanDeveloperPaths.S_ServerConfigJson, o);
|
||||
finally
|
||||
o.Free;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -831,12 +831,22 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
shared.copyUserDefaults(to: nonShared, withKeys: keysToCopy, shouldOverwrite: true)
|
||||
do {
|
||||
try shared.copyFiles(to: nonShared)
|
||||
FontManager.shared.registerCustomFonts()
|
||||
} catch {
|
||||
let message = ("Failed to copy from shared container: \(error)")
|
||||
os_log("%{public}s", log:KeymanEngineLogger.settings, type: .error, message)
|
||||
SentryManager.capture(error, message:message)
|
||||
}
|
||||
|
||||
// This operation has a surprisingly high cost and isn't critical
|
||||
// for getting the keyboard loaded and available to the OS.
|
||||
//
|
||||
// We certainly want it done _soon_... but we don't want it to cause
|
||||
// the primary, synchronous initialization call from the OS to run so
|
||||
// long that the keyboard fails to start due to its enforced time
|
||||
// constraints.
|
||||
DispatchQueue.main.async {
|
||||
FontManager.shared.registerCustomFonts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
<key>teamID</key>
|
||||
<string>3YE4W86L3G</string>
|
||||
<key>signingCertificate</key>
|
||||
<string>BB842A0081298BBA6D034C85451750B08FC9EE8A</string>
|
||||
<string>683E4A3CDF5277C13004E84D63E31D2F720AB063</string>
|
||||
<key>installerSigningCertificate</key>
|
||||
<string>BB842A0081298BBA6D034C85451750B08FC9EE8A</string>
|
||||
<string>683E4A3CDF5277C13004E84D63E31D2F720AB063</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>Tavultesoft.Keyman</key>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (c) 2022-2023 SIL International. All rights reserved.
|
||||
# Copyright (c) 2022-2023 SIL Global. All rights reserved.
|
||||
#
|
||||
# builder image for a linux build
|
||||
# see ../docs/build/linux-ubuntu.md
|
||||
|
|
@ -7,7 +7,7 @@ ARG OS_VERSION=latest
|
|||
ARG OS_PLATFORM=amd64
|
||||
|
||||
FROM --platform=${OS_PLATFORM} ubuntu:${OS_VERSION}
|
||||
LABEL org.opencontainers.image.authors="SIL International."
|
||||
LABEL org.opencontainers.image.authors="SIL Global."
|
||||
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
|
||||
LABEL org.opencontainers.image.title="Keyman Linux Build Image"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,18 @@
|
|||
keyman (17.0.333-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Eberhard Beilharz <eb1@sil.org> Thu, 16 Jan 2025 16:29:44 +0100
|
||||
|
||||
keyman (17.0.332-1) unstable; urgency=medium
|
||||
|
||||
* set environment variable for rendering of downloads dialog (#12617)
|
||||
* add `keymanFacename` to .ldml file (#12283)
|
||||
* ignore exceptions trying to install cache (#11885)
|
||||
* New upstream release.
|
||||
|
||||
-- Eberhard Beilharz <eb1@sil.org> Wed, 11 Dec 2024 15:53:59 +0100
|
||||
|
||||
keyman (17.0.326-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<project_group>Keyman</project_group>
|
||||
<developer_name>SIL International</developer_name>
|
||||
<developer id="org.sil.keyman">
|
||||
<name>SIL Global</name>
|
||||
</developer>
|
||||
<icon type="remote" width="96" height="96">https://keyman.com/cdn/dev/img/keyman-logo.png</icon>
|
||||
<name>Keyman for Linux</name>
|
||||
<summary>Keyman makes it possible for you to type in over 2,000 languages</summary>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-info">mild</content_attribute>
|
||||
</content_rating>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
<component type="inputmethod">
|
||||
<id>com.keyman.ibus_keyman</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>GPL-2+</project_license>
|
||||
<project_license>GPL-2.0+</project_license>
|
||||
<project_group>Keyman</project_group>
|
||||
<developer_name>SIL International</developer_name>
|
||||
<developer id="org.sil.keyman">
|
||||
<name>SIL Global</name>
|
||||
</developer>
|
||||
<icon type="remote" width="96" height="96">https://keyman.com/cdn/dev/img/keyman-logo.png</icon>
|
||||
|
||||
<name>ibus-keyman</name>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ Build-Depends:
|
|||
pkgconf,
|
||||
python3-all (>= 3.5),
|
||||
python3-dbus,
|
||||
python3-fonttools,
|
||||
python3-gi,
|
||||
python3-lxml,
|
||||
python3-magic,
|
||||
|
|
@ -90,6 +91,7 @@ Depends:
|
|||
gir1.2-webkit2-4.1 | gir1.2-webkit2-4.0,
|
||||
keyman-engine,
|
||||
python3-bs4,
|
||||
python3-fonttools,
|
||||
python3-gi,
|
||||
python3-packaging,
|
||||
python3-sentry-sdk (>= 1.1),
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@ Upstream-Contact: Keyman team <support@keyman.com>
|
|||
Source: https://github.com/keymanapp/keyman
|
||||
|
||||
Files: *
|
||||
Copyright: 2018-2024 SIL International
|
||||
Copyright: 2018-2025 SIL Global
|
||||
License: MIT
|
||||
|
||||
Files: linux/ibus-keyman/*
|
||||
Copyright: 2004-2024 SIL International
|
||||
Copyright: 2004-2025 SIL Global
|
||||
License: GPL-2+
|
||||
|
||||
Files: linux/ibus-keyman/src/keymanutil.c
|
||||
linux/ibus-keyman/src/keymanutil.h
|
||||
linux/ibus-keyman/src/kmpdetails.c
|
||||
linux/ibus-keyman/src/kmpdetails.h
|
||||
Copyright: 2009-2024 SIL International
|
||||
Copyright: 2009-2025 SIL Global
|
||||
License: GPL-2+ or MIT
|
||||
|
||||
Files: linux/ibus-keyman/src/keyman-service.c
|
||||
linux/ibus-keyman/src/keyman-service.h
|
||||
linux/keyman-config/buildtools/help2md
|
||||
Copyright: 2018-2024 SIL International
|
||||
Copyright: 2018-2025 SIL Global
|
||||
License: GPL-3+
|
||||
|
||||
Files: linux/ibus-keyman/tests/ibusimcontext.c
|
||||
|
|
@ -31,7 +31,7 @@ Copyright: 2008-2010, Peng Huang <shawn.p.huang@gmail.com>
|
|||
2008-2013, Peng Huang <shawn.p.huang@gmail.com>
|
||||
2008-2021, Red Hat, Inc.
|
||||
2015-2021, Takao Fujiwara <takao.fujiwara1@gmail.com>
|
||||
2021-2024, SIL International
|
||||
2021-2025, SIL Global
|
||||
License: LGPL-2.1+
|
||||
|
||||
Files: linux/keyman-config/buildtools/help2man
|
||||
|
|
@ -41,7 +41,7 @@ License: GPL-3+
|
|||
Files: debian/com.keyman.config.appdata.xml
|
||||
debian/com.keyman.ibus_keyman.metainfo.xml
|
||||
Copyright: 2019 Daniel Glassey <wdg@debian.org>
|
||||
2022-2024 SIL International
|
||||
2022-2025 SIL Global
|
||||
License: MIT
|
||||
|
||||
License: MIT
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
libkeymancore.so.1 libkeymancore1 #MINVER#
|
||||
* Build-Depends-Package: libkeymancore-dev
|
||||
|
||||
(c++|optional)"typeinfo name for std::codecvt_utf8_utf16<char16_t, 1114111ul, (std::codecvt_mode)0>@Base" 17.0.244
|
||||
# We only use a C API, so ignore all C++ symbols in the std namespace that bleed through
|
||||
(c++|regex|optional)"std::" 17.0.332
|
||||
km_core_context_clear@Base 17.0.195
|
||||
km_core_context_get@Base 17.0.195
|
||||
km_core_context_item_list_size@Base 17.0.195
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2009-2023 SIL International
|
||||
* Copyright (C) 2009-2023 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018 SIL International
|
||||
* Copyright (C) 2018 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018-2023 SIL International
|
||||
* Copyright (C) 2018-2023 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018-2023 SIL International
|
||||
* Copyright (C) 2018-2023 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018 SIL International
|
||||
* Copyright (C) 2018 SIL Global
|
||||
*
|
||||
* keymanutil is dual licensed under the MIT or GPL licenses as described below.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018-2023 SIL International
|
||||
* Copyright (C) 2018-2023 SIL Global
|
||||
*
|
||||
* keymanutil is dual licensed under the MIT or GPL licenses as described below.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018 SIL International
|
||||
* Copyright (C) 2018 SIL Global
|
||||
*
|
||||
* kmpdetails is dual licensed under the MIT or GPL licenses as described below.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/*
|
||||
* Keyman Input Method for IBUS (The Input Bus)
|
||||
*
|
||||
* Copyright (C) 2018 SIL International
|
||||
* Copyright (C) 2018 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public
|
||||
|
|
|
|||
2
linux/ibus-keyman/src/test/testdata/kmp.json
vendored
2
linux/ibus-keyman/src/test/testdata/kmp.json
vendored
|
|
@ -14,7 +14,7 @@
|
|||
"description": "LIBTRALO"
|
||||
},
|
||||
"copyright": {
|
||||
"description": "\u00A9 2008-2018 SIL International"
|
||||
"description": "\u00A9 2008-2018 SIL Global"
|
||||
},
|
||||
"author": {
|
||||
"description": "support@keyman.com",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"description": "LIBTRALO"
|
||||
},
|
||||
"copyright": {
|
||||
"description": "\u00A9 2008-2018 SIL International"
|
||||
"description": "\u00A9 2008-2018 SIL Global"
|
||||
},
|
||||
"author": {
|
||||
"description": "support@keyman.com",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"description": "LIBTRALO"
|
||||
},
|
||||
"copyright": {
|
||||
"description": "\u00A9 2008-2018 SIL International"
|
||||
"description": "\u00A9 2008-2018 SIL Global"
|
||||
},
|
||||
"author": {
|
||||
"description": "support@keyman.com",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Copyright (C) 2008-2013 Peng Huang <shawn.p.huang@gmail.com>
|
||||
* Copyright (C) 2015-2022 Takao Fujiwara <takao.fujiwara1@gmail.com>
|
||||
* Copyright (C) 2008-2022 Red Hat, Inc.
|
||||
* Copyright (C) 2021-2022 SIL International
|
||||
* Copyright (C) 2021-2022 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/* ibus - The Input Bus
|
||||
* Copyright (C) 2008-2010 Peng Huang <shawn.p.huang@gmail.com>
|
||||
* Copyright (C) 2008-2010 Red Hat, Inc.
|
||||
* Copyright (C) 2021-2022 SIL International
|
||||
* Copyright (C) 2021-2022 SIL Global
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2018 SIL International
|
||||
Copyright (c) 2018 SIL Global
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ MOFILES := $(POFILES:.po=/LC_MESSAGES/keyman-config.mo)
|
|||
|
||||
update-template: version
|
||||
xgettext --package-name "keyman" --package-version "$(VERSION)" \
|
||||
--msgid-bugs-address "<support@keyman.com>" --copyright-holder "SIL International" \
|
||||
--msgid-bugs-address "<support@keyman.com>" --copyright-holder "SIL Global" \
|
||||
--language=Python --directory=. --output-dir=locale --output=keyman-config.pot \
|
||||
--add-comments=i18n: --sort-by-file --width=98 \
|
||||
keyman_config/*.py km-*
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009,
|
||||
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software
|
||||
# Foundation, Inc.
|
||||
# Copyright (C) 2021 SIL International
|
||||
# Copyright (C) 2021 SIL Global
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -76,7 +76,7 @@ my $version_info = enc_user sprintf _(<<'EOT'), $this_program, $this_version;
|
|||
Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009,
|
||||
2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2020, 2021 Free Software
|
||||
Foundation, Inc.
|
||||
Copyright (C) 2021 SIL International
|
||||
Copyright (C) 2021 SIL Global
|
||||
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import atexit
|
||||
import gettext
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
|
|
@ -53,6 +54,14 @@ def initialize_sentry():
|
|||
SentryErrorHandling().initialize_sentry()
|
||||
|
||||
|
||||
def are_requirements_missing():
|
||||
try:
|
||||
ttLib = importlib.import_module('fontTools.ttLib')
|
||||
except ImportError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class FileCleanup():
|
||||
"""
|
||||
Allow to register files that will be deleted when the process exits
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ class InstallKmp():
|
|||
# Special handling to convert kvk into LDML
|
||||
logging.info("Converting %s to LDML and installing both as as keyman file",
|
||||
f['name'])
|
||||
ldml = convert_kvk_to_ldml(fpath)
|
||||
name, ext = os.path.splitext(f['name'])
|
||||
ldml = convert_kvk_to_ldml(name, fpath)
|
||||
ldmlfile = os.path.join(self.packageDir, f"{name}.ldml")
|
||||
output_ldml(ldmlfile, ldml)
|
||||
elif ftype == KMFileTypes.KM_ICON:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from fontTools import ttLib
|
||||
from lxml import etree
|
||||
|
||||
from keyman_config.kmpmetadata import parsemetadata
|
||||
|
||||
# .kvk file format
|
||||
# KVK files are variable length files with variable sized structures.
|
||||
|
||||
|
|
@ -311,7 +315,18 @@ def get_modifer(key):
|
|||
return modifier
|
||||
|
||||
|
||||
def convert_ldml(kvkData):
|
||||
def _fontFacename(fontFilename):
|
||||
"""Get the facename from fontFilename's TTF tables"""
|
||||
# From https://github.com/mcfletch/ttfquery/blob/master/ttfquery/describe.py
|
||||
FONT_SPECIFIER_NAME_ID = 4
|
||||
font = ttLib.TTFont(fontFilename)
|
||||
for record in font['name'].names:
|
||||
if record.nameID == FONT_SPECIFIER_NAME_ID:
|
||||
return record.toUnicode()
|
||||
return None
|
||||
|
||||
|
||||
def convert_ldml(keyboardName, kvkData, kmpJsonFilename):
|
||||
keymaps = {}
|
||||
|
||||
for key in kvkData.Keys:
|
||||
|
|
@ -349,7 +364,19 @@ def convert_ldml(kvkData):
|
|||
else:
|
||||
keymaps["shift"] = (uskey,)
|
||||
|
||||
info, system, options, keyboards, files = parsemetadata(kmpJsonFilename)
|
||||
|
||||
ldml = etree.Element("keyboard", locale="zzz-keyman")
|
||||
for keyboard in keyboards:
|
||||
if keyboard['id'] != keyboardName:
|
||||
continue
|
||||
if 'oskFont' in keyboard:
|
||||
fontFile = os.path.join(os.path.dirname(kmpJsonFilename), keyboard['oskFont'])
|
||||
font = _fontFacename(fontFile)
|
||||
if font:
|
||||
ldml.set('keymanFacename', font)
|
||||
break
|
||||
|
||||
etree.SubElement(ldml, "version", platform="11")
|
||||
names = etree.SubElement(ldml, "names")
|
||||
names.append(etree.Element("name", value="ZZZ"))
|
||||
|
|
@ -402,6 +429,7 @@ def parse_kvk_file(kvkfile):
|
|||
return kvkData
|
||||
|
||||
|
||||
def convert_kvk_to_ldml(kvkfile):
|
||||
def convert_kvk_to_ldml(name, kvkfile):
|
||||
kvkData = parse_kvk_file(kvkfile)
|
||||
return convert_ldml(kvkData)
|
||||
kmpJsonFilename = os.path.join(os.path.dirname(kvkfile), 'kmp.json')
|
||||
return convert_ldml(name, kvkData, kmpJsonFilename)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
#!/usr/bin/python3
|
||||
'''
|
||||
Keyman is copyright (C) SIL Global. MIT License.
|
||||
|
||||
Implements the Sentry error handling
|
||||
'''
|
||||
import getpass
|
||||
import hashlib
|
||||
import importlib
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import gi
|
||||
|
||||
|
|
@ -9,7 +10,10 @@ gi.require_version('Gtk', '3.0')
|
|||
|
||||
from gi.repository import Gtk
|
||||
|
||||
from keyman_config import __versionwithtag__, __pkgversion__, add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
|
||||
from keyman_config import (
|
||||
_, __versionwithtag__, __pkgversion__, add_standard_arguments,
|
||||
are_requirements_missing, initialize_logging, initialize_sentry,
|
||||
verify_dbus_running)
|
||||
from keyman_config.handle_install import download_and_install_package
|
||||
from keyman_config.ibus_util import verify_ibus_daemon
|
||||
from keyman_config.view_installed import ViewInstalledWindow
|
||||
|
|
@ -42,6 +46,18 @@ if __name__ == '__main__':
|
|||
initialize_sentry()
|
||||
verify_dbus_running()
|
||||
|
||||
if are_requirements_missing():
|
||||
if args.install or args.url:
|
||||
logging.error('Missing requirements. Please install python3-fonttools.')
|
||||
else:
|
||||
dialog = Gtk.MessageDialog(
|
||||
None, 0, Gtk.MessageType.ERROR,
|
||||
Gtk.ButtonsType.OK, _("Missing requirements. Please install python3-fonttools."))
|
||||
dialog.run()
|
||||
dialog.destroy()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
logging.info('Keyman version %s %s', __versionwithtag__, __pkgversion__)
|
||||
|
||||
verify_ibus_daemon(False)
|
||||
|
|
@ -51,6 +67,10 @@ if __name__ == '__main__':
|
|||
elif args.url:
|
||||
download_and_install_package(args.url)
|
||||
else:
|
||||
# Workaround for bug in webkit2gtk (#12587)
|
||||
if not 'WEBKIT_DISABLE_DMABUF_RENDERER' in os.environ:
|
||||
os.environ['WEBKIT_DISABLE_DMABUF_RENDERER'] = '1'
|
||||
|
||||
w = ViewInstalledWindow()
|
||||
try:
|
||||
w.run()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ import logging
|
|||
import os
|
||||
import sys
|
||||
|
||||
from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
|
||||
from keyman_config import (
|
||||
are_requirements_missing, add_standard_arguments, initialize_logging,
|
||||
initialize_sentry, verify_dbus_running)
|
||||
from keyman_config.kvk2ldml import parse_kvk_file, print_kvk, convert_ldml, output_ldml
|
||||
|
||||
|
||||
|
|
@ -25,6 +27,10 @@ def main():
|
|||
initialize_sentry()
|
||||
verify_dbus_running()
|
||||
|
||||
if are_requirements_missing():
|
||||
logging.error('km-kvk2ldml: Missing requirements. Please install python3-fonttools.')
|
||||
sys.exit(1)
|
||||
|
||||
name, ext = os.path.splitext(args.kvkfile)
|
||||
# Check if input file extension is kvk
|
||||
if ext != ".kvk":
|
||||
|
|
@ -54,7 +60,8 @@ def main():
|
|||
|
||||
try:
|
||||
with open(outputfile, 'wb') as ldmlfile:
|
||||
ldml = convert_ldml(kvkData)
|
||||
kmpJsonFilename = os.path.join(os.path.dirname(args.kvkfile), 'kmp.json')
|
||||
ldml = convert_ldml(name, kvkData, kmpJsonFilename)
|
||||
output_ldml(ldmlfile, ldml)
|
||||
except PermissionError:
|
||||
logging.error(f'km-kvk2ldml: error, permission denied writing file `{outputfile}`')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR SIL International
|
||||
# Copyright (C) YEAR SIL Global
|
||||
# This file is distributed under the same license as the keyman package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
|
|
|
|||
57
linux/keyman-config/tests/test_kvk2ldml.py
Normal file
57
linux/keyman-config/tests/test_kvk2ldml.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from keyman_config.kvk2ldml import KVKData, NFont, convert_ldml
|
||||
|
||||
class Kvk2LdmlTests(unittest.TestCase):
|
||||
def _createKmpJson(self, packagedir):
|
||||
kmpJsonFilename = os.path.join(packagedir, 'kmp.json')
|
||||
with open(kmpJsonFilename, 'w') as file:
|
||||
file.write('''{
|
||||
"system": {
|
||||
"keymanDeveloperVersion": "18.0",
|
||||
"fileVersion": "7.0"
|
||||
},
|
||||
"files": [ {
|
||||
"name": "khmer_angkor.kmx",
|
||||
"description": "Keyboard Khmer Angkor"
|
||||
}, {
|
||||
"name": "kmp.json",
|
||||
"description": "Package information (JSON)"
|
||||
} ],
|
||||
"keyboards": [ {
|
||||
"name": "Khmer Angkor",
|
||||
"id": "khmer_angkor",
|
||||
"version": "1.5",
|
||||
"oskFont": "keymanweb-osk.ttf",
|
||||
"languages": [ {
|
||||
"name": "Central Khmer (Khmer, Cambodia)",
|
||||
"id": "km"
|
||||
} ]}
|
||||
]}''')
|
||||
return kmpJsonFilename
|
||||
|
||||
def test_convert_ldml__adds_keymanFacename(self):
|
||||
# Setup
|
||||
keyboardName = 'khmer_angkor'
|
||||
kvkData = KVKData()
|
||||
kvkData.AssociatedKeyboard = keyboardName
|
||||
kvkData.UnicodeFont = NFont()
|
||||
kvkData.UnicodeFont.name='DontUseThis!'
|
||||
|
||||
workdir = tempfile.TemporaryDirectory()
|
||||
kmpJsonFilename = self._createKmpJson(workdir.name)
|
||||
shutil.copy2(os.path.join(sys.path[0], '../../../common/resources/fonts/keymanweb-osk.ttf'),
|
||||
workdir.name)
|
||||
|
||||
# Execute
|
||||
ldml = convert_ldml(keyboardName, kvkData, kmpJsonFilename)
|
||||
|
||||
# Verify
|
||||
self.assertEqual(ldml.get('locale'), "zzz-keyman")
|
||||
self.assertEqual(ldml.get('keymanFacename'), 'SymChar')
|
||||
|
||||
workdir.cleanup()
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue