mirror of
https://github.com/keymanapp/keyman.git
synced 2026-11-03 09:37:47 +00:00
chore(linux): Merge remote-tracking branch 'origin/master' into chore/linux/9733_RenameLibknmkbp
This commit is contained in:
commit
fdc9baadb2
55 changed files with 1470 additions and 503 deletions
14
.github/workflows/deb-packaging.yml
vendored
14
.github/workflows/deb-packaging.yml
vendored
|
|
@ -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]
|
||||
|
|
|
|||
16
HISTORY.md
16
HISTORY.md
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.194
|
||||
17.0.196
|
||||
24
common/schemas/kmp/README.md
Normal file
24
common/schemas/kmp/README.md
Normal 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
|
||||
353
common/schemas/kmp/kmp.schema.json
Normal file
353
common/schemas/kmp/kmp.schema.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
8
common/web/types/test/fixtures/test-fr.xml
vendored
8
common/web/types/test/fixtures/test-fr.xml
vendored
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
},
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
15
core/tests/unit/ldml/keyboards/k_006_backspace-test.xml
Normal file
15
core/tests/unit/ldml/keyboards/k_006_backspace-test.xml
Normal 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>
|
||||
|
|
@ -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" />
|
||||
|
|
|
|||
136
core/tests/unit/ldml/keyboards/k_008_transform_norm-test.xml
Normal file
136
core/tests/unit/ldml/keyboards/k_008_transform_norm-test.xml
Normal 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>
|
||||
52
core/tests/unit/ldml/keyboards/k_008_transform_norm.xml
Normal file
52
core/tests/unit/ldml/keyboards/k_008_transform_norm.xml
Normal 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>
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const compilerTestCallbacks = new TestCompilerCallbacks();
|
|||
|
||||
export const compilerTestOptions: LdmlCompilerOptions = {
|
||||
readerOptions: {
|
||||
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
|
||||
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ async function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: Compi
|
|||
...defaultCompilerOptions,
|
||||
...options,
|
||||
readerOptions: {
|
||||
importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL)
|
||||
importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL))
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
158
docs/build/macos.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)";
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ title: Advanced Help
|
|||
|
||||
* [proxy_config](proxy_config)
|
||||
* [locale_edit](locale_edit)
|
||||
* [text_services_framework](text_services_framework)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
||||

|
||||
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.
|
||||

|
||||
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.
|
||||
|
|
@ -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.
|
||||

|
||||
|
||||
5. Click Uninstall.
|
||||
|
||||
6. Follow the prompts to complete the uninstall.
|
||||
|
|
|
|||
BIN
windows/src/desktop/help/desktop_images/win11-taskbar1.png
Normal file
BIN
windows/src/desktop/help/desktop_images/win11-taskbar1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
windows/src/desktop/help/desktop_images/win11-taskbar2.png
Normal file
BIN
windows/src/desktop/help/desktop_images/win11-taskbar2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
windows/src/desktop/help/desktop_images/win11-uninstall.png
Normal file
BIN
windows/src/desktop/help/desktop_images/win11-uninstall.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||

|
||||
|
||||
4. You will be asked to allow the installer to access your system.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||

|
||||
|
||||
|
||||
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*.
|
||||

|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue