Merge branch 'beta' into fix/android/selectively-disable-autocorrect
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled

This commit is contained in:
Joshua Horton 2026-10-08 10:11:34 -05:00
commit 2abfcdf3cc
556 changed files with 36157 additions and 14689 deletions

View file

@ -1,5 +1,87 @@
# Keyman Version History
## 19.0.303 beta 2026-10-07
* fix(resources): export KEYMAN_TIER so we get beta tagging on release build (#16751)
## 19.0.302 beta 2026-10-07
* fix(mac): lower case 'k' for .pkg.download_info (#16743)
* docs: update v19 whats-new (#16748)
## 19.0.300 beta 2026-10-06
* chore: move to beta
## 19.0.299 alpha 2026-10-05
* chore(deps): bump ip-address from 10.5.0 to 10.7.2 in /developer/src/server/src/win32/trayicon/addon-src (#16698)
* chore(deps): bump ip-address from 10.4.0 to 10.7.2 (#16696)
* chore(deps-dev): bump brace-expansion from 2.0.2 to 2.1.7 in /resources/build/pr-build-status (#16721)
* chore(deps): bump multer from 2.3.0 to 2.4.0 (#16692)
* chore(deps): bump brace-expansion (#16728)
* fix(mac): show or hide the OSK from the Keyman menu (#16708)
* chore(deps-dev): bump brace-expansion from 2.1.1 to 2.1.7 in /core/tests/unit/wasm (#16730)
## 19.0.298 alpha 2026-10-03
* chore(linux): update Debian standards version (#16714)
## 19.0.297 alpha 2026-10-02
* fix(windows): add warning when system startup disabled (#16603)
* docs(windows): add startup app disabled warning help (#16695)
## 19.0.296 alpha 2026-09-30
* fix(developer): handle missing `&name` store in package compiler (#16697)
* fix(developer): handle out-of-range return values from `GetExitCodeProcess` (#16702)
* fix(developer): handle .gitignore in npm-based installations of kmc-generate (#16686)
* fix(developer): transform unpaired surrogates in editor to U+FFFD (#16694)
* chore(web): allow to open guide example from TC artifacts (#16689)
## 19.0.295 alpha 2026-09-29
* fix(mac): open config app from input method (#16676)
* maint(web): improve build perf and test params (#16582)
* fix(developer): support Rich Edit in Charmap drag + drop (#16685)
## 19.0.294 alpha 2026-09-28
* fix(mac): disable the help button entirely in the title bar of OSK (#16679)
## 19.0.293 alpha 2026-09-25
* fix(mac): change .pkg name to lower case (#16659)
* chore(deps-dev): bump grunt from 1.4.1 to 1.5.3 in /developer/src/tike/xml/layoutbuilder/src/ext/jquery-ui (#16648)
* chore(deps): bump multer from 2.2.0 to 2.3.0 (#16543)
* chore(web): clarify `LanguageProcessorSpec` usage (#16662)
* docs(web): deprecate `keyman.moveToElement(elem)` API (#16661)
* chore(developer): cleanup tike build.sh for touch-layout-editor (#16665)
* change(mac): remove unused minizip code (#16639)
## 19.0.292 alpha 2026-09-24
* chore(windows): use ✕ U+2715 consistently in localizations (#16566)
* docs(windows): replace 'keep in touch' tab with 'update' tab to help … (#16544)
* chore(web): improve reporting on dictionary load errors (#16647)
* maint(mac): use build/upload as upload path for downloads.keyman.com (#16654)
## 19.0.291 alpha 2026-09-24
* epic: mac-config (#15328)
* chore(common): fix TC reporter reporting failures (#16643)
* chore(android): remove unused map-polyfill.js and es6-shim.min.js polyfills (#16637)
* fix(developer): address crashes in touch layout editor (#16572)
* feat(mac): remove obsolete configuration UI (#16570)
* change(developer): reorganize touch layout editor files (#16517)
* change(developer): move touch layout editor to typescript (#16520)
## 19.0.290 alpha 2026-09-23
* fix(windows): set basekeyboard as current user not the admin user on an elevated process (#16162)
* fix(windows): add non-admin user install with different base keyboard (#16528)
## 19.0.289 alpha 2026-09-21
* fix(android): suppress autocorrection of hardware keystroke input (#16606)
@ -643,6 +725,7 @@
## 19.0.182 alpha 2025-12-22
* epic: ARM support for Windows
* maint(common): Fix crowdin sync version to v2.7.0 (#15373)
* chore(linux): Update keyman-config.pot strings (#15366)
* chore(windows): show architecture in support texteditor (#15378)
@ -1744,6 +1827,10 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
## 18.0.253 stable 2026-10-02
* fix(mac): disable the help button entirely in the title bar of OSK (#16680)
## 18.0.252 stable 2026-09-22
* change(android): use webview asset loader (#16628)
@ -1752,7 +1839,7 @@
## 18.0.251 stable 2026-09-21
* maint(android): update Android Target API to 36 (#16620)
*
## 18.0.250 stable 2026-09-21
* fix(linux): fix memory problem (#15828)

View file

@ -1 +1 @@
alpha
beta

View file

@ -1 +1 @@
19.0.290
19.0.304

2
android/.gitignore vendored
View file

@ -37,10 +37,8 @@ KMEA/**/assets/keymanandroid.js
KMEA/**/assets/keyman.js.map
KMEA/**/assets/keymanweb-webview.js
KMEA/**/assets/keymanweb-webview.js.map
KMEA/**/assets/map-polyfill.js
KMEA/**/assets/sentry.min.js
KMEA/**/assets/keyman-sentry.js
KMEA/**/assets/es6-shim.min.js
KMEA/**/assets/kmwosk.css
KMEA/**/assets/globe-hint.css
KMEA/**/assets/resources

View file

@ -9,6 +9,7 @@ plugins {
ext.rootPath = '../../'
apply from: "$rootPath/version.gradle"
String tier = System.getenv("KEYMAN_TIER");
println "Using tier $tier"
java {
toolchain {

View file

@ -437,12 +437,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
protected void onResume() {
super.onResume();
if (navigationView != null) {
updateCurrentKeyboardDrawerItemTitle(navigationView);
updateInstalledLanguagesDrawerTitle(navigationView);
initializeDrawerItemSubtitles(navigationView);
}
if (textView != null) {
KMManager.setSuggestionType(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.defaultSuggestionModeForInputType(textView.getInputType()));
}
@ -464,12 +458,35 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
}
resizeTextView(textView.isKeyboardVisible());
// The keyboard may have been changed while we were paused, for example if
// the keyboard picker activity is started.
com.keyman.engine.data.Keyboard currentKeyboard = KMManager.getCurrentKeyboardInfo(context);
if(currentKeyboard != null) {
// TODO(v20): eliminate `lastKnownKeyboardId` and always use `currentKeyboard.getKeyboardID()`
// TODO(v20): this lines up with onKeyboardChanged(), refactor accordingly
lastKnownKeyboardId = currentKeyboard.getKeyboardID();
}
if (navigationView != null) {
// TODO(v20): these three calls are repeated in various places and should be cleaned up
updateCurrentKeyboardDrawerItemTitle(navigationView);
updateInstalledLanguagesDrawerTitle(navigationView);
initializeDrawerItemSubtitles(navigationView);
}
if (textView != null) {
// Reset inAppPredictionsSuspendedForSensitiveInput flag
KMManager.setPredictionsSuspended(textView.getInputType(), KeyboardType.KEYBOARD_TYPE_INAPP);
textView.setTypeface(KMManager.getKeyboardTextFontTypeface(this));
}
KMManager.addKeyboardEventListener(this);
KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
PackageActivity.addKeyboardDownloadEventListener(this);
CheckInstallReferrer.checkGooglePlayInstallReferrer(this, context);
// TODO(v20): move loadingIntentUri logic into a separate function
Intent intent = getIntent();
Uri loadingIntentUri = intent.getData();
@ -662,6 +679,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
if (drawerLayout.isDrawerOpen(GravityCompat.END)) {
drawerLayout.closeDrawer(GravityCompat.END);
} else {
updateCurrentKeyboardDrawerItemTitle(navigationView);
updateInstalledLanguagesDrawerTitle(navigationView);
initializeDrawerItemSubtitles(navigationView);
drawerLayout.openDrawer(GravityCompat.END);
}
return true;
@ -865,8 +885,9 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
* If URL is valid, download the kmp.
* @param packageUri URI to download the package.
* @param installMode KMP installation mode (silent, welcome only, or full)
* TODO: only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for
* TODO(v20): only ever pass packageId and bcp47 from callers, as KMPLink should be responsible for
* URL parsing, not this function.
* TODO(v20): move this into a separate module
*/
public void downloadKMP(Uri packageUri, KmpInstallMode installMode) {
if (packageUri == null) {
@ -982,6 +1003,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
}
// TODO(v20): remove remaining tendrils
private void showWebBrowser() {
// Telemetry for in-app browser usage.
// Logging here because WebBrowserActivity is launched in a separate process.
@ -1266,7 +1288,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
}
}
// TODO: Move this to KMEA during Keyman 13.0 refactoring
// TODO(v20): Move this to KMEA during Keyman 13.0 refactoring
public static void useLocalKMP(Context context, Uri data) {
if (data != null) {
useLocalKMP(context, data, false);
@ -1409,6 +1431,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
});
}
// TODO(v20): cleanup setting draw item [sub]titles; lots of repetition and unnecessary conditions
private void initializeDrawerItemSubtitles(NavigationView navigationView) {
setDrawerItemSubtitle(navigationView, R.id.nav_spacebar_caption,
getString(R.string.drawer_subtitle_spacebar_caption));
@ -1416,8 +1439,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
getString(R.string.drawer_subtitle_show_osk));
setDrawerItemSubtitle(navigationView, R.id.nav_toggle_send_crash_report,
getString(R.string.drawer_subtitle_send_crash_report));
setDrawerItemSubtitle(navigationView, R.id.nav_about_current_keyboard,
getString(R.string.drawer_subtitle_about_current_keyboard));
updateThemeDrawerSubtitle(navigationView);
updateCurrentKeyboardDrawerSubtitle(navigationView);
}
@ -1631,6 +1652,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
}
}
// TODO(v20): remove
private void bindDrawerCheckboxAction(NavigationView navigationView, int menuItemId,
final Runnable onActivate) {
MenuItem menuItem = navigationView.getMenu().findItem(menuItemId);
@ -1670,6 +1692,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
});
}
// TODO(v20): remove
private void setDrawerCheckboxState(NavigationView navigationView, int menuItemId, boolean isChecked) {
MenuItem menuItem = navigationView.getMenu().findItem(menuItemId);
if (menuItem == null) {

View file

@ -380,9 +380,7 @@ public final class KMManager {
protected static final String KMFilename_KmwCss = "kmwosk.css";
protected static final String KMFilename_KmwGlobeHintCss = "globe-hint.css";
protected static final String KMFilename_Osk_Ttf_Font = "keymanweb-osk.ttf";
protected static final String KMFilename_JSPolyfill = "es6-shim.min.js";
protected static final String KMFilename_JSPolyfill2 = "other-polyfills.js";
protected static final String KMFilename_JSPolyfill3 = "map-polyfill.js";
// Deprecated by KeyboardController.KMFilename_Installed_KeyboardsList
public static final String KMFilename_KeyboardsList = "keyboards_list.dat";
@ -3026,6 +3024,9 @@ public final class KMManager {
public static int getCurrentKeyboardIndex(Context context) {
String key = KMKeyboard.currentKeyboard();
if(key == null) {
return KeyboardController.INDEX_NOT_FOUND;
}
return KeyboardController.getInstance().getKeyboardIndex(key);
}
@ -3034,11 +3035,6 @@ public final class KMManager {
if(index < 0) {
// index can be undefined if user installs Keyman (without launching it)
// and then enables Keyaman as a system keyboard from the Android settings menus.
// We'll only log if key isn't for fallback keyboard
String key = KMKeyboard.currentKeyboard();
if (!isDefaultKey(key)) {
KMLog.LogError(TAG, "Failed getCurrentKeyboardIndex check for keyboard: " + key);
}
return null;
}
return KeyboardController.getInstance().getKeyboardInfo(index);

View file

@ -188,16 +188,15 @@ public class KeyboardController {
* If no match, returns INDEX_NOT_FOUND
* @param key - String of the key to find
* @return int - Index of the matching keyboard
*
* Note: do not perform logging here because KMLog.tagDebugInfo can call this, causing recursion
*/
public int getKeyboardIndex(String key) {
int index = INDEX_NOT_FOUND;
if (!isInitialized || list == null) {
KMLog.LogError(TAG, "getKeyboardIndex while KeyboardController() not initialized");
return index;
return INDEX_NOT_FOUND;
}
if (key == null || key.isEmpty()) {
KMLog.LogError(TAG, "getKeyboardIndex while key is null");
return index;
return INDEX_NOT_FOUND;
}
synchronized (list) {
@ -209,11 +208,7 @@ public class KeyboardController {
}
}
// We'll only log if key isn't for fallback keyboard
if (!KMManager.isDefaultKey(key)) {
KMLog.LogError(TAG, "getKeyboardIndex failed for key " + key);
}
return index;
return INDEX_NOT_FOUND;
}
/**

View file

@ -21,6 +21,7 @@ import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
// TODO(v20): make KMLog more resilient to re-entrancy by preventing nested calls with a sentinel flag
public final class KMLog {
private static final String TAG = "KMLog";

View file

@ -55,16 +55,12 @@ do_build() {
builder_echo "Copying Keyman Web artifacts"
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js.map" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/map-polyfill.js" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/ajax-loader.gif" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
builder_echo "Copying es6-shim polyfill"
cp "$KEYMAN_ROOT/node_modules/es6-shim/es6-shim.min.js" "$ENGINE_ASSETS/"
builder_echo "BUILD_FLAGS $BUILD_FLAGS"
# Build without test

View file

@ -2,9 +2,14 @@
title: What's New in Keyman Engine 19.0 for Android
---
* Added API for broadcasting when Keyman system keyboard font changes (#15193)
* Added API for selecting and controlling what types of suggestions are offered
for different `Editable` input types (#16644)
* **BREAKING** Apps that use the functionality of Keyman Engine for Android will have to add `androidx.webkit:webkit:1.14.0` as a dependency (#16146)
* **KNOWN ISSUE** `sil_euro_latin` keyboard must be included in an app if `setDefaultKeyboard()` is not called during initialization (#16215)
* Added [Intent `com.tavultesoft.kmapro.keyboard_changed`](KMAPro/) which is broadcast when Keyman system keyboard changes and includes font name (#15193)
* Added [`KMManager.getKeyboardHeightMax()`](KMManager/getKeyboardHeightMax) API (#13663)
* Added [`KMManager.getKeyboardHeightMin()`](KMManager/getKeyboardHeightMin) API (#13663)
* Clarified font filename vs facename in various APIs (#16211)
## See Also
* [Keyman Engine for Android Documentation](index)

View file

@ -4,3 +4,9 @@ title: What's New in Keyman 19.0 for Android
Here are some of the new features we have added to Keyman 19.0 for Android:
- Keyboard search is now localized for several languages (#15510)
- Support for dark mode (#15827)
- Refreshed and modernized settings user interface (#15827)
- Automatic acceptance of corrections (#12893)
- Improvements to interface for keyboard resizing (#15267)
- About [220 other fixes and changes](https://keyman.com/go/app/whatsnew/android/19.0)

63
common/cpp/kmx_file.cpp Normal file
View file

@ -0,0 +1,63 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Created by mcdurdin on 2026-10-03
*
* Number of parameters for xstring codes
*/
#include "kmx_file.h"
#ifdef KM_CORE_LIBRARY
// TODO: move this to a common namespace keyman::common::kmx_file or similar in the future
namespace km {
namespace core {
namespace kmx {
#endif
/**
* Define the number of parameters for each of the extended string codes:
* * -1 means the code should not (or cannot) occur; treated as if the code is
* not present.
*
* * 0, 1, 2, 3 parameters otherwise, each one 16 bits (i.e. one u16 char) wide.
* Each parameter must be a non-zero value (0x0001-0xFFFF).
*
* * If a UC_SENTINEL is followed by any other code, the code is returned as a
* character.
*/
const int CODE__SIZE[] = {
-1, // undefined 0x00
1, // CODE_ANY 0x01
2, // CODE_INDEX 0x02
0, // CODE_CONTEXT 0x03
0, // CODE_NUL 0x04
1, // CODE_USE 0x05
0, // CODE_RETURN 0x06
0, // CODE_BEEP 0x07
1, // CODE_DEADKEY 0x08
-1, // unused 0x09
2, // CODE_EXTENDED 0x0A
-1, // CODE_EXTENDEDEND 0x0B (unused)
1, // CODE_SWITCH 0x0C (unused, removed in 19.0)
-1, // CODE_KEY 0x0D (unused, removed in 19.0)
0, // CODE_CLEARCONTEXT 0x0E (unused, removed in 19.0)
1, // CODE_CALL 0x0F
-1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL)
1, // CODE_CONTEXTEX 0x11
1, // CODE_NOTANY 0x12
2, // CODE_SETOPT 0x13
3, // CODE_IFOPT 0x14
1, // CODE_SAVEOPT 0x15
1, // CODE_RESETOPT 0x16
3, // CODE_IFSYSTEMSTORE 0x17
2 // CODE_SETSYSTEMSTORE 0x18
};
// Ensure that all CODE_### sizes are defined
static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct");
#ifdef KM_CORE_LIBRARY
} // namespace kmx
} // namespace core
} // namespace km
#endif

View file

@ -200,9 +200,9 @@ namespace kmx {
// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL
#define CODE_EXTENDED 0x0A
//#define CODE_EXTENDEDEND 0x0B deprecated
#define CODE_SWITCH 0x0C
#define CODE_KEY 0x0D
#define CODE_CLEARCONTEXT 0x0E // deprecated in 19.0
//#define CODE_SWITCH 0x0C // unused, removed in 19.0
//#define CODE_KEY 0x0D // unused, removed in 19.0
//#define CODE_CLEARCONTEXT 0x0E // unused, removed in 19.0
#define CODE_CALL 0x0F
// UC_SENTINEL_EXTENDEDEND 0x10
#define CODE_CONTEXTEX 0x11
@ -234,8 +234,8 @@ namespace kmx {
#define U_CODE_BEEP u"\u0007"
#define U_CODE_DEADKEY u"\u0008"
#define U_CODE_EXTENDED u"\u000A"
#define U_CODE_SWITCH u"\u000C"
#define U_CODE_CLEARCONTEXT u"\u000E" // deprecated in 19.0
#define U_CODE_SWITCH u"\u000C" // unused, removed in 19.0; this macro kept for security tests
#define U_CODE_CLEARCONTEXT u"\u000E" // unused, removed in 19.0; this macro kept for security tests
#define U_CODE_CALL u"\u000F"
#define U_CODE_EXTENDEDEND u"\u0010"
#define U_CODE_CONTEXTEX u"\u0011"
@ -256,8 +256,8 @@ namespace kmx {
#define C_CODE_BEEP() U_UC_SENTINEL U_CODE_BEEP
#define C_CODE_DEADKEY(deadkey) U_UC_SENTINEL U_CODE_DEADKEY deadkey
#define C_CODE_EXTENDED(varargs) U_UC_SENTINEL U_CODE_EXTENDED varargs
#define C_CODE_SWITCH(val) U_UC_SENTINEL U_CODE_SWITCH val
#define C_CODE_CLEARCONTEXT() U_UC_SENTINEL U_CODE_CLEARCONTEXT // deprecated in 19.0
#define C_CODE_SWITCH(val) U_UC_SENTINEL U_CODE_SWITCH val // unused, removed in 19.0; this macro kept for security tests
#define C_CODE_CLEARCONTEXT() U_UC_SENTINEL U_CODE_CLEARCONTEXT // unused, removed in 19.0; this macro kept for security tests
#define C_CODE_CALL(val) U_UC_SENTINEL U_CODE_CALL val
#define C_CODE_CONTEXTEX(val) U_UC_SENTINEL U_CODE_CONTEXTEX val
#define C_CODE_NOTANY(val) U_UC_SENTINEL U_CODE_NOTANY val

View file

@ -63,12 +63,12 @@ export default function teamcityReporter({ name="Web Test Runner JavaScript test
} else {
summary.failed++;
const message = test.error ? `message='${e(test.error.message)}'` : '';
const details = test.error ? `\ndetails='${e(test.error.stack)}\n`: '';
const details = test.error ? `details='${e(test.error.stack)}'`: '';
if(test.error?.actual !== undefined && test.error?.expected !== undefined) {
logger.log(`##teamcity[testFailed type='comparisonFailure' name='${e(test.name)}' ${e(message)}] ${e(details)} expected='${e(test.error?.expected)}' actual='${e(test.error?.actual)}']`);
logger.log(`##teamcity[testFailed type='comparisonFailure' name='${e(test.name)}' ${message} ${details} expected='${e(test.error?.expected)}' actual='${e(test.error?.actual)}']`);
}
logger.log(`##teamcity[testFailed name='${e(test.name)}' ${e(message)}] ${e(details)}']`);
logger.log(`##teamcity[testFailed name='${e(test.name)}' ${message} ${details}]`);
}
logger.log(`##teamcity[testFinished name='${e(test.name)}' duration='${e(test.duration ?? 0)}']`);

View file

@ -308,9 +308,9 @@ export class KMXFile {
// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL
public static readonly CODE_EXTENDED = 0x0A;
//public static readonly CODE_EXTENDEDEND = 0x0B; deprecated
public static readonly CODE_SWITCH = 0x0C;
public static readonly CODE_KEY = 0x0D;
public static readonly CODE_CLEARCONTEXT = 0x0E; // deprecated in 19.0
//public static readonly CODE_SWITCH = 0x0C; // unused, removed in 19.0
//public static readonly CODE_KEY = 0x0D; // unused, removed in 19.0
//public static readonly CODE_CLEARCONTEXT = 0x0E; // unused, removed in 19.0
public static readonly CODE_CALL = 0x0F;
// UC_SENTINEL_EXTENDEDEND 0x10
public static readonly CODE_CONTEXTEX = 0x11;

View file

@ -256,9 +256,9 @@
// 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL
#define CODE_EXTENDED 0x0A
//#define CODE_EXTENDEDEND 0x0B deprecated
#define CODE_SWITCH 0x0C
#define CODE_KEY 0x0D
#define CODE_CLEARCONTEXT 0x0E // deprecated in 19.0
//#define CODE_SWITCH 0x0C // unused, removed in 19.0
//#define CODE_KEY 0x0D // unused, removed in 19.0
//#define CODE_CLEARCONTEXT 0x0E // unused, removed in 19.0
#define CODE_CALL 0x0F
// UC_SENTINEL_EXTENDEDEND 0x10
#define CODE_CONTEXTEX 0x11

View file

@ -48,7 +48,6 @@ PWSTR incxstr(PWSTR p)
case CODE_USE: return p+1;
case CODE_DEADKEY: return p+1;
case CODE_EXTENDED: p += 2; while(*p && *p != UC_SENTINEL_EXTENDEDEND) p++; return p+1;
case CODE_CLEARCONTEXT: return p+1;
case CODE_CALL: return p+1;
case CODE_CONTEXTEX: return p+1;
case CODE_IFOPT: return p+3;
@ -98,7 +97,6 @@ PWSTR decxstr(PWSTR p, PWSTR pStart)
case CODE_NOTANY:
case CODE_USE:
case CODE_DEADKEY:
case CODE_CLEARCONTEXT:
case CODE_CALL:
case CODE_CONTEXTEX:
case CODE_RESETOPT:

View file

@ -283,6 +283,8 @@ const
SRegValue_WindowsRun_Keyman = 'Keyman';
SRegKey_StartupApproved_Run = 'Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run'; // CU
SRegValue_LanguageCheckDisabledItems = 'language check disabled items'; // CU
{-------------------------------------------------------------------------------

View file

@ -154,7 +154,7 @@ begin
end;
until b = 0;
ExitCode := ec; // I3631
ExitCode := Integer(ec); // I3631
Result := True;
finally

View file

@ -54,9 +54,9 @@ const
// = $09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL
CODE_EXTENDED = $0A;
//#define CODE_EXTENDEDEND = $0B deprecated
CODE_SWITCH = $0C;
CODE_KEY = $0D;
CODE_CLEARCONTEXT = $0E; // deprecated in 19.0
//CODE_SWITCH = $0C; // unused, removed in 19.0
//CODE_KEY = $0D; // unused, removed in 19.0
//CODE_CLEARCONTEXT = $0E; // unused, removed in 19.0
CODE_CALL = $0F;
// UC_SENTINEL_EXTENDEDEND 0x10
CODE_CONTEXTEX = $11;
@ -74,15 +74,6 @@ const
CODE_SETSYSTEMSTORE = $18; // I3437
CODE_LASTCODE = $18;
const
KMXCodeNames: array[CODE_FIRSTCODE..CODE_LASTCODE] of string = (
'any', 'index', 'context', 'nul', 'use', 'return', 'beep', 'deadkey',
'',
'extended', '', 'switch', 'key', 'clearcontext', 'call',
'', 'contextex', 'notany',
'set', 'if', 'save', 'reset', // I3429
'if(&system)', 'set(&system)'); // I3430 // I3437
const
VERSION_30 = $00000300;
VERSION_31 = $00000301;

View file

@ -163,7 +163,6 @@ begin
CODE_CONTEXTEX: Inc(Result, 2);
CODE_NOTANY: Inc(Result, 2);
CODE_CLEARCONTEXT: Inc(Result, 2); // I3442 // deprecated in 19.0
CODE_IFOPT: Inc(Result, 4); // I3442
CODE_IFSYSTEMSTORE: Inc(Result, 4); // I3442
CODE_SETOPT: Inc(Result, 3); // I3442

View file

@ -20,37 +20,6 @@ KMX_BOOL KMX_ProcessEvent::Load(PKMX_BYTE buf, size_t sz) {
return TRUE;
}
const int km::core::kmx::CODE__SIZE[] = {
-1, // undefined 0x00
1, // CODE_ANY 0x01
2, // CODE_INDEX 0x02
0, // CODE_CONTEXT 0x03
0, // CODE_NUL 0x04
1, // CODE_USE 0x05
0, // CODE_RETURN 0x06
0, // CODE_BEEP 0x07
1, // CODE_DEADKEY 0x08
-1, // unused 0x09
2, // CODE_EXTENDED 0x0A
-1, // CODE_EXTENDEDEND 0x0B (unused)
1, // CODE_SWITCH 0x0C
-1, // CODE_KEY 0x0D (never used)
0, // CODE_CLEARCONTEXT 0x0E (deprecated in 19.0)
1, // CODE_CALL 0x0F
-1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL)
1, // CODE_CONTEXTEX 0x11
1, // CODE_NOTANY 0x12
2, // CODE_SETOPT 0x13
3, // CODE_IFOPT 0x14
1, // CODE_SAVEOPT 0x15
1, // CODE_RESETOPT 0x16
3, // CODE_IFSYSTEMSTORE 0x17
2 // CODE_SETSYSTEMSTORE 0x18
};
// Ensure that all CODE_### sizes are defined
static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct");
KMX_BOOL KMX_ProcessEvent::LoadKeyboardFromBlob(
PKMX_BYTE original_buf,
size_t sz,

View file

@ -494,10 +494,6 @@ int KMX_ProcessEvent::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR end
if(m_state.StopOutput) return psrPostMessages;
FoundUse = TRUE;
break;
case CODE_CLEARCONTEXT:
// statement deprecated in 19.0
// no longer supported, no-op
break;
case CODE_INDEX:
p++;
s = &m_keyboard.Keyboard->dpStoreArray[*p - 1];

View file

@ -132,6 +132,7 @@ core_files = files(
'state.cpp',
'jsonpp.cpp',
'../../common/cpp/utfcodec.cpp',
'../../common/cpp/kmx_file.cpp',
)
mock_files = files(

View file

@ -235,7 +235,7 @@ void test_decxstr() {
test_assert(q == (p - 4) );
//runs OK with NEW version of decxstr (with CODE_SWITCH pointer moves 2 ( 3 altogether)
p_start = (PKMX_WCHAR)u"abc" C_CODE_SWITCH(u"\u0001") u"d";
p_start = (PKMX_WCHAR)u"abc" C_CODE_SWITCH(u"\u0001") u"d"; // unused, marked as such in 19.0
p = find_ptr_to_last_character(p_start);
q = decxstr(p, p_start);
test_assert(q == (p - 3) );
@ -625,7 +625,7 @@ void test_decxstr() {
test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001");
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001"); // unused, marked as such in 19.0
q = incxstr(p);
test_assert(q == p + 3 );
@ -860,8 +860,8 @@ void test_decxstr() {
q = incxstr(p);
test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") u"\u1234\u2468";
// --- Test for FFFF + CODE_SWITCH ----------------------------------- ------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") u"\u1234\u2468"; // unused, marked as such in 19.0
q = incxstr(p);
test_assert(q == p + 3 );
@ -1112,7 +1112,7 @@ void test_decxstr() {
test_assert(q == p + 2 );
// --- Test for FFFF + CODE_SWITCH -----------------------------------------------------------------------------------------------------------
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") U_1F609_WINKING_FACE;
p = (PKMX_WCHAR) C_CODE_SWITCH(u"\u0001") U_1F609_WINKING_FACE; // unused, marked as such in 19.0
q = incxstr(p);
test_assert(q == p + 3 );

View file

@ -133,9 +133,9 @@
}
},
"node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
"integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
"dev": true,
"license": "MIT",
"dependencies": {

View file

@ -3,3 +3,13 @@ title: What's new in Keyman Developer 19.0
---
Keyman Developer 19 has the following significant changes:
- Updated to Unicode 17.0 (#14728)
- Updated langtags.json, ISO639-3, language-subtag-registry (#16371)
- Validate a keyboard or lexical model project frmo within Keyman Developer (#16076)
- Support for line numbers in LDML keyboard compiler messages (#13375)
- Convert from .keylayout (macOS keyboards, Ukelele) into .kmn (#12191)
- Compiler improvements - prevent illegal constructs (#16723, #16726, #36710, #16717)
- Various improvements to touch layout editor (#16572, #16520, #16517, #16516, #16509, #16456, #16370, #16347, #16129, #15371, #15338, #15243, #15060, #14228)
- Support output folder and `--continue-on-error` in kmc (#16453)
- About [400 other fixes and changes](https://keyman.com/go/app/whatsnew/developer/19.0)

View file

@ -205,7 +205,7 @@ namespace KmnCompilerMessages {
WARN_TooManyWarnings = SevWarn | 0x080,
WARN_OldVersion = SevWarn | 0x081,
WARN_BitmapNotUsed = SevWarn | 0x082,
// WARN_BitmapNotUsed = SevWarn | 0x082, removed in 19.0.295-alpha
WARN_CustomLanguagesNotSupported = SevWarn | 0x083,
WARN_KeyBadLength = SevWarn | 0x084,
WARN_IndexStoreShort = SevWarn | 0x085,
@ -285,7 +285,11 @@ namespace KmnCompilerMessages {
ERROR_NameMustNotContainParentheses = SevError | 0x0B9,
ERROR_NameMustNotContainSquareBrackets = SevError | 0x0BA,
WARN_DeprecatedStatement = SevWarn | 0x0BB,
// 0x0BB unused
WARN_DeprecatedValueFormat = SevWarn | 0x0BC,
WARN_DeprecatedCompileTarget = SevWarn | 0x0BD,
ERROR_StoreContainsUnsupportedStatement = SevError | 0x0BE,
FATAL_BufferOverflow = SevFatal | 0x0C0
// FATAL_Break = SevFatal | 0x0C1, unused

View file

@ -256,7 +256,7 @@ function make-kmc-install-zip() {
add_zip_files "${KMCOMP_ZIP}" -q -r \
kmconvert.exe \
keyboard_info.schema.json \
xml/layoutbuilder/*.keyman-touch-layout \
xml/layoutbuilder/templates/*.keyman-touch-layout \
projects/ \
server/
)

View file

@ -249,7 +249,7 @@
</Component>
<Component Directory="FontsFolder">
<File Name='keymanweb-osk.ttf' TrueType='yes' Source='..\tike\xml\layoutbuilder\keymanweb-osk.ttf' KeyPath="yes" />
<File Name='keymanweb-osk.ttf' TrueType='yes' Source='..\tike\xml\layoutbuilder\src\assets\fonts\keymanweb-osk.ttf' KeyPath="yes" />
</Component>
</ComponentGroup>

View file

@ -543,43 +543,6 @@ DWORD NextUTF32(PWCHAR pc) {
}
return *pc;
}
/*
void PrintRule(LPKEYBOARD kbd, LPKEY kp) {
LPSTORE sp;
std::vector<DWORD> context, output;
// Always take first char in each referenced store
for (PWCHAR pc = kp->dpContext; pc && *pc; pc = incxstr(pc)) {
if (*pc == UC_SENTINEL) {
switch (*(pc + 1)) {
case CODE_ANY:
sp = &kbd->dpStoreArray[*(pc + 2) - 1];
context.push_back(NextUTF32(sp->dpString));
break;
case CODE_NOTANY:
assert(FALSE); //TODO
case CODE_INDEX:
assert(FALSE); //TODO
case CODE_DEADKEY:
case CODE_EXTENDED: p += 2; while (*p != UC_SENTINEL_EXTENDEDEND) p++; return p + 1;
case CODE_CLEARCONTEXT: return p + 1;
case CODE_CALL: return p + 1;
case CODE_CONTEXTEX: return p + 1;
case CODE_IFOPT: return p + 3;
case CODE_IFSYSTEMSTORE: return p + 3;
case CODE_SETOPT: return p + 2;
case CODE_SETSYSTEMSTORE: return p + 2;
case CODE_RESETOPT: return p + 1;
case CODE_SAVEOPT: return p + 1;
}
}
else {
// Character (either 1 or 2 word)
}
}
}
*/
int groupindex(LPKEYBOARD kbd, LPGROUP gp) {
return gp - kbd->dpGroupArray;

View file

@ -145,6 +145,7 @@ export class AbstractGenerator {
protected static readonly SFile_LicenseMD = 'LICENSE.md';
protected static readonly SFile_ReadmeMD = 'README.md';
protected static readonly SFile_GitIgnore = '.gitignore';
protected static readonly SFile_GitIgnore_NoDot = 'gitignore'; // #16683
protected get callbacks(): CompilerCallbacks { return this._callbacks; }
private _callbacks: CompilerCallbacks;

View file

@ -106,6 +106,13 @@ export class BasicGenerator extends AbstractGenerator {
private transform(sourceFile: string, destFile: string, artifacts: GeneratorArtifacts) {
destFile = this.callbacks.path.join(this.options.outPath, this.options.id, destFile == '' ? sourceFile : destFile);
if(sourceFile == AbstractGenerator.SFile_GitIgnore) {
// #16683: npm does not package .gitignore, so we renamed it in our
// template distribution. The source file will have the name 'gitignore',
// but the output file will be '.gitignore'
sourceFile = AbstractGenerator.SFile_GitIgnore_NoDot;
}
sourceFile = this.callbacks.path.join(this.templateBasePath, this.templatePath, sourceFile);
const sourceData = this.callbacks.loadFile(sourceFile);

View file

@ -650,8 +650,6 @@ export class KmnCompilerMessages {
static WARN_OldVersion = SevWarn | 0x081;
static Warn_OldVersion = () => mc(this.WARN_OldVersion, `The keyboard file is an old version`);
static WARN_BitmapNotUsed = SevWarn | 0x082;
static Warn_BitmapNotUsed = () => mc(this.WARN_BitmapNotUsed, `The 'bitmaps' statement is obsolete and only the first bitmap referred to will be used, you should use 'bitmap'.`);
static WARN_CustomLanguagesNotSupported = SevWarn | 0x083;
static Warn_CustomLanguagesNotSupported = () => mc(this.WARN_CustomLanguagesNotSupported, `Languages over 0x1FF, 0x1F are not supported correctly by Windows. You should use no LANGUAGE line instead.`);
@ -756,7 +754,7 @@ export class KmnCompilerMessages {
static Warn_PlatformNotInTargets = () => mc(this.WARN_PlatformNotInTargets, `The specified platform is not a target platform`);
static WARN_HeaderStatementIsDeprecated = SevWarn | 0x09D;
static Warn_HeaderStatementIsDeprecated = () => mc(this.WARN_HeaderStatementIsDeprecated, `Header statements are deprecated; use instead the equivalent system store`);
static Warn_HeaderStatementIsDeprecated = () => mc(this.WARN_HeaderStatementIsDeprecated, `Header statements are deprecated; use instead the equivalent system store. Header statements are scheduled to be removed in Keyman 20.`);
static WARN_UseNotLastStatementInRule = SevWarn | 0x09E;
static Warn_UseNotLastStatementInRule = () => mc(this.WARN_UseNotLastStatementInRule, `A rule with use() statements in the output should not have other content following the use() statements`);
@ -784,7 +782,7 @@ export class KmnCompilerMessages {
static Warn_MixingLeftAndRightModifiers = () => mc(this.WARN_MixingLeftAndRightModifiers, `Left and right modifiers should not both be used in the same rule`);
static WARN_LanguageHeadersDeprecatedInKeyman10 = SevWarn | 0x0A5;
static Warn_LanguageHeadersDeprecatedInKeyman10 = () => mc(this.WARN_LanguageHeadersDeprecatedInKeyman10, `This language header has been deprecated in Keyman 10. Instead, add language metadata in the package file`);
static Warn_LanguageHeadersDeprecatedInKeyman10 = () => mc(this.WARN_LanguageHeadersDeprecatedInKeyman10, `This language header was deprecated in Keyman 10. Instead, add language metadata in the package file. This header is scheduled to be removed in Keyman 20.`);
static HINT_NonUnicodeFile = SevHint | 0x0A6;
static Hint_NonUnicodeFile = () => mc(this.HINT_NonUnicodeFile, `Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8`);
@ -914,10 +912,22 @@ export class KmnCompilerMessages {
square brackets, control characters, or Unicode non-characters.`
);
static WARN_DeprecatedStatement = SevWarn | 0x0BB;
static Warn_DeprecatedStatement = (o: KmcmpLibMessageParameters) => mc(
this.WARN_DeprecatedStatement,
`The statement '${def(o.p?.[0])}' has been deprecated in Keyman ${def(o.p?.[1])}`,
static WARN_DeprecatedValueFormat = SevWarn | 0x0BC;
static Warn_DeprecatedValueFormat = () => mc(
this.WARN_DeprecatedValueFormat,
`The decimal (d###), octal (###), and hexadecimal (x###) value formats were deprecated in Keyman 19 and are scheduled to be removed in Keyman 20.`,
);
static WARN_DeprecatedCompileTarget = SevWarn | 0x0BD;
static Warn_DeprecatedCompileTarget = (o: KmcmpLibMessageParameters) => mc(
this.WARN_DeprecatedCompileTarget,
`Compile target '${def(o.p?.[0])}' was deprecated in Keyman 19 and will cause the line to always be ignored by kmc in Keyman 20.`,
);
static ERROR_StoreContainsUnsupportedStatement = SevError | 0x0BE;
static Error_StoreContainsUnsupportedStatement = () => mc(
this.ERROR_StoreContainsUnsupportedStatement,
`A store can contain only characters, deadkeys, virtual keys, and 'outs()' statements.`,
);
static FATAL_BufferOverflow = SevFatal | 0x0C0;

View file

@ -53,7 +53,7 @@ KMXCodeNames: string[] = [
'',
'any', 'index', 'context', 'nul', 'use', 'return', 'beep', 'deadkey',
'',
'extended', '', 'switch', 'key', 'clearcontext', 'call',
'extended', '', ''/*switch*/, ''/*key*/, ''/*clearcontext*/, 'call',
'', 'contextex', 'notany',
'set', 'if', 'save', 'reset', // I3429
'if(&system)', 'set(&system)']; // I3430 // I3437

View file

@ -248,7 +248,6 @@ export function incxstr(p: string, x: number): number {
case KMX.KMXFile.CODE_CONTEXTEX: x += 2; break;
case KMX.KMXFile.CODE_NOTANY: x += 2; break;
case KMX.KMXFile.CODE_CLEARCONTEXT: x += 2; break; // deprecated in 19.0
case KMX.KMXFile.CODE_IFOPT: x += 4; break;
case KMX.KMXFile.CODE_IFSYSTEMSTORE: x += 4; break;
case KMX.KMXFile.CODE_SETOPT: x += 3; break;

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-any'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(letter) 'F'
c ERROR_StoreContainsUnsupportedStatement
store(bad-any) any(letter)

View file

@ -0,0 +1,12 @@
store(&NAME) 'error_store_contains_unsupported_statement-baselayout'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement; note baselayout() compiles to CODE_IFSYSTEMSTORE
store(bad-baselayout) baselayout('us')

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-beep'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-beep) beep

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-call'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(callee) 'dll.dll:func'
c ERROR_StoreContainsUnsupportedStatement
store(bad-call) call(callee)

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-context'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-context) context

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-contextex'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-contextex) context(1)

View file

@ -0,0 +1,12 @@
store(&NAME) 'error_store_contains_unsupported_statement-ifopt'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement
store(bad-if) if(opt='F')

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-ifsystemstore'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-ifs) if(&layer='ah') 'eek'

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-index'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(letter) 'F'
c ERROR_StoreContainsUnsupportedStatement
store(bad-index) index(letter, 1)

View file

@ -0,0 +1,12 @@
store(&NAME) 'error_store_contains_unsupported_statement-layer'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement; note layer() compiles to CODE_IFSYSTEMSTORE
store(bad-layer) layer('shift')

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-notany'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(letter) 'F'
c ERROR_StoreContainsUnsupportedStatement
store(bad-notany) notany(letter)

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-nul'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-nul) nul

View file

@ -0,0 +1,12 @@
store(&NAME) 'error_store_contains_unsupported_statement-platform'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement; note platform() compiles to CODE_IFSYSTEMSTORE
store(bad-platform) platform('windows')

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-resetopt'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement
store(bad-reset) reset(opt)

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-return'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-return) return

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-saveopt'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement
store(bad-save) save(opt)

View file

@ -0,0 +1,11 @@
store(&NAME) 'error_store_contains_unsupported_statement-setopt'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(opt) 'opt'
c ERROR_StoreContainsUnsupportedStatement
store(bad-set) set(opt='X')

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-setsystemstore'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-setsystemstore) set(&layer='X')

View file

@ -0,0 +1,9 @@
store(&NAME) 'error_store_contains_unsupported_statement-use'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
c ERROR_StoreContainsUnsupportedStatement
store(bad-use) use(main)

View file

@ -0,0 +1,11 @@
store(&NAME) 'ERROR_VirtualKeyInContext-any'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(keys) [NCAPS SHIFT K_S]
c This should generate warning KM02074 ERROR_VirtualKeyInContext
any(keys) + 'X' > 'x'

View file

@ -0,0 +1,12 @@
store(&NAME) 'ERROR_VirtualKeyInContext-index'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(keys) [NCAPS SHIFT K_S]
store(letter) 'F'
c This should generate warning KM02074 ERROR_VirtualKeyInContext
any(letter) index(keys, 1) + 'X' > 'x'

View file

@ -0,0 +1,11 @@
store(&NAME) 'ERROR_VirtualKeyInContext-any'
store(&VERSION) '9.0'
begin Unicode > use(main)
group(main) using keys
store(keys) [NCAPS SHIFT K_S]
c This should generate warning KM02074 ERROR_VirtualKeyInContext
notany(keys) + 'X' > 'x'

View file

@ -0,0 +1,12 @@
store(&VERSION) '10.0'
store(&NAME) 'WARN_VirtualKeyInOutput'
begin Unicode > use(main)
group(main) using keys
store(keys) [NCAPS SHIFT K_S]
store(outputs) [NCAPS SHIFT K_S]
c This should generate warning KM020AF WARN_VirtualKeyInOutput
+ any(keys) > index(outputs,1)

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_deprecated_compile_target-keymanonly'
begin unicode > use(main)
group(main) using keys
c $keymanonly: deprecated in v19 compiler, will not work in v20+, so always warn regardless of target version
$keymanonly: + ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_deprecated_compile_target-keymanweb'
begin unicode > use(main)
group(main) using keys
c $keymanweb: deprecated in v19 compiler, will not work in v20+, so always warn regardless of target version
$keymanweb: + ' ' > 'hello'

View file

@ -1,9 +0,0 @@
store(&NAME) 'warn_deprecated_statement-clearcontext-17'
store(&VERSION) '17.0'
begin unicode > use(main)
group(main) using keys
c NOTE: using `clearcontext` should not generate a warning in v17 target version compile (unlike v19 target version)
+ 'x' > clearcontext

View file

@ -1,9 +0,0 @@
store(&NAME) 'warn_deprecated_statement-clearcontext-19'
store(&VERSION) '19.0'
begin unicode > use(main)
group(main) using keys
c NOTE: using `clearcontext` SHOULD generate a warning in v19 target version compile (unlike v17 target version)
+ 'x' > clearcontext

View file

@ -1,9 +0,0 @@
store(&NAME) 'warn_deprecated_statement-fix-17'
store(&VERSION) '17.0'
begin unicode > use(main)
group(main) using keys
c NOTE: using `fix` should not generate a warning in v17 target version compile (unlike v19 target version)
+ 'x' > fix

View file

@ -1,9 +0,0 @@
store(&NAME) 'warn_deprecated_statement-fix-19'
store(&VERSION) '19.0'
begin unicode > use(main)
group(main) using keys
c NOTE: using `fix` SHOULD generate a warning in v19 target version compile (unlike v17 target version)
+ 'x' > fix

View file

@ -0,0 +1,9 @@
store(&VERSION) '10.0'
store(&NAME) 'warn_deprecated_value_format-decimal'
begin unicode > use(main)
group(main) using keys
c d### deprecated in v19 compiler, so always warn regardless of target version
+ ' ' > d160

View file

@ -0,0 +1,9 @@
store(&VERSION) '10.0'
store(&NAME) 'warn_deprecated_value_format-hexadecimal'
begin unicode > use(main)
group(main) using keys
c x## deprecated in v19 compiler, so always warn regardless of target version
+ ' ' > xA0

View file

@ -0,0 +1,9 @@
store(&VERSION) '10.0'
store(&NAME) 'warn_deprecated_value_format-octal'
begin unicode > use(main)
group(main) using keys
c ### deprecated in v19 compiler, so always warn regardless of target version
+ ' ' > 240

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-bitmap'
BITMAP test.bmp
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-bitmaps'
BITMAPS test,test
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-caps-always-off'
CAPS ALWAYS OFF
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-caps-on-only'
CAPS ON ONLY
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-copyright'
COPYRIGHT "SIL Global"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-hotkey'
HOTKEY "^+P"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-language'
LANGUAGE x1d, x1
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-languagename'
LANGUAGENAME "test"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-layout'
LAYOUT x11
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-test'
MESSAGE "test"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,8 @@
store(&VERSION) '19.0'
NAME "warn_header_statement_is_deprecated-name"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) '19.0'
store(&NAME) 'warn_header_statement_is_deprecated-shift-frees-caps'
SHIFT FREES CAPS
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,8 @@
VERSION 19.0
store(&NAME) 'warn_header_statement_is_deprecated-version'
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) "19.0"
store(&NAME) 'warn_language_headers_deprecated_in_keyman10-ethnologuecode'
store(&ETHNOLOGUECODE) "eng"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) "19.0"
store(&NAME) 'warn_language_headers_deprecated_in_keyman10-language'
store(&LANGUAGE) "x401"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -0,0 +1,9 @@
store(&VERSION) "19.0"
store(&NAME) 'warn_language_headers_deprecated_in_keyman10-windowslanguages'
store(&WINDOWSLANGUAGES) "x401"
begin unicode > use(main)
group(main) using keys
+ ' ' > 'hello'

View file

@ -62,7 +62,7 @@ describe('KmnCompilerMessages', function () {
const params = getParamNames(m[f]);
if(params.length) {
const v2: KmcKmnCompilerEvent = m[f]({p:['1','2','3','4','5','6','7','8','9']});
assert.notEqual(v.message, v2.message, `Message '${key}' (${hex}) must use KmcmpLibMessageParameters pattern`);
assert.notEqual(v.message, v2.message, `Message '${key}' (${hex}) must use \`(o: KmcmpLibMessageParameters)\` pattern or have no parameters`);
}
}
} else if(code >= KmnCompilerMessageRanges.RANGE_LEXICAL_MODEL_MIN && code <= KmnCompilerMessageRanges.RANGE_LEXICAL_MODEL_MAX) {
@ -157,7 +157,11 @@ describe('KmnCompilerMessages', function () {
await compiler.run(kmnPath, null);
if(Array.isArray(messageId)) {
assert.sameMembers(messageId, callbacks.messages.map(m=>m.code));
assert.sameMembers(
callbacks.messages.map(m=>m.code),
messageId,
`messages did not match, received ${JSON.stringify(callbacks.messages,null,2)}, expected: ${JSON.stringify(messageId)}`
);
} else if(messageId) {
assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
assert.lengthOf(callbacks.messages, 1, `messages should have 1 entry, instead has: `+JSON.stringify(callbacks.messages,null,2));
@ -194,6 +198,9 @@ describe('KmnCompilerMessages', function () {
it('should generate ERROR_VirtualKeyInContext if a virtual key is found in the context part of a rule', async function() {
await testForMessage(this, ['invalid-keyboards', 'error_virtual_key_in_context.kmn'], KmnCompilerMessages.ERROR_VirtualKeyInContext);
await testForMessage(this, ['invalid-keyboards', 'error_virtual_key_in_context-any.kmn'], KmnCompilerMessages.ERROR_VirtualKeyInContext);
await testForMessage(this, ['invalid-keyboards', 'error_virtual_key_in_context-index.kmn'], KmnCompilerMessages.ERROR_VirtualKeyInContext);
await testForMessage(this, ['invalid-keyboards', 'error_virtual_key_in_context-notany.kmn'], KmnCompilerMessages.ERROR_VirtualKeyInContext);
});
// WARN_TouchLayoutUnidentifiedKey
@ -213,6 +220,7 @@ describe('KmnCompilerMessages', function () {
it('should generate WARN_VirtualKeyInOutput if a virtual key is found in the output part of a rule', async function() {
await testForMessage(this, ['invalid-keyboards', 'warn_virtual_key_in_output.kmn'], KmnCompilerMessages.WARN_VirtualKeyInOutput);
await testForMessage(this, ['invalid-keyboards', 'warn_virtual_key_in_output-index.kmn'], KmnCompilerMessages.WARN_VirtualKeyInOutput);
});
// ERROR_OutsTooLong
@ -347,13 +355,67 @@ describe('KmnCompilerMessages', function () {
await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_square_brackets-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustNotContainSquareBrackets, KmnCompilerMessages.ERROR_InvalidDeadkey]);
});
// WARN_DeprecatedStatement
//
// Deprecations
//
it('should generate WARN_DeprecatedStatement if the file has `clearcontext` or `fix` statements and is 19.0', async function() {
await testForMessage(this, ['keyboards', 'warn_deprecated_statement-clearcontext-19.kmn'], KmnCompilerMessages.WARN_DeprecatedStatement);
await testForMessage(this, ['keyboards', 'warn_deprecated_statement-fix-19.kmn'], KmnCompilerMessages.WARN_DeprecatedStatement);
await testForMessage(this, ['keyboards', 'warn_deprecated_statement-clearcontext-17.kmn']);
await testForMessage(this, ['keyboards', 'warn_deprecated_statement-fix-17.kmn']);
// WARN_DeprecatedValueFormat
it('should generate WARN_DeprecatedValueFormat if the file has decimal, octal, or hexadecimal value formats', async function() {
await testForMessage(this, ['keyboards', 'warn_deprecated_value_format-decimal.kmn'], KmnCompilerMessages.WARN_DeprecatedValueFormat);
await testForMessage(this, ['keyboards', 'warn_deprecated_value_format-octal.kmn'], KmnCompilerMessages.WARN_DeprecatedValueFormat);
await testForMessage(this, ['keyboards', 'warn_deprecated_value_format-hexadecimal.kmn'], KmnCompilerMessages.WARN_DeprecatedValueFormat);
});
// WARN_DeprecatedCompileTarget
it('should generate WARN_DeprecatedCompileTarget if the file contains a deprecated compile target', async function() {
await testForMessage(this, ['keyboards', 'warn_deprecated_compile_target-keymanonly.kmn'], KmnCompilerMessages.WARN_DeprecatedCompileTarget);
await testForMessage(this, ['keyboards', 'warn_deprecated_compile_target-keymanweb.kmn'], KmnCompilerMessages.WARN_DeprecatedCompileTarget);
});
// WARN_HeaderStatementIsDeprecated
['bitmap', 'bitmaps', 'caps-always-off', 'caps-on-only', 'copyright', 'hotkey', 'message', 'name', 'shift-frees-caps', 'version'].forEach(statement =>
it(`should generate WARN_HeaderStatementIsDeprecated if the file contains deprecated header statement "${statement.replaceAll('-',' ').toUpperCase()}"`, async function() {
await testForMessage(this, ['keyboards', `warn_header_statement_is_deprecated-${statement}.kmn`], KmnCompilerMessages.WARN_HeaderStatementIsDeprecated);
})
);
['language', 'languagename'].forEach(statement =>
// Two messages generated for these legacy header statements
it(`should generate WARN_HeaderStatementIsDeprecated and WARN_LanguageHeadersDeprecatedInKeyman10 if the file contains deprecated header statement "${statement.replaceAll('-',' ').toUpperCase()}"`, async function() {
await testForMessage(this, ['keyboards', `warn_header_statement_is_deprecated-${statement}.kmn`], [
KmnCompilerMessages.WARN_HeaderStatementIsDeprecated, KmnCompilerMessages.WARN_LanguageHeadersDeprecatedInKeyman10
]);
})
);
['layout'].forEach(statement =>
// LAYOUT statement requires LANGUAGE as well, so we'll get two messages here
it(`should generate WARN_HeaderStatementIsDeprecated and ERROR_LayoutButNoLanguage if the file contains deprecated header statement "${statement.replaceAll('-',' ').toUpperCase()}"`, async function() {
await testForMessage(this, ['keyboards', `warn_header_statement_is_deprecated-${statement}.kmn`], [
KmnCompilerMessages.WARN_HeaderStatementIsDeprecated, KmnCompilerMessages.ERROR_LayoutButNoLanguage
]);
})
);
// WARN_LanguageHeadersDeprecatedInKeyman10
['ethnologuecode', 'language', 'windowslanguages'].forEach(storeName =>
it(`should generate WARN_LanguageHeadersDeprecatedInKeyman10 if the file contains deprecated system store "&${storeName}"`, async function() {
await testForMessage(this, ['keyboards', `warn_language_headers_deprecated_in_keyman10-${storeName}.kmn`], KmnCompilerMessages.WARN_LanguageHeadersDeprecatedInKeyman10);
})
);
// ERROR_StoreContainsUnsupportedStatement
['any', 'index', 'context', 'nul', 'use', 'return', 'beep',
'call', 'contextex', 'notany', 'setopt',
'ifopt', 'baselayout', 'layer', 'platform',
'saveopt', 'resetopt', 'ifsystemstore', 'setsystemstore'].forEach(statement =>
it(`should generate ERROR_StoreContainsUnsupportedStatement if a store contains statement "${statement}"`, async function() {
await testForMessage(this, ['invalid-keyboards', `error_store_contains_unsupported_statement-${statement}.kmn`], KmnCompilerMessages.ERROR_StoreContainsUnsupportedStatement);
})
);
});

View file

@ -1,3 +1,8 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*
* Assign collected metadata from keyboards to package metadata fields
*/
import { KeyboardMetadataCollection } from './package-metadata-collector.js';
export class PackageMetadataUpdater {
@ -5,7 +10,7 @@ export class PackageMetadataUpdater {
public updatePackage(metadata: KeyboardMetadataCollection) {
for(const id of Object.keys(metadata)) {
const keyboard = metadata[id];
keyboard.keyboard.name = keyboard.data.keyboardName;
keyboard.keyboard.name = keyboard.data.keyboardName ?? id;
keyboard.keyboard.rtl = keyboard.data.isRtl ? true : undefined;
keyboard.keyboard.version = keyboard.data.keyboardVersion ?? '1.0';
}

View file

@ -0,0 +1,6 @@
c no &name store for testing #16681
store(&keyboardversion) '1.0'
begin unicode > use(main)
group(main) using keys

Binary file not shown.

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Options>
<FollowKeyboardVersion/>
</Options>
<StartMenu>
<Folder></Folder>
<Items/>
</StartMenu>
<Info>
<Name URL="">Khmer Angkor</Name>
<Copyright URL="">© 2015-2022 SIL International</Copyright>
<Author URL="mailto:makara_sok@sil.org">Makara Sok</Author>
<Version URL=""></Version>
<WebSite URL="https://keyman.com/keyboards/khmer_angkor">https://keyman.com/keyboards/khmer_angkor</WebSite>
</Info>
<Files>
<File>
<Name>kmx_no_name.kmx</Name>
<Description>Keyboard kmx_no_name</Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
</Files>
<Keyboards>
<Keyboard>
<ID>kmx_no_name</ID>
<Version>1.3</Version>
<Languages>
<Language ID="km">Central Khmer (Khmer, Cambodia)</Language>
</Languages>
</Keyboard>
</Keyboards>
<Strings/>
</Package>

View file

@ -274,6 +274,12 @@ describe('KmpCompiler', function () {
assert.equal(kmpJson.keyboards[0].name, 'version 4'); // picks up example.kmx's name
});
it(`should load a package with a .kmx file without a &name store`, function () {
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'kmx_no_name.kps'));
assert.isNotNull(kmpJson);
assert.equal(kmpJson.keyboards[0].name, 'kmx_no_name'); // picks up kmx_no_name's id
});
it(`should load a package with missing keyboard version metadata`, function () {
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_version.kps'));
assert.equal(kmpJson.keyboards[0].version, '4.0'); // picks up example.kmx's version

View file

@ -7,7 +7,6 @@ import 'mocha';
import { unitTestEndpoints } from '../src/util/extendedCompilerOptions.js';
import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js';
import { CompilerError, CompilerMessageOverride, CompilerErrorSeverity } from '@keymanapp/developer-utils';
import { KmnCompilerMessages } from '@keymanapp/kmc-kmn';
interface MessageTest {input: string, result: CompilerMessageOverride};
interface InvalidMessageTest {input: string, code: number};
@ -25,7 +24,6 @@ describe('commandOptionsMessageToCompilerOptionsMessage', function () {
// Test hint and warn messages
{input: 'KM05009:E', result: {code: InfrastructureMessages.HINT_FilenameHasDifferingCase, level: CompilerErrorSeverity.Error } },
{input: 'KM02082:E', result: {code: KmnCompilerMessages.WARN_BitmapNotUsed, level: CompilerErrorSeverity.Error } },
// Test different allowable patterns
{input: '5002', result: {code: InfrastructureMessages.INFO_BuildingFile, level: 'disable' } },

View file

@ -85,7 +85,7 @@ bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk)
while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == STATUS_Success)
{
p = str;
switch (LineTokenType(&p))
switch (LineTokenType(fk, &p, FALSE))
{
case T_VERSION:
*(p + 4) = 0;

View file

@ -139,6 +139,7 @@ KMX_DWORD GetRHS(PFILE_KEYBOARD fk, PKMX_WCHAR p, PKMX_WCHAR buf, int bufsize, i
PKMX_WCHAR GetDelimitedString(PKMX_WCHAR *p, KMX_WCHAR const * Delimiters, KMX_WORD Flags);
KMX_DWORD GetXString(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int isVKey, int isUnicode);
KMX_BOOL GetCompileTargetsFromTargetsStore(KMX_WCHAR *store, int &targets);
KMX_BOOL DoesStoreContainInvalidStatement(PKMX_WCHAR store);
int GetGroupNum(PFILE_KEYBOARD fk, PKMX_WCHAR p);
@ -361,12 +362,16 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) {
p = str;
pp = str;
switch (LineTokenType(&p))
switch (LineTokenType(fk, &p, TRUE))
{
case T_BLANK:
case T_COMMENT:
break; // Ignore the line
case T_VERSION:
kmcmp::WarnDeprecatedHeader();
break; // The line has already been processed
case T_STORE:
break; // The line has already been processed
@ -523,7 +528,7 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) {
{
kmcmp::WarnDeprecatedHeader(); // I4866
KMX_WCHAR *tokcontext = NULL;
ReportCompilerMessage(KmnCompilerMessages::WARN_BitmapNotUsed);
// ReportCompilerMessage(KmnCompilerMessages::WARN_BitmapNotUsed); : redundant, have WarnDeprecatedHeader
if ((q = u16tok(p, p_sep, &tokcontext)) == NULL) {
ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidBitmapLine); // I3481
@ -839,6 +844,11 @@ KMX_BOOL ProcessStoreLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) {
kmcmp::CodeConstants->reindex(); // has to be done after every character add due to possible use in another store. // I4982
}
if(DoesStoreContainInvalidStatement(sp->dpString)) {
ReportCompilerMessage(KmnCompilerMessages::ERROR_StoreContainsUnsupportedStatement);
return FALSE;
}
fk->cxStoreArray++; // increment now, because GetXString refers to stores
if (i > 0) {
@ -1155,7 +1165,7 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s
else if (u16ncmp(p, u"15.0", 4) == 0) fk->version = VERSION_150; // Adds support for U_xxxx_yyyy #2858
else if (u16ncmp(p, u"16.0", 4) == 0) fk->version = VERSION_160; // KMXPlus
else if (u16ncmp(p, u"17.0", 4) == 0) fk->version = VERSION_170; // Flicks and gestures
else if (u16ncmp(p, u"19.0", 4) == 0) fk->version = VERSION_190; // Deprecations - fix, clearcontext
else if (u16ncmp(p, u"19.0", 4) == 0) fk->version = VERSION_190; // Deprecations - compile targets $keymanonly, $keymanweb
else {
ReportCompilerMessage(KmnCompilerMessages::ERROR_InvalidVersion);
@ -1522,12 +1532,33 @@ KMX_DWORD CheckStatementOffsets(PFILE_KEYBOARD fk, PFILE_GROUP gp, PKMX_WCHAR co
return STATUS_Success;
}
/**
* Stores can contain only characters, deadkeys, and virtual keys (and `outs`).
* Note that `outs()` is expanded during read so there is no `CODE_OUTS`.
*
* @param store
* @return KMX_BOOL
*/
KMX_BOOL DoesStoreContainInvalidStatement(PKMX_WCHAR store) {
for (PKMX_WCHAR p = store; *p; p = incxstr(p)) {
if (*p == UC_SENTINEL) {
auto code = *(p + 1);
if(code == CODE_DEADKEY || code == CODE_EXTENDED) {
continue;
}
// No other codes are permitted; note, outs() does not have a code as it is expanded at compile time
return TRUE;
}
}
return FALSE;
}
/**
* Checks that the order of statements in the context matches the specification.
* Rule structure: [context] ['+' key] '>' output
* Context structure: [nul] [if()|baselayout()|platform()]+ [char|any|context()|deadkey()|dk()|index()|notany()|outs()]
* Test that nul is first, then if(), baselayout(), platform() statements are before any other content.
* Also verifies that virtual keys are not found in the context.
*/
void CheckContextStatementPositions(PKMX_WCHAR context) {
KMX_BOOL hadContextChar = FALSE;
@ -1545,9 +1576,6 @@ void CheckContextStatementPositions(PKMX_WCHAR context) {
ReportCompilerMessage(KmnCompilerMessages::WARN_IfShouldBeAtStartOfContext);
}
break;
case CODE_EXTENDED:
ReportCompilerMessage(KmnCompilerMessages::ERROR_VirtualKeyInContext);
break;
default:
hadContextChar = TRUE;
}
@ -1575,18 +1603,50 @@ KMX_DWORD CheckUseStatementsInOutput(PKMX_WCHAR output) {
return STATUS_Success;
}
KMX_BOOL DoesStringContainVirtualKeys(PFILE_KEYBOARD fk, PKMX_WCHAR p) {
for (; *p; p = incxstr(p)) {
if (*p == UC_SENTINEL) {
auto code = *(p+1);
if(code == CODE_EXTENDED) {
return TRUE;
} else if(code == CODE_INDEX) {
PFILE_STORE s;
s = &fk->dpStoreArray[*(p + 2) - 1];
if(DoesStringContainVirtualKeys(fk, s->dpString)) {
return TRUE;
}
} else if(code == CODE_ANY || code == CODE_NOTANY) {
PFILE_STORE s;
s = &fk->dpStoreArray[*(p + 2) - 1];
if(DoesStringContainVirtualKeys(fk, s->dpString)) {
return TRUE;
}
}
}
}
return FALSE;
}
/**
* Warn if output has virtual keys in it, which is not supported by Core at all,
* but was unofficially supported, but never worked properly, in Keyman for
* Windows for many years
*/
KMX_DWORD CheckVirtualKeysInOutput(PKMX_WCHAR output) {
PKMX_WCHAR p;
for (p = output; *p; p = incxstr(p)) {
if (*p == UC_SENTINEL && *(p + 1) == CODE_EXTENDED) {
ReportCompilerMessage(KmnCompilerMessages::WARN_VirtualKeyInOutput);
break;
}
KMX_DWORD CheckVirtualKeysInOutput(PFILE_KEYBOARD fk, PKMX_WCHAR output) {
if(DoesStringContainVirtualKeys(fk, output)) {
ReportCompilerMessage(KmnCompilerMessages::WARN_VirtualKeyInOutput);
return STATUS_Success;
}
return STATUS_Success;
}
/**
* Error if context has virtual keys in it -- context can never contain keys,
* only characters and deadkeys.
*/
KMX_DWORD CheckVirtualKeysInContext(PFILE_KEYBOARD fk, PKMX_WCHAR context) {
if(DoesStringContainVirtualKeys(fk, context)) {
return KmnCompilerMessages::ERROR_VirtualKeyInContext;
}
return STATUS_Success;
}
@ -1604,9 +1664,9 @@ const KMX_BOOL CODE__IS_TEXTUAL[] = {
-1, // unused 0x09
TRUE, // CODE_EXTENDED 0x0A
-1, // CODE_EXTENDEDEND 0x0B (unused)
FALSE, // CODE_SWITCH 0x0C
-1, // CODE_KEY 0x0D (never used)
FALSE, // CODE_CLEARCONTEXT 0x0E (deprecated in 19.0)
FALSE, // CODE_SWITCH 0x0C (unused, removed in 19.0)
-1, // CODE_KEY 0x0D (unused, removed in 19.0)
FALSE, // CODE_CLEARCONTEXT 0x0E (unused, removed in 19.0)
FALSE, // CODE_CALL 0x0F // may trigger text effects but indirectly
-1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL)
TRUE, // CODE_CONTEXTEX 0x11
@ -1781,6 +1841,9 @@ KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnico
CheckContextStatementPositions(pklIn);
// Error if virtual keys are used in the context
if ((msg = CheckVirtualKeysInContext(fk, pklIn)) != STATUS_Success) return msg;
// Test index and context offsets in context
if ((msg = CheckStatementOffsets(fk, gp, pklIn, pklOut, pklKey)) != STATUS_Success) return msg;
@ -1790,7 +1853,7 @@ KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnico
}
// Warn if virtual keys are used in the output, as they are unsupported by Core
if ((msg = CheckVirtualKeysInOutput(pklOut)) != STATUS_Success) {
if ((msg = CheckVirtualKeysInOutput(fk, pklOut)) != STATUS_Success) {
return msg;
}
@ -2074,7 +2137,7 @@ LinePrefixType GetLinePrefixType(PKMX_WCHAR *p)
return lptOther;
}
int LineTokenType(PKMX_WCHAR *str)
int LineTokenType(PFILE_KEYBOARD fk, PKMX_WCHAR *str, KMX_BOOL checkDeprecations)
{
int i;
size_t l;
@ -2084,6 +2147,14 @@ int LineTokenType(PKMX_WCHAR *str)
if (lpt == lptOther) return T_BLANK;
/* Test KeymanWeb, Keyman and KeymanOnly prefixes */
if(checkDeprecations && lpt == lptKeymanWebOnly) {
kmcmp::WarnDeprecatedCompileTarget(fk, u"$keymanweb:");
}
if(checkDeprecations && lpt == lptKeymanOnly) {
kmcmp::WarnDeprecatedCompileTarget(fk, u"$keymanonly:");
}
if (kmcmp::CompileTarget == CKF_KEYMAN && lpt == lptKeymanWebOnly) return T_BLANK;
if (kmcmp::CompileTarget == CKF_KEYMANWEB && lpt == lptKeymanOnly) return T_BLANK;
@ -2165,13 +2236,6 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
ErrChr = (int)(p - str) + offset + 1;
/*
char *tokenTypes[] = {
"clearcontext", "deadkey", "context", "return", "switch",
"index", "outs", "beep", "nul", "use", "any", "fix", "dk", "k_", "x", "d", "c",
"[", "]" };
*/
switch (towupper(*p))
{
case 'X':
@ -2182,7 +2246,7 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
case 'B': type = 4; break; // beep, baselayout (synonym for if(&baselayout)) // I3430
case 'I': type = 5; break; // index(s,n), if
case 'O': type = 6; break; // outs(s)
case 'C': type = 7; break; // context, comments, clearcontext, call(s)
case 'C': type = 7; break; // context, comments, call(s)
case 'N': type = 8; break; // nul, notany
case 'U': type = 9; break; // use(g)
case 'R': type = 10; break; // return, reset
@ -2190,7 +2254,7 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
//case ']': type = 12; break; // end of vkey section
//case 'K': type = 13; break; // virtual key name or "key"
case 'S': type = 14; break; // switch, set, save
case 'F': type = 15; break; // fix (synonym for clearcontext)
// 15 was 'F', for undocumented, unused statement 'fix'
case '$': type = 16; break; // named code constants
case 'P': type = 17; break; // platform (synonym for if(&platform)) // I3430
case 'L': type = 18; break; // layer (synonym for set(&layer)) // I3437
@ -2234,6 +2298,7 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
tstr[mx++] = n1;
if (n2 >= 0) tstr[mx++] = n2;
tstr[mx] = 0;
kmcmp::WarnDeprecatedValueFormat();
}
continue;
@ -2398,17 +2463,6 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
tstr[mx] = 0;
}
}
else if (u16nicmp(p, u"clearcontext", 12) == 0)
{
// deprecated in 19.0
if(fk->version >= VERSION_190) {
ReportCompilerMessage(KmnCompilerMessages::WARN_DeprecatedStatement, {"clearcontext", "19.0"}); // I3438
}
p += 12;
tstr[mx++] = UC_SENTINEL;
tstr[mx++] = CODE_CLEARCONTEXT;
tstr[mx] = 0;
}
else if (u16nicmp(p, u"call", 4) == 0)
{
if(!VerifyKeyboardVersion(fk, VERSION_501)) {
@ -2714,30 +2768,8 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX
}
else
{
if (u16nicmp(p, u"switch", 6) != 0) return KmnCompilerMessages::ERROR_InvalidToken;
p += 6;
q = GetDelimitedString(&p, u"()", GDS_CUTLEAD | GDS_CUTFOLL);
if (!q || !*q) return KmnCompilerMessages::ERROR_InvalidSwitch;
tstr[mx++] = UC_SENTINEL;
tstr[mx++] = CODE_SWITCH;
tstr[mx++] = atoiW(q);
tstr[mx] = 0;
}
continue;
case 15:
if (u16nicmp(p, u"fix", 3) == 0)
{
if(fk->version >= VERSION_190) {
// deprecated in 19.0
ReportCompilerMessage(KmnCompilerMessages::WARN_DeprecatedStatement, {"fix", "19.0"}); // I3438
}
p += 3;
tstr[mx++] = UC_SENTINEL;
tstr[mx++] = CODE_CLEARCONTEXT;
tstr[mx] = 0;
}
else
return KmnCompilerMessages::ERROR_InvalidToken;
}
continue;
case 16:
if(!VerifyKeyboardVersion(fk, VERSION_60)) {

View file

@ -8,10 +8,29 @@
void kmcmp::WarnDeprecatedHeader() { // I4866
if (AWarnDeprecatedCode_GLOBAL_LIB) {
// We warn on this for any keyboard version; keyboard authors should
// be moving to system stores
ReportCompilerMessage(KmnCompilerMessages::WARN_HeaderStatementIsDeprecated);
}
}
void kmcmp::WarnDeprecatedValueFormat() {
if (AWarnDeprecatedCode_GLOBAL_LIB) {
// We warn on this for any keyboard version; keyboard authors should
// be moving to U+xxxx format
ReportCompilerMessage(KmnCompilerMessages::WARN_DeprecatedValueFormat);
}
}
void kmcmp::WarnDeprecatedCompileTarget(PFILE_KEYBOARD fk, const KMX_WCHAR *compileTarget) {
if (AWarnDeprecatedCode_GLOBAL_LIB && fk->version >= VERSION_190) {
// We will warn on this for any keyboard version >= 19
ReportCompilerMessage(KmnCompilerMessages::WARN_DeprecatedCompileTarget, {
/* compileTarget */ string_from_u16string(compileTarget)
});
}
}
/* Flag presence of deprecated features */
void kmcmp::CheckForDeprecatedFeatures(PFILE_KEYBOARD fk) {
/*

View file

@ -5,5 +5,8 @@
namespace kmcmp{
void WarnDeprecatedHeader();
void WarnDeprecatedValueFormat();
void WarnDeprecatedCompileTarget(PFILE_KEYBOARD fk, const KMX_WCHAR *compileTarget);
void WarnDeprecatedFix(PFILE_KEYBOARD fk);
void CheckForDeprecatedFeatures(PFILE_KEYBOARD fk);
}

Some files were not shown because too many files have changed in this diff Show more