Merge branch 'feat/developer/kmc-convert' into feat/developer/kmc-convert-validateKeylayout

# Conflicts:
#	developer/src/common/web/utils/src/xml-utils.ts
#	developer/src/kmc-convert/src/converter-messages.ts
#	developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts
#	developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts
#	developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts
This commit is contained in:
Sabine 2025-07-03 11:54:41 +02:00
commit cebbda981f
789 changed files with 13522 additions and 9415 deletions

View file

@ -5,9 +5,7 @@ module.exports = {
es2021: true,
node: true,
},
// TODO: we need to move to the following, but this gets us started with infrastructure without breaking our build
// extends: [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended"],
extends: ["plugin:@typescript-eslint/eslint-recommended"],
extends: [ "plugin:@typescript-eslint/eslint-recommended"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",

9
.gitattributes vendored Normal file
View file

@ -0,0 +1,9 @@
# Always use LF for Dockerfile - otherwise HEREDOCs in Dockerfile
# will not work on Windows - we end up with a filename `foo\r` for
# the command
# ```Dockerfile
# RUN <<EOF cat > foo
# echo "Hello world"
# EOF
# ```
Dockerfile text eol=lf

View file

@ -25,13 +25,13 @@ runs:
using: 'composite'
steps:
- name: Download Artifacts
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: keyman-srcpkg
path: artifacts/keyman-srcpkg
- name: Build
uses: sillsdev/gha-ubuntu-packaging@7b56f50d5d5537e9e9cafd3f6139ec6da69cfcda # v1.1
uses: sillsdev/gha-ubuntu-packaging@556b268762be9bea0f39861a7392587211ef6a58 # v2.0.1
with:
dist: "${{ inputs.dist }}"
platform: "${{ inputs.arch }}"
@ -49,7 +49,7 @@ runs:
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Store binary packages
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: keyman-binarypkgs-${{ inputs.dist }}_${{ inputs.arch }}
path: |

54
.github/labeler.yml vendored
View file

@ -11,51 +11,56 @@ docs: docs/**
# Add labels based on changed files using actions/labeler
#
android/: android/**
android/:
- android/**
- resources/teamcity/includes/**
- resources/teamcity/android/**
android/app/: android/KMAPro/**
android/engine/: android/KMEA/**
android/samples/: android/Samples/**
common/:
- common/**
- resources/**
common/models/templates/: web/src/engine/predictive-text/templates/**
common/models/wordbreakers/: web/src/engine/predictive-text/wordbreakers/**
common/resources/: resources/**
- resources/teamcity/includes/**
- resources/teamcity/common/**
common/web/: common/web/**
core/:
- core/**
- resources/teamcity/includes/**
- resources/teamcity/core/**
developer/:
- developer/**
- resources/teamcity/includes/**
- resources/teamcity/developer/**
developer/compilers/:
- developer/src/kmc/**
- developer/src/kmcmplib/**
- developer/src/kmc-*/**
developer/ide/:
- developer/src/server/**
- developer/src/tike/**
ios/: ios/**
ios/:
- ios/**
- resources/teamcity/includes/**
- resources/teamcity/ios/**
ios/app/: ios/keyman/**
ios/engine/: ios/engine/**
ios/samples/: ios/samples/**
linux/: linux/**
linux/:
- linux/**
- resources/teamcity/includes/**
- resources/teamcity/linux/**
linux/config/: linux/keyman-config/**
linux/engine/:
- linux/ibus-keyman/**
- linux/legacy/ibus-kmfl/**
- linux/legacy/kmflcomp/**
- linux/legacy/libkmfl/**
linux/engine/: linux/ibus-keyman/**
mac/: mac/**
mac/:
- mac/**
- resources/teamcity/includes/**
- resources/teamcity/mac/**
# mac/config/:
# mac/engine/: mac/**
@ -65,12 +70,21 @@ oem/fv/android/: oem/firstvoices/android/**
oem/fv/ios/: oem/firstvoices/ios/**
oem/fv/windows/: oem/firstvoices/windows/**
web/: web/**
resources/: resources/**
web/:
- web/**
- resources/teamcity/includes/**
- resources/teamcity/web/**
# web/bookmarklet/
web/engine/: web/source/**
web/ui/: web/source/kmwui*
web/samples/: web/samples/**
web/predictive-text/: web/src/engine/predictive-text/**
windows/: windows/**
windows/:
- windows/**
- resources/teamcity/includes/**
- resources/teamcity/windows/**
windows/config/: windows/src/desktop/**
windows/engine/: windows/src/engine/**

View file

@ -33,6 +33,9 @@ labels:
- label: 'fix'
matcher:
title: '^fix(\(|:)'
- label: 'maint'
matcher:
title: '^maint(\(|:)'
- label: 'refactor'
matcher:
title: '^refactor(\(|:)'
@ -57,11 +60,6 @@ labels:
matcher:
title: '(🍒|:cherries:)'
# long-lived feature branches
- label: 'feature-branch'
matcher:
branch: '^feature-.+'
#
# Scopes that we look for in the PR title
#
@ -90,6 +88,9 @@ labels:
- label: 'oem/'
matcher:
title: '\(.*oem.*\):'
- label: 'resources/'
matcher:
title: '\(.*resources.*\):'
- label: 'web/'
matcher:
title: '\(.*web.*\):'

View file

@ -15,7 +15,7 @@ jobs:
name: Verify API for libkeymancore*.so
runs-on: ubuntu-latest
outputs:
VERSION: ${{ steps.environment_step.outputs.VERSION }}
KEYMAN_VERSION: ${{ steps.environment_step.outputs.KEYMAN_VERSION }}
PRERELEASE_TAG: ${{ steps.environment_step.outputs.PRERELEASE_TAG }}
GIT_SHA: ${{ steps.environment_step.outputs.GIT_SHA }}
GIT_BASE: ${{ steps.environment_step.outputs.GIT_BASE }}
@ -26,7 +26,7 @@ jobs:
steps:
- name: Restore artifacts
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
artifacts
@ -53,7 +53,7 @@ jobs:
-f context="$STATUS_CONTEXT"
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
ref: '${{ steps.environment_step.outputs.GIT_SHA }}'
fetch-depth: 0
@ -69,7 +69,7 @@ jobs:
run: |
echo "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }}):" >> $GITHUB_STEP_SUMMARY
BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb")
BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.KEYMAN_VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb")
cd ${{ github.workspace }}/keyman/linux
./scripts/deb-packaging.sh \
--gha \
@ -79,7 +79,7 @@ jobs:
verify 2>> $GITHUB_STEP_SUMMARY
- name: Archive .symbols file
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: libkeymancore.symbols
path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols

View file

@ -13,10 +13,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: crowdin action
uses: crowdin/github-action@1.3.2
uses: crowdin/github-action@2.7.0
with:
upload_sources: true

View file

@ -37,11 +37,11 @@ jobs:
if: github.repository == 'keymanapp/keyman'
runs-on: ubuntu-24.04
outputs:
VERSION: ${{ steps.version_step.outputs.VERSION }}
KEYMAN_VERSION: ${{ steps.version_step.outputs.KEYMAN_VERSION }}
PRERELEASE_TAG: ${{ steps.prerelease_tag.outputs.PRERELEASE_TAG }}
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
ref: '${{ github.event.client_payload.buildSha }}'
@ -72,10 +72,10 @@ jobs:
- name: Build source package
id: build_source_package
run: |
export TIER=$(cat TIER.md)
export KEYMAN_TIER=$(cat TIER.md)
export GHA_TEST_BUILD="${{ github.event.client_payload.isTestBuild }}"
export GHA_BRANCH="${{ github.event.client_payload.branch }}"
echo "TIER=$TIER" >> $GITHUB_ENV
echo "KEYMAN_TIER=$KEYMAN_TIER" >> $GITHUB_ENV
echo "GHA_TEST_BUILD=${GHA_TEST_BUILD}" >> $GITHUB_ENV
echo "GHA_BRANCH=${GHA_BRANCH}" >> $GITHUB_ENV
cd linux
@ -87,7 +87,7 @@ jobs:
run: |
THIS_SCRIPT="$GITHUB_WORKSPACE/.github/workflows/deb-packaging.yml"
. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh"
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "KEYMAN_VERSION=${KEYMAN_VERSION:-${VERSION}}" >> $GITHUB_OUTPUT
- name: Set prerelease tag as output parameter
id: prerelease_tag
@ -103,16 +103,16 @@ jobs:
- name: Output which branch or PR we're building plus name of .dsc file
run: |
if [ "${{ github.event.client_payload.isTestBuild }}" == "true" ]; then
echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY
echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.KEYMAN_VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY
else
echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.VERSION }}**" >> $GITHUB_STEP_SUMMARY
echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.KEYMAN_VERSION }}**" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo ":gift: Generated source package:" >> $GITHUB_STEP_SUMMARY
echo "- $(find . -name keyman_\*.dsc)" >> $GITHUB_STEP_SUMMARY
- name: Store source package
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: keyman-srcpkg
path: |
@ -131,7 +131,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
ref: '${{ github.event.client_payload.buildSha }}'
sparse-checkout: '.github/actions/'
@ -140,7 +140,7 @@ jobs:
uses: ./.github/actions/build-binary-packages
with:
dist: ${{ matrix.dist }}
version: ${{ needs.sourcepackage.outputs.VERSION }}
version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}
prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}
deb_fullname: ${{env.DEBFULLNAME}}
deb_email: ${{env.DEBEMAIL}}
@ -157,7 +157,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
with:
ref: '${{ github.event.client_payload.buildSha }}'
sparse-checkout: '.github/actions/'
@ -166,7 +166,7 @@ jobs:
uses: ./.github/actions/build-binary-packages
with:
dist: ${{ matrix.dist }}
version: ${{ needs.sourcepackage.outputs.VERSION }}
version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}
prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}
deb_fullname: ${{env.DEBFULLNAME}}
deb_email: ${{env.DEBEMAIL}}
@ -178,7 +178,7 @@ jobs:
steps:
# - name: Download Artifacts
# uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
# uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
# with:
# path: artifacts
# merge-multiple: true
@ -198,7 +198,7 @@ jobs:
# autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img
- name: Ignore
run: |
echo "Ignored for now - until working solution is in place"
echo "Ignored for now - until working solution is in place (#13777)"
deb_signing:
name: Sign source and binary packages
@ -211,8 +211,8 @@ jobs:
- name: Sign packages
uses: sillsdev/gha-deb-signing@a38dbde6bc9afabede5e07609105acc83c760ad4 # v0.6
with:
src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1_source.changes"
bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+"
src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1_source.changes"
bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+"
artifacts-prefix: "keyman-"
artifacts-result-name: "keyman-signedpkgs"
gpg-signing-key: "${{ secrets.GPG_SIGNING_KEY }}"
@ -247,7 +247,7 @@ jobs:
echo "::endgroup::"
- name: Download Artifacts
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: keyman-signedpkgs
@ -293,7 +293,7 @@ jobs:
echo "::endgroup::"
echo -e "::group::${COLOR_GREEN}Uploading binary packages"
pattern="keyman_${{ needs.sourcepackage.outputs.VERSION }}.+\+(.*)[0-9]_[^.]+.changes"
pattern="keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}.+\+(.*)[0-9]_[^.]+.changes"
for f in *_amd64.changes; do
if [[ $f =~ $pattern ]]; then
dist=${BASH_REMATCH[1]}
@ -303,20 +303,21 @@ jobs:
echo "::endgroup::"
prepare_api_verification:
name: Verify API for libkeymancore.so
name: Prepare API verification
needs: [sourcepackage, binary_packages_released]
runs-on: ubuntu-latest
steps:
- name: Download Artifacts
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts
pattern: keyman-*
merge-multiple: true
- name: Save environment
run: |
echo "VERSION=${{ needs.sourcepackage.outputs.VERSION }}" > artifacts/env
echo "KEYMAN_VERSION=${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}" > artifacts/env
echo "PRERELEASE_TAG=${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}" >> artifacts/env
echo "GIT_SHA=${{ github.event.client_payload.buildSha }}" >> artifacts/env
echo "GIT_BASE=${{ github.event.client_payload.baseRef }}" >> artifacts/env
@ -326,7 +327,7 @@ jobs:
echo "GIT_USER=${{ github.event.client_payload.user }}" >> artifacts/env
- name: Cache artifacts
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
artifacts

View file

@ -384,10 +384,11 @@ group.
| `auto` | [search][search-auto] | For PRs only: automatically-opened PRs, e.g. opened by CI. |
| `bug` | [search][search-bug] | For issues only: confirmed bugs or reports that are very likely to be bugs. PRs use `fix` to mark a bug fix. |
| `change` | [search][search-change] | Minor change in functionality, but not new. |
| `chore` | [search][search-chore] | Cleanup work, maintenance, without change in functionality. |
| `chore` | [search][search-chore] | Cleanup work, without change in functionality. |
| `docs` | [search][search-docs] | Relating to any type of documentation. |
| `feat` | [search][search-feat] | Feature requests. |
| `fix` | [search][search-fix] | For PRs only: a bug fix, corresponds to issue label `bug`. |
| `maint` | [search][search-maint] | Maintenance work -- continuous integration, build scripts, infrastructure. |
| `question` | [search][search-question] | Questions more than bug reports or feature requests (e.g. how do I do X). [**Usually better on the Keyman Community**](https://community.software.sil.org/c/keyman) |
| `refactor` | [search][search-refactor] | Code reorganization and refactoring, without change in functionality. |
| `spec` | [search][search-spec] | Issues which are specifications for a large scale feature. |
@ -443,6 +444,7 @@ These labels also include sublabels, such as `windows/config/`.
[search-docs]: https://github.com/keymanapp/keyman/labels/docs
[search-feat]: https://github.com/keymanapp/keyman/labels/feat
[search-fix]: https://github.com/keymanapp/keyman/labels/fix
[search-maint]: https://github.com/keymanapp/keyman/labels/maint
[search-question]: https://github.com/keymanapp/keyman/labels/question
[search-spec]: https://github.com/keymanapp/keyman/labels/spec
[search-style]: https://github.com/keymanapp/keyman/labels/style

View file

@ -1,5 +1,310 @@
# Keyman Version History
## 19.0.63 alpha 2025-06-12
* maint(web): add some more arguments for web release build script (#14167)
* maint(developer): use double quotes so that variable gets replaced (#14168)
* chore(deps): bump brace-expansion from 2.0.1 to 2.0.2 in /developer/src/server/src/win32/trayicon/addon-src (#14165)
## 19.0.62 alpha 2025-06-11
* maint(developer): export environment variables (#14157)
* maint(web): use path when calling powershell script (#14156)
* maint(resources): fix regression on mac triggering builds (#14155)
## 19.0.61 alpha 2025-06-10
* chore(developer): extend unit test timeout for getLastGitCommitDate (#14147)
* fix(common): Use zip.inc.sh for common test keyboards (#14145)
* maint(developer): add docker image to build developer node modules (#14058)
## 19.0.60 alpha 2025-06-06
* maint(developer): allow to build locally on Linux (#14059)
## 19.0.59 alpha 2025-06-05
* chore(deps): bump multer from 2.0.0 to 2.0.1 (#14127)
* maint(web): fix working directory for zipping artifacts for Web release (#14128)
* chore(linux): Update debian changelog (#14119)
* maint(web): improve TC build script for Keyman Web/Test (#14051)
* maint(developer): move TC configuration for Keyman Developer to scripts (#14052)
## 19.0.58 alpha 2025-06-04
* feat(developer): update strs compiler to collect context (#14064)
* feat(developer): line numbers for UsetCompiler, others (#14090)
* feat(developer): remaining line numbers for tran/bksp compilers (#14097)
* feat(developer): ldml: update errors for unparseable reorder (#14098)
* chore(developer): ldml: messages cleanup (#14100)
* maint(android): Create "archive" builder step to prep release artifacts (#14093)
* maint(web): move TC configuration for Web to script (#14049)
## 19.0.57 alpha 2025-06-03
* fix(windows): change from error to breadcrumb and log (#14069)
* maint(mac): make make-km-dmg.sh less quiet (#14105)
## 19.0.56 alpha 2025-06-02
* maint(ios): Use zip.inc.sh to prep release artifacts (#13919)
* maint(linux): update to new version of gha-ubuntu-packaging (#14094)
* chore(linux): add Python TC test runner (#14062)
## 19.0.55 alpha 2025-05-31
* maint(linux): `sudo` not required for removing temporary files (#14086)
* maint(linux): update to new version of gha-ubuntu-packaging (#14089)
## 19.0.54 alpha 2025-05-30
* maint(linux): move TC build scripts to `resources/teamcity/linux` (#14061)
## 19.0.53 alpha 2025-05-29
* fix(developer): ensure that kmc doesn't skip validate phase of kmc-ldml (#14068)
## 19.0.52 alpha 2025-05-28
* feat(developer): line numbers for tran compiler (#14037)
* feat(developer): add line numbers to disp compiler (#14048)
* feat(developer): line numbers for layr compiler (#14050)
* feat(developer): line numbers for LocaCompiler (#14054)
* fix(common): properly use newly installed node version (#14057)
* maint(common): update build coordinator and labels for `resources/teamcity` changes (#14047)
## 19.0.51 alpha 2025-05-27
* maint: add stable-19.0 triggers (#14043)
* maint: add empty 'scripts' tag to package.json to silence warning (#14044)
* fix(linux): fix version variable references in kmconfig (#14032)
## 19.0.50 alpha 2025-05-26
* chore(web): mark async methods in UI components (#14034)
## 19.0.49 alpha 2025-05-25
* feat(developer): revamp line numbers per design doc (#14016)
* docs(developer): add compiler message internal documentation (#14025)
* feat(developer): add line numbers to vars compiler (#14036)
## 19.0.48 alpha 2025-05-23
* maint(web): upgrade Sentry to 8.55.0 (#14030)
## 19.0.47 alpha 2025-05-22
* fix(windows): only breadcrumb unexpected state on first run (#13984)
* maint: change builder_completion script to a .inc.sh (#14007)
* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#13989)
* fix(windows): fix sentry error message reporting (#14027)
## 19.0.46 alpha 2025-05-21
* docs(common): update README with number of languages and keyboards (#14023)
## 19.0.45 alpha 2025-05-21
* maint(linux): use correct syntax to define local variables in script (#14021)
* maint(developer): update multer to v2.0.0 (#14012)
## 19.0.44 alpha 2025-05-21
* feat(android): log active keyboard, model IDs with reported errors (#13983)
* fix(windows): rename Enter and Exit methods TState (#13920)
* maint: update macOS environment setup, related documentation (#13979)
* chore(deps): bump multer from 1.4.5-lts.1 to 2.0.0 (#13982)
* feat(ios): log active keyboard, model IDs with reported errors (#13987)
* maint(linux): add quilt to release build configuration (#13986)
* chore(ios): update FirstVoices AppIcon (#13976)
* docs(developer): add security note to Upload documentation (#14015)
* fix(android): Check network access before trying to download keyboard (#13978)
## 19.0.43 alpha 2025-05-20
* chore(ios): FirstVoices certificate update (#13991)
* fix(linux): configurable path in `.service` file when using basu (#13980)
## 19.0.42 alpha 2025-05-20
* maint(linux): add `--no-werror` option to not report warnings as errors (#13988)
## 19.0.41 alpha 2025-05-19
* fix(web): inspectable test-script mode should not be auto-enabled in local-env tests (#13895)
* maint(common): evaluate and apply npm audit fix (without forced changes) (#13897)
* maint: update mocha (#13899)
* maint: update @actions/github + @octokit references (#13900)
* maint(common): update fast-json-patch to ^3.1.1 (#13901)
* change(web): cancel flicks when both returning to and releasing at original tap location (#13683)
* fix(web): empty layout fontSize property should default to 1em (#13959)
* maint(linux): fix release build to run all tests except integration tests (#13968)
## 19.0.40 alpha 2025-05-17
* maint(common): Add zip.inc.sh for cross-platform zipping (#13812)
## 19.0.39 alpha 2025-05-16
* chore(mac,windows): Update Crowdin strings for Italian (#13871)
* refactor(web): rename generic type variables (#13930)
* maint(linux): properly fallback to `VERSION` in deb-packaging (#13961)
* maint(linux): update gha-ubuntu-packaging dependency (#13964)
* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13962)
* maint(linux): don't include `dockerbuild` log files (#13965)
* maint(linux): update GHA dependencies for Linux package builds (#13966)
## 19.0.38 alpha 2025-05-15
* chore(android): Update FirstVoices launcher icon and red theme (#13907)
* feat(developer): add more line number issues for KeysCompiler (#13915)
* feat(developer): add line number for MetaCompiler (#13916)
* maint(linux): address comments from Debian package review (#13904)
* maint(linux): add Questing to launchpad package builds (#13910)
* maint(linux): fix logging in `deb-packaging.sh` (#13912)
* maint(linux): remove temporary files created during dependency installation (#13913)
* maint(common): move TC build configs for Linux to scripts (#13754)
* fix(developer): remove ngrok binary at build time (#13847)
* chore(linux): Update debian changelog (#13889)
* refactor(web): rename `HardKeyboard` to `HardKeyboardBase` (#13929)
* refactor(web): rename base `KeymanEngine` to `KeymanEngineBase` (#13942)
## 19.0.37 alpha 2025-05-14
* maint(linux): remove generated file that got accidentally committed (#13902)
* maint(linux): remove check for patched ibus from `meson.build` (#13909)
* maint(common): report builder errors to stderr (#13882)
## 19.0.36 alpha 2025-05-13
* feat(developer): line numbers for XML parsing and kmc-ldml (#13375)
* maint(android): Update to Java 21 and Gradle dependencies (#13858)
* maint(linux): improve getting PR# in `upload-to-debian.sh` (#13890)
* fix(linux): fallback to `VERSION` if `KEYMAN_VERSION` isn't set during package build (#13891)
* maint(common): move and document `builder_is_ci_*` functions (#13892)
* maint(android): use new variable name `KEYMAN_TIER` (#13898)
* fix(mac): handle PackageInfo section in kmp.inf file (#13876)
## 19.0.35 alpha 2025-05-12
* maint(common): add two more functions to detect if running on CI (#13869)
* maint(common): use unique names for Keyman version variables (#13854)
* maint(common): use new `builder_is_running_on_*` functions (#13875)
* maint(common): remove obsolete code from docker-images `run.sh` (#13879)
* chore(mac): remove obsolete key processing code (#13848)
## 19.0.34 alpha 2025-05-08
* docs(common): update `builder_echo` documentation for TC blocks (#13855)
* fix(windows): add breadcrumb to download failures (#13826)
* fix(web): sustain active batched-layout mode when keyboard is switched (#13860)
* maint(common): remove obsolete code from docker-images `build.sh` (#13861)
* maint(common): output start/end messages if not running on TC (#13868)
## 19.0.33 alpha 2025-05-07
* maint(common): automatically set --debug option for local-environment builder-script uses (#13827)
* docs(web): add a Web engine keystroke lifecycle internal doc (#13759)
* maint(common): use function to detect if running on TC (#13844)
* docs(common): document new `builder_is_running_on_teamcity` function (#13852)
## 19.0.32 alpha 2025-05-06
* change(web): replace USVString with string in predictive-text engine (#13785)
* chore(web): replace --ci flag with builder_is_ci_build (#13819)
* maint(web): simplify worker-main test.sh (#13820)
* change(web): lint kmwstring web-utility (#13800)
* fix(web): fix extraction of gesture-engine fixture used in DOM-based unit tests (#13805)
* fix(web): add robustness for bad touch-layout fontsize specs (#13774)
* fix(web): fix var declaration affected by linter strictness + length-style fix (#13839)
* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13840)
* chore(common): override check when running on TC (#13779)
* change(linux): add ICU dependency needed to build on Arch Linux (#13842)
* maint(common): add support for TC blocks (#13810)
## 19.0.31 alpha 2025-05-05
* fix(mac): add version metadata after building tests (#13818)
* maint(common): remove `eslint:recommended` configuration for .ts (#13825)
* maint(common): Update write_download_info() to be cross-platform (#13813)
* change(ios): limit reports about devices without predefined scaling entries (#13806)
* change(web): lint engine/common/web-utils (#13797)
* chore(web): replace kmwFromCharCode with its ES6 equivalent (#13716)
* chore(web): add unit tests for SMP-aware string-handling helper methods (#13719)
* refactor(web): refactor string-helper library to eliminate its module side-effect (#13726)
## 19.0.30 alpha 2025-05-02
* chore(android): Use `builder_is_ci_build` rather than `--ci` flag (#13772)
* docs: add `--deps` flags and move builder.md into /docs (#13815)
* chore(web): improve clean actions (#13804)
* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13773)
* maint(web): merge LMLayer CI build config with standard Web CI build config (#13807)
* maint(core): batch build kmx in tests for performance (#13795)
* maint(common): cleanup meson versioning (#13796)
* maint(common): test imported resource versions (#13803)
## 19.0.29 alpha 2025-04-30
* change(web): enable linting for Keyman Engine for Web modules (#13787)
* fix(web): reconnect unit tests for low-level web utility classes/methods (#13714)
* fix(android): Change Alert Dialog instead of Toast if package missing languages (#13786)
* maint(common): remove platform advocates from CODEOWNERS (#13801)
* chore(common): allow to run tests in virtual environment (#13793)
* chore: increase linter strictness (#13788)
## 19.0.28 alpha 2025-04-29
* chore(common): Update dependencies in crowdin GitHub action (#13775)
* chore: additional tweaks to scopes/labels (#13765)
* chore(common): rename `chore` GH label to `maint` (#13780)
* change(linux): add link to issue (#13778)
## 19.0.27 alpha 2025-04-28
* chore: update labels for v19 (#13744)
## 19.0.26 alpha 2025-04-24
* chore: merge beta version 18.0.235 history to master (#13735)
* chore(linux): Update debian changelog (#13738)
* chore: comment out CODEOWNERS (#13739)
* chore(linux): add a stub unit test to allow successful coverage reporting (#13740)
## 19.0.25 alpha 2025-04-22
* chore: merge beta to master (pre-release prep) (#13730)
## 19.0.24 alpha 2025-04-21
* build(deps): bump koa from 2.15.3 to 2.16.1 (#13694)
* chore: merge beta into master B18S5 (#13712)
## 19.0.23 alpha 2025-04-07
* chore(web): remove dead methods regarding keyboard state (#13671)
* chore(linux): Update debian changelog (#13675)
## 19.0.22 alpha 2025-04-04
* build(deps): bump tar-fs and puppeteer-core (#13661)
## 19.0.21 alpha 2025-04-03
* chore: use explicit `node:` prefix for node imports (#13650)
* refactor(developer): rename developer/web/utils/common-messages.ts to developer-utils-messages.ts (#13653)
* refactor(developer): move sentry and developer options into console apps (#13654)
* refactor(developer): consolidate project loader (#13656)
## 19.0.20 alpha 2025-04-01
* fix(android): move responsibility for setting keyboard height preference into `applyKeyboardHeight()` (#13578)
## 19.0.19 alpha 2025-03-31
* chore: merge beta to master B18S4 (#13606)
## 19.0.18 alpha 2025-03-25
* chore(developer): improve ldml code coverage (#13516)
@ -80,6 +385,134 @@
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
* chore: increment to alpha 19.0 (#13187)
## 18.0.238 stable 2025-06-04
* fix(developer): ldml: make sure .run() calls compile() for validation (#14071)
* maint(linux): fix typo of temporary dependency package (#14085)
* maint(linux): update to new version of gha-ubuntu-packaging (#14096)
* maint(mac): make make-km-dmg.sh less quiet (#14106)
## 18.0.237 stable 2025-05-30
* maint(android): Update FirstVoices launcher icon and red theme (#13917)
* fix(web): empty layout fontSize property should default to 1em (#13960)
* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13963)
* chore(linux): Update debian changelog (#13888)
* fix(mac): handle PackageInfo section in kmp.inf file (#13957)
* feat(android): log active keyboard, model IDs with reported errors (#14003)
* chore(ios): FirstVoices certificate update (#14005)
* feat(ios): log active keyboard, model IDs with reported errors (#14006)
* fix(linux): configurable path in `.service` file when using basu (#13999)
* maint(linux): add `--no-werror` option to not report warnings as errors (#14000)
* fix(windows): use json file name against cache folder and ename Enter and Exit methods for TState Object (#14010)
* chore(ios): update FirstVoices AppIcon (#14011)
* maint: change builder_completion script to a .inc.sh (#14008)
* fix(windows): only breadcrumb unexpected state on first run (#14026)
* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#14028)
* fix(windows): fix sentry error message reporting (#14029)
## 18.0.236 stable 2025-05-12
* chore(linux): Update debian changelog (#13737)
* chore(linux): add a stub unit test to allow successful coverage reporting (#13741)
* fix(android): Use Alert Dialog if keyboard pkg missing languages (#13802)
* maint(web): merge LMLayer CI build config with standard Web CI build config (#13808)
* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13817)
* change(ios): limit reports about devices without predefined scaling entries (#13828)
* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13841)
* fix(web): add robustness for bad touch-layout fontsize specs (#13838)
* change(linux): add ICU dependency needed to build on Arch Linux (#13843)
* fix(mac): add version metadata after building tests (#13853)
* fix(web): sustain active batched-layout mode when keyboard is switched (#13863)
* fix(developer): remove ngrok binary at build time (#13873)
* maint(common): apply new codeowners from master (#13874)
* chore(mac,windows): Update Crowdin strings for Italian (#13872)
## 18.0.234 beta 2025-04-22
* fix(android): Require RELEASE_OEM env vars to build FirstVoices Android (#13725)
## 18.0.233 beta 2025-04-21
* chore: cleanup kps.xsd version typos (#13670)
* fix(android): refresh system keyboard banner on reload (#13702)
* chore(ios): update keyboard-size data for current iOS devices (#13697)
* fix(ios): ensure update notification does not go translucent/transparent (#13698)
* fix(ios): resummon keyboard when keyboard picker is dismissed (#13701)
* fix(web): match banner font scaling to osk font scaling (#13646)
* docs(common): Document Keyman website server-side dependencies (#13642)
* docs(windows): update FAQ (#13708)
## 18.0.232 beta 2025-04-10
* fix(android): Downgrade UnknownHostException error to info (#13692)
* change(web): add unique classname to mobile-device page-trailer element (#13632)
* chore(android,mac,windows): Update crowdin strings for Spanish Latin America (#13691)
## 18.0.231 beta 2025-04-09
* docs(android): Document default keyboard height for xxxhdpi devices (#13673)
* fix(ios): allow swipe-dismissal of Settings to be cancelled (#13686)
* fix(ios): do not show notification for downloaded keyboard when showing its installer (#13687)
* fix(ios): restore banner-hiding mechanism for keyboard calibration tool (#13689)
## 18.0.230 beta 2025-04-08
* docs(web): documentation for touch-keyboard gesture models (#13659)
## 18.0.229 beta 2025-04-07
* fix(ios): ensure keyboard height is set when engine settings view is used in a NavigationController (#13643)
* chore(android,mac,windows): update Crowdin strings for Czech (#13680)
* chore(android,mac,windows): update Crowdin strings for Khmer (#13681)
* chore(linux): Update debian changelog (#13674)
## 18.0.228 beta 2025-04-04
* fix(ios): prevent re-display of keyboard when re-entering app to its menus (#13630)
* fix(ios): slide dismissal of keyboard-height adjuster will properly resize the keyboard (#13631)
* docs(web): improve section on motivation for gesture input serialization (#13652)
* fix(linux): fix crash if kmp doesn't have name field (#13648)
* chore(web): improve error logging in predictive-text worker message validation (#13660)
* fix(android): Adjust keyboard and banner heights for mdpi and hdpi devices (#13651)
* fix(android): adjust keyboard and banner height for xhdpi portrait (#13655)
* fix(android): Adjust keyboard and banner height for 7" tablet landscape (#13667)
* fix(android): Adjust keyboard height for 10" tablet landscape (#13668)
## 18.0.227 beta 2025-04-03
* fix(developer): remove redundant keyboard fields from .kps (#13600)
* fix(developer): remove redundant Name and RTL fields from .kps `LexicalModel` (#13640)
* fix(mac): reevaluate API compliance for each change in context (#13550)
* fix(android): Move responsibility for setting keyboard height preference (#13635)
* fix(android): Add back button to "Adjust Keyboard Height "menu (#13645)
* fix(linux): allow unhandled keys to pass through to compliant apps (#13625)
## 18.0.226 beta 2025-04-02
* docs(web): document internals for gesture modeling and processing (#13587)
* docs(web): document input-serialization aspects of gesture processing (#13597)
* fix(android): Add dimensions for 320+dpi phones in landscape (#13592)
* chore(common): Update crowdin strings for Mon (#13595)
* docs(linux): add function headers for some of the engine methods (#13637)
## 18.0.225 beta 2025-04-01
* fix(web): remove extra px offset from last OSK row (#13621)
* fix(developer): add special handling for 'und' in kmc-keyboard-info (#13622)
* chore(mac,windows): Update Crowdin strings for Italian (#13594)
* change(ios): prevent file erasure during copyWithOverwrite error state (#13548)
* fix(ios): uninstalls keyboards upon failure to load (#13549)
* refactor(linux): remove warning if no custom keyboards exist (#13624)
## 18.0.224 beta 2025-03-31
* fix(windows): reload right modifier key option (#13585)
## 18.0.223 beta 2025-03-29
* chore(developer): improve kmc-ldml 'invalid version' message (#13602)
## 18.0.222 beta 2025-03-28
* fix(android): Handle banner overrides independently when device is locked and unlocked (#13546)

View file

@ -1,16 +1,14 @@
# License
Copyright (c) 2017-2024 SIL Global. All rights reserved.
Copyright (c) 2017-2025 SIL Global. All rights reserved.
Licensed under the MIT License.
[Keyman for Linux](./linux) is licensed under the MIT License apart from [ibus-kmfl](./linux/ibus-kmfl) which is licensed under the GNU General Public License as published by the Free Software Foundation; either [version 2](./linux/ibus-kmfl/COPYING) of the License, or (at your option) any later version.
---
The MIT License
Copyright (c) 2017-2024 SIL Global
Copyright (c) 2017-2025 SIL Global
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -28,4 +26,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View file

@ -1,6 +1,12 @@
![Keyman](https://keyman.com/cdn/dev/img/logo2.png)
[Keyman](https://keyman.com) makes it possible for you to type in any language on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even instantly in your web browser. Create keyboard layouts with Keyman Developer and share them with the community in the [keyboards repository](https://github.com/keymanapp/keyboards). The [Keyman Community](https://community.software.sil.org/c/keyman) have already contributed keyboard layouts for over 2,000 languages!
[Keyman](https://keyman.com) makes it possible for you to type in any language
on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even
instantly in your web browser. Create keyboard layouts with Keyman Developer and
share them with the community in the [keyboards
repository](https://github.com/keymanapp/keyboards). The [Keyman
Community](https://community.software.sil.org/c/keyman) have so far contributed
well over 1,000 keyboard layouts for over 2,500 languages!
# License

View file

@ -1 +1 @@
19.0.19
19.0.64

3
android/.gitignore vendored
View file

@ -57,6 +57,9 @@ KMAPro/**/assets/*.kmp
# Play Store release notes (copied from whatsnew.md)
KMAPro/kMAPro/src/main/play/release-notes/**/*.txt
# Release artifacts
upload/
# Eclipse files
.project
.settings/

View file

@ -11,8 +11,8 @@ function generateReleaseNotes() {
# Copy release notes for Gradle Play Publisher to upload
# Reference: https://github.com/Triple-T/gradle-play-publisher#uploading-release-notes
#
local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/$TIER.txt"
if [ $TIER = "stable" ]; then
local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/${KEYMAN_TIER}.txt"
if [ $KEYMAN_TIER = "stable" ]; then
PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/default.txt"
fi
builder_heading "Generating Play Store release notes to $PLAY_RELEASE_NOTES"

View file

@ -8,10 +8,10 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.9.2'
// sentry-android-gradle-plugin 2.1.5+ requires AGP 7.0
classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0'
classpath 'name.remal:gradle-plugins:1.5.0'
classpath 'name.remal:gradle-plugins:1.9.2'
// From jcenter() which was deprecated August 2024
// https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

View file

@ -7,6 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
. "$KEYMAN_ROOT/resources/build/build-help.inc.sh"
. "$KEYMAN_ROOT/resources/build/build-download-resources.sh"
@ -28,7 +29,6 @@ builder_describe "Builds Keyman for Android app." \
"build" \
"test Runs lint and unit tests." \
"publish Publishes symbols to Sentry and the APK to the Play Store." \
"--ci Don't start the Gradle daemon. For CI" \
"--upload-sentry Upload to sentry"
# parse before describe_outputs to check debug flags
@ -37,32 +37,32 @@ builder_parse "$@"
if builder_is_debug_build; then
builder_heading "### Debug config ####"
CONFIG="debug"
BUILD_FLAGS="assembleDebug -x lint -x test"
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
fi
builder_describe_outputs \
configure /android/KMAPro/kMAPro/libs/keyman-engine.aar \
build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${VERSION}.apk
build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk
#### Build
# Parse args
if builder_has_option --ci; then
if builder_is_ci_build; then
DAEMON_FLAG=--no-daemon
fi
#### Build action definitions ####
function makeLocalSentryRelease() {
echo "Making a Sentry release for tag $VERSION_GIT_TAG"
echo "Making a Sentry release for tag $KEYMAN_VERSION_GIT_TAG"
sentry-cli upload-dif -p keyman-android --include-sources
sentry-cli releases -p keyman-android files $VERSION_GIT_TAG upload-sourcemaps ./
sentry-cli releases -p keyman-android files $KEYMAN_VERSION_GIT_TAG upload-sourcemaps ./
echo "Finalizing release tag $VERSION_GIT_TAG"
sentry-cli releases finalize "$VERSION_GIT_TAG"
echo "Finalizing release tag $KEYMAN_VERSION_GIT_TAG"
sentry-cli releases finalize "$KEYMAN_VERSION_GIT_TAG"
}

View file

@ -1,4 +1,7 @@
android.enableJetifier=true
android.useAndroidX=true
# AGP 8.0 generates R classes for resources defined in the current module only.
android.nonTransitiveRClass=false
org.gradle.jvmargs=-Xmx1024m

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -20,6 +20,15 @@ android {
noCompress "kmp"
}
buildFeatures {
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
defaultConfig {
applicationId "com.tavultesoft.kmapro"
minSdkVersion 21
@ -29,11 +38,11 @@ android {
//dumpProperties(project) // Use this to dump all external properties for debugging TeamCity integration
//println "===END DUMP==="
// VERSION strings from version.gradle
versionCode VERSION_CODE as Integer
versionName VERSION_NAME
buildConfigField 'String', 'VERSION_ENVIRONMENT', '"'+VERSION_ENVIRONMENT+'"'
buildConfigField 'String', 'VERSION_GIT_TAG', '"'+VERSION_GIT_TAG+'"'
// KEYMAN_VERSION strings from version.gradle
versionCode KEYMAN_VERSION_CODE as Integer
versionName KEYMAN_VERSION_NAME
buildConfigField 'String', 'KEYMAN_VERSION_ENVIRONMENT', '"'+KEYMAN_VERSION_ENVIRONMENT+'"'
buildConfigField 'String', 'KEYMAN_VERSION_GIT_TAG', '"'+KEYMAN_VERSION_GIT_TAG+'"'
}
String env_release_store_file = System.getenv("release_store_file")
@ -77,7 +86,7 @@ android {
// - PR builds also have "-test-1234" suffix (where 1234 is PR#)
// Gradle sync will nullify these values, so have a fallback
String appSuffix = ''
String tag = VERSION_TAG ? VERSION_TAG : '-' + tier;
String tag = KEYMAN_VERSION_TAG ? KEYMAN_VERSION_TAG : '-' + tier;
if (tag.startsWithAny("-beta", "beta")) {
appSuffix = ' Beta';
} else if (tag.startsWithAny("-alpha", "alpha")) {
@ -88,16 +97,16 @@ android {
// -test-PR_NUMBER
appSuffix = appSuffix + match.group(1)
} else {
String environment = VERSION_ENVIRONMENT ? VERSION_ENVIRONMENT : 'local';
String environment = KEYMAN_VERSION_ENVIRONMENT ? KEYMAN_VERSION_ENVIRONMENT : 'local';
if (environment.matches("local")) {
appSuffix = appSuffix + '-' + environment;
}
}
variant.resValue "string", "app_name", 'Keyman' + appSuffix;
// Adjust output name to "keyman-${VERSION_MD}.apk"
// Adjust output name to "keyman-${KEYMAN_VERSION_MD}.apk"
variant.outputs.all {
outputFileName = "keyman-" + VERSION_MD + ".apk"
outputFileName = "keyman-" + KEYMAN_VERSION_MD + ".apk"
}
}
lintOptions {
@ -156,7 +165,7 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.stepstone.stepper:material-stepper:4.3.1'
api(name: 'keyman-engine', ext: 'aar')

View file

@ -62,8 +62,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
Log.d(TAG, "Initializing Sentry");
SentryAndroid.init(getApplicationContext(), options -> {
options.setEnableAutoSessionTracking(false);
options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG);
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT);
options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG);
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT);
});
}
if (BuildConfig.DEBUG) {
@ -110,9 +110,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
@Override
public void onInitializeInterface() {
super.onInitializeInterface();
// KeymanWeb reloaded, so we have to pass the banner again
BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM);
}
/**
@ -272,7 +269,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
@Override
public void onKeyboardShown() {
// Do nothing
// Refresh banner theme
BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM);
}
@Override

View file

@ -4,7 +4,6 @@
package com.tavultesoft.kmapro;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
@ -34,7 +33,6 @@ public class AdjustKeyboardHeightActivity extends BaseActivity {
private static ImageView sampleKeyboard = null;
// Keeps track of the adjusted keyboard height for saving
private static SharedPreferences.Editor editor = null;
private static int currentHeight = 0;
private static ViewGroup.LayoutParams layoutParams;
@ -52,22 +50,19 @@ public class AdjustKeyboardHeightActivity extends BaseActivity {
if (actionBar != null) {
actionBar.setTitle(null);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue)));
}
TextView adjustKeyboardHeightActivityTitle = (TextView) findViewById(R.id.bar_title);
adjustKeyboardHeightActivityTitle.setWidth((int) getResources().getDimension(R.dimen.package_label_width));
String titleStr = getString(R.string.adjust_keyboard_height);
adjustKeyboardHeightActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white));
adjustKeyboardHeightActivityTitle.setText(titleStr);
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
editor = prefs.edit();
sampleKeyboard = (ImageView) findViewById(R.id.sample_keyboard);
layoutParams = sampleKeyboard.getLayoutParams();
currentHeight = KMManager.getKeyboardHeight(context);
@ -77,9 +72,7 @@ public class AdjustKeyboardHeightActivity extends BaseActivity {
resetButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Clear the keyboard height preferences to reset
editor.remove(KMManager.KMKey_KeyboardHeightPortrait);
editor.remove(KMManager.KMKey_KeyboardHeightLandscape);
editor.commit();
KMManager.applyKeyboardHeight(context, KMManager.KeyboardHeight_Reset);
// Restore default height
currentHeight = KMManager.getKeyboardHeight(context);
@ -107,11 +100,7 @@ public class AdjustKeyboardHeightActivity extends BaseActivity {
break;
case MotionEvent.ACTION_UP:
// Save the currentHeight when the user releases
int orientation = KMManager.getOrientation(context);
String keyboardHeightKey = (orientation == Configuration.ORIENTATION_LANDSCAPE) ?
KMManager.KMKey_KeyboardHeightLandscape : KMManager.KMKey_KeyboardHeightPortrait;
editor.putInt(keyboardHeightKey, currentHeight);
editor.commit();
KMManager.applyKeyboardHeight(context, currentHeight);
break;
}
return true;
@ -140,11 +129,8 @@ public class AdjustKeyboardHeightActivity extends BaseActivity {
}
@Override
public void onBackPressed() {
// Apply the adjusted height on exit
KMManager.applyKeyboardHeight(this, currentHeight);
super.onBackPressed();
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
}

View file

@ -114,7 +114,14 @@ public class KMPBrowserActivity extends BaseActivity {
Intent intent = new Intent(context, MainActivity.class);
intent.setData(downloadURI);
KMLog.LogBreadcrumb(TAG, "Successful keyboard search now triggering package install", true);
startActivity(intent);
// Verify network access before trying to download
if (KMManager.hasConnection(context)) {
startActivity(intent);
} else {
Toast.makeText(context, getString(R.string.no_internet_connection), Toast.LENGTH_LONG).show();
return true;
}
// Finish activity
finish();

View file

@ -144,8 +144,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
if (KMManager.getMaySendCrashReport()) {
SentryAndroid.init(context, options -> {
options.setEnableAutoSessionTracking(false);
options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG);
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT);
options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG);
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT);
});
}
@ -440,41 +440,41 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_info:
showInfo();
return true;
case R.id.action_share:
showShareDialog();
return true;
// Android Gradle 8.0 no longer declares resources final, so can't use switch statement here
if (item.getItemId() == R.id.action_info) {
showInfo();
return true;
} else if (item.getItemId() == R.id.action_share) {
showShareDialog();
return true;
/* Disable Web Browser to investigate Google sign-in
case R.id.action_web:
} else if ((item.getItemId() == R.id.action_web) {
showWebBrowser();
return true;*/
case R.id.action_text_size:
showTextSizeDialog();
return true;
case R.id.action_clear_text:
showClearTextDialog();
return true;
case R.id.action_get_started:
showGetStarted();
return true;
case R.id.action_settings:
showSettings();
return true;
case R.id.action_update_keyboards:
KMManager.getUpdateTool().executeOpenUpdates();
// Dismiss icon
updateUpdateCountIndicator(0);
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
_keyboardupdate.setVisible(false);
}
} else if (item.getItemId() == R.id.action_text_size) {
showTextSizeDialog();
return true;
} else if (item.getItemId() == R.id.action_clear_text){
showClearTextDialog();
return true;
} else if (item.getItemId() == R.id.action_get_started) {
showGetStarted();
return true;
} else if (item.getItemId() == R.id.action_settings) {
showSettings();
return true;
} else if (item.getItemId() == R.id.action_update_keyboards) {
KMManager.getUpdateTool().executeOpenUpdates();
// Dismiss icon
updateUpdateCountIndicator(0);
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
_keyboardupdate.setVisible(false);
}
return true;
default:
return super.onOptionsItemSelected(item);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@ -632,7 +632,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
Intent i = new Intent(this, InfoActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivity(i);
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
}
private void showWebBrowser() {
@ -643,7 +643,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
Intent i = new Intent(this, WebBrowserActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivity(i);
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
}
private void showShareDialog() {

View file

@ -116,12 +116,14 @@ public class PackageActivity extends AppCompatActivity implements
kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0;
// Sanity check for keyboard packages
// Not using showErrorToast to avoid cluttering Sentry with noise
if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) {
if (keyboardCount == 0) {
showErrorDialog(context, pkgId, getString(R.string.no_new_touch_keyboards_to_install));
return;
} else if (languageCount == 0) {
showErrorToast(getString(R.string.no_associated_languages));
showErrorDialog(context, pkgId, getString(R.string.no_associated_languages));
return;
}
}

View file

@ -68,7 +68,7 @@
<!-- Context: Keyman Settings menu -->
<string name="adjust_keyboard_height" comment="Menu action to adjust keyboard height">Ajustar la altura del teclado</string>
<!-- Context: Keyman Settings menu -->
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Adjust longpress delay</string>
<string name="adjust_longpress_delay" comment="Menu action to adjust longpress delay duration">Ajustar demora de pulsación larga</string>
<!-- Context: Keyman Settings menu -->
<string name="spacebar_caption" comment="Menu action to set the spacebar caption">Leyenda de barra espaciadora</string>
<!-- Context: Keyman Settings menu / Spacebar caption -->
@ -132,7 +132,7 @@
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="ic_delay_time_down" comment="Make longpress delay shorter">Tiempo de retraso más corto</string>
<!-- Context: Adjust Longpress Delay Time menu -->
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Longpress delay time slider</string>
<string name="ic_longpress_delay_slider" comment="Adjust longpress delay time">Ajustador del tiempo de retraso de pulsación larga</string>
<!-- Context: Keyman Web Browser -->
<string name="hint_text" comment="Prompt to type in the browser search bar">Buscar o escribir una dirección web</string>
<!-- Context: Keyman Web Browser -->

View file

@ -1,4 +1,5 @@
plugins {
id 'base'
id 'com.android.library'
id 'name.remal.default-plugins'
}
@ -6,20 +7,32 @@ plugins {
ext.rootPath = '../../'
apply from: "$rootPath/version.gradle"
base {
// Specify library filename keyman-engine-$CONFIG.aar
archivesName = "keyman-engine"
}
android {
compileSdk 34
namespace "com.keyman.engine"
buildFeatures {
// needed for custom BuildConfigField values in defaultConfig
buildConfig = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
// VERSION_CODE and VERSION_NAME from version.gradle but Gradle removes them for libraries
buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+VERSION_NAME+"\""
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
// Sepcify library filename keyman-engine-$CONFIG.aar
setProperty("archivesBaseName", "keyman-engine")
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle but Gradle removes them for libraries
buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+KEYMAN_VERSION_NAME+"\""
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
}
buildTypes {
@ -36,10 +49,6 @@ android {
}
testOptions {
unitTests {
// Allows use of a simulated Android API for tests. (Thanks, roboelectric!)
includeAndroidResources = true
}
unitTests.all {
testLogging {
// May also add "passed", "skipped".
@ -55,17 +64,17 @@ android {
dependencies {
// 1.6.0-rc01 needed to resolve https://issuetracker.google.com/issues/238425626
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
// material:1.7.0 will need Gradle plugin 7.1.0+
implementation 'com.google.android.material:material:1.12.0'
implementation 'commons-io:commons-io:2.13.0'
implementation 'commons-io:commons-io:2.16.1'
implementation 'io.sentry:sentry-android:7.8.0'
implementation 'androidx.preference:preference:1.2.1'
// Robolectric
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'androidx.test.ext:junit:1.1.5'
testImplementation 'org.robolectric:robolectric:4.10.3'
testImplementation 'androidx.test.ext:junit:1.2.1'
testImplementation 'org.robolectric:robolectric:4.14.1'
testImplementation 'androidx.test:core:1.6.1'
// Generate QR Codes
implementation ('com.github.kenglxn.QRGen:android:3.0.1') {

View file

@ -9,7 +9,6 @@
<title>Keyman</title>
<!-- DOMRect only exists as of Chrome 61... thus coming later than ES6. -->
<script src="other-polyfills.js"></script>
<script src="sentry.min.js"></script>
<script src="keyman-sentry.js"></script>
<script src="keymanweb-webview.js"></script>
<script src="android-host.js"></script>

View file

@ -25,6 +25,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Typeface;
import android.inputmethodservice.InputMethodService;
@ -273,6 +274,8 @@ public final class KMManager {
// will be blocked.
private static boolean maySendCrashReport = true;
private final static String KMEngine_PrefsKey = "KMAPreferences";
// Keyman public keys
public static final String KMKey_ID = "id";
public static final String KMKey_Name = "name";
@ -295,8 +298,8 @@ public final class KMManager {
public static final String KMKey_KMPInstall_Mode = "kmpInstallMode";
public static final String KMKey_KeyboardModified = "lastModified";
public static final String KMKey_KeyboardRTL = "rtl";
public static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait";
public static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape";
private static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait";
private static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape";
public static final String KMKey_LongpressDelay = "longpressDelay";
@ -345,14 +348,21 @@ public final class KMManager {
public static final int KMMinimum_LongpressDelay = 300;
public static final int KMMaximum_LongpressDelay = 1500;
// Default keyboard heights
public static final int KeyboardHeight_Reset = 0; // To reset to default, applyKeyboardHeight
public static final int KeyboardHeight_Invalid = -1; // If invalid orientation passed to functions
public static int KeyboardHeight_Context_Portrait_Default = 0; // Default portrait height
public static int KeyboardHeight_Context_Landscape_Default = 0; // Default landscape height
public static int KeyboardHeight_Context_Portrait_Current = 0; // Current portrait height
public static int KeyboardHeight_Context_Landscape_Current = 0; // Current landscape height
// Default prediction/correction setting
public static final int KMDefault_Suggestion = SuggestionType.PREDICTIONS_WITH_CORRECTIONS.toInt();
// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
protected static final String KMFilename_JSEngine = "keymanweb-webview.js";
protected static final String KMFilename_JSSentry = "sentry.min.js";
protected static final String KMFilename_JSSentryInit = "keyman-sentry.js";
protected static final String KMFilename_JSSentry = "keyman-sentry.js";
protected static final String KMFilename_AndroidHost = "android-host.js";
protected static final String KMFilename_KmwCss = "kmwosk.css";
protected static final String KMFilename_KmwGlobeHintCss = "globe-hint.css";
@ -498,6 +508,12 @@ public final class KMManager {
migrateCloudKeyboards(appContext);
CloudDownloadMgr.getInstance().initialize(appContext);
calculateDefaultKeyboardHeights(context);
SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
KeyboardHeight_Context_Portrait_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, KMManager.KeyboardHeight_Context_Portrait_Default);
KeyboardHeight_Context_Landscape_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, KMManager.KeyboardHeight_Context_Landscape_Default);
}
public static void executeResourceUpdate(Context aContext)
@ -965,7 +981,6 @@ public final class KMManager {
copyAsset(context, KMFilename_JSEngine, "", true);
copyAsset(context, KMFilename_JSSentry, "", true);
copyAsset(context, KMFilename_JSSentryInit, "", true);
copyAsset(context, KMFilename_AndroidHost, "", true);
copyAsset(context, KMFilename_KmwCss, "", true);
copyAsset(context, KMFilename_KmwGlobeHintCss, "", true);
@ -1463,7 +1478,7 @@ public final class KMManager {
* @param suggestType SuggestionType
*/
public static void setMaySuggest(String languageID, SuggestionType suggestType) {
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KMManager.getLanguageAutoCorrectionPreferenceKey(languageID), suggestType.toInt());
editor.commit();
@ -1582,7 +1597,7 @@ public final class KMManager {
model = model.replaceAll("\"", "'");
// When entering password field, maySuggest should override to disabled
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
int suggestionPreference = prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion);
RelativeLayout.LayoutParams params;
@ -2213,7 +2228,7 @@ public final class KMManager {
*/
public static int getLongpressDelay() {
SharedPreferences prefs = appContext.getSharedPreferences(
appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
return prefs.getInt(KMKey_LongpressDelay, KMDefault_LongpressDelay);
}
@ -2230,7 +2245,7 @@ public final class KMManager {
}
SharedPreferences prefs = appContext.getSharedPreferences(
appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KMKey_LongpressDelay, longpressDelay);
editor.commit();
@ -2266,21 +2281,87 @@ public final class KMManager {
return bannerHeight;
}
/**
* Returns the current keyboard height in dp for the current device orientation
*/
public static int getKeyboardHeight(Context context) {
int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height);
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
int orientation = getOrientation(context);
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, defaultHeight);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, defaultHeight);
}
return defaultHeight;
return getKeyboardHeight(context, orientation);
}
/**
* Returns the current keyboard height in dp for the specified device
* orientation
*/
public static int getKeyboardHeight(Context context, int orientation) {
SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return KMManager.KeyboardHeight_Context_Portrait_Current;
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return KMManager.KeyboardHeight_Context_Landscape_Current;
} else {
return KMManager.KeyboardHeight_Invalid; // Invalid orientation
}
}
/**
* Apply the keyboard height for the current orientation and store it in SharedPreferences.
* If height is KMManager.KeyboardHeight_Reset, remove the stored height for that
* orientation, resetting to the default height for the device/orientation.
* @param context Context
* @param height int - keyboard height in dp
*/
public static void applyKeyboardHeight(Context context, int height) {
int orientation = getOrientation(context);
applyKeyboardHeight(context, height, orientation);
}
/**
* Apply the keyboard height for the specified orientation and store it in SharedPreferences.
* If height is KMManager.KeyboardHeight_Reset, remove the stored height for that orientation.
* @param context Context
* @param height int - keyboard height in dp
* @param orientation int - Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE
*/
public static void applyKeyboardHeight(Context context, int height, int orientation) {
SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
int defaultHeightForContext = getDefaultKeyboardHeight(orientation);
if (orientation != Configuration.ORIENTATION_PORTRAIT && orientation != Configuration.ORIENTATION_LANDSCAPE) {
return; // Invalid orientation, do nothing
}
if (height == KMManager.KeyboardHeight_Reset) {
// Passing 0 will reset the stored height for this orientation.
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
editor.remove(KMManager.KMKey_KeyboardHeightLandscape);
} else /* (orientation == Configuration.ORIENTATION_PORTRAIT) */{
editor.remove(KMManager.KMKey_KeyboardHeightPortrait);
}
height = defaultHeightForContext;
} else {
// Applying gating to 50%-200% of default height (following Keyman)
if (height < (defaultHeightForContext / 2)) {
height = (int) (defaultHeightForContext / 2);
} else if (height > (defaultHeightForContext * 2)) {
height = (int) (defaultHeightForContext * 2);
}
// Store the new height based on the current orientation
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
editor.putInt(KMManager.KMKey_KeyboardHeightLandscape, height);
} else /* (orientation == Configuration.ORIENTATION_PORTRAIT) */ {
editor.putInt(KMManager.KMKey_KeyboardHeightPortrait, height);
}
}
// Update the height based on the current orientation
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
KeyboardHeight_Context_Landscape_Current = height;
} else /* (orientation == Configuration.ORIENTATION_PORTRAIT) */ {
KeyboardHeight_Context_Portrait_Current = height;
}
editor.commit();
// Confirm new LayoutParams for in-app or system keyboards
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) {
InAppKeyboard.loadJavascript(KMString.format("setOskHeight('%s')", height));
RelativeLayout.LayoutParams params = getKeyboardLayoutParams();
@ -2293,6 +2374,53 @@ public final class KMManager {
}
}
/**
* Returns the default keyboard height in dp for the current device
* and orientation
*/
public static int getDefaultKeyboardHeight(Context context) {
int orientation = getOrientation(context);
return getDefaultKeyboardHeight(orientation);
}
/**
* Returns the current keyboard height in dp for the specified orientation
* for the current device
*/
public static int getDefaultKeyboardHeight(int orientation) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return KMManager.KeyboardHeight_Context_Portrait_Default;
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return KMManager.KeyboardHeight_Context_Landscape_Default;
} else {
return KMManager.KeyboardHeight_Invalid; // Invalid orientation
}
}
private static void calculateDefaultKeyboardHeights(Context context) {
if (isTestMode()) {
// Keyboard height not needed for unit tests #13578
KeyboardHeight_Context_Portrait_Default = 0;
KeyboardHeight_Context_Landscape_Default = 0;
return;
}
Resources resources = context.getResources();
Configuration originalConfig = resources.getConfiguration();
Configuration newConfig = new Configuration(originalConfig);
newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
Context newContext = context.createConfigurationContext(newConfig);
Resources newResources = newContext.getResources();
KeyboardHeight_Context_Portrait_Default = (int) newResources.getDimension(R.dimen.keyboard_height);
newConfig = new Configuration(originalConfig);
newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
newContext = context.createConfigurationContext(newConfig);
newResources = newContext.getResources();
KeyboardHeight_Context_Landscape_Default = (int) newResources.getDimension(R.dimen.keyboard_height);
}
/**
* Get the size of the area the window would occupy.
* API 30+
@ -2322,7 +2450,7 @@ public final class KMManager {
}
protected static void setPersistentShouldShowHelpBubble(boolean flag) {
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(KMManager.KMKey_ShouldShowHelpBubble, flag);
editor.commit();

View file

@ -1,5 +1,6 @@
package com.keyman.engine.cloud;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -60,6 +61,7 @@ public class CloudDownloadMgr{
* Append downloadreceiver to the main context.
* @param aContext the context
*/
@SuppressLint("UnspecifiedRegisterReceiverFlag")
public synchronized void initialize(Context aContext)
{
if(isInitialized)
@ -73,6 +75,7 @@ public class CloudDownloadMgr{
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
Context.RECEIVER_EXPORTED);
} else {
// Needs @SuppressLint UnspecifiedRegisterReceiverFlag
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
} catch (IllegalArgumentException e) {

View file

@ -5,6 +5,7 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
public final class Connection {
private static final String TAG = "Connection";
@ -103,6 +104,9 @@ public final class Connection {
} catch (SocketTimeoutException e) {
KMLog.LogBreadcrumb(TAG, "Connection.initialize timeout for: " + originalUrl, true);
KMLog.LogInfo(TAG, "SocketTimeoutException");
} catch (UnknownHostException e) {
KMLog.LogBreadcrumb(TAG, "Connection.initialize could not resolve host: " + originalUrl, true);
KMLog.LogInfo(TAG, "UnknownHostException");
} catch (Exception e) {
KMLog.LogException(TAG, "Initialization failed:", e);
}

View file

@ -4,15 +4,19 @@
package com.keyman.engine.util;
import static com.keyman.engine.KMManager.KMKey_LexicalModelID;
import android.util.Log;
import android.widget.Toast;
import com.keyman.engine.BaseActivity;
import com.keyman.engine.BuildConfig;
import com.keyman.engine.KMManager;
import com.keyman.engine.util.DependencyUtil;
import com.keyman.engine.data.Keyboard;
import com.keyman.engine.util.DependencyUtil.LibraryType;
import java.util.Map;
import io.sentry.Breadcrumb;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
@ -20,19 +24,67 @@ import io.sentry.SentryLevel;
public final class KMLog {
private static final String TAG = "KMLog";
private static final String KEYBOARD_TAG = "keyboardId";
private static final String KEYBOARD_COUNT_TAG = "installedKeyboardCount";
private static final String MODEL_TAG = "modelId";
private static final String LANGCODE_TAG = "languageCode";
// Some of the methods used to generate debug logging information can, themselves,
// trigger errors that can also trigger the same logging. We must not get
// caught in an infinite loop / stack-overflow; this field helps us avoid states
// that would otherwise cause error-looping, etc.
private static boolean isLogging = false;
private static void tagDebugInfo() {
String kbdId = "";
String lngCode = "";
String modelId = "";
int kbdCount = 0;
// Do not risk raising a new error while tagging info for another error.
try {
// Both take a context parameter... but don't actually need or use it!
Keyboard kbd = KMManager.getCurrentKeyboardInfo(null);
kbdCount = KMManager.getKeyboardsList(null).size();
if (kbd != null) {
kbdId = kbd.getKeyboardID();
lngCode = kbd.getLanguageCode();
Map<String, String> modelMap = KMManager.getAssociatedLexicalModel(kbd.getLanguageID());
if (modelMap != null) {
modelId = modelMap.get(KMKey_LexicalModelID);
if (modelId == null) {
modelId = "";
}
}
}
} catch (Exception ex) {
String msg = ex.getMessage() == null ? "" : ex.getMessage();
Sentry.setExtra("debugLoggingError", msg);
}
Sentry.setExtra(KEYBOARD_TAG, kbdId);
Sentry.setExtra(KEYBOARD_COUNT_TAG, "" + kbdCount);
Sentry.setExtra(LANGCODE_TAG, lngCode);
Sentry.setExtra(MODEL_TAG, modelId);
}
/**
* Utility to log info and send to Sentry
* @param tag String of the caller
* @param msg String of the info message
*/
public static void LogInfo(String tag, String msg) {
if(isLogging) {
return;
}
isLogging = true;
if (msg != null && !msg.isEmpty()) {
Log.i(tag, msg);
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.captureMessage(msg, SentryLevel.INFO);
}
}
isLogging = false;
}
/**
@ -46,9 +98,15 @@ public final class KMLog {
return;
}
if(isLogging) {
return;
}
isLogging = true;
Log.i(tag, msg);
if (!DependencyUtil.libraryExists(LibraryType.SENTRY) || !Sentry.isEnabled()) {
isLogging = false;
return;
}
@ -74,7 +132,9 @@ public final class KMLog {
crumb.setData("stacktrace", trace);
}
}
tagDebugInfo();
Sentry.addBreadcrumb(crumb);
isLogging = false;
}
/**
@ -83,6 +143,10 @@ public final class KMLog {
* @param msg String of the error message
*/
public static void LogError(String tag, String msg) {
if(isLogging) {
return;
}
isLogging = true;
if (msg != null && !msg.isEmpty()) {
Log.e(tag, msg);
@ -91,9 +155,11 @@ public final class KMLog {
}
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.captureMessage(msg, SentryLevel.ERROR);
}
}
isLogging = false;
}
/**
@ -103,6 +169,10 @@ public final class KMLog {
* @param e Throwable exception
*/
public static void LogException(String tag, String msg, Throwable e) {
if(isLogging) {
return;
}
isLogging = true;
String errorMsg = "";
if (msg != null && !msg.isEmpty()) {
errorMsg = msg + "\n" + e;
@ -116,9 +186,11 @@ public final class KMLog {
}
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
Sentry.addBreadcrumb(errorMsg);
Sentry.captureException(e);
}
isLogging = false;
}
/**
@ -131,7 +203,12 @@ public final class KMLog {
*/
public static void LogExceptionWithData(String tag, String msg,
String objName, Object obj, Throwable e) {
if(isLogging) {
return;
}
isLogging = true;
if (obj != null && DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
tagDebugInfo();
String objStr = null;
try {
objStr = obj.toString();
@ -141,6 +218,10 @@ public final class KMLog {
}
// Report the original exception
LogException(tag, msg, e);
// And remove the exception-specific tagged data, lest it also be
// tracked on subsequent errors not associated with the current call.
Sentry.removeExtra(objName);
}
isLogging = false;
}
}

View file

@ -12,11 +12,11 @@ public final class VersionUtils {
public static final String TAG = "VersionUtils";
public static boolean isLocalBuild() {
return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("local");
return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("local");
}
public static boolean isTestBuild() {
return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("test");
return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("test");
}
// Utility for local and PR test builds - e.g. force check of keyboard updates

View file

@ -86,6 +86,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> Fatální chyba klávesnice s %1$s:%2$s pro jazyk %3$s . Načítání výchozí klávesnice.</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> Chyba v klávesnici %1$s:%2$s pro jazyk %3$s.</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed">Keyman vyžaduje, aby byl nainstalován WebView.“</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">Keyman vyžaduje, aby byl povolen WebView.“</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">Nainstalovat WebView</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">Povolit WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Kontroluji související slovník ke stažení</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> Nelze se připojit ke Keyman serveru a zkontrolovat, zda je možné stáhnout přidružený slovník</string>
@ -119,6 +127,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Nepodařilo se načíst stažený soubor</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Přístup k serveru se nezdařil!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager zakázán - nelze zkontrolovat aktualizace</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"Všechny zdroje jsou aktuální!"</string>
<!-- Context: General Updates -->

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for hdpi Portrait (Phone) -->
<resources>
<dimen name="banner_height">40dp</dimen>
<dimen name="keyboard_height">170dp</dimen>
</resources>

View file

@ -80,6 +80,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> មាន​បញ្ហាធ្ងន់ធ្ងរចំពោះក្ដារចុច %1$s:%2$s សម្រាប់​ភាសា %3$s។ កំពុង​ផ្ទុក​ក្ដារចុច​លំនាំដើម។</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> បញ្ហាក្នុងក្ដារចុច %1$s:%2$s សម្រាប់ភាសា %3$s។</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed">Keyman តម្រូវឱ្យដំឡើង WebView ។\"</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">Keyman តម្រូវឱ្យបើក WebView ។\"</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">ដំឡើង WebView</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">បើក WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">កំពុង​ពិនិត្យ​រក​បញ្ជី​ពាក្យ​ពាក់ព័ន្ធ​ដើម្បី​ទាញយក</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> មិនអាចភ្ជាប់ទៅម៉ាស៊ីនមេ Keyman ដើម្បីរកបញ្ជីពាក្យសម្រាប់ទាញយកបានទេ</string>
@ -113,6 +121,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">រក​មិន​ឃើញ​ឯកសារដែល​បាន​ទាញ​យក</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">មិន​អាច​ចូលប្រើ​ម៉ាស៊ីនមេ​បាន​ទេ!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">DownloadManager បានបិទដំណើរការ - មិនអាចពិនិត្យរកអាប់ដេតផ្សេងៗ</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"ធនធានទាំង​អស់​គឺ​ថ្មី​បំផុត​ហើយ!"</string>
<!-- Context: General Updates -->

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for hdpi Landscape (Phone) -->
<resources>
<dimen name="banner_height">25dp</dimen>
<dimen name="keyboard_height">100dp</dimen>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for Landscape (Extra-high-density ~320dpi) -->
<resources>
<dimen name="banner_height">30dp</dimen>
<dimen name="keyboard_height">140dp</dimen>
<dimen name="key_width">83.5dp</dimen>
<dimen name="key_height">78.5dp</dimen>
<dimen name="popup_arrow_width">38dp</dimen>
<dimen name="popup_arrow_height">14dp</dimen>
<dimen name="popup_margin">6dp</dimen>
<dimen name="popup_padding">4dp</dimen>
<dimen name="popup_offset_y">8dp</dimen>
<dimen name="help_bubble_width">125dp</dimen>
<dimen name="help_bubble_height">85dp</dimen>
<dimen name="help_bubble_offset_x">0.5dp</dimen>
<dimen name="help_bubble_offset_y">8dp</dimen>
<dimen name="fab_margin">6dp</dimen>
</resources>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for Landscape (Phone) -->
<resources>
<dimen name="banner_height">40dp</dimen>
<dimen name="keyboard_height">100dp</dimen>
<dimen name="banner_height">28dp</dimen>
<dimen name="keyboard_height">120dp</dimen>
<dimen name="key_width">49.25dp</dimen>
<dimen name="key_height">49.25dp</dimen>
<dimen name="popup_arrow_width">21dp</dimen>

View file

@ -80,6 +80,14 @@
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> ကဳဗုဒ်ယောၚ်ယာဇၞော်ဇၞော်နကဵု %1$s:%2$s သွက် ဘာသာ %3$s ။ ဂြဲပ္တိုန်မံၚ် ကဳဗုဒ်ပကတိ။</string>
<!-- Context: Fatal keyboard error. -->
<string name="fatal_keyboard_error_short" comment="Error in keyboard (keyboard ID::packageID for language)."> တၚ်ယောၚ်ယာ ပ္ဍဲကဳဗုဒ် %1$s:%2$s သွက် ဘာသာ %3$s</string>
<!-- Context: Alert Dialog body: System WebView not installed -->
<string name="body_install_webview" comment="Notification when WebView not installed">သ္ဂောံဂြဲဖျေံ Keyman ဂှ် ဒးသုၚ်စောဲ WebView ရောၚ်။</string>
<!-- Context: Alert Dialog body: System WebView installed but not enabled -->
<string name="body_enable_webview" comment="Notification when WebView installed but not enabled">သ္ဂောံပံက် Keyman ဂှ် ဒးသုၚ်စောဲ WebView ရောၚ်။</string>
<!-- Context Button label: Install WebView -->
<string name="label_install_webview" comment="Button label to install webview">ပ္တိုန်စုတ် WebView ရောၚ်</string>
<!-- Context Button label: Enable WebView -->
<string name="label_enable_webview" comment="Button label to enable webview">ပံက် WebView</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">စၟဳစၟတ်ဒၟံၚ် အဘိဓာန် မဆက်စပ် သ္ဂောံဂြဲဖျေံ</string>
<string name="cannot_query_associated_model" comment="Cannot reach server to check if there's an available dictionary to download"> သ္ဂောံဂြဲဖျေံ အဘိဓာန်မဆက်စပ်တံဂှ် ဆက်စၠောံကု သာပါ ကဳမာန်ဟွံဂွံ</string>
@ -113,6 +121,8 @@
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">ဝှာၚ်မဂၠောၚ်ဂြဲ​ဖျေံလဝ်ဂှ် ကလေၚ်​ကေတ်ဟွံဂွံ</string>
<!-- Context: General Updates -->
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">ဆက်စၠောံကုသာပါဟွံအံၚ်ဇၞး!</string>
<!-- Context: Update-check failure notification -->
<string name="update_check_download_manager_disabled" comment="Notification that a system service needed for updates is disabled">စက်ဂြဲကၟာတ်လဝ်ရောၚ် - စၟဳစၟတ်တၚ်သမၠုၚ်ဟွံဂွံ</string>
<!-- Context: General Updates -->
<string name="update_check_current" comment="Notification that all resources are up to date">"တံရိုဟ်ဖအိုတ်ဂှ် ဒှ်တၟိရ!"</string>
<!-- Context: General Updates -->

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for Landscape (Tablet 7") -->
<resources>
<dimen name="banner_height">50dp</dimen>
<dimen name="keyboard_height">140dp</dimen>
<dimen name="banner_height">45dp</dimen>
<dimen name="keyboard_height">200dp</dimen>
<dimen name="key_width">73.5dp</dimen>
<dimen name="key_height">58.5dp</dimen>
<dimen name="popup_arrow_width">32dp</dimen>

View file

@ -2,7 +2,7 @@
<!-- Dimens for Landscape (Tablet 10") -->
<resources>
<dimen name="banner_height">60dp</dimen>
<dimen name="keyboard_height">200dp</dimen>
<dimen name="keyboard_height">280dp</dimen>
<dimen name="key_width">98.5dp</dimen>
<dimen name="key_height">98.5dp</dimen>
<dimen name="popup_arrow_width">42dp</dimen>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dimens for xhdpi Portrait (Phone) -->
<resources>
<dimen name="banner_height">50dp</dimen>
<dimen name="keyboard_height">270dp</dimen>
</resources>

View file

@ -5,10 +5,10 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.9.2'
// io.sentry:sentry-android-gradle-plugin not available for library project
classpath 'io.sentry:sentry-android:7.8.0'
classpath 'name.remal:gradle-plugins:1.5.0'
classpath 'name.remal:gradle-plugins:1.9.2'
}
}

View file

@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
# ################################ Main script ################################
@ -28,8 +29,7 @@ builder_describe "Builds Keyman Engine for Android." \
"configure" \
"build" \
"test Runs lint and unit tests." \
":engine Builds Engine" \
"--ci Don't start the Gradle daemon. For CI"
":engine Builds Engine"
# parse before describe_outputs to check debug flags
builder_parse "$@"
@ -37,7 +37,7 @@ builder_parse "$@"
if builder_is_debug_build; then
builder_heading "### Debug config ####"
CONFIG="debug"
BUILD_FLAGS="assembleDebug -x lint -x test"
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testDebugUnitTest\']"
fi
@ -51,7 +51,7 @@ builder_describe_outputs \
# Parse args
DAEMON_FLAG=
if builder_has_option --ci; then
if builder_is_ci_build; then
DAEMON_FLAG=--no-daemon
fi
@ -82,8 +82,6 @@ if builder_start_action build:engine; then
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/kmwosk.css"
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/globe-hint.css"
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/keymanweb-osk.ttf"
cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$ENGINE_ASSETS/sentry.min.js"
cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
echo "Copying es6-shim polyfill"
@ -98,7 +96,7 @@ fi
if builder_start_action test:engine; then
if builder_has_option --ci; then
if builder_is_ci_build; then
# Report JUnit test results to CI
echo "$JUNIT_RESULTS"
fi

View file

@ -1,5 +1,6 @@
#Wed Apr 30 11:17:37 ICT 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -141,6 +141,10 @@ android {
aaptOptions {
noCompress "kmp"
}
buildFeatures {
buildConfig = true
}
```
5. After the `android {}` object, include the following:
````gradle
@ -154,7 +158,7 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
api (name:'keyman-engine', ext:'aar')
implementation 'androidx.preference:preference:1.2.1'

View file

@ -36,7 +36,7 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
api(name: 'keyman-engine', ext: 'aar')
implementation 'androidx.preference:preference:1.2.1'

View file

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.9.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
################################ Main script ################################
@ -22,8 +23,7 @@ builder_describe "Build KMSample1 app for Android." \
"configure" \
"build" \
"test" \
":app KMSample1" \
"--ci Don't start the Gradle daemon. Use for CI"
":app KMSample1"
# parse before describe_outputs to check debug flags
builder_parse "$@"
@ -46,7 +46,7 @@ builder_describe_outputs \
# Parse args
if builder_has_option --ci; then
if builder_is_ci_build; then
SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon"
fi

View file

@ -18,4 +18,8 @@
# org.gradle.parallel=true
android.enableJetifier=true
android.useAndroidX=true
# AGP 8.0 generates R classes for resources defined in the current module only.
android.nonTransitiveRClass=false
org.gradle.jvmargs=-Xmx1024m

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View file

@ -35,7 +35,7 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
api (name:'keyman-engine', ext:'aar')
implementation 'androidx.preference:preference:1.2.1'

View file

@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.android.tools.build:gradle:8.9.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
################################ Main script ################################
@ -22,8 +23,7 @@ builder_describe "Build KMSample2 app for Android." \
"configure" \
"build" \
"test" \
":app KMSample2" \
"--ci Don't start the Gradle daemon. Use for CI"
":app KMSample2"
# parse before describe_outputs to check debug flags
builder_parse "$@"
@ -47,7 +47,7 @@ builder_describe_outputs \
# Parse args
if builder_has_option --ci; then
if builder_is_ci_build; then
SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon"
fi

View file

@ -18,4 +18,8 @@
# org.gradle.parallel=true
android.enableJetifier=true
android.useAndroidX=true
# AGP 8.0 generates R classes for resources defined in the current module only.
android.nonTransitiveRClass=false
org.gradle.jvmargs=-Xmx1024m

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View file

@ -14,15 +14,19 @@ android {
noCompress "kmp"
}
buildFeatures {
buildConfig = true
}
defaultConfig {
applicationId "com.keyman.android.tests.keyboardHarness"
minSdkVersion 21
targetSdkVersion 34
// VERSION_CODE and VERSION_NAME from version.gradle
versionCode VERSION_CODE as Integer
versionName VERSION_NAME
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle
versionCode KEYMAN_VERSION_CODE as Integer
versionName KEYMAN_VERSION_NAME
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
}
buildTypes {
release {
@ -42,7 +46,7 @@ repositories {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
api (name:'keyman-engine', ext:'aar')
implementation 'androidx.preference:preference:1.2.1'

View file

@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'com.android.tools.build:gradle:8.9.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
################################ Main script ################################
@ -22,8 +23,7 @@ builder_describe "Build KeyboardHarness test app for Android." \
"configure" \
"build" \
"test" \
":app KeyboardHarness" \
"--ci Don't start the Gradle daemon. Use for CI"
":app KeyboardHarness"
# parse before describe outputs to check debug flags
builder_parse "$@"
@ -33,7 +33,7 @@ ARTIFACT="app-release-unsigned.apk"
if builder_is_debug_build; then
builder_heading "### Debug config ####"
CONFIG="debug"
BUILD_FLAGS="assembleDebug -x lint -x test"
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
ARTIFACT="app-$CONFIG.apk"
fi
@ -55,7 +55,7 @@ SHLVL=0
# Parse args
# Build flags that apply to all targets
if builder_has_option --ci; then
if builder_is_ci_build; then
BUILD_FLAGS="$BUILD_FLAGS -no-daemon"
TEST_FLAGS="$TEST_FLAGS -no-daemon"
fi

View file

@ -19,3 +19,6 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1024m
# AGP 8.0 generates R classes for resources defined in the current module only.
android.nonTransitiveRClass=false

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View file

@ -12,10 +12,10 @@ android {
minSdkVersion 21
targetSdkVersion 34
// VERSION_CODE and VERSION_NAME from version.gradle
versionCode VERSION_CODE as Integer
versionName VERSION_NAME
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle
versionCode KEYMAN_VERSION_CODE as Integer
versionName KEYMAN_VERSION_NAME
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
}
buildTypes {
release {

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -2,6 +2,11 @@
#
# Build Keyman Engine for Android, Keyman for Android, OEM FirstVoices Android app,
# Samples: KMsample1 and KMSample2, Test - KeyboardHarness
#
# OEM FirstVoices Android app requires the following env vars set:
# RELEASE_OEM should be set
# RELEASE_OEM_FIRSTVOICES should be true
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
@ -9,9 +14,22 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
################################ Main script ################################
#
# Restrict available OEM publish targets to those that can be built on the current system
#
oemtargets=()
if [ ! -z "${RELEASE_OEM+x}" ]; then
if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
oemtargets+=(":fv=../oem/firstvoices/android OEM FirstVoices for Android app")
fi
fi
builder_describe \
"Build Keyman Engine for Android, Keyman for Android, and FirstVoices Android app." \
"@/resources/tools/check-markdown test:help" \
@ -20,7 +38,8 @@ builder_describe \
build \
test \
"publish Publishes symbols to Sentry and the APKs to the Play Store." \
--ci+ \
"archive Copy release artifacts to upload/ and rsync to downloads.keyman" \
"--ci+ Deprecated build option. Remove in 20.0" \
--upload-sentry+ \
":engine=KMEA Keyman Engine for Android" \
":app=KMAPro Keyman for Android" \
@ -28,7 +47,7 @@ builder_describe \
":sample1=Samples/KMSample1 Sample app: KMSample1" \
":sample2=Samples/KMSample2 Sample app: KMSample2" \
":keyboardharness=Tests/KeyboardHarness Test app: KeyboardHarness" \
":fv=../oem/firstvoices/android OEM FirstVoices for Android app"
"${oemtargets[@]}" \
builder_parse "$@"
@ -53,4 +72,51 @@ function do_test_help() {
builder_run_action test:help do_test_help
builder_run_child_actions publish
builder_run_child_actions publish
# Copy release artifacts to upload/ and rsync to downloads.keyman.com
if builder_start_action archive; then
UPLOAD_PATH="$KEYMAN_ROOT/android/upload/${KEYMAN_VERSION}"
KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip"
KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk"
FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk"
ZIP_FILE="${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}"
ZIP_FLAGS=("-q" "-r") # quiet, recursive
mkdir -p "${UPLOAD_PATH}"
# Create Keyman Engine for Android archive
builder_echo "Copying Keyman Engine for Android into ${UPLOAD_PATH}..."
cd "${UPLOAD_PATH}"
cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" ./
add_zip_files "${ZIP_FILE}" "${ZIP_FLAGS[@]}" "keyman-engine.aar"
rm -f "keyman-engine.aar"
builder_echo "Copying Keyman Engine for Android Sample projects into ${UPLOAD_PATH}..."
cp -rf "${KEYMAN_ROOT}/android/Samples" ./
add_zip_files "${ZIP_FILE}" "-x@../../zip-excludes" "${ZIP_FLAGS[@]}" "Samples"
rm -rf "Samples"
# Copy release APK
cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" ./
# FirstVoices app
if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" ./
fi
#
# Write download info files
#
cd "${UPLOAD_PATH}"
write_download_info "Keyman Engine for Android" "${KEYMAN_ENGINE_ANDROID_ZIP}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
write_download_info "Keyman for Android" "${KEYMAN_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
write_download_info "FirstVoices Keyboards" "${FIRSTVOICES_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
fi
builder_finish_action success archive
fi

View file

@ -3,14 +3,19 @@ title: KMManager.applyKeyboardHeight()
---
## Summary
The **applyKeyboardHeight()** method sets the height of the keyboard frame for
the device's current [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali)
(portrait vs landscape).
The **applyKeyboardHeight()** method sets the height of the keyboard frame for
the device's current [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali)
(portrait vs landscape) or a specified orientation.
## Syntax
```java
KMManager.applyKeyboardHeight(Context context, int height)
```
or
```java
KMManager.applyKeyboardHeight(Context context, int height, int orientation)
```
### Parameters
@ -18,25 +23,28 @@ KMManager.applyKeyboardHeight(Context context, int height)
: The context
`height`
: The height of the keyboard frame in *density-independent pixels (dp)*
: The height of the keyboard frame in *density-independent pixels (dp)*. Pass `KMManager.KeyboardHeight_Reset` to reset the keyboard height (for current orientation) to device-specific defaults.
`orientation` _(Optional)_
: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to change the size of the keyboard in the other (non-current) orientation. If `orientation` is not defined, keyboard height is set for the current device orientation.
## Description
Use this method when you want to increase or decrease the keyboard height for
the device in the current screen orientation. This height is independent from
Use this method when you want to increase or decrease the keyboard height for
the device in the current screen orientation. This height is independent from
the height of the suggestion banner frame.
For reference, here's a table of the default Keyman keyboard heights for various devices and screen orientation.
Device Type and Screen Orientation | Default height (dp) |
|-----------------------------------|---------------------|
| Default handset in portrait | 205 |
| Default handset in landscape | 150 |
| 7" tablet in portrait | 305 |
| 7" tablet in landscape | 200 |
| 10" tablet in portrait | 405 |
| 10" tablet in landscape | 300 |
**Note:** This new keyboard height would be applied for all platforms, so an
Device Type | Default height (dp)<br>Portrait | Default height (dp)<br>Landscape |
|-------------------------------|---------------------------------|----------------------------------|
| Default handset (160dpi) | 205 | 120 |
| High Density handset (240dpi) | 170 | 100 |
| Extra High Density handset (320dpi) | 270 | 140 |
| Extra Extra Extra<br>High Density handset (640 dpi) | 270 | 140 |
| 7" tablet | 305 | 200 |
| 10" tablet | 405 | 280 |
**Note:** This new keyboard height would be applied for all platforms, so an
adjusted keyboard height for a phone would appear too small for a tablet.
## Examples
@ -50,5 +58,23 @@ The following script illustrates the use of `applyKeyboardHeight()`:
KMManager.applyKeyboardHeight(this, newKeyboardHeight);
```
or
```java
import android.content.res.Configuration;
...
// Increase the Keyman keyboard height for landscape mode to 250dp (default Keyman value for most phones is 100dp)
int newKeyboardHeight = 250;
KMManager.applyKeyboardHeight(this, newKeyboardHeight, Configuration.ORIENTATION_LANDSCAPE);
```
The following script illustrates the use of `applyKeyboardHeight()` to reset keyboard height to default:
```java
// Reset keyboard to default height
KMManager.applyKeyboardHeight(this, KMManager.KeyboardHeight_Reset);
```
## See also
* [getKeyboardHeight()](getKeyboardHeight)

View file

@ -0,0 +1,55 @@
---
title: KMManager.getDefaultKeyboardHeight()
---
## Summary
The **`getDefaultKeyboardHeight()`** method returns the default height of the keyboard frame for the current or specefied orientation.
## Syntax
``` javascript
KMManager.getDefaultKeyboardHeight(Context context)
```
or
``` javascript
KMManager.getDefaultKeyboardHeight(int orientation)
```
### Parameters (Choose One)
`context` _(Alternate)_
: The context.
`orientation` _(Alternate)_
: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to get the size of the keyboard in the other (non-current) orientation. If `orientation` parameter is not passed in, keyboard height is returned for the current device orientation.
### Returns
Returns the default height of the keyboard frame in *density-independent pixels (dp)*.
## Description
Use this method to get the default height of the keyboard frame.
## Examples
### Example: Using `getDefaultKeyboardHeight()`
The following script illustrate the use of `getDefaultKeyboardHeight()`:
``` javascript
int keyboardHeight = KMManager.getDefaultKeyboardHeight(this);
```
or
```java
import android.content.res.Configuration;
...
// Get the current Keyman keyboard height for landscape mode.
int keyboardHeightLandscape = KMManager.getDefaultKeyboardHeight( Configuration.ORIENTATION_LANDSCAPE);
```
## See also
- [applyKeyboardHeight()](applyKeyboardHeight)

View file

@ -12,12 +12,19 @@ frame.
``` javascript
KMManager.getKeyboardHeight(Context context)
```
or
``` javascript
KMManager.getKeyboardHeight(Context context, int orientation)
```
### Parameters
`context`
: The context.
`orientation` _(Optional)_
: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to get the size of the keyboard in the other (non-current) orientation. If `orientation` parameter is not passed in, keyboard height is returned for the current device orientation.
### Returns
Returns the height of the keyboard frame in *density-independent pixels
@ -36,7 +43,15 @@ The following script illustrate the use of `getKeyboardHeight()`:
``` javascript
int keyboardHeight = KMManager.getKeyboardHeight(this);
```
or
```java
import android.content.res.Configuration;
...
// Get the current Keyman keyboard height for landscape mode.
int keyboardHeightLandscape = KMManager.getKeyboardHeight(this, Configuration.ORIENTATION_LANDSCAPE);
```
## See also
- [applyKeyboardHeight](applyKeyboardHeight)
- [applyKeyboardHeight()](applyKeyboardHeight)

View file

@ -28,7 +28,7 @@ Use this method to specify the HTML content to display in the banner to theme yo
If the banner theme references assets (like .svg or .css files), ensure you've also called [copyHTMLBannerAssets()](copyHTMLBannerAssets).
Note: The HTML banner needs to be updated whenever the keyboard is reloaded, so call this in `SystemKeyboard.onInitializeInterface(()`.
Note: The HTML banner needs to be updated whenever the keyboard is reloaded, so call this in `SystemKeyboard.onKeyboardShown()`.
## Examples

View file

@ -6,11 +6,11 @@ title: Adjust Keyboard Height - Keyman for Android Help
This menu is used to adjust the height of the on-screen keyboard (OSK), which does not include the suggestion banner.
Drag the blank OSK at the bottom of the screen to increase or decrease the keyboard height for the device's current orientation (portrait vs landscape). The adjusted keyboard heights are automatically saved when the blank OSK is released.
Drag the blank OSK at the bottom of the screen to increase or decrease the keyboard height for the device's current orientation (portrait vs landscape). Turn the device 90 degrees to the alternate orientation and repeat the process. The adjusted keyboard heights are automatically saved when the blank OSK is released.
![](../../android_images/blank-osk.png)
### Reset to Default Height
Press the button to reset the OSK height to the default values.
Press the button to reset the OSK height (for the current orientation) to the default device-specific height.
![](../../android_images/reset-to-default.png)

View file

@ -7,9 +7,9 @@
//
def getVersionCode = { ->
String env_version_major = System.getenv("VERSION_MAJOR")
String env_version_minor = System.getenv("VERSION_MINOR")
String env_version_patch = System.getenv("VERSION_PATCH")
String env_version_major = System.getenv("KEYMAN_VERSION_MAJOR")
String env_version_minor = System.getenv("KEYMAN_VERSION_MINOR")
String env_version_patch = System.getenv("KEYMAN_VERSION_PATCH")
if(env_version_patch != null && env_version_minor != null && env_version_patch != null) {
// Version code is, for 17.1.33: 1810033. This supports up to 9999
// builds for a given major.minor version. We only support up to 9 minor
@ -36,10 +36,10 @@ def getVersionCode = { ->
}
def getVersionMD = { ->
String env_version = System.getenv("VERSION")
String env_version = System.getenv("KEYMAN_VERSION")
if (env_version != null) {
// If building from script, we have build number in VERSION
println "Using build $env_version from VERSION"
// If building from script, we have build number in KEYMAN_VERSION
println "Using build $env_version from KEYMAN_VERSION"
return "$env_version"
} else {
String version_md = file("$rootPath/../VERSION.md").text.trim()
@ -48,15 +48,15 @@ def getVersionMD = { ->
}
def getVersionName = { ->
String env_version = System.getenv("VERSION_WITH_TAG")
String env_version = System.getenv("KEYMAN_VERSION_WITH_TAG")
if (env_version != null) {
// If building from script, we have build number in VERSION_WITH_TAG
println "Using build $env_version from VERSION_WITH_TAG"
// If building from script, we have build number in KEYMAN_VERSION_WITH_TAG
println "Using build $env_version from KEYMAN_VERSION_WITH_TAG"
return "$env_version"
} else {
// Building probably from IDE, so let's use VERSION.md and TIER.md directly
// This is a close match with VERSION_WITH_TAG, except that 'stable' tier
// is not normally included with VERSION_WITH_TAG
// This is a close match with KEYMAN_VERSION_WITH_TAG, except that 'stable' tier
// is not normally included with KEYMAN_VERSION_WITH_TAG
String version_md = file("$rootPath/../VERSION.md").text.trim()
String tier_md = file("$rootPath/../TIER.md").text.trim()
println "Using build $version_md-$tier_md-local from project VERSION.md/TIER.md"
@ -65,10 +65,10 @@ def getVersionName = { ->
}
def getVersionEnvironment = { ->
String env_environment = System.getenv("VERSION_ENVIRONMENT")
String env_environment = System.getenv("KEYMAN_VERSION_ENVIRONMENT")
if (env_environment != null) {
// If building from script, we have build number in VERSION_ENVIRONMENT
println "Using $env_environment from VERSION_ENVIRONMENT"
// If building from script, we have build number in KEYMAN_VERSION_ENVIRONMENT
println "Using $env_environment from KEYMAN_VERSION_ENVIRONMENT"
return "$env_environment"
} else {
// Building probably from IDE, so use "local"
@ -78,23 +78,23 @@ def getVersionEnvironment = { ->
}
def getVersionGitTag = { ->
String env_git_tag = System.getenv("VERSION_GIT_TAG")
String env_git_tag = System.getenv("KEYMAN_VERSION_GIT_TAG")
if (env_git_tag != null) {
println "Using $env_git_tag from VERSION_GIT_TAG"
println "Using $env_git_tag from KEYMAN_VERSION_GIT_TAG"
return "$env_git_tag"
} else {
// Building probably from IDE
println "Using release@$VERSION_NAME"
return "release@$VERSION_NAME"
println "Using release@$KEYMAN_VERSION_NAME"
return "release@$KEYMAN_VERSION_NAME"
}
}
ext {
VERSION_CODE=getVersionCode()
VERSION_MD=getVersionMD()
VERSION_NAME=getVersionName()
VERSION_ENVIRONMENT=getVersionEnvironment()
VERSION_TAG=System.getenv("VERSION_TAG")
VERSION_GIT_TAG=getVersionGitTag()
KEYMAN_VERSION_CODE=getVersionCode()
KEYMAN_VERSION_MD=getVersionMD()
KEYMAN_VERSION_NAME=getVersionName()
KEYMAN_VERSION_ENVIRONMENT=getVersionEnvironment()
KEYMAN_VERSION_TAG=System.getenv("KEYMAN_VERSION_TAG")
KEYMAN_VERSION_GIT_TAG=getVersionGitTag()
}
//println "version.gradle: VERSION_TAG: " + VERSION_TAG
//println "version.gradle: KEYMAN_VERSION_TAG: " + KEYMAN_VERSION_TAG

1
android/zip-excludes Normal file
View file

@ -0,0 +1 @@
Samples/*/build.sh

View file

@ -2,6 +2,14 @@
Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/17.0/kps.xsd
## 2025-04-02 18.0
* Version 18.0 deprecates the following fields, and kmc-package does not use them (#13600):
- Keyboards/Keyboard/Version
- Keyboards/Keyboard/Name
- Keyboards/Keyboard/RTL
- LexicalModels/LexicalModel/Name
- LexicalModels/LexicalModel/RTL (Note: was never read or written by any Keyman tooling)
## 2023-10-19 17.0
* Version 17.0 adds:
- LicenseFile - a .md file, usually named LICENSE.md
@ -9,13 +17,13 @@ Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/
- 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
- Keyboards/Keyboard/WebOSKFonts - a list of font filenames (not necessarily in package) suitable for rendering the on screen keyboard
- Keyboards/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
* Removes LexicalModels/LexicalModel/Version, as it was never read or written
## 2021-07-19 7.0
* Initial version 7.0

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
.kps file schema, version 7.0
.kps file schema, version 18.0
Copyright (C) SIL International
Supports KPS files for Keyman Developer 7+
Expects FileVersion 7.0
Expects FileVersion '7.0' or '12.0'
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
@ -119,10 +119,10 @@
<xs:element minOccurs="0" maxOccurs="unbounded" name="Keyboard">
<xs:complexType>
<xs:all>
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" /> <!-- deprecated: 18.0 -->
<xs:element minOccurs="0" maxOccurs="1" name="ID" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Version" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="RTL" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Version" type="xs:string" /> <!-- deprecated: 18.0 -->
<xs:element minOccurs="0" maxOccurs="1" name="RTL" type="xs:string" /> <!-- deprecated: 18.0 -->
<xs:element minOccurs="0" maxOccurs="1" name="OSKFont" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="DisplayFont" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Languages" type="km-keyboard-languages" />
@ -143,9 +143,9 @@
<xs:element minOccurs="0" maxOccurs="unbounded" name="LexicalModel">
<xs:complexType>
<xs:all>
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" /> <!-- deprecated: 18.0 -->
<xs:element minOccurs="0" maxOccurs="1" name="ID" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="RTL" type="xs:string" />
<xs:element minOccurs="0" maxOccurs="1" name="RTL" type="xs:string" /> <!-- deprecated: 18.0 -->
<xs:element minOccurs="0" maxOccurs="1" name="Languages" type="km-keyboard-languages" />
</xs:all>
</xs:complexType>

View file

@ -5,6 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
targets=()
# Build list of available targets from subfolders, if none specified
for d in */; do
@ -29,7 +31,7 @@ builder_parse "$@"
function zipsource() {
local target="$1"
pushd "$1" > /dev/null
7z a -r -x!build -x"!$target.kpj.user" "${target}_source.zip" .
add_zip_files "${target}_source.zip" -x@../zip-excludes -q -r . # -q quiet, -r recursive
popd > /dev/null
}
@ -40,10 +42,10 @@ function build_index() {
<html>
<head>
<meta charset='utf-8'>
<title>Test Keyboards - $VERSION_WITH_TAG</title>
<title>Test Keyboards - $KEYMAN_VERSION_WITH_TAG</title>
</head>
<body>
<h1>Test Keyboards - $VERSION_WITH_TAG</h1>
<h1>Test Keyboards - $KEYMAN_VERSION_WITH_TAG</h1>
<ul>
EOF
@ -90,4 +92,4 @@ function build() {
fi
}
builder_run_action build build
builder_run_action build build

View file

@ -0,0 +1,3 @@
build/
*.kpj.user
*_source.zip

View file

@ -13,17 +13,9 @@ import vm from 'vm';
// Ensure that we can successfully load the module & apply kmwLength, as it's
// needed for some of the unit tests.
// // Verify that the KMW string extensions are loaded via side-effect.
import { extendString } from '@keymanapp/web-utils';
import { createRequire } from "module";
import { fileURLToPath } from 'url';
// Ensure that our KMW string-extensions activate.
extendString();
assert.ok('💩'.kmwLength);
/**
* Creates a MessageEvent (for inter-worker communication), with the given data payload.
*

View file

@ -3,6 +3,7 @@
"description": "Common test resources for Keyman's JS & TS-based components & products",
"type": "module",
"license": "MIT",
"scripts": {},
"devDependencies": {
"@keymanapp/resources-gosh": "*",
"typescript": "^5.4.5"

View file

@ -1,5 +1,5 @@
import * as fs from 'fs';
import * as rd from 'readline';
import * as fs from 'node:fs';
import * as rd from 'node:readline';
export default async function hextobin(inputFilename: string, outputFilename?: string, options?: {silent?: boolean}): Promise<Uint8Array> {

View file

@ -18,7 +18,6 @@
"homepage": "https://github.com/keymanapp/keyman#readme",
"devDependencies": {
"@keymanapp/resources-gosh": "*",
"mocha": "^10.0.0",
"mocha-teamcity-reporter": "^4.0.0",
"typescript": "^5.4.5"
},

View file

@ -1,5 +1,5 @@
import * as fs from 'fs';
import path from 'path';
import * as fs from 'node:fs';
import path from 'node:path';
import convertSourceMap from 'convert-source-map'; // Transforms sourcemaps among various common formats.
// Base64, stringified-JSON, end-of-file comment...

View file

@ -33,16 +33,16 @@ function do_build() {
// Note: does not use the 'default' keyword so that the export name is
// correct when converted to a CommonJS module with \`esbuild\`.
export class KEYMAN_VERSION {
static readonly VERSION = \"$VERSION\";
static readonly VERSION_RELEASE =\"$VERSION_RELEASE\";
static readonly VERSION_MAJOR = \"$VERSION_MAJOR\";
static readonly VERSION_MINOR = \"$VERSION_MINOR\";
static readonly VERSION_PATCH = \"$VERSION_PATCH\";
static readonly TIER =\"$TIER\";
static readonly VERSION_TAG = \"$VERSION_TAG\";
static readonly VERSION_WITH_TAG = \"$VERSION_WITH_TAG\";
static readonly VERSION_ENVIRONMENT = \"$VERSION_ENVIRONMENT\";
static readonly VERSION_GIT_TAG = \"$VERSION_GIT_TAG\";
static readonly VERSION = \"$KEYMAN_VERSION\";
static readonly VERSION_RELEASE =\"$KEYMAN_VERSION_RELEASE\";
static readonly VERSION_MAJOR = \"$KEYMAN_VERSION_MAJOR\";
static readonly VERSION_MINOR = \"$KEYMAN_VERSION_MINOR\";
static readonly VERSION_PATCH = \"$KEYMAN_VERSION_PATCH\";
static readonly TIER =\"$KEYMAN_TIER\";
static readonly VERSION_TAG = \"$KEYMAN_VERSION_TAG\";
static readonly VERSION_WITH_TAG = \"$KEYMAN_VERSION_WITH_TAG\";
static readonly VERSION_ENVIRONMENT = \"$KEYMAN_VERSION_ENVIRONMENT\";
static readonly VERSION_GIT_TAG = \"$KEYMAN_VERSION_GIT_TAG\";
}
// Also provides it as a 'default' export.

View file

@ -11,6 +11,7 @@
"/build/"
],
"license": "MIT",
"scripts": {},
"type": "module",
"devDependencies": {
"typescript": "^5.4.5"

View file

@ -16,8 +16,5 @@ module.exports = {
files: "src/**/*.ts",
extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
},
],
rules: {
"prefer-const": "off", // TODO: enable this once infrastructure is in place and cleanup the problem cases
},
]
};

View file

@ -83,7 +83,7 @@ function do_configure() {
function do_test() {
local MOCHA_FLAGS=
if [[ "${TEAMCITY_GIT_PATH:-}" != "" ]]; then
if builder_is_running_on_teamcity; then
# we're running in TeamCity
MOCHA_FLAGS="-reporter mocha-teamcity-reporter"
fi

View file

@ -44,7 +44,6 @@
"c8": "^7.12.0",
"chalk": "^2.4.2",
"hexy": "^0.3.4",
"mocha": "^8.4.0",
"typescript": "^5.4.5"
},
"mocha": {

View file

@ -54,7 +54,7 @@ export const
export function keymanTargetsFromString(targets: string, options?: {expandTargets?: boolean}): KeymanTarget[] {
let result = new Set<KeymanTarget>(<KeymanTarget[]>targets.split(/ +/));
const result = new Set<KeymanTarget>(<KeymanTarget[]>targets.split(/ +/));
if(options?.expandTargets) {
if(result.has(KeymanTarget.any)) {
return AllKeymanTargets;

View file

@ -60,14 +60,13 @@ export class KmxFileReader {
}
public read(source: Uint8Array): KEYBOARD {
let binaryKeyboard: BUILDER_COMP_KEYBOARD;
let kmx = new KMXFile();
binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(source);
const kmx = new KMXFile();
const binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(source);
if(binaryKeyboard.dwIdentifier != KMXFile.FILEID_COMPILED) {
throw new KmxFileReaderError(`Not a .kmx file: header does not contain FILEID_COMPILED`);
}
let result = new KEYBOARD();
const result = new KEYBOARD();
result.fileVersion = binaryKeyboard.dwFileVersion;
result.flags = binaryKeyboard.dwFlags;
result.hotkey = binaryKeyboard.dwHotKey;
@ -88,7 +87,7 @@ export class KmxFileReader {
this.readGroupsAndRules(binaryKeyboard, kmx, source, result);
// Process system stores once we have all stores and groups loaded
for(let store of result.stores) {
for(const store of result.stores) {
this.processSystemStore(store, result);
}
@ -109,8 +108,8 @@ export class KmxFileReader {
let offset = binaryKeyboard.dpGroupArray;
for (let i = 0; i < binaryKeyboard.cxGroupArray; i++) {
const data = new Uint8Array(source.buffer, source.byteOffset + offset);
let binaryGroup = kmx.COMP_GROUP.fromBuffer(data);
let group = new GROUP();
const binaryGroup = kmx.COMP_GROUP.fromBuffer(data);
const group = new GROUP();
group.dpMatch = this.readString(source, binaryGroup.dpMatch);
group.dpName = this.readString(source, binaryGroup.dpName);
group.dpNoMatch = this.readString(source, binaryGroup.dpNoMatch);
@ -120,8 +119,8 @@ export class KmxFileReader {
let keyOffset = binaryGroup.dpKeyArray;
for (let j = 0; j < binaryGroup.cxKeyArray; j++) {
const keyData = new Uint8Array(source.buffer, source.byteOffset + keyOffset);
let binaryKey = kmx.COMP_KEY.fromBuffer(keyData);
let key = new KEY();
const binaryKey = kmx.COMP_KEY.fromBuffer(keyData);
const key = new KEY();
key.Key = binaryKey.Key;
key.Line = binaryKey.Line;
key.ShiftFlags = binaryKey.ShiftFlags;
@ -140,8 +139,8 @@ export class KmxFileReader {
let offset = binaryKeyboard.dpStoreArray;
for (let i = 0; i < binaryKeyboard.cxStoreArray; i++) {
const data = new Uint8Array(source.buffer, source.byteOffset + offset);
let binaryStore = kmx.COMP_STORE.fromBuffer(data);
let store = new STORE();
const binaryStore = kmx.COMP_STORE.fromBuffer(data);
const store = new STORE();
store.dwSystemID = binaryStore.dwSystemID;
store.dpName = this.readString(source, binaryStore.dpName);
store.dpString = this.readString(source, binaryStore.dpString);

View file

@ -1,5 +1,5 @@
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { DependencySections, StrsItem, UsetItem } from './kmx-plus.js';
import { DependencySections, StrsItem, StrsOptions, UsetItem } from './kmx-plus.js';
import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js';
import * as util from '../../util/util.js';
import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE;
@ -33,7 +33,7 @@ export class ElementString extends Array<ElemElement> {
* @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem.
* If it is a string, will be interpreted per reorder element rules.
*/
static fromStrings(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) : ElementString {
static fromStrings(sections: DependencySections, options: StrsOptions, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) : ElementString {
// the returned array
const array = new ElementString();
if(!source) {
@ -69,7 +69,7 @@ export class ElementString extends Array<ElemElement> {
}
for(let i = 0; i < items.length; i++) {
let elem = new ElemElement();
const elem = new ElemElement();
const item = items[i];
let typeFlag = 0;
if (item.type === ElementType.uset) {
@ -81,21 +81,21 @@ export class ElementString extends Array<ElemElement> {
// error. So we can just exit here.
return null; // UnicodeSet error
}
const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges);
const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.x);
if (!uset) {
return null; // UnicodeSet error already thrown
return null; // UnicodeSet error already added to callback
}
elem.uset = sections.uset.allocUset(uset, sections);
elem.value = sections.strs.allocString('', {singleOk: true}); // no string
elem.uset = sections.uset.allocUset(uset, sections, options?.x);
elem.value = sections.strs.allocString('', {...options, singleOk: true}); // no string
} else if (item.type === ElementType.codepoint || item.type === ElementType.escaped || item.type === ElementType.string) {
// some kind of a string
let str = item.segment;
if (item.type === ElementType.escaped && !MATCH_HEX_ESCAPE.test(str)) {
str = unescapeOneQuadString(str);
// TODO-LDML: any other escape forms here?
elem.value = sections.strs.allocString(str, { singleOk: true });
elem.value = sections.strs.allocString(str, { ...options, singleOk: true });
} else {
elem.value = sections.strs.allocString(str, { unescape: true, singleOk: true });
elem.value = sections.strs.allocString(str, { ...options, unescape: true, singleOk: true });
}
// Now did we end up with one char or no?
if (elem.value.isOneChar) {

View file

@ -41,14 +41,14 @@ export class Elem extends Section {
strings: ElementString[] = [];
constructor(sections: DependencySections) {
super();
this.strings.push(ElementString.fromStrings(sections, '')); // C7043: null element string
this.strings.push(ElementString.fromStrings(sections, {}, '')); // C7043: null element string
}
/**
* @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem.
* If it is a string, will be interpreted per reorder element ruls.
*/
allocElementString(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString {
let s = ElementString.fromStrings(sections, source, order, tertiary, tertiary_base, prebase);
allocElementString(sections: DependencySections, options: StrsOptions, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString {
const s = ElementString.fromStrings(sections, options, source, order, tertiary, tertiary_base, prebase);
if (!s) return s;
let result = this.strings.find(item => item.isEqual(s));
if(result === undefined) {
@ -135,6 +135,18 @@ export class StrsItem {
isEqual(a: StrsItem): boolean {
return a.value === this.value && a.char === this.char;
}
private _context: any = undefined;
/** add any context from the options to this strsitem */
setContext(opts?: StrsOptions) {
// At present, there's only a single piece of context available
this._context = this._context || opts?.x;
}
get context() : any {
return this._context;
}
};
/**
@ -161,6 +173,8 @@ export interface StrsOptions {
nfd?: boolean;
/** string can be stored as a single CharStrsItem, not in strs table. */
singleOk?: boolean;
/** optional context */
x?: any;
};
export class Strs extends Section {
@ -192,6 +206,8 @@ export class Strs extends Section {
result = new StrsItem(s);
this.strings.push(result);
}
// give an option to set the context
result.setContext(opts);
return result;
}
@ -200,7 +216,7 @@ export class Strs extends Section {
s = s ?? '';
// type check everything else
if (typeof s !== 'string') {
throw new Error('alloc_string: s must be a string, undefined, or null.');
throw new Error(`Internal Error: processString: s must be a string, undefined, or null, not ${typeof s} ${s}`);
}
// substitute variables
if (opts?.stringVariables) {
@ -358,11 +374,13 @@ export class Vars extends Section {
export class VarsItem extends Section {
id: StrsItem;
value: StrsItem;
x?: any;
constructor(id: string, value: string, sections: DependencySections) {
constructor(id: string, value: string, sections: DependencySections, x?: any) {
super();
this.id = sections.strs.allocString(id);
this.value = sections.strs.allocString(value, {unescape: true});
this.x = x;
}
valid() : boolean {
@ -371,8 +389,8 @@ export class VarsItem extends Section {
};
export class UnicodeSetItem extends VarsItem {
constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser) {
super(id, value, sections);
constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, x?: any) {
super(id, value, sections, x);
const needRanges = sections.usetparser.sizeUnicodeSet(value);
if (needRanges >= 0) {
this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges);
@ -385,9 +403,9 @@ export class UnicodeSetItem extends VarsItem {
};
export class SetVarItem extends VarsItem {
constructor(id: string, value: string[], sections: DependencySections) {
super(id, value.join(' '), sections);
this.items = sections.elem.allocElementString(sections, value);
constructor(id: string, value: string[], sections: DependencySections, x?: any) {
super(id, value.join(' '), sections, x);
this.items = sections.elem.allocElementString(sections, {x}, value);
}
items: ElementString; // element string array
valid() : boolean {
@ -396,8 +414,8 @@ export class SetVarItem extends VarsItem {
};
export class StringVarItem extends VarsItem {
constructor(id: string, value: string, sections: DependencySections) {
super(id, value, sections);
constructor(id: string, value: string, sections: DependencySections, x?: any) {
super(id, value, sections, x);
}
// no added fields
};
@ -435,7 +453,7 @@ export class Tran extends Section {
};
export class UsetItem {
constructor(public uset: UnicodeSet, public str: StrsItem) {
constructor(public uset: UnicodeSet, public str: StrsItem, public x?: any) {
}
compareTo(other: UsetItem) : number {
return this.str.compareTo(other.str);
@ -444,11 +462,11 @@ export class UsetItem {
export class Uset extends Section {
usets: UsetItem[] = [];
allocUset(set: UnicodeSet, sections: DependencySections) : UsetItem {
allocUset(set: UnicodeSet, sections: DependencySections, x?: any) : UsetItem {
// match the same pattern
let result = this.usets.find(s => set.pattern == s.uset.pattern);
if (result === undefined) {
result = new UsetItem(set, sections.strs.allocString(set.pattern));
result = new UsetItem(set, sections.strs.allocString(set.pattern), x);
this.usets.push(result);
}
return result;
@ -546,7 +564,7 @@ export class Keys extends Section {
kmap: KeysKmap[] = [];
constructor(strs: Strs) {
super();
let nullFlicks = new KeysFlicks(strs.allocString(''));
const nullFlicks = new KeysFlicks(strs.allocString(''));
this.flicks.push(nullFlicks); // C7043: null element string
}
};

View file

@ -1,16 +1,15 @@
import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js";
import KVKFile, { BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js";
import { VisualKeyboard, VisualKeyboardKey } from "./visual-keyboard.js";
export default class KvkFileReader {
public read(source: Uint8Array): VisualKeyboard {
let binary: BUILDER_KVK_FILE;
let kvk = new KVKFile();
binary = kvk.KVK_FILE.fromBuffer(source);
const kvk = new KVKFile();
const binary = kvk.KVK_FILE.fromBuffer(source);
if(binary.header.identifier != BUILDER_KVK_HEADER_IDENTIFIER ||
binary.header.version != BUILDER_KVK_HEADER_VERSION) {
return null;
}
let result = new VisualKeyboard();
const result = new VisualKeyboard();
result.header.version = binary.header.version;
result.header.flags = binary.header.flags;
result.header.associatedKeyboard = binary.header.associatedKeyboard.str;
@ -18,8 +17,8 @@ export default class KvkFileReader {
result.header.ansiFont.size = binary.header.ansiFont.size;
result.header.unicodeFont.name = binary.header.unicodeFont.name.str;
result.header.unicodeFont.size = binary.header.unicodeFont.size;
for(let binaryKey of binary.keys) {
let key: VisualKeyboardKey = {
for(const binaryKey of binary.keys) {
const key: VisualKeyboardKey = {
flags: binaryKey.flags,
vkey: binaryKey.vkey,
shift: binaryKey.shift,

View file

@ -50,7 +50,7 @@ export default class KvkFileWriter {
this.setString(binary.header.ansiFont.name, source.header.ansiFont.name);
this.setString(binary.header.unicodeFont.name, source.header.unicodeFont.name);
for(let sourceKey of source.keys) {
for(const sourceKey of source.keys) {
const binaryKey: BUILDER_KVK_KEY = {
flags: sourceKey.flags,
vkey: sourceKey.vkey,

View file

@ -233,7 +233,7 @@ export class MarkerParser {
}
}
// Then, take each codepoint (from back to front)
for (let p of [...s].reverse()) {
for (const p of [...s].reverse()) {
// reverse order code units, prepend to out
out = p + out;
@ -525,7 +525,7 @@ export class ElementParser {
throw Error(`Unsupported: nested square brackets in element segment: ${str}`);
}
const list: ElementSegment[] = [];
for(let m of str.match(ElementParser.MATCH_ELEMENT_SEGMENTS)) {
for(const m of str.match(ElementParser.MATCH_ELEMENT_SEGMENTS)) {
const e = new ElementSegment(m);
if (e.type === ElementType.escaped) {
// unescape
@ -534,7 +534,7 @@ export class ElementParser {
list.push(e);
} else {
// need to split the escaped segment, \u{41 42} -> \u{41}, \u{42}
for (let s of unescaped) {
for (const s of unescaped) {
list.push(new ElementSegment(`\\u{${s.codePointAt(0).toString(16)}}`));
}
}

View file

@ -39,7 +39,7 @@ export class ListItem extends Array<ListIndex> implements OrderedStringList {
return a;
}
for (const str of source) {
let index = new ListIndex(sections.strs.allocString(str, opts, sections));
const index = new ListIndex(sections.strs.allocString(str, opts, sections));
a.push(index);
}
return a;

View file

@ -8,13 +8,13 @@ export interface UnicodeSetParser {
* @param pattern string to parse such as `[a-z]`
* @param rangeCount number of ranges to allow for
*/
parseUnicodeSet(pattern: string, rangeCount: number) : UnicodeSet | null;
parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : UnicodeSet | null;
/**
* Calculate the number of ranges in a UnicodeSet
* @param pattern string to parse such as `[a-z]`
* @returns number of ranges, or -1 (with callback-reported err) on err
*/
sizeUnicodeSet(pattern: string) : number;
sizeUnicodeSet(pattern: string, x?: any) : number;
}
/**

View file

@ -5,18 +5,6 @@
/****************************** Lexical Models ******************************/
/**
* A JavaScript string with the restriction that it must only
* contain Unicode scalar values.
*
* This means that any lone high surrogate must be paired with
* a low surrogate, if it exists. Lone surrogate code units are
* forbidden.
*
* See also: https://developer.mozilla.org/en-US/docs/Web/API/USVString
*/
export type USVString = string;
export type CasingForm = 'lower' | 'initial' | 'upper';
/**
@ -67,7 +55,7 @@ export interface LexiconTraversal {
* - If `char` = 'e', the child represents a prefix of 'the'.
* - Then `traversal` allows traversing the part of the lexicon prefixed by 'the'.
*/
children(): Generator<{char: USVString, traversal: () => LexiconTraversal}>;
children(): Generator<{char: string, traversal: () => LexiconTraversal}>;
/**
* Allows direct access to the traversal state that results when appending one
@ -81,7 +69,7 @@ export interface LexiconTraversal {
* That is, if a model "keys" `è` to `e`, there will be no `è` child.
* @param char
*/
child(char: USVString): LexiconTraversal | undefined;
child(char: string): LexiconTraversal | undefined;
/**
* Any entries directly keyed by the currently-represented lookup prefix. Entries and
@ -178,7 +166,7 @@ export interface LexicalModel {
* @param text The original input text.
* @returns The 'keyed' form of that text.
*/
toKey?(text: USVString): USVString;
toKey?(text: string): string;
/**
* Generates predictive suggestions corresponding to the state of context after the proposed
@ -248,7 +236,7 @@ export interface LexicalModel {
* @param context
* @deprecated
*/
wordbreak?(context: Context): USVString;
wordbreak?(context: Context): string;
/**
* Lexical models _may_ provide a LexiconTraversal object usable to enhance
@ -279,7 +267,7 @@ export interface Transform {
*
* Corresponds to `s` in com.keyman.KeyboardInterface.output.
*/
insert: USVString;
insert: string;
/**
* The number of code units to delete to the left of the cursor.
@ -380,7 +368,7 @@ export interface Context {
* buffer. If there is nothing to the left of the buffer, this is
* an empty string.
*/
readonly left: USVString;
readonly left: string;
/**
* Up to maxRightContextCodeUnits code units of Unicode scalar value
@ -390,7 +378,7 @@ export interface Context {
*
* This property may be missing entirely.
*/
readonly right?: USVString;
readonly right?: string;
/**
* Whether the insertion point is at the start of the buffer.

View file

@ -9,8 +9,8 @@ export { MATCH_HEX_ESCAPE, CONTAINS_QUAD_ESCAPE, MATCH_QUAD_ESCAPE };
* @param x Name of element to box
*/
export function boxXmlArray(o: any, x: string): void {
if(typeof o == 'object' && !Array.isArray(o[x])) {
if(o[x] === null || o[x] === undefined) {
if (typeof o == 'object' && !Array.isArray(o[x])) {
if (o[x] === null || o[x] === undefined) {
o[x] = [];
}
else {
@ -64,7 +64,7 @@ export function unescapeQuadString(s: string): string {
* @returns
*/
export function unescapeString(s: string): string {
if(!s) {
if (!s) {
return s;
}
try {
@ -74,13 +74,13 @@ export function unescapeString(s: string): string {
* @param matched the entire match such as '0127' or '22 22'
* @returns the unescaped match
*/
function processMatch(str: string, matched: string) : string {
function processMatch(str: string, matched: string): string {
const codepoints = matched.split(' ');
const unescaped = codepoints.map(unescapeOne);
return unescaped.join('');
}
s = s.replaceAll(MATCH_HEX_ESCAPE, processMatch);
} catch(e) {
} catch (e) {
if (e instanceof RangeError) {
throw new UnescapeError(`Out of range while unescaping '${s}': ${e.message}`, { cause: e });
/* c8 ignore next 3 */
@ -142,7 +142,7 @@ function regexOne(hex: string): string {
/**
* Escape a string (\uxxxx form) if there are any problematic codepoints
*/
export function escapeStringForRegex(s: string) : string {
export function escapeStringForRegex(s: string): string {
return s.split('').map(ch => escapeRegexCharIfSyntax(ch)).join('');
}
@ -152,7 +152,7 @@ export function escapeStringForRegex(s: string) : string {
* @returns
*/
export function unescapeStringToRegex(s: string): string {
if(!s) {
if (!s) {
return s;
}
try {
@ -162,13 +162,13 @@ export function unescapeStringToRegex(s: string): string {
* @param matched the entire match such as '0127' or '22 22'
* @returns the unescaped match
*/
function processMatch(str: string, matched: string) : string {
function processMatch(str: string, matched: string): string {
const codepoints = matched.split(' ');
const unescaped = codepoints.map(regexOne);
return unescaped.join('');
}
s = s.replaceAll(MATCH_HEX_ESCAPE, processMatch);
} catch(e) {
} catch (e) {
if (e instanceof RangeError) {
throw new UnescapeError(`Out of range while unescaping '${s}': ${e.message}`, { cause: e });
/* c8 ignore next 3 */
@ -181,19 +181,19 @@ export function unescapeStringToRegex(s: string): string {
/** True if this string *could* be a UTF-32 single char */
export function
isOneChar(value: string) : boolean {
isOneChar(value: string): boolean {
return [...value].length === 1;
}
export function
toOneChar(value: string) : number {
toOneChar(value: string): number {
if (!isOneChar(value)) {
throw Error(`Not a single char: ${value}`);
}
return value.codePointAt(0);
}
export function describeCodepoint(ch : number) : string {
export function describeCodepoint(ch: number): string {
let s;
const p = BadStringAnalyzer.getProblem(ch);
if (p != null) {
@ -207,13 +207,85 @@ export function describeCodepoint(ch : number) : string {
}
/**
* @brief function to convert a numeric character reference to a unicode codepoint e.g. &#x63 -> U+0063; &#1111553 -> U+10F601
* @param instr the value that will converted
* @return a unicode codepoint if instr is a numeric character reference
* instr if instr is not a numeric character reference
* @brief function to convert a Html-Entity Hex value to a unicode character e.g. &#x63 -> c; &#x1F60E; -> 😎
* @param hex_Entity the value that will converted
* @return a unicode character or undefined if in_str is not recognized
*/
export function decodeHexEntity(hex_Entity: string) {
const hex = hex_Entity.replace('&#x', '').replace(';', '');
if (hex_Entity.length > 9)
return undefined;
else
return String.fromCodePoint(parseInt(hex, 16));
}
/**
* @brief function to convert a Html-Entity decimal value value to a unicode character e.g. &#99 -> c; &#128526; -> 😎
* @param dec_Entity the value that will converted
* @return a unicode character or undefined if in_str is not recognized
*/
export function decodeDecEntity(dec_Entity: string) {
const dec = dec_Entity.replace('&#', '').replace(';', '');
if (dec_Entity.length > 9)
return undefined;
else
return String.fromCodePoint(parseInt(dec, 10));
}
/**
* @brief function to convert a unicode hex value to a unicode character e.g. U+0063 -> c; U+1F60E; -> 😎
* @param Uni_Val the value that will converted
* @return a unicode character or undefined if in_str is not recognized
*/
export function decodeUniHexValue(Uni_Val: string): any {
const hexval = Uni_Val.replace('U+', '0x');
if (Uni_Val.length > 8)
return undefined;
else
return String.fromCodePoint(parseInt(hexval, 16));
}
/**
* @brief function to convert a numeric character reference or a unicode value to a unicode character e.g. &#x63 -> c; U+1F60E -> 😎
* @param in_str the value that will converted
* @return a unicode character or undefined if in_str is not recognized
*/
export function convertToUnicodeCharacter(in_str: string): string {
if ((in_str === null) || (in_str === undefined)) {
return undefined;
}
else if (in_str.substring(0, 3) === "&#x") {
return decodeHexEntity(in_str);
}
else if (in_str.substring(0, 2) === "&#") {
return decodeDecEntity(in_str);
}
else if (in_str.substring(0, 1) === "&") {
return undefined;
}
else if (in_str.substring(0, 2) === "U+") {
return decodeUniHexValue(in_str);
}
else if ( [...new Intl.Segmenter().segment(in_str)].length <= 1) {
return in_str;
}
else {
return undefined;
}
}
export function convertToUnicodeCodePoint(instr: string): string {
if ((instr === null) || (instr === undefined)) {
return undefined;
}
if (instr.substring(0, 3) === "&#x") {
const num_length = instr.length - instr.indexOf("x") - 1;
const num_str = instr.substring(instr.indexOf("x") + 1, instr.length - 1);
@ -251,26 +323,26 @@ const Uni_FFFE_NONCHARACTER = 0xFFFE;
const Uni_PLANE_MASK = 0x1F0000;
const Uni_MAX_CODEPOINT = 0x10FFFF;
// plane 0, 15, and 16 PUA
const Uni_PUA_00_START = 0xE000;
const Uni_PUA_00_END = 0xF8FF;
const Uni_PUA_00_START = 0xE000;
const Uni_PUA_00_END = 0xF8FF;
const Uni_PUA_15_START = 0x0F0000;
const Uni_PUA_15_END = 0x0FFFFD;
const Uni_PUA_15_END = 0x0FFFFD;
const Uni_PUA_16_START = 0x100000;
const Uni_PUA_16_END = 0x10FFFD;
const Uni_PUA_16_END = 0x10FFFD;
/**
* @brief True if a lead surrogate
* \def Uni_IsSurrogate1
*/
export function Uni_IsSurrogate1(ch : number) {
export function Uni_IsSurrogate1(ch: number) {
return ((ch) >= Uni_LEAD_SURROGATE_START && (ch) <= Uni_LEAD_SURROGATE_END);
}
/**
* @brief True if a trail surrogate
* \def Uni_IsSurrogate2
*/
export function Uni_IsSurrogate2(ch : number) {
export function Uni_IsSurrogate2(ch: number) {
return ((ch) >= Uni_TRAIL_SURROGATE_START && (ch) <= Uni_TRAIL_SURROGATE_END);
}
@ -278,19 +350,19 @@ export function Uni_IsSurrogate2(ch : number) {
* @brief True if any surrogate
* \def UniIsSurrogate
*/
export function Uni_IsSurrogate(ch : number) {
export function Uni_IsSurrogate(ch: number) {
return (Uni_IsSurrogate1(ch) || Uni_IsSurrogate2(ch));
}
function Uni_IsEndOfPlaneNonCharacter(ch : number) {
function Uni_IsEndOfPlaneNonCharacter(ch: number) {
return (((ch) & Uni_FFFE_NONCHARACTER) == Uni_FFFE_NONCHARACTER); // matches FFFF or FFFE
}
function Uni_IsNoncharacter(ch : number) {
function Uni_IsNoncharacter(ch: number) {
return (((ch) >= Uni_FD_NONCHARACTER_START && (ch) <= Uni_FD_NONCHARACTER_END) || Uni_IsEndOfPlaneNonCharacter(ch));
}
function Uni_InCodespace(ch : number) {
function Uni_InCodespace(ch: number) {
return (ch >= 0 && ch <= Uni_MAX_CODEPOINT);
};
@ -329,8 +401,8 @@ export function isPUA(ch: number) {
}
/** @returns false if s is NEITHER NFC nor NFD. (Returns true for falsy) */
export function isNormalized(s: string) : boolean {
if(!s) return true; // empty or null
export function isNormalized(s: string): boolean {
if (!s) return true; // empty or null
const nfc = s.normalize("NFC");
const nfd = s.normalize("NFD");
if (s !== nfc && s !== nfd) return false;
@ -338,7 +410,7 @@ export function isNormalized(s: string) : boolean {
}
class BadStringMap extends Map<BadStringType, Set<number>> {
public toString() : string {
public toString(): string {
if (!this.size) {
return "{}";
}
@ -349,7 +421,7 @@ class BadStringMap extends Map<BadStringType, Set<number>> {
/** abstract class for analyzing and categorizing strings */
export abstract class StringAnalyzer {
/** add a string for analysis */
public add(s : string) {
public add(s: string) {
for (const c of [...s]) {
const ch = c.codePointAt(0);
const problem = this.analyzeCodePoint(c, ch);
@ -364,10 +436,10 @@ export abstract class StringAnalyzer {
* @param c single codepoint to analyze (string)
* @param ch single codepoint to analyze (scalar)
*/
protected abstract analyzeCodePoint(c: string, ch: number) : BadStringType;
protected abstract analyzeCodePoint(c: string, ch: number): BadStringType;
/** internal interface for the result of an analysis */
protected addProblem(ch : number, type : BadStringType) {
protected addProblem(ch: number, type: BadStringType) {
if (!this.m.has(type)) {
this.m.set(type, new Set<number>());
}
@ -375,7 +447,7 @@ export abstract class StringAnalyzer {
}
/** get the results of the analysis */
public analyze() : BadStringMap {
public analyze(): BadStringMap {
if (this.m.size == 0) {
return null;
} else {
@ -397,7 +469,7 @@ export class BadStringAnalyzer extends StringAnalyzer {
public static getProblem(ch: number) {
if (!isValidUnicode(ch)) {
return BadStringType.illegal;
} else if(isPUA(ch)) {
} else if (isPUA(ch)) {
return BadStringType.pua;
} else { // TODO-LDML: unassigned
return null;

View file

@ -91,12 +91,12 @@ describe('Test of ElementString file', () => {
});
describe('Test of fromStrings()', () => {
it('returns an empty ElementString if source is null', () => {
const es = ElementString.fromStrings({}, null);
const es = ElementString.fromStrings({}, {}, null);
assert.deepEqual(es, new ElementString());
});
it('can create an ElementString from a string array', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(sections, ["𐌰", "𐌱", "𐌲"]);
const actual = ElementString.fromStrings(sections, {}, ["𐌰", "𐌱", "𐌲"]);
const expected = [
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
@ -106,7 +106,7 @@ describe('Test of ElementString file', () => {
});
it('can create an ElementString from a string', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(sections, "𐌰𐌱𐌲");
const actual = ElementString.fromStrings(sections, {}, "𐌰𐌱𐌲");
const expected = [
initElemElement(GOTHIC_A),
initElemElement(GOTHIC_B),
@ -118,6 +118,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
"1 2 3",
);
@ -132,6 +133,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
"1",
);
@ -146,6 +148,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
"1 2",
);
@ -160,6 +163,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
"1 A 3",
);
@ -174,6 +178,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
"1 2 3",
@ -189,6 +194,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
"1",
@ -204,6 +210,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
"1 2",
@ -219,6 +226,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
"1 A 3",
@ -234,6 +242,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -250,6 +259,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -266,6 +276,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -282,6 +293,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -299,6 +311,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -316,6 +329,7 @@ describe('Test of ElementString file', () => {
sections.strs.allocString = stubStrsAllocString_Char;
const actual = ElementString.fromStrings(
sections,
{},
"𐌰𐌱𐌲",
null,
null,
@ -333,7 +347,7 @@ describe('Test of ElementString file', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.strs.allocString = stubStrsAllocString_Str;
sections.uset.allocUset = stubUsetAllocUset;
const actual = ElementString.fromStrings(sections, "[𐌰-𐍊]");
const actual = ElementString.fromStrings(sections, {}, "[𐌰-𐍊]");
const expected = [
initElemElement(
new StrsItem(''),
@ -348,17 +362,17 @@ describe('Test of ElementString file', () => {
it('returns null for an invalid unicode set size', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.usetparser.sizeUnicodeSet = (pattern: string) : number => -1;
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
assert.isNull(ElementString.fromStrings(sections, {}, "[𐌰-𐍊]"));
});
it('returns null if it cannot parse the unicode set', () => {
ElementParser.segment = stubElementParserSegment_Uset;
sections.usetparser.parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => null;
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
assert.isNull(ElementString.fromStrings(sections, {}, "[𐌰-𐍊]"));
});
it('can handle quad strings', () => {
sections.strs.allocString = stubStrsAllocString_Char;
ElementParser.segment = stubElementParserSegment_Escaped;
const actual = ElementString.fromStrings(sections, "\\ud800\\udf30");
const actual = ElementString.fromStrings(sections, {}, "\\ud800\\udf30");
const expected = [
initElemElement(HI_GOTHIC_A),
initElemElement(LO_GOTHIC_A),
@ -367,7 +381,7 @@ describe('Test of ElementString file', () => {
});
it('can handle ElemElement of string type', () => {
sections.strs.allocString = stubStrsAllocString_Str;
const actual = ElementString.fromStrings(sections, ["𐌰𐌱𐌲",]);
const actual = ElementString.fromStrings(sections, {}, ["𐌰𐌱𐌲",]);
const expected = [
initElemElement(
new StrsItem("𐌰𐌱𐌲"),

View file

@ -7,7 +7,7 @@
import { KMXPlus, LdmlKeyboardTypes, LexicalModelTypes } from "@keymanapp/common-types";
export let u: LexicalModelTypes.USVString;
export let u: string;
export let l: LexicalModelTypes.Transform;
export let s: LexicalModelTypes.Suggestion;
export let st: LexicalModelTypes.SuggestionTag;

View file

@ -14,7 +14,7 @@ var
KeymanRoot: string;
ReportPath: string;
begin
if GetEnvironmentVariable('TEAMCITY_VERSION') <> '' then
if GetEnvironmentVariable('TEAMCITY_GIT_PATH') <> '' then
begin
KeymanRoot := ExcludeTrailingPathDelimiter(GetEnvironmentVariable('KEYMAN_ROOT'));
ReportPath := ExtractRelativePath(KeymanRoot, ExtractFilePath(ParamStr(0)) + 'dunitx-results.xml');

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