chore(linux): Merge remote-tracking branch 'origin/master' into chore/linux/9733_RenameLibknmkbp

This commit is contained in:
Eberhard Beilharz 2023-10-20 12:31:14 +02:00
commit fdc9baadb2
No known key found for this signature in database
GPG key ID: E9140597606020D3
55 changed files with 1470 additions and 503 deletions

View file

@ -242,8 +242,13 @@ jobs:
- name: Verify API
run: |
cd linux
if [ -f debian/libkeymancore.symbols ]; then
PKG_NAME=libkeymancore
else
PKG_NAME=libkmnkbp0-0
fi
SRC_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-srcpkg/keyman_${{ needs.sourcepackage.outputs.VERSION }}-1.debian.tar.xz" \
BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/libkmnkbp0-0_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \
BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/${PKG_NAME}_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \
PKG_VERSION="${{ needs.sourcepackage.outputs.VERSION }}" \
./scripts/deb-packaging.sh --gha verify 2>> $GITHUB_STEP_SUMMARY
@ -254,6 +259,13 @@ jobs:
path: linux/debian/libkmnkbp0-0.symbols
if: always()
- name: Archive .symbols file
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: libkeymancore.symbols
path: linux/debian/libkeymancore.symbols
if: always()
set_status:
name: Set result status on PR builds
needs: [sourcepackage, binary_packages, api_verification]

View file

@ -1,5 +1,21 @@
# Keyman Version History
## 17.0.195 alpha 2023-10-19
* chore(linux): Allow to collect coverage on TC (#9790)
* fix(common): don't use URL in common/web/types (#9798)
* chore: update kmp.schema.json and docs for kps schema (#9800)
* docs(windows): update text and images for windows 11 (#9689)
## 17.0.194 alpha 2023-10-18
* chore(linux): Re-enable building for Ubuntu 23.10 Mantic (#9780)
* chore(linux): Add missing tests (#9783)
* fix(web): fixes touch form-factor default kbd on cookieless keymanweb.com page load (#9786)
* fix(developer): three kmc .keyboard_info generation bugs (#9784)
* fix(developer): handle invalid project folders cleanly (#9785)
* chore(linux): Fix build scripts (#9781)
## 17.0.193 alpha 2023-10-17
* fix(developer): kmc crash on start (#9771)

View file

@ -1 +1 @@
17.0.194
17.0.196

View file

@ -0,0 +1,24 @@
# kmp.schema.json
* kmp.json file format, metadata included in Keyman .kmp package files
Documentation at https://help.keyman.com/developer/current-version/reference/file-types/metadata
# kmp.schema.json version history
## 2023-10-19 2.0
* Add relatedPackages, options.licenseFile, options.welcomeFile,
keyboard.examples, keyboard.webOskFonts, keyboard.webDisplayFonts,
info.description (all of these formerly were stored in .keyboard_info)
## 2019-01-31 1.1.0
* Add lexicalModels properties (note: `version` is optional and currently unused)
## 2018-02-13 1.0.2
* Add rtl property for keyboard layouts
## 2018-01-22 1.0.1
* Remove id field as it is derived from the filename anyway
## 2017-11-30 1.0 beta
* Initial version

View file

@ -0,0 +1,353 @@
{
"$schema": "http://json-schema.org/schema#",
"$ref": "#/definitions/package",
"definitions": {
"package": {
"type": "object",
"properties": {
"system": {
"$ref": "#/definitions/system"
},
"options": {
"$ref": "#/definitions/options"
},
"startMenu": {
"$ref": "#/definitions/startMenu"
},
"strings": {
"$ref": "#/definitions/strings"
},
"files": {
"$ref": "#/definitions/files"
},
"keyboards": {
"$ref": "#/definitions/keyboards"
},
"lexicalModels": {
"$ref": "#/definitions/lexicalModels"
},
"info": {
"$ref": "#/definitions/info"
},
"relatedPackages": {
"type": "array",
"items": {
"$ref": "#/definitions/relatedPackage"
}
}
},
"additionalProperties": false,
"required": [
"options",
"system"
]
},
"system": {
"type": "object",
"properties": {
"keymanDeveloperVersion": {
"type": "string"
},
"fileVersion": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"fileVersion"
]
},
"options": {
"type": "object",
"properties": {
"readmeFile": {
"type": "string"
},
"graphicFile": {
"type": "string"
},
"licenseFile": {
"type": "string"
},
"welcomeFile": {
"type": "string"
},
"executeProgram": {
"type": "string"
},
"msiFilename": {
"type": "string"
},
"msiOptions": {
"type": "string"
}
},
"additionalProperties": false
},
"startMenu": {
"type": "object",
"properties": {
"folder": {
"type": "string"
},
"addUninstallEntry": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/startMenuItem"
}
}
},
"additionalProperties": false
},
"startMenuItem": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"filename": {
"type": "string"
},
"arguments": {
"type": "string"
},
"icon": {
"type": "string"
},
"location": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"name",
"filename"
]
},
"strings": {
"type": "object",
"patternProperties": {
".": {
"type": "string"
}
},
"additionalProperties": false
},
"files": {
"type": "array",
"items": {
"$ref": "#/definitions/file"
}
},
"file": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"copyLocation": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"name",
"description"
]
},
"keyboards": {
"type": "array",
"items": {
"$ref": "#/definitions/keyboard"
}
},
"keyboard": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "string"
},
"version": {
"type": "string"
},
"oskFont": {
"type": "string"
},
"displayFont": {
"type": "string"
},
"rtl": {
"type": "boolean"
},
"languages": {
"type": "array",
"items": {
"$ref": "#/definitions/keyboardLanguage"
}
},
"examples": {
"type": "array",
"items": {
"$ref": "#/definitions/keyboardExample"
}
},
"webOskFonts": {
"type": "array",
"items": {
"type": "string"
}
},
"webDisplayFonts": {
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": [
"name",
"id",
"version"
]
},
"keyboardLanguage": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"name"
]
},
"info": {
"type": "object",
"properties": {
"website": {
"$ref": "#/definitions/infoItem"
},
"version": {
"$ref": "#/definitions/infoItem"
},
"name": {
"$ref": "#/definitions/infoItem"
},
"copyright": {
"$ref": "#/definitions/infoItem"
},
"author": {
"$ref": "#/definitions/infoItem"
},
"description": {
"$ref": "#/definitions/infoItem"
}
},
"additionalProperties": false
},
"infoItem": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"url": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"description"
]
},
"lexicalModels": {
"type": "array",
"items": {
"$ref": "#/definitions/lexicalModel"
}
},
"lexicalModel": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "string"
},
"version": {
"type": "string"
},
"rtl": {
"type": "boolean"
},
"languages": {
"type": "array",
"items": {
"$ref": "#/definitions/keyboardLanguage"
}
}
},
"additionalProperties": false,
"required": [
"name",
"id",
"languages"
]
},
"keyboardExample": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"keys": {
"type": "string"
},
"text": {
"type": "string"
},
"note": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"id",
"keys"
]
},
"relatedPackage": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"relationship": {
"type": "string",
"enum": ["deprecates", "related"]
}
},
"additionalProperties": false,
"required": [
"id",
"relationship"
]
}
}
}

View file

@ -1,9 +1,22 @@
# kps.xsd
Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/7.0/kps.xsd
Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/17.0/kps.xsd
## 2023-10-19 17.0
* Version 17.0 adds:
- LicenseFile - a .md file, usually named LICENSE.md
- WelcomeFile - a .htm file, usually named welcome.htm (later versions will support .md)
- Info/Description - a short Markdown description of the content of the package, e.g. shown in search results on keyman.com
- RelatedPackages - a list of other packages which relate to this one, or are deprecated by it
- Keyboards/Keyboard/Examples - a list of typing examples for the keyboard
- Keyboarsd/Keyboard/WebOSKFonts - a list of font filenames (not necessarily in package) suitable for rendering the on screen keyboard
- Keyboarsd/Keyboard/WebDisplayFonts - a list of font filenames (not necessarily in package) suitable for use with the keyboard
* Version 17.0 removes:
- LexicalModels/LexicalModel/Version - version information is not stored in the models, but only in the package metadata (was unused)
## 2023-04-21 7.0.1
* Removes LexicalModel.Version, as it was never read or written
## 2021-07-19 7.0
* Initial version 7.0
## 2023-04-21 7.0.1
* Removes LexicalModel.Version, as it was never read or written

View file

@ -41,34 +41,41 @@ export interface LKTTests {
export interface LKTTest {
name?: string;
startContext?: LKTStartContext;
actions?: LKTAction[]; // differs from XML, to represent order of actions
actions?: LKTAnyAction[]; // differs from XML, to represent order of actions
};
export interface LKTStartContext {
to?: string;
};
export interface LKTCheck {
result?: string;
};
export interface LKTEmit {
to?: string;
};
export interface LKTKeystroke {
key?: string;
flick?: string;
longPress?: string;
tapCount?: string;
};
/**
* Test Actions.
* The expectation is that each LKTAction object will have exactly one non-falsy field.
*/
export interface LKTAction {
check?: LKTCheck;
emit?: LKTEmit;
keystroke?: LKTKeystroke;
type?: "check" | "emit" | "keystroke" | "backspace";
};
export interface LKTCheck extends LKTAction {
type: "check";
result?: string;
};
export interface LKTEmit extends LKTAction {
type: "emit";
to?: string;
};
export interface LKTKeystroke extends LKTAction {
type: "keystroke";
key?: string;
flick?: string;
longPress?: string;
tapCount?: string;
};
export interface LKTBackspace extends LKTAction {
type: "backspace";
}
export type LKTAnyAction = LKTCheck | LKTEmit | LKTKeystroke | LKTBackspace;

View file

@ -21,8 +21,8 @@ export class LDMLKeyboardXMLSourceFileReader {
constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) {
}
static get defaultImportsURL() {
return new URL(`../import/`, import.meta.url);
static get defaultImportsURL(): [string,string] {
return ['../import/', import.meta.url];
}
readImportFile(version: string, subpath: string): Uint8Array {
@ -393,15 +393,12 @@ export class LDMLKeyboardXMLSourceFileReader {
r.stuffBoxes(test, $$, 'startContext'); // singleton
// now the actions
test.actions = $$.map(v => {
const subtag = v['#name'];
const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r);
switch(subtag) {
case 'keystroke': return { keystroke: subv };
case 'check': return { check: subv };
case 'emit': return { emit: subv };
case 'startContext': return null; // handled above
default: this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedAction({ subtag })); return null;
const type = v['#name']; // element name
if (type === 'startContext') {
return null; // handled above
}
const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r);
return Object.assign({ type }, subv);
}).filter(v => v !== null);
return test;
});

View file

@ -43,8 +43,4 @@ export class CommonTypesMessages {
m(this.ERROR_TestDataUnexpectedArray,
`Problem reading test data: expected single ${o.subtag} element, found multiple`);
static ERROR_TestDataUnexpectedArray = SevError | 0x0007;
static Error_TestDataUnexpectedAction = (o: {subtag: string}) =>
m(this.ERROR_TestDataUnexpectedAction,
`Problem reading test data: unexpected action element ${o.subtag}`);
static ERROR_TestDataUnexpectedAction = SevError | 0x0008;
};

View file

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!--
Read by:
- common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts
Also see the other test-fr.xml in developer
-->
<info keyboard="fr-t-k0-azerty.xml" author="Team Keyboard" name="fr-test" />
<repertoire name="simple-repertoire" chars="[a b c d e \u{22}]" type="simple" /> <!-- verify that these outputs are all available from simple keys on any layer, for all form factors -->
<repertoire name="chars-repertoire" chars="[á é ó]" type="gesture" /> <!-- verify that these outputs are all available from simple or gesture keys on any layer, for touch -->
@ -17,6 +23,8 @@
<!-- tests by specifying 'to' output char -->
<emit to="v"/>
<check result="abc\u0022...stuv" />
<backspace />
<check result="abc\u0022...stu" />
</test>
</tests>
</keyboardTest3>

View file

@ -9,7 +9,7 @@ import { TestCompilerCallbacks } from './TestCompilerCallbacks.js';
import { fileURLToPath } from 'url';
const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = {
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
};
export interface CompilationCase {

View file

@ -2,12 +2,14 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import { assert } from 'chai';
import 'mocha';
import { testTestdataReaderCases } from '../helpers/reader-callback-test.js';
import { LKTAnyAction } from './ldml-keyboard-testdata-xml.js';
describe('ldml keyboard xml reader tests', function () {
this.slow(500); // 0.5 sec -- json schema validation takes a while
testTestdataReaderCases([
{
// Note! There's another test case against similar data, in developer/src/kmc-ldml/test/test-testdata-e2e.ts using test-fr.json
subpath: 'test-fr.xml',
callback: (data, source) => {
assert.ok(source);
@ -35,16 +37,19 @@ describe('ldml keyboard xml reader tests', function () {
const test0 = source.keyboardTest3.tests[0].test[0];
assert.equal('key-test', test0.name);
assert.equal('abc\\u0022...', test0.startContext?.to);
assert.sameDeepOrderedMembers([
{ keystroke: { key: 's' } },
{ check: { result: 'abc\\u0022...s' } },
{ keystroke: { key: 't' } },
{ check: { result: 'abc\\u0022...st' } },
{ keystroke: { key: 'u' } },
{ check: { result: 'abc\\u0022...stu' } },
{ emit: { to: 'v' } },
{ check: { result: 'abc\\u0022...stuv' } },
], test0.actions);
const expectedActions : LKTAnyAction[] = [
{ type: "keystroke", key: 's' },
{ type: "check", result: 'abc\\u0022...s' },
{ type: "keystroke", key: 't' },
{ type: "check", result: 'abc\\u0022...st' },
{ type: "keystroke", key: 'u' },
{ type: "check", result: 'abc\\u0022...stu' },
{ type: "emit", to: 'v' },
{ type: "check", result: 'abc\\u0022...stuv' },
{ type: "backspace" },
{ type: "check", result: 'abc\\u0022...stu' },
];
assert.sameDeepOrderedMembers(expectedActions, test0.actions, 'Static data in .ts file should match parsed test-fr.xml');
},
}
]);

View file

@ -1,5 +1,7 @@
/* Debugging */
#pragma once
#include <keyman/keyman_core_api_bits.h>
namespace km {
@ -18,10 +20,12 @@ extern const char *s_key_names[];
#ifdef _MSC_VER
#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0)
#define DebugLog2(file,line,function,msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(file, line, function, (msg),__VA_ARGS__) : 0)
#define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__)
#define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__)
#else
#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0)
#define DebugLog2(file,line,function,msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(file, line, function, (msg), ##__VA_ARGS__) : 0)
#define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__)
#define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__)
#endif

View file

@ -6,6 +6,7 @@
*/
#include <fstream>
#include <algorithm>
#include "ldml/ldml_processor.hpp"
#include "state.hpp"
#include "kmx_file.h"
@ -236,88 +237,16 @@ ldml_processor::process_event(
// all other VKs
{
// Look up the key
const std::u16string str = keys.lookup(vk, modifier_state);
const std::u16string key_str = keys.lookup(vk, modifier_state);
if (str.empty()) {
if (key_str.empty()) {
// no key was found, so pass the keystroke on to the Engine
state->actions().push_invalidate_context();
state->actions().push_emit_keystroke();
break; // ----- commit and exit
}
// found a string - push it into the context and actions
// we convert it here instead of using the emit_text() overload
// so that we don't have to reconvert it inside the transform code.
const std::u32string str32 = kmx::u16string_to_u32string(str);
if (!transforms) {
// No transforms: just emit the string.
emit_text(state, str32);
} else {
// Process transforms here
/**
* a copy of the current/changed context, for transform use.
*
*/
std::u32string ctxtstr;
(void)context_to_string(state, ctxtstr);
// add the newly added key output to ctxtstr
ctxtstr.append(str32);
/** the output buffer for transforms */
std::u32string outputString;
// apply the transform, get how much matched (at the end)
const size_t matchedContext = transforms->apply(ctxtstr, outputString);
if (matchedContext == 0) {
// No match, just emit the original string
emit_text(state, str32);
} else {
// We have a match.
ctxtstr.resize(ctxtstr.length() - str32.length());
/** how many chars of the context we need to clear */
auto charsToDelete = matchedContext - str32.length(); /* we don't need to clear the output of the current key */
/** how many context items need to be removed */
size_t contextRemoved = 0;
for (auto c = state->context().rbegin(); charsToDelete > 0 && c != state->context().rend(); c++, contextRemoved++) {
/** last char of context */
km_core_usv lastCtx = ctxtstr.back();
uint8_t type = c->type;
assert(type == KM_CORE_BT_CHAR || type == KM_CORE_BT_MARKER);
if (type == KM_CORE_BT_CHAR) {
// single char, drop it
charsToDelete--;
assert(c->character == lastCtx);
ctxtstr.pop_back();
state->actions().push_backspace(KM_CORE_BT_CHAR, lastCtx); // Cause prior char to be removed
} else if (type == KM_CORE_BT_MARKER) {
// it's a marker, 'worth' 3 uchars
assert(charsToDelete >= 3);
assert(lastCtx == c->marker); // end of list
charsToDelete -= 3;
// pop off the three-part sentinel string
ctxtstr.pop_back();
ctxtstr.pop_back();
ctxtstr.pop_back();
// push a special backspace to delete the marker
state->actions().push_backspace(KM_CORE_BT_MARKER, c->marker);
}
}
// now, pop the right number of context items
for (size_t i = 0; i < contextRemoved; i++) {
// we don't pop during the above loop because the iterator gets confused
state->context().pop_back();
}
// Now, add in the updated text. This will convert UC_SENTINEL, etc back to marker actions.
emit_text(state, outputString);
// If we needed it further. we could update ctxtstr here:
// ctxtstr.append(outputString);
// ... but it is no longer needed at this point.
} // end of transform match
} // end of processing transforms
process_key_string(state, key_str);
} // end of processing a 'normal' vk
} // end of switch
// end of normal processing: commit and exit
@ -330,6 +259,103 @@ ldml_processor::process_event(
return KM_CORE_STATUS_OK;
}
void
ldml_processor::process_key_string(km_core_state *state, const std::u16string &key_str) const {
// We know that key_str is not empty per the caller.
assert(!key_str.empty());
// we convert the keys str to UTF-32 here instead of using the emit_text() overload
// so that we don't have to reconvert it inside the transform code.
std::u32string key_str32 = kmx::u16string_to_u32string(key_str);
assert(ldml::normalize_nfd(key_str32)); // TODO-LDML: else fail?
// extract context string, in NFC
std::u32string old_ctxtstr_nfc;
(void)context_to_string(state, old_ctxtstr_nfc, false);
assert(ldml::normalize_nfc(old_ctxtstr_nfc)); // TODO-LDML: else fail?
// context string in NFD
std::u32string ctxtstr;
(void)context_to_string(state, ctxtstr, true); // with markers
// add the newly added key output to ctxtstr
ctxtstr.append(key_str32);
assert(ldml::normalize_nfd(ctxtstr)); // TODO-LDML: else fail?
/** transform output string */
std::u32string outputString;
/** how many chars of the ctxtstr to replace */
size_t matchedContext = 0; // zero if no transforms
// begin modifications to the string
if(transforms) {
matchedContext = transforms->apply(ctxtstr, outputString);
} else {
// no transforms, no output
}
// drop last 'matchedContext':
ctxtstr.resize(ctxtstr.length() - matchedContext);
ctxtstr.append(outputString); // TODO-LDML: should be able to do a normalization-safe append here.
assert(ldml::normalize_nfd(ctxtstr)); // TODO-LDML: else fail?
// Ok. We've done all the happy manipulations.
/** NFC and no markers */
std::u32string ctxtstr_cleanedup = ctxtstr;
// TODO-LDML: remove markers!
assert(ldml::normalize_nfc(ctxtstr_cleanedup)); // TODO-LDML: else fail?
// find common prefix
auto ctxt_prefix = mismatch(old_ctxtstr_nfc.begin(), old_ctxtstr_nfc.end(), ctxtstr_cleanedup.begin(), ctxtstr_cleanedup.end());
/** the part of the old str that changed */
std::u32string old_ctxtstr_changed(ctxt_prefix.first,old_ctxtstr_nfc.end());
std::u32string new_ctxtstr_changed(ctxt_prefix.second,ctxtstr_cleanedup.end());
// drop the old suffix. Note: this mutates old_ctxtstr_changed.
remove_text(state, old_ctxtstr_changed, old_ctxtstr_changed.length());
assert(old_ctxtstr_changed.length() == 0);
emit_text(state, new_ctxtstr_changed);
}
void
ldml_processor::remove_text(km_core_state *state, std::u32string &str, size_t length) {
/** how many context items need to be removed */
size_t contextRemoved = 0;
for (auto c = state->context().rbegin(); length > 0 && c != state->context().rend(); c++, contextRemoved++) {
/** last char of context */
km_core_usv lastCtx = str.back();
uint8_t type = c->type;
assert(type == KM_CORE_BT_CHAR || type == KM_CORE_BT_MARKER);
if (type == KM_CORE_BT_CHAR) {
// single char, drop it
length--;
assert(c->character == lastCtx);
str.pop_back();
state->actions().push_backspace(KM_CORE_BT_CHAR, c->character); // Cause prior char to be removed
} else if (type == KM_CORE_BT_MARKER) {
// it's a marker, 'worth' 3 uchars
assert(length >= 3);
assert(lastCtx == c->marker); // end of list
length -= 3;
// pop off the three-part sentinel string (in reverse order of course)
assert(str.back() == c->marker); // marker #
str.pop_back();
assert(str.back() == LDML_MARKER_CODE);
str.pop_back();
assert(str.back() == LDML_UC_SENTINEL);
str.pop_back();
// push a special backspace to delete the marker
state->actions().push_backspace(KM_CORE_BT_MARKER, c->marker);
}
}
// now, pop the right number of context items
for (size_t i = 0; i < contextRemoved; i++) {
// we don't pop during the above loop because the iterator gets confused
state->context().pop_back();
}
}
km_core_attr const & ldml_processor::attributes() const {
return engine_attrs;
}
@ -395,10 +421,10 @@ ldml_processor::emit_marker(km_core_state *state, KMX_DWORD marker_no) {
}
size_t
ldml_processor::context_to_string(km_core_state *state, std::u32string &str) {
ldml_processor::context_to_string(km_core_state *state, std::u32string &str, bool include_markers) {
str.clear();
auto &cp = state->context();
size_t ctxlen = 0; // TODO-LDML: is this needed?
size_t ctxlen = 0; // TODO-LDML: not used by callers?
uint8_t last_type = KM_CORE_BT_UNKNOWN;
for (auto c = cp.rbegin(); c != cp.rend(); c++, ctxlen++) {
last_type = c->type;
@ -406,7 +432,9 @@ ldml_processor::context_to_string(km_core_state *state, std::u32string &str) {
str.insert(0, 1, c->character);
} else if (last_type == KM_CORE_BT_MARKER) {
assert(km::kbp::kmx::is_valid_marker(c->marker));
prepend_marker(str, c->marker);
if (include_markers) {
prepend_marker(str, c->marker);
}
} else {
break;
}
@ -414,6 +442,5 @@ ldml_processor::context_to_string(km_core_state *state, std::u32string &str) {
return ctxlen; // consumed the entire context buffer.
}
} // namespace kbp
} // namespace km

View file

@ -93,6 +93,15 @@ namespace kbp {
static void emit_text(km_core_state *state, km_core_usv ch);
/** emit a marker */
static void emit_marker(km_core_state *state, KMX_DWORD marker);
/**
* Delete text from the state.
* @param str string with text to remove, from the end
* @param length number of chars from the end of str to drop
*/
static void remove_text(km_core_state *state, std::u32string &str, size_t length);
/** process a typed key */
void process_key_string(km_core_state *state, const std::u16string &key_str) const;
/**
* add the string+marker portion of the context to the beginning of str.
@ -100,7 +109,7 @@ namespace kbp {
* Convert markers into the UC_SENTINEL format.
* @return the number of context items consumed
*/
static size_t context_to_string(km_core_state *state, std::u32string &str);
static size_t context_to_string(km_core_state *state, std::u32string &str, bool include_markers = true);
/** prepend the marker string in UC_SENTINEL format to the str */
inline static void prepend_marker(std::u32string &str, KMX_DWORD marker);

View file

@ -12,7 +12,7 @@
#include "kmx/kmx_xstring.h"
#ifndef assert
#define assert(x) // TODO-LDML
#define assert(x) ((void)0)
#endif
namespace km {
@ -403,25 +403,29 @@ transform_entry::transform_entry(const transform_entry &other)
transform_entry::transform_entry(const std::u32string &from, const std::u32string &to)
: fFrom(from), fTo(to), fFromPattern(nullptr), fMapFromStrId(), fMapToStrId(), fMapFromList(), fMapToList() {
assert(!fFrom.empty()); // TODO-LDML: should not happen?
assert(!fFrom.empty());
init();
}
// TODO-LDML: How do we return errors from here?
transform_entry::transform_entry(
const std::u32string &from,
const std::u32string &to,
KMX_DWORD mapFrom,
KMX_DWORD mapTo,
const kmx::kmx_plus &kplus)
const kmx::kmx_plus &kplus,
bool &valid)
: fFrom(from), fTo(to), fFromPattern(nullptr), fMapFromStrId(mapFrom), fMapToStrId(mapTo) {
if (!valid)
return; // exit early
assert(!fFrom.empty()); // TODO-LDML: should not happen?
assert((fMapFromStrId == 0) == (fMapToStrId == 0)); // we have both or we have neither.
assert(kplus.strs != nullptr);
assert(kplus.vars != nullptr);
assert(kplus.elem != nullptr);
init();
if(!init()) {
valid = false;
}
// setup mapFrom
if (fMapFromStrId != 0) {
@ -456,18 +460,23 @@ transform_entry::transform_entry(
}
}
void
bool
transform_entry::init() {
if (!fFrom.empty()) {
// TODO-LDML: if we have mapFrom, may need to do other processing.
const std::u16string patstr = km::kbp::kmx::u32string_to_u16string(fFrom);
UErrorCode status = U_ZERO_ERROR;
/* const */ icu::UnicodeString patustr = icu::UnicodeString(patstr.data(), (int32_t)patstr.length());
// add '$' to match to end
patustr.append(u'$');
fFromPattern.reset(icu::RegexPattern::compile(patustr, 0, status));
assert(U_SUCCESS(status)); // TODO-LDML: may be best to propagate status up ^^
if (fFrom.empty()) {
return false;
}
// TODO-LDML: if we have mapFrom, may need to do other processing.
const std::u16string patstr = km::kbp::kmx::u32string_to_u16string(fFrom);
UErrorCode status = U_ZERO_ERROR;
/* const */ icu::UnicodeString patustr_raw = icu::UnicodeString(patstr.data(), (int32_t)patstr.length());
// add '$' to match to end
patustr_raw.append(u'$');
icu::UnicodeString patustr;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
// NFD normalize on pattern creation
nfd->normalize(patustr_raw, patustr, status);
fFromPattern.reset(icu::RegexPattern::compile(patustr, 0, status));
return (UASSERT_SUCCESS(status));
}
size_t
@ -480,7 +489,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
icu::UnicodeString matchustr = icu::UnicodeString(matchstr.data(), (int32_t)matchstr.length());
// TODO-LDML: create a new Matcher every time. These could be cached and reset.
std::unique_ptr<icu::RegexMatcher> matcher(fFromPattern->matcher(matchustr, status));
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
if (!matcher->find(status)) { // i.e. matches somewhere, in this case at end of str
return 0; // no match
@ -490,7 +499,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// TODO-LDML: if we had an underlying UText this would be simpler.
int32_t matchStart = matcher->start(status);
int32_t matchEnd = matcher->end(status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
// extract..
const icu::UnicodeString substr = matchustr.tempSubStringBetween(matchStart, matchEnd);
// preflight to UTF-32 to get length
@ -517,7 +526,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// we actually need the group(1) string here.
// this is only the content in parenthesis ()
icu::UnicodeString group1 = matcher->group(1, status);
assert(U_SUCCESS(status)); // TODO-LDML: could be a malformed from pattern
UASSERT_SUCCESS(status); // TODO-LDML: could be a malformed from pattern
// now, how long is group1 in UTF-32, hmm?
UErrorCode preflightStatus = U_ZERO_ERROR; // throwaway status
auto group1Len = group1.toUTF32(nullptr, 0, preflightStatus);
@ -525,7 +534,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
assert(s != nullptr); // TODO-LDML: OOM
// convert
substr.toUTF32((UChar32 *)s, group1Len + 1, status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
std::u32string match32(s, group1Len); // taken from just group1
// clean up buffer
delete [] s;
@ -545,12 +554,21 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
rustr = icu::UnicodeString(rstr.data(), (int32_t)rstr.length());
// and we return to the regular code flow.
}
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
icu::UnicodeString rustr2;
nfd->normalize(rustr, rustr2, status);
UASSERT_SUCCESS(status);
// here we replace the match output.
icu::UnicodeString entireOutput = matcher->replaceFirst(rustr, status);
assert(U_SUCCESS(status)); // TODO-LDML: could fail here due to bad input (syntax err)
icu::UnicodeString entireOutput = matcher->replaceFirst(rustr2, status);
UASSERT_SUCCESS(status); // TODO-LDML: could fail here due to bad input (syntax err)
// entireOutput includes all of 'input', but modified. Need to substring it.
icu::UnicodeString outu = entireOutput.tempSubString(matchStart);
icu::UnicodeString outu_raw = entireOutput.tempSubString(matchStart);
// normalize the replaced string
icu::UnicodeString outu;
nfd->normalize(outu_raw, outu, status);
UASSERT_SUCCESS(status);
// Special case if there's no output, save some allocs
if (outu.length() == 0) {
@ -565,7 +583,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
assert(s != nullptr);
// convert
outu.toUTF32((UChar32 *)s, out32len + 1, status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
output.assign(s, out32len);
// now, build a u32string
std::u32string out32(s, out32len);
@ -754,35 +772,34 @@ transforms::load(
const kmx::kmx_plus &kplus,
const kbp::kmx::COMP_KMXPLUS_TRAN *tran,
const kbp::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper) {
bool valid = true;
if (tran == nullptr) {
DebugLog("for tran: tran is null");
assert(false);
return nullptr;
}
if (!tranHelper.valid()) {
valid = false;
} else if (!tranHelper.valid()) {
DebugLog("for tran: tranHelper is invalid");
assert(false);
return nullptr;
}
if (nullptr == kplus.elem) {
valid = false;
} else if (nullptr == kplus.elem) {
DebugLog("for tran: kplus.elem == nullptr");
assert(false);
return nullptr;
}
if (nullptr == kplus.strs) {
valid = false;
} else if (nullptr == kplus.strs) {
DebugLog("for tran: kplus.strs == nullptr"); // need a string table to get strings
assert(false);
return nullptr;
}
if (nullptr == kplus.vars) {
valid = false;
} else if (nullptr == kplus.vars) {
DebugLog("for tran: kplus.vars == nullptr"); // need a vars table to get maps
assert(false);
valid = false;
}
assert(valid);
if (!valid) {
return nullptr;
}
// with that out of the way, let's set it up
transforms *transforms = new ldml::transforms();
std::unique_ptr<transforms> transforms;
transforms.reset(new ldml::transforms());
for (KMX_DWORD groupNumber = 0; groupNumber < tran->groupCount; groupNumber++) {
const kmx::COMP_KMXPLUS_TRAN_GROUP *group = tranHelper.getGroup(groupNumber);
@ -798,7 +815,15 @@ transforms::load(
const std::u32string toStr = kmx::u16string_to_u32string(kplus.strs->get(element->to));
KMX_DWORD mapFrom = element->mapFrom; // copy, because of alignment
KMX_DWORD mapTo = element->mapTo; // copy, because of alignment
newGroup.emplace_back(fromStr, toStr, mapFrom, mapTo, kplus); // creating a transform_entry
assert(!fromStr.empty());
if (fromStr.empty()) {
valid = false;
}
newGroup.emplace_back(fromStr, toStr, mapFrom, mapTo, kplus, valid); // creating a transform_entry
assert(valid);
if(!valid) {
return nullptr;
}
}
transforms->addGroup(newGroup);
} else if (group->type == LDML_TRAN_GROUP_TYPE_REORDER) {
@ -828,7 +853,61 @@ transforms::load(
return nullptr;
}
}
return transforms;
assert(valid);
if (!valid) {
return nullptr;
} else {
return transforms.release();
}
}
// string manipulation
bool normalize_nfd(std::u32string &str) {
std::u16string rstr = km::kbp::kmx::u32string_to_u16string(str);
if(!normalize_nfd(rstr)) {
return false;
} else {
str = km::kbp::kmx::u16string_to_u32string(rstr);
return true;
}
}
/** internal function to normalize with a specified mode */
static bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status) {
UASSERT_SUCCESS(status);
assert(n != nullptr);
icu::UnicodeString dest;
icu::UnicodeString src = icu::UnicodeString(str.data(), (int32_t)str.length());
n->normalize(src, dest, status);
if (UASSERT_SUCCESS(status)) {
str.assign(dest.getBuffer(), dest.length());
}
return U_SUCCESS(status);
}
bool normalize_nfd(std::u16string &str) {
UErrorCode status = U_ZERO_ERROR;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
UASSERT_SUCCESS(status);
return normalize(nfd, str, status);
}
bool normalize_nfc(std::u32string &str) {
std::u16string rstr = km::kbp::kmx::u32string_to_u16string(str);
if(!normalize_nfc(rstr)) {
return false;
} else {
str = km::kbp::kmx::u16string_to_u32string(rstr);
return true;
}
}
bool normalize_nfc(std::u16string &str) {
UErrorCode status = U_ZERO_ERROR;
const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(status);
UASSERT_SUCCESS(status);
return normalize(nfc, str, status);
}
} // namespace ldml

View file

@ -13,6 +13,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include "debuglog.h"
#if !defined(HAVE_ICU4C)
#error icu4c is required for this code
@ -25,11 +26,24 @@
#include "unicode/unistr.h"
#include "unicode/regex.h"
#include "unicode/utext.h"
#include "unicode/normalizer2.h"
namespace km {
namespace kbp {
namespace ldml {
/** @returns true on success */
inline bool uassert_success(const char *file, int line, const char *function, UErrorCode status) {
if (U_FAILURE(status)) {
DebugLog2(file, line, function, "U_FAILURE(%s)", u_errorName(status));
return false;
} else {
return true;
}
}
#define UASSERT_SUCCESS(status) assert(U_SUCCESS(status)), uassert_success(__FILE__, __LINE__, __FUNCTION__, status)
using km::kbp::kmx::SimpleUSet;
/**
@ -94,7 +108,8 @@ public:
const std::u32string &to,
KMX_DWORD mapFrom,
KMX_DWORD mapTo,
const kmx::kmx_plus &kplus);
const kmx::kmx_plus &kplus,
bool &valid);
/**
* If matching, apply the match to the output string
@ -113,8 +128,8 @@ private:
const KMX_DWORD fMapToStrId;
std::deque<std::u32string> fMapFromList;
std::deque<std::u32string> fMapToList;
/** Internal function to setup pattern string */
void init();
/** Internal function to setup pattern string @returns true on success */
bool init();
/** @returns the index of the item in the fMapFromList list, or -1 */
int32_t findIndexFrom(const std::u32string &match) const;
public:
@ -270,6 +285,17 @@ public:
const kbp::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper);
};
// string routines
/** Normalize a u32string inplace to NFD. @return false on failure */
bool normalize_nfd(std::u32string &str);
/** Normalize a u16string inplace to NFD. @return false on failure */
bool normalize_nfd(std::u16string &str);
/** Normalize a u32string inplace to NFC. @return false on failure */
bool normalize_nfc(std::u32string &str);
/** Normalize a u16string inplace to NFC. @return false on failure */
bool normalize_nfc(std::u16string &str);
} // namespace ldml
} // namespace kbp
} // namespace km

View file

@ -35,7 +35,7 @@ uconfig.set('U_ENABLE_DYLOAD', 0) # no DLL
uconfig.set('U_CHECK_DYLOAD', 0) # no DLL
uconfig.set('UCONFIG_NO_FILE_IO', 1)
uconfig.set('UCONFIG_NO_LEGACY_CONVERSION', 1) # turn off file based codepage conversion
uconfig.set('UCONFIG_NO_NORMALIZATION', 1) # TODO-LDML: may want this
uconfig.set('UCONFIG_NO_NORMALIZATION', 0)
uconfig.set('UCONFIG_NO_BREAK_ITERATION', 1) # TODO-LDML: may want this
uconfig.set('UCONFIG_NO_IDNA', 1)
uconfig.set('UCONFIG_NO_COLLATION', 1)

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_006_backspace.xml" author="Team Keyboard" name="backspace" />
<tests name="backspace">
<test name="regex-test-0-a">
<startContext to="" />
<keystroke key="t" />
<keystroke key="a" />
<keystroke key="b" />
<backspace />
<check result="ta" /> <!-- TODO-LDML: #9760 Should be 't' due to backspace transform -->
</test>
</tests>
</keyboardTest3>

View file

@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
from https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-keyboards.md#element-transform
@@keys: [K_T][K_A][K_B][K_BKSP]
@@expected: t
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<info author="srl295" indicator="🙀" layout="qwerty" normalization="NFC" />

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_008_transform_norm.xml" author="Team Keyboard" name="transform-norm" />
<tests name="regex-tests-0">
<!-- none of these should match the transform -->
<test name="regex-test-0-a">
<startContext to="" />
<keystroke key="e" />
<keystroke key="u-0300" />
<keystroke key="u-0320" />
<check result="e\u{0320}\u{0300}" />
</test>
<test name="regex-test-0-b">
<startContext to="" />
<keystroke key="e" />
<keystroke key="u-0320" />
<keystroke key="u-0300" />
<check result="e\u{0320}\u{0300}" />
</test>
<test name="regex-test-0-c">
<startContext to="" />
<keystroke key="u-00e8" />
<keystroke key="u-0320" />
<check result="e\u{0320}\u{0300}" />
</test>
</tests>
<tests name="regex-tests-1">
<test name="regex-test-1-nfd">
<startContext to="" />
<keystroke key="1" />
<keystroke key="e" />
<keystroke key="u-0300" />
<keystroke key="u-0320" />
<check result="YES1" />
</test>
<test name="regex-test-1-nfd2">
<startContext to="" />
<keystroke key="1" />
<keystroke key="nfd" />
<check result="YES1" />
</test>
<test name="regex-test-1-nfc">
<startContext to="" />
<keystroke key="1" />
<keystroke key="nfc" />
<check result="YES1" />
</test>
<test name="regex-test-1-nfd-ooo">
<startContext to="" />
<keystroke key="1" />
<keystroke key="e" />
<keystroke key="u-0320" />
<keystroke key="u-0300" />
<check result="YES1" />
</test>
<test name="regex-test-1-nfd-ooo2">
<startContext to="" />
<keystroke key="1" />
<keystroke key="not-nfd" />
<check result="YES1" />
</test>
</tests>
<tests name="regex-tests-2">
<test name="regex-test-2-nfd">
<startContext to="" />
<keystroke key="2" />
<keystroke key="e" />
<keystroke key="u-0300" />
<keystroke key="u-0320" />
<check result="YES2" />
</test>
<test name="regex-test-2-nfd2">
<startContext to="" />
<keystroke key="2" />
<keystroke key="nfd" />
<check result="YES2" />
</test>
<test name="regex-test-2-nfc">
<startContext to="" />
<keystroke key="2" />
<keystroke key="nfc" />
<check result="YES2" />
</test>
<test name="regex-test-2-nfd-ooo">
<startContext to="" />
<keystroke key="2" />
<keystroke key="e" />
<keystroke key="u-0320" />
<keystroke key="u-0300" />
<check result="YES2" />
</test>
<test name="regex-test-2-nfd-ooo2">
<startContext to="" />
<keystroke key="2" />
<keystroke key="not-nfd" />
<check result="YES2" />
</test>
</tests>
<tests name="regex-tests-3">
<test name="regex-test-3-nfd">
<startContext to="" />
<keystroke key="3" />
<keystroke key="e" />
<keystroke key="u-0300" />
<keystroke key="u-0320" />
<check result="YES3" />
</test>
<test name="regex-test-3-nfd2">
<startContext to="" />
<keystroke key="3" />
<keystroke key="nfd" />
<check result="YES3" />
</test>
<test name="regex-test-3-nfc">
<startContext to="" />
<keystroke key="3" />
<keystroke key="nfc" />
<check result="YES3" />
</test>
<test name="regex-test-3-nfd-ooo">
<startContext to="" />
<keystroke key="3" />
<keystroke key="e" />
<keystroke key="u-0320" />
<keystroke key="u-0300" />
<check result="YES3" />
</test>
<test name="regex-test-3-nfd-ooo2">
<startContext to="" />
<keystroke key="3" />
<keystroke key="not-nfd" />
<check result="YES3" />
</test>
</tests>
</keyboardTest3>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
from https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-keyboards.md#element-transform
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<info author="srl295" indicator="🙀" layout="qwerty" normalization="NFC" />
<names>
<name value="k_008_transform_norm" />
</names>
<keys>
<key id="u-0320" to="\u{0320}" /> <!-- ◌̠ -->
<key id="u-0300" to="\u{0300}" /> <!-- ◌̀ -->
<key id="u-00e8" to="\u{00e8}" /> <!-- è -->
<key id="nfd" to="e\u{0320}\u{0300}" />
<key id="nfc" to="\u{00E8}\u{0320}" />
<key id="not-nfd" to="e\u{0300}\u{0320}" />
</keys>
<layers form="us">
<layer modifier="none" id="base">
<row keys="u-0320 1 2 3 4 5 6 7 8 9 0" />
<row keys="q w e r t y u i o p" />
<row keys="a s d f g h j k l" />
<row keys="z x c v b n m" />
<row keys="space" />
</layer>
<layer modifier="shift" id="shift">
<row keys="u-0300 u-00e8 nfd nfc not-nfd" />
</layer>
</layers>
<variables>
<string id="under" value="\u{0320}" />
<string id="grave" value="\u{0300}" />
</variables>
<transforms type="simple">
<transformGroup>
<!-- these should NOT match-->
<transform from="e[\u{0300}][\u{0320}]" to="NO0" /> <!-- out of order -->
<!-- these SHOULD match -->
<transform from="1e\u{0300}\u{0320}" to="YES1" /> <!-- out of order but should get normalized -->
<transform from="2e\u{0320}\u{0300}" to="YES2" /> <!-- in order NFD -->
<transform from="3\u{00E8}\u{0320}" to="YES3" /> <!-- in order NFC -->
</transformGroup>
</transforms>
</keyboard3>

View file

@ -21,7 +21,6 @@ tests_without_testdata = [
'k_003_transform',
'k_004_tinyshift',
'k_005_modbittest',
# 'k_006_backspace', ## not quite there yet. TODO-LDML
'k_010_mt',
'k_011_mt_iso',
'k_100_keytest',
@ -33,7 +32,9 @@ tests_without_testdata = [
# These tests have a k_001_tiny-test.xml file as well.
tests_with_testdata = [
'k_001_tiny',
'k_006_backspace',
'k_007_transform_rgx',
'k_008_transform_norm',
'k_020_fr', # TODO-LDML: move to cldr above (fix vkey)
'k_200_reorder_nod_Lana',
'k_210_marker',

View file

@ -213,6 +213,7 @@ run_test(const km::kbp::path &source, const km::kbp::path &compiled, km::tests::
// Run through actions, applying output for each event
for (test_source.next_action(action); action.type != km::tests::LDML_ACTION_DONE; test_source.next_action(action)) {
// handle backspace here
if (action.type == km::tests::LDML_ACTION_KEY_EVENT) {
auto &p = action.k;
std::cout << "- key action: 0x" << std::hex << p.vk << "/modifier 0x" << p.modifier_state << std::dec << std::endl;
@ -294,9 +295,12 @@ run_test(const km::kbp::path &source, const km::kbp::path &compiled, km::tests::
/**
* Run all tests for this keyboard
*/
int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
std::cout << "source file = " << source << std::endl
<< "compiled file = " << compiled << std::endl;
int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled, const std::string &filter) {
std::wcout << console_color::fg(console_color::BLUE) << "source file = " << source << std::endl
<< "compiled file = " << compiled << console_color::reset() << std::endl;
if(!filter.empty()) {
std::wcout << "Running only tests matching (substring search): " << filter.c_str() << std::endl;
}
km::tests::LdmlEmbeddedTestSource embedded_test_source;
@ -304,9 +308,15 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
int embedded_result = embedded_test_source.load_source(source);
if (embedded_result == 0) {
if (!filter.empty()) {
// Always skip the embedded test if there's a filter.
std::wcout << console_color::fg(console_color::YELLOW) << "SKIP: " << source.name() << " (embedded)" << console_color::reset()
<< std::endl;
embedded_result = 0; // no error
} else if (embedded_result == 0) {
// embedded loaded OK, try it
std::cout << "TEST: " << source.name() << " (embedded)" << std::endl;
std::wcout << console_color::fg(console_color::BLUE) << console_color::bold() << "TEST: " << source.name() << " (embedded)"
<< console_color::reset() << std::endl;
embedded_result = run_test(source, compiled, embedded_test_source);
if (embedded_result != 0) {
failures.push_back("in-XML (@@ comment) embedded test failed");
@ -324,29 +334,55 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
if (json_result != -1) {
const km::tests::JsonTestMap& json_tests = json_factory.get_tests();
size_t skip_count = 0;
assert(json_tests.size() > 0);
// Loop over all tests
for (const auto& n : json_tests) {
std::cout << "TEST: " << json_path.stem() << "/" << n.first << std::endl;
const auto test_name = n.first;
auto qq = test_name.find(filter);
if (filter == "--list" || (qq == std::string::npos)) {
skip_count ++;
std::wcout << console_color::fg(console_color::YELLOW) << "SKIP: " << json_path.stem().c_str() << "/" << console_color::bold() << n.first.c_str() << console_color::reset() << std::endl;
continue;
}
std::wcout << console_color::fg(console_color::BLUE) << "TEST: " << json_path.stem().c_str() << "/" << console_color::bold() << n.first.c_str() << console_color::reset() << std::endl;
int sub_test = run_test(source, compiled, *n.second);
if (sub_test != 0) {
std::cout << " FAIL: " << json_path.stem() << "/" << n.first << std::endl;
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "FAIL: " << json_path.stem() << "/" << console_color::bold() << n.first.c_str()
<< console_color::reset() << std::endl;
failures.push_back(json_path.stem() + "/" + n.first);
json_result = sub_test; // set to last failure
} else {
std::cout << " PASS: " << json_path.stem() << "/" << n.first << std::endl;
std::wcout << console_color::fg(console_color::GREEN) << "PASS: " << console_color::reset() << json_path.stem()
<< "/" << console_color::bold() << n.first.c_str() << std::endl;
}
}
std::cout << " " << json_tests.size() << " JSON test(s) in " << json_path.stem() << std::endl;
auto all_count = json_tests.size();
auto fail_count = failures.size();
auto pass_count = all_count - fail_count - skip_count;
if (pass_count > 0) {
std::wcout << console_color::fg(console_color::GREEN) << " +" << pass_count;
}
if (fail_count > 0) {
std::wcout << console_color::fg(console_color::BRIGHT_RED) <<
" -" << fail_count;
}
if (skip_count > 0) {
std::wcout << console_color::fg(console_color::YELLOW) <<
" (skipped " << skip_count << ")";
}
std::wcout << console_color::reset() << " of " << all_count << " JSON tests in "
<< json_path.stem() << std::endl;
}
// OK.
std::wcout << console_color::fg(console_color::YELLOW) << "---- Summary of " << source.name() << " ----" << console_color::reset() << std::endl;
if (embedded_result == -1) {
std::cout << "Note: No embedded test." << std::endl;
std::wcout << console_color::fg(console_color::YELLOW) << "Note: No embedded test." << console_color::reset() << std::endl;
}
if (json_result == -1) {
std::cout << "Note: No json test." << std::endl;
std::wcout << console_color::fg(console_color::YELLOW) << "Note: No json test." << console_color::reset() << std::endl;
}
// if both are missing, that's an error in itself.
@ -358,7 +394,7 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
// recap the failures
if (failures.size() > 0) {
for (const auto& f : failures) {
std::cerr << "failure summary: " << f << std::endl;
std::wcerr << console_color::fg(console_color::RED) << "failed: " << f.c_str() << console_color::reset() << std::endl;
}
return -1;
} else {
@ -371,10 +407,11 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
constexpr const auto help_str =
"\
ldml [--color] <LDML_TEST_FILE> <KMX_FILE>\n\
ldml [--color] <LDML_FILE> <KMX_FILE> [ <TEST_FILTER> | --list ]\n\
help:\n\
\tKMN_FILE:\tThe ldml test file for the keyboard under test.\n\
\tKMX_FILE:\tThe corresponding compiled kmx file.\n";
\tLDML_FILE:\tThe .xml file for the keyboard under test.\n\
\tKMX_FILE:\tThe corresponding compiled kmx file.\n\
\tTEST_FILTER:\tIf present, only run json tests containing the filter substring. --list will list all tests\n";
} // namespace
@ -387,22 +424,30 @@ int error_args() {
int main(int argc, char *argv[]) {
int first_arg = 1;
if (argc < 3) {
if ((argc - first_arg) < 2) { // if < 2 remaining args
return error_args();
}
auto arg_color = std::string(argv[1]) == "--color";
auto arg_color = std::string(argv[first_arg]) == "--color";
if(arg_color) {
first_arg++;
if(argc < 4) {
return error_args();
}
}
console_color::enabled = console_color::isaterminal() || arg_color;
int rc = run_all_tests(argv[first_arg], argv[first_arg + 1]);
if ((argc - first_arg) < 2) {
return error_args();
}
const km::kbp::path ldml_file = argv[first_arg++];
const km::kbp::path kmx_file = argv[first_arg++];
std::string filter; // default to 'all tests'
if ((argc - first_arg) >= 1) {
filter = argv[first_arg++];
}
int rc = run_all_tests(ldml_file, kmx_file, filter);
if (rc != EXIT_SUCCESS) {
std::cerr << "FAILED" << std::endl;
std::wcerr << console_color::fg(console_color::BRIGHT_RED) << "FAILED" << console_color::reset() << std::endl;
rc = EXIT_FAILURE;
}
return rc;

View file

@ -24,6 +24,7 @@
#include <kmx/kmx_plus.h>
#include "ldml/keyboardprocessor_ldml.h"
#include "ldml/ldml_processor.hpp"
#include "ldml/ldml_transforms.hpp"
#include "path.hpp"
#include "state.hpp"
@ -461,35 +462,39 @@ LdmlJsonTestSource::next_action(ldml_action &fillin) {
action_index++;
auto action = data["/actions"_json_pointer].at(action_index);
// load up several common attributes
auto type = action["/type"_json_pointer];
auto result = action["/result"_json_pointer];
auto key = action["/key"_json_pointer];
auto to = action["/to"_json_pointer];
// is it a check event?
auto as_check = action["/check/result"_json_pointer];
if (as_check.is_string()) {
if (type == "check") {
fillin.type = LDML_ACTION_CHECK_EXPECTED;
fillin.string = LdmlTestSource::parse_u8_source_string(as_check.get<std::string>());
fillin.string = LdmlTestSource::parse_u8_source_string(result.get<std::string>());
assert(km::kbp::ldml::normalize_nfc(fillin.string));
return;
}
// is it a keystroke by id?
auto as_key = action["/keystroke/key"_json_pointer];
if (as_key.is_string()) {
} else if (type == "keystroke") {
fillin.type = LDML_ACTION_KEY_EVENT;
auto keyId = LdmlTestSource::parse_u8_source_string(as_key.get<std::string>());
auto keyId = LdmlTestSource::parse_u8_source_string(key.get<std::string>());
// now, look up the key
set_key_from_id(fillin.k, keyId);
return;
}
// TODO-LDML: handle gesture, etc
auto as_emit = action["/emit/to"_json_pointer];
if (as_emit.is_string()) {
} else if (type == "emit") {
fillin.type = LDML_ACTION_EMIT_STRING;
fillin.string = LdmlTestSource::parse_u8_source_string(as_emit.get<std::string>());
fillin.string = LdmlTestSource::parse_u8_source_string(to.get<std::string>());
assert(km::kbp::ldml::normalize_nfc(fillin.string));
return;
} else if (type == "backspace") {
// backspace is handled as a key event
fillin.type = LDML_ACTION_KEY_EVENT;
fillin.k.modifier_state = 0;
fillin.k.vk = KM_CORE_VKEY_BKSP;
return;
}
// TODO-LDML: error passthrough
std::cerr << "TODO-LDML: Error, unknown/unhandled action: " << action << std::endl;
std::cerr << "TODO-LDML: Error, unknown/unhandled action: " << type << std::endl;
fillin.type = LDML_ACTION_DONE;
}
@ -502,7 +507,7 @@ int LdmlJsonTestSource::load(const nlohmann::json &data) {
this->data = data; // TODO-LDML
auto startContext = data["/startContext/to"_json_pointer];
context = LdmlTestSource::parse_u8_source_string(startContext);
assert(km::kbp::ldml::normalize_nfc(context));
return 0;
}
@ -704,7 +709,9 @@ int LdmlJsonTestSourceFactory::load(const km::kbp::path &compiled, const km::kbp
auto info_author = data["/keyboardTest3/info/author"_json_pointer].get<std::string>();
auto info_name = data["/keyboardTest3/info/name"_json_pointer].get<std::string>();
// TODO-LDML: store these elsewhere?
std::cout << "JSON: reading " << info_name << " test of " << info_keyboard << " by " << info_author << std::endl;
std::wcout << console_color::fg(console_color::BLUE) << "test file = " << path.name().c_str() << console_color::reset() << std::endl;
std::wcout << console_color::fg(console_color::YELLOW) << info_name.c_str() << "/ " << console_color::reset()
<< " test: " << info_keyboard.c_str() << " author: " << info_author.c_str() << std::endl;
auto all_tests = data["/keyboardTest3/tests"_json_pointer];
assert_or_return((!all_tests.empty()) && (all_tests.size() > 0)); // TODO-LDML: can be empty if repertoire only?
@ -715,7 +722,7 @@ int LdmlJsonTestSourceFactory::load(const km::kbp::path &compiled, const km::kbp
auto test_name = test["/name"_json_pointer].get<std::string>();
std::string test_path;
test_path.append(info_name).append("/tests/").append(tests_name).append("/").append(test_name);
std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl;
// std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl;
std::unique_ptr<LdmlJsonTestSource> subtest(new LdmlJsonTestSource(test_path, kmxplus.get()));
assert_or_return(subtest->load(test) == 0);

View file

@ -29,44 +29,43 @@
},
"actions": [
{
"keystroke": {
"key": "s"
}
"type": "keystroke",
"key": "s"
},
{
"check": {
"result": "abc\\u0022...s"
}
"type": "check",
"result": "abc\\u0022...s"
},
{
"keystroke": {
"key": "t"
}
"type": "keystroke",
"key": "t"
},
{
"check": {
"result": "abc\\u0022...st"
}
"type": "check",
"result": "abc\\u0022...st"
},
{
"keystroke": {
"key": "u"
}
"type": "keystroke",
"key": "u"
},
{
"check": {
"result": "abc\\u0022...stu"
}
"type": "check",
"result": "abc\\u0022...stu"
},
{
"emit": {
"to": "v"
}
"type": "emit",
"to": "v"
},
{
"check": {
"result": "abc\\u0022...stuv"
}
"type": "check",
"result": "abc\\u0022...stuv"
},
{
"type": "backspace"
},
{
"type": "check",
"result": "abc\\u0022...stu"
}
]
}

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!-- Also see the other test-fr.xml in common -->
<info keyboard="fr-t-k0-azerty.xml" author="Team Keyboard" name="fr-test" />
<repertoire name="simple-repertoire" chars="[a b c d e \u{22}]" type="simple" /> <!-- verify that these outputs are all available from simple keys on any layer, for all form factors -->
<repertoire name="chars-repertoire" chars="[á é ó]" type="gesture" /> <!-- verify that these outputs are all available from simple or gesture keys on any layer, for touch -->
@ -17,6 +18,8 @@
<!-- tests by specifying 'to' output char -->
<emit to="v"/>
<check result="abc\u0022...stuv" />
<backspace />
<check result="abc\u0022...stu" />
</test>
</tests>
</keyboardTest3>

View file

@ -37,7 +37,7 @@ export const compilerTestCallbacks = new TestCompilerCallbacks();
export const compilerTestOptions: LdmlCompilerOptions = {
readerOptions: {
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
}
};

View file

@ -9,6 +9,7 @@ describe('testdata-tests', function() {
it('should-build-testdata-fixtures', async function() {
// Let's build test-fr.json
// It should match test-fr.json (built from test-fr.xml)
// Note! There's another test case against similar data, in common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts
const inputFilename = makePathToFixture('test-fr.xml');
const jsonFilename = makePathToFixture('test-fr.json');
@ -19,6 +20,6 @@ describe('testdata-tests', function() {
const jsonData = JSON.parse(readFileSync(jsonFilename, 'utf-8'));
assert.deepEqual(testData, jsonData);
assert.deepEqual(testData, jsonData, 'parsed +test-fr.xml should match -test-fr.json');
});
});

View file

@ -48,7 +48,7 @@ async function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: Compi
...defaultCompilerOptions,
...options,
readerOptions: {
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
}
};

View file

@ -14,7 +14,7 @@ export function buildTestData(infile: string, _options: any, commander: any) {
saveDebug: false,
shouldAddCompilerVersion: false,
readerOptions: {
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
}
};

158
docs/build/macos.md vendored
View file

@ -1,6 +1,8 @@
# Setup your Keyman build environment on macOS
This document describes prerequisites and tools required for building various Keyman projects on macOS. Each project will also have additional notes linked below.
This document describes prerequisites and tools required for building various
Keyman projects on macOS. Each project will also have additional notes linked
below.
## Target Projects
@ -10,6 +12,7 @@ On macOS, you can build the following projects:
* Keyman for iOS ([additional details](../../ios/README.md))
* Keyman for macOS ([additional details](../../mac/README.md))
* KeymanWeb ([additional details](../../web/README.md))
* Keyman Developer Command Line Tools (kmc)
The following libraries can also be built:
@ -20,19 +23,24 @@ The following projects **cannot** be built on macOS:
* Keyman for Linux
* Keyman for Windows
* Keyman Developer
* Keyman Developer (IDE component)
## System Requirements
* Minimum macOS version: macOS Catalina 10.15 or Big Sur 11.0
**Note:** to make a fully M1-compatible release build of Keyman for macOS (for the setup Applescript), Big Sur 11.0 is required, as osacompile on earlier versions does not build for arm64 (M1). The build will still work on earlier versions, but the installer won't be able to run on M1 Macs that do not have Rosetta 2 installed.
**Note:** to make a fully M1-compatible release build of Keyman for macOS (for
the setup Applescript), Big Sur 11.0 is required, as osacompile on earlier
versions does not build for arm64 (M1). The build will still work on earlier
versions, but the installer won't be able to run on M1 Macs that do not have
Rosetta 2 installed.
## Prerequisites
Many dependencies are only required for specific projects.
* XCode (iOS, macOS) 12.4 or later
* XCode (iOS, macOS) 12.4 or later is needed only for Keyman for macOS and Keyman
for iOS.
* Install from App Store
* Accept the Xcode license: `sudo xcodebuild -license accept`
@ -48,73 +56,83 @@ These dependencies are also listed below if you'd prefer to install manually.
## Shared Dependencies
* Shared: HomeBrew, Bash 5.0+, jq, Python 2.7, Python 3, Meson, Ninja, coreutils, Pandoc
* HomeBrew, Bash 5.0+, jq, Python 2.7, Python 3, Meson, Ninja, coreutils, Pandoc
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install bash jq python3 meson ninja coreutils pandoc pyenv
# Python 2.7 required for DeviceKit (among others?) at present
pyenv install 2.7.18
pyenv global 2.7.18
echo 'eval "$(pyenv init --path)"' >> ~/.bash_profile
```
On macOS, you will need to adjust your PATH so that coreutils’ `realpath` takes
precedence over the BSD one:
```shell
# Credit: brew info coreutils
PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
```
## KeymanWeb Dependencies
* node.js 18+, emscripten, openjdk 8
```shell
brew install node emscripten openjdk@8
```
Note: if you install emscripten with brew on macOS, only emscripten binaries are
added to the path via symlinks. This makes it reasonably safe to have emscripten
on the path, unlike on other platforms where emscripten also ends up adding its
versions of node, python, and other binaries to the path.
## Keyman for iOS Dependencies
* XCode, swiftlint, carthage
```shell
brew install swiftlint carthage
```
## Keyman for Mac Dependencies
* XCode, carthage, cocoapods
```shell
brew install carthage cocoapods
```
## Keyman for Android Dependencies
* openjdk 11, Android SDK, Android Studio, Ant, Gradle, Maven
```shell
brew install openjdk@11 android-sdk android-studio ant gradle maven
# update path
source ../resources/devbox/macos/keyman.macos.env.sh
# optionally install sdk images
sdkmanager "system-images;android-30;google_apis;armeabi-v7a"
sdkmanager --update
sdkmanager --licenses
```
* Note: Run Android Studio once after installation to install additional
components such as emulator images and SDK updates.
## Keyman Developer Command Line (kmc)
* node.js, emscripten
```shell
brew install node emscripten
```
## Optional Tools
* sentry-cli: Uploading symbols for Sentry-based error reporting
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install bash jq python3 meson ninja coreutils pandoc pyenv
# Python 2.7 required for DeviceKit (among others?) at present
pyenv install 2.7.18
pyenv global 2.7.18
echo 'eval "$(pyenv init --path)"' >> ~/.bash_profile
```
On macOS, you will need to adjust your PATH so that coreutils’ `realpath` takes precedence over the BSD one:
```shell
# Credit: brew info coreutils
PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
```
* Web: node.js 18+, emscripten, openjdk 8
```shell
brew install node emscripten openjdk@8
```
Note: if you install emscripten with brew on macOS, only emscripten binaries
are added to the path via symlinks. This makes it reasonably safe to have
emscripten on the path, unlike on other platforms where emscripten also ends
up adding its versions of node, python, and other binaries to the path.
* iOS: swiftlint, carthage
```shell
brew install swiftlint carthage
```
* macOS: carthage, cocoapods
```shell
brew install carthage cocoapods
```
* Android: openjdk 8, Android SDK, Android Studio, Ant, Gradle, Maven
```shell
brew install openjdk@8 android-sdk android-studio ant gradle maven
# update path
source ../resources/devbox/macos/keyman.macos.env.sh
# optionally install sdk images
sdkmanager "system-images;android-30;google_apis;armeabi-v7a"
sdkmanager --update
sdkmanager --licenses
```
* kmc (optional): node
- Required to build keyboards using kmc
```bash
brew tap homebrew/cask-versions
brew install --cask --no-quarantine wine-stable
```
* sentry-cli (optional)
- Uploading symbols for Sentry-based error reporting
```
brew install getsentry/tools/sentry-cli
```
* Run Android Studio once after installation to install additional components
such as emulator images and SDK updates.

View file

@ -102,6 +102,9 @@ sudo apt install -y lcov libdatetime-perl gcovr
pip3 install coverage
```
**Note:** You want lcov > 1.16, so you might have to download and install
a newer version e.g. from <https://packages.ubuntu.com/mantic/lcov>.
#### Creating and displaying code coverage reports
All three projects (ibus-keyman, keyman-config, and keyman-system-service)

View file

@ -46,7 +46,7 @@ execute_with_temp_schema() {
TEMP_DATA_DIR=$(mktemp -d)
SCHEMA_DIR="${TEMP_DATA_DIR}/glib-2.0/schemas"
export XDG_DATA_DIRS="${TEMP_DATA_DIR}":${XDG_DATA_DIRS-}
export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}"
export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}:/usr/share/glib-2.0/schemas/:${GSETTINGS_SCHEMA_DIR-}"
mkdir -p "${SCHEMA_DIR}"
cp resources/com.keyman.gschema.xml "${SCHEMA_DIR}"/
glib-compile-schemas "${SCHEMA_DIR}"

View file

@ -16,10 +16,13 @@ if [ -n "$TEAMCITY_VERSION" ]; then
if ! pip3 list --format=columns | grep -q teamcity-messages; then
pip3 install teamcity-messages
fi
python3 -m teamcity.unittestpy discover -s tests -p test_*.py
test_module=teamcity.unittestpy
else
# shellcheck disable=SC2086
python3 ${coverage:-} -m unittest discover -v -s tests -p test_*.py
test_module=unittest
extra_opts=-v
fi
# shellcheck disable=SC2086
python3 ${coverage:-} -m ${test_module:-} discover ${extra_opts:-} -s tests -p test_*.py
rm -rf "$XDG_CONFIG_HOME"

View file

@ -57,14 +57,21 @@ fi
if builder_start_action verify; then
tar xf "${SRC_PKG}"
if [ ! -f debian/libkmnkbp0-0.symbols ]; then
echo ":warning: Missing libkmnkbp0-0.symbols file" >&2
if [ ! -f debian/libkmnkbp0-0.symbols ] && [ ! -f debian/libkeymancore.symbols ]; then
echo ":warning: Missing libkmnkbp0-0.symbols/libkeymancore.symbols file" >&2
else
if [ -f debian/libeymancore.symbols ]; then
PKG_NAME=libkeymancore
LIB_NAME=libkeymancore
else
PKG_NAME=libkmnkbp0-0
LIB_NAME=libkmnkbp0
fi
tmpDir=$(mktemp -d)
dpkg -x "${BIN_PKG}" "$tmpDir"
cd debian
dpkg-gensymbols -v"${PKG_VERSION}" -plibkmnkbp0-0 -e"${tmpDir}"/usr/lib/x86_64-linux-gnu/libkeymancore.so* -Olibkmnkbp0-0.symbols -c4
echo ":heavy_check_mark: libkmnkbp0-0 API didn't change" >&2
dpkg-gensymbols -v"${PKG_VERSION}" -p${PKG_NAME} -e"${tmpDir}"/usr/lib/x86_64-linux-gnu/${LIB_NAME}.so* -O${PKG_NAME}.symbols -c4
echo ":heavy_check_mark: ${LIB_NAME} API didn't change" >&2
fi
builder_finish_action success verify
exit 0

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -19,19 +19,16 @@
</customObject>
<menu id="Uod-T8-nBO">
<items>
<menuItem title="Keyboards" tag="1" id="bQa-j9-nHe">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Keyboards" id="goP-aK-3WB"/>
</menuItem>
<menuItem title="Configuration..." tag="2" id="P1b-lE-yFw"/>
<menuItem title="On-Screen Keyboard" tag="3" id="s96-JI-YDh">
<menuItem isSeparatorItem="YES" tag="-4" id="uqs-C7-aK3"/>
<menuItem title="Configuration..." tag="-3" id="P1b-lE-yFw"/>
<menuItem title="On-Screen Keyboard" tag="-2" id="s96-JI-YDh">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="About" tag="4" id="kb2-ww-RS3">
<menuItem title="About..." tag="-1" id="kb2-ww-RS3" userLabel="About">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
<point key="canvasLocation" x="57" y="152"/>
<point key="canvasLocation" x="56.5" y="151.5"/>
</menu>
</objects>
</document>

View file

@ -364,6 +364,8 @@
[self saveActiveKeyboards];
}
else if (checkBox.state == NSOffState) {
if ([self.AppDelegate debugMode])
NSLog(@"Disabling active keyboard: %@", kmxFilePath);
[self.activeKeyboards removeObject:kmxFilePath];
[self saveActiveKeyboards];
}

View file

@ -183,65 +183,40 @@ NSMutableArray *servers;
return self.AppDelegate.kmx;
}
- (void)menuAction:(id)sender {
NSMenuItem *mItem = [sender objectForKey:kIMKCommandMenuItemName];
NSInteger itag = mItem.tag;
if ([self.AppDelegate debugMode])
NSLog(@"Keyman menu clicked - tag: %lu", itag);
if (itag == 2) {
// Using `showConfigurationWindow` instead of `showPreferences:` because `showPreferences:` is missing in
// High Sierra (10.13.1 - 10.13.3). See: https://bugreport.apple.com/web/?problemID=35422518
// rrb: where Apple's API is broken (10.13.1-10.13.3) call our workaround, otherwise, call showPreferences
u_int16_t systemVersion = [KMOSVersion SystemVersion];
if ([KMOSVersion Version_10_13_1] <= systemVersion && systemVersion <= [KMOSVersion Version_10_13_3]) // between 10.13.1 and 10.13.3 inclusive
{
NSLog(@"Input Menu: calling workaround instead of showPreferences (sys ver %x)", systemVersion);
[self.AppDelegate showConfigurationWindow]; // call our workaround
}
else
{
NSLog(@"Input Menu: calling Apple's showPreferences (sys ver %x)", systemVersion);
[self showPreferences:sender]; // call Apple API
}
if (itag == CONFIG_MENUITEM_TAG) {
[self showConfigurationWindow:sender];
}
else if (itag == 3) {
else if (itag == OSK_MENUITEM_TAG) {
[self.AppDelegate showOSK];
}
else if (itag == 4) {
else if (itag == ABOUT_MENUITEM_TAG) {
[self.AppDelegate showAboutWindow];
}
else if (itag >= 1000) {
NSMenuItem *keyboards = [self.AppDelegate.menu itemWithTag:1];
for (NSMenuItem *item in keyboards.submenu.itemArray) {
if (item.tag == itag)
[item setState:NSOnState];
else
[item setState:NSOffState];
}
NSString *path = [self.AppDelegate.activeKeyboards objectAtIndex:itag%1000];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path];
[self.AppDelegate setKmx:kmx];
KVKFile *kvk = nil;
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
NSString *kvkFilePath = [self.AppDelegate kvkFilePathFromFilename:kvkFilename];
if (kvkFilePath != nil)
kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath];
}
[self.AppDelegate setKvk:kvk];
NSString *keyboardName = [kmxInfo objectForKey:kKMKeyboardNameKey];
if ([self.AppDelegate debugMode])
NSLog(@"Selected keyboard from menu: %@", keyboardName);
[self.AppDelegate setKeyboardName:keyboardName];
[self.AppDelegate setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
[self.AppDelegate setContextBuffer:nil];
[self.AppDelegate setSelectedKeyboard:path];
[self.AppDelegate loadSavedStores];
if (kvk != nil && self.AppDelegate.alwaysShowOSK)
[self.AppDelegate showOSK];
else if (itag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) {
[self.AppDelegate selectKeyboardFromMenu:itag];
}
}
- (void)showConfigurationWindow:(id)sender {
// Using `showConfigurationWindow` instead of `showPreferences:` because `showPreferences:` is missing in
// High Sierra (10.13.1 - 10.13.3). See: https://bugreport.apple.com/web/?problemID=35422518
// rrb: where Apple's API is broken (10.13.1-10.13.3) call our workaround, otherwise, call showPreferences
u_int16_t systemVersion = [KMOSVersion SystemVersion];
if ([KMOSVersion Version_10_13_1] <= systemVersion && systemVersion <= [KMOSVersion Version_10_13_3]) // between 10.13.1 and 10.13.3 inclusive
{
NSLog(@"Input Menu: calling workaround instead of showPreferences (sys ver %x)", systemVersion);
[self.AppDelegate showConfigurationWindow]; // call our workaround
}
else
{
NSLog(@"Input Menu: calling Apple's showPreferences (sys ver %x)", systemVersion);
[self showPreferences:sender]; // call Apple API
}
}
@end

View file

@ -38,6 +38,21 @@ typedef struct {
NSString *apiKeymanCom;
} KeymanVersionInfo;
// tags for default menu items, displayed whether keyboards are active or not
static const int DIVIDER_MENUITEM_TAG = -4;
static const int CONFIG_MENUITEM_TAG = -3;
static const int OSK_MENUITEM_TAG = -2;
static const int ABOUT_MENUITEM_TAG = -1;
// the number of menu items that do not represent active keyboards
static const int DEFAULT_KEYMAN_MENU_ITEM_COUNT = 4;
// the tag for the first keyboard dynamically added to the menu
static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG = 0;
// the menu index for the first keyboard dynamically added to the menu
static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0;
@interface KMInputMethodAppDelegate : NSObject
#define USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE 1
#ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE
@ -55,6 +70,7 @@ typedef struct {
@property (nonatomic, strong) NSMutableArray *kmxFileList;
@property (nonatomic, strong) NSString *selectedKeyboard;
@property (nonatomic, strong) NSMutableArray *activeKeyboards;
@property (assign) int numberOfKeyboardMenuItems;
@property (nonatomic, strong) NSMutableString *contextBuffer;
@property (nonatomic, assign) NSEventModifierFlags currentModifierFlags;
@property (nonatomic, assign) CFMachPortRef lowLevelEventTap;
@ -88,6 +104,7 @@ typedef struct {
- (void)showAboutWindow;
- (void)showOSK;
- (void)showConfigurationWindow;
- (void)selectKeyboardFromMenu:(NSInteger)tag;
- (void)sleepFollowingDeactivationOfServer:(id)lastServer;
- (void)wakeUpWith:(id)newServer;
- (void)handleKeyEvent:(NSEvent *)event;

View file

@ -700,14 +700,14 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
[userData setObject:_activeKeyboards forKey:kKMActiveKeyboardsKey];
[userData synchronize];
[self resetActiveKeyboards];
[self setKeyboardsSubMenu];
[self updateKeyboardMenuItems];
}
- (void)clearActiveKeyboards {
NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
[userData setObject:nil forKey:kKMActiveKeyboardsKey];
[userData synchronize];
[self setKeyboardsSubMenu];
[self updateKeyboardMenuItems];
}
- (void)resetActiveKeyboards {
@ -760,90 +760,174 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef
}
- (void)awakeFromNib {
[self setKeyboardsSubMenu];
NSMenuItem *config = [self.menu itemWithTag:2];
if (config)
[config setAction:@selector(menuAction:)];
NSMenuItem *osk = [self.menu itemWithTag:3];
if (osk)
[osk setAction:@selector(menuAction:)];
NSMenuItem *about = [self.menu itemWithTag:4];
if (about)
[about setAction:@selector(menuAction:)];
[self setDefaultKeymanMenuItems];
[self updateKeyboardMenuItems];
}
- (void)setKeyboardsSubMenu {
NSMenuItem *keyboards = [self.menu itemWithTag:1];
if (keyboards) {
KVKFile *kvk = nil;
BOOL didSetKeyboard = NO;
NSInteger itag = 1000;
[keyboards.submenu removeAllItems];
for (NSString *path in self.activeKeyboards) {
NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:path];
if (!infoDict)
continue;
//NSString *str = [NSString stringWithFormat:@"%@ (%@)", [infoDict objectForKey:kKMKeyboardNameKey], [infoDict objectForKey:kKMKeyboardVersionKey]];
NSString *str = [infoDict objectForKey:kKMKeyboardNameKey];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:str action:@selector(menuAction:) keyEquivalent:@""];
[item setTag:itag++];
if ([path isEqualToString:self.selectedKeyboard]) {
[item setState:NSOnState];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path];
[self setKmx:kmx];
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename];
if (kvkFilePath != nil)
kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath];
}
[self setKvk:kvk];
[self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]];
[self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
[self loadSavedStores];
- (void)setDefaultKeymanMenuItems {
NSMenuItem *config = [self.menu itemWithTag:CONFIG_MENUITEM_TAG];
if (config) {
[config setAction:@selector(menuAction:)];
}
NSMenuItem *osk = [self.menu itemWithTag:OSK_MENUITEM_TAG];
if (osk) {
[osk setAction:@selector(menuAction:)];
}
NSMenuItem *about = [self.menu itemWithTag:ABOUT_MENUITEM_TAG];
if (about) {
[about setAction:@selector(menuAction:)];
}
}
didSetKeyboard = YES;
}
else
[item setState:NSOffState];
- (void)updateKeyboardMenuItems {
self.numberOfKeyboardMenuItems = [self calculateNumberOfKeyboardMenuItems];
[self removeDynamicKeyboardMenuItems];
[self addDynamicKeyboardMenuItems];
}
[keyboards.submenu addItem:item];
- (int)calculateNumberOfKeyboardMenuItems {
if (self.activeKeyboards.count == 0) {
// if there are no active keyboards, then we will insert one placeholder menu item 'No Active Keyboards'
return 1;
} else {
return (int) self.activeKeyboards.count;
}
}
- (void)removeDynamicKeyboardMenuItems {
int numberToRemove = (int) self.menu.numberOfItems - DEFAULT_KEYMAN_MENU_ITEM_COUNT;
if (self.debugMode) {
NSLog(@"*** removeDynamicKeyboardMenuItems, self.menu.numberOfItems = %ld, number of items to remove = %d", (long)self.menu.numberOfItems, numberToRemove);
}
if (numberToRemove > 0) {
for (int i = 0; i < numberToRemove; i++) {
[self.menu removeItemAtIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX];
}
}
}
- (void)addDynamicKeyboardMenuItems {
BOOL didSetSelectedKeyboard = NO;
NSInteger itag = KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG;
NSString *keyboardMenuName = @"";
int menuItemIndex = KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX;
if (self.debugMode) {
NSLog(@"*** populateKeyboardMenuItems, number of active keyboards=%lu", self.activeKeyboards.count);
}
// loop through the active keyboards list and add them to the menu
for (NSString *path in self.activeKeyboards) {
NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:path];
if (!infoDict) {
continue;
}
if (keyboards.submenu.numberOfItems == 0) {
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"(None)" action:NULL keyEquivalent:@""];
[keyboards.submenu addItem:item];
[self setKmx:nil];
[self setKvk:nil];
[self setKeyboardName:nil];
[self setKeyboardIcon:nil];
[self setContextBuffer:nil];
[self setSelectedKeyboard:nil];
keyboardMenuName = [infoDict objectForKey:kKMKeyboardNameKey];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:keyboardMenuName action:@selector(menuAction:) keyEquivalent:@""];
[item setTag:itag++];
// if this is the selected keyboard, then configure it as selected
if ([path isEqualToString:self.selectedKeyboard]) {
[self setSelectedKeyboard:path inMenuItem:item];
didSetSelectedKeyboard = YES;
}
else if (!didSetKeyboard) {
[keyboards.submenu itemAtIndex:0].state = NSOnState;
NSString *path = [self.activeKeyboards objectAtIndex:0];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path];
[self setKmx:kmx];
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename];
if (kvkFilePath != nil)
kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath];
}
[self setKvk:kvk];
[self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]];
[self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
[self setContextBuffer:nil];
[self loadSavedStores];
[self setSelectedKeyboard:path];
else {
[item setState:NSOffState];
}
[self.menu insertItem:item atIndex:menuItemIndex++];
}
if (self.activeKeyboards.count == 0) {
[self addKeyboardPlaceholderMenuItem];
} else if (!didSetSelectedKeyboard) {
[self setDefaultSelectedKeyboard];
}
}
- (void) setSelectedKeyboard:(NSString*)keyboardName inMenuItem:(NSMenuItem*) menuItem {
KVKFile *kvk = nil;
[menuItem setState:NSOnState];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:keyboardName];
[self setKmx:kmx];
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:keyboardName];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename];
if (kvkFilePath != nil) {
kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath];
}
}
[self setKvk:kvk];
[self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]];
[self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
[self loadSavedStores];
}
// defaults to the whatever keyboard happens to be first in the list
- (void) setDefaultSelectedKeyboard {
NSMenuItem* menuItem = [self.menu itemAtIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX];
NSString *keyboardName = [self.activeKeyboards objectAtIndex:0];
[self setSelectedKeyboard:keyboardName inMenuItem:menuItem];
[self setSelectedKeyboard:keyboardName];
[self setContextBuffer:nil];
}
- (void) addKeyboardPlaceholderMenuItem {
NSString* placeholder = NSLocalizedString(@"no-keyboard-configured-menu-placeholder", nil);
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:placeholder action:NULL keyEquivalent:@""];
[self.menu insertItem:item atIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX];
[self setKmx:nil];
[self setKvk:nil];
[self setKeyboardName:nil];
[self setKeyboardIcon:nil];
[self setContextBuffer:nil];
[self setSelectedKeyboard:nil];
}
- (void)selectKeyboardFromMenu:(NSInteger)tag {
NSMenuItem *menuItem = [self.menu itemWithTag:tag];
NSString *title = menuItem.title;
NSLog(@"Input Menu, selected Keyboards menu, itag: %lu, title: %@", tag, title);
for (NSMenuItem *item in self.menu.itemArray) {
// set the state of the keyboard items in the Keyman menu based on the new selection
if (item.tag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) {
if (item.tag == tag) {
[item setState:NSOnState];
}
else {
[item setState:NSOffState];
}
}
}
NSString *path = [self.activeKeyboards objectAtIndex:tag-KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG];
KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path];
[self setKmx:kmx];
KVKFile *kvk = nil;
NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path];
NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey];
if (kvkFilename != nil) {
NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename];
if (kvkFilePath != nil)
kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath];
}
[self setKvk:kvk];
NSString *keyboardName = [kmxInfo objectForKey:kKMKeyboardNameKey];
if ([self debugMode])
NSLog(@"Selected keyboard from menu: %@", keyboardName);
[self setKeyboardName:keyboardName];
[self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]];
[self setContextBuffer:nil];
[self setSelectedKeyboard:path];
[self loadSavedStores];
if (kvk != nil && self.alwaysShowOSK)
[self showOSK];
}
- (NSArray *)KMXFiles {

View file

@ -71,4 +71,6 @@
/* message displayed to alert user to need grant accessibility permission */
"privacy-alert-text" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Preferences, Security & Privacy.\nRestart your system.";
/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */
"no-keyboard-configured-menu-placeholder" = "(No Keyboard Configured)";

View file

@ -184,7 +184,17 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
public deactivateCurrentTarget() {
const priorTarget = this.activeTarget || this.lastActiveTarget;
if(priorTarget) {
/* During integrated tests, it was possible in the past for a `beforeAll`
* -initialized KMW to reach this state between tests. The target fixture
* got cleared, but the `mostRecentTarget` / `lastActiveTarget` was not
* - just the `currentTarget` / `activeTarget`. See #9718.
*
* Newly-added code in `forgetActiveTarget` seeks to prevent this scenario,
* but as there's no consistent repro to prove it sufficient, an appropriate
* guard-condition has been added here too.
*/
if(priorTarget && this.page.isAttached(priorTarget.getElement())) {
this._BlurKeyboardSettings(priorTarget.getElement());
}
@ -198,12 +208,19 @@ export default class ContextManager extends ContextManagerBase<BrowserConfigurat
this.focusAssistant.maintainingFocus = false;
this.focusAssistant.restoringFocus = false;
const priorTarget = this.activeTarget || this.lastActiveTarget;
const priorTarget = this.activeTarget || this.mostRecentTarget;
if(priorTarget) {
this._BlurKeyboardSettings(priorTarget.getElement());
}
// Will ensure that the element is no longer active. Does not erase
// it from being the `lastActiveTarget`, though.
this.setActiveTarget(null, true);
// So we erase it here.
if(priorTarget == this.lastActiveTarget) {
this.mostRecentTarget = null;
}
}
public setActiveTarget(target: OutputTarget<any>, sendEvents: boolean) {

View file

@ -4,4 +4,3 @@ title: Advanced Help
* [proxy_config](proxy_config)
* [locale_edit](locale_edit)
* [text_services_framework](text_services_framework)

View file

@ -1,28 +0,0 @@
---
title: Text Services Framework
---
## What is the Text Services Framework?
The Text Services Framework, or TSF, is a set of components for modern
applications that support advanced input and text processing. It
supports features such as keyboard drivers, handwriting recognition,
speech recognition, as well as spell checking and other text processing
functions.
With Keyman Desktop 9 and later versions, all keyboards are registered
through the Windows interfaces, and the key advantage is that Keyman now
automatically detects applications that have support for TSF and
upgrades the experience for those applications.
For those applications that support TSF, the most important advantage is
that Keyman can read the current 'context' from the application. The
'context' is the characters on the screen around the insertion point.
Earlier versions of Keyman remembered the context while inputting text,
but as soon as an arrow key was pressed, or the mouse clicked, Keyman
would forget the context. This means that existing text can be edited in
a much more intuitive manner when using TSF.
## What applications support TSF?
TSF is supported by a wide range of software and Windows components.

View file

@ -37,3 +37,16 @@ Here's how to make the change with Windows Settings:
5. The Keyman icon will now always appear in the Windows
Taskbar near the clock, if Keyman is on.
6. Continue on to [Step 5](../start/tutorial#step-5-) of this guide.
- On Windows 11
1. Right-click on the Windows Taskbar.
![](../desktop_images/win11-taskbar1.png)
2. Select 'Taskbar settings'.
3. Under 'Other system tray icons', expand the list,
click the toggle button beside 'Keyman Engine x86'to turn it on.
![](../desktop_images/win11-taskbar2.png)
4. The Keyman icon will now always appear in the Windows
Taskbar near the clock, if Keyman is on.
5. Continue on to [Step 5](../start/tutorial#step-5-) of this guide.

View file

@ -15,3 +15,18 @@ title: Software Task - Uninstall Keyman
5. Click Uninstall.
6. Follow the prompts to complete the uninstall.
## Uninstall Keyman from Windows 11
1. Exit Keyman.
2. Open Windows Start Menu and type "Add or Remove Programs", and open that app.
3. In the Apps > Installed apps search box, type "Keyman".
4. Click the three horizontal dots to the right of Keyman in the list.
![](../desktop_images/win11-uninstall.png)
5. Click Uninstall.
6. Follow the prompts to complete the uninstall.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -33,7 +33,6 @@ frequently asked questions, tutorials, and videos.
### [Advanced Topics](advanced/)
* [Character Map](basic/toolbox/character-map)
* [Hotkeys](start/hotkey_set)
* [Text Services Framework Addin](advanced/text_services_framework)
* [Use Word macros to switch keyboard and font](https://help.keyman.com/kb/93)
* [Application Programming Interface (API)](https://help.keyman.com/developer/engine/desktop/current-version/api/)
* [Keyman for Windows version history](https://help.keyman.com/products/windows/version-history)

View file

@ -17,7 +17,7 @@ After you download Keyman, to install the program:
2. Double-click the file to begin installation.
3. You may be warned to use a "Microsoft-verfied" App from the Microsoft Store. This is because Keyman is not available in the Microsoft Store. If "**Install Anyway**" is an option, select that. Otherwise follow the instructions found here [How To - Allow Windows 10 to Install Apps From Anywhere](../troubleshooting/install-app-from-anywhere)
3. You may be warned to use a "Microsoft-verfied" App from the Microsoft Store. This is because Keyman is not available in the Microsoft Store. If "**Install Anyway**" is an option, select that. Otherwise follow the instructions found here [How To - Allow Windows to Install Apps From Anywhere](../troubleshooting/install-app-from-anywhere)
![](../desktop_images/km_non_app_store_2.png)
4. You will be asked to allow the installer to access your system.

View file

@ -10,4 +10,4 @@ title: Troubleshooting
* [How To - Resolve Security Software Conflicts with keyman32.dll](securitysoftware)
* [How To - Use the Keyman Setup Bootstrapper](bootstrapper)
* [How To - Allow Windows 10 to Install Apps From Anywhere](install-app-from-anywhere)
* [How To - Allow Windows to Install Apps From Anywhere](install-app-from-anywhere)

View file

@ -1,5 +1,5 @@
---
title: How To - Allow Windows 10 to Install Apps From Anywhere
title: How To - Allow Windows to Install Apps From Anywhere
---
## Allow Windows 10 to Install Apps From Anywhere
@ -20,6 +20,22 @@ If "**Change my app recommendation settings**" is shown, select that and go to s
5. Now continue installing by double-clicking on the Keyman file identified in Step 1 of [How To - Download and Install a Keyman](../start/download-and-install-keyman).
## Allow Windows 11 to Install Apps From Anywhere
1. You may be warned to use a "Microsoft-verified" App from the Microsoft Store. This is because Keyman is not available in the Microsoft Store.
If "**Change my app recommendation settings**" is shown, select that and go to step 4.
![](../desktop_images/km_non_app_store_1.png)
2. If that is not available, open the Windows 11 start menu (Windows logo) and select **Settings**.
3. Click on the **Apps** in the **Windows Settings** menu
4. Click on **Advanced app settings** from the option **choose where to get apps**, click on the dropdown menu below and select "Anywhere, but warn me before installing an app that's not from the Microsoft Store" or one of the other options that allows you to install from *Anywhere*.
![](../desktop_images/win11_km_install_anywhere_1.png)
5. Now continue installing by double-clicking on the Keyman file identified in Step 1 of [How To - Download and Install a Keyman](../start/download-and-install-keyman).
## Related Topics
- [How To - Download and Install a Keyman](../start/download-and-install-keyman)