Merge remote-tracking branch 'upstream/master' into feat/developer/10622-xml-parse-line-numbers-epic-ldml

This commit is contained in:
Steven R. Loomis 2025-03-31 16:16:10 -05:00
commit 29f387bebe
118 changed files with 2004 additions and 836 deletions

View file

@ -1,4 +1,4 @@
# Debian Packaging GitHub Action
# Ubuntu Packaging GitHub Action
You can manually trigger a deb-packaging action on GitHub, e.g. to test changes:

View file

@ -1,3 +1,14 @@
#
# This workflow builds the source package for Keyman for Linux, and then builds
# the binary packages for the released versions of Ubuntu. It also signs the
# packages. Released Keyman versions get uploaded to the LLSO repository.
# It is triggered by a repository_dispatch event with the
# types `deb-release-packaging:*` and `deb-pr-packaging:*`.
#
# We use the filename `deb-packaging.yml` even though we only do Ubuntu
# packaging here, because we're packaging the .deb format, and because it's shorter.
#
name: "Ubuntu packaging"
run-name: "Ubuntu packaging - ${{ github.event.client_payload.branch }} (branch ${{ github.event.client_payload.baseBranch }}), by @${{ github.event.client_payload.user }}, testbuild: ${{ github.event.client_payload.isTestBuild }}"
on:
@ -16,7 +27,7 @@ on:
env:
COLOR_GREEN: "\e[32m"
GH_TOKEN: ${{ github.token }}
STATUS_CONTEXT: 'Debian Packaging'
STATUS_CONTEXT: 'Ubuntu Packaging'
DEBFULLNAME: 'Keyman GHA packager'
DEBEMAIL: 'support@keyman.com'
@ -45,7 +56,7 @@ jobs:
/repos/$GITHUB_REPOSITORY/statuses/${{ github.event.client_payload.buildSha }} \
-f state='pending' \
-f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
-f description='Debian packaging started' \
-f description='Ubuntu packaging started' \
-f context="$STATUS_CONTEXT"
- name: Install devscripts

View file

@ -1,5 +1,9 @@
# Keyman Version History
## 19.0.19 alpha 2025-03-31
* chore: merge beta to master B18S4 (#13606)
## 19.0.18 alpha 2025-03-25
* chore(developer): improve ldml code coverage (#13516)
@ -80,6 +84,59 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
## 18.0.222 beta 2025-03-28
* fix(android): Handle banner overrides independently when device is locked and unlocked (#13546)
## 18.0.221 beta 2025-03-27
* fix(windows): configuration update tab text changes (#13555)
* chore(common): update language-subtag-registry to 2025-03-10 release (#13577)
* docs(mac): update help documentation due to changes in Settings in macOS 13.0 Ventura (#13501)
* fix(linux): improve emitting keystrokes (#13558)
* fix(linux): improve key mapping to allow Core to reset context (#13372)
## 18.0.220 beta 2025-03-24
* fix(windows,mac): Update Spanish configuration string and mac strings (#13564)
* chore(developer): use 'statement' instead of 'command' for messages about kmn statements (#13562)
## 18.0.219 beta 2025-03-21
* fix(windows): add exception handling for loading xml file in XSL Rendering (#13530)
* refactor(linux): rename `initialize_queue` method (#13554)
## 18.0.218 beta 2025-03-19
* fix(developer): add validation of `nul` usage in output part of rule (#13532)
* fix(web): model unload and load should use consistent, canEnable check (#13534)
* feat(web): adds gesture 'traces' for warning/error logging (#13360)
* fix(linux): fix building in Docker with different Ubuntu/Debian version (#13537)
* chore(linux): replace deprecated setup.py (#13524)
* chore(linux): rename "Debian packaging" to "Ubuntu packaging" (#13523)
## 18.0.217 beta 2025-03-18
* fix(windows): cherry pick hard code key cap (#13526)
* fix(android,windows): Update Russian language strings to use Cyrillic script (#13527)
* docs(web): add internal documentation for context state and keystroke processing (#13520)
* docs(common): add link to schemas (#13521)
* chore(linux): update URL for lcov package (#13535)
* chore(linux): allow to build and use Debian docker image (#13536)
## 18.0.216 beta 2025-03-17
* change(android): re-order host page script load so Sentry can report script load failures (#13333)
* chore(web): remove dead osk code (#13358)
* fix(web): cancel held backspace on new key input (#13359)
* fix(developer): handle non bmp numeric entities in xml reader (#13365)
## 18.0.215 beta 2025-03-14
* docs(common): Update Sentry links to keyman.sentry.io (#13491)
* chore(android,windows): Update Crowdin for Spanish and Spanish (Latin America) (#13499)
* chore(common): update crowdin strings for Italian (#13500)
## 18.0.214 beta 2025-03-13
* fix(mac): change text in privacy alert for macOS 13.0 and later (#13480)

View file

@ -1 +1 @@
19.0.19
19.0.20

View file

@ -159,9 +159,11 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
Context appContext = getApplicationContext();
// Temporarily disable predictions on certain fields (e.g. hidden password field or numeric)
inputType = attribute.inputType;
KMManager.setMayPredictOverride(inputType);
if (KMManager.getMayPredictOverride()) {
KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM);
if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) {
KMManager.setBannerOptions(false);
// Set the system keyboard HTML banner
BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM);
} else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){
// Check if predictions needs to be re-enabled per Settings preference
Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext);

View file

@ -244,6 +244,12 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
@Override
protected void onResume() {
super.onResume();
if (textView != null) {
// Reset inAppPredictionsSuspendedForSensitiveInput flag
KMManager.setPredictionsSuspended(textView.getInputType(), KeyboardType.KEYBOARD_TYPE_INAPP);
}
KMManager.onResume();
KMManager.hideSystemKeyboard();

View file

@ -126,11 +126,11 @@
<!-- Context: Adjust Keyboard Height menu -->
<string name="reset_to_defaults" comment="Button to reset to default heights">Restaurar valores predeterminados</string>
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Delay Time: %1$.1f seconds</string>
<string name="longpress_delay_time" comment="Current longpress delay time (seconds)">Tiempo de retraso: %1$.1f segundos</string>
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="ic_delay_time_up" comment="Make longpress delay longer">Delay time longer</string>
<string name="ic_delay_time_up" comment="Make longpress delay longer">Tiempo de retraso más largo</string>
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Delay time shorter</string>
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Tiempo de retraso más corto</string>
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Longpress delay time slider</string>
<!-- Context: Keyman Web Browser -->

View file

@ -9,9 +9,9 @@
<title>Keyman</title>
<!-- DOMRect only exists as of Chrome 61... thus coming later than ES6. -->
<script src="other-polyfills.js"></script>
<script src="keymanweb-webview.js"></script>
<script src="sentry.min.js"></script>
<script src="keyman-sentry.js"></script>
<script src="keymanweb-webview.js"></script>
<script src="android-host.js"></script>
<style type="text/css">
body {background-color:#000000;}

View file

@ -66,7 +66,7 @@ public class DisplayLanguages {
new DisplayLanguageType("el", "Polytonic Greek"),
new DisplayLanguageType("pt-PT", "Português de Portugal"),
new DisplayLanguageType("ff-ZA", "Pulaar-Fulfulde"), // or Fulah
new DisplayLanguageType("ru-RU", "Pyccĸий (Russian)"),
new DisplayLanguageType("ru-RU", "Русский (Russian)"),
new DisplayLanguageType("shu-latn", "Shuwa (Latin)"),
new DisplayLanguageType("sv-SE", "svenska (Swedish)"),
new DisplayLanguageType("vi-VN", "Tiếng Việt (Vietnamese)"),

View file

@ -165,12 +165,12 @@ public final class KMKeyboardWebViewClient extends WebViewClient {
// appContext instead of context?
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean modelPredictionPref = false;
if (!KMManager.getMayPredictOverride() && KMManager.currentLexicalModel != null) {
if (!KMManager.getPredictionsSuspended(keyboardType) && KMManager.currentLexicalModel != null) {
modelPredictionPref = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(
KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), KMManager.KMDefault_Suggestion)
!= SuggestionType.SUGGESTIONS_DISABLED.toInt();
}
KMManager.setBannerOptions(modelPredictionPref);
KMManager.setBannerOptions(modelPredictionPref, keyboardType);
RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams();
kmKeyboard.setLayoutParams(params);
} else if (url.indexOf("suggestPopup") >= 0) {

View file

@ -257,10 +257,13 @@ public final class KMManager {
// haptic feedback disabled for hardware keystrokes
private static boolean mayHaveHapticFeedback = false;
// Special override for when keyboard is entering a password text field.
// When mayPredictOverride is true, the option {'mayPredict' = false} is set in the lm-layer
// Special flags to temporarily disable predictions when editing a password field.
// These are maintained independently for inapp keyboard and system keyboard.
// When true, the suggestion banner passes the option {'mayPredict' = false} to KeymanWeb in the lm-layer
// regardless what the Settings preference is.
private static boolean mayPredictOverride = false;
// API added Keyman 18.0
private static boolean inAppPredictionsSuspendedForSensitiveInput = false;
private static boolean systemPredictionsSuspendedForSensitiveInput = false;
// Determine how system keyboard handles ENTER key
public static EnterModeType enterMode = EnterModeType.DEFAULT;
@ -1409,25 +1412,49 @@ public final class KMManager {
}
/**
* Sets mayPredictOverride true if the InputType field is a hidden password text field
* (either TYPE_TEXT_VARIATION_PASSWORD or TYPE_TEXT_VARIATION_WEB_PASSWORD
* but not TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)
* Determine the predictions should be suspended for a given InputType field: hidden password text field
* (but not TYPE_TEXT_VARIATION_VISIBLE_PASSWORD).
* Also true for numeric text fields
* @param inputType android.text.InputType
* @return boolean
*/
public static void setMayPredictOverride(int inputType) {
mayPredictOverride =
((inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) ||
(inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) ||
isNumericField(inputType));
private static boolean shouldSuspendPredictions(int inputType) {
return ((inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) ||
(inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) ||
isNumericField(inputType));
}
/**
* Get the value of mayPredictOverride
* @return boolean
* Sets PredictionsSuspended true if the given InputType field for the keyboard is a hidden password text field
* (either TYPE_TEXT_VARIATION_PASSWORD or TYPE_TEXT_VARIATION_WEB_PASSWORD
* but not TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)
* Also true for numeric text fields.
* This is to temporarily disable suggestions in these text fields.
* API added Keyman 18.0
* @param inputType android.text.InputType
* @param {KeyboardType} keyboard
*/
public static boolean getMayPredictOverride() {
return mayPredictOverride;
public static void setPredictionsSuspended(int inputType, KeyboardType keyboard) {
if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) {
inAppPredictionsSuspendedForSensitiveInput = shouldSuspendPredictions(inputType);
} else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
systemPredictionsSuspendedForSensitiveInput = shouldSuspendPredictions(inputType);
}
}
/**
* Get the value of PredictionsSuspendedForSensitiveInput based on KeyboardType.
* API added in Keyman 18.0
* @param {KeyboardType} keyboard
* @return boolean of inAppPredictionsSuspendedForSensitiveInput or systemPredictionsSuspendedForSensitiveInput
*/
public static boolean getPredictionsSuspended(KeyboardType keyboard) {
if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) {
return inAppPredictionsSuspendedForSensitiveInput;
} else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
return systemPredictionsSuspendedForSensitiveInput;
}
return false;
}
/**
@ -1556,20 +1583,23 @@ public final class KMManager {
// When entering password field, maySuggest should override to disabled
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
int maySuggest = mayPredictOverride ? SuggestionType.SUGGESTIONS_DISABLED.toInt() :
prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion);
int suggestionPreference = prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion);
RelativeLayout.LayoutParams params;
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();
// Do NOT re-layout here; it'll be triggered once the banner loads.
InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %d)", model, maySuggest));
int inappMaySuggest = inAppPredictionsSuspendedForSensitiveInput ? SuggestionType.SUGGESTIONS_DISABLED.toInt() :
suggestionPreference;
InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %d)", model, inappMaySuggest));
}
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();
// Do NOT re-layout here; it'll be triggered once the banner loads.
int maySuggest = systemPredictionsSuspendedForSensitiveInput ? SuggestionType.SUGGESTIONS_DISABLED.toInt() :
suggestionPreference;
SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %d)", model, maySuggest));
}
return true;
@ -1621,6 +1651,25 @@ public final class KMManager {
return true;
}
/**
* setBannerOptions - Update KMW for inapp/system keyboard whether to generate predictions.
* For now, also display banner
* @param mayPredict - boolean whether KMW should generate predictions
* @param {KeyboardType} keyboard
* @return boolean - Success
*/
public static boolean setBannerOptions(boolean mayPredict, KeyboardType keyboard) {
String url = KMString.format("setBannerOptions(%s)", mayPredict);
if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) {
InAppKeyboard.loadJavascript(url);
}
if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) {
SystemKeyboard.loadJavascript(url);
}
return true;
}
/**
* Update KeymanWeb banner type
* @param {KeyboardType} keyboard

View file

@ -82,6 +82,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> Error fatal del teclado con %1$s:%2$s para el idioma %3$s. Cargando teclado predeterminado.</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> Error en el teclado %1$s:%2$s para el idioma %3$s.</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed"> Keyman requiere que WebView esté instalado.\"</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">Keyman requiere que WebView esté habilitado.\"</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">Instalar WebView</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">Habilitar WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Buscando diccionario asociado para descargar</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> No se pudo conectar al servidor de Keyman para buscar un diccionario asociado para descargar</string>
@ -115,6 +123,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Error al recuperar el archivo descargado</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">¡Error al acceder al servidor!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager deshabilitado - No se pueden buscar actualizaciones</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"¡Todos los recursos están actualizados!"</string>
<!-- Context: General Updates -->

View file

@ -82,6 +82,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> Error de teclado fatal con %1$s:%2$s para %3$s idioma. Cargando teclado predeterminado.</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> Error en el teclado %1$s:%2$s para %3$s idioma.</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed"> Keyman requires WebView to be installed.\"</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled"> Keyman requires WebView to be enabled.\"</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">Install WebView</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">Enable WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Comprobando si hay un diccionario asociado para descargar</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> No se puede conectar al servidor Keyman para comprobar si hay un diccionario asociado para descargar</string>
@ -115,6 +123,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Falló al recuperar el archivo descargado</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">¡Falló al acceder al servidor!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager disabled - cannot check for updates</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"¡Todos los recursos están actualizados!"</string>
<!-- Context: General Updates -->

View file

@ -82,6 +82,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> Errore fatale della tastiera con %1$s:%2$s per %3$s lingua. Caricamento tastiera predefinita.</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> Errore nella tastiera %1$s:%2$s per %3$s lingua.</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed"> Keyman richiede che WebView sia installato.\"</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled"> Keyman richiede che WebView sia abilitato.\"</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">Installa WebView</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">Abilita WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Controllo del dizionario associato da scaricare</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> Non è possibile connettersi al server Keyman per verificare dizionari associati da scaricare</string>
@ -115,6 +123,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Recupero del file scaricato non riuscito</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Accesso al server non riuscito!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager disabilitato - impossibile controllare aggiornamenti</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"Tutte le risorse sono aggiornate!"</string>
<!-- Context: General Updates -->

View file

@ -3,7 +3,7 @@ title: KMManager.getMaySendCrashReport()
---
## Summary
The **getMaySendCrashReport()** method returns whether Keyman Engine is allowed to send crash reports over the network to sentry.keyman.com.
The **getMaySendCrashReport()** method returns whether Keyman Engine is allowed to send crash reports over the network to Sentry.
## Syntax
```java
@ -11,7 +11,7 @@ KMManager.getMaySendCrashReport()
```
### Returns
Returns `true` if crash reports can be sent over the network to sentry.keyman.com, `false` otherwise.
Returns `true` if crash reports can be sent over the network to Sentry, `false` otherwise.
## Description
Use this method to check if Keyman Engine will be accessing the network to send crash reports.

View file

@ -0,0 +1,29 @@
---
title: KMManager.getPredictionsSuspended()
---
## Summary
The **getPredictionsSuspended()** method returns a flag that determines whether predictions are temporarily disabled because the currently selected text field is a hidden password text field or a numeric text field.
## Syntax
```java
KMManager.getPredictionsSuspended(KeyboardType keyboardType)
```
### Parameters
`keyboardType`
: The keyboard type. `KEYBOARD_TYPE_INAPP` or
`KEYBOARD_TYPE_SYSTEM`.
### Returns
Returns `true` if predictions are temporarily disabled because the currently selected text field is a hidden password text field or a numeric text field. `false` otherwise.
## Description
Use this method to check if predictions are temporarily disabled because of the type of currently selected text field. This value takes precedence over the user preference in the LanguageSettings menu.
## History
Added syntax in Keyman Engine for Android 18.0.
## See also
* [setPredictionsSuspended](setPredictionsSuspended)

View file

@ -147,11 +147,14 @@ The KMManager is the core class which provides most of the methods and constants
: returns from stored preference the number of milliseconds to trigger a longpress gesture
[`getMaySendCrashReport()`](getMaySendCrashReport)
: returns whether Keyman Engine is allowed to send crash reports over the network to sentry.keyman.com
: returns whether Keyman Engine is allowed to send crash reports over the network to Sentry
[`getOrientation()`](getOrientation)
: returns the device's current orientation (Portrait vs Landscape)
[`getPredictionsSuspended()`](getPredictionsSuspended)
: returns whether predictions are temporarily disabled because the currently selected text field is a hidden password text field or a numeric text field.
[`getSpacebarText()`](getSpacebarText)
: returns the current text display pattern for the spacebar
@ -255,7 +258,10 @@ The KMManager is the core class which provides most of the methods and constants
: stores the longpress delay in milliseconds as a preference.
[`setMaySendCrashReport()`](setMaySendCrashReport)
: sets whether Keyman Engine can send crash reports over the network to sentry.keyman.com
: sets whether Keyman Engine can send crash reports over the network to Sentry
[`setPredictionsSuspended()`](setPredictionsSuspended)
: sets whether predictions are temporarily disabled because the currently selected text field is a hidden password text field or a numeric text field.
[`setShouldAllowSetKeyboard()`](setShouldAllowSetKeyboard)
: sets whether Keyman Engine allows setting a keyboard other than the default keyboard

View file

@ -4,7 +4,7 @@ title: KMManager.setMaySendCrashReport()
## Summary
The **setMaySendCrashReport()** enables or disables whether Keyman Engine can send crash reports over the
network to sentry.keyman.com.
network to Sentry.
## Syntax
```java

View file

@ -0,0 +1,36 @@
---
title: KMManager.setPredictionsSuspended()
---
## Summary
The **setPredictionsSuspended()** method sets a flag to temporarily disable predictions when the currently selected text field is a hidden password text field or a numeric text field.
## Syntax
```java
KMManager.setPredictionsSuspended(int inputType, KeyboardType keyboardType)
```
### Parameters
[`inputType`](https://developer.android.com/reference/android/text/InputType)
: An integer defining the basic context type of text being edited. If the text is one of the following InputTypes, this methods returns `true`:
* TYPE_CLASS_TEXT
* TYPE_TEXT_VARIATION_PASSWORD
* TYPE_CLASS_TEXT
* TYPE_TEXT_VARIATION_WEB_PASSWORD
* TYPE_CLASS_NUMBER
* TYPE_CLASS_PHONE
`keyboardType`
: The keyboard type. `KEYBOARD_TYPE_INAPP` or
`KEYBOARD_TYPE_SYSTEM`.
## Description
Use this method to temporarily disable predictions because of the currently selected text field. This value takes precedence over the user preference in the LanguageSettings menu.
## History
Added syntax in Keyman Engine for Android 18.0.
## See also
* [getPredictionsSuspended](getPredictionsSuspended)

View file

@ -4,7 +4,7 @@ title: Data Privacy Policy
## Privacy Policy for Keyman
This application will send crash reporting information to https://sentry.keyman.com. No personally identifiable information or keyboard strokes are recorded or shared.
This application will send crash reporting information to https://keyman.sentry.io. No personally identifiable information or keyboard strokes are recorded or shared.
You can disable crash reporting on the Keyman
[settings menu](../basic/config/)

View file

@ -59,4 +59,4 @@ When enabled, the Keyman keyboard will provide haptic feedback (vibrate) when th
When enabled, the Keyman app will display the 'Get Started' screen on app startup.
## Allow sending crash reports over network
When enabled, the Keyman app will send crash reporting information to https://sentry.keyman.com. No personally identifiable information or keyboard strokes are recorded.
When enabled, the Keyman app will send crash reporting information to [Sentry](https://keyman.sentry.io). No personally identifiable information or keyboard strokes are recorded.

View file

@ -0,0 +1,16 @@
store(&NAME) '050 - deadkey cancelled by arrow'
c Description: Tests that context gets reset if we encounter a right-arrow
c after a deadkey (#12968)
c keys: [SHIFT K_6][K_RIGHT][K_E][SHIFT K_6][K_LEFT][K_E][SHIFT K_6][K_UP][K_E][SHIFT K_6][K_DOWN][K_E]
c expected: PASS.PASS.PASS.PASS.
store(&version) '10.0'
begin Unicode > use(Main)
group(Main) using keys
+ '^' > dk(1)
dk(1) + 'e' > 'FAIL.'
+ 'e' > 'PASS.'

View file

@ -53,261 +53,265 @@ enum km_core_modifier_mask {
};
// These are Windows API VKEYs, using Keyman VKEY names.
// Underlying values from winuser.h (https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes)
// Note, some codes are defined here for parity with winuser.h, but are
// not available in .kmn language. These codes are marked as "internal"
// by using `__` in the name: `KM_CORE_VKEY__<name>__`
enum km_kpb_virtual_key {
KM_CORE_VKEY__00,
KM_CORE_VKEY_LBUTTON,
KM_CORE_VKEY_RBUTTON,
KM_CORE_VKEY_CANCEL,
KM_CORE_VKEY_MBUTTON,
KM_CORE_VKEY__05,
KM_CORE_VKEY__06,
KM_CORE_VKEY__07,
KM_CORE_VKEY_BKSP,
KM_CORE_VKEY_TAB,
KM_CORE_VKEY__0A,
KM_CORE_VKEY__0B,
KM_CORE_VKEY_KP5,
KM_CORE_VKEY_ENTER,
KM_CORE_VKEY__0E,
KM_CORE_VKEY__0F,
KM_CORE_VKEY_SHIFT,
KM_CORE_VKEY_CONTROL,
KM_CORE_VKEY_ALT,
KM_CORE_VKEY_PAUSE,
KM_CORE_VKEY_CAPS,
KM_CORE_VKEY__15,
KM_CORE_VKEY__16,
KM_CORE_VKEY__17,
KM_CORE_VKEY__18,
KM_CORE_VKEY__19,
KM_CORE_VKEY__1A,
KM_CORE_VKEY_ESC,
KM_CORE_VKEY__1C,
KM_CORE_VKEY__1D,
KM_CORE_VKEY__1E,
KM_CORE_VKEY__1F,
KM_CORE_VKEY_SPACE,
KM_CORE_VKEY_PGUP,
KM_CORE_VKEY_PGDN,
KM_CORE_VKEY_END,
KM_CORE_VKEY_HOME,
KM_CORE_VKEY_LEFT,
KM_CORE_VKEY_UP,
KM_CORE_VKEY_RIGHT,
KM_CORE_VKEY_DOWN,
KM_CORE_VKEY_SEL,
KM_CORE_VKEY_PRINT,
KM_CORE_VKEY_EXEC,
KM_CORE_VKEY_PRTSCN,
KM_CORE_VKEY_INS,
KM_CORE_VKEY_DEL,
KM_CORE_VKEY_HELP,
KM_CORE_VKEY_0,
KM_CORE_VKEY_1,
KM_CORE_VKEY_2,
KM_CORE_VKEY_3,
KM_CORE_VKEY_4,
KM_CORE_VKEY_5,
KM_CORE_VKEY_6,
KM_CORE_VKEY_7,
KM_CORE_VKEY_8,
KM_CORE_VKEY_9,
KM_CORE_VKEY__3A,
KM_CORE_VKEY__3B,
KM_CORE_VKEY__3C,
KM_CORE_VKEY__3D,
KM_CORE_VKEY__3E,
KM_CORE_VKEY__3F,
KM_CORE_VKEY__40,
KM_CORE_VKEY_A,
KM_CORE_VKEY_B,
KM_CORE_VKEY_C,
KM_CORE_VKEY_D,
KM_CORE_VKEY_E,
KM_CORE_VKEY_F,
KM_CORE_VKEY_G,
KM_CORE_VKEY_H,
KM_CORE_VKEY_I,
KM_CORE_VKEY_J,
KM_CORE_VKEY_K,
KM_CORE_VKEY_L,
KM_CORE_VKEY_M,
KM_CORE_VKEY_N,
KM_CORE_VKEY_O,
KM_CORE_VKEY_P,
KM_CORE_VKEY_Q,
KM_CORE_VKEY_R,
KM_CORE_VKEY_S,
KM_CORE_VKEY_T,
KM_CORE_VKEY_U,
KM_CORE_VKEY_V,
KM_CORE_VKEY_W,
KM_CORE_VKEY_X,
KM_CORE_VKEY_Y,
KM_CORE_VKEY_Z,
KM_CORE_VKEY__5B,
KM_CORE_VKEY__5C,
KM_CORE_VKEY__5D,
KM_CORE_VKEY__5E,
KM_CORE_VKEY__5F,
KM_CORE_VKEY_NP0,
KM_CORE_VKEY_NP1,
KM_CORE_VKEY_NP2,
KM_CORE_VKEY_NP3,
KM_CORE_VKEY_NP4,
KM_CORE_VKEY_NP5,
KM_CORE_VKEY_NP6,
KM_CORE_VKEY_NP7,
KM_CORE_VKEY_NP8,
KM_CORE_VKEY_NP9,
KM_CORE_VKEY_NPSTAR,
KM_CORE_VKEY_NPPLUS,
KM_CORE_VKEY_SEPARATOR,
KM_CORE_VKEY_NPMINUS,
KM_CORE_VKEY_NPDOT,
KM_CORE_VKEY_NPSLASH,
KM_CORE_VKEY_F1,
KM_CORE_VKEY_F2,
KM_CORE_VKEY_F3,
KM_CORE_VKEY_F4,
KM_CORE_VKEY_F5,
KM_CORE_VKEY_F6,
KM_CORE_VKEY_F7,
KM_CORE_VKEY_F8,
KM_CORE_VKEY_F9,
KM_CORE_VKEY_F10,
KM_CORE_VKEY_F11,
KM_CORE_VKEY_F12,
KM_CORE_VKEY_F13,
KM_CORE_VKEY_F14,
KM_CORE_VKEY_F15,
KM_CORE_VKEY_F16,
KM_CORE_VKEY_F17,
KM_CORE_VKEY_F18,
KM_CORE_VKEY_F19,
KM_CORE_VKEY_F20,
KM_CORE_VKEY_F21,
KM_CORE_VKEY_F22,
KM_CORE_VKEY_F23,
KM_CORE_VKEY_F24,
KM_CORE_VKEY__88,
KM_CORE_VKEY__89,
KM_CORE_VKEY__8A,
KM_CORE_VKEY__8B,
KM_CORE_VKEY__8C,
KM_CORE_VKEY__8D,
KM_CORE_VKEY__8E,
KM_CORE_VKEY__8F,
KM_CORE_VKEY_NUMLOCK,
KM_CORE_VKEY_SCROLL,
KM_CORE_VKEY__92,
KM_CORE_VKEY__93,
KM_CORE_VKEY__94,
KM_CORE_VKEY__95,
KM_CORE_VKEY__96,
KM_CORE_VKEY__97,
KM_CORE_VKEY__98,
KM_CORE_VKEY__99,
KM_CORE_VKEY__9A,
KM_CORE_VKEY__9B,
KM_CORE_VKEY__9C,
KM_CORE_VKEY__9D,
KM_CORE_VKEY__9E,
KM_CORE_VKEY__9F,
KM_CORE_VKEY__A0,
KM_CORE_VKEY__A1,
KM_CORE_VKEY__A2,
KM_CORE_VKEY__A3,
KM_CORE_VKEY__A4,
KM_CORE_VKEY__A5,
KM_CORE_VKEY__A6,
KM_CORE_VKEY__A7,
KM_CORE_VKEY__A8,
KM_CORE_VKEY__A9,
KM_CORE_VKEY__AA,
KM_CORE_VKEY__AB,
KM_CORE_VKEY__AC,
KM_CORE_VKEY__AD,
KM_CORE_VKEY__AE,
KM_CORE_VKEY__AF,
KM_CORE_VKEY__B0,
KM_CORE_VKEY__B1,
KM_CORE_VKEY__B2,
KM_CORE_VKEY__B3,
KM_CORE_VKEY__B4,
KM_CORE_VKEY__B5,
KM_CORE_VKEY__B6,
KM_CORE_VKEY__B7,
KM_CORE_VKEY__B8,
KM_CORE_VKEY__B9,
KM_CORE_VKEY_COLON,
KM_CORE_VKEY_EQUAL,
KM_CORE_VKEY_COMMA,
KM_CORE_VKEY_HYPHEN,
KM_CORE_VKEY_PERIOD,
KM_CORE_VKEY_SLASH,
KM_CORE_VKEY_BKQUOTE,
KM_CORE_VKEY__C1,
KM_CORE_VKEY__C2,
KM_CORE_VKEY__C3,
KM_CORE_VKEY__C4,
KM_CORE_VKEY__C5,
KM_CORE_VKEY__C6,
KM_CORE_VKEY__C7,
KM_CORE_VKEY__C8,
KM_CORE_VKEY__C9,
KM_CORE_VKEY__CA,
KM_CORE_VKEY__CB,
KM_CORE_VKEY__CC,
KM_CORE_VKEY__CD,
KM_CORE_VKEY__CE,
KM_CORE_VKEY__CF,
KM_CORE_VKEY__D0,
KM_CORE_VKEY__D1,
KM_CORE_VKEY__D2,
KM_CORE_VKEY__D3,
KM_CORE_VKEY__D4,
KM_CORE_VKEY__D5,
KM_CORE_VKEY__D6,
KM_CORE_VKEY__D7,
KM_CORE_VKEY__D8,
KM_CORE_VKEY__D9,
KM_CORE_VKEY__DA,
KM_CORE_VKEY_LBRKT,
KM_CORE_VKEY_BKSLASH,
KM_CORE_VKEY_RBRKT,
KM_CORE_VKEY_QUOTE,
KM_CORE_VKEY_oDF,
KM_CORE_VKEY_oE0,
KM_CORE_VKEY_oE1,
KM_CORE_VKEY_oE2, // 102nd key on European layouts
KM_CORE_VKEY_oE3,
KM_CORE_VKEY_oE4,
KM_CORE_VKEY__E5,
KM_CORE_VKEY_oE6,
KM_CORE_VKEY__E7,
KM_CORE_VKEY__E8,
KM_CORE_VKEY_oE9,
KM_CORE_VKEY_oEA,
KM_CORE_VKEY_oEB,
KM_CORE_VKEY_oEC,
KM_CORE_VKEY_oED,
KM_CORE_VKEY_oEE,
KM_CORE_VKEY_oEF,
KM_CORE_VKEY_oF0,
KM_CORE_VKEY_oF1,
KM_CORE_VKEY_oF2,
KM_CORE_VKEY_oF3,
KM_CORE_VKEY_oF4,
KM_CORE_VKEY_oF5,
KM_CORE_VKEY__F6,
KM_CORE_VKEY__F7,
KM_CORE_VKEY__F8,
KM_CORE_VKEY__F9,
KM_CORE_VKEY__FA,
KM_CORE_VKEY__FB,
KM_CORE_VKEY__FC,
KM_CORE_VKEY__FD,
KM_CORE_VKEY__FE,
KM_CORE_VKEY__FF,
KM_CORE_VKEY_LBUTTON, // 0x01
KM_CORE_VKEY_RBUTTON, // 0x02
KM_CORE_VKEY_CANCEL, // 0x03
KM_CORE_VKEY_MBUTTON, // 0x04
KM_CORE_VKEY__XBUTTON1__, // 0x05
KM_CORE_VKEY__XBUTTON2__, // 0x06
KM_CORE_VKEY__07, // 0x07 - Reserved
KM_CORE_VKEY_BKSP, // 0x08 - VK_BACK
KM_CORE_VKEY_TAB, // 0x09
KM_CORE_VKEY__0A, // 0x0a - Reserved
KM_CORE_VKEY__0B, // 0x0b - Reserved
KM_CORE_VKEY_KP5, // 0x0c - VK_CLEAR
KM_CORE_VKEY_ENTER, // 0x0d - VK_RETURN
KM_CORE_VKEY__0E, // 0x0e - Reserved
KM_CORE_VKEY__0F, // 0x0f - Reserved
KM_CORE_VKEY_SHIFT, // 0x10
KM_CORE_VKEY_CONTROL, // 0x11
KM_CORE_VKEY_ALT, // 0x12 - VK_MENU
KM_CORE_VKEY_PAUSE, // 0x13
KM_CORE_VKEY_CAPS, // 0x14 - VK_CAPITAL
KM_CORE_VKEY__KANA__, // 0x15
KM_CORE_VKEY__HANGUL__, // 0x16
KM_CORE_VKEY__JUNJA__, // 0x17
KM_CORE_VKEY__FINAL__, // 0x18
KM_CORE_VKEY__HANJA__, // 0x19 - or VK_KANJI
KM_CORE_VKEY__IMEOFF__, // 0x1a - VK_IME_OFF
KM_CORE_VKEY_ESC, // 0x1b - VK_ESCAPE
KM_CORE_VKEY__CONVERT__, // 0x1c
KM_CORE_VKEY__NONCONVERT__, // 0x1d
KM_CORE_VKEY__ACCEPT__, // 0x1e
KM_CORE_VKEY__MODECHANGE__, // 0x1f
KM_CORE_VKEY_SPACE, // 0x20
KM_CORE_VKEY_PGUP, // 0x21 - VK_PRIOR
KM_CORE_VKEY_PGDN, // 0x22 - VK_NEXT
KM_CORE_VKEY_END, // 0x23
KM_CORE_VKEY_HOME, // 0x24
KM_CORE_VKEY_LEFT, // 0x25
KM_CORE_VKEY_UP, // 0x26
KM_CORE_VKEY_RIGHT, // 0x27
KM_CORE_VKEY_DOWN, // 0x28
KM_CORE_VKEY_SEL, // 0x29 - VK_SELECT
KM_CORE_VKEY_PRINT, // 0x2a
KM_CORE_VKEY_EXEC, // 0x2b - VK_EXECUTE
KM_CORE_VKEY_PRTSCN, // 0x2c - VK_SNAPSHOT
KM_CORE_VKEY_INS, // 0x2d - VK_INSERT
KM_CORE_VKEY_DEL, // 0x2e - VK_DELETE
KM_CORE_VKEY_HELP, // 0x2f
KM_CORE_VKEY_0, // 0x30
KM_CORE_VKEY_1, // 0x31
KM_CORE_VKEY_2, // 0x32
KM_CORE_VKEY_3, // 0x33
KM_CORE_VKEY_4, // 0x34
KM_CORE_VKEY_5, // 0x35
KM_CORE_VKEY_6, // 0x36
KM_CORE_VKEY_7, // 0x37
KM_CORE_VKEY_8, // 0x38
KM_CORE_VKEY_9, // 0x39
KM_CORE_VKEY__3A, // 0x3a - undefined
KM_CORE_VKEY__3B, // 0x3b - undefined
KM_CORE_VKEY__3C, // 0x3c - undefined
KM_CORE_VKEY__3D, // 0x3d - undefined
KM_CORE_VKEY__3E, // 0x3e - undefined
KM_CORE_VKEY__3F, // 0x3f - undefined
KM_CORE_VKEY__40, // 0x40 - undefined
KM_CORE_VKEY_A, // 0x41
KM_CORE_VKEY_B, // 0x42
KM_CORE_VKEY_C, // 0x43
KM_CORE_VKEY_D, // 0x44
KM_CORE_VKEY_E, // 0x45
KM_CORE_VKEY_F, // 0x46
KM_CORE_VKEY_G, // 0x47
KM_CORE_VKEY_H, // 0x48
KM_CORE_VKEY_I, // 0x49
KM_CORE_VKEY_J, // 0x4a
KM_CORE_VKEY_K, // 0x4b
KM_CORE_VKEY_L, // 0x4c
KM_CORE_VKEY_M, // 0x4d
KM_CORE_VKEY_N, // 0x4e
KM_CORE_VKEY_O, // 0x4f
KM_CORE_VKEY_P, // 0x50
KM_CORE_VKEY_Q, // 0x51
KM_CORE_VKEY_R, // 0x52
KM_CORE_VKEY_S, // 0x53
KM_CORE_VKEY_T, // 0x54
KM_CORE_VKEY_U, // 0x55
KM_CORE_VKEY_V, // 0x56
KM_CORE_VKEY_W, // 0x57
KM_CORE_VKEY_X, // 0x58
KM_CORE_VKEY_Y, // 0x59
KM_CORE_VKEY_Z, // 0x5a
KM_CORE_VKEY__LWIN__, // 0x5b
KM_CORE_VKEY__RWIN__, // 0x5c
KM_CORE_VKEY__APPS__, // 0x5d
KM_CORE_VKEY__5E, // 0x5e - reserved
KM_CORE_VKEY__SLEEP__, // 0x5f
KM_CORE_VKEY_NP0, // 0x60 - VK_NUMPAD0
KM_CORE_VKEY_NP1, // 0x61 - VK_NUMPAD1
KM_CORE_VKEY_NP2, // 0x62 - VK_NUMPAD2
KM_CORE_VKEY_NP3, // 0x63 - VK_NUMPAD3
KM_CORE_VKEY_NP4, // 0x64 - VK_NUMPAD4
KM_CORE_VKEY_NP5, // 0x65 - VK_NUMPAD5
KM_CORE_VKEY_NP6, // 0x66 - VK_NUMPAD6
KM_CORE_VKEY_NP7, // 0x67 - VK_NUMPAD7
KM_CORE_VKEY_NP8, // 0x68 - VK_NUMPAD8
KM_CORE_VKEY_NP9, // 0x69 - VK_NUMPAD9
KM_CORE_VKEY_NPSTAR, // 0x6a - VK_MULTIPLY
KM_CORE_VKEY_NPPLUS, // 0x6b - VK_ADD
KM_CORE_VKEY_SEPARATOR, // 0x6c
KM_CORE_VKEY_NPMINUS, // 0x6d - VK_SUBTRACT
KM_CORE_VKEY_NPDOT, // 0x6e - VK_DECIMAL
KM_CORE_VKEY_NPSLASH, // 0x6f - VK_DIVIDE
KM_CORE_VKEY_F1, // 0x70
KM_CORE_VKEY_F2, // 0x71
KM_CORE_VKEY_F3, // 0x72
KM_CORE_VKEY_F4, // 0x73
KM_CORE_VKEY_F5, // 0x74
KM_CORE_VKEY_F6, // 0x75
KM_CORE_VKEY_F7, // 0x76
KM_CORE_VKEY_F8, // 0x77
KM_CORE_VKEY_F9, // 0x78
KM_CORE_VKEY_F10, // 0x79
KM_CORE_VKEY_F11, // 0x7a
KM_CORE_VKEY_F12, // 0x7b
KM_CORE_VKEY_F13, // 0x7c
KM_CORE_VKEY_F14, // 0x7d
KM_CORE_VKEY_F15, // 0x7e
KM_CORE_VKEY_F16, // 0x7f
KM_CORE_VKEY_F17, // 0x80
KM_CORE_VKEY_F18, // 0x81
KM_CORE_VKEY_F19, // 0x82
KM_CORE_VKEY_F20, // 0x83
KM_CORE_VKEY_F21, // 0x84
KM_CORE_VKEY_F22, // 0x85
KM_CORE_VKEY_F23, // 0x86
KM_CORE_VKEY_F24, // 0x87
KM_CORE_VKEY__88, // 0x88 - reserved
KM_CORE_VKEY__89, // 0x89 - reserved
KM_CORE_VKEY__8A, // 0x8a - reserved
KM_CORE_VKEY__8B, // 0x8b - reserved
KM_CORE_VKEY__8C, // 0x8c - reserved
KM_CORE_VKEY__8D, // 0x8d - reserved
KM_CORE_VKEY__8E, // 0x8e - reserved
KM_CORE_VKEY__8F, // 0x8f - reserved
KM_CORE_VKEY_NUMLOCK, // 0x90
KM_CORE_VKEY_SCROLL, // 0x91
KM_CORE_VKEY__92, // 0x92 - OEM specific
KM_CORE_VKEY__93, // 0x93 - OEM specific
KM_CORE_VKEY__94, // 0x94 - OEM specific
KM_CORE_VKEY__95, // 0x95 - OEM specific
KM_CORE_VKEY__96, // 0x96 - OEM specific
KM_CORE_VKEY__97, // 0x97 - unassigned
KM_CORE_VKEY__98, // 0x98 - unassigned
KM_CORE_VKEY__99, // 0x99 - unassigned
KM_CORE_VKEY__9A, // 0x9a - unassigned
KM_CORE_VKEY__9B, // 0x9b - unassigned
KM_CORE_VKEY__9C, // 0x9c - unassigned
KM_CORE_VKEY__9D, // 0x9d - unassigned
KM_CORE_VKEY__9E, // 0x9e - unassigned
KM_CORE_VKEY__9F, // 0x9f - unassigned
KM_CORE_VKEY__LSHIFT__, // 0xa0
KM_CORE_VKEY__RSHIFT__, // 0xa1
KM_CORE_VKEY__LCONTROL__, // 0xa2
KM_CORE_VKEY__RCONTROL__, // 0xa3
KM_CORE_VKEY__LMENU__, // 0xa4
KM_CORE_VKEY__RMENU__, // 0xa5
KM_CORE_VKEY__BROWSERBACK__, // 0xa6 - VK_BROWSER_BACK
KM_CORE_VKEY__BROWSERFORWARD__, // 0xa7 - VK_BROWSER_FORWARD
KM_CORE_VKEY__BROWSERREFRESH__, // 0xa8 - VK_BROWSER_REFRESH
KM_CORE_VKEY__BROWSERSTOP__, // 0xa9 - VK_BROWSER_STOP
KM_CORE_VKEY__BROWSERSEARCH__, // 0xaa - VK_BROWSER_SEARCH
KM_CORE_VKEY_BROWSERFAVORITES, // 0xab - VK_BROWSER_FAVORITES
KM_CORE_VKEY__BROWSERHOME__, // 0xac - VK_BROWSER_HOME
KM_CORE_VKEY__VOLUMEMUTE__, // 0xad - VK_VOLUME_MUTE
KM_CORE_VKEY__VOLUMEDOWN__, // 0xae - VK_VOLUME_DOWN
KM_CORE_VKEY__VOLUMEUP__, // 0xaf - VK_VOLUME_UP
KM_CORE_VKEY__MEDIANEXT__, // 0xb0 - VK_MEDIA_NEXT_TRACK
KM_CORE_VKEY__MEDIAPREV__, // 0xb1 - VK_MEDIA_PREV_TRACK
KM_CORE_VKEY__MEDIASTOP__, // 0xb2 - VK_MEDIA_STOP
KM_CORE_VKEY__MEDIAPLAY__, // 0xb3 - VK_MEDIA_PLAY_PAUSE
KM_CORE_VKEY__MAIL__, // 0xb4 - VK_LAUNCH_MAIL
KM_CORE_VKEY__MEDIA__, // 0xb5 - VK_LAUNCH_MEDIA_SELECT
KM_CORE_VKEY__APP1__, // 0xb6 - VK_LAUNCH_APP1
KM_CORE_VKEY__APP2__, // 0xb7 - VK_LAUNCH_APP2
KM_CORE_VKEY__B8, // 0xb8 - reserved
KM_CORE_VKEY__B9, // 0xb9 - reserved
KM_CORE_VKEY_COLON, // 0xba - VK_OEM_1
KM_CORE_VKEY_EQUAL, // 0xbb - VK_OEM_PLUS
KM_CORE_VKEY_COMMA, // 0xbc - VK_OEM_COMMA
KM_CORE_VKEY_HYPHEN, // 0xbd - VK_OEM_MINUS
KM_CORE_VKEY_PERIOD, // 0xbe - VK_OEM_PERIOD
KM_CORE_VKEY_SLASH, // 0xbf - VK_OEM_2
KM_CORE_VKEY_BKQUOTE, // 0xc0 - VK_OEM_3
KM_CORE_VKEY__C1, // 0xc1 - reserved
KM_CORE_VKEY__C2, // 0xc2 - reserved
KM_CORE_VKEY__C3, // 0xc3 - reserved
KM_CORE_VKEY__C4, // 0xc4 - reserved
KM_CORE_VKEY__C5, // 0xc5 - reserved
KM_CORE_VKEY__C6, // 0xc6 - reserved
KM_CORE_VKEY__C7, // 0xc7 - reserved
KM_CORE_VKEY__C8, // 0xc8 - reserved
KM_CORE_VKEY__C9, // 0xc9 - reserved
KM_CORE_VKEY__CA, // 0xca - reserved
KM_CORE_VKEY__CB, // 0xcb - reserved
KM_CORE_VKEY__CC, // 0xcc - reserved
KM_CORE_VKEY__CD, // 0xcd - reserved
KM_CORE_VKEY__CE, // 0xce - reserved
KM_CORE_VKEY__CF, // 0xcf - reserved
KM_CORE_VKEY__D0, // 0xd0 - reserved
KM_CORE_VKEY__D1, // 0xd1 - reserved
KM_CORE_VKEY__D2, // 0xd2 - reserved
KM_CORE_VKEY__D3, // 0xd3 - reserved
KM_CORE_VKEY__D4, // 0xd4 - reserved
KM_CORE_VKEY__D5, // 0xd5 - reserved
KM_CORE_VKEY__D6, // 0xd6 - reserved
KM_CORE_VKEY__D7, // 0xd7 - reserved
KM_CORE_VKEY__D8, // 0xd8 - reserved
KM_CORE_VKEY__D9, // 0xd9 - reserved
KM_CORE_VKEY__DA, // 0xda - reserved
KM_CORE_VKEY_LBRKT, // 0xdb - VK_OEM_4
KM_CORE_VKEY_BKSLASH, // 0xdc - VK_OEM_5
KM_CORE_VKEY_RBRKT, // 0xdd - VK_OEM_6
KM_CORE_VKEY_QUOTE, // 0xde - VK_OEM_7
KM_CORE_VKEY_oDF, // 0xdf - VK_OEM_8
KM_CORE_VKEY_oE0, // 0xe0 - reserved
KM_CORE_VKEY_oE1, // 0xe1 - reserved
KM_CORE_VKEY_oE2, // 0xe2 - VK_OEM_102 - 102nd key on European layouts
KM_CORE_VKEY_oE3, // 0xe3 - OEM specific
KM_CORE_VKEY_oE4, // 0xe4 - OEM specific
KM_CORE_VKEY__PROCESSKEY__, // 0xe5
KM_CORE_VKEY_oE6, // 0xe6 - OEM specific
KM_CORE_VKEY__PACKET__, // 0xe7
KM_CORE_VKEY__E8, // 0xe8 - unassigned
KM_CORE_VKEY_oE9, // 0xe9 - OEM specific
KM_CORE_VKEY_oEA, // 0xea - OEM specific
KM_CORE_VKEY_oEB, // 0xeb - OEM specific
KM_CORE_VKEY_oEC, // 0xec - OEM specific
KM_CORE_VKEY_oED, // 0xed - OEM specific
KM_CORE_VKEY_oEE, // 0xee - OEM specific
KM_CORE_VKEY_oEF, // 0xef - OEM specific
KM_CORE_VKEY_oF0, // 0xf0 - OEM specific
KM_CORE_VKEY_oF1, // 0xf1 - OEM specific
KM_CORE_VKEY_oF2, // 0xf2 - OEM specific
KM_CORE_VKEY_oF3, // 0xf3 - OEM specific
KM_CORE_VKEY_oF4, // 0xf4 - OEM specific
KM_CORE_VKEY_oF5, // 0xf5 - OEM specific
KM_CORE_VKEY__ATTN__, // 0xf6
KM_CORE_VKEY__CRSEL__, // 0xf7
KM_CORE_VKEY__EXSEL__, // 0xf8
KM_CORE_VKEY__EREOF__, // 0xf9
KM_CORE_VKEY__PLAY__, // 0xfa
KM_CORE_VKEY__ZOOM__, // 0xfb
KM_CORE_VKEY__NONAME__, // 0xfc
KM_CORE_VKEY__PA1__, // 0xfd
KM_CORE_VKEY__OEMCLEAR__, // 0xfe
KM_CORE_VKEY__FF, // 0xff
};

View file

@ -275,8 +275,10 @@ namespace KmnCompilerMessages {
ERROR_ContextExCannotReferenceIf = SevError | 0x0B1,
ERROR_ContextExCannotReferenceNul = SevError | 0x0B2,
FATAL_BufferOverflow = SevFatal | 0x0C0
// FATAL_Break = SevFatal | 0x0C1, unused
ERROR_TextBeforeOrAfterNulInOutput = SevError | 0x0B3,
FATAL_BufferOverflow = SevFatal | 0x0C0
// FATAL_Break = SevFatal | 0x0C1, unused
};
}

View file

@ -24,17 +24,30 @@ export type KeymanXMLType =
/** Bag of options, maximally one for each KeymanXMLType */
type KeymanXMLParserOptionsBag = {
[key in KeymanXMLType]?: X2jOptions
[key in KeymanXMLType]?: X2jOptions;
};
const PARSER_COMMON_OPTIONS: X2jOptions = {
attributeNamePrefix: '$', // causes remapping into $: { … } objects
htmlEntities: true,
ignoreAttributes: false,
ignorePiTags: true,
numberParseOptions: { // TODO: query is this option really necessary?
eNotation: null,
hex: null,
leadingZeros: null,
skipLike: /(?:)/, // parse numbers as strings
},
textNodeName: '_',
};
/** map of options for the XML parser */
const PARSER_OPTIONS: KeymanXMLParserOptionsBag = {
'keyboard3': {
ignoreAttributes: false, // We'd like attributes, please
attributeNamePrefix: '@__', // We'll use this to convert attributes to strings and subobjects to arrays, when empty.
trimValues: false, // preserve spaces, but:
htmlEntities: true,
tagValueProcessor: (tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => {
ignoreAttributes: false, // We'd like attributes, please
tagValueProcessor: (_tagName: string, tagValue: string /*, jPath, hasAttributes, isLeafNode*/) => {
// since trimValues: false, we need to zap any element values that would be trimmed.
// currently, the LDML spec doesn't have any element values, but this
// future-proofs us a little in that element values are allowed, just trimmed.
@ -42,60 +55,32 @@ const PARSER_OPTIONS: KeymanXMLParserOptionsBag = {
return tagValue?.trim();
},
captureMetaData: true,
trimValues: false, // preserve spaces, but see tagValueProcessor
},
'keyboardTest3': {
ignorePiTags: true,
attributeNamePrefix: '', // avoid @_
htmlEntities: true,
ignoreAttributes: false, // We'd like attributes, please
attributeNamePrefix: '', // avoid @_
ignorePiTags: true,
preserveOrder: true, // Gives us a 'special' format
},
'kps': {
ignorePiTags: true,
ignoreAttributes: false,
htmlEntities: true,
attributeNamePrefix: '$', // causes remapping into $: { … } objects
textNodeName: '_',
numberParseOptions: {
skipLike: /(?:)/, // parse numbers as strings
hex: null,
leadingZeros: null,
eNotation: null,
},
...PARSER_COMMON_OPTIONS,
},
'kpj': {
ignorePiTags: true,
textNodeName: '_',
htmlEntities: true,
ignoreAttributes: false, // We'd like attributes, please
...PARSER_COMMON_OPTIONS,
attributeNamePrefix: '', // to avoid '@_' prefixes
numberParseOptions: {
skipLike: /(?:)/, // parse numbers as strings
hex: null,
leadingZeros: null,
eNotation: null,
},
},
'kvks': {
ignorePiTags: true,
textNodeName: '_',
htmlEntities: true,
ignoreAttributes: false, // We'd like attributes, please
attributeNamePrefix: '$', // causes remapping into $: { … } objects
numberParseOptions: {
skipLike: /(?:)/, // parse numbers as strings
hex: null,
leadingZeros: null,
eNotation: null,
},
trimValues: false, // preserve spaces, but:
tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) : string | undefined => {
...PARSER_COMMON_OPTIONS,
tagValueProcessor: (_tagName: string, tagValue: string, _jPath: string, _hasAttributes: boolean, isLeafNode: boolean) : string | undefined => {
if (!isLeafNode) {
return tagValue?.trim(); // trimmed value
} else {
return null; // no change to leaf nodes
}
},
trimValues: false, // preserve spaces
},
};
@ -103,27 +88,23 @@ type KeymanXMLGeneratorOptionsBag = {
[key in KeymanXMLType]?: XmlBuilderOptions
};
const GENERATOR_COMMON_OPTIONS: XmlBuilderOptions = {
attributeNamePrefix: '$',
ignoreAttributes: false,
format: true,
textNodeName: '_',
suppressEmptyNode: true,
};
const GENERATOR_OPTIONS: KeymanXMLGeneratorOptionsBag = {
kvks: {
attributeNamePrefix: '$',
ignoreAttributes: false,
format: true,
textNodeName: '_',
suppressEmptyNode: true,
...GENERATOR_COMMON_OPTIONS,
},
kpj: {
attributeNamePrefix: '$',
ignoreAttributes: false,
format: true,
textNodeName: '_',
suppressEmptyNode: true,
...GENERATOR_COMMON_OPTIONS,
},
kps: {
attributeNamePrefix: '$',
ignoreAttributes: false,
format: true,
textNodeName: '_',
suppressEmptyNode: true,
...GENERATOR_COMMON_OPTIONS,
},
keyboard3: {
attributeNamePrefix: '$',

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<visualkeyboard>
<header>
<version>10.0</version>
<kbdname>hex_escape</kbdname>
<flags/>
</header>
<encoding name="unicode" fontname="arial" fontsize="-12">
<layer shift="">
<key vkey="K_1">&#x1234;</key>
<key vkey="K_2">&#256;</key>
<key vkey="K_3">&#65536;</key>
<key vkey="K_4">&#x12345;</key>
</layer>
</encoding>
</visualkeyboard>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="mt" conformsTo="46">
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
<keys>
<key id="1" output="ħ" />
<key id="2" output="ថា" />
</keys>
</keyboard3>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/46/dtd/ldmlKeyboardTest3.dtd">
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../../../../resources/standards-data/ldml-keyboards/46/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!--
Read by:

View file

@ -41,6 +41,21 @@ describe('kvks-file-reader', function() {
const reader = new KvksFileReader();
assert.throws(() => reader.read(input), 'File appears to be a binary .kvk file');
});
it('should read non-bmp hex escapes correctly', function() {
const path = makePathToFixture('kvks', 'hex_escape.kvks');
const input = fs.readFileSync(path);
const reader = new KvksFileReader();
const kvks = reader.read(input);
const invalidVkeys: string[] = [];
const vk = reader.transform(kvks, invalidVkeys);
assert.isEmpty(invalidVkeys);
assert.equal(vk.keys[0].text, '\u{1234}');
assert.equal(vk.keys[1].text, '\u{100}');
assert.equal(vk.keys[2].text, String.fromCodePoint(0x10000));
assert.equal(vk.keys[3].text, String.fromCodePoint(0x12345));
});
});
describe('kvks-file-writer', function() {

View file

@ -2,7 +2,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import { assert } from 'chai';
import 'mocha';
import { testTestdataReaderCases } from '../helpers/reader-callback-test.js';
import { LKTAnyAction } from '../../src/types/ldml-keyboard/ldml-keyboard-testdata-xml.js';
import { LKTAnyAction } from '../types/ldml-keyboard/ldml-keyboard-testdata-xml.js';
describe('ldml keyboard xml reader tests', function () {
this.slow(500); // 0.5 sec -- json schema validation takes a while

View file

@ -1,9 +1,9 @@
import { LKKey, ImportStatus } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js';
import 'mocha';
import {assert} from 'chai';
import { CommonTypesMessages } from '../../src/common-messages.js';
import { testReaderCases } from '../helpers/reader-callback-test.js';
import { Constants } from '@keymanapp/common-types';
import { CommonTypesMessages } from '../../src/common-messages.js';
import { LKKey, ImportStatus } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js';
import { testReaderCases } from '../helpers/reader-callback-test.js';
import CLDRScanToVkey = Constants.CLDRScanToVkey;
import CLDRScanToKeyMap = Constants.CLDRScanToKeyMap;
@ -210,6 +210,25 @@ describe('ldml keyboard xml reader tests', function () {
}),
],
},
{
subpath: 'numeric-id.xml',
callback: (data, source, subpath, callbacks) => {
assert.ok(source?.keyboard3?.keys);
const k = pluckKeysFromKeybag(source?.keyboard3?.keys.key, ['1', '2']);
assert.sameDeepOrderedMembers(k.map((entry) => {
// Drop the Symbol members from the returned keys; assertions may expect their presence.
return {
id: entry.id,
output: entry.output
};
}), [
{id: '1', output: '1'}, //default import
{id: '2', output: '2'}, //default import
{id: '1', output: 'ħ'}, //override
{id: '2', output: 'ថា'}, //override
]);
},
},
]);
});

View file

@ -227,40 +227,40 @@ export class KmnCompilerMessages {
static ERROR_InvalidToken = SevError | 0x00A;
static Error_InvalidToken = () => m(this.ERROR_InvalidToken, `Invalid token found`);
// kmcmplib: static Error_InvalidBegin = () => m(this.ERROR_InvalidBegin, `Invalid 'begin' command`);
// kmcmplib: static Error_InvalidBegin = () => m(this.ERROR_InvalidBegin, `Invalid 'begin' statement`);
static ERROR_InvalidBegin = SevError | 0x00B;
static Error_InvalidBegin = () => mw(this.ERROR_InvalidBegin,
`A "begin unicode" statement is required to compile a KeymanWeb keyboard`);
static ERROR_InvalidName = SevError | 0x00C;
static Error_InvalidName = () => m(this.ERROR_InvalidName, `Invalid 'name' command`);
static Error_InvalidName = () => m(this.ERROR_InvalidName, `Invalid 'name' statement`);
static ERROR_InvalidVersion = SevError | 0x00D;
static Error_InvalidVersion = () => m(this.ERROR_InvalidVersion, `Invalid 'version' command`);
static Error_InvalidVersion = () => m(this.ERROR_InvalidVersion, `Invalid 'version' statement`);
static ERROR_InvalidLanguageLine = SevError | 0x00E;
static Error_InvalidLanguageLine = () => m(this.ERROR_InvalidLanguageLine, `Invalid 'language' command`);
static Error_InvalidLanguageLine = () => m(this.ERROR_InvalidLanguageLine, `Invalid 'language' statement`);
static ERROR_LayoutButNoLanguage = SevError | 0x00F;
static Error_LayoutButNoLanguage = () => m(this.ERROR_LayoutButNoLanguage, `Layout command found but no language command`);
static Error_LayoutButNoLanguage = () => m(this.ERROR_LayoutButNoLanguage, `Layout statement found but no language statement`);
static ERROR_InvalidLayoutLine = SevError | 0x010;
static Error_InvalidLayoutLine = () => m(this.ERROR_InvalidLayoutLine, `Invalid 'layout' command`);
static Error_InvalidLayoutLine = () => m(this.ERROR_InvalidLayoutLine, `Invalid 'layout' statement`);
static ERROR_NoVersionLine = SevError | 0x011;
static Error_NoVersionLine = () => m(this.ERROR_NoVersionLine, `No version line found for file`);
static ERROR_InvalidGroupLine = SevError | 0x012;
static Error_InvalidGroupLine = () => m(this.ERROR_InvalidGroupLine, `Invalid 'group' command`);
static Error_InvalidGroupLine = () => m(this.ERROR_InvalidGroupLine, `Invalid 'group' statement`);
static ERROR_InvalidStoreLine = SevError | 0x013;
static Error_InvalidStoreLine = () => m(this.ERROR_InvalidStoreLine, `Invalid 'store' command`);
static Error_InvalidStoreLine = () => m(this.ERROR_InvalidStoreLine, `Invalid 'store' statement`);
static ERROR_InvalidCodeInKeyPartOfRule = SevError | 0x014;
static Error_InvalidCodeInKeyPartOfRule = () => m(this.ERROR_InvalidCodeInKeyPartOfRule, `Invalid command or code found in key part of rule`);
static Error_InvalidCodeInKeyPartOfRule = () => m(this.ERROR_InvalidCodeInKeyPartOfRule, `Invalid statement or code found in key part of rule`);
static ERROR_InvalidDeadkey = SevError | 0x015;
static Error_InvalidDeadkey = () => m(this.ERROR_InvalidDeadkey, `Invalid 'deadkey' or 'dk' command`);
static Error_InvalidDeadkey = () => m(this.ERROR_InvalidDeadkey, `Invalid 'deadkey' or 'dk' statement`);
static ERROR_InvalidValue = SevError | 0x016;
static Error_InvalidValue = () => m(this.ERROR_InvalidValue, `Invalid value in extended string`);
@ -269,7 +269,7 @@ export class KmnCompilerMessages {
static Error_ZeroLengthString = () => m(this.ERROR_ZeroLengthString, `A string of zero characters was found`);
static ERROR_TooManyIndexToKeyRefs = SevError | 0x018;
static Error_TooManyIndexToKeyRefs = () => m(this.ERROR_TooManyIndexToKeyRefs, `Too many index commands refering to key string`);
static Error_TooManyIndexToKeyRefs = () => m(this.ERROR_TooManyIndexToKeyRefs, `Too many index statements refering to key string`);
static ERROR_UnterminatedString = SevError | 0x019;
static Error_UnterminatedString = () => m(this.ERROR_UnterminatedString, `Unterminated string in line`);
@ -278,34 +278,34 @@ export class KmnCompilerMessages {
// static Error_StringInVirtualKeySection = () => m(this.ERROR_StringInVirtualKeySection, `extend string illegal in virtual key section`);
// static ERROR_AnyInVirtualKeySection = SevError | 0x01B; // no longer used, see #12612
// static Error_AnyInVirtualKeySection = () => m(this.ERROR_AnyInVirtualKeySection, `'any' command is illegal in virtual key section`);
// static Error_AnyInVirtualKeySection = () => m(this.ERROR_AnyInVirtualKeySection, `'any' statement is illegal in virtual key section`);
static ERROR_InvalidAny = SevError | 0x01C;
static Error_InvalidAny = () => m(this.ERROR_InvalidAny, `Invalid 'any' command`);
static Error_InvalidAny = () => m(this.ERROR_InvalidAny, `Invalid 'any' statement`);
static ERROR_StoreDoesNotExist = SevError | 0x01D;
static Error_StoreDoesNotExist = () => m(this.ERROR_StoreDoesNotExist, `Store referenced does not exist`);
// static ERROR_BeepInVirtualKeySection = SevError | 0x01E; // no longer used, see #12612
// static Error_BeepInVirtualKeySection = () => m(this.ERROR_BeepInVirtualKeySection, `'beep' command is illegal in virtual key section`);
// static Error_BeepInVirtualKeySection = () => m(this.ERROR_BeepInVirtualKeySection, `'beep' statement is illegal in virtual key section`);
// static ERROR_IndexInVirtualKeySection = SevError | 0x01F; // no longer used, see #12612
// static Error_IndexInVirtualKeySection = () => m(this.ERROR_IndexInVirtualKeySection, `'index' command is illegal in virtual key section`);
// static Error_IndexInVirtualKeySection = () => m(this.ERROR_IndexInVirtualKeySection, `'index' statement is illegal in virtual key section`);
static ERROR_InvalidIndex = SevError | 0x020;
static Error_InvalidIndex = () => m(this.ERROR_InvalidIndex, `Invalid 'index' command`);
static Error_InvalidIndex = () => m(this.ERROR_InvalidIndex, `Invalid 'index' statement`);
// static ERROR_OutsInVirtualKeySection = SevError | 0x021; // no longer used, see #12612
// static Error_OutsInVirtualKeySection = () => m(this.ERROR_OutsInVirtualKeySection, `'outs' command is illegal in virtual key section`);
// static Error_OutsInVirtualKeySection = () => m(this.ERROR_OutsInVirtualKeySection, `'outs' statement is illegal in virtual key section`);
static ERROR_InvalidOuts = SevError | 0x022;
static Error_InvalidOuts = () => m(this.ERROR_InvalidOuts, `Invalid 'outs' command`);
static Error_InvalidOuts = () => m(this.ERROR_InvalidOuts, `Invalid 'outs' statement`);
// static ERROR_ContextInVirtualKeySection = SevError | 0x024; // no longer used, see #12612
// static Error_ContextInVirtualKeySection = () => m(this.ERROR_ContextInVirtualKeySection, `'context' command is illegal in virtual key section`);
// static Error_ContextInVirtualKeySection = () => m(this.ERROR_ContextInVirtualKeySection, `'context' statement is illegal in virtual key section`);
static ERROR_InvalidUse = SevError | 0x025;
static Error_InvalidUse = () => m(this.ERROR_InvalidUse, `Invalid 'use' command`);
static Error_InvalidUse = () => m(this.ERROR_InvalidUse, `Invalid 'use' statement`);
static ERROR_GroupDoesNotExist = SevError | 0x026;
static Error_GroupDoesNotExist = () => m(this.ERROR_GroupDoesNotExist, `Group does not exist`);
@ -314,7 +314,7 @@ export class KmnCompilerMessages {
static Error_VirtualKeyNotAllowedHere = () => m(this.ERROR_VirtualKeyNotAllowedHere, `Virtual key is not allowed here`);
static ERROR_InvalidSwitch = SevError | 0x028;
static Error_InvalidSwitch = () => m(this.ERROR_InvalidSwitch, `Invalid 'switch' command`);
static Error_InvalidSwitch = () => m(this.ERROR_InvalidSwitch, `Invalid 'switch' statement`);
static ERROR_NoTokensFound = SevError | 0x029;
static Error_NoTokensFound = () => m(this.ERROR_NoTokensFound, `No tokens found in line`);
@ -326,19 +326,19 @@ export class KmnCompilerMessages {
static Error_LineTooLong = () => m(this.ERROR_LineTooLong, `Line too long`);
static ERROR_InvalidCopyright = SevError | 0x02C;
static Error_InvalidCopyright = () => m(this.ERROR_InvalidCopyright, `Invalid 'copyright' command`);
static Error_InvalidCopyright = () => m(this.ERROR_InvalidCopyright, `Invalid 'copyright' statement`);
static ERROR_CodeInvalidInThisSection = SevError | 0x02D;
static Error_CodeInvalidInThisSection = () => m(this.ERROR_CodeInvalidInThisSection, `This line is invalid in this section of the file`);
static ERROR_InvalidMessage = SevError | 0x02E;
static Error_InvalidMessage = () => m(this.ERROR_InvalidMessage, `Invalid 'message' command`);
static Error_InvalidMessage = () => m(this.ERROR_InvalidMessage, `Invalid 'message' statement`);
static ERROR_InvalidLanguageName = SevError | 0x02F;
static Error_InvalidLanguageName = () => m(this.ERROR_InvalidLanguageName, `Invalid 'languagename' command`);
static Error_InvalidLanguageName = () => m(this.ERROR_InvalidLanguageName, `Invalid 'languagename' statement`);
static ERROR_InvalidBitmapLine = SevError | 0x030;
static Error_InvalidBitmapLine = () => m(this.ERROR_InvalidBitmapLine, `Invalid 'bitmaps' command`);
static Error_InvalidBitmapLine = () => m(this.ERROR_InvalidBitmapLine, `Invalid 'bitmaps' statement`);
static ERROR_CannotReadBitmapFile = SevError | 0x031;
static Error_CannotReadBitmapFile = () => m(this.ERROR_CannotReadBitmapFile, `Cannot open the bitmap or icon file for reading`);
@ -353,13 +353,13 @@ export class KmnCompilerMessages {
static Error_InvalidCharacter = () => m(this.ERROR_InvalidCharacter, `A character was found that is outside the valid Unicode range (U+0000 - U+10FFFF)`);
static ERROR_InvalidCall = SevError | 0x035;
static Error_InvalidCall = () => m(this.ERROR_InvalidCall, `The 'call' command is invalid`);
static Error_InvalidCall = () => m(this.ERROR_InvalidCall, `The 'call' statement is invalid`);
// static ERROR_CallInVirtualKeySection = SevError | 0x036; // no longer used, see #12612
// static Error_CallInVirtualKeySection = () => m(this.ERROR_CallInVirtualKeySection, `'call' command is illegal in virtual key section`);
// static Error_CallInVirtualKeySection = () => m(this.ERROR_CallInVirtualKeySection, `'call' statement is illegal in virtual key section`);
static ERROR_CodeInvalidInKeyStore = SevError | 0x037;
static Error_CodeInvalidInKeyStore = () => m(this.ERROR_CodeInvalidInKeyStore, `The command is invalid inside a store that is used in a key part of the rule`);
static Error_CodeInvalidInKeyStore = () => m(this.ERROR_CodeInvalidInKeyStore, `The statement is invalid inside a store that is used in a key part of the rule`);
static ERROR_CannotLoadIncludeFile = SevError | 0x038;
static Error_CannotLoadIncludeFile = () => m(this.ERROR_CannotLoadIncludeFile, `Cannot load the included file: it is either invalid or does not exist`);
@ -747,11 +747,30 @@ export class KmnCompilerMessages {
);
static ERROR_ContextExCannotReferenceNul = SevError | 0x0B2;
static Error_ContextExCannotReferenceNul = () => m(
static Error_ContextExCannotReferenceNul = () => m(
this.ERROR_ContextExCannotReferenceNul,
`The offset in context() points to a non-character nul statement in the context`,
);
static ERROR_TextBeforeOrAfterNulInOutput = SevError | 0x0B3;
static Error_TextBeforeOrAfterNulInOutput = () => m(
this.ERROR_TextBeforeOrAfterNulInOutput,
`A rule with a nul statement in the output cannot also output text or text-emitting statements`,
`If a rule has [\`nul\`](https://help.keyman.com/developer/language/reference/nul)
in the output, it means that it deletes any context referenced on the left-hand
side of the rule, and has no other output, so it does not make sense to have any
content apart from the \`nul\` statement in the output.
The following statements can still be used with \`nul\` in the output:
* [\`beep()\`](https://help.keyman.com/developer/language/reference/beep)
* [\`call()\`](https://help.keyman.com/developer/language/reference/call)
* [\`reset()\`](https://help.keyman.com/developer/language/reference/reset)
* [\`return\`](https://help.keyman.com/developer/language/reference/return)
* [\`save()\`](https://help.keyman.com/developer/language/reference/save)
* [\`set()\`](https://help.keyman.com/developer/language/reference/set)
* [\`use()\`](https://help.keyman.com/developer/language/reference/use)`
);
static FATAL_BufferOverflow = SevFatal | 0x0C0;
static Fatal_BufferOverflow = () => m(this.FATAL_BufferOverflow, `The compiler memory buffer overflowed`);

View file

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

View file

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

View file

@ -1,10 +1,10 @@
import 'mocha';
import { assert } from 'chai';
import { KmnCompilerMessageRanges, KmnCompilerMessages } from '../src/compiler/kmn-compiler-messages.js';
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { makePathToFixture } from './helpers/index.js';
import { KmnCompiler } from '../src/main.js';
import { CompilerErrorMask, CompilerErrorNamespace } from '@keymanapp/developer-utils';
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
import { KmnCompiler } from '../src/main.js';
import { KmnCompilerMessageRanges, KmnCompilerMessages } from '../src/compiler/kmn-compiler-messages.js';
import { makePathToFixture } from './helpers/index.js';
describe('KmnCompilerMessages', function () {
const callbacks = new TestCompilerCallbacks();
@ -216,4 +216,11 @@ describe('KmnCompilerMessages', function () {
await testForMessage(this, ['invalid-keyboards', 'error_contextex_cannot_reference_nul.kmn'], KmnCompilerMessages.ERROR_ContextExCannotReferenceNul);
});
// ERROR_TextBeforeOrAfterNulInOutput
it('should generate ERROR_TextBeforeOrAfterNulInOutput if text before or after nul in output', async function() {
await testForMessage(this, ['invalid-keyboards', 'error_text_before_or_after_nul_in_output_before.kmn'], KmnCompilerMessages.ERROR_TextBeforeOrAfterNulInOutput);
await testForMessage(this, ['invalid-keyboards', 'error_text_before_or_after_nul_in_output_after.kmn'], KmnCompilerMessages.ERROR_TextBeforeOrAfterNulInOutput);
});
});

View file

@ -88,7 +88,7 @@ describe('KmwCompilerMessages', function () {
// ERROR_NotSupportedInKeymanWebOutput
it('should generate ERROR_NotSupportedInKeymanWebOutput if the command is not supported in output for KeymanWeb', async function() {
it('should generate ERROR_NotSupportedInKeymanWebOutput if the statement is not supported in output for KeymanWeb', async function() {
await testForMessage(this, ['kmw', 'error_not_supported_in_keyman_web_output.kmn'], KmwCompilerMessages.ERROR_NotSupportedInKeymanWebOutput);
});

View file

@ -62,8 +62,18 @@ export class LdmlCompilerMessages {
m(this.HINT_NoDisplayForMarker, `Key element with id "${def(o.id)}" has only marker output, but there is no matching display element by output or keyId. Keycap may be blank.`);
static ERROR_InvalidVersion = SevError | 0x000D;
static Error_InvalidVersion = (o:{version: string}) =>
m(this.ERROR_InvalidVersion, `Version number '${def(o.version)}' must be a semantic version format string.`);
static Error_InvalidVersion = (o:{version: string}) => m(
this.ERROR_InvalidVersion,
`Version number '${def(o.version)}' must be a semantic version format string with 'major.minor.patch' components.`,
`The version number in the LDML keyboard file must be a [semantic
version](https://semver.org) (semver) string. This string has a format of three
integers representing major, minor, and patch versions, separated by periods. In
the LDML keyboard specification, the full semver format is permitted, including
pre-release suffix strings, but the Keyman toolchain currently restricts the
format to the three integer components.
Example: \`"1.12.3"\``
);
static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E;
static Error_MustBeAtLeastOneLayerElement = () =>

View file

@ -1556,6 +1556,71 @@ KMX_DWORD CheckVirtualKeysInOutput(PKMX_WCHAR output) {
return STATUS_Success;
}
const KMX_BOOL CODE__IS_TEXTUAL[] = {
-1, // undefined 0x00
TRUE, // CODE_ANY 0x01
TRUE, // CODE_INDEX 0x02
TRUE, // CODE_CONTEXT 0x03
FALSE, // CODE_NUL 0x04
FALSE, // CODE_USE 0x05 // may trigger text effects but indirectly
FALSE, // CODE_RETURN 0x06
FALSE, // CODE_BEEP 0x07
TRUE, // CODE_DEADKEY 0x08
-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
FALSE, // CODE_CALL 0x0F // may trigger text effects but indirectly
-1, // UC_SENTINEL_EXTENDEDEND 0x10 (not valid with UC_SENTINEL)
TRUE, // CODE_CONTEXTEX 0x11
TRUE, // CODE_NOTANY 0x12
FALSE, // CODE_SETOPT 0x13
FALSE, // CODE_IFOPT 0x14
FALSE, // CODE_SAVEOPT 0x15
FALSE, // CODE_RESETOPT 0x16
FALSE, // CODE_IFSYSTEMSTORE 0x17
FALSE // CODE_SETSYSTEMSTORE 0x18
};
static_assert(sizeof(CODE__IS_TEXTUAL) / sizeof(CODE__IS_TEXTUAL[0]) == (CODE_LASTCODE + 1), "Size of array CODE__EMITS_TEXT not correct");
KMX_BOOL IsTextualCode(KMX_WORD code) {
assert(code >= CODE_ANY && code <= CODE_LASTCODE);
if(!(code >= CODE_ANY && code <= CODE_LASTCODE)) {
return FALSE;
}
return CODE__IS_TEXTUAL[code];
}
/**
* Warn if output has text before or after a nul statement, which
* doesn't make sense -- if nul is used, it indicates no output
*/
KMX_DWORD CheckNulUsageInOutput(PKMX_WCHAR output) {
PKMX_WCHAR p;
KMX_BOOL hasNul = FALSE, hasOther = FALSE;
for (p = output; *p; p = incxstr(p)) {
if (*p == UC_SENTINEL) {
auto code = *(p+1);
if(code == CODE_NUL) {
hasNul = TRUE;
} else if(IsTextualCode(code)) {
hasOther = TRUE;
}
} else {
hasOther = TRUE;
}
}
if(hasNul && hasOther) {
return KmnCompilerMessages::ERROR_TextBeforeOrAfterNulInOutput;
}
return STATUS_Success;
}
/**
* Adds implicit `context` to start of output of rules for readonly groups
*/
@ -1694,6 +1759,10 @@ KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnico
return msg;
}
if ((msg = CheckNulUsageInOutput(pklOut)) != STATUS_Success) {
return msg;
}
if (gp->fReadOnly) {
// Ensure no output is made from the rule, and that
// use() statements meet required readonly semantics

View file

@ -2,7 +2,7 @@
Contact the Keyman team if you need access to sentry.keyman.com for development.
You will also need to install [sentry-cli](https://docs.sentry.io/cli/installation/) for uploading Debug symbols.
After setting up your personal [Auth token](http://sentry.keyman.com/settings/account/api/auth-tokens/), add the following to **~/.bashrc**
After setting up your personal [Auth token](http://keyman.sentry.io/settings/account/api/auth-tokens/), add the following to **~/.bashrc**
```bash
export SENTRY_AUTH_TOKEN={your Sentry auth token}

View file

@ -0,0 +1,4 @@
# Description of file formats
In addition to the file formats described in this folder, see also
the schema files in `common/schemas` for additional file formats.

View file

@ -72,7 +72,7 @@ https://help.keyman.com/developer/engine/android/latest-version/
| KEYMAN_VERSION_ISO639_3 | 2024-05-22 |
| KEYMAN_VERSION_JAVA | 11 |
| KEYMAN_VERSION_LANGTAGS | 2025-02-18 |
| KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2024-05-16 |
| KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2025-03-10 |
| KEYMAN_VERSION_UNICODE | 16.0.0 |
> ### This file is auto-generated by publish-minimum-versions.sh. Do not modify

View file

@ -30,7 +30,7 @@ Click on this to [search for a keyboard or language](../../start/searching-for-k
When enabled, the Keyman app will display the 'Get Started' screen on app startup.
## Allow error reporting
When enabled, the Keyman app will send crash reporting information to https://sentry.keyman.com. No personally identifiable information or keyboard strokes are recorded.
When enabled, the Keyman app will send crash reporting information to Sentry. No personally identifiable information or keyboard strokes are recorded.
## Spacebar Caption
Click on this to [change the displayed keyboard name](spacebar-caption) on the spacebar.

View file

@ -226,6 +226,24 @@
/* Text showing name of spacebar caption - language + keyboard */
"menu-settings-spacebar-item-languageKeyboard" = "Lingua e tastiera";
/* Label for the "Adjust Keyboard Height" item on the main settings screen */
"menu-settings-adjust-keyboard-height" = "Regola altezza tastiera";
/* Title for the "Adjust Keyboard Height" settings child screen */
"adjust-keyboard-height-title" = "Regola tastiera";
/* Label for "Reset to Default Keyboard Height" button on the adjust height screen */
"button-label-reset-default-keyboard-height" = "Reimposta l'altezza predefinita tastiera";
/* Instruction text to drag keyboard to resize */
"keyboard-drag-instructions" = "Trascina il controllo freccia per regolare l'altezza della tastiera.";
/* Instruction to rotate to adjust landscape keyboard height (displayed when device is portrait) */
"portrait-keyboard-rotate-instructions" = "Ruota il dispositivo per regolarlo in orizzontale.";
/* Instruction to rotate to adjust portrait keyboard height (displayed when device is landscape) */
"landscape-keyboard-rotate-instructions" = "Ruota il dispositivo per regolarlo in verticale.";
/* Short text for notification: download failure for keyboard */
"notification-download-failure-keyboard" = "Download tastiera non riuscito";

View file

@ -25,6 +25,7 @@ Build-Depends:
perl,
pkgconf,
python3-all (>= 3.5),
python3-build,
python3-dbus,
python3-fonttools,
python3-gi,
@ -39,6 +40,7 @@ Build-Depends:
python3-requests,
python3-sentry-sdk (>= 1.1),
python3-setuptools,
python3-venv,
python3-xdg,
xserver-xephyr,
xvfb,

View file

@ -232,7 +232,7 @@ get_context_debug(IBusEngine *engine) {
static gchar *
debug_utf8_with_codepoints(const gchar *utf8) {
GString *output = g_string_new("");
g_string_append_printf(output, "|%s| len:%ld) [", utf8, g_utf8_strlen(utf8, -1));
g_string_append_printf(output, "|%s| (len:%ld) [", utf8, g_utf8_strlen(utf8, -1));
gunichar2 *utf16 = g_utf8_to_utf16(utf8, -1, NULL, NULL, NULL);
for (int i = 0; utf16[i] != '\0'; i++) {
g_string_append_printf(output, "U+%04x ", utf16[i]);
@ -290,7 +290,7 @@ set_context_if_needed(IBusEngine *engine) {
}
static void
initialize_queue(IBusKeymanEngine *keyman, int index, int count) {
initialize_queue_items(IBusKeymanEngine *keyman, int index, int count) {
g_assert(keyman != NULL);
g_assert(index >= 0 && index < MAX_QUEUE_SIZE);
g_assert(count > 0 && count <= MAX_QUEUE_SIZE);
@ -463,7 +463,7 @@ ibus_keyman_engine_constructor(
keyman->lctrl_pressed = FALSE;
keyman->ralt_pressed = FALSE;
keyman->rctrl_pressed = FALSE;
initialize_queue(keyman, 0, MAX_QUEUE_SIZE);
initialize_queue_items(keyman, 0, MAX_QUEUE_SIZE);
keyman->commit_item = &keyman->commit_queue[0];
gchar **split_name = g_strsplit(engine_name, ":", 2);
if (split_name[0] == NULL)
@ -703,18 +703,19 @@ process_persist_action(IBusEngine *engine, km_core_option_item *persist_options)
static void
process_emit_keystroke_action(IBusEngine *engine, km_core_bool emit_keystroke) {
if (!emit_keystroke) {
return;
}
IBusKeymanEngine *keyman = (IBusKeymanEngine *)engine;
if (client_supports_surrounding_text(engine)) {
// compliant app
if (!emit_keystroke) {
return;
}
ibus_engine_forward_key_event(engine, keyman->commit_item->keyval,
keyman->commit_item->keycode, keyman->commit_item->state);
return;
}
// non-compliant app
keyman->commit_item->emitting_keystroke = TRUE;
keyman->commit_item->emitting_keystroke = emit_keystroke;
}
static void
@ -768,7 +769,7 @@ commit_current_queue_item(IBusKeymanEngine *keyman) {
}
keyman->commit_item--;
memmove(keyman->commit_queue, &keyman->commit_queue[1], sizeof(commit_queue_item) * MAX_QUEUE_SIZE - 1);
initialize_queue(keyman, MAX_QUEUE_SIZE - 1, 1);
initialize_queue_items(keyman, MAX_QUEUE_SIZE - 1, 1);
}
static void

View file

@ -7,109 +7,255 @@
// uses kernel keycodes which are (X11 keycode - 8)
// see /usr/include/linux/input-event-codes.h
static km_core_virtual_key const keycode_to_vk[256] = {
0, // KEY_RESERVED = 0x00;
KM_CORE_VKEY_ESC, // KEY_ESC = 0x01;
KM_CORE_VKEY_1, // KEY_1 = 0x02;
KM_CORE_VKEY_2, // KEY_2 = 0x03;
KM_CORE_VKEY_3, // KEY_3 = 0x04;
KM_CORE_VKEY_4, // KEY_4 = 0x05;
KM_CORE_VKEY_5, // KEY_5 = 0x06;
KM_CORE_VKEY_6, // KEY_6 = 0x07;
KM_CORE_VKEY_7, // KEY_7 = 0x08;
KM_CORE_VKEY_8, // KEY_8 = 0x09;
KM_CORE_VKEY_9, // KEY_9 = 0x0A;
KM_CORE_VKEY_0, // KEY_0 = 0x0B;
KM_CORE_VKEY_HYPHEN, // KEY_MINUS = 0x0C;
KM_CORE_VKEY_EQUAL, // KEY_EQUALS = 0x0D;
KM_CORE_VKEY_BKSP, // KEY_BACKSPACE = 0x0E;
KM_CORE_VKEY_TAB, // KEY_TAB = 0x0F;
KM_CORE_VKEY_Q, // KEY_Q = 0x10;
KM_CORE_VKEY_W, // KEY_W = 0x11;
KM_CORE_VKEY_E, // KEY_E = 0x12;
KM_CORE_VKEY_R, // KEY_R = 0x13;
KM_CORE_VKEY_T, // KEY_T = 0x14;
KM_CORE_VKEY_Y, // KEY_Y = 0x15;
KM_CORE_VKEY_U, // KEY_U = 0x16;
KM_CORE_VKEY_I, // KEY_I = 0x17;
KM_CORE_VKEY_O, // KEY_O = 0x18;
KM_CORE_VKEY_P, // KEY_P = 0x19;
KM_CORE_VKEY_LBRKT, // KEY_LEFTBRACE = 0x1A;
KM_CORE_VKEY_RBRKT, // KEY_RIGHTBRACE = 0x1B;
KM_CORE_VKEY_ENTER, // KEY_ENTER = 0x1C;
KM_CORE_VKEY_CONTROL, // KEY_LEFTCTRL = 0x1D;
KM_CORE_VKEY_A, // KEY_A = 0x1E;
KM_CORE_VKEY_S, // KEY_S = 0x1F;
KM_CORE_VKEY_D, // KEY_D = 0x20;
KM_CORE_VKEY_F, // KEY_F = 0x21;
KM_CORE_VKEY_G, // KEY_G = 0x22;
KM_CORE_VKEY_H, // KEY_H = 0x23;
KM_CORE_VKEY_J, // KEY_J = 0x24;
KM_CORE_VKEY_K, // KEY_K = 0x25;
KM_CORE_VKEY_L, // KEY_L = 0x26;
KM_CORE_VKEY_COLON, // KEY_SEMICOLON = 0x27;
KM_CORE_VKEY_QUOTE, // KEY_APOSTROPHE = 0x28;
KM_CORE_VKEY_BKQUOTE, // KEY_GRAVE = 0x29;
KM_CORE_VKEY_SHIFT, // KEY_LEFTSHIFT = 0x2A;
KM_CORE_VKEY_BKSLASH, // KEY_BACKSLASH = 0x2B;
KM_CORE_VKEY_Z, // KEY_Z = 0x2C;
KM_CORE_VKEY_X, // KEY_X = 0x2D;
KM_CORE_VKEY_C, // KEY_C = 0x2E;
KM_CORE_VKEY_V, // KEY_V = 0x2F;
KM_CORE_VKEY_B, // KEY_B = 0x30;
KM_CORE_VKEY_N, // KEY_N = 0x31;
KM_CORE_VKEY_M, // KEY_M = 0x32;
KM_CORE_VKEY_COMMA, // KEY_COMMA = 0x33;
KM_CORE_VKEY_PERIOD, // KEY_DOT = 0x34;
KM_CORE_VKEY_SLASH, // KEY_SLASH = 0x35;
KM_CORE_VKEY_SHIFT, // KEY_RIGHTSHIFT = 0x36;
KM_CORE_VKEY_NPSTAR, // KEY_KPASTERISK = 0x37;
KM_CORE_VKEY_ALT, // KEY_LEFTALT = 0x38;
KM_CORE_VKEY_SPACE, // KEY_SPACE = 0x39;
KM_CORE_VKEY_CAPS, // KEY_CAPSLOCK = 0x3A;
KM_CORE_VKEY_F1, // KEY_F1 = 0x3B;
KM_CORE_VKEY_F2, // KEY_F2 = 0x3C;
KM_CORE_VKEY_F3, // KEY_F3 = 0x3D;
KM_CORE_VKEY_F4, // KEY_F4 = 0x3E;
KM_CORE_VKEY_F5, // KEY_F5 = 0x3F;
KM_CORE_VKEY_F6, // KEY_F6 = 0x40;
KM_CORE_VKEY_F7, // KEY_F7 = 0x41;
KM_CORE_VKEY_F8, // KEY_F8 = 0x42;
KM_CORE_VKEY_F9, // KEY_F9 = 0x43;
KM_CORE_VKEY_F10, // KEY_F10 = 0x44;
0, // KEY_NUMLOCK = 0x45;
0, // KEY_SCROLLLOCK = 0x46;
KM_CORE_VKEY_NP7, // KEY_KP7 = 0x47;
KM_CORE_VKEY_NP8, // KEY_KP8 = 0x48;
KM_CORE_VKEY_NP9, // KEY_KP9 = 0x49;
KM_CORE_VKEY_NPMINUS, // KEY_KPMINUS = 0x4A;
KM_CORE_VKEY_NP4, // KEY_KP4 = 0x4B;
KM_CORE_VKEY_NP5, // KEY_KP5 = 0x4C;
KM_CORE_VKEY_NP6, // KEY_KP6 = 0x4D;
KM_CORE_VKEY_NPPLUS, // KEY_KPPLUS = 0x4E;
KM_CORE_VKEY_NP1, // KEY_KP1 = 0x4F;
KM_CORE_VKEY_NP2, // KEY_KP2 = 0x50;
KM_CORE_VKEY_NP3, // KEY_KP3 = 0x51;
KM_CORE_VKEY_NP0, // KEY_KP0 = 0x52;
KM_CORE_VKEY_NPDOT, // KEY_KPDOT = 0x53;
0, // padding 0x54;
0, // KEY_ZENKAKUHANKAKU = 0x55;
KM_CORE_VKEY_oE2, // KEY_102ND = 0x56;
KM_CORE_VKEY_F11, // KEY_F11 = 0x57;
KM_CORE_VKEY_F12, // KEY_F12 = 0x58;
0, // KEY_RO = 0x59;
0, // KEY_KATAKANA = 0x5a;
0, // KEY_HIRAGANA = 0x5b;
0, // KEY_HENKAN = 0x5c;
0, // KEY_KATAKANAHIRAGANA = 0x5d;
0, // KEY_MUHENKAN = 0x5e;
0, // KEY_KPJPCOMMA = 0x5f;
0, // KEY_KPENTER = 0x60;
KM_CORE_VKEY_CONTROL, // KEY_RIGHTCTRL = 0x61;
0, // KEY_KPSLASH = 0x62;
0, // KEY_SYSRQ = 0x63;
KM_CORE_VKEY_ALT // KEY_RIGHTALT = 0x64;
// Many more KEYS currently not used by KMW...
0, // 0x00 - KEY_RESERVED
KM_CORE_VKEY_ESC, // 0x01 - KEY_ESC
KM_CORE_VKEY_1, // 0x02 - KEY_1
KM_CORE_VKEY_2, // 0x03 - KEY_2
KM_CORE_VKEY_3, // 0x04 - KEY_3
KM_CORE_VKEY_4, // 0x05 - KEY_4
KM_CORE_VKEY_5, // 0x06 - KEY_5
KM_CORE_VKEY_6, // 0x07 - KEY_6
KM_CORE_VKEY_7, // 0x08 - KEY_7
KM_CORE_VKEY_8, // 0x09 - KEY_8
KM_CORE_VKEY_9, // 0x0A - KEY_9
KM_CORE_VKEY_0, // 0x0B - KEY_0
KM_CORE_VKEY_HYPHEN, // 0x0C - KEY_MINUS
KM_CORE_VKEY_EQUAL, // 0x0D - KEY_EQUALS
KM_CORE_VKEY_BKSP, // 0x0E - KEY_BACKSPACE
KM_CORE_VKEY_TAB, // 0x0F - KEY_TAB
KM_CORE_VKEY_Q, // 0x10 - KEY_Q
KM_CORE_VKEY_W, // 0x11 - KEY_W
KM_CORE_VKEY_E, // 0x12 - KEY_E
KM_CORE_VKEY_R, // 0x13 - KEY_R
KM_CORE_VKEY_T, // 0x14 - KEY_T
KM_CORE_VKEY_Y, // 0x15 - KEY_Y
KM_CORE_VKEY_U, // 0x16 - KEY_U
KM_CORE_VKEY_I, // 0x17 - KEY_I
KM_CORE_VKEY_O, // 0x18 - KEY_O
KM_CORE_VKEY_P, // 0x19 - KEY_P
KM_CORE_VKEY_LBRKT, // 0x1A - KEY_LEFTBRACE
KM_CORE_VKEY_RBRKT, // 0x1B - KEY_RIGHTBRACE
KM_CORE_VKEY_ENTER, // 0x1C - KEY_ENTER
KM_CORE_VKEY_CONTROL, // 0x1D - KEY_LEFTCTRL
KM_CORE_VKEY_A, // 0x1E - KEY_A
KM_CORE_VKEY_S, // 0x1F - KEY_S
KM_CORE_VKEY_D, // 0x20 - KEY_D
KM_CORE_VKEY_F, // 0x21 - KEY_F
KM_CORE_VKEY_G, // 0x22 - KEY_G
KM_CORE_VKEY_H, // 0x23 - KEY_H
KM_CORE_VKEY_J, // 0x24 - KEY_J
KM_CORE_VKEY_K, // 0x25 - KEY_K
KM_CORE_VKEY_L, // 0x26 - KEY_L
KM_CORE_VKEY_COLON, // 0x27 - KEY_SEMICOLON
KM_CORE_VKEY_QUOTE, // 0x28 - KEY_APOSTROPHE
KM_CORE_VKEY_BKQUOTE, // 0x29 - KEY_GRAVE
KM_CORE_VKEY_SHIFT, // 0x2A - KEY_LEFTSHIFT
KM_CORE_VKEY_BKSLASH, // 0x2B - KEY_BACKSLASH
KM_CORE_VKEY_Z, // 0x2C - KEY_Z
KM_CORE_VKEY_X, // 0x2D - KEY_X
KM_CORE_VKEY_C, // 0x2E - KEY_C
KM_CORE_VKEY_V, // 0x2F - KEY_V
KM_CORE_VKEY_B, // 0x30 - KEY_B
KM_CORE_VKEY_N, // 0x31 - KEY_N
KM_CORE_VKEY_M, // 0x32 - KEY_M
KM_CORE_VKEY_COMMA, // 0x33 - KEY_COMMA
KM_CORE_VKEY_PERIOD, // 0x34 - KEY_DOT
KM_CORE_VKEY_SLASH, // 0x35 - KEY_SLASH
KM_CORE_VKEY_SHIFT, // 0x36 - KEY_RIGHTSHIFT
KM_CORE_VKEY_NPSTAR, // 0x37 - KEY_KPASTERISK
KM_CORE_VKEY_ALT, // 0x38 - KEY_LEFTALT
KM_CORE_VKEY_SPACE, // 0x39 - KEY_SPACE
KM_CORE_VKEY_CAPS, // 0x3A - KEY_CAPSLOCK
KM_CORE_VKEY_F1, // 0x3B - KEY_F1
KM_CORE_VKEY_F2, // 0x3C - KEY_F2
KM_CORE_VKEY_F3, // 0x3D - KEY_F3
KM_CORE_VKEY_F4, // 0x3E - KEY_F4
KM_CORE_VKEY_F5, // 0x3F - KEY_F5
KM_CORE_VKEY_F6, // 0x40 - KEY_F6
KM_CORE_VKEY_F7, // 0x41 - KEY_F7
KM_CORE_VKEY_F8, // 0x42 - KEY_F8
KM_CORE_VKEY_F9, // 0x43 - KEY_F9
KM_CORE_VKEY_F10, // 0x44 - KEY_F10
KM_CORE_VKEY_NUMLOCK, // 0x45 - KEY_NUMLOCK
KM_CORE_VKEY_SCROLL, // 0x46 - KEY_SCROLLLOCK
KM_CORE_VKEY_NP7, // 0x47 - KEY_KP7
KM_CORE_VKEY_NP8, // 0x48 - KEY_KP8
KM_CORE_VKEY_NP9, // 0x49 - KEY_KP9
KM_CORE_VKEY_NPMINUS, // 0x4A - KEY_KPMINUS
KM_CORE_VKEY_NP4, // 0x4B - KEY_KP4
KM_CORE_VKEY_NP5, // 0x4C - KEY_KP5
KM_CORE_VKEY_NP6, // 0x4D - KEY_KP6
KM_CORE_VKEY_NPPLUS, // 0x4E - KEY_KPPLUS
KM_CORE_VKEY_NP1, // 0x4F - KEY_KP1
KM_CORE_VKEY_NP2, // 0x50 - KEY_KP2
KM_CORE_VKEY_NP3, // 0x51 - KEY_KP3
KM_CORE_VKEY_NP0, // 0x52 - KEY_KP0
KM_CORE_VKEY_NPDOT, // 0x53 - KEY_KPDOT
0, // padding 0x54;
KM_CORE_VKEY__KANA__, // 0x55 - KEY_ZENKAKUHANKAKU
KM_CORE_VKEY_oE2, // 0x56 - KEY_102ND
KM_CORE_VKEY_F11, // 0x57 - KEY_F11
KM_CORE_VKEY_F12, // 0x58 - KEY_F12
0, // 0x59 - KEY_RO - ?
KM_CORE_VKEY__KANA__, // 0x5a - KEY_KATAKANA
KM_CORE_VKEY__KANA__, // 0x5b - KEY_HIRAGANA
KM_CORE_VKEY__KANA__, // 0x5c - KEY_HENKAN
KM_CORE_VKEY__KANA__, // 0x5d - KEY_KATAKANAHIRAGANA
KM_CORE_VKEY__KANA__, // 0x5e - KEY_MUHENKAN
KM_CORE_VKEY_COMMA, // 0x5f - KEY_KPJPCOMMA
KM_CORE_VKEY_ENTER, // 0x60 - KEY_KPENTER
KM_CORE_VKEY_CONTROL, // 0x61 - KEY_RIGHTCTRL
KM_CORE_VKEY_SLASH, // 0x62 - KEY_KPSLASH
KM_CORE_VKEY_PRTSCN, // 0x63 - KEY_SYSRQ
KM_CORE_VKEY_ALT, // 0x64 - KEY_RIGHTALT
0, // 0x65 - KEY_LINEFEED - ?
KM_CORE_VKEY_HOME, // 0x66 - KEY_HOME
KM_CORE_VKEY_UP, // 0x67 - KEY_UP
KM_CORE_VKEY_PGUP, // 0x68 - KEY_PAGEUP
KM_CORE_VKEY_LEFT, // 0x69 - KEY_LEFT
KM_CORE_VKEY_RIGHT, // 0x6A - KEY_RIGHT
KM_CORE_VKEY_END, // 0x6B - KEY_END
KM_CORE_VKEY_DOWN, // 0x6C - KEY_DOWN
KM_CORE_VKEY_PGDN, // 0x6D - KEY_PAGEDOWN
KM_CORE_VKEY_INS, // 0x6E - KEY_INSERT
KM_CORE_VKEY_DEL, // 0x6F - KEY_DELETE
0, // 0x70 - KEY_MACRO - ?
KM_CORE_VKEY__VOLUMEMUTE__, // 0x71 - KEY_MUTE
KM_CORE_VKEY__VOLUMEDOWN__, // 0x72 - KEY_VOLUMEDOWN
KM_CORE_VKEY__VOLUMEUP__, // 0x73 - KEY_VOLUMEUP
0, // 0x74 - KEY_POWER
KM_CORE_VKEY_EQUAL, // 0x75 - KEY_KPEQUAL
KM_CORE_VKEY_NPPLUS, // 0x76 - KEY_KPPLUSMINUS
KM_CORE_VKEY_PAUSE, // 0x77 - KEY_PAUSE
KM_CORE_VKEY__ZOOM__, // 0x78 - KEY_SCALE
KM_CORE_VKEY_NPDOT, // 0x79 - KEY_KPCOMMA
KM_CORE_VKEY__HANGUL__, // 0x7a - KEY_HANGEUL
KM_CORE_VKEY__HANJA__, // 0x7b - KEY_HANJA
0, // 0x7c - KEY_YEN - ?
KM_CORE_VKEY__LWIN__, // 0x7d - KEY_LEFTMETA
KM_CORE_VKEY__RWIN__, // 0x7e - KEY_RIGHTMETA
0, // 0x7f - KEY_COMPOSE - ?
KM_CORE_VKEY__BROWSERSTOP__, // 0x80 - KEY_STOP
KM_CORE_VKEY__BROWSERREFRESH__,// 0x81 - KEY_AGAIN
0, // 0x82 - KEY_PROPS - ?
0, // 0x83 - KEY_UNDO - ?
0, // 0x84 - KEY_FRONT - ?
0, // 0x85 - KEY_COPY - ?
0, // 0x86 - KEY_OPEN - ?
0, // 0x87 - KEY_PASTE - ?
KM_CORE_VKEY__BROWSERSEARCH__, // 0x88 - KEY_FIND
0, // 0x89 - KEY_CUT - ?
KM_CORE_VKEY_HELP, // 0x8a - KEY_HELP
KM_CORE_VKEY_ALT, // 0x8b - KEY_MENU
0, // 0x8c - KEY_CALC - ?
0, // 0x8d - KEY_SETUP - ?
KM_CORE_VKEY__SLEEP__, // 0x8e - KEY_SLEEP
0, // 0x8f - KEY_WAKEUP - ?
0, // 0x90 - KEY_FILE - ?
0, // 0x91 - KEY_SENDFILE - ?
0, // 0x92 - KEY_DELETEFILE - ?
0, // 0x93 - KEY_XFER - ?
KM_CORE_VKEY__APP1__, // 0x94 - KEY_PROG1
KM_CORE_VKEY__APP2__, // 0x95 - KEY_PROG2
KM_CORE_VKEY__BROWSERHOME__, // 0x96 - KEY_WWW
0, // 0x97 - KEY_MSDOS - ?
0, // 0x98 - KEY_COFFEE, KEY_SCREENLOCK - ?
0, // 0x99 - KEY_ROTATE_DISPLAY, KEY_DIRECTION - ?
0, // 0x9a - KEY_CYCLEWINDOWS - ?
KM_CORE_VKEY__MAIL__, // 0x9b - KEY_MAIL
0, // 0x9c - KEY_BOOKMARKS - ?
0, // 0x9d - KEY_COMPUTER - ?
KM_CORE_VKEY__BROWSERBACK__, // 0x9e - KEY_BACK
KM_CORE_VKEY__BROWSERFORWARD__,// 0x9f - KEY_FORWARD
0, // 0xa0 - KEY_CLOSECD - ?
0, // 0xa1 - KEY_EJECTCD - ?
0, // 0xa2 - KEY_EJECTCLOSECD - ?
KM_CORE_VKEY__MEDIANEXT__, // 0xa3 - KEY_NEXTSONG
KM_CORE_VKEY__MEDIAPLAY__, // 0xa4 - KEY_PLAYPAUSE
KM_CORE_VKEY__MEDIAPREV__, // 0xa5 - KEY_PREVIOUSSONG
0, // 0xa6 - KEY_STOPCD - ?
0, // 0xa7 - KEY_RECORD - ?
0, // 0xa8 - KEY_REWIND - ?
KM_CORE_VKEY__MEDIA__, // 0xa9 - KEY_PHONE
0, // 0xaa - KEY_ISO - ?
0, // 0xab - KEY_CONFIG - ?
KM_CORE_VKEY__BROWSERHOME__, // 0xac - KEY_HOMEPAGE
KM_CORE_VKEY__BROWSERREFRESH__,// 0xad - KEY_REFRESH
0, // 0xae - KEY_EXIT - ?
0, // 0xaf - KEY_MOVE - ?
0, // 0xb0 - KEY_EDIT - ?
0, // 0xb1 - KEY_SCROLLUP - ?
0, // 0xb2 - KEY_SCROLLDOWN - ?
0, // 0xb3 - KEY_KPLEFTPAREN - ?
0, // 0xb4 - KEY_KPRIGHTPAREN - ?
0, // 0xb5 - KEY_NEW - ?
0, // 0xb6 - KEY_REDO - ?
KM_CORE_VKEY_F13, // 0xb7 - KEY_F13
KM_CORE_VKEY_F14, // 0xb8 - KEY_F14
KM_CORE_VKEY_F15, // 0xb9 - KEY_F15
KM_CORE_VKEY_F16, // 0xba - KEY_F16
KM_CORE_VKEY_F17, // 0xbb - KEY_F17
KM_CORE_VKEY_F18, // 0xbc - KEY_F18
KM_CORE_VKEY_F19, // 0xbd - KEY_F19
KM_CORE_VKEY_F20, // 0xbe - KEY_F20
KM_CORE_VKEY_F21, // 0xbf - KEY_F21
KM_CORE_VKEY_F22, // 0xc0 - KEY_F22
KM_CORE_VKEY_F23, // 0xc1 - KEY_F23
KM_CORE_VKEY_F24, // 0xc2 - KEY_F24
0, // 0xc3 - unassigned
0, // 0xc4 - unassigned
0, // 0xc5 - unassigned
0, // 0xc6 - unassigned
0, // 0xc7 - unassigned
KM_CORE_VKEY__MEDIAPLAY__, // 0xc8 - KEY_PLAYCD
KM_CORE_VKEY__MEDIASTOP__, // 0xc9 - KEY_PAUSECD
0, // 0xca - KEY_PROG3 - ?
0, // 0xcb - KEY_PROG4 - ?
0, // 0xcc - KEY_ALL_APPLICATIONS, KEY_DASHBOARD - ?
0, // 0xcd - KEY_SUSPEND - ?
0, // 0xce - KEY_CLOSE - ?
KM_CORE_VKEY__MEDIAPLAY__, // 0xcf - KEY_PLAY
0, // 0xd0 - KEY_FASTFORWARD - ?
0, // 0xd1 - KEY_BASSBOOST - ?
KM_CORE_VKEY_PRINT, // 0xd2 - KEY_PRINT
0, // 0xd3 - KEY_HP - ?
0, // 0xd4 - KEY_CAMERA - ?
0, // 0xd5 - KEY_SOUND - ?
0, // 0xd6 - KEY_QUESTION - ?
KM_CORE_VKEY__MAIL__, // 0xd7 - KEY_EMAIL
0, // 0xd8 - KEY_CHAT - ?
0, // 0xd9 - KEY_SEARCH - ?
0, // 0xda - KEY_CONNECT - ?
0, // 0xdb - KEY_FINANCE - ?
0, // 0xdc - KEY_SPORT - ?
0, // 0xdd - KEY_SHOP - ?
0, // 0xde - KEY_ALTERASE - ?
0, // 0xdf - KEY_CANCEL - ?
0, // 0xe0 - KEY_BRIGHTNESSDOWN - ?
0, // 0xe1 - KEY_BRIGHTNESSUP - ?
KM_CORE_VKEY__MEDIA__, // 0xe2 - KEY_MEDIA
0, // 0xe3 - KEY_SWITCHVIDEOMODE - ?
0, // 0xe4 - KEY_KBDILLUMTOGGLE - ?
0, // 0xe5 - KEY_KBDILLUMDOWN - ?
0, // 0xe6 - KEY_KBDILLUMUP - ?
0, // 0xe7 - KEY_SEND - ?
0, // 0xe8 - KEY_REPLY - ?
0, // 0xe9 - KEY_FORWARDMAIL - ?
0, // 0xea - KEY_SAVE - ?
0, // 0xeb - KEY_DOCUMENTS - ?
0, // 0xec - KEY_BATTERY - ?
0, // 0xed - KEY_BLUETOOTH - ?
0, // 0xee - KEY_WLAN - ?
0, // 0xef - KEY_UWB - ?
0, // 0xf0 - KEY_UNKNOWN - ?
0, // 0xf1 - KEY_VIDEO_NEXT - ?
0, // 0xf2 - KEY_VIDEO_PREV - ?
0, // 0xf3 - KEY_BRIGHTNESS_CYCLE - ?
0, // 0xf4 - KEY_BRIGHTNESS_AUTO, KEY_BRIGHTNESS_ZERO - ?
0, // 0xf5 - KEY_DISPLAY_OFF - ?
0, // 0xf6 - KEY_WWAN, KEY_WIMAX - ?
0, // 0xf7 - KEY_RFKILL - ?
0, // 0xf8 - KEY_MICMUTE - ?
};
#endif // __KEYCODES_H__

View file

@ -15,11 +15,13 @@ clean:
check:
./build.sh test
devdist: version
python3 setup.py egg_info -b.`TZ=UTC git log -1 --pretty=format:%cd --date=format-local:%Y%m%d%H%M` sdist
dist: clean version
python3 setup.py sdist
# TODO: remove this if when we no longer support Ubuntu 22.04 Jammy
if dpkg --compare-versions "$(lsb_release -r -s)" lt 24.04; then \
python3 setup.py sdist ; \
else \
python3 -m build --outdir build --sdist ; \
fi
deb: dist
@VERSION=$(shell echo `basename dist/*.gz .tar.gz|cut -d "-" -f2` > /tmp/keyman_version)

View file

@ -83,7 +83,14 @@ build_action() {
builder_echo "Building man and help pages"
build_man_and_help_pages
builder_echo "Building keyman-config"
python3 setup.py build
# we use `dpkg --compare-versions` to compare the current Ubuntu version
if dpkg --compare-versions "$(lsb_release -r -s)" lt 24.04; then
# Ubuntu 22.04 Jammy has a buggy version of python3-build which doesn't work
# TODO: remove once we drop support for Ubuntu 22.04 Jammy
python3 setup.py build
else
python3 -m build --outdir build .
fi
}
test_action() {
@ -106,8 +113,7 @@ install_action() {
if [ -n "${SUDO_USER:-}" ]; then
# with sudo install into /usr/local
pip3 install qrcode sentry-sdk
# eventually change this to: pip3 install .
python3 setup.py install
pip3 install .
# install icons
mkdir -p /usr/local/share/keyman/icons
cp keyman_config/icons/* /usr/local/share/keyman/icons
@ -117,9 +123,7 @@ install_action() {
else
# without sudo install into /tmp/keyman (or $DESTDIR)
mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')"
# when we no longer have to support old pip version (python > 3.6) change this to:
# pip3 install --prefix /tmp/keyman .
PYTHONUSERBASE=${DESTDIR:-/tmp/keyman} python3 setup.py install --user
pip3 install --prefix /tmp/keyman .
fi
}

View file

@ -68,10 +68,10 @@
/* copyright label in the Package Information window */
"copyright-label" = "Copyright:";
/* message displayed to alert user to need grant accessibility permission in Preferences, pre-Ventura */
/* message displayed to alert user to grant accessibility permission in Preferences, pre-Ventura */
"privacy-alert-text" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Preferences, Security & Privacy.\nRestart your system.";
/* message displayed to alert user to need grant accessibility permission in Settings, for macOS Ventura and later */
/* message displayed to alert user to grant accessibility permission in Settings, for macOS Ventura and later */
"privacy-alert-text-ventura" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Settings, Privacy & Security.\nRestart your system.";
/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */

View file

@ -67,8 +67,11 @@
/* copyright label in the Package Information window */
"copyright-label" = "Derechos de autor:";
/* message displayed to alert user to need grant accessibility permission */
/* message displayed to alert user to grant accessibility permission in Preferences, pre-Ventura */
"privacy-alert-text" = "Para funcionar correctamente, Keyman requiere las siguientes características de accesibilidad:\nel acceso en Preferencias del Sistema, Seguridad y Privacidad.\nel reinicio del sistema.";
/* message displayed to alert user to grant accessibility permission in Settings, for macOS Ventura and later */
"privacy-alert-text-ventura" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Settings, Privacy & Security.\nRestart your system.";
/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */
"no-keyboard-configured-menu-placeholder" = "(Sin teclado configurado)";

View file

@ -8,7 +8,7 @@ Keyman works, as well as access information about installed keyboards.
## Opening Keyman Configuration
To open Keyman Configuration:
1. ![mac OS menu bar](../../mac_images/menu_bar_keyman.png)
1. ![mac OS menu bar](../../mac_images/menu-bar-with-keyman.png)
Make sure that Keyman for macOS is the active input source (as shown above).
For more information, see [Getting Started with Keyman for macOS](../../start/tutorial).
@ -16,16 +16,15 @@ To open Keyman Configuration:
3. On the Input menu, in the section for Keyman, click **Configuration**. Note: If you just
installed Keyman and the Configuration menu is not available, please restart the computer.
![Keyman Configuration in menu](../../mac_images/keyman_menu_config.png)
![Keyman Configuration in menu](../../mac_images/keyman-configuration-menu.png)
4. The Keyman Configuration window appears:
![Keyman Configuration](../../mac_images/keyman_config.png)
![Keyman Configuration](../../mac_images/keyman-configuration-window.png)
## Using Keyman Configuration
Following are instructions for performing various tasks in the Configuration window:
- [Keyman Configuration - Keyboard Layouts Tab](keyboards)
- [Keyman Configuration - Options Tab](options)
- [Keyman Configuration - Support Tab](support)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -17,7 +17,7 @@ Here's how to download and install a Keyman keyboard within Keyman for macOS:
Note: If you just installed Keyman and the Configuration menu is not available, please
restart the computer.
![Keyman Configuration in menu](../mac_images/keyman_menu_config.png)
![Keyman Configuration in menu](../mac_images/keyman-configuration-menu.png)
3. Select the Keyboard Layouts tab.
4. Click **Download keyboard…**.
@ -39,7 +39,7 @@ window. Here's how:
1. Choose Keyman from the Input menu in the menu bar.
2. Open Keyman Configuration, from the Configuration link under Keyman in the Input menu.
![Keyman Configuration in menu](../mac_images/keyman_menu_config.png)
![Keyman Configuration in menu](../mac_images/keyman-configuration-menu.png)
3. Select the Keyboard Layouts tab.
4. Back in Finder, find the keyboard package on your computer. Keyman keyboard packages

View file

@ -16,7 +16,7 @@ Apple Disk Image (.dmg) file. It should be installed according to the following
4. Keyman should now be an option in the Input Sources menu towards the right hand
side of the menu bar.
![Input Sources menu](../mac_images/input_menu.png)
![Input Sources menu](../mac_images/input-menu-keyman-selected.png)
**Note**: On recent versions of macOS, the first time you enable Keyman by selecting Keyman
from the menu, you will be prompted to give certain permissions to Keyman. It is

View file

@ -9,7 +9,7 @@ installing *Keyman for macOS*, please see: [Install Keyman for macOS](install-ke
## Step 1: Is Keyman active?
![Keyman menu bar](../mac_images/menu_bar_keyman.png)
![Keyman menu bar](../mac_images/menu-bar-with-keyman.png)
Check the menu bar near the date and time to see if Keyman for macOS is
active. If it is active you will see the Keyman icon: ![Keyman icon](../mac_images/icon-keyman.png)
@ -21,38 +21,45 @@ active. If it is active you will see the Keyman icon: ![Keyman icon](../mac_imag
If the Keyman icon is not in the menu bar, choose **Keyman** from the Input menu in the
menu bar and continue on to **Step 4**.
![Input menu](../mac_images/input_menu.png)
![Input menu](../mac_images/input-menu-keyman-selected.png)
The Input menu appears in the menu bar only if you enable at least two input
sources in Keyboard preferences. The Input menu is identified by a flag or character
that represents the input source currently in use.
The Input menu appears in the menu bar only if you enable 'Show Input menu in menu bar'
under Keyboard settings > Input Sources > Edit. The Input source currently in use is represented in the menu bar by an icon or a symbol containing one or more characters.
If Keyman is not in the Input menu, Keyman for macOS is not added as an input source.
If Keyman is not listed in the Input menu, then Keyman for macOS has not been added as an input source.
Follow **Step 3** to add Keyman for macOS as an input source.
## Step 3: Add Keyman for macOS
To add Keyman for macOS as an input source:
1. On the Apple menu, click System Preferences.
1. On the Apple menu, click System Settings
![Apple menu](../mac_images/apple_menu.png)
![Apple menu](../mac_images/open-apple-menu.png)
2. Click Keyboard, select the **Input Sources** tab, and then click ![+](../mac_images/add.png).
2. Click Keyboard, go to the **Text Input** section and then click the Edit... button
3. In the left-hand pane, select **Multiple Languages**, and then select **Keyman** in the
right-hand pane.
![Edit Input Sources](../mac_images/keyboard-settings-annotated.png)
![Add Keyman](../mac_images/add_keyman.png)
3. In the Input Sources window, click the plus sign to add a keyboard
4. Click Add.
![Add Input Source](../mac_images/keyboard-input-sources-annotated.png)
4. In the left-hand pane, select **Multiple Languages**, and then select **Keyman** in the
right-hand pane and click the Add button.
![Add Keyman](../mac_images/keyboard-add-input-source.png)
Now, return to **Step 2**.
## Step 4: Select a Keyman keyboard
On the Input menu, point to Keyboards, and then select a Keyman keyboard. Note: If
the Keyman menu items are not available, please restart the computer.
With Keyman selected in the Input menu, select a Keyman keyboard.
Note: If the Keyman menu items are not available, please restart the computer.
![Select Keyboard](../mac_images/keyman-keyboard-selected.png)
**Tip:**: If there are no Keyman keyboards in the Keyboards menu, you can learn
how to install one here: [Install a keyboard](../start/install-keyboard).

View file

@ -4,9 +4,9 @@ title: Uninstalling Keyman for macOS
To uninstall Keyman for macOS:
1. Choose Apple menu > System Preferences.
1. Choose Apple menu > System Settings.
![Apple menu](../mac_images/apple_menu.png)
![Apple menu](../mac_images/open-apple-menu.png)
2. Click Keyboard, then click Input Sources.

View file

@ -9,4 +9,4 @@ This keyboard is used only to test the crash reporting.
5. Type **Sentry force now**
- With each keystroke, Console messages should indicate that the character is being added to the Easter egg string.
- When the final **w** is typed, a Console message should indicate: **Forcing crash now!**
- After some delay (you may need to switch Keyman off and on again in order for the report to upload), the new crash should appear in the console on sentry.keyman.com.
- After some delay (you may need to switch Keyman off and on again in order for the report to upload), the new crash should appear in the console on Sentry.

View file

@ -135,9 +135,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM);
}
// Temporarily disable predictions if entering a hidden password field
KMManager.setMayPredictOverride(inputType);
if (KMManager.getMayPredictOverride()) {
// Temporarily disable predictions on certain fields (e.g. hidden password field or numeric)
KMManager.setPredictionsSuspended(inputType, KeyboardType.KEYBOARD_TYPE_SYSTEM);
if (KMManager.getPredictionsSuspended(KeyboardType.KEYBOARD_TYPE_SYSTEM)) {
KMManager.setBannerOptions(false);
} else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){
// Check if predictions needs to be re-enabled per Settings preference

View file

@ -15,5 +15,6 @@ builder_parse "$@"
if builder_start_action test; then
./build/test/test.sh
./build/publish-minimum-versions.sh test
builder_finish_action success test
fi

View file

@ -43,8 +43,8 @@ KEYMAN_MIN_VERSION_ANDROID_SDK=21
KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER=noble # Ubuntu 24.04 Noble
# Data versions -- see resources/standards-data/readme.md
KEYMAN_VERSION_CLDR=45 # LDML Keyboards version
KEYMAN_VERSION_ISO639_3=2024-05-22 # Date of last import
KEYMAN_VERSION_LANGTAGS=2025-02-18 # Date of last import
KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY=2024-05-16 # Date of last import
KEYMAN_VERSION_UNICODE=16.0.0 # UCD + related data
KEYMAN_VERSION_CLDR=45 # LDML Keyboards version
KEYMAN_VERSION_ISO639_3=2024-05-22 # Date of last import
KEYMAN_VERSION_LANGTAGS=2025-02-18 # Date of last import
KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY=2025-03-10 # Date from first line of language-subtag-registry
KEYMAN_VERSION_UNICODE=16.0.0 # UCD + related data

View file

@ -1,10 +1,5 @@
#!/usr/bin/env bash
set -e
set -u
#
# Usage: publish-minimum-versions.sh
# Usage: ./publish-minimum-versions.sh build test
#
# Parses minimum-versions.inc.sh to update template file /docs/minimum-versions.md.in.
# If /docs/minimum-versions.md is changed, commit and push changes
@ -13,12 +8,17 @@ set -u
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/build-utils.sh"
. "${THIS_SCRIPT%/*}/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh"
function generate_table {
builder_describe "Build and test minimum-versions" \
clean build test
builder_parse "$@"
generate_table() {
echo ""
echo "## minimum-versions.inc.sh Variables"
echo ""
@ -37,5 +37,34 @@ function generate_table {
echo "> ### This file is auto-generated by publish-minimum-versions.sh. Do not modify"
}
cp "$KEYMAN_ROOT/docs/minimum-versions.md.in" "$KEYMAN_ROOT/docs/minimum-versions.md"
generate_table >> "$KEYMAN_ROOT/docs/minimum-versions.md"
do_build() {
cp "$KEYMAN_ROOT/docs/minimum-versions.md.in" "$KEYMAN_ROOT/docs/minimum-versions.md"
generate_table >> "$KEYMAN_ROOT/docs/minimum-versions.md"
}
do_test() {
# Verify that minimum-versions.md matches the generated data
local new_table="$(mktemp)"
cp "$KEYMAN_ROOT/docs/minimum-versions.md.in" "$new_table"
generate_table >> "$new_table"
diff --suppress-common-lines -yw "$KEYMAN_ROOT/docs/minimum-versions.md" "$new_table" || ( \
rm "$new_table"
builder_die "minimum-versions.md is not up to date. Please run 'publish-minimum-versions.sh build'"
)
rm "$new_table"
# TODO: this should have a test for each of the known machine-extractable versions.
# language-subtag-registry
local NEW_KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY="$(head ../standards-data/language-subtag-registry/language-subtag-registry -n 1 | cut -d" " -f 2 -)"
if [[ "$NEW_KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY" != "$KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY" ]]; then
builder_die "language-subtag-registry is version '$NEW_KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY' but minimum-version.inc.sh has version '$KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY'"
fi
}
builder_run_action clean rm -f "$KEYMAN_ROOT/docs/minimum-versions.md"
builder_run_action build do_build
builder_run_action test do_test

View file

@ -31,7 +31,7 @@ and are named e.g. `keyman-core-ci:default`.
The versions can be changed, e.g.
```shell
resources/docker-images/build.sh --ubuntu-version jammy --node 20
resources/docker-images/build.sh --distro-version jammy --node 20
```
This will create an image named e.g. `keyman-core-ci:jammy-node20`.

View file

@ -8,6 +8,8 @@ LABEL org.opencontainers.image.authors="SIL Global."
LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git"
LABEL org.opencontainers.image.title="Keyman Build Base Image"
SHELL ["/bin/bash", "-c"]
# We will switch to a build user after some installation
USER root
ENV HOME=/home/build

View file

@ -9,7 +9,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
################################ Main script ################################
. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
. "${KEYMAN_ROOT}/resources/docker-images/docker-build.inc.sh"
builder_describe \
"Build docker images" \
@ -18,7 +19,8 @@ builder_describe \
":core" \
":linux" \
":web" \
"--distro=DISTRO The distribution to use for the base image (debian or ubuntu, default: ubuntu)" \
"--distro=DISTRO The distribution to use for the base image "\
" (debian or ubuntu, default: ubuntu)" \
"--distro-version=DISTRO_VERSION The Ubuntu/Debian version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \
"--no-cache Force rebuild of docker images" \
"build Build docker images" \
@ -97,7 +99,7 @@ build_action() {
docker build ${OPTION_NO_CACHE:-} -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" .
# If the user didn't specify particular versions we will additionaly create an image
# with the tag 'default'.
if _is_default_values; then
if is_default_values; then
builder_echo debug "Setting default tag for ${platform}"
docker build -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" .
fi
@ -119,7 +121,6 @@ _convert_parameters_to_build_args
if builder_has_action build; then
build_action base
BASE_VERSION="${build_version}"
builder_run_action build:android build_action android
builder_run_action build:core build_action core
builder_run_action build:linux build_action linux

View file

@ -0,0 +1,61 @@
# shellcheck shell=bash
# no hashbang for .inc.sh
. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh"
_add_build_args() {
local var=$1
local default_var=$2
local name=$3
local value
value="${!var:=${!default_var:-}}"
build_args+=(--build-arg="${var}=${value}")
if [[ -n "${build_version:-}" ]]; then
build_version="${build_version}-${name:-}${value}"
else
build_version="${name}${value}"
fi
}
# Convert the parameters passed in to the script to build arguments
# that form the docker image name
# Returns:
# build_args: array of build arguments
# build_version: version string of the docker image
convert_parameters_to_args() {
build_args=()
build_version=
local required_node_version keyman_default_distro
# shellcheck disable=SC2034
required_node_version="$(_print_expected_node_version)"
# shellcheck disable=SC2034
keyman_default_distro="ubuntu"
_add_build_args DISTRO keyman_default_distro ""
_add_build_args DISTRO_VERSION KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER ""
_add_build_args JAVA_VERSION KEYMAN_VERSION_JAVA java
_add_build_args REQUIRED_NODE_VERSION required_node_version node
_add_build_args REQUIRED_EMSCRIPTEN_VERSION KEYMAN_MIN_VERSION_EMSCRIPTEN emsdk
if [[ -n "${BASE_VERSION:-}" ]]; then
build_args+=(--build-arg="BASE_VERSION=${BASE_VERSION}")
else
build_args+=(--build-arg="BASE_VERSION=${build_version}")
fi
}
check_for_default_values() {
if [[ -z "${DISTRO_VERSION:-}" ]] && [[ -z "${JAVA_VERSION:-}" ]]; then
is_default_values=true
else
is_default_values=false
fi
}
# Check if a specific Ubuntu or Java version is specified
is_default_values() {
${is_default_values}
}

View file

@ -7,6 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh"
. "${KEYMAN_ROOT}/resources/docker-images/docker-build.inc.sh"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
################################ Main script ################################
@ -22,6 +23,17 @@ builder_describe \
builder_parse "$@"
check_for_default_values
convert_parameters_to_args
if is_default_values; then
image_version=default
build_dir=default
else
image_version=${build_version:-}
build_dir="${DISTRO:-}-${DISTRO_VERSION:-}"
fi
run_android() {
docker run -it --rm -v "${KEYMAN_ROOT}":/home/build/build \
-v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \

View file

@ -1,4 +1,4 @@
File-Date: 2024-05-16
File-Date: 2025-03-10
%%
Type: language
Subtag: aa
@ -5950,6 +5950,7 @@ Added: 2009-07-29
%%
Type: language
Subtag: bql
Description: Karian
Description: Bilakura
Added: 2009-07-29
%%
@ -9083,6 +9084,7 @@ Scope: collection
%%
Type: language
Subtag: daz
Description: Moi-Wadea
Description: Dao
Added: 2009-07-29
%%
@ -9290,6 +9292,8 @@ Type: language
Subtag: dek
Description: Dek
Added: 2009-07-29
Deprecated: 2024-12-12
Preferred-Value: sqm
%%
Type: language
Subtag: del
@ -14082,6 +14086,12 @@ Added: 2009-07-29
Macrolanguage: hmn
%%
Type: language
Subtag: hnm
Description: Hainanese
Added: 2024-12-12
Macrolanguage: zh
%%
Type: language
Subtag: hnn
Description: Hanunoo
Added: 2009-07-29
@ -21081,6 +21091,12 @@ Description: Laua
Added: 2009-07-29
%%
Type: language
Subtag: luh
Description: Leizhou Chinese
Added: 2024-12-12
Macrolanguage: zh
%%
Type: language
Subtag: lui
Description: Luiseno
Added: 2005-10-16
@ -22850,6 +22866,8 @@ Added: 2009-07-29
%%
Type: language
Subtag: mmi
Description: Hember Avu
Description: Amben
Description: Musar
Added: 2009-07-29
%%
@ -26641,6 +26659,8 @@ Type: language
Subtag: nte
Description: Nathembo
Added: 2009-07-29
Deprecated: 2024-12-12
Preferred-Value: eko
%%
Type: language
Subtag: ntg
@ -32147,6 +32167,12 @@ Description: Sajau Basap
Added: 2009-07-29
%%
Type: language
Subtag: sjc
Description: Shaojiang Chinese
Added: 2024-12-12
Macrolanguage: zh
%%
Type: language
Subtag: sjd
Description: Kildin Sami
Added: 2009-07-29
@ -41302,6 +41328,11 @@ Description: Aluo
Added: 2009-07-29
%%
Type: language
Subtag: ynb
Description: Yamben
Added: 2025-02-06
%%
Type: language
Subtag: ynd
Description: Yandruwandha
Added: 2009-07-29
@ -43616,6 +43647,14 @@ Preferred-Value: hks
Prefix: sgn
%%
Type: extlang
Subtag: hnm
Description: Hainanese
Added: 2024-12-12
Preferred-Value: hnm
Prefix: zh
Macrolanguage: zh
%%
Type: extlang
Subtag: hos
Description: Ho Chi Minh City Sign Language
Added: 2009-07-29
@ -43958,6 +43997,14 @@ Prefix: lv
Macrolanguage: lv
%%
Type: extlang
Subtag: luh
Description: Leizhou Chinese
Added: 2024-12-12
Preferred-Value: luh
Prefix: zh
Macrolanguage: zh
%%
Type: extlang
Subtag: lvs
Description: Standard Latvian
Added: 2010-03-11
@ -44393,6 +44440,14 @@ Prefix: ar
Macrolanguage: ar
%%
Type: extlang
Subtag: sjc
Description: Shaojiang Chinese
Added: 2024-12-12
Preferred-Value: sjc
Prefix: zh
Macrolanguage: zh
%%
Type: extlang
Subtag: slf
Description: Swiss-Italian Sign Language
Added: 2009-07-29
@ -44844,6 +44899,11 @@ Description: Bangla
Added: 2005-10-16
%%
Type: script
Subtag: Berf
Description: Beria Erfe
Added: 2025-02-06
%%
Type: script
Subtag: Bhks
Description: Bhaiksuki
Added: 2015-07-24
@ -47919,6 +47979,12 @@ Comments: Written standard developed by Romanilha in 1853 and used by
dóu Po, Escolo Gaston Febus, and others
%%
Type: variant
Subtag: hanoi
Description: The Hà Nội variant of Vietnamese
Added: 2025-03-10
Prefix: vi
%%
Type: variant
Subtag: hepburn
Description: Hepburn romanization
Added: 2009-10-01
@ -47949,6 +48015,12 @@ Added: 2017-03-14
Prefix: eo
%%
Type: variant
Subtag: huett
Description: The Huế (province Thừa Thiên) variant of Vietnamese
Added: 2025-03-10
Prefix: vi
%%
Type: variant
Subtag: ijekavsk
Description: Serbian with Ijekavian pronunciation
Prefix: sr
@ -47991,6 +48063,16 @@ Added: 2008-10-14
Prefix: kw
%%
Type: variant
Subtag: kleinsch
Description: Kleinschmidt orthography
Description: Allattaasitaamut
Added: 2024-07-20
Prefix: kl
Prefix: kl-tunumiit
Comments: Orthography for Greenlandic designed by Samuel Kleinschmidt,
used from 1851 to 1973.
%%
Type: variant
Subtag: kociewie
Description: The Kociewie dialect of Polish
Added: 2014-11-27
@ -48009,7 +48091,16 @@ Type: variant
Subtag: laukika
Description: Classical Sanskrit
Added: 2010-07-28
Deprecated: 2024-06-08
Prefix: sa
Comments: Preferred tag is cls
%%
Type: variant
Subtag: leidentr
Description: Ancient Egyptian in Leiden Unified Transliteration
Added: 2025-02-06
Prefix: egy
Comments: Recommended by the International Association of Egyptologists
%%
Type: variant
Subtag: lemosin
@ -48056,6 +48147,19 @@ Comments: Russian orthography as established by the 1917/1918
orthographic reforms
%%
Type: variant
Subtag: mdcegyp
Description: Ancient Egyptian hieroglyphs encoded in Manuel de Codage
Added: 2025-02-06
Prefix: egy
%%
Type: variant
Subtag: mdctrans
Description: Ancient Egyptian transliteration encoded in Manuel de
Codage
Added: 2025-02-06
Prefix: egy
%%
Type: variant
Subtag: metelko
Description: Slovene in Metelko alphabet
Added: 2012-06-27
@ -48243,6 +48347,12 @@ Prefix: rm
Comments: Supraregional Romansh written standard
%%
Type: variant
Subtag: saigon
Description: The Sài Gòn variant of Vietnamese
Added: 2025-03-10
Prefix: vi
%%
Type: variant
Subtag: scotland
Description: Scottish Standard English
Added: 2007-08-31
@ -48385,9 +48495,11 @@ Type: variant
Subtag: vaidika
Description: Vedic Sanskrit
Added: 2010-07-28
Deprecated: 2024-06-08
Prefix: sa
Comments: The most ancient dialect of Sanskrit used in verse and prose
composed until about the 4th century B.C.E.
Comments: Preferred tag is vsn
%%
Type: variant
Subtag: valbadia

View file

@ -0,0 +1,72 @@
# Context State Management
## The `OutputTarget` Abstraction
The `OutputTarget` abstraction and its associated types and classes exist to facilitate handling different types of context sources within Keyman Engine for Web through a common interface. In essence, any implementing type is valid within the engine as a "target" for "output" from any existing keyboard supported by the engine. Through 18.0, only JS-based keyboards were supported due to lack of implementation of alternate keystroke-processing engines.
At the most basic level, the abstraction is defined at [web/src/engine/keyboard/outputTarget.interface.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/outputTarget.interface.ts). The methods defined therein support properties and methods for fetching, setting, and manipulating deadkey markers, text-selection, and text within whatever context source it represents. JS-keyboard keystroke processing directly uses these methods during operation.
- `epic/web-core` note: moved to `outputTarget.ts` in the same folder, dropping the `.interface` component.
The base implementation for this type may be found at [web/src/engine/js-processor/src/outputTarget.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/outputTarget.ts). This implementation provides common support for deadkey tracking, matching, and handling - the same strategy is used regardless of context-source type. A few additional methods are provided to assist with comparison and contrast between two different context states and restoration of a prior context state.
- `epic/web-core` note: renamed `OutputTargetBase`, moved to `outputTargetBase.ts` in the same folder
### Deadkey management
Specifics for the implementation of JS-keyboard deadkeys can be found here: https://github.com/keymanapp/keyman/blob/b4df4ab80862bc90da42bcdbd333df0a14da01ca/web/src/engine/js-processor/src/deadkeys.ts#L2-L6
- `epic/web-core` note: unaltered.
`ordinal`: if two deadkeys are in the same "virtual" position, this field resolves which came first.
`matched`: used during keyboard-rule processing.
Note that we do NOT actually insert the deadkeys into the raw text-context! Web's pattern here is different than that of Keyman Core.
## Manipulating context-states
Initially used to support predictive-text, the `Transform` type aims to encapsulate the minimal information necessary to transition from one context-state to another. It is both selection-agnostic and deadkey-agnostic. `Transform`s are used both by predictive-text and by the webview-embedded build of the engine in order to succinctly communicate the data needed to update context upon receiving keystrokes.
- `Transform`'s specification may be found at [common/web/types/src/lexical-model-types.ts](https://github.com/keymanapp/keyman/blob/master/common/web/types/src/lexical-model-types.ts).
`Transform`s consist of three values:
- `deleteLeft` - the number of codepoints prior to the caret/selection that should be deleted
- `insert` - the text to insert at the caret and/or replace currently-selected text
- `deleteRight` - the number of codepoints _after_ the caret/selection that should be deleted
- Note that `deleteRight` does not currently see actual use due to iOS platform limitations.
This type may then be used as an argument to `OutputTarget.apply()` (defined on `OutputTarget` (`epic/web-core`: `OutputTargetBase`)) to update any context source accordingly.
It is possible to determine the `Transform` needed to transition from one `OutputTarget` to another using `OutputTarget.buildTransformFrom` (defined on `OutputTarget` (`epic/web-core`: `OutputTargetBase`)).
### The `Mock` - representing context-state
The comparison and contrast methods mentioned above for `OutputTarget` are of particular use for predictive text, which usually operates with a headless implementation of the type, termed a `Mock`. This class may be found in [web/src/engine/js-processor/src/mock.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/mock.ts). It is a fully-featured implementation of the `OutputTarget` interface.
- `epic/web-core` note: unaltered.
It is possible to make a `Mock`-based clone of any `OutputTarget`-derived type - a feature leveraged significantly within the inner workings of Keyman Engine for Web. As JS keyboards can have side effects beyond text-manipulation, predictive text generally operates by first _cloning_ the "true" context source. `Mock`s are also used when saving context states within the engine for later reference and/or reuse - a feature also utilized significantly for multitap support.
`Mock`s can also easily be constructed from scratch for a simple string. Optionally, caret position or selection data may be specified at construction time as well. `epic/web-core`: in theory, this should make them easy to utilize for integration with Keyman Core.
### The `Transcription` - representing context-state transitions
The `Transcription` class (defined within [web/src/engine/js-processor/src/outputTarget.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/outputTarget.ts)) is the type within the engine that most closely matches a sense of transition in context state. These are generally constructed by comparing two `OutputTarget` instances to each other via `OutputTarget.buildTranscriptionFrom`, with the base instance corresponding to the "new" state and the first parameter matching the original state before transition.
- `epic/web-core` note: moved to `outputTargetBase.ts` in the same folder; the method is on `OutputTargetBase`.
`Transcription`s are granted unique identifiers and are used within the engine for preservation of recent context states. These identifiers are currently generated within the class's constructor and are internally set. When predictive-text generates new suggestions or a multitap needs to revert to a prior context, both will use the `Transcription`'s unique identifier in order to find the corresponding context state and leverage it as needed for their operations.
Important fields:
- `keystroke` - the keystroke that triggered the context change corresponding to this `Transcription`
- `transform` - the direct effects of the keystroke
- This uses a specialized variant that also notes if the transition destroyed previously-existing selected text.
- `preInput` - the state of the context immediately before the keystroke was processed
Note that the transition metadata does not include deadkeys generated by its triggering `keystroke`. The decision was made long ago to forgo directly recording deadkey changes when recording `Transcriptions`, as any operation that restores an old context also seeks to apply deadkey-destroying operations immediately afterward. Should we ever need to do so, PR #1611 contains code that was originally designed for actively detecting and recording deadkey transition data in `Transcription`s.
A cache of recent context-state transitions is stored at `keyman.core.contextCache`, with `keyman.core` being an instance of `InputProcessor` ([web/src/engine/main/src/headless/inputProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/main/src/headless/inputProcessor.ts)), the component responsible for linking keystroke processing with predictive-text support and restoration of context-state for multitap-generated keystrokes.
### JS-keyboard keystroke processing
For JS-keyboard keystroke processing, a `Mock` clone of the context is generated before any actual keyboard rule checks are applied. This provides a clear "before" state (eventually saved at `Transcription.preInput`) useful for determining the scope of the keystroke's changes once processing is completed via `buildTranscriptionFrom`.
Once keystroke processing is completed by a JS-keyboard, the JS-processor constructs a `RuleBehavior` object describing all primary and side effects of the keystroke. Defined at [web/src/engine/js-processor/src/ruleBehavior.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/ruleBehavior.ts), all of its fields aside from `transcription` are specific to JS-keyboard side effects, some of which do need special handling and support outside of the keystroke processor. None of these side effects apply for common-case keystrokes and so have default handling in place within the engine for cases where they are not needed.
---
In case referenced classes/files have moved: this doc was last updated in 18.0-beta, based upon commit d1e45a0df49f81597887577a8c83af36d4a85283

View file

@ -0,0 +1,81 @@
# Keystroke Processing
In addition to handling keystroke events produced from hardware keyboards, Keyman Engine for Web manages an on-screen keyboard that can also be used to generate such events. This document will enumerate the various components of data management and control flow used to handle and process keystrokes.
## Input: KeyEvent - the pre-processed keystroke
Defined at [web/src/engine/keyboard/src/keyEvent.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/keyEvent.ts), the `KeyEvent` is used to represent incoming _and_ potential keystrokes. This type is what the JS-keyboard processor references when evaluating keyboard rules during its keystroke processing. For versions of the engine that support AltGr aliasing, such aliasing will be applied during generation of `KeyEvent` objects.
Note that for predictive-text's fat-finger correction functionality, the engine will also generate versions of this type for nearby but _unpressed_ keys into the keystroke processing engine as well in order to facilitate predictions that follow context manipulations that could have resulted from specialized rules or reorders on neighboring keys. `Mock`-cloned copies of the active context-state will be leveraged to prevent unwanted manipulation of the true context source.
### `isSynthetic`
`isSynthetic` should be set to `true` if generated through interaction with an on-screen-keyboard or for fat-finger simulation. It should only be set to `false` if sending the basic key-event data through to the destination, without rule processing, leads to default handling picking up the slack. Browsers provide default handling of keystrokes not directly defined within keyboards, but this is not available for keystrokes against the engine's OSK without internal support.
For other applications of `false`, consider a hardware K_ENTER keystroke - this could be interpreted as a "submit form" / "search" command depending on the context.
When `true`, the keystroke processor will need to simulate the results of "standard" default key-event handling, as nothing else can handle it. At present, we don't attempt to force / synthesize an event that could trigger the "true" default.
### `on('keyevent')`
On-screen keyboard modules and hardware-keyboard event interception modules will produce instances of `KeyEvent` as noted above. Once this is completed, both module types will _signal_ the keystroke event by raising an event with ID `keyevent` and provide the `KeyEvent` instance as its first parameter. The main module of the engine will then forward that `KeyEvent` object to the input processing pipeline via `InputProcessor.processKeyEvent` (see below).
## Output: RuleBehavior
Once keystroke processing is completed, the outward-facing components of the engine expect to receive a `RuleBehavior` object describing all primary and side effects of the keystroke. Defined at [web/src/engine/js-processor/src/ruleBehavior.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/ruleBehavior.ts), all of its fields aside from `transcription` are specific to existing JS-keyboard side effects. (Certain Keyman language features can make permanent side-effect changes to state that should only be persisted for the "true" keystroke - not for any predictive-text 'alternative' keys.)
See also: [context-state-management.md](context-state-management.md#js-keyboard-keystroke-processing)
## Processing Pipeline
### Input Processor
The "first stop" for incoming keystrokes is the `InputProcessor`, found at [web/src/engine/main/src/headless/inputProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/main/src/headless/.inputProcessor.ts), through its `processKeyEvent` method. This class manages higher-level functionality triggered by keystroke events while deferring actual interpretation of the incoming keystroke further down the line. Of particular note is that it also handles control-flows that require restoration of previously-occuring contexts.
This class is the connection point for generating prediction requests and receiving corresponding suggestions. In order to facilitate higher-quality predictive-text when enabled, the `InputProcessor` will _also_ generate and trigger processing for nearby keys. This process allows transforms, reorders, and KMN keyboard rules to take effect and be used as alternative context roots for predictions. These are generally run against `Mock`-based clones of the true context source and are additionally prevented from triggering long-term side-effects, such as changes to KMN-keyboard variable stores, by only calling `RuleBehavior.finalize` for the true input keystroke's result object.
Keys generated by OSK multitap need special handling here as well; they should always be applied to the context state as it existed at the time of the initial tap. To facilitate this, the `InputProcessor` will directly rewind the active context-source to match the corresponding context state before requesting that the `KeyEvent` be processed.
Due to the role `InputProcessor` plays, it is intended that _all_ keystroke processing variants leverage this class in order to maintain integration with such higher-level functionality.
#### The "context cache"
As both predictive-text and multitaps require the ability to reference old context states, the `InputProcessor` maintains a cache of recently-occurring context-state transitions - of [`Transcription`](./context-state-management.md#the-transcription---representing-context-state-transitions)s. This cache is kept reasonably small to prevent memory bloat, so functionality referencing the cache should verify the results of any related lookup operation.
Thanks to the `Transcription.preInput`, it is possible to rewind the current context to its state at the time the `Transcription` was generated - a feature quite useful for applying predictive-text suggestions and for handling multitap keystrokes. Predictive-text reversions may even re-use cached predictions associated with the `Transcription`, avoiding the need to recompute them.
#### LanguageProcessor
The `LanguageProcessor` class, whose instances are owned within the engine by `InputProcessor`, are the anchor point for connecting rest of Keyman Engine for Web with the predictive-text web-worker and its functionalities.
### KeyboardProcessor
- `epic/web-core` note: renamed to `JSKeyboardProcessor`.
Found within [web/src/engine/js-processor/src/keyboardProcessor.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/js-processor/src/keyboardProcessor.ts), the `KeyboardProcessor` class is the top-level class responsible for processing keystrokes targeting JS-based keyboards. It is also responsible for providing default output for OSK-based keystrokes not explicitly handled by the keyboard that should receive browser-default handling (when JS-based keyboards are active).
- `epic/web-core` note: renamed to `JSKeyboardProcessor`, moved to `jsKeyboardProcessor.ts` within the same folder.
This is likely the top-level class that `epic/web-core` would likely need to replace wholesale with a Keyman Core wrapper - hopefully with a mutual abstraction providing a similar interface for both. (`KeyboardProcessor` was originally named after Core's equivalent class, as it pretty much fills the same role.) `InputProcessor` could then leverage that mutual abstraction for its functions without needing a specialized version per keystroke interpreting engine.
#### The `processKeystroke` method
https://github.com/keymanapp/keyman/blob/b4df4ab80862bc90da42bcdbd333df0a14da01ca/common/web/keyboard-processor/src/text/keyboardProcessor.ts#L217
The method linked above is the primary entrypoint for rule processing of individual, preprocessed `KeyEvent`s. There's a little infrastructure between it and the actual keyboard-script for JS keyboards, but it is the sole entry point in `KeyboardProcessor` responsible for keyboard rule evaluation.
- `epic/web-core` note: renamed to `JSKeyboardProcessor`.
#### JS-keyboard interfacing
Certain Keyman language features can make permanent side-effect changes to state. In order to prevent these from taking place for every keystroke, the method that interfaces with JS keyboards - `KeyboardInterface.process` - saves the context state (as a `Mock`) and current variable store values, then prepares a fresh `RuleBehavior` instance, before passing control off to the keyboard's backing script. (Note that `KeyboardInterface` itself primarily consists of keyboard-script API called by JS-keyboard script.)
- `epic/web-core` note: renamed to `JSKeyboardInterface`.
A few of the keyboard-script API methods will mark `RuleBehavior` properties directly when called, but the bulk of its data will be set once the keyboard-script returns control to Keyman Engine for Web. At this time, variable store values will also be reverted to prevent possible cross-contamination effects when predictive text is active - they're reapplied later if `RuleBehavior.finalize` is leveraged on the resulting instance. Components documented in [context-state-management.md](./context-state-management.md) are then leveraged to determine the total change to context caused by the keystroke.
Note that for JS-keyboards, in 18.0 and before the true keystroke is processed against the _true_ context state, not a cloned copy, and thus its changes are applied immediately.
#### Keystroke-default emulation
Note that browser-default keystroke processing is defined within the `DefaultRules` class found at [web/src/engine/keyboard/src/defaultRules.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/defaultRules.ts). It currently produces a (possibly `null`) string output rather than a `RuleBehavior`, but the latter can be easily constructed based on the returned string and the existing context state.
- For `epic/web-core`, it may be wise to spin the `DefaultRules` component off as its own processor, serving as a backup for _all_ keystroke processing variants.
----
In case referenced classes/files have moved: this doc was last updated in 18.0-beta, based upon commit d1e45a0df49f81597887577a8c83af36d4a85283

View file

@ -92,6 +92,15 @@ export default class KeyEvent implements KeyEventSpec {
// Holds a generated fat-finger distribution (when appropriate)
keyDistribution?: KeyDistribution;
/**
* Information provided here may be used for breadcrumbs regarding internal engine errors and warnings.
* It should be scrubbed of info that could be used to directly reproduce what the user has typed.
*
* One great use is to identify scenarios such as how the current state of an OSK gesture was reached and
* culminated in this key event.
*/
inputBreadcrumb?: string;
/**
* The device model for web-core to follow when processing the keystroke.
*/

View file

@ -120,7 +120,9 @@ export class InputProcessor {
if this is a purely-layer-switching multitap. (#11230)
*/
} else {
console.warn('The base context for the multitap could not be found');
console.warn('The base context for the multitap could not be found!\n\n' + this.contextCache.buildLog());
// would be lovely to report a desire for gesture debug output
// maybe add something to RuleBehavior?
}
}
@ -242,7 +244,7 @@ export class InputProcessor {
// Multitaps operate in part by referencing 'committed' Transcriptions to rewind
// the context as necessary.
this.contextCache.save(ruleBehavior.transcription);
this.contextCache.save(ruleBehavior.transcription); //
// The keyboard may want to take an action after all other keystroke processing is
// finished, for example to switch layers. This action may not have any output

View file

@ -68,7 +68,7 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {
}
public unloadModel() {
if(!this.isActive) {
if(!this.canEnable) {
return;
}

View file

@ -34,4 +34,11 @@ export class TranscriptionCache {
this.map.delete(this.map.keys().next().value);
}
}
public buildLog(): string {
const entries = [...this.map.entries()];
return entries
.map(([key, entry]) => `Context state ${key}'s keystroke:\n${entry.keystroke.inputBreadcrumb ?? ''}`)
.join('\n');
}
}

View file

@ -33,6 +33,9 @@ export class GestureStageReport<Type, StateToken = any> {
*/
public readonly sources: GestureSourceSubview<Type>[];
public get currentSourceIds(): string[] {
return this.sources.map((entry) => entry.identifier);
}
public readonly allSourceIds: string[];
constructor(selection: MatcherSelection<Type, StateToken>, gestureSetId: string) {
@ -66,6 +69,32 @@ export class GestureStageReport<Type, StateToken = any> {
this.allSourceIds = matcher?.allSourceIds || [];
}
/**
* Returns the time interval covered by recorded touch events during this stage and its
* matched gesture model.
*
* May return `undefined` if no actual touches occurred during this time.
*/
public get interval() {
if(this.sources.length == 0) {
return undefined;
}
const timestamps = this.sources.map((entry => {
return {
start: entry.path.stats.initialSample.t,
end: entry.path.stats.lastTimestamp
}
}));
return timestamps.reduce((prior, current) => {
return {
start: prior.start > current.start ? current.start : prior.start,
end: prior.end < current.end ? current.end : prior.end
}
}, { start: Number.POSITIVE_INFINITY, end: Number.NEGATIVE_INFINITY });
}
}
interface PushConfig<Type> {
@ -83,10 +112,30 @@ interface PopConfig {
export type ConfigChangeClosure<Type> = (configStackCommand: PushConfig<Type> | PopConfig) => void;
interface EventMap<Type, StateToken> {
/**
* This event is raised when a new, valid gesture component for the sequence has been matched and
* committed.
*
* It is always called synchronously with the match, before any further matches are possible.
* - The sole exception to the "synchronous" aspect is if this gesture enacted a 'pushed' state
* that has active 'nested' gestures that need to be sustained.
* - Even in the case above, no further 'stage' events for this GestureSequence will occur.
* @param stageReport
* @param changeConfiguration
* @returns
*/
stage: (
stageReport: GestureStageReport<Type, StateToken>,
changeConfiguration: ConfigChangeClosure<Type>
) => void;
/**
* This event is raised when the sequence of connected gesture components has reached its end,
* with no further continuation possible.
*
* It is called synchronously with the end of the gesture sequence, though it may be 'kept alive'
* when sustaining 'nested' gestures dependent upon a 'pushed' state.
* @returns
*/
complete: () => void;
}
@ -104,7 +153,10 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
private pushedSelector?: MatcherSelector<Type, StateToken>;
private gestureConfig: GestureModelDefs<Type, StateToken>;
private markedComplete: boolean = false;
private completionTime: number;
private get markedComplete(): boolean {
return this.completionTime !== undefined;
}
// Note: the first stage will be available under `stageReports` after awaiting a simple Promise.resolve().
constructor(
@ -119,6 +171,8 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
this.selector = selector;
this.selector.on('rejectionwithaction', this.modelResetHandler);
this.once('complete', () => {
this.completionTime = performance.now();
if(this.pushedSelector) {
// The `popSelector` method is responsible for triggering cascading cancellations if
// there are nested GestureSequences.
@ -200,6 +254,18 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
return potentialMatches;
}
/**
* Processes newly-matched gesture stages and ensures that the corresponding state machines are
* set in motion. Also processes the case where there is no longer a possible matching gesture stage.
* - The gesture sources (touchpoints) will no longer be tracked and signal their paths' `complete` events.
* - The gesture models for any potential followups to the completed stage will be initialized.
* - If there are none, the `complete` event will be triggered.
* - The `stage` event (when there is a matched stage) will be triggered before the corresponding `complete`.
* - It will occur synchronously unless this sequence is kept alive, with no further stages possible and
* pending completion, to 'sustain' nested gestures.
* @param selection Data about the matching gesture and match type.
* @returns
*/
private readonly selectionHandler = async (selection: MatcherSelection<Type, StateToken>) => {
const gestureSet = this.pushedSelector?.baseGestureSetId || this.selector?.baseGestureSetId;
const matchReport = new GestureStageReport<Type, StateToken>(selection, gestureSet);
@ -222,13 +288,13 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
if(!selection.result.matched) {
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
return;
}
}
// Only source of async: actually completing a gesture that caused an alt-state.
if(actionType == 'complete' && this.touchpointCoordinator && this.pushedSelector) {
// Cascade-terminade all nested selectors, but don't remove / pop them yet.
// Their selection mode remains valid while their gestures are sustained.
@ -349,7 +415,6 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
} else {
// Any extra finalization stuff should go here, before the event, if needed.
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
}
@ -385,7 +450,6 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
const sources = this.stageReports[this.stageReports.length - 1].sources;
sources.forEach((src) => src.baseSource.isPathComplete || src.baseSource.terminate(true));
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
}
@ -393,6 +457,48 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
public toJSON(): any {
return this.stageReports;
}
/**
* Prints out a 'trace' of the gesture sequence in reverse chronological order. Each frame includes:
* - the unique touch-identifiers active for each stage
* - the matched gesture ID
* - the time interval spanned by the frame (if at least one touch was active during it)
* @returns
*/
public trace(padding?: string): string {
padding ||= ' ';
const logTouches = (idArr: string[]) => {
if(idArr.length == 0) {
return `<empty>`;
} else if(idArr.length == 1) {
return idArr[0];
} else {
return idArr.join(', ');
}
}
const logInterval = (interval: {start: number, end: number}) => {
if(!interval) {
return '';
}
if(interval.start == interval.end) {
return `@ ${interval.start.toFixed(2)} ms`;
} else {
return `from ${interval.start.toFixed(2)} ms - ${interval.end.toFixed(2)} ms`
}
};
// Sanitizes the actual keystrokes involved but preserves a decent amount of gesture diagnostic information.
let logs = this.stageReports.map((entry) => padding + `${logTouches(entry.currentSourceIds)} - ${entry.matchedId} ${logInterval(entry.interval)}`)
// Orders each stage from most recent to least recent
.reverse();
if(this.markedComplete) {
logs.unshift(padding + `complete @ ${this.completionTime}`);
}
return logs.join('\n');
}
}
export function modelSetForAction<Type, StateToken>(

View file

@ -195,8 +195,7 @@ export class TouchpointCoordinator<HoveredItemType, StateToken=any> extends Even
*
* This, in turn, can affect what the initial 'item' for the new gesture will be.
*/
const modelingSpinupPromise = potentialSelector.matchGesture(touchpoint, getGestureModelSet(modelDefs, potentialSelector.baseGestureSetId));
const modelingSpinupResults = await modelingSpinupPromise;
const modelingSpinupResults = await potentialSelector.matchGesture(touchpoint, getGestureModelSet(modelDefs, potentialSelector.baseGestureSetId));
if(modelingSpinupResults.sustainModeWithoutMatch) {

View file

@ -119,7 +119,8 @@ export default class Flick implements GestureHandler {
let source: GestureSource<KeyElement> = baseSource;
sequence.on('complete', () => {
previewHost?.cancel()
previewHost?.cancel();
this.baseSpec;
});
this.sequence.on('stage', (result) => {
@ -206,6 +207,7 @@ export default class Flick implements GestureHandler {
}
keyEvent.keyDistribution = this.currentStageKeyDistribution(this.baseKeyDistances);
keyEvent.inputBreadcrumb = this.sequence.trace();
// emit the keystroke
vkbd.raiseKeyEvent(keyEvent, null);

View file

@ -26,6 +26,8 @@ export default class Multitap implements GestureHandler {
public readonly hasModalVisualization = false;
private readonly originalLayer: string;
// Used solely for error & warning messages.
private readonly keyboardId: string;
private readonly multitaps: ActiveSubKey[];
private tapIndex = 0;
@ -56,6 +58,7 @@ export default class Multitap implements GestureHandler {
}
this.originalLayer = vkbd.layerId;
this.keyboardId = vkbd.layoutKeyboard.id;
const tapLookahead = (offset: number) => (this.tapIndex + offset) % this.multitaps.length;
@ -93,7 +96,7 @@ export default class Multitap implements GestureHandler {
case 'multitap-start':
break;
default:
throw new Error(`Unsupported gesture state encountered during multitap: ${tap.matchedId}`);
throw new Error(`Unsupported gesture state encountered during multitap: \n${this.sequence.trace()}`);
}
// For rota-style behavior
@ -104,6 +107,10 @@ export default class Multitap implements GestureHandler {
const keyEvent = vkbd.keyEventFromSpec(selection);
keyEvent.baseTranscriptionToken = this.baseContextToken;
if(this.baseContextToken == null) {
console.warn('Multitap init warning: no associated base context (with base key '+ e.key.spec.coreID + ')');
}
const coord = tap.sources[0].currentSample;
const baseSpec = this.baseKey.key.spec;
@ -120,7 +127,7 @@ export default class Multitap implements GestureHandler {
// with the multitap base key.
const p = baseDistances.get(matchKey.key.spec);
if(p == null) {
console.warn("Could not find current layer's key")
console.warn(`Could not find fat-finger probability for current layer's key: current key = ${matchKey.key.spec.elementID}, current layer = ${vkbd.layerId}, keyboard = ${this.keyboardId}`);
} else {
baseDistances.delete(matchKey.key.spec);
baseDistances.set(coord.item.key.spec, p);
@ -140,6 +147,7 @@ export default class Multitap implements GestureHandler {
// if no such 'nextLayer' is specified by default.
keyEvent.kNextLayer ||= this.originalLayer;
keyEvent.inputBreadcrumb = this.sequence.trace();
vkbd.raiseKeyEvent(keyEvent, null);
// Now that the key has been processed, with a layer possibly changed as a result...
@ -182,7 +190,7 @@ export default class Multitap implements GestureHandler {
if(keyIndex == -1) { // also covers undefined, but does not include 0.
// Modipress keys generally get left out of the key-correction calculations.
if(!keySupportsModipress(this.baseKey)) {
console.warn("Could not find base key's probability for multitap correction");
console.warn(`Could not find base key's probability for multitap correction adjustments: ${this.baseKey.key.spec.elementID}, keyboard = ${this.keyboardId}`);
}
// Decently recoverable; just use the simple-tap distances instead.

View file

@ -97,6 +97,7 @@ export default class SubkeyPopup implements GestureHandler {
const keyEvent = vkbd.keyEventFromSpec(key.key.spec);
keyEvent.keyDistribution = this.currentStageKeyDistribution();
keyEvent.inputBreadcrumb = this.source.trace();
vkbd.raiseKeyEvent(keyEvent, key);
}
});

View file

@ -522,7 +522,7 @@ export function longpressContactModel(params: FullGestureParams, enabledFlicks:
* The 'indexOf' allows 'n', 'nw', and 'ne' - approx 67.5 degrees on
* each side of due N in total.
*/
if((enabledFlicks && spec.permitsFlick(stats.lastSample.item)) && (stats.cardinalDirection?.indexOf('n') != -1 ?? false)) {
if((enabledFlicks && spec.permitsFlick(stats.lastSample.item)) && ((stats.cardinalDirection ?? '').indexOf('n') != -1)) {
const baseDistance = stats.netDistance;
const angle = stats.angle; // from <0, -1> (straight up) going clockwise.
const verticalDistance = baseDistance * Math.cos(angle);
@ -675,6 +675,9 @@ export function specialKeyEndModel(params: FullGestureParams): GestureModel<any>
itemChangeAction: 'resolve'
},
endOnResolve: true,
}, {
model: instantContactResolutionModel(),
resetOnInstantFulfill: true
}
],
resolutionAction: {

View file

@ -35,7 +35,7 @@ import { KeyEventHandler, KeyEventResultCallback } from './views/keyEventSource.
import GlobeHint from './globehint.interface.js';
import KeyboardView from './components/keyboardView.interface.js';
import { type KeyElement, getKeyFrom } from './keyElement.js';
import { type KeyElement } from './keyElement.js';
import KeyTip from './keytip.interface.js';
import OSKKey from './keyboard-layout/oskKey.js';
import OSKLayer, { LayerLayoutParams } from './keyboard-layout/oskLayer.js';
@ -597,9 +597,7 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
// First, if we've configured the gesture to generate a keystroke, let's handle that.
const gestureKey = gestureStage.item;
const coordSource = gestureStage.sources[0];
const coord: InputSample<KeyElement, string> = coordSource ? coordSource.currentSample : null;
let keyResult: KeyRuleEffects = null;
@ -628,7 +626,7 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
try {
shouldLockLayer && this.lockLayer(true);
// Once the best coord to use for fat-finger calculations has been determined:
keyResult = this.modelKeyClick(gestureStage.item, coord, correctionKeyDistribution);
keyResult = this.modelKeyClick(gestureSequence, correctionKeyDistribution); // second spot for modelKeyClick
} finally {
shouldLockLayer && this.lockLayer(false);
}
@ -660,7 +658,7 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
//
// Merely constructing the instance is enough; it'll link into the sequence's events and
// handle everything that remains for the backspace from here.
handlers = [new HeldRepeater(gestureSequence, () => this.modelKeyClick(gestureKey, coord))];
handlers = [new HeldRepeater(gestureSequence, () => this.modelKeyClick(gestureSequence))]; // site 1 for modelKeyClick
} else if(gestureKey.key.spec.baseKeyID == "K_LOPT") { // globe key
gestureSequence.on('complete', () => {
gestureKey.key.highlight(false);
@ -944,56 +942,21 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
const correctiveLayout = buildCorrectiveLayout(this.kbdLayout.getLayer(this.layerId), kbdAspectRatio);
return keyTouchDistances(touchKbdPos, correctiveLayout);
}
/**
* Get the current key target from the touch point element within the key
*
* @param {Object} t element at touch point
* @return {Object} the key element (or null)
**/
keyTarget(target: HTMLElement | EventTarget): KeyElement {
let t = <HTMLElement>target;
try {
if (t) {
if (t.classList.contains('kmw-key')) {
return getKeyFrom(t);
}
if (t.parentNode && (t.parentNode as HTMLElement).classList.contains('kmw-key')) {
return getKeyFrom(t.parentNode);
}
if (t.firstChild && (t.firstChild as HTMLElement).classList.contains('kmw-key')) {
return getKeyFrom(t.firstChild);
}
}
} catch (ex) { }
return null;
}
/**
* Repeat backspace as long as the backspace key is held down
**/
repeatDelete: () => void = function (this: VisualKeyboard) {
if (this.deleting) {
this.modelKeyClick(this.deleteKey);
this.deleting = window.setTimeout(this.repeatDelete, 100);
}
}.bind(this);
/**
* Cancels any active repeatDelete() timeouts, ensuring that
* repeating backspace operations are properly terminated.
*/
cancelDelete() {
// Clears the delete-repeating timeout.
if (this.deleting) {
window.clearTimeout(this.deleting);
}
this.deleting = 0;
}
//#endregion
modelKeyClick(e: KeyElement, input?: InputSample<KeyElement, string>, keyDistribution?: KeyDistribution) {
// Retool: take in `gestureStage` (or its index?) and the SEQUENCE; the latter lets us log gesture breadcrumbs.
modelKeyClick(sequence: GestureSequence<KeyElement>, keyDistribution?: KeyDistribution) {
if(!sequence) {
throw new Error("cannot model key event without its gesture");
}
const stage = sequence.stageReports[sequence.stageReports.length-1];
const e = stage.item;
const input = stage.sources[0]?.currentSample;
if(!e) {
throw new Error(`Cannot model keypress without a selected key: \n${sequence.trace()}`);
}
let keyEvent = this.initKeyEvent(e);
if (input) {
@ -1003,6 +966,7 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
keyEvent.keyDistribution = keyDistribution;
}
keyEvent.inputBreadcrumb = sequence.trace();
return this.raiseKeyEvent(keyEvent, e);
}
@ -1026,12 +990,9 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
}
keyEventFromSpec(keySpec: ActiveKey | ActiveSubKey) {
//let core = com.keyman.singleton.core; // only singleton-based ref currently needed here.
// Start: mirrors _GetKeyEventProperties
// First check the virtual key, and process shift, control, alt or function keys
//let Lkc = keySpec.constructKeyEvent(core.keyboardProcessor, this.device);
let Lkc = this.layoutKeyboard.constructKeyEvent(keySpec, this.device, this.stateKeys);
/* In case of "fun" edge cases caused by JS's single-threadedness & event processing queue.
@ -1060,7 +1021,6 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
*/
_UpdateVKShiftStyle(layerId?: string) {
var i;
//let core = com.keyman.singleton.core;
if (!layerId) {
layerId = this.layerId;
@ -1305,48 +1265,12 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
};
}
// Appears to be abandoned now - candidate for removal in future.
/*private*/ computedAdjustedOskHeight(allottedHeight: number): number {
if (!this.layerGroup) {
return allottedHeight;
}
/*
Note: these may not be fully preprocessed yet!
However, any "empty row bug" preprocessing has been applied, and that's
what we care about here.
*/
const layers = this.layerGroup.spec.layer;
let oskHeight = 0;
// In case the keyboard's layers have differing row counts, we check them all for the maximum needed oskHeight.
for (const layerID in layers) {
const layer = layers[layerID];
let nRows = layer.row.length;
let rowHeight = Math.floor(allottedHeight / (nRows == 0 ? 1 : nRows));
let layerHeight = nRows * rowHeight;
if (layerHeight > oskHeight) {
oskHeight = layerHeight;
}
}
// This isn't set anywhere else; it's a legacy part of the original methods.
const oskPad = 0;
let oskPaddedHeight = oskHeight + oskPad;
return oskPaddedHeight;
}
/**
* Append a style sheet for the current keyboard if needed for specifying an embedded font
* or to re-apply the default element font
*
**/
appendStyleSheet() {
//let util = com.keyman.singleton.util;
var activeKeyboard = this.layoutKeyboard;
var activeStub = this.layoutKeyboardProperties;

View file

@ -182,7 +182,6 @@ uses
Keyman.System.UpdateStateMachine in 'main\Keyman.System.UpdateStateMachine.pas',
Keyman.System.DownloadUpdate in 'main\Keyman.System.DownloadUpdate.pas',
Keyman.System.ExecutionHistory in '..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas',
Keyman.Configuration.UI.UfrmStartInstallNow in 'main\Keyman.Configuration.UI.UfrmStartInstallNow.pas' {frmInstallNow},
Keyman.Configuration.UI.UfrmStartInstall in 'main\Keyman.Configuration.UI.UfrmStartInstall.pas' {frmStartInstall};
{$R VERSION.RES}

View file

@ -354,9 +354,6 @@
<DCCReference Include="main\Keyman.System.UpdateStateMachine.pas"/>
<DCCReference Include="main\Keyman.System.DownloadUpdate.pas"/>
<DCCReference Include="..\..\..\..\common\windows\delphi\general\Keyman.System.ExecutionHistory.pas"/>
<DCCReference Include="main\Keyman.Configuration.UI.UfrmStartInstallNow.pas">
<Form>frmInstallNow</Form>
</DCCReference>
<DCCReference Include="main\Keyman.Configuration.UI.UfrmStartInstall.pas">
<Form>frmStartInstall</Form>
</DCCReference>

View file

@ -465,7 +465,7 @@
<!-- Context: Configuration Dialog - Update tab -->
<!-- String Type: PlainText -->
<!-- Introduced: 18.0.168 -->
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">K dispozici jsou aktualizace, které budou aplikovány při příštím restartu Windows.</string>
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">K dispozici jsou aktualizace, které budou aplikovány při příštím restartu Windows:</string>
<!-- Context: Configuration Dialog - Update tab pop up -->
<!-- String Type: PlainText -->
<!-- Introduced: 18.0.168 -->

View file

@ -465,7 +465,7 @@
<!-- Context: Configuration Dialog - Update tab -->
<!-- String Type: PlainText -->
<!-- Introduced: 18.0.168 -->
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">Ὑπάρχουν ἐνημερώσεις ποὺ θὰ ἐφαρμοσθοῦν κατὰ τὴν ἑπόμενη ἐκκίνηση τῶν Windows.</string>
<string name="S_Updates_Available" comment="Update tab - informs user updates are available ">Ὑπάρχουν ἐνημερώσεις ποὺ θὰ ἐφαρμοσθοῦν κατὰ τὴν ἑπόμενη ἐκκίνηση τῶν Windows:</string>
<!-- Context: Configuration Dialog - Update tab pop up -->
<!-- String Type: PlainText -->
<!-- Introduced: 18.0.168 -->

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