mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-11 11:25:34 +00:00
Merge pull request #10120 from keymanapp/chore/web/conflict-fix
chore(web): merge conflict fix for feature-gestures 🐵
This commit is contained in:
commit
d8a6467b2d
82 changed files with 3052 additions and 691 deletions
23
HISTORY.md
23
HISTORY.md
|
|
@ -1,5 +1,28 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 17.0.220 alpha 2023-11-30
|
||||
|
||||
* fix(core): set_if_needed updates an empty cached context (#10098)
|
||||
* fix(core): check for null termination (#10101)
|
||||
|
||||
## 17.0.219 alpha 2023-11-29
|
||||
|
||||
* fix(developer): path separator for kmc-package (#10064)
|
||||
* fix(developer): projects 2.0 internal path enumeration (#10016)
|
||||
* fix(web): Fix attachment-api tests (#10085)
|
||||
* fix(web): Also move source map (#10089)
|
||||
|
||||
## 17.0.218 alpha 2023-11-27
|
||||
|
||||
* feat(developer): ldml: err/hint on illegal/pua chars (#10029)
|
||||
|
||||
## 17.0.217 alpha 2023-11-24
|
||||
|
||||
* feat(developer): warn on usage of virtual keys in rule output (#10062)
|
||||
* fix(core): memory management of options in action struct (#10073)
|
||||
* chore(linux): Update debian changelog (#10047)
|
||||
* chore(core): Add test keyboard for text selection tests (#10026)
|
||||
|
||||
## 17.0.216 alpha 2023-11-23
|
||||
|
||||
* fix(common): kmx struct alignment (#9977)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.217
|
||||
17.0.221
|
||||
|
|
@ -34,6 +34,10 @@ function init() {
|
|||
keyman.getOskHeight = getOskHeight;
|
||||
keyman.getOskWidth = getOskWidth;
|
||||
keyman.beepKeyboard = beepKeyboard;
|
||||
|
||||
// Readies the keyboard stub for instant loading during the init process.
|
||||
KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard()));
|
||||
|
||||
keyman.init({
|
||||
'embeddingApp':device,
|
||||
'fonts':'packages/',
|
||||
|
|
@ -102,7 +106,7 @@ function setBannerHeight(h) {
|
|||
|
||||
if (keyman.osk) {
|
||||
keyman.osk.bannerView.activeBannerHeight = bannerHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh KMW's OSK
|
||||
|
|
@ -149,8 +153,8 @@ function onStateChange(change) {
|
|||
keyman.refreshOskLayout();
|
||||
|
||||
fragmentToggle = (fragmentToggle + 1) % 100;
|
||||
if(change != 'configured') { // doesn't change the display; only initiates suggestions.
|
||||
window.location.hash = 'refreshBannerHeight-'+fragmentToggle+'+change='+change;
|
||||
if(change != 'configured') {
|
||||
window.location.hash = 'refreshBannerHeight-'+fragmentToggle;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ final class KMKeyboard extends WebView {
|
|||
private boolean shouldIgnoreSelectionChange = false;
|
||||
|
||||
protected KeyboardType keyboardType = KeyboardType.KEYBOARD_TYPE_UNDEFINED;
|
||||
protected ArrayList<String> javascriptAfterLoad = new ArrayList<String>();
|
||||
protected ArrayList<String> javascriptAfterLoad = new ArrayList<>();
|
||||
|
||||
private static String currentKeyboard = null;
|
||||
|
||||
|
|
@ -214,10 +214,8 @@ final class KMKeyboard extends WebView {
|
|||
}
|
||||
|
||||
// Send console errors to Sentry in case they're missed by KMW sentryManager
|
||||
// (Ignoring spurious message "No keyboard stubs exist = ...")
|
||||
// TODO: Fix base error rather than trying to ignore it "No keyboard stubs exist"
|
||||
|
||||
if ((cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) && (!cm.message().startsWith("No keyboard stubs exist"))) {
|
||||
if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
|
||||
// Make Toast notification of error and send log about falling back to default keyboard (ignore language ID)
|
||||
// Sanitize sourceId info
|
||||
String NAVIGATION_PATTERN = "^(.*)?(keyboard\\.html#[^-]+)-.*$";
|
||||
|
|
@ -302,15 +300,21 @@ final class KMKeyboard extends WebView {
|
|||
this.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(javascriptAfterLoad.size() > 0) {
|
||||
loadUrl("javascript:" + javascriptAfterLoad.get(0));
|
||||
javascriptAfterLoad.remove(0);
|
||||
// Make sure we didn't reset the page in the middle of the queue!
|
||||
if(keyboardSet) {
|
||||
if (javascriptAfterLoad.size() > 0) {
|
||||
callJavascriptAfterLoad();
|
||||
}
|
||||
}
|
||||
StringBuilder allCalls = new StringBuilder();
|
||||
if(javascriptAfterLoad.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while(javascriptAfterLoad.size() > 0) {
|
||||
String entry = javascriptAfterLoad.remove(0);
|
||||
allCalls.append(entry);
|
||||
allCalls.append(";");
|
||||
}
|
||||
|
||||
loadUrl("javascript:" + allCalls.toString());
|
||||
|
||||
if(javascriptAfterLoad.size() > 0 && keyboardSet) {
|
||||
callJavascriptAfterLoad();
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.keyman.engine;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
|
@ -20,6 +21,7 @@ import android.webkit.JavascriptInterface;
|
|||
import static android.content.Context.VIBRATOR_SERVICE;
|
||||
|
||||
import com.keyman.engine.KMManager.KeyboardType;
|
||||
import com.keyman.engine.data.Keyboard;
|
||||
import com.keyman.engine.util.CharSequenceUtil;
|
||||
import com.keyman.engine.util.KMLog;
|
||||
|
||||
|
|
@ -62,6 +64,21 @@ public class KMKeyboardJSHandler {
|
|||
return kbWidth;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String initialKeyboard() {
|
||||
// Note: KMManager.getCurrentKeyboard() (and similar) will throw errors until the host-page is first fully
|
||||
// loaded and has set a keyboard. To allow the host-page to have earlier access, we instead get the stored
|
||||
// keyboard index directly.
|
||||
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
int index = prefs.getInt(KMManager.KMKey_UserKeyboardIndex, 0);
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
Keyboard kbd = KMManager.getKeyboardInfo(this.context, index);
|
||||
return kbd.toStub(context);
|
||||
}
|
||||
|
||||
// This annotation is required in Jelly Bean and later:
|
||||
@JavascriptInterface
|
||||
public void beepKeyboard() {
|
||||
|
|
|
|||
|
|
@ -164,16 +164,13 @@ public final class KMKeyboardWebViewClient extends WebViewClient {
|
|||
// for the rest of the lifetime of this keyboard instance.
|
||||
kmKeyboard.setShouldShowHelpBubble(false);
|
||||
} else if (url.indexOf("refreshBannerHeight") >= 0) {
|
||||
int start = url.indexOf("change=") + 7;
|
||||
String change = url.substring(start);
|
||||
boolean isModelActive = change.equals("active");
|
||||
// appContext instead of context?
|
||||
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
boolean modelPredictionPref = false;
|
||||
if (KMManager.currentLexicalModel != null) {
|
||||
modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true);
|
||||
}
|
||||
KMManager.setBannerOptions(isModelActive && modelPredictionPref);
|
||||
KMManager.setBannerOptions(modelPredictionPref);
|
||||
RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams();
|
||||
kmKeyboard.setLayoutParams(params);
|
||||
} else if (url.indexOf("suggestPopup") >= 0) {
|
||||
|
|
|
|||
|
|
@ -777,12 +777,10 @@ public final class KMManager {
|
|||
// KMKeyboard
|
||||
if (InAppKeyboard != null) {
|
||||
RelativeLayout.LayoutParams params = getKeyboardLayoutParams();
|
||||
InAppKeyboard.setLayoutParams(params);
|
||||
InAppKeyboard.onConfigurationChanged(newConfig);
|
||||
}
|
||||
if (SystemKeyboard != null) {
|
||||
RelativeLayout.LayoutParams params = getKeyboardLayoutParams();
|
||||
SystemKeyboard.setLayoutParams(params);
|
||||
SystemKeyboard.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
|
@ -1388,12 +1386,14 @@ public final class KMManager {
|
|||
RelativeLayout.LayoutParams params;
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreTextChange() && modelFileExists) {
|
||||
params = getKeyboardLayoutParams();
|
||||
InAppKeyboard.setLayoutParams(params);
|
||||
|
||||
// Do NOT re-layout here; it'll be triggered once the banner loads.
|
||||
InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
|
||||
}
|
||||
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreTextChange() && modelFileExists) {
|
||||
params = getKeyboardLayoutParams();
|
||||
SystemKeyboard.setLayoutParams(params);
|
||||
|
||||
// Do NOT re-layout here; it'll be triggered once the banner loads.
|
||||
SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import android.widget.Toast;
|
|||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
public final class KeyboardPickerActivity extends BaseActivity {
|
||||
private boolean hasDeleted = false;
|
||||
|
||||
//TODO: view instances should not be static
|
||||
private static Toolbar toolbar = null;
|
||||
|
|
@ -123,7 +124,7 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
switchKeyboard(position,dismissOnSelect && ! KMManager.isTestMode());
|
||||
switchKeyboard(position);
|
||||
if (dismissOnSelect)
|
||||
finish();
|
||||
}
|
||||
|
|
@ -140,6 +141,7 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.popup_delete) {
|
||||
deleteKeyboard(context, position);
|
||||
KeyboardPickerActivity.this.hasDeleted = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
@ -267,11 +269,16 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (KMManager.InAppKeyboard != null) {
|
||||
KMManager.InAppKeyboard.loadKeyboard();
|
||||
}
|
||||
if (KMManager.SystemKeyboard != null) {
|
||||
KMManager.SystemKeyboard.loadKeyboard();
|
||||
if (this.hasDeleted) {
|
||||
this.hasDeleted = false;
|
||||
|
||||
if (KMManager.InAppKeyboard != null) {
|
||||
KMManager.InAppKeyboard.loadKeyboard();
|
||||
}
|
||||
|
||||
if (KMManager.SystemKeyboard != null) {
|
||||
KMManager.SystemKeyboard.loadKeyboard();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,7 +342,7 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
* @param position the keyboard index in list
|
||||
* @param aPrepareOnly prepare switch, it is executed on keyboard reload
|
||||
*/
|
||||
private static void switchKeyboard(int position, boolean aPrepareOnly) {
|
||||
private static void switchKeyboard(int position) {
|
||||
setSelection(position);
|
||||
int size = KeyboardController.getInstance().get().size();
|
||||
int listPosition = (position >= size) ? size-1 : position;
|
||||
|
|
@ -344,10 +351,7 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
String kbId = kbInfo.getKeyboardID();
|
||||
String langId = kbInfo.getLanguageID();
|
||||
String kbName = kbInfo.getKeyboardName();
|
||||
if(aPrepareOnly)
|
||||
KMManager.prepareKeyboardSwitch(pkgId, kbId, langId, kbName);
|
||||
else
|
||||
KMManager.setKeyboard(kbInfo);
|
||||
KMManager.setKeyboard(kbInfo);
|
||||
}
|
||||
|
||||
protected static boolean addKeyboard(Context context, Keyboard keyboardInfo) {
|
||||
|
|
@ -449,7 +453,7 @@ public final class KeyboardPickerActivity extends BaseActivity {
|
|||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
if (position == curKbPos) {
|
||||
switchKeyboard(0,false);
|
||||
switchKeyboard(0);
|
||||
} else if(listView != null) { // A bit of a hack, since LanguageSettingsActivity calls this method too.
|
||||
curKbPos = KeyboardController.getInstance().getKeyboardIndex(KMKeyboard.currentKeyboard());
|
||||
setSelection(curKbPos);
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ import com.keyman.engine.util.FileUtils;
|
|||
import com.keyman.engine.util.KMLog;
|
||||
import com.keyman.engine.util.KMString;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Keyboard extends LanguageResource implements Serializable {
|
||||
private static final String TAG = "Keyboard";
|
||||
|
|
@ -171,6 +173,91 @@ public class Keyboard extends LanguageResource implements Serializable {
|
|||
return o;
|
||||
}
|
||||
|
||||
private String getKeyboardRoot(Context context) {
|
||||
String keyboardRoot = context.getDir("data", Context.MODE_PRIVATE).toString() +
|
||||
File.separator;
|
||||
|
||||
if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) {
|
||||
return keyboardRoot + KMManager.KMDefault_UndefinedPackageID + File.separator;
|
||||
} else {
|
||||
return keyboardRoot + KMManager.KMDefault_AssetPackages + File.separator + packageID + File.separator;
|
||||
}
|
||||
}
|
||||
|
||||
public String getKeyboardPath(Context context) {
|
||||
String keyboardID = this.getKeyboardID();
|
||||
String keyboardVersion = this.getVersion();
|
||||
if (packageID.equals(KMManager.KMDefault_UndefinedPackageID)) {
|
||||
return getKeyboardRoot(context) + keyboardID + "-" + keyboardVersion + ".js";
|
||||
} else {
|
||||
return getKeyboardRoot(context) + keyboardID + ".js";
|
||||
}
|
||||
}
|
||||
|
||||
public String toStub(Context context) {
|
||||
JSONObject stubObj = new JSONObject();
|
||||
|
||||
try {
|
||||
stubObj.put("KN", this.getKeyboardName());
|
||||
stubObj.put("KI", "Keyboard_" + this.getKeyboardID());
|
||||
stubObj.put("KLC", this.getLanguageID());
|
||||
stubObj.put("KL", this.getLanguageName());
|
||||
stubObj.put("KF", this.getKeyboardPath(context));
|
||||
stubObj.put("KP", this.getPackageID());
|
||||
|
||||
String displayFont = this.getFont();
|
||||
if(displayFont != null) {
|
||||
stubObj.put("KFont", this.buildDisplayFontObject(displayFont, context));
|
||||
}
|
||||
|
||||
String oskFont = this.getOSKFont();
|
||||
if(oskFont != null) {
|
||||
stubObj.put("KOskFont", this.buildDisplayFontObject(oskFont, context));
|
||||
}
|
||||
|
||||
String displayName = this.getDisplayName();
|
||||
if(displayName != null) {
|
||||
stubObj.put("displayName", displayName);
|
||||
}
|
||||
|
||||
return stubObj.toString();
|
||||
} catch(JSONException e) {
|
||||
KMLog.LogException(TAG, "", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a font JSON object and adjust to pass to JS
|
||||
* 1. Replace "source" keys for "files" keys
|
||||
* 2. Create full font paths for .ttf or .svg
|
||||
* @param font String font JSON object as a string
|
||||
* @return JSONObject of modified font information with full paths. If font is invalid, return `null`
|
||||
*/
|
||||
private JSONObject buildDisplayFontObject(String font, Context context) {
|
||||
if(font == null || font.equals("")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String keyboardRoot = this.getKeyboardRoot(context);
|
||||
|
||||
try {
|
||||
if (FileUtils.hasFontExtension(font)) {
|
||||
JSONObject jfont = new JSONObject();
|
||||
jfont.put(KMManager.KMKey_FontFamily, font.substring(0, font.length() - 4));
|
||||
JSONArray jfiles = new JSONArray();
|
||||
jfiles.put(keyboardRoot + font);
|
||||
jfont.put(KMManager.KMKey_FontFiles, jfiles);
|
||||
return jfont;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
KMLog.LogException(TAG, "Failed to make font for '"+font+"'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fallback keyboard. If never specified, use sil_euro_latin
|
||||
* @param context Context
|
||||
|
|
|
|||
|
|
@ -562,7 +562,9 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
void tryFinalizeUpdate() {
|
||||
if (openUpdates.isEmpty()) {
|
||||
|
||||
// Trigger a host-page reset - we need to transition to the up-to-date versions.
|
||||
// TODO: make it smoother. Documented as #11097.
|
||||
KMManager.clearKeyboardCache();
|
||||
|
||||
if (failedUpdateCount > 0) {
|
||||
BaseActivity.makeToast(currentContext, R.string.update_failed, Toast.LENGTH_SHORT);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
Text Selection Tests Keyboard Change History
|
||||
====================
|
||||
|
||||
1.0 (2023-11-14)
|
||||
----------------
|
||||
* Created by Keyman Team
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
© 2023 Keyman Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
Text Selection Tests Keyboard keyboard
|
||||
==============
|
||||
|
||||
Version 1.0
|
||||
|
||||
Description
|
||||
-----------
|
||||
Text Selection Tests Keyboard generated from template
|
||||
|
||||
Links
|
||||
-----
|
||||
https://github.com/keymanapp/keyman/issues/9073
|
||||
|
||||
Copyright
|
||||
---------
|
||||
See [LICENSE.md](LICENSE.md)
|
||||
|
||||
Supported Platforms
|
||||
-------------------
|
||||
* Windows
|
||||
* macOS
|
||||
* Linux
|
||||
* Web
|
||||
* iPhone
|
||||
* iPad
|
||||
* Android phone
|
||||
* Android tablet
|
||||
* Mobile devices
|
||||
* Desktop devices
|
||||
* Tablet devices
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Text Selection Tests Keyboard</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Text Selection Tests Keyboard</h1>
|
||||
|
||||
<p>
|
||||
Text Selection Tests Keyboard 1.0 generated from template.
|
||||
</p>
|
||||
|
||||
<p>© Keyman Team</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,532 @@
|
|||
{
|
||||
"tablet": {
|
||||
"displayUnderlying": false,
|
||||
"layer": [
|
||||
{
|
||||
"id": "default",
|
||||
"row": [
|
||||
{
|
||||
"id": 1,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_1",
|
||||
"text": "1",
|
||||
"nextlayer": "shift"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "2"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "3"
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "4"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "5"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": "6"
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": "7"
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": "8"
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "9"
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": "0"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": "-"
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "="
|
||||
},
|
||||
{
|
||||
"id": "K_BKSP",
|
||||
"text": "*BkSp*",
|
||||
"width": 100,
|
||||
"sp": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"text": "q",
|
||||
"pad": 75
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": "w"
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": "e"
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "r"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "t"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": "y"
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": "u"
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": "i"
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": "o"
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "p"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": "["
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "]"
|
||||
},
|
||||
{
|
||||
"id": "T_new_136",
|
||||
"width": 10,
|
||||
"sp": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "dk(1)"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": "a"
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": "s"
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "d"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "f"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "g"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "h"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": "j"
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "k"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "l"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ";"
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": "'"
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "\\"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_SHIFT",
|
||||
"text": "*Shift*",
|
||||
"width": 160,
|
||||
"sp": 1,
|
||||
"nextlayer": "shift"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": "\\"
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "z"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "c"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": "v"
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "b"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "n"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": "m"
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": ","
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": "."
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": "/"
|
||||
},
|
||||
{
|
||||
"id": "T_new_162",
|
||||
"width": 10,
|
||||
"sp": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_LOPT",
|
||||
"text": "*Menu*",
|
||||
"width": 140,
|
||||
"sp": 1
|
||||
},
|
||||
{
|
||||
"id": "K_SPACE",
|
||||
"width": 930
|
||||
},
|
||||
{
|
||||
"id": "K_ENTER",
|
||||
"text": "*Enter*",
|
||||
"width": 145,
|
||||
"sp": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "shift",
|
||||
"row": [
|
||||
{
|
||||
"id": 1,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_1",
|
||||
"text": "!"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "@"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "#"
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "$"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "%"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": "^"
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": "&"
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": "*"
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "("
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": ")"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": "_"
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "+"
|
||||
},
|
||||
{
|
||||
"id": "K_BKSP",
|
||||
"text": "*BkSp*",
|
||||
"width": 100,
|
||||
"sp": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"text": "Q",
|
||||
"pad": 75
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": "W"
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": "E"
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "R"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "T"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": "Y"
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": "U"
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": "I"
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": "O"
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "P"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": "{"
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "}"
|
||||
},
|
||||
{
|
||||
"id": "T_new_246",
|
||||
"width": 10,
|
||||
"sp": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "~"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": "A"
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": "S"
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "D"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "F"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "G"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "H"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": "J"
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "K"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "L"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ":"
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": "\""
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "|"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_SHIFT",
|
||||
"text": "*Shift*",
|
||||
"width": 160,
|
||||
"sp": 1,
|
||||
"nextlayer": "default"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": "|"
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "Z"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "X"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "C"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": "V"
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "B"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "N"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": "M"
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": "<"
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": ">"
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": "?"
|
||||
},
|
||||
{
|
||||
"id": "T_new_272",
|
||||
"width": 10,
|
||||
"sp": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"key": [
|
||||
{
|
||||
"id": "K_LOPT",
|
||||
"text": "*Menu*",
|
||||
"width": 140,
|
||||
"sp": 1
|
||||
},
|
||||
{
|
||||
"id": "K_SPACE",
|
||||
"width": 930
|
||||
},
|
||||
{
|
||||
"id": "K_ENTER",
|
||||
"text": "*Enter*",
|
||||
"width": 145,
|
||||
"sp": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
c text_selection_tests_keyboard_9073 generated from template at 2023-11-14 15:23:49
|
||||
c with name "Text Selection Tests Keyboard"
|
||||
store(&VERSION) '10.0'
|
||||
store(&NAME) 'Text Selection Tests Keyboard'
|
||||
store(©RIGHT) '© Keyman Team'
|
||||
store(&KEYBOARDVERSION) '1.0'
|
||||
store(&TARGETS) 'any'
|
||||
store(&BITMAP) 'text_selection_tests_keyboard_9073.ico'
|
||||
store(&VISUALKEYBOARD) 'text_selection_tests_keyboard_9073.kvks'
|
||||
store(&LAYOUTFILE) 'text_selection_tests_keyboard_9073.keyman-touch-layout'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
'^' + [K_A] > 'â'
|
||||
'^' + [SHIFT K_A] > 'Â'
|
||||
'^' + [K_BKSP] > 'foo'
|
||||
|
||||
+ '`' > dk(1)
|
||||
|
||||
+ [K_T] > U+0009 c TAB
|
||||
|
||||
'a' dk(1) 'b' + [K_BKSP] > 'ok1'
|
||||
'a' 'b' + [K_BKSP] > 'fail1'
|
||||
'a' dk(1) + [K_BKSP] > 'fail2'
|
||||
dk(1) + 'o' > 'ok3'
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package>
|
||||
<System>
|
||||
<KeymanDeveloperVersion>16.0.142.0</KeymanDeveloperVersion>
|
||||
<FileVersion>7.0</FileVersion>
|
||||
</System>
|
||||
<Options>
|
||||
<ExecuteProgram></ExecuteProgram>
|
||||
<ReadMeFile>readme.htm</ReadMeFile>
|
||||
<MSIFileName></MSIFileName>
|
||||
<MSIOptions></MSIOptions>
|
||||
<FollowKeyboardVersion/>
|
||||
</Options>
|
||||
<StartMenu>
|
||||
<Folder></Folder>
|
||||
<Items/>
|
||||
</StartMenu>
|
||||
<Info>
|
||||
<Name URL="">Text Selection Tests Keyboard</Name>
|
||||
<Copyright URL="">© Keyman Team</Copyright>
|
||||
<Author URL="">Keyman Team</Author>
|
||||
<Version URL=""></Version>
|
||||
</Info>
|
||||
<Files>
|
||||
<File>
|
||||
<Name>..\build\text_selection_tests_keyboard_9073.kmx</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kmx</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>..\build\text_selection_tests_keyboard_9073.js</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.js</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>..\build\text_selection_tests_keyboard_9073.kvk</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kvk</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>welcome.htm</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.htm</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<Name>readme.htm</Name>
|
||||
<Description></Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.htm</FileType>
|
||||
</File>
|
||||
</Files>
|
||||
<Keyboards>
|
||||
<Keyboard>
|
||||
<Name>Text Selection Tests Keyboard</Name>
|
||||
<ID>text_selection_tests_keyboard_9073</ID>
|
||||
<Version>1.0</Version>
|
||||
<Languages>
|
||||
<Language ID="en">English</Language>
|
||||
</Languages>
|
||||
</Keyboard>
|
||||
</Keyboards>
|
||||
<Strings/>
|
||||
</Package>
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<visualkeyboard>
|
||||
<header>
|
||||
<version>10.0</version>
|
||||
<kbdname>text_selection_tests_keyboard_9073</kbdname>
|
||||
<flags/>
|
||||
</header>
|
||||
<encoding name="unicode" fontname="Arial" fontsize="-12">
|
||||
<layer shift="">
|
||||
<key vkey="K_BKQUOTE">dk(1)</key>
|
||||
<key vkey="K_1">1</key>
|
||||
<key vkey="K_2">2</key>
|
||||
<key vkey="K_3">3</key>
|
||||
<key vkey="K_4">4</key>
|
||||
<key vkey="K_5">5</key>
|
||||
<key vkey="K_6">6</key>
|
||||
<key vkey="K_7">7</key>
|
||||
<key vkey="K_8">8</key>
|
||||
<key vkey="K_9">9</key>
|
||||
<key vkey="K_0">0</key>
|
||||
<key vkey="K_HYPHEN">-</key>
|
||||
<key vkey="K_EQUAL">=</key>
|
||||
<key vkey="K_Q">q</key>
|
||||
<key vkey="K_W">w</key>
|
||||
<key vkey="K_E">e</key>
|
||||
<key vkey="K_R">r</key>
|
||||
<key vkey="K_T">t</key>
|
||||
<key vkey="K_Y">y</key>
|
||||
<key vkey="K_U">u</key>
|
||||
<key vkey="K_I">i</key>
|
||||
<key vkey="K_O">o</key>
|
||||
<key vkey="K_P">p</key>
|
||||
<key vkey="K_LBRKT">[</key>
|
||||
<key vkey="K_RBRKT">]</key>
|
||||
<key vkey="K_BKSLASH">\</key>
|
||||
<key vkey="K_A">a</key>
|
||||
<key vkey="K_S">s</key>
|
||||
<key vkey="K_D">d</key>
|
||||
<key vkey="K_F">f</key>
|
||||
<key vkey="K_G">g</key>
|
||||
<key vkey="K_H">h</key>
|
||||
<key vkey="K_J">j</key>
|
||||
<key vkey="K_K">k</key>
|
||||
<key vkey="K_L">l</key>
|
||||
<key vkey="K_COLON">;</key>
|
||||
<key vkey="K_QUOTE">'</key>
|
||||
<key vkey="K_oE2">\</key>
|
||||
<key vkey="K_Z">z</key>
|
||||
<key vkey="K_X">x</key>
|
||||
<key vkey="K_C">c</key>
|
||||
<key vkey="K_V">v</key>
|
||||
<key vkey="K_B">b</key>
|
||||
<key vkey="K_N">n</key>
|
||||
<key vkey="K_M">m</key>
|
||||
<key vkey="K_COMMA">,</key>
|
||||
<key vkey="K_PERIOD">.</key>
|
||||
<key vkey="K_SLASH">/</key>
|
||||
</layer>
|
||||
<layer shift="S">
|
||||
<key vkey="K_BKQUOTE">~</key>
|
||||
<key vkey="K_1">!</key>
|
||||
<key vkey="K_2">@</key>
|
||||
<key vkey="K_3">#</key>
|
||||
<key vkey="K_4">$</key>
|
||||
<key vkey="K_5">%</key>
|
||||
<key vkey="K_6">^</key>
|
||||
<key vkey="K_7">&</key>
|
||||
<key vkey="K_8">*</key>
|
||||
<key vkey="K_9">(</key>
|
||||
<key vkey="K_0">)</key>
|
||||
<key vkey="K_HYPHEN">_</key>
|
||||
<key vkey="K_EQUAL">+</key>
|
||||
<key vkey="K_Q">Q</key>
|
||||
<key vkey="K_W">W</key>
|
||||
<key vkey="K_E">E</key>
|
||||
<key vkey="K_R">R</key>
|
||||
<key vkey="K_T">T</key>
|
||||
<key vkey="K_Y">Y</key>
|
||||
<key vkey="K_U">U</key>
|
||||
<key vkey="K_I">I</key>
|
||||
<key vkey="K_O">O</key>
|
||||
<key vkey="K_P">P</key>
|
||||
<key vkey="K_LBRKT">{</key>
|
||||
<key vkey="K_RBRKT">}</key>
|
||||
<key vkey="K_BKSLASH">|</key>
|
||||
<key vkey="K_A">A</key>
|
||||
<key vkey="K_S">S</key>
|
||||
<key vkey="K_D">D</key>
|
||||
<key vkey="K_F">F</key>
|
||||
<key vkey="K_G">G</key>
|
||||
<key vkey="K_H">H</key>
|
||||
<key vkey="K_J">J</key>
|
||||
<key vkey="K_K">K</key>
|
||||
<key vkey="K_L">L</key>
|
||||
<key vkey="K_COLON">:</key>
|
||||
<key vkey="K_QUOTE">"</key>
|
||||
<key vkey="K_oE2">|</key>
|
||||
<key vkey="K_Z">Z</key>
|
||||
<key vkey="K_X">X</key>
|
||||
<key vkey="K_C">C</key>
|
||||
<key vkey="K_V">V</key>
|
||||
<key vkey="K_B">B</key>
|
||||
<key vkey="K_N">N</key>
|
||||
<key vkey="K_M">M</key>
|
||||
<key vkey="K_COMMA"><</key>
|
||||
<key vkey="K_PERIOD">></key>
|
||||
<key vkey="K_SLASH">?</key>
|
||||
</layer>
|
||||
</encoding>
|
||||
</visualkeyboard>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Start Using Text Selection Tests Keyboard</title>
|
||||
<style type="text/css">
|
||||
p { font: 10pt Tahoma; }
|
||||
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
|
||||
h2 { font: bold 12pt Tahoma; color: #4444cc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Start Using Text Selection Tests Keyboard</h1>
|
||||
|
||||
<p>
|
||||
Text Selection Tests Keyboard 1.0 generated from template.
|
||||
</p>
|
||||
|
||||
<h1>Keyboard Layout</h1>
|
||||
|
||||
<!-- Insert Keyboard Layout Images or HTML here -->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"license": "mit",
|
||||
"languages": [
|
||||
"en"
|
||||
],
|
||||
"description": "Text Selection Tests Keyboard generated from template"
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<BuildPath>$PROJECTPATH\build</BuildPath>
|
||||
<CompilerWarningsAsErrors>True</CompilerWarningsAsErrors>
|
||||
<WarnDeprecatedCode>True</WarnDeprecatedCode>
|
||||
<CheckFilenameConventions>True</CheckFilenameConventions>
|
||||
<ProjectType>keyboard</ProjectType>
|
||||
</Options>
|
||||
<Files>
|
||||
<File>
|
||||
<ID>id_dda967022de452e1fe199096e795f0ab</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.kmn</Filename>
|
||||
<Filepath>source\text_selection_tests_keyboard_9073.kmn</Filepath>
|
||||
<FileVersion>1.0</FileVersion>
|
||||
<FileType>.kmn</FileType>
|
||||
<Details>
|
||||
<Name>Text Selection Tests Keyboard</Name>
|
||||
<Copyright>© Keyman Team</Copyright>
|
||||
</Details>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_ba932837e6a67a86abc409a393242255</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.kps</Filename>
|
||||
<Filepath>source\text_selection_tests_keyboard_9073.kps</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kps</FileType>
|
||||
<Details>
|
||||
<Name>Text Selection Tests Keyboard</Name>
|
||||
<Copyright>© Keyman Team</Copyright>
|
||||
</Details>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_ede98e4633e239f933cbfd1f4e1b766c</ID>
|
||||
<Filename>HISTORY.md</Filename>
|
||||
<Filepath>HISTORY.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_53e892b8b41cc4caece1cfd5ef21d6e7</ID>
|
||||
<Filename>LICENSE.md</Filename>
|
||||
<Filepath>LICENSE.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_0730bb7c2e8f9ea2438b52e419dd86c9</ID>
|
||||
<Filename>README.md</Filename>
|
||||
<Filepath>README.md</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.md</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_4b87bd35cc2e16f1ff8680a6f2caed52</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.keyboard_info</Filename>
|
||||
<Filepath>text_selection_tests_keyboard_9073.keyboard_info</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.keyboard_info</FileType>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_bbf31cea8a9cfe0cb838f67055690bf8</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.ico</Filename>
|
||||
<Filepath>source\text_selection_tests_keyboard_9073.ico</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.ico</FileType>
|
||||
<ParentFileID>id_dda967022de452e1fe199096e795f0ab</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_b8f7a473cac52dd0436273de657cdf46</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.kmx</Filename>
|
||||
<Filepath>source\..\build\text_selection_tests_keyboard_9073.kmx</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kmx</FileType>
|
||||
<ParentFileID>id_ba932837e6a67a86abc409a393242255</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_73d0cd87e78d9b8d7f514809dbb36a47</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.js</Filename>
|
||||
<Filepath>source\..\build\text_selection_tests_keyboard_9073.js</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.js</FileType>
|
||||
<ParentFileID>id_ba932837e6a67a86abc409a393242255</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_71aafc060dc3251e4bb611ea539dc8e0</ID>
|
||||
<Filename>text_selection_tests_keyboard_9073.kvk</Filename>
|
||||
<Filepath>source\..\build\text_selection_tests_keyboard_9073.kvk</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.kvk</FileType>
|
||||
<ParentFileID>id_ba932837e6a67a86abc409a393242255</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_356e5d149c1e539356d72698c1e401a6</ID>
|
||||
<Filename>welcome.htm</Filename>
|
||||
<Filepath>source\welcome.htm</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.htm</FileType>
|
||||
<ParentFileID>id_ba932837e6a67a86abc409a393242255</ParentFileID>
|
||||
</File>
|
||||
<File>
|
||||
<ID>id_8da344c4cea6f467013357fe099006f5</ID>
|
||||
<Filename>readme.htm</Filename>
|
||||
<Filepath>source\readme.htm</Filepath>
|
||||
<FileVersion></FileVersion>
|
||||
<FileType>.htm</FileType>
|
||||
<ParentFileID>id_ba932837e6a67a86abc409a393242255</ParentFileID>
|
||||
</File>
|
||||
</Files>
|
||||
</KeymanDeveloperProject>
|
||||
|
|
@ -128,8 +128,10 @@ export default class LanguageProcessor extends EventEmitter<LanguageProcessorEve
|
|||
|
||||
return this.lmEngine.loadModel(source, specType).then((config: Configuration) => {
|
||||
this.configuration = config;
|
||||
this._state = 'configured';
|
||||
this.emit('statechange', 'configured');
|
||||
if(this.mayPredict) {
|
||||
this._state = 'configured';
|
||||
this.emit('statechange', 'configured');
|
||||
}
|
||||
}).catch((error) => {
|
||||
// Does this provide enough logging information?
|
||||
let message: string;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ await esbuild.build({
|
|||
//
|
||||
// Alternatively, we can just build it separately like the node-oriented one.
|
||||
fs.renameSync('build/lib/keyboards/loaders/dom-keyboard-loader.mjs', 'build/lib/dom-keyboard-loader.mjs');
|
||||
fs.renameSync('build/lib/keyboards/loaders/dom-keyboard-loader.mjs.map', 'build/lib/dom-keyboard-loader.mjs.map');
|
||||
fs.rmSync('build/lib/keyboards', { recursive: true, force: true });
|
||||
|
||||
// The node-based keyboard loader needs an extra parameter due to Node-built-in imports:
|
||||
|
|
|
|||
|
|
@ -146,7 +146,11 @@ export interface StrsOptions {
|
|||
};
|
||||
|
||||
export class Strs extends Section {
|
||||
strings: StrsItem[] = [ new StrsItem('') ]; // C7043: The null string is always requierd
|
||||
/** the in-memory string table */
|
||||
strings: StrsItem[] = [ new StrsItem('') ]; // C7043: The null string is always required
|
||||
|
||||
/** for validating */
|
||||
allProcessedStrings = new Set<string>();
|
||||
/**
|
||||
* Allocate a StrsItem given the string, unescaping if necessary.
|
||||
* @param s escaped string
|
||||
|
|
@ -158,6 +162,11 @@ export class Strs extends Section {
|
|||
// Run the string processing pipeline
|
||||
s = Strs.processString(s, opts, sections);
|
||||
|
||||
// add to the set, for testing
|
||||
if (s) {
|
||||
this.allProcessedStrings.add(s);
|
||||
}
|
||||
|
||||
// if it's a single char, don't push it into the strs table
|
||||
if (opts?.singleOk && isOneChar(s)) {
|
||||
return new CharStrsItem(s);
|
||||
|
|
|
|||
|
|
@ -101,3 +101,161 @@ toOneChar(value: string) : number {
|
|||
}
|
||||
return value.codePointAt(0);
|
||||
}
|
||||
|
||||
export function describeCodepoint(ch : number) : string {
|
||||
let s;
|
||||
const p = getProblem(ch);
|
||||
if (p != null) {
|
||||
// for example: 'PUA (U+E010)'
|
||||
s = p;
|
||||
} else {
|
||||
// for example: '"a" (U+61)'
|
||||
s = `"${String.fromCodePoint(ch)}"`;
|
||||
}
|
||||
return `${s} (U+${Number(ch).toString(16).toUpperCase()})`;
|
||||
}
|
||||
|
||||
export enum BadStringType {
|
||||
pua = 'PUA',
|
||||
unassigned = 'Unassigned',
|
||||
illegal = 'Illegal',
|
||||
};
|
||||
|
||||
// Following from kmx_xstring.h / .cpp
|
||||
|
||||
const Uni_LEAD_SURROGATE_START = 0xD800;
|
||||
const Uni_LEAD_SURROGATE_END = 0xDBFF;
|
||||
const Uni_TRAIL_SURROGATE_START = 0xDC00;
|
||||
const Uni_TRAIL_SURROGATE_END = 0xDFFF;
|
||||
const Uni_SURROGATE_START = Uni_LEAD_SURROGATE_START;
|
||||
const Uni_SURROGATE_END = Uni_TRAIL_SURROGATE_END;
|
||||
const Uni_FD_NONCHARACTER_START = 0xFDD0;
|
||||
const Uni_FD_NONCHARACTER_END = 0xFDEF;
|
||||
const Uni_FFFE_NONCHARACTER = 0xFFFE;
|
||||
const Uni_PLANE_MASK = 0x1F0000;
|
||||
const Uni_MAX_CODEPOINT = 0x10FFFF;
|
||||
// plane 0, 15, and 16 PUA
|
||||
const Uni_PUA_00_START = 0xE000;
|
||||
const Uni_PUA_00_END = 0xF8FF;
|
||||
const Uni_PUA_15_START = 0x0F0000;
|
||||
const Uni_PUA_15_END = 0x0FFFFD;
|
||||
const Uni_PUA_16_START = 0x100000;
|
||||
const Uni_PUA_16_END = 0x10FFFD;
|
||||
|
||||
|
||||
/**
|
||||
* @brief True if a lead surrogate
|
||||
* \def Uni_IsSurrogate1
|
||||
*/
|
||||
function Uni_IsSurrogate1(ch : number) {
|
||||
return ((ch) >= Uni_LEAD_SURROGATE_START && (ch) <= Uni_LEAD_SURROGATE_END);
|
||||
}
|
||||
/**
|
||||
* @brief True if a trail surrogate
|
||||
* \def Uni_IsSurrogate2
|
||||
*/
|
||||
function Uni_IsSurrogate2(ch : number) {
|
||||
return ((ch) >= Uni_TRAIL_SURROGATE_START && (ch) <= Uni_TRAIL_SURROGATE_END);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief True if any surrogate
|
||||
* \def UniIsSurrogate
|
||||
*/
|
||||
function Uni_IsSurrogate(ch : number) {
|
||||
return (Uni_IsSurrogate1(ch) || Uni_IsSurrogate2(ch));
|
||||
}
|
||||
|
||||
function Uni_IsEndOfPlaneNonCharacter(ch : number) {
|
||||
return (((ch) & Uni_FFFE_NONCHARACTER) == Uni_FFFE_NONCHARACTER); // matches FFFF or FFFE
|
||||
}
|
||||
|
||||
function Uni_IsNoncharacter(ch : number) {
|
||||
return (((ch) >= Uni_FD_NONCHARACTER_START && (ch) <= Uni_FD_NONCHARACTER_END) || Uni_IsEndOfPlaneNonCharacter(ch));
|
||||
}
|
||||
|
||||
function Uni_InCodespace(ch : number) {
|
||||
return (ch >= 0 && ch <= Uni_MAX_CODEPOINT);
|
||||
};
|
||||
|
||||
function Uni_IsValid1(ch: number) {
|
||||
return (Uni_InCodespace(ch) && !Uni_IsSurrogate(ch) && !Uni_IsNoncharacter(ch));
|
||||
}
|
||||
|
||||
export function isValidUnicode(start: number, end?: number) {
|
||||
if (!end) {
|
||||
// single char
|
||||
return Uni_IsValid1(start);
|
||||
} else if (!Uni_IsValid1(end) || !Uni_IsValid1(start) || (end < start)) {
|
||||
// start or end out of range, or inverted range
|
||||
return false;
|
||||
} else if ((start <= Uni_SURROGATE_END) && (end >= Uni_SURROGATE_START)) {
|
||||
// contains some of the surrogate range
|
||||
return false;
|
||||
} else if ((start <= Uni_FD_NONCHARACTER_END) && (end >= Uni_FD_NONCHARACTER_START)) {
|
||||
// contains some of the noncharacter range
|
||||
return false;
|
||||
} else if ((start & Uni_PLANE_MASK) != (end & Uni_PLANE_MASK)) {
|
||||
// start and end are on different planes, meaning that the U+__FFFE/U+__FFFF noncharacters
|
||||
// are contained.
|
||||
// As a reminder, we already checked that start/end are themselves valid,
|
||||
// so we know that 'end' is not on a noncharacter at end of plane.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function isPUA(ch: number) {
|
||||
return ((ch >= Uni_PUA_00_START && ch <= Uni_PUA_00_END) ||
|
||||
(ch >= Uni_PUA_15_START && ch <= Uni_PUA_15_END) ||
|
||||
(ch >= Uni_PUA_16_START && ch <= Uni_PUA_16_END));
|
||||
}
|
||||
|
||||
class BadStringMap extends Map<BadStringType, Set<number>> {
|
||||
public toString() : string {
|
||||
if (!this.size) {
|
||||
return "{}";
|
||||
}
|
||||
return Array.from(this.entries()).map(([t, s]) => `${t}: ${Array.from(s.values()).map(describeCodepoint).join(' ')}`).join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
function getProblem(ch : number) : BadStringType {
|
||||
if (!isValidUnicode(ch)) {
|
||||
return BadStringType.illegal;
|
||||
} else if(isPUA(ch)) {
|
||||
return BadStringType.pua;
|
||||
} else { // TODO-LDML: unassigned
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export class BadStringAnalyzer {
|
||||
/** add a string for analysis */
|
||||
public add(s : string) {
|
||||
for (const c of s) {
|
||||
const ch = c.codePointAt(0);
|
||||
const problem = getProblem(ch);
|
||||
if (problem) {
|
||||
this.addProblem(ch, problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addProblem(ch : number, type : BadStringType) {
|
||||
if (!this.m.has(type)) {
|
||||
this.m.set(type, new Set<number>());
|
||||
}
|
||||
this.m.get(type).add(ch);
|
||||
}
|
||||
|
||||
public analyze() : BadStringMap {
|
||||
if (this.m.size == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return this.m;
|
||||
}
|
||||
}
|
||||
|
||||
private m = new BadStringMap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import {unescapeString, UnescapeError, isOneChar, toOneChar, unescapeOneQuadString} from '../../src/util/util.js';
|
||||
import {unescapeString, UnescapeError, isOneChar, toOneChar, unescapeOneQuadString, BadStringAnalyzer, isValidUnicode, describeCodepoint, isPUA, BadStringType} from '../../src/util/util.js';
|
||||
|
||||
describe('test UTF32 functions()', function() {
|
||||
it('should properly categorize strings', () => {
|
||||
|
|
@ -68,3 +68,146 @@ describe('test unescapeOneQuadString()', () => {
|
|||
assert.throws(() => unescapeOneQuadString('\uFFFFFFFFFFFF'));
|
||||
});
|
||||
});
|
||||
|
||||
function titleize(o : any) {
|
||||
const s = JSON.stringify(o);
|
||||
if (!s) {
|
||||
return `''`;
|
||||
} else if (s.length < 10) {
|
||||
return s;
|
||||
} else {
|
||||
return s.substring(0,10)+'…';
|
||||
}
|
||||
}
|
||||
|
||||
describe('test bad char functions', () => {
|
||||
it('should match test_kmx_xstring.cpp', () => {
|
||||
function Uni_IsValid(start: number, end?: number) {
|
||||
return [ start, end ];
|
||||
}
|
||||
function assert_equal(range: number[], expect : boolean) {
|
||||
const [start, end] = range;
|
||||
if (end) {
|
||||
assert.equal(isValidUnicode(start, end), expect, `for ${describeCodepoint(start)}-${describeCodepoint(end)}}`);
|
||||
} else {
|
||||
// if branch just for the message
|
||||
assert.equal(isValidUnicode(start), expect, `for ${describeCodepoint(start)}`);
|
||||
}
|
||||
}
|
||||
// following lines are from test_kmx_xstring.cpp
|
||||
assert_equal(Uni_IsValid(0x0000), true);
|
||||
assert_equal(Uni_IsValid(0x0127), true);
|
||||
assert_equal(Uni_IsValid('🙀'.codePointAt(0)), true);
|
||||
assert_equal(Uni_IsValid(0xDECAFBAD), false); // out of range
|
||||
assert_equal(Uni_IsValid(0x566D4128), false);
|
||||
assert_equal(Uni_IsValid(0xFFFF), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0xFFFE), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x10FFFF), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x10FFFE), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x01FFFF), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x01FFFE), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x02FFFF), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x02FFFE), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0xFDD1), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0xD800), false); // orphaned surrogate
|
||||
assert_equal(Uni_IsValid(0xFDD0), false); // nonchar
|
||||
assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
|
||||
assert_equal(Uni_IsValid(0x10, 0x20), true);
|
||||
assert_equal(Uni_IsValid(0x100000, 0x10FFFD), true);
|
||||
assert_equal(Uni_IsValid(0x0000, 0xD7FF), true);
|
||||
assert_equal(Uni_IsValid(0xD800, 0xDFFF), false); // orphaned surrogate
|
||||
assert_equal(Uni_IsValid(0xE000, 0xFDCF), true);
|
||||
assert_equal(Uni_IsValid(0xFDD0, 0xFDEF), false);
|
||||
assert_equal(Uni_IsValid(0xFDF0, 0xFDFF), true);
|
||||
assert_equal(Uni_IsValid(0xFDF0, 0xFFFD), true);
|
||||
assert_equal(Uni_IsValid(0, 0x10FFFF), false); // ends with nonchar
|
||||
assert_equal(Uni_IsValid(0, 0x10FFFD), false); // contains lots o' nonchars
|
||||
assert_equal(Uni_IsValid(0x20, 0x10), false); // swapped
|
||||
assert_equal(Uni_IsValid(0xFDEF, 0xFDF0), false); // just outside range
|
||||
assert_equal(Uni_IsValid(0x0000, 0x010000), false); // crosses noncharacter plane boundary and other stuff
|
||||
assert_equal(Uni_IsValid(0x010000, 0x020000), false); // crosses noncharacter plane boundary
|
||||
assert_equal(Uni_IsValid(0x0000, 0xFFFF), false); // crosses other BMP prohibited and plane boundary
|
||||
assert_equal(Uni_IsValid(0x0000, 0xFFFD), false); // crosses other BMP prohibited
|
||||
assert_equal(Uni_IsValid(0x0000, 0xE000), false); // crosses surrogate space
|
||||
assert_equal(Uni_IsValid(0x0000, 0x20FFFF), false); // out of bounds
|
||||
assert_equal(Uni_IsValid(0x10FFFD, 0x20FFFF), false); // out of bounds
|
||||
});
|
||||
it('should detect non-PUA', () => {
|
||||
const strs = "abcd" +
|
||||
([
|
||||
0xF900,
|
||||
0xFFFFF,
|
||||
].map(ch => String.fromCodePoint(ch)).join(''));
|
||||
for (const s of strs) {
|
||||
const ch = s.codePointAt(0);
|
||||
assert.isFalse(isPUA(ch), describeCodepoint(ch));
|
||||
}
|
||||
});
|
||||
it('should detect PUA', () => {
|
||||
const strs = "\uE010" +
|
||||
([
|
||||
0xE000,0xE001,0xE002,
|
||||
0xF000,
|
||||
0xF800,
|
||||
0xF8FF,
|
||||
|
||||
0x0F0000,
|
||||
0x0FFFFD,
|
||||
|
||||
0x100000,
|
||||
0x10FFFD
|
||||
].map(ch => String.fromCodePoint(ch)).join(''));
|
||||
for (const s of strs) {
|
||||
const ch = s.codePointAt(0);
|
||||
assert.isTrue(isPUA(ch), describeCodepoint(ch));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('test BadStringAnalyzer', () => {
|
||||
describe('should return nothing for all valid strings', () => {
|
||||
const cases = [
|
||||
[],
|
||||
['a',],
|
||||
['a', 'b',]
|
||||
];
|
||||
for (const strs of cases) {
|
||||
const title = titleize(strs);
|
||||
it(`should analyze ${title}`, () => {
|
||||
const bsa = new BadStringAnalyzer();
|
||||
for (const s of strs) {
|
||||
bsa.add(s);
|
||||
}
|
||||
const m = bsa.analyze();
|
||||
assert.isNull(m, `${title}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('should return nothing for all valid strings', () => {
|
||||
it('should handle a case with some odd strs in it', () => {
|
||||
const strs = "But you can call me “\uE010\uFDD0\uFFFE\uD800”, for short." +
|
||||
([
|
||||
0xF800,
|
||||
0x05FFFF,
|
||||
0x102222,
|
||||
0x04FFFE,
|
||||
].map(ch => String.fromCodePoint(ch)).join(''));
|
||||
|
||||
const bsa = new BadStringAnalyzer();
|
||||
for (const s of strs) {
|
||||
bsa.add(s);
|
||||
}
|
||||
const m = bsa.analyze();
|
||||
assert.isNotNull(m);
|
||||
assert.containsAllKeys(m, [BadStringType.pua, BadStringType.illegal]);
|
||||
assert.sameDeepMembers(Array.from(m.get(BadStringType.pua).values()), [
|
||||
0xE010,0xF800, 0x102222,
|
||||
], `pua analysis`);
|
||||
assert.sameDeepMembers(Array.from(m.get(BadStringType.illegal).values()), [
|
||||
0xFDD0,0xD800,0xFFFE,
|
||||
0x05FFFF,
|
||||
0x04FFFE,
|
||||
], `illegal analysis`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,13 +45,19 @@ function KGetTempPath: string;
|
|||
|
||||
function GetLongFileName(const fname: string): string;
|
||||
|
||||
function DosSlashes(const filename: string): string;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.StrUtils,
|
||||
System.SysUtils,
|
||||
Winapi.Windows;
|
||||
|
||||
|
||||
function DosSlashes(const filename: string): string;
|
||||
begin
|
||||
Result := ReplaceStr(filename, '/', '\');
|
||||
end;
|
||||
|
||||
function DirectoryEmpty(dir: WideString): Boolean;
|
||||
var
|
||||
|
|
|
|||
|
|
@ -95,13 +95,15 @@ km_core_actions * km::core::action_item_list_to_actions_object(
|
|||
output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}});
|
||||
break;
|
||||
case KM_CORE_IT_PERSIST_OPT:
|
||||
{
|
||||
// TODO: lowpri: replace existing item if already present in options vector?
|
||||
options.push_back(km::core::option(
|
||||
static_cast<km_core_option_scope>(action_items->option->scope),
|
||||
km::core::option opt(static_cast<km_core_option_scope>(action_items->option->scope),
|
||||
action_items->option->key,
|
||||
action_items->option->value
|
||||
));
|
||||
);
|
||||
options.push_back(opt.release()); // hand over memory management of the option item to the action struct
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,6 +262,10 @@ void km_core_state_imx_deregister_callback(km_core_state *state)
|
|||
}
|
||||
|
||||
bool is_context_valid(km_core_cp const * context, km_core_cp const * cached_context) {
|
||||
if (context == nullptr || cached_context == nullptr || *cached_context == '\0') {
|
||||
// If the cached_context is "empty" then it needs updating
|
||||
return false;
|
||||
}
|
||||
km_core_cp const* context_p = context;
|
||||
while(*context_p) {
|
||||
context_p++;
|
||||
|
|
@ -355,4 +359,4 @@ km_core_status km_core_state_context_clear(
|
|||
}
|
||||
km_core_context_clear(km_core_state_context(state));
|
||||
return KM_CORE_STATUS_OK;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
using namespace km::core;
|
||||
using namespace kmx;
|
||||
|
||||
// TODO consolodate with appint.cpp and put in public library.
|
||||
|
||||
static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr)
|
||||
{
|
||||
assert(buf);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,14 @@ option::option(km_core_option_scope s, char16_t const *k, char16_t const *v)
|
|||
}
|
||||
}
|
||||
|
||||
km_core_option_item
|
||||
option::release() {
|
||||
km_core_option_item opt = *this;
|
||||
key = nullptr;
|
||||
value = nullptr;
|
||||
return opt;
|
||||
}
|
||||
|
||||
// TODO: Relocate this and fix it
|
||||
json & km::core::operator << (json &j, abstract_processor const &)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,10 +34,15 @@ namespace core
|
|||
option & operator=(option const & rhs);
|
||||
option & operator=(option && rhs);
|
||||
|
||||
/**
|
||||
* Returns contents of this object as a C struct, releasing memory
|
||||
* management of key and value, and invalidates this object.
|
||||
*/
|
||||
km_core_option_item release();
|
||||
|
||||
bool empty() const;
|
||||
};
|
||||
|
||||
|
||||
inline
|
||||
option::option(km_core_option_scope s,
|
||||
std::u16string const & k, std::u16string const & v)
|
||||
|
|
|
|||
|
|
@ -258,6 +258,27 @@ void test_context_set_if_needed_different_context() {
|
|||
teardown();
|
||||
}
|
||||
|
||||
void test_context_set_if_needed_cached_context_cleared() {
|
||||
km_core_cp const *application_context = u"This is a test";
|
||||
km_core_cp const *cached_context = u"";
|
||||
setup("k_000___null_keyboard.kmx", cached_context);
|
||||
km_core_state_context_clear(test_state);
|
||||
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
|
||||
assert(!is_identical_context(cached_context));
|
||||
assert(is_identical_context(application_context));
|
||||
teardown();
|
||||
}
|
||||
|
||||
void test_context_set_if_needed_application_context_empty() {
|
||||
km_core_cp const *application_context = u"";
|
||||
km_core_cp const *cached_context = u"This is a test";
|
||||
setup("k_000___null_keyboard.kmx", cached_context);
|
||||
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
|
||||
assert(!is_identical_context(cached_context));
|
||||
assert(is_identical_context(application_context));
|
||||
teardown();
|
||||
}
|
||||
|
||||
void test_context_set_if_needed_app_context_is_longer() {
|
||||
km_core_cp const *application_context = u"Longer This is a test";
|
||||
km_core_cp const *cached_context = u"This is a test";
|
||||
|
|
@ -319,6 +340,8 @@ void test_context_set_if_needed_cached_context_has_markers() {
|
|||
void test_context_set_if_needed() {
|
||||
test_context_set_if_needed_identical_context();
|
||||
test_context_set_if_needed_different_context();
|
||||
test_context_set_if_needed_cached_context_cleared();
|
||||
test_context_set_if_needed_application_context_empty();
|
||||
test_context_set_if_needed_app_context_is_longer();
|
||||
test_context_set_if_needed_app_context_is_shorter();
|
||||
test_context_set_if_needed_cached_context_has_markers();
|
||||
|
|
@ -374,6 +397,7 @@ int main(int argc, char *argv []) {
|
|||
test_alert();
|
||||
test_emit_keystroke();
|
||||
test_invalidate_context();
|
||||
test_persist_opt();
|
||||
|
||||
// context -- todo move to another file
|
||||
test_context_set_if_needed();
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@
|
|||
|
||||
#define CHINT_UnreachableRule 0x000010AE
|
||||
|
||||
#define CWARN_VirtualKeyInOutput 0x000020AF
|
||||
|
||||
#define CERR_BufferOverflow 0x000080C0
|
||||
#define CERR_Break 0x000080C1
|
||||
|
||||
|
|
|
|||
|
|
@ -302,6 +302,8 @@ export class KmnCompilerMessages {
|
|||
|
||||
static HINT_UnreachableRule = SevHint | 0x0AE;
|
||||
|
||||
static WARN_VirtualKeyInOutput = SevWarn | 0x0AF;
|
||||
|
||||
static FATAL_BufferOverflow = SevFatal | 0x0C0;
|
||||
static FATAL_Break = SevFatal | 0x0C1;
|
||||
};
|
||||
|
|
|
|||
9
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_virtual_key_in_output.kmn
vendored
Normal file
9
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_virtual_key_in_output.kmn
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
store(&NAME) 'WARN_VirtualKeyInOutput'
|
||||
store(&VERSION) '9.0'
|
||||
|
||||
begin Unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
||||
c WARN_VirtualKeyInOutput
|
||||
+ 'a' > [K_BKQUOTE]
|
||||
|
|
@ -87,4 +87,11 @@ describe('CompilerMessages', function () {
|
|||
assert.equal(callbacks.messages[0].message, "Statement 'return' is not currently supported in output for web and touch targets");
|
||||
});
|
||||
|
||||
// WARN_VirtualKeyInOutput
|
||||
|
||||
it('should generate WARN_VirtualKeyInOutput if a virtual key is found in the output part of a rule', async function() {
|
||||
await testForMessage(this, ['invalid-keyboards', 'warn_virtual_key_in_output.kmn'], KmnCompilerMessages.WARN_VirtualKeyInOutput);
|
||||
assert.equal(callbacks.messages[0].message, "Virtual keys are not supported in output");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ export class LdmlKeyboardCompiler {
|
|||
* @param source
|
||||
* @returns true if the file validates
|
||||
*/
|
||||
public validate(source: LDMLKeyboardXMLSourceFile): boolean {
|
||||
return !!this.compile(source);
|
||||
public async validate(source: LDMLKeyboardXMLSourceFile): Promise<boolean> {
|
||||
return !!(await this.compile(source, true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,7 +157,7 @@ export class LdmlKeyboardCompiler {
|
|||
* @param source in-memory representation of LDML keyboard xml file
|
||||
* @returns KMXPlusFile intermediate file
|
||||
*/
|
||||
public async compile(source: LDMLKeyboardXMLSourceFile): Promise<KMXPlus.KMXPlusFile> {
|
||||
public async compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise<KMXPlus.KMXPlusFile> {
|
||||
const sections = this.buildSections(source);
|
||||
let passed = true;
|
||||
|
||||
|
|
@ -207,6 +207,15 @@ export class LdmlKeyboardCompiler {
|
|||
kmx.kmxplus[section.id] = sect as any;
|
||||
}
|
||||
|
||||
// give all sections a chance to postValidate
|
||||
if (postValidate) {
|
||||
for(let section of sections) {
|
||||
if(!section.postValidate(kmx.kmxplus[section.id])) {
|
||||
passed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return passed ? kmx : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { SectionCompiler } from "./section-compiler.js";
|
||||
import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types";
|
||||
import { LDMLKeyboard, KMXPlus, CompilerCallbacks, util, MarkerParser } from "@keymanapp/common-types";
|
||||
import { VarsCompiler } from './vars.js';
|
||||
import { CompilerMessages } from './messages.js';
|
||||
|
||||
/**
|
||||
* Compiler for typrs that don't actually consume input XML
|
||||
|
|
@ -28,6 +29,45 @@ export class StrsCompiler extends EmptyCompiler {
|
|||
public compile(sections: KMXPlus.DependencySections): KMXPlus.Section {
|
||||
return new KMXPlus.Strs();
|
||||
}
|
||||
public postValidate(section?: KMXPlus.Section): boolean {
|
||||
const strs = <KMXPlus.Strs>section;
|
||||
|
||||
if (strs) {
|
||||
const badStringAnalyzer = new util.BadStringAnalyzer();
|
||||
const CONTAINS_MARKER_REGEX = new RegExp(MarkerParser.ANY_MARKER_MATCH);
|
||||
for (let s of strs.allProcessedStrings.values()) {
|
||||
// skip marker strings
|
||||
if (CONTAINS_MARKER_REGEX.test(s)) {
|
||||
// it had a marker, take out all marker strings, as the sentinel is illegal
|
||||
// need a new regex to match
|
||||
const REPLACE_MARKER_REGEX = new RegExp(MarkerParser.ANY_MARKER_MATCH, 'g');
|
||||
s = s.replaceAll(REPLACE_MARKER_REGEX, ''); // remove markers.
|
||||
}
|
||||
badStringAnalyzer.add(s);
|
||||
}
|
||||
const m = badStringAnalyzer.analyze();
|
||||
if (m?.size > 0) {
|
||||
const puas = m.get(util.BadStringType.pua);
|
||||
const unassigneds = m.get(util.BadStringType.unassigned);
|
||||
const illegals = m.get(util.BadStringType.illegal);
|
||||
if (puas) {
|
||||
const [count, lowestCh] = [puas.size, Array.from(puas.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(CompilerMessages.Hint_PUACharacters({ count, lowestCh }))
|
||||
}
|
||||
if (unassigneds) {
|
||||
const [count, lowestCh] = [unassigneds.size, Array.from(unassigneds.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(CompilerMessages.Warn_UnassignedCharacters({ count, lowestCh }))
|
||||
}
|
||||
if (illegals) {
|
||||
// do this last, because we will return false.
|
||||
const [count, lowestCh] = [illegals.size, Array.from(illegals.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(CompilerMessages.Error_IllegalCharacters({ count, lowestCh }))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class ElemCompiler extends EmptyCompiler {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types";
|
||||
|
||||
import { util, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types";
|
||||
// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
|
|
@ -146,6 +145,18 @@ export class CompilerMessages {
|
|||
static Error_DisplayNeedsToOrId = (o:{output?: string, keyId?: string}) =>
|
||||
m(this.ERROR_DisplayNeedsToOrId, `display ${CompilerMessages.outputOrKeyId(o)} needs output= or keyId=, but not both`);
|
||||
static ERROR_DisplayNeedsToOrId = SevError | 0x0022;
|
||||
|
||||
static Hint_PUACharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.HINT_PUACharacters, `File contains ${o.count} PUA character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static HINT_PUACharacters = SevHint | 0x0023;
|
||||
|
||||
static Warn_UnassignedCharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.WARN_UnassignedCharacters, `File contains ${o.count} unassigned character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static WARN_UnassignedCharacters = SevWarn | 0x0024;
|
||||
|
||||
static Error_IllegalCharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.ERROR_IllegalCharacters, `File contains ${o.count} illegal character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static ERROR_IllegalCharacters = SevError | 0x0025;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types";
|
||||
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
|
||||
/* istanbul ignore next */
|
||||
export class SectionCompiler {
|
||||
/** newable interface to SectionCompiler c'tor */
|
||||
export type SectionCompilerNew = new (source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) => SectionCompiler;
|
||||
export abstract class SectionCompiler {
|
||||
protected readonly keyboard3: LDMLKeyboard.LKKeyboard;
|
||||
protected readonly callbacks: CompilerCallbacks;
|
||||
|
||||
|
|
@ -11,19 +12,35 @@ export class SectionCompiler {
|
|||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/* c8 ignore next 11 */
|
||||
public get id(): SectionIdent {
|
||||
throw Error(`Internal Error: id() not implemented`);
|
||||
}
|
||||
|
||||
public compile(sections: KMXPlus.DependencySections): KMXPlus.Section {
|
||||
throw Error(`Internal Error: compile() not implemented`);
|
||||
}
|
||||
public abstract get id(): SectionIdent;
|
||||
|
||||
/**
|
||||
* This is called before compile.
|
||||
* @returns false if this compiler failed to validate.
|
||||
*/
|
||||
public validate(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the compilation for this section, returning the correct Section subclass
|
||||
* object.
|
||||
*
|
||||
* @param sections any declared dependency sections per dependencies()
|
||||
*/
|
||||
public abstract compile(sections: KMXPlus.DependencySections): KMXPlus.Section;
|
||||
|
||||
/**
|
||||
* This is called after all other compile phases have completed,
|
||||
* when being called by validate(), and provides an
|
||||
* opportunity for late error reporting, for example for invalid strings.
|
||||
* @param section the compiled section, if any.
|
||||
* @returns false if validate fails
|
||||
*/
|
||||
public postValidate(section?: KMXPlus.Section): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies for this compiler.
|
||||
* @returns set of dependent sections
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { MarkerTracker, MarkerUse } from "./marker-tracker.js";
|
|||
|
||||
type TransformCompilerType = 'simple' | 'backspace';
|
||||
|
||||
export class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {
|
||||
export abstract class TransformCompiler<T extends TransformCompilerType, TranBase extends Tran> extends SectionCompiler {
|
||||
|
||||
static validateMarkers(keyboard: LDMLKeyboard.LKKeyboard, mt : MarkerTracker): boolean {
|
||||
keyboard?.transforms?.forEach(transforms =>
|
||||
|
|
|
|||
58
developer/src/kmc-ldml/test/fixtures/sections/strs/hint-pua.xml
vendored
Normal file
58
developer/src/kmc-ldml/test/fixtures/sections/strs/hint-pua.xml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
@@keys: [K_Q][K_W][K_Q]
|
||||
@@expected: \u0127\u1790\u17B6\u0127
|
||||
-->
|
||||
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
|
||||
<keyboard3 locale="mt" conformsTo="techpreview">
|
||||
<version number="1.0.0" />
|
||||
|
||||
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
|
||||
|
||||
<displays>
|
||||
<!-- values chosen to reuse strings -->
|
||||
<display output="a" display="^" />
|
||||
<display keyId="\u{E020}" display="^e" />
|
||||
<displayOptions baseCharacter="e" />
|
||||
</displays>
|
||||
<keys>
|
||||
<!-- Note: implied keys here: 'a' and 'e' -->
|
||||
<key id="hmaqtugha" output="hhh" longPressKeyIds="a e" />
|
||||
</keys>
|
||||
|
||||
<layers formId="us" minDeviceWidth="123">
|
||||
<layer id="zz">
|
||||
<row keys="hmaqtugha" />
|
||||
</layer>
|
||||
</layers>
|
||||
|
||||
<variables>
|
||||
<string id="a" value="\m{a}"/>
|
||||
<string id="vst" value="pua:\u{E010}"/>
|
||||
<set id="vse" value="a b"/>
|
||||
<unicodeSet id="vus" value="[abc]"/>
|
||||
</variables>
|
||||
|
||||
<transforms type="simple">
|
||||
<transformGroup>
|
||||
<transform from="^a" to="q" />
|
||||
<transform from="a" to="\m{a}" />
|
||||
</transformGroup>
|
||||
<transformGroup>
|
||||
<!-- delete that marker -->
|
||||
<transform from="\m{a}\u{E020}" />
|
||||
</transformGroup>
|
||||
|
||||
<transformGroup>
|
||||
<!-- Northern Thai example from spec -->
|
||||
<reorder before="\u{1A6B}" from="\u{1A60}[\u1A75-\u1A79]\u{1A45}" order="10 55 10" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
|
||||
<transforms type="backspace">
|
||||
<transformGroup>
|
||||
<transform from="^e" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
</keyboard3>
|
||||
59
developer/src/kmc-ldml/test/fixtures/sections/strs/invalid-illegal.xml
vendored
Normal file
59
developer/src/kmc-ldml/test/fixtures/sections/strs/invalid-illegal.xml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
@@keys: [K_Q][K_W][K_Q]
|
||||
@@expected: \u0127\u1790\u17B6\u0127
|
||||
-->
|
||||
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
|
||||
<keyboard3 locale="mt" conformsTo="techpreview">
|
||||
<version number="1.0.0" />
|
||||
|
||||
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
|
||||
|
||||
<displays>
|
||||
<!-- values chosen to reuse strings -->
|
||||
<display output="a" display="^" />
|
||||
<display keyId="e" display="^e" />
|
||||
<displayOptions baseCharacter="e" />
|
||||
</displays>
|
||||
<keys>
|
||||
<!-- Note: implied keys here: 'a' and 'e' -->
|
||||
<key id="hmaqtugha" output="h\u{FDD0}" longPressKeyIds="a e" />
|
||||
<key id="that" output="" /> <!-- illegal NCR (single char)-->
|
||||
</keys>
|
||||
|
||||
<layers formId="us" minDeviceWidth="123">
|
||||
<layer id="">
|
||||
<row keys="hmaqtugha that" />
|
||||
</layer>
|
||||
</layers>
|
||||
|
||||
<variables>
|
||||
<string id="a" value="\m{a}"/>
|
||||
<string id="vst" value="abc pua:\u{E010}"/>
|
||||
<set id="vse" value="a b \u{04FFFE} \u{5FFFF}"/>
|
||||
<unicodeSet id="vus" value="[abc]"/>
|
||||
</variables>
|
||||
|
||||
<transforms type="simple">
|
||||
<transformGroup>
|
||||
<transform from="^a" to="\u{FDD0}" />
|
||||
<transform from="a" to="\m{a}\u{E020}" /> <!-- another PUA, with a marker -->
|
||||
</transformGroup>
|
||||
<transformGroup>
|
||||
<!-- delete that marker -->
|
||||
<transform from="\m{a}" />
|
||||
</transformGroup>
|
||||
|
||||
<transformGroup>
|
||||
<!-- Northern Thai example from spec -->
|
||||
<reorder before="\u{1A6B}" from="\u{1A60}[\u1A75-\u1A79]\u{1A45}" order="10 55 10" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
|
||||
<transforms type="backspace">
|
||||
<transformGroup>
|
||||
<transform from="^e" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
</keyboard3>
|
||||
58
developer/src/kmc-ldml/test/fixtures/sections/strs/warn-unassigned.xml
vendored
Normal file
58
developer/src/kmc-ldml/test/fixtures/sections/strs/warn-unassigned.xml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
@@keys: [K_Q][K_W][K_Q]
|
||||
@@expected: \u0127\u1790\u17B6\u0127
|
||||
-->
|
||||
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
|
||||
<keyboard3 locale="mt" conformsTo="techpreview">
|
||||
<version number="1.0.0" />
|
||||
|
||||
<info author="srl295" indicator="🙀" layout="qwerty" name="TestKbd"/>
|
||||
|
||||
<displays>
|
||||
<!-- values chosen to reuse strings -->
|
||||
<display output="a" display="^" />
|
||||
<display keyId="\u{E020}" display="^e" />
|
||||
<displayOptions baseCharacter="e" />
|
||||
</displays>
|
||||
<keys>
|
||||
<!-- unassigned char (as of this writing) -->
|
||||
<key id="hmaqtugha" output="\u{CFFFD}" longPressKeyIds="a e" />
|
||||
</keys>
|
||||
|
||||
<layers formId="us" minDeviceWidth="123">
|
||||
<layer id="zz">
|
||||
<row keys="hmaqtugha" />
|
||||
</layer>
|
||||
</layers>
|
||||
|
||||
<variables>
|
||||
<string id="a" value="\m{a}"/>
|
||||
<string id="vst" value="pua:\u{E010}"/>
|
||||
<set id="vse" value="a b"/>
|
||||
<unicodeSet id="vus" value="[abc]"/>
|
||||
</variables>
|
||||
|
||||
<transforms type="simple">
|
||||
<transformGroup>
|
||||
<transform from="^a" to="q" />
|
||||
<transform from="a" to="\m{a}" />
|
||||
</transformGroup>
|
||||
<transformGroup>
|
||||
<!-- delete that marker -->
|
||||
<transform from="\m{a}\u{E020}" />
|
||||
</transformGroup>
|
||||
|
||||
<transformGroup>
|
||||
<!-- Northern Thai example from spec -->
|
||||
<reorder before="\u{1A6B}" from="\u{1A60}[\u1A75-\u1A79]\u{1A45}" order="10 55 10" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
|
||||
<transforms type="backspace">
|
||||
<transformGroup>
|
||||
<transform from="^e" />
|
||||
</transformGroup>
|
||||
</transforms>
|
||||
</keyboard3>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { SectionCompiler } from '../../src/compiler/section-compiler.js';
|
||||
import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js';
|
||||
import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, LDMLKeyboardTestDataXMLSourceFile, compilerEventFormat, LDMLKeyboard, UnicodeSetParser, CompilerCallbacks } from '@keymanapp/common-types';
|
||||
import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles
|
||||
import { assert } from 'chai';
|
||||
|
|
@ -52,7 +52,7 @@ afterEach(function() {
|
|||
});
|
||||
|
||||
|
||||
export async function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: TestCompilerCallbacks, dependencies?: typeof SectionCompiler[]): Promise<Section> {
|
||||
export async function loadSectionFixture(compilerClass: SectionCompilerNew, filename: string, callbacks: TestCompilerCallbacks, dependencies?: SectionCompilerNew[], postValidateFail?: boolean): Promise<Section> {
|
||||
callbacks.messages = [];
|
||||
const inputFilename = makePathToFixture(filename);
|
||||
const data = callbacks.loadFile(inputFilename);
|
||||
|
|
@ -83,13 +83,16 @@ export async function loadSectionFixture(compilerClass: typeof SectionCompiler,
|
|||
compiler.dependencies.forEach(dep => assert.ok(sections[dep],
|
||||
`Required dependency '${dep}' for '${compiler.id}' was not supplied: Check the 'dependencies' argument to loadSectionFixture or testCompilationCases`));
|
||||
|
||||
return compiler.compile(sections);
|
||||
const section = await compiler.compile(sections);
|
||||
const postValidate = compiler.postValidate(section);
|
||||
assert.equal(postValidate, !postValidateFail, `expected postValidate() to return ${!postValidateFail}`);
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively load dependencies. Normally they are loaded in SECTION_COMPILERS order
|
||||
*/
|
||||
async function loadDepsFor(sections: DependencySections, parentCompiler: SectionCompiler, source: LDMLKeyboardXMLSourceFile, callbacks: TestCompilerCallbacks, dependencies?: typeof SectionCompiler[]) {
|
||||
async function loadDepsFor(sections: DependencySections, parentCompiler: SectionCompiler, source: LDMLKeyboardXMLSourceFile, callbacks: TestCompilerCallbacks, dependencies?: SectionCompilerNew[]) {
|
||||
const parentId = parentCompiler.id;
|
||||
if (!dependencies) {
|
||||
// default dependencies
|
||||
|
|
@ -115,18 +118,29 @@ export function loadTestdata(inputFilename: string, options: LdmlCompilerOptions
|
|||
return source;
|
||||
}
|
||||
|
||||
export async function compileKeyboard(inputFilename: string, options: LdmlCompilerOptions): Promise<KMXPlusFile> {
|
||||
export async function compileKeyboard(inputFilename: string, options: LdmlCompilerOptions, validateMessages?: CompilerEvent[], expectFailValidate?: boolean, compileMessages?: CompilerEvent[]): Promise<KMXPlusFile> {
|
||||
const k = new LdmlKeyboardCompiler(compilerTestCallbacks, options);
|
||||
const source = k.load(inputFilename);
|
||||
checkMessages();
|
||||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
const valid = k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
const valid = await k.validate(source);
|
||||
if (validateMessages) {
|
||||
assert.sameDeepMembers(compilerTestCallbacks.messages, validateMessages, "validation messages mismatch");
|
||||
assert.notEqual(valid, expectFailValidate, 'validation failure');
|
||||
} else {
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
}
|
||||
|
||||
if (!valid) return null; // get out, if the above asserts didn't get us out.
|
||||
|
||||
const kmx = await k.compile(source);
|
||||
checkMessages();
|
||||
if (compileMessages) {
|
||||
assert.sameDeepMembers(compilerTestCallbacks.messages, compileMessages, "compiler messages mismatch");
|
||||
} else {
|
||||
checkMessages();
|
||||
}
|
||||
assert.isNotNull(kmx, 'k.compile should not have returned null');
|
||||
|
||||
// In order for the KMX file to be loaded by non-KMXPlus components, it is helpful
|
||||
|
|
@ -136,13 +150,13 @@ export async function compileKeyboard(inputFilename: string, options: LdmlCompil
|
|||
return kmx;
|
||||
}
|
||||
|
||||
export function compileVisualKeyboard(inputFilename: string, options: LdmlCompilerOptions): VisualKeyboard.VisualKeyboard {
|
||||
export async function compileVisualKeyboard(inputFilename: string, options: LdmlCompilerOptions): Promise<VisualKeyboard.VisualKeyboard> {
|
||||
const k = new LdmlKeyboardCompiler(compilerTestCallbacks, options);
|
||||
const source = k.load(inputFilename);
|
||||
checkMessages();
|
||||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
const valid = k.validate(source);
|
||||
const valid = await k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
|
||||
|
|
@ -184,7 +198,11 @@ export interface CompilationCase {
|
|||
/**
|
||||
* Optional dependent sections to load. Will be strs+list+elem if falsy.
|
||||
*/
|
||||
dependencies?: (typeof SectionCompiler)[];
|
||||
dependencies?: (SectionCompilerNew)[];
|
||||
/**
|
||||
* Optional, if true, postValidate() must return false. (must be != postValidate())
|
||||
*/
|
||||
postValidateFail?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -193,7 +211,7 @@ export interface CompilationCase {
|
|||
* @param compiler argument to loadSectionFixture()
|
||||
* @param callbacks argument to loadSectionFixture()
|
||||
*/
|
||||
export function testCompilationCases(compiler: typeof SectionCompiler, cases : CompilationCase[], dependencies?: (typeof SectionCompiler)[]) {
|
||||
export function testCompilationCases(compiler: SectionCompilerNew, cases : CompilationCase[], dependencies?: (SectionCompilerNew)[]) {
|
||||
// we need our own callbacks rather than using the global so messages don't get mixed
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
for (let testcase of cases) {
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import hextobin from '@keymanapp/hextobin';
|
|||
import { KMXBuilder } from '@keymanapp/common-types';
|
||||
import {checkMessages, compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture} from './helpers/index.js';
|
||||
import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js';
|
||||
import { CompilerMessages } from '../src/compiler/messages.js';
|
||||
|
||||
describe('compiler-tests', function() {
|
||||
this.slow(500); // 0.5 sec -- json schema validation takes a while
|
||||
|
||||
before(function() {
|
||||
compilerTestCallbacks.clear();
|
||||
});
|
||||
|
||||
it('should-build-fixtures', async function() {
|
||||
// Let's build basic.xml
|
||||
// It should match basic.kmx (built from basic.txt)
|
||||
|
|
@ -61,4 +66,50 @@ describe('compiler-tests', function() {
|
|||
const source = k.load(filename);
|
||||
assert.notOk(source, `Trying to loadTestData(${filename})`);
|
||||
});
|
||||
it('should fail on illegal chars', async function() {
|
||||
const inputFilename = makePathToFixture('sections/strs/invalid-illegal.xml');
|
||||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
CompilerMessages.Error_IllegalCharacters({ count: 5, lowestCh: 0xFDD0 }),
|
||||
CompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
],
|
||||
true, // validation should fail
|
||||
[
|
||||
// compiler messages (not reached, we've already failed)
|
||||
]);
|
||||
assert.isNull(kmx); // should fail post-validate
|
||||
});
|
||||
it('should hint on pua chars', async function() {
|
||||
const inputFilename = makePathToFixture('sections/strs/hint-pua.xml');
|
||||
// Compile the keyboard
|
||||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
CompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
],
|
||||
false, // validation should pass
|
||||
[
|
||||
// same messages
|
||||
CompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
]);
|
||||
assert.isNotNull(kmx);
|
||||
});
|
||||
it.skip('should warn on unassigned chars', async function() {
|
||||
// unassigned not implemented yet
|
||||
const inputFilename = makePathToFixture('sections/strs/warn-unassigned.xml');
|
||||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
CompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
CompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: 0x0CFFFD }),
|
||||
],
|
||||
false, // validation should pass
|
||||
[
|
||||
// same messages
|
||||
CompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
CompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: 0x0CFFFD }),
|
||||
]);
|
||||
assert.isNotNull(kmx);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() {
|
|||
assert.isNotNull(source, 'k.load should not have returned null');
|
||||
|
||||
// Sanity check ... this is also checked in other tests
|
||||
const valid = k.validate(source);
|
||||
const valid = await k.validate(source);
|
||||
checkMessages();
|
||||
assert.isTrue(valid, 'k.validate should not have failed');
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ describe('visual-keyboard-compiler', function() {
|
|||
const binaryFilename = makePathToFixture('basic-kvk.txt');
|
||||
|
||||
// Compile the visual keyboard
|
||||
const vk = compileVisualKeyboard(inputFilename, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
|
||||
const vk = await compileVisualKeyboard(inputFilename, {...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false});
|
||||
assert.isNotNull(vk);
|
||||
|
||||
// Use the builder to generate the binary output file
|
||||
|
|
@ -28,4 +28,4 @@ describe('visual-keyboard-compiler', function() {
|
|||
let expected = await hextobin(binaryFilename, undefined, {silent:true});
|
||||
assert.deepEqual<Uint8Array>(code, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export class KmpCompiler {
|
|||
if(kps.Files && kps.Files.File) {
|
||||
kmp.files = this.arrayWrap(kps.Files.File).map((file: KpsFile.KpsFileContentFile) => {
|
||||
return {
|
||||
name: file.Name.trim(),
|
||||
name: file.Name.trim().replaceAll('\\','/'),
|
||||
description: file.Description.trim(),
|
||||
copyLocation: parseInt(file.CopyLocation, 10) || undefined
|
||||
// note: we don't emit fileType as that is not permitted in kmp.json
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
},
|
||||
"files": [
|
||||
{
|
||||
"name": "..\\build\\withfolders.qaa.sencoten.model.js",
|
||||
"name": "../build/withfolders.qaa.sencoten.model.js",
|
||||
"description": "Lexical model withfolders.qaa.sencoten.model.js"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ const struct CompilerError CompilerErrors[] = {
|
|||
{ CWARN_NulNotFirstStatementInContext , "nul must be the first statement in the context"},
|
||||
{ CWARN_IfShouldBeAtStartOfContext , "if, platform and baselayout should be at start of context (after nul, if present)"},
|
||||
{ CWARN_KeyShouldIncludeNCaps , "Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches"},
|
||||
{ CWARN_VirtualKeyInOutput , "Virtual keys are not supported in output"},
|
||||
|
||||
{ 0, nullptr }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1331,6 +1331,7 @@ KMX_BOOL CheckContextStatementPositions(PKMX_WCHAR context) {
|
|||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a use() statement is followed by other content in the output of a rule
|
||||
*/
|
||||
|
|
@ -1348,6 +1349,22 @@ KMX_DWORD CheckUseStatementsInOutput(PKMX_WCHAR output) {
|
|||
return CERR_None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn if output has virtual keys in it, which is not supported by Core at all,
|
||||
* but was unofficially supported, but never worked properly, in Keyman for
|
||||
* Windows for many years
|
||||
*/
|
||||
KMX_DWORD CheckVirtualKeysInOutput(PKMX_WCHAR output) {
|
||||
PKMX_WCHAR p;
|
||||
for (p = output; *p; p = incxstr(p)) {
|
||||
if (*p == UC_SENTINEL && *(p + 1) == CODE_EXTENDED) {
|
||||
AddWarning(CWARN_VirtualKeyInOutput);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return CERR_None;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds implicit `context` to start of output of rules for readonly groups
|
||||
*/
|
||||
|
|
@ -1472,6 +1489,11 @@ KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnico
|
|||
return msg; // I4867
|
||||
}
|
||||
|
||||
// Warn if virtual keys are used in the output, as they are unsupported by Core
|
||||
if ((msg = CheckVirtualKeysInOutput(pklOut)) != CERR_None) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (gp->fReadOnly) {
|
||||
// Ensure no output is made from the rule, and that
|
||||
// use() statements meet required readonly semantics
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ const DefaultProjectOptions: array[TProjectVersion] of TProjectOptionsRecord = (
|
|||
ProjectType: ptKeyboard;
|
||||
Version: pv10
|
||||
), ( // 2.0
|
||||
BuildPath: '$PROJECTPATH/build';
|
||||
SourcePath: '$PROJECTPATH/source';
|
||||
BuildPath: '$PROJECTPATH\build';
|
||||
SourcePath: '$PROJECTPATH\source';
|
||||
CompilerWarningsAsErrors: False;
|
||||
WarnDeprecatedCode: True;
|
||||
CheckFilenameConventions: False;
|
||||
|
|
@ -621,8 +621,8 @@ begin
|
|||
Exit(True);
|
||||
|
||||
// Only return true if the file is directly in the ProjectOptions.SourcePath folder
|
||||
SourcePath := ReplaceStr(IncludeTrailingPathDelimiter(FProject.ResolveProjectPath(FProject.Options.SourcePath)), '/', '\');
|
||||
FilePath := ReplaceStr(ExtractFilePath(FFileName), '/', '\');
|
||||
SourcePath := DosSlashes(FProject.ResolveProjectPath(FProject.Options.SourcePath));
|
||||
FilePath := DosSlashes(ExtractFilePath(FFileName));
|
||||
Result := SameFileName(SourcePath, FilePath);
|
||||
end;
|
||||
|
||||
|
|
@ -662,7 +662,7 @@ procedure TProjectFile.Save(node: IXMLNode); // I4698
|
|||
begin
|
||||
node.AddChild('ID').NodeValue := FID;
|
||||
node.AddChild('Filename').NodeValue := ExtractFileName(FFileName);
|
||||
node.AddChild('Filepath').NodeValue := ExtractRelativePath(FProject.FileName, FFileName);
|
||||
node.AddChild('Filepath').NodeValue := ExtractRelativePath(FProject.FileName, DosSlashes(FFileName));
|
||||
node.AddChild('FileVersion').NodeValue := FFileVersion; // I4701
|
||||
|
||||
// Note: FileType is only ever written in Delphi code; it is used by xsl
|
||||
|
|
@ -978,18 +978,21 @@ end;
|
|||
///
|
||||
function TProject.PopulateFiles: Boolean;
|
||||
var
|
||||
ProjectPath: string;
|
||||
SourcePath, ProjectPath: string;
|
||||
begin
|
||||
if FOptions.Version <> pv20 then
|
||||
raise EProjectLoader.Create('PopulateFiles can only be called on a v2.0 project');
|
||||
|
||||
FFiles.Clear;
|
||||
|
||||
ProjectPath := ExtractFilePath(FileName);
|
||||
ProjectPath := ExpandFileName(ExtractFilePath(FileName));
|
||||
if not DirectoryExists(ProjectPath) then
|
||||
Exit(False);
|
||||
|
||||
PopulateFolder(ProjectPath);
|
||||
SourcePath := ResolveProjectPath(FOptions.SourcePath);
|
||||
if not SameFileName(ProjectPath, SourcePath) and DirectoryExists(SourcePath) then
|
||||
PopulateFolder(SourcePath);
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
|
@ -999,7 +1002,7 @@ var
|
|||
ff: string;
|
||||
f: TSearchRec;
|
||||
begin
|
||||
if FindFirst(path + '*', faDirectory, f) = 0 then
|
||||
if FindFirst(path + '*', 0, f) = 0 then
|
||||
begin
|
||||
repeat
|
||||
ff := path + f.Name;
|
||||
|
|
@ -1009,12 +1012,6 @@ begin
|
|||
Continue;
|
||||
end;
|
||||
|
||||
if (f.Attr and faDirectory) = faDirectory then
|
||||
begin
|
||||
PopulateFolder(ff + '\');
|
||||
Continue;
|
||||
end;
|
||||
|
||||
CreateProjectFile(Self, ff, nil);
|
||||
until FindNext(f) <> 0;
|
||||
System.SysUtils.FindClose(f);
|
||||
|
|
@ -1231,7 +1228,7 @@ end;
|
|||
|
||||
function TProject.ResolveProjectPath(APath: string): string;
|
||||
begin
|
||||
Result := ReplaceText(APath, '$PROJECTPATH', ExtractFileDir(ExpandFileName(FFileName)));
|
||||
Result := IncludeTrailingPathDelimiter(ReplaceText(APath, '$PROJECTPATH', ExtractFileDir(ExpandFileName(FFileName))));
|
||||
end;
|
||||
|
||||
function TProject.GetTargetFilename10(ATargetFile, ASourceFile, AVersion: string): string; // I4688
|
||||
|
|
@ -1256,7 +1253,6 @@ begin
|
|||
Exit(ExtractFilePath(ExpandFileName(ASourceFile)) + ExtractFileName(ATargetFile));
|
||||
end;
|
||||
|
||||
Result := IncludeTrailingPathDelimiter(Result);
|
||||
Result := ResolveProjectPath(Result);
|
||||
Result := Result + ExtractFileName(ATargetFile);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ uses
|
|||
Keyman.Developer.System.Project.ProjectFiles,
|
||||
Keyman.Developer.System.Project.ProjectFileType,
|
||||
|
||||
utildir,
|
||||
utilfiletypes;
|
||||
|
||||
{ TProjectLoader }
|
||||
|
|
@ -131,10 +132,10 @@ begin
|
|||
FProject.Options.Assign(DefaultProjectOptions[FProject.Options.Version]);
|
||||
|
||||
if not VarIsNull(node.ChildValues['BuildPath']) then
|
||||
FProject.Options.BuildPath := VarToStr(node.ChildValues['BuildPath']);
|
||||
FProject.Options.BuildPath := DosSlashes(VarToStr(node.ChildValues['BuildPath']));
|
||||
|
||||
if not VarIsNull(node.ChildValues['SourcePath']) then
|
||||
FProject.Options.SourcePath := VarToStr(node.ChildValues['SourcePath']);
|
||||
FProject.Options.SourcePath := DosSlashes(VarToStr(node.ChildValues['SourcePath']));
|
||||
|
||||
if not VarIsNull(node.ChildValues['CompilerWarningsAsErrors']) then
|
||||
FProject.Options.CompilerWarningsAsErrors := node.ChildValues['CompilerWarningsAsErrors'];
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
(*
|
||||
Name: Keyman.Developer.UI.Project.UfrmProjectSettings
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 4 May 2015
|
||||
|
||||
Modified Date: 24 Aug 2015
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 04 May 2015 - mcdurdin - I4688 - V9.0 - Add build path to project settings
|
||||
24 Aug 2015 - mcdurdin - I4865 - Add treat hints and warnings as errors into project
|
||||
24 Aug 2015 - mcdurdin - I4866 - Add warn on deprecated features to project and compile
|
||||
|
||||
|
||||
*)
|
||||
unit Keyman.Developer.UI.Project.UfrmProjectSettings20; // I4688
|
||||
|
||||
|
|
@ -54,12 +54,13 @@ implementation
|
|||
{$R *.dfm}
|
||||
|
||||
uses
|
||||
Keyman.Developer.System.Project.Project;
|
||||
Keyman.Developer.System.Project.Project,
|
||||
utildir;
|
||||
|
||||
procedure TfrmProjectSettings20.cmdOKClick(Sender: TObject);
|
||||
begin
|
||||
FGlobalProject.Options.BuildPath := Trim(editOutputPath.Text);
|
||||
FGlobalProject.Options.SourcePath := Trim(editSourcePath.Text);
|
||||
FGlobalProject.Options.BuildPath := Trim(DosSlashes(editOutputPath.Text));
|
||||
FGlobalProject.Options.SourcePath := Trim(DosSlashes(editSourcePath.Text));
|
||||
FGlobalProject.Options.SkipMetadataFiles := not chkBuildMetadataFiles.Checked;
|
||||
FGlobalProject.Options.CompilerWarningsAsErrors := chkCompilerWarningsAsErrors.Checked; // I4865
|
||||
FGlobalProject.Options.WarnDeprecatedCode := chkWarnDeprecatedCode.Checked; // I4866
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@
|
|||
<xsl:call-template name="file">
|
||||
<xsl:with-param name="file_description"></xsl:with-param>
|
||||
<xsl:with-param name="file_has_details">false</xsl:with-param>
|
||||
<xsl:with-param name="file_relative_path">true</xsl:with-param>
|
||||
</xsl:call-template>
|
||||
</xsl:for-each>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
<xsl:param name="file_description" />
|
||||
<xsl:param name="file_has_details" />
|
||||
<xsl:param name="file_has_no_options" />
|
||||
<xsl:param name="file_relative_path" />
|
||||
|
||||
<span tabindex="1" class="file" onmousedown="javascript:this.focus();">
|
||||
<xsl:attribute name="id">file<xsl:value-of select="ID"/></xsl:attribute>
|
||||
|
|
@ -103,7 +104,10 @@
|
|||
<div class="filename">
|
||||
<a tabindex="-1">
|
||||
<xsl:attribute name="href">keyman:editfile?id=<xsl:value-of select="ID"/></xsl:attribute>
|
||||
<xsl:value-of select="Filename" />
|
||||
<xsl:choose>
|
||||
<xsl:when test="$file_relative_path = 'true'"><xsl:value-of select="Filepath" /></xsl:when>
|
||||
<xsl:otherwise><xsl:value-of select="Filename" /></xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</a>
|
||||
</div>
|
||||
<div class="filedescription"><xsl:value-of select="$file_description"/></div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
keyman (16.0.143-1) unstable; urgency=medium
|
||||
|
||||
* Fix failure to build source after successful build (Closes #1046776)
|
||||
* New upstream release.
|
||||
* Re-release to Debian
|
||||
|
||||
-- Eberhard Beilharz <eb1@sil.org> Wed, 22 Nov 2023 15:24:59 +0100
|
||||
|
||||
keyman (16.0.141-1) unstable; urgency=medium
|
||||
|
||||
* Work around mips64el build failure (#1041499)
|
||||
|
|
|
|||
|
|
@ -88,15 +88,21 @@ export default class KeyboardInterface<ContextManagerType extends ContextManager
|
|||
//
|
||||
// The mobile apps typically have fully-preconfigured paths, but Developer's
|
||||
// test-host page does not.
|
||||
const pathConfig = this.engine.config.paths;
|
||||
const stub = new KeyboardStub(Pstub, pathConfig.keyboards, pathConfig.fonts);
|
||||
if(this.engine.keyboardRequisitioner?.cache.findMatchingStub(stub)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const buildStub = () => {
|
||||
const pathConfig = this.engine.config.paths;
|
||||
return new KeyboardStub(Pstub, pathConfig.keyboards, pathConfig.fonts);
|
||||
};
|
||||
|
||||
if(!this.engine.config.deferForInitialization.isResolved) {
|
||||
this.engine.config.deferForInitialization.then(() => this.engine.keyboardRequisitioner.cache.addStub(stub));
|
||||
// pathConfig is not ready until KMW initializes, which prevents proper stub-building.
|
||||
this.engine.config.deferForInitialization.then(() => this.engine.keyboardRequisitioner.cache.addStub(buildStub()));
|
||||
} else {
|
||||
const stub = buildStub();
|
||||
|
||||
if(this.engine.keyboardRequisitioner?.cache.findMatchingStub(stub)) {
|
||||
return 1;
|
||||
}
|
||||
this.engine.keyboardRequisitioner.cache.addStub(stub);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ export class BannerController {
|
|||
|
||||
const oldBanner = this.container.banner;
|
||||
if(oldBanner instanceof SuggestionBanner) {
|
||||
this.predictionContext.off('update', oldBanner.onSuggestionUpdate);
|
||||
// Frees all handlers, etc registered previously by the banner.
|
||||
oldBanner.predictionContext = null;
|
||||
}
|
||||
|
||||
if(!on) {
|
||||
|
|
@ -78,7 +79,7 @@ export class BannerController {
|
|||
suggestBanner.predictionContext = this.predictionContext;
|
||||
suggestBanner.events.on('apply', (selection) => this.predictionContext.accept(selection.suggestion));
|
||||
|
||||
this.predictionContext.on('update', suggestBanner.onSuggestionUpdate);
|
||||
// Registers for prediction-engine events & handles its needed connections.
|
||||
this.container.banner = suggestBanner;
|
||||
}
|
||||
}
|
||||
|
|
@ -92,4 +93,10 @@ export class BannerController {
|
|||
// Only display a SuggestionBanner when LanguageProcessor states it is active.
|
||||
this.activateBanner(state == 'active' || state == 'configured');
|
||||
}
|
||||
|
||||
public shutdown() {
|
||||
if(this.container.banner instanceof SuggestionBanner) {
|
||||
this.container.banner.predictionContext = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -692,6 +692,7 @@ export default abstract class OSKView
|
|||
private loadActiveKeyboard() {
|
||||
this.setBoxStyling();
|
||||
|
||||
// Do not erase / 'shutdown' the banner-controller; we simply re-use its elements.
|
||||
if(this.vkbd) {
|
||||
this.vkbd.shutdown();
|
||||
}
|
||||
|
|
@ -1140,6 +1141,8 @@ export default abstract class OSKView
|
|||
|
||||
this.kbdStyleSheetManager.unlinkAll();
|
||||
this.uiStyleSheetManager.unlinkAll();
|
||||
|
||||
this.bannerController.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Page-global variable definitions.
|
||||
{
|
||||
var inputCounter = 0;
|
||||
var kmw = keyman;
|
||||
}
|
||||
|
||||
function generateDiagnosticDiv(elem) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<h1>KeymanWeb 17 Testing</h1>
|
||||
<h2><a href="./unminified.html">Test unminified Keymanweb</a></h2>
|
||||
<h2><a href="./unminified - manual.html">Test unminified Keymanweb in manual-attachment mode.</a></h2>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<h2><a href="./desktop-ui/index.html">Desktop UI module testing</a></h2>
|
||||
<h2><a href="./build-visual-keyboard/index.html">Tests keyboard documentation rendering.</a></h2>
|
||||
<h2><a href="./gesture-recognizer/index.html">Stand-alone gesture recognition</a></h2>
|
||||
|
||||
|
||||
<h1>Miscellaneous tests for smaller features</h1>
|
||||
<h2><a href="./chirality/index.html">Chirality testing/bootstrapping</a><h2>
|
||||
<h2><a href="./options-with-save/index.html">Tests option/variable store functionality</a></h2>
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
<h2><a href="./issue29/index.html">Test desktop MutationObserver functionality</a></h2>
|
||||
<h2><a href="./issue62/index.html">Light test for touch-based MutationObserver functionality</a></h2>
|
||||
<h2><a href="./issue63/index.html">Test/stress-test touch-based MutationObserver functionality</a></h2>
|
||||
|
||||
|
||||
<h1>Issue Testing</h1>
|
||||
<h2><a href="./empty-row/index.html">Tests OSK handling of empty rows</a></h2>
|
||||
<h2><a href="./keyboard-errors/index.html">Tests keyboard error-handling functionality</a></h2>
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
<h2><a href="./issue6005/index.html">Tests predictive text & other handling of rule matching when the final rule group does not match (#6005)</a></h2>
|
||||
<h2><a href="./default-subkey/index.html">Tests handling of new default-subkey feature (#9430)</a></h2>
|
||||
<h2><a href="./issue9469/index.html">Test special characters rendering with keymanweb-osk.ttf (#9469)</a></h2>
|
||||
|
||||
<h2><a href="./text_selection_tests_9073/index.html">Test text selection (#9073)</a></h2>
|
||||
<h1>Other</h1>
|
||||
<h2><a href="./regression-tests/index.html">Keystroke processing regression test engine.</a></h2>
|
||||
<hr>
|
||||
|
|
|
|||
78
web/src/test/manual/web/text_selection_tests_9073/index.html
Normal file
78
web/src/test/manual/web/text_selection_tests_9073/index.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
|
||||
<!-- Set the viewport width to match phone and tablet device widths -->
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no" />
|
||||
|
||||
<title>KeymanWeb #9073</title>
|
||||
|
||||
<!-- Your page CSS -->
|
||||
<style type='text/css'>
|
||||
body {font-family: Tahoma,helvetica;}
|
||||
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
|
||||
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
|
||||
#KeymanWebControl {width:50%;min-width:600px;}
|
||||
</style>
|
||||
<script src="../../../../../build/publish/debug/keymanweb.js" type="application/javascript"></script>
|
||||
<script src="../../../../../build/publish/debug/kmwuitoggle.js"></script>
|
||||
|
||||
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
|
||||
<script>
|
||||
(function(kmw) {
|
||||
kmw.init({
|
||||
attachType:'auto'
|
||||
}).then(function() {
|
||||
kmw.addKeyboards({
|
||||
id:'text_selection_tests_keyboard_9073',
|
||||
name:'Text selection test cases (#9073)',
|
||||
languages:{
|
||||
id:'en',
|
||||
name:'English'
|
||||
},
|
||||
filename:'./text_selection_tests_keyboard_9073.js',
|
||||
displayName: 'Text selection test cases (#9073)'
|
||||
});
|
||||
});
|
||||
})(keyman);
|
||||
</script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<!-- Sample page HTML -->
|
||||
<body>
|
||||
<h2>Text Selection Test Cases (#9073)</h2>
|
||||
|
||||
<div>
|
||||
<!--
|
||||
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
|
||||
If omitted, those User Interfaces will appear at the top of the document body.
|
||||
(It is ignored by other User Interfaces.)
|
||||
-->
|
||||
<div id='KeymanWebControl'></div>
|
||||
|
||||
<textarea id='ta1' class='test' placeholder='Type here'></textarea>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<h3><a href="../index.html">Return to testing home page</a></h3>
|
||||
</body>
|
||||
|
||||
<!--
|
||||
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
|
||||
*
|
||||
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
|
||||
* to display the special characters used in the On-Screen Keyboard.
|
||||
*
|
||||
* To work around this Firefox bug, navigate to <b>about:config</b>
|
||||
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
|
||||
* while testing.
|
||||
*
|
||||
* Firefox resolves website-based CSS URI references correctly without needing
|
||||
* any configuration change, so this change should only be made for file-based testing.
|
||||
*
|
||||
***
|
||||
-->
|
||||
</html>
|
||||
|
|
@ -0,0 +1,641 @@
|
|||
if(typeof keyman === 'undefined') {
|
||||
console.log('Keyboard requires KeymanWeb 10.0 or later');
|
||||
if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later");
|
||||
} else {
|
||||
KeymanWeb.KR(new Keyboard_text_selection_tests_keyboard_9073());
|
||||
}
|
||||
function Keyboard_text_selection_tests_keyboard_9073()
|
||||
{
|
||||
var modCodes = keyman.osk.modifierCodes;
|
||||
var keyCodes = keyman.osk.keyCodes;
|
||||
|
||||
this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;
|
||||
this.KI="Keyboard_text_selection_tests_keyboard_9073";
|
||||
this.KN="Text Selection Tests Keyboard";
|
||||
this.KMINVER="10.0";
|
||||
this.KV={F:' 1em "Arial"',K102:0};
|
||||
this.KV.KLS={
|
||||
"default": ["dk(1)","1","2","3","4","5","6","7","8","9","0","-","=","","","","q","w","e","r","t","y","u","i","o","p","[","]","\\","","","","a","s","d","f","g","h","j","k","l",";","'","","","","","","\\","z","x","c","v","b","n","m",",",".","/","","","","","",""],
|
||||
"shift": ["~","!","@","#","$","%","^","&","*","(",")","_","+","","","","Q","W","E","R","T","Y","U","I","O","P","{","}","|","","","","A","S","D","F","G","H","J","K","L",":","\"","","","","","","|","Z","X","C","V","B","N","M","<",">","?","","","","","",""]
|
||||
};
|
||||
this.KV.BK=(function(x){
|
||||
var
|
||||
empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""),
|
||||
result=[], v, i,
|
||||
modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt'];
|
||||
for(i=modifiers.length-1;i>=0;i--) {
|
||||
v = x[modifiers[i]];
|
||||
if(v || result.length > 0) {
|
||||
result=(v ? v : empty).slice().concat(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})(this.KV.KLS);
|
||||
this.KDU=0;
|
||||
this.KH='';
|
||||
this.KM=0;
|
||||
this.KBVER="1.0";
|
||||
this.KMBM=modCodes.SHIFT /* 0x0010 */;
|
||||
this.KVKL={
|
||||
"tablet": {
|
||||
"displayUnderlying": false,
|
||||
"layer": [
|
||||
{
|
||||
"id": "default",
|
||||
"row": [
|
||||
{
|
||||
"id": "1",
|
||||
"key": [
|
||||
{
|
||||
"nextlayer": "shift",
|
||||
"id": "K_1",
|
||||
"text": "1"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "2"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "3"
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "4"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "5"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": "6"
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": "7"
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": "8"
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "9"
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": "0"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": "-"
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "="
|
||||
},
|
||||
{
|
||||
"width": "100",
|
||||
"id": "K_BKSP",
|
||||
"sp": "1",
|
||||
"text": "*BkSp*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"pad": "75",
|
||||
"text": "q"
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": "w"
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": "e"
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "r"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "t"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": "y"
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": "u"
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": "i"
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": "o"
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "p"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": "["
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "]"
|
||||
},
|
||||
{
|
||||
"width": "10",
|
||||
"id": "T_new_136",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "dk(1)"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": "a"
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": "s"
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "d"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "f"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "g"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "h"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": "j"
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "k"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "l"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ";"
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": "'"
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "\\"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"key": [
|
||||
{
|
||||
"nextlayer": "shift",
|
||||
"width": "160",
|
||||
"id": "K_SHIFT",
|
||||
"sp": "1",
|
||||
"text": "*Shift*"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": "\\"
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "z"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "x"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "c"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": "v"
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "b"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "n"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": "m"
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": ","
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": "."
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": "/"
|
||||
},
|
||||
{
|
||||
"width": "10",
|
||||
"id": "T_new_162",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"key": [
|
||||
{
|
||||
"width": "140",
|
||||
"id": "K_LOPT",
|
||||
"sp": "1",
|
||||
"text": "*Menu*"
|
||||
},
|
||||
{
|
||||
"width": "930",
|
||||
"id": "K_SPACE"
|
||||
},
|
||||
{
|
||||
"width": "145",
|
||||
"id": "K_ENTER",
|
||||
"sp": "1",
|
||||
"text": "*Enter*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "shift",
|
||||
"row": [
|
||||
{
|
||||
"id": "1",
|
||||
"key": [
|
||||
{
|
||||
"id": "K_1",
|
||||
"text": "!"
|
||||
},
|
||||
{
|
||||
"id": "K_2",
|
||||
"text": "@"
|
||||
},
|
||||
{
|
||||
"id": "K_3",
|
||||
"text": "#"
|
||||
},
|
||||
{
|
||||
"id": "K_4",
|
||||
"text": "$"
|
||||
},
|
||||
{
|
||||
"id": "K_5",
|
||||
"text": "%"
|
||||
},
|
||||
{
|
||||
"id": "K_6",
|
||||
"text": "^"
|
||||
},
|
||||
{
|
||||
"id": "K_7",
|
||||
"text": "&"
|
||||
},
|
||||
{
|
||||
"id": "K_8",
|
||||
"text": "*"
|
||||
},
|
||||
{
|
||||
"id": "K_9",
|
||||
"text": "("
|
||||
},
|
||||
{
|
||||
"id": "K_0",
|
||||
"text": ")"
|
||||
},
|
||||
{
|
||||
"id": "K_HYPHEN",
|
||||
"text": "_"
|
||||
},
|
||||
{
|
||||
"id": "K_EQUAL",
|
||||
"text": "+"
|
||||
},
|
||||
{
|
||||
"width": "100",
|
||||
"id": "K_BKSP",
|
||||
"sp": "1",
|
||||
"text": "*BkSp*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"key": [
|
||||
{
|
||||
"id": "K_Q",
|
||||
"pad": "75",
|
||||
"text": "Q"
|
||||
},
|
||||
{
|
||||
"id": "K_W",
|
||||
"text": "W"
|
||||
},
|
||||
{
|
||||
"id": "K_E",
|
||||
"text": "E"
|
||||
},
|
||||
{
|
||||
"id": "K_R",
|
||||
"text": "R"
|
||||
},
|
||||
{
|
||||
"id": "K_T",
|
||||
"text": "T"
|
||||
},
|
||||
{
|
||||
"id": "K_Y",
|
||||
"text": "Y"
|
||||
},
|
||||
{
|
||||
"id": "K_U",
|
||||
"text": "U"
|
||||
},
|
||||
{
|
||||
"id": "K_I",
|
||||
"text": "I"
|
||||
},
|
||||
{
|
||||
"id": "K_O",
|
||||
"text": "O"
|
||||
},
|
||||
{
|
||||
"id": "K_P",
|
||||
"text": "P"
|
||||
},
|
||||
{
|
||||
"id": "K_LBRKT",
|
||||
"text": "{"
|
||||
},
|
||||
{
|
||||
"id": "K_RBRKT",
|
||||
"text": "}"
|
||||
},
|
||||
{
|
||||
"width": "10",
|
||||
"id": "T_new_246",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"key": [
|
||||
{
|
||||
"id": "K_BKQUOTE",
|
||||
"text": "~"
|
||||
},
|
||||
{
|
||||
"id": "K_A",
|
||||
"text": "A"
|
||||
},
|
||||
{
|
||||
"id": "K_S",
|
||||
"text": "S"
|
||||
},
|
||||
{
|
||||
"id": "K_D",
|
||||
"text": "D"
|
||||
},
|
||||
{
|
||||
"id": "K_F",
|
||||
"text": "F"
|
||||
},
|
||||
{
|
||||
"id": "K_G",
|
||||
"text": "G"
|
||||
},
|
||||
{
|
||||
"id": "K_H",
|
||||
"text": "H"
|
||||
},
|
||||
{
|
||||
"id": "K_J",
|
||||
"text": "J"
|
||||
},
|
||||
{
|
||||
"id": "K_K",
|
||||
"text": "K"
|
||||
},
|
||||
{
|
||||
"id": "K_L",
|
||||
"text": "L"
|
||||
},
|
||||
{
|
||||
"id": "K_COLON",
|
||||
"text": ":"
|
||||
},
|
||||
{
|
||||
"id": "K_QUOTE",
|
||||
"text": "\""
|
||||
},
|
||||
{
|
||||
"id": "K_BKSLASH",
|
||||
"text": "|"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"key": [
|
||||
{
|
||||
"nextlayer": "default",
|
||||
"width": "160",
|
||||
"id": "K_SHIFT",
|
||||
"sp": "1",
|
||||
"text": "*Shift*"
|
||||
},
|
||||
{
|
||||
"id": "K_oE2",
|
||||
"text": "|"
|
||||
},
|
||||
{
|
||||
"id": "K_Z",
|
||||
"text": "Z"
|
||||
},
|
||||
{
|
||||
"id": "K_X",
|
||||
"text": "X"
|
||||
},
|
||||
{
|
||||
"id": "K_C",
|
||||
"text": "C"
|
||||
},
|
||||
{
|
||||
"id": "K_V",
|
||||
"text": "V"
|
||||
},
|
||||
{
|
||||
"id": "K_B",
|
||||
"text": "B"
|
||||
},
|
||||
{
|
||||
"id": "K_N",
|
||||
"text": "N"
|
||||
},
|
||||
{
|
||||
"id": "K_M",
|
||||
"text": "M"
|
||||
},
|
||||
{
|
||||
"id": "K_COMMA",
|
||||
"text": "<"
|
||||
},
|
||||
{
|
||||
"id": "K_PERIOD",
|
||||
"text": ">"
|
||||
},
|
||||
{
|
||||
"id": "K_SLASH",
|
||||
"text": "?"
|
||||
},
|
||||
{
|
||||
"width": "10",
|
||||
"id": "T_new_272",
|
||||
"sp": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"key": [
|
||||
{
|
||||
"width": "140",
|
||||
"id": "K_LOPT",
|
||||
"sp": "1",
|
||||
"text": "*Menu*"
|
||||
},
|
||||
{
|
||||
"width": "930",
|
||||
"id": "K_SPACE"
|
||||
},
|
||||
{
|
||||
"width": "145",
|
||||
"id": "K_ENTER",
|
||||
"sp": "1",
|
||||
"text": "*Enter*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
;
|
||||
this.KVER="16.0.142.0";
|
||||
this.KVS=[];
|
||||
this.gs=function(t,e) {
|
||||
return this.g_main_0(t,e);
|
||||
};
|
||||
this.gs=function(t,e) {
|
||||
return this.g_main_0(t,e);
|
||||
};
|
||||
this.g_main_0=function(t,e) {
|
||||
var k=KeymanWeb,r=0,m=0;
|
||||
if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKSP /* 0x08 */)) {
|
||||
if(k.KFCM(3,t,['a',{t:'d',d:0},'b'])){
|
||||
r=m=1; // Line 23
|
||||
k.KDC(3,t);
|
||||
k.KO(-1,t,"ok1");
|
||||
}
|
||||
else if(k.KFCM(2,t,['a','b'])){
|
||||
r=m=1; // Line 24
|
||||
k.KDC(2,t);
|
||||
k.KO(-1,t,"fail1");
|
||||
}
|
||||
else if(k.KFCM(2,t,['a',{t:'d',d:0}])){
|
||||
r=m=1; // Line 25
|
||||
k.KDC(2,t);
|
||||
k.KO(-1,t,"fail2");
|
||||
}
|
||||
else if(k.KFCM(1,t,['^'])){
|
||||
r=m=1; // Line 17
|
||||
k.KDC(1,t);
|
||||
k.KO(-1,t,"foo");
|
||||
}
|
||||
}
|
||||
else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_A /* 0x41 */)) {
|
||||
if(k.KFCM(1,t,['^'])){
|
||||
r=m=1; // Line 16
|
||||
k.KDC(1,t);
|
||||
k.KO(-1,t,"Â");
|
||||
}
|
||||
}
|
||||
else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKQUOTE /* 0xC0 */)) {
|
||||
if(1){
|
||||
r=m=1; // Line 19
|
||||
k.KDC(0,t);
|
||||
k.KDO(-1,t,0);
|
||||
}
|
||||
}
|
||||
else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_A /* 0x41 */)) {
|
||||
if(k.KFCM(1,t,['^'])){
|
||||
r=m=1; // Line 15
|
||||
k.KDC(1,t);
|
||||
k.KO(-1,t,"â");
|
||||
}
|
||||
}
|
||||
else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_O /* 0x4F */)) {
|
||||
if(k.KFCM(1,t,[{t:'d',d:0}])){
|
||||
r=m=1; // Line 26
|
||||
k.KDC(1,t);
|
||||
k.KO(-1,t,"ok3");
|
||||
}
|
||||
}
|
||||
else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_T /* 0x54 */)) {
|
||||
if(1){
|
||||
r=m=1; // Line 21
|
||||
k.KDC(0,t);
|
||||
k.KO(-1,t,"\t");
|
||||
}
|
||||
}
|
||||
return r;
|
||||
};
|
||||
}
|
||||
166
windows/src/engine/keyman32/appcontext.cpp
Normal file
166
windows/src/engine/keyman32/appcontext.cpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "pch.h"
|
||||
// AppContext Class Methods
|
||||
AppContext::AppContext() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
WCHAR *
|
||||
AppContext::BufMax(int n) {
|
||||
WCHAR *p = wcschr(CurContext, 0); // I3091
|
||||
|
||||
if (CurContext == p || n == 0)
|
||||
return p; /* empty context or 0 characters requested, return pointer to end of context */ // I3091
|
||||
|
||||
WCHAR *q = p; // I3091
|
||||
for (; p != NULL && p > CurContext && (INT_PTR)(q - p) < n; p = decxstr(p, CurContext))
|
||||
; // I3091
|
||||
|
||||
if ((INT_PTR)(q - p) > n)
|
||||
p = incxstr(p); /* Copes with deadkey or supplementary pair at start of returned buffer making it too long */ // I3091
|
||||
|
||||
return p; // I3091
|
||||
}
|
||||
|
||||
void
|
||||
AppContext::Delete() {
|
||||
if (CharIsDeadkey()) {
|
||||
pos -= 2;
|
||||
} else if (CharIsSurrogatePair()) {
|
||||
pos--;
|
||||
}
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Delete");
|
||||
|
||||
if (pos > 0)
|
||||
pos--;
|
||||
CurContext[pos] = 0;
|
||||
// if(--pos < 0) pos = 0;
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Delete");
|
||||
}
|
||||
|
||||
void
|
||||
AppContext::Reset() {
|
||||
pos = 0;
|
||||
CurContext[0] = 0;
|
||||
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Reset");
|
||||
}
|
||||
|
||||
void
|
||||
AppContext::Get(WCHAR *buf, int bufsize) {
|
||||
// surrogate pairs need to be treated as a single unit, therefore use
|
||||
// BufMax to find a start index.
|
||||
// BufMax handles the case where a surrogate pair at the
|
||||
// start of the buffer is split by bufsize
|
||||
for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) {
|
||||
*buf = *p;
|
||||
if (Uni_IsSurrogate1(*p) && bufsize - 2 > 0) {
|
||||
buf++;
|
||||
p++;
|
||||
*buf = *p;
|
||||
bufsize--;
|
||||
}
|
||||
buf++;
|
||||
}
|
||||
|
||||
*buf = 0;
|
||||
}
|
||||
|
||||
void
|
||||
AppContext::Set(const WCHAR *buf) {
|
||||
const WCHAR *p;
|
||||
WCHAR *q;
|
||||
|
||||
// We may be past a buffer longer than our internal
|
||||
// buffer. So we shift to make sure we capture the end
|
||||
// of the string, not the start
|
||||
p = wcschr(buf, 0);
|
||||
q = (WCHAR *)p;
|
||||
while (p != NULL && p > buf && (intptr_t)(q - p) < MAXCONTEXT - 1) {
|
||||
p = decxstr((WCHAR *)p, (WCHAR *)buf);
|
||||
}
|
||||
|
||||
// If the first character in the buffer is a surrogate pair,
|
||||
// or a deadkey, our buffer may be too long, so move to the
|
||||
// next character in the buffer
|
||||
if ((intptr_t)(q - p) > MAXCONTEXT - 1) {
|
||||
p = incxstr((WCHAR *)p);
|
||||
}
|
||||
|
||||
for (q = CurContext; *p; p++, q++) {
|
||||
*q = *p;
|
||||
}
|
||||
|
||||
*q = 0;
|
||||
pos = (int)(intptr_t)(q - CurContext);
|
||||
CurContext[MAXCONTEXT - 1] = 0;
|
||||
}
|
||||
|
||||
BOOL
|
||||
AppContext::CharIsDeadkey() {
|
||||
if (pos < 3) // code_sentinel, deadkey, #, 0
|
||||
return FALSE;
|
||||
return CurContext[pos - 3] == UC_SENTINEL && CurContext[pos - 2] == CODE_DEADKEY;
|
||||
}
|
||||
|
||||
BOOL
|
||||
AppContext::CharIsSurrogatePair() {
|
||||
if (pos < 2) // low_surrogate, high_surrogate
|
||||
return FALSE;
|
||||
|
||||
return Uni_IsSurrogate1(CurContext[pos - 2]) && Uni_IsSurrogate2(CurContext[pos - 1]);
|
||||
}
|
||||
|
||||
BOOL
|
||||
AppContext::IsEmpty() {
|
||||
return (BOOL)(pos == 0);
|
||||
}
|
||||
|
||||
BOOL
|
||||
ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len) {
|
||||
assert(contextItems);
|
||||
assert(outBuf);
|
||||
|
||||
km_core_context_item *km_core_context_it = contextItems;
|
||||
uint8_t contextLen = 0;
|
||||
for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) {
|
||||
++contextLen;
|
||||
}
|
||||
|
||||
WCHAR *buf = new WCHAR[(contextLen * 3) + 1]; // *3 if every context item was a deadkey
|
||||
uint8_t idx = 0;
|
||||
km_core_context_it = contextItems;
|
||||
for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) {
|
||||
switch (km_core_context_it->type) {
|
||||
case KM_CORE_CT_CHAR:
|
||||
if (Uni_IsSMP(km_core_context_it->character)) {
|
||||
buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate1(km_core_context_it->character);
|
||||
buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate2(km_core_context_it->character);
|
||||
} else {
|
||||
buf[idx++] = (km_core_cp)km_core_context_it->character;
|
||||
}
|
||||
break;
|
||||
case KM_CORE_CT_MARKER:
|
||||
assert(km_core_context_it->marker > 0);
|
||||
buf[idx++] = UC_SENTINEL;
|
||||
buf[idx++] = CODE_DEADKEY;
|
||||
buf[idx++] = static_cast<WCHAR>(km_core_context_it->marker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf[idx] = 0; // Null terminate character array
|
||||
|
||||
if (wcslen(buf) > len) {
|
||||
// Truncate to length 'len' using AppContext so that the context closest to the caret is preserved
|
||||
// and the truncation will not split deadkeys or surrogate pairs
|
||||
// Note by using the app context class we will truncate the context to the MAXCONTEXT length if 'len'
|
||||
// is greater than MAXCONTEXT
|
||||
AppContext context;
|
||||
context.Set(buf);
|
||||
context.Get(outBuf, len);
|
||||
} else {
|
||||
wcscpy_s(outBuf, wcslen(buf) + 1, buf);
|
||||
}
|
||||
delete[] buf;
|
||||
return TRUE;
|
||||
}
|
||||
107
windows/src/engine/keyman32/appcontext.h
Normal file
107
windows/src/engine/keyman32/appcontext.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#ifndef _APPCONTEXT_H
|
||||
#define _APPCONTEXT_H
|
||||
/*
|
||||
Name: appcontext
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 23 Nov 2023
|
||||
|
||||
Modified Date: 23 Nov 2023
|
||||
Authors: rcruickshank
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes: AppContext is retained to support calldll with the external interface for the 3rd party IMX keyboards
|
||||
that worked with KMX formatted Context Strings. It is also used once for debug logging the ProcessHook.
|
||||
History:
|
||||
*/
|
||||
|
||||
class AppContext {
|
||||
private:
|
||||
WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated.
|
||||
int pos;
|
||||
|
||||
public:
|
||||
AppContext();
|
||||
|
||||
/**
|
||||
* Removes a single code point from the end of the CurContext closest to the caret;
|
||||
* i.e. it will be both code units if a surrogate pair. If it is a deadkey it will
|
||||
* remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value.
|
||||
*/
|
||||
void Delete();
|
||||
|
||||
/**
|
||||
* Clears the CurContext and resets the position - pos - index
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* Copies the characters in CurContext to supplied buffer.
|
||||
* If bufsize is reached before the entire context was copied, the buf
|
||||
* will be truncated to number of valid characters possible with null character
|
||||
* termination. e.g. it will be one code unit less than bufsize if that would
|
||||
* have meant splitting a surrogate pair
|
||||
* @param buf The data buffer to copy current context
|
||||
* @param bufsize The number of code units ie size of the WCHAR buffer - not the code points
|
||||
*/
|
||||
void Get(WCHAR *buf, int bufsize);
|
||||
|
||||
/**
|
||||
* Sets the CurContext to the supplied buf character array and updates the pos index.
|
||||
*
|
||||
* @param buf
|
||||
*/
|
||||
void Set(const WCHAR *buf);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the character in the current context buffer which
|
||||
* will have at most n valid xstring units remaining until the null terminating
|
||||
* character. It will be one code unit less than bufsize if that would
|
||||
* have meant splitting a surrogate pair or deadkey.
|
||||
*
|
||||
* @param n The maximum number of valid xstring units (not code points or code units)
|
||||
* @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units
|
||||
*/
|
||||
WCHAR *BufMax(int n);
|
||||
|
||||
/**
|
||||
* Returns TRUE if the last xstring unit in the context is a deadkey
|
||||
*
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL CharIsDeadkey();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the last xstring unit in the CurContext is a surrogate pair.
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL CharIsSurrogatePair();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the context is empty
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL AppContext::IsEmpty();
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert km_core_context_item array into an kmx char buffer.
|
||||
* Caller is responsible for freeing the memory.
|
||||
* The length is restricted to a maximum of MAXCONTEXT length. If the number
|
||||
* of input km_core_context_items exceeds this length the characters furthest
|
||||
* from the caret will be truncated.
|
||||
*
|
||||
* @param contextItems the input core context array. (km_core_context_item)
|
||||
* @param [out] outBuf the kmx character array output. caller to free memory.
|
||||
*
|
||||
* @return BOOL True if array created successfully
|
||||
*/
|
||||
BOOL ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len);
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -269,89 +269,27 @@ char *debugstr(PWSTR buf) {
|
|||
|
||||
/* Context functions */
|
||||
|
||||
void AITIP::MergeContextWithCache(PWSTR buf, AppContext *local_context) { // I4262
|
||||
WCHAR tmpbuf[MAXCONTEXT], contextExDeadkeys[MAXCONTEXT];
|
||||
local_context->Get(tmpbuf, MAXCONTEXT-1);
|
||||
|
||||
int n = 0;
|
||||
PWSTR p = tmpbuf, q = contextExDeadkeys, r = buf; // I4266
|
||||
while(*p) {
|
||||
if(*p == UC_SENTINEL) {
|
||||
p += 2; // We know the only UC_SENTINEL CODE in the context is CODE_DEADKEY, which has only 1 parameter: UC_SENTINEL CODE_DEADKEY <deadkey_id>
|
||||
n++;
|
||||
} else {
|
||||
*q++ = *p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
*q = 0;
|
||||
|
||||
if(n > 0 && wcslen(buf) > wcslen(contextExDeadkeys)) { // I4266
|
||||
r += wcslen(buf) - wcslen(contextExDeadkeys);
|
||||
BOOL AITIP::ReadContext(PWSTR buf) {
|
||||
if (buf == nullptr) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// We have to cut off the context comparison from the left by #deadkeys matched to ensure we are comparing like with like,
|
||||
// at least when tmpbuf len=MAXCONTEXT-1 at entry.
|
||||
|
||||
#ifdef DEBUG_MERGECONTEXT
|
||||
char *mc1 = debugstr(buf), *mc2 = debugstr(contextExDeadkeys), *mc3 = debugstr(tmpbuf);
|
||||
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache TIP:'%s' Context:'%s' DKContext:'%s'",
|
||||
mc1, mc2, mc3);
|
||||
|
||||
delete mc1;
|
||||
delete mc2;
|
||||
delete mc3;
|
||||
#endif
|
||||
|
||||
if(wcscmp(r, contextExDeadkeys) != 0) {
|
||||
// context has changed, reset context
|
||||
#ifdef DEBUG_MERGECONTEXT
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache --> load context from app (losing deadkeys)");
|
||||
#endif
|
||||
local_context->Set(buf);
|
||||
} else {
|
||||
#ifdef DEBUG_MERGECONTEXT
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache --> loading cached context");
|
||||
#endif
|
||||
wcscpy_s(buf, MAXCONTEXT, tmpbuf);
|
||||
}
|
||||
}
|
||||
|
||||
void AITIP::ReadContext() {
|
||||
WCHAR buf[MAXCONTEXT];
|
||||
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
||||
if(!_td) return;
|
||||
if(!_td) return FALSE;
|
||||
|
||||
if(_td->TIPGetContext && (*_td->TIPGetContext)(MAXCONTEXT-1, buf) == S_OK) { // I3575 // I4262
|
||||
if(ShouldDebug(sdmKeyboard)) {
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: full context [Updateable=%d] %s", _td->TIPFUpdateable, Debug_UnicodeString(buf));
|
||||
}
|
||||
useLegacy = FALSE; // I3575
|
||||
|
||||
// If the text content of the context is identical, inject the deadkeys
|
||||
// Otherwise, reset the cachedContext to match buf, no deadkeys
|
||||
|
||||
MergeContextWithCache(buf, context);
|
||||
|
||||
if(ShouldDebug(sdmKeyboard)) {
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: after merge [Updateable=%d] %s", _td->TIPFUpdateable, Debug_UnicodeString(buf));
|
||||
}
|
||||
|
||||
context->Set(buf);
|
||||
return TRUE;
|
||||
} else {
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: transitory context, so use buffered context [Updateable=%d]", _td->TIPFUpdateable);
|
||||
useLegacy = TRUE; // I3575
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void AITIP::CopyContext(AppContext *savedContext) {
|
||||
savedContext->CopyFrom(context);
|
||||
}
|
||||
|
||||
void AITIP::RestoreContextOnly(AppContext *savedContext) {
|
||||
context->CopyFrom(savedContext);
|
||||
}
|
||||
|
||||
/* Output actions */
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,6 @@
|
|||
|
||||
class AITIP : public AIWin2000Unicode
|
||||
{
|
||||
private:
|
||||
void MergeContextWithCache(PWSTR buf, AppContext *context); // I4262
|
||||
|
||||
private:
|
||||
BOOL useLegacy;
|
||||
|
||||
|
|
@ -50,20 +47,6 @@ public:
|
|||
AITIP();
|
||||
~AITIP();
|
||||
|
||||
/**
|
||||
* Copy the member context
|
||||
*
|
||||
* @param[out] savedContext the copied context
|
||||
*/
|
||||
void CopyContext(AppContext *savedContext);
|
||||
|
||||
/**
|
||||
* Restore the passed context to the member context
|
||||
*
|
||||
* @param savedContext the context to restore
|
||||
*/
|
||||
void RestoreContextOnly(AppContext *savedContext);
|
||||
|
||||
/* Information functions */
|
||||
|
||||
virtual BOOL CanHandleWindow(HWND ahwnd);
|
||||
|
|
@ -71,7 +54,12 @@ public:
|
|||
|
||||
/* Context functions */
|
||||
|
||||
virtual void ReadContext();
|
||||
/**
|
||||
* Reads the current application context upto MAXCONTEXT length into the supplied buffer.
|
||||
* @param buf The data buffer to copy current application context into, must
|
||||
* be MAXCONTEXT WCHARs or larger.
|
||||
*/
|
||||
virtual BOOL ReadContext(PWSTR buf);
|
||||
|
||||
/* Queue and sending functions */
|
||||
|
||||
|
|
|
|||
|
|
@ -43,16 +43,9 @@
|
|||
#include "pch.h" // I4128 // I4287
|
||||
#include "serialkeyeventclient.h"
|
||||
|
||||
|
||||
AIWin2000Unicode::AIWin2000Unicode()
|
||||
{
|
||||
context = new AppContext;
|
||||
}
|
||||
|
||||
AIWin2000Unicode::~AIWin2000Unicode()
|
||||
{
|
||||
delete context;
|
||||
AIWin2000Unicode::AIWin2000Unicode() {
|
||||
}
|
||||
AIWin2000Unicode::~AIWin2000Unicode(){}
|
||||
|
||||
/* Information functions */
|
||||
|
||||
|
|
@ -68,7 +61,7 @@ BOOL AIWin2000Unicode::HandleWindow(HWND ahwnd)
|
|||
if(hwnd != ahwnd)
|
||||
{
|
||||
hwnd = ahwnd;
|
||||
context->Reset();
|
||||
ResetContext();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -87,33 +80,20 @@ BOOL AIWin2000Unicode::IsUnicode()
|
|||
|
||||
/* Context functions */
|
||||
|
||||
void AIWin2000Unicode::ReadContext()
|
||||
{
|
||||
}
|
||||
|
||||
void AIWin2000Unicode::AddContext(WCHAR ch) //I2436
|
||||
{
|
||||
context->Add(ch);
|
||||
BOOL AIWin2000Unicode::ReadContext(PWSTR buf) {
|
||||
UNREFERENCED_PARAMETER(buf);
|
||||
// We cannot read any context from legacy apps, so we return a
|
||||
// failure here -- telling Core to maintain its own cached
|
||||
// context.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void AIWin2000Unicode::ResetContext()
|
||||
{
|
||||
context->Reset();
|
||||
}
|
||||
|
||||
WCHAR *AIWin2000Unicode::ContextBuf(int n)
|
||||
{
|
||||
return context->Buf(n);
|
||||
}
|
||||
|
||||
WCHAR *AIWin2000Unicode::ContextBufMax(int n)
|
||||
{
|
||||
return context->BufMax(n);
|
||||
}
|
||||
|
||||
void AIWin2000Unicode::SetContext(const WCHAR* buf)
|
||||
{
|
||||
return context->Set(buf);
|
||||
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
||||
if (_td && _td->lpActiveKeyboard && _td->lpActiveKeyboard->lpCoreKeyboardState) {
|
||||
km_core_state_context_clear(_td->lpActiveKeyboard->lpCoreKeyboardState);
|
||||
}
|
||||
}
|
||||
|
||||
BYTE SavedKbdState[256];
|
||||
|
|
@ -126,40 +106,6 @@ BOOL AIWin2000Unicode::SendActions() // I4196
|
|||
return PostKeys();
|
||||
}
|
||||
|
||||
BOOL AIWin2000Unicode::QueueAction(int ItemType, DWORD dwData)
|
||||
{
|
||||
int result = AppIntegration::QueueAction(ItemType, dwData);
|
||||
|
||||
//SendDebugMessageFormat(hwnd, sdmAIDefault, 0, "App::QueueAction ItemType=%d dwData=%x", ItemType, dwData);
|
||||
|
||||
switch(ItemType)
|
||||
{
|
||||
case QIT_VKEYDOWN:
|
||||
break;
|
||||
|
||||
case QIT_DEADKEY:
|
||||
context->Add(UC_SENTINEL);
|
||||
context->Add(CODE_DEADKEY);
|
||||
context->Add((WORD) dwData);
|
||||
break;
|
||||
|
||||
case QIT_CHAR:
|
||||
context->Add((WORD) dwData);
|
||||
break;
|
||||
|
||||
case QIT_BACK:
|
||||
if(dwData & BK_BACKSPACE)
|
||||
while(context->CharIsDeadkey()) context->Delete();
|
||||
//if(dwData == CODE_DEADKEY) break;
|
||||
context->Delete();
|
||||
if(dwData & BK_BACKSPACE)
|
||||
while(context->CharIsDeadkey()) context->Delete();
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// I1512 - SendInput with VK_PACKET for greater robustness
|
||||
|
||||
BOOL AIWin2000Unicode::PostKeys()
|
||||
|
|
|
|||
|
|
@ -32,15 +32,9 @@ private:
|
|||
|
||||
BOOL PostKeys();
|
||||
|
||||
|
||||
protected:
|
||||
AppContext *context;
|
||||
|
||||
public:
|
||||
AIWin2000Unicode();
|
||||
~AIWin2000Unicode();
|
||||
|
||||
virtual BOOL QueueAction(int ItemType, DWORD dwData);
|
||||
AIWin2000Unicode();
|
||||
~AIWin2000Unicode();
|
||||
|
||||
/* Information functions */
|
||||
|
||||
|
|
@ -51,13 +45,9 @@ public:
|
|||
|
||||
/* Context functions */
|
||||
|
||||
virtual void ReadContext();
|
||||
virtual BOOL ReadContext(PWSTR buf);
|
||||
virtual void ResetContext();
|
||||
virtual void AddContext(WCHAR ch); //I2436
|
||||
virtual WCHAR *ContextBuf(int n);
|
||||
virtual WCHAR *ContextBufMax(int n);
|
||||
virtual void SetContext(const WCHAR* buf);
|
||||
|
||||
|
||||
/* Queue and sending functions */
|
||||
|
||||
virtual BOOL SendActions(); // I4196
|
||||
|
|
|
|||
|
|
@ -32,161 +32,6 @@ const LPSTR ItemTypes[8] = {
|
|||
"QIT_VKEYDOWN", "QIT_VKEYUP", "QIT_VSHIFTDOWN", "QIT_VSHIFTUP",
|
||||
"QIT_CHAR", "QIT_DEADKEY", "QIT_BELL", "QIT_BACK" };
|
||||
|
||||
/* AppContext */
|
||||
|
||||
AppContext::AppContext()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AppContext::Add(WCHAR ch)
|
||||
{
|
||||
if(pos == MAXCONTEXT - 1) {
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: MAXCONTEXT[%d]: %ws", pos, CurContext);
|
||||
auto p = incxstr(CurContext);
|
||||
auto n = p - CurContext;
|
||||
memmove(CurContext, p, (MAXCONTEXT - n) * 2);
|
||||
pos -= (int)n;
|
||||
}
|
||||
|
||||
CurContext[pos++] = ch;
|
||||
CurContext[pos] = 0;
|
||||
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Add(%x) [%d]: %s", ch, pos, Debug_UnicodeString(CurContext));
|
||||
}
|
||||
|
||||
WCHAR *AppContext::Buf(int n)
|
||||
{
|
||||
WCHAR *p;
|
||||
|
||||
//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Buf(%d)", n);
|
||||
//if(n == 0) return wcschr(CurContext, 0);
|
||||
//if(*CurContext == 0) return NULL;
|
||||
|
||||
for(p = wcschr(CurContext, 0); p != NULL && n > 0 && p > CurContext; p = decxstr(p, CurContext), n--);
|
||||
//for(p = wcschr(CurContext, 0); n > 0 && p > CurContext; p--, n--);
|
||||
|
||||
if(n > 0) return NULL;
|
||||
return p;
|
||||
}
|
||||
|
||||
WCHAR *AppContext::BufMax(int n)
|
||||
{
|
||||
WCHAR *p = wcschr(CurContext, 0); // I3091
|
||||
|
||||
if(CurContext == p || n == 0) return p; /* empty context or 0 characters requested, return pointer to end of context */ // I3091
|
||||
|
||||
WCHAR *q = p; // I3091
|
||||
for(; p != NULL && p > CurContext && (INT_PTR)(q-p) < n; p = decxstr(p, CurContext)); // I3091
|
||||
|
||||
if((INT_PTR)(q-p) > n) p = incxstr(p); /* Copes with deadkey or supplementary pair at start of returned buffer making it too long */ // I3091
|
||||
|
||||
return p; // I3091
|
||||
}
|
||||
|
||||
void AppContext::Delete()
|
||||
{
|
||||
if (CharIsDeadkey()) {
|
||||
pos -= 2;
|
||||
} else if (CharIsSurrogatePair()) {
|
||||
pos--;
|
||||
}
|
||||
//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Delete");
|
||||
|
||||
if(pos > 0) pos--;
|
||||
CurContext[pos] = 0;
|
||||
//if(--pos < 0) pos = 0;
|
||||
//SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Delete");
|
||||
}
|
||||
|
||||
void AppContext::Reset()
|
||||
{
|
||||
pos = 0;
|
||||
CurContext[0] = 0;
|
||||
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Reset");
|
||||
}
|
||||
|
||||
void AppContext::Get(WCHAR *buf, int bufsize)
|
||||
{
|
||||
// surrogate pairs need to be treated as a single unit, therefore use
|
||||
// BufMax to find a start index.
|
||||
// BufMax handles the case where a surrogate pair at the
|
||||
// start of the buffer is split by bufsize
|
||||
for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--)
|
||||
{
|
||||
*buf = *p;
|
||||
if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) {
|
||||
buf++; p++;
|
||||
*buf = *p;
|
||||
bufsize--;
|
||||
}
|
||||
buf++;
|
||||
}
|
||||
|
||||
*buf = 0;
|
||||
}
|
||||
|
||||
void AppContext::CopyFrom(AppContext *source) // I3575
|
||||
{
|
||||
SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 0));
|
||||
wcscpy_s(CurContext, _countof(CurContext), source->CurContext);
|
||||
pos = source->pos;
|
||||
}
|
||||
|
||||
|
||||
void AppContext::Set(const WCHAR *buf)
|
||||
{
|
||||
const WCHAR *p;
|
||||
WCHAR *q;
|
||||
|
||||
// We may be past a buffer longer than our internal
|
||||
// buffer. So we shift to make sure we capture the end
|
||||
// of the string, not the start
|
||||
p = wcschr(buf, 0);
|
||||
q = (WCHAR *)p;
|
||||
while (p != NULL && p > buf && (intptr_t)(q - p) < MAXCONTEXT - 1) {
|
||||
p = decxstr((WCHAR *)p, (WCHAR *)buf);
|
||||
}
|
||||
|
||||
// If the first character in the buffer is a surrogate pair,
|
||||
// or a deadkey, our buffer may be too long, so move to the
|
||||
// next character in the buffer
|
||||
if ((intptr_t)(q - p) > MAXCONTEXT - 1) {
|
||||
p = incxstr((WCHAR *)p);
|
||||
}
|
||||
|
||||
for (q = CurContext; *p; p++, q++) {
|
||||
*q = *p;
|
||||
}
|
||||
|
||||
*q = 0;
|
||||
pos = (int)(intptr_t)(q - CurContext);
|
||||
CurContext[MAXCONTEXT - 1] = 0;
|
||||
|
||||
}
|
||||
|
||||
BOOL AppContext::CharIsDeadkey()
|
||||
{
|
||||
if(pos < 3) // code_sentinel, deadkey, #, 0
|
||||
return FALSE;
|
||||
return CurContext[pos-3] == UC_SENTINEL &&
|
||||
CurContext[pos-2] == CODE_DEADKEY;
|
||||
}
|
||||
|
||||
BOOL AppContext::CharIsSurrogatePair()
|
||||
{
|
||||
if (pos < 2) // low_surrogate, high_surrogate
|
||||
return FALSE;
|
||||
|
||||
return Uni_IsSurrogate1(CurContext[pos - 2]) &&
|
||||
Uni_IsSurrogate2(CurContext[pos - 1]);
|
||||
}
|
||||
|
||||
BOOL AppContext::IsEmpty() {
|
||||
return (BOOL)(pos == 0);
|
||||
}
|
||||
|
||||
/* AppActionQueue */
|
||||
|
||||
AppActionQueue::AppActionQueue()
|
||||
|
|
@ -226,84 +71,3 @@ AppIntegration::AppIntegration()
|
|||
hwnd = NULL;
|
||||
FShiftFlags = 0;
|
||||
}
|
||||
|
||||
BOOL ContextItemsFromAppContext(WCHAR const* buf, km_core_context_item** outPtr)
|
||||
{
|
||||
assert(buf);
|
||||
assert(outPtr);
|
||||
km_core_context_item* context_items = new km_core_context_item[wcslen(buf) + 1];
|
||||
WCHAR const *p = buf;
|
||||
uint8_t contextIndex = 0;
|
||||
while (*p) {
|
||||
if (*p == UC_SENTINEL) {
|
||||
assert(*(p + 1) == CODE_DEADKEY);
|
||||
// we know the only uc_sentinel code in the context is code_deadkey, which has only 1 parameter: uc_sentinel code_deadkey <deadkey_id>
|
||||
// setup dead key context item
|
||||
p += 2;
|
||||
context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_MARKER, {0,}, {*p} };
|
||||
} else if (Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p + 1))) {
|
||||
// handle surrogate
|
||||
context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_CHAR, {0,}, {(char32_t)Uni_SurrogateToUTF32(*p, *(p + 1))} };
|
||||
p++;
|
||||
} else {
|
||||
context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_CHAR, {0,}, {*p} };
|
||||
}
|
||||
p++;
|
||||
}
|
||||
// terminate the context_items array.
|
||||
context_items[contextIndex] = km_core_context_item KM_CORE_CONTEXT_ITEM_END;
|
||||
|
||||
*outPtr = context_items;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
BOOL
|
||||
ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len) {
|
||||
assert(contextItems);
|
||||
assert(outBuf);
|
||||
|
||||
km_core_context_item *km_core_context_it = contextItems;
|
||||
uint8_t contextLen = 0;
|
||||
for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) {
|
||||
++contextLen;
|
||||
}
|
||||
|
||||
WCHAR *buf = new WCHAR[(contextLen*3)+ 1 ]; // *3 if every context item was a deadkey
|
||||
uint8_t idx = 0;
|
||||
km_core_context_it = contextItems;
|
||||
for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) {
|
||||
switch (km_core_context_it->type) {
|
||||
case KM_CORE_CT_CHAR:
|
||||
if (Uni_IsSMP(km_core_context_it->character)) {
|
||||
buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate1(km_core_context_it->character);
|
||||
buf[idx++] = static_cast<WCHAR> Uni_UTF32ToSurrogate2(km_core_context_it->character);
|
||||
} else {
|
||||
buf[idx++] = (km_core_cp)km_core_context_it->character;
|
||||
}
|
||||
break;
|
||||
case KM_CORE_CT_MARKER:
|
||||
assert(km_core_context_it->marker > 0);
|
||||
buf[idx++] = UC_SENTINEL;
|
||||
buf[idx++] = CODE_DEADKEY;
|
||||
buf[idx++] = static_cast<WCHAR>(km_core_context_it->marker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf[idx] = 0; // Null terminate character array
|
||||
|
||||
if (wcslen(buf) > len) {
|
||||
// Truncate to length 'len' using AppContext so that the context closest to the caret is preserved
|
||||
// and the truncation will not split deadkeys or surrogate pairs
|
||||
// Note by using the app context class we will truncate the context to the MAXCONTEXT length if 'len'
|
||||
// is greater than MAXCONTEXT
|
||||
AppContext context;
|
||||
context.Set(buf);
|
||||
context.Get(outBuf, len);
|
||||
} else {
|
||||
wcscpy_s(outBuf, wcslen(buf) + 1, buf);
|
||||
}
|
||||
delete[] buf;
|
||||
return TRUE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,103 +65,6 @@ public:
|
|||
int GetQueueSize() { return QueueSize; }
|
||||
};
|
||||
|
||||
class AppContext
|
||||
{
|
||||
private:
|
||||
WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated.
|
||||
int pos;
|
||||
|
||||
public:
|
||||
AppContext();
|
||||
/**
|
||||
* Copy "source" AppContext to this AppContext
|
||||
*
|
||||
* @param source AppContext to copy
|
||||
*/
|
||||
void CopyFrom(AppContext *source);
|
||||
|
||||
/**
|
||||
* Add a single code unit to the Current Context. Not necessarily a complete code point
|
||||
*
|
||||
* @param Code unit to add
|
||||
*/
|
||||
void Add(WCHAR ch);
|
||||
|
||||
/**
|
||||
* Removes a single code point from the end of the CurContext closest to the caret;
|
||||
* i.e. it will be both code units if a surrogate pair. If it is a deadkey it will
|
||||
* remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value.
|
||||
*/
|
||||
void Delete();
|
||||
|
||||
/**
|
||||
* Clears the CurContext and resets the position - pos - index
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* Copies the characters in CurContext to supplied buffer.
|
||||
* If bufsize is reached before the entire context was copied, the buf
|
||||
* will be truncated to number of valid characters possible with null character
|
||||
* termination. e.g. it will be one code unit less than bufsize if that would
|
||||
* have meant splitting a surrogate pair
|
||||
* @param buf The data buffer to copy current context
|
||||
* @param bufsize The number of code units ie size of the WCHAR buffer - not the code points
|
||||
*/
|
||||
void Get(WCHAR *buf, int bufsize);
|
||||
|
||||
/**
|
||||
* Sets the CurContext to the supplied buf character array and updates the pos index.
|
||||
*
|
||||
* @param buf
|
||||
*/
|
||||
void Set(const WCHAR *buf);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the character in the current context buffer which
|
||||
* will have at most n valid xstring units remaining until the null terminating
|
||||
* character. It will be one code unit less than bufsize if that would
|
||||
* have meant splitting a surrogate pair or deadkey.
|
||||
*
|
||||
* @param n The maximum number of valid xstring units (not code points or code units)
|
||||
* @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units
|
||||
*/
|
||||
WCHAR *BufMax(int n);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the character in the current context buffer which
|
||||
* will have n valid xstring units remaining until the the null terminating character.
|
||||
* OR
|
||||
* Returns NULL if there are less than n valid xstring units in the current context.
|
||||
* Background this was historically for performance during rule evaluation, if there
|
||||
* are not enough characters to compare, don't event attempt the comparison.
|
||||
*
|
||||
* @param n The number of valid xstring units (not code points or code units)
|
||||
* @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n characters
|
||||
*/
|
||||
WCHAR *Buf(int n);
|
||||
|
||||
/**
|
||||
* Returns TRUE if the last xstring unit in the context is a deadkey
|
||||
*
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL CharIsDeadkey();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the last xstring unit in the CurContext is a surrogate pair.
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL CharIsSurrogatePair();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the context is empty
|
||||
* @return BOOL
|
||||
*/
|
||||
BOOL AppContext::IsEmpty();
|
||||
|
||||
};
|
||||
|
||||
class AppIntegration:public AppActionQueue
|
||||
{
|
||||
protected:
|
||||
|
|
@ -180,12 +83,12 @@ public:
|
|||
virtual BOOL IsUnicode() = 0;
|
||||
|
||||
/* Context functions */
|
||||
|
||||
virtual void ReadContext() = 0;
|
||||
/**
|
||||
* Reads the current application context upto MAXCONTEXT length into the supplied buffer.
|
||||
* @param buf The data buffer to copy current application context
|
||||
*/
|
||||
virtual BOOL ReadContext(PWSTR buf) = 0;
|
||||
virtual void ResetContext() = 0;
|
||||
virtual void AddContext(WCHAR ch) = 0; //I2436
|
||||
virtual WCHAR *ContextBuf(int n) = 0;
|
||||
virtual WCHAR *ContextBufMax(int n) = 0;
|
||||
|
||||
/* Queue and sending functions */
|
||||
|
||||
|
|
@ -193,30 +96,6 @@ public:
|
|||
virtual BOOL SendActions() = 0; // I4196
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert AppContext array into an array of core context items.
|
||||
* Caller is responsible for freeing the memory.
|
||||
*
|
||||
* @param buf appcontext character array
|
||||
* @param outPtr The ouput array of context items. caller to free memory
|
||||
* @return BOOL True if array created successfully
|
||||
*/
|
||||
BOOL ContextItemsFromAppContext(WCHAR const* buf, km_core_context_item** outPtr);
|
||||
|
||||
/**
|
||||
* Convert km_core_context_item array into an kmx char buffer.
|
||||
* Caller is responsible for freeing the memory.
|
||||
* The length is restricted to a maximum of MAXCONTEXT length. If the number
|
||||
* of input km_core_context_items exceeds this length the characters furthest
|
||||
* from the caret will be truncated.
|
||||
*
|
||||
* @param contextItems the input core context array. (km_core_context_item)
|
||||
* @param [out] outBuf the kmx character array output. caller to free memory.
|
||||
*
|
||||
* @return BOOL True if array created successfully
|
||||
*/
|
||||
BOOL ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len);
|
||||
|
||||
extern const LPSTR ItemTypes[];
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="..\..\global\cpp\kmtip_guids.cpp" />
|
||||
<ClCompile Include="$(KEYMAN_ROOT)\common\windows\cpp\src\xstring.cpp" />
|
||||
<ClCompile Include="appcontext.cpp" />
|
||||
<ClCompile Include="appint\aiTIP.cpp">
|
||||
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
|
|
@ -354,6 +355,7 @@
|
|||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="appcontext.h" />
|
||||
<ClInclude Include="keymanengine.h" />
|
||||
<ClInclude Include="..\..\..\include\kmtip_guids.h" />
|
||||
<ClInclude Include="appint\aiTIP.h" />
|
||||
|
|
@ -387,4 +389,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -138,6 +138,9 @@
|
|||
<ClCompile Include="CoreEnvironment.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="appcontext.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="KEYMAN32.DEF">
|
||||
|
|
@ -246,6 +249,9 @@
|
|||
<ClInclude Include="..\..\..\include\kmtip_guids.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="appcontext.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="keyman-debug-etw.man" />
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ BOOL IsSysTrayWindow(HWND hwnd);
|
|||
|
||||
BOOL InitialiseProcess(HWND hwnd);
|
||||
BOOL UninitialiseProcess(BOOL Lock);
|
||||
BOOL IsKeyboardUnicode();
|
||||
|
||||
BOOL IsFocusedThread();
|
||||
|
||||
|
|
@ -231,6 +230,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd);
|
|||
#include "keymancontrol.h"
|
||||
#include "keyboardoptions.h"
|
||||
#include "kmprocessactions.h"
|
||||
#include "appcontext.h"
|
||||
|
||||
#include "syskbd.h"
|
||||
#include "vkscancodes.h"
|
||||
|
|
|
|||
|
|
@ -70,23 +70,29 @@
|
|||
|
||||
BOOL fOutputKeystroke;
|
||||
|
||||
/*char *getcontext()
|
||||
{
|
||||
WCHAR buf[128];
|
||||
static char bufout[128];
|
||||
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
||||
if(!_td) return "";
|
||||
_td->app->GetWindowContext(buf, 128);
|
||||
WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL);
|
||||
return bufout;
|
||||
}*/
|
||||
|
||||
|
||||
char *getcontext_debug() {
|
||||
//return "";
|
||||
|
||||
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
||||
if(!_td) return "";
|
||||
return Debug_UnicodeString(_td->app->ContextBufMax(128));
|
||||
if (!_td || !_td->lpActiveKeyboard || !_td->lpActiveKeyboard->lpCoreKeyboardState){
|
||||
return "";
|
||||
}
|
||||
|
||||
WCHAR buf[(MAXCONTEXT * 3) + 1]; // *3 if every context item was a deadkey
|
||||
km_core_context_item *citems = nullptr;
|
||||
|
||||
if (KM_CORE_STATUS_OK != km_core_context_get(
|
||||
km_core_state_context(_td->lpActiveKeyboard->lpCoreKeyboardState), &citems)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
DWORD context_length = (DWORD)km_core_context_item_list_size(citems);
|
||||
if (!ContextItemToAppContext(citems, buf, context_length)) {
|
||||
km_core_context_items_dispose(citems);
|
||||
return "";
|
||||
}
|
||||
km_core_context_items_dispose(citems);
|
||||
return Debug_UnicodeString(buf);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,14 +104,15 @@ char *getcontext_debug() {
|
|||
|
||||
static BOOL
|
||||
Process_Event_Core(PKEYMAN64THREADDATA _td) {
|
||||
PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT);
|
||||
km_core_context_item *citems = nullptr;
|
||||
ContextItemsFromAppContext(contextBuf, &citems);
|
||||
if (KM_CORE_STATUS_OK != km_core_context_set(km_core_state_context(_td->lpActiveKeyboard->lpCoreKeyboardState), citems)) {
|
||||
km_core_context_items_dispose(citems);
|
||||
return FALSE;
|
||||
WCHAR application_context[MAXCONTEXT];
|
||||
if (_td->app->ReadContext(application_context)) {
|
||||
km_core_context_status result;
|
||||
result = km_core_state_context_set_if_needed(_td->lpActiveKeyboard->lpCoreKeyboardState, reinterpret_cast<const km_core_cp *>(application_context));
|
||||
if (result == KM_CORE_CONTEXT_STATUS_ERROR || result == KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT) {
|
||||
SendDebugMessageFormat(0, sdmGlobal, 0, "Process_Event_Core: km_core_state_context_set_if_needed returned [%d]", result);
|
||||
}
|
||||
}
|
||||
km_core_context_items_dispose(citems);
|
||||
|
||||
SendDebugMessageFormat(
|
||||
0, sdmGlobal, 0, "ProcessEvent: vkey[%d] ShiftState[%d] isDown[%d]", _td->state.vkey,
|
||||
static_cast<uint16_t>(Globals::get_ShiftState() & (KM_CORE_MODIFIER_MASK_ALL | KM_CORE_MODIFIER_MASK_CAPS)), (uint8_t)_td->state.isDown);
|
||||
|
|
@ -139,8 +146,6 @@ BOOL ProcessHook()
|
|||
|
||||
fOutputKeystroke = FALSE; // TODO: 5442 no longer needs to be global once we use core processor
|
||||
|
||||
_td->app->ReadContext();
|
||||
|
||||
if(_td->state.msg.message == wm_keymankeydown) { // I4827
|
||||
if (ShouldDebug(sdmKeyboard)) {
|
||||
SendDebugMessageFormat(_td->state.msg.hwnd, sdmKeyboard, 0, "Key pressed: %s Context '%s'",
|
||||
|
|
@ -212,9 +217,6 @@ BOOL ProcessHook()
|
|||
_td->app->SetCurrentShiftState(Globals::get_ShiftState());
|
||||
_td->app->SendActions(); // I4196
|
||||
}
|
||||
// output context for debugging
|
||||
// PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT);
|
||||
// SendDebugMessageFormat(0, sdmAIDefault, 0, "Kmprocess::ProcessHook After cxt=%s", Debug_UnicodeString(contextBuf, 1));
|
||||
|
||||
return !fOutputKeystroke;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,10 +78,8 @@ static BOOL processPersistOpt(
|
|||
}
|
||||
|
||||
static BOOL processInvalidateContext(
|
||||
AITIP* app,
|
||||
km_core_state* keyboardState
|
||||
AITIP* app
|
||||
) {
|
||||
km_core_context_clear(km_core_state_context(keyboardState));
|
||||
app->ResetContext();
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -158,7 +156,7 @@ BOOL ProcessActions(BOOL* emitKeyStroke)
|
|||
continueProcessingActions = TRUE;
|
||||
break;
|
||||
case KM_CORE_IT_INVALIDATE_CONTEXT:
|
||||
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);
|
||||
continueProcessingActions = processInvalidateContext(_td->app);
|
||||
break;
|
||||
case KM_CORE_IT_CAPSLOCK:
|
||||
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
|
||||
|
|
@ -202,7 +200,7 @@ ProcessActionsNonUpdatableParse(BOOL* emitKeyStroke) {
|
|||
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
|
||||
break;
|
||||
case KM_CORE_IT_INVALIDATE_CONTEXT:
|
||||
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);
|
||||
continueProcessingActions = processInvalidateContext(_td->app);
|
||||
break;
|
||||
}
|
||||
if (!continueProcessingActions) {
|
||||
|
|
@ -228,7 +226,7 @@ ProcessActionsExternalEvent() {
|
|||
continueProcessingActions = processCapsLock(act, !_td->state.isDown, FALSE, TRUE);
|
||||
break;
|
||||
case KM_CORE_IT_INVALIDATE_CONTEXT:
|
||||
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);
|
||||
continueProcessingActions = processInvalidateContext(_td->app);
|
||||
break;
|
||||
}
|
||||
if (!continueProcessingActions) {
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@
|
|||
<ItemGroup>
|
||||
<ClCompile Include="..\..\global\cpp\kmtip_guids.cpp" />
|
||||
<ClCompile Include="$(KEYMAN_ROOT)\common\windows\cpp\src\xstring.cpp" />
|
||||
<ClCompile Include="..\keyman32\appcontext.cpp" />
|
||||
<ClCompile Include="..\keyman32\appint\aiTIP.cpp" />
|
||||
<ClCompile Include="..\keyman32\appint\aiWin2000Unicode.cpp" />
|
||||
<ClCompile Include="..\keyman32\appint\appint.cpp" />
|
||||
|
|
@ -276,6 +277,7 @@
|
|||
<ResourceCompile Include="Keyman64.RC" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\keyman32\appcontext.h" />
|
||||
<ClInclude Include="..\keyman32\keymanengine.h" />
|
||||
<ClInclude Include="..\..\..\include\kmtip_guids.h" />
|
||||
<ClInclude Include="..\keyman32\appint\aiTIP.h" />
|
||||
|
|
@ -327,4 +329,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
@ -38,6 +38,7 @@
|
|||
<ClCompile Include="..\keyman32\kmprocessactions.cpp" />
|
||||
<ClCompile Include="..\..\global\cpp\kmtip_guids.cpp" />
|
||||
<ClCompile Include="..\keyman32\CoreEnvironment.cpp" />
|
||||
<ClCompile Include="..\keyman32\appcontext.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\keyman32\appint\aiTIP.h" />
|
||||
|
|
@ -72,6 +73,7 @@
|
|||
<ClInclude Include="..\keyman32\keymanengine.h" />
|
||||
<ClInclude Include="..\keyman32\kmprocessactions.h" />
|
||||
<ClInclude Include="..\..\..\include\kmtip_guids.h" />
|
||||
<ClInclude Include="..\keyman32\appcontext.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="..\keyman32\keyman-debug-etw.man" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue