feat(developer): automatic version detection for U_xxxx_yyyy ids

Currently, automatic Keyman minimum version determination works for all
features in Keyman keyboards _except_ `U_xxxx_yyyy` touch key
identifiers.

This change adds the automatic keyboard versioning functionality to the
keymanweb compiler as well, following the pattern that's in kmcmplib.

This makes the `store(&version)` line completely optional, and it may
be possible to deprecate it in future versions of Keyman Developer.
This commit is contained in:
Marc Durdin 2024-07-13 07:28:18 +10:00
parent c537bcd8fa
commit 3497e97b00
12 changed files with 273 additions and 82 deletions

View file

@ -7,6 +7,25 @@ import * as r from 'restructure';
// In memory representations of KMX structures
// kmx-builder will transform these to the corresponding COMP_xxxx
export enum KMX_Version {
VERSION_30 = 0x00000300,
VERSION_31 = 0x00000301,
VERSION_32 = 0x00000302,
VERSION_40 = 0x00000400,
VERSION_50 = 0x00000500,
VERSION_501 = 0x00000501,
VERSION_60 = 0x00000600,
VERSION_70 = 0x00000700,
VERSION_80 = 0x00000800,
VERSION_90 = 0x00000900,
VERSION_100 = 0x00000A00,
VERSION_140 = 0x00000E00,
VERSION_150 = 0x00000F00,
VERSION_160 = 0x00001000,
VERSION_170 = 0x00001100
};
export class KEYBOARD {
fileVersion?: number; // dwFileVersion (TSS_FILEVERSION)
@ -129,22 +148,21 @@ export class KMXFile {
// File version identifiers (COMP_KEYBOARD.dwFileVersion)
//
public static readonly VERSION_30 = 0x00000300;
public static readonly VERSION_31 = 0x00000301;
public static readonly VERSION_32 = 0x00000302;
public static readonly VERSION_40 = 0x00000400;
public static readonly VERSION_50 = 0x00000500;
public static readonly VERSION_501 = 0x00000501;
public static readonly VERSION_60 = 0x00000600;
public static readonly VERSION_70 = 0x00000700;
public static readonly VERSION_80 = 0x00000800;
public static readonly VERSION_90 = 0x00000900;
public static readonly VERSION_100 = 0x00000A00;
public static readonly VERSION_140 = 0x00000E00;
public static readonly VERSION_150 = 0x00000F00;
public static readonly VERSION_160 = 0x00001000;
public static readonly VERSION_170 = 0x00001100;
public static readonly VERSION_30 = KMX_Version.VERSION_30;
public static readonly VERSION_31 = KMX_Version.VERSION_31;
public static readonly VERSION_32 = KMX_Version.VERSION_32;
public static readonly VERSION_40 = KMX_Version.VERSION_40;
public static readonly VERSION_50 = KMX_Version.VERSION_50;
public static readonly VERSION_501 = KMX_Version.VERSION_501;
public static readonly VERSION_60 = KMX_Version.VERSION_60;
public static readonly VERSION_70 = KMX_Version.VERSION_70;
public static readonly VERSION_80 = KMX_Version.VERSION_80;
public static readonly VERSION_90 = KMX_Version.VERSION_90;
public static readonly VERSION_100 = KMX_Version.VERSION_100;
public static readonly VERSION_140 = KMX_Version.VERSION_140;
public static readonly VERSION_150 = KMX_Version.VERSION_150;
public static readonly VERSION_160 = KMX_Version.VERSION_160;
public static readonly VERSION_170 = KMX_Version.VERSION_170;
public static readonly VERSION_MIN = this.VERSION_50;
public static readonly VERSION_MAX = this.VERSION_170;

View file

@ -14,6 +14,18 @@ export let FUnreachableKeys: KMX.KEY[];
export let FCallFunctions: string[];
export let FFix183_LadderLength: number;
let _minimumKeymanVersion: number;
export function minimumKeymanVersion() {
return _minimumKeymanVersion;
}
export function minimumKeymanVersionToString() {
const major = (_minimumKeymanVersion & KMX.KMXFile.VERSION_MASK_MAJOR) >> 8;
const minor = _minimumKeymanVersion & KMX.KMXFile.VERSION_MASK_MINOR;
return `${major}.${minor}`;
}
export function setupGlobals(
_callbacks: CompilerCallbacks,
_options: CompilerOptions,
@ -33,20 +45,45 @@ export function setupGlobals(
FUnreachableKeys = [];
FCallFunctions = [];
FFix183_LadderLength = 100; // TODO: option
_minimumKeymanVersion = fk.fileVersion;
}
export function IsKeyboardVersion10OrLater(): boolean {
function isKeyboardVersionAutomaticallyDetermined() {
return (fk.flags & KMX.KMXFile.KF_AUTOMATICVERSION) == KMX.KMXFile.KF_AUTOMATICVERSION;
}
function verifyMinimumRequiredKeymanVersion(version: KMX.KMX_Version) {
if(isKeyboardVersionAutomaticallyDetermined()) {
_minimumKeymanVersion = Math.max(version, _minimumKeymanVersion);
return true;
}
return _minimumKeymanVersion >= version;
}
/**
* Verify that minimum supported Keyman version in the keyboard is version 15.0,
* and upgrade to that version if possible and necessary.
*
* Will upgrade the minimum version to 15.0 if `KF_AUTOMATICVERSION` flag is set
* for the keyboard, which correlates to having no `store(&version)` line in the
* .kmn source file.
*
* @returns `true` if the version is now 15.0 or higher, `false` if a lower
* version has been specified in the source file `store(&version)` line.
*/
export function verifyMinimumRequiredKeymanVersion15(): boolean {
return verifyMinimumRequiredKeymanVersion(KMX.KMX_Version.VERSION_150);
}
export function isKeyboardVersion10OrLater(): boolean {
return fk.fileVersion >= KMX.KMXFile.VERSION_100;
}
export function IsKeyboardVersion14OrLater(): boolean {
export function isKeyboardVersion14OrLater(): boolean {
return fk.fileVersion >= KMX.KMXFile.VERSION_140;
}
export function IsKeyboardVersion15OrLater(): boolean {
return fk.fileVersion >= KMX.KMXFile.VERSION_150;
}
export function IsKeyboardVersion17OrLater(): boolean {
export function isKeyboardVersion17OrLater(): boolean {
return fk.fileVersion >= KMX.KMXFile.VERSION_170;
}

View file

@ -1,10 +1,12 @@
import { TSentinelRecord, GetSuppChar, ExpandSentinel, incxstr, xstrlen, xstrlen_printing } from "./util.js";
import { KMX } from "@keymanapp/common-types";
import { callbacks, FCallFunctions, FFix183_LadderLength, FMnemonic, FTabStop, FUnreachableKeys, IsKeyboardVersion10OrLater, IsKeyboardVersion14OrLater, kmxResult, nl, options } from "./compiler-globals.js";
import { callbacks, FCallFunctions, FFix183_LadderLength, FMnemonic, FTabStop, FUnreachableKeys,
kmxResult, nl, options, isKeyboardVersion10OrLater, isKeyboardVersion14OrLater } from "./compiler-globals.js";
import { KmwCompilerMessages } from "./kmw-compiler-messages.js";
import { FormatModifierAsBitflags, RuleIsExcludedByPlatform } from "./kmw-compiler.js";
import { KMXCodeNames, SValidIdentifierCharSet, UnreachableKeyCodes, USEnglishShift, USEnglishUnshift, USEnglishValues } from "./constants.js";
import { KMXCodeNames, SValidIdentifierCharSet, UnreachableKeyCodes, USEnglishShift,
USEnglishUnshift, USEnglishValues } from "./constants.js";
import { KMWVKeyNames, TKeymanWebTouchStandardKey, VKeyNames } from "./keymanweb-key-codes.js";
export function JavaScript_Name(i: number, pwszName: string, KeepNameForPersistentStorage: boolean = false): string { // I3659
@ -49,7 +51,7 @@ export function JavaScript_Store(fk: KMX.KEYBOARD, line: number, pwsz: string):
let n = pwsz.indexOf(wcsentinel);
// Start: plain text store. Always use for < 10.0, conditionally for >= 10.0.
if(n < 0 || !IsKeyboardVersion10OrLater()) {
if(n < 0 || !isKeyboardVersion10OrLater()) {
result = '"';
while(pwsz.length) {
if(pwsz.charCodeAt(0) == KMX.KMXFile.UC_SENTINEL) {
@ -247,20 +249,31 @@ export function JavaScript_Shift(fkp: KMX.KEY, FMnemonic: boolean): number {
}
if (fkp.ShiftFlags & KMX.KMXFile.ISVIRTUALKEY) {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
// don't attempt to upgrade to v10 at this point, only if we are already v10+
// Full chiral modifier and state key support starts with KeymanWeb 10.0
return fkp.ShiftFlags;
}
// Non-chiral support only and no support for state keys
if (fkp.ShiftFlags & (KMX.KMXFile.LCTRLFLAG | KMX.KMXFile.RCTRLFLAG | KMX.KMXFile.LALTFLAG | KMX.KMXFile.RALTFLAG)) { // I4118
// TODO: automatic version upgrade
// if(verifyMinimumRequiredKeymanVersion10()) {
// // upgrade to v10 if possible
// return fkp.ShiftFlags;
// }
callbacks.reportMessage(KmwCompilerMessages.Warn_ExtendedShiftFlagsNotSupportedInKeymanWeb({line:fkp.Line, flags: 'LALT, RALT, LCTRL, RCTRL'}));
}
if (fkp.ShiftFlags & (
KMX.KMXFile.CAPITALFLAG | KMX.KMXFile.NOTCAPITALFLAG | KMX.KMXFile.NUMLOCKFLAG | KMX.KMXFile.NOTNUMLOCKFLAG |
KMX.KMXFile.SCROLLFLAG | KMX.KMXFile.NOTSCROLLFLAG)) { // I4118
callbacks.reportMessage(KmwCompilerMessages.Warn_ExtendedShiftFlagsNotSupportedInKeymanWeb({line:fkp.Line, flags: 'CAPS and NCAPS'}));
// TODO: automatic version upgrade
// if(verifyMinimumRequiredKeymanVersion10()) {
// // upgrade to v10 if possible
// return fkp.ShiftFlags;
// }
callbacks.reportMessage(KmwCompilerMessages.Warn_ExtendedShiftFlagsNotSupportedInKeymanWeb({line:fkp.Line, flags: 'CAPS and NCAPS'}));
}
return KMX.KMXFile.ISVIRTUALKEY | (fkp.ShiftFlags & (KMX.KMXFile.K_SHIFTFLAG | KMX.KMXFile.K_CTRLFLAG | KMX.KMXFile.K_ALTFLAG));
@ -403,7 +416,7 @@ export function JavaScript_KeyAsString(fkp: KMX.KEY, FMnemonic: boolean): string
}
export function JavaScript_ContextMatch(fk: KMX.KEYBOARD, fkp: KMX.KEY, context: string): string {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
return JavaScript_FullContextValue(fk, fkp, context);
}
else {
@ -429,7 +442,7 @@ function CheckStoreForInvalidFunctions(fk: KMX.KEYBOARD, key: KMX.KEY, store: KM
n = store.dpString.indexOf(wcsentinel);
// Disable the check with versions >= 10.0, since we now support deadkeys in stores.
if (n >= 0 && !IsKeyboardVersion10OrLater) {
if (n >= 0 && !isKeyboardVersion10OrLater()) {
rec = ExpandSentinel(fk, store.dpString, n);
callbacks.reportMessage(KmwCompilerMessages.Error_NotSupportedInKeymanWebStore({code:GetCodeName(rec.Code), store:store.dpName}));
}
@ -630,7 +643,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
for(let I = 1; I < Index; I++) {
let recContext = ExpandSentinel(fk, pwszContext, x);
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
if(recContext.IsSentinel && [KMX.KMXFile.CODE_NUL, KMX.KMXFile.CODE_IFOPT, KMX.KMXFile.CODE_IFSYSTEMSTORE].includes(recContext.Code)) {
Result--;
}
@ -666,7 +679,8 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
case KMX.KMXFile.CODE_NOTANY:
// #917: Minimum version required is 14.0: the KCXO function was only added for 14.0
// Note that this is checked in compiler.cpp as well, so this error can probably never occur
if(!IsKeyboardVersion14OrLater()) {
// TODO: automatic version upgrade
if(!isKeyboardVersion14OrLater()) {
callbacks.reportMessage(KmwCompilerMessages.Error_NotAnyRequiresVersion14({line:fkp.Line}));
}
Result += nlt + `k.KCXO(${len},t,${AdjustIndex(fkp.dpContext, xstrlen(fkp.dpContext))},${AdjustIndex(fkp.dpContext, ContextIndex)+1});`;
@ -701,7 +715,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
let pwsz = pwszOutput;
if(fkp != null) {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
// KMW >= 10.0 use the full, sentinel-based length for context deletions.
len = xstrlen(fkp.dpContext);
let n = len;
@ -726,7 +740,7 @@ export function JavaScript_OutputString(fk: KMX.KEYBOARD, FTabStops: string, fkp
}
let x = 0;
if(IsKeyboardVersion10OrLater() && pwsz.length > 0) {
if(isKeyboardVersion10OrLater() && pwsz.length > 0) {
if(!isGroupReadOnly(fk, fgp)) {
Result += nlt+`k.KDC(${len},t);`; // I3681
}
@ -932,7 +946,8 @@ export function zeroPadHex(n: number, len: number): string {
* @return string of JavaScript code, e.g. 'keyCodes.K_A /* 0x41 * /'
*/
function FormatKeyAsString(key: number): string {
if(IsKeyboardVersion10OrLater()) {
// TODO: automatic version upgrade
if(isKeyboardVersion10OrLater()) {
// Depends on flags defined in KeymanWeb 10.0
if (key <= 255 && KMWVKeyNames[key] != '') {
return 'keyCodes.'+KMWVKeyNames[key]+ ' /* 0x' + zeroPadHex(key, 2) + ' */';

View file

@ -3,7 +3,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerM
import { kmnfile } from "./compiler-globals.js";
const Namespace = CompilerErrorNamespace.KmwCompiler;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
const SevHint = CompilerErrorSeverity.Hint | Namespace;
// const SevWarn = CompilerErrorSeverity.Warn | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
@ -49,4 +49,8 @@ export class KmwCompilerMessages extends KmnCompilerMessages {
static HINT_TouchLayoutUsesUnsupportedGesturesDownlevel = SevHint | 0x0005;
static Hint_TouchLayoutUsesUnsupportedGesturesDownlevel = (o:{keyId:string}) => m(this.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel,
`The touch layout uses a flick or multi-tap gesture on key ${def(o.keyId)}, which is only available on version 17.0+ of Keyman`);
static INFO_MinimumEngineVersion = SevInfo | 0x0006;
static Info_MinimumEngineVersion = (o:{version:string}) => m(this.INFO_MinimumEngineVersion,
`The compiler has assigned a minimum web engine version of ${o.version} based on features used in this keyboard`);
};

View file

@ -1,6 +1,6 @@
import { KMX, CompilerOptions, CompilerCallbacks, KvkFileReader, VisualKeyboard, KmxFileReader, KvkFile } from "@keymanapp/common-types";
import { ExpandSentinel, incxstr, xstrlen } from "./util.js";
import { options, nl, FTabStop, setupGlobals, IsKeyboardVersion10OrLater, callbacks, FFix183_LadderLength, FCallFunctions, fk, IsKeyboardVersion17OrLater } from "./compiler-globals.js";
import { options, nl, FTabStop, setupGlobals, callbacks, FFix183_LadderLength, FCallFunctions, fk, minimumKeymanVersionToString, isKeyboardVersion10OrLater, isKeyboardVersion17OrLater } from "./compiler-globals.js";
import { JavaScript_ContextMatch, JavaScript_KeyAsString, JavaScript_Name, JavaScript_OutputString, JavaScript_Rules, JavaScript_Shift, JavaScript_ShiftAsString, JavaScript_Store, zeroPadHex } from './javascript-strings.js';
import { KmwCompilerMessages } from "./kmw-compiler-messages.js";
import { ValidateLayoutFile } from "./validate-layout-file.js";
@ -206,8 +206,13 @@ export function WriteCompiledKeyboard(
// Following line caches the Keyman major version
`${FTabStop}this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;${nl}` +
`${FTabStop}this.KI="${sName}";${nl}` +
`${FTabStop}this.KN="${RequotedString(sFullName)}";${nl}` +
`${FTabStop}this.KMINVER="${(keyboard.fileVersion & KMX.KMXFile.VERSION_MASK_MAJOR) >> 8}.${keyboard.fileVersion & KMX.KMXFile.VERSION_MASK_MINOR}";${nl}` +
`${FTabStop}this.KN="${RequotedString(sFullName)}";${nl}`;
// We split the result text here to allow for setting the minimum required
// version at the very end of this function
const resultPrefix = result;
result =
`${FTabStop}this.KV=${sVisualKeyboard};${nl}` +
`${FTabStop}this.KDU=${fDisplayUnderlying?'1':'0'};${nl}` +
`${FTabStop}this.KH=${sHelp};${nl}` +
@ -400,7 +405,17 @@ export function WriteCompiledKeyboard(
}
result += sEmbedJS + '}' + nl; // I3681
return result;
const minVer = minimumKeymanVersionToString();
const resultMinVer = `${FTabStop}this.KMINVER="${minVer}";${nl}`;
if((fk.flags & KMX.KMXFile.KF_AUTOMATICVERSION) == KMX.KMXFile.KF_AUTOMATICVERSION) {
// Note: the KeymanWeb compiler is responsible for reporting minimum
// version for the web targets
callbacks.reportMessage(KmwCompilerMessages.Info_MinimumEngineVersion({version:minVer}));
}
return resultPrefix + resultMinVer + result;
}
///
@ -440,9 +455,9 @@ function GetKeyboardModifierBitmask(keyboard: KMX.KEYBOARD, fMnemonic: boolean):
/// @return string of JavaScript code
///
function JavaScript_SetupDebug() {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
if(options.saveDebug) {
if(IsKeyboardVersion17OrLater()) {
if(isKeyboardVersion17OrLater()) {
return 'var modCodes = KeymanWeb.Codes.modifierCodes;'+nl+
FTabStop+'var keyCodes = KeymanWeb.Codes.keyCodes;'+nl;
} else {
@ -455,7 +470,7 @@ function JavaScript_SetupDebug() {
}
function JavaScript_SetupProlog() {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
return 'if(typeof keyman === \'undefined\') {'+nl+
FTabStop+'console.log(\'Keyboard requires KeymanWeb 10.0 or later\');'+nl+
FTabStop+'if(typeof tavultesoft !== \'undefined\') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");'+nl+
@ -465,7 +480,7 @@ function JavaScript_SetupProlog() {
}
function JavaScript_SetupEpilog() {
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
return '}';
}
return '';
@ -560,7 +575,7 @@ export function FormatModifierAsBitflags(FBitMask: number): string {
//TODO: We need to think about mnemonic layouts which are incompletely supported at present
//tavultesoft.keymanweb.osk.
if(IsKeyboardVersion10OrLater()) {
if(isKeyboardVersion10OrLater()) {
// This depends on flags defined in KeymanWeb 10.0
result = '';

View file

@ -1,7 +1,9 @@
import { KMX, TouchLayout, TouchLayoutFileReader, TouchLayoutFileWriter } from "@keymanapp/common-types";
import { callbacks, fk, IsKeyboardVersion14OrLater, IsKeyboardVersion15OrLater, IsKeyboardVersion17OrLater } from "./compiler-globals.js";
import { callbacks, minimumKeymanVersion, verifyMinimumRequiredKeymanVersion15,
isKeyboardVersion14OrLater, isKeyboardVersion17OrLater } from "./compiler-globals.js";
import { JavaScript_Key } from "./javascript-strings.js";
import { TRequiredKey, CRequiredKeys, CSpecialText, CSpecialText14Map, CSpecialText17Map, CSpecialTextMinVer, CSpecialTextMaxVer } from "./constants.js";
import { TRequiredKey, CRequiredKeys, CSpecialText, CSpecialText14Map, CSpecialText17Map,
CSpecialTextMinVer, CSpecialTextMaxVer } from "./constants.js";
import { KeymanWebTouchStandardKeyNames, KMWAdditionalKeyNames, VKeyNames } from "./keymanweb-key-codes.js";
import { KmwCompilerMessages } from "./kmw-compiler-messages.js";
import * as Osk from '../compiler/osk.js';
@ -121,7 +123,7 @@ function CheckKey(
callbacks.reportMessage(KmwCompilerMessages.Error_TouchLayoutInvalidIdentifier({keyId: FId, platformName: platformId, layerId: layer.id}));
return false;
}
else if (FValid == TKeyIdType.Key_Unicode_Multi && !IsKeyboardVersion15OrLater()) {
else if (FValid == TKeyIdType.Key_Unicode_Multi && !verifyMinimumRequiredKeymanVersion15()) {
callbacks.reportMessage(KmwCompilerMessages.Error_TouchLayoutIdentifierRequires15({keyId: FId, platformName: platformId, layerId: layer.id}));
return false;
}
@ -144,10 +146,11 @@ function CheckKey(
// Keyman versions before 14 do not support '*special*' labels on non-special keys.
// ZWNJ use, however, is safe because it will be transformed in function
// TransformSpecialKeys14 to '<|>', which does not require the custom OSK font.
const mapVersion = Math.max(Math.min(fk.fileVersion, CSpecialTextMaxVer), CSpecialTextMinVer);
const mapVersion = Math.max(Math.min(minimumKeymanVersion(), CSpecialTextMaxVer), CSpecialTextMinVer);
const specialText = CSpecialText.get(mapVersion);
if(specialText.includes(FText) &&
!IsKeyboardVersion14OrLater() &&
// TODO: automatic version upgrade
!isKeyboardVersion14OrLater() &&
!([TouchLayout.TouchLayoutKeySp.special, TouchLayout.TouchLayoutKeySp.specialActive].includes(FKeyType))) {
callbacks.reportMessage(KmwCompilerMessages.Warn_TouchLayoutSpecialLabelOnNormalKey({
keyId: FId,
@ -187,7 +190,7 @@ function CheckDictionaryKeyValidity(fk: KMX.KEYBOARD, FDictionary: string[]) {
function TransformSpecialKeys14(FDebug: boolean, sLayoutFile: string): string {
// Rewrite Special key labels that are only supported in Keyman 14+
// This code is a little ugly but effective.
if(!IsKeyboardVersion14OrLater()) {
if(!isKeyboardVersion14OrLater()) {
for(let i = 0; i < CSpecialText14Map.length; i++) {
// Assumes the JSON output format will not change
if(FDebug) {
@ -203,7 +206,7 @@ function TransformSpecialKeys14(FDebug: boolean, sLayoutFile: string): string {
function TransformSpecialKeys17(FDebug: boolean, sLayoutFile: string): string {
// Rewrite Special key labels that are only supported in Keyman 17+
// This code is a little ugly but effective.
if(!IsKeyboardVersion17OrLater()) {
if(!isKeyboardVersion17OrLater()) {
for(let i = 0; i < CSpecialText17Map.length; i++) {
// Assumes the JSON output format will not change
if(FDebug) {
@ -239,7 +242,8 @@ export function ValidateLayoutFile(fk: KMX.KEYBOARD, FDebug: boolean, sLayoutFil
let hasWarnedOfGestureUseDownlevel = false;
const warnGesturesIfNeeded = function(keyId: string) {
if(!hasWarnedOfGestureUseDownlevel && !IsKeyboardVersion17OrLater()) {
// TODO: automatic version upgrade
if(!hasWarnedOfGestureUseDownlevel && !isKeyboardVersion17OrLater()) {
hasWarnedOfGestureUseDownlevel = true;
callbacks.reportMessage(KmwCompilerMessages.Hint_TouchLayoutUsesUnsupportedGesturesDownlevel({keyId}));
}

View file

@ -0,0 +1,53 @@
{
"tablet": {
"font": "Tahoma",
"layer": [
{
"id": "default",
"row": [
{
"id": 1,
"key": [
{
"id": "K_Q",
"text": "q"
},
{
"id": "U_0041_0300",
"text": "A'"
},
{
"id": "U_0042_0300",
"text": "B'"
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"width": 90,
"sp": 1
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": 120,
"sp": 1
},
{
"id": "K_SPACE",
"text": "",
"width": 630,
"sp": 0
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": 140,
"sp": 1
}
]
}
]
}
]
}
}

View file

@ -0,0 +1,7 @@
store(&NAME) 'version_u_xxxx_yyyy'
store(&TARGETS) 'mobile'
store(&LAYOUTFILE) 'version_u_xxxx_yyyy.keyman-touch-layout'
begin Unicode > use(main)
group(main) using keys

View file

@ -0,0 +1,8 @@
store(&NAME) 'version_u_xxxx_yyyy'
store(&TARGETS) 'mobile'
store(&LAYOUTFILE) 'version_u_xxxx_yyyy.keyman-touch-layout'
store(&VERSION) '14.0'
begin Unicode > use(main)
group(main) using keys

View file

@ -6,8 +6,10 @@ import { fileURLToPath } from 'url';
import fs from 'fs';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { KmnCompilerResult, KmnCompiler } from '../../src/compiler/compiler.js';
import { ETLResult, extractTouchLayout as parseWebTestResult } from './util.js';
import { ETLResult, extractTouchLayout } from './util.js';
import { KeymanFileTypes } from '@keymanapp/common-types';
import { KmnCompilerMessages } from '../../src/compiler/kmn-compiler-messages.js';
import { KmwCompilerMessages } from '../../src/kmw-compiler/kmw-compiler-messages.js';
const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/');
const fixturesDir = __dirname + '/../../../test/fixtures/kmw/';
@ -38,50 +40,74 @@ describe('KeymanWeb Compiler', function() {
callbacks.clear();
});
it('should compile a complex keyboard', function() {
run_test_keyboard(kmnCompiler, 'khmer_angkor');
it('should compile a complex keyboard', async function() {
await run_test_keyboard(kmnCompiler, 'khmer_angkor');
});
it('should handle option stores', function() {
it('should handle option stores', async function() {
//
// This is enough to verify that the option store is set appropriately with
// KLOAD because the fixture has that code present:
//
// this.s_foo_6=KeymanWeb.KLOAD(this.KI,"foo","0");
//
run_test_keyboard(kmnCompiler, 'test_options');
await run_test_keyboard(kmnCompiler, 'test_options');
});
it('should translate every "character style" key correctly', function() {
it('should translate every "character style" key correctly', async function() {
//
// This is enough to verify that every character style key is encoded in the
// same way as the fixture.
//
run_test_keyboard(kmnCompiler, 'test_keychars');
await run_test_keyboard(kmnCompiler, 'test_keychars');
});
it('should handle readonly groups', function() {
run_test_keyboard(kmnCompiler, 'test_readonly_groups');
it('should handle readonly groups', async function() {
await run_test_keyboard(kmnCompiler, 'test_readonly_groups');
});
it('should handle context(n) in output of rule, v10.0 generation', function() {
run_test_keyboard(kmnCompiler, 'test_contextn_in_output');
it('should handle context(n) in output of rule, v10.0 generation', async function() {
await run_test_keyboard(kmnCompiler, 'test_contextn_in_output');
});
it('should handle context(n) in output of rule, v9.0 generation', function() {
run_test_keyboard(kmnCompiler, 'test_contextn_in_output_9');
it('should handle context(n) in output of rule, v9.0 generation', async function() {
await run_test_keyboard(kmnCompiler, 'test_contextn_in_output_9');
});
it('should handle context(n) in context part of rule, v9.0 generation', function() {
run_test_keyboard(kmnCompiler, 'test_context_in_context_9');
it('should handle context(n) in context part of rule, v9.0 generation', async function() {
await run_test_keyboard(kmnCompiler, 'test_context_in_context_9');
});
it('should handle context(n) in context part of rule, v10.0 generation', function() {
run_test_keyboard(kmnCompiler, 'test_context_in_context');
it('should handle context(n) in context part of rule, v10.0 generation', async function() {
await run_test_keyboard(kmnCompiler, 'test_context_in_context');
});
it('should determine the minimum version correctly with U_xxxx_yyyy touch ids', async function() {
const filenames = generateTestFilenames('version_u_xxxx_yyyy');
let result = await kmnCompiler.run(filenames.source, null);
assert.isNotNull(result);
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.INFO_MinimumEngineVersion));
// The min version message from the .kmn compiler is generic 208A INFO_Info;
// we expect only 1 of the info messages -- for the .kmx target (not 2)
assert.equal(callbacks.messages.filter(item => item.code == KmnCompilerMessages.INFO_Info).length, 1);
const data = new TextDecoder('utf-8').decode(result.artifacts.js.data);
assert.match(data, /KMINVER="15.0"/, `Could not find expected 'KMINVER="15.0"'`);
});
it('should give an error if the minimum version specified in the keyboard does not support U_xxxx_yyyy touch ids', async function() {
const filenames = generateTestFilenames('version_u_xxxx_yyyy_14');
let result = await kmnCompiler.run(filenames.source, null);
assert.isNull(result);
// The min version message from the .kmn compiler is generic 208A INFO_Info
assert.isFalse(callbacks.hasMessage(KmnCompilerMessages.INFO_Info));
assert.isFalse(callbacks.hasMessage(KmwCompilerMessages.INFO_MinimumEngineVersion));
assert.isTrue(callbacks.hasMessage(KmwCompilerMessages.ERROR_TouchLayoutIdentifierRequires15));
});
});
async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):
Promise<{ result: KmnCompilerResult, actualCode: string, actual: ETLResult, expectedCode: string, expected: ETLResult }> {
const filenames = generateTestFilenames(id);
@ -96,11 +122,11 @@ async function run_test_keyboard(kmnCompiler: KmnCompiler, id: string):
expected: <ETLResult>null,
actual: <ETLResult>null,
};
value.actual = parseWebTestResult(value.actualCode);
value.expected = parseWebTestResult(value.expectedCode);
value.actual = extractTouchLayout(value.actualCode);
value.expected = extractTouchLayout(value.expectedCode);
if(debug) {
// This is mostly to allow us to verify that parseWebTestResult is doing what we want
// This is mostly to allow us to verify that extractTouchLayout is doing what we want
// fs.writeFileSync(filenames.binary + '.strip.js', value.actual.js);
// fs.writeFileSync(filenames.fixture + '.strip.js', value.expected.js);
fs.writeFileSync(filenames.binary, value.actualCode);

View file

@ -24,21 +24,21 @@ describe('Keyboard compiler features', function() {
const versions = [
// TODO(lowpri): we should add a test for each version + also test automatic feature detection
['16.0', '160', KMX.KMXFile.VERSION_160],
['17.0', '170', KMX.KMXFile.VERSION_170],
{major: '16.0', vstr: '160', vernum: KMX.KMXFile.VERSION_160},
{major: '17.0', vstr: '170', vernum: KMX.KMXFile.VERSION_170},
];
for(const v of versions) {
it(`should build a version ${v[0]} keyboard`, async function() {
const fixtureName = makePathToFixture('features', `version_${v[1]}.kmn`);
it(`should build a version ${v.major} keyboard`, async function() {
const fixtureName = makePathToFixture('features', `version_${v.vstr}.kmn`);
const result = await compiler.run(fixtureName, `version_${v[1]}.kmx`);
const result = await compiler.run(fixtureName, `version_${v.vstr}.kmx`);
if(result === null) callbacks.printMessages();
assert.isNotNull(result);
const reader = new KmxFileReader();
const keyboard = reader.read(result.artifacts.kmx.data);
assert.equal(keyboard.fileVersion, v[2]);
assert.equal(keyboard.fileVersion, v.vernum);
});
}
});

View file

@ -12,8 +12,12 @@ KMX_BOOL kmcmp::CheckKeyboardFinalVersion(PFILE_KEYBOARD fk) {
fk->version = VERSION_60; // minimum version that we can be safe with
}
snprintf(buf, 128, "The compiler has assigned a minimum engine version of %d.%d based on features used in this keyboard", (int)((fk->version & 0xFF00) >> 8), (int)(fk->version & 0xFF));
AddCompileWarning(buf);
if(kmcmp::CompileTarget != CKF_KEYMANWEB) {
// Note: the KeymanWeb compiler is responsible for reporting minimum
// version for the web targets
snprintf(buf, 128, "The compiler has assigned a minimum engine version of %d.%d based on features used in this keyboard", (int)((fk->version & 0xFF00) >> 8), (int)(fk->version & 0xFF));
AddCompileWarning(buf);
}
}
return TRUE;