diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml index e8cc9aa95a..af6bc682dc 100644 --- a/.github/workflows/api-verification.yml +++ b/.github/workflows/api-verification.yml @@ -23,6 +23,7 @@ jobs: GIT_BRANCH: ${{ steps.environment_step.outputs.GIT_BRANCH }} GIT_BASE_BRANCH: ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }} GIT_USER: ${{ steps.environment_step.outputs.GIT_USER }} + SKIP_API_CHECK: ${{ steps.environment_step.outputs.SKIP_API_CHECK }} steps: - name: Restore artifacts @@ -40,7 +41,7 @@ jobs: - name: Set pending status on PR builds id: set_status - if: steps.environment_step.outputs.IS_TEST_BUILD == 'true' + if: steps.environment_step.outputs.IS_TEST_BUILD == 'true' && steps.environment_step.outputs.SKIP_API_CHECK != 'true' shell: bash run: | gh api \ @@ -53,6 +54,7 @@ jobs: -f context="$STATUS_CONTEXT" - name: Checkout + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ steps.environment_step.outputs.GIT_SHA }}' @@ -61,11 +63,13 @@ jobs: path: ${{ github.workspace }}/keyman/ - name: Install devscripts + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' uses: ./keyman/.github/actions/apt-install with: packages: devscripts equivs - name: "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 }})" + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' 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 @@ -79,17 +83,17 @@ jobs: verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' && always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: libkeymancore.symbols path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols - if: always() set_status: name: Set result status on PR builds needs: [api_verification] runs-on: ubuntu-latest - if: ${{ always() && needs.api_verification.outputs.IS_TEST_BUILD == 'true' }} + if: ${{ always() && needs.api_verification.outputs.IS_TEST_BUILD == 'true' && needs.api_verification.outputs.SKIP_API_CHECK != 'true' }} steps: - name: Set success if: needs.api_verification.result == 'success' diff --git a/.github/workflows/auto-merge-keyman-server-pr.yml b/.github/workflows/auto-merge-keyman-server-pr.yml index 771fd6ae5e..bc1759f6bc 100644 --- a/.github/workflows/auto-merge-keyman-server-pr.yml +++ b/.github/workflows/auto-merge-keyman-server-pr.yml @@ -13,7 +13,7 @@ on: jobs: build: runs-on: ubuntu-latest - if: ${{ github.repository == 'keymanapp/keyman' && github.actor == 'keyman-server' && startsWith(github.event.pull_request.title, 'auto:') }} + if: ${{ github.repository == 'keymanapp/keyman' && github.actor == 'keyman-server' && startsWith(github.head_ref, 'auto/') }} steps: - name: auto approve PR from keyman-server shell: bash @@ -23,7 +23,6 @@ jobs: gh pr review -R keymanapp/keyman --approve ${{ github.event.pull_request.number }} - name: mark PR for auto-merge from keyman-server shell: bash - if: contains(github.event.pull_request.labels.*.name, 'automerge') env: GH_TOKEN: "${{ secrets.AUTOINC_GITHUB_TOKEN }}" run: | diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml new file mode 100644 index 0000000000..3c85d4ff91 --- /dev/null +++ b/.github/workflows/build-test-publish-docker.yml @@ -0,0 +1,79 @@ +# This workflow runs the build, test and publish steps for the +# Keyman build Docker images. +# It is triggered by a schedule and repository_dispatch event. + +name: "Docker images: Build, publish and test " +run-name: "Docker images: Build, publish and test (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" +defaults: + run: + shell: bash + +on: + schedule: + #run every Fri, 3 pm UTC + - cron: '0 15 * * 5' + + repository_dispatch: + types: ['build-test-publish-docker:*'] + +env: + REGISTRY: ghcr.io + USERNAME: "${{ github.actor }}" + PASSWORD: "${{ secrets.KEYMAN_SERVER_REPO_ACCESS_PAT }}" + +jobs: + + message: + name: Print message + runs-on: ubuntu-latest + if: ${{ github.event.act }} + + steps: + - name: Print message + id: print-message + run: | + echo "Docker images: Build, publish and test (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" + + build: + name: Build Docker images + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + id: checkout + uses: actions/checkout@v4 + - name: Build Docker images + id: build + run: | + if [ -z "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi + - name: Publish Docker images + id: publish + run: | + if [ -z "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi + + test: + name: Test Docker images + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout repository + id: checkout + uses: actions/checkout@v4 + + - name: Test Docker images + id: test + run: | + if [ -z "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi diff --git a/.github/workflows/cleanup-ghcr.yml b/.github/workflows/cleanup-ghcr.yml new file mode 100644 index 0000000000..15e0a14ec8 --- /dev/null +++ b/.github/workflows/cleanup-ghcr.yml @@ -0,0 +1,45 @@ +# This workflow removes all untagged Docker images from the +# GitHub Container Registry. +# It is triggered by a schedule event or a manual workflow dispatch event. + +name: "Docker images: Cleanup GitHub Container Registry" +run-name: "Docker images: Cleanup GitHub Container Registry (branch ${{ github.ref_name }} on ${{ github.event_name }} by ${{ github.actor }})" +defaults: + run: + shell: bash + +on: + + schedule: + #run every Fri, 6 pm UTC + - cron: '0 18 * * 5' # + + workflow_dispatch: + + +jobs: + message: + name: Print message + runs-on: ubuntu-latest + if: ${{ github.event.act }} + + steps: + - name: Print message + id: print-message + run: | + echo "Docker images: Cleanup GitHub Container Registry (branch ${{ github.ref_name }} on ${{ github.event_name }} by ${{ github.actor }})" + + cleanup-images: + name: Cleanup GitHub Container Registry images + runs-on: ubuntu-latest + + steps: + - name: Removing all untagged images from GitHub Container Registry + id: cleanup-ghcr + uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 # v1.0.16 + with: + dry-run: false + expand-packages: true + packages: keyman-*-ci + delete-untagged: true + token: ${{ secrets.KEYMAN_SERVER_REPO_ACCESS_PAT }} diff --git a/.github/workflows/deb-packaging.README.md b/.github/workflows/deb-packaging.README.md index 9dcfce20db..662526a875 100644 --- a/.github/workflows/deb-packaging.README.md +++ b/.github/workflows/deb-packaging.README.md @@ -18,7 +18,8 @@ You can manually trigger a deb-packaging action on GitHub, e.g. to test changes: \"baseBranch\": \"master\", \"baseRef\": \"$(git rev-parse refs/heads/master^)\", \"user\": \"${USER}\", - \"isTestBuild\": \"true\"}" \ + \"isTestBuild\": \"true\", + \"force\": \"true\"}" \ https://api.github.com/repos//keyman/dispatches ``` @@ -36,6 +37,7 @@ You can manually trigger a deb-packaging action on GitHub, e.g. to test changes: \"baseBranch\": \"master\", \"baseRef\": \"$(git rev-parse refs/heads/master)\", \"user\": \"${USER}\", - \"isTestBuild\": \"true\"}" \ + \"isTestBuild\": \"true\", + \"force\": \"true\"}" \ https://api.github.com/repos//keyman/dispatches ``` diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index a40fcb7259..34fd04509e 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -23,6 +23,7 @@ on: # baseRef: The ref of the previous commit. For a PR the same as `baseBranch`. # user: The user that triggered the build or created the PR # isTestBuild: false for Releases, otherwise true +# skipApiCheck: true to skip the API check, otherwise false env: COLOR_GREEN: "\e[32m" @@ -30,11 +31,12 @@ env: STATUS_CONTEXT: 'Ubuntu Packaging' DEBFULLNAME: 'Keyman GHA packager' DEBEMAIL: 'support@keyman.com' + _builder_timings: false jobs: sourcepackage: name: Build source package - if: github.repository == 'keymanapp/keyman' + if: github.repository == 'keymanapp/keyman' || github.event.client_payload.force runs-on: ubuntu-24.04 outputs: KEYMAN_VERSION: ${{ steps.version_step.outputs.KEYMAN_VERSION }} @@ -134,7 +136,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [jammy, noble, plucky] + dist: [jammy, noble, plucky, questing] steps: - name: Checkout @@ -160,7 +162,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [questing] + dist: [devel] steps: - name: Checkout @@ -184,28 +186,53 @@ jobs: runs-on: ubuntu-latest steps: - # - name: Download Artifacts - # uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - # with: - # path: artifacts - # merge-multiple: true - - # - name: Install dependencies - # run: | - # sudo DEBIAN_FRONTEND=noninteractive apt-get -q -y install autopkgtest qemu-system qemu-utils autodep8 genisoimage python3-distro-info - - # - name: Build test image - # run: | - # cd "${GITHUB_WORKSPACE}/artifacts" - # autopkgtest-buildvm-ubuntu-cloud -v --release=jammy - - # - name: Run tests - # run: | - # cd "${GITHUB_WORKSPACE}/artifacts" - # autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img - - name: Ignore + - name: Free disk space run: | - echo "Ignored for now - until working solution is in place (#13777)" + # Free disk space by removing some large directories that we don't use (#15171) + # see https://dev.to/mathio/squeezing-disk-space-from-github-actions-runners-an-engineers-guide-3pjg + + # Remove Android SDKs (frees 12 GB on Ubuntu 24.04) + sudo rm -rf /usr/local/lib/android + + # Remove Haskell (GHC) (frees 6.4 GB) + sudo rm -rf /usr/local/.ghcup + + # Remove CodeQL and other toolcaches (frees 5.8 GB) + sudo rm -rf /opt/hostedtoolcache + + # Remove .NET SDKs (frees 4.0 GB) + sudo rm -rf /usr/share/dotnet + + - name: Download Source Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: keyman-srcpkg + path: artifacts + + - name: Download Binary Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: artifacts + pattern: keyman-binarypkgs-* + merge-multiple: true + + - name: Install dependencies + run: | + sudo apt update + sudo DEBIAN_FRONTEND=noninteractive apt-get -q -y install autopkgtest qemu-system qemu-utils autodep8 genisoimage python3-distro-info + + - name: Build test image + run: | + cd "${GITHUB_WORKSPACE}/artifacts" + sudo chown ${USER} /dev/kvm + autopkgtest-buildvm-ubuntu-cloud -v --release=$(lsb_release -c -s) + + - name: Run tests + run: | + # Tests are defined in linux/debian/tests/test-build + cd "${GITHUB_WORKSPACE}/artifacts" + DIST="$(lsb_release -c -s)" + autopkgtest -B *${DIST}*.deb keyman_*.dsc -- qemu autopkgtest-${DIST}-amd64.img deb_signing: name: Sign source and binary packages @@ -332,6 +359,7 @@ jobs: echo "GIT_BRANCH=${{ github.event.client_payload.branch }}" >> artifacts/env echo "GIT_BASE_BRANCH=${{ github.event.client_payload.baseBranch }}" >> artifacts/env echo "GIT_USER=${{ github.event.client_payload.user }}" >> artifacts/env + echo "SKIP_API_CHECK=${{ github.event.client_payload.skipApiCheck }}" >> artifacts/env - name: Cache artifacts uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000000..fc94cc688e --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,153 @@ +# +# Keyman is copyright (C) SIL Global. MIT License. +# +# Created by mcdurdin on 2025-10-27 +# +# This workflow publishes all our npm packages using Trusted Publishers OIDC +# permissions. It is triggered by a repository_dispatch event with the types +# `npm-publish:*`, from the trigger-builds infrastructure. For test builds, this +# does a `npm pack` only. +# +# Inputs: +# buildSha: The SHA of the commit to build, e.g. of the branch or +# refs/pull/1234/head for PR +# user: The user that triggered the build or created the PR +# isTestBuild: false for Releases, otherwise true + +name: "npm pack/publish" +run-name: "npm pack/publish - branch: ${{ github.event.client_payload.branch }} sha: ${{ github.event.client_payload.buildSha }}, user: @${{ github.event.client_payload.user }}, isTestBuild: ${{ github.event.client_payload.isTestBuild }}" +on: + repository_dispatch: + types: ['npm-publish:*'] + +permissions: + id-token: write # Required for OIDC + contents: read + statuses: write + +env: + GH_TOKEN: ${{ github.token }} + STATUS_CONTEXT: 'npm pack/publish' + IS_TEST_BUILD: ${{ github.event.client_payload.isTestBuild }} + # This property causes node.inc.sh to skip invocation of nvm, as otherwise, we + # end up downgrading to node v20 as of Keyman 19. See #15040 and corresponding + # change in node.inc.sh; we should be able to remove this in Keyman 20. + KEYMAN_CI_SKIP_NVM: true + +jobs: + npm_publish: + name: Publish or pack @keymanapp packages + if: github.repository == 'keymanapp/keyman' || github.event.client_payload.force + runs-on: ubuntu-24.04 + steps: + + - name: Set env var for pack vs publish + run: | + if [[ "$IS_TEST_BUILD" == true ]]; then + echo "NPM_ACTION=pack" >> $GITHUB_ENV + else + echo "NPM_ACTION=publish" >> $GITHUB_ENV + fi + + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + with: + ref: '${{ github.event.client_payload.buildSha }}' + + - name: Set pending status + id: set_status + if: github.event.client_payload.isTestBuild == 'true' + shell: bash + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/$GITHUB_REPOSITORY/statuses/${{ github.event.client_payload.buildSha }} \ + -f state='pending' \ + -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + -f description="npm $NPM_ACTION started" \ + -f context="$STATUS_CONTEXT" + + - name: Report initial npm/node versions + run: | + echo "Initial npm/node versions" + npm -v + node -v + + - uses: actions/setup-node@v6 + with: + node-version: '24.10' + + - name: Report current npm/node versions + run: | + echo "Current npm/node versions" + npm -v + node -v + + - name: Configure build environment + run: | + ./resources/build/ci/npm-publish.sh configure + + - name: npm pack/publish + run: | + export KEYMAN_TIER=$(cat TIER.md) + export GHA_TEST_BUILD="${{ github.event.client_payload.isTestBuild }}" + export GHA_BRANCH="${{ github.event.client_payload.branch }}" + echo "KEYMAN_TIER=$KEYMAN_TIER" >> $GITHUB_ENV + echo "GHA_TEST_BUILD=${GHA_TEST_BUILD}" >> $GITHUB_ENV + echo "GHA_BRANCH=${GHA_BRANCH}" >> $GITHUB_ENV + + echo "KEYMAN_TIER: $KEYMAN_TIER" + echo "GHA_TEST_BUILD: $GHA_TEST_BUILD" + echo "GHA_BRANCH: $GHA_BRANCH" + + if [[ -f ./resources/build/ci/npm-publish.sh ]]; then + ./resources/build/ci/npm-publish.sh $NPM_ACTION + else + echo WARNING: npm-publish.sh is not yet available + fi + + set_status: + name: Set result status on PR builds + needs: [npm_publish] + runs-on: ubuntu-latest + if: ${{ always() }} + steps: + + - name: Set env var for pack vs publish + run: | + if [[ "$IS_TEST_BUILD" == true ]]; then + echo "NPM_ACTION=pack" >> $GITHUB_ENV + else + echo "NPM_ACTION=publish" >> $GITHUB_ENV + fi + + - name: Set success + if: needs.npm_publish.result == 'success' + run: | + echo "RESULT=success" >> $GITHUB_ENV + echo "MSG=npm $NPM_ACTION succeeded" >> $GITHUB_ENV + + - name: Set cancelled + if: needs.npm_publish.result == 'cancelled' + run: | + echo "RESULT=error" >> $GITHUB_ENV + echo "MSG=npm $NPM_ACTION cancelled" >> $GITHUB_ENV + + - name: Set failure + if: needs.npm_publish.result == 'failure' + run: | + echo "RESULT=failure" >> $GITHUB_ENV + echo "MSG=npm $NPM_ACTION failed" >> $GITHUB_ENV + + - name: Set final status + if: github.event.client_payload.isTestBuild == 'true' + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/$GITHUB_REPOSITORY/statuses/${{ github.event.client_payload.buildSha }} \ + -f state="$RESULT" \ + -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + -f description="$MSG" \ + -f context="$STATUS_CONTEXT" diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 236970f141..407dc23ef1 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -55,43 +55,63 @@ jobs: // check/web/file-size // + function addLog(message) { + console.log(message); + return message + String.fromCharCode(10); + } + + function addStatus(o, type, context, state) { + if(!o[context]) { + o[context] = {type, state}; + return addLog(`Found ${context}: ${state}`); + } else { + return addLog(`Skipping repeated ${context}: ${state}`); + } + } + function reduceStatuses(statuses) { + let summary = ''; const filtered_statuses = statuses.reduce((o, status) => { if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { - if(!o[status.context]) o[status.context] = {type: 'user-test', state: status.state};; + summary += addStatus(o, 'user-test', status.context, status.state); } else if(status.context == 'API Verification') { - if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + summary += addStatus(o, 'check', status.context, status.state); } else if(status.context == 'Ubuntu Packaging') { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); + } else if(status.context == 'npm pack/publish') { + summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'check/web/file-size') { // Ignore check/web/file-size -- we won't block automerge for this at this point + summary += addLog(`Skipping ${status.context}`); } else { // We fail with an 'unknown status' response if we get a new status check // so we can be sure we are not skipping known status checks + summary += addLog(`Found unknown ${status.context}: ${status.state}`); o[status.context] = {type: 'unknown', state: status.state}; } return o; }, {}); - return filtered_statuses; + return { filtered_statuses, summary }; } // // Given the collection of status checks we care about, return - // an aggregate status -- error, failed, pending, or success, + // an aggregate status -- error, failure, pending, or success, // and a summary description // - function calculateFinalStatus(filtered_statuses) { + function calculateFinalStatus(filtered_statuses, summary) { const counts = {}; let hasBuilds = false; for(const context of Object.keys(filtered_statuses)) { const { state, type } = filtered_statuses[context]; if(type == 'unknown') { // We special-case for unknown status checks, and never permit them + console.error(`An unknown context ${context} was found, cannot calculate build status.`); return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.` + 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; } if(type == 'build') { @@ -102,12 +122,13 @@ jobs: // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Builds have not yet been triggered ⌛']; + console.log(`Builds not yet triggered`); + return ['pending', 'Builds have not yet been triggered ⌛', summary]; } const state = counts.error ? 'error' : - counts.failed ? 'failed' : + counts.failure ? 'failure' : counts.pending ? 'pending' : 'success'; @@ -118,14 +139,16 @@ jobs: description += `${count} check${count == 1 ? '' : 's'} ${state}`; } appendDescription(counts.error, 'in an error state ❌'); - appendDescription(counts.failed, 'failed ❌'); + appendDescription(counts.failure, 'failed ❌'); appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); - return [ state, description ]; + console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); + return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { + console.log(`Getting commit statuses for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { owner, repo, @@ -138,6 +161,7 @@ jobs: } async function getCommitCheckRuns(github, owner, repo, sha) { + console.log(`Getting commit check runs for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { owner, repo, @@ -151,13 +175,11 @@ jobs: function calculateCheckResult(statuses) { if(!Array.isArray(statuses)) { - return ['error', 'Failed to retrieve status checks from GitHub ❌']; + return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; } - const filtered_statuses = reduceStatuses(statuses); - - const result = calculateFinalStatus(filtered_statuses); - return result; + const { filtered_statuses, summary } = reduceStatuses(statuses); + return calculateFinalStatus(filtered_statuses, summary); } async function test(github, owner, repo, sha) { @@ -177,7 +199,7 @@ jobs: return check.data.id; } - async function updateCheck(github, owner, repo, checkRunId, status, description) { + async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); @@ -189,7 +211,7 @@ jobs: conclusion, output: { title: description, - summary: '' + summary } }); } @@ -204,5 +226,5 @@ jobs: const checkRunId = await createCheck(github, owner, repo, sha); const res = await test(github, owner, repo, sha); - await updateCheck(github, owner, repo, checkRunId, res[0], res[1]); + await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); diff --git a/.shellcheckrc b/.shellcheckrc index 1c46a510eb..0124e83789 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -1,5 +1,6 @@ external-sources=true -# If you get SC1091 for sourced files, add the following setting to -# your .vscode/settings.json file: -# "shellcheck.useWorkspaceRootAsCwd": false +source-path=resources/build/ +source-path=resources/ +source-path=SCRIPTDIR +source-path=linux/ibus-keyman/tests/scripts/ diff --git a/HISTORY.md b/HISTORY.md index e39d76bd02..0e32221064 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,381 @@ # Keyman Version History +## 19.0.170 alpha 2025-12-03 + +* chore(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 in /resources/build/pr-build-status (#15173) +* chore(deps-dev): bump glob from 10.4.5 to 10.5.0 in /resources/build/pr-build-status (#15184) +* chore(deps): bump js-yaml and @microsoft/api-documenter (#15185) +* chore(deps): bump glob from 10.4.5 to 10.5.0 in /developer/src/server/src/win32/trayicon/addon-src (#15186) +* docs(developer): add/update API docs (#15275) + +## 19.0.169 alpha 2025-12-02 + +* maint(core): verify that keyman_core_ldml.h is in sync (#15256) +* chore(common): Update FirstVoices keyboard versions in keyboards.csv (#15261) + +## 19.0.168 alpha 2025-12-01 + +* chore(android): add intent to broadcast font name (#15193) +* docs(android): Document Keyman API for keyboard font change (#15247) +* docs(ios): Use /cdn/dev assets for Engine guides (#15253) +* fix(developer): handle errors in .keyman-touch-layout file when loading (#15243) +* fix(android): address keyboard height vs touch height race after keyboard height preference change (also min max heights + docs) (#13663) + +## 19.0.167 alpha 2025-11-28 + +* change(windows): add unit testing to keyman32 build (#15244) +* refactor(windows): merge syskbdnt.cpp and syskbdnt64.cpp into RightAltEmulationCheck.cpp (#15245) +* chore(windows): add wow64kbd support app for researching WOW64 reqs (#15246) + +## 19.0.166 alpha 2025-11-26 + +* chore(android): Clarify PR title for #15136 (#15224) +* fix(windows): escape backslashes in path in debugging message (#15236) + +## 19.0.165 alpha 2025-11-21 + +* chore(mac): add keyTrace log (#14972) +* fix(developer): avoid race in page.onload vs kmw.init in Server (#15087) +* fix(windows): reinstall low level keyboard hook if it gets removed (#15179) + +## 19.0.164 alpha 2025-11-20 + +* feat(windows): update wix to 3.14.1 (#15194) + +## 19.0.163 alpha 2025-11-17 + +* chore(linux): Update debian changelog (#15133) +* fix(windows): handle exceptions when walking stack in exception handler (#15150) +* fix(windows): disable OutputDebugString from DllMain (#15151) +* change(windows): include map2pdb generation for relevant Delphi projects (#14799) +* change(windows): use circular buffer for logging (#15163) +* fix(windows): handle forward slash for KEYMAN_ROOT env var (#15156) +* maint(mac): handle hdiutil detach failure correctly in make-km-dmg.sh (#15176) + +## 19.0.162 alpha 2025-11-14 + +* chore(linux): use `devel` for next Ubuntu release (#15157) +* maint(resources): add build timing report to builder (#15114) +* maint(resources): add `builder_launch` function (#15129) +* fix(web): improve determination of default path (#15159) +* maint(linux): free disk space before running autopkgtests (#15172) + +## 19.0.161 alpha 2025-11-13 + +* docs(android): Add documentation explaining Android keyboard safety warning dialog (#15136) + +## 19.0.160 alpha 2025-11-12 + +* fix(android): Change Alert Dialog instead of Toast if package missing keyboard and lexical-model files (#15098) +* chore(linux): add support for Ubuntu 26.04 LTS Resolute (#15137) + +## 19.0.159 alpha 2025-11-11 + +* maint(core): fix option defaults for Meson >= 1.1 (#15131) + +## 19.0.158 alpha 2025-11-10 + +* fix(web): add unit tests for DeadkeyTracker and fix a few bugs (#15113) + +## 19.0.157 alpha 2025-11-06 + +* chore(common): Consolidate platformtest.js keyboard to `/common/test/keyboards/` (#15034) +* chore(common): Consolidate test9469.js keyboard to `/common/test/keyboards/` (#15053) + +## 19.0.156 alpha 2025-11-05 + +* chore(linux): Update debian changelog (#14965) +* maint(resources): Docker container changes permissions to match the file owner uid and gid (#14900) +* fix(developer): remove package version if FollowKeyboardVersion is set in Package Editor (#15088) +* maint(windows): new timestamp server for test signing (#15091) + +## 19.0.155 alpha 2025-11-03 + +* maint(resources): auto merge on branch name rather than label (#15084) +* change(developer): remove kmlmc and kmlmp (#15083) + +## 19.0.154 alpha 2025-10-31 + +* chore(developer): further debugging for assertion failure (#15056) +* chore(developer): add breadcrumbs to trace sporadic crashes on exit (#15058) +* fix(developer): handle exceptions loading .keyman-touch-layout files (#15060) +* chore(developer): fix always-false nullish coalescing (#15061) +* chore(windows): upgrade VC++ projects to v143 (VS2022) (#15062) + +## 19.0.153 alpha 2025-10-30 + +* test(developer): add kmc-analyze tests for AnalyzeOskCharacterUse output formats (#15030) + +## 19.0.152 alpha 2025-10-30 + +* maint(resources): include sourcemaps in npm publish (#15051) + +## 19.0.151 alpha 2025-10-29 + +* maint(resources): add repository record for all published packages (#15049) + +## 19.0.150 alpha 2025-10-29 + +* maint(resources): set build flags for npm-publish (#15047) + +## 19.0.149 alpha 2025-10-29 + +* maint(resources): don't report status check on release builds for npm-publish (#15045) + +## 19.0.148 alpha 2025-10-29 + +* chore(web,android): Consolidate chirality.js test keyboard to `/common/test/resources/keyboards/` (#14989) +* maint(resources): npm publish - build all packages first (#15042) + +## 19.0.147 alpha 2025-10-28 + +* maint(resources): move npm package publishing to GitHub Actions (#15029) +* maint(resources): use actions/setup-node to upgrade npm (#15035) +* maint(resources): use a string for version, rather than number (#15036) +* maint(resources): set env variables in npm publish GHA (#15037) +* maint(mac): minor tweaks to mac setup, using build.sh (#15028) +* maint(resources): add build and test for npm packaging and add status check results (#15038) +* maint(resources): add write permission for statuses for npm-publish (#15039) +* maint(resources): skip nvm in npm-publish script (#15041) + +## 19.0.146 alpha 2025-10-27 + +* fix(mac): ignore non-Unicode key caps when preparing OSK (#15008) +* fix(developer): prevent ANSI keyboards crashing debugger (#15022) +* test(developer): add coverage tests for warning messages in kmc-analyze/AnalyzeOskCharacterUse (#15021) + +## 19.0.145 alpha 2025-10-24 + +* fix(developer): do not treat backslash as a string escape in syntax highlighting (#15001) +* fix(developer): handle missing `begin Unicode` in KMW compiler (#15002) +* test(developer): add test to kmc-analyze for Warn_PreviousMapFileCouldNotBeLoaded (#14038) + +## 19.0.144 alpha 2025-10-23 + +* chore(deps-dev): bump playwright from 1.46.1 to 1.56.1 (#14984) +* chore(web): revert "chore(deps-dev): bump playwright from 1.46.1 to 1.56.1" (#15000) + +## 19.0.143 alpha 2025-10-20 + +* fix(android): Set the main app background white (#14935) + +## 19.0.142 alpha 2025-10-17 + +* chore(common): add .model_info schema (#14967) +* chore(web): update helpURL documentation and float ui link (#14960) +* fix(linux): use `ibus` command to restart ibus (#14971) +* chore(linux): replace deprecated `parse_version` (#14973) + +## 19.0.141 alpha 2025-10-16 + +* refactor(linux): extract functions (#14937) +* refactor(linux): remove no-longer-needed Python 3.7 code (#14956) +* chore(linux): don't report errors to Sentry when running autopkgtests (#14938) +* fix(linux): fix installing keyboard in shared area (#14953) +* fix(linux): install schema during `keyman-config/build.sh install` (#14962) +* test(linux): add regression tests for cli tools to autopkgtests (#14950) + +## 19.0.140 alpha 2025-10-15 + +* fix(mac): add Microsoft Word to non-compliant app list (#14940) + +## 19.0.139 alpha 2025-10-13 + +* fix(windows): osk: handle simulated left Control event when AltGr pressed (#14909) + +## 19.0.138 alpha 2025-10-11 + +* fix(android): Update Sample1 text field to resize (#14922) + +## 19.0.137 alpha 2025-10-10 + +* chore(common): Update crowdin strings for Vietnamese (#14915) +* maint(resources): fix watch bug for common files, and add more logging for triggering builds (#14893) +* docs(developer): Update keyboard-editor.md to clarify when entire row will be deleted (#14919) + +## 19.0.136 alpha 2025-10-09 + +* fix(android): Refactor bottom inset calculations for in-app and system keyboard (#14873) +* fix(linux): replace `dbus-x11` dependency (#14895) +* chore(ios): renew certificate (#14914) + +## 19.0.135 alpha 2025-10-08 + +* fix(android): Convert longpress delay menu to ConstraintLayout (#14897) + +## 19.0.134 alpha 2025-10-07 + +* maint(resources): nest unit tests under parent block (#14871) + +## 19.0.133 alpha 2025-10-06 + +* chore(linux): Update debian changelog (#14856) +* maint(linux): fix integration tests in Docker (#14817) + +## 19.0.132 alpha 2025-10-03 + +* fix(windows): remove cached files in idle state (#14683) + +## 19.0.131 alpha 2025-10-02 + +* refactor(web): cleanup in test scripts (#14870) + +## 19.0.130 alpha 2025-09-30 + +* maint(linux): allow to remote debug integration tests in Docker (#14818) +* maint(resources): running the container for core in privileged mode (#14867) + +## 19.0.129 alpha 2025-09-29 + +* chore(linux): remove no longer needed lintian overrides (#14810) + +## 19.0.128 alpha 2025-09-26 + +* fix(windows): sort setup UI language selector according to language names (#13573) +* feat(windows): improve startup time by caching locale names (#14795) + +## 19.0.127 alpha 2025-09-25 + +* fix(common): registry clean-up enabled, no terminal in github action (#14824) +* maint(windows): check code signatures only in CI builds (#14815) +* chore(deps-dev): bump tar-fs from 3.0.9 to 3.1.1 (#14827) +* fix(windows): disable MSI shortcut advertisement (#14814) +* maint(resources): lookup 7z on path on Windows if SEVENZ_HOME is not set (#14816) +* fix(android): Apply bottom insets to system keyboard (#14808) +* fix(android): Fix bottom insets for Sample and FirstVoices apps (#14828) +* docs(android): Document getNavigationBarHeight API (#14830) + +## 19.0.126 alpha 2025-09-24 + +* maint(linux): remove several shellcheck directives (#14819) + +## 19.0.125 alpha 2025-09-23 + +* fix(common): some more tests running successfully in a container (#14784) + +## 19.0.124 alpha 2025-09-22 + +* maint(linux): improve `upload-to-debian` script (#14798) +* refactor(linux): remove a meson deprecation warning (#14801) +* refactor(linux): remove shellcheck warnings (#14802) +* refactor(resources): use array for Docker arguments (#14803) + +## 19.0.123 alpha 2025-09-19 + +* chore(linux): Update debian changelog (#14783) + +## 19.0.122 alpha 2025-09-18 + +* fix(android): Refresh the banner theme when system keyboard is changed (#14768) +* maint(common): update HISTORY.md for 18.0.241 (#14780) + +## 19.0.121 alpha 2025-09-17 + +* docs(developer): document `fileVersion` (#14766) +* maint(linux): allow to force package build on non-Keyman repo (#14749) +* feat(linux): add breadcrumbs and system information to Sentry error reports (#14765) +* maint(resources): fix broken test build trigger for `master` branch (#14770) +* chore(linux): remove automatic installation of `onboard-keyman` (#14772) + +## 19.0.120 alpha 2025-09-16 + +* refactor(linux): remove warning (#14761) +* fix(linux): don't crash on invalid languages in keyboard (#14760) + +## 19.0.119 alpha 2025-09-15 + +* fix(developer): transform slashes to backslashes in paths in Package Editor (#14721) +* fix(developer): restrict invalid characters in identifiers in kmcmplib (#14746) +* fix(common): add name for cleanup ghcr (#14755) + +## 19.0.118 alpha 2025-09-12 + +* chore(common): Update to Unicode 17.0 (#14728) +* fix(resources): match cr to lf change in pr-build-status.mjs (#14734) +* fix(common): merging build and publish step for docker (#14747) +* chore(developer): use icons.res to verify that configure is done (#14733) + +## 19.0.117 alpha 2025-09-11 + +* fix(resources): ignore KEYMAN_VERSION_FOR_FILENAME in test (#14730) +* fix(resources): pr build status - find 'failure' state in status checks and log (#14732) + +## 19.0.116 alpha 2025-09-10 + +* fix(windows): kmshell continues when sm is in waitingrestart and returns to idle (#14644) +* maint(linux): allow to skip API check (#14699) +* maint(resources): adding GitHub actions workflow for Docker builds (#14674) +* maint(developer): use versioned filenames for PR artifacts (#14701) +* maint(windows): use versioned filenames for PR artifacts (#14702) +* maint(android): use versioned filenames for PR artifacts (#14703) +* maint(ios): use versioned filenames for PR artifacts (#14704) +* maint(mac): use versioned filenames for PR artifacts (#14705) +* maint(resources): rename KEYMAN_VERSION_FOR_PR_FILENAME (#14720) + +## 19.0.115 alpha 2025-09-09 + +* fix(linux): don't crash if `kmp.json` is missing `keyboards` section (#14708) + +## 19.0.114 alpha 2025-09-08 + +* docs(web): Improve empty What's New page for Web (#14660) +* maint(linux): remove obsolete Makefile (#14694) +* maint(resources): honor configure comment character in commit message (#14695) +* fix(web): fix error trying to calculate style scaling effects (#14684) +* refactor(resources): remove shellcheck warnings (#14698) + +## 19.0.113 alpha 2025-09-04 + +* fix(android): Update KMSample1 layout when changing orientation (#14678) + +## 19.0.112 alpha 2025-09-03 + +* fix(developer): add clear messages about validation failures to New LM Project Parameters form (#14659) +* docs(developer): tweak get-started for kmc and add entry to HISTORY.md (#14667) +* fix(android): Cleanup list layout in Engine and FirstVoices for edge to edge (#14655) +* docs(common): update license date and owner (#14668) +* maint(linux): skip tarball creation for `Build-bot: build` buildLevel (#14671) +* docs(common): update docs Keyman Desktop → Keyman for Windows (#14669) +* feat(web): adds transform cataloging methods to predictive-text worker (#14581) +* feat(web): batch correction-search paths by related subsets (#14592) +* refactor(web): simplify edit-distance calculation-object inputs (#14618) + +## 19.0.111 alpha 2025-09-02 + +* change(developer): update SIL logo (#14656) +* fix(developer): remove incorrect offset for compiler error line (#14651) +* fix(developer): add clear messages about validation failures to New Project Parameters form (#14657) +* fix(developer): add clear messages about validation failures to New LDML Project Parameters form (#14658) +* maint: add registry usage for Docker images (#14648) + +## 19.0.110 alpha 2025-09-01 + +* maint(windows): for `build` buildlevel, do not publish artifacts (#14654) +* maint(linux): generate tar-ignore in `dist.sh` (#14639) +* fix: fixing some issues for bulding and testing with Docker containers (#14645) +* maint(linux): create temporary worktree for packaging (#14598) + +## 19.0.109 alpha 2025-08-31 + +* fix(developer): handle missing model id in package with compiler error (#14649) +* fix(developer): handle failure to create destination path when generating new project (#14650) + +## 19.0.108 alpha 2025-08-29 + +* fix(android): Fix insets for in-app vs system keyboard (#14619) +* maint(resources): ensure REPO_ROOT is fully resolved (#14643) + +## 19.0.107 alpha 2025-08-28 + +* maint(resources): fix parse bug in build-bot and add extra logging (#14611) +* fix(android): Update Gradle files to remove flatDir warnings (#14583) +* maint(resources): move build trigger scripts into resources/teamcity (#14607) +* chore(linux): Update debian changelog (#14590) +* docs: update package-editor page section details tab (#14585) +* fix(android): polyfill globalThis for API 28 devices (#14541) +* feat: support pagination in build bot PR commit reading (#14638) +* refactor(linux): remove shellcheck warnings and reformat (#14572) + ## 19.0.106 alpha 2025-08-27 * fix(windows): check if automatic updates off before prompt (#14295) @@ -663,6 +1039,64 @@ * refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168) * chore: increment to alpha 19.0 (#13187) +## 18.0.245 stable 2025-12-03 + +* chore(windows): VS2022 patch for stable-18.0 (#15081) +* maint(resources): use VERSION_ENVIRONMENT var for v18 (#15068) +* chore(linux): Update debian changelog (#14964) +* fix(windows): add check a fix for registry datatypes (#15175) +* chore(linux): Update debian changelog (#15132) +* feat(windows): update wix to 3.14.1 (#15195) +* fix(windows): reinstall low level keyboard hook if it gets removed (#15219) + +## 18.0.244 stable 2025-10-31 + +* fix(linux): use `ibus` command to restart ibus (#14975) +* fix(android): Set the main app background white (#14982) +* fix(developer): do not treat backslash as a string escape in syntax highlighting (#15005) +* fix(developer): handle missing `begin Unicode` in KMW compiler (#15006) +* fix(mac): ignore non-Unicode key caps when preparing OSK (#15009) +* maint(resources): move NPM package publishing to GitHub Actions (#15054) +* chore(developer): add breadcrumbs to trace sporadic crashes on exit (#15059) +* chore(developer): further debugging for assertion failure (#15057) + +## 18.0.243 stable 2025-10-15 + +* chore(linux): Update debian changelog (#14855) +* fix(android): Convert longpress delay menu to ConstraintLayout (#14906) +* fix(linux): replace `dbus-x11` dependency (#14898) +* chore(ios): renew certificate (#14917) +* fix(android): Refactor bottom inset calculations for inapp and system keyboard (#14913) +* fix(mac): add Microsoft Word to non-compliant app list (#14951) + +## 18.0.242 stable 2025-09-29 + +* maint(linux): remove Oracular, add Questing (#14781) +* maint(resources): use VERSION variable in increment-version.sh (#14779) +* chore(linux): Update debian changelog (#14782) +* fix(developer): remove incorrect offset for compiler error line (#14792) +* maint(linux): improve getting PR# in `upload-to-debian.sh` (#14785) +* chore(linux): remove no longer needed lintian overrides (#14811) +* fix(android): Apply bottom insets to system keyboard for Keyman, KMSample2, and FirstVoices (#14833) +* fix(windows): disable .msi shortcut advertisement (#14829) +* feat(windows): improve startup time by caching locale names (#14832) + +## 18.0.241 stable 2025-09-18 + +* maint(resources): use correct version for PR title for history cherry-pick (#14614) +* chore(linux): Update debian changelog (#14589) +* fix(android): Update: Android Target API to 35, Java to 21, and Gradle dependencies (#14584) +* fix(android): Convert Keyman app layouts for edge to edge (#14597) +* fix(android): Fix insets for in-app vs system keyboard (#14642) +* maint(linux): create temporary worktree for packaging (#14599) +* fix(android): Cleanup layouts for Engine, Sample apps, and FirstVoices apps for edge to edge (#14663) +* fix(web): fix error trying to calculate style scaling effects (#14696) +* fix(linux): don't crash if `kmp.json` is missing `keyboards` section (#14714) +* fix(developer): transform slashes to backslashes in paths in Package Editor (#14722) +* chore(linux): remove automatic installation of `onboard-keyman` (#14773) +* docs(linux): update release notes for stable release (#14774) +* fix(linux): don't crash on invalid languages in keyboard (#14771) + ## 18.0.240 stable 2025-08-27 * maint(resources): automatically cherry-pick history updates to master (#14596) @@ -674,6 +1108,7 @@ * docs(linux): clarify requirement of patched ibus for Keyman < 18 (#14113) * chore(linux): Update debian changelog (#14118) * fix(windows): add values wucUpdateAvailable and wucNotChecked to TRemoteUpdateCheckResult enum (#14208) +* fix(windows): fix download failing when cache.json missing (#14207) * fix(web): deploy `web/build/publish` folder (#14238) * fix(ios): restore keyboard scale map for iPad Air 2 (#14306) * chore(developer): upgrade multer to 2.0.2 (#14417) diff --git a/VERSION.md b/VERSION.md index 84e9e82913..430c06443e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.107 \ No newline at end of file +19.0.171 \ No newline at end of file diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index 74fed10588..06370c06d2 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -46,7 +46,7 @@ MODEL_PACKAGE_ID="nrc.en.mtnt" MODELS_TARGET="android/KMAPro/kMAPro/src/main/assets/${MODEL_PACKAGE_ID}.model.kmp" builder_describe_outputs \ configure "/${MODELS_TARGET}" \ - build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk + build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk #### Build @@ -77,13 +77,15 @@ if builder_start_action build; then # Copy Keyman Engine for Android cp "${KEYMAN_ROOT}/android/KMEA/app/build/outputs/aar/keyman-engine-${CONFIG}.aar" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" - + # Convert markdown to html for offline help build_help_html android KMAPro/kMAPro/src/main/assets/info echo "BUILD_FLAGS $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS + mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk" + builder_finish_action success build fi diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index f6acd350da..97f8ab7d38 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -35,6 +35,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.widget.FrameLayout; +import android.content.Intent; import io.sentry.android.core.SentryAndroid; import io.sentry.Sentry; @@ -124,8 +125,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven } ViewGroup parent = (ViewGroup) inputView.getParent(); - if (parent != null) + if (parent != null) { parent.removeView(inputView); + } return inputView; } @@ -139,6 +141,22 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); } + private void sendCurrentFontName() { + Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); + if (keyboardInfo != null) { + String fontName = keyboardInfo.getFont(); + + Intent intent; + if (BuildConfig.DEBUG) { + intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); + } else { + intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); + } + intent.putExtra("fontName", fontName); + sendBroadcast(intent); + } + } + /** * This is the main point where we do our initialization of the input method * to begin operating on an application. At this point we have been @@ -197,6 +215,10 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven if (KMManager.isNumericField(inputType)) { KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); } + + if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + sendCurrentFontName(); + } } @Override @@ -237,7 +259,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.onConfigurationChanged(newConfig); } - // We should extend the touchable region so that Keyman sub keys menu can receive touch events outside the keyboard frame + // Update the touchable region of the Keyman keyboard Point size = KMManager.getWindowSize(getApplicationContext()); int inputViewHeight = 0; @@ -245,9 +267,10 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven inputViewHeight = inputView.getHeight(); } + int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); int bannerHeight = KMManager.getBannerHeight(this); int kbHeight = KMManager.getKeyboardHeight(this); - outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight; + outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; outInsets.visibleTopInsets = outInsets.contentTopInsets; outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); @@ -263,7 +286,10 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onKeyboardChanged(String newKeyboard) { + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); KMManager.showSystemKeyboard(); + sendCurrentFontName(); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java index cc23b2d2ed..0afd3e5b28 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java @@ -95,9 +95,10 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { currentHeight -= y; // Apply lower and upper bounds on currentHeight - int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height); - currentHeight = Math.max(defaultHeight/2, currentHeight); - currentHeight = Math.min(defaultHeight*2, currentHeight); + int minKeyboardHeight = KMManager.getKeyboardHeightMin(context); + int maxKeyboardHeight = KMManager.getKeyboardHeightMax(context); + currentHeight = Math.max(minKeyboardHeight, currentHeight); + currentHeight = Math.min(maxKeyboardHeight, currentHeight); refreshSampleKeyboard(context); break; diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java index 596c247d09..e0d7c9d5f5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustLongpressDelayActivity.java @@ -57,6 +57,10 @@ public class AdjustLongpressDelayActivity extends BaseActivity { final Context context = this; setContentView(R.layout.activity_adjust_longpress_delay); + + setupEdgeToEdge(R.id.adjust_longpress_layout); + setupStatusBarColors(R.color.keyman_blue, android.R.color.white); + Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index b422d7e881..89ad50d046 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -619,22 +619,19 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene getTheme().resolveAttribute(android.R.attr.actionBarSize, outValue, true); int actionBarHeight = getResources().getDimensionPixelSize(outValue.resourceId); - // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** + // Status bar height only used for portrait orientation int statusBarHeight = 0; - int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) - statusBarHeight = getResources().getDimensionPixelSize(resourceId); - - // Navigation bar height - int navigationBarHeight = 0; - resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId > 0) { - navigationBarHeight = getResources().getDimensionPixelSize(resourceId); + if (lastOrientation == Configuration.ORIENTATION_PORTRAIT) { + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + statusBarHeight = getResources().getDimensionPixelSize(resourceId); + } } + int navigationBarHeight = KMManager.getNavigationBarHeight(context, KeyboardType.KEYBOARD_TYPE_INAPP); Point size = KMManager.getWindowSize(context); int screenHeight = size.y; - Log.d(TAG, "Main resizeTextView bannerHeight: " + bannerHeight); textView.setHeight( screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java index 28517ef04a..c065eec5af 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/PackageActivity.java @@ -85,7 +85,8 @@ public class PackageActivity extends BaseActivity implements if (pkgTarget.equals(PackageProcessor.PP_TARGET_LEXICAL_MODELS)) { kmpProcessor = new LexicalModelPackageProcessor(resourceRoot); } else if (!pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) { - showErrorToast(getString(R.string.no_targets_to_install)); + // Not using showErrorToast to avoid cluttering Sentry with noise + showErrorDialog(context, pkgId, getString(R.string.no_targets_to_install)); return; } tempPackagePath = kmpProcessor.unzipKMP(kmpFile); diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml index ef1ebd613b..3b4e390257 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_longpress_delay.xml @@ -1,8 +1,11 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> @@ -13,7 +16,8 @@ android:gravity="center" android:orientation="horizontal" android:paddingBottom="25dp" - android:paddingTop="60dp" > + android:paddingTop="60dp" + app:layout_constraintTop_toBottomOf="@id/titlebar" > + app:layout_constraintTop_toBottomOf="@id/linearLayout" /> - + diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml index 5a468df05e..ffcd4a2421 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_main.xml @@ -8,7 +8,7 @@ android:fitsSystemWindows="true" android:layout_above="@+id/KMKeyboard" android:orientation="vertical" - android:background="@color/yellow_background" + android:background="@android:color/white" tools:context=".MainActivity"> = Build.VERSION_CODES.M) { - window.setStatusBarColor(getColor(statusBarColor)); - window.setNavigationBarColor(getColor(navigationBarColor)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + WindowInsetsControllerCompat windowInsetsController = + WindowCompat.getInsetsController(window, window.getDecorView()); + windowInsetsController.setAppearanceLightStatusBars(true); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + window.setStatusBarColor(getColor(statusBarColor)); + window.setNavigationBarColor(getColor(navigationBarColor)); + } } } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 4061100002..9152f84360 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -453,10 +453,22 @@ final class KMKeyboard extends WebView { public void onResume() { DisplayMetrics dms = context.getResources().getDisplayMetrics(); int kbWidth = (int) (dms.widthPixels / dms.density); + + // Get the correct keyboard height for current orientation + int kbHeight = KMManager.getKeyboardHeight(context); + + // Check if there's a pending height update for this specific keyboard type and orientation + int currentOrientation = KMManager.getOrientation(context); + if (KMManager.getAndClearPendingHeightUpdate(this.keyboardType, currentOrientation)) { + // There was a pending height update - force apply it now + RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); + this.setLayoutParams(params); + } + // Ensure window is loaded for javascript functions loadJavascript(KMString.format( "window.onload = function(){ setOskWidth(\"%d\");"+ - "setOskHeight(\"0\"); };", kbWidth)); + "setOskHeight(\"%d\"); };", kbWidth, kbHeight)); if (this.getShouldShowHelpBubble()) { this.showHelpBubbleAfterDelay(2000); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 60218cb2de..f87219f512 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -42,6 +42,7 @@ import android.view.Display; import android.view.Surface; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; import android.view.inputmethod.EditorInfo; @@ -52,6 +53,10 @@ import android.widget.FrameLayout; import android.widget.RelativeLayout; import androidx.core.content.ContextCompat; +import androidx.core.graphics.Insets; +import androidx.core.view.DisplayCutoutCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import io.sentry.Breadcrumb; import io.sentry.Sentry; @@ -86,6 +91,8 @@ public final class KMManager { private static final String TAG = "KMManager"; private static ResourcesUpdateTool updateTool; + private static int inappBottomInset; // Bottom inset for in-app keyboard + private static int sysBottomInset; // Bottom inset for system keyboard // Keyboard types public enum KeyboardType { @@ -301,6 +308,12 @@ public final class KMManager { private static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait"; private static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape"; + // Preference keys for tracking pending keyboard height updates per keyboard type and orientation + private static final String KMKey_PendingHeightUpdate_InappPortrait = "pendingHeightUpdate_inapp_portrait"; + private static final String KMKey_PendingHeightUpdate_InappLandscape = "pendingHeightUpdate_inapp_landscape"; + private static final String KMKey_PendingHeightUpdate_SystemPortrait = "pendingHeightUpdate_system_portrait"; + private static final String KMKey_PendingHeightUpdate_SystemLandscape = "pendingHeightUpdate_system_landscape"; + public static final String KMKey_LongpressDelay = "longpressDelay"; public static final String KMKey_CustomHelpLink = "CustomHelpLink"; @@ -701,10 +714,6 @@ public final class KMManager { public static RelativeLayout.LayoutParams getKeyboardLayoutParams() { int bannerHeight = getBannerHeight(appContext); int kbHeight = getKeyboardHeight(appContext); - int navigationHeight = getNavigationBarHeight(appContext); - int orientation = getOrientation(appContext); - FormFactor formFactor = getFormFactor(); - RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, bannerHeight + kbHeight); params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); @@ -713,24 +722,28 @@ public final class KMManager { /** * Applies padding to the keyboard so it's not covered by system insets + * @param {KeyboardType} keyboard * @param left int padding to account for notch/navigation bar on left side of screen * @param right int padding to account for notch/navigation bar on right side of screen * @param bottom int padding to account for navigation bar on bottom of screen */ - public static void applyInsetsToKeyboard(int left, int right, int bottom) { - // System keyboard - if (SystemKeyboard != null) { - applyInsetsPaddingToKeyboardView(SystemKeyboard, left, right, bottom); + public static void applyInsetsToKeyboard(KeyboardType keyboardType, int left, int right, int bottom) { + if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { + applyInsetsPaddingToKeyboardView(InAppKeyboard, left, right, bottom); } - // In-App Keyboard - if (InAppKeyboard != null) { - applyInsetsPaddingToKeyboardView(InAppKeyboard, left, right, bottom); + if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { + applyInsetsPaddingToKeyboardView(SystemKeyboard, left, right, bottom); } } private static void applyInsetsPaddingToKeyboardView(KMKeyboard keyboard, int left, int right, int bottom) { View parent = (View) keyboard.getParent(); + if (keyboard.keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) { + inappBottomInset = bottom; + } else if (keyboard.keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + sysBottomInset = bottom; + } if (parent != null) { // Keep existing top padding, add bottom padding for the inset @@ -742,8 +755,7 @@ public final class KMManager { ); // No need to change WebView margins; keep height as-is parent.requestLayout(); - } - else { + } else { // Fallback: apply on the WebView itself keyboard.setPadding( left, @@ -883,28 +895,46 @@ public final class KMManager { @SuppressLint("InflateParams") public static View createInputView(InputMethodService inputMethodService) { - //final Context context = appContext; IMService = inputMethodService; Context appContext = IMService.getApplicationContext(); final FrameLayout mainLayout = new FrameLayout(appContext); mainLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); if (SystemKeyboard == null) { + installSystemKeyboardInsetsListener(mainLayout); return mainLayout; } RelativeLayout keyboardLayout = new RelativeLayout(appContext); keyboardLayout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); ViewGroup parent = (ViewGroup) SystemKeyboard.getParent(); - if (parent != null) + if (parent != null) { parent.removeView(SystemKeyboard); + } keyboardLayout.addView(SystemKeyboard); mainLayout.addView(keyboardLayout); //mainLayout.addView(overlayLayout); + + installSystemKeyboardInsetsListener(mainLayout); return mainLayout; } + private static void installSystemKeyboardInsetsListener(View anchor) { + ViewCompat.setOnApplyWindowInsetsListener(anchor, (v, insets) -> { + // System bars (status bar and navigation bar) + Insets systemBarInsets = insets.getInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()); + + sysBottomInset = systemBarInsets.bottom; + applyInsetsToKeyboard(KeyboardType.KEYBOARD_TYPE_SYSTEM, systemBarInsets.left, systemBarInsets.right, sysBottomInset); + return insets; + }); + + // Trigger an initial pass + ViewCompat.requestApplyInsets(anchor); + } + public static void onStartInput(EditorInfo attribute, boolean restarting) { if (!restarting && SystemKeyboard != null) { String packageName = attribute.packageName; @@ -2391,12 +2421,11 @@ public final class KMManager { } 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); - } + // Ensure keyboard height stay within min/max bounds + int minKeyboardHeight = KMManager.getKeyboardHeightMin(context, orientation); + int maxKeyboardHeight = KMManager.getKeyboardHeightMax(context, orientation); + height = Math.max(minKeyboardHeight, height); + height = Math.min(maxKeyboardHeight, height); // Store the new height based on the current orientation if (orientation == Configuration.ORIENTATION_LANDSCAPE) { @@ -2413,16 +2442,22 @@ public final class KMManager { KeyboardHeight_Context_Portrait_Current = height; } editor.commit(); + // Confirm new LayoutParams for in-app or system keyboards + // If keyboard is not currently loaded, set a pending flag so height gets applied when keyboard loads if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { InAppKeyboard.loadJavascript(KMString.format("setOskHeight('%s')", height)); RelativeLayout.LayoutParams params = getKeyboardLayoutParams(); InAppKeyboard.setLayoutParams(params); + } else { + setPendingHeightUpdate(KeyboardType.KEYBOARD_TYPE_INAPP, orientation); } if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { SystemKeyboard.loadJavascript(KMString.format("setOskHeight('%s')", height)); RelativeLayout.LayoutParams params = getKeyboardLayoutParams(); SystemKeyboard.setLayoutParams(params); + } else { + setPendingHeightUpdate(KeyboardType.KEYBOARD_TYPE_SYSTEM, orientation); } } @@ -2473,21 +2508,179 @@ public final class KMManager { KeyboardHeight_Context_Landscape_Default = (int) newResources.getDimension(R.dimen.keyboard_height); } - // Get the navigation bar height (if visible) in pixels - private static int getNavigationBarHeight(Context context) { - // Determine if navigation bar is visible - int resourceId = context.getResources().getIdentifier( - "config_showNavigationBar", "bool", "android"); - if (resourceId <= 0) { - return 0; + /** + * Get the navigation bar height (if visible) in pixels + * @param context - The context + * @param {KeyboardType} - KeyboardType.KEYBOARD_TYPE_INAPP or KeyboardType.KEYBOARD_TYPE_SYSTEM + * @return int - navigation bar height in pixels + */ + public static int getNavigationBarHeight(Context context, KeyboardType keyboardType) { + // bottomInset already updated from the inset listener + if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) { + return inappBottomInset; + } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + return sysBottomInset; } - // Navigation bar visible so get the height - resourceId = context.getResources().getIdentifier( + // Navigation bar height uninitialized, so log and revert to legacy computation. + // Note. Inconsistent for gesture vs 3-button navigation mode #14893 + KMLog.LogError(TAG, "bottom inset not initialized for keyboard type: " + keyboardType); + int resourceId = context.getResources().getIdentifier( "navigation_bar_height", "dimen", "android"); return (resourceId > 0) ? context.getResources().getDimensionPixelSize(resourceId) : 0; } + /** + * Returns the maximum allowed height of the keyboard frame for the current device orientation. + * The maximum height is calculated as twice the default keyboard height for the orientation. + * Use this method to get the upper bound for keyboard height adjustments. + * + * @param context The context. + * @return The maximum allowed keyboard height in density-independent pixels (dp), + * or {@link #KeyboardHeight_Reset} (0) if orientation is invalid. + * @see #getKeyboardHeightMax(Context, int) + * @see #getKeyboardHeightMin(Context) + * @see #applyKeyboardHeight(Context, int) + */ + public static int getKeyboardHeightMax(Context context) { + int orientation = getOrientation(context); + return getKeyboardHeightMax(context, orientation); + } + + /** + * Returns the maximum allowed height of the keyboard frame for the specified orientation. + * The maximum height is calculated as twice the default keyboard height for the orientation. + * Use this method to get the upper bound for keyboard height adjustments. + * + * @param context The context. + * @param orientation The screen orientation: {@link Configuration#ORIENTATION_PORTRAIT} + * or {@link Configuration#ORIENTATION_LANDSCAPE}. + * @return The maximum allowed keyboard height in density-independent pixels (dp), + * or {@link #KeyboardHeight_Reset} (0) if orientation is invalid. + * @see #getKeyboardHeightMin(Context, int) + * @see #applyKeyboardHeight(Context, int, int) + */ + public static int getKeyboardHeightMax(Context context, int orientation) { + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return (int) KMManager.KeyboardHeight_Context_Landscape_Default * 2; + } else /* orientation == Configuration.ORIENTATION_PORTRAIT */ { + return (int) KMManager.KeyboardHeight_Context_Portrait_Default * 2; + } + } + + /** + * Returns the minimum allowed height of the keyboard frame for the current device orientation. + * The minimum height is calculated as half the default keyboard height for the orientation. + * Use this method to get the lower bound for keyboard height adjustments. + * + * @param context The context. + * @return The minimum allowed keyboard height in density-independent pixels (dp), + * or {@link #KeyboardHeight_Reset} (0) if orientation is invalid. + * @see #getKeyboardHeightMin(Context, int) + * @see #getKeyboardHeightMax(Context) + * @see #applyKeyboardHeight(Context, int) + */ + public static int getKeyboardHeightMin(Context context) { + int orientation = getOrientation(context); + return getKeyboardHeightMin(context, orientation); + } + + /** + * Returns the minimum allowed height of the keyboard frame for the specified orientation. + * The minimum height is calculated as half the default keyboard height for the orientation. + * Use this method to get the lower bound for keyboard height adjustments. + * + * @param context The context. + * @param orientation The screen orientation: {@link Configuration#ORIENTATION_PORTRAIT} + * or {@link Configuration#ORIENTATION_LANDSCAPE}. + * @return The minimum allowed keyboard height in density-independent pixels (dp), + * or {@link #KeyboardHeight_Reset} (0) if orientation is invalid. + * @see #getKeyboardHeightMax(Context, int) + * @see #applyKeyboardHeight(Context, int, int) + */ + public static int getKeyboardHeightMin(Context context, int orientation) { + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return (int) KMManager.KeyboardHeight_Context_Landscape_Default / 2; + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return (int) KMManager.KeyboardHeight_Context_Portrait_Default / 2; + } else { + return KMManager.KeyboardHeight_Reset; + } + } + + /** + * Returns the preference key for tracking pending keyboard height updates. + * This allows separate tracking for each keyboard type (in-app/system) and orientation. + * + * @param keyboardType The keyboard type: {@link KeyboardType#KEYBOARD_TYPE_INAPP} + * or {@link KeyboardType#KEYBOARD_TYPE_SYSTEM}. + * @param orientation The screen orientation: {@link Configuration#ORIENTATION_PORTRAIT} + * or {@link Configuration#ORIENTATION_LANDSCAPE}. + * @return The preference key string, or null if parameters are invalid. + */ + private static String getPendingHeightKey(KeyboardType keyboardType, int orientation) { + if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) { + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return KMKey_PendingHeightUpdate_InappPortrait; + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return KMKey_PendingHeightUpdate_InappLandscape; + } + } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return KMKey_PendingHeightUpdate_SystemPortrait; + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return KMKey_PendingHeightUpdate_SystemLandscape; + } + } + return null; + } + + /** + * Sets a flag indicating that a keyboard height update is pending for the specified + * keyboard type and orientation. This is used when the keyboard is not currently loaded, + * so the height update will be applied when the keyboard loads. + * + * @param keyboardType The keyboard type: {@link KeyboardType#KEYBOARD_TYPE_INAPP} + * or {@link KeyboardType#KEYBOARD_TYPE_SYSTEM}. + * @param orientation The screen orientation: {@link Configuration#ORIENTATION_PORTRAIT} + * or {@link Configuration#ORIENTATION_LANDSCAPE}. + */ + private static void setPendingHeightUpdate(KeyboardType keyboardType, int orientation) { + String pendingKey = getPendingHeightKey(keyboardType, orientation); + if (pendingKey == null) { + return; + } + SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(pendingKey, true); + editor.commit(); + } + + /** + * Checks if there is a pending keyboard height update for the specified keyboard type + * and orientation, and clears the flag if present. + * + * @param keyboardType The keyboard type: {@link KeyboardType#KEYBOARD_TYPE_INAPP} + * or {@link KeyboardType#KEYBOARD_TYPE_SYSTEM}. + * @param orientation The screen orientation: {@link Configuration#ORIENTATION_PORTRAIT} + * or {@link Configuration#ORIENTATION_LANDSCAPE}. + * @return true if there was a pending height update (which has been cleared), false otherwise. + */ + protected static boolean getAndClearPendingHeightUpdate(KeyboardType keyboardType, int orientation) { + String pendingKey = getPendingHeightKey(keyboardType, orientation); + if (pendingKey == null) { + return false; + } + SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); + boolean hasPending = prefs.getBoolean(pendingKey, false); + if (hasPending) { + SharedPreferences.Editor editor = prefs.edit(); + editor.remove(pendingKey); + editor.commit(); + } + return hasPending; + } + /** * Get the size of the area the window would occupy. * API 30+ diff --git a/android/KMEA/app/src/main/res/layout/activity_list_layout.xml b/android/KMEA/app/src/main/res/layout/activity_list_layout.xml index cb1044524b..c865d841b6 100644 --- a/android/KMEA/app/src/main/res/layout/activity_list_layout.xml +++ b/android/KMEA/app/src/main/res/layout/activity_list_layout.xml @@ -9,6 +9,7 @@ android:orientation="vertical"> diff --git a/android/KMEA/app/src/main/res/layout/list_layout.xml b/android/KMEA/app/src/main/res/layout/list_layout.xml index aeea32ab35..0bd7dfd224 100644 --- a/android/KMEA/app/src/main/res/layout/list_layout.xml +++ b/android/KMEA/app/src/main/res/layout/list_layout.xml @@ -1,15 +1,20 @@ - - + + + android:clipToPadding="false" + android:paddingBottom="?attr/actionBarSize" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent"/> diff --git a/android/KMEA/app/src/main/res/layout/list_toolbar.xml b/android/KMEA/app/src/main/res/layout/list_toolbar.xml index cd90384196..fcfd16377f 100644 --- a/android/KMEA/app/src/main/res/layout/list_toolbar.xml +++ b/android/KMEA/app/src/main/res/layout/list_toolbar.xml @@ -1,45 +1,43 @@ - + - + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent"> - + android:layout_gravity="center_horizontal" + android:gravity="center" + android:ellipsize="end" + android:singleLine="true" + android:text="@string/title_add_keyboard" + android:textSize="@dimen/titlebar_label_textsize" + android:textStyle="bold" /> - + - + + - - - - diff --git a/android/KMEA/app/src/main/res/values-vi-rVN/strings.xml b/android/KMEA/app/src/main/res/values-vi-rVN/strings.xml index 5715cf325b..bf130166e3 100644 --- a/android/KMEA/app/src/main/res/values-vi-rVN/strings.xml +++ b/android/KMEA/app/src/main/res/values-vi-rVN/strings.xml @@ -80,6 +80,14 @@ Lỗi bàn phím nghiêm trọng với %1$s: %2$s cho ngôn ngữ %3$s. Đang tải bàn phím mặc định. Lỗi trong bàn phím %1$s:%2$s cho ngôn ngữ %3$s. + + Keyman yêu cầu phải cài đặt WebView\". + + Keyman yêu cầu phải bật WebView\". + + Cài đặt WebView + + Bật WebView Kiểm tra từ điển liên quan để tải về Không thể kết nối với server của Keyman để kiểm tra từ điển được liên kết để tải xuống @@ -113,6 +121,8 @@ Không thể thu hồi file đã tải về Không thể kết nối với máy chủ! + + DownloadManager đâ bị vô hiệu hoá - không thể kiểm tra các bản cập nhật "Toàn bộ kho dữ liệu đã được cập nhật!" diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 86e1f5af22..1cc8529389 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -2,7 +2,9 @@ package com.keyman.kmsample1; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Point; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; @@ -25,6 +27,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public static Context context; private ConstraintLayout constraintLayout; private KMTextView textView; + private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; @Override protected void onCreate(Bundle savedInstanceState) { @@ -99,6 +102,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene protected void onResume() { super.onResume(); KMManager.onResume(); + + // onConfigurationChanged() only triggers when device is rotated while app is in foreground + // This handles when device is rotated while app is in background + // using KMManager.getOrientation() since getConfiguration().orientation is unreliable #10241 + int newOrientation = KMManager.getOrientation(context); + if (newOrientation != lastOrientation) { + lastOrientation = newOrientation; + Configuration newConfig = this.getResources().getConfiguration(); + KMManager.onConfigurationChanged(newConfig); + } + resizeTextView(textView.isKeyboardVisible()); + KMManager.addKeyboardEventListener(this); KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); } @@ -116,6 +131,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + KMManager.onConfigurationChanged(newConfig); + + resizeTextView(textView.isKeyboardVisible()); + lastOrientation = newConfig.orientation; } @Override @@ -131,11 +150,39 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onKeyboardShown() { // Handle Keyman keyboard shown event here if needed + resizeTextView(true); } @Override public void onKeyboardDismissed() { // Handle Keyman keyboard dismissed event here if needed + resizeTextView(false); + } + + public void resizeTextView(boolean isKeyboardVisible) { + int bannerHeight = 0; + int keyboardHeight = 0; + if (isKeyboardVisible) { + bannerHeight = KMManager.getBannerHeight(this); + keyboardHeight = KMManager.getKeyboardHeight(this); + } + + TypedValue outValue = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.actionBarSize, outValue, true); + int actionBarHeight = getResources().getDimensionPixelSize(outValue.resourceId); + + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** + int statusBarHeight = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + statusBarHeight = getResources().getDimensionPixelSize(resourceId); + } + int navigationBarHeight = KMManager.getNavigationBarHeight(context, KeyboardType.KEYBOARD_TYPE_INAPP); + + Point size = KMManager.getWindowSize(context); + int screenHeight = size.y; + textView.setHeight( + screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } @Override diff --git a/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml b/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml index 6ec32a3923..f87524457e 100644 --- a/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml +++ b/android/Samples/KMSample1/app/src/main/res/layout/activity_main.xml @@ -1,3 +1,4 @@ + + 2.0 $PROJECTPATH + $PROJECTPATH\ True - True True + True lexicalmodel - - - id_165fd1598b940efe4a973aeb6eb61aba - keyboardharness.kps - keyboardharness.kps - 1.0 - .kps -
- keyboardharness - ©2020 SIL International - 1.0 -
-
- - id_f9b1213d4e83cb32ce1ead1ee852d618 - chirality.js - ..\..\..\..\..\..\..\web\testing\chirality\chirality.js - - .js - id_165fd1598b940efe4a973aeb6eb61aba - - - id_e01841691fa129428aea555142873f69 - platformtest.js - ..\..\..\..\..\..\..\web\testing\platform\platformtest.js - - .js - id_165fd1598b940efe4a973aeb6eb61aba - - - id_03df89bb84a1aa6cab40dfebf3a04c5b - code2001.ttf - code2001.ttf - - .ttf - id_165fd1598b940efe4a973aeb6eb61aba - - - id_2d07bd2b8c94e9600ac1a920c8cb9e0a - longpress.js - longpress.js - - .js - id_165fd1598b940efe4a973aeb6eb61aba - -
diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kps b/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kps index 157aefd71c..f18aac7a8e 100644 --- a/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kps +++ b/android/Tests/KeyboardHarness/app/src/main/assets/keyboardharness.kps @@ -20,17 +20,23 @@ - ..\..\..\..\..\..\..\web\src\test\manual\web\chirality\chirality.js + ../../../../../../../common/test/resources/keyboards/chirality.js File chirality.js 0 .js - ..\..\..\..\..\..\..\web\src\test\manual\web\platform\platformtest.js + ../../../../../../../common/test/keyboards/platform-rules/platformtest.js File platformtest.js 0 .js + + ../../../../../../../common/test/keyboards/test9469/build/test9469.js + File test9469.js + 0 + .js + code2001.ttf Font Code2001 @@ -61,6 +67,14 @@ English + + Test9469 + test9469 + 1.0 + + English + + longpress '"\|5% + longpress diff --git a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp b/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp deleted file mode 100644 index 8390d5dee3..0000000000 Binary files a/android/Tests/KeyboardHarness/app/src/main/assets/test9469.kmp and /dev/null differ diff --git a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java index 7b73341786..becec2868a 100644 --- a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java +++ b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java @@ -89,6 +89,21 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.KMDefault_KeyboardFont); KMManager.addKeyboard(this, platformtestKBbInfo); + // Issue #9469 Verify special characters in keyamnweb-osk.ttf keyboard + Keyboard specialKBInfo = new Keyboard( + "keyboardharness", + "test9469", + "test9469 Keyboard", + "en", + "English", + "1.0", + "", + "", + true, + KMManager.KMDefault_KeyboardFont, + KMManager.KMDefault_KeyboardFont); + KMManager.addKeyboard(this, specialKBInfo); + // Final K_ENTER test keyboard Keyboard finalKBInfo = new Keyboard( "final", @@ -103,21 +118,6 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene KMManager.KMDefault_KeyboardFont, KMManager.KMDefault_KeyboardFont); KMManager.addKeyboard(this, finalKBInfo); - - // Issue #9469 Verify special characters in keyamnweb-osk.ttf keyboard - Keyboard specialKBInfo = new Keyboard( - "test9469", - "test9469", - "test9469 Keyboard", - "en", - "English", - "1.0", - "", - "", - true, - KMManager.KMDefault_KeyboardFont, - KMManager.KMDefault_KeyboardFont); - KMManager.addKeyboard(this, specialKBInfo); } @Override diff --git a/android/build.sh b/android/build.sh index 9d48effbf1..54e61dd9a5 100755 --- a/android/build.sh +++ b/android/build.sh @@ -62,11 +62,12 @@ function do_test_help() { function archive_artifacts() { local UPLOAD_PATH KEYMAN_ENGINE_ANDROID_ZIP KEYMAN_APK FIRSTVOICES_APK + local KEYMAN_FULLY_VERSIONED_APK FIRSTVOICES_FULLY_VERSIONED_APK 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" + KEYMAN_APK="keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk" + FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.apk" rm -rf "${UPLOAD_PATH}" mkdir -p "${UPLOAD_PATH}" @@ -87,12 +88,12 @@ function archive_artifacts() { ) # Copy release APK - cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}/${KEYMAN_APK}" # FirstVoices app if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then - cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}/${FIRSTVOICES_APK}" fi # diff --git a/android/docs/engine/KMAPro/index.md b/android/docs/engine/KMAPro/index.md new file mode 100644 index 0000000000..9e93f57b61 --- /dev/null +++ b/android/docs/engine/KMAPro/index.md @@ -0,0 +1,23 @@ +--- +title: Keyman for Android APIs +--- + +## Summary + +The **`Keyman for Android`** app has the following APIs for integration: + + +## Description + +The Keyman for Android app installs as an Input Method Extension. When selected as the active system keyboard, it provides the following APIs: + + +## Intents + +When the Keyman system keyboard changes the associated font name, the following Intent is broadcast so the integrating application can match the font: + +```java +intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); +intent.putExtra("fontName", fontName); +sendBroadcast(intent); +``` diff --git a/android/docs/engine/KMManager/applyKeyboardHeight.md b/android/docs/engine/KMManager/applyKeyboardHeight.md index 70af3e7652..01460841d9 100644 --- a/android/docs/engine/KMManager/applyKeyboardHeight.md +++ b/android/docs/engine/KMManager/applyKeyboardHeight.md @@ -23,7 +23,7 @@ KMManager.applyKeyboardHeight(Context context, int height, int orientation) : The context `height` -: 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. +: The height of the keyboard frame in *density-independent pixels (dp)*. Pass `KMManager.KeyboardHeight_Reset` to reset the keyboard height (for the 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. @@ -78,3 +78,5 @@ The following script illustrates the use of `applyKeyboardHeight()` to reset key ## See also * [getKeyboardHeight()](getKeyboardHeight) +* [getKeyboardHeightMax()](getKeyboardHeightMax) +* [getKeyboardHeightMin()](getKeyboardHeightMin) diff --git a/android/docs/engine/KMManager/getKeyboardHeight.md b/android/docs/engine/KMManager/getKeyboardHeight.md index fb8efb2b96..4cb13d3266 100644 --- a/android/docs/engine/KMManager/getKeyboardHeight.md +++ b/android/docs/engine/KMManager/getKeyboardHeight.md @@ -55,3 +55,5 @@ or ## See also - [applyKeyboardHeight()](applyKeyboardHeight) +- [getKeyboardHeightMax](getKeyboardHeightMax) +- [getKeyboardHeightMin](getKeyboardHeightMin) diff --git a/android/docs/engine/KMManager/getKeyboardHeightMax.md b/android/docs/engine/KMManager/getKeyboardHeightMax.md new file mode 100644 index 0000000000..7132ebed40 --- /dev/null +++ b/android/docs/engine/KMManager/getKeyboardHeightMax.md @@ -0,0 +1,57 @@ +--- +title: KMManager.getKeyboardHeightMax() +--- + +## Summary + +The **`getKeyboardHeightMax()`** method returns the maximum allowed height of the keyboard frame for this context. + +## Syntax + +``` javascript +KMManager.getKeyboardHeightMax(Context context) +``` +or +``` javascript +KMManager.getKeyboardHeightMax(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` is not defined, maximum keyboard height is returned for the current device orientation. + +### Returns + +Returns the maximum allowed height of the keyboard frame in *density-independent pixels +(dp)*. + +## Description + +Use this method to get the maximum allowed height of the keyboard frame. + +## Examples + +### Example: Using `getKeyboardHeightMax()` + +The following script illustrate the use of `getKeyboardHeightMax()`: + +``` javascript + int maxKeyboardHeight = KMManager.getKeyboardHeightMax(this); +``` +or +```java + import android.content.res.Configuration; + ... + // Get the maximum allowed Keyman keyboard height for landscape mode. + int maxKeyboardHeightLandscape = KMManager.getKeyboardHeightMax(this, Configuration.ORIENTATION_LANDSCAPE); +``` + +## See also + +- [applyKeyboardHeight()](applyKeyboardHeight) +- [getKeyboardHeight()](getKeyboardHeight) +- [getKeyboardHeightMin()](getKeyboardHeightMin) diff --git a/android/docs/engine/KMManager/getKeyboardHeightMin.md b/android/docs/engine/KMManager/getKeyboardHeightMin.md new file mode 100644 index 0000000000..c81af2aa1e --- /dev/null +++ b/android/docs/engine/KMManager/getKeyboardHeightMin.md @@ -0,0 +1,57 @@ +--- +title: KMManager.getKeyboardHeightMin() +--- + +## Summary + +The **`getKeyboardHeightMin()`** method returns the minimum allowed height of the keyboard frame for this context. + +## Syntax + +``` javascript +KMManager.getKeyboardHeightMin(Context context) +``` +or +``` javascript +KMManager.getKeyboardHeightMin(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` is not defined, minimum keyboard height is returned for the current device orientation. + +### Returns + +Returns the minimum allowed height of the keyboard frame in *density-independent pixels +(dp)*. + +## Description + +Use this method to get the minimum allowed height of the keyboard frame. + +## Examples + +### Example: Using `getKeyboardHeightMin()` + +The following script illustrate the use of `getKeyboardHeightMin()`: + +``` javascript + int minKeyboardHeight = KMManager.getKeyboardHeightMin(this); +``` +or +```java + import android.content.res.Configuration; + ... + // Get the minimum allowed Keyman keyboard height for landscape mode. + int minKeyboardHeightLandscape = KMManager.getKeyboardHeightMin(this, Configuration.ORIENTATION_LANDSCAPE); +``` + +## See also + +- [applyKeyboardHeight()](applyKeyboardHeight) +- [getKeyboardHeight()](getKeyboardHeight) +- [getKeyboardHeightMax()](getKeyboardHeightMax) diff --git a/android/docs/engine/KMManager/getNavigationBarHeight.md b/android/docs/engine/KMManager/getNavigationBarHeight.md new file mode 100644 index 0000000000..727dd24d3f --- /dev/null +++ b/android/docs/engine/KMManager/getNavigationBarHeight.md @@ -0,0 +1,49 @@ +--- +title: KMManager.getNavigationBarHeight() +--- + +## Summary + +The **`getNavigationBarHeight()`** method returns the height of the navigation bar corresponding to the in-app or system keyboard. + +## Syntax + +``` javascript +KMManager.getNavigationBarHeight(Context context, KeyboardType keyboardType) +``` + +### Parameters + +`context` +: The context. + +`keyboardType` +: The keyboard type. `KEYBOARD_TYPE_INAPP` or + `KEYBOARD_TYPE_SYSTEM`.) + +### Returns + +Returns the height of the navigation bar in pixels. + +## Description + +Use this method to get the height of the navigation bar, which depends on +the keyboard type (in-app or system) and navigation mode (gesture, 2-button, or 3-button navigation). +The keyboard offsets must account for this height below the keyboard. + +## Examples + +### Example: Using `getNavigationBarHeight()` + +The following script illustrate the use of `getNavigationBarHeight()`: + +``` javascript + int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.SYSTEM_KEYBOARD); + + outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; +``` + +## See Also + +- [`getBannerHeight()`](getBannerHeight) +- [`getKeyboardHeight()`](getKeyboardHeight) diff --git a/android/docs/engine/KMManager/index.md b/android/docs/engine/KMManager/index.md index 15a2666063..0f9d9c0fba 100644 --- a/android/docs/engine/KMManager/index.md +++ b/android/docs/engine/KMManager/index.md @@ -101,6 +101,12 @@ The KMManager is the core class which provides most of the methods and constants [`getKeyboardHeight()`](getKeyboardHeight) : returns the height of the keyboard frame +[`getKeyboardHeightMax()`](getKeyboardHeightMax) +: returns the maximum allowed height of the keyboard frame + +[`getKeyboardHeightMin()`](getKeyboardHeightMin) +: returns the minimum allowed height of the keyboard frame + [`getKeyboardIndex()`](getKeyboardIndex) : returns index number of the specified keyboard in keyboards list @@ -149,6 +155,9 @@ The KMManager is the core class which provides most of the methods and constants [`getMaySendCrashReport()`](getMaySendCrashReport) : returns whether Keyman Engine is allowed to send crash reports over the network to Sentry +[`getNavigationBarHeight()`](getNavigationBarHeight) +: returns the height of the navigation bar system inset + [`getOrientation()`](getOrientation) : returns the device's current orientation (Portrait vs Landscape) diff --git a/android/docs/engine/index.md b/android/docs/engine/index.md index 38f255592b..258c27c85e 100644 --- a/android/docs/engine/index.md +++ b/android/docs/engine/index.md @@ -27,8 +27,12 @@ see [What's New](whatsnew) for breaking changes [KeyboardEventHandler](KeyboardEventHandler/) : Provides keyboard and lexical-model events and methods to notify registered listeners +[Keyman for Android app](KMAPro/) +: Public APIs for integrating with the Keyman application + ## See also +* [Keyman Engine for Android 18.0](/developer/engine/android/18.0/) * [Keyman Engine for Android 17.0](/developer/engine/android/17.0/) * [Keyman Engine for Android 16.0](/developer/engine/android/16.0/) * [Keyman Engine for Android 15.0](/developer/engine/android/15.0/) diff --git a/android/docs/engine/whatsnew.md b/android/docs/engine/whatsnew.md index 5d6f717f8a..9d9f748b3d 100644 --- a/android/docs/engine/whatsnew.md +++ b/android/docs/engine/whatsnew.md @@ -2,5 +2,7 @@ title: What's New in Keyman Engine 19.0 for Android --- +* Added API for broadcasting when Keyman system keyboard font changes (#15193) + ## See Also * [Keyman Engine for Android Documentation](index) \ No newline at end of file diff --git a/android/docs/help/about/adware.md b/android/docs/help/about/adware.md new file mode 100644 index 0000000000..ecc7acf422 --- /dev/null +++ b/android/docs/help/about/adware.md @@ -0,0 +1,28 @@ +--- +title: Does Keyman include adware or spyware? +--- + +No! + +We never have and never will include adware or spyware components. + +When [enabling Keyman as a system keyboard](../start/enabling-system-keyboard), Android displays the following warning, which is the same message that is displayed for any keyboard app that is installed onto an Android device. + +![](../android_images/attention.png) + +> Attention: This input method may be able to collect all the text you +> type, including personal data like passwords and credit card numbers. +> It comes from the app Keyman. Use this input method? Cancel / OK + +The Keyman app does not record the keystrokes you type and does not send keystrokes off-device, unlike many other keyboard apps. All predictive text suggestions are entirely on your device. + +Keyman is an open source application, and you are most welcome to inspect the source code at https://github.com/keymanapp/keyman. + +## Checking for Updates + +Keyman does contact the keyman.com website periodically to check for updates for the keyboards and predictive text dictionaries you have installed. +In this process, the only information communicated is the current version of Keyman and the version of the keyboards and dictionaries. + +## Crash Report Information + +When enabled, the Keyman app will send crash reporting information to [Sentry](https://keyman.sentry.io). No personally identifiable information or keyboard strokes are recorded. This can be disabled in the Keyman [settings menu](../basic/config/). diff --git a/android/docs/help/about/index.md b/android/docs/help/about/index.md index 4428a4bb75..728cdc6ec9 100644 --- a/android/docs/help/about/index.md +++ b/android/docs/help/about/index.md @@ -5,5 +5,6 @@ title: About Keyman for Android * [Welcome to Keyman](welcome) * [What's New](whatsnew) * [System Requirements](system-requirements) +* [Does Keyman include adware or spyware?](adware) * [Data Privacy Policy](privacy) * [Version History](history) \ No newline at end of file diff --git a/android/docs/help/android_images/attention.png b/android/docs/help/android_images/attention.png new file mode 100644 index 0000000000..febe155e34 Binary files /dev/null and b/android/docs/help/android_images/attention.png differ diff --git a/android/docs/help/index.md b/android/docs/help/index.md index 360af91e6d..758e44d661 100644 --- a/android/docs/help/index.md +++ b/android/docs/help/index.md @@ -6,6 +6,7 @@ title: Keyman for Android 19.0 Help * [Welcome to Keyman](about/welcome) * [What's New](about/whatsnew) * [System Requirements](about/system-requirements) +* [Does Keyman include adware or spyware?](about/adware) * [Data Privacy Policy](about/privacy) * [Version History](about/history) diff --git a/common/schemas/model_info/README.md b/common/schemas/model_info/README.md new file mode 100644 index 0000000000..35f944eee8 --- /dev/null +++ b/common/schemas/model_info/README.md @@ -0,0 +1,19 @@ +# model_info + +* **model_info.schema.json** + +Documentation at https://help.keyman.com/developer/cloud/model_info + +* Primary version: + * https://github.com/keymanapp/api.keyman.com/tree/master/schemas/model_info +* Synchronized copies at: + * https://github.com/keymanapp/keyman/tree/master/common/schemas/model_info + +# .model_info version history + +## 2023-08-11 2.0 stable +* Removed: + `.links` + `.related[].note` + - Source .model_info files are no longer needed, so source vs distribution + model_info distinction is removed diff --git a/common/schemas/model_info/model_info.schema.json b/common/schemas/model_info/model_info.schema.json new file mode 100644 index 0000000000..d23482d2c3 --- /dev/null +++ b/common/schemas/model_info/model_info.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$ref": "#/definitions/ModelInfo", + + "definitions": { + "ModelInfo": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "authorName": { "type": "string" }, + "authorEmail": { "type": "string", "format": "email" }, + "description": { "type": "string" }, + "license": { "type": "string", "enum": ["mit"] }, + "languages": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }, + "lastModifiedDate": { "type": "string", "format": "date-time" }, + "packageFilename": { "type": "string" }, + "packageFileSize": { "type": "number" }, + "jsFilename": { "type": "string" }, + "jsFileSize": { "type": "number" }, + "isRTL": { "type": "boolean" }, + "packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["fonts"] }, "additionalItems": false }, + "version": { "type": "string" }, + "minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.0$" }, + "helpLink": { "type": "string", "pattern": "^http(s)?://help\\.keyman(-staging)?\\.com(.localhost)?/model/" }, + "sourcePath": { "type": "string", "pattern": "^(release|experimental)/.+/.+$" }, + "related": { "type": "object", "patternProperties": { + ".": { "$ref": "#/definitions/ModelRelatedInfo" } + }, + "additionalProperties": false + } + }, + "required": [ + "license", "languages" + ], + "additionalProperties": false + }, + + "ModelRelatedInfo": { + "type": "object", + "properties": { + "deprecates": { "type": "boolean" }, + "deprecatedBy": { "type": "boolean" } + }, + "required": [], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/windows/src/test/manual-tests/platform-rules/platform-results.xlsx b/common/test/keyboards/platform-rules/platform-results.xlsx similarity index 100% rename from windows/src/test/manual-tests/platform-rules/platform-results.xlsx rename to common/test/keyboards/platform-rules/platform-results.xlsx diff --git a/common/test/keyboards/platform-rules/platformtest.js b/common/test/keyboards/platform-rules/platformtest.js new file mode 100644 index 0000000000..341447c7a8 --- /dev/null +++ b/common/test/keyboards/platform-rules/platformtest.js @@ -0,0 +1,499 @@ +if (typeof keyman === 'undefined') { + console.log('Keyboard requires KeymanWeb 10.0 or later'); + if (typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later"); +} else { + KeymanWeb.KR(new Keyboard_platformtest()); +} + +function Keyboard_platformtest() { + this._v = (typeof keyman != "undefined" && typeof keyman.version == "string") ? parseInt(keyman.version, 10) : 9; + this.KI = "Keyboard_platformtest"; + this.KN = "PlatformTest"; + this.KMINVER = "10.0"; + this.KV = null; + this.KDU = 0; + this.KH = ''; + this.KM = 0; + this.KBVER = "1.0"; + this.KMBM = 0x0; + this.s20 = "touch"; + this.s21 = "hardware"; + this.s24 = "windows"; + this.s25 = "android"; + this.s26 = "ios"; + this.s27 = "macosx"; + this.s28 = "linux"; + this.s31 = "desktop"; + this.s32 = "tablet"; + this.s33 = "phone"; + this.s36 = "native"; + this.s37 = "web"; + this.s40 = "ie"; + this.s41 = "chrome"; + this.s42 = "edge"; + this.s43 = "firefox"; + this.s44 = "safari"; + this.s45 = "opera"; + this.s48 = "touch"; + this.s49 = "hardware"; + this.s52 = "platform-x"; + this.s55 = "WinDOWS"; + this.s56 = "ANDroid"; + this.s57 = "iOS"; + this.s58 = "macOSX"; + this.s59 = "LINUX"; + this.s61 = "touch"; + this.s62 = "hardware"; + this.s63 = "windows"; + this.s64 = "android"; + this.s65 = "ios"; + this.s66 = "macosx"; + this.s67 = "linux"; + this.s68 = "desktop"; + this.s69 = "tablet"; + this.s70 = "phone"; + this.s71 = "native"; + this.s72 = "web"; + this.s73 = "ie"; + this.s74 = "chrome"; + this.s75 = "edge"; + this.s76 = "firefox"; + this.s77 = "safari"; + this.s78 = "opera"; + this.s79 = "touch"; + this.s80 = "hardware"; + this.s81 = "platform-x"; + this.s82 = "WinDOWS"; + this.s83 = "ANDroid"; + this.s84 = "iOS"; + this.s85 = "macOSX"; + this.s86 = "LINUX"; + this.KVER = "18.0.241.0"; + this.KVS = []; + this.gs = function(t, e) { + return this.g0(t, e); + }; + this.gs = function(t, e) { + return this.g0(t, e); + }; + this.g0 = function(t, e) { + var k = KeymanWeb, + r = 0, + m = 0; + if (k.KKM(e, 16384, 65)) { + if (1) { + r = m = 1; + k.KDC(0, t); + r = this.g1(t, e); + m = 2; + } + } + if (!m && k.KIK(e)) { + r = 1; + k.KDC(-1, t); + r = this.g9(t, e); + m = 2; + } + return r; + }; + this.g1 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s20, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, "touch"); + } else if (k.KIFS(31, this.s21, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, "hardware"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g2(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, "undefined"); + r = this.g2(t, e); + m = 2; + } + return r; + }; + this.g2 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s24, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " windows"); + } else if (k.KIFS(31, this.s25, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " android"); + } else if (k.KIFS(31, this.s26, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " ios"); + } else if (k.KIFS(31, this.s27, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " macosx"); + } else if (k.KIFS(31, this.s28, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " linux"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g3(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g3(t, e); + m = 2; + } + return r; + }; + this.g3 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s31, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " desktop"); + } else if (k.KIFS(31, this.s32, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " tablet"); + } else if (k.KIFS(31, this.s33, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " phone"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g4(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g4(t, e); + m = 2; + } + return r; + }; + this.g4 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s36, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " native"); + } else if (k.KIFS(31, this.s37, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " web"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g5(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g5(t, e); + m = 2; + } + return r; + }; + this.g5 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s40, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " ie"); + } else if (k.KIFS(31, this.s41, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " chrome"); + } else if (k.KIFS(31, this.s42, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " edge"); + } else if (k.KIFS(31, this.s43, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " firefox"); + } else if (k.KIFS(31, this.s44, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " safari"); + } else if (k.KIFS(31, this.s45, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " opera"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g6(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g6(t, e); + m = 2; + } + return r; + }; + this.g6 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (!k.KIFS(31, this.s48, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " !touch"); + } else if (!k.KIFS(31, this.s49, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " !hardware"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g7(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g7(t, e); + m = 2; + } + return r; + }; + this.g7 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s52, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " platform-x"); + } + if (m == 1) { + k.KDC(-1, t); + r = this.g8(t, e); + m = 2; + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + r = this.g8(t, e); + m = 2; + } + return r; + }; + this.g8 = function(t, e) { + var k = KeymanWeb, + r = 1, + m = 0; + if (k.KIFS(31, this.s55, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " windows"); + } else if (k.KIFS(31, this.s56, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " android"); + } else if (k.KIFS(31, this.s57, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " ios"); + } else if (k.KIFS(31, this.s58, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " macosx"); + } else if (k.KIFS(31, this.s59, t)) { + m = 1; + k.KDC(0, t); + k.KO(-1, t, " linux"); + } + if (!m) { + k.KDC(-1, t); + k.KO(-1, t, " undefined"); + } + return r; + }; + this.g9 = function(t, e) { + var k = KeymanWeb, + r = 0, + m = 0; + if (k.KKM(e, 16384, 69)) { + if (k.KIFS(31, this.s82, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Windows"); + } else if (k.KIFS(31, this.s83, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Android"); + } else if (k.KIFS(31, this.s84, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " iOS"); + } else if (k.KIFS(31, this.s85, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " macOS"); + } else if (k.KIFS(31, this.s86, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Linux"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [OS Undefined (case insensitive test)]"); + } + } else if (k.KKM(e, 16384, 73)) { + if (k.KIFS(31, this.s68, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Desktop"); + } else if (k.KIFS(31, this.s69, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Tablet"); + } else if (k.KIFS(31, this.s70, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Phone"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [FF Undefined]"); + } + } else if (k.KKM(e, 16384, 79)) { + if (k.KIFS(31, this.s63, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Windows"); + } else if (k.KIFS(31, this.s64, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Android"); + } else if (k.KIFS(31, this.s65, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " iOS"); + } else if (k.KIFS(31, this.s66, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " OSX"); + } else if (k.KIFS(31, this.s67, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Linux"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [OS Undefined]"); + } + } else if (k.KKM(e, 16384, 80)) { + if (k.KIFS(31, this.s61, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, "Touch"); + } else if (k.KIFS(31, this.s62, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, "Hardware"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, "[UI Undefined]"); + } + } else if (k.KKM(e, 16384, 82)) { + if (k.KIFS(31, this.s81, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Platform-X"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [Platform-X Undefined]"); + } + } else if (k.KKM(e, 16384, 84)) { + if (!k.KIFS(31, this.s79, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " !Touch"); + } else if (!k.KIFS(31, this.s80, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " !Hardware"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [Inverted OS Undefined]"); + } + } else if (k.KKM(e, 16384, 85)) { + if (k.KIFS(31, this.s71, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Native"); + } else if (k.KIFS(31, this.s72, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Web"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [Nativeness Undefined]"); + } + } else if (k.KKM(e, 16384, 89)) { + if (k.KIFS(31, this.s73, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " IE"); + } else if (k.KIFS(31, this.s74, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Chrome"); + } else if (k.KIFS(31, this.s75, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Edge"); + } else if (k.KIFS(31, this.s76, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Firefox"); + } else if (k.KIFS(31, this.s77, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Safari"); + } else if (k.KIFS(31, this.s78, t)) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " Opera"); + } else if (1) { + r = m = 1; + k.KDC(0, t); + k.KO(-1, t, " [Browser Undefined]"); + } + } + return r; + }; +} diff --git a/common/test/keyboards/platform-rules/platformtest.kmp b/common/test/keyboards/platform-rules/platformtest.kmp new file mode 100644 index 0000000000..ecef09c75c Binary files /dev/null and b/common/test/keyboards/platform-rules/platformtest.kmp differ diff --git a/common/test/keyboards/platform-rules/platformtest.kmx b/common/test/keyboards/platform-rules/platformtest.kmx new file mode 100644 index 0000000000..6d1ff7e97a Binary files /dev/null and b/common/test/keyboards/platform-rules/platformtest.kmx differ diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.kpj b/common/test/keyboards/platform-rules/platformtest.kpj similarity index 79% rename from windows/src/test/manual-tests/platform-rules/platformtest.kpj rename to common/test/keyboards/platform-rules/platformtest.kpj index e476987511..447f7d86d7 100644 --- a/windows/src/test/manual-tests/platform-rules/platformtest.kpj +++ b/common/test/keyboards/platform-rules/platformtest.kpj @@ -4,22 +4,25 @@ False True + True + keyboard id_c1593d698cb624690005787147089dab platformtest.kmn - platformtest.kmn + source/platformtest.kmn 1.0 .kmn
PlatformTest + © SIL Global
id_18e336f9ad890bf821ff56e1a7764478 platformtest.kps - platformtest.kps + source/platformtest.kps .kps
@@ -28,8 +31,8 @@ id_61f04814d427ce02953436f3ea7d7a65 - PlatformTest.kmx - PlatformTest.kmx + platformtest.kmx + platformtest.kmx .kmx id_18e336f9ad890bf821ff56e1a7764478 diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.kmn b/common/test/keyboards/platform-rules/source/platformtest.kmn similarity index 96% rename from windows/src/test/manual-tests/platform-rules/platformtest.kmn rename to common/test/keyboards/platform-rules/source/platformtest.kmn index b33fe0e763..490f569e3e 100644 --- a/windows/src/test/manual-tests/platform-rules/platformtest.kmn +++ b/common/test/keyboards/platform-rules/source/platformtest.kmn @@ -1,5 +1,8 @@ -store(&TARGETS) 'any' +store(&VERSION) '10.0' +store(&TARGETS) 'any' store(&NAME) 'PlatformTest' +store(&KEYBOARDVERSION) '1.0' +store(©RIGHT) '© SIL Global' begin Unicode > use(main) group(main) using keys diff --git a/common/test/keyboards/platform-rules/source/platformtest.kmp b/common/test/keyboards/platform-rules/source/platformtest.kmp new file mode 100644 index 0000000000..636859726f Binary files /dev/null and b/common/test/keyboards/platform-rules/source/platformtest.kmp differ diff --git a/common/test/keyboards/platform-rules/source/platformtest.kmx b/common/test/keyboards/platform-rules/source/platformtest.kmx new file mode 100644 index 0000000000..52d72993d0 Binary files /dev/null and b/common/test/keyboards/platform-rules/source/platformtest.kmx differ diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.kps b/common/test/keyboards/platform-rules/source/platformtest.kps similarity index 81% rename from windows/src/test/manual-tests/platform-rules/platformtest.kps rename to common/test/keyboards/platform-rules/source/platformtest.kps index 825b0d5607..9a9186d5bb 100644 --- a/windows/src/test/manual-tests/platform-rules/platformtest.kps +++ b/common/test/keyboards/platform-rules/source/platformtest.kps @@ -17,16 +17,17 @@ PlatformTest + Test keyboard for displaying platform information. - PlatformTest.kmx - Keyboard PlatformTest + ../platformtest.kmx + Keyboard Platformtest 0 .kmx - platformtest.js + ../platformtest.js File platformtest.js 0 .js diff --git a/web/src/test/manual/web/issue9469/test9469/HISTORY.md b/common/test/keyboards/test9469/HISTORY.md similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/HISTORY.md rename to common/test/keyboards/test9469/HISTORY.md diff --git a/web/src/test/manual/web/issue9469/test9469/LICENSE.md b/common/test/keyboards/test9469/LICENSE.md similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/LICENSE.md rename to common/test/keyboards/test9469/LICENSE.md diff --git a/web/src/test/manual/web/issue9469/test9469/README.md b/common/test/keyboards/test9469/README.md similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/README.md rename to common/test/keyboards/test9469/README.md diff --git a/web/src/test/manual/web/issue9469/test9469/build/test9469.js b/common/test/keyboards/test9469/build/test9469.js similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/build/test9469.js rename to common/test/keyboards/test9469/build/test9469.js diff --git a/common/test/keyboards/test9469/build/test9469.kmp b/common/test/keyboards/test9469/build/test9469.kmp new file mode 100644 index 0000000000..00a111e260 Binary files /dev/null and b/common/test/keyboards/test9469/build/test9469.kmp differ diff --git a/common/test/keyboards/test9469/build/test9469.kmx b/common/test/keyboards/test9469/build/test9469.kmx new file mode 100644 index 0000000000..717a37dad3 Binary files /dev/null and b/common/test/keyboards/test9469/build/test9469.kmx differ diff --git a/common/test/keyboards/test9469/build/test9469.kvk b/common/test/keyboards/test9469/build/test9469.kvk new file mode 100644 index 0000000000..1f708b42b7 Binary files /dev/null and b/common/test/keyboards/test9469/build/test9469.kvk differ diff --git a/web/src/test/manual/web/issue9469/test9469/source/readme.htm b/common/test/keyboards/test9469/source/readme.htm similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/source/readme.htm rename to common/test/keyboards/test9469/source/readme.htm diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.ico b/common/test/keyboards/test9469/source/test9469.ico similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/source/test9469.ico rename to common/test/keyboards/test9469/source/test9469.ico diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout b/common/test/keyboards/test9469/source/test9469.keyman-touch-layout similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/source/test9469.keyman-touch-layout rename to common/test/keyboards/test9469/source/test9469.keyman-touch-layout diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn b/common/test/keyboards/test9469/source/test9469.kmn similarity index 96% rename from web/src/test/manual/web/issue9469/test9469/source/test9469.kmn rename to common/test/keyboards/test9469/source/test9469.kmn index d0f84c749a..cd90a48d70 100644 --- a/web/src/test/manual/web/issue9469/test9469/source/test9469.kmn +++ b/common/test/keyboards/test9469/source/test9469.kmn @@ -1,6 +1,6 @@ c test9469 generated from template at 2023-08-17 19:34:17 c with name "test9469" -store(&VERSION) '10.0' +store(&VERSION) '14.0' store(&NAME) 'test9469' store(©RIGHT) '© 2023 SIL International' store(&KEYBOARDVERSION) '1.1' diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.kps b/common/test/keyboards/test9469/source/test9469.kps similarity index 93% rename from web/src/test/manual/web/issue9469/test9469/source/test9469.kps rename to common/test/keyboards/test9469/source/test9469.kps index 607e31751f..64c5231819 100644 --- a/web/src/test/manual/web/issue9469/test9469/source/test9469.kps +++ b/common/test/keyboards/test9469/source/test9469.kps @@ -20,6 +20,7 @@ © 2023 SIL International SIL International + Test keyboard for displaying special characters rendered with keymanweb-osk.ttf diff --git a/web/src/test/manual/web/issue9469/test9469/source/test9469.kvks b/common/test/keyboards/test9469/source/test9469.kvks similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/source/test9469.kvks rename to common/test/keyboards/test9469/source/test9469.kvks diff --git a/web/src/test/manual/web/issue9469/test9469/source/welcome.htm b/common/test/keyboards/test9469/source/welcome.htm similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/source/welcome.htm rename to common/test/keyboards/test9469/source/welcome.htm diff --git a/web/src/test/manual/web/issue9469/test9469/test9469.keyboard_info b/common/test/keyboards/test9469/test9469.keyboard_info similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/test9469.keyboard_info rename to common/test/keyboards/test9469/test9469.keyboard_info diff --git a/web/src/test/manual/web/issue9469/test9469/test9469.kpj b/common/test/keyboards/test9469/test9469.kpj similarity index 100% rename from web/src/test/manual/web/issue9469/test9469/test9469.kpj rename to common/test/keyboards/test9469/test9469.kpj diff --git a/web/src/test/manual/web/chirality/chirality.js b/common/test/resources/keyboards/chirality.js similarity index 98% rename from web/src/test/manual/web/chirality/chirality.js rename to common/test/resources/keyboards/chirality.js index 68740ffc30..9c620ce5c2 100644 --- a/web/src/test/manual/web/chirality/chirality.js +++ b/common/test/resources/keyboards/chirality.js @@ -3,7 +3,8 @@ /** * Please note that this is an experimental handwritten keyboard designed for initial testing of modifier chirality support for KeymanWeb. * As it is handcoded, it uses multiple codesize optimization techniques that Keyman Developer simply does not and will not employ, and it - * will only work with KeymanWeb based implementations. (Not supported for Keyman Desktop or Keyman for Mac.) + * will only work with KeymanWeb based implementations. (Not supported for Keyman for Windows, + * Keyman for Linux, or Keyman for macOS.) * * (This keyboard was written before Developer's implementation of chirality support and was used to bootstrap KeymanWeb chirality, serving * as the initial wave of testing.) diff --git a/common/test/resources/mocha-teamcity-reporter/README.md b/common/test/resources/mocha-teamcity-reporter/README.md new file mode 100644 index 0000000000..6060f53a5d --- /dev/null +++ b/common/test/resources/mocha-teamcity-reporter/README.md @@ -0,0 +1,8 @@ +# Custom mocha reporter for TeamCity + +This is an enhanced version of the [mocha-teamcity-reporter](https://github.com/travisjeffery/mocha-teamcity-reporter) +(renamed to *.cjs) that allows to specify the parent flowId so that the tests +can be grouped together under the parent block in TeamCity. + +If [travisjeffery/mocha-teamcity-reporter#69](https://github.com/travisjeffery/mocha-teamcity-reporter/issues/69) +gets fixed one day, we can go back to using the original version. diff --git a/common/test/resources/mocha-teamcity-reporter/teamcity.cjs b/common/test/resources/mocha-teamcity-reporter/teamcity.cjs new file mode 100644 index 0000000000..24c76a5f2b --- /dev/null +++ b/common/test/resources/mocha-teamcity-reporter/teamcity.cjs @@ -0,0 +1,259 @@ +/* eslint-disable complexity */ +/** + * Teamcity doc reference https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity + * + * Module dependencies. + */ +'use strict'; + +const processPID = process.pid.toString(); +const TEST_IGNORED = `##teamcity[testIgnored name='%s' message='%s']`; +const SUITE_START = `##teamcity[testSuiteStarted name='%s']`; +const SUITE_END = `##teamcity[testSuiteFinished name='%s' duration='%s']`; +const SUITE_END_NO_DURATION = `##teamcity[testSuiteFinished name='%s']`; +const TEST_START = `##teamcity[testStarted name='%s' captureStandardOutput='true']`; +const TEST_FAILED = `##teamcity[testFailed name='%s' message='%s' details='%s' captureStandardOutput='true']`; +const TEST_FAILED_COMPARISON = `##teamcity[testFailed type='comparisonFailure' name='%s' message='%s' \ +details='%s' captureStandardOutput='true' actual='%s' expected='%s']`; +const TEST_END = `##teamcity[testFinished name='%s' duration='%s']`; +const TEST_END_NO_DURATION = `##teamcity[testFinished name='%s']`; +const FLOW_START = `##teamcity[flowStarted flowId='%s' parent='%s']`; +const FLOW_END = `##teamcity[flowFinished flowId='%s']`; + +const Mocha = require('mocha'); +const { + EVENT_SUITE_BEGIN, + EVENT_TEST_BEGIN, + EVENT_TEST_FAIL, + EVENT_TEST_PENDING, + EVENT_TEST_END, + EVENT_HOOK_BEGIN, + EVENT_HOOK_END, + EVENT_SUITE_END, + EVENT_RUN_END +} = Mocha.Runner.constants; + +const util = require('util'); + +let Base, log, logError; + +Base = require('mocha').reporters.Base; +log = console.log; +logError = console.error; + +let flowIds = []; + +/** + * Escape the given `str`. + */ + +function escape(str) { + if (!str) return ''; + return str + .toString() + .replace(/\x1B.*?m/g, '') // eslint-disable-line no-control-regex + .replace(/\|/g, '||') + .replace(/\n/g, '|n') + .replace(/\r/g, '|r') + .replace(/\[/g, '|[') + .replace(/\]/g, '|]') + .replace(/\u0085/g, '|x') + .replace(/\u2028/g, '|l') + .replace(/\u2029/g, '|p') + .replace(/'/g, '|\''); +} + +function isNil(value) { + return value == null; // eslint-disable-line +} + +function formatString() { + let formattedArguments = []; + const args = Array.prototype.slice.call(arguments, 0); + // Format all arguments for TC display (it escapes using the pipe char). + let tcMessage = args.shift(); + args.forEach((param) => { + formattedArguments.push(escape(param)); + }); + formattedArguments.unshift(tcMessage); + return util.format.apply(util, formattedArguments); +} + +function handleFlow(isStarted, nestFlows) { + if (!nestFlows) { + return; + } + if (isStarted) { + flowIds.push(Math.floor(Math.random() * 100000 + 1)); + log(formatString(FLOW_START, flowIds[flowIds.length - 1], flowIds[flowIds.length - 2])); + } else { + log(formatString(FLOW_END, flowIds[flowIds.length - 1])); + flowIds.pop(); + } +} + +function getFlowId() { + return flowIds[flowIds.length - 1]; +} + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @param {options} options + * @api public + */ + +function Teamcity(runner, options) { + options = options || {}; + const reporterOptions = options.reporterOptions || {}; + let flowIdOpt, useStdError, recordHookFailures, actualVsExpected, ignoreHookWithName, displayIgnoredAsIgnored; + (reporterOptions.flowId) ? flowIdOpt = reporterOptions.flowId : flowIdOpt = process.env['MOCHA_TEAMCITY_FLOWID'] || processPID; + (reporterOptions.useStdError) ? useStdError = reporterOptions.useStdError : useStdError = process.env['USE_STD_ERROR']; + (reporterOptions.recordHookFailures) ? recordHookFailures = reporterOptions.recordHookFailures : recordHookFailures = process.env['RECORD_HOOK_FAILURES']; + (reporterOptions.actualVsExpected) ? actualVsExpected = reporterOptions.actualVsExpected : actualVsExpected = process.env['ACTUAL_VS_EXPECTED']; + (reporterOptions.ignoreHookWithName) ? ignoreHookWithName = reporterOptions.ignoreHookWithName : ignoreHookWithName = process.env['IGNORE_HOOK_WITH_NAME']; + (reporterOptions.displayIgnoredAsIgnored) + ? displayIgnoredAsIgnored = reporterOptions.displayIgnoredAsIgnored + : displayIgnoredAsIgnored = process.env['DISPLAY_IGNORED_AS_IGNORED']; + (useStdError) ? useStdError = (useStdError.toLowerCase() === 'true') : useStdError = false; + (recordHookFailures) ? recordHookFailures = (recordHookFailures.toLowerCase() === 'true') : recordHookFailures = false; + (displayIgnoredAsIgnored) ? displayIgnoredAsIgnored = (displayIgnoredAsIgnored.toLowerCase() === 'true') : displayIgnoredAsIgnored = false; + (ignoreHookWithName) ? ignoreHookWithName : null; + actualVsExpected ? actualVsExpected = (actualVsExpected.toLowerCase() === 'true') : actualVsExpected = false; + Base.call(this, runner); + let stats = this.stats; + const topLevelSuite = reporterOptions.topLevelSuite || process.env['MOCHA_TEAMCITY_TOP_LEVEL_SUITE']; + const parentFlowId = reporterOptions.parentFlowId || process.env['MOCHA_TEAMCITY_PARENT_FLOW_ID']; + if (parentFlowId) { + flowIds.push(parentFlowId); + } else { + flowIds.push(flowIdOpt); + } + const hasParentFlowId = !!parentFlowId; + + const ignoredTests = {}; + const testState = { pending: 0 }; + + runner.on(EVENT_SUITE_BEGIN, function (suite) { + handleFlow(true, hasParentFlowId); + if (suite.root) { + if (topLevelSuite) { + log(formatString(SUITE_START, topLevelSuite)); + } + return; + } + suite.startDate = new Date(); + log(formatString(SUITE_START, suite.title)); + }); + + runner.on(EVENT_TEST_BEGIN, function (test) { + if (displayIgnoredAsIgnored && ignoredTests[`${test.title}-${getFlowId()}`] === testState.pending) { + return; + } + handleFlow(true, hasParentFlowId); + log(formatString(TEST_START, test.title)); + }); + + runner.on(EVENT_TEST_FAIL, function (test, err) { + let isHook = false; + if (test.title.includes(`"before all" hook`) || + test.title.includes(`"before each" hook`) || + test.title.includes(`"after all" hook`) || + test.title.includes(`"after each" hook`) + ) { + isHook = true; + } + + if(actualVsExpected && (err.actual && err.expected)){ + if (useStdError) { + logError(formatString(TEST_FAILED_COMPARISON, + test.title, err.message, err.stack, err.actual, err.expected)); + } else { + log(formatString(TEST_FAILED_COMPARISON, test.title, err.message, err.stack, err.actual, err.expected)); + } + } else{ + if (useStdError) { + logError(formatString(TEST_FAILED, test.title, err.message, err.stack)); + } else { + log(formatString(TEST_FAILED, test.title, err.message, err.stack)); + } + } + // Log testFinished for failed hook (hook end event is not fired for failed hook) + if (recordHookFailures && !ignoreHookWithName || recordHookFailures && ignoreHookWithName && !test.title.includes(ignoreHookWithName)) { + if (isHook) { + if(isNil(test.duration)){ + log(formatString(TEST_END_NO_DURATION, test.title)); + } else { + log(formatString(TEST_END, test.title, test.duration.toString())); + } + handleFlow(false, hasParentFlowId); + } + } + }); + + runner.on(EVENT_TEST_PENDING, function (test) { + log(formatString(TEST_IGNORED, test.title, test.title)); + if (displayIgnoredAsIgnored) { + ignoredTests[`${test.title}-${getFlowId()}`] = testState.pending; + } else { + handleFlow(true, hasParentFlowId) + } + }); + + runner.on(EVENT_TEST_END, function (test) { + if (displayIgnoredAsIgnored && ignoredTests[`${test.title}-${getFlowId()}`] === testState.pending) { + delete ignoredTests[`${test.title}-${getFlowId()}`]; + return; + } + if(isNil(test.duration)){ + log(formatString(TEST_END_NO_DURATION, test.title)); + } else { + log(formatString(TEST_END, test.title, test.duration.toString())); + } + handleFlow(false, hasParentFlowId); + }); + + runner.on(EVENT_HOOK_BEGIN, function (test) { + if (recordHookFailures && !ignoreHookWithName || recordHookFailures && ignoreHookWithName && !test.title.includes(ignoreHookWithName)) { + handleFlow(true, hasParentFlowId); + log(formatString(TEST_START, test.title)); + } + }); + + runner.on(EVENT_HOOK_END, function (test) { + if (recordHookFailures && !ignoreHookWithName || recordHookFailures && ignoreHookWithName && !test.title.includes(ignoreHookWithName)) { + if(isNil(test.duration)){ + log(formatString(TEST_END_NO_DURATION, test.title)); + } else { + log(formatString(TEST_END, test.title, test.duration.toString())); + } + handleFlow(false, hasParentFlowId); + } + }); + + runner.on(EVENT_SUITE_END, function (suite) { + if (!suite.root) { + log(formatString(SUITE_END, suite.title, new Date() - suite.startDate)); + } + handleFlow(false, hasParentFlowId); + }); + + runner.on(EVENT_RUN_END, function () { + let duration; + (typeof stats === 'undefined') ? duration = null : duration = stats.duration; + if (topLevelSuite) { + isNil(duration) + ? log(formatString(SUITE_END_NO_DURATION, topLevelSuite)) + : log(formatString(SUITE_END, topLevelSuite, duration)); + handleFlow(false, hasParentFlowId); + } + }); +} + + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; diff --git a/common/tools/sourcemap-path-remapper/package.json b/common/tools/sourcemap-path-remapper/package.json index 88aaae8296..21252bfee0 100644 --- a/common/tools/sourcemap-path-remapper/package.json +++ b/common/tools/sourcemap-path-remapper/package.json @@ -18,7 +18,6 @@ "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "dependencies": { diff --git a/common/web/keyman-version/build.sh b/common/web/keyman-version/build.sh index 781c63bbec..9e165e8662 100755 --- a/common/web/keyman-version/build.sh +++ b/common/web/keyman-version/build.sh @@ -7,7 +7,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" ################################ Main script ################################ @@ -15,10 +14,7 @@ builder_describe "Build the include script for current Keyman version" \ configure \ clean \ build \ - test \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + test builder_describe_outputs \ configure "/node_modules" \ @@ -56,4 +52,3 @@ export default KEYMAN_VERSION; builder_run_action clean rm -rf version.inc.ts keyman-version.mts build/ builder_run_action configure node_select_version_and_npm_ci builder_run_action build do_build -builder_run_action publish ci_publish_npm diff --git a/common/web/keyman-version/package.json b/common/web/keyman-version/package.json index 74cce896bd..891edbdb87 100644 --- a/common/web/keyman-version/package.json +++ b/common/web/keyman-version/package.json @@ -1,5 +1,10 @@ { "name": "@keymanapp/keyman-version", + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "common/web/keyman-version" + }, "description": "Keyman global version data", "exports": { ".": { diff --git a/common/web/langtags/build.sh b/common/web/langtags/build.sh index 4eec3d2c6a..c4db9e1061 100755 --- a/common/web/langtags/build.sh +++ b/common/web/langtags/build.sh @@ -7,16 +7,12 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Build Keyman langtags.js common module" \ "clean" \ "configure" \ "build" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /common/web/langtags/src/imports/langtags.js \ @@ -43,4 +39,3 @@ builder_run_action clean rm -rf ./build ./src/imports ./node_modules builder_run_action configure do_configure builder_run_action build tsc --build builder_run_action test echo 'no tests for langtags' -builder_run_action publish ci_publish_npm diff --git a/common/web/langtags/package.json b/common/web/langtags/package.json index f2848f587f..5746955d2d 100644 --- a/common/web/langtags/package.json +++ b/common/web/langtags/package.json @@ -33,7 +33,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "common/web/langtags" }, "sideEffects": false } diff --git a/common/web/types/build.sh b/common/web/types/build.sh index c21c18a043..85ff8c1f44 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -7,7 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" +. "$KEYMAN_ROOT/resources/build/typescript.inc.sh" builder_describe "Build Keyman common file types module" \ "@/core/include/ldml" \ @@ -15,10 +15,7 @@ builder_describe "Build Keyman common file types module" \ "configure" \ "build" \ "clean" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /common/web/types/src/schemas/kpj.schema.ts \ @@ -81,29 +78,9 @@ function do_configure() { node_select_version_and_npm_ci } -function do_test() { - local MOCHA_FLAGS= - - if builder_is_running_on_teamcity; then - # we're running in TeamCity - MOCHA_FLAGS="-reporter mocha-teamcity-reporter" - fi - - eslint . - tsc --build tests - - readonly C8_THRESHOLD=60 - - # Excludes are defined in package.json - c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha ${MOCHA_FLAGS} "${builder_extra_params[@]}" - builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." - builder_echo warning "Please increase threshold in build.sh as test coverage improves." -} - #------------------------------------------------------------------------------------------------------------------- builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo ./src/schemas/ ./node_modules/ ./obj/ builder_run_action configure do_configure builder_run_action build tsc --build -builder_run_action test do_test -builder_run_action publish ci_publish_npm +builder_run_action test typescript_run_eslint_mocha_tests 60 diff --git a/common/web/types/package.json b/common/web/types/package.json index 2ab86cf03c..21bb83d7aa 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -54,7 +54,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "common/web/types" }, "c8": { "all": true, @@ -74,6 +75,7 @@ "src/ldml-keyboard/unicodeset-parser-api.ts", "src/keyman-touch-layout/keyman-touch-layout-file-writer.ts", "src/osk/osk.ts", + "src/util/types.ts", "src/schemas/*", "tests/", "src/keyboard-object.ts", diff --git a/common/web/types/src/util/kmcConvertUtil.ts b/common/web/types/src/util/kmcConvertUtil.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/windows/delphi/ext/sentry/Sentry.Client.pas b/common/windows/delphi/ext/sentry/Sentry.Client.pas index 94faf74f85..2471a7900b 100644 --- a/common/windows/delphi/ext/sentry/Sentry.Client.pas +++ b/common/windows/delphi/ext/sentry/Sentry.Client.pas @@ -10,6 +10,8 @@ uses Winapi.Windows, jwaimagehlp, + jwantstatus, + jwawinbase, sentry; @@ -229,20 +231,41 @@ var Skip: Integer; LastMask: WORD; begin - // Floating point state may be broken here, so let's mask it out and continue - // We'll restore state afterwards - LastMask := Get8087CW; - Set8087CW_Threadsafe($1332); + try + if (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_ACCESS_VIOLATION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_ILLEGAL_INSTRUCTION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_PRIV_INSTRUCTION) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_POSSIBLE_DEADLOCK) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_IN_PAGE_ERROR) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> EXCEPTION_STACK_OVERFLOW) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> STATUS_INVALID_PARAMETER) and + (ExceptionInfo.ExceptionRecord.ExceptionCode <> cDelphiException) then + begin + // These are the most common exceptions we wish to catch; we won't get + // call stacks for any other exception types, but we will still log + // important exceptions + Exit(0); + end; - if ExceptionInfo.ExceptionRecord.ExceptionCode = cDelphiException - then Skip := DELPHI_FRAMES_TO_SKIP - else Skip := 0; - CaptureStackTraceForException(ExceptionInfo.ExceptionRecord.ExceptionAddress, ExceptionInfo, Skip); + // Floating point state may be broken here, so let's mask it out and continue + // We'll restore state afterwards + LastMask := Get8087CW; + Set8087CW_Threadsafe($1332); - // Restore FP state - Set8087CW_Threadsafe(LastMask); + if ExceptionInfo.ExceptionRecord.ExceptionCode = cDelphiException + then Skip := DELPHI_FRAMES_TO_SKIP + else Skip := 0; + CaptureStackTraceForException(ExceptionInfo.ExceptionRecord.ExceptionAddress, ExceptionInfo, Skip); - Result := 0; //EXCEPTION_CONTINUE_SEARCH; + // Restore FP state + Set8087CW_Threadsafe(LastMask); + + Result := 0; //EXCEPTION_CONTINUE_SEARCH; + except + // Silently handle errors here because we can't do anything with them; + // we'll just get no stack + Result := 0; + end; end; constructor TSentryClient.Create(AOptions: TSentryClientOptions; const ALogger: string; AFlags: TSentryClientFlags); diff --git a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj index 8d57ae5c66..5183081136 100644 --- a/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj +++ b/common/windows/delphi/ext/tds2dbg/tds2dbg.vcxproj @@ -1,119 +1,119 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {40BFECE3-7FC0-4649-A3DC-E017FF84C75B} - Win32Proj - 10.0.17763.0 - - - - Application - true - v141 - - - Application - false - v141 - - - Application - true - v141 - - - Application - false - v141 - - - - - - - - - - - - - - - - - - - - - true - - - true - - - - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - Level3 - ProgramDatabase - Disabled - - - MachineX86 - true - Console - - - - - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - - - MachineX86 - true - Console - true - true - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {40BFECE3-7FC0-4649-A3DC-E017FF84C75B} + Win32Proj + 10.0 + + + + Application + true + v143 + + + Application + false + v143 + + + Application + true + v143 + + + Application + false + v143 + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Console + + + + + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Console + true + true + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index 4086be32ce..a0cc07d5b4 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -395,7 +395,7 @@ var begin // On developer machines, if we are running within the source repo, then use // those paths - keyman_root := GetEnvironmentVariable('KEYMAN_ROOT'); + keyman_root := GetEnvironmentVariable('KEYMAN_ROOT').Replace('/', '\'); if (keyman_root <> '') and SameText(keyman_root, ParamStr(0).Substring(0, keyman_root.Length)) then begin // Source repo, bin folder diff --git a/common/windows/delphi/general/UserMessages.pas b/common/windows/delphi/general/UserMessages.pas index 1c7fe77ce3..bd7ce946de 100644 --- a/common/windows/delphi/general/UserMessages.pas +++ b/common/windows/delphi/general/UserMessages.pas @@ -1,18 +1,18 @@ (* Name: UserMessages Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 5 Jul 2012 Modified Date: 6 Nov 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 05 Jul 2012 - mcdurdin - I3390 - Font helper can crash when WM_FONTCHANGE received [CrashID:keyman.exe_8.0.350.0_2C53A3AE_EAccessViolation] 04 Nov 2012 - mcdurdin - I3519 - V9.0 - Merge of I3390 - Font helper can crash when WM_FONTCHANGE received 28 May 2014 - mcdurdin - I4242 - Crash when OSK closed/reopened without dismissing hint window [CrashID:keyman.exe_9.0.449.0_2C405C5D_EInvalidPointer] @@ -57,9 +57,14 @@ const WM_USER_SYNTAXCOLOURCHANGE = WM_USER + 130; const - // UfrmOSKOnScreenKeyboard + // UfrmPrintOSK WM_USER_PrintKeyboard = WM_USER + 140; +const + // UfrmOSKOnScreenKeyboard + // Receive notification of modifier key changes from keyman32.dll + WM_KEYMAN_OSK_MODIFIER_EVENT = WM_USER + 141; + const // UfrmOSKFontHelper WM_USER_FontChange = WM_USER + 150; // I3390 // I3519 diff --git a/common/windows/delphi/packages/PackageInfo.pas b/common/windows/delphi/packages/PackageInfo.pas index 8370416e5e..7de369f957 100644 --- a/common/windows/delphi/packages/PackageInfo.pas +++ b/common/windows/delphi/packages/PackageInfo.pas @@ -455,6 +455,7 @@ type procedure DoSaveJSON(ARoot: TJSONObject); virtual; procedure DoLoadIni(ini: TIniFile); virtual; procedure DoSaveIni(ini: TIniFile); virtual; + procedure Cleanup; virtual; public Options: TPackageOptions; StartMenu: TPackageStartMenu; @@ -635,6 +636,11 @@ begin Result := ReplaceStr(ReplaceStr(Trim(VarToStr(v)), #$D#$A, #$A), #$A, #$D#$A); end; +function TransformSlashes(filename: string): string; +begin + Result := ReplaceStr(filename, '/', '\'); +end; + {------------------------------------------------------------------------------- - TPackageOptions - ------------------------------------------------------------------------------} @@ -697,10 +703,10 @@ procedure TPackageOptions.LoadXML(ARoot: IXMLNode); begin FileVersion := XmlVarToStr(ARoot.ChildNodes['System'].ChildNodes['FileVersion'].NodeValue); ExecuteProgram := XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ExecuteProgram'].NodeValue); - ReadmeFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ReadMeFile'].NodeValue)); - GraphicFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue)); - LicenseFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue)); - WelcomeFile := Package.Files.FromFileName(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['WelcomeFile'].NodeValue)); + ReadmeFile := Package.Files.FromFileName(TransformSlashes(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['ReadMeFile'].NodeValue))); + GraphicFile := Package.Files.FromFileName(TransformSlashes(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['GraphicFile'].NodeValue))); + LicenseFile := Package.Files.FromFileName(TransformSlashes(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['LicenseFile'].NodeValue))); + WelcomeFile := Package.Files.FromFileName(TransformSlashes(XmlVarToStr(ARoot.ChildNodes['Options'].ChildNodes['WelcomeFile'].NodeValue))); if Assigned(ReadmeFile) then ReadmeFile.AddNotifyObject(ReadmeRemoved); if Assigned(GraphicFile) then GraphicFile.AddNotifyObject(GraphicRemoved); if Assigned(LicenseFile) then LicenseFile.AddNotifyObject(LicenseRemoved); @@ -1425,7 +1431,7 @@ begin with ANode.ChildNodes[i] do begin subfile := TPackageContentFile.Create(Package); - subfile.FileName := XmlVarToStr(ChildNodes['Name'].NodeValue); + subfile.FileName := TransformSlashes(XmlVarToStr(ChildNodes['Name'].NodeValue)); subfile.Description := XmlVarToStr(ChildNodes['Description'].NodeValue); subfile.FCopyLocation := TPackageFileCopyLocation(StrToIntDef(XmlVarToStr(ChildNodes['Location'].NodeValue), 0)); Add(subfile); @@ -1884,11 +1890,18 @@ begin end; end; +procedure TPackage.Cleanup; +begin + // No op in basic package +end; + procedure TPackage.SaveXML; var doc: IXMLDocument; root: IXMLNode; begin + Cleanup; + doc := NewXMLDocument; doc.Encoding := 'utf-8'; @@ -1909,6 +1922,8 @@ var doc: IXMLDocument; root: IXMLNode; begin + Cleanup; + doc := NewXMLDocument; doc.Encoding := 'utf-8'; @@ -2238,8 +2253,8 @@ begin keyboard := TPackageKeyboard.Create(Package); keyboard.ID := XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_ID]); - keyboard.OSKFont := Package.Files.FromFileNameEx(XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_OSKFont])); - keyboard.DisplayFont := Package.Files.FromFileNameEx(XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_DisplayFont])); + keyboard.OSKFont := Package.Files.FromFileNameEx(TransformSlashes(XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_OSKFont]))); + keyboard.DisplayFont := Package.Files.FromFileNameEx(TransformSlashes(XmlVarToStr(AKeyboard.ChildValues[SXML_PackageKeyboard_DisplayFont]))); keyboard.Languages.LoadXML(AKeyboard); keyboard.Examples.LoadXML(AKeyboard); @@ -2877,7 +2892,7 @@ begin for i := 0 to ARoot.ChildNodes.Count - 1 do begin ANode := ARoot.ChildNodes[i]; - f := Package.Files.FromFileNameEx(ANode.Attributes[SXML_PackageKeyboardFont_Filename]); + f := Package.Files.FromFileNameEx(TransformSlashes(XmlVarToStr(ANode.Attributes[SXML_PackageKeyboardFont_Filename]))); if Assigned(f) then Add(f); end; diff --git a/common/windows/delphi/tools/devtools/DevUtils.pas b/common/windows/delphi/tools/devtools/DevUtils.pas index f8cb248dd7..4e2de93a4d 100644 --- a/common/windows/delphi/tools/devtools/DevUtils.pas +++ b/common/windows/delphi/tools/devtools/DevUtils.pas @@ -39,6 +39,7 @@ uses DevInstallPackages, DevIncludePaths, DevReleaseBuildCheck, + Keyman.System.DevTools.BuildLocaleIndex, Keyman.System.DevTools.BuildMessageConstants, Keyman.System.DevTools.BuildSetupStringTranslations, RegistryKeys; @@ -75,6 +76,7 @@ begin writeln(' -git : check git repository commit/update status'); // I3726 // I5087 //TODO: remove as unused writeln(' -buildmessageconstants : build MessageIdentifierConsts.pas from current strings.xml'); writeln(' -buildsetupstrings : build Keyman.Setup.System.Locale.*.pas from strings.xml found in sourcepath'); + writeln(' -buildlocaleindex : build index.xml from strings.xml found in locale-folder'); ExitCode := 1; Exit; end; @@ -97,6 +99,8 @@ begin Success := TBuildMessageConstants.Run(ParamStr(2), ParamStr(3)) else if (ParamStr(1) = '-buildsetupstrings') and (ParamCount = 3) then Success := TBuildSetupStringTranslations.Run(ParamStr(2), ParamStr(3)) + else if (ParamStr(1) = '-buildlocaleindex') and (ParamCount = 3) then + Success := TBuildLocaleIndex.Run(ParamStr(2), ParamStr(3)) else begin writeln('Invalid parameters'); diff --git a/common/windows/delphi/tools/devtools/Keyman.System.DevTools.BuildLocaleIndex.pas b/common/windows/delphi/tools/devtools/Keyman.System.DevTools.BuildLocaleIndex.pas new file mode 100644 index 0000000000..fbd5a577bd --- /dev/null +++ b/common/windows/delphi/tools/devtools/Keyman.System.DevTools.BuildLocaleIndex.pas @@ -0,0 +1,138 @@ +(* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Build index.xml from individual locale.xml files + *) +unit Keyman.System.DevTools.BuildLocaleIndex; + +interface + +uses + System.Classes, + System.SysUtils, + + Xml.XMLIntf, + Xml.XMLDoc; + +type + TBuildLocaleIndex = class + private + class function ProcessFile(const SourceFile: string; LocalesNode: IXmlNode; + const BCP47: string): Boolean; static; + public + class function Run(SourcePath, Index: string): Boolean; static; + end; + +implementation + +uses + Xml.Win.msxmldom, + Winapi.ActiveX, + System.StrUtils, + System.TypInfo, + System.Variants; + +{ TBuildLocaleIndex } + +(** + * Processes a single `strings.xml` file. + * + * Extracts the values of `SKUILanguageName` and + * `SKUILanguageNameWithEnglish`, and appends a `` entry to the + * given XML node. + * + * @param SourceFile Full path to the `strings.xml` file to process. + * @param LocalesNode The `` XML node to append to. + * @param BCP47 The BCP-47 language tag. + * @return True if successful; False if required strings were missing. + *) +class function TBuildLocaleIndex.ProcessFile(const SourceFile: string; LocalesNode: IXmlNode; + const BCP47: string): Boolean; +var + doc: IXmlDocument; + node, locale: IXMLNode; + uiLanguageName, uiLanguageNameWithEnglish: string; +begin + doc := TXMLDocument.Create(nil); + doc.ParseOptions := [poResolveExternals]; // I902 - resolve externals when loading XML files + doc.LoadFromFile(SourceFile); + + node := doc.DocumentElement.ChildNodes[0]; + while Assigned(node) do + begin + if (node.NodeName = 'string') and (node.Attributes['name'] = 'SKUILanguageName') then + uiLanguageName := node.NodeValue + else if (node.NodeName = 'string') and (node.Attributes['name'] = 'SKUILanguageNameWithEnglish') then + uiLanguageNameWithEnglish := node.NodeValue; + node := node.NextSibling; + end; + + if (uiLanguageName = '') or (uiLanguageNameWithEnglish = '') then + begin + writeln('ERROR: '+SourceFile+' is missing SKUILanguageName or SKUILanguageNameWithEnglish strings'); + Exit(False); + end; + + locale := LocalesNode.AddChild('locale'); + locale.Attributes['SKLanguageCode'] := BCP47; + locale.Attributes['SKUILanguageName'] := uiLanguageName; + locale.Attributes['SKUILanguageNameWithEnglish'] := uiLanguageNameWithEnglish; + + Result := True; +end; + +(** + * Finds all localizations and generates an index.xml of the names and + * language codes for each localization, for performance purposes. + * + * @param SourcePath Full path to the locale folder with subfolders + for each localization. + * @param Index Path to the index.xml file to write. + * @return True if successful; False if required strings were missing. + *) +class function TBuildLocaleIndex.Run(SourcePath, Index: string): Boolean; +var + f: TSearchRec; + SourceFile, BCP47: string; + OutputDoc: IXmlDocument; + LocalesNode: IXmlNode; +begin + SourcePath := IncludeTrailingPathDelimiter(SourcePath); + + // Iterate through SourcePath/*/strings.xml and generate index.xml + CoInitializeEx(nil, COINIT_APARTMENTTHREADED); + try + Xml.Win.msxmldom.MSXMLDOMDocumentFactory.AddDOMProperty('ProhibitDTD', False); + + OutputDoc := NewXMLDocument; + OutputDoc.Encoding := 'UTF-8'; + OutputDoc.Active := True; + LocalesNode := OutputDoc.AddChild('locales'); + + if FindFirst(SourcePath+'*', faDirectory, f) = 0 then + begin + repeat + BCP47 := f.Name; + SourceFile := SourcePath+BCP47+'\strings.xml'; + if (BCP47 <> '..') and (BCP47 <> '.') and FileExists(SourcePath+BCP47+'\strings.xml') then + begin + if not ProcessFile(SourceFile, LocalesNode, BCP47) then + Exit(False); + end; + until FindNext(f) <> 0; + FindClose(f); + end; + + LocalesNode := nil; + + OutputDoc.SaveToFile(Index); + OutputDoc := nil; + + finally + CoUninitialize; + end; + + Result := True; +end; + +end. diff --git a/common/windows/delphi/tools/devtools/build.sh b/common/windows/delphi/tools/devtools/build.sh index f7b9dd52d4..4875458014 100755 --- a/common/windows/delphi/tools/devtools/build.sh +++ b/common/windows/delphi/tools/devtools/build.sh @@ -16,13 +16,13 @@ builder_parse "$@" source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" +builder_describe_internal_dependency \ + prepublish:project build:project + builder_describe_outputs \ configure:project /resources/build/win/delphi_environment_generated.inc.sh \ build:project /common/windows/delphi/tools/devtools/$WIN32_TARGET_PATH/devtools.exe -builder_describe_internal_dependency \ - prepublish:project build:project - #------------------------------------------------------------------------------------------------------------------- builder_run_action clean:project clean_windows_project_files diff --git a/common/windows/delphi/tools/devtools/devtools.dpr b/common/windows/delphi/tools/devtools/devtools.dpr index acbabcc560..8b2dc67226 100644 --- a/common/windows/delphi/tools/devtools/devtools.dpr +++ b/common/windows/delphi/tools/devtools/devtools.dpr @@ -5,24 +5,25 @@ program devtools; {$R *.res} uses - System.SysUtils, - DevIncludePaths in 'DevIncludePaths.pas', - DevInstallPackages in 'DevInstallPackages.pas', - DevUtils in 'DevUtils.pas', - DevReleaseBuildCheck in 'DevReleaseBuildCheck.pas', - DevCheckGitStatus in 'DevCheckGitStatus.pas', - RegistryKeys in '..\..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', - KeymanVersion in '..\..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', - utilsystem in '..\..\..\..\..\common\windows\delphi\general\utilsystem.pas', - utilexecute in '..\..\..\..\..\common\windows\delphi\general\utilexecute.pas', - Unicode in '..\..\..\..\..\common\windows\delphi\general\Unicode.pas', - GetOsVersion in '..\..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', - SourceRootPath in 'SourceRootPath.pas', - Keyman.System.DevTools.BuildMessageConstants in 'Keyman.System.DevTools.BuildMessageConstants.pas', - Keyman.System.DevTools.BuildSetupStringTranslations in 'Keyman.System.DevTools.BuildSetupStringTranslations.pas', - SetupStrings in '..\..\..\..\..\windows\src\desktop\setup\SetupStrings.pas', - Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas'; - + System.SysUtils, + DevIncludePaths in 'DevIncludePaths.pas', + DevInstallPackages in 'DevInstallPackages.pas', + DevUtils in 'DevUtils.pas', + DevReleaseBuildCheck in 'DevReleaseBuildCheck.pas', + DevCheckGitStatus in 'DevCheckGitStatus.pas', + RegistryKeys in '..\..\general\RegistryKeys.pas', + KeymanVersion in '..\..\general\KeymanVersion.pas', + utilsystem in '..\..\general\utilsystem.pas', + utilexecute in '..\..\general\utilexecute.pas', + Unicode in '..\..\general\Unicode.pas', + GetOsVersion in '..\..\general\GetOsVersion.pas', + SourceRootPath in 'SourceRootPath.pas', + Keyman.System.DevTools.BuildMessageConstants in 'Keyman.System.DevTools.BuildMessageConstants.pas', + Keyman.System.DevTools.BuildSetupStringTranslations in 'Keyman.System.DevTools.BuildSetupStringTranslations.pas', + SetupStrings in '..\..\..\..\..\windows\src\desktop\setup\SetupStrings.pas', + Keyman.System.AndroidStringToKeymanLocaleString in '..\..\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', + Keyman.System.DevTools.BuildLocaleIndex in 'Keyman.System.DevTools.BuildLocaleIndex.pas'; + begin try DevUtils.Run; diff --git a/common/windows/delphi/tools/devtools/devtools.dproj b/common/windows/delphi/tools/devtools/devtools.dproj index b87be1a2de..90af6a2417 100644 --- a/common/windows/delphi/tools/devtools/devtools.dproj +++ b/common/windows/delphi/tools/devtools/devtools.dproj @@ -1,980 +1,981 @@ - - - {49F05380-7150-4925-A5AF-81382713F77E} - 18.8 - VCL - devtools.dpr - True - Debug - Win32 - 1 - Console - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - devtools - $(BDS)\bin\delphi_PROJECTICON.ico - $(BDS)\bin\delphi_PROJECTICNS.icns - bindcompfmx;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;xmlrtl;ibxpress;DbxCommonDriver;IndyProtocols;DBXMySQLDriver;dbxcds;soaprtl;bindengine;CustomIPTransport;dsnap;IndyCore;fmxase;inet;fmxobj;inetdbxpress;fmxdae;dbexpress;IPIndyImpl;$(DCC_UsePackage) - System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) - .\obj\$(Platform)\$(Config) - .\bin\$(Platform)\$(Config) - false - false - false - false - false - 3081 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - - - vcldbx;frx16;TeeDB;vclib;inetdbbde;Tee;Rave100VCL;svnui;vclimg;intrawebdb_120_160;fmi;frxDB16;fs16;vclactnband;FMXTee;vcldb;TeeUI;bindcompvcl;vcldsnap;vclie;vcltouch;Intraweb_120_160;websnap;vclribbon;VclSmp;frxe16;fsDB16;vcl;CloudService;CodeSiteExpressPkg;FmxTeeUI;dsnapcon;vclx;webdsnap;svn;bdertl;adortl;$(DCC_UsePackage) - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) - 1033 - (None) - - - TeeDB;vclib;Tee;vclimg;vclactnband;vcldb;TeeUI;bindcompvcl;vcldsnap;vclie;vcltouch;websnap;VclSmp;vcl;dsnapcon;vclx;webdsnap;adortl;$(DCC_UsePackage) - - - DEBUG;$(DCC_Define) - false - true - true - true - - - - -buildsetupstrings ..\..\..\..\..\windows\src\desktop\setup\locale ..\..\..\..\..\windows\src\desktop\setup - 1033 - None - false - - - false - RELEASE;$(DCC_Define) - 0 - 0 - - - 1033 - - - - MainSource - - - - - - - - - - - - - - - - - - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - - - - Delphi.Personality.12 - - - - - False - False - 1 - 0 - 0 - 0 - False - False - False - False - False - 3081 - 1252 - - - - - 1.0.0.0 - - - - - - 1.0.0.0 - - - - devtools.dpr - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - - - devtools.exe - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - True - False - - - 12 - - - - - + + + {49F05380-7150-4925-A5AF-81382713F77E} + 18.8 + VCL + devtools.dpr + True + Debug + Win32 + 1 + Console + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + devtools + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + bindcompfmx;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;xmlrtl;ibxpress;DbxCommonDriver;IndyProtocols;DBXMySQLDriver;dbxcds;soaprtl;bindengine;CustomIPTransport;dsnap;IndyCore;fmxase;inet;fmxobj;inetdbxpress;fmxdae;dbexpress;IPIndyImpl;$(DCC_UsePackage) + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + .\obj\$(Platform)\$(Config) + .\bin\$(Platform)\$(Config) + false + false + false + false + false + 3081 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + + + vcldbx;frx16;TeeDB;vclib;inetdbbde;Tee;Rave100VCL;svnui;vclimg;intrawebdb_120_160;fmi;frxDB16;fs16;vclactnband;FMXTee;vcldb;TeeUI;bindcompvcl;vcldsnap;vclie;vcltouch;Intraweb_120_160;websnap;vclribbon;VclSmp;frxe16;fsDB16;vcl;CloudService;CodeSiteExpressPkg;FmxTeeUI;dsnapcon;vclx;webdsnap;svn;bdertl;adortl;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + (None) + + + TeeDB;vclib;Tee;vclimg;vclactnband;vcldb;TeeUI;bindcompvcl;vcldsnap;vclie;vcltouch;websnap;VclSmp;vcl;dsnapcon;vclx;webdsnap;adortl;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + false + true + true + true + + + D:\Projects\Keyman\app\windows\src + -buildlocaleindex desktop\kmshell\locale desktop\kmshell\locale\index.xml + 1033 + None + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + 1033 + + + + MainSource + + + + + + + + + + + + + + + + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + + + + + False + False + 1 + 0 + 0 + 0 + False + False + False + False + False + 3081 + 1252 + + + + + 1.0.0.0 + + + + + + 1.0.0.0 + + + + devtools.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + devtools.exe + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + False + + + 12 + + + + + diff --git a/common/windows/delphi/tools/test-klog/build.sh b/common/windows/delphi/tools/test-klog/build.sh index acdd90cd53..4bd78b08a5 100755 --- a/common/windows/delphi/tools/test-klog/build.sh +++ b/common/windows/delphi/tools/test-klog/build.sh @@ -7,19 +7,20 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Tool for validating klog is disabled for release builds" \ clean configure build test prepublish edit + builder_parse "$@" #------------------------------------------------------------------------------------------------------------------- source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" +builder_describe_internal_dependency \ + prepublish:project build:project + builder_describe_outputs \ configure:project /resources/build/win/delphi_environment_generated.inc.sh \ build:project /common/windows/delphi/tools/test-klog/$WIN32_TARGET_PATH/test_klog.exe -builder_describe_internal_dependency \ - prepublish:project build:project - #------------------------------------------------------------------------------------------------------------------- function do_build() { diff --git a/common/windows/signtime.bat b/common/windows/signtime.bat index 80eb4770ef..d29cf58b75 100644 --- a/common/windows/signtime.bat +++ b/common/windows/signtime.bat @@ -4,8 +4,8 @@ rem Build agents have a separate signtime script. We don't use 'call' here so rem executing that script will terminate this one. if exist c:\codesign\signtime.bat c:\codesign\signtime.bat %1 %2 %3 %4 %5 %6 %7 %8 -set SERVERLIST=(http://timestamp.comodoca.com/authenticode http://timestamp.verisign.com/scripts/timstamp.dll http://timestamp.globalsign.com/scripts/timestamp.dll http://tsa.starfieldtech.com) -set RFC3161SERVERLIST=(http://timestamp.comodoca.com/rfc3161) +set SERVERLIST=(http://timestamp.digicert.com http://timestamp.sectigo.com) +set RFC3161SERVERLIST=(http://timestamp.digicert.com http://timestamp.sectigo.com) set SIGNTOOL=%1 set PFX_SHA1=%2 set PFX_SHA256=%3 diff --git a/core/build.bat b/core/build.bat index 5dd219e9f2..fc4f400bfb 100644 --- a/core/build.bat +++ b/core/build.bat @@ -83,7 +83,7 @@ if "!COMMAND!" == "configure" ( if "%1" == "--no-tests" ( meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! -Dkeyman_core_tests=false --werror %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! ) else ( - meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! --werror %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! + meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! -Dkeyman_core_tests=true --werror %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! ) shift ) diff --git a/core/build.sh b/core/build.sh index 1c1d337b1c..1defefe13b 100755 --- a/core/build.sh +++ b/core/build.sh @@ -79,7 +79,7 @@ builder_parse "$@" # if we don't plan to run them, for example when doing a dependency build # in CI # -MESON_OPTION_keyman_core_tests= +MESON_OPTION_keyman_core_tests="-Dkeyman_core_tests=true" BUILD_BAT_keyman_core_tests= if builder_is_dep_build || builder_has_option --no-tests; then diff --git a/core/include/ldml/build.sh b/core/include/ldml/build.sh index 4029d732ce..8adf30773e 100755 --- a/core/include/ldml/build.sh +++ b/core/include/ldml/build.sh @@ -10,17 +10,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Keyman ldml-keyboard-constants package" \ "@/common/web/keyman-version" \ "clean" \ "configure" \ "build" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -34,4 +30,3 @@ builder_run_action clean rm -rf ./build/ builder_run_action configure node_select_version_and_npm_ci builder_run_action build tsc --build # builder_run_action test # no tests at this time -builder_run_action publish ci_publish_npm diff --git a/core/include/ldml/package.json b/core/include/ldml/package.json index ba9acf063e..6cd3da4562 100644 --- a/core/include/ldml/package.json +++ b/core/include/ldml/package.json @@ -16,6 +16,7 @@ "scripts": {}, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "core/include/ldml" } } diff --git a/core/src/kmx/kmx_processevent.cpp b/core/src/kmx/kmx_processevent.cpp index 921e8d9212..09ebcdd4cd 100644 --- a/core/src/kmx/kmx_processevent.cpp +++ b/core/src/kmx/kmx_processevent.cpp @@ -107,10 +107,14 @@ KMX_BOOL KMX_ProcessEvent::ProcessEvent( if (kbd->StartGroup[BEGIN_UNICODE] == (KMX_DWORD) -1) { DebugLog("Non-Unicode keyboards are not supported."); + DeleteInternalDebugItems(); + state->debug_items().push_end(m_actions.Length(), 0); m_core_state = nullptr; return FALSE; } + // TODO: what about debug_item state for all shortcut return paths below? + // TODO: this needs to be cleaned up; see #11909 switch (vkey) { case KM_CORE_VKEY_CAPS: if (KeyCapsLockPress(modifiers, isKeyDown)) diff --git a/core/tools/ldml-const-builder/build.sh b/core/tools/ldml-const-builder/build.sh index 8c8bc364b5..dbee106f5a 100755 --- a/core/tools/ldml-const-builder/build.sh +++ b/core/tools/ldml-const-builder/build.sh @@ -9,35 +9,44 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/build/utils.inc.sh" +. "$KEYMAN_ROOT/resources/build/node.inc.sh" CORE_LDML_H_FILE="../../include/ldml/keyman_core_ldml.h" +CORE_LDML_TS_FILE="../../include/ldml/keyman_core_ldml.ts" ################################ Main script ################################ -builder_describe "Build and run the constant builder for LDML" clean build run +builder_describe "Build and run the constant builder for LDML" clean configure build run test + builder_parse "$@" -# TODO: build if out-of-date if test is specified -# TODO: configure if npm has not been run, and build is specified +builder_describe_internal_dependency \ + run:project build:project \ + test:project run:project +builder_describe_outputs \ + configure "/node_modules" \ + build "/core/include/ldml/ldml-const-builder/ldml-const-builder.js" \ + run "/core/include/ldml/ldml-const-builder/run.txt" -if builder_start_action clean; then - rm -rf ../../include/ldml/build/ - # Not removing ${CORE_LDML_H_FILE} as it is checked in - builder_finish_action success clean -fi - -if builder_start_action build; then - # Generate index.ts - npx tsc -b ../../include/ldml/tsconfig.build.json - - builder_finish_action success build -fi - -if builder_start_action run; then +function do_run() { node --enable-source-maps ../../include/ldml/ldml-const-builder/ldml-const-builder.js > ${CORE_LDML_H_FILE} + touch ../../include/ldml/ldml-const-builder/run.txt echo "Updated ${CORE_LDML_H_FILE}" +} - builder_finish_action success run -fi +function do_test() { + if ! git diff --exit-code "${CORE_LDML_H_FILE}"; then + builder_echo error "File ${CORE_LDML_TS_FILE} has changed, but /core/tools/ldml-const-builder has not been run." + builder_echo error "Run ${COLOR_BRIGHT_WHITE}$KEYMAN_ROOT/core/tools/ldml-const-build/build.sh run${COLOR_RESET} and commit changes" + return 1 + fi +} + +# clean: Not removing ${CORE_LDML_H_FILE} as it is checked in +builder_run_action clean rm -rf ../../include/ldml/build/ ../../include/ldml/ldml-const-builder/ ../../include/ldml/coverage/ +builder_run_action configure node_select_version_and_npm_ci +builder_run_action build tsc -b ../../include/ldml/tsconfig.build.json +builder_run_action run do_run +builder_run_action test do_test diff --git a/developer/docs/api/etc/kmc-analyze.api.md b/developer/docs/api/etc/kmc-analyze.api.md index 45bbd8101c..8784db6b95 100644 --- a/developer/docs/api/etc/kmc-analyze.api.md +++ b/developer/docs/api/etc/kmc-analyze.api.md @@ -1,55 +1,87 @@ -## API Report File for "@keymanapp/kmc-analyze" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { Osk } from '@keymanapp/kmc-kmn'; - -// @public -export class AnalyzeOskCharacterUse { - constructor(callbacks: CompilerCallbacks, options?: AnalyzeOskCharacterUseOptions); - analyze(file: string): Promise; - clear(): void; - getStrings(format?: '.txt' | '.md' | '.json'): string[]; -} - -// @public -export interface AnalyzeOskCharacterUseOptions { - includeCounts?: boolean; - puaBase?: number; - stripDottedCircle?: boolean; -} - -// @public -export class AnalyzeOskRewritePua { - constructor(callbacks: CompilerCallbacks); - analyze(file: string, mapping: Osk.StringResult[]): Promise; - clear(): void; - get data(): { - [index: string]: Uint8Array; - }; -} - -// Warning: (ae-internal-missing-underscore) The name "AnalyzerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal -export class AnalyzerMessages { - // (undocumented) - static readonly FATAL_UnexpectedException: number; - // (undocumented) - static readonly Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static readonly INFO_ScanningFile: number; - // (undocumented) - static readonly Info_ScanningFile: (o: { - type: string; - name: string; - }) => CompilerEvent; -} - -``` +## API Report File for "@keymanapp/kmc-analyze" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { Osk } from '@keymanapp/kmc-kmn'; + +// @public +export class AnalyzeOskCharacterUse { + constructor(callbacks: CompilerCallbacks, options?: AnalyzeOskCharacterUseOptions); + analyze(file: string): Promise; + clear(): void; + getStrings(format?: '.txt' | '.md' | '.json'): string[]; + // @internal (undocumented) + unitTestEndPoints: { + loadPreviousMap: (filename: string) => Osk.StringResult[]; + addStrings: (strings: string[], filename: string) => void; + stringToUnicodeSequence: typeof AnalyzeOskCharacterUse.stringToUnicodeSequence; + }; +} + +// @public +export interface AnalyzeOskCharacterUseOptions { + includeCounts?: boolean; + mergeMapFile?: string; + puaBase?: number; + stripDottedCircle?: boolean; +} + +// @public +export class AnalyzeOskRewritePua { + constructor(callbacks: CompilerCallbacks); + analyze(file: string, mapping: Osk.StringResult[]): Promise; + clear(): void; + get data(): { + [index: string]: Uint8Array; + }; +} + +// Warning: (ae-internal-missing-underscore) The name "AnalyzerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export class AnalyzerMessages { + // (undocumented) + static readonly FATAL_UnexpectedException: number; + // (undocumented) + static readonly Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static readonly INFO_ScanningFile: number; + // (undocumented) + static readonly Info_ScanningFile: (o: { + type: string; + name: string; + }) => CompilerEvent; + // (undocumented) + static readonly WARN_PreviousMapDidIncludeCounts: number; + // (undocumented) + static readonly Warn_PreviousMapDidIncludeCounts: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static readonly WARN_PreviousMapDidNotIncludeCounts: number; + // (undocumented) + static readonly Warn_PreviousMapDidNotIncludeCounts: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static readonly WARN_PreviousMapFileCouldNotBeLoaded: number; + // (undocumented) + static readonly Warn_PreviousMapFileCouldNotBeLoaded: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static readonly WARN_PreviousMapFileCouldNotBeLoadedDueToError: number; + // (undocumented) + static readonly Warn_PreviousMapFileCouldNotBeLoadedDueToError: (o: { + filename: string; + e: any; + }) => CompilerEvent; +} + +``` diff --git a/developer/docs/api/etc/kmc-copy.api.md b/developer/docs/api/etc/kmc-copy.api.md new file mode 100644 index 0000000000..f61dd59e10 --- /dev/null +++ b/developer/docs/api/etc/kmc-copy.api.md @@ -0,0 +1,258 @@ +## API Report File for "@keymanapp/kmc-copy" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerAsyncCallbacks } from '@keymanapp/developer-utils'; +import { CompilerBaseOptions } from '@keymanapp/developer-utils'; +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerFileSystemAsyncCallbacks } from '@keymanapp/developer-utils'; +import { CompilerNetAsyncCallbacks } from '@keymanapp/developer-utils'; +import { CompilerPathCallbacks } from '@keymanapp/developer-utils'; +import { GitHubUrls } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; + +// Warning: (ae-internal-missing-underscore) The name "CopierMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class CopierMessages { + // (undocumented) + static ERROR_CannotCreateFolder: number; + // (undocumented) + static Error_CannotCreateFolder: (o: { + folderName: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_CannotDownloadFolderFromGitHub: number; + // (undocumented) + static Error_CannotDownloadFolderFromGitHub: (o: { + ref: string; + message?: string; + cause?: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_CannotDownloadRepoFromGitHub: number; + // (undocumented) + static Error_CannotDownloadRepoFromGitHub: (o: { + ref: string; + message?: string; + cause?: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_CannotFindInputProject: number; + // (undocumented) + static Error_CannotFindInputProject: (o: { + project: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_CannotWriteOutputFile: number; + // (undocumented) + static Error_CannotWriteOutputFile: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_CloudDoesNotHaveSource: number; + // (undocumented) + static Error_CloudDoesNotHaveSource: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_CouldNotFindDefaultBranchOnGitHub: number; + // (undocumented) + static Error_CouldNotFindDefaultBranchOnGitHub: (o: { + ref: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_CouldNotRetrieveFromCloud: number; + // (undocumented) + static Error_CouldNotRetrieveFromCloud: (o: { + id: string; + message?: string; + cause?: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FolderDownloadedFromGitHubIsNotAValidFolder: number; + // (undocumented) + static Error_FolderDownloadedFromGitHubIsNotAValidFolder: (o: { + ref: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidCloudKeyboardId: number; + // (undocumented) + static Error_InvalidCloudKeyboardId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidKeyboardId: number; + // (undocumented) + static Error_InvalidKeyboardId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLexicalModelId: number; + // (undocumented) + static Error_InvalidLexicalModelId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_KeymanCloudReturnedInvalidData: number; + // (undocumented) + static Error_KeymanCloudReturnedInvalidData: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_OutputPathAlreadyExists: number; + // (undocumented) + static Error_OutputPathAlreadyExists: (o: { + outPath: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_PackageFileCouldNotBeRead: number; + // (undocumented) + static Error_PackageFileCouldNotBeRead: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static INFO_CannotDownloadBinaryFileFromGitHub: number; + // (undocumented) + static Info_CannotDownloadBinaryFileFromGitHub: (o: { + ref: string; + message?: string; + cause?: string; + }) => CompilerEvent; + // (undocumented) + static INFO_DryRun: number; + // (undocumented) + static Info_DryRun: (o: { + outPath: string; + }) => CompilerEvent; + // (undocumented) + static INFO_DryRunCreatingFolder: number; + // (undocumented) + static Info_DryRunCreatingFolder: (o: { + path: string; + }) => CompilerEvent; + // (undocumented) + static INFO_DryRunWritingFile: number; + // (undocumented) + static Info_DryRunWritingFile: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static VERBOSE_CopyingFile: number; + // (undocumented) + static Verbose_CopyingFile: (o: { + from: string; + to: string; + }) => CompilerEvent; + // (undocumented) + static VERBOSE_DownloadingFile: number; + // (undocumented) + static Verbose_DownloadingFile: (o: { + filename: string; + url: string; + }) => CompilerEvent; + // (undocumented) + static VERBOSE_DownloadingFolder: number; + // (undocumented) + static Verbose_DownloadingFolder: (o: { + path: string; + url: string; + }) => CompilerEvent; + // (undocumented) + static WARN_CannotDownloadFileFromGitHub: number; + // (undocumented) + static Warn_CannotDownloadFileFromGitHub: (o: { + ref: string; + message?: string; + cause?: string; + }) => CompilerEvent; + // (undocumented) + static WARN_FilenameCollides: number; + // (undocumented) + static Warn_FilenameCollides: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_FileNotFound: number; + // (undocumented) + static Warn_FileNotFound: (o: { + filename: string; + newFilename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_ModelIdDoesNotFollowLexicalModelConventions: number; + // (undocumented) + static Warn_ModelIdDoesNotFollowLexicalModelConventions: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static WARN_NoWordlistsFound: number; + // (undocumented) + static Warn_NoWordlistsFound: (o: { + filename: string; + }) => CompilerEvent; +} + +// @public +export interface CopierOptions extends CompilerBaseOptions { + dryRun: boolean; + outPath: string; + relocateExternalFiles?: boolean; +} + +// @public (undocumented) +export class KeymanProjectCopier implements KeymanCompiler { + // Warning: (ae-forgotten-export) The symbol "CopierAsyncCallbacks" needs to be exported by the entry point main.d.ts + // + // (undocumented) + asyncCallbacks: CopierAsyncCallbacks; + // (undocumented) + callbacks: CompilerCallbacks; + // Warning: (ae-forgotten-export) The symbol "KeymanCloudSource" needs to be exported by the entry point main.d.ts + // + // (undocumented) + cloudSource: KeymanCloudSource; + // Warning: (ae-forgotten-export) The symbol "GitHubRef" needs to be exported by the entry point main.d.ts + // + // (undocumented) + githubRef: GitHubRef; + // (undocumented) + init(callbacks: CompilerCallbacks, options: CopierOptions): Promise; + // (undocumented) + isLocalOrigin(): boolean; + // (undocumented) + options: CopierOptions; + // (undocumented) + outPath: string; + // (undocumented) + outputId: string; + // Warning: (ae-forgotten-export) The symbol "CopierResult" needs to be exported by the entry point main.d.ts + run(source: string): Promise; + // (undocumented) + sourceId: string; + // @internal (undocumented) + unitTestEndPoints: { + getGithubSourceProject: (source: string) => Promise; + getCloudSourceProject: (source: string) => Promise; + }; + // Warning: (ae-forgotten-export) The symbol "CopierArtifacts" needs to be exported by the entry point main.d.ts + write(artifacts: CopierArtifacts): Promise; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-generate.api.md b/developer/docs/api/etc/kmc-generate.api.md new file mode 100644 index 0000000000..427f2f2d49 --- /dev/null +++ b/developer/docs/api/etc/kmc-generate.api.md @@ -0,0 +1,181 @@ +## API Report File for "@keymanapp/kmc-generate" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerBaseOptions } from '@keymanapp/developer-utils'; +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { KeymanTargets } from '@keymanapp/common-types'; + +// @public +export class AbstractGenerator { + // (undocumented) + protected get callbacks(): CompilerCallbacks; + protected defaultArtifacts(): GeneratorArtifacts; + protected get filenameMap(): { + [index: string]: string; + }; + protected fullCopyright: string; + protected get includedPrefixes(): string[]; + init(callbacks: CompilerCallbacks, options: GeneratorOptions): Promise; + // (undocumented) + protected get options(): GeneratorOptions; + // (undocumented) + protected static readonly SFile_GitIgnore = ".gitignore"; + // (undocumented) + protected static readonly SFile_HistoryMD = "HISTORY.md"; + // (undocumented) + protected static readonly SFile_LicenseMD = "LICENSE.md"; + // (undocumented) + protected static readonly SFile_ReadmeHTM: string; + // (undocumented) + protected static readonly SFile_ReadmeMD = "README.md"; + // (undocumented) + protected static readonly SFile_WelcomeHTM: string; + // (undocumented) + protected static readonly SPath_Source = "source/"; + protected get templateBasePath(): string; + // @internal (undocumented) + get test_tokenMap(): { + [index: string]: string; + }; + protected get tokenMap(): { + [index: string]: string; + }; + // Warning: (ae-forgotten-export) The symbol "GeneratorArtifacts" needs to be exported by the entry point main.d.ts + write(artifacts: GeneratorArtifacts): Promise; +} + +// Warning: (ae-internal-missing-underscore) The name "GeneratorMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class GeneratorMessages { + // (undocumented) + static ERROR_CannotCreateFolder: number; + // (undocumented) + static Error_CannotCreateFolder: (o: { + folderName: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_CannotWriteOutputFile: number; + // (undocumented) + static Error_CannotWriteOutputFile: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidKeymanKeyboardId: number; + // (undocumented) + static Error_InvalidKeymanKeyboardId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLdmlKeyboardId: number; + // (undocumented) + static Error_InvalidLdmlKeyboardId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLexicalModelId: number; + // (undocumented) + static Error_InvalidLexicalModelId: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidTarget: number; + // (undocumented) + static Error_InvalidTarget: (o: { + target: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_OutputPathAlreadyExists: number; + // (undocumented) + static Error_OutputPathAlreadyExists: (o: { + outPath: string; + }) => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static WARN_ModelIdDoesNotFollowLexicalModelConventions: number; + // (undocumented) + static Warn_ModelIdDoesNotFollowLexicalModelConventions: (o: { + id: string; + }) => CompilerEvent; +} + +// @public +export interface GeneratorOptions extends CompilerBaseOptions { + author?: string; + copyright?: string; + description?: string; + id: string; + keymanVersion?: string; + languageTags?: string[]; + name?: string; + outPath: string; + targets?: KeymanTargets.KeymanTarget[]; + version?: string; +} + +// Warning: (ae-forgotten-export) The symbol "BasicGenerator" needs to be exported by the entry point main.d.ts +// +// @public +export class KeymanKeyboardGenerator extends BasicGenerator implements KeymanCompiler { + // Warning: (ae-forgotten-export) The symbol "GeneratorResult" needs to be exported by the entry point main.d.ts + run(): Promise; + // (undocumented) + static readonly SFile_Keyboard = "keyboard"; + // (undocumented) + static readonly SFile_KeyboardKMN: string; + // (undocumented) + static readonly SFile_KeyboardKPS: string; + // (undocumented) + static readonly SFile_KeyboardKVKS: string; + // (undocumented) + static readonly SFile_Project: string; + // (undocumented) + static readonly SFile_TouchLayout: string; +} + +// @public +export class LdmlKeyboardGenerator extends BasicGenerator implements KeymanCompiler { + run(): Promise; + // (undocumented) + protected static readonly SFile_Keyboard = "keyboard"; + // (undocumented) + static readonly SFile_KeyboardXML: string; + // (undocumented) + static readonly SFile_Package: string; + // (undocumented) + static readonly SFile_Project: string; +} + +// @public +export class LexicalModelGenerator extends BasicGenerator implements KeymanCompiler { + run(): Promise; + // (undocumented) + static readonly SFile_Model = "model"; + // (undocumented) + static readonly SFile_ModelTs: string; + // (undocumented) + static readonly SFile_Package: string; + // (undocumented) + static readonly SFile_Project: string; + // (undocumented) + static readonly SFile_WordlistTsv: string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-keyboard-info.api.md b/developer/docs/api/etc/kmc-keyboard-info.api.md index 0b1ee4f427..0f320b803f 100644 --- a/developer/docs/api/etc/kmc-keyboard-info.api.md +++ b/developer/docs/api/etc/kmc-keyboard-info.api.md @@ -1,119 +1,157 @@ -## API Report File for "@keymanapp/kmc-keyboard-info" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifact } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; -import { KmpJsonFile } from '@keymanapp/common-types'; - -// @public -export class KeyboardInfoCompiler implements KeymanCompiler { - constructor(); - // Warning: (ae-forgotten-export) The symbol "KeyboardInfoFileLanguageFont" needs to be exported by the entry point index.d.ts - // - // @internal (undocumented) - fontSourceToKeyboardInfoFont(kpsFilename: string, kmpJsonData: KmpJsonFile.KmpJsonFile, source: string[]): Promise; - init(callbacks: CompilerCallbacks, options: KeyboardInfoCompilerOptions): Promise; - run(inputFilename: string, outputFilename?: string): Promise; - write(artifacts: KeyboardInfoCompilerArtifacts): Promise; -} - -// @public -export interface KeyboardInfoCompilerArtifacts extends KeymanCompilerArtifacts { - keyboard_info: KeymanCompilerArtifact; -} - -// Warning: (ae-internal-missing-underscore) The name "KeyboardInfoCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class KeyboardInfoCompilerMessages { - // (undocumented) - static ERROR_CannotBuildWithoutKmpFile: number; - // (undocumented) - static Error_CannotBuildWithoutKmpFile: () => CompilerEvent; - // (undocumented) - static ERROR_FileDoesNotExist: number; - // (undocumented) - static Error_FileDoesNotExist: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_FontFileCannotBeRead: number; - // (undocumented) - static Error_FontFileCannotBeRead: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_FontFileMetaDataIsInvalid: number; - // (undocumented) - static Error_FontFileMetaDataIsInvalid: (o: { - filename: string; - message: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidAuthorEmail: number; - // (undocumented) - static Error_InvalidAuthorEmail: (o: { - email: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseFileIsDamaged: number; - // (undocumented) - static Error_LicenseFileIsDamaged: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseFileIsMissing: number; - // (undocumented) - static Error_LicenseFileIsMissing: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseIsNotValid: number; - // (undocumented) - static Error_LicenseIsNotValid: (o: { - filename: string; - message: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_NoLicenseFound: number; - // (undocumented) - static Error_NoLicenseFound: () => CompilerEvent; - // (undocumented) - static FATAL_UnexpectedException: number; - // (undocumented) - static Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; -} - -// @public -export interface KeyboardInfoCompilerOptions extends CompilerOptions { - sources: KeyboardInfoSources; -} - -// @public -export interface KeyboardInfoCompilerResult extends KeymanCompilerResult { - artifacts: KeyboardInfoCompilerArtifacts; -} - -// @public -export interface KeyboardInfoSources { - forPublishing: boolean; - jsFilename?: string; - kmpFilename: string; - kpsFilename: string; - lastCommitDate?: string; - sourcePath: string; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@keymanapp/kmc-keyboard-info" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { KmpJsonFile } from '@keymanapp/common-types'; + +// @public +export class KeyboardInfoCompiler implements KeymanCompiler { + // Warning: (ae-forgotten-export) The symbol "KeyboardInfoFileLanguageFont" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + fontSourceToKeyboardInfoFont(kpsFilename: string, kmpJsonData: KmpJsonFile.KmpJsonFile, source: string[]): Promise; + init(callbacks: CompilerCallbacks, options: KeyboardInfoCompilerOptions): Promise; + run(inputFilename: string, outputFilename?: string): Promise; + // @internal (undocumented) + unitTestEndPoints: { + fillLanguageMetadata: (language: KeyboardInfoFileLanguage, bcp47: string, commonScript: string, firstBcp47: string) => { + commonScript: string; + firstBcp47: string; + }; + }; + write(artifacts: KeyboardInfoCompilerArtifacts): Promise; +} + +// @public +export interface KeyboardInfoCompilerArtifacts extends KeymanCompilerArtifacts { + keyboard_info: KeymanCompilerArtifact; +} + +// Warning: (ae-internal-missing-underscore) The name "KeyboardInfoCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class KeyboardInfoCompilerMessages { + // (undocumented) + static ERROR_CannotBuildWithoutKmpFile: number; + // (undocumented) + static Error_CannotBuildWithoutKmpFile: () => CompilerEvent; + // (undocumented) + static ERROR_DescriptionIsMissing: number; + // (undocumented) + static Error_DescriptionIsMissing: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FileDoesNotExist: number; + // (undocumented) + static Error_FileDoesNotExist: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FontFileCannotBeRead: number; + // (undocumented) + static Error_FontFileCannotBeRead: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FontFileMetaDataIsInvalid: number; + // (undocumented) + static Error_FontFileMetaDataIsInvalid: (o: { + filename: string; + message: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidAuthorEmail: number; + // (undocumented) + static Error_InvalidAuthorEmail: (o: { + email: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseFileIsDamaged: number; + // (undocumented) + static Error_LicenseFileIsDamaged: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseFileIsMissing: number; + // (undocumented) + static Error_LicenseFileIsMissing: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseIsNotValid: number; + // (undocumented) + static Error_LicenseIsNotValid: (o: { + filename: string; + message: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NoLicenseFound: number; + // (undocumented) + static Error_NoLicenseFound: () => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static HINT_ScriptDoesNotMatch: number; + // (undocumented) + static Hint_ScriptDoesNotMatch: (o: { + script: string; + bcp47: string; + commonScript: string; + firstBcp47: string; + }) => CompilerEvent; + // (undocumented) + static WARN_LanguageTagNotFound: number; + // (undocumented) + static Warn_LanguageTagNotFound: (o: { + bcp47: string; + langSubtag: string; + }) => CompilerEvent; + // (undocumented) + static WARN_LanguageTagNotFound2: number; + // (undocumented) + static Warn_LanguageTagNotFound2: (o: { + bcp47: string; + }) => CompilerEvent; +} + +// @public +export interface KeyboardInfoCompilerOptions extends CompilerOptions { + sources: KeyboardInfoSources; +} + +// @public +export interface KeyboardInfoCompilerResult extends KeymanCompilerResult { + artifacts: KeyboardInfoCompilerArtifacts; +} + +// @public +export interface KeyboardInfoSources { + forPublishing: boolean; + jsFilename?: string; + kmpFilename: string; + kpsFilename: string; + lastCommitDate?: string; + sourcePath: string; +} + +// Warnings were encountered during analysis: +// +// src/keyboard-info-compiler.ts:674:14 - (ae-forgotten-export) The symbol "KeyboardInfoFileLanguage" needs to be exported by the entry point index.d.ts + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-kmn.api.md b/developer/docs/api/etc/kmc-kmn.api.md index de1bd503f8..0805075e56 100644 --- a/developer/docs/api/etc/kmc-kmn.api.md +++ b/developer/docs/api/etc/kmc-kmn.api.md @@ -1,958 +1,978 @@ -## API Report File for "@keymanapp/kmc-kmn" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifactOptional } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; -import { TouchLayout } from '@keymanapp/common-types'; -import { UnicodeSet } from '@keymanapp/common-types'; -import { UnicodeSetParser } from '@keymanapp/common-types'; -import { VisualKeyboard } from '@keymanapp/common-types'; - -// Warning: (ae-internal-missing-underscore) The name "CompilerResultExtraGroup" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export interface CompilerResultExtraGroup { - // (undocumented) - isReadOnly: boolean; - // (undocumented) - name: string; -} - -// Warning: (ae-internal-missing-underscore) The name "CompilerResultExtraStore" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export interface CompilerResultExtraStore { - // (undocumented) - line: number; - // (undocumented) - name: string; - // (undocumented) - storeType: number; -} - -// @public -export class KmnCompiler implements KeymanCompiler, UnicodeSetParser { - constructor(); - // @internal - static fixNewPattern(pattern: string): string; - init(callbacks: CompilerCallbacks, options: KmnCompilerOptions): Promise; - // @internal (undocumented) - parseUnicodeSet(pattern: string, rangeCount: number): UnicodeSet | null; - run(infile: string, outfile: string): Promise; - // @internal (undocumented) - sizeUnicodeSet(pattern: string): number; - // @internal - testSentry(): any; - verifyInitialized(): boolean; - write(artifacts: KmnCompilerArtifacts): Promise; -} - -// @public -export interface KmnCompilerArtifacts extends KeymanCompilerArtifacts { - js?: KeymanCompilerArtifactOptional; - kmx?: KeymanCompilerArtifactOptional; - kvk?: KeymanCompilerArtifactOptional; -} - -// Warning: (ae-internal-missing-underscore) The name "KmnCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal -export class KmnCompilerMessages { - // (undocumented) - static ERROR_140FeatureOnlyContextAndNotAnyWeb: number; - // (undocumented) - static Error_140FeatureOnlyContextAndNotAnyWeb: () => CompilerEvent; - // (undocumented) - static ERROR_501FeatureOnly_Call: number; - // (undocumented) - static Error_501FeatureOnly_Call: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_Contextn: number; - // (undocumented) - static Error_60FeatureOnly_Contextn: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_EthnologueCode: number; - // (undocumented) - static Error_60FeatureOnly_EthnologueCode: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_MnemonicLayout: number; - // (undocumented) - static Error_60FeatureOnly_MnemonicLayout: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_NamedCodes: number; - // (undocumented) - static Error_60FeatureOnly_NamedCodes: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_OldCharPosMatching: number; - // (undocumented) - static Error_60FeatureOnly_OldCharPosMatching: () => CompilerEvent; - // (undocumented) - static ERROR_60FeatureOnly_VirtualCharKey: number; - // (undocumented) - static Error_60FeatureOnly_VirtualCharKey: () => CompilerEvent; - // (undocumented) - static ERROR_70FeatureOnly: number; - // (undocumented) - static Error_70FeatureOnly: () => CompilerEvent; - // (undocumented) - static ERROR_80FeatureOnly: number; - // (undocumented) - static Error_80FeatureOnly: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnly_IfSystemStores: number; - // (undocumented) - static Error_90FeatureOnly_IfSystemStores: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnly_SetSystemStores: number; - // (undocumented) - static Error_90FeatureOnly_SetSystemStores: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnlyEmbedCSS: number; - // (undocumented) - static Error_90FeatureOnlyEmbedCSS: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnlyKeyboardVersion: number; - // (undocumented) - static Error_90FeatureOnlyKeyboardVersion: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnlyLayoutFile: number; - // (undocumented) - static Error_90FeatureOnlyLayoutFile: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnlyTargets: number; - // (undocumented) - static Error_90FeatureOnlyTargets: () => CompilerEvent; - // (undocumented) - static ERROR_90FeatureOnlyVirtualKeyDictionary: number; - // (undocumented) - static Error_90FeatureOnlyVirtualKeyDictionary: () => CompilerEvent; - // (undocumented) - static ERROR_AnyInVirtualKeySection: number; - // (undocumented) - static Error_AnyInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_BeepInVirtualKeySection: number; - // (undocumented) - static Error_BeepInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_CallInVirtualKeySection: number; - // (undocumented) - static Error_CallInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_CannotLoadIncludeFile: number; - // (undocumented) - static Error_CannotLoadIncludeFile: () => CompilerEvent; - // (undocumented) - static ERROR_CannotReadBitmapFile: number; - // (undocumented) - static Error_CannotReadBitmapFile: () => CompilerEvent; - // (undocumented) - static ERROR_CannotReadInfile: number; - // (undocumented) - static Error_CannotReadInfile: () => CompilerEvent; - // (undocumented) - static ERROR_CannotUseReadWriteGroupFromReadonlyGroup: number; - // (undocumented) - static Error_CannotUseReadWriteGroupFromReadonlyGroup: () => CompilerEvent; - // (undocumented) - static ERROR_CasedKeysMustContainOnlyVirtualKeys: number; - // (undocumented) - static Error_CasedKeysMustContainOnlyVirtualKeys: () => CompilerEvent; - // (undocumented) - static ERROR_CasedKeysMustNotIncludeShiftStates: number; - // (undocumented) - static Error_CasedKeysMustNotIncludeShiftStates: () => CompilerEvent; - // (undocumented) - static ERROR_CasedKeysNotSupportedWithMnemonicLayout: number; - // (undocumented) - static Error_CasedKeysNotSupportedWithMnemonicLayout: () => CompilerEvent; - // (undocumented) - static ERROR_CharacterExpansionMustBeFollowedByCharacter: number; - // (undocumented) - static Error_CharacterExpansionMustBeFollowedByCharacter: () => CompilerEvent; - // (undocumented) - static ERROR_CharacterRangeTooLong: number; - // (undocumented) - static Error_CharacterRangeTooLong: () => CompilerEvent; - // (undocumented) - static ERROR_CodeInvalidInKeyStore: number; - // (undocumented) - static Error_CodeInvalidInKeyStore: () => CompilerEvent; - // (undocumented) - static ERROR_CodeInvalidInThisSection: number; - // (undocumented) - static Error_CodeInvalidInThisSection: () => CompilerEvent; - // (undocumented) - static ERROR_ContextAndIndexInvalidInMatchNomatch: number; - // (undocumented) - static Error_ContextAndIndexInvalidInMatchNomatch: () => CompilerEvent; - // (undocumented) - static ERROR_ContextExHasInvalidOffset: number; - // (undocumented) - static Error_ContextExHasInvalidOffset: () => CompilerEvent; - // (undocumented) - static ERROR_ContextInVirtualKeySection: number; - // (undocumented) - static Error_ContextInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_DuplicateGroup: number; - // (undocumented) - static Error_DuplicateGroup: () => CompilerEvent; - // (undocumented) - static ERROR_DuplicateStore: number; - // (undocumented) - static Error_DuplicateStore: () => CompilerEvent; - // (undocumented) - static ERROR_ExpansionMustBePositive: number; - // (undocumented) - static Error_ExpansionMustBePositive: () => CompilerEvent; - // (undocumented) - static ERROR_ExpansionMustFollowCharacterOrVKey: number; - // (undocumented) - static Error_ExpansionMustFollowCharacterOrVKey: () => CompilerEvent; - // (undocumented) - static ERROR_ExtendedStringTooLong: number; - // (undocumented) - static Error_ExtendedStringTooLong: () => CompilerEvent; - // (undocumented) - static ERROR_FileNotFound: number; - // (undocumented) - static Error_FileNotFound: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_GroupDoesNotExist: number; - // (undocumented) - static Error_GroupDoesNotExist: () => CompilerEvent; - // (undocumented) - static ERROR_IfSystemStore_NotFound: number; - // (undocumented) - static Error_IfSystemStore_NotFound: () => CompilerEvent; - // (undocumented) - static ERROR_IndexDoesNotPointToAny: number; - // (undocumented) - static Error_IndexDoesNotPointToAny: () => CompilerEvent; - // (undocumented) - static ERROR_IndexInVirtualKeySection: number; - // (undocumented) - static Error_IndexInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_InfileNotExist: number; - // (undocumented) - static Error_InfileNotExist: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidAny: number; - // (undocumented) - static Error_InvalidAny: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidBegin: number; - // (undocumented) - static Error_InvalidBegin: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidBitmapLine: number; - // (undocumented) - static Error_InvalidBitmapLine: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidCall: number; - // (undocumented) - static Error_InvalidCall: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidCharacter: number; - // (undocumented) - static Error_InvalidCharacter: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidCodeInKeyPartOfRule: number; - // (undocumented) - static Error_InvalidCodeInKeyPartOfRule: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidCopyright: number; - // (undocumented) - static Error_InvalidCopyright: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidDeadkey: number; - // (undocumented) - static Error_InvalidDeadkey: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidDisplayMapFile: number; - // (undocumented) - static Error_InvalidDisplayMapFile: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidEthnologueCode: number; - // (undocumented) - static Error_InvalidEthnologueCode: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidGroupLine: number; - // (undocumented) - static Error_InvalidGroupLine: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidIf: number; - // (undocumented) - static Error_InvalidIf: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidIndex: number; - // (undocumented) - static Error_InvalidIndex: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidInVirtualKeySection: number; - // (undocumented) - static Error_InvalidInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidKeyCode: number; - // (undocumented) - static Error_InvalidKeyCode: (o: { - keyId: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidKvkFile: number; - // (undocumented) - static Error_InvalidKvkFile: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidKvksFile: number; - // (undocumented) - static Error_InvalidKvksFile: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidLanguageLine: number; - // (undocumented) - static Error_InvalidLanguageLine: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidLanguageName: number; - // (undocumented) - static Error_InvalidLanguageName: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidLayoutLine: number; - // (undocumented) - static Error_InvalidLayoutLine: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidLineContinuation: number; - // (undocumented) - static Error_InvalidLineContinuation: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidMessage: number; - // (undocumented) - static Error_InvalidMessage: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidName: number; - // (undocumented) - static Error_InvalidName: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidNamedCode: number; - // (undocumented) - static Error_InvalidNamedCode: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidOuts: number; - // (undocumented) - static Error_InvalidOuts: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidReset: number; - // (undocumented) - static Error_InvalidReset: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidSave: number; - // (undocumented) - static Error_InvalidSave: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidSet: number; - // (undocumented) - static Error_InvalidSet: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidStoreLine: number; - // (undocumented) - static Error_InvalidStoreLine: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidSwitch: number; - // (undocumented) - static Error_InvalidSwitch: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidSystemStore: number; - // (undocumented) - static Error_InvalidSystemStore: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidTarget: number; - // (undocumented) - static Error_InvalidTarget: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidToken: number; - // (undocumented) - static Error_InvalidToken: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidTouchLayoutFile: number; - // (undocumented) - static Error_InvalidTouchLayoutFile: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidUse: number; - // (undocumented) - static Error_InvalidUse: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidValue: number; - // (undocumented) - static Error_InvalidValue: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidVersion: number; - // (undocumented) - static Error_InvalidVersion: () => CompilerEvent; - // (undocumented) - static ERROR_KeyboardVersionFormatInvalid: number; - // (undocumented) - static Error_KeyboardVersionFormatInvalid: () => CompilerEvent; - // (undocumented) - static ERROR_LayoutButNoLanguage: number; - // (undocumented) - static Error_LayoutButNoLanguage: () => CompilerEvent; - // (undocumented) - static ERROR_LineTooLong: number; - // (undocumented) - static Error_LineTooLong: () => CompilerEvent; - // (undocumented) - static ERROR_NewContextGroupMustBeReadonly: number; - // (undocumented) - static Error_NewContextGroupMustBeReadonly: () => CompilerEvent; - // (undocumented) - static ERROR_NonBMPCharactersNotSupportedInKeySection: number; - // (undocumented) - static Error_NonBMPCharactersNotSupportedInKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_NoTargetsSpecified: number; - // (undocumented) - static Error_NoTargetsSpecified: () => CompilerEvent; - // (undocumented) - static ERROR_NoTokensFound: number; - // (undocumented) - static Error_NoTokensFound: () => CompilerEvent; - // (undocumented) - static ERROR_NotSupportedInKeymanWebContext: number; - // (undocumented) - static Error_NotSupportedInKeymanWebContext: (o: { - line: number; - code: String; - }) => CompilerEvent; - // (undocumented) - static ERROR_NotSupportedInKeymanWebOutput: number; - // (undocumented) - static Error_NotSupportedInKeymanWebOutput: (o: { - line: number; - code: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_NotSupportedInKeymanWebStore: number; - // (undocumented) - static Error_NotSupportedInKeymanWebStore: (o: { - code: string; - store: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_NoVersionLine: number; - // (undocumented) - static Error_NoVersionLine: () => CompilerEvent; - // (undocumented) - static ERROR_OutputInReadonlyGroup: number; - // (undocumented) - static Error_OutputInReadonlyGroup: () => CompilerEvent; - // (undocumented) - static ERROR_OutsInVirtualKeySection: number; - // (undocumented) - static Error_OutsInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_OutsTooLong: number; - // (undocumented) - static Error_OutsTooLong: () => CompilerEvent; - // (undocumented) - static ERROR_PostKeystrokeGroupMustBeReadonly: number; - // (undocumented) - static Error_PostKeystrokeGroupMustBeReadonly: () => CompilerEvent; - // (undocumented) - static ERROR_RepeatedBegin: number; - // (undocumented) - static Error_RepeatedBegin: () => CompilerEvent; - // (undocumented) - static ERROR_ReservedCharacter: number; - // (undocumented) - static Error_ReservedCharacter: () => CompilerEvent; - // (undocumented) - static ERROR_SetSystemStore_NotFound: number; - // (undocumented) - static Error_SetSystemStore_NotFound: () => CompilerEvent; - // (undocumented) - static ERROR_StatementNotPermittedInReadonlyGroup: number; - // (undocumented) - static Error_StatementNotPermittedInReadonlyGroup: () => CompilerEvent; - // (undocumented) - static ERROR_StoreDoesNotExist: number; - // (undocumented) - static Error_StoreDoesNotExist: () => CompilerEvent; - // (undocumented) - static ERROR_StringInVirtualKeySection: number; - // (undocumented) - static Error_StringInVirtualKeySection: () => CompilerEvent; - // (undocumented) - static ERROR_TooManyIndexToKeyRefs: number; - // (undocumented) - static Error_TooManyIndexToKeyRefs: () => CompilerEvent; - // (undocumented) - static ERROR_TouchLayoutInvalidIdentifier: number; - // (undocumented) - static Error_TouchLayoutInvalidIdentifier: (o: { - keyId: string; - platformName: string; - layerId: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnicodeSetHasProperties: number; - // (undocumented) - static Error_UnicodeSetHasProperties: () => CompilerEvent; - // (undocumented) - static ERROR_UnicodeSetHasStrings: number; - // (undocumented) - static Error_UnicodeSetHasStrings: () => CompilerEvent; - // (undocumented) - static ERROR_UnicodeSetSyntaxError: number; - // (undocumented) - static Error_UnicodeSetSyntaxError: () => CompilerEvent; - // (undocumented) - static ERROR_UnterminatedString: number; - // (undocumented) - static Error_UnterminatedString: () => CompilerEvent; - // (undocumented) - static ERROR_VersionAlreadyIncluded: number; - // (undocumented) - static Error_VersionAlreadyIncluded: () => CompilerEvent; - // (undocumented) - static ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb: number; - // (undocumented) - static Error_VirtualCharacterKeysNotSupportedInKeymanWeb: (o: { - line: number; - }) => CompilerEvent; - // (undocumented) - static ERROR_VirtualKeyExpansionTooLong: number; - // (undocumented) - static Error_VirtualKeyExpansionTooLong: () => CompilerEvent; - // (undocumented) - static ERROR_VirtualKeyInContext: number; - // (undocumented) - static Error_VirtualKeyInContext: () => CompilerEvent; - // (undocumented) - static ERROR_VirtualKeyNotAllowedHere: number; - // (undocumented) - static Error_VirtualKeyNotAllowedHere: () => CompilerEvent; - // (undocumented) - static ERROR_VirtualKeysNotValidForMnemonicLayouts: number; - // (undocumented) - static Error_VirtualKeysNotValidForMnemonicLayouts: (o: { - line: number; - }) => CompilerEvent; - // (undocumented) - static ERROR_VKeyExpansionMustBeFollowedByVKey: number; - // (undocumented) - static Error_VKeyExpansionMustBeFollowedByVKey: () => CompilerEvent; - // (undocumented) - static ERROR_VKeyExpansionMustUseConsistentShift: number; - // (undocumented) - static Error_VKeyExpansionMustUseConsistentShift: () => CompilerEvent; - // (undocumented) - static ERROR_ZeroLengthString: number; - // (undocumented) - static Error_ZeroLengthString: () => CompilerEvent; - // (undocumented) - static FATAL_BadCallParams: number; - // (undocumented) - static Fatal_BadCallParams: () => CompilerEvent; - // (undocumented) - static FATAL_Break: number; - // (undocumented) - static Fatal_Break: () => CompilerEvent; - // (undocumented) - static FATAL_BufferOverflow: number; - // (undocumented) - static Fatal_BufferOverflow: () => CompilerEvent; - // (undocumented) - static FATAL_CallbacksNotSet: number; - // (undocumented) - static Fatal_CallbacksNotSet: () => CompilerEvent; - // (undocumented) - static FATAL_CannotAllocateMemory: number; - // (undocumented) - static Fatal_CannotAllocateMemory: () => CompilerEvent; - // (undocumented) - static FATAL_CannotCreateTempfile: number; - // (undocumented) - static Fatal_CannotCreateTempfile: () => CompilerEvent; - // (undocumented) - static FATAL_MissingWasmModule: number; - // (undocumented) - static Fatal_MissingWasmModule: (o: { - e?: any; - }) => CompilerEvent; - // (undocumented) - static FATAL_SomewhereIGotItWrong: number; - // (undocumented) - static Fatal_SomewhereIGotItWrong: () => CompilerEvent; - // (undocumented) - static FATAL_UnableToWriteFully: number; - // (undocumented) - static Fatal_UnableToWriteFully: () => CompilerEvent; - // (undocumented) - static FATAL_UnexpectedException: number; - // (undocumented) - static Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static FATAL_UnicodeSetOutOfRange: number; - // (undocumented) - static Fatal_UnicodeSetOutOfRange: () => CompilerEvent; - // (undocumented) - static HINT_IndexStoreLong: number; - // (undocumented) - static Hint_IndexStoreLong: () => CompilerEvent; - // (undocumented) - static HINT_NonUnicodeFile: number; - // (undocumented) - static Hint_NonUnicodeFile: () => CompilerEvent; - // (undocumented) - static HINT_UnreachableKeyCode: number; - // (undocumented) - static Hint_UnreachableKeyCode: (o: { - line: number; - key: string; - }) => CompilerEvent; - // (undocumented) - static HINT_UnreachableRule: number; - // (undocumented) - static Hint_UnreachableRule: () => CompilerEvent; - // (undocumented) - static INFO_EndOfFile: number; - // (undocumented) - static Info_EndOfFile: () => CompilerEvent; - // (undocumented) - static INFO_Info: number; - // (undocumented) - static Info_Info: () => CompilerEvent; - // (undocumented) - static WARN_ANSIInUnicodeGroup: number; - // (undocumented) - static Warn_ANSIInUnicodeGroup: () => CompilerEvent; - // (undocumented) - static WARN_BitmapNotUsed: number; - // (undocumented) - static Warn_BitmapNotUsed: () => CompilerEvent; - // (undocumented) - static WARN_CustomLanguagesNotSupported: number; - // (undocumented) - static Warn_CustomLanguagesNotSupported: () => CompilerEvent; - // (undocumented) - static WARN_DontMixChiralAndNonChiralModifiers: number; - // (undocumented) - static Warn_DontMixChiralAndNonChiralModifiers: () => CompilerEvent; - // (undocumented) - static WARN_EmbedJsFileMissing: number; - // (undocumented) - static Warn_EmbedJsFileMissing: (o: { - line: number; - jsFilename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb: number; - // (undocumented) - static Warn_ExtendedShiftFlagsNotSupportedInKeymanWeb: (o: { - line: number; - flags: string; - }) => CompilerEvent; - // (undocumented) - static WARN_HeaderStatementIsDeprecated: number; - // (undocumented) - static Warn_HeaderStatementIsDeprecated: () => CompilerEvent; - // (undocumented) - static WARN_HelpFileMissing: number; - // (undocumented) - static Warn_HelpFileMissing: (o: { - line: number; - helpFilename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static WARN_HotkeyHasInvalidModifier: number; - // (undocumented) - static Warn_HotkeyHasInvalidModifier: () => CompilerEvent; - // (undocumented) - static WARN_IfShouldBeAtStartOfContext: number; - // (undocumented) - static Warn_IfShouldBeAtStartOfContext: () => CompilerEvent; - // (undocumented) - static WARN_IndexStoreShort: number; - // (undocumented) - static Warn_IndexStoreShort: () => CompilerEvent; - // (undocumented) - static WARN_InvalidVkeyInKvksFile: number; - // (undocumented) - static Warn_InvalidVkeyInKvksFile: (o: { - filename: string; - invalidVkey: string; - }) => CompilerEvent; - // (undocumented) - static WARN_KeyBadLength: number; - // (undocumented) - static Warn_KeyBadLength: () => CompilerEvent; - // (undocumented) - static WARN_KeyShouldIncludeNCaps: number; - // (undocumented) - static Warn_KeyShouldIncludeNCaps: () => CompilerEvent; - // (undocumented) - static WARN_KVKFileIsInSourceFormat: number; - // (undocumented) - static Warn_KVKFileIsInSourceFormat: () => CompilerEvent; - // (undocumented) - static WARN_LanguageHeadersDeprecatedInKeyman10: number; - // (undocumented) - static Warn_LanguageHeadersDeprecatedInKeyman10: () => CompilerEvent; - // (undocumented) - static WARN_MixingLeftAndRightModifiers: number; - // (undocumented) - static Warn_MixingLeftAndRightModifiers: () => CompilerEvent; - // (undocumented) - static WARN_NulNotFirstStatementInContext: number; - // (undocumented) - static Warn_NulNotFirstStatementInContext: () => CompilerEvent; - // (undocumented) - static WARN_OldVersion: number; - // (undocumented) - static Warn_OldVersion: () => CompilerEvent; - // (undocumented) - static WARN_OptionStoreNameInvalid: number; - // (undocumented) - static Warn_OptionStoreNameInvalid: (o: { - name: string; - }) => CompilerEvent; - // (undocumented) - static WARN_PlatformNotInTargets: number; - // (undocumented) - static Warn_PlatformNotInTargets: () => CompilerEvent; - // (undocumented) - static WARN_PunctuationInEthnologueCode: number; - // (undocumented) - static Warn_PunctuationInEthnologueCode: () => CompilerEvent; - // (undocumented) - static WARN_ReservedCharacter: number; - // (undocumented) - static Warn_ReservedCharacter: () => CompilerEvent; - // (undocumented) - static WARN_StoreAlreadyUsedAsOptionOrCall: number; - // (undocumented) - static Warn_StoreAlreadyUsedAsOptionOrCall: () => CompilerEvent; - // (undocumented) - static WARN_StoreAlreadyUsedAsStoreOrCall: number; - // (undocumented) - static Warn_StoreAlreadyUsedAsStoreOrCall: () => CompilerEvent; - // (undocumented) - static WARN_StoreAlreadyUsedAsStoreOrOption: number; - // (undocumented) - static Warn_StoreAlreadyUsedAsStoreOrOption: () => CompilerEvent; - // (undocumented) - static WARN_TooManyWarnings: number; - // (undocumented) - static Warn_TooManyWarnings: () => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutCustomKeyNotDefined: number; - // (undocumented) - static Warn_TouchLayoutCustomKeyNotDefined: (o: { - keyId: string; - platformName: string; - layerId: string; - }) => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutFontShouldBeSameForAllPlatforms: number; - // (undocumented) - static Warn_TouchLayoutFontShouldBeSameForAllPlatforms: () => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutMissingLayer: number; - // (undocumented) - static Warn_TouchLayoutMissingLayer: (o: { - keyId: string; - platformName: string; - layerId: string; - nextLayer: string; - }) => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutMissingRequiredKeys: number; - // (undocumented) - static Warn_TouchLayoutMissingRequiredKeys: (o: { - layerId: string; - platformName: string; - missingKeys: string; - }) => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutSpecialLabelOnNormalKey: number; - // (undocumented) - static Warn_TouchLayoutSpecialLabelOnNormalKey: (o: { - keyId: string; - platformName: string; - layerId: string; - label: string; - }) => CompilerEvent; - // (undocumented) - static WARN_TouchLayoutUnidentifiedKey: number; - // (undocumented) - static Warn_TouchLayoutUnidentifiedKey: (o: { - layerId: string; - }) => CompilerEvent; - // (undocumented) - static WARN_UnicodeInANSIGroup: number; - // (undocumented) - static Warn_UnicodeInANSIGroup: () => CompilerEvent; - // (undocumented) - static WARN_UnicodeSurrogateUsed: number; - // (undocumented) - static Warn_UnicodeSurrogateUsed: () => CompilerEvent; - // (undocumented) - static WARN_UseNotLastStatementInRule: number; - // (undocumented) - static Warn_UseNotLastStatementInRule: () => CompilerEvent; - // (undocumented) - static WARN_VirtualCharKeyWithPositionalLayout: number; - // (undocumented) - static Warn_VirtualCharKeyWithPositionalLayout: () => CompilerEvent; - // (undocumented) - static WARN_VirtualKeyInOutput: number; - // (undocumented) - static Warn_VirtualKeyInOutput: () => CompilerEvent; - // (undocumented) - static WARN_VirtualKeyWithMnemonicLayout: number; - // (undocumented) - static Warn_VirtualKeyWithMnemonicLayout: () => CompilerEvent; -} - -// @public -export interface KmnCompilerOptions extends CompilerOptions { -} - -// @public -export interface KmnCompilerResult extends KeymanCompilerResult { - artifacts: KmnCompilerArtifacts; - displayMap?: Osk.PuaMap; - // Warning: (ae-incompatible-release-tags) The symbol "extra" is marked as @public, but its signature references "KmnCompilerResultExtra" which is marked as @internal - extra: KmnCompilerResultExtra; -} - -// Warning: (ae-internal-missing-underscore) The name "KmnCompilerResultExtra" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal -export interface KmnCompilerResultExtra { - // (undocumented) - displayMapFilename?: string; - // (undocumented) - groups: CompilerResultExtraGroup[]; - // (undocumented) - kvksFilename?: string; - // (undocumented) - stores: CompilerResultExtraStore[]; - targets: number; -} - -// Warning: (ae-internal-missing-underscore) The name "KmwCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal -export class KmwCompilerMessages extends KmnCompilerMessages { - // (undocumented) - static ERROR_InvalidTouchLayoutFileFormat: number; - // (undocumented) - static Error_InvalidTouchLayoutFileFormat: (o: { - msg: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_TouchLayoutFileDoesNotExist: number; - // (undocumented) - static Error_TouchLayoutFileDoesNotExist: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_TouchLayoutIdentifierRequires15: number; - // (undocumented) - static Error_TouchLayoutIdentifierRequires15: (o: { - keyId: string; - platformName: string; - layerId: string; - }) => CompilerEvent; - // (undocumented) - static HINT_TouchLayoutUsesUnsupportedGesturesDownlevel: number; - // (undocumented) - static Hint_TouchLayoutUsesUnsupportedGesturesDownlevel: (o: { - keyId: string; - }) => CompilerEvent; - // (undocumented) - static INFO_MinimumEngineVersion: number; - // (undocumented) - static Info_MinimumEngineVersion: (o: { - version: string; - }) => CompilerEvent; -} - -declare namespace Osk { - export { - parseMapping, - remapVisualKeyboard, - remapTouchLayout, - StringRefUsage, - StringRef, - StringResult, - PuaMap - } -} -export { Osk } - -// @public (undocumented) -function parseMapping(mapping: any): PuaMap; - -// @public (undocumented) -type PuaMap = { - [index: string]: string; -}; - -// @public (undocumented) -function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMap): boolean; - -// @public (undocumented) -function remapVisualKeyboard(vk: VisualKeyboard.VisualKeyboard, map: PuaMap): boolean; - -// @public (undocumented) -interface StringRef { - // (undocumented) - str: string; - // (undocumented) - usages: StringRefUsage[]; -} - -// @public (undocumented) -interface StringRefUsage { - // (undocumented) - count: number; - // (undocumented) - filename: string; -} - -// @public -interface StringResult { - pua: string; - str: string; - unicode: string; - usages: StringRefUsage[] | string[]; -} - -``` +## API Report File for "@keymanapp/kmc-kmn" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifactOptional } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { LdmlKeyboardTypes } from '@keymanapp/common-types'; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; +import { TouchLayout } from '@keymanapp/common-types'; +import { VisualKeyboard } from '@keymanapp/common-types'; + +// Warning: (ae-internal-missing-underscore) The name "CompilerResultExtraGroup" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export interface CompilerResultExtraGroup { + // (undocumented) + isReadOnly: boolean; + // (undocumented) + name: string; +} + +// Warning: (ae-internal-missing-underscore) The name "CompilerResultExtraStore" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export interface CompilerResultExtraStore { + // (undocumented) + line: number; + // (undocumented) + name: string; + // (undocumented) + storeType: number; +} + +// @public +export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSetParser { + // @internal + static fixNewPattern(pattern: string): string; + init(callbacks: CompilerCallbacks, options: KmnCompilerOptions): Promise; + // @internal (undocumented) + parseUnicodeSet(pattern: string, rangeCount: number, compileContext?: any): LdmlKeyboardTypes.UnicodeSet | null; + run(infile: string, outfile: string): Promise; + // @internal (undocumented) + sizeUnicodeSet(pattern: string, compileContext?: any): number; + // @internal + testSentry(): any; + verifyInitialized(): boolean; + write(artifacts: KmnCompilerArtifacts): Promise; +} + +// @public +export interface KmnCompilerArtifacts extends KeymanCompilerArtifacts { + js?: KeymanCompilerArtifactOptional; + kmx?: KeymanCompilerArtifactOptional; + kvk?: KeymanCompilerArtifactOptional; +} + +// Warning: (ae-internal-missing-underscore) The name "KmnCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export class KmnCompilerMessages { + // (undocumented) + static ERROR_140FeatureOnlyContextAndNotAnyWeb: number; + // (undocumented) + static Error_140FeatureOnlyContextAndNotAnyWeb: () => CompilerEvent; + // (undocumented) + static ERROR_501FeatureOnly_Call: number; + // (undocumented) + static Error_501FeatureOnly_Call: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_Contextn: number; + // (undocumented) + static Error_60FeatureOnly_Contextn: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_EthnologueCode: number; + // (undocumented) + static Error_60FeatureOnly_EthnologueCode: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_MnemonicLayout: number; + // (undocumented) + static Error_60FeatureOnly_MnemonicLayout: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_NamedCodes: number; + // (undocumented) + static Error_60FeatureOnly_NamedCodes: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_OldCharPosMatching: number; + // (undocumented) + static Error_60FeatureOnly_OldCharPosMatching: () => CompilerEvent; + // (undocumented) + static ERROR_60FeatureOnly_VirtualCharKey: number; + // (undocumented) + static Error_60FeatureOnly_VirtualCharKey: () => CompilerEvent; + // (undocumented) + static ERROR_70FeatureOnly: number; + // (undocumented) + static Error_70FeatureOnly: () => CompilerEvent; + // (undocumented) + static ERROR_80FeatureOnly: number; + // (undocumented) + static Error_80FeatureOnly: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnly_IfSystemStores: number; + // (undocumented) + static Error_90FeatureOnly_IfSystemStores: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnly_SetSystemStores: number; + // (undocumented) + static Error_90FeatureOnly_SetSystemStores: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnlyEmbedCSS: number; + // (undocumented) + static Error_90FeatureOnlyEmbedCSS: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnlyKeyboardVersion: number; + // (undocumented) + static Error_90FeatureOnlyKeyboardVersion: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnlyLayoutFile: number; + // (undocumented) + static Error_90FeatureOnlyLayoutFile: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnlyTargets: number; + // (undocumented) + static Error_90FeatureOnlyTargets: () => CompilerEvent; + // (undocumented) + static ERROR_90FeatureOnlyVirtualKeyDictionary: number; + // (undocumented) + static Error_90FeatureOnlyVirtualKeyDictionary: () => CompilerEvent; + // (undocumented) + static ERROR_CannotLoadIncludeFile: number; + // (undocumented) + static Error_CannotLoadIncludeFile: () => CompilerEvent; + // (undocumented) + static ERROR_CannotReadBitmapFile: number; + // (undocumented) + static Error_CannotReadBitmapFile: () => CompilerEvent; + // (undocumented) + static ERROR_CannotReadInfile: number; + // (undocumented) + static Error_CannotReadInfile: () => CompilerEvent; + // (undocumented) + static ERROR_CannotUseReadWriteGroupFromReadonlyGroup: number; + // (undocumented) + static Error_CannotUseReadWriteGroupFromReadonlyGroup: () => CompilerEvent; + // (undocumented) + static ERROR_CasedKeysMustContainOnlyVirtualKeys: number; + // (undocumented) + static Error_CasedKeysMustContainOnlyVirtualKeys: () => CompilerEvent; + // (undocumented) + static ERROR_CasedKeysMustNotIncludeShiftStates: number; + // (undocumented) + static Error_CasedKeysMustNotIncludeShiftStates: () => CompilerEvent; + // (undocumented) + static ERROR_CasedKeysNotSupportedWithMnemonicLayout: number; + // (undocumented) + static Error_CasedKeysNotSupportedWithMnemonicLayout: () => CompilerEvent; + // (undocumented) + static ERROR_CharacterExpansionMustBeFollowedByCharacter: number; + // (undocumented) + static Error_CharacterExpansionMustBeFollowedByCharacter: () => CompilerEvent; + // (undocumented) + static ERROR_CharacterRangeTooLong: number; + // (undocumented) + static Error_CharacterRangeTooLong: () => CompilerEvent; + // (undocumented) + static ERROR_CodeInvalidInKeyStore: number; + // (undocumented) + static Error_CodeInvalidInKeyStore: () => CompilerEvent; + // (undocumented) + static ERROR_CodeInvalidInThisSection: number; + // (undocumented) + static Error_CodeInvalidInThisSection: () => CompilerEvent; + // (undocumented) + static ERROR_ContextAndIndexInvalidInMatchNomatch: number; + // (undocumented) + static Error_ContextAndIndexInvalidInMatchNomatch: () => CompilerEvent; + // (undocumented) + static ERROR_ContextExCannotReferenceIf: number; + // (undocumented) + static Error_ContextExCannotReferenceIf: () => CompilerEvent; + // (undocumented) + static ERROR_ContextExCannotReferenceNul: number; + // (undocumented) + static Error_ContextExCannotReferenceNul: () => CompilerEvent; + // (undocumented) + static ERROR_ContextExHasInvalidOffset: number; + // (undocumented) + static Error_ContextExHasInvalidOffset: () => CompilerEvent; + // (undocumented) + static ERROR_DuplicateGroup: number; + // Warning: (ae-forgotten-export) The symbol "KmcmpLibMessageParameters" needs to be exported by the entry point main.d.ts + // + // (undocumented) + static Error_DuplicateGroup: (o: KmcmpLibMessageParameters) => CompilerEvent; + // (undocumented) + static ERROR_DuplicateStore: number; + // (undocumented) + static Error_DuplicateStore: (o: KmcmpLibMessageParameters) => CompilerEvent; + // (undocumented) + static ERROR_ExpansionMustBePositive: number; + // (undocumented) + static Error_ExpansionMustBePositive: () => CompilerEvent; + // (undocumented) + static ERROR_ExpansionMustFollowCharacterOrVKey: number; + // (undocumented) + static Error_ExpansionMustFollowCharacterOrVKey: () => CompilerEvent; + // (undocumented) + static ERROR_ExtendedStringTooLong: number; + // (undocumented) + static Error_ExtendedStringTooLong: () => CompilerEvent; + // (undocumented) + static ERROR_FileNotFound: number; + // (undocumented) + static Error_FileNotFound: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_GroupDoesNotExist: number; + // (undocumented) + static Error_GroupDoesNotExist: () => CompilerEvent; + // (undocumented) + static ERROR_IfSystemStore_NotFound: number; + // (undocumented) + static Error_IfSystemStore_NotFound: () => CompilerEvent; + // (undocumented) + static ERROR_IndexDoesNotPointToAny: number; + // (undocumented) + static Error_IndexDoesNotPointToAny: () => CompilerEvent; + // (undocumented) + static ERROR_InfileNotExist: number; + // (undocumented) + static Error_InfileNotExist: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidAny: number; + // (undocumented) + static Error_InvalidAny: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidBegin: number; + // (undocumented) + static Error_InvalidBegin: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidBitmapLine: number; + // (undocumented) + static Error_InvalidBitmapLine: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidCall: number; + // (undocumented) + static Error_InvalidCall: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidCharacter: number; + // (undocumented) + static Error_InvalidCharacter: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidCodeInKeyPartOfRule: number; + // (undocumented) + static Error_InvalidCodeInKeyPartOfRule: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidCopyright: number; + // (undocumented) + static Error_InvalidCopyright: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidDeadkey: number; + // (undocumented) + static Error_InvalidDeadkey: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidDisplayMapFile: number; + // (undocumented) + static Error_InvalidDisplayMapFile: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidEthnologueCode: number; + // (undocumented) + static Error_InvalidEthnologueCode: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidGroupLine: number; + // (undocumented) + static Error_InvalidGroupLine: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidIf: number; + // (undocumented) + static Error_InvalidIf: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidIndex: number; + // (undocumented) + static Error_InvalidIndex: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidKeyCode: number; + // (undocumented) + static Error_InvalidKeyCode: (o: { + keyId: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidKvkFile: number; + // (undocumented) + static Error_InvalidKvkFile: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidKvksFile: number; + // (undocumented) + static Error_InvalidKvksFile: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLanguageLine: number; + // (undocumented) + static Error_InvalidLanguageLine: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidLanguageName: number; + // (undocumented) + static Error_InvalidLanguageName: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidLayoutLine: number; + // (undocumented) + static Error_InvalidLayoutLine: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidLineContinuation: number; + // (undocumented) + static Error_InvalidLineContinuation: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidMessage: number; + // (undocumented) + static Error_InvalidMessage: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidName: number; + // (undocumented) + static Error_InvalidName: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidNamedCode: number; + // (undocumented) + static Error_InvalidNamedCode: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidOuts: number; + // (undocumented) + static Error_InvalidOuts: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidReset: number; + // (undocumented) + static Error_InvalidReset: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidSave: number; + // (undocumented) + static Error_InvalidSave: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidSet: number; + // (undocumented) + static Error_InvalidSet: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidStoreLine: number; + // (undocumented) + static Error_InvalidStoreLine: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidSwitch: number; + // (undocumented) + static Error_InvalidSwitch: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidSystemStore: number; + // (undocumented) + static Error_InvalidSystemStore: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidTarget: number; + // (undocumented) + static Error_InvalidTarget: (o: KmcmpLibMessageParameters) => CompilerEvent; + // (undocumented) + static ERROR_InvalidToken: number; + // (undocumented) + static Error_InvalidToken: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidTouchLayoutFile: number; + // (undocumented) + static Error_InvalidTouchLayoutFile: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidUse: number; + // (undocumented) + static Error_InvalidUse: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidValue: number; + // (undocumented) + static Error_InvalidValue: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidVersion: number; + // (undocumented) + static Error_InvalidVersion: () => CompilerEvent; + // (undocumented) + static ERROR_KeyboardVersionFormatInvalid: number; + // (undocumented) + static Error_KeyboardVersionFormatInvalid: () => CompilerEvent; + // (undocumented) + static ERROR_LayoutButNoLanguage: number; + // (undocumented) + static Error_LayoutButNoLanguage: () => CompilerEvent; + // (undocumented) + static ERROR_LineTooLong: number; + // (undocumented) + static Error_LineTooLong: () => CompilerEvent; + // (undocumented) + static ERROR_NameContainsInvalidCharacter: number; + // (undocumented) + static Error_NameContainsInvalidCharacter: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NameMustBeAtLeastOneCharLong: number; + // (undocumented) + static Error_NameMustBeAtLeastOneCharLong: () => CompilerEvent; + // (undocumented) + static ERROR_NameMustBeAtMostNCharsLong: number; + // (undocumented) + static Error_NameMustBeAtMostNCharsLong: (o: { + name: string; + length: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NameMustNotContainComma: number; + // (undocumented) + static Error_NameMustNotContainComma: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NameMustNotContainParentheses: number; + // (undocumented) + static Error_NameMustNotContainParentheses: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NameMustNotContainSpaces: number; + // (undocumented) + static Error_NameMustNotContainSpaces: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NameMustNotContainSquareBrackets: number; + // (undocumented) + static Error_NameMustNotContainSquareBrackets: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NewContextGroupMustBeReadonly: number; + // (undocumented) + static Error_NewContextGroupMustBeReadonly: () => CompilerEvent; + // (undocumented) + static ERROR_NonBMPCharactersNotSupportedInKeySection: number; + // (undocumented) + static Error_NonBMPCharactersNotSupportedInKeySection: () => CompilerEvent; + // (undocumented) + static ERROR_NoTargetsSpecified: number; + // (undocumented) + static Error_NoTargetsSpecified: () => CompilerEvent; + // (undocumented) + static ERROR_NoTokensFound: number; + // (undocumented) + static Error_NoTokensFound: () => CompilerEvent; + // (undocumented) + static ERROR_NotSupportedInKeymanWebContext: number; + // (undocumented) + static Error_NotSupportedInKeymanWebContext: (o: { + line: number; + code: String; + }) => CompilerEvent; + // (undocumented) + static ERROR_NotSupportedInKeymanWebOutput: number; + // (undocumented) + static Error_NotSupportedInKeymanWebOutput: (o: { + line: number; + code: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NotSupportedInKeymanWebStore: number; + // (undocumented) + static Error_NotSupportedInKeymanWebStore: (o: { + code: string; + store: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NoVersionLine: number; + // (undocumented) + static Error_NoVersionLine: () => CompilerEvent; + // (undocumented) + static ERROR_OutputInReadonlyGroup: number; + // (undocumented) + static Error_OutputInReadonlyGroup: () => CompilerEvent; + // (undocumented) + static ERROR_OutsTooLong: number; + // (undocumented) + static Error_OutsTooLong: () => CompilerEvent; + // (undocumented) + static ERROR_PostKeystrokeGroupMustBeReadonly: number; + // (undocumented) + static Error_PostKeystrokeGroupMustBeReadonly: () => CompilerEvent; + // (undocumented) + static ERROR_RepeatedBegin: number; + // (undocumented) + static Error_RepeatedBegin: () => CompilerEvent; + // (undocumented) + static ERROR_ReservedCharacter: number; + // (undocumented) + static Error_ReservedCharacter: () => CompilerEvent; + // (undocumented) + static ERROR_SetSystemStore_NotFound: number; + // (undocumented) + static Error_SetSystemStore_NotFound: () => CompilerEvent; + // (undocumented) + static ERROR_StatementNotPermittedInReadonlyGroup: number; + // (undocumented) + static Error_StatementNotPermittedInReadonlyGroup: () => CompilerEvent; + // (undocumented) + static ERROR_StoreDoesNotExist: number; + // (undocumented) + static Error_StoreDoesNotExist: () => CompilerEvent; + // (undocumented) + static ERROR_TextBeforeOrAfterNulInOutput: number; + // (undocumented) + static Error_TextBeforeOrAfterNulInOutput: () => CompilerEvent; + // (undocumented) + static ERROR_TooManyIndexToKeyRefs: number; + // (undocumented) + static Error_TooManyIndexToKeyRefs: () => CompilerEvent; + // (undocumented) + static ERROR_TouchLayoutInvalidIdentifier: number; + // (undocumented) + static Error_TouchLayoutInvalidIdentifier: (o: { + keyId: string; + platformName: string; + layerId: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static ERROR_UnicodeSetHasProperties: number; + // (undocumented) + static Error_UnicodeSetHasProperties: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_UnicodeSetHasStrings: number; + // (undocumented) + static Error_UnicodeSetHasStrings: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_UnicodeSetSyntaxError: number; + // (undocumented) + static Error_UnicodeSetSyntaxError: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_UnterminatedString: number; + // (undocumented) + static Error_UnterminatedString: () => CompilerEvent; + // (undocumented) + static ERROR_VersionAlreadyIncluded: number; + // (undocumented) + static Error_VersionAlreadyIncluded: () => CompilerEvent; + // (undocumented) + static ERROR_VirtualCharacterKeysNotSupportedInKeymanWeb: number; + // (undocumented) + static Error_VirtualCharacterKeysNotSupportedInKeymanWeb: (o: { + line: number; + }) => CompilerEvent; + // (undocumented) + static ERROR_VirtualKeyExpansionTooLong: number; + // (undocumented) + static Error_VirtualKeyExpansionTooLong: () => CompilerEvent; + // (undocumented) + static ERROR_VirtualKeyInContext: number; + // (undocumented) + static Error_VirtualKeyInContext: () => CompilerEvent; + // (undocumented) + static ERROR_VirtualKeyNotAllowedHere: number; + // (undocumented) + static Error_VirtualKeyNotAllowedHere: () => CompilerEvent; + // (undocumented) + static ERROR_VirtualKeysNotValidForMnemonicLayouts: number; + // (undocumented) + static Error_VirtualKeysNotValidForMnemonicLayouts: (o: { + line: number; + }) => CompilerEvent; + // (undocumented) + static ERROR_VKeyExpansionMustBeFollowedByVKey: number; + // (undocumented) + static Error_VKeyExpansionMustBeFollowedByVKey: () => CompilerEvent; + // (undocumented) + static ERROR_VKeyExpansionMustUseConsistentShift: number; + // (undocumented) + static Error_VKeyExpansionMustUseConsistentShift: () => CompilerEvent; + // (undocumented) + static ERROR_ZeroLengthString: number; + // (undocumented) + static Error_ZeroLengthString: () => CompilerEvent; + // (undocumented) + static FATAL_BadCallParams: number; + // (undocumented) + static Fatal_BadCallParams: () => CompilerEvent; + // (undocumented) + static FATAL_BufferOverflow: number; + // (undocumented) + static Fatal_BufferOverflow: () => CompilerEvent; + // (undocumented) + static FATAL_CallbacksNotSet: number; + // (undocumented) + static Fatal_CallbacksNotSet: () => CompilerEvent; + // (undocumented) + static FATAL_CannotAllocateMemory: number; + // (undocumented) + static Fatal_CannotAllocateMemory: () => CompilerEvent; + // (undocumented) + static FATAL_CannotCreateTempfile: number; + // (undocumented) + static Fatal_CannotCreateTempfile: () => CompilerEvent; + // (undocumented) + static FATAL_MissingWasmModule: number; + // (undocumented) + static Fatal_MissingWasmModule: (o: { + e?: any; + }) => CompilerEvent; + // (undocumented) + static FATAL_SomewhereIGotItWrong: number; + // (undocumented) + static Fatal_SomewhereIGotItWrong: () => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static FATAL_UnicodeSetOutOfRange: number; + // (undocumented) + static Fatal_UnicodeSetOutOfRange: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_IndexStoreLong: number; + // (undocumented) + static Hint_IndexStoreLong: () => CompilerEvent; + // (undocumented) + static HINT_NonUnicodeFile: number; + // (undocumented) + static Hint_NonUnicodeFile: () => CompilerEvent; + // (undocumented) + static HINT_UnreachableKeyCode: number; + // (undocumented) + static Hint_UnreachableKeyCode: (o: { + line: number; + key: string; + }) => CompilerEvent; + // (undocumented) + static HINT_UnreachableRule: number; + // (undocumented) + static Hint_UnreachableRule: (o: KmcmpLibMessageParameters) => CompilerEvent; + // (undocumented) + static INFO_MinimumCoreEngineVersion: number; + // (undocumented) + static Info_MinimumCoreEngineVersion: (o: KmcmpLibMessageParameters) => CompilerEvent; + // (undocumented) + static WARN_ANSIInUnicodeGroup: number; + // (undocumented) + static Warn_ANSIInUnicodeGroup: () => CompilerEvent; + // (undocumented) + static WARN_BitmapNotUsed: number; + // (undocumented) + static Warn_BitmapNotUsed: () => CompilerEvent; + // (undocumented) + static WARN_CustomLanguagesNotSupported: number; + // (undocumented) + static Warn_CustomLanguagesNotSupported: () => CompilerEvent; + // (undocumented) + static WARN_DontMixChiralAndNonChiralModifiers: number; + // (undocumented) + static Warn_DontMixChiralAndNonChiralModifiers: () => CompilerEvent; + // (undocumented) + static WARN_EmbedJsFileMissing: number; + // (undocumented) + static Warn_EmbedJsFileMissing: (o: { + line: number; + jsFilename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb: number; + // (undocumented) + static Warn_ExtendedShiftFlagsNotSupportedInKeymanWeb: (o: { + line: number; + flags: string; + }) => CompilerEvent; + // (undocumented) + static WARN_HeaderStatementIsDeprecated: number; + // (undocumented) + static Warn_HeaderStatementIsDeprecated: () => CompilerEvent; + // (undocumented) + static WARN_HelpFileMissing: number; + // (undocumented) + static Warn_HelpFileMissing: (o: { + line: number; + helpFilename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static WARN_HotkeyHasInvalidModifier: number; + // (undocumented) + static Warn_HotkeyHasInvalidModifier: () => CompilerEvent; + // (undocumented) + static WARN_IfShouldBeAtStartOfContext: number; + // (undocumented) + static Warn_IfShouldBeAtStartOfContext: () => CompilerEvent; + // (undocumented) + static WARN_IndexStoreShort: number; + // (undocumented) + static Warn_IndexStoreShort: () => CompilerEvent; + // (undocumented) + static WARN_InvalidVkeyInKvksFile: number; + // (undocumented) + static Warn_InvalidVkeyInKvksFile: (o: { + filename: string; + invalidVkey: string; + }) => CompilerEvent; + // (undocumented) + static WARN_KeyBadLength: number; + // (undocumented) + static Warn_KeyBadLength: () => CompilerEvent; + // (undocumented) + static WARN_KeyShouldIncludeNCaps: number; + // (undocumented) + static Warn_KeyShouldIncludeNCaps: () => CompilerEvent; + // (undocumented) + static WARN_KVKFileIsInSourceFormat: number; + // (undocumented) + static Warn_KVKFileIsInSourceFormat: () => CompilerEvent; + // (undocumented) + static WARN_LanguageHeadersDeprecatedInKeyman10: number; + // (undocumented) + static Warn_LanguageHeadersDeprecatedInKeyman10: () => CompilerEvent; + // (undocumented) + static WARN_MixingLeftAndRightModifiers: number; + // (undocumented) + static Warn_MixingLeftAndRightModifiers: () => CompilerEvent; + // (undocumented) + static WARN_NulNotFirstStatementInContext: number; + // (undocumented) + static Warn_NulNotFirstStatementInContext: () => CompilerEvent; + // (undocumented) + static WARN_OldVersion: number; + // (undocumented) + static Warn_OldVersion: () => CompilerEvent; + // (undocumented) + static WARN_OptionStoreNameInvalid: number; + // (undocumented) + static Warn_OptionStoreNameInvalid: (o: { + name: string; + }) => CompilerEvent; + // (undocumented) + static WARN_PlatformNotInTargets: number; + // (undocumented) + static Warn_PlatformNotInTargets: () => CompilerEvent; + // (undocumented) + static WARN_PunctuationInEthnologueCode: number; + // (undocumented) + static Warn_PunctuationInEthnologueCode: () => CompilerEvent; + // (undocumented) + static WARN_ReservedCharacter: number; + // (undocumented) + static Warn_ReservedCharacter: () => CompilerEvent; + // (undocumented) + static WARN_StoreAlreadyUsedAsOptionOrCall: number; + // (undocumented) + static Warn_StoreAlreadyUsedAsOptionOrCall: () => CompilerEvent; + // (undocumented) + static WARN_StoreAlreadyUsedAsStoreOrCall: number; + // (undocumented) + static Warn_StoreAlreadyUsedAsStoreOrCall: () => CompilerEvent; + // (undocumented) + static WARN_StoreAlreadyUsedAsStoreOrOption: number; + // (undocumented) + static Warn_StoreAlreadyUsedAsStoreOrOption: () => CompilerEvent; + // (undocumented) + static WARN_TooManyWarnings: number; + // (undocumented) + static Warn_TooManyWarnings: () => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutCustomKeyNotDefined: number; + // (undocumented) + static Warn_TouchLayoutCustomKeyNotDefined: (o: { + keyId: string; + platformName: string; + layerId: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutFontShouldBeSameForAllPlatforms: number; + // (undocumented) + static Warn_TouchLayoutFontShouldBeSameForAllPlatforms: () => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutMissingLayer: number; + // (undocumented) + static Warn_TouchLayoutMissingLayer: (o: { + keyId: string; + platformName: string; + layerId: string; + nextLayer: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutMissingRequiredKeys: number; + // (undocumented) + static Warn_TouchLayoutMissingRequiredKeys: (o: { + layerId: string; + platformName: string; + missingKeys: string; + }) => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutSpecialLabelOnNormalKey: number; + // (undocumented) + static Warn_TouchLayoutSpecialLabelOnNormalKey: (o: { + keyId: string; + platformName: string; + layerId: string; + label: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static WARN_TouchLayoutUnidentifiedKey: number; + // (undocumented) + static Warn_TouchLayoutUnidentifiedKey: (o: { + layerId: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static WARN_UnicodeInANSIGroup: number; + // (undocumented) + static Warn_UnicodeInANSIGroup: () => CompilerEvent; + // (undocumented) + static WARN_UnicodeSurrogateUsed: number; + // (undocumented) + static Warn_UnicodeSurrogateUsed: () => CompilerEvent; + // (undocumented) + static WARN_UseNotLastStatementInRule: number; + // (undocumented) + static Warn_UseNotLastStatementInRule: () => CompilerEvent; + // (undocumented) + static WARN_VirtualCharKeyWithPositionalLayout: number; + // (undocumented) + static Warn_VirtualCharKeyWithPositionalLayout: () => CompilerEvent; + // (undocumented) + static WARN_VirtualKeyInOutput: number; + // (undocumented) + static Warn_VirtualKeyInOutput: () => CompilerEvent; + // (undocumented) + static WARN_VirtualKeyWithMnemonicLayout: number; + // (undocumented) + static Warn_VirtualKeyWithMnemonicLayout: () => CompilerEvent; +} + +// @public +export interface KmnCompilerOptions extends CompilerOptions { +} + +// @public +export interface KmnCompilerResult extends KeymanCompilerResult { + artifacts: KmnCompilerArtifacts; + displayMap?: Osk.PuaMap; + // Warning: (ae-incompatible-release-tags) The symbol "extra" is marked as @public, but its signature references "KmnCompilerResultExtra" which is marked as @internal + extra: KmnCompilerResultExtra; +} + +// Warning: (ae-internal-missing-underscore) The name "KmnCompilerResultExtra" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export interface KmnCompilerResultExtra { + // (undocumented) + displayMapFilename?: string; + // (undocumented) + groups: CompilerResultExtraGroup[]; + // (undocumented) + kvksFilename?: string; + // (undocumented) + stores: CompilerResultExtraStore[]; + targets: number; +} + +// Warning: (ae-internal-missing-underscore) The name "KmwCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal +export class KmwCompilerMessages extends KmnCompilerMessages { + // (undocumented) + static ERROR_InvalidTouchLayoutFileFormat: number; + // (undocumented) + static Error_InvalidTouchLayoutFileFormat: (o: { + msg: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_TouchLayoutFileDoesNotExist: number; + // (undocumented) + static Error_TouchLayoutFileDoesNotExist: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_TouchLayoutIdentifierRequires15: number; + // (undocumented) + static Error_TouchLayoutIdentifierRequires15: (o: { + keyId: string; + platformName: string; + layerId: string; + address: KeyAddress; + }) => CompilerEvent; + // (undocumented) + static HINT_TouchLayoutUsesUnsupportedGesturesDownlevel: number; + // (undocumented) + static Hint_TouchLayoutUsesUnsupportedGesturesDownlevel: (o: { + keyId: string; + }) => CompilerEvent; + // (undocumented) + static INFO_MinimumWebEngineVersion: number; + // (undocumented) + static Info_MinimumWebEngineVersion: (o: { + version: string; + }) => CompilerEvent; +} + +declare namespace Osk { + export { + parseMapping, + remapVisualKeyboard, + remapTouchLayout, + StringRefUsage, + StringRef, + StringResult, + PuaMap + } +} +export { Osk } + +// @public (undocumented) +function parseMapping(mapping: any): PuaMap; + +// @public (undocumented) +type PuaMap = { + [index: string]: string; +}; + +// @public (undocumented) +function remapTouchLayout(source: TouchLayout.TouchLayoutFile, map: PuaMap): boolean; + +// @public (undocumented) +function remapVisualKeyboard(vk: VisualKeyboard.VisualKeyboard, map: PuaMap): boolean; + +// @public (undocumented) +interface StringRef { + // (undocumented) + str: string; + // (undocumented) + usages: StringRefUsage[]; +} + +// @public (undocumented) +interface StringRefUsage { + // (undocumented) + count: number; + // (undocumented) + filename: string; +} + +// @public +interface StringResult { + pua: string; + str: string; + unicode: string; + usages: StringRefUsage[] | string[]; +} + +// Warnings were encountered during analysis: +// +// src/compiler/kmn-compiler-messages.ts:468:103 - (ae-forgotten-export) The symbol "KeyAddress" needs to be exported by the entry point main.d.ts + +``` diff --git a/developer/docs/api/etc/kmc-ldml.api.md b/developer/docs/api/etc/kmc-ldml.api.md index cc32c5e357..0e5ffcff79 100644 --- a/developer/docs/api/etc/kmc-ldml.api.md +++ b/developer/docs/api/etc/kmc-ldml.api.md @@ -1,330 +1,365 @@ -## API Report File for "@keymanapp/kmc-ldml" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifactOptional } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; -import { KMXPlus } from '@keymanapp/common-types'; -import { LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; -import { LDMLKeyboardXMLSourceFileReaderOptions } from '@keymanapp/common-types'; -import { UnicodeSetParser } from '@keymanapp/common-types'; - -// Warning: (ae-internal-missing-underscore) The name "LdmlCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class LdmlCompilerMessages { - // (undocumented) - static ERROR_CantReferenceSetFromUnicodeSet: number; - // (undocumented) - static Error_CantReferenceSetFromUnicodeSet: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_DisplayIsRepeated: number; - // (undocumented) - static Error_DisplayIsRepeated: (o: { - output?: string; - keyId?: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_DisplayNeedsToOrId: number; - // (undocumented) - static Error_DisplayNeedsToOrId: (o: { - output?: string; - keyId?: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_DuplicateTransformsType: number; - // (undocumented) - static Error_DuplicateTransformsType: (o: { - types: string[]; - }) => CompilerEvent; - // (undocumented) - static ERROR_DuplicateVariable: number; - // (undocumented) - static Error_DuplicateVariable: (o: { - ids: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_EmptyTransformGroup: number; - // (undocumented) - static Error_EmptyTransformGroup: () => CompilerEvent; - // (undocumented) - static ERROR_ExcessHardware: number; - // (undocumented) - static Error_ExcessHardware: (o: { - formId: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_GestureKeyNotFoundInKeyBag: number; - // (undocumented) - static Error_GestureKeyNotFoundInKeyBag: (o: { - keyId: string; - parentKeyId: string; - attribute: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_HardwareLayerHasTooManyRows: number; - // (undocumented) - static Error_HardwareLayerHasTooManyRows: () => CompilerEvent; - // (undocumented) - static ERROR_IllegalCharacters: number; - // (undocumented) - static Error_IllegalCharacters: (o: { - count: number; - lowestCh: number; - }) => CompilerEvent; - // (undocumented) - static ERROR_IllegalTransformAsterisk: number; - // (undocumented) - static Error_IllegalTransformAsterisk: (o: { - from: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_IllegalTransformDollarsign: number; - // (undocumented) - static Error_IllegalTransformDollarsign: (o: { - from: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_IllegalTransformPlus: number; - // (undocumented) - static Error_IllegalTransformPlus: (o: { - from: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidFile: number; - // (undocumented) - static Error_InvalidFile: (o: { - errorText: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidHardware: number; - // (undocumented) - static Error_InvalidHardware: (o: { - formId: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidLocale: number; - // (undocumented) - static Error_InvalidLocale: (o: { - tag: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidModifier: number; - // (undocumented) - static Error_InvalidModifier: (o: { - layer: string; - modifiers: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidQuadEscape: number; - // (undocumented) - static Error_InvalidQuadEscape: (o: { - cp: number; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidScanCode: number; - // (undocumented) - static Error_InvalidScanCode: (o: { - form?: string; - codes?: string[]; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidTransformsType: number; - // (undocumented) - static Error_InvalidTransformsType: (o: { - types: string[]; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidVersion: number; - // (undocumented) - static Error_InvalidVersion: (o: { - version: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_KeyMissingToGapOrSwitch: number; - // (undocumented) - static Error_KeyMissingToGapOrSwitch: (o: { - keyId: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_KeyNotFoundInKeyBag: number; - // (undocumented) - static Error_KeyNotFoundInKeyBag: (o: { - keyId: string; - col: number; - row: number; - layer: string; - form: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_MissingFlicks: number; - // (undocumented) - static Error_MissingFlicks: (o: { - flickId: string; - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_MissingMarkers: number; - // (undocumented) - static Error_MissingMarkers: (o: { - ids: string[]; - }) => CompilerEvent; - // (undocumented) - static ERROR_MissingSetVariable: number; - // (undocumented) - static Error_MissingSetVariable: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_MissingStringVariable: number; - // (undocumented) - static Error_MissingStringVariable: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_MissingUnicodeSetVariable: number; - // (undocumented) - static Error_MissingUnicodeSetVariable: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_MixedTransformGroup: number; - // (undocumented) - static Error_MixedTransformGroup: () => CompilerEvent; - // (undocumented) - static ERROR_MustBeAtLeastOneLayerElement: number; - // (undocumented) - static Error_MustBeAtLeastOneLayerElement: () => CompilerEvent; - // (undocumented) - static ERROR_NeedSpacesBetweenSetVariables: number; - // (undocumented) - static Error_NeedSpacesBetweenSetVariables: (o: { - item: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_RowOnHardwareLayerHasTooManyKeys: number; - // (undocumented) - static Error_RowOnHardwareLayerHasTooManyKeys: (o: { - row: number; - hardware: string; - modifiers: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_TransformFromMatchesNothing: number; - // (undocumented) - static Error_TransformFromMatchesNothing: (o: { - from: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnparseableReorderSet: number; - // (undocumented) - static Error_UnparseableReorderSet: (o: { - from: string; - set: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnparseableTransformFrom: number; - // (undocumented) - static Error_UnparseableTransformFrom: (o: { - from: string; - message: string; - }) => CompilerEvent; - // (undocumented) - static HINT_CharClassImplicitDenorm: number; - // (undocumented) - static Hint_CharClassImplicitDenorm: (o: { - lowestCh: number; - }) => CompilerEvent; - // (undocumented) - static HINT_LocaleIsNotMinimalAndClean: number; - // (undocumented) - static Hint_LocaleIsNotMinimalAndClean: (o: { - sourceLocale: string; - locale: string; - }) => CompilerEvent; - // (undocumented) - static HINT_NoDisplayForMarker: number; - // (undocumented) - static Hint_NoDisplayForMarker: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static HINT_NoDisplayForSwitch: number; - // (undocumented) - static Hint_NoDisplayForSwitch: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static HINT_NormalizationDisabled: number; - // (undocumented) - static Hint_NormalizationDisabled: () => CompilerEvent; - // (undocumented) - static HINT_OneOrMoreRepeatedLocales: number; - // (undocumented) - static Hint_OneOrMoreRepeatedLocales: () => CompilerEvent; - // (undocumented) - static HINT_PUACharacters: number; - // (undocumented) - static Hint_PUACharacters: (o: { - count: number; - lowestCh: number; - }) => CompilerEvent; - // (undocumented) - static WARN_CharClassExplicitDenorm: number; - // (undocumented) - static Warn_CharClassExplicitDenorm: (o: { - lowestCh: number; - }) => CompilerEvent; - // (undocumented) - static WARN_CustomForm: number; - // (undocumented) - static Warn_CustomForm: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static WARN_UnassignedCharacters: number; - // (undocumented) - static Warn_UnassignedCharacters: (o: { - count: number; - lowestCh: number; - }) => CompilerEvent; -} - -// @public -export interface LdmlCompilerOptions extends CompilerOptions { - readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; -} - -// @public -export class LdmlKeyboardCompiler implements KeymanCompiler { - // @internal - compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise; - // @internal - getUsetParser(): Promise; - init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise; - // Warning: (ae-forgotten-export) The symbol "LDMLKeyboardXMLSourceFile" needs to be exported by the entry point main.d.ts - // - // @internal - load(filename: string): LDMLKeyboardXMLSourceFile | null; - // @internal - loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null; - // Warning: (ae-forgotten-export) The symbol "LdmlKeyboardCompilerResult" needs to be exported by the entry point main.d.ts - run(inputFilename: string, outputFilename?: string): Promise; - // @internal - validate(source: LDMLKeyboardXMLSourceFile): Promise; - // Warning: (ae-forgotten-export) The symbol "LdmlKeyboardCompilerArtifacts" needs to be exported by the entry point main.d.ts - write(artifacts: LdmlKeyboardCompilerArtifacts): Promise; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@keymanapp/kmc-ldml" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifactOptional } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { KMXPlus } from '@keymanapp/common-types'; +import { LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/developer-utils'; +import { LdmlKeyboardTypes } from '@keymanapp/common-types'; +import { LDMLKeyboardXMLSourceFileReaderOptions } from '@keymanapp/developer-utils'; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; + +// Warning: (ae-internal-missing-underscore) The name "LdmlCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class LdmlCompilerMessages { + // (undocumented) + static ERROR_CantReferenceSetFromUnicodeSet: number; + // (undocumented) + static Error_CantReferenceSetFromUnicodeSet: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_DisplayIsRepeated: number; + // (undocumented) + static Error_DisplayIsRepeated: (o: { + display?: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_DisplayNeedsToOrId: number; + // (undocumented) + static Error_DisplayNeedsToOrId: (o: { + display?: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_DuplicateLayerWidth: number; + // (undocumented) + static Error_DuplicateLayerWidth: (o: { + minDeviceWidth: number; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_DuplicateTransformsType: number; + // (undocumented) + static Error_DuplicateTransformsType: (o: { + type: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_DuplicateVariable: number; + // (undocumented) + static Error_DuplicateVariable: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_EmptyTransformGroup: number; + // (undocumented) + static Error_EmptyTransformGroup: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_ExcessHardware: number; + // (undocumented) + static Error_ExcessHardware: (o: { + formId: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_GestureKeyNotFoundInKeyBag: number; + // (undocumented) + static Error_GestureKeyNotFoundInKeyBag: (o: { + keyId: string; + parentKeyId: string; + attribute: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_HardwareLayerHasTooManyRows: number; + // (undocumented) + static Error_HardwareLayerHasTooManyRows: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_IllegalCharacters: number; + // (undocumented) + static Error_IllegalCharacters: (o: { + count: number; + lowestCh: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_IllegalTransformAsterisk: number; + // (undocumented) + static Error_IllegalTransformAsterisk: (o: { + from: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_IllegalTransformDollarsign: number; + // (undocumented) + static Error_IllegalTransformDollarsign: (o: { + from: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_IllegalTransformPlus: number; + // (undocumented) + static Error_IllegalTransformPlus: (o: { + from: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_IllegalTransformToUset: number; + // (undocumented) + static Error_IllegalTransformToUset: (o: { + to: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidFile: number; + // (undocumented) + static Error_InvalidFile: (o: { + errorText: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidHardware: number; + // (undocumented) + static Error_InvalidHardware: (o: { + formId: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLayerWidth: number; + // (undocumented) + static Error_InvalidLayerWidth: (o: { + minDeviceWidth: number; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidLocale: number; + // (undocumented) + static Error_InvalidLocale: (o: { + tag: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidMarkerIdentifier: number; + // (undocumented) + static Error_InvalidMarkerIdentifier: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidModifier: number; + // (undocumented) + static Error_InvalidModifier: (o: { + modifiers: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidQuadEscape: number; + // (undocumented) + static Error_InvalidQuadEscape: (o: { + cp: string; + recommended: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidScanCode: number; + // (undocumented) + static Error_InvalidScanCode: (o: { + codes: string; + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidTransformsType: number; + // (undocumented) + static Error_InvalidTransformsType: (o: { + type: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidVariableIdentifier: number; + // (undocumented) + static Error_InvalidVariableIdentifier: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_InvalidVersion: number; + // (undocumented) + static Error_InvalidVersion: (o: { + version: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_KeyMissingToGapOrSwitch: number; + // (undocumented) + static Error_KeyMissingToGapOrSwitch: (o: { + keyId: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_KeyNotFoundInKeyBag: number; + // (undocumented) + static Error_KeyNotFoundInKeyBag: (o: { + keyId: string; + col: number; + row: number; + layer: string; + form: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MissingFlicks: number; + // (undocumented) + static Error_MissingFlicks: (o: { + id: string; + flickId: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MissingMarkers: number; + // (undocumented) + static Error_MissingMarkers: (o: { + ids: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MissingSetVariable: number; + // (undocumented) + static Error_MissingSetVariable: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MissingStringVariable: number; + // (undocumented) + static Error_MissingStringVariable: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MissingUnicodeSetVariable: number; + // (undocumented) + static Error_MissingUnicodeSetVariable: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MixedTransformGroup: number; + // (undocumented) + static Error_MixedTransformGroup: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_MustBeAtLeastOneLayerElement: number; + // (undocumented) + static Error_MustBeAtLeastOneLayerElement: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_NeedSpacesBetweenSetVariables: number; + // (undocumented) + static Error_NeedSpacesBetweenSetVariables: (o: { + item: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_RowOnHardwareLayerHasTooManyKeys: number; + // (undocumented) + static Error_RowOnHardwareLayerHasTooManyKeys: (o: { + row: number; + hardware: string; + modifiers: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_TransformFromMatchesNothing: number; + // (undocumented) + static Error_TransformFromMatchesNothing: (o: { + from: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_UnparseableTransformFrom: number; + // (undocumented) + static Error_UnparseableTransformFrom: (o: { + from: string; + message: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static ERROR_UnparseableTransformTo: number; + // (undocumented) + static Error_UnparseableTransformTo: (o: { + to: string; + message: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_CharClassImplicitDenorm: number; + // (undocumented) + static Hint_CharClassImplicitDenorm: (o: { + lowestCh: number; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_LocaleIsNotMinimalAndClean: number; + // (undocumented) + static Hint_LocaleIsNotMinimalAndClean: (o: { + sourceLocale: string; + locale: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_NoDisplayForMarker: number; + // (undocumented) + static Hint_NoDisplayForMarker: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_NoDisplayForSwitch: number; + // (undocumented) + static Hint_NoDisplayForSwitch: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_NormalizationDisabled: number; + // (undocumented) + static Hint_NormalizationDisabled: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_OneOrMoreRepeatedLocales: number; + // (undocumented) + static Hint_OneOrMoreRepeatedLocales: (compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static HINT_PUACharacters: number; + // (undocumented) + static Hint_PUACharacters: (o: { + count: number; + lowestCh: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static WARN_CharClassExplicitDenorm: number; + // (undocumented) + static Warn_CharClassExplicitDenorm: (o: { + lowestCh: number; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static WARN_CustomForm: number; + // (undocumented) + static Warn_CustomForm: (o: { + id: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static WARN_StringDenorm: number; + // (undocumented) + static Warn_StringDenorm: (o: { + s: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; + // (undocumented) + static WARN_UnassignedCharacters: number; + // (undocumented) + static Warn_UnassignedCharacters: (o: { + count: number; + lowestCh: string; + }, compileContext?: ObjectWithCompileContext) => CompilerEvent; +} + +// @public +export interface LdmlCompilerOptions extends CompilerOptions { + readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; +} + +// @public +export class LdmlKeyboardCompiler implements KeymanCompiler { + // @internal + compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise; + // @internal + getUsetParser(): Promise; + init(callbacks: CompilerCallbacks, options: LdmlCompilerOptions): Promise; + // Warning: (ae-forgotten-export) The symbol "LDMLKeyboardXMLSourceFile" needs to be exported by the entry point main.d.ts + // + // @internal + load(filename: string): LDMLKeyboardXMLSourceFile | null; + // @internal + loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null; + // Warning: (ae-forgotten-export) The symbol "LdmlKeyboardCompilerResult" needs to be exported by the entry point main.d.ts + run(inputFilename: string, outputFilename?: string): Promise; + // @internal + validate(source: LDMLKeyboardXMLSourceFile): Promise; + // Warning: (ae-forgotten-export) The symbol "LdmlKeyboardCompilerArtifacts" needs to be exported by the entry point main.d.ts + write(artifacts: LdmlKeyboardCompilerArtifacts): Promise; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-model-info.api.md b/developer/docs/api/etc/kmc-model-info.api.md index af54f5b882..3d1c7a6e26 100644 --- a/developer/docs/api/etc/kmc-model-info.api.md +++ b/developer/docs/api/etc/kmc-model-info.api.md @@ -1,115 +1,121 @@ -## API Report File for "@keymanapp/kmc-model-info" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifact } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; -import { KmpJsonFile } from '@keymanapp/common-types'; - -// @public -export class ModelInfoCompiler implements KeymanCompiler { - constructor(); - init(callbacks: CompilerCallbacks, options: ModelInfoCompilerOptions): Promise; - run(inputFilename: string, outputFilename?: string): Promise; - write(artifacts: ModelInfoCompilerArtifacts): Promise; -} - -// @public -export interface ModelInfoCompilerArtifacts extends KeymanCompilerArtifacts { - model_info: KeymanCompilerArtifact; -} - -// Warning: (ae-internal-missing-underscore) The name "ModelInfoCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class ModelInfoCompilerMessages { - // (undocumented) - static ERROR_FileDoesNotExist: number; - // (undocumented) - static Error_FileDoesNotExist: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_FileIsNotValid: number; - // (undocumented) - static Error_FileIsNotValid: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidAuthorEmail: number; - // (undocumented) - static Error_InvalidAuthorEmail: (o: { - email: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseFileIsDamaged: number; - // (undocumented) - static Error_LicenseFileIsDamaged: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseFileIsMissing: number; - // (undocumented) - static Error_LicenseFileIsMissing: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_LicenseIsNotValid: number; - // (undocumented) - static Error_LicenseIsNotValid: (o: { - filename: string; - message: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_NoLicenseFound: number; - // (undocumented) - static Error_NoLicenseFound: () => CompilerEvent; - // (undocumented) - static FATAL_UnexpectedException: number; - // (undocumented) - static Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static WARN_MetadataFieldInconsistent: number; - // (undocumented) - static Warn_MetadataFieldInconsistent: (o: { - field: string; - value: any; - expected: any; - }) => CompilerEvent; -} - -// @public -export interface ModelInfoCompilerOptions extends CompilerOptions { - sources: ModelInfoSources; -} - -// @public -export interface ModelInfoCompilerResult extends KeymanCompilerResult { - artifacts: ModelInfoCompilerArtifacts; -} - -// @public -export class ModelInfoSources { - forPublishing: boolean; - kmpFileName: string; - kmpJsonData: KmpJsonFile.KmpJsonFile; - kpsFilename: string; - lastCommitDate?: string; - model_id: string; - modelFileName: string; - sourcePath: string; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@keymanapp/kmc-model-info" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { KmpJsonFile } from '@keymanapp/common-types'; + +// @public +export class ModelInfoCompiler implements KeymanCompiler { + constructor(); + init(callbacks: CompilerCallbacks, options: ModelInfoCompilerOptions): Promise; + run(inputFilename: string, outputFilename?: string): Promise; + write(artifacts: ModelInfoCompilerArtifacts): Promise; +} + +// @public +export interface ModelInfoCompilerArtifacts extends KeymanCompilerArtifacts { + model_info: KeymanCompilerArtifact; +} + +// Warning: (ae-internal-missing-underscore) The name "ModelInfoCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class ModelInfoCompilerMessages { + // (undocumented) + static ERROR_DescriptionIsMissing: number; + // (undocumented) + static Error_DescriptionIsMissing: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FileDoesNotExist: number; + // (undocumented) + static Error_FileDoesNotExist: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FileIsNotValid: number; + // (undocumented) + static Error_FileIsNotValid: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_InvalidAuthorEmail: number; + // (undocumented) + static Error_InvalidAuthorEmail: (o: { + email: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseFileIsDamaged: number; + // (undocumented) + static Error_LicenseFileIsDamaged: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseFileIsMissing: number; + // (undocumented) + static Error_LicenseFileIsMissing: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_LicenseIsNotValid: number; + // (undocumented) + static Error_LicenseIsNotValid: (o: { + filename: string; + message: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NoLicenseFound: number; + // (undocumented) + static Error_NoLicenseFound: () => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static WARN_MetadataFieldInconsistent: number; + // (undocumented) + static Warn_MetadataFieldInconsistent: (o: { + field: string; + value: any; + expected: any; + }) => CompilerEvent; +} + +// @public +export interface ModelInfoCompilerOptions extends CompilerOptions { + sources: ModelInfoSources; +} + +// @public +export interface ModelInfoCompilerResult extends KeymanCompilerResult { + artifacts: ModelInfoCompilerArtifacts; +} + +// @public +export class ModelInfoSources { + forPublishing: boolean; + kmpFileName: string; + kmpJsonData: KmpJsonFile.KmpJsonFile; + kpsFilename: string; + lastCommitDate?: string; + model_id: string; + modelFileName: string; + sourcePath: string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-model.api.md b/developer/docs/api/etc/kmc-model.api.md index 51ea5dffb8..f42c1a12fc 100644 --- a/developer/docs/api/etc/kmc-model.api.md +++ b/developer/docs/api/etc/kmc-model.api.md @@ -1,137 +1,152 @@ -## API Report File for "@keymanapp/kmc-model" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -/// - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifact } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; - -// @public -export type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string; - -// @public -export class LexicalModelCompiler implements KeymanCompiler { - // @internal - generateLexicalModelCode(model_id: string, modelSource: LexicalModelSource, sourcePath: string): string; - init(callbacks: CompilerCallbacks, _options: CompilerOptions): Promise; - // @internal - loadFromFilename(filename: string): LexicalModelSource; - run(inputFilename: string, outputFilename?: string): Promise; - // @internal (undocumented) - transpileSources(sources: Array): Array; - write(artifacts: LexicalModelCompilerArtifacts): Promise; -} - -// @public -export interface LexicalModelCompilerArtifacts extends KeymanCompilerArtifacts { - js: KeymanCompilerArtifact; -} - -// @public -export interface LexicalModelCompilerResult extends KeymanCompilerResult { - artifacts: LexicalModelCompilerArtifacts; -} - -// Warning: (ae-forgotten-export) The symbol "LexicalModelDeclaration" needs to be exported by the entry point main.d.ts -// -// @public -export interface LexicalModelSource extends LexicalModelDeclaration { - readonly applyCasing?: CasingFunction; - readonly languageUsesCasing?: boolean; - readonly punctuation?: LexicalModelPunctuation; - readonly rootClass?: string; - readonly searchTermToKey?: WordformToKeySpec; - // (undocumented) - readonly sources: Array; - readonly wordBreaker?: WordBreakerSpec | SimpleWordBreakerSpec; -} - -// Warning: (ae-internal-missing-underscore) The name "ModelCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class ModelCompilerMessages { - // (undocumented) - static ERROR_NoDefaultExport: number; - // (undocumented) - static Error_NoDefaultExport: () => CompilerEvent; - // (undocumented) - static ERROR_SearchTermToKeyMustBeExplicitlySpecified: number; - // (undocumented) - static Error_SearchTermToKeyMustBeExplicitlySpecified: () => CompilerEvent; - // (undocumented) - static ERROR_UnimplementedModelFormat: number; - // (undocumented) - static Error_UnimplementedModelFormat: (o: { - format: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnknownModelFormat: number; - // (undocumented) - static Error_UnknownModelFormat: (o: { - format: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnknownWordBreaker: number; - // (undocumented) - static Error_UnknownWordBreaker: (o: { - spec: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UnsupportedScriptOverride: number; - // (undocumented) - static Error_UnsupportedScriptOverride: (o: { - option: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_UTF16BEUnsupported: number; - // (undocumented) - static Error_UTF16BEUnsupported: () => CompilerEvent; - // (undocumented) - static FATAL_UnexpectedException: number; - // (undocumented) - static Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static HINT_DuplicateWordInSameFile: number; - // (undocumented) - static Hint_DuplicateWordInSameFile: (o: { - wordform: string; - }) => CompilerEvent; - // (undocumented) - static HINT_MixedNormalizationForms: number; - // (undocumented) - static Hint_MixedNormalizationForms: (o: { - wordform: string; - }) => CompilerEvent; -} - -// @public -export type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction; - -// @public -export type SimpleWordformToKeySpec = (term: string) => string; - -// @public -export interface WordBreakerSpec { - readonly joinWordsAt?: string[]; - // Warning: (ae-forgotten-export) The symbol "OverrideScriptDefaults" needs to be exported by the entry point main.d.ts - readonly overrideScriptDefaults?: OverrideScriptDefaults; - // (undocumented) - readonly use: SimpleWordBreakerSpec; -} - -// @public -export type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec; - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@keymanapp/kmc-model" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; + +// Warning: (ae-forgotten-export) The symbol "CasingFunction" needs to be exported by the entry point main.d.ts +// +// @public +export type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string; + +// @public +export class LexicalModelCompiler implements KeymanCompiler { + // @internal + generateLexicalModelCode(model_id: string, modelSource: LexicalModelSource, sourcePath: string): string; + init(callbacks: CompilerCallbacks, _options: CompilerOptions): Promise; + // @internal + loadFromFilename(filename: string): LexicalModelSource; + run(inputFilename: string, outputFilename?: string): Promise; + // @internal (undocumented) + transpileSources(sources: Array): Array; + write(artifacts: LexicalModelCompilerArtifacts): Promise; +} + +// @public +export interface LexicalModelCompilerArtifacts extends KeymanCompilerArtifacts { + js: KeymanCompilerArtifact; +} + +// @public +export interface LexicalModelCompilerResult extends KeymanCompilerResult { + artifacts: LexicalModelCompilerArtifacts; +} + +// Warning: (ae-forgotten-export) The symbol "LexicalModelDeclaration" needs to be exported by the entry point main.d.ts +// +// @public +export interface LexicalModelSource extends LexicalModelDeclaration { + readonly applyCasing?: CasingFunction; + readonly languageUsesCasing?: boolean; + // Warning: (ae-forgotten-export) The symbol "LexicalModelPunctuation" needs to be exported by the entry point main.d.ts + readonly punctuation?: LexicalModelPunctuation; + readonly rootClass?: string; + readonly searchTermToKey?: WordformToKeySpec; + // (undocumented) + readonly sources: Array; + readonly wordBreaker?: WordBreakerSpec | SimpleWordBreakerSpec; +} + +// Warning: (ae-internal-missing-underscore) The name "ModelCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class ModelCompilerMessages { + // (undocumented) + static ERROR_ModelFileNotFound: number; + // (undocumented) + static Error_ModelFileNotFound: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_NoDefaultExport: number; + // (undocumented) + static Error_NoDefaultExport: () => CompilerEvent; + // (undocumented) + static ERROR_SearchTermToKeyMustBeExplicitlySpecified: number; + // (undocumented) + static Error_SearchTermToKeyMustBeExplicitlySpecified: () => CompilerEvent; + // (undocumented) + static ERROR_UnimplementedModelFormat: number; + // (undocumented) + static Error_UnimplementedModelFormat: (o: { + format: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_UnknownModelFormat: number; + // (undocumented) + static Error_UnknownModelFormat: (o: { + format: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_UnknownWordBreaker: number; + // (undocumented) + static Error_UnknownWordBreaker: (o: { + spec: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_UnsupportedScriptOverride: number; + // (undocumented) + static Error_UnsupportedScriptOverride: (o: { + option: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_UTF16BEUnsupported: number; + // (undocumented) + static Error_UTF16BEUnsupported: () => CompilerEvent; + // (undocumented) + static ERROR_WordlistFileNotFound: number; + // (undocumented) + static Error_WordlistFileNotFound: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static HINT_DuplicateWordInSameFile: number; + // (undocumented) + static Hint_DuplicateWordInSameFile: (o: { + wordform: string; + }) => CompilerEvent; + // (undocumented) + static HINT_MixedNormalizationForms: number; + // (undocumented) + static Hint_MixedNormalizationForms: (o: { + wordform: string; + }) => CompilerEvent; +} + +// Warning: (ae-forgotten-export) The symbol "WordBreakingFunction" needs to be exported by the entry point main.d.ts +// +// @public +export type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction; + +// @public +export type SimpleWordformToKeySpec = (term: string) => string; + +// @public +export interface WordBreakerSpec { + readonly joinWordsAt?: string[]; + // Warning: (ae-forgotten-export) The symbol "OverrideScriptDefaults" needs to be exported by the entry point main.d.ts + readonly overrideScriptDefaults?: OverrideScriptDefaults; + // (undocumented) + readonly use: SimpleWordBreakerSpec; +} + +// @public +export type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/api/etc/kmc-package.api.md b/developer/docs/api/etc/kmc-package.api.md index eb2c535160..41475fa791 100644 --- a/developer/docs/api/etc/kmc-package.api.md +++ b/developer/docs/api/etc/kmc-package.api.md @@ -1,296 +1,347 @@ -## API Report File for "@keymanapp/kmc-package" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { CompilerCallbacks } from '@keymanapp/common-types'; -import { CompilerEvent } from '@keymanapp/common-types'; -import { CompilerOptions } from '@keymanapp/common-types'; -import { KeymanCompiler } from '@keymanapp/common-types'; -import { KeymanCompilerArtifact } from '@keymanapp/common-types'; -import { KeymanCompilerArtifacts } from '@keymanapp/common-types'; -import { KeymanCompilerResult } from '@keymanapp/common-types'; -import { KmpJsonFile } from '@keymanapp/common-types'; -import { KpsFile } from '@keymanapp/common-types'; - -// @public -export class KmpCompiler implements KeymanCompiler { - // @internal - buildKmpFile(kpsFilename: string, kmpJsonData: KmpJsonFile.KmpJsonFile): Promise; - init(callbacks: CompilerCallbacks, options: KmpCompilerOptions): Promise; - // @internal (undocumented) - loadKpsFile(kpsFilename: string): KpsFile.KpsFile; - // (undocumented) - readonly normalizePath: (path: string) => string; - run(inputFilename: string, outputFilename?: string): Promise; - // @internal (undocumented) - transformKpsFileToKmpObject(kpsFilename: string, kps: KpsFile.KpsFile): KmpJsonFile.KmpJsonFile; - // @internal (undocumented) - transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile; - write(artifacts: KmpCompilerArtifacts): Promise; -} - -// @public -export interface KmpCompilerArtifacts extends KeymanCompilerArtifacts { - kmp: KeymanCompilerArtifact; -} - -// @public -export interface KmpCompilerOptions extends CompilerOptions { -} - -// @public -export interface KmpCompilerResult extends KeymanCompilerResult { - artifacts: KmpCompilerArtifacts; -} - -// Warning: (ae-internal-missing-underscore) The name "PackageCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class PackageCompilerMessages { - // (undocumented) - static ERROR_FileCouldNotBeRead: number; - // (undocumented) - static Error_FileCouldNotBeRead: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_FileDoesNotExist: number; - // (undocumented) - static Error_FileDoesNotExist: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_FileRecordIsMissingName: number; - // (undocumented) - static Error_FileRecordIsMissingName: (o: { - description: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_FollowKeyboardVersionButNoKeyboards: number; - // (undocumented) - static Error_FollowKeyboardVersionButNoKeyboards: () => CompilerEvent; - // (undocumented) - static ERROR_FollowKeyboardVersionNotAllowedForModelPackages: number; - // (undocumented) - static Error_FollowKeyboardVersionNotAllowedForModelPackages: () => CompilerEvent; - // (undocumented) - static ERROR_InvalidAuthorEmail: number; - // (undocumented) - static Error_InvalidAuthorEmail: (o: { - email: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_InvalidPackageFile: number; - // (undocumented) - static Error_InvalidPackageFile: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_KeyboardContentFileNotFound: number; - // (undocumented) - static Error_KeyboardContentFileNotFound: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_KeyboardFileNotFound: number; - // (undocumented) - static Error_KeyboardFileNotFound: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_KeyboardFileNotValid: number; - // (undocumented) - static Error_KeyboardFileNotValid: (o: { - filename: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_LanguageTagIsNotValid: number; - // (undocumented) - static Error_LanguageTagIsNotValid: (o: { - resourceType: string; - id: string; - lang: string; - e: any; - }) => CompilerEvent; - // (undocumented) - static ERROR_ModelMustHaveAtLeastOneLanguage: number; - // (undocumented) - static Error_ModelMustHaveAtLeastOneLanguage: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static ERROR_PackageCannotContainBothModelsAndKeyboards: number; - // (undocumented) - static Error_PackageCannotContainBothModelsAndKeyboards: () => CompilerEvent; - // (undocumented) - static ERROR_PackageMustContainAModelOrAKeyboard: number; - // (undocumented) - static Error_PackageMustContainAModelOrAKeyboard: () => CompilerEvent; - // (undocumented) - static ERROR_PackageNameCannotBeBlank: number; - // (undocumented) - static Error_PackageNameCannotBeBlank: () => CompilerEvent; - // (undocumented) - static FATAL_UnexpectedException: number; - // (undocumented) - static Fatal_UnexpectedException: (o: { - e: any; - }) => CompilerEvent; - // (undocumented) - static HINT_JsKeyboardFileHasNoTouchTargets: number; - // (undocumented) - static Hint_JsKeyboardFileHasNoTouchTargets: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static HINT_LanguageTagIsNotMinimal: number; - // (undocumented) - static Hint_LanguageTagIsNotMinimal: (o: { - resourceType: string; - id: string; - actual: string; - expected: string; - }) => CompilerEvent; - // (undocumented) - static HINT_PackageContainsSourceFile: number; - // (undocumented) - static Hint_PackageContainsSourceFile: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static HINT_PackageShouldNotRepeatLanguages: number; - // (undocumented) - static Hint_PackageShouldNotRepeatLanguages: (o: { - resourceType: string; - id: string; - minimalTag: string; - firstTag: string; - secondTag: string; - }) => CompilerEvent; - // (undocumented) - static INFO_KeyboardFileHasNoKeyboardVersion: number; - // (undocumented) - static Info_KeyboardFileHasNoKeyboardVersion: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_AbsolutePath: number; - // (undocumented) - static Warn_AbsolutePath: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_DocFileDangerous: number; - // (undocumented) - static Warn_DocFileDangerous: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_FileInPackageDoesNotFollowFilenameConventions: number; - // (undocumented) - static Warn_FileInPackageDoesNotFollowFilenameConventions: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_FileIsNotABinaryKvkFile: number; - // (undocumented) - static Warn_FileIsNotABinaryKvkFile: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_JsKeyboardFileIsMissing: number; - // (undocumented) - static Warn_JsKeyboardFileIsMissing: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static WARN_KeyboardShouldHaveAtLeastOneLanguage: number; - // (undocumented) - static Warn_KeyboardShouldHaveAtLeastOneLanguage: (o: { - id: string; - }) => CompilerEvent; - // (undocumented) - static WARN_KeyboardVersionsDoNotMatch: number; - // (undocumented) - static Warn_KeyboardVersionsDoNotMatch: (o: { - keyboard: string; - version: string; - firstKeyboard: string; - firstVersion: string; - }) => CompilerEvent; - // (undocumented) - static WARN_PackageNameDoesNotFollowKeyboardConventions: number; - // (undocumented) - static Warn_PackageNameDoesNotFollowKeyboardConventions: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_PackageNameDoesNotFollowLexicalModelConventions: number; - // (undocumented) - static Warn_PackageNameDoesNotFollowLexicalModelConventions: (o: { - filename: string; - }) => CompilerEvent; - // (undocumented) - static WARN_RedistFileShouldNotBeInPackage: number; - // (undocumented) - static Warn_RedistFileShouldNotBeInPackage: (o: { - filename: string; - }) => CompilerEvent; -} - -// Warning: (ae-internal-missing-underscore) The name "PackageValidation" should be prefixed with an underscore because the declaration is marked as @internal -// -// @internal (undocumented) -export class PackageValidation { - constructor(callbacks: CompilerCallbacks, options: CompilerOptions); - // (undocumented) - validate(filename: string, kmpJson: KmpJsonFile.KmpJsonFile): boolean; -} - -// @public -export class WindowsPackageInstallerCompiler implements KeymanCompiler { - init(callbacks: CompilerCallbacks, options: WindowsPackageInstallerCompilerOptions): Promise; - run(inputFilename: string, outputFilename?: string): Promise; - write(artifacts: WindowsPackageInstallerCompilerArtifacts): Promise; -} - -// @public -export interface WindowsPackageInstallerCompilerArtifacts extends KeymanCompilerArtifacts { - exe: KeymanCompilerArtifact; -} - -// @public -export interface WindowsPackageInstallerCompilerOptions extends KmpCompilerOptions { - sources: WindowsPackageInstallerSources; -} - -// @public -export interface WindowsPackageInstallerCompilerResult extends KeymanCompilerResult { - artifacts: WindowsPackageInstallerCompilerArtifacts; -} - -// @public -export interface WindowsPackageInstallerSources { - // (undocumented) - appName?: string; - // (undocumented) - licenseFilename: string; - // (undocumented) - msiFilename: string; - // (undocumented) - setupExeFilename: string; - // (undocumented) - startDisabled: boolean; - // (undocumented) - startWithConfiguration: boolean; - // (undocumented) - titleImageFilename?: string; -} - -// (No @packageDocumentation comment for this package) - -``` +## API Report File for "@keymanapp/kmc-package" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CompilerCallbacks } from '@keymanapp/developer-utils'; +import { CompilerEvent } from '@keymanapp/developer-utils'; +import { CompilerOptions } from '@keymanapp/developer-utils'; +import { KeymanCompiler } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifact } from '@keymanapp/developer-utils'; +import { KeymanCompilerArtifacts } from '@keymanapp/developer-utils'; +import { KeymanCompilerResult } from '@keymanapp/developer-utils'; +import { KmpJsonFile } from '@keymanapp/common-types'; +import { KpsFile } from '@keymanapp/developer-utils'; + +// @public +export class KmpCompiler implements KeymanCompiler { + // @internal + buildKmpFile(kpsFilename: string, kmpJsonData: KmpJsonFile.KmpJsonFile): Promise; + init(callbacks: CompilerCallbacks, options: KmpCompilerOptions): Promise; + // (undocumented) + readonly normalizePath: (path: string) => string; + run(inputFilename: string, outputFilename?: string): Promise; + // @internal (undocumented) + transformKpsFileToKmpObject(kpsFilename: string, kps: KpsFile.KpsFile): KmpJsonFile.KmpJsonFile; + // @internal (undocumented) + transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile; + write(artifacts: KmpCompilerArtifacts): Promise; +} + +// @public +export interface KmpCompilerArtifacts extends KeymanCompilerArtifacts { + kmp: KeymanCompilerArtifact; +} + +// @public +export interface KmpCompilerOptions extends CompilerOptions { +} + +// @public +export interface KmpCompilerResult extends KeymanCompilerResult { + artifacts: KmpCompilerArtifacts; +} + +// Warning: (ae-internal-missing-underscore) The name "PackageCompilerMessages" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class PackageCompilerMessages { + // (undocumented) + static ERROR_FileCouldNotBeRead: number; + // (undocumented) + static Error_FileCouldNotBeRead: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_FileDoesNotExist: number; + // (undocumented) + static Error_FileDoesNotExist: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FileRecordIsMissingName: number; + // (undocumented) + static Error_FileRecordIsMissingName: (o: { + description: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_FollowKeyboardVersionButNoKeyboards: number; + // (undocumented) + static Error_FollowKeyboardVersionButNoKeyboards: () => CompilerEvent; + // (undocumented) + static ERROR_FollowKeyboardVersionNotAllowedForModelPackages: number; + // (undocumented) + static Error_FollowKeyboardVersionNotAllowedForModelPackages: () => CompilerEvent; + // (undocumented) + static ERROR_InvalidAuthorEmail: number; + // (undocumented) + static Error_InvalidAuthorEmail: (o: { + email: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_KeyboardContentFileNotFound: number; + // (undocumented) + static Error_KeyboardContentFileNotFound: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_KeyboardFileNotFound: number; + // (undocumented) + static Error_KeyboardFileNotFound: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_KeyboardFileNotValid: number; + // (undocumented) + static Error_KeyboardFileNotValid: (o: { + filename: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_LanguageTagIsNotValid: number; + // (undocumented) + static Error_LanguageTagIsNotValid: (o: { + resourceType: string; + id: string; + lang: string; + e: any; + }) => CompilerEvent; + // (undocumented) + static ERROR_MissingKeyboardId: number; + // (undocumented) + static Error_MissingKeyboardId: (o: { + index: number; + }) => CompilerEvent; + // (undocumented) + static ERROR_MissingModelId: number; + // (undocumented) + static Error_MissingModelId: (o: { + index: number; + }) => CompilerEvent; + // (undocumented) + static ERROR_ModelMustHaveAtLeastOneLanguage: number; + // (undocumented) + static Error_ModelMustHaveAtLeastOneLanguage: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_PackageCannotContainBothModelsAndKeyboards: number; + // (undocumented) + static Error_PackageCannotContainBothModelsAndKeyboards: () => CompilerEvent; + // (undocumented) + static ERROR_PackageFileHasEmptyVersion: number; + // (undocumented) + static Error_PackageFileHasEmptyVersion: () => CompilerEvent; + // (undocumented) + static ERROR_PackageMustContainAModelOrAKeyboard: number; + // (undocumented) + static Error_PackageMustContainAModelOrAKeyboard: () => CompilerEvent; + // (undocumented) + static ERROR_PackageMustNotContainItself: number; + // (undocumented) + static Error_PackageMustNotContainItself: (o: { + outputFilename: string; + }) => CompilerEvent; + // (undocumented) + static ERROR_PackageNameCannotBeBlank: number; + // (undocumented) + static Error_PackageNameCannotBeBlank: () => CompilerEvent; + // (undocumented) + static ERROR_RequiredParameterMissing: number; + // (undocumented) + static Error_RequiredParameterMissing: (o: { + param: string; + }) => CompilerEvent; + // (undocumented) + static FATAL_UnexpectedException: number; + // (undocumented) + static Fatal_UnexpectedException: (o: { + e: any; + }) => CompilerEvent; + // (undocumented) + static HINT_JsKeyboardFileHasNoTouchTargets: number; + // (undocumented) + static Hint_JsKeyboardFileHasNoTouchTargets: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static HINT_LanguageTagIsNotMinimal: number; + // (undocumented) + static Hint_LanguageTagIsNotMinimal: (o: { + resourceType: string; + id: string; + actual: string; + expected: string; + }) => CompilerEvent; + // (undocumented) + static HINT_PackageContainsSourceFile: number; + // (undocumented) + static Hint_PackageContainsSourceFile: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static HINT_PackageShouldNotRepeatLanguages: number; + // (undocumented) + static Hint_PackageShouldNotRepeatLanguages: (o: { + resourceType: string; + id: string; + minimalTag: string; + firstTag: string; + secondTag: string; + }) => CompilerEvent; + // (undocumented) + static INFO_KeyboardFileHasNoKeyboardVersion: number; + // (undocumented) + static Info_KeyboardFileHasNoKeyboardVersion: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_AbsolutePath: number; + // (undocumented) + static Warn_AbsolutePath: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_CannotFindFontForKeyboard: number; + // (undocumented) + static Warn_CannotFindFontForKeyboard: (o: { + id: string; + fontFilename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_CannotFindMatchingKeyboardForVisualKeyboard: number; + // (undocumented) + static Warn_CannotFindMatchingKeyboardForVisualKeyboard: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_CannotReadFont: number; + // (undocumented) + static Warn_CannotReadFont: (o: { + fontFilename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_DocFileDangerous: number; + // (undocumented) + static Warn_DocFileDangerous: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_FileInPackageDoesNotFollowFilenameConventions: number; + // (undocumented) + static Warn_FileInPackageDoesNotFollowFilenameConventions: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_FileIsNotABinaryKvkFile: number; + // (undocumented) + static Warn_FileIsNotABinaryKvkFile: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_JsKeyboardFileIsMissing: number; + // (undocumented) + static Warn_JsKeyboardFileIsMissing: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static WARN_KeyboardShouldHaveAtLeastOneLanguage: number; + // (undocumented) + static Warn_KeyboardShouldHaveAtLeastOneLanguage: (o: { + id: string; + }) => CompilerEvent; + // (undocumented) + static WARN_KeyboardVersionsDoNotMatch: number; + // (undocumented) + static Warn_KeyboardVersionsDoNotMatch: (o: { + keyboard: string; + version: string; + firstKeyboard: string; + firstVersion: string; + }) => CompilerEvent; + // (undocumented) + static WARN_PackageNameDoesNotFollowKeyboardConventions: number; + // (undocumented) + static Warn_PackageNameDoesNotFollowKeyboardConventions: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_PackageNameDoesNotFollowLexicalModelConventions: number; + // (undocumented) + static Warn_PackageNameDoesNotFollowLexicalModelConventions: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_PackageVersionIsUnrecognizedFormat: number; + // (undocumented) + static Warn_PackageVersionIsUnrecognizedFormat: (o: { + version: string; + }) => CompilerEvent; + // (undocumented) + static WARN_RedistFileShouldNotBeInPackage: number; + // (undocumented) + static Warn_RedistFileShouldNotBeInPackage: (o: { + filename: string; + }) => CompilerEvent; + // (undocumented) + static WARN_VisualKeyboardFileIsInvalid: number; + // (undocumented) + static Warn_VisualKeyboardFileIsInvalid: (o: { + filename: string; + }) => CompilerEvent; +} + +// Warning: (ae-internal-missing-underscore) The name "PackageValidation" should be prefixed with an underscore because the declaration is marked as @internal +// +// @internal (undocumented) +export class PackageValidation { + constructor(callbacks: CompilerCallbacks, options: CompilerOptions); + // (undocumented) + validate(filename: string, outputFilename: string, kmpJson: KmpJsonFile.KmpJsonFile): boolean; +} + +// @public +export class WindowsPackageInstallerCompiler implements KeymanCompiler { + init(callbacks: CompilerCallbacks, options: WindowsPackageInstallerCompilerOptions): Promise; + run(inputFilename: string, outputFilename?: string): Promise; + write(artifacts: WindowsPackageInstallerCompilerArtifacts): Promise; +} + +// @public +export interface WindowsPackageInstallerCompilerArtifacts extends KeymanCompilerArtifacts { + exe: KeymanCompilerArtifact; +} + +// @public +export interface WindowsPackageInstallerCompilerOptions extends KmpCompilerOptions { + sources: WindowsPackageInstallerSources; +} + +// @public +export interface WindowsPackageInstallerCompilerResult extends KeymanCompilerResult { + artifacts: WindowsPackageInstallerCompilerArtifacts; +} + +// @public +export interface WindowsPackageInstallerSources { + // (undocumented) + appName?: string; + // (undocumented) + licenseFilename: string; + // (undocumented) + msiFilename: string; + // (undocumented) + setupExeFilename: string; + // (undocumented) + startDisabled: boolean; + // (undocumented) + startWithConfiguration: boolean; + // (undocumented) + titleImageFilename?: string; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/developer/docs/help/context/index.md b/developer/docs/help/context/index.md index 04028b35c1..d7bf164c30 100644 --- a/developer/docs/help/context/index.md +++ b/developer/docs/help/context/index.md @@ -42,12 +42,6 @@ title: Context Help [kmc Command-line Options](kmc) -[kmlmc Command-line Options](kmlmc) - -[kmlmi Command-line Options](kmlmi) - -[kmlmp Command-line Options](kmlmp) - [Character Map](character-map) [Message Window](messages) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index 577640fc65..8dbc6e04b5 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -322,7 +322,7 @@ on any key to edit it. There are a number of controls: Red circle with an X : This button to the top right of the key will delete the key from the - row; if it is the last key on the row, the entire row will be + row; if it is the only remaining key on the row, the entire row will be deleted. Green arrow with a + @@ -602,7 +602,7 @@ Open source, build, and project folder ### Windows, macOS, and Linux Targets -If your keyboard is designed to target Windows and macOS, then the +If your keyboard is designed to target Windows, Linux, and macOS, then the following buttons will also be visible: Install @@ -649,4 +649,4 @@ web page. The Send to email function makes it easier to open the addresses on your mobile device without having to enter them manually. -Learn more about [testing on the web and mobile devices](../guides/test/). \ No newline at end of file +Learn more about [testing on the web and mobile devices](../guides/test/). diff --git a/developer/docs/help/context/kmlmc.md b/developer/docs/help/context/kmlmc.md deleted file mode 100644 index 33edf500f5..0000000000 --- a/developer/docs/help/context/kmlmc.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: kmlmc - Command Line Lexical Model Compiler (deprecated) ---- - -kmlmc has been replaced by [kmc](../reference/kmc). diff --git a/developer/docs/help/context/kmlmi.md b/developer/docs/help/context/kmlmi.md deleted file mode 100644 index dc47cbbc39..0000000000 --- a/developer/docs/help/context/kmlmi.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: kmlmi - Command Line Lexical Model model_info Compiler (deprecated) ---- - -kmlmi has been replaced by [kmc](../reference/kmc). diff --git a/developer/docs/help/context/kmlmp.md b/developer/docs/help/context/kmlmp.md deleted file mode 100644 index 3442d8f142..0000000000 --- a/developer/docs/help/context/kmlmp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: kmlmp - Command Line Lexical Model Package Compiler (deprecated) ---- - -kmlmp has been replaced by [kmc](../reference/kmc). diff --git a/developer/docs/help/context/new-ldml-project-parameters.md b/developer/docs/help/context/new-ldml-project-parameters.md index c1bf7cabb6..adbe623d70 100644 --- a/developer/docs/help/context/new-ldml-project-parameters.md +++ b/developer/docs/help/context/new-ldml-project-parameters.md @@ -2,9 +2,12 @@ title: New LDML Keyboard Project Parameters Dialog --- -Allows you to quickly fill in common parameters for a new LDML keyboard project, adding keyboard, package, documentation and metadata files, following the [file layout](/developer/keyboards/) used in the [Keyman keyboards repository](https://github.com/keymanapp/keyboards). +Allows you to quickly fill in common parameters for a new LDML keyboard project, +adding keyboard, package, documentation and metadata files, following the [file +layout](/developer/keyboards/) used in the [Keyman keyboards +repository](https://github.com/keymanapp/keyboards). -Projects can also be created from the command line with [KMConvert](kmconvert). +Projects can also be created from the command line with [kmc generate](kmc). ![Keyboard Editor - New file, Details tab](../images/ui/frmNewLDMLProjectParameters.png) @@ -12,25 +15,54 @@ Projects can also be created from the command line with [KMConvert](kmconvert). Keyboard Name -: **Required.** The descriptive name of the keyboard. This will be set in the - ` [!IMPORTANT] + > Images should not be included in the first paragraph. + + * Learn more about the [keyboard repository](https://help.keyman.com/developer/keyboards/) + * The description can be edited later in the [Package Editor](package-editor). Author : Optional. The name of the developer of the keyboard. This will be set in the package metadata, and where appropriate in documentation and metadata. -Copyright, Full Copyright +Copyright -: Optional. A copyright string for the keyboard. This will be set in the - package metadata, and where appropriate in documentation and metadata. +: Optional. A copyright string for the keyboard, excluding copyright year(s). + This will be set in the package metadata, and where appropriate in + documentation and metadata. + +Full Copyright + +: The same copyright string for the keyboard, including copyright year. Version diff --git a/developer/docs/help/context/new-lm-project-parameters.md b/developer/docs/help/context/new-lm-project-parameters.md index ad81590346..e20f800b19 100644 --- a/developer/docs/help/context/new-lm-project-parameters.md +++ b/developer/docs/help/context/new-lm-project-parameters.md @@ -11,29 +11,44 @@ information following the file layout used in the [Keyman lexical-models reposit ### Parameters Author Name -: The name of the developer of the lexical model. + +: **Required.** The name of the developer of the lexical model. Model Name -: The descriptive name of the lexical model. This should be a unique - name to distinguish this lexical model from others in the same + +: **Required.** The descriptive name of the lexical model. This should be a + unique name to distinguish this lexical model from others in the same language. Copyright -: A copyright string for the model. This will be set in the the - package metadata, and where appropriate in documentation and + +: A copyright string for the model, excluding copyright year(s). This will be + set in the the package metadata, and where appropriate in documentation and metadata. +Full Copyright + +: The same copyright string for the keyboard, including copyright year. + Version -: The initial version number of the model should usually be left at - 1.0. This will be set also in the package metadata, and where - appropriate in documentation and metadata. The version string is - made of `major revision`.`minor revision`. When releasing - significant updates to the model, increment the major revision (e.g. - 2.0). Increment the minor revision (e.g. 1.1) for small changes to - the model. + +: **Required.** The initial version number of the model should usually be left + at 1.0. This will be set also in the package metadata, and where appropriate + in documentation and metadata. The version string is made of `major + revision`.`minor revision`. When releasing significant updates to the model, + increment the major revision (e.g. 2.0). Increment the minor revision (e.g. + 1.1) for small changes to the model. Languages -: Specifies the default BCP 47 language tags which will be added to - the [package metadata](../reference/file-types/metadata) and project + +: **Required.** Specifies the default BCP 47 language tags which will be added + to the [package metadata](../reference/file-types/metadata) and project metadata. In order to install and use a lexical model with a Keyman - keyboard, the BCP 47 language tags for both must be identical. \ No newline at end of file + keyboard, the BCP 47 language tags for both must be identical. + +Path + +: **Required.** Specifies the base path where the project folder will be + created. The project folder name will be the model ID. If the folder already + exists, then you will be prompted before Keyman Developer overwrites files + inside it. diff --git a/developer/docs/help/context/new-project-parameters.md b/developer/docs/help/context/new-project-parameters.md index 6cbde2c3d3..ff59ed2601 100644 --- a/developer/docs/help/context/new-project-parameters.md +++ b/developer/docs/help/context/new-project-parameters.md @@ -4,56 +4,101 @@ title: New Project Parameters Dialog ![New Project Parameters dialog](../images/ui/frmNewProjectParameters.png) -Allows you to quickly fill in common parameters for a new keyboard -project, adding keyboard, package, documentation and metadata files, -following the [file layout](/developer/keyboards/) used in the [Keyman keyboards repository](https://github.com/keymanapp/keyboards). +Allows you to quickly fill in common parameters for a new keyboard project, +adding keyboard, package, documentation and metadata files, following the +[file layout](/developer/keyboards/) used in the +[Keyman keyboards repository](https://github.com/keymanapp/keyboards). Projects can also be created from the command line with -[KMConvert](kmconvert). +[kmc generate](kmc). ### Parameters Keyboard Name -: The descriptive name of the keyboard. This will be set in the - [`&Name`](/developer/language/reference/name) store in the keyboard, - in the package name, and where appropriate in documentation and + +: **Required.** The name of the keyboard presented to end users. This will be + set in the [`&Name`](/developer/language/reference/name) store in the + keyboard, in the package name, and where appropriate in documentation and metadata. -Copyright -: A copyright string for the keyboard. This will be set in the - [`&Copyright`](/developer/language/reference/copyright) store in the - keyboard, in the package metadata, and where appropriate in - documentation and metadata. + Choose the keyboard name carefully; while it is possible to change it later, + you will have to update the name in a number of locations. -Version -: The initial version number of the keyboard. This should follow the - format specified in the - [`&Keyboardversion`](/developer/language/reference/keyboardversion) - store for the keyboard. This will be set also in the package - metadata, and where appropriate in documentation and metadata. +Description + +: **Required.** This field should describe your keyboard to people who find it + online. It should explain the purpose of the keyboard in a friendly, + descriptive way. The description could include details that will help users + to decide if this keyboard is the appropriate choice for their needs. + +: The first paragraph of the description will appear in the keyboard search + result on keyman.com; the full description will be shown in the keyboard + details page on keyman.com. Use + [Markdown](https://www.markdownguide.org/basic-syntax) to include basic + formatting, but not embedded HTML is not allowed. + +: To add images to the description, you will need to reference an image on a + server accessible online. For example, to use images from the Keyman + keyboard repository where this keyboard can be uploaded, you can use the + GitHub source. The URL starts with + https://raw.githubusercontent.com/keymanapp/keyboards/master/ + the path to + the image within the repository. For example: + + ```md + ![GFF Mesobe Fidelat keyboard](https://raw.githubusercontent.com/keymanapp/keyboards/master/release/gff/gff_mesobe_fidelat/source/help/images/gff_mesobe_fidelat-default-1.jpeg) + ``` + + > [!IMPORTANT] + > Images should not be included in the first paragraph. + + * Learn more about the [keyboard repository](https://help.keyman.com/developer/keyboards/) + * The description can be edited later in the [Package Editor](package-editor). Author -: The name of the developer of the keyboard. This will be set in the - package metadata, and where appropriate in documentation and - metadata. + +: The name of the developer of the keyboard. This will be set in the package + metadata, and where appropriate in documentation and metadata. + +Copyright + +: A copyright string for the keyboard, excluding copyright year(s). This will + be set in the [`&Copyright`](/developer/language/reference/copyright) store + in the keyboard, in the package metadata, and where appropriate in + documentation and metadata. + +Full Copyright + +: The same copyright string for the keyboard, including copyright year. + +Version + +: **Required.** The initial version number of the keyboard. This should follow + the format specified in the + [`&Keyboardversion`](/developer/language/reference/keyboardversion) store + for the keyboard. This will be set also in the package metadata, and where + appropriate in documentation and metadata. Targets -: Specifies the default deployment targets for the keyboard, set in - the [`&Targets`](/developer/language/reference/targets) store in the - keyboard, and controls the files added to the package initially. - This also is reflected in documentation and metadata. + +: **Required.** Specifies the default deployment targets for the keyboard, set + in the [`&Targets`](/developer/language/reference/targets) store in the + keyboard, and controls the files added to the package initially. This also + is reflected in documentation and metadata. Languages -: Specifies the default BCP 47 language tags which will be added to - the package metadata and project metadata. + +: Specifies the default BCP 47 language tags which will be added to the + package metadata and project metadata. Path -: Specifies the base path where the project folder will be created. - The project folder name will be the keyboard ID. If the folder - already exists, then you will be prompted before Keyman Developer - overwrites files inside it. + +: **Required.** Specifies the base path where the project folder will be + created. The project folder name will be the keyboard ID. If the folder + already exists, then you will be prompted before Keyman Developer overwrites + files inside it. Keyboard ID -: The base filename of the keyboard, project and package. This must - conform to the Keyman keyboard identifier rules, using the - characters a-z, 0-9 and _ (underscore) only. \ No newline at end of file + +: **Required.** The base filename of the keyboard, project and package. This + must conform to the Keyman keyboard identifier rules, using the characters + a-z, 0-9 and _ (underscore) only. \ No newline at end of file diff --git a/developer/docs/help/context/package-editor.md b/developer/docs/help/context/package-editor.md index 8beaa2dbc2..2ed0df0481 100644 --- a/developer/docs/help/context/package-editor.md +++ b/developer/docs/help/context/package-editor.md @@ -123,7 +123,7 @@ be displayed before the package is installed as information about the package. : Version numbers should be in the form `major.minor[.patch]`. Patch is optional but is helpful for small bug fix releases. Each of the sections of the version -should be an integer. Keyman Desktop does integer comparisons on the version +should be an integer. Keyman for Windows does integer comparisons on the version number components, so, for example, version 2.04 is regarded as newer than version 2.1. Alphabetic or date formats should be avoided as the installer for the keyboard cannot determine which version is older reliably. @@ -134,7 +134,11 @@ version" to have the package version automatically track the keyboard version. **Copyright** : The Copyright indicates the overall copyright of the package and all its -contents. +contents. It should be included here without the copyright year(s). + +**Full Copyright** + +: The same copyright string for the keyboard, including copyright year. **Author, E-mail address** @@ -155,16 +159,32 @@ then selected from the list here. **Description** -: The first paragraph of the description appears in the keyboard search result on keyman.com, the full description will be shown in the keyboard page. Writing in the right syntax will ensure your keyboard displays correctly in keyman.com. **"[Markdown](https://www.markdownguide.org/basic-syntax) accepted, no embedded HTML description"**. +: This field should describe your keyboard to people who find it online. It + should explain the purpose of the keyboard in a friendly, descriptive way. + The description could include details that will help users to decide if this + keyboard is the appropriate choice for their needs. -: To add images to the description, use the image syntax from Markdown. As long as you know where the images will be, you could specify the path before merging the PR. The URL starts https://raw.githubusercontent.com/keymanapp/keyboards/master/ + the path to the image within the repository. +: The first paragraph of the description will appear in the keyboard search + result on keyman.com; the full description will be shown in the keyboard + details page on keyman.com. Use + [Markdown](https://www.markdownguide.org/basic-syntax) to include basic + formatting, but not embedded HTML is not allowed. - ```md - ![GFF Mesobe Fidelat keyboard](https://raw.githubusercontent.com/keymanapp/keyboards/master/release/gff/gff_mesobe_fidelat/source/help/images/gff_mesobe_fidelat-default-1.jpeg) - ``` +: To add images to the description, you will need to reference an image on a + server accessible online. For example, to use images from the Keyman + keyboard repository where this keyboard can be uploaded, you can use the + GitHub source. The URL starts with + https://raw.githubusercontent.com/keymanapp/keyboards/master/ + the path to + the image within the repository. For example: - > [!IMPORTANT] - > Images should not be included in the first paragraph. + ```md + ![GFF Mesobe Fidelat keyboard](https://raw.githubusercontent.com/keymanapp/keyboards/master/release/gff/gff_mesobe_fidelat/source/help/images/gff_mesobe_fidelat-default-1.jpeg) + ``` + + > [!IMPORTANT] + > Images should not be included in the first paragraph. + + * Learn more about the [keyboard repository](https://help.keyman.com/developer/keyboards/) **Related packages** diff --git a/developer/docs/help/guides/develop/imx/index.md b/developer/docs/help/guides/develop/imx/index.md index 6bc606b8f9..2dd41dead9 100644 --- a/developer/docs/help/guides/develop/imx/index.md +++ b/developer/docs/help/guides/develop/imx/index.md @@ -27,7 +27,7 @@ The DLL should be placed in any of the following locations: The best option is the first, as you can then include the DLL in a Keyman package for easy installation and uninstallation. -Full x64 support for IMX DLLs was introduced in Keyman Desktop +Full x64 support for IMX DLLs was introduced in Keyman for Windows 8.0.333.0. An x64 version of the DLL may be included simply by giving it the file extension .x64.dll, with the same base name as the x86 DLL. Keyman will call the appropriate DLL. diff --git a/developer/docs/help/guides/distribute/index.md b/developer/docs/help/guides/distribute/index.md index 7b84bf472f..4ffe845c38 100644 --- a/developer/docs/help/guides/distribute/index.md +++ b/developer/docs/help/guides/distribute/index.md @@ -2,7 +2,7 @@ title: Distributing keyboards --- -- [Package development tutorial](tutorial) (Keyman Desktop, Keyman for macOS, Keyman for mobile apps) +- [Package development tutorial](tutorial) (Keyman for Windows, Keyman for macOS, Keyman for mobile apps) - [Distribute keyboards to Keyman applications](packages) ## See also diff --git a/developer/docs/help/guides/distribute/packages.md b/developer/docs/help/guides/distribute/packages.md index e3382220a3..5bc1e7de80 100644 --- a/developer/docs/help/guides/distribute/packages.md +++ b/developer/docs/help/guides/distribute/packages.md @@ -19,9 +19,9 @@ archive. You can distribute keyboards and lexical models in package files, but you can't include both in the same package file. -#### Keyman Desktop +#### Keyman for Windows -Keyman Desktop can install package files, including installing fonts, +Keyman for Windows can install package files, including installing fonts, creating Start Menu shortcuts, and adding appropriate registry entries for uninstallation. @@ -37,20 +37,20 @@ in Keyman Configuration. #### Keyman for Android and Keyman for iPhone and iPad Keyman mobile applications can install the same package files as Keyman -Desktop, as long as the package includes keyboards for touch layouts. +for Windows, as long as the package includes keyboards for touch layouts. ## Package file contents A package can have a variety of different files contained within. The following files and file types are recognized by the package installer: -\*.kmx (Desktop and macOS only) +\*.kmx (Keyman for Windows, Linux, and macOS only) : Keyboard files. Each of these will be installed. Keyman Configuration will not allow installation or uninstallation of a single keyboard from a package. They will always be treated as a group for installation and uninstallation. -\*.kvk (Desktop and macOS only) +\*.kvk (Keyman for Windows, Linux, and macOS only) : On Screen Keyboard files, associated with each keyboard file. \*.js (mobile only) @@ -91,7 +91,7 @@ kmp.json > ### Tip Read [Step 2: Organizing the Keyboard Files](../../../keyboards/github/step-2) -to know which files to include or exclude in the keybord folder. +to know which files to include or exclude in the keybord folder. Once the keyboard package .kmp file is created, you can share them via external storage devices (USB drive, SD card, etc). You can also share diff --git a/developer/docs/help/guides/distribute/tutorial/index.md b/developer/docs/help/guides/distribute/tutorial/index.md index ec57e57e10..89bc2eb2a9 100644 --- a/developer/docs/help/guides/distribute/tutorial/index.md +++ b/developer/docs/help/guides/distribute/tutorial/index.md @@ -21,7 +21,7 @@ Welcome! In this tutorial, you will learn how to create a of files, compressed into a single file, just like a ZIP file. It is designed to make installation of keyboards, fonts and related documentation straightforward for the end user. When done, the package -can be installed into Keyman Desktop through a simple dialog. Keyman for +can be installed into Keyman for Windows through a simple dialog. Keyman for mobile apps can also download and install keyboards through the keyboard package. @@ -33,4 +33,4 @@ package based on the Quick French keyboard from the [keyboard tutorial](../../de Let's get started! Move on to the next topic to begin the first step, choosing what to include in the package. -[Step 1: What do we include?](step-1) \ No newline at end of file +[Step 1: What do we include?](step-1) diff --git a/developer/docs/help/guides/distribute/tutorial/step-4.md b/developer/docs/help/guides/distribute/tutorial/step-4.md index b1860df2a1..fcebc042f6 100644 --- a/developer/docs/help/guides/distribute/tutorial/step-4.md +++ b/developer/docs/help/guides/distribute/tutorial/step-4.md @@ -10,7 +10,7 @@ many details as you can on this page. Package Name : The Package Name will be displayed in the package install dialog, - and within Keyman Desktop Configuration, wherever the package is + and within Keyman Configuration, wherever the package is referred to. Readme File @@ -31,7 +31,7 @@ Version Version numbers should be in the form `major.minor[.subversion]`. Subversion is optional but is helpful for small bug fix releases. Each of the sections of the version should be an integer. Keyman - Desktop does integer comparisons on the version numbers, so, for + for Windows does integer comparisons on the version numbers, so, for example, version `2.01` is regarded as older than version `2.2`. Alphabetic or date formats should be avoided as the installer for the keyboard cannot determine which version is older reliably. diff --git a/developer/docs/help/guides/distribute/tutorial/step-5.md b/developer/docs/help/guides/distribute/tutorial/step-5.md index eb24d70f13..f8c89fd635 100644 --- a/developer/docs/help/guides/distribute/tutorial/step-5.md +++ b/developer/docs/help/guides/distribute/tutorial/step-5.md @@ -31,19 +31,19 @@ package. Enter a description for the shortcut, and select the program or file to start from the Program list. Four predefined program entries (Start Product), (Product Configuration), (Product Help) and (About Product) will be translated into the appropriate shortcuts to start -Keyman Desktop tasks as described. +Keyman for Windows tasks as described. > ### Note In Keyman 6 and earlier, the predefined targets $KEYMAN\keyman.exe and $KEYMAN\kmshell.exe were available. These are translated to (Start -Product) and (Product Configuration), respectively, in Keyman Desktop +Product) and (Product Configuration), respectively, in Keyman 7.0 and later versions. > ### Tip Packages are not listed in the Control Panel Add/Remove Programs applet -with Keyman Desktop 7.0 and later versions. Packages can be uninstalled -through the Start Menu shortcut or from Keyman Desktop Configuration. +with Keyman 7.0 and later versions. Packages can be uninstalled +through the Start Menu shortcut or from Keyman Configuration. -[Step 6: Compiling, testing and distributing a Package](step-6) \ No newline at end of file +[Step 6: Compiling, testing and distributing a Package](step-6) diff --git a/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png index 6002ea9943..d1f4b57227 100644 Binary files a/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png and b/developer/docs/help/images/ui/frmNewLDMLProjectParameters.png differ diff --git a/developer/docs/help/images/ui/frmNewLMProjectParameters.png b/developer/docs/help/images/ui/frmNewLMProjectParameters.png index d58b569064..a0f3a722dd 100644 Binary files a/developer/docs/help/images/ui/frmNewLMProjectParameters.png and b/developer/docs/help/images/ui/frmNewLMProjectParameters.png differ diff --git a/developer/docs/help/images/ui/frmNewProjectParameters.png b/developer/docs/help/images/ui/frmNewProjectParameters.png index df93ecac2f..d4262810e5 100644 Binary files a/developer/docs/help/images/ui/frmNewProjectParameters.png and b/developer/docs/help/images/ui/frmNewProjectParameters.png differ diff --git a/developer/docs/help/index.md b/developer/docs/help/index.md index d7fbc4098d..0b2f452087 100644 --- a/developer/docs/help/index.md +++ b/developer/docs/help/index.md @@ -8,7 +8,7 @@ and tutorials, and full reference information. ## Guides and Tutorials - [What is Keyman Developer?](guides/intro) -- [What's new in 19.0](whats-new) +- [What's new in 19.0](whatsnew) - [Developing Keyman keyboard layouts](guides/develop) - [Testing Keyman keyboards](guides/test) - [Distributing Keyman keyboards](guides/distribute) @@ -18,7 +18,7 @@ and tutorials, and full reference information. ## Reference - [Keyman keyboard language reference](../language/reference) - [Keyman keyboard language guide](../language/guide) -- [Customising Keyman Engine for Desktop](../engine/desktop) +- [Customising Keyman Engine for Windows](../engine/windows) - [Error codes, File types and Tools](reference/) - [Keyman Developer user interface](context/) - [Additional Notes](main/) @@ -31,8 +31,8 @@ and tutorials, and full reference information. - [Keyboard Quality White Paper](/developer/whitepaper1.1.pdf) - [Knowledge Base](/kb) -- [Keyman Desktop Help](/products/desktop) -- [Keyman Engine for Desktop](/developer/engine/desktop/current-version/) +- [Keyman for Windows Help](/products/windows) +- [Keyman Engine for Windows](/developer/engine/windows/current-version/) - [Keyman Engine for Web](/developer/engine/web/) - [Keyman Engine for iPhone and iPad](/developer/engine/iphone-and-ipad/current-version/) -- [Keyman Engine for Android](/developer/engine/android/current-version/) \ No newline at end of file +- [Keyman Engine for Android](/developer/engine/android/current-version/) diff --git a/developer/docs/help/reference/file-types/metadata.md b/developer/docs/help/reference/file-types/metadata.md index 59d30375dc..8630872cf8 100644 --- a/developer/docs/help/reference/file-types/metadata.md +++ b/developer/docs/help/reference/file-types/metadata.md @@ -88,10 +88,15 @@ The `System` object is used by Keyman Desktop to install keyboards The version of Keyman Developer used to create the package file. If undefined, use `'0.0.0.0'` -`fileVersion,` +`fileVersion` : `string` + The minimum version of Keyman required to load the package metadata. Note that + the files within the package may have other version requirements. The lowest + version here will be `MIN_KBD_FILEVERSION_KMP_JSON` (7.0) for keyboard packages, + and `MIN_LM_FILEVERSION_KMP_JSON` (12.0) for lexical model packages. + ### The Options object The `Options` object is used by Keyman Desktop to install keyboards diff --git a/developer/docs/help/reference/kmc/cli/get-started.md b/developer/docs/help/reference/kmc/cli/get-started.md index cb3e9073e2..348bf4c4b9 100644 --- a/developer/docs/help/reference/kmc/cli/get-started.md +++ b/developer/docs/help/reference/kmc/cli/get-started.md @@ -149,13 +149,16 @@ referenced folder, e.g. `kmc build .`. [KMConvert](../../../context/kmconvert) generates keyboards and models from templates, and converts keyboard layouts between different formats. +[kmc generate][kmc] replicates most of the functionality in KMConvert, except for +importing Windows keyboards. + The [New Project dialog](../../../context/new-project) in Keyman Developer provides a graphical version of the most common functionality in KMConvert. * [KMConvert reference](../../../context/kmconvert) -**Note:** KMConvert is currently a Windows executable, and will be integrated -into kmc in an upcoming version of Keyman. +**Note:** KMConvert is currently a Windows executable, and the remaining +functionality will be integrated into kmc in an upcoming version of Keyman. [kmc]: ./reference [file-layout]: ../../file-layout diff --git a/developer/docs/help/reference/kmc/cli/kmcomp-migration.md b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md index c864e1bc90..e3cd9f28b7 100644 --- a/developer/docs/help/reference/kmc/cli/kmcomp-migration.md +++ b/developer/docs/help/reference/kmc/cli/kmcomp-migration.md @@ -5,9 +5,8 @@ title: Migrating from kmcomp to kmc `kmcomp` was the command-line compiler for Keyman Developer through version 16.0. Version 17.0 replaces `kmcomp` with `kmc`. -The lexical model command-line tooling, `kmlmc`, `kmlmp`, and `kmlmi`, are all -still present in version 17, but are deprecated, as the same tasks can be -performed with `kmc`. +The lexical model command-line tooling, `kmlmc`, `kmlmp`, and `kmlmi`, have +been removed in version 19.0; instead use `kmc`. ## Benefits @@ -99,21 +98,6 @@ kmcomp | kmc | notes ## Compiling a lexical model -`kmlmc` and `kmlmp` were separate tools in earlier versions of Keyman Developer, -for compiling lexical models and lexical model packages. They have both been -replaced with `kmc`. - -Old method, using kmlmc and kmlmp: - -```bash -kmlmc file.model.ts -# or specifying output filename -kmlmc -o output/path/file.model.js file.model.ts -kmlmp file.model.kps -``` - -New method, using kmc: - ```bash # recommended, build the model project: kmc build . diff --git a/developer/docs/help/reference/tools/setup.md b/developer/docs/help/reference/tools/setup.md index d628634f06..d88e07842f 100644 --- a/developer/docs/help/reference/tools/setup.md +++ b/developer/docs/help/reference/tools/setup.md @@ -4,14 +4,14 @@ title: Setup Bootstrapper # Setup Bootstrapper -The Keyman Desktop installation bootstrapper is a self-extracting executable file that contains a Windows Installer technology MSI file, and optionally keyboards. The bootstrapper is used whenever a package installer is created. +The Keyman for Windows installation bootstrapper is a self-extracting executable file that contains a Windows Installer technology MSI file, and optionally keyboards. The bootstrapper is used whenever a package installer is created. The bootstrapper: 1. Checks the system before starting the installation to ensure that all prerequisites are met, and optionally downloads and installs the prerequisites. -2. Optionally checks online for an updated version of Keyman Desktop before installing. -3. Starts the Keyman Desktop Windows Installer MSI package. +2. Optionally checks online for an updated version of Keyman for Windows before installing. +3. Starts the Keyman for Windows Windows Installer MSI package. 4. Installs any keyboards included. -5. Starts Keyman Desktop after the install completes. +5. Starts Keyman for Windows after the install completes. For command line parameters and more information see [Knowledge Base article KMKB0035](/kb/35) diff --git a/developer/docs/help/whats-new.md b/developer/docs/help/whatsnew.md similarity index 100% rename from developer/docs/help/whats-new.md rename to developer/docs/help/whatsnew.md diff --git a/developer/src/README.md b/developer/src/README.md index bb94888cb7..517b593eb3 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -84,7 +84,7 @@ in Keyman for Windows. ## kmc -node-based next generation compiler, hosts kmc, (and legacy kmlmc, kmlmp) +node-based next generation compiler, hosts kmc ### kmc-analyze - Analysis tools diff --git a/developer/src/build.sh b/developer/src/build.sh index 18ead3cb82..f8b7ec3808 100755 --- a/developer/src/build.sh +++ b/developer/src/build.sh @@ -6,8 +6,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/build/utils.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" -. "$KEYMAN_ROOT/developer/src/packages.inc.sh" builder_describe \ "Keyman Developer" \ @@ -17,7 +15,7 @@ builder_describe \ build \ test \ "api Analyze API and prepare API documentation" \ - "publish Prepare files for distribution, publish symbols, publish or pack npm packages, and build installer" \ + "publish Prepare files for distribution, publish symbols, and build installer" \ "install Install built programs locally" \ ":common Developer common files" \ ":ext Third party components" \ @@ -43,9 +41,7 @@ builder_describe \ ":setup Keyman Developer setup bootstrap" \ ":test=test/auto Various older tests (others in each module)" \ ":tike Keyman Developer IDE" \ - ":inst Bundled installers" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n+ Don't actually publish anything to external endpoints, just dry run" + ":inst Bundled installers" builder_describe_platform \ :ext win,delphi \ @@ -77,25 +73,6 @@ fi function do_prepublish() { builder_heading "prepublish - verify environment before build" - # - # Make sure that package*.json have not been modified before - # - - pushd "$KEYMAN_ROOT" >/dev/null - if git status --porcelain | grep -qP 'package(-lock)?\.json'; then - builder_echo "The following package.json files have been modified:" - git status --porcelain | grep -P 'package(-lock)?\.json' - builder_die "The publish action will not run until these files are checked in or reverted." - fi - popd - - # - # To ensure that we cache the top-level package.json, we must call this before - # the global publish - # - - ci_publish_cleanup - # # Verify that the Delphi environment is correct for a release build # @@ -139,44 +116,7 @@ builder_run_action api api-documenter markdown -i ../build/api -o .. #------------------------------------------------------------------------------------------------------------------- -function do_publish() { - #-------------------------------------------------------- - # TODO: Hard-coded calls to /common packages which need - # publishing; this should be able to be removed once we - # move the publish call to the top-level - - local DRY_RUN= NPM_PUBLISH= - - if builder_has_option --npm-publish; then - NPM_PUBLISH=--npm-publish - fi - - if builder_has_option --dry-run; then - DRY_RUN=--dry-run - fi - - ./common/web/utils/build.sh publish $DRY_RUN $NPM_PUBLISH - ../../common/web/keyman-version/build.sh publish $DRY_RUN $NPM_PUBLISH - ../../common/web/langtags/build.sh publish $DRY_RUN $NPM_PUBLISH - ../../common/web/types/build.sh publish $DRY_RUN $NPM_PUBLISH - ../../core/include/ldml/build.sh publish $DRY_RUN $NPM_PUBLISH - # end TODO - #-------------------------------------------------------- - - builder_echo info "Cleaning up package.json after 'npm version'" - # And then cleanup the mess - ci_publish_cleanup - # Restore all the package.json files and package-lock.json files that - # were clobbered by 'npm version' - pushd "$KEYMAN_ROOT" >/dev/null - git checkout package.json package-lock.json '**/package.json' - popd - - # TODO: Copy ../build/docs {from api action} to help.keyman.com and open PR -} - builder_run_child_actions publish -builder_run_action publish do_publish #------------------------------------------------------------------------------------------------------------------- diff --git a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas index c6a703581b..54e2c87ee8 100644 --- a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas +++ b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas @@ -36,6 +36,7 @@ type TMemoSelection = record Start, Finish: Integer; Anchor: Integer; + function ToString: string; end; TKeymanDeveloperDebuggerMemo = class(TRichEdit41) @@ -107,6 +108,8 @@ end; function TKeymanDeveloperDebuggerMemo.GetSelection: TMemoSelection; begin + Result.Anchor := 0; + // EM_GETSEL doesn't tell us the anchor position, but we can figure // it out with this kludge. I am not aware of side effects from this // at this time. @@ -170,4 +173,11 @@ begin RegisterComponents('Keyman', [TKeymanDeveloperDebuggerMemo]); end; +{ TMemoSelection } + +function TMemoSelection.ToString: string; +begin + Result := Format('%d %d %d', [Start, Finish, Anchor]); +end; + end. diff --git a/developer/src/common/delphi/packages/kpsfile.pas b/developer/src/common/delphi/packages/kpsfile.pas index d92c8b9ae7..87bd7b2296 100644 --- a/developer/src/common/delphi/packages/kpsfile.pas +++ b/developer/src/common/delphi/packages/kpsfile.pas @@ -70,6 +70,7 @@ type procedure DoSaveJSON(ARoot: TJSONObject); override; procedure DoLoadIni(ini: TIniFile); override; procedure DoSaveIni(ini: TIniFile); override; + procedure Cleanup; override; public function KPSOptions: TKPSOptions; @@ -110,6 +111,15 @@ begin FStrings.Assign((Source as TKPSFile).Strings); end; +procedure TKPSFile.Cleanup; +begin + inherited; + if KPSOptions.FollowKeyboardVersion then + begin + Info.Desc[PackageInfo_Version] := ''; + end; +end; + constructor TKPSFile.Create; begin FStrings := TStringList.Create; diff --git a/developer/src/common/include/kmn_compiler_errors.h b/developer/src/common/include/kmn_compiler_errors.h index 44dc5e3359..f5d623ae28 100644 --- a/developer/src/common/include/kmn_compiler_errors.h +++ b/developer/src/common/include/kmn_compiler_errors.h @@ -67,7 +67,7 @@ namespace CompilerErrorSeverity { // code). namespace KmnCompilerMessages { - enum { + enum KmnCompilerMessages { FATAL_BadCallParams = SevFatal | 0x002, FATAL_CannotAllocateMemory = SevFatal | 0x004, ERROR_InfileNotExist = SevError | 0x005, // #10678: reduced from fatal to error in 17.0 @@ -277,6 +277,14 @@ namespace KmnCompilerMessages { ERROR_TextBeforeOrAfterNulInOutput = SevError | 0x0B3, + ERROR_NameMustBeAtLeastOneCharLong = SevError | 0x0B4, + ERROR_NameMustBeAtMostNCharsLong = SevError | 0x0B5, + ERROR_NameContainsInvalidCharacter = SevError | 0x0B6, + ERROR_NameMustNotContainSpaces = SevError | 0x0B7, + ERROR_NameMustNotContainComma = SevError | 0x0B8, + ERROR_NameMustNotContainParentheses = SevError | 0x0B9, + ERROR_NameMustNotContainSquareBrackets = SevError | 0x0BA, + FATAL_BufferOverflow = SevFatal | 0x0C0 // FATAL_Break = SevFatal | 0x0C1, unused }; diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh index 0f5aacbac5..fd94a8ab66 100755 --- a/developer/src/common/web/utils/build.sh +++ b/developer/src/common/web/utils/build.sh @@ -8,7 +8,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Build Keyman Developer web utility module" \ "@/common/web/types" \ @@ -16,10 +15,7 @@ builder_describe "Build Keyman Developer web utility module" \ "configure" \ "build" \ "api analyze API and prepare API documentation (no-op for now)" \ - "test" \ - publish \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -51,5 +47,4 @@ function do_build() { builder_run_action clean rm -rf ./build/ builder_run_action configure node_select_version_and_npm_ci builder_run_action build do_build -builder_run_action test typescript_run_eslint_mocha_tests 45 -builder_run_action publish ci_publish_npm +builder_run_action test typescript_run_eslint_mocha_tests 40 diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 089800c92f..3bf769dbad 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -43,6 +43,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/common/web/utils" } } diff --git a/developer/src/inst/License.rtf b/developer/src/inst/License.rtf index 472971f09a..25bd35b0ec 100644 Binary files a/developer/src/inst/License.rtf and b/developer/src/inst/License.rtf differ diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 8b37048e1e..5d44f228eb 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -16,7 +16,7 @@ builder_describe "Installation files for Keyman Developer" \ # after all other builds complete builder_describe_outputs \ - publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe + publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -78,7 +78,7 @@ function do_publish() { # Copy the installation archive # - copy-kmdev + copy-keymandeveloper-installer-to-artifacts builder_if_release_build_level verify-installer-signatures } @@ -87,8 +87,8 @@ function do_test() { verify-node-installer-version } -function copy-kmdev() { - builder_heading copy-kmdev +function copy-keymandeveloper-installer-to-artifacts() { + builder_heading copy-keymandeveloper-installer-to-artifacts mkdir -p "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}" @@ -96,7 +96,7 @@ function copy-kmdev() { make-kmc-install-zip cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper.msi" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper.msi" - cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION}.exe" + cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe" } function verify-program-signatures() { @@ -234,8 +234,8 @@ function make-installer() { echo "MSIFileName=keymandeveloper.msi" >> setup.inf echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf add_zip_files setup.zip keymandeveloper.msi setup.inf - cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION.exe" - wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION.exe" + cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION_FOR_FILENAME.exe" + wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION_FOR_FILENAME.exe" # # Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip @@ -251,7 +251,7 @@ function make-kmc-install-zip() { cd "${DEVELOPER_ROOT}/bin" # TODO: rename this to keyman-developer-cli-$Version.zip - local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION}.zip" + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION_FOR_FILENAME}.zip" add_zip_files "${KMCOMP_ZIP}" -q -r \ kmconvert.exe \ diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index c57de43d82..8b10c138c7 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -262,14 +262,6 @@ - - - - - - - - diff --git a/developer/src/inst/node/kmlmc.cmd b/developer/src/inst/node/kmlmc.cmd deleted file mode 100644 index be3bdf06e0..0000000000 --- a/developer/src/inst/node/kmlmc.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@rem This script avoids path dependencies for node for distribution -@rem with Keyman Developer. When used on platforms other than Windows, -@rem node can be used directly with the compiler (`npm link` will setup). -@"%~dp0\node.js\node.exe" "%~dp0\kmc\kmlmc.mjs" %* diff --git a/developer/src/inst/node/kmlmp.cmd b/developer/src/inst/node/kmlmp.cmd deleted file mode 100644 index 2062ae091c..0000000000 --- a/developer/src/inst/node/kmlmp.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@rem This script avoids path dependencies for node for distribution -@rem with Keyman Developer. When used on platforms other than Windows, -@rem node can be used directly with the compiler (`npm link` will setup). -@"%~dp0\node.js\node.exe" "%~dp0\kmc\kmlmp.mjs" %* diff --git a/developer/src/kmanalyze/kmanalyze.vcxproj b/developer/src/kmanalyze/kmanalyze.vcxproj index 20fd857a35..da59aaf833 100644 --- a/developer/src/kmanalyze/kmanalyze.vcxproj +++ b/developer/src/kmanalyze/kmanalyze.vcxproj @@ -1,210 +1,210 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {4EAE6DFD-E18A-493D-A00F-4846A2593F56} - Win32Proj - kmanalyze - 10.0 - - - - Application - true - v142 - MultiByte - - - Application - false - v142 - true - MultiByte - - - Application - true - v142 - MultiByte - - - Application - false - v142 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreadedDebug - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - true - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - pch.h - MultiThreaded - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - - - Console - true - true - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - Create - Create - Create - Create - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {4EAE6DFD-E18A-493D-A00F-4846A2593F56} + Win32Proj + kmanalyze + 10.0 + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreadedDebug + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + MultiThreaded + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + + + Console + true + true + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.x64.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + Create + Create + Create + Create + + + + + + + + \ No newline at end of file diff --git a/developer/src/kmc-analyze/build.sh b/developer/src/kmc-analyze/build.sh index 5dc53b4312..f28c822b5a 100755 --- a/developer/src/kmc-analyze/build.sh +++ b/developer/src/kmc-analyze/build.sh @@ -8,15 +8,12 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Keyman Developer Compiler Analysis Tools" \ "@/common/web/types" \ "@/developer/src/kmc-kmn" \ "@/developer/src/common/web/utils" \ - clean configure build api test publish \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + clean configure build api test builder_describe_outputs \ configure /node_modules \ @@ -31,5 +28,4 @@ builder_run_action clean rm -rf ./build/ builder_run_action configure node_select_version_and_npm_ci builder_run_action build tsc --build builder_run_action api api-extractor run --local --verbose -builder_run_action test typescript_run_eslint_mocha_tests 70 -builder_run_action publish ci_publish_npm +builder_run_action test typescript_run_eslint_mocha_tests 75 diff --git a/developer/src/kmc-analyze/package.json b/developer/src/kmc-analyze/package.json index 496e04be05..c7e2ebabe2 100644 --- a/developer/src/kmc-analyze/package.json +++ b/developer/src/kmc-analyze/package.json @@ -43,6 +43,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-analyze" } } diff --git a/developer/src/kmc-analyze/src/osk-character-use/index.ts b/developer/src/kmc-analyze/src/osk-character-use/index.ts index de725b9b69..7058ad6776 100644 --- a/developer/src/kmc-analyze/src/osk-character-use/index.ts +++ b/developer/src/kmc-analyze/src/osk-character-use/index.ts @@ -406,4 +406,10 @@ export class AnalyzeOskCharacterUse { return result.join(' '); } + /** @internal */ + public unitTestEndPoints = { + loadPreviousMap: this.loadPreviousMap.bind(this), + addStrings: this.addStrings.bind(this), + stringToUnicodeSequence: AnalyzeOskCharacterUse.stringToUnicodeSequence + }; } \ No newline at end of file diff --git a/developer/src/kmc-analyze/test/analyzer-messages.tests.ts b/developer/src/kmc-analyze/test/analyzer-messages.tests.ts index c6fced0945..7839c7fe39 100644 --- a/developer/src/kmc-analyze/test/analyzer-messages.tests.ts +++ b/developer/src/kmc-analyze/test/analyzer-messages.tests.ts @@ -1,11 +1,13 @@ /* * Keyman is copyright (C) SIL Global. MIT License. */ -// import { assert } from 'chai'; +import { assert } from 'chai'; import 'mocha'; import { AnalyzerMessages } from '../src/analyzer-messages.js'; import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; import { CompilerErrorNamespace } from '@keymanapp/developer-utils'; +import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js'; + describe('AnalyzerMessages', function () { @@ -25,5 +27,19 @@ describe('AnalyzerMessages', function () { return verifyCompilerMessagesObject(AnalyzerMessages, CompilerErrorNamespace.Analyzer); }); - // TODO: test each message + it('should generate Warn_PreviousMapFileCouldNotBeLoaded when previous map file is missing or invalid', function () { + const analyzer = new AnalyzeOskCharacterUse(callbacks); + + const invalidFile = 'nonexistent-file.json'; + + assert.isNull( + analyzer.unitTestEndPoints.loadPreviousMap(invalidFile), + 'Expected null when map file cannot be loaded' + ); + + assert.isTrue( + callbacks.hasMessage(AnalyzerMessages.WARN_PreviousMapFileCouldNotBeLoaded), + 'Expected warning when previous map file could not be loaded'); + }); }); + diff --git a/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-no-counts.json b/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-no-counts.json new file mode 100644 index 0000000000..713091194b --- /dev/null +++ b/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-no-counts.json @@ -0,0 +1,10 @@ +{ + "map": [ + { + "usages": [ + "U+1780", + "U+1781" + ] + } + ] +} \ No newline at end of file diff --git a/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-with-counts.json b/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-with-counts.json new file mode 100644 index 0000000000..bec0e8727b --- /dev/null +++ b/developer/src/kmc-analyze/test/fixtures/osk-character-use/mock-map-with-counts.json @@ -0,0 +1,12 @@ +{ + "map": [ + { + "usages": [ + { + "char": "U+1780", + "count": 2 + } + ] + } + ] +} \ No newline at end of file diff --git a/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts b/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts new file mode 100644 index 0000000000..9acd87403f --- /dev/null +++ b/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts @@ -0,0 +1,70 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { assert } from 'chai'; +import 'mocha'; +import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; + +describe('AnalyzeOskCharacterUse output formats', function() { + const callbacks = new TestCompilerCallbacks(); + + const dummyStrings = { + 'a': [{ filename: 'file1.kvks', count: 1 }], + 'b': [{ filename: 'file2.kvks', count: 2 }] + }; + + this.beforeEach(function() { + callbacks.clear(); + }); + + this.afterEach(function() { + if (this.currentTest?.isFailed()) { + callbacks.printMessages(); + } + }); + + it('generates .txt format correctly', function() { + const a = new AnalyzeOskCharacterUse(callbacks, { includeCounts: true }); + a.unitTestEndPoints.addStrings(['a', 'b'], 'testfile.kvks'); + const txt = a.getStrings('.txt'); + assert.isTrue(txt.some(line => line.includes('U+'))); + assert.match(txt[0], /^U\+[A-F0-9]{4}/); + }); + + it('generates .md format correctly', function() { + const a = new AnalyzeOskCharacterUse(callbacks, { includeCounts: true }); + (a as any)._strings = dummyStrings; + const md = a.getStrings('.md'); + // test header + assert.match(md[0], /^PUA\s+\|\s+Code Points\s+\|\s+Key Caps$/); + // test data + assert.match(md[2], /^U\+[A-F0-9]{4}\s+\|\s+U\+[A-F0-9]{4}\s+\|\s+\S/); + }); + + it('generates .json format correctly', function() { + const a = new AnalyzeOskCharacterUse(callbacks, { includeCounts: true }); + (a as any)._strings = dummyStrings; + const json = a.getStrings('.json').join('\n'); + const parsed = JSON.parse(json); + assert.isArray(parsed.map); + assert.lengthOf(parsed.map, 2); + assert.equal(parsed.map[0].usages[0].filename, 'file1.kvks'); + assert.equal(parsed.map[1].usages[0].filename, 'file2.kvks'); + }); + + describe('unitTestEndPoints', function() { + it('converts BMP strings (U+0000–U+FFFF) correctly', function() { + const a = new AnalyzeOskCharacterUse(callbacks); + const seq = a.unitTestEndPoints.stringToUnicodeSequence('ab'); + assert.equal(seq, 'U+0061 U+0062'); + }); + + it('converts supplementary characters (U+10000–U+10FFFF) correctly', function() { + const a = new AnalyzeOskCharacterUse(callbacks); + // 😀 (U+1F600) and 🦊 (U+1F98A) + const seq = a.unitTestEndPoints.stringToUnicodeSequence('😀🦊'); + assert.equal(seq, 'U+1F600 U+1F98A'); + }); + }); +}); \ No newline at end of file diff --git a/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts b/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts new file mode 100644 index 0000000000..540ef3cb41 --- /dev/null +++ b/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts @@ -0,0 +1,60 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { assert } from 'chai'; +import 'mocha'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js'; +import { AnalyzerMessages } from '../src/analyzer-messages.js'; +import { makePathToFixture } from './helpers/index.js'; + +describe('AnalyzeOskCharacterUse warnings', function() { + const callbacks = new TestCompilerCallbacks(); + + const MOCK_MAP_NO_COUNTS = makePathToFixture( + 'osk-character-use', + 'mock-map-no-counts.json' + ); + const MOCK_MAP_WITH_COUNTS = makePathToFixture( + 'osk-character-use', + 'mock-map-with-counts.json' + ); + + this.beforeEach(function() { + callbacks.clear(); + }); + + this.afterEach(function() { + if (this.currentTest?.isFailed()) { + callbacks.printMessages(); + } + }); + + it('warns if previous map did not include counts but includeCounts=true', function() { + const a = new AnalyzeOskCharacterUse(callbacks, { + includeCounts: true + }); + + const result = a.unitTestEndPoints.loadPreviousMap(MOCK_MAP_NO_COUNTS); + assert.isNotNull(result, 'Expected map to be loaded successfully'); + + assert.isTrue( + callbacks.hasMessage(AnalyzerMessages.WARN_PreviousMapDidNotIncludeCounts), + 'Expected Warn_PreviousMapDidNotIncludeCounts warning' + ); + }); + + it('warns if previous map did include counts but includeCounts=false', function() { + const a = new AnalyzeOskCharacterUse(callbacks, { + includeCounts: false + }); + + const result = a.unitTestEndPoints.loadPreviousMap(MOCK_MAP_WITH_COUNTS); + assert.isNotNull(result, 'Expected map to be loaded successfully'); + + assert.isTrue( + callbacks.hasMessage(AnalyzerMessages.WARN_PreviousMapDidIncludeCounts), + 'Expected Warn_PreviousMapDidIncludeCounts warning' + ); + }); +}); diff --git a/developer/src/kmc-convert/src/convert-utils.ts b/developer/src/kmc-convert/src/convert-utils.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/developer/src/kmc-copy/build.sh b/developer/src/kmc-copy/build.sh index 45e9029f09..fe9ffc1086 100755 --- a/developer/src/kmc-copy/build.sh +++ b/developer/src/kmc-copy/build.sh @@ -11,16 +11,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Build Keyman kmc-copy module" \ "@/common/web/keyman-version" \ "@/common/web/types" \ "@/developer/src/common/web/test-helpers" \ "@/developer/src/common/web/utils" \ - clean configure build api test publish \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + clean configure build api test builder_describe_outputs \ configure /node_modules \ @@ -42,5 +39,3 @@ builder_run_action api api-extractor run --local --verbose # note: `export TEST_SAVE_FIXTURES=1` to get a copy of cloud-based fixtures saved to online/ # TODO: -skip-full builder_run_action test typescript_run_eslint_mocha_tests 75 - -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-copy/package.json b/developer/src/kmc-copy/package.json index 130ada5681..1b3f8de371 100644 --- a/developer/src/kmc-copy/package.json +++ b/developer/src/kmc-copy/package.json @@ -58,6 +58,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-copy" } } diff --git a/developer/src/kmc-generate/build.sh b/developer/src/kmc-generate/build.sh index c02a7ed26a..d67cbaf21a 100755 --- a/developer/src/kmc-generate/build.sh +++ b/developer/src/kmc-generate/build.sh @@ -11,7 +11,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Build Keyman kmc-generate module" \ "@/common/web/keyman-version" \ @@ -19,9 +18,7 @@ builder_describe "Build Keyman kmc-generate module" \ "@/common/web/types" \ "@/developer/src/common/web/test-helpers" \ "@/developer/src/common/web/utils" \ - clean configure build api test publish \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + clean configure build api test builder_describe_outputs \ configure /node_modules \ @@ -44,4 +41,3 @@ builder_run_action configure node_select_version_and_npm_ci builder_run_action build do_build builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-generate/package.json b/developer/src/kmc-generate/package.json index da581a127e..20738b0d2a 100644 --- a/developer/src/kmc-generate/package.json +++ b/developer/src/kmc-generate/package.json @@ -59,6 +59,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-generate" } } diff --git a/developer/src/kmc-keyboard-info/build.sh b/developer/src/kmc-keyboard-info/build.sh index 36a840212f..fe8ecfac58 100755 --- a/developer/src/kmc-keyboard-info/build.sh +++ b/developer/src/kmc-keyboard-info/build.sh @@ -18,10 +18,7 @@ builder_describe "Build Keyman kmc keyboard-info Compiler module" \ "configure" \ "build" \ "api analyze API and prepare API documentation" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -39,7 +36,3 @@ builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests #------------------------------------------------------------------------------------------------------------------- - -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" - -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 13af66aae0..5b0c463b3a 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -43,6 +43,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-keyboard-info" } } diff --git a/developer/src/kmc-kmn/build.sh b/developer/src/kmc-kmn/build.sh index 7d94f880df..27fb958486 100755 --- a/developer/src/kmc-kmn/build.sh +++ b/developer/src/kmc-kmn/build.sh @@ -11,7 +11,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Keyman Developer Compiler Module for .kmn to .kmx" \ "@/common/web/keyman-version" \ @@ -23,10 +22,7 @@ builder_describe "Keyman Developer Compiler Module for .kmn to .kmx" \ "build" \ "clean" \ "api analyze API and prepare API documentation" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -71,7 +67,3 @@ function do_test() { builder_run_action build do_build builder_run_action api api-extractor run --local --verbose builder_run_action test do_test - -#------------------------------------------------------------------------------------------------------------------- - -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-kmn/package.json b/developer/src/kmc-kmn/package.json index 34e076b5c0..f9c82fb4e9 100644 --- a/developer/src/kmc-kmn/package.json +++ b/developer/src/kmc-kmn/package.json @@ -61,6 +61,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-kmn" } } diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts index 46f573f253..b85a7086e6 100644 --- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts +++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts @@ -773,6 +773,58 @@ export class KmnCompilerMessages { * [\`use()\`](https://help.keyman.com/developer/language/reference/use)` ); + static ERROR_NameMustBeAtLeastOneCharLong = SevError | 0x0B4; + static Error_NameMustBeAtLeastOneCharLong = () => m( + this.ERROR_NameMustBeAtLeastOneCharLong, + `The name parameter is required`, + ); + + static ERROR_NameMustBeAtMostNCharsLong = SevError | 0x0B5; + static Error_NameMustBeAtMostNCharsLong = (o:{name:string,length:string}) => m( + this.ERROR_NameMustBeAtMostNCharsLong, + `The referenced name '${def(o.name)}' must be at most ${def(o.length)} characters long`, + ); + + static ERROR_NameContainsInvalidCharacter = SevError | 0x0B6; + static Error_NameContainsInvalidCharacter = (o:{name:string}) => m( + this.ERROR_NameContainsInvalidCharacter, + `The referenced name '${def(o.name)}' contains an invalid character`, + `Names (or identifiers) must not contain spaces, commas, parentheses, + square brackets, control characters, or Unicode non-characters.` + ); + + static ERROR_NameMustNotContainSpaces = SevError | 0x0B7; + static Error_NameMustNotContainSpaces = (o:{name:string}) => m( + this.ERROR_NameMustNotContainSpaces, + `The referenced name '${def(o.name)}' must not contain spaces`, + `Names (or identifiers) must not contain spaces, commas, parentheses, + square brackets, control characters, or Unicode non-characters.` + ); + + static ERROR_NameMustNotContainComma = SevError | 0x0B8; + static Error_NameMustNotContainComma = (o:{name:string}) => m( + this.ERROR_NameMustNotContainComma, + `The referenced name '${def(o.name)}' must not contain commas`, + `Names (or identifiers) must not contain spaces, commas, parentheses, + square brackets, control characters, or Unicode non-characters.` + ); + + static ERROR_NameMustNotContainParentheses = SevError | 0x0B9; + static Error_NameMustNotContainParentheses = (o:{name:string}) => m( + this.ERROR_NameMustNotContainParentheses, + `The referenced name '${def(o.name)}' must not contain opening or closing parentheses`, + `Names (or identifiers) must not contain spaces, commas, parentheses, + square brackets, control characters, or Unicode non-characters.` + ); + + static ERROR_NameMustNotContainSquareBrackets = SevError | 0x0BA; + static Error_NameMustNotContainSquareBrackets = (o:{name:string}) => m( + this.ERROR_NameMustNotContainSquareBrackets, + `The referenced name '${def(o.name)}' must not contain opening or closing square brackets`, + `Names (or identifiers) must not contain spaces, commas, parentheses, + square brackets, control characters, or Unicode non-characters.` + ); + static FATAL_BufferOverflow = SevFatal | 0x0C0; static Fatal_BufferOverflow = () => m(this.FATAL_BufferOverflow, `The compiler memory buffer overflowed`); diff --git a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts index b7e36cd581..4319108bc7 100644 --- a/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts +++ b/developer/src/kmc-kmn/src/kmw-compiler/kmw-compiler.ts @@ -270,7 +270,7 @@ export function WriteCompiledKeyboard( // Write the groups out // I853 - begin unicode missing causes crash - if (keyboard.startGroup.unicode == 0xFFFFFFFF) { + if (keyboard.startGroup.unicode == -1 || keyboard.startGroup.unicode == 0xFFFFFFFF) { callbacks.reportMessage(KmwCompilerMessages.Error_InvalidBegin()); return null; } diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/ansi.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/ansi.kmn new file mode 100644 index 0000000000..2fa668df51 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/ansi.kmn @@ -0,0 +1,13 @@ +c +c test for ERROR_InvalidBegin in kmw-compiler ('begin Unicode' is required) +c + +store(&version) '9.0' +store(&name) 'Test ANSI' +store(&targets) 'web' + +begin ANSI > use(main) + +group(main) using keys + ++ 'a' > 'aah' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-deadkey.kmn new file mode 100644 index 0000000000..83588fe15b --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-deadkey.kmn @@ -0,0 +1,14 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_contains_invalid_character-deadkey' + +begin unicode > use(main) + +group(main) using keys + + c the character between `test` and `test` below is a U+007F DEL, which may not + c be visible in all contexts. + + deadkey(testtest) + [K_A] > 'a' + + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-group.kmn new file mode 100644 index 0000000000..15e3325a41 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-group.kmn @@ -0,0 +1,14 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_contains_invalid_character-group' + +begin unicode > use(main) + +c the character between `test` and `test` below is a U+007F DEL, which may not +c be visible in all contexts. + +group(testtest) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-store.kmn new file mode 100644 index 0000000000..c73f7c5382 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_contains_invalid_character-store.kmn @@ -0,0 +1,13 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_contains_invalid_character-store' + +begin unicode > use(main) + +c the character between `test` and `test` below is a U+007F DEL, which may not +c be visible in all contexts. + +store(testtest) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-deadkey.kmn new file mode 100644 index 0000000000..2d04018ac1 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-deadkey.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +c Note: currently generates ERROR_InvalidDeadkey + +store(&NAME) 'error_name_must_be_at_least_one_char_long-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey() + [K_A] > 'a' + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-group.kmn new file mode 100644 index 0000000000..67da0be966 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_be_at_least_one_char_long-group' + +begin unicode > use(main) + +group() using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-store.kmn new file mode 100644 index 0000000000..8b22b49dfb --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_least_one_char_long-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_be_at_least_one_char_long-store' + +begin unicode > use(main) + +store() 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-deadkey.kmn new file mode 100644 index 0000000000..1dd348ed96 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-deadkey.kmn @@ -0,0 +1,9 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_be_at_most_n_chars_long-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey(12345678901234567890123456789012345678901234567890123456789012345678901234567890) + [K_A] > 'a' + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-group.kmn new file mode 100644 index 0000000000..3381076ef0 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_be_at_most_n_chars_long-group' + +begin unicode > use(main) + +group(12345678901234567890123456789012345678901234567890123456789012345678901234567890) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-store.kmn new file mode 100644 index 0000000000..d10e721233 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_be_at_most_n_chars_long-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_be_at_most_n_chars_long-store' + +begin unicode > use(main) + +store(12345678901234567890123456789012345678901234567890123456789012345678901234567890) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-deadkey.kmn new file mode 100644 index 0000000000..7d40c0f38f --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-deadkey.kmn @@ -0,0 +1,9 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_comma-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey(main,test) + [K_A] > 'a' + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-group.kmn new file mode 100644 index 0000000000..7efa666a4f --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_comma-group' + +begin unicode > use(main) + +group(main,test) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-store.kmn new file mode 100644 index 0000000000..db3a57e115 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_comma-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_comma-store' + +begin unicode > use(main) + +store(main,test) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-deadkey.kmn new file mode 100644 index 0000000000..69a338b34e --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-deadkey.kmn @@ -0,0 +1,9 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_parentheses-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey(test(test) + [K_A] > 'a' + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-group.kmn new file mode 100644 index 0000000000..8e80e252f6 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_parentheses-group' + +begin unicode > use(main) + +group(test(test) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-store.kmn new file mode 100644 index 0000000000..414f3764a5 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_parentheses-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_parentheses-store' + +begin unicode > use(main) + +store(test(test) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-deadkey.kmn new file mode 100644 index 0000000000..452af6e2bf --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-deadkey.kmn @@ -0,0 +1,9 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_spaces-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey(dead key) + [K_A] > 'a' + diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-group.kmn new file mode 100644 index 0000000000..1066b65ae2 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_spaces-group' + +begin unicode > use(main) + +group(my group) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-store.kmn new file mode 100644 index 0000000000..f7c4bdb210 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_spaces-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_spaces-store' + +begin unicode > use(main) + +store(my test) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-deadkey.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-deadkey.kmn new file mode 100644 index 0000000000..18aefbaaf0 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-deadkey.kmn @@ -0,0 +1,8 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_square_brackets-deadkey' + +begin unicode > use(main) + +group(main) using keys + deadkey(test[yeah]) + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-group.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-group.kmn new file mode 100644 index 0000000000..d20440989b --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-group.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_square_brackets-group' + +begin unicode > use(main) + +group(test[yeah]) using keys + + [K_A] > 'a' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-store.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-store.kmn new file mode 100644 index 0000000000..a019af519e --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_name_must_not_contain_square_brackets-store.kmn @@ -0,0 +1,10 @@ +c Description: Verifies that kmc reports an error with invalid names in groups, stores, and deadkeys + +store(&NAME) 'error_name_must_not_contain_square_brackets-store' + +begin unicode > use(main) + +store(test[yeah]) 'test' + +group(main) using keys + + [K_A] > 'a' diff --git a/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts b/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts index 74b471c36a..055535513d 100644 --- a/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts +++ b/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts @@ -80,7 +80,7 @@ describe('KmnCompilerMessages', function () { // Message tests // - async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) { + async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number | number[]) { context.timeout(10000); callbacks.clear(); @@ -94,7 +94,9 @@ describe('KmnCompilerMessages', function () { // Note: throwing away compile results (just to memory) await compiler.run(kmnPath, null); - if(messageId) { + if(Array.isArray(messageId)) { + assert.sameMembers(messageId, callbacks.messages.map(m=>m.code)); + } else if(messageId) { assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); assert.lengthOf(callbacks.messages, 1, `messages should have 1 entry, instead has: `+JSON.stringify(callbacks.messages,null,2)); } else { @@ -223,4 +225,64 @@ describe('KmnCompilerMessages', function () { await testForMessage(this, ['invalid-keyboards', 'error_text_before_or_after_nul_in_output_after.kmn'], KmnCompilerMessages.ERROR_TextBeforeOrAfterNulInOutput); }); + // Note: for historical reasons, invalid deadkeys can generate two errors: one + // for the name itself, the second being the return value from GetXString. + + // ERROR_NameMustBeAtLeastOneCharLong + + it('should generate ERROR_NameMustBeAtLeastOneCharLong if a required name is empty', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_least_one_char_long-group.kmn'], KmnCompilerMessages.ERROR_NameMustBeAtLeastOneCharLong); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_least_one_char_long-store.kmn'], KmnCompilerMessages.ERROR_NameMustBeAtLeastOneCharLong); + // Note, currently caught earlier by invalid deadkey check, keeping (misleadingly-named) test here anyway + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_least_one_char_long-deadkey.kmn'], KmnCompilerMessages.ERROR_InvalidDeadkey); + }); + + // ERROR_NameMustBeAtMostNCharsLong + + it('should generate ERROR_NameMustBeAtMostNCharsLong if a name is too long', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_most_n_chars_long-group.kmn'], KmnCompilerMessages.ERROR_NameMustBeAtMostNCharsLong); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_most_n_chars_long-store.kmn'], KmnCompilerMessages.ERROR_NameMustBeAtMostNCharsLong); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_be_at_most_n_chars_long-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustBeAtMostNCharsLong, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + + // ERROR_NameContainsInvalidCharacter + + it('should generate ERROR_NameContainsInvalidCharacter if the name contains a Unicode invalid/control character', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_contains_invalid_character-group.kmn'], KmnCompilerMessages.ERROR_NameContainsInvalidCharacter); + await testForMessage(this, ['invalid-keyboards', 'error_name_contains_invalid_character-store.kmn'], KmnCompilerMessages.ERROR_NameContainsInvalidCharacter); + await testForMessage(this, ['invalid-keyboards', 'error_name_contains_invalid_character-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameContainsInvalidCharacter, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + + // ERROR_NameMustNotContainSpaces + + it('should generate ERROR_NameMustNotContainSpaces if the name has a space', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_spaces-group.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainSpaces); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_spaces-store.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainSpaces); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_spaces-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustNotContainSpaces, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + + // ERROR_NameMustNotContainComma + + it('should generate ERROR_NameMustNotContainComma if the name has a comma', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_comma-group.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainComma); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_comma-store.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainComma); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_comma-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustNotContainComma, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + + // ERROR_NameMustNotContainParentheses + + it('should generate ERROR_NameMustNotContainParentheses if the name has parentheses', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_parentheses-group.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainParentheses); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_parentheses-store.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainParentheses); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_parentheses-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustNotContainParentheses, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + + // ERROR_NameMustNotContainSquareBrackets + + it('should generate ERROR_NameMustNotContainSquareBrackets if the name has square brackets', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_square_brackets-group.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainSquareBrackets); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_square_brackets-store.kmn'], KmnCompilerMessages.ERROR_NameMustNotContainSquareBrackets); + await testForMessage(this, ['invalid-keyboards', 'error_name_must_not_contain_square_brackets-deadkey.kmn'], [KmnCompilerMessages.ERROR_NameMustNotContainSquareBrackets, KmnCompilerMessages.ERROR_InvalidDeadkey]); + }); + }); diff --git a/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts b/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts index 3a6fc5dd0f..a69db77020 100644 --- a/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts +++ b/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts @@ -45,6 +45,12 @@ describe('KmwCompilerMessages', function () { await testForMessage(this, ['kmw', 'validate_gesture.kmn'], KmwCompilerMessages.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel); }); + // ERROR_InvalidBegin (from KmnCompilerMessages, but in KeymanWeb compiler context) + + it('should generate ERROR_InvalidBegin if the keyboard has no "begin unicode" statement', async function() { + await testForMessage(this, ['invalid-keyboards', 'ansi.kmn'], KmwCompilerMessages.ERROR_InvalidBegin); + }); + // TODO: other messages // WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb: diff --git a/developer/src/kmc-ldml/build.sh b/developer/src/kmc-ldml/build.sh index 2f3eb01bc2..708a4c9b3d 100755 --- a/developer/src/kmc-ldml/build.sh +++ b/developer/src/kmc-ldml/build.sh @@ -23,10 +23,7 @@ builder_describe "Keyman kmc Keyboard Compiler module" \ "api analyze API and prepare API documentation" \ "clean" \ "test" \ - "build-fixtures builds test fixtures for manual examination" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "build-fixtures builds test fixtures for manual examination" builder_describe_outputs \ configure /developer/src/kmc-ldml/src/util/abnf/46/transform-from-required.js \ @@ -90,9 +87,3 @@ builder_run_action build do_build builder_run_action build-fixtures do_build_fixtures builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests 90 - -#------------------------------------------------------------------------------------------------------------------- - -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" - -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index ce0a83e18e..9e2a1c6831 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -65,6 +65,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-ldml" } } diff --git a/developer/src/kmc-model-info/build.sh b/developer/src/kmc-model-info/build.sh index 00983ae6ea..fb4743700f 100755 --- a/developer/src/kmc-model-info/build.sh +++ b/developer/src/kmc-model-info/build.sh @@ -15,10 +15,7 @@ builder_describe "Build Keyman kmc Lexical Model model-info Compiler module" \ "configure" \ "build" \ "api analyze API and prepare API documentation" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -34,9 +31,3 @@ builder_run_action configure node_select_version_and_npm_ci builder_run_action build tsc --build builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests 55 - -#------------------------------------------------------------------------------------------------------------------- - -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" - -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index 4c715ccf6f..c747a922c4 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -48,6 +48,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-model-info" } } diff --git a/developer/src/kmc-model/build.sh b/developer/src/kmc-model/build.sh index bd758b18f9..4b5f15cc30 100755 --- a/developer/src/kmc-model/build.sh +++ b/developer/src/kmc-model/build.sh @@ -8,7 +8,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Keyman kmc Lexical Model Compiler module" \ "@/common/web/keyman-version" \ @@ -18,10 +17,7 @@ builder_describe "Keyman kmc Lexical Model Compiler module" \ "configure" \ "build" \ "api analyze API and prepare API documentation" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" builder_describe_outputs \ configure /node_modules \ @@ -42,5 +38,3 @@ builder_run_action configure node_select_version_and_npm_ci builder_run_action build do_build builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests -builder_run_action publish ci_publish_npm - diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index 8dff9fef1e..481822e686 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -63,6 +63,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-model" } } diff --git a/developer/src/kmc-package/build.sh b/developer/src/kmc-package/build.sh index 66f430ceb4..f1d6ba8dcc 100755 --- a/developer/src/kmc-package/build.sh +++ b/developer/src/kmc-package/build.sh @@ -11,7 +11,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" builder_describe "Build Keyman kmc Package Compiler module" \ "@/common/web/keyman-version" \ @@ -21,10 +20,8 @@ builder_describe "Build Keyman kmc Package Compiler module" \ "build" \ "api analyze API and prepare API documentation" \ "clean" \ - "test" \ - "publish publish to npm" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "test" + builder_describe_outputs \ configure /node_modules \ build /developer/src/kmc-package/build/src/main.js \ @@ -39,4 +36,3 @@ builder_run_action configure node_select_version_and_npm_ci builder_run_action build tsc --build builder_run_action api api-extractor run --local --verbose builder_run_action test typescript_run_eslint_mocha_tests -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index 5e2df84fc6..edfba42073 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -61,6 +61,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc-package" } } diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 726ab28df9..4999336cee 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -289,11 +289,15 @@ export class KmpCompiler implements KeymanCompiler { // if(kps.Keyboards?.Keyboard?.length) { + if(!this.validateKeyboards(kps.Keyboards.Keyboard)) { + // error reported by validateKeyboards + return null; + } kmp.keyboards = kps.Keyboards.Keyboard.map((keyboard: KpsFile.KpsFileKeyboard) => ({ displayFont: keyboard.DisplayFont ? this.callbacks.path.basename(this.normalizePath(keyboard.DisplayFont)) : undefined, oskFont: keyboard.OSKFont ? this.callbacks.path.basename(this.normalizePath(keyboard.OSKFont)) : undefined, name: '', // filled by PackageMetadataUpdater - id:keyboard.ID?.trim(), + id:keyboard.ID.trim(), version: DEFAULT_KEYBOARD_VERSION, // filled by PackageMetadataUpdater rtl: false, // filled by PackageMetadataUpdater languages: keyboard.Languages?.Language?.length ? @@ -322,6 +326,10 @@ export class KmpCompiler implements KeymanCompiler { // if(kps.LexicalModels?.LexicalModel?.length) { + if(!this.validateLexicalModels(kps.LexicalModels.LexicalModel)) { + // error reported by validateLexicalModels + return null; + } kmp.lexicalModels = kps.LexicalModels.LexicalModel.map((model: KpsFile.KpsFileLexicalModel) => ({ name:model.ID.trim(), id:model.ID.trim(), @@ -453,6 +461,30 @@ export class KmpCompiler implements KeymanCompiler { return o; } + private validateKeyboards(keyboards: KpsFile.KpsFileKeyboard[]): boolean { + let index = 1; + for(const keyboard of keyboards) { + if(!keyboard.ID || keyboard.ID.trim() == '') { + this.callbacks.reportMessage(PackageCompilerMessages.Error_MissingKeyboardId({index})); + return false; + } + index++; + } + return true; + } + + private validateLexicalModels(models: KpsFile.KpsFileLexicalModel[]): boolean { + let index = 1; + for(const model of models) { + if(!model.ID || model.ID.trim() == '') { + this.callbacks.reportMessage(PackageCompilerMessages.Error_MissingModelId({index})); + return false; + } + index++; + } + return true; + } + /** * @internal * Returns a Promise to the serialized data which can then be written to a .kmp file. diff --git a/developer/src/kmc-package/src/compiler/kmp-inf-writer.ts b/developer/src/kmc-package/src/compiler/kmp-inf-writer.ts index d7b6f8bab6..2c71035ae8 100644 --- a/developer/src/kmc-package/src/compiler/kmp-inf-writer.ts +++ b/developer/src/kmc-package/src/compiler/kmp-inf-writer.ts @@ -138,7 +138,7 @@ export class KmpInfWriter { private saveKeyboards() { // keyboards - for(let i = 0; i < this.data.keyboards?.length ?? 0; i++) { + for(let i = 0; i < (this.data.keyboards?.length ?? 0); i++) { const keyboard = this.data.keyboards[i]; this.addSection('Keyboard'+i.toString()); this.addString('Name', keyboard.name); @@ -154,7 +154,7 @@ export class KmpInfWriter { this.addString('DisplayFont', keyboard.displayFont); } - for(let j = 0; j < keyboard.languages?.length ?? 0; j++) { + for(let j = 0; j < (keyboard.languages?.length ?? 0); j++) { const language = keyboard.languages[j]; this.addString('Language'+j.toString(), language.id+','+language.name); } diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts index d98ff747e0..82c2d0b023 100644 --- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts +++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts @@ -207,6 +207,22 @@ export class PackageCompilerMessages { may violate this rule transitively. `); + static ERROR_MissingModelId = SevError | 0x0029; + static Error_MissingModelId = (o:{index:number}) => m( + this.ERROR_MissingModelId, `The lexical model at index ${def(o.index)} has a missing or empty ID field.`, ` + Each LexicalModel element must have an ID sub-element and a Languages + sub-element. The content of the ID sub-element must correspond to the basename + of a .model.js file inside the Files element. + `); + + static ERROR_MissingKeyboardId = SevError | 0x002A; + static Error_MissingKeyboardId = (o:{index:number}) => m( + this.ERROR_MissingKeyboardId, `The keyboard at index ${def(o.index)} has a missing or empty ID field.`, ` + Each Keyboard element must have an ID sub-element sub-element. The content of + the ID sub-element must correspond to the basename of a keyboard .kmx or .js + file inside the Files element. + `); + //------------------------------------------------------------------------------| // max length of detail message lines (checked by verifyCompilerMessagesObject) | //------------------------------------------------------------------------------| diff --git a/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps b/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps new file mode 100644 index 0000000000..ef84326634 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/missing_model_id.kps @@ -0,0 +1,30 @@ + + + + 12.0.1500.0 + 12.0 + + + SENĆOŦEN (Saanich Dialect) Lexical Model + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0.3 + + + + example.qaa.sencoten.model.js + Lexical model example.qaa.sencoten.model.js + 0 + .model.js + + + + + + + North Straits Salish + SENĆOŦEN + + + + diff --git a/developer/src/kmc-package/test/package-compiler.tests.ts b/developer/src/kmc-package/test/package-compiler.tests.ts index 76d00d99be..33b00e6f00 100644 --- a/developer/src/kmc-package/test/package-compiler.tests.ts +++ b/developer/src/kmc-package/test/package-compiler.tests.ts @@ -86,7 +86,7 @@ describe('KmpCompiler', function () { assert.deepEqual(kmpJsonOutput, kmpJsonZippedFixture); }), zipFile.file(`${modelID}.model.js`).async('uint8array').then(modelJsFile => { - assert.deepEqual(modelJsFile, fs.readFileSync(jsPath)); + assert.deepEqual(modelJsFile, new Uint8Array(fs.readFileSync(jsPath))); }) ]); }); @@ -278,8 +278,13 @@ describe('KmpCompiler', function () { it(`should load a package with missing keyboard ID metadata`, function () { const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_id.kps')); assert.isNull(kmpJson); // with a missing keyboard_id, the package shouldn't load, but it shouldn't crash either - assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_KeyboardContentFileNotFound); + assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingKeyboardId); + }); + it(`should load a package with missing model ID metadata`, function () { + const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_model_id.kps')); + assert.isNull(kmpJson); // with a missing model_id, the package shouldn't load, but it shouldn't crash either + assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingModelId); }); it(`should load a package with missing keyboard name metadata`, function () { diff --git a/developer/src/kmc/README.md b/developer/src/kmc/README.md index 2ab17f67e7..0490998e4d 100644 --- a/developer/src/kmc/README.md +++ b/developer/src/kmc/README.md @@ -175,14 +175,6 @@ The temp_path must be a path outside the repository to avoid npm getting confused by the root package.json. This is called by inst/download.in.mak normally when building the Keyman Developer installer. -## Publishing to NPM - -```shell -./build.sh publish [--dry-run] -``` - -Publishes the current release to NPM. This should only be run from CI. - [kmc]: https://help.keyman.com/developer/current-version/reference/kmc/cli [file-layout]: https://help.keyman.com/developer/current-version/reference/file-layout diff --git a/developer/src/kmc/build-bundler.js b/developer/src/kmc/build-bundler.js index 9b5f9cdd9b..1cfbe2568f 100644 --- a/developer/src/kmc/build-bundler.js +++ b/developer/src/kmc/build-bundler.js @@ -7,8 +7,6 @@ import esbuild from 'esbuild'; await esbuild.build({ entryPoints: [ 'build/src/kmc.js', - 'build/src/kmlmc.js', - 'build/src/kmlmp.js', ], bundle: true, format: 'esm', diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 5a64faa170..5864607605 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -8,11 +8,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" . "$KEYMAN_ROOT/resources/build/node.inc.sh" . "$KEYMAN_ROOT/resources/build/typescript.inc.sh" -. "$KEYMAN_ROOT/developer/src/packages.inc.sh" +. "$KEYMAN_ROOT/resources/build/ci/npm-packages.inc.sh" builder_describe "Build Keyman Keyboard Compiler kmc" \ "@/common/include" \ @@ -36,10 +35,7 @@ builder_describe "Build Keyman Keyboard Compiler kmc" \ "bundle creates a bundled version of kmc" \ "api prepare compiler error documentation" \ "test run automated tests for kmc" \ - publish \ - "--build-path=BUILD_PATH build directory for bundle" \ - "--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \ - "--dry-run,-n don't actually publish, just dry run" + "--build-path=BUILD_PATH build directory for bundle" builder_describe_outputs \ configure /node_modules \ @@ -58,7 +54,7 @@ function do_build() { function do_test() { typescript_run_eslint_mocha_tests 45 - ./test/command-line-tests.sh test + builder_launch /developer/src/kmc/test/command-line-tests.sh test } #------------------------------------------------------------------------------------------------------------------- @@ -115,4 +111,3 @@ builder_run_action build do_build builder_run_action test do_test builder_run_action api do_api builder_run_action bundle do_bundle -builder_run_action publish ci_publish_npm diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index 7e04cae164..de0acd3c4f 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -11,10 +11,8 @@ ], "scripts": { "build": "tsc -b", - "bundle": "npm run bundle-kmc && npm run bundle-kmlmc && npm run bundle-kmlmp", + "bundle": "npm run bundle-kmc", "bundle-kmc": "esbuild build/src/kmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmc.cjs", - "bundle-kmlmc": "esbuild build/src/kmlmc.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmc.cjs", - "bundle-kmlmp": "esbuild build/src/kmlmp.js --bundle --platform=node --target=es2022 > build/cjs-src/kmlmp.cjs", "test": "eslint . && cd test && tsc -b && cd .. && mocha" }, "type": "module", @@ -29,9 +27,7 @@ }, "main": "build/src/kmc.js", "bin": { - "kmc": "build/src/kmc.js", - "kmlmc": "build/src/kmlmc.js", - "kmlmp": "build/src/kmlmp.js" + "kmc": "build/src/kmc.js" }, "dependencies": { "@keymanapp/common-types": "*", @@ -82,6 +78,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/keymanapp/keyman.git" + "url": "git+https://github.com/keymanapp/keyman.git", + "directory": "developer/src/kmc" } } diff --git a/developer/src/kmc/src/kmlmc.ts b/developer/src/kmc/src/kmlmc.ts deleted file mode 100644 index ca2fd4d6b0..0000000000 --- a/developer/src/kmc/src/kmlmc.ts +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env node -/** - * kmlmc - Keyman Lexical Model Compiler - */ - -import { Command } from 'commander'; -import { LexicalModelCompiler } from '@keymanapp/kmc-model'; -import { SysExits } from './util/sysexits.js'; -import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; - -let inputFilename: string; -const program = new Command(); - -/* Arguments */ -program - .description('Compiles Keyman lexical models') - .version(KEYMAN_VERSION.VERSION_WITH_TAG) - .arguments('') - .action(infile => inputFilename = infile) - .option('-o, --outFile ', 'where to save the resultant file'); - -program.parse(process.argv); - -// Deal with input arguments: -if (!inputFilename) { - exitDueToUsageError('Must provide a lexical model source file.'); -} - -const callbacks = new NodeCompilerCallbacks({logLevel: 'info'}); - -const compiler = new LexicalModelCompiler(); -if(!await compiler.init(callbacks, null)) { - console.error('Initialization failed.'); - process.exit(SysExits.EX_DATAERR); -} - -let code = null; -// Compile: -try { - code = await compiler.run(inputFilename, program.opts().outFile); -} catch(e) { - console.error(e); - process.exit(SysExits.EX_DATAERR); -} - -if(!code) { - console.error('Compilation failed.') - process.exit(SysExits.EX_DATAERR); -} - -// Output: -if (program.opts().outFile) { - compiler.write(code.artifacts); -} else { - // TODO(lowpri): if writing to console then log messages should all be to stderr? - const decoder = new TextDecoder(); - const text = decoder.decode(code.artifacts.js.data); - console.log(text); -} - -function exitDueToUsageError(message: string): never { - console.error(`${program.name()}: ${message}`); - console.error(); - program.outputHelp(); - return process.exit(SysExits.EX_USAGE); -} \ No newline at end of file diff --git a/developer/src/kmc/src/kmlmp.ts b/developer/src/kmc/src/kmlmp.ts deleted file mode 100644 index d5fdb20c16..0000000000 --- a/developer/src/kmc/src/kmlmp.ts +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node -/** - * kmlmp - Keyman Lexical Model Package Compiler - */ - -// Note: this is a deprecated package and will be removed in Keyman 19.0 - -import { Command } from 'commander'; -import { KmpCompiler } from '@keymanapp/kmc-package'; -import { SysExits } from './util/sysexits.js'; -import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; - -let inputFilename: string; -const program = new Command(); - -/* Arguments */ -program - .description('Compiles Keyman lexical model packages\nDeprecated: use instead; will be removed in v18') - .version(KEYMAN_VERSION.VERSION_WITH_TAG) - .arguments('') - .action(infile => inputFilename = infile) - .option('-o, --outFile ', 'where to save the resultant file'); - -program.parse(process.argv); - -// Deal with input arguments: - -if (!inputFilename) { - exitDueToUsageError('Must provide a lexical model package source file.'); -} - -const outputFilename: string = program.opts().outFile ? program.opts().outFile : inputFilename.replace(/\.kps$/, ".kmp"); - -// -// Run the compiler -// - -const callbacks = new NodeCompilerCallbacks({logLevel: 'info'}); -const kmpCompiler = new KmpCompiler(); -if(!await kmpCompiler.init(callbacks, null)) { - process.exit(1); -} - -const result = await kmpCompiler.run(inputFilename, outputFilename); -if(!result) { - process.exit(1); -} - -if(!await kmpCompiler.write(result.artifacts)) { - console.error('Failed to write kmp file'); - process.exit(1); -} - -function exitDueToUsageError(message: string): never { - console.error(`${program.name()}: ${message}`); - console.error(); - program.outputHelp(); - return process.exit(SysExits.EX_USAGE); -} diff --git a/developer/src/kmc/test/getLastGitCommitDate.tests.ts b/developer/src/kmc/test/getLastGitCommitDate.tests.ts index 2aa88aa5fa..066d986f63 100644 --- a/developer/src/kmc/test/getLastGitCommitDate.tests.ts +++ b/developer/src/kmc/test/getLastGitCommitDate.tests.ts @@ -1,3 +1,4 @@ +import { execFileSync } from 'node:child_process'; import { assert } from 'chai'; import 'mocha'; import { makePathToFixture } from './helpers/index.js'; @@ -17,8 +18,29 @@ describe('getLastGitCommitDate', function () { assert.isNull(date); }); + function isShallowRepository() { + try { + const result = execFileSync('git', ['rev-parse', '--is-shallow-repository'], { + encoding: 'utf-8', // force a string result rather than Buffer + windowsHide: true, // on windows, we may need this to suppress a console window popup + stdio: ['pipe', 'pipe', 'pipe'] // all output via pipe, so we don't get git errors on console + }); + return result.trim() == 'true'; + } catch (e) { + return false; + } + } + it('should return a valid date for a specific file in the repo', async function() { this.timeout(5000); // getLastGitCommitDate depends on git which can sometimes take longer + + if(isShallowRepository()) { + // If we have a shallow clone, then the date will be the most recent + // commit date, and we'll just skip this test. For example, this happens + // on GHA checkouts by default with actions/checkout depth=1 + this.skip(); + } + const path = makePathToFixture('get-last-git-commit-date/README.md'); const date = getLastGitCommitDate(path); // The expected date was manually extracted using the following command, with msec appended: diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index ab6296609b..099a5e6978 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -103,6 +103,7 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" #include "km_u16.h" +#include "validation.h" /* These macros are adapted from winnt.h and legacy use only */ #define MAKELANGID(p, s) ((((uint16_t)(s)) << 10) | (uint16_t)(p)) @@ -239,6 +240,9 @@ int kmcmp::currentLine = 0; kmcmp::NamedCodeConstants *kmcmp::CodeConstants = NULL; +DefaultCompilerMessage compilerMessage; +Validation validation(compilerMessage); + PKMX_WCHAR strtowstr(PKMX_STR in) { PKMX_WCHAR result; @@ -675,6 +679,10 @@ KMX_BOOL ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) gp->fUsingKeys = TRUE; } + if(!validation.ValidateIdentifier(q, SZMAX_GROUPNAME)) { + return FALSE; + } + safe_wcsncpy(gp->szName, q, SZMAX_GROUPNAME); gp->Line = kmcmp::currentLine; @@ -794,6 +802,10 @@ KMX_BOOL ProcessStoreLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) { sp->fIsDebug = FALSE; sp->fIsCall = FALSE; + if(!validation.ValidateIdentifier(q, SZMAX_STORENAME)) { + return FALSE; + } + safe_wcsncpy(sp->szName, q, SZMAX_STORENAME); { PKMX_WCHAR temp = new KMX_WCHAR[GLOBAL_BUFSIZE]; @@ -2082,9 +2094,6 @@ int LineTokenType(PKMX_WCHAR *str) return T_UNKNOWN; } -KMX_WCHAR const * DeadKeyChars = -u"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; - KMX_BOOL StrValidChrs(PKMX_WCHAR q, KMX_WCHAR const * chrs) { for (; *q; q++) @@ -2185,7 +2194,12 @@ KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX tstr[mx++] = UC_SENTINEL; tstr[mx++] = CODE_DEADKEY; - if (!StrValidChrs(q, DeadKeyChars)) return KmnCompilerMessages::ERROR_InvalidDeadkey; + + if(!validation.ValidateIdentifier(q, SZMAX_DEADKEYNAME)) { + // Note, this means 2 messages will be generated for invalid deadkey names + return KmnCompilerMessages::ERROR_InvalidDeadkey; + } + tstr[mx++] = GetDeadKey(fk, q); //atoiW(q); 7-5-01: named deadkeys tstr[mx] = 0; } diff --git a/developer/src/kmcmplib/src/CompilerErrors.cpp b/developer/src/kmcmplib/src/CompilerErrors.cpp index 6de261c8e9..cb378021a9 100644 --- a/developer/src/kmcmplib/src/CompilerErrors.cpp +++ b/developer/src/kmcmplib/src/CompilerErrors.cpp @@ -21,7 +21,7 @@ void ReportCompilerMessage(KMX_DWORD msg, const std::vector& parame KMCMP_COMPILER_RESULT_MESSAGE message; message.errorCode = msg; - message.lineNumber = kmcmp::currentLine + 1; + message.lineNumber = kmcmp::currentLine; message.columnNumber = kmcmp::ErrChr; message.parameters = parameters; message.filename = kmcmp::messageFilename; @@ -29,3 +29,11 @@ void ReportCompilerMessage(KMX_DWORD msg, const std::vector& parame kmcmp::ErrChr = 0; } + +void DefaultCompilerMessage::report(enum KmnCompilerMessages::KmnCompilerMessages msg, const std::vector& parameters) { + ::ReportCompilerMessage(msg, parameters); +} + +void DefaultCompilerMessage::report(enum KmnCompilerMessages::KmnCompilerMessages msg) { + ::ReportCompilerMessage(msg); +} diff --git a/developer/src/kmcmplib/src/CompilerErrors.h b/developer/src/kmcmplib/src/CompilerErrors.h index ba57784639..dfe520f3cd 100644 --- a/developer/src/kmcmplib/src/CompilerErrors.h +++ b/developer/src/kmcmplib/src/CompilerErrors.h @@ -2,6 +2,22 @@ #include #include +#include "kmcmplibapi.h" +#include "kmn_compiler_errors.h" + +class CompilerMessage { +public: + virtual void report(enum KmnCompilerMessages::KmnCompilerMessages msg, const std::vector& parameters) = 0; + virtual void report(enum KmnCompilerMessages::KmnCompilerMessages msg) = 0; + virtual ~CompilerMessage() = default; +}; + +class DefaultCompilerMessage : public CompilerMessage { +public: + virtual void report(enum KmnCompilerMessages::KmnCompilerMessages msg, const std::vector& parameters) ; + virtual void report(enum KmnCompilerMessages::KmnCompilerMessages msg); + virtual ~DefaultCompilerMessage() = default; +}; namespace kmcmp { extern kmcmp_CompilerMessageProc msgproc; diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 51acf70409..3e188b0b0d 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -81,6 +81,7 @@ lib = library('kmcmplib', 'NamedCodeConstants.cpp', 'UnreachableRules.cpp', 'uset-api.cpp', + 'validation.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', 'xstring.cpp', diff --git a/developer/src/kmcmplib/src/validation.cpp b/developer/src/kmcmplib/src/validation.cpp new file mode 100644 index 0000000000..b245aee721 --- /dev/null +++ b/developer/src/kmcmplib/src/validation.cpp @@ -0,0 +1,127 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-09-12 + * + * Validation of various aspects of .kmn source files, such as identifiers + */ +#include "pch.h" + +#include "km_u16.h" + +#include "compfile.h" +#include "kmcmplib.h" + +#include "xstring.h" +#include "validation.h" + +KMX_BOOL Uni_IsSpaceCharacter(KMX_WCHAR ch); +KMX_BOOL Uni_IsValidCharacter(KMX_WCHAR *p); +KMX_BOOL Uni_IsControlCharacter(KMX_WCHAR ch); + +/* + Unicode version 16.0; GC=Zs. + + List of all space characters in Unicode 16.0, by General Category Zs: + + 0020;SPACE;Zs;0;WS;;;;;N;;;;; + 00A0;NO-BREAK SPACE;Zs;0;CS; 0020;;;;N;NON-BREAKING SPACE;;;; + 1680;OGHAM SPACE MARK;Zs;0;WS;;;;;N;;;;; + 2000;EN QUAD;Zs;0;WS;2002;;;;N;;;;; + 2001;EM QUAD;Zs;0;WS;2003;;;;N;;;;; + 2002;EN SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2003;EM SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2004;THREE-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2005;FOUR-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2006;SIX-PER-EM SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2007;FIGURE SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2008;PUNCTUATION SPACE;Zs;0;WS; 0020;;;;N;;;;; + 2009;THIN SPACE;Zs;0;WS; 0020;;;;N;;;;; + 200A;HAIR SPACE;Zs;0;WS; 0020;;;;N;;;;; + 202F;NARROW NO-BREAK SPACE;Zs;0;CS; 0020;;;;N;;;;; + 205F;MEDIUM MATHEMATICAL SPACE;Zs;0;WS; 0020;;;;N;;;;; + 3000;IDEOGRAPHIC SPACE;Zs;0;WS; 0020;;;;N;;;;; + + TODO(lowpri): Use icu instead +*/ + +const KMX_WCHAR SpacingCharacters[] = + u"\u0020\u00A0\u1680\u2000\u2001\u2002\u2003" + "\u2004\u2005\u2006\u2007\u2008\u2009\u200A" + "\u202F\u205F\u3000"; + +KMX_BOOL Uni_IsSpaceCharacter(KMX_WCHAR ch) { + return !!u16chr(SpacingCharacters, ch); +} + +KMX_BOOL Uni_IsValidCharacter(KMX_WCHAR const *p) { + auto ch = Uni_UTF16ToUTF32(p); + return + // In range (although, not possible to be out of range with UTF-16) + // Note: end-of-string zero-terminator is not considered a valid character + ch > 0 && ch <= 0x10FFFF && + // invalid characters + (ch & 0xFFFF) != 0xFFFF && + (ch & 0xFFFF) != 0xFFFE && + // reserved characters + (ch < 0xFDD0 || ch > 0xFDEF) && + // Unpaired surrogate + !Uni_IsSurrogate1(ch) && + !Uni_IsSurrogate2(ch); +} + +KMX_BOOL Uni_IsControlCharacter(KMX_WCHAR ch) { + return + ch < 0x0020 || + (ch >= 0x007F && ch <= 0x009F); +} + +KMX_BOOL Validation::ValidateIdentifier(KMX_WCHAR const *name, size_t maxLength) { + // whitespace already trimmed from start and end + + // length of name > 0 + if(*name == 0) { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustBeAtLeastOneCharLong); + return FALSE; + } + + // length of name within bounds, cater for terminating \0 + if(u16len(name) >= maxLength) { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustBeAtMostNCharsLong, {std::to_string(maxLength - 1)}); + return FALSE; + } + + // invalid characters - Unicode non-chars, any spacing characters, comma + // close paren automatically impossible due to earlier phases in parser, but + // including here for completeness. + // TODO: incxstr should be `KMX_WCHAR const *` + for(auto q = name; *q; q = incxstr(const_cast(q))) { + if(!Uni_IsValidCharacter(q)) { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameContainsInvalidCharacter); + return FALSE; + } + if(Uni_IsControlCharacter(*q)) { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameContainsInvalidCharacter); + return FALSE; + } + + if(Uni_IsSpaceCharacter(*q)) { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustNotContainSpaces); + return FALSE; + } + if(*q == ',') { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustNotContainComma); + return FALSE; + } + if(*q == '(' || *q == ')') { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustNotContainParentheses); + return FALSE; + } + if(*q == '[' || *q == ']') { + this->compilerMessage.report(KmnCompilerMessages::ERROR_NameMustNotContainSquareBrackets); + return FALSE; + } + } + + return TRUE; +} diff --git a/developer/src/kmcmplib/src/validation.h b/developer/src/kmcmplib/src/validation.h new file mode 100644 index 0000000000..7fe4111fdf --- /dev/null +++ b/developer/src/kmcmplib/src/validation.h @@ -0,0 +1,23 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-09-12 + * + * Validation of various aspects of .kmn source files, such as identifiers + */ + +#pragma once + +#include "CompilerErrors.h" + +class Validation { +private: + CompilerMessage &compilerMessage; +public: + Validation(CompilerMessage &cm) : compilerMessage(cm) { + } + + virtual ~Validation() = default; + virtual KMX_BOOL ValidateIdentifier(KMX_WCHAR const *p, size_t maxLength); +}; + diff --git a/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx deleted file mode 100644 index c4bb0ad3ea..0000000000 Binary files a/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx and /dev/null differ diff --git a/developer/src/kmcmplib/tests/get-test-source.sh b/developer/src/kmcmplib/tests/get-test-source.sh index af647ee311..c4ec390ead 100755 --- a/developer/src/kmcmplib/tests/get-test-source.sh +++ b/developer/src/kmcmplib/tests/get-test-source.sh @@ -9,10 +9,11 @@ set -eu find "$1" -name '*.kmn' | \ grep -E '(release|experimental)/([a-z0-9_]+)/([a-z0-9_]+)/source/\3\.kmn$' | \ - grep -vE 'masaram_gondi|anii|sil_kmhmu|fv_statimcets|fv_nuucaanul|basic_kbdcherp|basic_kbdolch' + grep -vE 'masaram_gondi|anii|sil_kmhmu|fv_statimcets|fv_nuucaanul|basic_kbdcherp|basic_kbdolch|sil_yi' # #12623: exclude masaram_gondi due to #11806 # #12631: exclude anii, sil_kmhmu as ico references have mismatching case # #12631: exclude fv_statimcets, fv_nuucaanul as these include U+2002 which is not # treated as whitespace on mac arch # #12604: exclude basic_kbdcherp, basic_kbdolch as these do not include now necessary # whitespace, see also issue #12307 +# #14746: exclude sil_yi, as this has whitespace in group name 'Unicode Group' \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp b/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp index 757767762f..7e65bfdd55 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler.tests.cpp @@ -175,7 +175,7 @@ TEST_F(CompilerTest, ReportCompilerMessage_test) { ReportCompilerMessage(KmnCompilerMessages::FATAL_CannotCreateTempfile, params); EXPECT_EQ(1, kmcmp::nErrors); EXPECT_EQ(KmnCompilerMessages::FATAL_CannotCreateTempfile, msgproc_errors[0].errorCode); - EXPECT_EQ(kmcmp::currentLine+1, msgproc_errors[0].lineNumber); + EXPECT_EQ(kmcmp::currentLine, msgproc_errors[0].lineNumber); EXPECT_EQ(kmcmp::ErrChr, msgproc_errors[0].columnNumber); EXPECT_TRUE(msgproc_errors[0].filename == kmcmp::messageFilename); EXPECT_TRUE(msgproc_errors[0].parameters == params); @@ -752,8 +752,16 @@ TEST_F(CompilerTest, GetXStringImpl_type_xd_test) { EXPECT_EQ(0, u16cmp(tstr_deadkey_valid, tstr)); fileKeyboard.cxDeadKeyArray = 0; + // deadkey, valid + u16cpy(str, u"deadkey(%)"); + EXPECT_EQ(0, (int)fileKeyboard.cxDeadKeyArray); + EXPECT_EQ(STATUS_Success, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_deadkey_valid_pct[] = { UC_SENTINEL, CODE_DEADKEY, 1, 0 }; // setup deadkeys + EXPECT_EQ(0, u16cmp(tstr_deadkey_valid_pct, tstr)); + fileKeyboard.cxDeadKeyArray = 0; + // dk, KmnCompilerMessages::ERROR_InvalidDeadkey, bad character - u16cpy(str, u"dk(%)"); + u16cpy(str, u"dk(,)"); EXPECT_EQ(KmnCompilerMessages::ERROR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); // dk, KmnCompilerMessages::ERROR_InvalidDeadkey, no delimiters => NULL diff --git a/developer/src/kmcmplib/tests/gtest-validation.tests.cpp b/developer/src/kmcmplib/tests/gtest-validation.tests.cpp new file mode 100644 index 0000000000..07b47e71fe --- /dev/null +++ b/developer/src/kmcmplib/tests/gtest-validation.tests.cpp @@ -0,0 +1,112 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-09-12 + * + * Unit test for validation.cpp + */ +#include +#include +#include +#include +#include "../src/validation.h" +#include "../src/CompilerErrors.h" +#include "../src/compfile.h" + +using ::testing::_; + +class MockCompilerMessage : public CompilerMessage { +public: + MOCK_METHOD(void, report, (enum KmnCompilerMessages::KmnCompilerMessages, const std::vector&), (override)); + MOCK_METHOD(void, report, (enum KmnCompilerMessages::KmnCompilerMessages), (override)); +}; + +TEST(ValidationTest, ValidateIdentifierHandlesEmptyString) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustBeAtLeastOneCharLong)); + EXPECT_FALSE(v.ValidateIdentifier(u"", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesOverlongString) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + // The group name struct member is `KMX_WCHAR szName[SZMAX_GROUPNAME]`, + // so we need to verify that we keep space for terminating \0 + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustBeAtMostNCharsLong, _)); + EXPECT_FALSE(v.ValidateIdentifier(std::u16string(SZMAX_GROUPNAME, u'X').c_str(), SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesInvalidCharacters) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameContainsInvalidCharacter)).Times(5); + EXPECT_FALSE(v.ValidateIdentifier(u"test\uFFFFtest", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\uFFFEtest", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\uFDD0test", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\U0010FFFFtest", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\u0003test", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesSpace) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustNotContainSpaces)).Times(3); + EXPECT_FALSE(v.ValidateIdentifier(u"test test", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\u00A0test", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test\u3000test", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesComma) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustNotContainComma)); + EXPECT_FALSE(v.ValidateIdentifier(u"test,test", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesParentheses) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustNotContainParentheses)).Times(2); + EXPECT_FALSE(v.ValidateIdentifier(u"test(test", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test)test", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesSquareBrackets) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(KmnCompilerMessages::ERROR_NameMustNotContainSquareBrackets)).Times(2); + EXPECT_FALSE(v.ValidateIdentifier(u"test[test", SZMAX_GROUPNAME)); + EXPECT_FALSE(v.ValidateIdentifier(u"test]test", SZMAX_GROUPNAME)); +} + +TEST(ValidationTest, ValidateIdentifierHandlesValidStrings) { + MockCompilerMessage compilermessage; + Validation v(compilermessage); + + EXPECT_CALL(compilermessage, report(_)).Times(0); + + // Normal group names + EXPECT_TRUE(v.ValidateIdentifier(u"main", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"Main", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"main_group", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"main.group", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"main-group", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"group0", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"group#special", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"ጀ_ቤተሰብ", SZMAX_GROUPNAME)); + + // Pathological identifiers that are allowed, though we kinda wish they weren't + EXPECT_TRUE(v.ValidateIdentifier(u"0", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"_", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u".", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"#@$%&", SZMAX_GROUPNAME)); + EXPECT_TRUE(v.ValidateIdentifier(u"©RIGHT", SZMAX_GROUPNAME)); +} diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index b5da144115..e0a042ece4 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -163,7 +163,10 @@ test('uset-api-tests', usetapitest) # Google Test -gtestcompilertest = executable('gtest-compiler-tests', 'gtest-compiler.tests.cpp', +gtestcompilertest = executable('gtest-compiler-tests', [ + 'gtest-compiler.tests.cpp', + 'gtest-validation.tests.cpp', + ], cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, diff --git a/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas b/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas index f5384903d9..3643f8c70c 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas @@ -251,6 +251,8 @@ begin except on E:EKeyboardProjectTemplate do Exit(Fail('Unable to generate template: '+E.Message)); + on E:EFOpenError do + Exit(Fail('Unable to create project; template files may be missing: '+E.Message)); end; FProjectFilename := FTemplate.ProjectFilename; diff --git a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas index dc274d082d..b1f5a3e406 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.KeyboardProjectTemplate.pas @@ -86,7 +86,8 @@ end; procedure TKeyboardProjectTemplate.Generate; begin if not ForceDirectories(BasePath + ID + '\' + SFolder_Source) then - raise EKeyboardProjectTemplate.Create('Could not create destination path '+BasePath+ID); + raise EKeyboardProjectTemplate.Create('Could not create destination path '''+BasePath+ID+'''. '+ + 'Please check the path and try again. The error was: '+SysErrorMessage(GetLastError)); WriteDocumentation; diff --git a/developer/src/kmconvert/Keyman.Developer.System.KeymanConvertMain.pas b/developer/src/kmconvert/Keyman.Developer.System.KeymanConvertMain.pas index f65bfb0683..b6eae962d2 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.KeymanConvertMain.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.KeymanConvertMain.pas @@ -7,6 +7,7 @@ procedure Run; implementation uses + System.Classes, System.SysUtils, Winapi.ActiveX, @@ -100,7 +101,12 @@ begin except on E:EKeyboardProjectTemplate do begin - writeln(E.Message); + writeln('ERROR: Unable to create project: '+E.Message); + Exit(False); + end; + on E:EFOpenError do + begin + writeln('ERROR: Unable to create project; template files may be missing: '+E.Message); Exit(False); end; end; @@ -138,7 +144,12 @@ begin except on E:ELDMLKeyboardProjectTemplate do begin - writeln(E.Message); + writeln('ERROR: Unable to create project: '+E.Message); + Exit(False); + end; + on E:EFOpenError do + begin + writeln('ERROR: Unable to create project; template files may be missing: '+E.Message); Exit(False); end; end; @@ -176,7 +187,12 @@ begin except on E:EModelProjectTemplate do begin - writeln(E.Message); + writeln('ERROR: Unable to create project: '+E.Message); + Exit(False); + end; + on E:EFOpenError do + begin + writeln('ERROR: Unable to create project; template files may be missing: '+E.Message); Exit(False); end; end; diff --git a/developer/src/kmconvert/Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas index 6f5bfc7041..4b396dbc8e 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas @@ -56,7 +56,8 @@ end; procedure TLDMLKeyboardProjectTemplate.Generate; begin if not ForceDirectories(BasePath + ID + '\' + SFolder_Source) then - raise ELDMLKeyboardProjectTemplate.Create('Could not create destination path '+BasePath+ID); + raise ELDMLKeyboardProjectTemplate.Create('Could not create destination path '''+BasePath+ID+'''. '+ + 'Please check the path and try again. The error was: '+SysErrorMessage(GetLastError)); WriteDocumentation; diff --git a/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas b/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas index 42ac45d082..cceee29475 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.ModelProjectTemplate.pas @@ -70,7 +70,8 @@ end; procedure TModelProjectTemplate.Generate; begin if not ForceDirectories(BasePath + ID + '\' + SFolder_Source) then - raise EModelProjectTemplate.Create('Could not create destination path '+BasePath+ID); + raise EModelProjectTemplate.Create('Could not create destination path '''+BasePath+ID+'''. '+ + 'Please check the path and try again. The error was: '+SysErrorMessage(GetLastError)); WriteDocumentation; diff --git a/developer/src/kmdecomp/kmdecomp.vcxproj b/developer/src/kmdecomp/kmdecomp.vcxproj index 223ae72199..36cea5f3ff 100644 --- a/developer/src/kmdecomp/kmdecomp.vcxproj +++ b/developer/src/kmdecomp/kmdecomp.vcxproj @@ -1,171 +1,171 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {963D608A-6689-469C-AE42-F56696DD42CC} - 10.0 - - - - Application - false - MultiByte - v142 - - - Application - false - MultiByte - v142 - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - false - AllRules.ruleset - - - AllRules.ruleset - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(IncludePath) - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(IncludePath) - - - - .\Debug/kmdecomp.tlb - - - - - Disabled - $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - true - EditAndContinue - stdcpp17 - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - true - Console - MachineX86 - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - true - .\Debug/kmdecomp.bsc - - - - - .\Release/kmdecomp.tlb - - - - - MaxSpeed - OnlyExplicitInline - $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - MultiThreaded - true - Level3 - true - true - stdcpp17 - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - true - Console - MachineX86 - true - $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - true - .\Release/kmdecomp.bsc - - - - - NotUsing - NotUsing - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - Create - Create - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {963D608A-6689-469C-AE42-F56696DD42CC} + 10.0 + + + + Application + false + MultiByte + v143 + + + Application + false + MultiByte + v143 + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + false + AllRules.ruleset + + + AllRules.ruleset + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(IncludePath) + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(IncludePath) + + + + .\Debug/kmdecomp.tlb + + + + + Disabled + $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + true + EditAndContinue + stdcpp17 + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + true + true + Console + MachineX86 + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + true + .\Debug/kmdecomp.bsc + + + + + .\Release/kmdecomp.tlb + + + + + MaxSpeed + OnlyExplicitInline + $(KEYMAN_ROOT)\windows\src\global\inc;$(KEYMAN_ROOT)\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + true + Level3 + true + true + stdcpp17 + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + true + Console + MachineX86 + true + $(KEYMAN_ROOT)\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + true + .\Release/kmdecomp.bsc + + + + + NotUsing + NotUsing + + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + Create + Create + + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/samples/imsample/imsample.vcxproj b/developer/src/samples/imsample/imsample.vcxproj index 9abee9aeff..93413dbe8f 100644 --- a/developer/src/samples/imsample/imsample.vcxproj +++ b/developer/src/samples/imsample/imsample.vcxproj @@ -1,266 +1,266 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - - - {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E} - 10.0 - imsample - - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - - - - - - - - - - - - - - - - - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - .dll - $(ProjectName).x64 - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - .dll - $(ProjectName).x64 - - - - MultiThreadedDebug - Default - true - Disabled - true - Level3 - true - EditAndContinue - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - _DEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - Win32 - - - 0x0c09 - _DEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - true - Windows - $(OutDir)$(TargetName)$(TargetExt) - .\IMSample.lib - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreadedDebug - Default - false - Disabled - true - Level3 - ProgramDatabase - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - _DEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - - - 0x0c09 - _DEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - true - Windows - $(OutDir)$(TargetName)$(TargetExt) - $(OutDir)$(TargetName).lib - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreaded - OnlyExplicitInline - true - true - MaxSpeed - true - Level3 - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - NDEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - Win32 - - - 0x0c09 - NDEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - Windows - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - MultiThreaded - OnlyExplicitInline - true - true - MaxSpeed - true - Level3 - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - .\IMSample.pch - true - - - true - NDEBUG;%(PreprocessorDefinitions) - .\IMSample.tlb - true - NUL - - - 0x0c09 - NDEBUG;%(PreprocessorDefinitions) - - - true - .\IMSample.bsc - - - true - true - Windows - odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - .\imsample.def - true - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + {0C9DB8F9-B788-8782-A97D-4F8294AA0B4E} + 10.0 + imsample + + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + .dll + $(ProjectName).x64 + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + .dll + $(ProjectName).x64 + + + + MultiThreadedDebug + Default + true + Disabled + true + Level3 + true + EditAndContinue + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + _DEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + Win32 + + + 0x0c09 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + true + Windows + $(OutDir)$(TargetName)$(TargetExt) + .\IMSample.lib + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreadedDebug + Default + false + Disabled + true + Level3 + ProgramDatabase + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + _DEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + + + 0x0c09 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + true + Windows + $(OutDir)$(TargetName)$(TargetExt) + $(OutDir)$(TargetName).lib + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreaded + OnlyExplicitInline + true + true + MaxSpeed + true + Level3 + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + NDEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + Win32 + + + 0x0c09 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + Windows + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + MultiThreaded + OnlyExplicitInline + true + true + MaxSpeed + true + Level3 + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + .\IMSample.pch + true + + + true + NDEBUG;%(PreprocessorDefinitions) + .\IMSample.tlb + true + NUL + + + 0x0c09 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\IMSample.bsc + + + true + true + Windows + odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + .\imsample.def + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 785fd496c0..73eb5c9b45 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -18,6 +18,7 @@ builder_describe "Build Keyman Developer Server" \ configure \ build \ test \ + "run Run Server in dev mode" \ "installer Prepare for Keyman Developer installer" \ publish \ ":server Keyman Developer Server main program" \ @@ -165,11 +166,16 @@ function publish_server() { wrap-signcode //d "Keyman Developer" "$DEVELOPER_PROGRAM/server/build/src/win32/trayicon/addon.x64.node" } +function run_server() { + node . +} + builder_run_action clean:server clean_server builder_run_action configure:server configure_server builder_run_action build:addins build_addins builder_run_action build:server build_server builder_run_action test:server test_server +builder_run_action run:server run_server # builder_run_action test:addins # no op builder_run_action installer:server installer_server # TODO: rename to install-prep builder_run_action publish:server publish_server diff --git a/developer/src/server/src/site/lib/sentry/init.js.in b/developer/src/server/src/site/lib/sentry/init.js.in index dbaf0ea5c1..9dd14bbd23 100644 --- a/developer/src/server/src/site/lib/sentry/init.js.in +++ b/developer/src/server/src/site/lib/sentry/init.js.in @@ -7,11 +7,13 @@ // DO NOT MODIFY init.js; instead make changes in init.js.in. // -Sentry.init({ - dsn: 'https://39b25a09410349a58fe12aaf721565af@o1005580.ingest.sentry.io/5983519', // Keyman Developer - environment: '$KEYMAN_VERSION_ENVIRONMENT', - release: '$KEYMAN_VERSION_GIT_TAG' -}); +if(typeof Sentry != 'undefined') { + Sentry.init({ + dsn: 'https://39b25a09410349a58fe12aaf721565af@o1005580.ingest.sentry.io/5983519', // Keyman Developer + environment: '$KEYMAN_VERSION_ENVIRONMENT', + release: '$KEYMAN_VERSION_GIT_TAG' + }); +} function keymanEnableDiagnostics() { var e = document.createElement('button'); diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index 770833e2f0..436dd68d13 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -41,6 +41,7 @@ function enableControls(enable) { }); } +let shouldSetupKeyman = false; let keymanInitialized = false; enableControls(false); @@ -58,7 +59,13 @@ keyman.init({ }).then(function() { keyman.attachToControl(document.getElementById('ta1')); enableControls(true); + keymanInitialized = true; + + if(shouldSetupKeyman) { + console.log('keyman.init after window.onload'); + setupKeyman(); + } }); /* Initialization */ @@ -190,6 +197,15 @@ document.getElementById('ta1').addEventListener('input', logContent, false); */ window.onload = function() { + if(keymanInitialized) { + console.log('keyman.init before window.onload'); + setupKeyman(); + } else { + shouldSetupKeyman = true; + } +} + +function setupKeyman() { window.setTimeout( function () { keyman.moveToElement('ta1'); @@ -200,6 +216,8 @@ window.onload = function() { let deviceDropdown = null; let currentDevice = null; + checkKeyboardsAndModels(false); + buildKeyboardList(); if(!keyman.util.isTouchDevice()) { @@ -333,6 +351,10 @@ function handleKeyboardsAndModelsResponse(responseText, shouldReload) { } function checkKeyboardsAndModels(shouldReload) { + if(!keymanInitialized) { + // setupKeyman will run the check after initialization + return false; + } let req=new XMLHttpRequest(); console.log('Checking for updated keyboards and models ('+shouldReload+')'); req.onreadystatechange = function() { @@ -344,10 +366,9 @@ function checkKeyboardsAndModels(shouldReload) { } req.open("GET", "inc/keyboards.js", true); req.send(null); + return true; } -checkKeyboardsAndModels(false); - /* Lexical models */ const modelDropdown = new DropdownMenu('model'); diff --git a/developer/src/server/src/win32/trayicon/addon-src/package-lock.json b/developer/src/server/src/win32/trayicon/addon-src/package-lock.json index 4f2af24f7c..e4fe8a6537 100644 --- a/developer/src/server/src/win32/trayicon/addon-src/package-lock.json +++ b/developer/src/server/src/win32/trayicon/addon-src/package-lock.json @@ -315,9 +315,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -1331,9 +1331,9 @@ } }, "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "requires": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", diff --git a/developer/src/setup/SetupStrings.pas b/developer/src/setup/SetupStrings.pas index b2126b9a18..a0f9635699 100644 --- a/developer/src/setup/SetupStrings.pas +++ b/developer/src/setup/SetupStrings.pas @@ -62,7 +62,7 @@ const 'Keyman Developer Setup', 'Keyman Developer %0:s Setup', 'Setup will install %0:s', - 'Created by SIL International', + 'Created by SIL Global', 'Check for &updates online before installing', '&Install Keyman Developer', 'E&xit', diff --git a/developer/src/setup/icons.rc b/developer/src/setup/icons.rc index 0b8ab2b3b3..09cff03b45 100644 --- a/developer/src/setup/icons.rc +++ b/developer/src/setup/icons.rc @@ -39,8 +39,8 @@ BEGIN DEFPUSHBUTTON "",IDOK,141,85,145,23 PUSHBUTTON "",IDCANCEL,267,164,49,15 CONTROL "",IDC_IMAGE,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,8,7,93,154 - CONTROL "",IDC_LOGO,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,109,130,30,31 - LTEXT "",IDC_LOGOMESSAGE,145,144,206,18,0,WS_EX_TRANSPARENT + CONTROL "",IDC_LOGO,"Static",SS_BLACKFRAME | NOT WS_VISIBLE,109,130,64,31 + LTEXT "",IDC_LOGOMESSAGE,175,152,206,10,0,WS_EX_TRANSPARENT CONTROL "",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,167,198,10,WS_EX_TRANSPARENT LTEXT "",IDC_TITLE,109,5,201,33,0,WS_EX_TRANSPARENT LTEXT "",IDC_MESSAGE,109,42,206,22,0,WS_EX_TRANSPARENT diff --git a/developer/src/setup/logo.bmp b/developer/src/setup/logo.bmp index 0b5ef77dd7..f025934ea7 100644 Binary files a/developer/src/setup/logo.bmp and b/developer/src/setup/logo.bmp differ diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index 0ba6c98642..15e2add3c4 100755 --- a/developer/src/tike/build.sh +++ b/developer/src/tike/build.sh @@ -19,7 +19,7 @@ source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" WIN32_TARGET="$WIN32_TARGET_PATH/tike.exe" builder_describe_outputs \ - configure:project /developer/src/tike/xml/layoutbuilder/keymanweb-osk.ttf \ + configure:project /developer/src/tike/icons.res \ build:project /developer/src/tike/$WIN32_TARGET #------------------------------------------------------------------------------------------------------------------- @@ -72,8 +72,6 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - cp kmlmc.cmd "$DEVELOPER_PROGRAM" - cp kmlmp.cmd "$DEVELOPER_PROGRAM" cp kmc.cmd "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$DEVELOPER_PROGRAM" builder_if_release_build_level cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" diff --git a/developer/src/tike/child/UfrmDebug.pas b/developer/src/tike/child/UfrmDebug.pas index 15a5b858b7..6137cab712 100644 --- a/developer/src/tike/child/UfrmDebug.pas +++ b/developer/src/tike/child/UfrmDebug.pas @@ -109,7 +109,7 @@ type procedure ResetEvents; procedure ExecuteEvent(n: Integer); - procedure ExecuteEventAction(n: Integer); + procedure ExecuteEventAction(EventNumber: Integer); procedure ExecuteEventRule(n: Integer); procedure SetExecutionPointLine(ALine: Integer); procedure SetUIStatus(const Value: TDebugUIStatus); @@ -210,6 +210,7 @@ uses dmActionsMain, Glossary, Keyman.Developer.System.Project.ProjectLog, + Keyman.System.KeymanSentryClient, Keyman.UI.Debug.CharacterGridRenderer, KeyNames, kmxfile, @@ -462,6 +463,10 @@ begin modifier := modifier and not KM_CORE_MODIFIER_LCTRL; end; + TKeymanSentryClient.Instance.Breadcrumb('default', + Format('ProcessKeyEvent: vk=%x mod=%x scan=%x beforeText="%s" beforeSelection=%s',[vkey, modifier, scan, Copy(memo.GetTextCR, 1, 256), memo.Selection.ToString]), + 'debugger'); + if not SetKeyEventContext then Exit(False); @@ -598,8 +603,14 @@ end; procedure TfrmDebug.ExecuteEvent(n: Integer); begin + TKeymanSentryClient.Instance.Breadcrumb('default', + Format('ExecuteEvent(%d/%d): [%s]; b:%s; s:%s', + [n, FEvents.Count, FEvents[n].ToString, memo.Selection.ToString, FSavedSelection.ToString]), + 'debugger'); + memo.ReadOnly := False; memo.Selection := FSavedSelection; + if FEvents[n].EventType = etAction then ExecuteEventAction(n) else ExecuteEventRule(n); @@ -667,7 +678,7 @@ begin end; -procedure TfrmDebug.ExecuteEventAction(n: Integer); +procedure TfrmDebug.ExecuteEventAction(EventNumber: Integer); type TMemoSelectionState = record Selection: TMemoSelection; @@ -711,24 +722,25 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); procedure DoBackspace(BackspaceType: km_core_backspace_type; ExpectedValue: NativeUInt); var t: string; - m, n: Integer; + m, m1: Integer; dk: TDeadKeyInfo; state: TMemoSelectionState; function AssertionMessage: string; begin Result := 'Assertion failed. Extra data: '+ + 'EventNumber='+IntToStr(EventNumber)+'; '+ 'BackspaceType='+IntToStr(Ord(BackspaceType))+'; '+ 'ExpectedValue=U+'+IntToHex(ExpectedValue, 4)+'; '+ 'memo.SelStart='+IntToStr(memo.SelStart)+'; '+ 'memo.SelLength='+IntToStr(memo.SelLength)+'; '+ - 'memo.Text='+Copy(memo.GetTextCR, 1, 256); + 'memo.Text="'+Copy(memo.GetTextCR, 1, 256)+'"'; end; begin // Offset is zero-based, but string is 1-based. Beware! state := SaveMemoSelectionState; - n := memo.SelStart; - m := n; + m1 := memo.SelStart; + m := m1; if memo.SelLength > 0 then begin @@ -800,7 +812,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); memo.Lines.BeginUpdate; try - memo.Text := Copy(t, 1, m) + Copy(t, n+1, MaxInt); + memo.Text := Copy(t, 1, m) + Copy(t, m1+1, MaxInt); memo.SelStart := m; finally memo.Lines.EndUpdate; @@ -894,7 +906,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); begin DisableUI; frmDebugStatus.Elements.UpdateStores(nil); - with FEvents[n].Action do + with FEvents[EventNumber].Action do begin case ActionType of KM_CORE_IT_EMIT_KEYSTROKE: DoEmitKeystroke(dwData); diff --git a/developer/src/tike/child/UfrmKeymanWizard.pas b/developer/src/tike/child/UfrmKeymanWizard.pas index 4f0cbadb19..9dec62cbc5 100644 --- a/developer/src/tike/child/UfrmKeymanWizard.pas +++ b/developer/src/tike/child/UfrmKeymanWizard.pas @@ -461,7 +461,7 @@ type procedure FeatureModified(Sender: TObject); function SaveFeature(ID: TKeyboardParser_FeatureID): Boolean; procedure SelectTouchLayoutTemplate(APromptChange: Boolean); - function LoadTouchLayout: Boolean; // I4034 + procedure LoadTouchLayout; // I4034 function GetFontInfo(Index: TKeyboardFont): TKeyboardFontInfo; // I4057 procedure SetFontInfo(Index: TKeyboardFont; const Value: TKeyboardFontInfo); // I4057 @@ -1659,8 +1659,7 @@ begin end; kfTouchLayout: begin - if not LoadTouchLayout then - Exit(False); + LoadTouchLayout; end; else begin @@ -3202,17 +3201,15 @@ begin FFeature[kfTouchLayout].Modified := True; end; -function TfrmKeymanWizard.LoadTouchLayout: Boolean; // I4034 +procedure TfrmKeymanWizard.LoadTouchLayout; // I4034 begin if pagesTouchLayout.ActivePage = pageTouchLayoutDesign then begin - Result := frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False); - end - else - begin - frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); - Result := True; + if frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False) then + Exit; + pagesTouchLayout.ActivePage := pageTouchLayoutCode; end; + frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); end; procedure TfrmKeymanWizard.SaveTouchLayout; // I3885 diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index f3dcc71f09..5beb0c6d77 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -635,21 +635,21 @@ end; procedure TfrmPackageEditor.editInfoNameChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['Name'] := Trim(editInfoName.Text); + pack.Info.Desc[PackageInfo_Name] := Trim(editInfoName.Text); Modified := True; end; procedure TfrmPackageEditor.editInfoVersionChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['Version'] := Trim(editInfoVersion.Text); + pack.Info.Desc[PackageInfo_Version] := Trim(editInfoVersion.Text); Modified := True; end; procedure TfrmPackageEditor.editInfoCopyrightChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['Copyright'] := Trim(editInfoCopyright.Text); + pack.Info.Desc[PackageInfo_Copyright] := Trim(editInfoCopyright.Text); Modified := True; end; @@ -663,7 +663,7 @@ end; procedure TfrmPackageEditor.editInfoAuthorChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['Author'] := Trim(editInfoAuthor.Text); + pack.Info.Desc[PackageInfo_Author] := Trim(editInfoAuthor.Text); Modified := True; end; @@ -671,16 +671,16 @@ procedure TfrmPackageEditor.editInfoEmailChange(Sender: TObject); begin if FSetup > 0 then Exit; if Trim(editInfoEmail.Text) = '' - then pack.Info.URL['Author'] := '' - else pack.Info.URL['Author'] := 'mailto:'+Trim(editInfoEmail.Text); + then pack.Info.URL[PackageInfo_Author] := '' + else pack.Info.URL[PackageInfo_Author] := 'mailto:'+Trim(editInfoEmail.Text); Modified := True; end; procedure TfrmPackageEditor.editInfoWebSiteChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['WebSite'] := Trim(editInfoWebSite.Text); - pack.Info.URL['WebSite'] := Trim(editInfoWebSite.Text); + pack.Info.Desc[PackageInfo_WebSite] := Trim(editInfoWebSite.Text); + pack.Info.URL[PackageInfo_WebSite] := Trim(editInfoWebSite.Text); Modified := True; end; @@ -734,7 +734,7 @@ begin try f.Description := 'Keyboard '+ki.KeyboardName; // Fill in some info stuff as well... - if pack.Info.Desc[PackageInfo_Version] = '' then // I4690 + if not pack.KPSOptions.FollowKeyboardVersion and (pack.Info.Desc[PackageInfo_Version] = '') then // I4690 begin pack.Info.Desc[PackageInfo_Version] := ki.KeyboardVersion; end; @@ -1021,6 +1021,11 @@ procedure TfrmPackageEditor.chkFollowKeyboardVersionClick(Sender: TObject); begin if FSetup > 0 then Exit; pack.KPSOptions.FollowKeyboardVersion := chkFollowKeyboardVersion.Checked; + if chkFollowKeyboardVersion.Checked then + begin + editInfoVersion.Text := ''; + pack.Info.Desc[PackageInfo_Version] := ''; + end; EnableDetailsTabControls; Modified := True; end; @@ -1082,7 +1087,7 @@ end; procedure TfrmPackageEditor.memoInfoDescriptionChange(Sender: TObject); begin if FSetup > 0 then Exit; - pack.Info.Desc['Description'] := Trim(memoInfoDescription.Text); + pack.Info.Desc[PackageInfo_Description] := Trim(memoInfoDescription.Text); Modified := True; end; diff --git a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas index a5853faba0..8b9875f7b0 100644 --- a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas +++ b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas @@ -62,6 +62,7 @@ type public constructor Create; destructor Destroy; override; + function ToString: string; override; property Action: TDebugEventActionData read FAction; property Rule: TDebugEventRuleData read FRule; property EventType: TDebugEventType read FEventType write SetEventType; @@ -94,6 +95,8 @@ type vk: uint16_t; modifier_state: uint16_t ): Boolean; overload; + + function ToString: string; override; end; implementation @@ -134,6 +137,23 @@ begin end; end; +function TDebugEvent.ToString: string; +begin + // Debug string + if FEventType = etAction then + begin + Result := Format('A %d %d %d "%s"', [Ord(FAction.ActionType), FAction.dwData, FAction.nExpectedValue, FAction.Text]); + end + else if FEventType = etRuleMatch then + begin + Result := Format('R %d %x %x %d "%s"', [FRule.ItemType, FRule.Key.VirtualKey, FRule.Key.Modifiers, FRule.Line, FRule.Context]); + end + else + begin + Result := '?'+IntToStr(Ord(FEventType)); + end; +end; + function TDebugEventList.AddActionItem(key: Word; action: pkm_core_action_item): Boolean; begin Result := True; @@ -330,6 +350,15 @@ begin end; end; +function TDebugEventList.ToString: string; +var + I: Integer; +begin + Result := ''; + for I := 0 to Count - 1 do + Result := Result + '['+IntToStr(I)+': '+Items[I].ToString + '] '; +end; + function TDebugEventList.AddStateItems( state: pkm_core_state; vk: uint16_t; @@ -342,8 +371,18 @@ var action_index: Integer; begin Result := True; + + if not Assigned(state) then + raise Exception.Create('TDebugEventList.AddStateItems: expected state not to be nil'); + debug := km_core_state_debug_items(state, nil); + if not Assigned(debug) then + raise Exception.Create('TDebugEventList.AddStateItems: expected debug not to be nil'); + action := km_core_state_action_items(state, nil); + if not Assigned(action) then + raise Exception.Create('TDebugEventList.AddStateItems: expected action not to be nil'); + action_index := 0; while debug._type <> KM_CORE_DEBUG_END do begin diff --git a/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas b/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas index 2260b8a183..904d389a08 100644 --- a/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas +++ b/developer/src/tike/http/Keyman.Developer.System.HttpServer.AppSource.pas @@ -56,6 +56,7 @@ uses Xml.XMLDoc, Xml.XMLIntf, + Keyman.System.KeymanSentryClient, RedistFiles; { TAppSourceHttpResponder } @@ -145,6 +146,7 @@ begin begin AResponseInfo.ContentType := 'application/json'; AResponseInfo.Charset := 'UTF-8'; + TKeymanSentryClient.Breadcrumb('default', Format('RespondTouchEditorState: Filename=%s[#state]', [AFilename]), 'AppSourceHttpResponder'); if TryGetSource(AFilename + '#state', FData) then AResponseInfo.ContentText := FData else AResponseInfo.ContentText := '{}'; @@ -167,6 +169,7 @@ var FData: string; begin // TODO: data will be passed separate call from touch editor later + TKeymanSentryClient.Breadcrumb('default', Format('RespondTouchEditor: Filename=%s', [AFilename]), 'AppSourceHttpResponder'); if not TryGetSource(AFilename, FData) then begin Respond404(AContext, ARequestInfo, AResponseInfo); @@ -216,6 +219,8 @@ var begin T := FSources.LockList; try + TKeymanSentryClient.Breadcrumb('default', Format('TrySetSource: Filename=%s count=%d', [Filename, T.Count]), 'AppSourceHttpResponder'); + for i := 0 to T.Count - 1 do if T[i].Filename = Filename then begin @@ -236,6 +241,7 @@ procedure TAppSourceHttpResponder.RespondGet(const AFilename: string; var FData: string; begin + TKeymanSentryClient.Breadcrumb('default', Format('RespondGet: Filename=%s', [AFilename]), 'AppSourceHttpResponder'); if TryGetSource(AFilename, FData) then begin AResponseInfo.ContentType := 'application/json'; @@ -267,6 +273,7 @@ var begin T := FSources.LockList; try + TKeymanSentryClient.Breadcrumb('default', Format('TryGetSource: Filename=%s count=%d', [Filename, T.Count]), 'AppSourceHttpResponder'); for i := 0 to T.Count - 1 do if T[i].Filename = Filename then begin @@ -281,6 +288,7 @@ end; function TAppSourceHttpResponder.GetSource(const Filename: string): string; begin + TKeymanSentryClient.Breadcrumb('default', Format('GetSource: Filename=%s', [Filename]), 'AppSourceHttpResponder'); Assert(TryGetSource(Filename, Result), 'TAppSourceHttpResponder.GetSource was asked for a file that was not registered: '+Filename); end; @@ -289,6 +297,7 @@ function TAppSourceHttpResponder.IsSourceRegistered(const Filename: string): Boo var Data: string; begin + TKeymanSentryClient.Breadcrumb('default', Format('IsSourceRegistered: Filename=%s', [Filename]), 'AppSourceHttpResponder'); Result := TryGetSource(Filename, Data); end; @@ -300,6 +309,8 @@ var begin T := FSources.LockList; try + TKeymanSentryClient.Breadcrumb('default', Format('RegisterSource: Filename=%s Update=%s count=%d', [Filename, BoolToStr(Update,True), T.Count]), 'AppSourceHttpResponder'); + S.Filename := Filename; S.Data := Data; @@ -326,6 +337,7 @@ var begin T := FSources.LockList; try + TKeymanSentryClient.Breadcrumb('default', Format('UnregisterSource: Filename=%s count=%d', [Filename, T.Count]), 'AppSourceHttpResponder'); for i := 0 to T.Count - 1 do if T[i].Filename = Filename then begin diff --git a/developer/src/tike/kmlmc.cmd b/developer/src/tike/kmlmc.cmd deleted file mode 100644 index 4f86f2e3bd..0000000000 --- a/developer/src/tike/kmlmc.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -setlocal -rem This script is based on /developer/src/node/inst/kmlmc.cmd. It is stored here -rem in order to allow TIKE to call out to the compiler while debugging. -if exist "%~dp0..\inst\node\dist\node.exe" ( - rem If running in developer/src/tike/: - set nodeexe="%~dp0..\inst\node\dist\node.exe" - set nodecli="%~dp0..\kmc\build\src\kmlmc.js" -) else if exist "%~dp0..\src\inst\node\dist\node.exe" ( - rem If running in developer/bin/: - set nodeexe="%~dp0..\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\src\kmc\build\src\kmlmc.js" -) else ( - rem Cannot find node or kmlmc.js relative to execution path - echo Error: node.exe or kmlmc.js not found. - exit /b 1 -) - -%nodeexe% --enable-source-maps %nodecli% %* -exit /b %errorlevel% diff --git a/developer/src/tike/kmlmp.cmd b/developer/src/tike/kmlmp.cmd deleted file mode 100644 index 716a5bf940..0000000000 --- a/developer/src/tike/kmlmp.cmd +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -setlocal -rem This script is based on /developer/src/node/inst/kmlmp.cmd. It is stored here -rem in order to allow TIKE to call out to the compiler while debugging. -if exist "%~dp0..\inst\node\dist\node.exe" ( - rem If running in developer/src/tike/: - set nodeexe="%~dp0..\inst\node\dist\node.exe" - set nodecli="%~dp0..\kmc\build\src\kmlmp.js" -) else if exist "%~dp0..\src\inst\node\dist\node.exe" ( - rem If running in developer/bin/: - set nodeexe="%~dp0..\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\src\kmc\build\src\kmlmp.js" -) else ( - rem Cannot find node or kmlmp.js relative to execution path - echo Error: node.exe or kmlmp.js not found. - exit /b 1 -) - -%nodeexe% --enable-source-maps %nodecli% %* -exit /b %errorlevel% diff --git a/developer/src/tike/main/Keyman.Developer.UI.FormValidation.pas b/developer/src/tike/main/Keyman.Developer.UI.FormValidation.pas new file mode 100644 index 0000000000..c84363e56f --- /dev/null +++ b/developer/src/tike/main/Keyman.Developer.UI.FormValidation.pas @@ -0,0 +1,132 @@ +(* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-09-01 + * + * Utility class to simplify form validation + *) +unit Keyman.Developer.UI.FormValidation; + +interface + +uses + System.Classes, + System.SysUtils, + Vcl.Controls, + Vcl.Graphics, + Vcl.StdCtrls; + +type + TValidationMethod = record + Method: TFunc; + Message: string; + end; + + TValidationField = record + CaptionLabel: TLabel; + ValidationLabel: TLabel; + Control: TControl; + Methods: TArray; + ControlTouched: Boolean; + end; + + TValidationFields = TArray; + + TFormValidation = class + private + Fields: TValidationFields; + public + procedure Add(CaptionLabel, ValidationLabel: TLabel; Control: TControl); + procedure AddMethod(control: TControl; method: TFunc; + message: string); + function Find(control: TControl): Integer; + procedure TouchField(ChangingControl: TControl); public + procedure TouchAllFields; + function Update: Boolean; + end; + +implementation + +procedure TFormValidation.TouchAllFields; +var + i: Integer; +begin + for i := 0 to High(Fields) do + Fields[i].ControlTouched := True; + Update; +end; + +function TFormValidation.Update: Boolean; +var + f: TValidationField; + m: TValidationMethod; + n: Integer; +begin + Result := True; + + for f in Fields do + begin + f.ValidationLabel.Caption := ''; + f.CaptionLabel.ParentFont := True; + + for m in f.Methods do + begin + if not m.Method() then + begin + if f.ControlTouched then + begin + f.ValidationLabel.Caption := m.Message; + f.CaptionLabel.Font.Color := clRed; + end; + + Result := False; + Break; // Still report on following fields + end; + end; + end; +end; + +function TFormValidation.Find(control: TControl): Integer; +begin + for Result := 0 to High(Fields) do + if Fields[Result].Control = control then + Exit; + Result := -1; +end; + +procedure TFormValidation.Add(CaptionLabel, ValidationLabel: TLabel; Control: TControl); +var + n: Integer; +begin + n := Length(Fields); + SetLength(Fields, n+1); + Fields[n].CaptionLabel := CaptionLabel; + Fields[n].ValidationLabel := ValidationLabel; + Fields[n].Control := Control; + SetLength(Fields[n].Methods, 0); +end; + +procedure TFormValidation.AddMethod(control: TControl; method: TFunc; message: string); +var + m, n: Integer; +begin + n := Find(control); + if n < 0 then + raise Exception.Create('Could not find control '+control.Name); + m := Length(Fields[n].Methods); + SetLength(Fields[n].Methods, m + 1); + Fields[n].Methods[m].Method := method; + Fields[n].Methods[m].Message := message; +end; + +procedure TFormValidation.TouchField(ChangingControl: TControl); +var + n: Integer; +begin + n := Find(ChangingControl); + if n < 0 then + raise Exception.Create('Could not find control '+ChangingControl.Name); + Fields[n].ControlTouched := True; +end; + +end. diff --git a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas index 1bc2c5c8de..fb1102ba62 100644 --- a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas +++ b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas @@ -95,6 +95,7 @@ type procedure CharMapDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure UnregisterSources; + procedure TouchLayoutMessage(Sender: TObject; const Message: string); protected function GetHelpTopic: string; override; @@ -140,7 +141,9 @@ uses xmldoc, Keyman.Developer.System.HelpTopics, + Keyman.Developer.System.Project.ProjectLog, Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter, + Keyman.Developer.UI.Project.ProjectFileUI, CharacterDragObject, CharMapDropTool, @@ -309,6 +312,11 @@ begin else Result := s <> FSavedLayoutJS; end; +procedure TframeTouchLayoutBuilder.TouchLayoutMessage(Sender: TObject; const Message: string); +begin + LogMessage(plsError, FFilename, Message, 0, 0); +end; + function TframeTouchLayoutBuilder.Load(const AFilename: string; ALoadFromTemplate, ALoadFromString: Boolean): Boolean; var FLastFilename: string; @@ -355,8 +363,16 @@ begin with TStringList.Create do try - LoadFromFile(FBaseFileName, TEncoding.UTF8); - FNewLayoutJS := Text; + try + LoadFromFile(FBaseFileName, TEncoding.UTF8); + FNewLayoutJS := Text; + except + on E:Exception do + begin + ShowMessage(E.Message); + Exit(False); + end; + end; finally Free; end; @@ -364,6 +380,7 @@ begin FTouchLayout := TTouchLayout.Create; // I3642 try + FTouchLayout.OnMessage := TouchLayoutMessage; if not FTouchLayout.Load(FNewLayoutJS) then begin FLastError := FTouchLayout.LoadError; // I4083 diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.dfm b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.dfm index 6ef44a8f7a..22caaa8904 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.dfm +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.dfm @@ -1,222 +1,338 @@ -inherited frmNewLDMLKeyboardProjectParameters: TfrmNewLDMLKeyboardProjectParameters - BorderIcons = [biSystemMenu] - BorderStyle = bsDialog - Caption = 'New LDML Keyboard Project' - ClientHeight = 465 - ClientWidth = 625 - Position = poScreenCenter - ExplicitWidth = 631 - ExplicitHeight = 494 - PixelsPerInch = 96 - TextHeight = 13 - object lblFileName: TLabel - Left = 12 - Top = 406 - Width = 64 - Height = 13 - Caption = '&Keyboard ID:' - FocusControl = editKeyboardID - end - object lblPath: TLabel - Left = 12 - Top = 379 - Width = 26 - Height = 13 - Caption = '&Path:' - FocusControl = editPath - end - object lblKeyboardName: TLabel - Left = 12 - Top = 11 - Width = 80 - Height = 13 - Caption = 'Keyboard &Name:' - FocusControl = editKeyboardName - end - object lblCoypright: TLabel - Left = 12 - Top = 180 - Width = 51 - Height = 13 - Caption = '&Copyright:' - FocusControl = editCopyright - end - object lblVersion: TLabel - Left = 12 - Top = 233 - Width = 39 - Height = 13 - Caption = '&Version:' - FocusControl = editVersion - end - object lblAuthor: TLabel - Left = 12 - Top = 153 - Width = 37 - Height = 13 - Caption = 'A&uthor:' - FocusControl = editAuthor - end - object lblKeyboardLanguages: TLabel - Left = 419 - Top = 260 - Width = 145 - Height = 26 - Caption = '(one BCP 47 tag per line, first tag is primary)' - WordWrap = True - end - object lblProjectFilename: TLabel - Left = 12 - Top = 433 - Width = 77 - Height = 13 - Caption = 'Project &filename' - FocusControl = editProjectFilename - end - object Label1: TLabel - Left = 12 - Top = 206 - Width = 68 - Height = 13 - Caption = 'Fu&ll copyright:' - FocusControl = editFullCopyright - end - object Label2: TLabel - Left = 12 - Top = 38 - Width = 53 - Height = 13 - Caption = '&Description' - FocusControl = memoDescription - end - object Label3: TLabel - Left = 12 - Top = 260 - Width = 102 - Height = 13 - Caption = '&Supported languages' - FocusControl = memoLanguages - end - object Label4: TLabel - Left = 331 - Top = 233 - Width = 232 - Height = 13 - Caption = 'Semantic version: major.minor.patch, e.g. 1.0.1' - end - object editKeyboardID: TEdit - Left = 120 - Top = 403 - Width = 205 - Height = 21 - TabOrder = 9 - OnChange = editKeyboardIDChange - end - object cmdBrowse: TButton - Left = 340 - Top = 376 - Width = 73 - Height = 21 - Caption = '&Browse...' - TabOrder = 8 - OnClick = cmdBrowseClick - end - object editPath: TEdit - Left = 120 - Top = 376 - Width = 205 - Height = 21 - TabOrder = 7 - OnChange = editPathChange - end - object editKeyboardName: TEdit - Left = 120 - Top = 8 - Width = 205 - Height = 21 - TabOrder = 0 - OnChange = editKeyboardNameChange - end - object editCopyright: TEdit - Left = 120 - Top = 176 - Width = 205 - Height = 21 - TabOrder = 3 - Text = 'Copyright '#169 - OnChange = editCopyrightChange - end - object editVersion: TEdit - Left = 120 - Top = 230 - Width = 205 - Height = 21 - TabOrder = 5 - Text = '1.0.0' - OnChange = editVersionChange - end - object editAuthor: TEdit - Left = 120 - Top = 150 - Width = 205 - Height = 21 - TabOrder = 2 - OnChange = editAuthorChange - end - object cmdOK: TButton - Left = 463 - Top = 428 - Width = 73 - Height = 25 - Caption = 'OK' - Default = True - TabOrder = 11 - OnClick = cmdOKClick - end - object cmdCancel: TButton - Left = 542 - Top = 428 - Width = 73 - Height = 25 - Cancel = True - Caption = 'Cancel' - ModalResult = 2 - TabOrder = 12 - end - object editProjectFilename: TEdit - Left = 120 - Top = 430 - Width = 293 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 10 - OnChange = editKeyboardIDChange - end - object editFullCopyright: TEdit - Left = 120 - Top = 203 - Width = 205 - Height = 21 - TabOrder = 4 - Text = 'Copyright '#169' YYYY' - OnChange = editFullCopyrightChange - end - object memoDescription: TMemo - Left = 120 - Top = 35 - Width = 497 - Height = 109 - TabOrder = 1 - OnChange = memoDescriptionChange - end - object memoLanguages: TMemo - Left = 120 - Top = 257 - Width = 293 - Height = 113 - TabOrder = 6 - OnChange = memoLanguagesChange - end -end +inherited frmNewLDMLKeyboardProjectParameters: TfrmNewLDMLKeyboardProjectParameters + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'New LDML Keyboard Project' + ClientHeight = 520 + ClientWidth = 721 + Position = poScreenCenter + OnDestroy = FormDestroy + ExplicitWidth = 727 + ExplicitHeight = 549 + PixelsPerInch = 96 + TextHeight = 13 + object lblKeyboardID: TLabel + Left = 12 + Top = 430 + Width = 64 + Height = 13 + Caption = '&Keyboard ID:' + FocusControl = editKeyboardID + end + object lblPath: TLabel + Left = 12 + Top = 403 + Width = 26 + Height = 13 + Caption = '&Path:' + FocusControl = editPath + end + object lblKeyboardName: TLabel + Left = 12 + Top = 11 + Width = 80 + Height = 13 + Caption = 'Keyboard &Name:' + FocusControl = editKeyboardName + end + object lblCoypright: TLabel + Left = 12 + Top = 180 + Width = 51 + Height = 13 + Caption = '&Copyright:' + FocusControl = editCopyright + end + object lblVersion: TLabel + Left = 12 + Top = 233 + Width = 39 + Height = 13 + Caption = '&Version:' + FocusControl = editVersion + end + object lblAuthor: TLabel + Left = 12 + Top = 153 + Width = 37 + Height = 13 + Caption = 'A&uthor:' + FocusControl = editAuthor + end + object lblKeyboardLanguages: TLabel + Left = 120 + Top = 381 + Width = 265 + Height = 26 + Caption = '(one BCP 47 tag per line, first tag is primary)' + WordWrap = True + end + object lblProjectFilename: TLabel + Left = 12 + Top = 457 + Width = 77 + Height = 13 + Caption = 'Project &filename' + FocusControl = editProjectFilename + end + object lblFullCopyright: TLabel + Left = 12 + Top = 206 + Width = 68 + Height = 13 + Caption = 'Fu&ll copyright:' + FocusControl = editFullCopyright + end + object lblDescription: TLabel + Left = 12 + Top = 38 + Width = 53 + Height = 13 + Caption = '&Description' + FocusControl = memoDescription + end + object lblLanguages: TLabel + Left = 12 + Top = 284 + Width = 102 + Height = 13 + Caption = '&Supported languages' + FocusControl = memoLanguages + end + object Label4: TLabel + Left = 120 + Top = 256 + Width = 232 + Height = 13 + Caption = 'Semantic version: major.minor.patch, e.g. 1.0.1' + end + object lblKeyboardNameValidation: TLabel + Left = 424 + Top = 11 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editKeyboardName + WordWrap = True + end + object lblDescriptionValidation: TLabel + Left = 424 + Top = 38 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = memoDescription + WordWrap = True + end + object lblCopyrightValidation: TLabel + Left = 424 + Top = 180 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editCopyright + WordWrap = True + end + object lblVersionValidation: TLabel + Left = 424 + Top = 233 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editVersion + WordWrap = True + end + object lblAuthorValidation: TLabel + Left = 424 + Top = 153 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editAuthor + WordWrap = True + end + object lblFullCopyrightValidation: TLabel + Left = 424 + Top = 206 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editFullCopyright + WordWrap = True + end + object lblLanguagesValidation: TLabel + Left = 424 + Top = 284 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + WordWrap = True + end + object lblPathValidation: TLabel + Left = 424 + Top = 403 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object lblKeyboardIDValidation: TLabel + Left = 424 + Top = 430 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editKeyboardID + WordWrap = True + end + object lblProjectFilenameValidation: TLabel + Left = 424 + Top = 457 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editProjectFilename + WordWrap = True + end + object lblDescriptionMarkdown: TLabel + Left = 120 + Top = 123 + Width = 197 + Height = 13 + Caption = 'Markdown accepted, no embedded HTML' + Transparent = True + end + object editKeyboardID: TEdit + Left = 120 + Top = 427 + Width = 293 + Height = 21 + TabOrder = 9 + OnChange = editKeyboardIDChange + OnExit = ControlExit + end + object cmdBrowse: TButton + Left = 340 + Top = 400 + Width = 73 + Height = 21 + Caption = '&Browse...' + TabOrder = 8 + OnClick = cmdBrowseClick + end + object editPath: TEdit + Left = 120 + Top = 400 + Width = 205 + Height = 21 + TabOrder = 7 + OnChange = editPathChange + OnExit = ControlExit + end + object editKeyboardName: TEdit + Left = 120 + Top = 8 + Width = 293 + Height = 21 + TabOrder = 0 + OnChange = editKeyboardNameChange + OnExit = ControlExit + end + object editCopyright: TEdit + Left = 120 + Top = 176 + Width = 293 + Height = 21 + TabOrder = 3 + Text = 'Copyright '#169 + OnChange = editCopyrightChange + OnExit = ControlExit + end + object editVersion: TEdit + Left = 120 + Top = 230 + Width = 293 + Height = 21 + TabOrder = 5 + Text = '1.0.0' + OnChange = editVersionChange + OnExit = ControlExit + end + object editAuthor: TEdit + Left = 120 + Top = 150 + Width = 293 + Height = 21 + TabOrder = 2 + OnChange = editAuthorChange + OnExit = ControlExit + end + object cmdOK: TButton + Left = 120 + Top = 487 + Width = 73 + Height = 25 + Caption = 'OK' + Default = True + TabOrder = 11 + OnClick = cmdOKClick + end + object cmdCancel: TButton + Left = 199 + Top = 487 + Width = 73 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 12 + end + object editProjectFilename: TEdit + Left = 120 + Top = 454 + Width = 293 + Height = 21 + TabStop = False + ParentColor = True + ReadOnly = True + TabOrder = 10 + end + object editFullCopyright: TEdit + Left = 120 + Top = 203 + Width = 293 + Height = 21 + TabOrder = 4 + Text = 'Copyright '#169' YYYY' + OnChange = editFullCopyrightChange + OnExit = ControlExit + end + object memoDescription: TMemo + Left = 120 + Top = 35 + Width = 293 + Height = 82 + TabOrder = 1 + OnChange = memoDescriptionChange + OnExit = ControlExit + end + object memoLanguages: TMemo + Left = 120 + Top = 279 + Width = 293 + Height = 96 + TabOrder = 6 + OnChange = memoLanguagesChange + OnExit = ControlExit + end +end diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas index 3868b6281b..4ffede1c08 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas @@ -19,12 +19,13 @@ uses Browse4Folder, + Keyman.Developer.UI.FormValidation, UfrmTike, utilfiletypes; type TfrmNewLDMLKeyboardProjectParameters = class(TTikeForm) - lblFileName: TLabel; + lblKeyboardID: TLabel; lblPath: TLabel; editKeyboardID: TEdit; cmdBrowse: TButton; @@ -42,13 +43,24 @@ type lblKeyboardLanguages: TLabel; lblProjectFilename: TLabel; editProjectFilename: TEdit; - Label1: TLabel; + lblFullCopyright: TLabel; editFullCopyright: TEdit; - Label2: TLabel; + lblDescription: TLabel; memoDescription: TMemo; memoLanguages: TMemo; - Label3: TLabel; + lblLanguages: TLabel; Label4: TLabel; + lblKeyboardNameValidation: TLabel; + lblDescriptionValidation: TLabel; + lblCopyrightValidation: TLabel; + lblVersionValidation: TLabel; + lblAuthorValidation: TLabel; + lblFullCopyrightValidation: TLabel; + lblLanguagesValidation: TLabel; + lblPathValidation: TLabel; + lblKeyboardIDValidation: TLabel; + lblProjectFilenameValidation: TLabel; + lblDescriptionMarkdown: TLabel; procedure cmdOKClick(Sender: TObject); procedure editKeyboardNameChange(Sender: TObject); procedure FormCreate(Sender: TObject); @@ -61,7 +73,10 @@ type procedure editFullCopyrightChange(Sender: TObject); procedure memoDescriptionChange(Sender: TObject); procedure memoLanguagesChange(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure ControlExit(Sender: TObject); private + FormValidation: TFormValidation; dlgBrowse: TBrowse4Folder; function GetAuthor: string; function GetBasePath: string; @@ -71,13 +86,14 @@ type function GetBCP47Tags: string; function GetVersion: string; function Validate: Boolean; - procedure EnableControls; + procedure EnableControls(ChangingControl: TControl); procedure SetBCP47Tags(const Value: string); procedure SetKeyboardID(const Value: string); procedure SetKeyboardName(const Value: string); procedure UpdateProjectFilename; function GetFullCopyright: string; function GetDescription: string; + procedure SetupValidation; protected function GetHelpTopic: string; override; public @@ -154,11 +170,16 @@ begin try pt.Generate; except - on E:EFOpenError do + on E:ELDMLKeyboardProjectTemplate do begin ShowMessage('Unable to create project: '+E.Message); Exit(False); end; + on E:EFOpenError do + begin + ShowMessage('Unable to create project; template files may be missing: '+E.Message); + Exit(False); + end; end; frmKeymanDeveloper.OpenProject(pt.ProjectFilename); @@ -177,6 +198,9 @@ end; procedure TfrmNewLDMLKeyboardProjectParameters.FormCreate(Sender: TObject); begin inherited; + + SetupValidation; + editPath.Text := FKeymanDeveloperOptions.DefaultProjectPath; editFullCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+FormatDateTime('yyyy', Now)+' '; @@ -186,9 +210,15 @@ begin dlgBrowse.Root := Desktop; dlgBrowse.Title := 'Select folder to save project to'; - EnableControls; + EnableControls(nil); end; +procedure TfrmNewLDMLKeyboardProjectParameters.FormDestroy(Sender: TObject); +begin + inherited; + FreeAndNil(FormValidation); +end; + procedure TfrmNewLDMLKeyboardProjectParameters.cmdBrowseClick(Sender: TObject); begin dlgBrowse.InitialDir := editPath.Text; @@ -199,51 +229,66 @@ end; procedure TfrmNewLDMLKeyboardProjectParameters.cmdOKClick(Sender: TObject); begin + FormValidation.TouchAllFields; if Validate then ModalResult := mrOk; end; procedure TfrmNewLDMLKeyboardProjectParameters.editAuthorChange(Sender: TObject); begin - EnableControls; if not editCopyright.Modified then + begin editCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+Author; + FormValidation.TouchField(editCopyright); + end; if not editFullCopyright.Modified then + begin editFullCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+FormatDateTime('yyyy', Now)+' '+Author; + FormValidation.TouchField(editFullCopyright); + end; + EnableControls(editAuthor); end; procedure TfrmNewLDMLKeyboardProjectParameters.editCopyrightChange(Sender: TObject); begin - EnableControls; + EnableControls(editCopyright); end; procedure TfrmNewLDMLKeyboardProjectParameters.editFullCopyrightChange(Sender: TObject); begin - EnableControls; + EnableControls(editFullCopyright); end; procedure TfrmNewLDMLKeyboardProjectParameters.editKeyboardIDChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editKeyboardID); end; procedure TfrmNewLDMLKeyboardProjectParameters.editKeyboardNameChange(Sender: TObject); begin if not editKeyboardID.Modified then + begin editKeyboardID.Text := TKeyboardUtils.CleanKeyboardID(Trim(editKeyboardName.Text)); - EnableControls; + FormValidation.TouchField(editKeyboardID); + end; + EnableControls(editKeyboardName); end; +procedure TfrmNewLDMLKeyboardProjectParameters.ControlExit(Sender: TObject); +begin + EnableControls(Sender as TControl); +end; + procedure TfrmNewLDMLKeyboardProjectParameters.editPathChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editPath); end; procedure TfrmNewLDMLKeyboardProjectParameters.editVersionChange(Sender: TObject); begin - EnableControls; + EnableControls(editVersion); end; function IsValidSemanticVersion(const s: string): Boolean; @@ -252,20 +297,6 @@ begin Result := TRegEx.Create('^\d+\.\d+\.\d+$').IsMatch(s); end; -procedure TfrmNewLDMLKeyboardProjectParameters.EnableControls; -var - e: Boolean; -begin - e := (Trim(editKeyboardName.Text) <> '') and - (Trim(editPath.Text) <> '') and - TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) and - IsValidSemanticVersion(Trim(editVersion.Text)) and - (Trim(memoDescription.Text) <> '') and - (Trim(memoLanguages.Text) <> ''); - - cmdOK.Enabled := e; -end; - function TfrmNewLDMLKeyboardProjectParameters.GetAuthor: string; begin Result := Trim(editAuthor.Text); @@ -322,6 +353,12 @@ function TfrmNewLDMLKeyboardProjectParameters.Validate: Boolean; var ProjectFolder: string; begin + if not FormValidation.Update then + begin + ShowMessage('One or more fields are missing or invalid. Please correct these fields and try again.'); + Exit(False); + end; + Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True); if Result then @@ -347,30 +384,30 @@ end; procedure TfrmNewLDMLKeyboardProjectParameters.SetBCP47Tags(const Value: string); begin memoLanguages.Text := Value.Replace(' ', #13#10); - EnableControls; + EnableControls(nil); end; procedure TfrmNewLDMLKeyboardProjectParameters.SetKeyboardID(const Value: string); begin editKeyboardID.Text := Value; - EnableControls; + EnableControls(nil); end; procedure TfrmNewLDMLKeyboardProjectParameters.SetKeyboardName(const Value: string); begin editKeyboardName.Text := Value; - EnableControls; + EnableControls(nil); end; procedure TfrmNewLDMLKeyboardProjectParameters.memoDescriptionChange(Sender: TObject); begin - EnableControls; + EnableControls(memoDescription); end; procedure TfrmNewLDMLKeyboardProjectParameters.memoLanguagesChange( Sender: TObject); begin - EnableControls; + EnableControls(memoLanguages); end; procedure TfrmNewLDMLKeyboardProjectParameters.UpdateProjectFilename; @@ -384,4 +421,75 @@ begin editProjectFilename.Perform(EM_SCROLLCARET, 0, 0); end; +//// +//// Validation +//// + +procedure TfrmNewLDMLKeyboardProjectParameters.SetupValidation; +begin + FormValidation := TFormValidation.Create; + + FormValidation.Add(lblKeyboardName, lblKeyboardNameValidation, editKeyboardName); + FormValidation.Add(lblDescription, lblDescriptionValidation, memoDescription); + FormValidation.Add(lblAuthor, lblAuthorValidation, editAuthor); + FormValidation.Add(lblCoypright, lblCopyrightValidation, editCopyright); + FormValidation.Add(lblFullCopyright, lblFullCopyrightValidation, editFullCopyright); + FormValidation.Add(lblVersion, lblVersionValidation, editVersion); + FormValidation.Add(lblKeyboardLanguages, lblLanguagesValidation, memoLanguages); + FormValidation.Add(lblPath, lblPathValidation, editPath); + FormValidation.Add(lblKeyboardID, lblKeyboardIDValidation, editKeyboardID); + FormValidation.Add(lblProjectFilename, lblProjectFilenameValidation, editProjectFilename); + + FormValidation.AddMethod( + editKeyboardName, + function: Boolean begin Result := Trim(editKeyboardName.Text) <> '' end, + 'Keyboard name cannot be empty' + ); + + FormValidation.AddMethod( + editPath, + function: Boolean begin Result := (Trim(editPath.Text) <> '') end, + 'Keyboard path cannot be empty' + ); + + FormValidation.AddMethod( + editKeyboardID, + function: Boolean begin Result := Trim(editKeyboardID.Text) <> '' end, + 'Keyboard ID cannot be empty' + ); + + FormValidation.AddMethod( + editKeyboardID, + function: Boolean begin Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) end, + 'Keyboard ID can include only a-z, 0-9, and _, and must not start with a digit' + ); + + FormValidation.AddMethod( + memoDescription, + function: Boolean begin Result := Trim(memoDescription.Text) <> '' end, + 'Description cannot be empty' + ); + + FormValidation.AddMethod( + editVersion, + function: Boolean begin Result := IsValidSemanticVersion(Trim(editVersion.Text)) end, + 'Version must follow a "1.2.3" semantic version format' + ); + + FormValidation.AddMethod( + memoLanguages, + function: Boolean begin Result := Trim(memoLanguages.Text) <> '' end, + 'Languages list cannot be empty' + ); +end; + +procedure TfrmNewLDMLKeyboardProjectParameters.EnableControls(ChangingControl: TControl); +begin + if Assigned(ChangingControl) then + begin + FormValidation.TouchField(ChangingControl); + end; + FormValidation.Update; +end; + end. diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.dfm b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.dfm index 9c4dc67724..41b5f810bc 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.dfm +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.dfm @@ -1,299 +1,445 @@ -inherited frmNewModelProjectParameters: TfrmNewModelProjectParameters - BorderIcons = [biSystemMenu] - BorderStyle = bsDialog - Caption = 'New Wordlist Lexical Model Project' - ClientHeight = 402 - ClientWidth = 756 - OldCreateOrder = True - Position = poScreenCenter - OnDestroy = FormDestroy - ExplicitWidth = 762 - ExplicitHeight = 431 - PixelsPerInch = 96 - TextHeight = 13 - object lblFileName: TLabel - Left = 8 - Top = 311 - Width = 46 - Height = 13 - Caption = 'Model &ID:' - FocusControl = editModelID - end - object lblPath: TLabel - Left = 8 - Top = 149 - Width = 26 - Height = 13 - Caption = '&Path:' - FocusControl = editPath - end - object lblAuthorID: TLabel - Left = 8 - Top = 230 - Width = 51 - Height = 13 - Caption = 'Aut&hor ID:' - FocusControl = editAuthorID - end - object lblCoypright: TLabel - Left = 8 - Top = 65 - Width = 51 - Height = 13 - Caption = '&Copyright:' - FocusControl = editCopyright - end - object lblVersion: TLabel - Left = 8 - Top = 120 - Width = 39 - Height = 13 - Caption = '&Version:' - FocusControl = editVersion - end - object lblAuthor: TLabel - Left = 8 - Top = 11 - Width = 67 - Height = 13 - Caption = 'A&uthor Name:' - FocusControl = editAuthor - end - object lblLanguages: TLabel - Left = 348 - Top = 114 - Width = 52 - Height = 13 - Caption = '&Languages' - FocusControl = gridLanguages - end - object lblBCP47: TLabel - Left = 8 - Top = 257 - Width = 90 - Height = 13 - Caption = '&Primary Language:' - end - object lblUniq: TLabel - Left = 8 - Top = 284 - Width = 67 - Height = 13 - Caption = 'Uni&que Name:' - FocusControl = editUniq - end - object lblModelName: TLabel - Left = 8 - Top = 38 - Width = 62 - Height = 13 - Caption = '&Model Name:' - FocusControl = editModelName - end - object Bevel1: TBevel - Left = 8 - Top = 205 - Width = 317 - Height = 4 - end - object lblProjectFilename: TLabel - Left = 8 - Top = 338 - Width = 77 - Height = 13 - Caption = 'Project &filename' - FocusControl = editProjectFilename - end - object Label1: TLabel - Left = 8 - Top = 92 - Width = 68 - Height = 13 - Caption = 'Fu&ll copyright:' - FocusControl = editFullCopyright - end - object Label2: TLabel - Left = 347 - Top = 10 - Width = 53 - Height = 13 - Caption = '&Description' - FocusControl = memoDescription - end - object editModelID: TEdit - Left = 120 - Top = 308 - Width = 205 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 15 - OnChange = editModelIDChange - end - object cmdBrowse: TButton - Left = 120 - Top = 173 - Width = 73 - Height = 21 - Caption = '&Browse...' - TabOrder = 6 - OnClick = cmdBrowseClick - end - object editPath: TEdit - Left = 120 - Top = 146 - Width = 205 - Height = 21 - TabOrder = 5 - OnChange = editPathChange - end - object editAuthorID: TEdit - Left = 120 - Top = 227 - Width = 205 - Height = 21 - TabOrder = 12 - OnChange = editModelIDComponentChange - end - object editCopyright: TEdit - Left = 120 - Top = 62 - Width = 205 - Height = 21 - TabOrder = 2 - OnChange = editCopyrightChange - end - object editVersion: TEdit - Left = 120 - Top = 117 - Width = 205 - Height = 21 - TabOrder = 4 - Text = '1.0' - OnChange = editVersionChange - end - object editAuthor: TEdit - Left = 120 - Top = 8 - Width = 205 - Height = 21 - TabOrder = 0 - OnChange = editAuthorChange - end - object cmdOK: TButton - Left = 302 - Top = 367 - Width = 73 - Height = 25 - Caption = 'OK' - Default = True - TabOrder = 17 - OnClick = cmdOKClick - end - object cmdCancel: TButton - Left = 381 - Top = 367 - Width = 73 - Height = 25 - Cancel = True - Caption = 'Cancel' - ModalResult = 2 - TabOrder = 18 - end - object gridLanguages: TStringGrid - Left = 347 - Top = 135 - Width = 317 - Height = 102 - ColCount = 2 - DefaultRowHeight = 16 - FixedCols = 0 - RowCount = 9 - Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] - TabOrder = 8 - OnClick = gridLanguagesClick - OnDblClick = gridLanguagesDblClick - ColWidths = ( - 78 - 64) - end - object cmdAddLanguage: TButton - Left = 671 - Top = 135 - Width = 73 - Height = 25 - Caption = '&Add...' - TabOrder = 9 - OnClick = cmdAddLanguageClick - end - object cmdEditLanguage: TButton - Left = 671 - Top = 166 - Width = 73 - Height = 25 - Caption = '&Edit...' - TabOrder = 10 - OnClick = cmdEditLanguageClick - end - object cmdRemoveLanguage: TButton - Left = 671 - Top = 197 - Width = 73 - Height = 25 - Caption = '&Remove' - TabOrder = 11 - OnClick = cmdRemoveLanguageClick - end - object editUniq: TEdit - Left = 120 - Top = 281 - Width = 205 - Height = 21 - TabOrder = 14 - OnChange = editModelIDComponentChange - end - object cbBCP47: TComboBox - Left = 120 - Top = 254 - Width = 205 - Height = 21 - Style = csDropDownList - TabOrder = 13 - OnClick = editModelIDComponentChange - end - object editModelName: TEdit - Left = 120 - Top = 35 - Width = 205 - Height = 21 - TabOrder = 1 - OnChange = editModelNameChange - end - object editProjectFilename: TEdit - Left = 120 - Top = 335 - Width = 284 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 16 - end - object editFullCopyright: TEdit - Left = 120 - Top = 89 - Width = 205 - Height = 21 - TabOrder = 3 - OnChange = editFullCopyrightChange - end - object memoDescription: TMemo - Left = 347 - Top = 31 - Width = 398 - Height = 79 - TabOrder = 7 - OnChange = memoDescriptionChange - end -end +inherited frmNewModelProjectParameters: TfrmNewModelProjectParameters + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'New Wordlist Lexical Model Project' + ClientHeight = 607 + ClientWidth = 684 + OldCreateOrder = True + Position = poScreenCenter + OnDestroy = FormDestroy + ExplicitWidth = 690 + ExplicitHeight = 636 + PixelsPerInch = 96 + TextHeight = 13 + object lblModelID: TLabel + Left = 9 + Top = 519 + Width = 46 + Height = 13 + Caption = 'Model &ID:' + FocusControl = editModelID + end + object lblPath: TLabel + Left = 8 + Top = 389 + Width = 26 + Height = 13 + Caption = '&Path:' + FocusControl = editPath + end + object lblAuthorID: TLabel + Left = 9 + Top = 438 + Width = 51 + Height = 13 + Caption = 'Aut&hor ID:' + FocusControl = editAuthorID + end + object lblCopyright: TLabel + Left = 8 + Top = 177 + Width = 51 + Height = 13 + Caption = '&Copyright:' + FocusControl = editCopyright + end + object lblVersion: TLabel + Left = 8 + Top = 232 + Width = 39 + Height = 13 + Caption = '&Version:' + FocusControl = editVersion + end + object lblAuthor: TLabel + Left = 8 + Top = 11 + Width = 67 + Height = 13 + Caption = 'A&uthor Name:' + FocusControl = editAuthor + end + object lblLanguages: TLabel + Left = 8 + Top = 259 + Width = 52 + Height = 13 + Caption = '&Languages' + FocusControl = gridLanguages + end + object lblBCP47: TLabel + Left = 9 + Top = 465 + Width = 90 + Height = 13 + Caption = '&Primary Language:' + end + object lblUniq: TLabel + Left = 9 + Top = 492 + Width = 67 + Height = 13 + Caption = 'Uni&que Name:' + FocusControl = editUniq + end + object lblModelName: TLabel + Left = 8 + Top = 38 + Width = 62 + Height = 13 + Caption = '&Model Name:' + FocusControl = editModelName + end + object Bevel1: TBevel + Left = 8 + Top = 421 + Width = 405 + Height = 2 + end + object lblProjectFilename: TLabel + Left = 9 + Top = 546 + Width = 77 + Height = 13 + Caption = 'Project &filename' + FocusControl = editProjectFilename + end + object lblFullCopyright: TLabel + Left = 8 + Top = 204 + Width = 68 + Height = 13 + Caption = 'Fu&ll copyright:' + FocusControl = editFullCopyright + end + object lblDescription: TLabel + Left = 8 + Top = 68 + Width = 53 + Height = 13 + Caption = '&Description' + FocusControl = memoDescription + end + object lblDescriptionMarkdown: TLabel + Left = 120 + Top = 150 + Width = 197 + Height = 13 + Caption = 'Markdown accepted, no embedded HTML' + Transparent = True + end + object lblModelNameValidation: TLabel + Left = 426 + Top = 38 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + WordWrap = True + end + object lblDescriptionValidation: TLabel + Left = 426 + Top = 68 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = memoDescription + WordWrap = True + end + object lblCopyrightValidation: TLabel + Left = 426 + Top = 180 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editCopyright + WordWrap = True + end + object lblVersionValidation: TLabel + Left = 426 + Top = 233 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editVersion + WordWrap = True + end + object lblAuthorValidation: TLabel + Left = 426 + Top = 11 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editAuthor + WordWrap = True + end + object lblFullCopyrightValidation: TLabel + Left = 426 + Top = 206 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editFullCopyright + WordWrap = True + end + object lblLanguagesValidation: TLabel + Left = 426 + Top = 259 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + WordWrap = True + end + object lblPathValidation: TLabel + Left = 426 + Top = 389 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object lblModelIDValidation: TLabel + Left = 426 + Top = 519 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + WordWrap = True + end + object lblProjectFilenameValidation: TLabel + Left = 426 + Top = 546 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editProjectFilename + WordWrap = True + end + object lblAuthorIDValidation: TLabel + Left = 426 + Top = 438 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object lblBCP47Validation: TLabel + Left = 426 + Top = 465 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object lblUniqValidation: TLabel + Left = 426 + Top = 492 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object editModelID: TEdit + Left = 121 + Top = 516 + Width = 292 + Height = 21 + TabStop = False + ParentColor = True + ReadOnly = True + TabOrder = 15 + OnChange = editModelIDChange + end + object cmdBrowse: TButton + Left = 340 + Top = 386 + Width = 73 + Height = 21 + Caption = '&Browse...' + TabOrder = 11 + OnClick = cmdBrowseClick + end + object editPath: TEdit + Left = 120 + Top = 386 + Width = 214 + Height = 21 + TabOrder = 10 + OnChange = editPathChange + OnExit = ControlExit + end + object editAuthorID: TEdit + Left = 121 + Top = 435 + Width = 292 + Height = 21 + TabOrder = 12 + OnChange = editModelIDComponentChange + OnExit = ControlExit + end + object editCopyright: TEdit + Left = 120 + Top = 174 + Width = 293 + Height = 21 + TabOrder = 3 + OnChange = editCopyrightChange + OnExit = ControlExit + end + object editVersion: TEdit + Left = 120 + Top = 229 + Width = 293 + Height = 21 + TabOrder = 5 + Text = '1.0' + OnChange = editVersionChange + OnExit = ControlExit + end + object editAuthor: TEdit + Left = 120 + Top = 8 + Width = 293 + Height = 21 + TabOrder = 0 + OnChange = editAuthorChange + OnExit = ControlExit + end + object cmdOK: TButton + Left = 120 + Top = 574 + Width = 73 + Height = 25 + Caption = 'OK' + Default = True + TabOrder = 17 + OnClick = cmdOKClick + end + object cmdCancel: TButton + Left = 199 + Top = 574 + Width = 73 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 18 + end + object gridLanguages: TStringGrid + Left = 121 + Top = 259 + Width = 292 + Height = 81 + ColCount = 2 + DefaultRowHeight = 16 + FixedCols = 0 + RowCount = 9 + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] + TabOrder = 6 + OnClick = gridLanguagesClick + OnDblClick = gridLanguagesDblClick + ColWidths = ( + 78 + 64) + end + object cmdAddLanguage: TButton + Left = 121 + Top = 346 + Width = 73 + Height = 25 + Caption = '&Add...' + TabOrder = 7 + OnClick = cmdAddLanguageClick + OnExit = cmdAddLanguageExit + end + object cmdEditLanguage: TButton + Left = 200 + Top = 346 + Width = 73 + Height = 25 + Caption = '&Edit...' + TabOrder = 8 + OnClick = cmdEditLanguageClick + end + object cmdRemoveLanguage: TButton + Left = 279 + Top = 346 + Width = 73 + Height = 25 + Caption = '&Remove' + TabOrder = 9 + OnClick = cmdRemoveLanguageClick + end + object editUniq: TEdit + Left = 121 + Top = 489 + Width = 292 + Height = 21 + TabOrder = 14 + OnChange = editModelIDComponentChange + OnExit = ControlExit + end + object cbBCP47: TComboBox + Left = 121 + Top = 462 + Width = 292 + Height = 21 + Style = csDropDownList + TabOrder = 13 + OnClick = editModelIDComponentChange + OnExit = ControlExit + end + object editModelName: TEdit + Left = 120 + Top = 35 + Width = 293 + Height = 21 + TabOrder = 1 + OnChange = editModelNameChange + OnExit = ControlExit + end + object editProjectFilename: TEdit + Left = 121 + Top = 543 + Width = 292 + Height = 21 + TabStop = False + ParentColor = True + ReadOnly = True + TabOrder = 16 + end + object editFullCopyright: TEdit + Left = 120 + Top = 201 + Width = 293 + Height = 21 + TabOrder = 4 + OnChange = editFullCopyrightChange + OnExit = ControlExit + end + object memoDescription: TMemo + Left = 120 + Top = 65 + Width = 293 + Height = 79 + TabOrder = 2 + OnChange = memoDescriptionChange + OnExit = ControlExit + end +end diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas index 6f94062480..0c238c16dc 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas @@ -20,6 +20,7 @@ uses Browse4Folder, + Keyman.Developer.UI.FormValidation, kpsfile, PackageInfo, UfrmTike, @@ -27,10 +28,10 @@ uses type TfrmNewModelProjectParameters = class(TTikeForm) - lblFileName: TLabel; + lblModelID: TLabel; lblPath: TLabel; lblAuthorID: TLabel; - lblCoypright: TLabel; + lblCopyright: TLabel; lblVersion: TLabel; lblAuthor: TLabel; lblLanguages: TLabel; @@ -56,10 +57,24 @@ type Bevel1: TBevel; lblProjectFilename: TLabel; editProjectFilename: TEdit; - Label1: TLabel; + lblFullCopyright: TLabel; editFullCopyright: TEdit; - Label2: TLabel; + lblDescription: TLabel; memoDescription: TMemo; + lblDescriptionMarkdown: TLabel; + lblModelNameValidation: TLabel; + lblDescriptionValidation: TLabel; + lblCopyrightValidation: TLabel; + lblVersionValidation: TLabel; + lblAuthorValidation: TLabel; + lblFullCopyrightValidation: TLabel; + lblLanguagesValidation: TLabel; + lblPathValidation: TLabel; + lblModelIDValidation: TLabel; + lblProjectFilenameValidation: TLabel; + lblAuthorIDValidation: TLabel; + lblBCP47Validation: TLabel; + lblUniqValidation: TLabel; procedure cmdOKClick(Sender: TObject); procedure editModelIDComponentChange(Sender: TObject); procedure FormCreate(Sender: TObject); @@ -78,7 +93,10 @@ type procedure editModelNameChange(Sender: TObject); procedure editFullCopyrightChange(Sender: TObject); procedure memoDescriptionChange(Sender: TObject); + procedure ControlExit(Sender: TObject); + procedure cmdAddLanguageExit(Sender: TObject); private + FormValidation: TFormValidation; pack: TKPSFile; FSetup: Integer; dlgBrowse: TBrowse4Folder; @@ -88,7 +106,7 @@ type function GetBCP47Tags: string; function GetVersion: string; function Validate: Boolean; - procedure EnableControls; + procedure EnableControls(ChangingControl: TControl); function SelectedLanguage: TPackageKeyboardLanguage; procedure LanguageGrid_Fill; procedure BCP47_Fill; @@ -104,7 +122,9 @@ type procedure UpdateProjectFilename; function GetFullCopyright: string; function GetDescription: string; - protected + procedure EnableGridControls; + + procedure SetupValidation; protected function GetHelpTopic: string; override; property AuthorID: string read GetAuthorID; property PrimaryBCP47: string read GetPrimaryBCP47; @@ -166,11 +186,16 @@ begin try pt.Generate; except - on E:EFOpenError do + on E:EModelProjectTemplate do begin ShowMessage('Unable to create project: '+E.Message); Exit(False); end; + on E:EFOpenError do + begin + ShowMessage('Unable to create project; template files may be missing: '+E.Message); + Exit(False); + end; end; frmKeymanDeveloper.OpenProject(pt.ProjectFilename); @@ -189,6 +214,8 @@ end; procedure TfrmNewModelProjectParameters.FormCreate(Sender: TObject); begin inherited; + SetupValidation; + editPath.Text := FKeymanDeveloperOptions.DefaultProjectPath; dlgBrowse := TBrowse4Folder.Create(Self); @@ -201,13 +228,14 @@ begin pack.LexicalModels.Add(TPackageLexicalModel.Create(pack)); LanguageGrid_Fill; - EnableControls; + EnableControls(nil); end; procedure TfrmNewModelProjectParameters.FormDestroy(Sender: TObject); begin inherited; FreeAndNil(pack); + FreeAndNil(FormValidation); end; function TfrmNewModelProjectParameters.GetHelpTopic: string; @@ -246,13 +274,18 @@ begin LanguageGrid_Fill; gridLanguages.Row := gridLanguages.RowCount - 1; gridLanguagesClick(gridLanguages); - EnableControls; + EnableControls(gridLanguages); end; finally frm.Free; end; end; +procedure TfrmNewModelProjectParameters.cmdAddLanguageExit(Sender: TObject); +begin + EnableControls(gridLanguages); +end; + procedure TfrmNewModelProjectParameters.cmdEditLanguageClick( Sender: TObject); var @@ -276,7 +309,7 @@ begin lang.Name := frm.LanguageName; BCP47_Fill; LanguageGrid_Fill; - EnableControls; + EnableControls(gridLanguages); end; finally frm.Free; @@ -297,84 +330,65 @@ begin lm.Languages.Remove(lang); LanguageGrid_Fill; BCP47_Fill; - EnableControls; + EnableControls(gridLanguages); end; procedure TfrmNewModelProjectParameters.cmdOKClick(Sender: TObject); begin + FormValidation.TouchAllFields; if Validate then ModalResult := mrOk; end; +procedure TfrmNewModelProjectParameters.ControlExit(Sender: TObject); +begin + EnableControls(Sender as TControl); +end; + procedure TfrmNewModelProjectParameters.editAuthorChange(Sender: TObject); begin UpdateAuthorIDFromAuthor; - EnableControls; + EnableControls(editAuthor); end; procedure TfrmNewModelProjectParameters.editModelNameChange(Sender: TObject); begin UpdateUniqFromModelName; - EnableControls; + EnableControls(editModelName); end; procedure TfrmNewModelProjectParameters.editCopyrightChange(Sender: TObject); begin - EnableControls; + EnableControls(editCopyright); end; procedure TfrmNewModelProjectParameters.editFullCopyrightChange( Sender: TObject); begin - EnableControls; + EnableControls(editFullCopyright); end; procedure TfrmNewModelProjectParameters.editModelIDChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editModelID); end; procedure TfrmNewModelProjectParameters.editModelIDComponentChange(Sender: TObject); begin UpdateModelIDFromComponents; - EnableControls; + EnableControls(Sender as TControl); end; procedure TfrmNewModelProjectParameters.editPathChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editPath); end; procedure TfrmNewModelProjectParameters.editVersionChange(Sender: TObject); begin - EnableControls; -end; - -procedure TfrmNewModelProjectParameters.EnableControls; -var - e: Boolean; -begin - e := - not Author.IsEmpty and - not Description.IsEmpty and - not ModelName.IsEmpty and - not AuthorID.IsEmpty and - not PrimaryBCP47.IsEmpty and - not Uniq.IsEmpty and - not Trim(editPath.Text).IsEmpty and - TLexicalModelUtils.DoesProjectFilenameFollowLexicalModelConventions(editModelID.Text + Ext_LexicalModelProject) and - (pack.LexicalModels[0].Languages.Count > 0); - - cmdOK.Enabled := e; - - e := gridLanguages.RowCount > 1; - gridLanguages.Enabled := e; - cmdRemoveLanguage.Enabled := e; - cmdEditLanguage.Enabled := e; - if e then - gridLanguages.FixedRows := 1; + EnableControls(editVersion); end; function TfrmNewModelProjectParameters.GetAuthor: string; @@ -447,7 +461,7 @@ end; procedure TfrmNewModelProjectParameters.gridLanguagesClick( Sender: TObject); begin - EnableControls; + EnableGridControls; end; procedure TfrmNewModelProjectParameters.gridLanguagesDblClick( @@ -461,6 +475,12 @@ function TfrmNewModelProjectParameters.Validate: Boolean; var ProjectFolder: string; begin + if not FormValidation.Update then + begin + ShowMessage('One or more fields are missing or invalid. Please correct these fields and try again.'); + Exit(False); + end; + Result := TLexicalModelUtils.DoesProjectFilenameFollowLexicalModelConventions(editModelID.Text + Ext_LexicalModelProject); if Result then @@ -548,7 +568,7 @@ begin if not Assigned(lm) then begin gridLanguages.RowCount := 1; - EnableControls; + EnableGridControls; Exit; end; @@ -562,7 +582,7 @@ begin gridLanguages.Cells[1, i+1] := lm.Languages[i].Name; end; - EnableControls; + EnableGridControls; finally Dec(FSetup); end; @@ -570,7 +590,7 @@ end; procedure TfrmNewModelProjectParameters.memoDescriptionChange(Sender: TObject); begin - EnableControls; + EnableControls(memoDescription); end; procedure TfrmNewModelProjectParameters.BCP47_Fill; @@ -594,4 +614,103 @@ begin editModelIDComponentChange(cbBCP47); end; +//// +//// Validation +//// + +procedure TfrmNewModelProjectParameters.SetupValidation; +begin + FormValidation := TFormValidation.Create; + + FormValidation.Add(lblAuthor, lblAuthorValidation, editAuthor); + FormValidation.Add(lblModelName, lblModelNameValidation, editModelName); + FormValidation.Add(lblDescription, lblDescriptionValidation, memoDescription); + FormValidation.Add(lblCopyright, lblCopyrightValidation, editCopyright); + FormValidation.Add(lblFullCopyright, lblFullCopyrightValidation, editFullCopyright); + FormValidation.Add(lblVersion, lblVersionValidation, editVersion); + FormValidation.Add(lblLanguages, lblLanguagesValidation, gridLanguages); + FormValidation.Add(lblPath, lblPathValidation, editPath); + FormValidation.Add(lblAuthorID, lblAuthorIDValidation, editAuthorID); + FormValidation.Add(lblBCP47, lblBCP47Validation, cbBCP47); + FormValidation.Add(lblUniq, lblUniqValidation, editUniq); + FormValidation.Add(lblModelID, lblModelIDValidation, editModelID); + FormValidation.Add(lblProjectFilename, lblProjectFilenameValidation, editProjectFilename); + + FormValidation.AddMethod( + editAuthor, + function: Boolean begin Result := not Author.IsEmpty end, + 'Author name cannot be empty' + ); + + FormValidation.AddMethod( + memoDescription, + function: Boolean begin Result := not Description.IsEmpty end, + 'Description cannot be empty' + ); + + FormValidation.AddMethod( + editModelName, + function: Boolean begin Result := not ModelName.IsEmpty end, + 'Model name cannot be empty' + ); + + FormValidation.AddMethod( + editAuthorID, + function: Boolean begin Result := not AuthorID.IsEmpty end, + 'Author ID cannot be empty' + ); + + FormValidation.AddMethod( + cbBCP47, + function: Boolean begin Result := not PrimaryBCP47.IsEmpty end, + 'Primary language cannot be empty' + ); + + FormValidation.AddMethod( + editUniq, + function: Boolean begin Result := not Uniq.IsEmpty end, + 'Unique name cannot be empty' + ); + + FormValidation.AddMethod( + editPath, + function: Boolean begin Result := (Trim(editPath.Text) <> '') end, + 'Model path cannot be empty' + ); + + FormValidation.AddMethod( + editModelID, + function: Boolean begin Result := TLexicalModelUtils.DoesProjectFilenameFollowLexicalModelConventions(editModelID.Text + Ext_LexicalModelProject) end, + 'Model ID must follow convention of author.bcp47.uniq and cannot be empty' + ); + + FormValidation.AddMethod( + gridLanguages, + function: Boolean begin Result := Assigned(pack) and (pack.LexicalModels[0].Languages.Count > 0) end, + 'Model must reference at least one language' + ); +end; + +procedure TfrmNewModelProjectParameters.EnableControls(ChangingControl: TControl); +begin + if Assigned(ChangingControl) then + begin + FormValidation.TouchField(ChangingControl); + end; + FormValidation.Update; + EnableGridControls; +end; + +procedure TfrmNewModelProjectParameters.EnableGridControls; +var + e: Boolean; +begin + e := gridLanguages.RowCount > 1; + gridLanguages.Enabled := e; + cmdRemoveLanguage.Enabled := e; + cmdEditLanguage.Enabled := e; + if e then + gridLanguages.FixedRows := 1; +end; + end. diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.dfm b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.dfm index c172f52cdc..8d48ea871c 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.dfm +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.dfm @@ -1,261 +1,387 @@ -inherited frmNewProjectParameters: TfrmNewProjectParameters - BorderIcons = [biSystemMenu] - BorderStyle = bsDialog - Caption = 'New Basic Keyboard Project' - ClientHeight = 444 - ClientWidth = 625 - Position = poScreenCenter - OnDestroy = FormDestroy - ExplicitWidth = 631 - ExplicitHeight = 473 - PixelsPerInch = 96 - TextHeight = 13 - object lblFileName: TLabel - Left = 12 - Top = 390 - Width = 64 - Height = 13 - Caption = '&Keyboard ID:' - FocusControl = editKeyboardID - end - object lblPath: TLabel - Left = 12 - Top = 363 - Width = 26 - Height = 13 - Caption = '&Path:' - FocusControl = editPath - end - object lblKeyboardName: TLabel - Left = 12 - Top = 11 - Width = 80 - Height = 13 - Caption = 'Keyboard &Name:' - FocusControl = editKeyboardName - end - object lblCoypright: TLabel - Left = 12 - Top = 180 - Width = 51 - Height = 13 - Caption = '&Copyright:' - FocusControl = editCopyright - end - object lblVersion: TLabel - Left = 12 - Top = 233 - Width = 39 - Height = 13 - Caption = '&Version:' - FocusControl = editVersion - end - object lblAuthor: TLabel - Left = 12 - Top = 153 - Width = 37 - Height = 13 - Caption = 'A&uthor:' - FocusControl = editAuthor - end - object lblTargets: TLabel - Left = 12 - Top = 260 - Width = 41 - Height = 13 - Caption = '&Targets:' - FocusControl = clbTargets - end - object lblKeyboardLanguages: TLabel - Left = 339 - Top = 152 - Width = 52 - Height = 13 - Caption = '&Languages' - FocusControl = gridKeyboardLanguages - end - object lblProjectFilename: TLabel - Left = 12 - Top = 417 - Width = 77 - Height = 13 - Caption = 'Project &filename' - FocusControl = editProjectFilename - end - object Label1: TLabel - Left = 12 - Top = 206 - Width = 68 - Height = 13 - Caption = 'Fu&ll copyright:' - FocusControl = editFullCopyright - end - object Label2: TLabel - Left = 12 - Top = 38 - Width = 53 - Height = 13 - Caption = '&Description' - FocusControl = memoDescription - end - object editKeyboardID: TEdit - Left = 120 - Top = 387 - Width = 205 - Height = 21 - TabOrder = 13 - OnChange = editKeyboardIDChange - end - object cmdBrowse: TButton - Left = 340 - Top = 360 - Width = 73 - Height = 21 - Caption = '&Browse...' - TabOrder = 12 - OnClick = cmdBrowseClick - end - object editPath: TEdit - Left = 120 - Top = 360 - Width = 205 - Height = 21 - TabOrder = 11 - OnChange = editPathChange - end - object editKeyboardName: TEdit - Left = 120 - Top = 8 - Width = 205 - Height = 21 - TabOrder = 0 - OnChange = editKeyboardNameChange - end - object editCopyright: TEdit - Left = 120 - Top = 176 - Width = 205 - Height = 21 - TabOrder = 3 - Text = 'Copyright '#169 - OnChange = editCopyrightChange - end - object editVersion: TEdit - Left = 120 - Top = 230 - Width = 205 - Height = 21 - TabOrder = 5 - Text = '1.0' - OnChange = editVersionChange - end - object editAuthor: TEdit - Left = 120 - Top = 150 - Width = 205 - Height = 21 - TabOrder = 2 - OnChange = editAuthorChange - end - object cmdOK: TButton - Left = 463 - Top = 412 - Width = 73 - Height = 25 - Caption = 'OK' - Default = True - TabOrder = 15 - OnClick = cmdOKClick - end - object cmdCancel: TButton - Left = 542 - Top = 412 - Width = 73 - Height = 25 - Cancel = True - Caption = 'Cancel' - ModalResult = 2 - TabOrder = 16 - end - object clbTargets: TCheckListBox - Left = 120 - Top = 257 - Width = 205 - Height = 97 - OnClickCheck = clbTargetsClickCheck - ItemHeight = 13 - TabOrder = 6 - end - object gridKeyboardLanguages: TStringGrid - Left = 339 - Top = 173 - Width = 278 - Height = 120 - ColCount = 2 - DefaultRowHeight = 16 - FixedCols = 0 - RowCount = 9 - Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] - TabOrder = 7 - OnClick = gridKeyboardLanguagesClick - OnDblClick = gridKeyboardLanguagesDblClick - ColWidths = ( - 78 - 64) - end - object cmdKeyboardAddLanguage: TButton - Left = 340 - Top = 299 - Width = 73 - Height = 25 - Caption = '&Add...' - TabOrder = 8 - OnClick = cmdKeyboardAddLanguageClick - end - object cmdKeyboardEditLanguage: TButton - Left = 419 - Top = 299 - Width = 73 - Height = 25 - Caption = 'Ed&it...' - TabOrder = 9 - OnClick = cmdKeyboardEditLanguageClick - end - object cmdKeyboardRemoveLanguage: TButton - Left = 498 - Top = 299 - Width = 72 - Height = 25 - Caption = '&Remove' - TabOrder = 10 - OnClick = cmdKeyboardRemoveLanguageClick - end - object editProjectFilename: TEdit - Left = 120 - Top = 414 - Width = 293 - Height = 21 - TabStop = False - ParentColor = True - ReadOnly = True - TabOrder = 14 - OnChange = editKeyboardIDChange - end - object editFullCopyright: TEdit - Left = 120 - Top = 203 - Width = 205 - Height = 21 - TabOrder = 4 - Text = 'Copyright '#169' YYYY' - OnChange = editFullCopyrightChange - end - object memoDescription: TMemo - Left = 120 - Top = 35 - Width = 497 - Height = 109 - TabOrder = 1 - OnChange = memoDescriptionChange - end -end +inherited frmNewProjectParameters: TfrmNewProjectParameters + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'New Basic Keyboard Project' + ClientHeight = 582 + ClientWidth = 693 + Position = poScreenCenter + OnDestroy = FormDestroy + ExplicitWidth = 699 + ExplicitHeight = 611 + PixelsPerInch = 96 + TextHeight = 13 + object lblKeyboardID: TLabel + Left = 12 + Top = 496 + Width = 64 + Height = 13 + Caption = '&Keyboard ID:' + FocusControl = editKeyboardID + end + object lblPath: TLabel + Left = 12 + Top = 469 + Width = 26 + Height = 13 + Caption = '&Path:' + FocusControl = editPath + end + object lblKeyboardName: TLabel + Left = 12 + Top = 11 + Width = 80 + Height = 13 + Caption = 'Keyboard &Name:' + FocusControl = editKeyboardName + end + object lblCoypright: TLabel + Left = 12 + Top = 150 + Width = 51 + Height = 13 + Caption = '&Copyright:' + FocusControl = editCopyright + end + object lblVersion: TLabel + Left = 12 + Top = 203 + Width = 39 + Height = 13 + Caption = '&Version:' + FocusControl = editVersion + end + object lblAuthor: TLabel + Left = 12 + Top = 123 + Width = 37 + Height = 13 + Caption = 'A&uthor:' + FocusControl = editAuthor + end + object lblTargets: TLabel + Left = 12 + Top = 230 + Width = 41 + Height = 13 + Caption = '&Targets:' + FocusControl = clbTargets + end + object lblKeyboardLanguages: TLabel + Left = 12 + Top = 332 + Width = 52 + Height = 13 + Caption = '&Languages' + FocusControl = gridKeyboardLanguages + end + object lblProjectFilename: TLabel + Left = 12 + Top = 523 + Width = 77 + Height = 13 + Caption = 'Project &filename' + FocusControl = editProjectFilename + end + object lblFullCopyright: TLabel + Left = 12 + Top = 176 + Width = 68 + Height = 13 + Caption = 'Fu&ll copyright:' + FocusControl = editFullCopyright + end + object lblDescription: TLabel + Left = 12 + Top = 38 + Width = 53 + Height = 13 + Caption = '&Description' + FocusControl = memoDescription + end + object lblKeyboardNameValidation: TLabel + Left = 424 + Top = 11 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editKeyboardName + WordWrap = True + end + object lblDescriptionValidation: TLabel + Left = 424 + Top = 38 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = memoDescription + WordWrap = True + end + object lblKeyboardIDValidation: TLabel + Left = 424 + Top = 496 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editKeyboardID + WordWrap = True + end + object lblPathValidation: TLabel + Left = 424 + Top = 469 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editPath + WordWrap = True + end + object lblCopyrightValidation: TLabel + Left = 424 + Top = 150 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editCopyright + WordWrap = True + end + object lblVersionValidation: TLabel + Left = 424 + Top = 203 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editVersion + WordWrap = True + end + object lblAuthorValidation: TLabel + Left = 424 + Top = 123 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editAuthor + WordWrap = True + end + object lblTargetsValidation: TLabel + Left = 424 + Top = 230 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = clbTargets + WordWrap = True + end + object lblLanguagesValidation: TLabel + Left = 424 + Top = 332 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = gridKeyboardLanguages + WordWrap = True + end + object lblProjectFilenameValidation: TLabel + Left = 424 + Top = 523 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editProjectFilename + WordWrap = True + end + object lblFullCopyrightValidation: TLabel + Left = 424 + Top = 176 + Width = 250 + Height = 26 + AutoSize = False + Caption = '(validation)' + FocusControl = editFullCopyright + WordWrap = True + end + object lblDescriptionMarkdown: TLabel + Left = 121 + Top = 101 + Width = 197 + Height = 13 + Caption = 'Markdown accepted, no embedded HTML' + Transparent = True + end + object editKeyboardID: TEdit + Left = 120 + Top = 493 + Width = 293 + Height = 21 + TabOrder = 13 + OnChange = editKeyboardIDChange + OnExit = ControlExit + end + object cmdBrowse: TButton + Left = 340 + Top = 466 + Width = 73 + Height = 21 + Caption = '&Browse...' + TabOrder = 12 + OnClick = cmdBrowseClick + end + object editPath: TEdit + Left = 120 + Top = 466 + Width = 205 + Height = 21 + TabOrder = 11 + OnChange = editPathChange + OnExit = ControlExit + end + object editKeyboardName: TEdit + Left = 120 + Top = 8 + Width = 293 + Height = 21 + TabOrder = 0 + OnChange = editKeyboardNameChange + OnExit = ControlExit + end + object editCopyright: TEdit + Left = 120 + Top = 146 + Width = 293 + Height = 21 + TabOrder = 3 + Text = 'Copyright '#169 + OnChange = editCopyrightChange + OnExit = ControlExit + end + object editVersion: TEdit + Left = 120 + Top = 200 + Width = 293 + Height = 21 + TabOrder = 5 + Text = '1.0' + OnChange = editVersionChange + OnExit = ControlExit + end + object editAuthor: TEdit + Left = 120 + Top = 120 + Width = 293 + Height = 21 + TabOrder = 2 + OnChange = editAuthorChange + OnExit = ControlExit + end + object cmdOK: TButton + Left = 120 + Top = 549 + Width = 73 + Height = 25 + Caption = 'OK' + Default = True + TabOrder = 15 + OnClick = cmdOKClick + end + object cmdCancel: TButton + Left = 199 + Top = 549 + Width = 73 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 16 + end + object clbTargets: TCheckListBox + Left = 120 + Top = 227 + Width = 293 + Height = 97 + OnClickCheck = clbTargetsClickCheck + ItemHeight = 13 + TabOrder = 6 + OnExit = ControlExit + end + object gridKeyboardLanguages: TStringGrid + Left = 120 + Top = 330 + Width = 293 + Height = 94 + ColCount = 2 + DefaultRowHeight = 16 + FixedCols = 0 + RowCount = 9 + Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goColSizing, goRowSelect] + TabOrder = 7 + OnClick = gridKeyboardLanguagesClick + OnDblClick = gridKeyboardLanguagesDblClick + ColWidths = ( + 78 + 64) + end + object cmdKeyboardAddLanguage: TButton + Left = 120 + Top = 430 + Width = 73 + Height = 25 + Caption = '&Add...' + TabOrder = 8 + OnClick = cmdKeyboardAddLanguageClick + end + object cmdKeyboardEditLanguage: TButton + Left = 199 + Top = 430 + Width = 73 + Height = 25 + Caption = 'Ed&it...' + TabOrder = 9 + OnClick = cmdKeyboardEditLanguageClick + end + object cmdKeyboardRemoveLanguage: TButton + Left = 278 + Top = 430 + Width = 72 + Height = 25 + Caption = '&Remove' + TabOrder = 10 + OnClick = cmdKeyboardRemoveLanguageClick + end + object editProjectFilename: TEdit + Left = 120 + Top = 520 + Width = 293 + Height = 21 + TabStop = False + ParentColor = True + ReadOnly = True + TabOrder = 14 + end + object editFullCopyright: TEdit + Left = 120 + Top = 173 + Width = 293 + Height = 21 + TabOrder = 4 + Text = 'Copyright '#169' YYYY' + OnChange = editFullCopyrightChange + OnExit = ControlExit + end + object memoDescription: TMemo + Left = 120 + Top = 35 + Width = 293 + Height = 62 + TabOrder = 1 + OnChange = memoDescriptionChange + OnExit = ControlExit + end +end diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas index bac2ffd9a9..c5323a7894 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas @@ -17,15 +17,18 @@ uses Vcl.Grids, Vcl.StdCtrls, + Browse4Folder, + + Keyman.Developer.UI.FormValidation, kpsfile, PackageInfo, UfrmTike, UKeymanTargets, - utilfiletypes, Browse4Folder; + utilfiletypes; type TfrmNewProjectParameters = class(TTikeForm) - lblFileName: TLabel; + lblKeyboardID: TLabel; lblPath: TLabel; editKeyboardID: TEdit; cmdBrowse: TButton; @@ -49,10 +52,22 @@ type lblKeyboardLanguages: TLabel; lblProjectFilename: TLabel; editProjectFilename: TEdit; - Label1: TLabel; + lblFullCopyright: TLabel; editFullCopyright: TEdit; - Label2: TLabel; + lblDescription: TLabel; memoDescription: TMemo; + lblKeyboardNameValidation: TLabel; + lblDescriptionValidation: TLabel; + lblKeyboardIDValidation: TLabel; + lblPathValidation: TLabel; + lblCopyrightValidation: TLabel; + lblVersionValidation: TLabel; + lblAuthorValidation: TLabel; + lblTargetsValidation: TLabel; + lblLanguagesValidation: TLabel; + lblProjectFilenameValidation: TLabel; + lblFullCopyrightValidation: TLabel; + lblDescriptionMarkdown: TLabel; procedure cmdOKClick(Sender: TObject); procedure editKeyboardNameChange(Sender: TObject); procedure FormCreate(Sender: TObject); @@ -71,7 +86,9 @@ type procedure cmdBrowseClick(Sender: TObject); procedure editFullCopyrightChange(Sender: TObject); procedure memoDescriptionChange(Sender: TObject); + procedure ControlExit(Sender: TObject); private + FormValidation: TFormValidation; dlgBrowse: TBrowse4Folder; pack: TKPSFile; // Used temporarily for storing language list FSetup: Integer; @@ -84,7 +101,7 @@ type function GetTargets: TKeymanTargets; function GetVersion: string; function Validate: Boolean; - procedure EnableControls; + procedure EnableControls(ChangingControl: TControl); function SelectedKeyboardLanguage: TPackageKeyboardLanguage; procedure LanguageGrid_Fill; function SelectedKeyboard: TPackageKeyboard; @@ -94,6 +111,9 @@ type procedure UpdateProjectFilename; function GetFullCopyright: string; function GetDescription: string; + procedure SetupValidation; + + procedure EnableGridControls; protected function GetHelpTopic: string; override; public @@ -167,11 +187,16 @@ begin try pt.Generate; except - on E:EFOpenError do + on E:EKeyboardProjectTemplate do begin ShowMessage('Unable to create project: '+E.Message); Exit(False); end; + on E:EFOpenError do + begin + ShowMessage('Unable to create project; template files may be missing: '+E.Message); + Exit(False); + end; end; frmKeymanDeveloper.OpenProject(pt.ProjectFilename); @@ -192,6 +217,9 @@ var i: TKeymanTarget; begin inherited; + + SetupValidation; + editPath.Text := FKeymanDeveloperOptions.DefaultProjectPath; editFullCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+FormatDateTime('yyyy', Now)+' '; @@ -210,18 +238,19 @@ begin clbTargets.Checked[0] := True; LanguageGrid_Fill; - EnableControls; + EnableControls(nil); end; procedure TfrmNewProjectParameters.FormDestroy(Sender: TObject); begin inherited; FreeAndNil(pack); + FreeAndNil(FormValidation); end; procedure TfrmNewProjectParameters.clbTargetsClickCheck(Sender: TObject); begin - EnableControls; + EnableControls(clbTargets); end; procedure TfrmNewProjectParameters.cmdBrowseClick(Sender: TObject); @@ -229,7 +258,10 @@ begin dlgBrowse.InitialDir := editPath.Text; if dlgBrowse.Execute and (dlgBrowse.FileName <> '') then + begin editPath.Text := ExcludeTrailingPathDelimiter(dlgBrowse.FileName); + EnableControls(editPath); + end; end; procedure TfrmNewProjectParameters.cmdKeyboardAddLanguageClick( @@ -253,7 +285,7 @@ begin LanguageGrid_Fill; gridKeyboardLanguages.Row := gridKeyboardLanguages.RowCount - 1; gridKeyboardLanguagesClick(gridKeyboardLanguages); - EnableControls; + EnableControls(gridKeyboardLanguages); end; finally frm.Free; @@ -282,7 +314,7 @@ begin lang.ID := frm.LanguageID; lang.Name := frm.LanguageName; LanguageGrid_Fill; - EnableControls; + EnableControls(gridKeyboardLanguages); end; finally frm.Free; @@ -302,76 +334,71 @@ begin k.Languages.Remove(lang); LanguageGrid_Fill; - EnableControls; + EnableControls(gridKeyboardLanguages); end; procedure TfrmNewProjectParameters.cmdOKClick(Sender: TObject); begin + FormValidation.TouchAllFields; if Validate then ModalResult := mrOk; end; +procedure TfrmNewProjectParameters.ControlExit(Sender: TObject); +begin + EnableControls(Sender as TControl); +end; + procedure TfrmNewProjectParameters.editAuthorChange(Sender: TObject); begin - EnableControls; if not editCopyright.Modified then + begin editCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+Author; + FormValidation.TouchField(editCopyright); + end; if not editFullCopyright.Modified then + begin editFullCopyright.Text := 'Copyright '+Char($00A9 {copyright})+' '+FormatDateTime('yyyy', Now)+' '+Author; + FormValidation.TouchField(editFullCopyright); + end; + EnableControls(editAuthor); end; procedure TfrmNewProjectParameters.editCopyrightChange(Sender: TObject); begin - EnableControls; + EnableControls(editCopyright); end; procedure TfrmNewProjectParameters.editFullCopyrightChange(Sender: TObject); begin - EnableControls; + EnableControls(editFullCopyright); end; procedure TfrmNewProjectParameters.editKeyboardIDChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editKeyboardID); end; procedure TfrmNewProjectParameters.editKeyboardNameChange(Sender: TObject); begin if not editKeyboardID.Modified then + begin editKeyboardID.Text := TKeyboardUtils.CleanKeyboardID(Trim(editKeyboardName.Text)); - EnableControls; + FormValidation.TouchField(editKeyboardID); + end; + EnableControls(editKeyboardName); end; procedure TfrmNewProjectParameters.editPathChange(Sender: TObject); begin UpdateProjectFilename; - EnableControls; + EnableControls(editPath); end; procedure TfrmNewProjectParameters.editVersionChange(Sender: TObject); begin - EnableControls; -end; - -procedure TfrmNewProjectParameters.EnableControls; -var - e: Boolean; -begin - e := (Trim(editKeyboardName.Text) <> '') and - (Trim(editPath.Text) <> '') and - TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) and - (GetTargets <> []) and - (Trim(memoDescription.Text) <> ''); - - cmdOK.Enabled := e; - - e := gridKeyboardLanguages.RowCount > 1; - gridKeyboardLanguages.Enabled := e; - cmdKeyboardRemoveLanguage.Enabled := e; - cmdKeyboardEditLanguage.Enabled := e; - if e then - gridKeyboardLanguages.FixedRows := 1; + EnableControls(editVersion); end; function TfrmNewProjectParameters.GetAuthor: string; @@ -444,7 +471,7 @@ end; procedure TfrmNewProjectParameters.gridKeyboardLanguagesClick( Sender: TObject); begin - EnableControls; + EnableGridControls; end; procedure TfrmNewProjectParameters.gridKeyboardLanguagesDblClick( @@ -458,6 +485,12 @@ function TfrmNewProjectParameters.Validate: Boolean; var ProjectFolder: string; begin + if not FormValidation.Update then + begin + ShowMessage('One or more fields are missing or invalid. Please correct these fields and try again.'); + Exit(False); + end; + Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True); if Result then @@ -526,19 +559,19 @@ begin end; end; LanguageGrid_Fill; - EnableControls; + EnableGridControls; end; procedure TfrmNewProjectParameters.SetKeyboardID(const Value: string); begin editKeyboardID.Text := Value; - EnableControls; + EnableControls(nil); end; procedure TfrmNewProjectParameters.SetKeyboardName(const Value: string); begin editKeyboardName.Text := Value; - EnableControls; + EnableControls(nil); end; procedure TfrmNewProjectParameters.LanguageGrid_Fill; @@ -557,7 +590,7 @@ begin if not Assigned(k) then begin gridKeyboardLanguages.RowCount := 1; - EnableControls; + EnableControls(nil); Exit; end; @@ -571,7 +604,7 @@ begin gridKeyboardLanguages.Cells[1, i+1] := k.Languages[i].Name; end; - EnableControls; + EnableControls(nil); finally Dec(FSetup); end; @@ -579,7 +612,7 @@ end; procedure TfrmNewProjectParameters.memoDescriptionChange(Sender: TObject); begin - EnableControls; + EnableControls(memoDescription); end; procedure TfrmNewProjectParameters.UpdateProjectFilename; @@ -591,6 +624,87 @@ begin // Scroll to the end of the control to show the filename editProjectFilename.Perform(EM_SETSEL, Length(editProjectFilename.Text), Length(editProjectFilename.Text)); editProjectFilename.Perform(EM_SCROLLCARET, 0, 0); + FormValidation.TouchField(editProjectFilename); +end; + +//// +//// Validation +//// + +procedure TfrmNewProjectParameters.SetupValidation; +begin + FormValidation := TFormValidation.Create; + + FormValidation.Add(lblKeyboardName, lblKeyboardNameValidation, editKeyboardName); + FormValidation.Add(lblDescription, lblDescriptionValidation, memoDescription); + FormValidation.Add(lblAuthor, lblAuthorValidation, editAuthor); + FormValidation.Add(lblCoypright, lblCopyrightValidation, editCopyright); + FormValidation.Add(lblFullCopyright, lblFullCopyrightValidation, editFullCopyright); + FormValidation.Add(lblVersion, lblVersionValidation, editVersion); + FormValidation.Add(lblTargets, lblTargetsValidation, clbTargets); + FormValidation.Add(lblKeyboardLanguages, lblLanguagesValidation, gridKeyboardLanguages); + FormValidation.Add(lblPath, lblPathValidation, editPath); + FormValidation.Add(lblKeyboardID, lblKeyboardIDValidation, editKeyboardID); + FormValidation.Add(lblProjectFilename, lblProjectFilenameValidation, editProjectFilename); + + FormValidation.AddMethod( + editKeyboardName, + function: Boolean begin Result := Trim(editKeyboardName.Text) <> '' end, + 'Keyboard name cannot be empty' + ); + + FormValidation.AddMethod( + editPath, + function: Boolean begin Result := (Trim(editPath.Text) <> '') end, + 'Keyboard path cannot be empty' + ); + + FormValidation.AddMethod( + editKeyboardID, + function: Boolean begin Result := Trim(editKeyboardID.Text) <> '' end, + 'Keyboard ID cannot be empty' + ); + + FormValidation.AddMethod( + editKeyboardID, + function: Boolean begin Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True) end, + 'Keyboard ID can include only a-z, 0-9, and _, and must not start with a digit' + ); + + FormValidation.AddMethod( + clbTargets, + function: Boolean begin Result := GetTargets <> []; end, + 'At least one target must be selected' + ); + + FormValidation.AddMethod( + memoDescription, + function: Boolean begin Result := Trim(memoDescription.Text) <> '' end, + 'Description cannot be empty' + ); +end; + +procedure TfrmNewProjectParameters.EnableControls(ChangingControl: TControl); +begin + if Assigned(ChangingControl) then + begin + FormValidation.TouchField(ChangingControl); + end; + FormValidation.Update; + EnableGridControls; +end; + +procedure TfrmNewProjectParameters.EnableGridControls; +var + e: Boolean; +begin + e := gridKeyboardLanguages.RowCount > 1; + gridKeyboardLanguages.Enabled := e; + cmdKeyboardRemoveLanguage.Enabled := e; + cmdKeyboardEditLanguage.Enabled := e; + if e then + gridKeyboardLanguages.FixedRows := 1; end; end. + diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index 6724e254f4..19f7ed7756 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -1,310 +1,311 @@ program tike; uses - Vcl.Forms, - Vcl.Dialogs, - Winapi.Windows, - System.Win.ComObj, - Winapi.ActiveX, - System.SysUtils, - kmxfile in '..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - UfrmMDIChild in 'child\UfrmMDIChild.pas' {frmTikeChild}, - UfrmNew in 'dialogs\UfrmNew.pas' {frmNew}, - UfrmAboutTike in 'dialogs\UfrmAboutTike.pas' {frmAboutTike}, - RegistryKeys in '..\..\..\common\windows\delphi\general\RegistryKeys.pas', - VersionInfo in '..\..\..\common\windows\delphi\general\VersionInfo.pas', - KeymanDeveloperOptions in 'main\KeymanDeveloperOptions.pas', - UfrmKeyTest in 'debug\UfrmKeyTest.pas' {frmKeyTest}, - KeymanDeveloperUtils in 'main\KeymanDeveloperUtils.pas', - UfrmEditor in 'child\UfrmEditor.pas' {frmEditor}, - MenuImgList in '..\common\delphi\components\MenuImgList.pas', - UfrmSelectKey in 'dialogs\UfrmSelectKey.pas' {frmSelectKey}, - keybtn in '..\..\..\common\windows\delphi\components\keybtn.pas', - ttinfo in '..\..\..\common\windows\delphi\general\ttinfo.pas', - KeyNames in '..\..\..\common\windows\delphi\general\KeyNames.pas', - PaintPanel in '..\..\..\common\windows\delphi\components\PaintPanel.pas', - UfrmMDIEditor in 'child\UfrmMDIEditor.pas' {frmTikeEditor}, - Keyman.Developer.UI.Project.UfrmProject in 'project\Keyman.Developer.UI.Project.UfrmProject.pas' {frmProject}, - Keyman.Developer.System.Project.ProjectFile in 'project\Keyman.Developer.System.Project.ProjectFile.pas', - Keyman.Developer.System.Project.ProjectFileType in 'project\Keyman.Developer.System.Project.ProjectFileType.pas', - Keyman.Developer.System.Project.ProjectFiles in 'project\Keyman.Developer.System.Project.ProjectFiles.pas', - Keyman.Developer.System.Project.kpsProjectFile in 'project\Keyman.Developer.System.Project.kpsProjectFile.pas', - Keyman.Developer.System.Project.kmxProjectFile in 'project\Keyman.Developer.System.Project.kmxProjectFile.pas', - Keyman.Developer.System.Project.modelTsProjectFile in 'project\Keyman.Developer.System.Project.modelTsProjectFile.pas', - Keyman.Developer.UI.Project.UfrmProjectSettings20 in 'project\Keyman.Developer.UI.Project.UfrmProjectSettings20.pas' {frmProjectSettings20}, - Keyman.Developer.UI.Project.ProjectFileUI in 'project\Keyman.Developer.UI.Project.ProjectFileUI.pas', - Keyman.Developer.UI.Project.ProjectUI in 'project\Keyman.Developer.UI.Project.ProjectUI.pas', - Keyman.Developer.UI.Project.ProjectUIFileType in 'project\Keyman.Developer.UI.Project.ProjectUIFileType.pas', - Keyman.Developer.UI.Project.kmnProjectFileUI in 'project\Keyman.Developer.UI.Project.kmnProjectFileUI.pas', - Keyman.Developer.UI.Project.kpsProjectFileUI in 'project\Keyman.Developer.UI.Project.kpsProjectFileUI.pas', - Keyman.Developer.UI.Project.kmxProjectFileUI in 'project\Keyman.Developer.UI.Project.kmxProjectFileUI.pas', - Keyman.Developer.UI.Project.kvkProjectFileUI in 'project\Keyman.Developer.UI.Project.kvkProjectFileUI.pas', - Keyman.Developer.UI.Project.ProjectFilesUI in 'project\Keyman.Developer.UI.Project.ProjectFilesUI.pas', - Keyman.Developer.System.Project.ProjectLog in 'project\Keyman.Developer.System.Project.ProjectLog.pas', - Keyman.Developer.System.Project.Project in 'project\Keyman.Developer.System.Project.Project.pas', - Keyman.Developer.System.Project.kvkProjectFile in 'project\Keyman.Developer.System.Project.kvkProjectFile.pas', - Keyman.Developer.System.Project.ProjectLoader in 'project\Keyman.Developer.System.Project.ProjectLoader.pas', - Keyman.Developer.System.Project.ProjectSaver in 'project\Keyman.Developer.System.Project.ProjectSaver.pas', - VKeys in '..\..\..\common\windows\delphi\general\VKeys.pas', - Unicode in '..\..\..\common\windows\delphi\general\Unicode.pas', - Keyman.System.Debug.DebugUIStatus in 'main\Keyman.System.Debug.DebugUIStatus.pas', - keymanstrings in 'rel\keymanstrings.pas', - kwhelp in 'help\kwhelp.pas', - UKeyBitmap in 'main\UKeyBitmap.pas', - UfrmSelectSystemKeyboard in 'debug\UfrmSelectSystemKeyboard.pas' {frmSelectSystemKeyboard}, - RegressionTest in 'debug\RegressionTest.pas', - DebugListView in '..\common\delphi\components\DebugListView.pas', - DebugBitBtn in '..\common\delphi\components\DebugBitBtn.pas', - DebugListBox in '..\common\delphi\components\DebugListBox.pas', - GetOsVersion in '..\..\..\common\windows\delphi\general\GetOsVersion.pas', - PackageFileFormats in '..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', - PackageInfo in '..\..\..\common\windows\delphi\packages\PackageInfo.pas', - kmpinffile in '..\..\..\common\windows\delphi\packages\kmpinffile.pas', - RedistFiles in 'main\RedistFiles.pas', - UfrmBitmapEditor in 'child\UfrmBitmapEditor.pas' {frmBitmapEditor}, - VisualKeyboard in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', - VisualKeyboardExportHTML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportHTML.pas', - VisualKeyboardParameters in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardParameters.pas', - VisualKeyboardSaverXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', - VisualKeyboardImportXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardImportXML.pas', - UfrmVisualKeyboardImportKMX in 'dialogs\UfrmVisualKeyboardImportKMX.pas' {frmVisualKeyboardImportKMX}, - ScanCodeMap in '..\..\..\common\windows\delphi\general\ScanCodeMap.pas', - UfrmRegressionTestFailure in 'dialogs\UfrmRegressionTestFailure.pas' {frmRegressionTestFailure}, - XString in 'debug\XString.pas', - debugkeyboard in 'debug\debugkeyboard.pas', - Keyman.Developer.UI.UfrmMessageDlgWithSave in 'dialogs\Keyman.Developer.UI.UfrmMessageDlgWithSave.pas' {frmMessageDlgWithSave}, - debugdeadkeys in 'debug\debugdeadkeys.pas', - MessageIdentifiers in 'main\MessageIdentifiers.pas', - kpsfile in '..\common\delphi\packages\kpsfile.pas', - Keyman.UI.UframeCEFHost in '..\..\..\common\windows\delphi\chromium\Keyman.UI.UframeCEFHost.pas' {frameCEFHost}, - klog in '..\..\..\common\windows\delphi\general\klog.pas', - BitmapEditor in '..\common\delphi\components\BitmapEditor.pas', - StringGridEditControlled in '..\common\delphi\components\StringGridEditControlled.pas', - KMDevResourceStrings in 'main\KMDevResourceStrings.pas', - httpuploader in '..\..\..\common\windows\delphi\general\httpuploader.pas', - httpuploader_messageprocessor_forms in '..\..\..\common\windows\delphi\general\httpuploader_messageprocessor_forms.pas', - utilsystem in '..\..\..\common\windows\delphi\general\utilsystem.pas', - utilfiletypes in '..\..\..\common\windows\delphi\general\utilfiletypes.pas', - UfrmNewFileDetails in 'dialogs\UfrmNewFileDetails.pas' {frmNewFileDetails}, - StockFileNames in '..\..\..\common\windows\delphi\general\StockFileNames.pas', - utildir in '..\..\..\common\windows\delphi\general\utildir.pas', - ADOX_TLB in '..\..\..\common\windows\delphi\tlb\ADOX_TLB.pas', - ADODB_TLB in '..\..\..\common\windows\delphi\tlb\ADODB_TLB.pas', - utilhttp in '..\..\..\common\windows\delphi\general\utilhttp.pas', - mrulist in 'main\mrulist.pas', - Upload_Settings in '..\..\..\common\windows\delphi\general\Upload_Settings.pas', - FixedTrackbar in '..\..\..\common\windows\delphi\components\FixedTrackbar.pas', - utilstr in '..\..\..\common\windows\delphi\general\utilstr.pas', - utilkeyboard in '..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', - KeyboardParser in 'main\KeyboardParser.pas', - TextFileFormat in '..\common\delphi\general\TextFileFormat.pas', - KMDActions in '..\common\delphi\components\KMDActions.pas', - KMDActionInterfaces in '..\common\delphi\components\KMDActionInterfaces.pas', - UfrmHelp in 'main\UfrmHelp.pas' {frmHelp}, - OnScreenKeyboard in '..\..\..\common\windows\delphi\components\OnScreenKeyboard.pas', - ExtShiftState in '..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', - CleartypeDrawCharacter in '..\..\..\common\windows\delphi\general\CleartypeDrawCharacter.pas', - CharMapDropTool in 'main\CharMapDropTool.pas', - UfrmBitmapEditorText in 'dialogs\UfrmBitmapEditorText.pas' {frmBitmapEditorText}, - UFixFontDialogBold in 'main\UFixFontDialogBold.pas', - UnicodeData in '..\..\..\common\windows\delphi\charmap\UnicodeData.pas', - CharacterDragObject in '..\..\..\common\windows\delphi\charmap\CharacterDragObject.pas', - CharacterMapSettings in '..\..\..\common\windows\delphi\charmap\CharacterMapSettings.pas', - CharacterRanges in '..\..\..\common\windows\delphi\charmap\CharacterRanges.pas', - CharMapInsertMode in '..\..\..\common\windows\delphi\charmap\CharMapInsertMode.pas', - UfrmCharacterMapFilter in '..\..\..\common\windows\delphi\charmap\UfrmCharacterMapFilter.pas' {frmCharacterMapFilter}, - UfrmUnicodeDataStatus in '..\..\..\common\windows\delphi\charmap\UfrmUnicodeDataStatus.pas' {frmUnicodeDataStatus}, - UfrmDebugStatus_RegTest in 'debug\UfrmDebugStatus_RegTest.pas' {frmDebugStatus_RegTest}, - UfrmDebugStatus_Events in 'debug\UfrmDebugStatus_Events.pas' {frmDebugStatus_Events}, - UfrmDebugStatus_Deadkeys in 'debug\UfrmDebugStatus_Deadkeys.pas' {frmDebugStatus_DeadKeys}, - UfrmDebugStatus_Elements in 'debug\UfrmDebugStatus_Elements.pas' {frmDebugStatus_Elements}, - UfrmDebugStatus in 'child\UfrmDebugStatus.pas' {frmDebugStatus}, - UfrmDebugStatus_Key in 'debug\UfrmDebugStatus_Key.pas' {frmDebugStatus_Key}, - UfrmDebugStatus_Child in 'debug\UfrmDebugStatus_Child.pas' {frmDebugStatus_Child}, - Keyman.Developer.UI.TikeOnlineUpdateCheck in 'update\Keyman.Developer.UI.TikeOnlineUpdateCheck.pas', - Keyman.Developer.UI.UfrmTikeOnlineUpdateNewVersion in 'update\Keyman.Developer.UI.UfrmTikeOnlineUpdateNewVersion.pas' {frmTikeOnlineUpdateNewVersion}, - DebugPaths in '..\..\..\common\windows\delphi\general\DebugPaths.pas', - UfrmDownloadProgress in 'main\UfrmDownloadProgress.pas' {frmDownloadProgress}, - UframeOnScreenKeyboardEditor in 'main\UframeOnScreenKeyboardEditor.pas' {frameOnScreenKeyboardEditor}, - UfrmOSKEditor in 'child\UfrmOSKEditor.pas' {frmOSKEditor}, - utilxml in '..\..\..\common\windows\delphi\general\utilxml.pas', - UfrmTike in 'main\UfrmTike.pas' {TikeForm}, - Keyman.Developer.UI.UfrmTikeOnlineUpdateSetup in 'update\Keyman.Developer.UI.UfrmTikeOnlineUpdateSetup.pas' {frmTikeOnlineUpdateSetup}, - Keyman.Developer.System.TikeOnlineUpdateCheckMessages in 'update\Keyman.Developer.System.TikeOnlineUpdateCheckMessages.pas', - VisualKeyboardExportBMP in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportBMP.pas', - VisualKeyboardExportPNG in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportPNG.pas', - MSXML2_TLB in '..\..\..\common\windows\delphi\tlb\MSXML2_TLB.pas', - CharacterInfo in 'main\CharacterInfo.pas', - UTikeDebugMode in 'main\UTikeDebugMode.pas', - kmxfileconsts in '..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', - kmxfileutils in '..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', - UfrmSelectWindowsLanguages in 'dialogs\languages\UfrmSelectWindowsLanguages.pas' {frmSelectWindowsLanguages: TTntForm}, - Keyman.Developer.UI.UfrmSelectBCP47Language in 'dialogs\languages\Keyman.Developer.UI.UfrmSelectBCP47Language.pas' {frmSelectBCP47Language: TTntForm}, - WindowsLanguages in '..\common\delphi\general\WindowsLanguages.pas', - wininet5 in '..\..\..\common\windows\delphi\general\wininet5.pas', - TextFileTemplates in 'main\TextFileTemplates.pas', - GlobalProxySettings in '..\..\..\common\windows\delphi\general\GlobalProxySettings.pas', - UfrmFontHelper in 'dialogs\UfrmFontHelper.pas' {Form1}, - VKeyChars in '..\..\..\common\windows\delphi\general\VKeyChars.pas', - usp10 in '..\..\..\common\windows\delphi\general\usp10.pas', - UserMessages in '..\..\..\common\windows\delphi\general\UserMessages.pas', - ErrorControlledRegistry in '..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', - UframeBitmapEditor in 'main\UframeBitmapEditor.pas' {frameBitmapEditor: TFrame}, - UfrmMessages in 'main\UfrmMessages.pas' {frmMessages}, - dmActionsDebugger in 'actions\dmActionsDebugger.pas' {modActionsDebugger: TDataModule}, - dmActionsMain in 'actions\dmActionsMain.pas' {modActionsMain: TDataModule}, - dmActionsTextEditor in 'actions\dmActionsTextEditor.pas' {modActionsTextEditor: TDataModule}, - UfrmCharacterMapNew in '..\..\..\common\windows\delphi\charmap\UfrmCharacterMapNew.pas' {frmCharacterMapNew}, - UframeTextEditor in 'main\UframeTextEditor.pas' {frameTextEditor}, - UfrmVisualKeyboardExportBMPParams in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardExportBMPParams.pas' {frmVisualKeyboardExportBMPParams}, - UfrmVisualKeyboardExportHTMLParams in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardExportHTMLParams.pas' {frmVisualKeyboardExportHTMLParams}, - UfrmVisualKeyboardKeyBitmap in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardKeyBitmap.pas' {frmVisualKeyboardKeyBitmap}, - UfrmOptions in 'dialogs\UfrmOptions.pas' {frmOptions}, - UfrmDebug in 'child\UfrmDebug.pas' {frmDebug}, - Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug in 'child\Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas' {frmLdmlKeyboardDebug}, - UfrmPackageEditor in 'child\UfrmPackageEditor.pas' {frmPackageEditor}, - utilexecute in '..\..\..\common\windows\delphi\general\utilexecute.pas', - KeymanVersion in '..\..\..\common\windows\delphi\general\KeymanVersion.pas', - CaptionPanel in '..\common\delphi\components\CaptionPanel.pas', - Glossary in '..\..\..\common\windows\delphi\general\Glossary.pas', - UframeTouchLayoutBuilder in 'oskbuilder\UframeTouchLayoutBuilder.pas' {frameTouchLayoutBuilder}, - TouchLayoutUtils in 'oskbuilder\TouchLayoutUtils.pas', - UfrmSelectTouchLayoutTemplate in 'oskbuilder\UfrmSelectTouchLayoutTemplate.pas' {frmSelectTouchLayoutTemplate}, - OnScreenKeyboardData in '..\..\..\common\windows\delphi\visualkeyboard\OnScreenKeyboardData.pas', - TouchLayout in 'oskbuilder\TouchLayout.pas', - TouchLayoutDefinitions in 'oskbuilder\TouchLayoutDefinitions.pas', - CharMapDropTool_TntCustomEdit in 'main\CharMapDropTool_TntCustomEdit.pas', - Winapi.UxTheme, - CheckboxGridHelper in 'main\CheckboxGridHelper.pas', - UfrmAddKeyboardFeature in 'dialogs\UfrmAddKeyboardFeature.pas' {frmAddKeyboardFeature}, - LeftTabbedPageControl in '..\common\delphi\components\LeftTabbedPageControl.pas', - UmodWebHttpServer in 'web\UmodWebHttpServer.pas' {modWebHttpServer: TDataModule}, - TempFileManager in '..\..\..\common\windows\delphi\general\TempFileManager.pas', - UfrmKeymanWizard in 'child\UfrmKeymanWizard.pas' {frmKeymanWizard}, - UfrmKeyboardFonts in 'dialogs\UfrmKeyboardFonts.pas' {frmKeyboardFonts}, - KeyboardFonts in '..\common\delphi\general\KeyboardFonts.pas', - JsonUtil in '..\..\..\common\windows\delphi\general\JsonUtil.pas', - TikeUnicodeData in 'main\TikeUnicodeData.pas', - UKeymanTargets in '..\common\delphi\general\UKeymanTargets.pas', - UfrmSMTPSetup in 'dialogs\UfrmSMTPSetup.pas' {frmSMTPSetup}, - UfrmSendURLsToEmail in 'dialogs\UfrmSendURLsToEmail.pas' {frmSendURLsToEmail}, - MSXMLDomCreate in '..\..\..\common\windows\delphi\general\MSXMLDomCreate.pas', - webhelp in 'help\webhelp.pas', - Vcl.Themes, - Vcl.Styles, - UfrmMain in 'main\UfrmMain.pas' {frmKeymanDeveloper}, - UfrmCharacterIdentifier in 'main\UfrmCharacterIdentifier.pas' {frmCharacterIdentifier}, - utilcheckfontchars in '..\..\..\common\windows\delphi\charmap\utilcheckfontchars.pas', - findfonts in '..\..\..\common\windows\delphi\general\findfonts.pas', - KeymanPaths in '..\..\..\common\windows\delphi\general\KeymanPaths.pas', - VisualKeyboardLoaderXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderXML.pas', - VisualKeyboardExportXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportXML.pas', - VisualKeyboardLoaderBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderBinary.pas', - VisualKeyboardSaverBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', - DropTarget in 'main\DropTarget.pas', - CloseButtonPageControl in '..\common\delphi\components\CloseButtonPageControl.pas', - UfrmTIKEDock in 'dockforms\UfrmTIKEDock.pas' {TIKEDockForm}, - KeymanDeveloperDebuggerMemo in '..\common\delphi\components\KeymanDeveloperDebuggerMemo.pas', - UfrmCharacterMapDock in 'dockforms\UfrmCharacterMapDock.pas' {frmCharacterMapDock}, - Keyman.System.PackageInfoRefreshKeyboards in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', - Keyman.System.Standards.ISO6393ToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', - Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', - Keyman.System.KeyboardJSInfo in '..\common\delphi\keyboards\Keyman.System.KeyboardJSInfo.pas', - Keyman.System.KeyboardUtils in '..\common\delphi\keyboards\Keyman.System.KeyboardUtils.pas', - BCP47Tag in '..\..\..\common\windows\delphi\general\BCP47Tag.pas', - Keyman.System.KMXFileLanguages in '..\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', - Keyman.System.LanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.LanguageCodeUtils.pas', - Keyman.System.RegExGroupHelperRSP19902 in '..\..\..\common\windows\delphi\vcl\Keyman.System.RegExGroupHelperRSP19902.pas', - Keyman.System.UpdateCheckResponse in '..\..\..\common\windows\delphi\general\Keyman.System.UpdateCheckResponse.pas', - Keyman.Developer.System.HelpTopics in 'help\Keyman.Developer.System.HelpTopics.pas', - Keyman.System.Standards.BCP47SubtagRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SubtagRegistry.pas', - Keyman.System.Standards.BCP47SuppressScriptRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SuppressScriptRegistry.pas', - Keyman.System.CanonicalLanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas', - Keyman.System.CEFManager in '..\..\..\common\windows\delphi\chromium\Keyman.System.CEFManager.pas', - Keyman.Developer.System.HttpServer.App in 'http\Keyman.Developer.System.HttpServer.App.pas', - Keyman.System.HttpServer.Base in '..\..\..\common\windows\delphi\web\Keyman.System.HttpServer.Base.pas', - Keyman.Developer.System.HttpServer.AppSource in 'http\Keyman.Developer.System.HttpServer.AppSource.pas', - Keyman.UI.FontUtils in 'main\Keyman.UI.FontUtils.pas', - Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter in '..\kmconvert\Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas', - Keyman.Developer.System.Project.kmnProjectFileAction in 'project\Keyman.Developer.System.Project.kmnProjectFileAction.pas', - Keyman.Developer.System.Project.kpsProjectFileAction in 'project\Keyman.Developer.System.Project.kpsProjectFileAction.pas', - Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas' {frmCloneGitHubProjectParameters}, - Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas' {frmNewLDMLKeyboardProjectParameters}, - Keyman.Developer.UI.Project.UfrmNewProject in 'project\Keyman.Developer.UI.Project.UfrmNewProject.pas' {frmNewProject}, - Keyman.Developer.System.KeyboardProjectTemplate in '..\kmconvert\Keyman.Developer.System.KeyboardProjectTemplate.pas', - Keyman.Developer.System.LdmlKeyboardProjectTemplate in '..\kmconvert\Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas', - Keyman.Developer.UI.ImportWindowsKeyboardDialogManager in 'main\Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas', - Keyman.Developer.System.ImportWindowsKeyboard in '..\kmconvert\Keyman.Developer.System.ImportWindowsKeyboard.pas', - Keyman.Developer.System.ImportKeyboardDLL in '..\kmconvert\Keyman.Developer.System.ImportKeyboardDLL.pas', - Keyman.System.Util.RenderLanguageIcon in '..\..\..\common\windows\delphi\ui\Keyman.System.Util.RenderLanguageIcon.pas', - Keyman.System.PackageInfoRefreshLexicalModels in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', - Keyman.System.LexicalModelUtils in '..\common\delphi\lexicalmodels\Keyman.System.LexicalModelUtils.pas', - Keyman.Developer.System.Project.kmnProjectFile in 'project\Keyman.Developer.System.Project.kmnProjectFile.pas', - Keyman.Developer.System.Project.modelTsProjectFileAction in 'project\Keyman.Developer.System.Project.modelTsProjectFileAction.pas', - Keyman.Developer.System.LexicalModelCompile in '..\common\delphi\lexicalmodels\Keyman.Developer.System.LexicalModelCompile.pas', - Keyman.Developer.UI.Project.modelTsProjectFileUI in 'project\Keyman.Developer.UI.Project.modelTsProjectFileUI.pas', - Keyman.Developer.System.ModelProjectTemplate in '..\kmconvert\Keyman.Developer.System.ModelProjectTemplate.pas', - Keyman.Developer.System.ProjectTemplate in '..\kmconvert\Keyman.Developer.System.ProjectTemplate.pas', - Keyman.Developer.UI.Project.UfrmNewModelProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas' {frmNewModelProjectParameters}, - Keyman.Developer.System.Project.wordlistTsvProjectFile in 'project\Keyman.Developer.System.Project.wordlistTsvProjectFile.pas', - Keyman.Developer.UI.UframeWordlistEditor in 'child\Keyman.Developer.UI.UframeWordlistEditor.pas' {frameWordlistEditor}, - Keyman.System.WordlistTsvFile in '..\common\delphi\lexicalmodels\Keyman.System.WordlistTsvFile.pas', - Keyman.Developer.UI.Project.wordlistTsvProjectFileUI in 'project\Keyman.Developer.UI.Project.wordlistTsvProjectFileUI.pas', - Keyman.Developer.System.Project.WelcomeRenderer in 'project\Keyman.Developer.System.Project.WelcomeRenderer.pas', - Browse4Folder in '..\ext\browse4folder\Browse4Folder.pas', - DelphiZXIngQRCode in '..\ext\zxingqrcode\Source\DelphiZXIngQRCode.pas', - Keyman.System.QRCode in 'main\Keyman.System.QRCode.pas', - Keyman.Developer.UI.UfrmModelEditor in 'child\Keyman.Developer.UI.UfrmModelEditor.pas' {frmModelEditor}, - Keyman.Developer.System.LexicalModelParser in 'main\Keyman.Developer.System.LexicalModelParser.pas', - Keyman.Developer.System.LexicalModelParserTypes in 'main\Keyman.Developer.System.LexicalModelParserTypes.pas', - Keyman.Developer.UI.UfrmWordlistEditor in 'child\Keyman.Developer.UI.UfrmWordlistEditor.pas' {frmWordlistEditor}, - Keyman.Developer.UI.dmActionsModelEditor in 'actions\Keyman.Developer.UI.dmActionsModelEditor.pas' {modActionsModelEditor: TDataModule}, - UfrmMustIncludeDebug in 'dialogs\UfrmMustIncludeDebug.pas' {frmMustIncludeDebug}, - Sentry.Client in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.pas', - Sentry.Client.Vcl in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.Vcl.pas', - sentry in '..\..\..\common\windows\delphi\ext\sentry\sentry.pas', - Keyman.System.KeymanSentryClient in '..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas', - Keyman.System.Standards.LangTagsRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', - Keyman.Developer.System.Project.UrlRenderer in 'project\Keyman.Developer.System.Project.UrlRenderer.pas', - Keyman.System.KeymanCore in 'main\Keyman.System.KeymanCore.pas', - Keyman.System.KeymanCoreDebug in 'main\Keyman.System.KeymanCoreDebug.pas', - UfrmDebugStatus_CallStack in 'debug\UfrmDebugStatus_CallStack.pas' {frmDebugStatus_CallStack}, - Keyman.System.Debug.DebugEvent in 'debug\Keyman.System.Debug.DebugEvent.pas', - Keyman.System.Debug.DebugCore in 'debug\Keyman.System.Debug.DebugCore.pas', - Keyman.System.VisualKeyboardImportKMX in 'main\Keyman.System.VisualKeyboardImportKMX.pas', - Keyman.UI.Debug.CharacterGridRenderer in 'debug\Keyman.UI.Debug.CharacterGridRenderer.pas', - UfrmDebugStatus_Platform in 'debug\UfrmDebugStatus_Platform.pas' {frmDebugStatus_Platform}, - UfrmDebugStatus_Options in 'debug\UfrmDebugStatus_Options.pas' {frmDebugStatus_Options}, - Keyman.Developer.System.KeymanDeveloperPaths in 'main\Keyman.Developer.System.KeymanDeveloperPaths.pas', - Keyman.Developer.UI.UfrmServerOptions in 'dialogs\Keyman.Developer.UI.UfrmServerOptions.pas' {frmServerOptions}, - Keyman.Developer.System.ServerAPI in 'http\Keyman.Developer.System.ServerAPI.pas', - Keyman.System.FontLoadUtil in 'main\Keyman.System.FontLoadUtil.pas', - Keyman.Developer.UI.ServerUI in 'http\Keyman.Developer.UI.ServerUI.pas', - Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', - Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}, - Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}, - Keyman.Developer.UI.UfrmEditPackageWebFonts in 'dialogs\packageWebFonts\Keyman.Developer.UI.UfrmEditPackageWebFonts.pas' {frmEditPackageWebFonts}, - Keyman.Developer.System.KmcWrapper in 'compile\Keyman.Developer.System.KmcWrapper.pas', - Keyman.Developer.UI.Project.UpgradeProject in 'project\Keyman.Developer.UI.Project.UpgradeProject.pas', - Keyman.Developer.System.Project.xmlLdmlProjectFile in 'project\Keyman.Developer.System.Project.xmlLdmlProjectFile.pas', - Keyman.Developer.System.Project.xmlLdmlProjectFileAction in 'project\Keyman.Developer.System.Project.xmlLdmlProjectFileAction.pas', - Keyman.Developer.UI.Project.xmlLdmlProjectFileUI in 'project\Keyman.Developer.UI.Project.xmlLdmlProjectFileUI.pas', - Keyman.Developer.UI.UfrmLdmlKeyboardEditor in 'child\Keyman.Developer.UI.UfrmLdmlKeyboardEditor.pas' {frmLdmlKeyboardEditor}, - dmActionsKeyboardEditor in 'actions\dmActionsKeyboardEditor.pas' {modActionsKeyboardEditor: TDataModule}, - Keyman.Developer.UI.Project.UfrmProjectSettings in 'project\Keyman.Developer.UI.Project.UfrmProjectSettings.pas' {frmProjectSettings}, - Keyman.Developer.System.TikeCommandLine in 'main\Keyman.Developer.System.TikeCommandLine.pas', - Keyman.Developer.System.MultiProcess in 'main\Keyman.Developer.System.MultiProcess.pas', - Keyman.Developer.System.TikeMultiProcess in 'main\Keyman.Developer.System.TikeMultiProcess.pas', - Keyman.System.CopyDataHelper in 'main\Keyman.System.CopyDataHelper.pas', - Keyman.Developer.System.ProjectOwningFile in 'main\Keyman.Developer.System.ProjectOwningFile.pas', - Keyman.Developer.System.Main in 'main\Keyman.Developer.System.Main.pas', - Keyman.Developer.System.LaunchProjects in 'main\Keyman.Developer.System.LaunchProjects.pas', - Keyman.System.Debug.DebugUtils in 'debug\Keyman.System.Debug.DebugUtils.pas', - Keyman.Developer.UI.RichEdit41 in '..\common\delphi\components\Keyman.Developer.UI.RichEdit41.pas', - Keyman.Developer.UI.Project.UfrmNewProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas' {frmNewProjectParameters}, - Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas' {frmCloneLocalProjectParameters}, - Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas' {frmCloneKeymanCloudProjectParameters}; - + Vcl.Forms, + Vcl.Dialogs, + Winapi.Windows, + System.Win.ComObj, + Winapi.ActiveX, + System.SysUtils, + kmxfile in '..\..\..\common\windows\delphi\keyboards\kmxfile.pas', + UfrmMDIChild in 'child\UfrmMDIChild.pas' {frmTikeChild}, + UfrmNew in 'dialogs\UfrmNew.pas' {frmNew}, + UfrmAboutTike in 'dialogs\UfrmAboutTike.pas' {frmAboutTike}, + RegistryKeys in '..\..\..\common\windows\delphi\general\RegistryKeys.pas', + VersionInfo in '..\..\..\common\windows\delphi\general\VersionInfo.pas', + KeymanDeveloperOptions in 'main\KeymanDeveloperOptions.pas', + UfrmKeyTest in 'debug\UfrmKeyTest.pas' {frmKeyTest}, + KeymanDeveloperUtils in 'main\KeymanDeveloperUtils.pas', + UfrmEditor in 'child\UfrmEditor.pas' {frmEditor}, + MenuImgList in '..\common\delphi\components\MenuImgList.pas', + UfrmSelectKey in 'dialogs\UfrmSelectKey.pas' {frmSelectKey}, + keybtn in '..\..\..\common\windows\delphi\components\keybtn.pas', + ttinfo in '..\..\..\common\windows\delphi\general\ttinfo.pas', + KeyNames in '..\..\..\common\windows\delphi\general\KeyNames.pas', + PaintPanel in '..\..\..\common\windows\delphi\components\PaintPanel.pas', + UfrmMDIEditor in 'child\UfrmMDIEditor.pas' {frmTikeEditor}, + Keyman.Developer.UI.Project.UfrmProject in 'project\Keyman.Developer.UI.Project.UfrmProject.pas' {frmProject}, + Keyman.Developer.System.Project.ProjectFile in 'project\Keyman.Developer.System.Project.ProjectFile.pas', + Keyman.Developer.System.Project.ProjectFileType in 'project\Keyman.Developer.System.Project.ProjectFileType.pas', + Keyman.Developer.System.Project.ProjectFiles in 'project\Keyman.Developer.System.Project.ProjectFiles.pas', + Keyman.Developer.System.Project.kpsProjectFile in 'project\Keyman.Developer.System.Project.kpsProjectFile.pas', + Keyman.Developer.System.Project.kmxProjectFile in 'project\Keyman.Developer.System.Project.kmxProjectFile.pas', + Keyman.Developer.System.Project.modelTsProjectFile in 'project\Keyman.Developer.System.Project.modelTsProjectFile.pas', + Keyman.Developer.UI.Project.UfrmProjectSettings20 in 'project\Keyman.Developer.UI.Project.UfrmProjectSettings20.pas' {frmProjectSettings20}, + Keyman.Developer.UI.Project.ProjectFileUI in 'project\Keyman.Developer.UI.Project.ProjectFileUI.pas', + Keyman.Developer.UI.Project.ProjectUI in 'project\Keyman.Developer.UI.Project.ProjectUI.pas', + Keyman.Developer.UI.Project.ProjectUIFileType in 'project\Keyman.Developer.UI.Project.ProjectUIFileType.pas', + Keyman.Developer.UI.Project.kmnProjectFileUI in 'project\Keyman.Developer.UI.Project.kmnProjectFileUI.pas', + Keyman.Developer.UI.Project.kpsProjectFileUI in 'project\Keyman.Developer.UI.Project.kpsProjectFileUI.pas', + Keyman.Developer.UI.Project.kmxProjectFileUI in 'project\Keyman.Developer.UI.Project.kmxProjectFileUI.pas', + Keyman.Developer.UI.Project.kvkProjectFileUI in 'project\Keyman.Developer.UI.Project.kvkProjectFileUI.pas', + Keyman.Developer.UI.Project.ProjectFilesUI in 'project\Keyman.Developer.UI.Project.ProjectFilesUI.pas', + Keyman.Developer.System.Project.ProjectLog in 'project\Keyman.Developer.System.Project.ProjectLog.pas', + Keyman.Developer.System.Project.Project in 'project\Keyman.Developer.System.Project.Project.pas', + Keyman.Developer.System.Project.kvkProjectFile in 'project\Keyman.Developer.System.Project.kvkProjectFile.pas', + Keyman.Developer.System.Project.ProjectLoader in 'project\Keyman.Developer.System.Project.ProjectLoader.pas', + Keyman.Developer.System.Project.ProjectSaver in 'project\Keyman.Developer.System.Project.ProjectSaver.pas', + VKeys in '..\..\..\common\windows\delphi\general\VKeys.pas', + Unicode in '..\..\..\common\windows\delphi\general\Unicode.pas', + Keyman.System.Debug.DebugUIStatus in 'main\Keyman.System.Debug.DebugUIStatus.pas', + keymanstrings in 'rel\keymanstrings.pas', + kwhelp in 'help\kwhelp.pas', + UKeyBitmap in 'main\UKeyBitmap.pas', + UfrmSelectSystemKeyboard in 'debug\UfrmSelectSystemKeyboard.pas' {frmSelectSystemKeyboard}, + RegressionTest in 'debug\RegressionTest.pas', + DebugListView in '..\common\delphi\components\DebugListView.pas', + DebugBitBtn in '..\common\delphi\components\DebugBitBtn.pas', + DebugListBox in '..\common\delphi\components\DebugListBox.pas', + GetOsVersion in '..\..\..\common\windows\delphi\general\GetOsVersion.pas', + PackageFileFormats in '..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', + PackageInfo in '..\..\..\common\windows\delphi\packages\PackageInfo.pas', + kmpinffile in '..\..\..\common\windows\delphi\packages\kmpinffile.pas', + RedistFiles in 'main\RedistFiles.pas', + UfrmBitmapEditor in 'child\UfrmBitmapEditor.pas' {frmBitmapEditor}, + VisualKeyboard in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboard.pas', + VisualKeyboardExportHTML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportHTML.pas', + VisualKeyboardParameters in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardParameters.pas', + VisualKeyboardSaverXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverXML.pas', + VisualKeyboardImportXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardImportXML.pas', + UfrmVisualKeyboardImportKMX in 'dialogs\UfrmVisualKeyboardImportKMX.pas' {frmVisualKeyboardImportKMX}, + ScanCodeMap in '..\..\..\common\windows\delphi\general\ScanCodeMap.pas', + UfrmRegressionTestFailure in 'dialogs\UfrmRegressionTestFailure.pas' {frmRegressionTestFailure}, + XString in 'debug\XString.pas', + debugkeyboard in 'debug\debugkeyboard.pas', + Keyman.Developer.UI.UfrmMessageDlgWithSave in 'dialogs\Keyman.Developer.UI.UfrmMessageDlgWithSave.pas' {frmMessageDlgWithSave}, + debugdeadkeys in 'debug\debugdeadkeys.pas', + MessageIdentifiers in 'main\MessageIdentifiers.pas', + kpsfile in '..\common\delphi\packages\kpsfile.pas', + Keyman.UI.UframeCEFHost in '..\..\..\common\windows\delphi\chromium\Keyman.UI.UframeCEFHost.pas' {frameCEFHost}, + klog in '..\..\..\common\windows\delphi\general\klog.pas', + BitmapEditor in '..\common\delphi\components\BitmapEditor.pas', + StringGridEditControlled in '..\common\delphi\components\StringGridEditControlled.pas', + KMDevResourceStrings in 'main\KMDevResourceStrings.pas', + httpuploader in '..\..\..\common\windows\delphi\general\httpuploader.pas', + httpuploader_messageprocessor_forms in '..\..\..\common\windows\delphi\general\httpuploader_messageprocessor_forms.pas', + utilsystem in '..\..\..\common\windows\delphi\general\utilsystem.pas', + utilfiletypes in '..\..\..\common\windows\delphi\general\utilfiletypes.pas', + UfrmNewFileDetails in 'dialogs\UfrmNewFileDetails.pas' {frmNewFileDetails}, + StockFileNames in '..\..\..\common\windows\delphi\general\StockFileNames.pas', + utildir in '..\..\..\common\windows\delphi\general\utildir.pas', + ADOX_TLB in '..\..\..\common\windows\delphi\tlb\ADOX_TLB.pas', + ADODB_TLB in '..\..\..\common\windows\delphi\tlb\ADODB_TLB.pas', + utilhttp in '..\..\..\common\windows\delphi\general\utilhttp.pas', + mrulist in 'main\mrulist.pas', + Upload_Settings in '..\..\..\common\windows\delphi\general\Upload_Settings.pas', + FixedTrackbar in '..\..\..\common\windows\delphi\components\FixedTrackbar.pas', + utilstr in '..\..\..\common\windows\delphi\general\utilstr.pas', + utilkeyboard in '..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', + KeyboardParser in 'main\KeyboardParser.pas', + TextFileFormat in '..\common\delphi\general\TextFileFormat.pas', + KMDActions in '..\common\delphi\components\KMDActions.pas', + KMDActionInterfaces in '..\common\delphi\components\KMDActionInterfaces.pas', + UfrmHelp in 'main\UfrmHelp.pas' {frmHelp}, + OnScreenKeyboard in '..\..\..\common\windows\delphi\components\OnScreenKeyboard.pas', + ExtShiftState in '..\..\..\common\windows\delphi\visualkeyboard\ExtShiftState.pas', + CleartypeDrawCharacter in '..\..\..\common\windows\delphi\general\CleartypeDrawCharacter.pas', + CharMapDropTool in 'main\CharMapDropTool.pas', + UfrmBitmapEditorText in 'dialogs\UfrmBitmapEditorText.pas' {frmBitmapEditorText}, + UFixFontDialogBold in 'main\UFixFontDialogBold.pas', + UnicodeData in '..\..\..\common\windows\delphi\charmap\UnicodeData.pas', + CharacterDragObject in '..\..\..\common\windows\delphi\charmap\CharacterDragObject.pas', + CharacterMapSettings in '..\..\..\common\windows\delphi\charmap\CharacterMapSettings.pas', + CharacterRanges in '..\..\..\common\windows\delphi\charmap\CharacterRanges.pas', + CharMapInsertMode in '..\..\..\common\windows\delphi\charmap\CharMapInsertMode.pas', + UfrmCharacterMapFilter in '..\..\..\common\windows\delphi\charmap\UfrmCharacterMapFilter.pas' {frmCharacterMapFilter}, + UfrmUnicodeDataStatus in '..\..\..\common\windows\delphi\charmap\UfrmUnicodeDataStatus.pas' {frmUnicodeDataStatus}, + UfrmDebugStatus_RegTest in 'debug\UfrmDebugStatus_RegTest.pas' {frmDebugStatus_RegTest}, + UfrmDebugStatus_Events in 'debug\UfrmDebugStatus_Events.pas' {frmDebugStatus_Events}, + UfrmDebugStatus_Deadkeys in 'debug\UfrmDebugStatus_Deadkeys.pas' {frmDebugStatus_DeadKeys}, + UfrmDebugStatus_Elements in 'debug\UfrmDebugStatus_Elements.pas' {frmDebugStatus_Elements}, + UfrmDebugStatus in 'child\UfrmDebugStatus.pas' {frmDebugStatus}, + UfrmDebugStatus_Key in 'debug\UfrmDebugStatus_Key.pas' {frmDebugStatus_Key}, + UfrmDebugStatus_Child in 'debug\UfrmDebugStatus_Child.pas' {frmDebugStatus_Child}, + Keyman.Developer.UI.TikeOnlineUpdateCheck in 'update\Keyman.Developer.UI.TikeOnlineUpdateCheck.pas', + Keyman.Developer.UI.UfrmTikeOnlineUpdateNewVersion in 'update\Keyman.Developer.UI.UfrmTikeOnlineUpdateNewVersion.pas' {frmTikeOnlineUpdateNewVersion}, + DebugPaths in '..\..\..\common\windows\delphi\general\DebugPaths.pas', + UfrmDownloadProgress in 'main\UfrmDownloadProgress.pas' {frmDownloadProgress}, + UframeOnScreenKeyboardEditor in 'main\UframeOnScreenKeyboardEditor.pas' {frameOnScreenKeyboardEditor}, + UfrmOSKEditor in 'child\UfrmOSKEditor.pas' {frmOSKEditor}, + utilxml in '..\..\..\common\windows\delphi\general\utilxml.pas', + UfrmTike in 'main\UfrmTike.pas' {TikeForm}, + Keyman.Developer.UI.UfrmTikeOnlineUpdateSetup in 'update\Keyman.Developer.UI.UfrmTikeOnlineUpdateSetup.pas' {frmTikeOnlineUpdateSetup}, + Keyman.Developer.System.TikeOnlineUpdateCheckMessages in 'update\Keyman.Developer.System.TikeOnlineUpdateCheckMessages.pas', + VisualKeyboardExportBMP in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportBMP.pas', + VisualKeyboardExportPNG in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportPNG.pas', + MSXML2_TLB in '..\..\..\common\windows\delphi\tlb\MSXML2_TLB.pas', + CharacterInfo in 'main\CharacterInfo.pas', + UTikeDebugMode in 'main\UTikeDebugMode.pas', + kmxfileconsts in '..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', + kmxfileutils in '..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', + UfrmSelectWindowsLanguages in 'dialogs\languages\UfrmSelectWindowsLanguages.pas' {frmSelectWindowsLanguages: TTntForm}, + Keyman.Developer.UI.UfrmSelectBCP47Language in 'dialogs\languages\Keyman.Developer.UI.UfrmSelectBCP47Language.pas' {frmSelectBCP47Language: TTntForm}, + WindowsLanguages in '..\common\delphi\general\WindowsLanguages.pas', + wininet5 in '..\..\..\common\windows\delphi\general\wininet5.pas', + TextFileTemplates in 'main\TextFileTemplates.pas', + GlobalProxySettings in '..\..\..\common\windows\delphi\general\GlobalProxySettings.pas', + UfrmFontHelper in 'dialogs\UfrmFontHelper.pas' {Form1}, + VKeyChars in '..\..\..\common\windows\delphi\general\VKeyChars.pas', + usp10 in '..\..\..\common\windows\delphi\general\usp10.pas', + UserMessages in '..\..\..\common\windows\delphi\general\UserMessages.pas', + ErrorControlledRegistry in '..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', + UframeBitmapEditor in 'main\UframeBitmapEditor.pas' {frameBitmapEditor: TFrame}, + UfrmMessages in 'main\UfrmMessages.pas' {frmMessages}, + dmActionsDebugger in 'actions\dmActionsDebugger.pas' {modActionsDebugger: TDataModule}, + dmActionsMain in 'actions\dmActionsMain.pas' {modActionsMain: TDataModule}, + dmActionsTextEditor in 'actions\dmActionsTextEditor.pas' {modActionsTextEditor: TDataModule}, + UfrmCharacterMapNew in '..\..\..\common\windows\delphi\charmap\UfrmCharacterMapNew.pas' {frmCharacterMapNew}, + UframeTextEditor in 'main\UframeTextEditor.pas' {frameTextEditor}, + UfrmVisualKeyboardExportBMPParams in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardExportBMPParams.pas' {frmVisualKeyboardExportBMPParams}, + UfrmVisualKeyboardExportHTMLParams in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardExportHTMLParams.pas' {frmVisualKeyboardExportHTMLParams}, + UfrmVisualKeyboardKeyBitmap in '..\..\..\common\windows\delphi\visualkeyboard\UfrmVisualKeyboardKeyBitmap.pas' {frmVisualKeyboardKeyBitmap}, + UfrmOptions in 'dialogs\UfrmOptions.pas' {frmOptions}, + UfrmDebug in 'child\UfrmDebug.pas' {frmDebug}, + Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug in 'child\Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas' {frmLdmlKeyboardDebug}, + UfrmPackageEditor in 'child\UfrmPackageEditor.pas' {frmPackageEditor}, + utilexecute in '..\..\..\common\windows\delphi\general\utilexecute.pas', + KeymanVersion in '..\..\..\common\windows\delphi\general\KeymanVersion.pas', + CaptionPanel in '..\common\delphi\components\CaptionPanel.pas', + Glossary in '..\..\..\common\windows\delphi\general\Glossary.pas', + UframeTouchLayoutBuilder in 'oskbuilder\UframeTouchLayoutBuilder.pas' {frameTouchLayoutBuilder}, + TouchLayoutUtils in 'oskbuilder\TouchLayoutUtils.pas', + UfrmSelectTouchLayoutTemplate in 'oskbuilder\UfrmSelectTouchLayoutTemplate.pas' {frmSelectTouchLayoutTemplate}, + OnScreenKeyboardData in '..\..\..\common\windows\delphi\visualkeyboard\OnScreenKeyboardData.pas', + TouchLayout in 'oskbuilder\TouchLayout.pas', + TouchLayoutDefinitions in 'oskbuilder\TouchLayoutDefinitions.pas', + CharMapDropTool_TntCustomEdit in 'main\CharMapDropTool_TntCustomEdit.pas', + Winapi.UxTheme, + CheckboxGridHelper in 'main\CheckboxGridHelper.pas', + UfrmAddKeyboardFeature in 'dialogs\UfrmAddKeyboardFeature.pas' {frmAddKeyboardFeature}, + LeftTabbedPageControl in '..\common\delphi\components\LeftTabbedPageControl.pas', + UmodWebHttpServer in 'web\UmodWebHttpServer.pas' {modWebHttpServer: TDataModule}, + TempFileManager in '..\..\..\common\windows\delphi\general\TempFileManager.pas', + UfrmKeymanWizard in 'child\UfrmKeymanWizard.pas' {frmKeymanWizard}, + UfrmKeyboardFonts in 'dialogs\UfrmKeyboardFonts.pas' {frmKeyboardFonts}, + KeyboardFonts in '..\common\delphi\general\KeyboardFonts.pas', + JsonUtil in '..\..\..\common\windows\delphi\general\JsonUtil.pas', + TikeUnicodeData in 'main\TikeUnicodeData.pas', + UKeymanTargets in '..\common\delphi\general\UKeymanTargets.pas', + UfrmSMTPSetup in 'dialogs\UfrmSMTPSetup.pas' {frmSMTPSetup}, + UfrmSendURLsToEmail in 'dialogs\UfrmSendURLsToEmail.pas' {frmSendURLsToEmail}, + MSXMLDomCreate in '..\..\..\common\windows\delphi\general\MSXMLDomCreate.pas', + webhelp in 'help\webhelp.pas', + Vcl.Themes, + Vcl.Styles, + UfrmMain in 'main\UfrmMain.pas' {frmKeymanDeveloper}, + UfrmCharacterIdentifier in 'main\UfrmCharacterIdentifier.pas' {frmCharacterIdentifier}, + utilcheckfontchars in '..\..\..\common\windows\delphi\charmap\utilcheckfontchars.pas', + findfonts in '..\..\..\common\windows\delphi\general\findfonts.pas', + KeymanPaths in '..\..\..\common\windows\delphi\general\KeymanPaths.pas', + VisualKeyboardLoaderXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderXML.pas', + VisualKeyboardExportXML in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardExportXML.pas', + VisualKeyboardLoaderBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardLoaderBinary.pas', + VisualKeyboardSaverBinary in '..\..\..\common\windows\delphi\visualkeyboard\VisualKeyboardSaverBinary.pas', + DropTarget in 'main\DropTarget.pas', + CloseButtonPageControl in '..\common\delphi\components\CloseButtonPageControl.pas', + UfrmTIKEDock in 'dockforms\UfrmTIKEDock.pas' {TIKEDockForm}, + KeymanDeveloperDebuggerMemo in '..\common\delphi\components\KeymanDeveloperDebuggerMemo.pas', + UfrmCharacterMapDock in 'dockforms\UfrmCharacterMapDock.pas' {frmCharacterMapDock}, + Keyman.System.PackageInfoRefreshKeyboards in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshKeyboards.pas', + Keyman.System.Standards.ISO6393ToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.ISO6393ToBCP47Registry.pas', + Keyman.System.Standards.LCIDToBCP47Registry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LCIDToBCP47Registry.pas', + Keyman.System.KeyboardJSInfo in '..\common\delphi\keyboards\Keyman.System.KeyboardJSInfo.pas', + Keyman.System.KeyboardUtils in '..\common\delphi\keyboards\Keyman.System.KeyboardUtils.pas', + BCP47Tag in '..\..\..\common\windows\delphi\general\BCP47Tag.pas', + Keyman.System.KMXFileLanguages in '..\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', + Keyman.System.LanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.LanguageCodeUtils.pas', + Keyman.System.RegExGroupHelperRSP19902 in '..\..\..\common\windows\delphi\vcl\Keyman.System.RegExGroupHelperRSP19902.pas', + Keyman.System.UpdateCheckResponse in '..\..\..\common\windows\delphi\general\Keyman.System.UpdateCheckResponse.pas', + Keyman.Developer.System.HelpTopics in 'help\Keyman.Developer.System.HelpTopics.pas', + Keyman.System.Standards.BCP47SubtagRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SubtagRegistry.pas', + Keyman.System.Standards.BCP47SuppressScriptRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.BCP47SuppressScriptRegistry.pas', + Keyman.System.CanonicalLanguageCodeUtils in '..\..\..\common\windows\delphi\general\Keyman.System.CanonicalLanguageCodeUtils.pas', + Keyman.System.CEFManager in '..\..\..\common\windows\delphi\chromium\Keyman.System.CEFManager.pas', + Keyman.Developer.System.HttpServer.App in 'http\Keyman.Developer.System.HttpServer.App.pas', + Keyman.System.HttpServer.Base in '..\..\..\common\windows\delphi\web\Keyman.System.HttpServer.Base.pas', + Keyman.Developer.System.HttpServer.AppSource in 'http\Keyman.Developer.System.HttpServer.AppSource.pas', + Keyman.UI.FontUtils in 'main\Keyman.UI.FontUtils.pas', + Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter in '..\kmconvert\Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas', + Keyman.Developer.System.Project.kmnProjectFileAction in 'project\Keyman.Developer.System.Project.kmnProjectFileAction.pas', + Keyman.Developer.System.Project.kpsProjectFileAction in 'project\Keyman.Developer.System.Project.kpsProjectFileAction.pas', + Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas' {frmCloneGitHubProjectParameters}, + Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas' {frmNewLDMLKeyboardProjectParameters}, + Keyman.Developer.UI.Project.UfrmNewProject in 'project\Keyman.Developer.UI.Project.UfrmNewProject.pas' {frmNewProject}, + Keyman.Developer.System.KeyboardProjectTemplate in '..\kmconvert\Keyman.Developer.System.KeyboardProjectTemplate.pas', + Keyman.Developer.System.LdmlKeyboardProjectTemplate in '..\kmconvert\Keyman.Developer.System.LdmlKeyboardProjectTemplate.pas', + Keyman.Developer.UI.ImportWindowsKeyboardDialogManager in 'main\Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas', + Keyman.Developer.System.ImportWindowsKeyboard in '..\kmconvert\Keyman.Developer.System.ImportWindowsKeyboard.pas', + Keyman.Developer.System.ImportKeyboardDLL in '..\kmconvert\Keyman.Developer.System.ImportKeyboardDLL.pas', + Keyman.System.Util.RenderLanguageIcon in '..\..\..\common\windows\delphi\ui\Keyman.System.Util.RenderLanguageIcon.pas', + Keyman.System.PackageInfoRefreshLexicalModels in '..\common\delphi\packages\Keyman.System.PackageInfoRefreshLexicalModels.pas', + Keyman.System.LexicalModelUtils in '..\common\delphi\lexicalmodels\Keyman.System.LexicalModelUtils.pas', + Keyman.Developer.System.Project.kmnProjectFile in 'project\Keyman.Developer.System.Project.kmnProjectFile.pas', + Keyman.Developer.System.Project.modelTsProjectFileAction in 'project\Keyman.Developer.System.Project.modelTsProjectFileAction.pas', + Keyman.Developer.System.LexicalModelCompile in '..\common\delphi\lexicalmodels\Keyman.Developer.System.LexicalModelCompile.pas', + Keyman.Developer.UI.Project.modelTsProjectFileUI in 'project\Keyman.Developer.UI.Project.modelTsProjectFileUI.pas', + Keyman.Developer.System.ModelProjectTemplate in '..\kmconvert\Keyman.Developer.System.ModelProjectTemplate.pas', + Keyman.Developer.System.ProjectTemplate in '..\kmconvert\Keyman.Developer.System.ProjectTemplate.pas', + Keyman.Developer.UI.Project.UfrmNewModelProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas' {frmNewModelProjectParameters}, + Keyman.Developer.System.Project.wordlistTsvProjectFile in 'project\Keyman.Developer.System.Project.wordlistTsvProjectFile.pas', + Keyman.Developer.UI.UframeWordlistEditor in 'child\Keyman.Developer.UI.UframeWordlistEditor.pas' {frameWordlistEditor}, + Keyman.System.WordlistTsvFile in '..\common\delphi\lexicalmodels\Keyman.System.WordlistTsvFile.pas', + Keyman.Developer.UI.Project.wordlistTsvProjectFileUI in 'project\Keyman.Developer.UI.Project.wordlistTsvProjectFileUI.pas', + Keyman.Developer.System.Project.WelcomeRenderer in 'project\Keyman.Developer.System.Project.WelcomeRenderer.pas', + Browse4Folder in '..\ext\browse4folder\Browse4Folder.pas', + DelphiZXIngQRCode in '..\ext\zxingqrcode\Source\DelphiZXIngQRCode.pas', + Keyman.System.QRCode in 'main\Keyman.System.QRCode.pas', + Keyman.Developer.UI.UfrmModelEditor in 'child\Keyman.Developer.UI.UfrmModelEditor.pas' {frmModelEditor}, + Keyman.Developer.System.LexicalModelParser in 'main\Keyman.Developer.System.LexicalModelParser.pas', + Keyman.Developer.System.LexicalModelParserTypes in 'main\Keyman.Developer.System.LexicalModelParserTypes.pas', + Keyman.Developer.UI.UfrmWordlistEditor in 'child\Keyman.Developer.UI.UfrmWordlistEditor.pas' {frmWordlistEditor}, + Keyman.Developer.UI.dmActionsModelEditor in 'actions\Keyman.Developer.UI.dmActionsModelEditor.pas' {modActionsModelEditor: TDataModule}, + UfrmMustIncludeDebug in 'dialogs\UfrmMustIncludeDebug.pas' {frmMustIncludeDebug}, + Sentry.Client in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.pas', + Sentry.Client.Vcl in '..\..\..\common\windows\delphi\ext\sentry\Sentry.Client.Vcl.pas', + sentry in '..\..\..\common\windows\delphi\ext\sentry\sentry.pas', + Keyman.System.KeymanSentryClient in '..\..\..\common\windows\delphi\general\Keyman.System.KeymanSentryClient.pas', + Keyman.System.Standards.LangTagsRegistry in '..\..\..\common\windows\delphi\standards\Keyman.System.Standards.LangTagsRegistry.pas', + Keyman.Developer.System.Project.UrlRenderer in 'project\Keyman.Developer.System.Project.UrlRenderer.pas', + Keyman.System.KeymanCore in 'main\Keyman.System.KeymanCore.pas', + Keyman.System.KeymanCoreDebug in 'main\Keyman.System.KeymanCoreDebug.pas', + UfrmDebugStatus_CallStack in 'debug\UfrmDebugStatus_CallStack.pas' {frmDebugStatus_CallStack}, + Keyman.System.Debug.DebugEvent in 'debug\Keyman.System.Debug.DebugEvent.pas', + Keyman.System.Debug.DebugCore in 'debug\Keyman.System.Debug.DebugCore.pas', + Keyman.System.VisualKeyboardImportKMX in 'main\Keyman.System.VisualKeyboardImportKMX.pas', + Keyman.UI.Debug.CharacterGridRenderer in 'debug\Keyman.UI.Debug.CharacterGridRenderer.pas', + UfrmDebugStatus_Platform in 'debug\UfrmDebugStatus_Platform.pas' {frmDebugStatus_Platform}, + UfrmDebugStatus_Options in 'debug\UfrmDebugStatus_Options.pas' {frmDebugStatus_Options}, + Keyman.Developer.System.KeymanDeveloperPaths in 'main\Keyman.Developer.System.KeymanDeveloperPaths.pas', + Keyman.Developer.UI.UfrmServerOptions in 'dialogs\Keyman.Developer.UI.UfrmServerOptions.pas' {frmServerOptions}, + Keyman.Developer.System.ServerAPI in 'http\Keyman.Developer.System.ServerAPI.pas', + Keyman.System.FontLoadUtil in 'main\Keyman.System.FontLoadUtil.pas', + Keyman.Developer.UI.ServerUI in 'http\Keyman.Developer.UI.ServerUI.pas', + Keyman.Developer.System.GenerateKeyboardIcon in '..\kmconvert\Keyman.Developer.System.GenerateKeyboardIcon.pas', + Keyman.Developer.UI.UfrmEditLanguageExample in 'dialogs\examples\Keyman.Developer.UI.UfrmEditLanguageExample.pas' {frmEditLanguageExample}, + Keyman.Developer.UI.UfrmEditRelatedPackage in 'dialogs\relatedPackages\Keyman.Developer.UI.UfrmEditRelatedPackage.pas' {frmEditRelatedPackage}, + Keyman.Developer.UI.UfrmEditPackageWebFonts in 'dialogs\packageWebFonts\Keyman.Developer.UI.UfrmEditPackageWebFonts.pas' {frmEditPackageWebFonts}, + Keyman.Developer.System.KmcWrapper in 'compile\Keyman.Developer.System.KmcWrapper.pas', + Keyman.Developer.UI.Project.UpgradeProject in 'project\Keyman.Developer.UI.Project.UpgradeProject.pas', + Keyman.Developer.System.Project.xmlLdmlProjectFile in 'project\Keyman.Developer.System.Project.xmlLdmlProjectFile.pas', + Keyman.Developer.System.Project.xmlLdmlProjectFileAction in 'project\Keyman.Developer.System.Project.xmlLdmlProjectFileAction.pas', + Keyman.Developer.UI.Project.xmlLdmlProjectFileUI in 'project\Keyman.Developer.UI.Project.xmlLdmlProjectFileUI.pas', + Keyman.Developer.UI.UfrmLdmlKeyboardEditor in 'child\Keyman.Developer.UI.UfrmLdmlKeyboardEditor.pas' {frmLdmlKeyboardEditor}, + dmActionsKeyboardEditor in 'actions\dmActionsKeyboardEditor.pas' {modActionsKeyboardEditor: TDataModule}, + Keyman.Developer.UI.Project.UfrmProjectSettings in 'project\Keyman.Developer.UI.Project.UfrmProjectSettings.pas' {frmProjectSettings}, + Keyman.Developer.System.TikeCommandLine in 'main\Keyman.Developer.System.TikeCommandLine.pas', + Keyman.Developer.System.MultiProcess in 'main\Keyman.Developer.System.MultiProcess.pas', + Keyman.Developer.System.TikeMultiProcess in 'main\Keyman.Developer.System.TikeMultiProcess.pas', + Keyman.System.CopyDataHelper in 'main\Keyman.System.CopyDataHelper.pas', + Keyman.Developer.System.ProjectOwningFile in 'main\Keyman.Developer.System.ProjectOwningFile.pas', + Keyman.Developer.System.Main in 'main\Keyman.Developer.System.Main.pas', + Keyman.Developer.System.LaunchProjects in 'main\Keyman.Developer.System.LaunchProjects.pas', + Keyman.System.Debug.DebugUtils in 'debug\Keyman.System.Debug.DebugUtils.pas', + Keyman.Developer.UI.RichEdit41 in '..\common\delphi\components\Keyman.Developer.UI.RichEdit41.pas', + Keyman.Developer.UI.Project.UfrmNewProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas' {frmNewProjectParameters}, + Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas' {frmCloneLocalProjectParameters}, + Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas' {frmCloneKeymanCloudProjectParameters}, + Keyman.Developer.UI.FormValidation in 'main\Keyman.Developer.UI.FormValidation.pas'; + {$R *.RES} {$R ICONS.RES} {$R VERSION.RES} diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index 9ee2d83579..ba4a86c430 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -1,1480 +1,1481 @@ - - - {82E04599-9378-4816-B0AB-8AB58BD307B3} - tike.dpr - True - Debug - 1 - Application - VCL - 18.8 - Win32 - - - true - - - true - Base - true - - - true - Base - true - - - true - Base - true - - - true - Cfg_1 - true - true - - - true - Base - true - - - true - Cfg_2 - true - true - - - Amakrits|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Amakrits.vsf;"Amethyst Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AmethystKamri.vsf";"Aqua Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AquaGraphite.vsf";"Aqua Light Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AquaLightSlate.vsf";Auric|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Auric.vsf;Carbon|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Carbon.vsf;"Charcoal Dark Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CharcoalDarkSlate.vsf";"Cobalt XEMedia|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CobaltXEMedia.vsf";"Cyan Dusk|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CyanDusk.vsf";"Cyan Night|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CyanNight.vsf";"Emerald Light Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\EmeraldLightSlate.vsf";"Golden Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\GoldenGraphite.vsf";"Iceberg Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\IcebergClassico.vsf";"Lavender Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\LavenderClassico.vsf";"Ruby Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\RubyGraphite.vsf";"Sapphire Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SapphireKamri.vsf";"Slate Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SlateClassico.vsf";"Smokey Quartz Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SmokeyQuartzKamri.vsf";"Turquoise Gray|VCLSTYLE|$(BDSCOMMONDIR)\Styles\TurquoiseGray.vsf";Windows10|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Windows10.vsf - tike - "Amakrits|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Amakrits.vsf";"Amethyst Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AmethystKamri.vsf";"Aqua Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AquaGraphite.vsf";"Aqua Light Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AquaLightSlate.vsf";"Auric|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Auric.vsf";"Carbon|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Carbon.vsf";"Charcoal Dark Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CharcoalDarkSlate.vsf";"Cobalt XEMedia|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CobaltXEMedia.vsf";"Cyan Dusk|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CyanDusk.vsf";"Cyan Night|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CyanNight.vsf";"Emerald Light Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\EmeraldLightSlate.vsf";"Golden Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\GoldenGraphite.vsf";"Iceberg Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\IcebergClassico.vsf";"Lavender Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\LavenderClassico.vsf";"Metro Black|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroBlack.vsf";"Metro Blue|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroBlue.vsf";"Metro Green|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroGreen.vsf";"Ruby Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\RubyGraphite.vsf";"Sapphire Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SapphireKamri.vsf";"Slate Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SlateClassico.vsf";"Smokey Quartz Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SmokeyQuartzKamri.vsf";"Turquoise Gray|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\TurquoiseGray.vsf" - tike_Icon.ico - None - true - VERSION_KEYMAN_DEVELOPER;$(DCC_Define) - false - 49 - true - true - true - Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win;$(DCC_Namespace) - true - 1 - 3 - 5 - false - false - 3081 - false - true - false - CompanyName=;FileDescription=Tavultesoft IKE;FileVersion=5.0.0.49;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=5.0.0.0;Comments= - Vcl40;Vclx40;comp51;VCLZipD4;PMemoPack4;dclkmn;vcljpg40;Vcl50;Vclx50;rtl;vcl;xmlrtl;vclactnband;vclx;vclie;dbrtl;vcldb;tb2k_d10;tbx_d10;TMSUnicodeD2006;SpTBXMemoPackage;dsnap;$(DCC_UsePackage) - 00400000 - C:\keyman\7.0\src\engine\kmcomapi\com\keyboards\;C:\keyman\7.0\src\engine\kmcomapi\util\;C:\keyman\7.0\src\engine\kmcomapi\com\;C:\keyman\7.0\src\engine\kmcomapi\com\packages\;C:\keyman\7.0\src\engine\kmcomapi\processes\package\;$(DebugSourcePath) - true - true - .\bin\$(Platform)\$(Config) - .\obj\$(Platform)\$(Config) - true - - - EmbeddedWebBrowser_XE;SpTBXLib_d16;keyman_components;$(DCC_UsePackage) - Data.Win;Datasnap.Win;Web.Win;Soap.Win;Bde;$(DCC_Namespace) - 1033 - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - KeymanDeveloper90.ico - - - tike_Icon.ico - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png - $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png - - - false - 0 - 0 - RELEASE;$(DCC_Define) - - - CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - 1033 - true - 2 - KeymanDeveloper90.ico - - - DEBUG;$(DCC_Define) - - - true - KeymanDeveloper90.ico - true - CompanyName=;FileVersion=5.0.0.49;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) - 1033 - Debug - true - - - - MainSource - - - -
frmTikeChild
-
- -
frmNew
-
- -
frmAboutTike
-
- - - - -
frmKeyTest
-
- - -
frmEditor
-
- - -
frmSelectKey
-
- - - - - -
frmTikeEditor
-
- -
frmProject
-
- - - - - - - -
frmProjectSettings20
-
- - - - - - - - - - - - - - - - - - - - -
frmSelectSystemKeyboard
-
- - - - - - - - - - -
frmBitmapEditor
-
- - - - - - -
frmVisualKeyboardImportKMX
-
- - -
frmRegressionTestFailure
-
- - - -
frmMessageDlgWithSave
-
- - - - -
frameCEFHost
-
- - - - - - - - - -
frmNewFileDetails
-
- - - - - - - - - - - - - - - -
frmHelp
-
- - - - - -
frmBitmapEditorText
-
- - - - - - - -
frmCharacterMapFilter
-
- -
frmUnicodeDataStatus
-
- -
frmDebugStatus_RegTest
-
- -
frmDebugStatus_Events
-
- -
frmDebugStatus_DeadKeys
-
- -
frmDebugStatus_Elements
-
- -
frmDebugStatus
-
- -
frmDebugStatus_Key
-
- -
frmDebugStatus_Child
-
- - -
frmTikeOnlineUpdateNewVersion
-
- - -
frmDownloadProgress
-
- -
frameOnScreenKeyboardEditor
-
- -
frmOSKEditor
-
- - -
TikeForm
-
- -
frmTikeOnlineUpdateSetup
-
- - - - - - - - - -
frmSelectWindowsLanguages
- TTntForm -
- -
frmSelectBCP47Language
- TTntForm -
- - - - - -
Form1
-
- - - - - -
frameBitmapEditor
- TFrame -
- -
frmMessages
-
- -
modActionsDebugger
- TDataModule -
- -
modActionsMain
- TDataModule -
- -
modActionsTextEditor
- TDataModule -
- -
frmCharacterMapNew
-
- -
frameTextEditor
-
- -
frmVisualKeyboardExportBMPParams
-
- -
frmVisualKeyboardExportHTMLParams
-
- -
frmVisualKeyboardKeyBitmap
-
- -
frmOptions
-
- -
frmDebug
-
- -
frmLdmlKeyboardDebug
-
- -
frmPackageEditor
-
- - - - - -
frameTouchLayoutBuilder
-
- - -
frmSelectTouchLayoutTemplate
-
- - - - - - -
frmAddKeyboardFeature
-
- - -
modWebHttpServer
- TDataModule -
- - -
frmKeymanWizard
-
- -
frmKeyboardFonts
-
- - - - - -
frmSMTPSetup
-
- -
frmSendURLsToEmail
-
- - - -
frmKeymanDeveloper
-
- -
frmCharacterIdentifier
-
- - - - - - - - - - -
TIKEDockForm
-
- - -
frmCharacterMapDock
-
- - - - - - - - - - - - - - - - - - - - - - - -
frmCloneGitHubProjectParameters
- dfm -
- -
frmNewLDMLKeyboardProjectParameters
-
- -
frmNewProject
-
- - - - - - - - - - - - - - - -
frmNewModelProjectParameters
-
- - -
frameWordlistEditor
-
- - - - - - - -
frmModelEditor
-
- - - -
frmWordlistEditor
-
- -
modActionsModelEditor
- TDataModule -
- -
frmMustIncludeDebug
-
- - - - - - - - - -
frmDebugStatus_CallStack
-
- - - - - -
frmDebugStatus_Platform
-
- -
frmDebugStatus_Options
-
- - -
frmServerOptions
-
- - - - - -
frmEditLanguageExample
-
- -
frmEditRelatedPackage
-
- -
frmEditPackageWebFonts
-
- - - - - - -
frmLdmlKeyboardEditor
-
- -
modActionsKeyboardEditor
- TDataModule -
- -
frmProjectSettings
-
- - - - - - - - - - -
frmNewProjectParameters
- dfm -
- -
frmCloneLocalProjectParameters
- dfm -
- -
frmCloneKeymanCloudProjectParameters
- dfm -
- - - Cfg_2 - Base - - - Base - - - Cfg_1 - Base - -
- - Delphi.Personality.12 - VCLApplication - - - - tike.dpr - - - $00000C09 - - - False - True - 5 - 0 - 0 - 83 - False - False - False - False - False - 3081 - 1252 - - - - Tavultesoft IKE - 5.0.0.83 - - - - - - 5.0.0.0 - - - - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - - - - True - False - - - - - tike.exe - true - - - - - tike.rsm - true - - - - - .\ - true - - - - - 1 - - - 0 - - - - - classes - 1 - - - classes - 1 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - - 12 - - - - -
+ + + {82E04599-9378-4816-B0AB-8AB58BD307B3} + tike.dpr + True + Debug + 1 + Application + VCL + 18.8 + Win32 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + Amakrits|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Amakrits.vsf;"Amethyst Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AmethystKamri.vsf";"Aqua Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AquaGraphite.vsf";"Aqua Light Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\AquaLightSlate.vsf";Auric|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Auric.vsf;Carbon|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Carbon.vsf;"Charcoal Dark Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CharcoalDarkSlate.vsf";"Cobalt XEMedia|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CobaltXEMedia.vsf";"Cyan Dusk|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CyanDusk.vsf";"Cyan Night|VCLSTYLE|$(BDSCOMMONDIR)\Styles\CyanNight.vsf";"Emerald Light Slate|VCLSTYLE|$(BDSCOMMONDIR)\Styles\EmeraldLightSlate.vsf";"Golden Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\GoldenGraphite.vsf";"Iceberg Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\IcebergClassico.vsf";"Lavender Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\LavenderClassico.vsf";"Ruby Graphite|VCLSTYLE|$(BDSCOMMONDIR)\Styles\RubyGraphite.vsf";"Sapphire Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SapphireKamri.vsf";"Slate Classico|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SlateClassico.vsf";"Smokey Quartz Kamri|VCLSTYLE|$(BDSCOMMONDIR)\Styles\SmokeyQuartzKamri.vsf";"Turquoise Gray|VCLSTYLE|$(BDSCOMMONDIR)\Styles\TurquoiseGray.vsf";Windows10|VCLSTYLE|$(BDSCOMMONDIR)\Styles\Windows10.vsf + tike + "Amakrits|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Amakrits.vsf";"Amethyst Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AmethystKamri.vsf";"Aqua Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AquaGraphite.vsf";"Aqua Light Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\AquaLightSlate.vsf";"Auric|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Auric.vsf";"Carbon|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\Carbon.vsf";"Charcoal Dark Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CharcoalDarkSlate.vsf";"Cobalt XEMedia|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CobaltXEMedia.vsf";"Cyan Dusk|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CyanDusk.vsf";"Cyan Night|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\CyanNight.vsf";"Emerald Light Slate|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\EmeraldLightSlate.vsf";"Golden Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\GoldenGraphite.vsf";"Iceberg Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\IcebergClassico.vsf";"Lavender Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\LavenderClassico.vsf";"Metro Black|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroBlack.vsf";"Metro Blue|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroBlue.vsf";"Metro Green|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\MetroGreen.vsf";"Ruby Graphite|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\RubyGraphite.vsf";"Sapphire Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SapphireKamri.vsf";"Slate Classico|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SlateClassico.vsf";"Smokey Quartz Kamri|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\SmokeyQuartzKamri.vsf";"Turquoise Gray|VCLSTYLE|$(PUBLIC)\Documents\RAD Studio\9.0\Styles\TurquoiseGray.vsf" + tike_Icon.ico + None + true + VERSION_KEYMAN_DEVELOPER;$(DCC_Define) + false + 49 + true + true + true + Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win;$(DCC_Namespace) + true + 1 + 3 + 5 + false + false + 3081 + false + true + false + CompanyName=;FileDescription=Tavultesoft IKE;FileVersion=5.0.0.49;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=5.0.0.0;Comments= + Vcl40;Vclx40;comp51;VCLZipD4;PMemoPack4;dclkmn;vcljpg40;Vcl50;Vclx50;rtl;vcl;xmlrtl;vclactnband;vclx;vclie;dbrtl;vcldb;tb2k_d10;tbx_d10;TMSUnicodeD2006;SpTBXMemoPackage;dsnap;$(DCC_UsePackage) + 00400000 + C:\keyman\7.0\src\engine\kmcomapi\com\keyboards\;C:\keyman\7.0\src\engine\kmcomapi\util\;C:\keyman\7.0\src\engine\kmcomapi\com\;C:\keyman\7.0\src\engine\kmcomapi\com\packages\;C:\keyman\7.0\src\engine\kmcomapi\processes\package\;$(DebugSourcePath) + true + true + .\bin\$(Platform)\$(Config) + .\obj\$(Platform)\$(Config) + true + + + EmbeddedWebBrowser_XE;SpTBXLib_d16;keyman_components;$(DCC_UsePackage) + Data.Win;Datasnap.Win;Web.Win;Soap.Win;Bde;$(DCC_Namespace) + 1033 + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + KeymanDeveloper90.ico + + + tike_Icon.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + false + 0 + 0 + RELEASE;$(DCC_Define) + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + 1033 + true + 2 + KeymanDeveloper90.ico + + + DEBUG;$(DCC_Define) + + + true + KeymanDeveloper90.ico + true + CompanyName=;FileVersion=5.0.0.49;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + 1033 + Debug + true + + + + MainSource + + + +
frmTikeChild
+
+ +
frmNew
+
+ +
frmAboutTike
+
+ + + + +
frmKeyTest
+
+ + +
frmEditor
+
+ + +
frmSelectKey
+
+ + + + + +
frmTikeEditor
+
+ +
frmProject
+
+ + + + + + + +
frmProjectSettings20
+
+ + + + + + + + + + + + + + + + + + + + +
frmSelectSystemKeyboard
+
+ + + + + + + + + + +
frmBitmapEditor
+
+ + + + + + +
frmVisualKeyboardImportKMX
+
+ + +
frmRegressionTestFailure
+
+ + + +
frmMessageDlgWithSave
+
+ + + + +
frameCEFHost
+
+ + + + + + + + + +
frmNewFileDetails
+
+ + + + + + + + + + + + + + + +
frmHelp
+
+ + + + + +
frmBitmapEditorText
+
+ + + + + + + +
frmCharacterMapFilter
+
+ +
frmUnicodeDataStatus
+
+ +
frmDebugStatus_RegTest
+
+ +
frmDebugStatus_Events
+
+ +
frmDebugStatus_DeadKeys
+
+ +
frmDebugStatus_Elements
+
+ +
frmDebugStatus
+
+ +
frmDebugStatus_Key
+
+ +
frmDebugStatus_Child
+
+ + +
frmTikeOnlineUpdateNewVersion
+
+ + +
frmDownloadProgress
+
+ +
frameOnScreenKeyboardEditor
+
+ +
frmOSKEditor
+
+ + +
TikeForm
+
+ +
frmTikeOnlineUpdateSetup
+
+ + + + + + + + + +
frmSelectWindowsLanguages
+ TTntForm +
+ +
frmSelectBCP47Language
+ TTntForm +
+ + + + + +
Form1
+
+ + + + + +
frameBitmapEditor
+ TFrame +
+ +
frmMessages
+
+ +
modActionsDebugger
+ TDataModule +
+ +
modActionsMain
+ TDataModule +
+ +
modActionsTextEditor
+ TDataModule +
+ +
frmCharacterMapNew
+
+ +
frameTextEditor
+
+ +
frmVisualKeyboardExportBMPParams
+
+ +
frmVisualKeyboardExportHTMLParams
+
+ +
frmVisualKeyboardKeyBitmap
+
+ +
frmOptions
+
+ +
frmDebug
+
+ +
frmLdmlKeyboardDebug
+
+ +
frmPackageEditor
+
+ + + + + +
frameTouchLayoutBuilder
+
+ + +
frmSelectTouchLayoutTemplate
+
+ + + + + + +
frmAddKeyboardFeature
+
+ + +
modWebHttpServer
+ TDataModule +
+ + +
frmKeymanWizard
+
+ +
frmKeyboardFonts
+
+ + + + + +
frmSMTPSetup
+
+ +
frmSendURLsToEmail
+
+ + + +
frmKeymanDeveloper
+
+ +
frmCharacterIdentifier
+
+ + + + + + + + + + +
TIKEDockForm
+
+ + +
frmCharacterMapDock
+
+ + + + + + + + + + + + + + + + + + + + + + + +
frmCloneGitHubProjectParameters
+ dfm +
+ +
frmNewLDMLKeyboardProjectParameters
+
+ +
frmNewProject
+
+ + + + + + + + + + + + + + + +
frmNewModelProjectParameters
+
+ + +
frameWordlistEditor
+
+ + + + + + + +
frmModelEditor
+
+ + + +
frmWordlistEditor
+
+ +
modActionsModelEditor
+ TDataModule +
+ +
frmMustIncludeDebug
+
+ + + + + + + + + +
frmDebugStatus_CallStack
+
+ + + + + +
frmDebugStatus_Platform
+
+ +
frmDebugStatus_Options
+
+ + +
frmServerOptions
+
+ + + + + +
frmEditLanguageExample
+
+ +
frmEditRelatedPackage
+
+ +
frmEditPackageWebFonts
+
+ + + + + + +
frmLdmlKeyboardEditor
+
+ +
modActionsKeyboardEditor
+ TDataModule +
+ +
frmProjectSettings
+
+ + + + + + + + + + +
frmNewProjectParameters
+ dfm +
+ +
frmCloneLocalProjectParameters
+ dfm +
+ +
frmCloneKeymanCloudProjectParameters
+ dfm +
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + VCLApplication + + + + tike.dpr + + + $00000C09 + + + False + True + 5 + 0 + 0 + 83 + False + False + False + False + False + 3081 + 1252 + + + + Tavultesoft IKE + 5.0.0.83 + + + + + + 5.0.0.0 + + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + False + + + + + tike.exe + true + + + + + tike.rsm + true + + + + + .\ + true + + + + + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + 12 + + + + +
diff --git a/developer/src/tike/xml/app/editor/language.keyman.js b/developer/src/tike/xml/app/editor/language.keyman.js index e758af12cf..3fb1cf0aaf 100644 --- a/developer/src/tike/xml/app/editor/language.keyman.js +++ b/developer/src/tike/xml/app/editor/language.keyman.js @@ -53,8 +53,8 @@ define("language.keyman", ["require", "exports"], function (require, exports) { [/\[/, { token: 'tag', bracket: '@open', next: '@virtualKey' } ], // strings - [/"([^"]|\\.)*$/, 'string.invalid' ], // non-teminated string - [/'([^']|\\.)*$/, 'string.invalid' ], // non-teminated string + [/"([^"])*$/, 'string.invalid' ], // non-teminated string + [/'([^'])*$/, 'string.invalid' ], // non-teminated string [/"/, { token: 'string.quote', bracket: '@open', next: '@stringDouble' } ], [/'/, { token: 'string.quote', bracket: '@open', next: '@stringSingle' } ], ], diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 66140f0ae9..646496f5c7 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -120,8 +120,8 @@ -

The beep statement produces a beep with the system speaker when the rule is matched. - In Keyman Desktop, beep will produce the sound specified by "Asterisk" in Windows Sounds +

The beep statement produces a beep with the system speaker when the rule is matched. + In Keyman for Windows, beep will produce the sound specified by "Asterisk" in Windows Sounds configuration.



Syntax

diff --git a/docs/build/windows.md b/docs/build/windows.md index ccf74be55d..edc756340f 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -267,17 +267,35 @@ of appropriate node versions during builds. copying the relevant Delphi-built components into windows/bin folders from a compatible installed version of Keyman for testing and debugging purposes. -* Visual C++ 2019 Community or Professional +* Visual Studio 2022 Community (C++ native desktop workload) - ```ps1 - choco install visualstudio2019community visualstudio2019-workload-nativedesktop visualstudio2019buildtools + ```cmd + winget install --id=Microsoft.VisualStudio.2022.Community -e --override "--passive --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --add Microsoft.VisualStudio.Component.CppBuildInsights --add Microsoft.VisualStudio.Component.Debugger.JustInTime --add Microsoft.VisualStudio.Component.VC.ASAN --add Microsoft.VisualStudio.Component.VC.DiagnosticTools --add Microsoft.VisualStudio.Component.VC.TestAdapterForGoogleTest --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows11SDK.26100 --add Microsoft.VisualStudio.Component.Windows11Sdk.WindowsPerformanceToolkit --add Microsoft.VisualStudio.Component.Windows10SDK.19041" ``` - * Verify required build tools are installed - * Run `Visual Studio Installer` - * Check the `Individual components` tab - * Verify `MSVC v142 - VS 2019 c++ x64/x86 build tools (Latest)` is installed. - If not, install it. + * You can omit the `--passive` parameter to open the installer dialog and + modify the selection before continuing with the install. + + * You can replace `--passive` with `--quiet` for a silent install (note that + the winget command returns before the Visual Studio Installer finishes, so + you won't be able to easily tell when installation completes; check Task + Manager for setup.exe). + + * If you prefer to use the Visual Studio Installer instead of the command + line install, then the following workloads and components should be included: + * Visual Studio core editor + * Desktop development with C++ + - C++ core desktop features + - MSVC v143 - VS 2022 C++ x64/x86 build tools (latest) + - C++ Build Insights + - Just-In-Time debugger + - C++ profiling tools + - Test Adapter for Google Test + - C++ AddressSanitizer + - Windows 10 SDK (10.0.19041.0) + - Windows 11 SDK (10.0.26100.6584) + * Under individual components, add: + - MSVC v143 - VS 2022 C++ ARM64/ARM64EC build tools (latest) Recommended: configure Visual Studio to use two-space tab stops: 1. Open the options dialog: Tools > Options. @@ -287,7 +305,7 @@ of appropriate node versions during builds. * Windows SDK (C++ Desktop Development) - https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ + This should be installed as a part of Visual Studio above **Required environment variables**: * `PATH` @@ -327,7 +345,7 @@ SETX KEYMAN_CEF4DELPHI_ROOT "c:\Projects\keyman\CEF4Delphi_Binary" ```ps1 # Elevated PowerShell choco install 7zip html-help-workshop -choco install wixtoolset --version=3.11.1 +choco install wixtoolset --version=3.14.1 git clone https://github.com/keymanapp/CEF4Delphi_Binary C:\Projects\keyman\CEF4Delphi_Binary ``` diff --git a/docs/builder.md b/docs/builder.md index 538bf783bd..7c609aff27 100644 --- a/docs/builder.md +++ b/docs/builder.md @@ -559,7 +559,7 @@ builder_describe_internal_dependency \ ``` **Note:** actions and targets must be fully specified, and this _must_ be called -before either [`builder_describe_outputs`] or [`builder_parse`] in order for +before both of [`builder_describe_outputs`] and [`builder_parse`] in order for dependencies to be resolved. -------------------------------------------------------------------------------- @@ -1038,6 +1038,28 @@ fi -------------------------------------------------------------------------------- +## `builder_launch` function + +Starts a child script build, passing current build dependency, inheritable +options, and timing status through as parameters. This should be used rather +than calling the build script directly, to avoid multiple builds of +dependencies. + +Do not use builder standard options such as `--deps` or `--debug`. + +### Parameters + +* 1: `script` path to script, relative to root of repo, with leading slash +* 2+: `parameters` action(s), target(s), parameters for the child script to run + +### Example + +```bash + builder_launch /core/build.sh configure,build:wasm --no-tests +``` + +-------------------------------------------------------------------------------- + ## `builder_parse` function Initializes a build.sh script, parses command line. Will abort the script if diff --git a/docs/core-api-verification.md b/docs/core-api-verification.md index fc3e0f629d..0bf266676f 100644 --- a/docs/core-api-verification.md +++ b/docs/core-api-verification.md @@ -99,6 +99,17 @@ the API version number in `CORE_API_VERSION.md`. If the API version numbers in `CORE_API_VERSION.md` and in `libkeymancore.symbols` don't match the API verification will fail. +## Skipping the API check + +Sometimes we have a PR that intentionally does not change the API version +number. To skip the API checks in this case, the trailer + +```text +Keyman-Api-Check: skip +``` + +can be added to the PR description. + ## How this works `.github/workflows/api-verification.yml` contains a `Verify API` step diff --git a/docs/linux/keyman-config.md b/docs/linux/keyman-config.md index 1a039d0f5e..88939ea3e4 100644 --- a/docs/linux/keyman-config.md +++ b/docs/linux/keyman-config.md @@ -4,36 +4,49 @@ Code for `km-config` is in [linux/keyman-config](../../linux/keyman-config/). ## Preparing to run +### Install Dependencies + If you are running from the repo or installing keyman-config manually rather than from a package then you will need to: ```bash sudo apt install python3-lxml python3-magic python3-numpy python3-qrcode python3-pil \ python3-requests python3-requests-cache python3 python3-gi gir1.2-webkit2-4.1 dconf-cli \ - python3-setuptools python3-pip python3-dbus ibus libglib2.0-bin liblocale-gettext-perl + python3-setuptools python3-pip python3-dbus ibus libglib2.0-bin liblocale-gettext-perl \ + python3-build python3-fonttools python3-sentry-sdk ``` -Either `python3-raven` or `python3-sentry-sdk` (>= 1.4) is required as well. -On Ubuntu 22.04 and later run: +Newer Ubuntu versions (>= 24.04) don't allow to modify the environment and directly +install non-Debian-packaged Python packages. Instead you'll have to +run the commands in a virtual environment. + +This requires the installation of some additional dependencies: ```bash -sudo apt install python3-sentry-sdk +sudo apt install python3-venv libglib2.0-dev libdbus-1-dev pkg-config +libcairo2-dev libgirepository-2.0-dev ``` -To install it on Ubuntu 18.04 and earlier run: +Then create the virtual environment with `python3 -m venv km-venv` and activate +it with `source km-venv/bin/activate`. -```bash -sudo apt install python3-raven -``` +### Prepare code -For Ubuntu versions that don't provide `python3-raven` but instead provide -`python3-sentry-sdk` in a too old version (i.e. Ubuntu 20.04): -Install `python3-sentry-sdk` from `packages.sil.org`, -or install it with pip: +Run `linux/keyman-config/build.sh build` to prepare the code. -```bash -pip3 install sentry-sdk -``` +### Installation + +You can install the files with `linux/keyman-config/build.sh install`. +This will install the files into `/usr/local`. + +To uninstall you can run `linux/keyman-config/build.sh uninstall`. + +## Manually installing + +If you prefer to manually copy the files you'll still have to run the +build step above. In addition you'll have to copy the GSettings schema: + +### GSettings schema Copy and compile the GSettings schema: @@ -48,15 +61,6 @@ Running `km-config` requires a language tag mapping file `keyman_config/standards/lang_tags_map.py`. This file gets generated during a package build, and also when running `build.sh build`. -## Installing manually from the repo - -`linux/keyman-config/build.sh build && sudo linux/keyman-config/build.sh install` -will install locally to `/usr/local`. - -`pip3 help install` will give you more install options. - -To uninstall you can run `sudo linux/keyman-config/build.sh uninstall`. - ## Things to run from the command line ### km-config @@ -119,15 +123,17 @@ Secondary-click gives you a menu including 'Back' to go back a page. ### km-kvk2ldml -`km-kvk2ldml [-p] [-k] [-o LDMLFILE] ` Convert a Keyman kvk on-screen keyboard file to an LDML file. Optionally print the details of the kvk file (`-p`) optionally with all keys (`-k`). +`km-kvk2ldml [-p] [-k] [-o LDMLFILE] ` Convert a Keyman kvk on-screen +keyboard file to an LDML file. Optionally print the details of the kvk file +(`-p`) optionally with all keys (`-k`). + +This command is run automatically when installing a keyboard package to create +the necessary files for the onboard onscreen keyboard. ## Building the Debian package -You will need the build dependencies as well as the runtime dependencies above - -`sudo apt install dh-python python3-all debhelper help2man` - -Run `make deb`. This will build the Debian package in the `make_deb` directory. +See the [packaging.md](https://github.com/keymanapp/keyman/blob/master/docs/linux/packaging.md#L26) +document. ## Internationalization diff --git a/docs/linux/packaging.md b/docs/linux/packaging.md index 8e97f8e303..dc62cd5af7 100644 --- a/docs/linux/packaging.md +++ b/docs/linux/packaging.md @@ -8,8 +8,8 @@ We use different channels to build and distribute the Linux packages: - Launchpad repo for [stable](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman), [beta](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman-beta) and [alpha](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman-alpha) versions -- [pso](http://packages.sil.org/) and [llso](http://linux.lsdev.sil.org/ubuntu/) - for stable, beta, and alpha versions +- [pso](http://packages.sil.org/) for stable versions +- [llso](http://linux.lsdev.sil.org/ubuntu/) for stable, beta, and alpha versions - artifacts on [GitHub](https://github.com/keymanapp/keyman/actions/workflows/deb-packaging.yml) for pull requests @@ -18,6 +18,84 @@ intended as a staging environment for testing. Packages on [pso](http://packages are uploaded manually after the packages received some testing. End users usually have only pso enabled. +## Building packages locally + +It is possible to build packages locally. This can be done either directly on the machine, +or in a Docker container which is more similar to the CI environment. + +### Building packages locally directly on the machine + +To build packages locally, you'll have to have the dependencies installed. + +The easiest way to achieve this is to use the `mk-build-deps` tool: + +```bash +sudo apt install dh-python python3-all debhelper help2man devscripts \ + equivs jq quilt +sudo mk-build-deps --install linux/debian/control +``` + +Then you can build the Debian package with: + +```bash +KEYMAN_TIER=$(cat TIER.md) +export KEYMAN_TIER +cd linux +./scripts/deb-packaging.sh source +mkdir -p make_deb +cd make_deb +dpkg-source --extract ../../keyman_*.dsc +cd keyman-* +dch -v$(cat VERSION.md)-1 "" +debuild -us -uc +``` + +### Local package builds with Docker + +It is possible to use the usual Debian/Ubuntu tools to create the package locally. +For someone who only occasionally deals with packaging it might be easier to use +Docker and the scripts that run on GitHub actions: + +#### Prerequisites for local package builds with Docker + +You'll have to create the docker image. + +- clone [gha-ubuntu-packaging](https://github.com/sillsdev/gha-ubuntu-packaging) + repo +- create the image: + + ```bash + cd /path/to/gha-ubuntu-packaging + docker build --build-arg DIST=jammy --build-arg PLATFORM=amd64 -t sillsdev/jammy . + ``` + +#### Building packages with Docker + +- create the source package + + ```bash + cd $KEYMAN_ROOT + KEYMAN_TIER=$(cat TIER.md) + export KEYMAN_TIER + cd linux + ./scripts/deb-packaging.sh source + ``` + + This will create the source package in $KEYMAN_ROOT directory. + +- Create the binary packages with Docker: + + ```bash + cd $KEYMAN_ROOT + DIST=jammy + docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \ + --env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \ + --env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \ + sillsdev/$DIST + ``` + + This will create the binary packages in `$KEYMAN_ROOT/artifacts`. + ## Package builds Package builds happen on [Launchpad](#package-builds-on-launchpad) and @@ -73,52 +151,6 @@ and of course the source code for the packages: to the main section `jammy`) - if the build is successful the job archives the artifacts -### Local package builds with Docker - -It is possible to use the usual Debian/Ubuntu tools to create the package locally. -For someone who only occasionally deals with packaging it might be easier to use -Docker and the scripts that run on GitHub actions: - -#### Prerequisites for local package builds with Docker - -You'll have to create the docker image. - -- clone [gha-ubuntu-packaging](https://github.com/sillsdev/gha-ubuntu-packaging) - repo -- create the image: - - ```bash - cd /path/to/gha-ubuntu-packaging - docker build --build-arg DIST=jammy --build-arg PLATFORM=amd64 -t sillsdev/jammy . - ``` - -#### Building packages with Docker - -- create the source package - - ```bash - cd $KEYMAN_ROOT - KEYMAN_TIER=$(cat TIER.md) - export KEYMAN_TIER - cd linux - ./scripts/deb-packaging.sh source - ``` - - This will create the source package in $KEYMAN_ROOT directory. - -- Create the binary packages with Docker: - - ```bash - cd $KEYMAN_ROOT - DIST=jammy - docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \ - --env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \ - --env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \ - sillsdev/$DIST - ``` - - This will create the binary packages in `$KEYMAN_ROOT/artifacts`. - ## Package builds on Launchpad Package builds on Launchpad are triggered manually by running the Keyman script diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md index 96b6e820a5..d538083d61 100644 --- a/docs/minimum-versions.md +++ b/docs/minimum-versions.md @@ -65,7 +65,7 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_MIN_VERSION_MESON | 1.0.0 | | KEYMAN_MIN_VERSION_NODE_MAJOR | 20 | | KEYMAN_MIN_VERSION_NPM | 10.5.1 | -| KEYMAN_MIN_VERSION_VISUAL_STUDIO | 2019 | +| KEYMAN_MIN_VERSION_VISUAL_STUDIO | 2022 | | KEYMAN_VERSION_CLDR | 46 | | KEYMAN_VERSION_GRADLE | 8.12 | | KEYMAN_VERSION_ICU | 73.1 | @@ -73,6 +73,6 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_VERSION_JAVA | 21 | | KEYMAN_VERSION_LANGTAGS | 2025-02-18 | | KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2025-03-10 | -| KEYMAN_VERSION_UNICODE | 16.0.0 | +| KEYMAN_VERSION_UNICODE | 17.0.0 | > ### This file is auto-generated by publish-minimum-versions.sh. Do not modify diff --git a/docs/npm-packages.md b/docs/npm-packages.md index 8306130d36..9cee3696f6 100644 --- a/docs/npm-packages.md +++ b/docs/npm-packages.md @@ -3,16 +3,21 @@ This is a guide for how the various Keyman `npm` packages are developed and published. -`npm` packages are published on the CI (continuous integration) server. -Currently, we're using TeamCity for this. +`npm` packages are published through CI (continuous integration). This is done +through the `.github/workflows/npm-publish.yml` GitHub Action. Packages are **never** published from a developer's machine. ## List of current published npm packages +* See `resources/build/ci/npm-packages.inc.sh` for an authoritative list. + * `@keymanapp/common-types` -- located at `common/web/types` * `@keymanapp/keyman-version` -- located at `common/web/keyman-version` * `@keymanapp/kmc` -- located at `developer/src/kmc` +* `@keymanapp/kmc-analyze` -- located at `developer/src/kmc-analyze` +* `@keymanapp/kmc-copy` -- located at `developer/src/kmc-copy` +* `@keymanapp/kmc-generate` -- located at `developer/src/kmc-generate` * `@keymanapp/kmc-keyboard-info` -- located at `developer/src/kmc-keyboard-unfo` * `@keymanapp/kmc-kmn` -- located at `developer/src/kmc-kmn` * `@keymanapp/kmc-ldml` -- located at `developer/src/kmc-ldml` @@ -20,6 +25,7 @@ Packages are **never** published from a developer's machine. * `@keymanapp/kmc-model-info` -- located at `developer/src/kmc-model-info` * `@keymanapp/kmc-package` -- located at `developer/src/kmc-package` * `@keymanapp/langtags` -- located at `common/web/langtags` +* `@keymanapp/ldml-keyboard-constants` -- located at `core/include/dml` ### Deprecated npm packages @@ -34,22 +40,16 @@ Packages are **never** published from a developer's machine. ### In general -Before publishing, a package must be **built** and **tested** on the CI -server. - -This is typically done with: +Before publishing, a package must be **built** and **tested**. This is typically +done with: ```bash ./build.sh configure build test ``` -Once the build succeeds and the tests pass, you can publish! Use the following -script for this (**only on the CI server**!); during this, the version is set in -`package.json`: - -```bash -./build.sh publish -``` +Once the build succeeds and the tests pass, you can publish! Add the package to +`resources/build/ci/npm-packages.inc.sh` and it will be published in the next +alpha release build. It is then uploaded to the npm package directory. **Ensure that the compiled sources are included in the tarball**. For example: @@ -94,26 +94,5 @@ npm notice 740B tsconfig.json npm notice 449B tsconfig.kmc-base.json ``` -For every release of **Keyman Developer**, the CI publishes a release of -`@keymanapp/kmc`. The version number is locked with the particular version of -Keyman Developer. +For every release build, CI publishes a release of all packages. -### `builder_publish_npm` - -Publishes the package in `cwd` to npm - -If the `--dry-run` option is available and specified as a command-line -parameter, will do a dry run - -Note that `package.json` will be dirty after this command, as the `version` -field will be added to it, and @keymanapp dependency versions will also be -modified. This change should not be committed to the repository. - -If --npm-publish is set: -* then builder_publish_npm publishes to the public registry -* else builder_publish_npm creates a local tarball which can be used to test - -```bash - . "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh" - builder_publish_npm -``` diff --git a/docs/settings/linux/launch.json b/docs/settings/linux/launch.json index d61153c2c6..1ddd8d7334 100644 --- a/docs/settings/linux/launch.json +++ b/docs/settings/linux/launch.json @@ -57,6 +57,23 @@ "GSETTINGS_BACKEND": "keyfile" } }, + { + // - Run Docker container with `resources/docker-images/run.sh --remote-debug :linux -- linux/ibus-keyman/tests/scripts/run-tests.sh --no-x11 --remote-debug -- --directory /home/build/.local/share/keyman/test_kmx k_001___basic_input_unicodei` + // - then attach debugger in vscode + "type": "gdb", + "request": "attach", + "name": "Attach to gdbserver (ibus-keyman integration tests in Docker)", + "executable": "${workspaceFolder}/linux/build/x86_64/debug/tests/ibus-keyman-tests", + "target": "localhost:2345", + "remote": true, + "cwd": "${workspaceFolder}/linux/ibus-keyman", + "valuesFormatting": "parseText", + "internalConsoleOptions": "openOnSessionStart", + "env": { + "KEYMAN_DEBUG": "1", + "G_ENABLE_DIAGNOSTIC": "1", + } + }, { "type": "gdb", "request": "launch", diff --git a/docs/settings/linux/settings.json b/docs/settings/linux/settings.json index 3fd13430d1..e0098e6a3c 100644 --- a/docs/settings/linux/settings.json +++ b/docs/settings/linux/settings.json @@ -104,9 +104,6 @@ "C_Cpp.clang_format_fallbackStyle": "Google", "bashIde.explainshellEndpoint": "http://localhost:5000", "bashIde.highlightParsingErrors": true, - "shellcheck.customArgs": [ - "--external-sources --source-path=resources/build/" - ], "C_Cpp.errorSquiggles": "EnabledIfIncludesResolve", - "shellcheck.useWorkspaceRootAsCwd": false + "shellcheck.useWorkspaceRootAsCwd": true } diff --git a/ios/ci.sh b/ios/ci.sh index 2222ee689c..cf461427bd 100755 --- a/ios/ci.sh +++ b/ios/ci.sh @@ -22,14 +22,13 @@ builder_parse "$@" mac_verify_on_mac function do_build() { - ./build.sh clean configure build:engine build:app --sim-artifact + builder_launch /ios/build.sh clean configure build:engine build:app --sim-artifact # Since we may be disabling it via build-agent environment variable at times... if [ ! -z "${RELEASE_OEM+x}" ]; then - "$KEYMAN_ROOT/oem/firstvoices/ios/build.sh" clean configure build --sim-artifact + builder_launch /oem/firstvoices/ios/build.sh clean configure build --sim-artifact fi } builder_run_action build do_build -# The script called below isn't builder-based, but... eh, "if it ain't broke." -builder_run_action prep_release tools/prepRelease.sh \ No newline at end of file +builder_run_action prep_release builder_launch /ios/tools/prepRelease.sh \ No newline at end of file diff --git a/ios/docs/engine/guides/in-app/index.md b/ios/docs/engine/guides/in-app/index.md index 3dfebbf82d..96240fa26d 100644 --- a/ios/docs/engine/guides/in-app/index.md +++ b/ios/docs/engine/guides/in-app/index.md @@ -4,7 +4,7 @@ title: Guide: build an in-app keyboard Keyman Engine for iPhone and iPad allows you to use any Keyman touch keyboard in your iOS app, or even to create your own system keyboard app -for purchase in the App Store. +for purchase in the App Store. This guide will walk you through the steps for creating your first iOS app with Keyman Engine for iPhone and iPad. @@ -38,23 +38,23 @@ Use Keyman Developer to build a touch layout. The following documents walk throu When your keyboard is ready, you should have a compiled keyboard package. The example below shows the Thamizha Tamil99 touch layout. -![Keyman-developer](/cdn/deploy/img/engine/ios/16.0/guides/in-app/keyboard-layout-in-developer-800wi.png "Keyman-developer") +![Keyman-developer](/cdn/dev/img/engine/ios/16.0/guides/in-app/keyboard-layout-in-developer-800wi.png "Keyman-developer") Open the package tab for your keyboard and compile it. Then, open the folder containing your package -![Keyman-developer-open-containing-folder](/cdn/deploy/img/engine/ios/16.0/guides/in-app/find-package-via-developer-800wi.png "Keyman-developer-open-containing-folder") +![Keyman-developer-open-containing-folder](/cdn/dev/img/engine/ios/16.0/guides/in-app/find-package-via-developer-800wi.png "Keyman-developer-open-containing-folder") -![Compiled-keyboard-file](/cdn/deploy/img/engine/ios/16.0/guides/in-app/package-file-800wi.png "Compiled-keyboard-file") +![Compiled-keyboard-file](/cdn/dev/img/engine/ios/16.0/guides/in-app/package-file-800wi.png "Compiled-keyboard-file") ### 4. Add your keyboard package to the project Copy your compiled keyboard package (in this example **ekwtamil99uni.kmp**) to your macOS machine within the **KMSample1\Keyboards** folder. -![Copy-keyboard-file](/cdn/deploy/img/engine/ios/16.0/guides/in-app/package-import.png "Copy-keyboard-file") +![Copy-keyboard-file](/cdn/dev/img/engine/ios/16.0/guides/in-app/package-import.png "Copy-keyboard-file") When you switch back into Xcode, use the right-click context menu to "Add Files to 'KMSample1'": -![Android-studio-assets](/cdn/deploy/img/engine/ios/16.0/guides/in-app/xcode-package-import-800wi.png "Android-studio-assets") +![Android-studio-assets](/cdn/dev/img/engine/ios/16.0/guides/in-app/xcode-package-import-800wi.png "Android-studio-assets") Next, edit `ViewController.viewDidLoad()` to add the keyboard (ekwtamil99uni) with `Manager.shared` from KeymanEngine.framework. @@ -73,7 +73,7 @@ Next, edit `ViewController.viewDidLoad()` to add the keyboard (ekwtamil99uni) wi } ``` -![Android-studio-adding-keyboard](/cdn/deploy/img/engine/ios/16.0/guides/in-app/fully-imported-800wi.png "Android-studio-adding-keyboard") +![Android-studio-adding-keyboard](/cdn/dev/img/engine/ios/16.0/guides/in-app/fully-imported-800wi.png "Android-studio-adding-keyboard") ### 5. Build and run the app @@ -82,7 +82,7 @@ emulate a number of iOS devices. Simply choose a target device to test against from the drop-down toward the top-left. For example, in the previous image, you can see "iPod Touch (7th generation)" selected. -![Android-emulator-tamil](/cdn/deploy/img/engine/ios/16.0/guides/in-app/ios-simulator-tamil.png "Android-emulator-tamil") +![Android-emulator-tamil](/cdn/dev/img/engine/ios/16.0/guides/in-app/ios-simulator-tamil.png "Android-emulator-tamil") And there you have it: your first Keyman Engine for iPhone and iPad app! diff --git a/ios/docs/engine/guides/system-keyboard/index.md b/ios/docs/engine/guides/system-keyboard/index.md index ea652360b2..9cad01956c 100644 --- a/ios/docs/engine/guides/system-keyboard/index.md +++ b/ios/docs/engine/guides/system-keyboard/index.md @@ -43,14 +43,14 @@ back out to the main screen. From there, launch the Settings app and go to **General > Keyboards > Keyboards** to enable KMSample2 as a system keyboard. -![Emulator](/cdn/deploy/img/engine/ios/16.0/guides/system-keyboard/set-sys-kbd.png "Emulator") +![Emulator](/cdn/dev/img/engine/ios/16.0/guides/system-keyboard/set-sys-kbd.png "Emulator") Once that's done, open any app that can accept text input and select KMSample2 as your keyboard! Note that in this sample, the Thamizha Tamil99 keyboard can be accessed through the keyboard's "globe" key if it is not set active within KMSample2. -![Emulator](/cdn/deploy/img/engine/ios/16.0/guides/system-keyboard/sys-kbd-active.png "Emulator") +![Emulator](/cdn/dev/img/engine/ios/16.0/guides/system-keyboard/sys-kbd-active.png "Emulator") ### 3. Extending the app @@ -60,7 +60,7 @@ one; again, follow the instructions from Part 1 to make this change. You can also customise the look and feel of the on screen keyboard by including a custom CSS file in your keyboard through Keyman Developer. -![AddEmbeddedCSS](/cdn/deploy/img/engine/ios/16.0/guides/system-keyboard/developer-add-css-800wi.png "AddEmbeddedCSS") +![AddEmbeddedCSS](/cdn/dev/img/engine/ios/16.0/guides/system-keyboard/developer-add-css-800wi.png "AddEmbeddedCSS") The CSS rules required to style your keyboard are beyond the scope of this post, but the **kmwosk.css** file included in the Keyman Developer diff --git a/ios/exportAppStore.plist b/ios/exportAppStore.plist index bb7315750e..46b26a7870 100644 --- a/ios/exportAppStore.plist +++ b/ios/exportAppStore.plist @@ -7,9 +7,9 @@ teamID 3YE4W86L3G signingCertificate - 683E4A3CDF5277C13004E84D63E31D2F720AB063 + AF5CC1ABB8118334C3FDDF03D23BF08148932D5F installerSigningCertificate - 683E4A3CDF5277C13004E84D63E31D2F720AB063 + AF5CC1ABB8118334C3FDDF03D23BF08148932D5F provisioningProfiles Tavultesoft.Keyman diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index 28adfbe1ff..8065aad2f8 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -29,8 +29,7 @@ fi . $KEYMAN_ROOT/resources/build/history/history-utils.sh #includes the following #. ../resources/build/utils.inc.sh -BUILD_NUMBER=`cat ../VERSION.md` -KEYMAN_TIER=`cat ../TIER.md` +BUILD_NUMBER="${KEYMAN_VERSION}" KEYMAN_CHANGELOG="changelog-${BUILD_NUMBER}-${KEYMAN_TIER}.txt" CHANGELOG_PATH="upload/$BUILD_NUMBER/$KEYMAN_CHANGELOG" @@ -59,7 +58,7 @@ mv -f "$CHANGELOG_PATH.1" "$CHANGELOG_PATH" # Keyman Engine # -KMEI_DST_NAME="keyman-engine-ios-${BUILD_NUMBER}.zip" +KMEI_DST_NAME="keyman-engine-ios-${KEYMAN_VERSION_FOR_FILENAME}.zip" KMEI_DST="${WORK_DIR}/${UPLOAD_DIR}/${KMEI_DST_NAME}" KMEI_FRAMEWORK_BASE="build/Build/Products/Release/" @@ -87,14 +86,14 @@ cd "$WORK_DIR" # KEYMANAPP_IPA="build/Build/Products/Release-iphoneos/Keyman.ipa" -KEYMANAPP_IPA_DST="keyman-ios-${BUILD_NUMBER}.ipa" +KEYMANAPP_IPA_DST="keyman-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" echo "Copying Keyman IPA ${KEYMANAPP_IPA} => ${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}..." cp "${KEYMANAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}" KEYMANAPP_SIM_FOLDER="build/Build/Products/Release-iphonesimulator" KEYMANAPP_SIM_APP="$KEYMANAPP_SIM_FOLDER/Keyman.app" -KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${BUILD_NUMBER}.app.zip" +KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${KEYMAN_VERSION_FOR_FILENAME}.app.zip" echo "Zipping Keyman simulator artifact ${KEYMANAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${KEYMANAPP_SIM_FOLDER}" @@ -108,14 +107,14 @@ cd "$WORK_DIR" if [ "${RELEASE_OEM_FIRSTVOICES}" = true ]; then FIRSTVOICESAPP_IPA="../oem/firstvoices/ios/build/Build/Products/Release-iphoneos/FirstVoices.ipa" - FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${BUILD_NUMBER}.ipa" + FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" echo "Copying FirstVoices IPA ${FIRSTVOICESAPP_IPA} => ${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}..." cp "${FIRSTVOICESAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}" FIRSTVOICESAPP_SIM_FOLDER="../oem/firstvoices/ios/build/Build/Products/Release-iphonesimulator/" FIRSTVOICESAPP_SIM_APP="$FIRSTVOICESAPP_SIM_FOLDER/FirstVoices.app" - FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${BUILD_NUMBER}.app.zip" + FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${KEYMAN_VERSION_FOR_FILENAME}.app.zip" echo "Zipping FirstVoices simulator artifact ${FIRSTVOICESAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${FIRSTVOICESAPP_SIM_FOLDER}" diff --git a/linux/Makefile b/linux/Makefile deleted file mode 100644 index 0df264e43d..0000000000 --- a/linux/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/make - -default: # builds only - BUILDONLY="yes" ./scripts/build.sh - -clean: - rm -rf build-* - -reconf: clean - ./scripts/reconf.sh - -configure: - CONFIGUREONLY="yes" ./scripts/build.sh - -fullbuild: # configure and build - ./scripts/build.sh - -tmpinstall: reconf - INSTALLDIR="/tmp/keyman" ./scripts/build.sh - INSTALLDIR="/tmp/keyman" ./scripts/install.sh - -install: # run as sudo - SUDOINSTALL="yes" ./scripts/install.sh - -uninstall: # run as sudo - SUDOINSTALL="uninstall" ./scripts/install.sh - -sources: - ./scripts/dist.sh - -tmpsources: - PKG_CONFIG_PATH="../../core/build/arch/release/meson-private" ./scripts/dist.sh - -origdist: - ./scripts/dist.sh origdist - -sourcepackage: reconf origdist - ./scripts/deb.sh - -nightlydist: reconf sources - echo "Built nightly source release in dist/" - -check: - cd keyman-config && ./run-tests.sh - cd ibus-keyman && ./build.sh test - -tmpcheck: check - -tests: check diff --git a/linux/debian/changelog b/linux/debian/changelog index b87a0a9dc9..5f9833317e 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,30 @@ +keyman (18.0.244-1) unstable; urgency=medium + + * New upstream release. + + -- Eberhard Beilharz Mon, 10 Nov 2025 17:06:33 +0100 + +keyman (18.0.243-1) unstable; urgency=medium + + * replace dbus-x11 dependency with default-dbus-session-bus | dbus-session-bus + (closes: 1117068) + * New upstream release. + + -- Eberhard Beilharz Wed, 15 Oct 2025 12:09:24 +0200 + +keyman (18.0.242-1) unstable; urgency=medium + + * New upstream release. + + -- Eberhard Beilharz Mon, 29 Sep 2025 11:38:38 +0200 + +keyman (18.0.241-1) unstable; urgency=medium + + * remove onboard-keyman recommends (closes: 1115423) + * New upstream release. + + -- Eberhard Beilharz Thu, 18 Sep 2025 11:35:01 +0200 + keyman (18.0.240-1) unstable; urgency=medium * New upstream release. diff --git a/linux/debian/control b/linux/debian/control index e4a0c0437a..74f60531d1 100644 --- a/linux/debian/control +++ b/linux/debian/control @@ -58,8 +58,6 @@ Depends: ibus-keyman (>= ${binary:Version}), ibus-keyman (<< ${binary:Version}.1~), ${misc:Depends}, -Recommends: - onboard-keyman, Description: Type in your language with Keyman for Linux Keyman makes it possible for you to type in over 2,000 languages on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even instantly @@ -88,7 +86,7 @@ Package: python3-keyman-config Section: python Architecture: all Depends: - dbus-x11, + default-dbus-session-bus | dbus-session-bus, dconf-cli, gir1.2-webkit2-4.1, keyman-engine, diff --git a/linux/debian/source/include-binaries b/linux/debian/source/include-binaries new file mode 100644 index 0000000000..6858e3af25 --- /dev/null +++ b/linux/debian/source/include-binaries @@ -0,0 +1,2 @@ +debian/tests/data/alephwithbeth.kmp +debian/tests/data/khmer_angkor.kmp diff --git a/linux/debian/source/lintian-overrides b/linux/debian/source/lintian-overrides deleted file mode 100644 index 54bef6adb5..0000000000 --- a/linux/debian/source/lintian-overrides +++ /dev/null @@ -1,8 +0,0 @@ -# The folders XXX.dist-info/ and XXX.egg-info/ hold metadata for -# Python modules. Those files are not documentation even though -# some of their names carry the .txt file extension. -keyman source: package-contains-documentation-outside-usr-share-doc - -# keyman-system-service gets started by dbus, therefore it should not -# have an [INSTALL] section -keyman source: systemd-service-file-missing-install-key diff --git a/linux/debian/tests/README.md b/linux/debian/tests/README.md index 9327defb8b..60867a8681 100644 --- a/linux/debian/tests/README.md +++ b/linux/debian/tests/README.md @@ -24,11 +24,40 @@ lowercase ASCII letters, plus or minus signs, dots or slashes. For example: ```bash - autopkgtest-buildvm-ubuntu-cloud -v --release=jammy + autopkgtest-buildvm-ubuntu-cloud -v --release=noble ``` - Run tests ```bash - autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img + autopkgtest --apt-upgrade --no-built-binaries *.deb keyman_*.dsc -- qemu autopkgtest-noble-amd64.img ``` + +## Developing tests + +While developing tests, it is possible to run the tests directly from +the source tree: + +```bash +cd linux +autopkgtest --apt-upgrade --no-built-binaries ../artifacts/*.deb . -- qemu /path/to/autopkgtest-noble-amd64.img +``` + +This requires packages to be available in `${KEYMAN_ROOT}/artifacts`. +These can be built from source with: + +```bash +cd linux +rm -rf debian/patches/ keyman-config/keyman_config/.pc +scripts/deb-packaging.sh source +DIST=noble +cd .. +docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \ + --env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \ + --env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \ + sillsdev/$DIST +``` + +New packages have to be built for changes outside of `linux/debian`. +Changes in files in `linux/debian` are automatically considered when +running `autopkgtest`. diff --git a/linux/debian/tests/control b/linux/debian/tests/control index 71c228c089..9163c8c213 100644 --- a/linux/debian/tests/control +++ b/linux/debian/tests/control @@ -1,4 +1,4 @@ -Tests: test-build +Tests: test-build test-gnome-cli test-ibus-cli Depends: @, build-essential, pkg-config, diff --git a/linux/debian/tests/data/alephwithbeth.kmp b/linux/debian/tests/data/alephwithbeth.kmp new file mode 100644 index 0000000000..d432c48d21 Binary files /dev/null and b/linux/debian/tests/data/alephwithbeth.kmp differ diff --git a/linux/debian/tests/data/khmer_angkor.kmp b/linux/debian/tests/data/khmer_angkor.kmp new file mode 100644 index 0000000000..5c18f9d277 Binary files /dev/null and b/linux/debian/tests/data/khmer_angkor.kmp differ diff --git a/linux/debian/tests/test-cli.inc.sh b/linux/debian/tests/test-cli.inc.sh new file mode 100644 index 0000000000..233af94167 --- /dev/null +++ b/linux/debian/tests/test-cli.inc.sh @@ -0,0 +1,100 @@ +# shellcheck shell=bash + +verify_ibus_restarted() { + local ibus_count new_pid + + ibus_count=$(pgrep --count ibus-daemon) + if ! (( ibus_count == 1 )); then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: ibus-daemon not running" + exit 1 + fi + new_pid=$(pgrep ibus-daemon) + if [[ "${new_pid}" == "${ibus_pid}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: ibus-daemon was not restarted" + exit 1 + fi + ibus_pid="${new_pid}" +} + +verify_gnome_input_sources() { + local expected_sources="$1" + local sources + + sources=$(gsettings get org.gnome.desktop.input-sources sources) + if [[ "${sources}" != "${expected_sources}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: input-sources wrong. Got ${sources} instead of ${expected_sources}" + exit 1 + fi +} + +verify_ibus_preload_engines() { + local expected="$1" + local sources + + sources=$(gsettings get org.freedesktop.ibus.general preload-engines) + if [[ "${sources}" != "${expected}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: preload-engines wrong. Got ${sources} instead of ${expected}" + exit 1 + fi +} + +verify_custom_keyboards() { + local expected_custom_keyboards="$1" + local custom_keyboards + + custom_keyboards=$(gsettings get com.keyman.engine additional-keyboards) + if [[ "${custom_keyboards}" != "${expected_custom_keyboards}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: custom keyboards wrong. Got ${custom_keyboards} instead of ${expected_custom_keyboards}" + exit 1 + fi +} + +verify_keyboard_file_exists() { + local expected_keyboard_file="$1" + + if [[ ! -f "${expected_keyboard_file}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: keyboard file ${expected_keyboard_file} not found" + exit 1 + fi +} + +verify_keyboard_file_not_exists() { + local expected_keyboard_file="$1" + + if [[ -f "${expected_keyboard_file}" ]]; then + echo "FAIL ${FUNCNAME[1]}: keyboard file ${expected_keyboard_file} still exists" + exit 1 + fi +} + +verify_no_error() { + local errocode="$1" + + if [[ "${errocode}" != "0" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: non-zero exit code ${errocode}." + exit 1 + fi +} + +run_tests() { + export SLEEP_TIME=5 + export TEST_SCRIPT + TEST_SCRIPT="$(basename "${BASH_SOURCE[${#BASH_SOURCE[@]}-1]}")" + + gsettings reset com.keyman.engine additional-keyboards + + echo "Keyman Version: $(km-config --version)" + if ! pgrep --uid "$(id -u)" ibus-daemon > /dev/null; then + echo "Starting ibus" + ibus start -d + sleep "${SLEEP_TIME}" + else + echo "ibus-daemon already running" + fi + + km_package_install__user + km_package_install__shared + km_package_list_installed + km_package_uninstall__user + km_package_uninstall__shared +} diff --git a/linux/debian/tests/test-gnome-cli b/linux/debian/tests/test-gnome-cli new file mode 100755 index 0000000000..4b46f1fa97 --- /dev/null +++ b/linux/debian/tests/test-gnome-cli @@ -0,0 +1,104 @@ +#!/bin/bash + +# shellcheck disable=SC1091 +. "$(dirname "$0")/test-cli.inc.sh" + +#------------------------------------------------------------------------------- +# gnome-shell tests +#------------------------------------------------------------------------------- + +km_package_install__user() { + # Setup + gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us')]" + + # Run + km-package-install --file "$(dirname "$0")/data/khmer_angkor.kmp" + + # Verify + verify_no_error $? + verify_ibus_restarted + verify_keyboard_file_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx" + verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx')]" + verify_custom_keyboards "@as []" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_install__shared() { + # Setup - continue from previous tests + + # Run + sudo km-package-install --shared --bcp47 ady --file "$(dirname "$0")/data/alephwithbeth.kmp" + + # Verify + verify_no_error $? + verify_ibus_restarted + verify_keyboard_file_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx" + verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx'), ('ibus', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx')]" + verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_list_installed() { + # Setup - continue from previous tests + local result expected + + # Run + result=$(km-package-list-installed) + + # Verify + verify_no_error $? + expected="--- Installed user Keyman keyboard packages --- +Khmer Angkor, version: 1.5, id: khmer_angkor +--- Installed shared Keyman keyboard packages --- +Aleph With Beth, version: 1.0.6, id: alephwithbeth +--- Installed OS Keyman keyboard packages ---" + if [[ "${result}" != "${expected}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[0]}: result wrong." + echo "EXPECTED: +${expected}" + echo "ACTUAL: +${result}" + exit 1 + fi + + echo "PASS ${FUNCNAME[0]}" +} + +km_package_uninstall__user() { + # Setup - continue from previous tests + + # Run + km-package-uninstall khmer_angkor + + # Verify + verify_no_error $? + verify_keyboard_file_not_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx" + verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx')]" + verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_uninstall__shared() { + # Setup - continue from previous tests + + # Run + sudo km-package-uninstall --shared alephwithbeth + + # Verify + verify_no_error $? + verify_keyboard_file_not_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx" + verify_gnome_input_sources "[('xkb', 'us')]" + verify_custom_keyboards "@as []" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +#------------------------------------------------------------------------------- + +# Simulate running gnome-shell +export XDG_CURRENT_DESKTOP=gnome + +run_tests diff --git a/linux/debian/tests/test-ibus-cli b/linux/debian/tests/test-ibus-cli new file mode 100755 index 0000000000..6a44bb8e33 --- /dev/null +++ b/linux/debian/tests/test-ibus-cli @@ -0,0 +1,97 @@ +#!/bin/bash + +# shellcheck disable=SC1091 +. "$(dirname "$0")/test-cli.inc.sh" + +km_package_install__user() { + # Setup + gsettings set org.freedesktop.ibus.general preload-engines "['xkb:us::eng']" + + # Run + km-package-install --file "$(dirname "$0")/data/khmer_angkor.kmp" + + # Verify + verify_no_error $? + verify_ibus_restarted + verify_keyboard_file_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx" + verify_ibus_preload_engines "['xkb:us::eng', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx']" + verify_custom_keyboards "@as []" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_install__shared() { + # Setup - continue from previous tests + + # Run + sudo km-package-install --shared --bcp47 ady --file "$(dirname "$0")/data/alephwithbeth.kmp" + + # Verify + verify_no_error $? + verify_ibus_restarted + verify_keyboard_file_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx" + verify_ibus_preload_engines "['xkb:us::eng', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_list_installed() { + # Setup - continue from previous tests + local result expected + + # Run + result=$(km-package-list-installed) + + # Verify + verify_no_error $? + expected="--- Installed user Keyman keyboard packages --- +Khmer Angkor, version: 1.5, id: khmer_angkor +--- Installed shared Keyman keyboard packages --- +Aleph With Beth, version: 1.0.6, id: alephwithbeth +--- Installed OS Keyman keyboard packages ---" + if [[ "${result}" != "${expected}" ]]; then + echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[0]}: result wrong." + echo "EXPECTED: +${expected}" + echo "ACTUAL: +${result}" + exit 1 + fi + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_uninstall__user() { + # Setup - continue from previous tests + + # Run + km-package-uninstall khmer_angkor + + # Verify + verify_no_error $? + verify_keyboard_file_not_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx" + verify_ibus_preload_engines "['xkb:us::eng', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +km_package_uninstall__shared() { + # Setup - continue from previous tests + + # Run + sudo km-package-uninstall --shared alephwithbeth + + # Verify + verify_no_error $? + verify_keyboard_file_not_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx" + verify_ibus_preload_engines "['xkb:us::eng']" + verify_custom_keyboards "@as []" + + echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}" +} + +#------------------------------------------------------------------------------- + +run_tests diff --git a/linux/ibus-keyman/src/KeymanSystemServiceClient.cpp b/linux/ibus-keyman/src/KeymanSystemServiceClient.cpp index ca3f077afc..502ae244f3 100644 --- a/linux/ibus-keyman/src/KeymanSystemServiceClient.cpp +++ b/linux/ibus-keyman/src/KeymanSystemServiceClient.cpp @@ -43,7 +43,7 @@ KeymanSystemServiceClient::~KeymanSystemServiceClient() { void KeymanSystemServiceClient::Init() { int result; if (testing) { - result = sd_bus_default(&bus); + result = sd_bus_default_user(&bus); } else { result = sd_bus_default_system(&bus); } diff --git a/linux/ibus-keyman/src/test/meson.build b/linux/ibus-keyman/src/test/meson.build index f1f34a63c5..70abf7033e 100644 --- a/linux/ibus-keyman/src/test/meson.build +++ b/linux/ibus-keyman/src/test/meson.build @@ -9,7 +9,7 @@ test_env = [ 'G_TEST_SRCDIR=' + meson.current_source_dir(), 'G_TEST_BUILDDIR=' + meson.current_build_dir(), 'TOP_SRCDIR=' + meson.global_source_root(), - 'TOP_BINDIR=' + meson.build_root(), + 'TOP_BINDIR=' + meson.global_build_root(), ] test_include_dirs = [ @@ -75,7 +75,7 @@ test( test( 'teardown-src-test', teardown_tests, - args: [cleanup_file], + args: [cleanup_file, '--x11'], priority: -9, is_parallel: false, protocol: 'exitcode' diff --git a/linux/ibus-keyman/src/test/run-single-test.sh b/linux/ibus-keyman/src/test/run-single-test.sh index 1ad8714449..62fc795b82 100755 --- a/linux/ibus-keyman/src/test/run-single-test.sh +++ b/linux/ibus-keyman/src/test/run-single-test.sh @@ -43,6 +43,6 @@ while (( $# )); do done # shellcheck source=/dev/null -. "$ARG_ENV" +. "${ARG_ENV}" run_tests "$@" diff --git a/linux/ibus-keyman/src/test/run-tests.sh b/linux/ibus-keyman/src/test/run-tests.sh index 8161797360..aefb77d63f 100755 --- a/linux/ibus-keyman/src/test/run-tests.sh +++ b/linux/ibus-keyman/src/test/run-tests.sh @@ -3,7 +3,7 @@ SRCDIR=${top_srcdir:-$(realpath "$(dirname $0)/../..")} PID_FILE=/tmp/keymanutil-tests-pids -if [ -v KEYMAN_PKG_BUILD ]; then +if [[ -v KEYMAN_PKG_BUILD ]]; then # During package builds we skip these tests - they often fail, e.g. # during Debian reproducibility testing with an error like # "cannot open display: :32" @@ -13,49 +13,49 @@ if [ -v KEYMAN_PKG_BUILD ]; then fi -if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null; then +if ! command -v Xvfb > /dev/null || ! command -v Xephyr > /dev/null || ! command -v metacity > /dev/null; then echo "# Please install Xvfb, Xephyr and metacity before running these tests!" echo "# sudo apt install xvfb xserver-xephyr metacity" exit 1 fi function cleanup() { - if [ -f $PID_FILE ]; then + if [[ -f "${PID_FILE}" ]]; then echo echo "# Shutting down processes..." - bash $PID_FILE > /dev/null 2>&1 - rm $PID_FILE - rm -rf "$TEMP_DATA_DIR" + bash "${PID_FILE}" > /dev/null 2>&1 + rm "${PID_FILE}" + rm -rf "${TEMP_DATA_DIR}" echo "# Finished shutdown of processes." fi } -echo > $PID_FILE +echo > "${PID_FILE}" trap cleanup EXIT SIGINT echo "# Starting Xvfb..." Xvfb -screen 0 1024x768x24 :33 &> /dev/null & -echo "kill -9 $!" >> $PID_FILE +echo "kill -9 $! &> /dev/null" >> "${PID_FILE}" sleep 1 echo "# Starting Xephyr..." DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null & -echo "kill -9 $!" >> $PID_FILE +echo "kill -9 $! &> /dev/null" >> "${PID_FILE}" sleep 1 echo "# Starting metacity" metacity --display=:32 &> /dev/null & -echo "kill -9 $!" >> $PID_FILE +echo "kill -9 $! &> /dev/null" >> "${PID_FILE}" export DISPLAY=:32 # Install schema to temporary directory. This removes the build dependency on the keyman package. TEMP_DATA_DIR=$(mktemp --directory) -SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas -export XDG_DATA_DIRS=$TEMP_DATA_DIR:$XDG_DATA_DIRS +SCHEMA_DIR="${TEMP_DATA_DIR}/glib-2.0/schemas" +export XDG_DATA_DIRS="${TEMP_DATA_DIR}:${XDG_DATA_DIRS}" -mkdir -p "$SCHEMA_DIR" -cp "$SRCDIR/../keyman-config/resources/com.keyman.gschema.xml" "$SCHEMA_DIR/" -glib-compile-schemas "$SCHEMA_DIR" +mkdir -p "${SCHEMA_DIR}" +cp "${SRCDIR}/../keyman-config/resources/com.keyman.gschema.xml" "${SCHEMA_DIR}/" +glib-compile-schemas "${SCHEMA_DIR}" export GSETTINGS_BACKEND=memory diff --git a/linux/ibus-keyman/tests/README.md b/linux/ibus-keyman/tests/README.md index 8882bcae00..0bee95e3b8 100644 --- a/linux/ibus-keyman/tests/README.md +++ b/linux/ibus-keyman/tests/README.md @@ -47,3 +47,13 @@ On the host, you can then attach to the VM. See the in `docs/settings/linux/launch.json` for a sample configuration in vscode. You'll have to adjust the IP address to match the VM which the `run-tests.sh` script will output. + +## Manually running tests in TeamCity + +To run the tests manually in TeamCity, you'll have to set an +environment variable `TEAMCITY_GIT_PATH` (the actual value doesn't +matter): + +```bash +export TEAMCITY_GIT_PATH=/something +``` diff --git a/linux/ibus-keyman/tests/meson.build b/linux/ibus-keyman/tests/meson.build index 24f4bca851..763ef11777 100644 --- a/linux/ibus-keyman/tests/meson.build +++ b/linux/ibus-keyman/tests/meson.build @@ -41,7 +41,7 @@ test_exe = executable( subdir('dbus-test-server') env_file = '/tmp/env.txt' -cleanup_file = '/tmp/ibus-keyman-test-pids' +cleanup_file = '/tmp/ibus-keyman-test-cleanup' pid_file = '/tmp/ibus-keyman-test-pids.pids' setup_tests = find_program('setup-tests.sh', dirs: [meson.current_source_dir() / 'scripts']) @@ -66,7 +66,7 @@ test( test( 'teardown-x11', teardown_tests, - args: [cleanup_file], + args: [cleanup_file, '--x11'], priority: -19, is_parallel: false, protocol: 'exitcode' @@ -86,7 +86,7 @@ if can_build_wayland test( 'teardown-wayland', teardown_tests, - args: [cleanup_file], + args: [cleanup_file, '--wayland'], priority: -29, is_parallel: false, protocol: 'exitcode' diff --git a/linux/ibus-keyman/tests/scripts/find-tests.sh b/linux/ibus-keyman/tests/scripts/find-tests.sh index 502cbab55f..2d154a2a47 100755 --- a/linux/ibus-keyman/tests/scripts/find-tests.sh +++ b/linux/ibus-keyman/tests/scripts/find-tests.sh @@ -1,15 +1,15 @@ #!/usr/bin/env bash set -eu -while [ -n "$1" ]; do - if [ ! -d "$1" ]; then +while [[ -n "$1" ]]; do + if [[ ! -d "$1" ]]; then shift continue fi pushd "$1" > /dev/null while IFS= read -r -d '' file; do - testname=$(basename "$file" .kmx) + testname=$(basename "${file}" .kmx) #shellcheck disable=SC2059 - printf "$(basename "$file")\t${testname#k_}\n" + printf "$(basename "${file}")\t${testname#k_}\n" done < <(find . -name \*.kmx -print0 | sort -z) popd > /dev/null exit 0 diff --git a/linux/ibus-keyman/tests/scripts/run-single-test.sh b/linux/ibus-keyman/tests/scripts/run-single-test.sh index 39075421fc..a95a7b8c45 100755 --- a/linux/ibus-keyman/tests/scripts/run-single-test.sh +++ b/linux/ibus-keyman/tests/scripts/run-single-test.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash set -eu -TESTDIR=${XDG_DATA_HOME:-$HOME/.local/share}/keyman/test_kmx +TESTDIR=${XDG_DATA_HOME:-${HOME}/.local/share}/keyman/test_kmx . "$(dirname "$0")"/test-helper.inc.sh -if [ -v KEYMAN_PKG_BUILD ]; then +if [[ -v KEYMAN_PKG_BUILD ]]; then # During package builds we skip these tests that require to start ibus because # ibus requires to find /var/lib/dbus/machine-id or /etc/machine-id, otherwise it fails with: # "Bail out! IBUS-FATAL-WARNING: Unable to load /var/lib/dbus/machine-id: Failed to open file - # “/var/lib/dbus/machine-id”: No such file or directory" + # "/var/lib/dbus/machine-id": No such file or directory" echo "TAP version 14" echo "1..0 # SKIP on package build" exit 0 fi -if ! which Xvfb > /dev/null || ! which Xephyr > /dev/null || ! which metacity > /dev/null || ! which mutter > /dev/null; then +if ! command -v Xvfb > /dev/null || ! command -v Xephyr > /dev/null || ! command -v metacity > /dev/null || ! command -v mutter > /dev/null; then echo "Please install Xvfb, Xephyr, metacity and mutter before running these tests!" echo "sudo apt install xvfb xserver-xephyr metacity mutter" exit 1 @@ -53,7 +53,7 @@ function run_tests() { # shellcheck disable=SC2086 "${G_TEST_BUILDDIR:-.}"/ibus-keyman-tests ${ARG_K-} ${ARG_TAP-} \ ${ARG_VERBOSE-} ${ARG_DEBUG-} ${ARG_SURROUNDING_TEXT-} ${ARG_NO_SURROUNDING_TEXT-} \ - --directory "$TESTDIR" ${ARG_DISPLAY_SERVER} "$TESTFILE" + --directory "${TESTDIR}" ${ARG_DISPLAY_SERVER} "${TESTFILE}" echo "# Finished tests." } @@ -79,14 +79,14 @@ while (( $# )); do done # shellcheck disable=SC2236 -if [ -n "${ARG_PIDS:-}" ] && [ ! -n "${ARG_CLEANUP:-}" ]; then +if [[ -n "${ARG_PIDS:-}" ]] && [[ ! -n "${ARG_CLEANUP:-}" ]]; then echo "Error: '--check' also requires '--cleanup'. Exiting." exit 6 fi -check_processes_running "$ARG_DISPLAY_SERVER" "$ARG_ENV" "$ARG_CLEANUP" "$ARG_PIDS" "$ARG_TESTNAME" >&2 +check_processes_running "${ARG_DISPLAY_SERVER}" "${ARG_ENV}" "${ARG_CLEANUP}" "${ARG_PIDS}" "${ARG_TESTNAME}" >&2 # shellcheck source=/dev/null -. "$ARG_ENV" +. "${ARG_ENV}" run_tests diff --git a/linux/ibus-keyman/tests/scripts/run-tests.sh b/linux/ibus-keyman/tests/scripts/run-tests.sh index c2b9d796df..f5338ccf62 100755 --- a/linux/ibus-keyman/tests/scripts/run-tests.sh +++ b/linux/ibus-keyman/tests/scripts/run-tests.sh @@ -3,7 +3,6 @@ set -eu -# shellcheck disable=SC2034 TOP_SRCDIR=${top_srcdir:-$(realpath "$(dirname "$0")/../..")} TESTBASEDIR=${XDG_DATA_HOME:-${HOME}/.local/share}/keyman TESTDIR=${TESTBASEDIR}/test_kmx @@ -11,11 +10,10 @@ CLEANUP_FILE=/tmp/ibus-keyman-test-cleanup PID_FILE=/tmp/ibus-keyman-test.pids ENV_FILE=/tmp/keyman-env.txt -# shellcheck disable=SC1091 . "$(dirname "$0")"/test-helper.inc.sh local_cleanup() { - cleanup "${CLEANUP_FILE}" + cleanup "${CLEANUP_FILE}" "${DISPLAY_SERVER:-}" } if [[ -v KEYMAN_PKG_BUILD ]]; then @@ -69,7 +67,7 @@ function run_tests() { G_TEST_BUILDDIR="$(dirname "$0")/../../../build/$(arch)/${CONFIG}/tests" - setup "$DISPLAY_SERVER" "$ENV_FILE" "$CLEANUP_FILE" "$PID_FILE" --standalone + setup "${DISPLAY_SERVER}" "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" --standalone if [[ "${DOCKER_RUNNING:-false}" == "true" ]]; then echo "# NOTE: When the tests fail check ibus-engine-keyman.log, ibus-daemon.log and km-test-server.log in build/docker-linux/tmp/!" @@ -105,7 +103,7 @@ function run_tests() { --directory "${TESTDIR}" "${DISPLAY_SERVER}" ${TESTFILES[@]} echo "# Finished tests." - cleanup "${CLEANUP_FILE}" + cleanup "${CLEANUP_FILE}" "${DISPLAY_SERVER:-}" } USE_WAYLAND=1 @@ -122,7 +120,18 @@ while (( $# )); do --no-x11) USE_X11=0;; --verbose|-v) ARG_VERBOSE=--verbose;; --debug) ARG_DEBUG=--debug-log;; - --remote-debug) REMOTE_HOST="$(ip route get 8.8.8.8 | sed -E 's/.*src (\S+) .*/\1/;t;d'):2345" && REMOTE_DEBUG="gdbserver ${REMOTE_HOST}";; + --remote-debug) + if [[ -z "${DOCKER_RUNNING:-}" ]]; then + REMOTE_HOST="$(ip route get 8.8.8.8 | sed -E 's/.*src (\S+) .*/\1/;t;d'):2345" + else + REMOTE_HOST="localhost:2345" + if ! dpkg -l gdbserver &> /dev/null; then + echo "Installing gdbserver..." + sudo apt update && sudo apt install -y gdbserver + fi + fi + REMOTE_DEBUG="gdbserver ${REMOTE_HOST}" + ;; --) shift && break ;; *) echo "Error: Unexpected argument \"$1\". Exiting." ; exit 4 ;; esac @@ -149,6 +158,14 @@ if [[ ! -f "${G_TEST_BUILDDIR}/ibus-keyman-tests" ]]; then G_TEST_BUILDDIR="${G_TEST_BUILDDIR:-../../build/$(arch)/release/tests}" fi +if [[ "${USE_WAYLAND}" == "1" ]] && [[ "${USE_X11}" == "1" ]]; then + # Restart script to first run Wayland tests + "$0" --no-x11 "$@" + # and now X11 tests + "$0" --no-wayland "$@" + exit 0 +fi + echo > "${CLEANUP_FILE}" echo > "${PID_FILE}" trap local_cleanup EXIT SIGINT diff --git a/linux/ibus-keyman/tests/scripts/setup-tests.sh b/linux/ibus-keyman/tests/scripts/setup-tests.sh index 7ea0070ae8..9ea27876d3 100755 --- a/linux/ibus-keyman/tests/scripts/setup-tests.sh +++ b/linux/ibus-keyman/tests/scripts/setup-tests.sh @@ -5,4 +5,9 @@ set -eu exit_on_package_build -setup "$1" "$2" "$3" "$4" +DISPLAY_SERVER="$1" +ENV_FILE="$2" +CLEANUP_FILE="$3" +PID_FILE="$4" + +setup "${DISPLAY_SERVER}" "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" diff --git a/linux/ibus-keyman/tests/scripts/teardown-tests.sh b/linux/ibus-keyman/tests/scripts/teardown-tests.sh index c74513f281..08c46fd9dc 100755 --- a/linux/ibus-keyman/tests/scripts/teardown-tests.sh +++ b/linux/ibus-keyman/tests/scripts/teardown-tests.sh @@ -5,4 +5,7 @@ set -eu exit_on_package_build -cleanup "$1" +CLEANUP_FILE="$1" +DISPLAY_SERVER="$2" + +cleanup "${CLEANUP_FILE}" "${DISPLAY_SERVER}" diff --git a/linux/ibus-keyman/tests/scripts/test-helper.inc.sh b/linux/ibus-keyman/tests/scripts/test-helper.inc.sh index 2a6b38be3d..6f2bdd2806 100755 --- a/linux/ibus-keyman/tests/scripts/test-helper.inc.sh +++ b/linux/ibus-keyman/tests/scripts/test-helper.inc.sh @@ -94,10 +94,11 @@ function _link_test_keyboards() { } function _setup_init() { - local ENV_FILE CLEANUP_FILE PID_FILE + local ENV_FILE CLEANUP_FILE PID_FILE DISPLAY_SERVER ENV_FILE=$1 CLEANUP_FILE=$2 PID_FILE=$3 + DISPLAY_SERVER=$4 if [[ -z "${TOP_SRCDIR:-}" ]]; then TOP_SRCDIR=${G_TEST_SRCDIR:-$(realpath "$(dirname "$0")/..")}/.. @@ -110,7 +111,7 @@ function _setup_init() { if [[ -f "${CLEANUP_FILE}" ]]; then # kill previous instances - "$(dirname "$0")"/teardown-tests.sh "${CLEANUP_FILE}" || true + "$(dirname "$0")"/teardown-tests.sh "${CLEANUP_FILE}" "${DISPLAY_SERVER}" || true fi echo > "${CLEANUP_FILE}" @@ -134,6 +135,54 @@ function _setup_init() { echo "export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> "${ENV_FILE}" } +function _setup_dbus_session() { + local ENV_FILE CLEANUP_FILE + ENV_FILE="$1" + CLEANUP_FILE="$2" + + if [[ -z "${DOCKER_RUNNING:-}" ]] && [[ -z "${TEAMCITY_GIT_PATH:-}" ]] && ! pgrep -f jetbrains.buildServer.agent.AgentMain > /dev/null; then + return + fi + + if [[ ! -d "/run/user/${UID:-$(id -u)}" ]]; then + # /run/user/$UID usually gets created by the session, but since we + # don't start the regular UI we don't have a session. Therefore we + # call the setupRuntimeDir.sh script to create the directory. The + # script should contain the following commands: + # mkdir -p "/run/user/${SUDO_UID:-$(id -u)}" + # chmod 0700 "/run/user/${SUDO_UID:-$(id -u)}" + # chown "${SUDO_USER:-$(whoami)}" "/run/user/${SUDO_UID:-$(id -u)}" + if [[ -f "/usr/local/bin/setupRuntimeDir.sh" ]]; then + echo "# Creating /run/user/${UID:-$(id -u)}..." + sudo /usr/local/bin/setupRuntimeDir.sh + else + echo "# WARNING: /run/user/${UID:-$(id -u)} is missing without having /usr/local/bin/setupRuntimeDir.sh, tests will likely fail" + fi + fi + + if [[ -z "${XDG_RUNTIME_DIR:-}" ]]; then + export XDG_RUNTIME_DIR=/run/user/${UID:-$(id -u)} + echo "export XDG_RUNTIME_DIR=/run/user/${UID:-$(id -u)}" >> "${ENV_FILE}" + fi + if [[ ! -d "${XDG_RUNTIME_DIR}" ]]; then + echo "${XDG_RUNTIME_DIR} is missing, please create it" + exit 1 + fi + + export NO_AT_BRIDGE=1 + echo "export NO_AT_BRIDGE=1" >> "${ENV_FILE}" + + if pgrep -u "${USER:-$(whoami)}" dbus-daemon > /dev/null; then + # dbus-daemon is already running, skipping setup + return + fi + + echo "# Starting dbus session..." + dbus-launch --auto-syntax >> "${ENV_FILE}" + PID=$(pidof dbus-daemon) + echo "kill -9 ${PID} || true # dbus-daemon" >> "${CLEANUP_FILE}" +} + function _setup_test_dbus_server() { local ENV_FILE CLEANUP_FILE ENV_FILE=$1 @@ -172,11 +221,11 @@ function _setup_display_server() { # mutter-Message: 18:56:15.422: Using Wayland display name 'wayland-1' mutter --wayland --headless --no-x11 --virtual-monitor 1024x768 &> "${TMPFILE}" & PID=$! - echo "kill -9 ${PID} || true # mutter" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # mutter" >> "${CLEANUP_FILE}" echo "${PID} mutter" >> "${PID_FILE}" sleep 1s - export WAYLAND_DISPLAY WAYLAND_DISPLAY=$(grep "Using Wayland display" "${TMPFILE}" | cut -d"'" -f2) + export WAYLAND_DISPLAY rm "${TMPFILE}" echo "export WAYLAND_DISPLAY=\"${WAYLAND_DISPLAY}\"" >> "${ENV_FILE}" else @@ -191,7 +240,7 @@ function _setup_display_server() { break fi done - echo "kill -9 ${PID} || true # Xvfb" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # Xvfb" >> "${CLEANUP_FILE}" echo "${PID} Xvfb" >> "${PID_FILE}" while true; do echo "Starting Xephyr..." @@ -203,12 +252,12 @@ function _setup_display_server() { break fi done - echo "kill -9 ${PID} || true # Xephyr" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # Xephyr" >> "${CLEANUP_FILE}" echo "${PID} Xephyr" >> "${PID_FILE}" echo "Starting metacity" metacity --display=":${DISP_XEPHYR}" &> /dev/null & PID=$! - echo "kill -9 ${PID} || true # metacity" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # metacity" >> "${CLEANUP_FILE}" echo "${PID} metacity" >> "${PID_FILE}" export DISPLAY=:${DISP_XEPHYR} @@ -252,13 +301,14 @@ function _setup_ibus() { echo "Starting ibus-daemon..." #shellcheck disable=SC2086 ibus-daemon ${ARG_VERBOSE-} --daemonize --panel=disable --address=unix:abstract="${TEMP_DATA_DIR}/test-ibus" ${IBUS_CONFIG-} &> /tmp/ibus-daemon.log + PID=$(pgrep -f "${TEMP_DATA_DIR}/test-ibus") - if [[ "${STANDALONE}" == "--standalone" ]] && [[ "${DOCKER_RUNNING:-false}" != "true" ]]; then + if [[ "${STANDALONE}" == "--standalone" ]] && [[ "${DOCKER_RUNNING:-false}" != "true" ]] && [[ -z "${TEAMCITY_GIT_PATH:-}" ]]; then # manual test run - echo "if kill -9 ${PID}; then ibus restart || ibus start; fi # ibus-daemon" >> "${CLEANUP_FILE}" + echo "if kill -9 ${PID} &> /dev/null; then ibus restart || ibus start -d; fi # ibus-daemon" >> "${CLEANUP_FILE}" else # test run as part of the build - echo "kill -9 ${PID} || true # ibus-daemon" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # ibus-daemon" >> "${CLEANUP_FILE}" fi echo "${PID} ibus-daemon" >> "${PID_FILE}" sleep 1s @@ -272,7 +322,7 @@ function _setup_ibus() { #shellcheck disable=SC2086 "${TOP_BINDIR}/src/ibus-engine-keyman" --testing ${ARG_VERBOSE-} &> /tmp/ibus-engine-keyman.log & PID=$! - echo "kill -9 ${PID} || true # ibus-engine-keyman" >> "${CLEANUP_FILE}" + echo "kill -9 ${PID} &> /dev/null || true # ibus-engine-keyman" >> "${CLEANUP_FILE}" echo "${PID} ibus-engine-keyman" >> "${PID_FILE}" sleep 1s } @@ -285,7 +335,7 @@ function setup() { PID_FILE=$4 STANDALONE=${5:-} - _setup_init "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" + _setup_init "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" "${DISPLAY_SERVER}" TESTBASEDIR=${XDG_DATA_HOME:-${HOME}/.local/share}/keyman TESTDIR=${TESTBASEDIR}/test_kmx @@ -294,6 +344,7 @@ function setup() { _generate_kmpjson "${TESTDIR}" + _setup_dbus_session "${ENV_FILE}" "${CLEANUP_FILE}" _setup_test_dbus_server "${ENV_FILE}" "${CLEANUP_FILE}" _setup_display_server "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" "${DISPLAY_SERVER}" _setup_schema_and_gsettings "${ENV_FILE}" @@ -307,30 +358,46 @@ function setup_display_server_only() { CLEANUP_FILE=$3 PID_FILE=$4 - _setup_init "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" + _setup_init "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" "${DISPLAY_SERVER}" _setup_display_server "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" "${DISPLAY_SERVER}" _setup_schema_and_gsettings "${ENV_FILE}" } function cleanup() { - local CLEANUP_FILE + local CLEANUP_FILE DISPLAY_SERVER CLEANUP_FILE=$1 + DISPLAY_SERVER=$2 if [[ -f "${CLEANUP_FILE}" ]]; then echo echo "# Shutting down processes..." - bash "${CLEANUP_FILE}" # > /dev/null 2>&1 - rm "$CLEANUP_FILE" + bash "${CLEANUP_FILE}" # &> /dev/null + rm "${CLEANUP_FILE}" echo "# Finished shutdown of processes." fi if [[ "${DOCKER_RUNNING:-false}" == "true" ]]; then - # Note: build/tmp is build/docker-linux/tmp on the host! + echo "# Copying log files..." + # Note: build/tmp is build/docker-linux//tmp on the host! BUILD_TMP="$(dirname "$0")/../../../build/tmp" mkdir -p "${BUILD_TMP}" - [[ -f /tmp/ibus-engine-keyman.log ]] && mv /tmp/ibus-engine-keyman.log "${BUILD_TMP}/ibus-engine-keyman.log" - [[ -f /tmp/ibus-daemon.log ]] && mv /tmp/ibus-daemon.log "${BUILD_TMP}/ibus-daemon.log" - [[ -f /tmp/km-test-server.log ]] && mv /tmp/km-test-server.log "${BUILD_TMP}/km-test-server.log" + [[ -f /tmp/ibus-engine-keyman.log ]] && mv /tmp/ibus-engine-keyman.log "${BUILD_TMP}/ibus-engine-keyman${DISPLAY_SERVER}.log" + [[ -f /tmp/ibus-daemon.log ]] && mv /tmp/ibus-daemon.log "${BUILD_TMP}/ibus-daemon${DISPLAY_SERVER}.log" + [[ -f /tmp/km-test-server.log ]] && mv /tmp/km-test-server.log "${BUILD_TMP}/km-test-server${DISPLAY_SERVER}.log" + echo "# Finished copying log files" + fi + + if [[ -z "${DOCKER_RUNNING:-}" ]] && [[ -z "${TEAMCITY_GIT_PATH:-}" ]] && ! pgrep -f jetbrains.buildServer.agent.AgentMain > /dev/null; then + # Not running in Docker or TeamCity: + # Killing the ibus-daemon that we started for testing might have caused + # some other ibus processes that belong to the real ibus daemon to + # be killed as well, leaving us with no way to type. + # So we kill the remaining lonely ibus-daemon and start ibus again. + if [[ "$(ibus engine)" == "" ]]; then + echo "# Killing and restarting ibus-daemon" + kill -9 "$(pgrep -u "${USER:-$(whoami)}" ibus-daemon)" + ibus start -d + fi fi } @@ -374,8 +441,8 @@ function check_processes_running() { echo "${MISSING_PROCS}" ; \ echo "Restarting..." ; } >> /tmp/debug.output mv /tmp/ibus-engine-keyman.log{,"-${TEST_NAME}-$(date -Iseconds)"} - cleanup "${CLEANUP_FILE}" > /dev/null 2>&1 - setup "${DISPLAY_SERVER}" "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" > /dev/null 2>&1 + cleanup "${CLEANUP_FILE}" "${DISPLAY_SERVER}" &> /dev/null + setup "${DISPLAY_SERVER}" "${ENV_FILE}" "${CLEANUP_FILE}" "${PID_FILE}" &> /dev/null fi if [[ "$(_get_missing_processes "$PID_FILE")" != "" ]]; then diff --git a/linux/keyman-config/Makefile b/linux/keyman-config/Makefile index b75c6b17ab..8176f0f144 100644 --- a/linux/keyman-config/Makefile +++ b/linux/keyman-config/Makefile @@ -1,47 +1,13 @@ #!/usr/bin/make default: - ./build.sh clean build - -install: - ./build.sh install - -uninstall: # run as sudo - ./build.sh uninstall - -clean: - ./build.sh clean - -check: - ./build.sh test - -dist: clean version - # TODO: remove this if when we no longer support Ubuntu 22.04 Jammy - if dpkg --compare-versions "$(lsb_release -r -s)" lt 24.04; then \ - python3 setup.py sdist ; \ - else \ - python3 -m build --outdir build --sdist ; \ - fi - -deb: dist - @KEYMAN_VERSION=$(shell echo `basename dist/*.gz .tar.gz|cut -d "-" -f2` > /tmp/keyman_version) - @echo KEYMAN_VERSION is $(shell cat /tmp/keyman_version) - @mkdir -p make_deb - cd make_deb && tar xf ../dist/keyman_config-$(shell cat /tmp/keyman_version).tar.gz && \ - mv keyman_config-$(shell cat /tmp/keyman_version) keyman-config-$(shell cat /tmp/keyman_version) && \ - tar cfz keyman-config_$(shell cat /tmp/keyman_version).orig.tar.gz keyman-config-$(shell cat /tmp/keyman_version) && \ - cd keyman-config-$(shell cat /tmp/keyman_version) && cp -a ../../../debian . && dch -v$(shell cat /tmp/keyman_version)-1 "" - cd make_deb/keyman-config-$(shell cat /tmp/keyman_version) && debuild -us -uc - rm /tmp/keyman_version - -man: - ./build-help.sh --man --no-reconf + echo "Use ./build.sh command instead of make!" version_reconf: cd .. && ./scripts/reconf.sh version: version_reconf - $(eval KEYMAN_VERSION := $(shell python3 -c "from keyman_config import __releaseversion__; print(__releaseversion__)")) + $(eval KEYMAN_VERSION := $(shell ./build.sh build && python3 -c "from keyman_config import __releaseversion__; print(__releaseversion__)")) # i18n diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index 32383ac83d..09061feda2 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -39,18 +39,25 @@ clean_action() { fi } +install_schema() { + local SCHEMA_DIR="$1" + mkdir -p "${SCHEMA_DIR}" + cp resources/com.keyman.gschema.xml "${SCHEMA_DIR}"/ + glib-compile-schemas "${SCHEMA_DIR}" +} + execute_with_temp_schema() { local TEMP_DATA_DIR SCHEMA_DIR TEMP_DATA_DIR=$(mktemp -d) SCHEMA_DIR="${TEMP_DATA_DIR}/glib-2.0/schemas" export XDG_DATA_DIRS="${TEMP_DATA_DIR}":${XDG_DATA_DIRS-} export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}:/usr/share/glib-2.0/schemas/:${GSETTINGS_SCHEMA_DIR-}" - mkdir -p "${SCHEMA_DIR}" - cp resources/com.keyman.gschema.xml "${SCHEMA_DIR}"/ - glib-compile-schemas "${SCHEMA_DIR}" + + install_schema "${SCHEMA_DIR}" "$@" - export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:} + unset GSETTINGS_SCHEMA_DIR + export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:} rm -rf "${TEMP_DATA_DIR}" } @@ -105,25 +112,42 @@ test_action() { if builder_has_option --report; then builder_echo "Creating coverage report" - python3 -m coverage html --directory="$THIS_SCRIPT_PATH/build/coveragereport/" --data-file=build/.coverage + python3 -m coverage html --directory="${THIS_SCRIPT_PATH}/build/coveragereport/" --data-file=build/.coverage fi } install_action() { - if [ -n "${SUDO_USER:-}" ]; then - # with sudo install into /usr/local - pip3 install qrcode sentry-sdk + if [[ -v VIRTUAL_ENV ]]; then + # Running in a virtual environment + pip3 install qrcode sentry-sdk fonttools setuptools pip3 install . # install icons + mkdir -p "${VIRTUAL_ENV}/share/keyman/icons" + cp keyman_config/icons/* "${VIRTUAL_ENV}/share/keyman/icons" + # install man pages + mkdir -p "${VIRTUAL_ENV}/share/man/man1" + cp ../../debian/man/*.1 "${VIRTUAL_ENV}/share/man/man1" + # install schema + install_schema "${VIRTUAL_ENV}/share/glib-2.0/schemas" + elif [[ -n "${SUDO_USER:-}" ]] || [[ "$(id -u)" == "0" ]]; then + # as root install into /usr/local + pip3 install qrcode sentry-sdk + pip3 install --prefix /usr/local . + # install icons mkdir -p /usr/local/share/keyman/icons cp keyman_config/icons/* /usr/local/share/keyman/icons # install man pages mkdir -p /usr/local/share/man/man1 cp ../../debian/man/*.1 /usr/local/share/man/man1 + # install schema + install_schema "/usr/local/share/glib-2.0/schemas" else # without sudo install into /tmp/keyman (or $DESTDIR) - mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')" + python_version=$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)') + mkdir -p "/tmp/keyman/${python_version}" pip3 install --prefix /tmp/keyman . + # install schema + install_schema "/tmp/keyman/local/share/glib-2.0/schemas" fi } @@ -132,6 +156,7 @@ uninstall_action() { clean_action rm -rf /usr/local/share/keyman/icons rm -f /usr/local/share/man/man1/km-*.1 + rm -f /usr/local/share/glib-2.0/schemas/com.keyman.gschema.xml pip3 uninstall keyman_config rm -f /usr/local/bin/km-config rm -f /usr/local/bin/km-kvk2ldml diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index 26fbeddf7d..937bcc7d08 100644 --- a/linux/keyman-config/keyman_config/__init__.py +++ b/linux/keyman-config/keyman_config/__init__.py @@ -1,4 +1,5 @@ import atexit +import dbus import gettext import importlib import logging @@ -110,7 +111,24 @@ def _set_dbus_started_for_session(value): def verify_dbus_running(): - if not 'DBUS_SESSION_BUS_ADDRESS' in os.environ: + if os.environ.get('SUDO_USER'): + # If running with sudo we won't try to start dbus since it will + # not always work. The main reason we need dbus is so that we can + # notify other km-config processes that the keyboard list has changed. + # If running with sudo we call dbus-send instead of instantiating the + # KeymanConfigServiceManager class. + return + + if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: + # already running - nothing to do + return + + try: + # This might already be enough to start dbus + bus = dbus.SessionBus() + bus.close() + return + except dbus.exceptions.DBusException: try: # Seems dbus isn't running for the current user. Try to start it # and set these environment variables diff --git a/linux/keyman-config/keyman_config/dbus_util.py b/linux/keyman-config/keyman_config/dbus_util.py index af64f2e517..f88143e55c 100644 --- a/linux/keyman-config/keyman_config/dbus_util.py +++ b/linux/keyman-config/keyman_config/dbus_util.py @@ -2,6 +2,7 @@ import contextlib import logging import os +import subprocess import dbus import dbus.bus @@ -12,6 +13,9 @@ from dbus import DBusException BUS_NAME = 'com.Keyman.Config' OBJECT_PATH = '/com/Keyman/Config' +DBusSessionBusAddressValue = f'unix:path=/run/user/{os.environ.get("SUDO_UID")}/bus' +DBusSessionBusAddress = f'DBUS_SESSION_BUS_ADDRESS={DBusSessionBusAddressValue}' +XdgRuntimeDir = f'XDG_RUNTIME_DIR=/run/user/{os.environ.get("SUDO_UID")}' __keyman_config_service = None @@ -36,6 +40,16 @@ class KeymanConfigService(dbus.service.Object): self.KeyboardListChangedSignal() +def keyboard_list_changed(): + if os.environ.get('SUDO_USER'): + args = ['sudo', '-u', os.environ.get('SUDO_USER'), 'dbus-send', + f'--bus={DBusSessionBusAddressValue}', + '/com/Keyman/Config', 'com.Keyman.Config.keyboard_list_changed'] + subprocess.run(args) + else: + get_keyman_config_service().keyboard_list_changed() + + class KeymanConfigServiceManager: def __init__(self, handler): self.__name_owner_watch = None diff --git a/linux/keyman-config/keyman_config/downloadkeyboard.py b/linux/keyman-config/keyman_config/downloadkeyboard.py index ca3b091f92..7eac44b5ec 100755 --- a/linux/keyman-config/keyman_config/downloadkeyboard.py +++ b/linux/keyman-config/keyman_config/downloadkeyboard.py @@ -94,8 +94,7 @@ class DownloadKmpWindow(Gtk.Dialog): qs = urllib.parse.parse_qs(parsed.query) package_id = parsed.path.split('/')[-1] downloadfile = os.path.join(get_download_folder(), package_id) - download_url = KeymanComUrl + '/go/package/download/' + package_id + \ - '?platform=linux&tier=' + __tier__ + download_url = f'{KeymanComUrl}/go/package/download/{package_id}?platform=linux&tier={__tier__}' if 'bcp47' in qs: self.language = qs['bcp47'][0] download_url += '&bcp47=' + qs['bcp47'][0] diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index 3addda759b..ed60d5b2e3 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -13,6 +13,7 @@ from gi.repository import GObject from keyman_config import _ from keyman_config import KeymanApiUrl, KeymanDownloadsUrl from keyman_config.deprecated_decorator import deprecated +from keyman_config.sentry_handling import SentryErrorHandling class InstallLocation(GObject.GEnum): @@ -21,7 +22,12 @@ class InstallLocation(GObject.GEnum): User = 3 Unknown = 99 -can_use_cache = True + +# When running under sudo, we don't want to use the cache +# since that stores files with root permissions so that they +# can't be deleted/replaced by the user later. +can_use_cache = not os.environ.get('SUDO_USER') + def get_install_area_string(area): if area == InstallLocation.OS: @@ -105,7 +111,7 @@ def get_keyboard_data(keyboardID, weekCache=False): dict: Keyboard data """ logging.info('Getting data for keyboard %s', keyboardID) - api_url = KeymanApiUrl + '/keyboard/' + keyboardID + api_url = f'{KeymanApiUrl}/keyboard/{keyboardID}' logging.debug('At URL %s', api_url) cache_dir = keyman_cache_dir() current_dir = os.getcwd() @@ -123,10 +129,7 @@ def get_keyboard_data(keyboardID, weekCache=False): logging.debug('Time: {0} / Used Cache: {1}'.format(now, _did_use_cache(response))) os.chdir(current_dir) _uninstall_cache() - if response.status_code == 200: - return response.json() - else: - return None + return response.json() if response.status_code == 200 else None def get_download_folder(): @@ -234,6 +237,8 @@ def download_kmp_file(url, kmpfile, cache=False): """ logging.info('Download URL: %s', url) downloadfile = None + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='download', message=f'Downloading keyboard from {url}') if cache: cache_dir = keyman_cache_dir() diff --git a/linux/keyman-config/keyman_config/gnome_keyboards_util.py b/linux/keyman-config/keyman_config/gnome_keyboards_util.py index 8d9a6fae2f..030f4d9234 100644 --- a/linux/keyman-config/keyman_config/gnome_keyboards_util.py +++ b/linux/keyman-config/keyman_config/gnome_keyboards_util.py @@ -3,6 +3,7 @@ import logging import os from keyman_config.gsettings import GSettings +from keyman_config.sentry_handling import SentryErrorHandling # pylint: disable=global-statement @@ -52,13 +53,20 @@ def _reset_gnome_shell(): def get_ibus_keyboard_id(keyboard, packageDir, language=None, ignore_language=False): if not keyboard: return None + sentry = SentryErrorHandling() kmx_file = os.path.join(packageDir, keyboard['id'] + ".kmx") if ignore_language: + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(1): {kmx_file}') return kmx_file if language is not None and language != '': logging.debug(language) + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(2): {language}:{kmx_file}') return f"{language}:{kmx_file}" if "languages" in keyboard and len(keyboard["languages"]) > 0: - logging.debug(keyboard["languages"][0]) - return f"{keyboard['languages'][0]['id']}:{kmx_file}" + firstLanguage = keyboard["languages"][0] + logging.debug(firstLanguage) + if 'id' in firstLanguage: + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(3): {firstLanguage["id"]}:{kmx_file}') + return f"{firstLanguage['id']}:{kmx_file}" + sentry.add_breadcrumb(category='keyboard', message=f'ibus_keyboard_id(4): {kmx_file}') return kmx_file diff --git a/linux/keyman-config/keyman_config/gsettings.py b/linux/keyman-config/keyman_config/gsettings.py index d674011c92..2509988c0a 100644 --- a/linux/keyman-config/keyman_config/gsettings.py +++ b/linux/keyman-config/keyman_config/gsettings.py @@ -8,6 +8,8 @@ import sys from gi.repository import Gio # needs to come before gi.overrides.GLib! from gi.overrides.GLib import Variant +from keyman_config.dbus_util import DBusSessionBusAddress, XdgRuntimeDir + # The xdg module got renamed to xdg_base_dirs, but the package # (python3-xdg) on Ubuntu still ships xdg. So we support both. try: @@ -71,30 +73,19 @@ class GSettings(): def get(self, key): if self.is_sudo: args = ['sudo', '-H', '-u', os.environ.get('SUDO_USER'), - f"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{os.environ.get('SUDO_UID')}/bus", + DBusSessionBusAddress, XdgRuntimeDir, 'gsettings', 'get', self.schema_id, key] - if sys.version_info.major <= 3 and sys.version_info.minor < 7: - # capture_output got added in Python 3.7 - try: - output = subprocess.check_output(args) - value = eval(output) - except subprocess.CalledProcessError: - value = None - logging.warning('Could not convert to sources') - else: - process = subprocess.run(args, capture_output=True) - if process.returncode == 0: - output = process.stdout - if output.decode('utf-8').startswith('['): - value = eval(output) - return value + process = subprocess.run(args, capture_output=True) + if process.returncode == 0: + output = process.stdout + if output.decode('utf-8').startswith('['): + return eval(output) - logging.debug('Could not convert to sources') - return None + logging.debug('Could not convert to sources') + return None else: variant = self.schema.get_value(key) - value = self._convert_variant_to_array(variant) - return value + return self._convert_variant_to_array(variant) def _get_schema_path(self): return self.schema_id.replace('.', '/') @@ -122,10 +113,10 @@ class GSettings(): def set(self, key, value, type_string) -> None: if self.is_sudo: variant = str(value) - subprocess.run( - ['sudo', '-H', '-u', os.environ.get('SUDO_USER'), - f"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{os.environ.get('SUDO_UID')}/bus", - 'gsettings', 'set', self.schema_id, key, variant], check=False) + args = ['sudo', '-H', '-u', os.environ.get('SUDO_USER'), + DBusSessionBusAddress, XdgRuntimeDir, + 'gsettings', 'set', self.schema_id, key, variant] + subprocess.run(args, check=False) elif get_dbus_started_for_session(): self._set_key_file(key, value, type_string) else: diff --git a/linux/keyman-config/keyman_config/handle_install.py b/linux/keyman-config/keyman_config/handle_install.py index f551a63dd6..9933978a57 100644 --- a/linux/keyman-config/keyman_config/handle_install.py +++ b/linux/keyman-config/keyman_config/handle_install.py @@ -6,6 +6,7 @@ from zipfile import is_zipfile from keyman_config import KeymanComUrl, __tier__ from keyman_config.get_kmp import download_kmp_file, get_download_folder from keyman_config.install_window import InstallKmpWindow +from keyman_config.sentry_handling import SentryErrorHandling def download_and_install_package(url): @@ -18,6 +19,7 @@ def download_and_install_package(url): url: a .kmp file, a keyman:// URL, or a file:// URL pointing to a .kmp file, possibly with a bcp47= specified """ + sentry = SentryErrorHandling() parsedUrl = urlparse(url) bcp47 = _extract_bcp47(parsedUrl.query) @@ -34,11 +36,13 @@ def download_and_install_package(url): downloadFile = os.path.join(get_download_folder(), packageId) downloadUrl = f'{KeymanComUrl}/go/package/download/{packageId}?platform=linux&tier={__tier__}' + sentry.add_breadcrumb(category='download', message=f'About to download keyboard {packageId} from {downloadUrl}') packageFile = download_kmp_file(downloadUrl, downloadFile) if packageFile is None: return elif parsedUrl.scheme in ['', 'file']: packageFile = parsedUrl.path + sentry.add_breadcrumb(category='keyboard', message=f'adding keyboard from file {packageFile}') else: logging.error(f"Invalid URL: {url}") return diff --git a/linux/keyman-config/keyman_config/ibus_util.py b/linux/keyman-config/keyman_config/ibus_util.py index fba1ec5d3f..7eec49a373 100644 --- a/linux/keyman-config/keyman_config/ibus_util.py +++ b/linux/keyman-config/keyman_config/ibus_util.py @@ -10,9 +10,11 @@ import time import gi +from keyman_config.dbus_util import DBusSessionBusAddress, XdgRuntimeDir + gi.require_version('IBus', '1.0') from gi.repository import IBus -from pkg_resources import parse_version +from packaging import version from keyman_config.gnome_keyboards_util import is_gnome_desktop from keyman_config.gsettings import GSettings @@ -114,10 +116,8 @@ def verify_ibus_daemon(start): return IbusDaemon.ERROR_USER try: - ps_output = subprocess.run(('ps', '--user', user, '-o', 's=', '-o', 'cmd'), - check=False, stdout=subprocess.PIPE).stdout - ibus_daemons = re.findall(r'^[^ZT] (/[^ ]+/|)ibus-daemon( .*|$)', - ps_output.decode('utf-8'), re.MULTILINE) + ibus_daemons = subprocess.run(('pgrep', '-u', user, 'ibus-daemon'), + check=False, stdout=subprocess.PIPE).stdout.split() if len(ibus_daemons) <= 0: if start: _start_ibus_daemon(realuser) @@ -156,7 +156,7 @@ def _get_ibus_version(): def _start_ibus_daemon(realuser): try: - if parse_version(_get_ibus_version()) >= parse_version('1.5.28'): + if version.parse(_get_ibus_version()) >= version.parse('1.5.28'): # IBus ~1.5.28 added the `start` command, so we use that if possible # and let IBus deal with the necessary parameters args = ['ibus', 'start', '-d'] @@ -171,36 +171,28 @@ def _start_ibus_daemon(realuser): if realuser: # we have been called with `sudo`. Start ibus-daemon for the real user. - logging.info('starting ibus-daemon for user %s', realuser) - subprocess.run(['sudo', '-u', realuser].extend(args), check=True) + args = ['sudo', '-u', realuser, + DBusSessionBusAddress, XdgRuntimeDir, *args] + logging.info(f'starting ibus-daemon for user {realuser} with args: {args}') else: - logging.info('ibus-daemon not running. Starting it...') - subprocess.run(args, check=True) - except Exception: - logging.warning('Failed to start ibus-daemon') + logging.info(f'ibus-daemon not running. Starting it with args: {args}...') + subprocess.run(args, check=True) + logging.info('ibus-daemon started successfully') + except Exception as e: + logging.warning(f'Failed to start ibus-daemon: {e}') def restart_ibus(bus=None): verify_ibus_daemon(False) if realuser := os.environ.get('SUDO_USER'): # we have been called with `sudo`. Restart ibus for the real user. - logging.info('restarting IBus by subprocess for user %s', realuser) - subprocess.run(['sudo', '-u', realuser, 'ibus', 'restart'], check=False) + logging.info(f'restarting IBus by subprocess for user {realuser}') + subprocess.run(['sudo', '-u', realuser, + DBusSessionBusAddress, XdgRuntimeDir, + 'ibus', 'restart'], check=False) else: - logging.info('restarting IBus through API') - try: - if not bus: - bus = get_ibus_bus() - if bus: - logging.info("restarting IBus") - # we no longer try to restart since we sometimes ended up with more than one - # ibus-daemon process (#6237). Instead we only exit ibus here, and start - # ibus again below. - bus.exit(False) - bus.destroy() - except Exception as e: - logging.warning("Failed to restart IBus") - logging.warning(e) + logging.info('restarting IBus by subprocess') + subprocess.run(['ibus', 'restart'], check=False) # give ibus a chance to shutdown (#6237) time.sleep(1) # 1s verify_ibus_daemon(True) diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index cb69d596af..55c638c8e8 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -12,7 +12,7 @@ from keyman_config import _, __version__, secure_lookup from keyman_config.canonical_language_code_utils import CanonicalLanguageCodeUtils from keyman_config.convertico import checkandsaveico, extractico from keyman_config.custom_keyboards import CustomKeyboards -from keyman_config.dbus_util import get_keyman_config_service +from keyman_config.dbus_util import keyboard_list_changed from keyman_config.fcitx_util import is_fcitx_running from keyman_config.get_kmp import (InstallLocation, get_keyboard_data, get_keyboard_dir, get_keyman_doc_dir, @@ -23,6 +23,7 @@ from keyman_config.gnome_keyboards_util import (GnomeKeyboardsUtil, from keyman_config.ibus_util import get_ibus_bus, install_to_ibus, restart_ibus from keyman_config.kmpmetadata import KMFileTypes, get_metadata from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml +from keyman_config.sentry_handling import SentryErrorHandling # TODO userdir install # special processing for kmn if needed @@ -105,6 +106,9 @@ class InstallKmp(): self.kmpdocdir = get_keyman_doc_dir(area, self.packageID) self.kmpfontdir = get_keyman_font_dir(area, self.packageID) + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='install', message=f'Installing kmp: {self.packageID} from "{inputfile}" for lang: {language}; area: {"OS" if area == 1 else "Shared" if area == 2 else "User" if area == 3 else "Unknown"}') + if not os.path.isfile(inputfile): message = _("File {kmpfile} doesn't exist").format(kmpfile=inputfile) logging.error("install_kmp.py: %s", message) @@ -179,8 +183,9 @@ class InstallKmp(): f['name']) name, ext = os.path.splitext(f['name']) ldml = convert_kvk_to_ldml(name, fpath) - ldmlfile = os.path.join(self.packageDir, f"{name}.ldml") - output_ldml(ldmlfile, ldml) + if ldml is not None and len(ldml) > 0: + ldmlfile = os.path.join(self.packageDir, f"{name}.ldml") + output_ldml(ldmlfile, ldml) elif ftype == KMFileTypes.KM_ICON: # Special handling of icon to convert to PNG logging.info("Converting %s to PNG and installing both as keyman files", @@ -257,6 +262,9 @@ class InstallKmp(): if not language: language = self._add_custom_keyboard(firstKeyboard, packageDir, requested_language) + sentry = SentryErrorHandling() + sentry.add_breadcrumb(category='install', message=f'Installing keyboards for lang: {language}') + if is_fcitx_running(): return self._install_keyboards_to_fcitx() @@ -308,7 +316,8 @@ def extract_kmp(kmpfile, directory): def process_keyboard_data(keyboardID, packageDir) -> bool: - if not (kbdata := get_keyboard_data(keyboardID)): + kbdata = get_keyboard_data(keyboardID) + if not kbdata or len(kbdata) <= 0: return False if not os.path.isdir(packageDir) and os.access(os.path.join(packageDir, os.pardir), os.X_OK | os.W_OK): try: @@ -342,5 +351,5 @@ def install_kmp(inputfile, sharedarea=False, language=None): else: return_value = InstallKmp().install_kmp_user(inputfile, language) - get_keyman_config_service().keyboard_list_changed() + keyboard_list_changed() return return_value diff --git a/linux/keyman-config/keyman_config/kmpmetadata.py b/linux/keyman-config/keyman_config/kmpmetadata.py index 82e90b138d..42fa9774a6 100755 --- a/linux/keyman-config/keyman_config/kmpmetadata.py +++ b/linux/keyman-config/keyman_config/kmpmetadata.py @@ -11,6 +11,7 @@ from json.decoder import JSONDecodeError import magic from keyman_config import secure_lookup +from keyman_config.sentry_handling import SentryErrorHandling class KMFileTypes(Enum): @@ -455,6 +456,7 @@ def parsemetadata(jsonfile, verbose=False): """ info = system = keyboards = files = options = nonexistent = None extracted_dir = os.path.dirname(jsonfile) + sentry = SentryErrorHandling() logging.debug("parsemetadata: reading file:%s dir:%s", jsonfile, extracted_dir) @@ -496,6 +498,11 @@ def parsemetadata(jsonfile, verbose=False): print_options(options) print_keyboards(keyboards) print_files(files, extracted_dir) + + if system: + sentry.add_breadcrumb( + category='keyboard', message=f'kmp.json: pkg: "{secure_lookup(info, "name", "description")}", KeymanDeveloperVersion: {secure_lookup(system, "keymanDeveloperVersion")}, dir: {os.path.basename(extracted_dir)}') + return info, system, options, keyboards, files @@ -532,19 +539,22 @@ def get_and_convert_infdata(tmpdirname): see kmpmetadata.parseinfdata for details """ kmpinf = os.path.join(tmpdirname, "kmp.inf") - if os.path.isfile(kmpinf): - info, system, options, keyboards, files = parseinfdata(kmpinf, False) - j = infmetadata_to_json(info, system, options, keyboards, files) - kmpjson = os.path.join(tmpdirname, "kmp.json") - try: - with open(kmpjson, "w") as write_file: - print(j, file=write_file) - except Exception as e: - logging.warning('Exception %s writing metadata %s %s', type(e), kmpjson, e.args) - return info, system, options, keyboards, files - else: + if not os.path.isfile(kmpinf): return None, None, None, None, None + sentry = SentryErrorHandling() + info, system, options, keyboards, files = parseinfdata(kmpinf, False) + j = infmetadata_to_json(info, system, options, keyboards, files) + kmpjson = os.path.join(tmpdirname, "kmp.json") + try: + with open(kmpjson, "w") as write_file: + print(j, file=write_file) + except Exception as e: + logging.warning('Exception %s writing metadata %s %s', type(e), kmpjson, e.args) + sentry.add_breadcrumb( + category='keyboard', message=f'kmp.inf: pkg: "{secure_lookup(info, "name", "description")}", KeymanDeveloperVersion: {secure_lookup(system, "keymanDeveloperVersion")}, dir: {os.path.basename(tmpdirname)}') + return info, system, options, keyboards, files + def infmetadata_to_json(info, system, options, keyboards, files): jsonfiles = [] diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index ddef89241a..4adf99ddfa 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -366,14 +366,18 @@ def convert_ldml(keyboardName, kvkData, kmpJsonFilename): info, system, options, keyboards, files = parsemetadata(kmpJsonFilename) + if not keyboards: + return None + ldml = etree.Element("keyboard", locale="zzz-keyman") + for keyboard in keyboards: if keyboard['id'] != keyboardName: continue if 'oskFont' in keyboard: fontFile = os.path.join(os.path.dirname(kmpJsonFilename), keyboard['oskFont']) font = _fontFacename(fontFile) - if font: + if font is not None and len(font) > 0: ldml.set('keymanFacename', font) break diff --git a/linux/keyman-config/keyman_config/sentry_handling.py b/linux/keyman-config/keyman_config/sentry_handling.py index e7b3d40da3..104296936a 100644 --- a/linux/keyman-config/keyman_config/sentry_handling.py +++ b/linux/keyman-config/keyman_config/sentry_handling.py @@ -30,6 +30,9 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gio, Gtk +is_sentry_sdk = False + + class SentryErrorHandling: def __init__(self) -> None: self.error_reporting_setting = KeymanOption('error-reporting') @@ -37,7 +40,8 @@ class SentryErrorHandling: def initialize_sentry(self): (enabled, reason) = self.is_sentry_enabled() if not enabled: - print(reason, file=sys.stderr) + if not self._is_running_tests(): + print(reason, file=sys.stderr) logging.info(reason) return (enabled, reason) else: @@ -55,8 +59,8 @@ class SentryErrorHandling: return (True, '') def is_sentry_enabled(self): - if self._is_unit_test(): - return (False, 'Running unit tests, not reporting to Sentry') + if self._is_running_tests(): + return (False, 'Running tests, not reporting to Sentry') elif self._get_environ_nosentry(): return (False, 'Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set') elif not __uploadsentry__: @@ -78,16 +82,29 @@ class SentryErrorHandling: if enabled != was_enabled: self._handle_enabled(enabled) + def add_breadcrumb(self, category, message, level='info'): + global is_sentry_sdk + if not self.is_sentry_enabled() or not is_sentry_sdk: + return + + # sentry_sdk = importlib.import_module('sentry_sdk') + import sentry_sdk + sentry_sdk.add_breadcrumb(category=category, message=message, level=level) + def _get_environ_nosentry(self): keyman_nosentry = os.environ.get('KEYMAN_NOSENTRY') return keyman_nosentry and (int(keyman_nosentry) == 1) - def _is_unit_test(self): # sourcery skip: use-any, use-next + def _is_running_tests(self): # sourcery skip: use-any, use-next # The suggested refactorings (using any() or next()) don't work # when testing on Ubuntu 20.04 for line in traceback.format_stack(): if '/unittest/' in line: return True + + if platform.node() == 'autopkgtest': + return True + return False def _handle_enabled(self, enabled): @@ -114,47 +131,61 @@ class SentryErrorHandling: def _sentry_sdk_initialize(self): # Try new sentry-sdk first + global is_sentry_sdk sentry_sdk = importlib.import_module('sentry_sdk') from sentry_sdk import configure_scope, set_user from sentry_sdk.integrations.logging import LoggingIntegration + is_sentry_sdk = True + sentry_logging = LoggingIntegration( level=logging.INFO, # Capture info and above as breadcrumbs event_level=logging.CRITICAL # Send critical errors as events ) SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" + + os_release = None + dist = None + try: + os_release = platform.freedesktop_os_release() + dist = os_release['VERSION_CODENAME'] + except OSError as e: + logging.debug(f'System does not have os_release file: {e.strerror}') + except AttributeError: + logging.debug('System does not have platform.freedesktop_os_release() method') + except Exception: + logging.debug( + 'Got exception trying to access platform.freedesktop_os_release() method or os_release information') + sentry_sdk.init( dsn=SentryUrl, environment=__environment__, release=__versiongittag__, integrations=[sentry_logging], + dist=dist, + in_app_include=['keyman_config'], before_send=self._before_send ) - hash = hashlib.md5() - hash.update(getpass.getuser().encode()) - set_user({'id': hash.hexdigest()}) + md5Hash = hashlib.md5() + md5Hash.update(getpass.getuser().encode()) + set_user({'id': md5Hash.hexdigest()}) with configure_scope() as scope: - scope.set_tag("app", os.path.basename(sys.argv[0])) - scope.set_tag("pkgversion", __pkgversion__) - scope.set_tag("platform", platform.platform()) - scope.set_tag("system", platform.system()) - scope.set_tag("tier", __tier__) - scope.set_tag("device", platform.node()) - try: - os_release = platform.freedesktop_os_release() - scope.set_tag('os', os_release['PRETTY_NAME']) - scope.set_tag('os.name', os_release['NAME']) - if 'VERSION' in os_release: - scope.set_tag('os.version', os_release['VERSION']) - except OSError as e: - logging.debug(f'System does not have os_release file: {e.strerror}') - except AttributeError: - logging.debug('System does not have platform.freedesktop_os_release() method') - except: - logging.debug( - 'Got exception trying to access platform.freedesktop_os_release() method or os_release information') + self._set_system_info(scope, os_release) logging.info("Initialized Sentry error reporting") + def _set_system_info(self, scope, os_release): + scope.set_tag("app", os.path.basename(sys.argv[0])) + scope.set_tag("pkgversion", __pkgversion__) + scope.set_tag("platform", platform.platform()) + scope.set_tag("system", platform.system()) + scope.set_tag("tier", __tier__) + scope.set_tag("device", platform.node()) + if os_release: + scope.set_tag('os', os_release['PRETTY_NAME']) + scope.set_tag('os.name', os_release['NAME']) + if 'VERSION' in os_release: + scope.set_tag('os.version', os_release['VERSION']) + def _raven_initialize(self): # sentry-sdk is not available, so use older raven raven = importlib.import_module('raven') diff --git a/linux/keyman-config/keyman_config/uninstall_kmp.py b/linux/keyman-config/keyman_config/uninstall_kmp.py index 9fc302cc5f..12a1bbee61 100755 --- a/linux/keyman-config/keyman_config/uninstall_kmp.py +++ b/linux/keyman-config/keyman_config/uninstall_kmp.py @@ -7,7 +7,7 @@ from shutil import rmtree from keyman_config import _ from keyman_config.custom_keyboards import CustomKeyboards -from keyman_config.dbus_util import get_keyman_config_service +from keyman_config.dbus_util import keyboard_list_changed from keyman_config.fcitx_util import is_fcitx_running from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir, get_keyman_doc_dir, get_keyman_font_dir) @@ -152,5 +152,5 @@ def uninstall_kmp(packageID, sharedarea=False, removeLanguages=True): else: msg = _uninstall_kmp_common(InstallLocation.User, packageID, removeLanguages) - get_keyman_config_service().keyboard_list_changed() + keyboard_list_changed() return msg diff --git a/linux/keyman-config/km-config b/linux/keyman-config/km-config index 21072329a8..bfb2063c90 100755 --- a/linux/keyman-config/km-config +++ b/linux/keyman-config/km-config @@ -71,7 +71,7 @@ if __name__ == '__main__': download_and_install_package(args.url) else: # Workaround for bug in webkit2gtk (#12587) - if not 'WEBKIT_DISABLE_DMABUF_RENDERER' in os.environ: + if 'WEBKIT_DISABLE_DMABUF_RENDERER' not in os.environ: os.environ['WEBKIT_DISABLE_DMABUF_RENDERER'] = '1' w = ViewInstalledWindow() diff --git a/linux/keyman-config/km-package-install b/linux/keyman-config/km-package-install index 195ad1ad07..419be8c832 100755 --- a/linux/keyman-config/km-package-install +++ b/linux/keyman-config/km-package-install @@ -109,6 +109,78 @@ def list_languages_for_keyboard(packageId, packageDir): print(result) +def try_install_kmp(inputfile, arg, language=None, sharedarea=False): + try: + install_kmp(inputfile, sharedarea, language) + except InstallError as e: + if e.status == InstallStatus.Abort: + logging.error("km-package-install: error: Failed to install %s", arg) + logging.error(e.message) + sys.exit(3) + else: + logging.warning(e.message) + + +def install_package(args): + if args.shared: + if get_kmp_version_user(args.package): + logging.warning( + f'km-package-install: Warning: Keyboard {args.package} is also installed for the current user.' + ) + installed_kmp_ver = get_kmp_version_shared(args.package) + else: + if get_kmp_version_shared(args.package): + logging.warning( + f'km-package-install: Warning: Keyboard {args.package} is already installed in the shared area.' + ) + installed_kmp_ver = get_kmp_version_user(args.package) + pkgdata = get_keyboard_data(args.package) + if not pkgdata: + logging.error("km-package-install: error: Could not download keyboard data for %s", args.package) + sys.exit(3) + pkg_version = secure_lookup(pkgdata, 'version') + if installed_kmp_ver and pkg_version: + if packaging.version.parse(installed_kmp_ver) > packaging.version.parse(pkg_version): + if args.force: + logging.warning(f'km-package-install: warning: The {args.package} keyboard ' + + f'is already installed with a newer version {installed_kmp_ver}. ' + + f'Downgrading to version {pkg_version}.') + else: + logging.error(f'km-package-install: error: The {args.package} keyboard is ' + + f'already installed with a newer version {installed_kmp_ver}. ' + + f'Not installing version {pkg_version}. Use `--force` to downgrade.') + sys.exit(1) + if args.force: + uninstall_kmp(args.package, args.shared, False) + + if kmpfile := get_kmp(args.package): + try_install_kmp(kmpfile, f"keyboard package {args.package}", args.bcp47, args.shared) + else: + logging.error("km-package-install: error: Could not download keyboard package %s", args.package) + sys.exit(2) + + +def install_file(parser, args): + name, ext = os.path.splitext(args.file) + if ext != ".kmp" or not is_zipfile(args.file): + print_usage_and_exit( + parser, + f"km-package-install: Input file ${args.file} is not a kmp file." + ) + if not os.path.isfile(args.file): + print_usage_and_exit( + parser, + f"km-package-install: Keyman kmp file {args.file} not found." + ) + try_install_kmp(args.file, f"file {args.file}", args.bcp47, args.shared) + + +def print_usage_and_exit(parser, msg): + parser.print_usage() + logging.error(msg) + sys.exit(2) + + def main(): parser = argparse.ArgumentParser( description='Install a Keyman keyboard package, either a local .kmp file or specify a ' + @@ -134,80 +206,24 @@ def main(): sys.exit(4) if args.package and args.file: - parser.print_usage() - logging.error( - "km-package-install: error: too many arguments: either install a local kmp file or " + - "specify a keyboard id to download and install.") - sys.exit(2) - + print_usage_and_exit( + parser, + "km-package-install: error: too many arguments: either install a local kmp file or " + + "specify a keyboard id to download and install.", + ) if os.path.exists(os.path.join(keyman_cache_dir(), 'kmpdirlist')): os.remove(os.path.join(keyman_cache_dir(), 'kmpdirlist')) - def try_install_kmp(inputfile, arg, language=None, sharedarea=False): - try: - install_kmp(inputfile, sharedarea, language) - except InstallError as e: - if e.status == InstallStatus.Abort: - logging.error("km-package-install: error: Failed to install %s", arg) - logging.error(e.message) - sys.exit(3) - else: - logging.warning(e.message) - if args.file: - name, ext = os.path.splitext(args.file) - if ext != ".kmp" or not is_zipfile(args.file): - logging.error("km-package-install: Input file %s is not a kmp file.", args.file) - logging.error("km-package-install -f ") - sys.exit(2) - - if not os.path.isfile(args.file): - logging.error("km-package-install: Keyman kmp file %s not found.", args.file) - logging.error("km-package-install -f ") - sys.exit(2) - try_install_kmp(args.file, f"file {args.file}", args.bcp47, args.shared) + install_file(parser, args) elif args.package: - if args.shared: - if get_kmp_version_user(args.package): - logging.warning(f'km-package-install: Warning: Keyboard {args.package} is ' + - 'also installed for the current user.') - installed_kmp_ver = get_kmp_version_shared(args.package) - else: - if get_kmp_version_shared(args.package): - logging.warning(f'km-package-install: Warning: Keyboard {args.package} is ' + - 'already installed in the shared area.') - installed_kmp_ver = get_kmp_version_user(args.package) - pkgdata = get_keyboard_data(args.package) - if not pkgdata: - logging.error("km-package-install: error: Could not download keyboard data for %s", args.package) - sys.exit(3) - pkg_version = secure_lookup(pkgdata, 'version') - if installed_kmp_ver and pkg_version: - if packaging.version.parse(installed_kmp_ver) > packaging.version.parse(pkg_version): - if args.force: - logging.warning(f'km-package-install: warning: The {args.package} keyboard ' + - f'is already installed with a newer version {installed_kmp_ver}. ' + - f'Downgrading to version {pkg_version}.') - else: - logging.error(f'km-package-install: error: The {args.package} keyboard is ' + - f'already installed with a newer version {installed_kmp_ver}. ' + - f'Not installing version {pkg_version}. Use `--force` to downgrade.') - sys.exit(1) - if args.force: - uninstall_kmp(args.package, args.shared, False) - - kmpfile = get_kmp(args.package) - if kmpfile: - try_install_kmp(kmpfile, f"keyboard package {args.package}", args.bcp47, args.shared) - else: - logging.error("km-package-install: error: Could not download keyboard package %s", args.package) - sys.exit(2) + install_package(args) else: - parser.print_usage() - logging.error( - "km-package-install: error: no arguments: either install a local kmp file " + - "or specify a keyboard package id to download and install.") - sys.exit(2) + print_usage_and_exit( + parser, + "km-package-install: error: no arguments: either install a local kmp file " + + "or specify a keyboard package id to download and install.", + ) if __name__ == "__main__": diff --git a/linux/keyman-config/tests/gnome_keyboards_util_tests.py b/linux/keyman-config/tests/gnome_keyboards_util_tests.py index f86443567c..fac51253a0 100644 --- a/linux/keyman-config/tests/gnome_keyboards_util_tests.py +++ b/linux/keyman-config/tests/gnome_keyboards_util_tests.py @@ -4,7 +4,7 @@ import unittest from unittest import mock from unittest.mock import patch -from keyman_config.gnome_keyboards_util import is_gnome_desktop, _reset_gnome_shell +from keyman_config.gnome_keyboards_util import get_ibus_keyboard_id, is_gnome_desktop, _reset_gnome_shell class GnomeKeyboardsUtilTests(unittest.TestCase): @@ -35,6 +35,37 @@ class GnomeKeyboardsUtilTests(unittest.TestCase): # Execute/Verify self.assertEqual(is_gnome_desktop(), True) + def test_GetIbusKeyboardId_NoKeyboard(self): + self.assertIsNone(get_ibus_keyboard_id(None, '/tmp')) + + def test_GetIbusKeyboardId_IgnoreLanguage(self): + keyboard = {'id': 'foo'} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp', ignore_language=True), '/tmp/foo.kmx') + + def test_GetIbusKeyboardId_NoLanguage(self): + keyboard = {'id': 'foo'} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp'), '/tmp/foo.kmx') + + def test_GetIbusKeyboardId_EmptyLanguage(self): + keyboard = {'id': 'foo'} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp', ''), '/tmp/foo.kmx') + + def test_GetIbusKeyboardId_Language(self): + keyboard = {'id': 'foo'} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp', 'en'), 'en:/tmp/foo.kmx') + + def test_GetIbusKeyboardId_LanguagesInKeyboard(self): + keyboard = {'id': 'foo', 'languages': [ {'id': 'fr'}]} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp'), 'fr:/tmp/foo.kmx') + + def test_GetIbusKeyboardId_MultipleLanguagesInKeyboard(self): + keyboard = {'id': 'foo', 'languages': [{'id': 'km'}, {'id': 'fr'}]} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp'), 'km:/tmp/foo.kmx') + + def test_GetIbusKeyboardId_InvalidLanguagesInKeyboard(self): # 14748 + keyboard = {'id': 'foo', 'languages': ['es']} + self.assertEqual(get_ibus_keyboard_id(keyboard, '/tmp'), '/tmp/foo.kmx') + if __name__ == '__main__': unittest.main() diff --git a/linux/keyman-config/tests/kvk2ldml_tests.py b/linux/keyman-config/tests/kvk2ldml_tests.py index fc8a34c28b..4bb9c337c0 100644 --- a/linux/keyman-config/tests/kvk2ldml_tests.py +++ b/linux/keyman-config/tests/kvk2ldml_tests.py @@ -8,31 +8,34 @@ import unittest from keyman_config.kvk2ldml import KVKData, NFont, convert_ldml class Kvk2LdmlTests(unittest.TestCase): - def _createKmpJson(self, packagedir): + def _createKmpJson(self, packagedir, add_keyboards=True): kmpJsonFilename = os.path.join(packagedir, 'kmp.json') + system = '''"system": { + "keymanDeveloperVersion": "18.0", + "fileVersion": "7.0" + }''' + files = '''"files": [ { + "name": "khmer_angkor.kmx", + "description": "Keyboard Khmer Angkor" + }, { + "name": "kmp.json", + "description": "Package information (JSON)" + } ]''' + if add_keyboards: + keyboards = '''"keyboards": [ { + "name": "Khmer Angkor", + "id": "khmer_angkor", + "version": "1.5", + "oskFont": "keymanweb-osk.ttf", + "languages": [ { + "name": "Central Khmer (Khmer, Cambodia)", + "id": "km" + } ]} + ]''' + else: + keyboards = '' with open(kmpJsonFilename, 'w') as file: - file.write('''{ - "system": { - "keymanDeveloperVersion": "18.0", - "fileVersion": "7.0" - }, - "files": [ { - "name": "khmer_angkor.kmx", - "description": "Keyboard Khmer Angkor" - }, { - "name": "kmp.json", - "description": "Package information (JSON)" - } ], - "keyboards": [ { - "name": "Khmer Angkor", - "id": "khmer_angkor", - "version": "1.5", - "oskFont": "keymanweb-osk.ttf", - "languages": [ { - "name": "Central Khmer (Khmer, Cambodia)", - "id": "km" - } ]} - ]}''') + file.write(f'{{ {system}, {files}, {keyboards} }}') return kmpJsonFilename def test_convert_ldml__adds_keymanFacename(self): @@ -56,3 +59,20 @@ class Kvk2LdmlTests(unittest.TestCase): self.assertEqual(ldml.get('keymanFacename'), 'SymChar') workdir.cleanup() + + def test_convert_ldml__no_crash_without_keyboards(self): + # Setup + keyboardName = 'khmer_angkor' + kvkData = KVKData() + kvkData.AssociatedKeyboard = keyboardName + + workdir = tempfile.TemporaryDirectory() + kmpJsonFilename = self._createKmpJson(workdir.name, add_keyboards=False) + + # Execute + ldml = convert_ldml(keyboardName, kvkData, kmpJsonFilename) + + # Verify + self.assertIsNone(ldml) + + workdir.cleanup() diff --git a/linux/keyman-config/tests/uninstall_kmp_tests.py b/linux/keyman-config/tests/uninstall_kmp_tests.py index 0d1b0d53dc..5a0404ef77 100644 --- a/linux/keyman-config/tests/uninstall_kmp_tests.py +++ b/linux/keyman-config/tests/uninstall_kmp_tests.py @@ -325,9 +325,7 @@ class UninstallKmpTests(unittest.TestCase): self.mockUninstallDir = self._setupMock('keyman_config.uninstall_kmp._uninstall_dir') self.mockGetMetaData = self._setupMock('keyman_config.uninstall_kmp.get_metadata') self.mockCustomKeyboardsClass = self._setupMock('keyman_config.uninstall_kmp.CustomKeyboards') - self.mockGetKeymanConfigService = self._setupMock('keyman_config.uninstall_kmp.get_keyman_config_service') - self.mockKeymanConfigServiceManager = Mock() - self.mockGetKeymanConfigService.return_value = self.mockKeymanConfigServiceManager + self.mockKeyboardListChanged = self._setupMock('keyman_config.uninstall_kmp.keyboard_list_changed') def _setupMock(self, arg0): patcher = patch(arg0) @@ -341,9 +339,6 @@ class UninstallKmpTests(unittest.TestCase): keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}, {'id': 'bar2', 'languages': [{'id': 'fr'}]}] self.mockGetMetaData.return_value = (None, None, None, keyboards, None) - keyboardListChangedMock = Mock() - self.mockKeymanConfigServiceManager.keyboard_list_changed = keyboardListChangedMock - # Execute uninstall_kmp('foo', False, True) @@ -351,5 +346,4 @@ class UninstallKmpTests(unittest.TestCase): self.mockUninstallKeyboardsFromIbus.assert_called_once_with(keyboards, '/tmp/foo') self.mockUninstallDir.assert_called() self.assertEqual(mockCustomKeyboardsInstance.remove.call_count, 2) - self.assertEqual(keyboardListChangedMock.call_count, 1) - + self.mockKeyboardListChanged.assert_called_once() diff --git a/linux/scripts/deb-packaging.sh b/linux/scripts/deb-packaging.sh index 97f069377b..f6f44be2d1 100755 --- a/linux/scripts/deb-packaging.sh +++ b/linux/scripts/deb-packaging.sh @@ -6,6 +6,9 @@ set -eu shopt -s inherit_errexit +# Avoid timing reports as we don't have node in this script +export _builder_timings=false + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" diff --git a/linux/scripts/dist.sh b/linux/scripts/dist.sh index cd04960335..ea68b33a40 100755 --- a/linux/scripts/dist.sh +++ b/linux/scripts/dist.sh @@ -15,8 +15,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../resources/build/builder-basic.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/linux/scripts/package-build.inc.sh" + BASEDIR=$(pwd) +cd "${KEYMAN_ROOT}/linux" + if [[ ! -z ${1+x} ]] && [[ "$1" == "origdist" ]]; then create_origdist=1 shift @@ -27,13 +32,55 @@ mkdir -p dist # dist for keyman cp -a debian ../ -cd .. +cd "${KEYMAN_ROOT}" echo "3.0 (native)" > debian/source/format +# shellcheck disable=SC2154 dch keyman --newversion "${KEYMAN_VERSION}" --force-bad-version --nomultimaint # Create the tarball -# Note: the files end up in subdirectories under `keyman`, so we can -# include that when matching files and directories to ignore. + +# files and folders to include in the tarball +# shellcheck disable=2034 # to_exclude appears to be unused +to_include=( + common/build.sh \ + common/cpp \ + common/include \ + common/linux \ + common/test/keyboards/baseline \ + core \ + linux \ + resources/build/*.sh \ + resources/build/meson \ + resources/standards-data \ + resources/*.sh \ + ./*.md \ + ./build.sh \ + ./*.json \ +) + +# files and subfolders to exclude from paths included in 'to_include', +# i.e. the exceptions to 'to_include'. +# shellcheck disable=2034 # to_exclude appears to be unused +to_exclude=( + common/test/keyboards/baseline/kmcomp-*.zip \ + core/build \ + linux/build \ + linux/builddebs \ + linux/docs/help \ + linux/keyman-config/keyman_config/version.py \ + linux/keyman-config/buildtools/build-langtags.py \ + linux/keyman-system-service/build +) + +# array to store list of --tar-ignore parameters generated from to_include and to_exclude. +ignored_files=() + +generate_tar_ignore_list "./" to_include to_exclude ignored_files "$(basename "${KEYMAN_ROOT}")" + +# Note: explicitly specify the --tar-ignores here for files/folders that we always +# want to ignore regardless of their location. Having them here allows us to pass +# the wildcards to dpkg-source - whereas the wildcards in 'to_exclude' will be +# resolved and replaced with multiple --tar-ignore entries. dpkg-source \ --tar-ignore=*~ \ --tar-ignore=.git \ @@ -45,68 +92,17 @@ dpkg-source \ --tar-ignore=.pc \ --tar-ignore=.vscode \ --tar-ignore=.devcontainer \ + --tar-ignore=.pc \ --tar-ignore=__pycache__ \ --tar-ignore=node_modules \ --tar-ignore=keyman_1* \ --tar-ignore=dist \ --tar-ignore=VERSION \ \ - --tar-ignore=keyman/android \ - --tar-ignore=keyman/artifacts \ - \ - --tar-ignore=keyman/common/mac \ - --tar-ignore=keyman/common/models \ - --tar-ignore=keyman/common/resources \ - --tar-ignore=keyman/common/schemas \ - --tar-ignore=keyman/common/test/keyboards/baseline/kmcomp-*.zip \ - --tar-ignore=keyman/common/test/keyboards/build.* \ - --tar-ignore=keyman/common/test/keyboards/caps* \ - --tar-ignore=keyman/common/test/keyboards/full* \ - --tar-ignore=keyman/common/test/keyboards/invalid \ - --tar-ignore=keyman/common/test/keyboards/issue \ - --tar-ignore=keyman/common/test/keyboards/obolo* \ - --tar-ignore=keyman/common/test/keyboards/start* \ - --tar-ignore=keyman/common/test/keyboards/text* \ - --tar-ignore=keyman/common/test/keyboards/u* \ - --tar-ignore=keyman/common/test/keyboards/web* \ - --tar-ignore=keyman/common/test/resources \ - --tar-ignore=keyman/common/tools \ - --tar-ignore=keyman/common/web \ - --tar-ignore=keyman/common/windows \ - \ - --tar-ignore=keyman/core/build \ - --tar-ignore=keyman/developer \ - --tar-ignore=keyman/docs \ - --tar-ignore=keyman/ios \ - --tar-ignore=keyman/linux/build \ - --tar-ignore=keyman/linux/builddebs \ - --tar-ignore=keyman/linux/docs/help \ - --tar-ignore=keyman/linux/ibus-keyman/build \ - --tar-ignore=keyman/linux/keyman-config/keyman_config/version.py \ - --tar-ignore=keyman/linux/keyman-config/buildtools/build-langtags.py \ - --tar-ignore=keyman/linux/keyman-system-service/build \ - --tar-ignore=keyman/mac \ - --tar-ignore=keyman/oem \ - --tar-ignore=keyman/resources/devbox \ - --tar-ignore=keyman/resources/docker-images \ - --tar-ignore=keyman/resources/environment.sh \ - --tar-ignore=keyman/resources/build/ci \ - --tar-ignore=keyman/resources/build/history \ - --tar-ignore=keyman/resources/build/mac \ - --tar-ignore=keyman/resources/build/pr-build-status \ - --tar-ignore=keyman/resources/build/version \ - --tar-ignore=keyman/resources/build/win \ - --tar-ignore=keyman/resources/git-hooks \ - --tar-ignore=keyman/resources/scopes \ - --tar-ignore=keyman/resources/teamcity \ - --tar-ignore=keyman/resources/build/*.lua \ - --tar-ignore=keyman/resources/build/jq-* \ - --tar-ignore=keyman/results \ - --tar-ignore=keyman/tmp \ - --tar-ignore=keyman/web \ - --tar-ignore=keyman/windows \ + "${ignored_files[@]}" \ \ -Zgzip -b . + mv ../keyman_"${KEYMAN_VERSION}".tar.gz linux/dist/keyman-"${KEYMAN_VERSION}".tar.gz echo "3.0 (quilt)" > debian/source/format cd "${BASEDIR}" @@ -114,7 +110,7 @@ cd "${BASEDIR}" # create orig.tar.gz if [[ ! -z "${create_origdist+x}" ]]; then cd dist - pkgvers="keyman-$KEYMAN_VERSION" + pkgvers="keyman-${KEYMAN_VERSION}" tar xfz keyman-"${KEYMAN_VERSION}".tar.gz mv -v keyman "${pkgvers}" 2>/dev/null || mv -v "$(find . -mindepth 1 -maxdepth 1 -type d)" "${pkgvers}" tar cfz "keyman_${KEYMAN_VERSION}.orig.tar.gz" "${pkgvers}" diff --git a/linux/scripts/launchpad.sh b/linux/scripts/launchpad.sh index 47e0144595..881fa5b42b 100755 --- a/linux/scripts/launchpad.sh +++ b/linux/scripts/launchpad.sh @@ -2,8 +2,6 @@ # Build source packages from nightly builds and upload to PPA -# must be run from linux dir - # parameters: [UPLOAD="yes"] [PROJECT=""] [DIST=""] [PACKAGEVERSION=""] ./scripts/launchpad.sh # UPLOAD="yes" do the dput for real # PROJECT="" only upload this project @@ -20,20 +18,22 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE # shellcheck source=linux/scripts/package-build.inc.sh -. "$(dirname "$THIS_SCRIPT")/package-build.inc.sh" +. "$(dirname "${THIS_SCRIPT}")/package-build.inc.sh" + +cd "${KEYMAN_ROOT}/linux" checkPrerequisites -if [ "${KEYMAN_TIER}" == "stable" ]; then - ppa="ppa:keymanapp/keyman" -elif [ "${KEYMAN_TIER}" == "beta" ]; then - ppa="ppa:keymanapp/keyman-beta" +if [[ "${KEYMAN_TIER}" == "stable" ]]; then + ppa="ppa:keymanapp/keyman" +elif [[ "${KEYMAN_TIER}" == "beta" ]]; then + ppa="ppa:keymanapp/keyman-beta" else - ppa="ppa:keymanapp/keyman-alpha" + ppa="ppa:keymanapp/keyman-alpha" fi echo "ppa: ${ppa}" -distributions="${DIST:-jammy noble plucky questing}" +distributions="${DIST:-jammy noble plucky questing resolute}" packageversion="${PACKAGEVERSION:-1~sil1}" BASEDIR=$(pwd) @@ -42,20 +42,21 @@ rm -rf launchpad mkdir -p launchpad for proj in ${projects:-}; do - downloadSource launchpad + downloadSource launchpad - cd "${proj}-${version:-}" - pwd - cp debian/changelog "../${proj}-changelog" - for dist in ${distributions}; do - cp "../${proj}-changelog" debian/changelog - dch -v "${version}-${packageversion}~${dist}" "source package for PPA" - dch -D "${dist}" -r "" - debuild -d -S -sa -Zxz - done - cd .. - for dist in ${distributions}; do - dput ${SIM} ${ppa} "${proj}_${version}-${packageversion}~${dist}_source.changes" - done - cd "${BASEDIR}" + cd "${proj}-${version:-}" + pwd + cp debian/changelog "../${proj}-changelog" + for dist in ${distributions}; do + cp "../${proj}-changelog" debian/changelog + dch -v "${version}-${packageversion}~${dist}" "source package for PPA" + dch -D "${dist}" -r "" + debuild -d -S -sa -Zxz + done + cd .. + for dist in ${distributions}; do + # shellcheck disable=SC2086,SC2248 # no quotes for $SIM - it might not be set + dput ${SIM:-} "${ppa}" "${proj}_${version}-${packageversion}~${dist}_source.changes" + done + cd "${BASEDIR}" done diff --git a/linux/scripts/package-build.inc.sh b/linux/scripts/package-build.inc.sh index c53b73c653..f2597911de 100644 --- a/linux/scripts/package-build.inc.sh +++ b/linux/scripts/package-build.inc.sh @@ -1,60 +1,62 @@ -#!/bin/bash +# shellcheck shell=bash +# Keyman is copyright (C) SIL Global. MIT License. function checkPrerequisites() { - if [ "${UPLOAD:=}" == "yes" ]; then - SIM="" - else - SIM="-s" - fi + if [[ "${UPLOAD:=}" == "yes" ]]; then + SIM="" + else + SIM="-s" + fi - # Check the tier - if [[ -z "${KEYMAN_TIER:=}" ]]; then - echo "TIER.md or \${KEYMAN_TIER} must be set to (alpha, beta, stable) to use this script" - exit 1 - fi + # Check the tier + if [[ -z "${KEYMAN_TIER:=}" ]]; then + echo "TIER.md or \${KEYMAN_TIER} must be set to (alpha, beta, stable) to use this script" + exit 1 + fi - if ! which xmllint > /dev/null; then - echo "you must install xmllint (libxml2-utils package) to use this script" - exit 1 - fi + if ! command -v xmllint > /dev/null; then + echo "you must install xmllint (libxml2-utils package) to use this script" + exit 1 + fi - # shellcheck disable=SC2034 - projects="${PROJECT:=keyman}" + # shellcheck disable=SC2034 + projects="${PROJECT:=keyman}" } function downloadSource() { - local packageDir - packageDir=$1 + local packageDir + packageDir=$1 - if [ "${proj:=}" == "keyman" ]; then - cd "${BASEDIR}" || exit - ./build.sh clean - fi + if [[ "${proj:=}" == "keyman" ]]; then + # shellcheck disable=SC2154 + cd "${BASEDIR}" || exit + ./build.sh clean + fi - # Update tier in Debian watch files (replacing any previously set tier) and remove comment - sed -e "s/\$tier\|alpha\|beta\|stable/${KEYMAN_TIER}/g" -e "s/^# .*$//" "$BASEDIR"/scripts/watch.in > debian/watch + # Update tier in Debian watch files (replacing any previously set tier) and remove comment + sed -e "s/\$tier\|alpha\|beta\|stable/${KEYMAN_TIER}/g" -e "s/^# .*$//" "${BASEDIR}"/scripts/watch.in > debian/watch - version=$(uscan --report --dehs|xmllint --xpath "//dehs/upstream-version/text()" -) - dirversion=$(uscan --report --dehs|xmllint --xpath "//dehs/upstream-url/text()" - | cut -d/ -f6) - echo "${proj} version is ${version}" - uscan || (echo "ERROR: No new version available for ${proj}" >&2 && exit 1) - cd .. - mv "${proj}-${version}" "${BASEDIR}/${packageDir}" - mv "${proj}_${version}.orig.tar.gz" "${BASEDIR}/${packageDir}" - mv "${proj}-${version}.tar.gz" "${BASEDIR}/${packageDir}" - mv "${proj}"*.asc "${BASEDIR}/${packageDir}" - rm "${proj}"*.debian.tar.xz - cd "${BASEDIR}/${packageDir}" || exit - wget -N "https://downloads.keyman.com/linux/${KEYMAN_TIER}/${dirversion}/SHA256SUMS" - sha256sum -c --ignore-missing SHA256SUMS |grep "${proj}" + version=$(uscan --report --dehs|xmllint --xpath "//dehs/upstream-version/text()" -) + dirversion=$(uscan --report --dehs|xmllint --xpath "//dehs/upstream-url/text()" - | cut -d/ -f6) + echo "${proj} version is ${version}" + uscan || (echo "ERROR: No new version available for ${proj}" >&2 && exit 1) + cd .. + mv "${proj}-${version}" "${BASEDIR}/${packageDir}" + mv "${proj}_${version}.orig.tar.gz" "${BASEDIR}/${packageDir}" + mv "${proj}-${version}.tar.gz" "${BASEDIR}/${packageDir}" + mv "${proj}"*.asc "${BASEDIR}/${packageDir}" + rm "${proj}"*.debian.tar.xz + cd "${BASEDIR}/${packageDir}" || exit + wget -N "https://downloads.keyman.com/linux/${KEYMAN_TIER}/${dirversion}/SHA256SUMS" + sha256sum -c --ignore-missing SHA256SUMS | grep "${proj}" } function wait_for_apt_deb { - # from https://gist.github.com/hrpatel/117419dcc3a75e46f79a9f1dce99ef52 - while sudo fuser /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock &>/dev/null 2>&1; do - echo "Waiting for apt/dpkg lock to release, sleeping 10s" - sleep 10 - done + # from https://gist.github.com/hrpatel/117419dcc3a75e46f79a9f1dce99ef52 + while sudo fuser /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock &>/dev/null 2>&1; do + echo "Waiting for apt/dpkg lock to release, sleeping 10s" + sleep 10 + done } function checkAndInstallRequirements() @@ -80,3 +82,87 @@ function checkAndInstallRequirements() apt-get -qy --allow-downgrades install ./keyman-build-deps_*.deb rm -f keyman-build-deps_* } + +# Generate an array of files and directories to ignore, prefixed with --tar-ignore=keyman/ +function generate_tar_ignore_list() { + local directory="$1" + local includes_var="$2" + local excludes_var="$3" + local list_var="$4" + local prefix="$5" + local includes_array="${includes_var}[@]" + local includes=("${!includes_array}") + local excludes_array="${excludes_var}[@]" + local excludes=("${!excludes_array}") + local dir all_dirs found_match inc + + mapfile -t all_dirs < <(find "${directory}" -mindepth 1 -maxdepth 1 -type d | sort) + for dir in "${all_dirs[@]}"; do + found_match=false + for inc in "${includes[@]}"; do + if [[ "./${inc}" =~ ^${dir} ]]; then + found_match=true + if [[ "./${inc}" != "${dir}" ]]; then + # check subdirectories + generate_tar_ignore_list "${dir}" "${includes_var}" "${excludes_var}" "${list_var}" "${prefix}" + fi + # check if files/subdir in $dir are in excludes list + _generate_excludes_for_dir "${dir}" "${includes_var}" "${excludes_var}" "${list_var}" + break + fi + done + if ! ${found_match}; then + _add_to_list "${list_var}" "${dir}" + fi + done +} + +function _generate_excludes_for_dir() { + local directory="$1" + local includes_var="$2" + local excludes_var="$3" + local list_var="$4" + local includes_array="${includes_var}[@]" + local includes=("${!includes_array}") + local excludes_array="${excludes_var}[@]" + local excludes=("${!excludes_array}") + local file all_files excluded included is_match + + mapfile -t all_files < <(find "${directory}" -mindepth 1 -maxdepth 1 | sort) + is_match=false + for file in "${all_files[@]}"; do + for included in "${includes[@]}"; do + if [[ "${file}" == ./${included} ]]; then + is_match=true + break + fi + done + if ${is_match} ; then + if [[ "${file}" != ./${included} ]] && [[ -f "${file}" ]]; then + _add_to_list "${list_var}" "${file}" + fi + else + for excluded in "${excludes[@]}"; do + if [[ "${file}" == ./${excluded} ]]; then + _add_to_list "${list_var}" "${file}" + break + elif [[ "./${excluded}" =~ ^${file} ]]; then + # check subdirectories + _generate_excludes_for_dir "${file}" "${includes_var}" "${excludes_var}" "${list_var}" + break + fi + done + fi + done +} + +function _add_to_list() { + local list_var="$1" + local filename="$2" + + # Note: the files end up in subdirectories under `keyman` (or rather + # the directory name of $KEYMAN_ROOT), so we can + # include that when matching files and directories to ignore. + # shellcheck disable=SC2154 + eval "${list_var}+=(\"--tar-ignore=${prefix}/${filename#./}\")" +} diff --git a/linux/scripts/test/package-build.inc.tests.sh b/linux/scripts/test/package-build.inc.tests.sh new file mode 100755 index 0000000000..145392658d --- /dev/null +++ b/linux/scripts/test/package-build.inc.tests.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash +# shellcheck disable=2034,1091,2154 +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder-basic.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "${KEYMAN_ROOT}/resources/build/test/testing-framework.inc.sh" +. "${KEYMAN_ROOT}/linux/scripts/package-build.inc.sh" + +function setup_file() { + temp_dir="$(mktemp -d)" + # Create directory structure for testing + # / + # /subdir1 + # /build.sh + # /README.md + # /path1 + # /build.sh + # /README.md + # /path2 + # /build.sh + # /path3 + # /build.sh + # /subdir2 + # /build.sh + # /path1 + # /subdir3 + # /build.sh + # /path1 + # /path2 + # /path3 + # /subdir4 + # /build.sh + # /path1 + # /build.sh + # /path2 + # /subpath1 + # /build.sh + # /subpath2 + # /build.sh + # /path3 + # /build.sh + # subdir1/path2, subdir2, subdir4/path1, subdir4/path2/subpath1, + # and subdir4/path3 will be ignored + mkdir -p "${temp_dir}/subdir1/path1" # include + touch "${temp_dir}/subdir1/build.sh" + touch "${temp_dir}/subdir1/README.md" # exclude + touch "${temp_dir}/subdir1/path1/build.sh" # include + touch "${temp_dir}/subdir1/path1/README.md" # exclude + mkdir -p "${temp_dir}/subdir1/path2" # exclude + touch "${temp_dir}/subdir1/path2/build.sh" + mkdir -p "${temp_dir}/subdir1/path3" # include + touch "${temp_dir}/subdir1/path3/build.sh" + mkdir -p "${temp_dir}/subdir2/path1" # exclude + touch "${temp_dir}/subdir2/build.sh" + mkdir -p "${temp_dir}/subdir3/path1" # include + touch "${temp_dir}/subdir3/build.sh" + mkdir -p "${temp_dir}/subdir3/path2" # include + mkdir -p "${temp_dir}/subdir3/path3" # include + mkdir -p "${temp_dir}/subdir4/path1" # exclude + touch "${temp_dir}/subdir4/build.sh" + touch "${temp_dir}/subdir4/path1/build.sh" + mkdir -p "${temp_dir}/subdir4/path2/subpath1" # exclude + touch "${temp_dir}/subdir4/path2/subpath1/build.sh" + mkdir -p "${temp_dir}/subdir4/path2/subpath2" # include + touch "${temp_dir}/subdir4/path2/subpath2/build.sh" + mkdir -p "${temp_dir}/subdir4/path3" # exclude + touch "${temp_dir}/subdir4/path3/build.sh" + touch "${temp_dir}/subdir4/path3/other.txt" +} + +function teardown_file() { + rm -rf "${temp_dir}" +} + +function test__generate_tar_ignore_list__basic() { + to_include=(subdir1/path1 subdir1/path3 subdir3 subdir4/path2) + to_exclude=(subdir1/README.md subdir4/path2/subpath1 subdir4/path2/subpath3) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files test1 + + # Verify + expected=( + --tar-ignore=test1/subdir1/path2 + --tar-ignore=test1/subdir1/README.md + --tar-ignore=test1/subdir2 + --tar-ignore=test1/subdir4/path1 + --tar-ignore=test1/subdir4/path2/subpath1 + --tar-ignore=test1/subdir4/path3 + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + +function test__generate_tar_ignore_list__path_wildcard_in_exclude() { + # Setup + to_include=(subdir1/path1 subdir1/path3 subdir3) + to_exclude=(subdir1/*/README.md subdir4/path2/subpath1 subdir4/path2/subpath3) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files foo + + # Verify + expected=( + --tar-ignore=foo/subdir1/path1/README.md # in exclude + --tar-ignore=foo/subdir1/path2 # not in include + --tar-ignore=foo/subdir1/README.md # not in include + --tar-ignore=foo/subdir2 # not in include + --tar-ignore=foo/subdir4 # not in include + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + +function test__generate_tar_ignore_list__file_wildcard_in_exclude() { + # Setup + to_include=(subdir1/path1 subdir1/path3 subdir3) + to_exclude=(*.sh) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files keyman + + # Verify + expected=( + --tar-ignore=keyman/subdir1/path1/build.sh # in exclude + --tar-ignore=keyman/subdir1/path2 # not in include + --tar-ignore=keyman/subdir1/path3/build.sh # in exclude + --tar-ignore=keyman/subdir1/build.sh # not in include + --tar-ignore=keyman/subdir1/README.md # not in include + --tar-ignore=keyman/subdir2 # not in include + --tar-ignore=keyman/subdir3/build.sh # in exclude + --tar-ignore=keyman/subdir4 # not in include + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + +function test__generate_tar_ignore_list__file_wildcard_in_include() { + # Setup + to_include=(subdir1/path1 subdir1/path3 subdir3 subdir4/path3/*.sh) + to_exclude=(subdir1/README.md subdir4/path2/subpath1 subdir4/path2/subpath3) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files xyz + + # Verify + expected=( + --tar-ignore=xyz/subdir1/path2 # not in include + --tar-ignore=xyz/subdir1/README.md # in exclude + --tar-ignore=xyz/subdir2 # not in include + --tar-ignore=xyz/subdir4/path1 # not in include + --tar-ignore=xyz/subdir4/path2 # not in include + --tar-ignore=xyz/subdir4/path3/other.txt # not in include + --tar-ignore=xyz/subdir4/path2/subpath1 # not necessary, but in exclude + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + +function test__generate_tar_ignore_list__path_wildcard_in_include() { + # Setup + to_include=(subdir1/path1 subdir1/path3 subdir3 subdir4/*/subpath2) + to_exclude=(subdir1/README.md) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files bar + + # Verify + expected=( + --tar-ignore=bar/subdir1/path2 # not in include + --tar-ignore=bar/subdir1/README.md # not in include + --tar-ignore=bar/subdir2 # not in include + --tar-ignore=bar/subdir4/path1 # not in include + --tar-ignore=bar/subdir4/path2/subpath1 # not in include + --tar-ignore=bar/subdir4/path3 # not in include + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + +function test__generate_tar_ignore_list__exclude_subsubdir() { + to_include=(subdir4) + to_exclude=(subdir4/path2/subpath2) + + cd "${temp_dir}" + ignored_files=() + + # Execute + generate_tar_ignore_list "./" to_include to_exclude ignored_files baz + + # Verify + expected=( + --tar-ignore=baz/subdir1 + --tar-ignore=baz/subdir2 + --tar-ignore=baz/subdir3 + --tar-ignore=baz/subdir4/path2/subpath2 + ) + + assert-equal "${ignored_files[*]}" "${expected[*]}" +} + + +# shellcheck disable=2119 +run_tests diff --git a/linux/scripts/test/test.sh b/linux/scripts/test/test.sh index f0a56655d5..960e3641d9 100755 --- a/linux/scripts/test/test.sh +++ b/linux/scripts/test/test.sh @@ -3,3 +3,4 @@ set -eu "$(dirname "$0")/deb-packaging.tests.sh" "$(dirname "$0")/verify_api.tests.sh" +"$(dirname "$0")/package-build.inc.tests.sh" diff --git a/linux/scripts/upload-to-debian.sh b/linux/scripts/upload-to-debian.sh index 50a1831830..fb91d66053 100755 --- a/linux/scripts/upload-to-debian.sh +++ b/linux/scripts/upload-to-debian.sh @@ -43,7 +43,6 @@ PUSH= DEBKEYID= REVISION=1 IS_BETA=false -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) while (( $# )); do case $1 in @@ -109,31 +108,73 @@ function push_to_github_and_create_pr() { if [[ -n ${PR_NUMBER} ]]; then builder_echo "PR #${PR_NUMBER} already exists" else - ${NOOP} gh pr create --draft --base "${BASE}" --title "${PR_TITLE}" --body "${PR_BODY}" - sleep 2s - PR_NUMBER=$(gh pr list --draft --search "${PR_TITLE}" --base "${BASE}" --json number --jq '.[].number') + local PR_URL + PR_URL=$(gh pr create --draft --base "${BASE}" --title "${PR_TITLE}" --body "${PR_BODY}") + PR_NUMBER="${PR_URL##*/}" fi else PR_NUMBER="" fi } +function cleanup_worktree() { + if [[ -d "${WORKTREE_DIR}/linux/debianpackage" ]]; then + cp -r "${WORKTREE_DIR}/linux/debianpackage" "${KEYMAN_ROOT}/linux" + fi + + cd "${PREV_DIR}" + + if [[ -n "${WORKTREE_DIR}" ]] && [[ -d "${WORKTREE_DIR}" ]]; then + builder_echo "Removing temporary worktree" + git worktree remove -f "${WORKTREE_DIR}" + git branch -D "${WORKTREE_BRANCH}" + fi + + local TEMP_DIR + TEMP_DIR=$(dirname "${WORKTREE_DIR}") + rm -rf "${TEMP_DIR}" +} + builder_heading "Fetching latest changes" git fetch -p origin if ${IS_BETA}; then - DEPLOY_BRANCH=origin/beta + ORIGIN_DEPLOY_BRANCH=origin/beta else # shellcheck disable=2311 - DEPLOY_BRANCH=$(get_latest_stable_branch_name) + ORIGIN_DEPLOY_BRANCH=$(get_latest_stable_branch_name) fi -# Checkout stable/beta branch so that `scripts/debian.sh` picks up correct version -git checkout "${DEPLOY_BRANCH#origin/}" -git pull origin "${DEPLOY_BRANCH#origin/}" +DEPLOY_BRANCH="${ORIGIN_DEPLOY_BRANCH#origin/}" +BRANCH_BASENAME="tmp-packaging-${DEPLOY_BRANCH}" +WORKTREE_BRANCH="maint/linux/${BRANCH_BASENAME}" +PREV_DIR="${PWD}" + +# cleanup any residues of previous runs +WORKTREE_DIR="$(git worktree list --porcelain | awk -v name="${BRANCH_BASENAME}" \ + '$1 == "worktree" && $2 ~ name { print $2 }')" +if [[ -n "${WORKTREE_DIR}" ]] && [[ -d "${WORKTREE_DIR}" ]]; then + git worktree remove -f "${WORKTREE_DIR}" +fi +if git branch | grep -q "${WORKTREE_BRANCH}"; then + git branch -D "${WORKTREE_BRANCH}" +fi + +# Create a temporary worktree so that we start with a clean copy of the +# target branch (stable/beta). Checkout stable/beta branch so that +# scripts/debian.sh picks up correct version +WORKTREE_DIR="$(mktemp -d)/${BRANCH_BASENAME}" +builder_heading "Creating temporary worktree at ${WORKTREE_DIR}" +git worktree add -f -b "${WORKTREE_BRANCH}" "${WORKTREE_DIR}" "${DEPLOY_BRANCH}" + +cd "${WORKTREE_DIR}" + +trap cleanup_worktree EXIT + +git pull origin "${DEPLOY_BRANCH}" builder_heading "Building source package" -cd "${KEYMAN_ROOT}/linux" +cd "${WORKTREE_DIR}/linux" DIST=unstable DEBREVISION=${REVISION} scripts/debian.sh cd debianpackage/ builder_heading "Signing source package" @@ -145,19 +186,21 @@ cd .. builder_heading "Updating changelog" # base changelog branch on remote stable/beta branch -git checkout -B chore/linux/changelog "${DEPLOY_BRANCH}" +git checkout -B chore/linux/changelog "${ORIGIN_DEPLOY_BRANCH}" cp debianpackage/keyman-*/debian/changelog debian/ git add debian/changelog COMMIT_MESSAGE="chore(linux): Update debian changelog" git commit -m "${COMMIT_MESSAGE}" -push_to_github_and_create_pr chore/linux/changelog "${DEPLOY_BRANCH#origin/}" "${COMMIT_MESSAGE} 🏠" "Test-bot: skip" +push_to_github_and_create_pr chore/linux/changelog "${DEPLOY_BRANCH}" "${COMMIT_MESSAGE} 🏠" "Build-bot: skip +Test-bot: skip" # Create cherry-pick on master branch git checkout -B chore/linux/cherry-pick/changelog origin/master git cherry-pick -x chore/linux/changelog push_to_github_and_create_pr chore/linux/cherry-pick/changelog master "${COMMIT_MESSAGE} 🍒" \ "Cherry-pick-of: #${PR_NUMBER} +Build-bot: skip Test-bot: skip" builder_heading "Finishing" -git checkout "${CURRENT_BRANCH}" +cleanup_worktree diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 9bac3e9c89..afd1ce9f73 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -329,7 +329,7 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef NSEvent* sysEvent = [NSEvent eventWithCGEvent:event]; // Too many of these to be useful for most debugging sessions, but we'll keep this around to be // un-commented when needed. - // os_log_debug([KMLogs eventsLog], "System Event: %@", sysEvent); + os_log_debug([KMLogs keyTraceLog], "System Event: %{public}@", sysEvent); switch (type) { case kCGEventFlagsChanged: @@ -350,26 +350,27 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef case kCGEventKeyDown: appDelegate.receivedKeyDownFromOsk = NO; - os_log_debug([KMLogs eventsLog], "Event tap keydown event, keyCode: 0x%X (%d)", sysEvent.keyCode, sysEvent.keyCode); + os_log_debug([KMLogs keyTraceLog], "** KEY DOWN Event **"); + os_log_debug([KMLogs keyTraceLog], "** event tap keydown event, keyCode: 0x%X (%d)", sysEvent.keyCode, sysEvent.keyCode); // Pass back low-level backspace events to the input method event handler // because some non-compliant apps do not allow us to see backspace events // that we have generated (and we need to see them, for serialization // of events) if(sysEvent.keyCode == kVK_Delete && appDelegate.inputController != nil) { - os_log_debug([KMLogs eventsLog], "Event tap handling kVK_Delete."); + os_log_debug([KMLogs keyTraceLog], "** event tap handling kVK_Delete"); [appDelegate.inputController handleBackspace:sysEvent]; } else if(sysEvent.keyCode == kVK_Delete) { - os_log_debug([KMLogs eventsLog], "Event tap not handling kVK_Delete, appDelegate.inputController = %{public}@", appDelegate.inputController); + os_log_debug([KMLogs eventsLog], "** event tap cannot handle kVK_Delete, appDelegate.inputController = %{public}@", appDelegate.inputController); } if(sysEvent.keyCode == 255) { - os_log_debug([KMLogs eventsLog], "*** kKeymanEventKeyCode = 0xFF"); + os_log_debug([KMLogs eventsLog], "** event tap received kKeymanEventKeyCode = 0xFF"); } else { if ([OSKView isOskKeyDownEvent:event]) { [KMSentryHelper addInfoBreadCrumb:@"event" message:@"processing OSK-generated keydown event"]; NSEventModifierFlags oskEventModifiers = [OSKView extractModifierFlagsFromOskEvent:event]; appDelegate.receivedKeyDownFromOsk = YES; appDelegate.oskEventModifiers = oskEventModifiers; - os_log_debug([KMLogs eventsLog], "*** keydown event received from OSK, modifiers: 0x%lX",(unsigned long)oskEventModifiers); + os_log_debug([KMLogs eventsLog], "*** event tap received event from OSK, modifiers: 0x%lX",(unsigned long)oskEventModifiers); } } @@ -1292,6 +1293,11 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef } - (void)handleKeyEvent:(NSEvent *)event { + os_log_debug([KMLogs keyTraceLog], "handleKeyEvent: %{public}@", event); + + NSArray *stackSymbols = [NSThread callStackSymbols]; + os_log_debug([KMLogs keyTraceLog], "call stack: %{public}@", stackSymbols); + if (_oskWindow == nil) return; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index eed737f06f..dd9a8a3cee 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -106,8 +106,9 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; return NO; } } - + output = [self processEventWithKeymanEngine:event in:sender]; + os_log_debug([KMLogs keyTraceLog], "keyman engine output: %{public}@", output); if (output == nil) { return NO; @@ -170,7 +171,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; Core for processing, as it is already the result of processing. */ - (void)handleBackspace:(NSEvent *)event { - os_log_debug([KMLogs eventsLog], "KMInputMethodEventHandler handleBackspace, event = %{public}@", event); + os_log_debug([KMLogs keyTraceLog], "KMInputMethodEventHandler handleBackspace, event = %{public}@", event); [KMSentryHelper addInfoBreadCrumb:@"user" message:@"handle backspace for non-compliant app"]; if (self.generatedBackspaceCount > 0) { @@ -250,18 +251,19 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; // handleEvent implementation for core event processing - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { BOOL handled = NO; - os_log_debug([KMLogs eventsLog], "handleEvent፡ event = %{public}@", event); + os_log_debug([KMLogs keyTraceLog], "eventHandler handleEvent፡ event = %{public}@", event); [self handleContextChangedByLowLevelEvent]; [self checkTextApiCompliance:sender]; if (event.type == NSEventTypeKeyDown) { + os_log_debug([KMLogs keyTraceLog], "eventHandler, event.type is NSEventTypeKeyDown"); [KMSentryHelper addDebugBreadCrumb:@"event" message:@"handling keydown event"]; // indicates that our generated backspace event(s) are consumed // and we can insert text that followed the backspace(s) if (event.keyCode == kKeymanEventKeyCode) { - os_log_debug([KMLogs eventsLog], "handleEvent, handling kKeymanEventKeyCode"); + os_log_debug([KMLogs keyTraceLog], "eventHandler, event is kKeymanEventKeyCode"); [self insertQueuedText: event client:sender]; return YES; } @@ -269,7 +271,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; // for some apps, handleEvent will not be called for the generated backspace // but if it is, we need to let it pass through rather than process again in core if((event.keyCode == kVK_Delete) && (self.lowLevelBackspaceCount > 0)) { - os_log_debug([KMLogs eventsLog], "handleEvent, allowing generated backspace to pass through"); + os_log_debug([KMLogs keyTraceLog], "eventHandler, generated backspace passing through"); self.lowLevelBackspaceCount--; // return NO to pass through to client app @@ -278,11 +280,13 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; } if (event.type == NSEventTypeFlagsChanged) { + os_log_debug([KMLogs keyTraceLog], "eventHandler, flags changed passing through"); [self handleFlagsChangedEvent:[event keyCode]]; return NO; } if ((event.modifierFlags & NSEventModifierFlagCommand) == NSEventModifierFlagCommand) { + os_log_debug([KMLogs keyTraceLog], "eventHandler, modifier flag passing through"); [self handleCommand:event]; return NO; // let the client app handle all Command-key events. } @@ -292,7 +296,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; handled = [self handleEventWithKeymanEngine:event in: sender]; } - os_log_debug([KMLogs eventsLog], "event, keycode: %u, characters='%{public}@' handled = %{public}@", event.keyCode, event.characters, handled?@"yes":@"no"); + os_log_debug([KMLogs keyTraceLog], "eventHandler complete, keycode: %u, characters='%{public}@' handled = %{public}@", event.keyCode, event.characters, handled?@"yes":@"no"); return handled; } @@ -327,11 +331,11 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; // we do this whether the context has changed or not if (self.apiCompliance.canReadText) { contextString = [self readContext:event forClient:client]; - os_log_debug([KMLogs eventsLog], "reportContext, setting new context for compliant app (if needed)"); + os_log_debug([KMLogs keyTraceLog], "reportContext, setting new context for compliant app (if needed)"); [self.kme setCoreContextIfNeeded:contextString]; } else if (self.contextChanged) { // we cannot read the text but know the context has changed, so we must clear it - os_log_debug([KMLogs eventsLog], "reportContext, clearing context for non-compliant app"); + os_log_debug([KMLogs keyTraceLog], "reportContext, clearing context for non-compliant app"); [self.kme clearCoreContext]; } @@ -379,8 +383,6 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; * Returns NO if we have not applied an event to the client or if Keyman Core determines that we should emit the keystroke */ -(BOOL)applyKeymanCoreActions:(CoreKeyOutput*)output event: (NSEvent*)event client:(id) client { - //os_log_debug([KMLogs eventsLog], "InputMethodEventHandler applyKeymanCoreActions: output = %{public}@ ", output); - BOOL handledEvent = [self applyKeyOutputToTextInputClient:output keyDownEvent:event client:client]; [self applyNonTextualOutput:output]; @@ -399,19 +401,19 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; if (output.isInsertOnlyScenario) { NSString *message = @"applyOutputToTextInputClient, insert only scenario"; - os_log_debug([KMLogs keyLog], "%@", message); + os_log_debug([KMLogs keyTraceLog], "%@", message); [KMSentryHelper addDebugBreadCrumb:@"event" message:message]; [self insertAndReplaceTextForOutput:output client:client]; } else if (output.isDeleteOnlyScenario) { if ((event.keyCode == kVK_Delete) && output.codePointsToDeleteBeforeInsert == 1) { // let the delete pass through in the original event rather than sending a new delete NSString *message = @"applyOutputToTextInputClient, delete only scenario with passthrough"; - os_log_debug([KMLogs keyLog], "%@", message); + os_log_debug([KMLogs keyTraceLog], "%@", message); [KMSentryHelper addDebugBreadCrumb:@"event" message:message]; handledEvent = NO; } else { NSString *message = @"applyOutputToTextInputClient, delete only scenario"; - os_log_debug([KMLogs keyLog], "%@", message); + os_log_debug([KMLogs keyTraceLog], "%@", message); [KMSentryHelper addDebugBreadCrumb:@"event" message:message]; [self sendEvents:event forOutput:output]; } @@ -435,12 +437,12 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; if (self.apiCompliance.mustBackspaceUsingEvents) { NSString *message = @"applyOutputToTextInputClient, delete and insert scenario with events"; - os_log_debug([KMLogs keyLog], "%@", message); + os_log_debug([KMLogs keyTraceLog], "%@", message); [KMSentryHelper addDebugBreadCrumb:@"event" message:message]; [self sendEvents:event forOutput:output]; } else { NSString *message = @"applyOutputToTextInputClient, delete and insert scenario with insert API"; - os_log_debug([KMLogs keyLog], "%@", message); + os_log_debug([KMLogs keyTraceLog], "%@", message); [KMSentryHelper addDebugBreadCrumb:@"event" message:message]; // directly insert text and handle backspaces by using replace [self insertAndReplaceTextForOutput:output client:client]; @@ -500,7 +502,8 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; -(void)insertAndReplaceText:(NSString *)text deleteCount:(int) replacementCount toReplace:(NSString*)textToDelete client:(id) client { NSRange selectionRange = [client selectedRange]; NSRange replacementRange = [self calculateInsertRangeForDeletedText:textToDelete selectionRange:selectionRange]; - + + os_log_debug([KMLogs keyTraceLog], "insertAndReplaceText: '%{public}@', selectionRange: %{public}@, replacementRange: %{public}@", text, selectionRange.location==NSNotFound?@"NSNotFound":NSStringFromRange(selectionRange), replacementRange.location==NSNotFound?@"NSNotFound":NSStringFromRange(replacementRange)); [client insertText:text replacementRange:replacementRange]; if ((self.apiCompliance.isComplianceUncertain) && (text != nil)) { @@ -508,7 +511,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; } } -/* +/** * Calculates the range where text will be inserted and replace existing text. * Returning {NSNotFound, NSNotFound} for range signifies to insert at current location without replacement. */ @@ -531,7 +534,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; } -(void)sendEvents:(NSEvent *)event forOutput:(CoreKeyOutput*)output { - os_log_debug([KMLogs keyLog], "sendEvents called, output = %{public}@", output); + os_log_debug([KMLogs keyTraceLog], "sendEvents called, output = %{public}@", output); _sourceForGeneratedEvent = CGEventCreateSourceFromEvent([event CGEvent]); @@ -540,13 +543,13 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; if (output.hasCodePointsToDelete) { for (int i = 0; i < output.codePointsToDeleteBeforeInsert; i++) { - os_log_debug([KMLogs keyLog], "sendEvents sending backspace key event"); + os_log_debug([KMLogs keyTraceLog], "sendEvents sending backspace key event"); [self.keySender sendBackspaceforEventSource:_sourceForGeneratedEvent]; } } if (output.hasTextToInsert) { - os_log_debug([KMLogs keyLog], "sendEvents, queueing text to insert: %{public}@", output.textToInsert); + os_log_debug([KMLogs keyTraceLog], "sendEvents, queueing text to insert: %{public}@", output.textToInsert); self.queuedText = output.textToInsert; } @@ -554,7 +557,7 @@ CGEventSourceRef _sourceForGeneratedEvent = nil; -(void)insertQueuedText: (NSEvent *)event client:(id) client { if (self.queuedText.length> 0) { - os_log_debug([KMLogs keyLog], "insertQueuedText, inserting %{public}@", self.queuedText); + os_log_debug([KMLogs keyTraceLog], "insertQueuedText, inserting %{public}@", self.queuedText); [self insertAndReplaceText:self.queuedText deleteCount:0 toReplace:@"" client:client]; self.queuedText = nil; } else { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h index f7f83702ec..d62ed17228 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.h @@ -27,6 +27,7 @@ NS_ASSUME_NONNULL_BEGIN + (os_log_t)keyboardLog; + (os_log_t)keyLog; + (os_log_t)oskLog; ++ (os_log_t)keyTraceLog; + (os_log_t)testLog; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m index 6a8a5cd957..91a276fad9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/Logging/KMLogs.m @@ -35,6 +35,7 @@ char *const eventsCategory = "events"; char *const keyboardCategory = "keyboard"; char *const keyCategory = "key"; char *const oskCategory = "osk"; +char *const keyTraceCategory = "keytrace"; char *const testCategory = "test"; + (void)reportLogStatus { @@ -88,6 +89,10 @@ char *const testCategory = "test"; return os_log_create(keymanSubsystem, oskCategory); } ++ (os_log_t)keyTraceLog { + return os_log_create(keymanSubsystem, keyTraceCategory); +} + + (os_log_t)testLog { return os_log_create(keymanSubsystem, testCategory); } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m index c93ff4d2ff..a5e04e6e09 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/TextApiCompliance.m @@ -365,7 +365,8 @@ NSString *const kKMLegacyApps = @"KMLegacyApps"; [clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] || [clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] || [clientAppId isEqual: @"org.sil.app.builder.dictionary.DictionaryAppBuilder"] || - //[clientAppId isEqual: @"com.microsoft.Word"] || // 2020-11-24[mcd]: Appears to work well in Word 16.43, disable legacy by default + [clientAppId isEqual: @"com.microsoft.Word"] || + // Microsoft Word removed on 2020-11-24 but added back on 2025-10-10 for issue #14160 [clientAppId isEqual: @"org.openoffice.script"] || // 2023-12-13[sgs]: Adobe apps automatically detected as non-compliant //[clientAppId isEqual: @"com.adobe.illustrator"] || diff --git a/mac/Keyman4MacIM/Keyman4MacIM/vi.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/vi.lproj/Localizable.strings index a0f3cf9c6c..d1e833c51a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/vi.lproj/Localizable.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/vi.lproj/Localizable.strings @@ -67,8 +67,11 @@ /* copyright label in the Package Information window */ "copyright-label" = "Bản quyền:"; -/* message displayed to alert user to need grant accessibility permission */ +/* message displayed to alert user to grant accessibility permission in Preferences, pre-Ventura */ "privacy-alert-text" = "Để hoạt động đúng, Keyman yêu cầu tính năng trợ năng:\n\nCấp quyền truy cập trong Thông tin Hệ thống, Bảo mật & Riêng tư.\nKhởi động lại hệ thống của bạn."; +/* message displayed to alert user to grant accessibility permission in Settings, for macOS Ventura and later */ +"privacy-alert-text-ventura" = "Để hoạt động đúng, Keyman yêu cầu tính năng trợ năng:\n\nCấp quyền truy cập trong Thông tin Hệ thống, Bảo mật & Riêng tư.\nKhởi động lại hệ thống của bạn."; + /* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */ "no-keyboard-configured-menu-placeholder" = "(Không có bàn phím được cấu hình)"; diff --git a/mac/Keyman4MacIM/make-km-dmg.sh b/mac/Keyman4MacIM/make-km-dmg.sh index 0ee1d7395b..5a3176b298 100755 --- a/mac/Keyman4MacIM/make-km-dmg.sh +++ b/mac/Keyman4MacIM/make-km-dmg.sh @@ -192,12 +192,17 @@ builder_echo info "Detaching \"$WORKING_COPY_OF_IMAGE\"" DETACH_SUCCESS=0 DETACH_FORCE= while (( DETACH_SUCCESS < 10 )); do - DETACH_RESULT=0 - hdiutil detach $STAGING_DIR $VERBOSITY $DETACH_FORCE || DETACH_RESULT=$? - if [[ $DETACH_RESULT != 0 || -d "$STAGING_DIR" ]] ; then + if hdiutil detach $STAGING_DIR $VERBOSITY $DETACH_FORCE; then + DETACH_RESULT=0 + echo "hdiutil detach reported success" + else + DETACH_RESULT=$? + echo "hdiutil detach reported failure code ${DETACH_RESULT}" + fi + if [[ $DETACH_RESULT != 0 ]] || [[ -d "$STAGING_DIR" ]] ; then (( DETACH_SUCCESS++ )) echo "Failed to unmount: \"$STAGING_DIR\" on attempt #$DETACH_SUCCESS. Waiting 5 seconds to try again." - if (( DETACH_SUCCESS > 5 )); then + if (( DETACH_SUCCESS == 6 )); then echo " Note: Detach failed first five times, so now attempting with '-force'" DETACH_FORCE=-force fi @@ -212,7 +217,7 @@ if (( DETACH_SUCCESS < 999 )); then fi # Step 5 - Convert image to a compressed readonly DMG image -DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION.dmg" +DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION_FOR_FILENAME.dmg" builder_echo info "Converting/compressing image to create \"$DMG_FILE_PATH\"" if [[ -e "$DMG_FILE_PATH" ]] ; then if [[ "$VERBOSITY" != "-quiet" ]] ; then diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m index e98fe60663..e576d000b8 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/OnScreenKeyboard/OSKView.m @@ -64,7 +64,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; // Custom initialization [self initOSKKeys]; } - + return self; } @@ -76,26 +76,26 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; - (void)drawRect:(NSRect)rect { os_log_debug([KMELogs oskLog], "OSKView drawRect: %{public}@", NSStringFromRect(rect)); - + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] CGContext]; CGContextSetLineJoin(context, kCGLineJoinRound); CGContextSetLineWidth(context, 1.0); CGColorRef cgClearColor = CGColorGetConstantColor(kCGColorClear); CGContextSetStrokeColorWithColor(context, cgClearColor); CGContextSetFillColorWithColor(context, cgClearColor); - + CGContextBeginPath(context); CGContextAddRect(context, CGRectMake(1.0, 1.0, rect.size.width-1.0, rect.size.height-1.0)); CGContextDrawPath(context, kCGPathStroke); - + CGContextBeginPath(context); CGContextAddRect(context, CGRectMake(1.0, 1.0, rect.size.width-1.0, rect.size.height-1.0)); CGContextClip(context); - + //TODO: gradient from clear to clear -- what does this do? NSColor *bgColor = [NSColor clearColor]; //[NSColor colorWithWhite:0.7 alpha:1.0]; NSColor *bgColor2 = [NSColor clearColor]; //[NSColor colorWithWhite:0.5 alpha:1.0]; - + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); NSArray *gradientColors = [NSArray arrayWithObjects:(id)bgColor.CGColor, bgColor2.CGColor, nil]; CGFloat gradientLocations[] = {0, 1}; @@ -135,11 +135,11 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; - (void)setKvk:(KVKFile *)kvk { os_log_debug([KMELogs oskLog], "OSKView setKvk, forces keyboard to re-layout"); _kvk = kvk; - + // Force the keyboard to re-layout _oskLayout = nil; _oskDefaultNKeys = nil; - + [self updateKeyLabelsForCurrentLayer]; } @@ -173,7 +173,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; } py += keyHeight; } - + [self updateKeyLabelsForCurrentLayer]; } @@ -195,7 +195,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; [[OSKKey alloc] initWithKeyCode:MVK_MINUS caption:@"-" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_EQUAL caption:@"=" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_BACKSPACE caption:@"⬅︎" scale:1.5], nil]; - + NSArray *row2 = [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_TAB caption:@"↹" scale:1.5], [[OSKKey alloc] initWithKeyCode:MVK_Q caption:@"Q" scale:1.0], @@ -211,7 +211,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; [[OSKKey alloc] initWithKeyCode:MVK_LEFT_BRACKET caption:@"[" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_RIGHT_BRACKET caption:@"]" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_BACKSLASH caption:@"\\" scale:1.0], nil]; - + NSArray *row3 = [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_CAPS_LOCK caption:@"Caps Lock" scale:1.75], [[OSKKey alloc] initWithKeyCode:MVK_A caption:@"A" scale:1.0], @@ -226,14 +226,14 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; [[OSKKey alloc] initWithKeyCode:MVK_SEMICOLON caption:@";" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_QUOTE caption:@"'" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_ENTER caption:@"↵" scale:1.75], nil]; - + NSArray *row4a = [self use102ndKey] ? [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_LEFT_SHIFT caption:@"⇧" scale:1.25], [[OSKKey alloc] initWithKeyCode:MVK_OEM102 caption:@"\\" scale:1.0], nil] : [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_LEFT_SHIFT caption:@"⇧" scale:2.25], nil]; - + NSArray *row4b = [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_Z caption:@"Z" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_X caption:@"X" scale:1.0], @@ -246,19 +246,19 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; [[OSKKey alloc] initWithKeyCode:MVK_PERIOD caption:@"." scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_SLASH caption:@"/" scale:1.0], [[OSKKey alloc] initWithKeyCode:MVK_RIGHT_SHIFT caption:@"⇧" scale:2.25], nil]; - + NSArray *row4 = [row4a arrayByAddingObjectsFromArray:row4b]; - + NSArray *row5 = [NSArray arrayWithObjects: [[OSKKey alloc] initWithKeyCode:MVK_LEFT_CTRL caption:@"Ctrl" scale:1.75], [[OSKKey alloc] initWithKeyCode:MVK_LEFT_ALT caption:@"Alt" scale:1.5], [[OSKKey alloc] initWithKeyCode:MVK_SPACE caption:@"" scale:8.0], [[OSKKey alloc] initWithKeyCode:MVK_RIGHT_ALT caption:@"Alt" scale:1.5], [[OSKKey alloc] initWithKeyCode:MVK_RIGHT_CTRL caption:@"Ctrl" scale:1.75], nil]; - + _oskLayout = [NSArray arrayWithObjects:row1, row2, row3, row4, row5, nil]; } - + return _oskLayout; } @@ -266,7 +266,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; if (_oskDefaultNKeys == nil) { os_log_debug([KMELogs oskLog], "oskDefaultNKeys -> creating new arrays of default number OSKKey objects"); NSMutableArray *defNKeys = [[NSMutableArray alloc] initWithCapacity:0]; - + // row 1 NKey *nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_GRAVE; nkey1.text = @"`"; nkey1.bitmap = nil; NKey *nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_GRAVE; nkey2.text = @"~"; nkey2.bitmap = nil; @@ -307,7 +307,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_EQUAL; nkey1.text = @"="; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_EQUAL; nkey2.text = @"+"; nkey2.bitmap = nil; [defNKeys addObjectsFromArray:@[nkey1, nkey2]]; - + // row 2 nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_KEY_Q; nkey1.text = @"q"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_KEY_Q; nkey2.text = @"Q"; nkey2.bitmap = nil; @@ -348,7 +348,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_BACKSLASH; nkey1.text = @"\\"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_BACKSLASH; nkey2.text = @"|"; nkey2.bitmap = nil; [defNKeys addObjectsFromArray:@[nkey1, nkey2]]; - + // row 3 nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_KEY_A; nkey1.text = @"a"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_KEY_A; nkey2.text = @"A"; nkey2.bitmap = nil; @@ -383,7 +383,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_QUOTE; nkey1.text = @"'"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_QUOTE; nkey2.text = @"\""; nkey2.bitmap = nil; [defNKeys addObjectsFromArray:@[nkey1, nkey2]]; - + if([self use102ndKey]) { nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_OEM_102; nkey1.text = @"\\"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_OEM_102; nkey2.text = @"|"; nkey2.bitmap = nil; @@ -420,10 +420,10 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; nkey1 = [[NKey alloc] init]; nkey1.typeFlags = 2; nkey1.modifierFlags = 0; nkey1.keyCode = VK_SLASH; nkey1.text = @"/"; nkey1.bitmap = nil; nkey2 = [[NKey alloc] init]; nkey2.typeFlags = 2; nkey2.modifierFlags = 1; nkey2.keyCode = VK_SLASH; nkey2.text = @"?"; nkey2.bitmap = nil; [defNKeys addObjectsFromArray:@[nkey1, nkey2]]; - + _oskDefaultNKeys = [[NSArray alloc] initWithArray:defNKeys]; } - + return _oskDefaultNKeys; } @@ -471,7 +471,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; CGEventPostToPid(processId, keyUpEvent); CFRelease(keyDownEvent); CFRelease(keyUpEvent); - + CFRelease(source); } else { @@ -501,7 +501,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; BOOL shift = self.physicalShiftState | self.oskShiftState; BOOL option = self.physicalOptionState | self.oskOptionState; BOOL control = self.physicalControlState | self.oskControlState; - + return [self getEventModifierFlags:shift optionFlag:option controlFlag:control]; } @@ -510,7 +510,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; */ - (NSEventModifierFlags)getEventModifierFlags:(BOOL)shift optionFlag:(BOOL)option controlFlag:(BOOL)control { NSEventModifierFlags modifierFlags = 0; - + if (shift) { modifierFlags = modifierFlags | NSEventModifierFlagShift; } @@ -524,7 +524,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; if (control) { modifierFlags = modifierFlags | NSEventModifierFlagControl; } - + return modifierFlags; } @@ -551,7 +551,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; NSView *view = [self viewWithTag:event.keyCode|0x1000]; if (view == nil || ![view isKindOfClass:[KeyView class]]) return; - + KeyView *keyView = (KeyView *)view; if (event.type == NSEventTypeKeyDown) [keyView setKeyPressed:YES]; @@ -570,7 +570,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; BOOL shift = self.physicalShiftState | self.oskShiftState; BOOL option = self.physicalOptionState | self.oskOptionState; BOOL control = self.physicalControlState | self.oskControlState; - + return [self getKeymanModifierFlags:shift optionFlag:option controlFlag:control]; } @@ -592,7 +592,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; if (control) { flags |= KVKS_RCTRL; } - + return flags; } @@ -602,15 +602,15 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; */ - (void)updateKeyLabelsForCurrentLayer { os_log_debug([KMELogs oskLog], "OSKView updateKeyLabelsForState, phsyical modifiers [shift: %d, option: %d, control: %d]\nosk modifiers [shift: %d, option: %d, control: %d]", self.physicalShiftState, self.physicalOptionState, self.physicalControlState, self.oskShiftState, self.oskOptionState, self.oskControlState); - + [self resetKeyLabels]; NSMutableArray *mKeys = [[self keyTags] mutableCopy]; NSArray *nkeys = [self.kvk keys]; if (nkeys == nil) nkeys = self.oskDefaultNKeys; - + WORD keymanFlagsForCurrentLayer = [self getKeymanModifierFlagsForCurrentLayer]; - + NSString *ansiFont = [self ansiFont]; NSString *unicodeFont = [self unicodeFont]; unsigned short keyCode; @@ -622,9 +622,8 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; NSView *view = [self viewWithTag:keyCode|0x1000]; if (view == nil || ![view isKindOfClass:[KeyView class]]) continue; - + KeyView *keyView = (KeyView *)view; - [keyView setLabelFont:ansiFont]; if (nkey.typeFlags & KVKK_UNICODE) { [keyView setLabelText:nkey.text]; NSString* label = nkey.text; @@ -632,12 +631,16 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; os_log_debug([KMELogs keyLog], "setLabelText to %{public}@, utf8data %{public}@", label, data); [keyView setLabelFont:unicodeFont]; [mKeys removeObject:[NSNumber numberWithInteger:(keyCode|0x1000)]]; + } else { + // This means it is a key definition for a non-Unicode layout. + // We do not support non-Unicode keyboards in Keyman for macOS. + continue; } - + if (nkey.typeFlags & KVKK_BITMAP) { [keyView setBitmap:nkey.bitmap]; } - + [keyView setNeedsDisplay:YES]; } } @@ -647,7 +650,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; NSView *view = [self viewWithTag:[t integerValue]|0x1000]; if (view == nil || ![view isKindOfClass:[KeyView class]]) continue; - + KeyView *keyView = (KeyView *)view; [keyView setLabelText:@""]; }*/ @@ -659,10 +662,10 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; for (NSView *view in views) { if (![view isKindOfClass:[KeyView class]]) continue; - + [keyTags addObject:[NSNumber numberWithInteger:[view tag]]]; } - + return keyTags; } @@ -671,7 +674,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; for (NSView *view in views) { if (![view isKindOfClass:[KeyView class]]) continue; - + [(KeyView *)view setKey:[(KeyView *)view key]]; [(KeyView *)view setBitmap:nil]; [(KeyView *)view setNeedsDisplay:YES]; @@ -687,7 +690,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; * The state of the physical key overrides the modifier key clicked in the OSK. */ _oskShiftState = NO; - + os_log_debug([KMELogs oskLog], "hardware shift key released"); [self updateKeyLabelsForCurrentLayer]; [self updateShiftKeysForState]; @@ -705,7 +708,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; - (void)setPhysicalOptionState:(BOOL)state { if (_physicalOptionState != state) { _physicalOptionState = state; - + /** * When setting the physical option state, clear the OSK option state * The state of the physical key overrides the modifier key clicked in the OSK. @@ -727,7 +730,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; - (void)setPhysicalControlState:(BOOL)state { if (_physicalControlState != state) { _physicalControlState = state; - + /** * When setting the physical control state, clear the OSK control state * The state of the physical key overrides the modifier key clicked in the OSK. @@ -793,7 +796,7 @@ const int64_t OSK_EVENT_MODIFIER_MASK = 0x00000000FFFFFFFF; break; } } - + return keyCode; } diff --git a/mac/README.md b/mac/README.md index ef4c0d4b41..f15997e827 100644 --- a/mac/README.md +++ b/mac/README.md @@ -27,8 +27,8 @@ keyboard input. You have two options for local builds: Keyman must be signed then notarized by Apple, even for local test builds. This requires additional configuration for your build environment. -1. First, open XCode, Preferences, Accounts, and select Manage Certificates for the identity - you wish to use for signing. Click **+** and select **Developer ID Application**. A +1. First, open XCode, Settings, Accounts, and select Manage Certificates for the identity + you wish to use for signing. Click **+** and select **Apple Development**. A certificate will then be generated and listed in your Keychain. 2. Find the SHA-1 hash. To find the certificate in terminal: @@ -64,23 +64,17 @@ configuration for your build environment. To build Keyman for macOS, do the following: 1. Open a Terminal window. -2. cd to **keyman/mac**. **build.sh** must be run in the directory containing the script. -3. Build using `./build.sh -no-codesign`. Run `./build.sh -help` to see all options. - * If you have signing credentials from the core development team, you can build a signed - version by omitting `-no-codesign`. Somewhat misleadingly, `-no-codesign` only stops - automatic signing using the certificate maintained by the core development team! - * If you want to deploy, you will need to also add `-config Release`, as a Debug build cannot be notarized. +2. Build using `mac/build.sh clean configure build` Note: If Carthage prompts you to allow it access to your github credentials, it's fine to click Deny. ### Running Keyman -1. Deploy Keyman locally using `./build.sh -deploy local -deploy-only`. +1. Deploy Keyman locally using `mac/build.sh install`. * This will notarize the app, signing with your local credentials if not already signed, and copy **keyman/mac/Keyman4MacIM/build/Debug/Keyman.app** to **~/Library/Input Methods** 2. If running for the first time, follow the installation instructions at [Install Keyman for macOS]. -You can also use `./build.sh -no-codesign -deploy local` to do a single-step build, notarize, -and deploy (see above for faster options). +You can also use `mac/build.sh build install` to do a single-step build, notarize, and deploy (see above for faster options). ### Compiling from Xcode diff --git a/mac/build.sh b/mac/build.sh index 875e4b5013..e032db998a 100755 --- a/mac/build.sh +++ b/mac/build.sh @@ -293,7 +293,7 @@ do_publish() { "${KM4MIM_BASE_PATH}/make-km-dmg.sh" local UPLOAD_PATH="${KM4MIM_BASE_PATH}/output/upload/${KEYMAN_VERSION}" - write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION}.dmg" "Keyman4MacIM" dmg mac + write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION_FOR_FILENAME}.dmg" "Keyman4MacIM" dmg mac if builder_is_ci_build && builder_is_ci_build_level_release; then do_sentry diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/KeyboardListActivity.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/KeyboardListActivity.java index 040d688fb2..dabfe4a41a 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/KeyboardListActivity.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/KeyboardListActivity.java @@ -23,6 +23,8 @@ public final class KeyboardListActivity extends BaseActivity { final Context context = this; setContentView(R.layout.activity_list_layout); + setupEdgeToEdge(R.id.activity_list_layout); + final Toolbar toolbar = findViewById(R.id.list_toolbar); TextView title = findViewById(R.id.bar_title); title.setText(getIntent().getStringExtra("regionName")); diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/RegionListActivity.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/RegionListActivity.java index 4b01c4b473..e5754b8974 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/RegionListActivity.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/RegionListActivity.java @@ -10,10 +10,10 @@ import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import com.keyman.engine.BaseActivity; -public final class RegionListActivity extends AppCompatActivity { +public final class RegionListActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { @@ -23,6 +23,8 @@ public final class RegionListActivity extends AppCompatActivity { // Setup layout setContentView(R.layout.activity_list_layout); + setupEdgeToEdge(R.id.activity_list_layout); + final Toolbar toolbar = findViewById(R.id.list_toolbar); final TextView textView = findViewById(R.id.bar_title); textView.setText(R.string.regions); diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java index 30e7d93c5a..962303a9dd 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java @@ -203,12 +203,14 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven Point size = KMManager.getWindowSize(getApplicationContext()); int inputViewHeight = 0; - if (inputView != null) + if (inputView != null) { inputViewHeight = inputView.getHeight(); + } + int navigationHeight = KMManager.getNavigationBarHeight(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); int bannerHeight = KMManager.getBannerHeight(this); int kbHeight = KMManager.getKeyboardHeight(this); - outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight; + outInsets.contentTopInsets = inputViewHeight - bannerHeight - kbHeight - navigationHeight; outInsets.visibleTopInsets = outInsets.contentTopInsets; outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_REGION; outInsets.touchableRegion.set(0, outInsets.contentTopInsets, size.x, size.y); diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index 00de2d98ec..2d8d30141f 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -38,7 +38,7 @@ if builder_is_debug_build; then TEST_FLAGS="-x assembleDebug lintDebug testDebug" fi -ARTIFACT="firstvoices-$KEYMAN_VERSION.apk" +ARTIFACT="firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.apk" KEYBOARD_PACKAGE_ID="fv_all" KEYBOARDS_TARGET="oem/firstvoices/android/app/src/main/assets/${KEYBOARD_PACKAGE_ID}.kmp" @@ -76,6 +76,8 @@ if builder_start_action build; then echo "BUILD_FLAGS: $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS + mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/firstvoices-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/${ARTIFACT}" + builder_finish_action success build fi diff --git a/oem/firstvoices/keyboards.csv b/oem/firstvoices/keyboards.csv index 5628d4f9b1..e078d91f2f 100644 --- a/oem/firstvoices/keyboards.csv +++ b/oem/firstvoices/keyboards.csv @@ -2,7 +2,7 @@ Shortname,ID,Name,Region,9.0 Web Keyboard,Version,Language ID,Language Name fv,fv_inuvialuktun,Inuvialuktun,Arctic,fv_inuvialuktun_kmw-9.0.js,9.1.1,ikt-Latn,Inuinnaqtun (Latin) i,inuktitut_pirurvik,Kiputtijjut | ᑭᐳᑦᑎᔾᔪᑦ,Arctic,inuktitut_pirurvik-1.1.js,1.4.1,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics) i,inuktitut_latin,Qaliujaaqpait | ᖃᓕᐅᔮᖅᐸᐃᑦ,Arctic,inuktitut_latin-1.1.js,1.4,ike-Latn,Eastern Canadian Inuktitut (Latin) -i,inuktitut_naqittaut,Qaniujaaqpait | ᖃᓂᐅᔮᖅᐸᐃᑦ,Arctic,inuktitut_naqittaut-1.3.js,1.6,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics) +i,inuktitut_naqittaut,Qaniujaaqpait | ᖃᓂᐅᔮᖅᐸᐃᑦ,Arctic,inuktitut_naqittaut-1.3.js,1.6.1,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics) fv,fv_uummarmiutun,Uummarmiutun,Arctic,fv_uummarmiutun_kmw-9.0.js,9.1.1,ikt-Latn,Inuinnaqtun (Latin) fv,fv_eastern_canadian_inuktitut,ᐃᓄᒃᑎᑐᑦ (Eastern Canadian Inuktitut),Arctic,fv_eastern_canadian_inuktitut_kmw-9.0.js,9.2.1,ike-Cans,Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics) fv,fv_migmaq,Mi'gmawi'simg / Mi'kmawi'simk,Atlantic,fv_migmaq_kmw-9.0.js,9.1.2,mic-Latn,Mi'kmaq (Latin) @@ -21,7 +21,7 @@ fv,fv_hulquminum_combine,Hul̓q̓umin̓um̓,BC Coast,fv_hulquminum_combine_kmw-9 fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.3,kwk-Latn,Kwakiutl (Latin) fv,fv_kwakwala,Kwak̕wala,BC Coast,fv_kwakwala_kmw-9.0.js,9.2,kwk-Latn,Kwakiutl (Latin) fv,fv_lekwungen,Lekwungen (Lək̓ʷəŋən),BC Coast,fv_lekwungen_kmw-9.0.js,1.0.1,str,Straits Salish -fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,11.0,clm,Clallam +fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,11.0.1,clm,Clallam fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.2.1,ncg-Latn,Nisga'a (Latin) fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.2,nuk-Latn,Nuu-chah-nulth (Latin) fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,10.0.1,blc-Latn,Bella Coola (Latin) @@ -53,14 +53,14 @@ fv,fv_ilnu_innu_aimun,Ilnu-Innu Aimun,Eastern Subarctic,fv_ilnu_innu_aimun_kmw-9 fv,fv_swampy_cree,ᐃᓂᓂᒧᐎᐣ (Swampy Cree),Eastern Subarctic,fv_swampy_cree_kmw-9.0.js,9.2.1,csw-Cans,Swampy Cree (Unified Canadian Aboriginal Syllabics) fv,fv_moose_cree,ᐃᓕᓖᒧᐎᓐ (Moose Cree),Eastern Subarctic,fv_moose_cree_kmw-9.0.js,9.2.1,crm-Cans,Moose Cree (Unified Canadian Aboriginal Syllabics) fv,fv_northern_east_cree,ᐄᔨᔫ-ᐄᓅ ᐊᔨᒨᓐ (Northern East Cree),Eastern Subarctic,fv_northern_east_cree_kmw-9.0.js,9.2.1,crl-Cans,Northern East Cree (Unified Canadian Aboriginal Syllabics) -fv,fv_severn_ojibwa,ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,10.1.1,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics) +fv,fv_severn_ojibwa,ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,10.1.2,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics) fv,fv_severn_ojibwa_rdot,ᐊᓂᔑᓂᓂᒧᐏᐣ (Severn Ojibwa right w-dot),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,1.1.1,ojs,Oji-Cree -fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (a-finals),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,10.1,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics) +fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (a-finals),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,10.1.1,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics) fv,fv_ojibwa_rdot,ᐁᓂᔑᓇᐯᒧᐏᓐ (a-finals right w-dot),Eastern Subarctic,fv_ojibwa_rdot_kmw-9.0.js,1.0.3,oj,Ojibwa -fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0.2,oj,Ojibwa -fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0.2,oj,Ojibwa +fv,fv_ojibwa_ifinal,ᐊᓂᔑᓇᐯᒧᐎᣙ (i-finals),Eastern Subarctic,fv_ojibwa_ifinal_kmw-9.0.js,1.0.3,oj,Ojibwa +fv,fv_ojibwa_ifinal_rdot,ᐊᓂᔑᓇᐯᒧᐏᣙ (i-finals right w-dot),Eastern Subarctic,fv_ojibwa_ifinal_rdot_kmw-9.0.js,1.0.3,oj,Ojibwa fv,fv_naskapi,ᓇᔅᑲᐱ (Naskapi),Eastern Subarctic,fv_naskapi_kmw-9.0.js,9.3.1,nsk-Cans,Naskapi (Unified Canadian Aboriginal Syllabics) -sil,sil_euro_latin,English,European,european2-1.6.js,3.0.2,en,English +sil,sil_euro_latin,English,European,european2-1.6.js,3.0.3,en,English basic,basic_kbdcan,Français,European,canadian_french-1.0.js,1.1.1,fr-CA,French (Canada) fv,fv_anishinaabemowin,Anishinaabemowin,Great Lakes - St. Lawrence,fv_anishinaabemowin_kmw-9.0.js,10.2,oj,Ojibwa fv,fv_bodewadminwen,Bodéwadminwen-Nishnabémwen,Great Lakes - St. Lawrence,fv_bodewadminwen_kmw-9.0.js,9.1.1,pot-Latn,Potawatomi (Latin) @@ -83,7 +83,7 @@ fv,fv_isga_iabi,Isga Iʔabi,Prairies,fv_isga_iabi_kmw-9.0.js,9.1.1,sto-Latn,Ston fv,fv_lakota,Lak̇ot̄a,Prairies,fv_lakota-9.0.js,9.1.1,lkt-Latn,Lakota (Latin) fv,fv_nakoda,Nakoda,Prairies,fv_nakoda_kmw-9.0.js,9.1.1,asb-Latn,Assiniboine (Latin) fv,fv_tsuutina,Tsúùt'ínà,Prairies,fv_tsuutina_kmw-9.0.js,9.1.1,srs-Latn,Sarsi (Latin) -fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,11.1,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics) +fv,fv_plains_cree,ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree),Prairies,fv_plains_cree_kmw-9.0.js,11.1.1,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics) fv,fv_dine_bizaad,Diné Bizaad,South West,fv_dine_bizaad_kmw-9.0.js,9.1.1,nv-Latn,Navajo (Latin) fv,fv_dane_zaa_zaage,Dane-Z̲aa Z̲áágéʔ,Western Subarctic,fv_dane_zaa_zaage_kmw-9.0.js,9.4.1,bea,Beaver fv,fv_dene_dzage,Danezāgéʼ,Western Subarctic,fv_dene_dzage_kmw-9.0.js,11.0.1,kkz-Latn,Kaska (Latin) diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index c264fcbc83..cad414d39b 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -15,8 +15,8 @@ builder_describe "Installation files for FirstVoices Keyboards" \ # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete -builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe +builder_if_release_build_level builder_describe_outputs \ + publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -95,7 +95,7 @@ function do_publish() { # wrap-signcode //d "FirstVoices Keyboards" firstvoices.exe - copy-installer + builder_if_release_build_level copy-installer } function copy-installer() { @@ -103,9 +103,9 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" - cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe" + cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.exe" - builder_if_release_build_level verify-installer-signatures + verify-installer-signatures } function verify-installer-signatures() { diff --git a/oem/firstvoices/windows/src/inst/license.html b/oem/firstvoices/windows/src/inst/license.html index 9c601dc5c7..695bc4b5cf 100644 --- a/oem/firstvoices/windows/src/inst/license.html +++ b/oem/firstvoices/windows/src/inst/license.html @@ -13,7 +13,7 @@

The MIT License

-

Copyright (c) 2001-2021 SIL International, First Peoples' Cultural Foundation

+

Copyright (c) 2001-2025 SIL Global, First Peoples' Cultural Foundation

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/oem/firstvoices/windows/src/inst/license.rtf b/oem/firstvoices/windows/src/inst/license.rtf index d26ff0142c..b549a01f48 100644 --- a/oem/firstvoices/windows/src/inst/license.rtf +++ b/oem/firstvoices/windows/src/inst/license.rtf @@ -31,30 +31,30 @@ {\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0; \red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128; \red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;}{\*\defchp \fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 } -\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang3081\langfe3081\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp3081\langfenp3081 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* \ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 -\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang3081\langfe3081\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp3081\langfenp3081 \snext11 \ssemihidden \sunhideused +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang3081\langfe3081\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp3081\langfenp3081 \snext11 \ssemihidden \sunhideused Normal Table;}}{\*\rsidtbl \rsid7948434\rsid12599147}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator Marc Durdin}{\creatim\yr2021\mo2\dy10\hr9\min58} {\revtim\yr2021\mo2\dy10\hr9\min58}{\version2}{\edmins0}{\nofpages1}{\nofwords173}{\nofchars991}{\nofcharsws1162}{\vern17}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} -\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect \deftab567\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120 \dghorigin1701\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot7948434 \nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1 \pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5 -\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang3081\langfe3081\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp3081\langfenp3081 {\rtlch\fcs1 \ab\af1\afs32 \ltrch\fcs0 \b\f1\fs32\cf1\insrsid7948434 \hich\af1\dbch\af31505\loch\f1 Key\hich\af1\dbch\af31505\loch\f1 man for FirstVoices and }{ -\rtlch\fcs1 \ab\af1\afs32 \ltrch\fcs0 \b\f1\fs32\cf1\insrsid12599147 \hich\af1\dbch\af31505\loch\f1 FirstVoices Keyboards}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 -\par }{\rtlch\fcs1 \ab\af1 \ltrch\fcs0 \b\f1\cf1\insrsid12599147 \hich\af1\dbch\af31505\loch\f1 The MIT License}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 -\par \hich\af1\dbch\af31505\loch\f1 Copyright (c) 2001-2019 SIL International, First Peoples' Cultural Foundation -\par \hich\af1\dbch\af31505\loch\f1 +\rtlch\fcs1 \ab\af1\afs32 \ltrch\fcs0 \b\f1\fs32\cf1\insrsid12599147 \hich\af1\dbch\af31505\loch\f1 FirstVoices Keyboards}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 +\par }{\rtlch\fcs1 \ab\af1 \ltrch\fcs0 \b\f1\cf1\insrsid12599147 \hich\af1\dbch\af31505\loch\f1 The MIT License}{\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 +\par \hich\af1\dbch\af31505\loch\f1 Copyright (c) 2001-2025 SIL Global, First Peoples' Cultural Foundation +\par \hich\af1\dbch\af31505\loch\f1 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, m \hich\af1\dbch\af31505\loch\f1 e\hich\af1\dbch\af31505\loch\f1 rge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \par \hich\af1\dbch\af31505\loch\f1 The above copyright notice and this permission notice shall be included in all \hich\af1\dbch\af31505\loch\f1 copies or substantial portions of the Software. -\par \hich\af1\dbch\af31505\loch\f1 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -\hich\af1\dbch\af31505\loch\f1 +\par \hich\af1\dbch\af31505\loch\f1 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +\hich\af1\dbch\af31505\loch\f1 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.}{ -\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 +\rtlch\fcs1 \af1\afs20 \ltrch\fcs0 \f1\fs20\cf1\insrsid12599147 \par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a 9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad 5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 diff --git a/oem/firstvoices/windows/src/xml/strings.xml b/oem/firstvoices/windows/src/xml/strings.xml index 51b19c060d..e69f624e0c 100644 --- a/oem/firstvoices/windows/src/xml/strings.xml +++ b/oem/firstvoices/windows/src/xml/strings.xml @@ -1147,7 +1147,7 @@ keyboard that you use in Windows. Keyman for FirstVoices will adapt automatical - Keyman debug information will be stored in a log file called %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). You should exit Keyman for FirstVoices before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. + Keyman debug information will be stored in a log file called %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). You should exit Keyman for FirstVoices before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. diff --git a/package-lock.json b/package-lock.json index 0b5221d43c..298a9b2f45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,7 @@ "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" }, "devDependencies": { - "@microsoft/api-documenter": "^7.23.31", + "@microsoft/api-documenter": "^7.28.0", "@microsoft/api-extractor": "^7.47.3", "@types/chai": "^4.3.14", "@types/node": "^20.4.1", @@ -65,7 +65,6 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "mocha": "^11.2.2", - "mocha-teamcity-reporter": "^4.0.0", "playwright": "^1.46.1", "sinon": "^17.0.1", "source-map-support": "^0.5.21", @@ -110,7 +109,6 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, @@ -285,9 +283,7 @@ "supports-color": "^9.4.0" }, "bin": { - "kmc": "build/src/kmc.js", - "kmlmc": "build/src/kmlmc.js", - "kmlmp": "build/src/kmlmp.js" + "kmc": "build/src/kmc.js" }, "devDependencies": { "@sentry/cli": "^2.31.0", @@ -1886,45 +1882,24 @@ "dev": true }, "node_modules/@microsoft/api-documenter": { - "version": "7.23.31", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.31.tgz", - "integrity": "sha512-dHCoUN98ihW5+iro0q31tIOt6t5jwk4DKLq+GGDKlGvepwzP3X2XUkcq3zlh9OtxqLt320+9BvKMiJknZo4SXw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.28.0.tgz", + "integrity": "sha512-XZ7UGhp8hWV7aCq+G2pY3Bg0h1dEyIbqXbLW6txULEF0WP71qhQAHCUlMLvmB5PsQp7+gKqdOtj2l2H2MICD+w==", "dev": true, + "license": "MIT", "dependencies": { - "@microsoft/api-extractor-model": "7.28.13", - "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "4.0.2", - "@rushstack/terminal": "0.10.0", - "@rushstack/ts-command-line": "4.17.4", - "js-yaml": "~3.13.1", + "@microsoft/api-extractor-model": "7.32.0", + "@microsoft/tsdoc": "~0.16.0", + "@rushstack/node-core-library": "5.18.0", + "@rushstack/terminal": "0.19.3", + "@rushstack/ts-command-line": "5.1.3", + "js-yaml": "~4.1.0", "resolve": "~1.22.1" }, "bin": { "api-documenter": "bin/api-documenter" } }, - "node_modules/@microsoft/api-documenter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@microsoft/api-extractor": { "version": "7.47.3", "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.3.tgz", @@ -1950,14 +1925,15 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.28.13", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.13.tgz", - "integrity": "sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==", + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.32.0.tgz", + "integrity": "sha512-QIVJSreb8fGGJy1Qx0yzGVXxvHJN1WXgkFNHFheVv1iBJNqgvp+xeT3ienJmRwXmPPc5Es/cxBrXtKZJR3i7iw==", "dev": true, + "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "4.0.2" + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.0", + "@rushstack/node-core-library": "5.18.0" } }, "node_modules/@microsoft/api-extractor/node_modules/@microsoft/api-extractor-model": { @@ -2029,20 +2005,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@microsoft/api-extractor/node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/terminal": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.13.2.tgz", @@ -2166,32 +2128,35 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", - "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", - "dev": true + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "dev": true, + "license": "MIT" }, "node_modules/@microsoft/tsdoc-config": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz", - "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz", + "integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==", "dev": true, + "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "0.14.2", - "ajv": "~6.12.6", + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.12.0", "jju": "~1.4.0", - "resolve": "~1.19.0" + "resolve": "~1.22.2" } }, "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -2199,25 +2164,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "dev": true, - "dependencies": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2930,17 +2876,20 @@ ] }, "node_modules/@rushstack/node-core-library": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-4.0.2.tgz", - "integrity": "sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.18.0.tgz", + "integrity": "sha512-XDebtBdw5S3SuZIt+Ra2NieT8kQ3D2Ow1HxhDQ/2soinswnOu9e7S69VSwTOLlQnx5mpWbONu+5JJjDxMAb6Fw==", "dev": true, + "license": "MIT", "dependencies": { - "fs-extra": "~7.0.1", + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", - "semver": "~7.5.4", - "z-schema": "~5.0.2" + "semver": "~7.5.4" }, "peerDependencies": { "@types/node": "*" @@ -2951,6 +2900,71 @@ } } }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", + "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@rushstack/rig-package": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", @@ -2962,12 +2976,14 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.10.0.tgz", - "integrity": "sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.3.tgz", + "integrity": "sha512-0P8G18gK9STyO+CNBvkKPnWGMxESxecTYqOcikHOVIHXa9uAuTK+Fw8TJq2Gng1w7W6wTC9uPX6hGNvrMll2wA==", "dev": true, + "license": "MIT", "dependencies": { - "@rushstack/node-core-library": "4.0.2", + "@rushstack/node-core-library": "5.18.0", + "@rushstack/problem-matcher": "0.1.1", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -2984,6 +3000,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -2995,12 +3012,13 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.4.tgz", - "integrity": "sha512-XPQQDaxgFqRHFRgt7jjCKnr0vrC75s/+ISU6kGhWpDlGzWl4vig6ZfZTs3HgM6Kh1Bb3wUKSyKQOV+G36cyZfg==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.3.tgz", + "integrity": "sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==", "dev": true, + "license": "MIT", "dependencies": { - "@rushstack/terminal": "0.10.0", + "@rushstack/terminal": "0.19.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -3011,6 +3029,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -3020,6 +3039,7 @@ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } @@ -4751,10 +4771,11 @@ } }, "node_modules/ajv-cli/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4775,6 +4796,21 @@ "node": ">=6" } }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -9428,7 +9464,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -9596,6 +9634,19 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jszip": { "version": "3.9.1", "license": "(MIT OR GPL-3.0-or-later)", @@ -9875,12 +9926,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", @@ -10381,17 +10426,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha-teamcity-reporter": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "mocha": ">=6" - } - }, "node_modules/mocha/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -12957,9 +12991,9 @@ } }, "node_modules/tar-fs": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", - "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "dev": true, "license": "MIT", "dependencies": { @@ -13502,6 +13536,16 @@ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", "license": "ISC" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "license": "MIT", @@ -13589,15 +13633,6 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, - "node_modules/validator": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", - "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -14054,36 +14089,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/z-schema": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", - "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", - "dev": true, - "dependencies": { - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "validator": "^13.7.0" - }, - "bin": { - "z-schema": "bin/z-schema" - }, - "engines": { - "node": ">=8.0.0" - }, - "optionalDependencies": { - "commander": "^9.4.1" - } - }, - "node_modules/z-schema/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "optional": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", @@ -14364,7 +14369,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/resources-gosh": "*", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, @@ -14376,7 +14380,6 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10", "typescript": "^5.4.5" } @@ -14395,7 +14398,6 @@ "@keymanapp/web-utils": "*", "@types/mocha": "^7.0.2", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, @@ -14414,7 +14416,6 @@ "@keymanapp/resources-gosh": "*", "@types/mocha": "^7.0.2", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, @@ -14432,7 +14433,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, @@ -14454,7 +14454,6 @@ "@keymanapp/resources-gosh": "*", "c8": "^7.12.0", "combine-source-map": "^0.8.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" } }, diff --git a/package.json b/package.json index 5cdef98255..87b62dfe63 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "root", "private": true, "devDependencies": { - "@microsoft/api-documenter": "^7.23.31", + "@microsoft/api-documenter": "^7.28.0", "@microsoft/api-extractor": "^7.47.3", "@types/chai": "^4.3.14", "@types/node": "^20.4.1", @@ -22,7 +22,6 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", "mocha": "^11.2.2", - "mocha-teamcity-reporter": "^4.0.0", "playwright": "^1.46.1", "sinon": "^17.0.1", "source-map-support": "^0.5.21", @@ -74,6 +73,10 @@ "fast-json-patch": "^3.1.1" } }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + }, "engines": { "node": "20.16.0" } diff --git a/resources/build/builder-basic.inc.sh b/resources/build/builder-basic.inc.sh index 83f77c3134..a2077b081a 100644 --- a/resources/build/builder-basic.inc.sh +++ b/resources/build/builder-basic.inc.sh @@ -13,6 +13,7 @@ # KEYMAN_VERSION_GIT_TAG: Git tag for the release, "release@$KEYMAN_VERSION_WITH_TAG", e.g. "release@14.0.1-alpha-test-1234" # KEYMAN_ROOT: fully resolved root path of Keyman repository # KEYMAN_VERSION_ENVIRONMENT: One of: local, test, alpha, beta, stable +# KEYMAN_VERSION_FOR_FILENAME: KEYMAN_VERSION, for release builds, or KEYMAN_VERSION_WITH_TAG, for PR and local builds; see #10521 # UPLOAD_SENTRY: true - if KEYMAN_VERSION_ENVIRONMENT is one of alpha, beta, stable # false - if local, test. Indicates if debug artifacts should be uploaded to Sentry # BUILDER_OS: win|mac|linux -- current build environment @@ -53,6 +54,7 @@ function _builder_basic_find_keyman_root() { KEYMAN_ROOT="${BASH_SOURCE[0]%/*/*/*}" KEYMAN_ROOT="$( cd "$KEYMAN_ROOT" && echo "$PWD" )" readonly KEYMAN_ROOT + export KEYMAN_ROOT fi } @@ -115,6 +117,16 @@ function _builder_basic_find_version() { KEYMAN_VERSION_WITH_TAG="$KEYMAN_VERSION$KEYMAN_VERSION_TAG" KEYMAN_VERSION_GIT_TAG="release@$KEYMAN_VERSION_WITH_TAG" + # For local builds, and for PR builds, we will use a filename including the tag + # but for release builds, we omit the tag for now. In the future, we may include + # the tag but that involves changing a number of other websites as dealing with + # the rename there too. + if builder_is_ci_release_build; then + KEYMAN_VERSION_FOR_FILENAME="${KEYMAN_VERSION}" + else + KEYMAN_VERSION_FOR_FILENAME="${KEYMAN_VERSION_WITH_TAG}" + fi + readonly KEYMAN_VERSION readonly KEYMAN_VERSION_MAJOR readonly KEYMAN_VERSION_MINOR @@ -125,6 +137,7 @@ function _builder_basic_find_version() { readonly KEYMAN_VERSION_WITH_TAG readonly KEYMAN_VERSION_ENVIRONMENT readonly KEYMAN_VERSION_GIT_TAG + readonly KEYMAN_VERSION_FOR_FILENAME # Export version strings so places like version.gradle can access them export KEYMAN_VERSION @@ -137,6 +150,7 @@ function _builder_basic_find_version() { export KEYMAN_VERSION_WITH_TAG export KEYMAN_VERSION_ENVIRONMENT export KEYMAN_VERSION_GIT_TAG + export KEYMAN_VERSION_FOR_FILENAME } function _builder_basic_find_tier() { @@ -163,18 +177,19 @@ function _builder_basic_print_build_number_for_teamcity() { } function _builder_basic_print_version_utils_debug() { - echo "KEYMAN_ROOT: $KEYMAN_ROOT" - echo "KEYMAN_VERSION: $KEYMAN_VERSION" - echo "KEYMAN_VERSION_WIN: $KEYMAN_VERSION_WIN" - echo "KEYMAN_VERSION_RELEASE: $KEYMAN_VERSION_RELEASE" - echo "KEYMAN_VERSION_MAJOR: $KEYMAN_VERSION_MAJOR" - echo "KEYMAN_VERSION_MINOR: $KEYMAN_VERSION_MINOR" - echo "KEYMAN_VERSION_PATCH: $KEYMAN_VERSION_PATCH" - echo "KEYMAN_TIER: $KEYMAN_TIER" - echo "KEYMAN_VERSION_TAG: $KEYMAN_VERSION_TAG" - echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" - echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" - echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" + echo "KEYMAN_ROOT: $KEYMAN_ROOT" + echo "KEYMAN_VERSION: $KEYMAN_VERSION" + echo "KEYMAN_VERSION_WIN: $KEYMAN_VERSION_WIN" + echo "KEYMAN_VERSION_RELEASE: $KEYMAN_VERSION_RELEASE" + echo "KEYMAN_VERSION_MAJOR: $KEYMAN_VERSION_MAJOR" + echo "KEYMAN_VERSION_MINOR: $KEYMAN_VERSION_MINOR" + echo "KEYMAN_VERSION_PATCH: $KEYMAN_VERSION_PATCH" + echo "KEYMAN_TIER: $KEYMAN_TIER" + echo "KEYMAN_VERSION_TAG: $KEYMAN_VERSION_TAG" + echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" + echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" + echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" + echo "KEYMAN_VERSION_FOR_FILENAME: $KEYMAN_VERSION_FOR_FILENAME" } # TODO: consolidate with buildLevel, see #14285 @@ -209,6 +224,7 @@ replaceVersionStrings() { s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION/g; + s/\$KEYMAN_VERSION_FOR_FILENAME/$KEYMAN_VERSION_FOR_FILENAME/g; " "$infile" > "$outfile" } @@ -249,6 +265,7 @@ replaceVersionStrings_Mkver() { s/\$KEYMAN_VERSION_WITH_TAG/$KEYMAN_VERSION_WITH_TAG/g; s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; + s/\$KEYMAN_VERSION_FOR_FILENAME/$KEYMAN_VERSION_FOR_FILENAME/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION_WIN/g; diff --git a/resources/build/ci/cancel-builds/trigger_definitions.mjs b/resources/build/ci/cancel-builds/trigger_definitions.mjs index f6acdf91bb..aef0526688 100644 --- a/resources/build/ci/cancel-builds/trigger_definitions.mjs +++ b/resources/build/ci/cancel-builds/trigger_definitions.mjs @@ -6,7 +6,7 @@ export const testBuildConfigurations = { 'mac': ['Keyman_KeymanMac_PullRequests', 'Keyman_Common_KPAPI_TestPullRequests_macOS'], 'windows': ['KeymanDesktop_TestPullRequests', 'KeymanDesktop_TestPrRenderOnScreenKeyboards', 'Keyman_Common_KPAPI_TestPullRequests_Windows'], 'web': ['Keymanweb_TestPullRequests', 'Keyman_Common_KPAPI_TestPullRequests_WASM'], - 'developer': ['Keyman_Developer_Test'], + 'developer': ['Keyman_Developer_Test', 'npm-publish_GitHub'], 'common_web': ['Keyman_Test_Common_Web'], 'common_windows': ['Keyman_Test_Common_Windows'], diff --git a/resources/build/ci/ci-publish.inc.sh b/resources/build/ci/ci-publish.inc.sh index d8fc1e77a2..2785fead35 100644 --- a/resources/build/ci/ci-publish.inc.sh +++ b/resources/build/ci/ci-publish.inc.sh @@ -8,39 +8,10 @@ . "$KEYMAN_ROOT/resources/build/jq.inc.sh" -# -# Publishes the package in `cwd` to npm -# -# If the `--dry-run` option is available and specified as a command-line -# parameter, will do a dry run -# -# Note that `package.json` will be dirty after this command, as the `version` -# field will be added to it, and @keymanapp dependency versions will also be -# modified. This change should not be committed to the repository. -# -# If --npm-publish is set: -# * then ci_publish_npm publishes to the public registry -# * else ci_publish_npm creates a local tarball which can be used to test -# -# Usage: -# ```bash -# ci_publish_npm -# ``` -# -function ci_publish_npm() { - if builder_has_option --npm-publish; then - # Require --dry-run if local or test to avoid accidentally clobbering npm packages - if [[ $KEYMAN_VERSION_ENVIRONMENT =~ local|test ]] && ! builder_has_option --dry-run; then - builder_die "publish --npm-publish must use --dry-run flag for local or test builds" - fi - _ci_publish_npm_package publish - else - _ci_publish_npm_package pack - fi -} - -function _ci_publish_npm_package() { - local action=$1 +# Used only by npm-publish.sh +function ci_publish_npm_package() { + local action="$1" + local package="$2" local dist_tag=$KEYMAN_TIER dry_run= if [[ $KEYMAN_TIER == stable ]]; then @@ -51,6 +22,8 @@ function _ci_publish_npm_package() { dry_run=--dry-run fi + pushd "${KEYMAN_ROOT}/${package}" >/dev/null + _ci_publish_cache_package_json _ci_write_npm_version _ci_prepublish @@ -62,12 +35,14 @@ function _ci_publish_npm_package() { if [[ $action == pack ]]; then # We can use --publish-to-pack to locally test a package # before publishing to the package registry - echo "Packing $dry_run npm package $THIS_SCRIPT_IDENTIFIER with tag $dist_tag" + echo "Packing $dry_run npm package ${package} with tag $dist_tag" npm pack $dry_run --access public --tag $dist_tag else # $action == publish - echo "Publishing $dry_run npm package $THIS_SCRIPT_IDENTIFIER with tag $dist_tag" + echo "Publishing $dry_run npm package ${package} with tag $dist_tag" npm publish $dry_run --access public --tag $dist_tag fi + + popd >/dev/null } function _ci_write_npm_version() { diff --git a/resources/build/ci/github/api-pull-commits.mjs b/resources/build/ci/github/api-pull-commits.mjs new file mode 100644 index 0000000000..2cc22bdf49 --- /dev/null +++ b/resources/build/ci/github/api-pull-commits.mjs @@ -0,0 +1,32 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-08-28 + * + * Paginate large commit lists for PRs so we get all build commands + */ +import { Octokit } from "octokit"; + +if(process.argv.length < 4) { + console.log(`Usage: node ${process.argv[1]} `); + process.exit(1); +} + +const GITHUB_TOKEN = process.argv[2]; +const pull_number = process.argv[3]; + +const octokit = new Octokit({ + auth: GITHUB_TOKEN +}); + +const data = await octokit.paginate("GET /repos/{owner}/{repo}/pulls/{pull_number}/commits", { + owner: "keymanapp", + repo: "keyman", + pull_number, + per_page: 100, + headers: { + "X-GitHub-Api-Version": "2022-11-28", + }, +}); + +process.stdout.write(JSON.stringify(data)); diff --git a/resources/build/ci/github/package-lock.json b/resources/build/ci/github/package-lock.json new file mode 100644 index 0000000000..80caa19b82 --- /dev/null +++ b/resources/build/ci/github/package-lock.json @@ -0,0 +1,434 @@ +{ + "name": "github-integration", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-integration", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "octokit": "^5.0.3" + } + }, + "node_modules/@octokit/app": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.0.tgz", + "integrity": "sha512-OdKHnm0CYLk8Setr47CATT4YnRTvWkpTYvE+B/l2B0mjszlfOIit3wqPHVslD2jfc1bD4UbO7Mzh6gjCuMZKsA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^8.1.0", + "@octokit/auth-unauthenticated": "^7.0.1", + "@octokit/core": "^7.0.2", + "@octokit/oauth-app": "^8.0.1", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/types": "^14.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-app": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.1.0.tgz", + "integrity": "sha512-6bWhyvLXqCSfHiqlwzn9pScLZ+Qnvh/681GR/UEEPCMIVwfpRDBw0cCzy3/t2Dq8B7W2X/8pBgmw6MOiyE0DXQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.1", + "@octokit/auth-oauth-user": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz", + "integrity": "sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.1", + "@octokit/auth-oauth-user": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz", + "integrity": "sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz", + "integrity": "sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.1", + "@octokit/oauth-methods": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.1.tgz", + "integrity": "sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.3.tgz", + "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.1", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.1.tgz", + "integrity": "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.1.tgz", + "integrity": "sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.1", + "@octokit/auth-oauth-user": "^6.0.0", + "@octokit/auth-unauthenticated": "^7.0.1", + "@octokit/core": "^7.0.2", + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/oauth-methods": "^6.0.0", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz", + "integrity": "sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz", + "integrity": "sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", + "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz", + "integrity": "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.1.tgz", + "integrity": "sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.1.tgz", + "integrity": "sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": "^7.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.1.3.tgz", + "integrity": "sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-webhooks-types": "12.0.3", + "@octokit/request-error": "^7.0.0", + "@octokit/webhooks-methods": "^6.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", + "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.152", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.152.tgz", + "integrity": "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/octokit": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.3.tgz", + "integrity": "sha512-+bwYsAIRmYv30NTmBysPIlgH23ekVDriB07oRxlPIAH5PI0yTMSxg5i5Xy0OetcnZw+nk/caD4szD7a9YZ3QyQ==", + "license": "MIT", + "dependencies": { + "@octokit/app": "^16.0.1", + "@octokit/core": "^7.0.2", + "@octokit/oauth-app": "^8.0.1", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-rest-endpoint-methods": "^16.0.0", + "@octokit/plugin-retry": "^8.0.1", + "@octokit/plugin-throttling": "^11.0.1", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + } + } +} diff --git a/resources/build/ci/github/package.json b/resources/build/ci/github/package.json new file mode 100644 index 0000000000..fc1a983fc9 --- /dev/null +++ b/resources/build/ci/github/package.json @@ -0,0 +1,15 @@ +{ + "name": "github-integration", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "description": "", + "private": true, + "dependencies": { + "octokit": "^5.0.3" + } +} diff --git a/developer/src/packages.inc.sh b/resources/build/ci/npm-packages.inc.sh similarity index 79% rename from developer/src/packages.inc.sh rename to resources/build/ci/npm-packages.inc.sh index eea2670ba0..3ede520012 100644 --- a/developer/src/packages.inc.sh +++ b/resources/build/ci/npm-packages.inc.sh @@ -1,7 +1,8 @@ +# +# Keyman is copyright (C) SIL Global. MIT License. +# # List of all NPM packages that need to be published # -# TODO: this should really be somewhere else, but right now Developer CI is -# responsible for pulling the publish lever readonly PACKAGES=( common/web/keyman-version diff --git a/resources/build/ci/npm-publish.sh b/resources/build/ci/npm-publish.sh new file mode 100755 index 0000000000..e9b522e2aa --- /dev/null +++ b/resources/build/ci/npm-publish.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# +# Keyman is copyright (C) SIL Global. MIT License. +# +# Publish all the @keymanapp packages listed in npm-packages.inc.sh +# +# If the `--dry-run` option is available and specified as a command-line +# parameter, will do a dry run +# +# Note that `package.json` will be dirty after this command, as the `version` +# field will be added to it, and @keymanapp dependency versions will also be +# modified. This change should not be committed to the repository. +# +# If `publish` is called: +# * then ci_publish_npm publishes to the public registry +# * else ci_publish_npm creates a local tarball which can be used to test +# +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder-full.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh" +. "$KEYMAN_ROOT/resources/build/ci/npm-packages.inc.sh" +. "$KEYMAN_ROOT/resources/build/minimum-versions.inc.sh" + +builder_describe \ + "Publish @keymanapp packages to NPM" \ + "configure Install required modules for build on GHA" \ + "pack Pack NPM packages to a .tgz for verification" \ + "publish Publish NPM packages to the NPM Registry" \ + "--dry-run Don't publish/pack anything, just dry run" + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +function do_pack() { + do_package_builds + do_pack_or_publish pack +} + +function do_package_builds() { + local npm_package_path + for npm_package_path in "${PACKAGES[@]}"; do + builder_heading "Building ${npm_package_path}" + "${KEYMAN_ROOT}/${npm_package_path}/build.sh" build test + done +} + +function inject_sourcemaps() { + local SOURCEMAP_PATHS + SOURCEMAP_PATHS=( "${PACKAGES[@]}" ) + SOURCEMAP_PATHS=( "${SOURCEMAP_PATHS[@]/%//build}" ) + + builder_heading "Injecting sourcemaps" + + pushd "${KEYMAN_ROOT}" + + sentry-cli sourcemaps inject \ + --org keyman \ + --project keyman-developer \ + --release "$KEYMAN_VERSION_GIT_TAG" \ + "${SOURCEMAP_PATHS[@]}" + + if [[ "${GHA_TEST_BUILD+x}" == false ]]; then + builder_heading "Uploading sourcemaps" + + sentry-cli sourcemaps upload \ + --no-dedupe \ + --org keyman \ + --project keyman-developer \ + --release "$KEYMAN_VERSION_GIT_TAG" \ + --ext js --ext mjs --ext ts --ext map \ + "${SOURCEMAP_PATHS[@]}" + else + builder_heading "Skipping upload of sourcemaps (test build)" + fi + + popd +} + +function do_pack_or_publish() { + local npm_action="$1" + local npm_package_path + + inject_sourcemaps + + for npm_package_path in "${PACKAGES[@]}"; do + builder_heading "npm ${npm_action} ${npm_package_path}" + ci_publish_npm_package "${npm_action}" "${npm_package_path}" + done +} + +function do_publish() { + if [[ $KEYMAN_VERSION_ENVIRONMENT =~ local|test ]] && ! builder_has_option --dry-run; then + builder_die "publish must use --dry-run flag for local or test builds" + fi + + do_package_builds + do_pack_or_publish publish +} + +function install_emscripten() { + builder_heading "Installing emscripten" + + local EMSDK_TEMP + EMSDK_TEMP=$(mktemp -d) + pushd "${EMSDK_TEMP}" + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" + ./emsdk activate "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" + cd upstream/emscripten + npm install + echo "EMSCRIPTEN_BASE=$(pwd)" >> $GITHUB_ENV + EMSCRIPTEN_BASE="$(pwd)" + export EMSCRIPTEN_BASE + popd +} + +function install_meson() { + builder_heading "Installing meson" + + builder_echo "Starting apt-get update + install meson" + sudo apt-get update + sudo apt-get install meson + echo "meson version:" + meson -v +} + +function install_sentry_cli() { + builder_heading "Installing sentry-cli" + + curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="2.57.0" sh + sentry-cli --version +} + +function do_configure() { + install_meson + install_emscripten + install_sentry_cli +} + +builder_run_action configure do_configure +builder_run_action pack do_pack +builder_run_action publish do_publish diff --git a/resources/build/ci/trigger-build-bot.inc.sh b/resources/build/ci/trigger-build-bot.inc.sh index f7cdc815f8..d9fd38294f 100644 --- a/resources/build/ci/trigger-build-bot.inc.sh +++ b/resources/build/ci/trigger-build-bot.inc.sh @@ -39,6 +39,34 @@ function test_bot_check_pr_body() { fi } +# +# Check the Keyman-Api-Check command whether or not to run API checks. +# Any value other than 'skip' or no Keyman-Api-Check command will run +# the API checks. +# +# Parameters: +# 1: PR number (not currently used) +# 2: PR JSON data from api.github.com/repos/keymanapp/keyman/pulls/# +# +function is_skip_keyman_api_check() { + local PRNUM=$1 + local prinfo="$2" + local prbody prApiCheck + + set -o noglob + IFS=$'\n' + prbody="$(echo "${prinfo}" | "${JQ}" -r '.body')" + prApiCheck="$(echo -e "${prbody}" | grep '^Keyman-Api-Check:' | cut -d: -f 2 - | tr -d '[:space:]')" + unset IFS + set +o noglob + + if [[ "${prApiCheck}" == "skip" ]]; then + echo "true" + else + echo "false" + fi +} + # # Check PR commit messages for Build-bot commands. Later commands override # earlier ones. Also checks PR body for any overriding commands. Note, only diff --git a/resources/build/ci/trigger-builds.inc.sh b/resources/build/ci/trigger-builds.inc.sh index 0d54971b15..d1110a19a1 100644 --- a/resources/build/ci/trigger-builds.inc.sh +++ b/resources/build/ci/trigger-builds.inc.sh @@ -84,31 +84,34 @@ function triggerTeamCityBuild() { # 2: 'skip' - don't run build; 'build' - run build and unit tests; 'test' - run build, unit tests, and deploy # 3: Action name # 4: branch name in git +# 5: 'true' to skip API checks, 'false' to run them (optional, default 'false') # function triggerGitHubActionsBuild() { local IS_TEST_BUILD="$1" local BUILD_LEVEL="$2" local GITHUB_ACTION="$3" local GIT_BRANCH="$4" + local SKIP_API_CHECK="${5:-"false"}" local GIT_BASE_BRANCH="${GIT_BRANCH}" local GIT_USER="keyman-server" local GIT_BUILD_SHA GIT_BASE_REF JSON local GITHUB_SERVER=https://api.github.com/repos/keymanapp/keyman - if [ "${action:-""}" == "commit" ]; then + if [[ "${action:-""}" == "commit" ]]; then # This will only be true if we created and pushed a tag - GIT_BUILD_SHA="$(git rev-parse "refs/tags/release@$KEYMAN_VERSION_WITH_TAG")" + GIT_BUILD_SHA="$(git rev-parse "refs/tags/release@${KEYMAN_VERSION_WITH_TAG}")" GIT_BASE_REF="$(git rev-parse "${GIT_BUILD_SHA}^")" GIT_EVENT_TYPE="${GITHUB_ACTION}: release@${KEYMAN_VERSION_WITH_TAG}" - elif [[ $GIT_BRANCH =~ ^[0-9]+$ ]]; then + elif [[ ${GIT_BRANCH} =~ ^[0-9]+$ ]]; then # pull request - local JSON=$(call_curl "${GITHUB_SERVER}/pulls/${GIT_BRANCH}" --header "Authorization: token $GITHUB_TOKEN") - GIT_BUILD_SHA="$(echo "$JSON" | $JQ -r '.head.sha')" + local JSON + JSON=$(call_curl "${GITHUB_SERVER}/pulls/${GIT_BRANCH}" --header "Authorization: token ${GITHUB_TOKEN}") + GIT_BUILD_SHA="$(echo "${JSON}" | ${JQ} -r '.head.sha')" GIT_EVENT_TYPE="${GITHUB_ACTION}: PR #${GIT_BRANCH}" - GIT_USER="$(echo "$JSON" | $JQ -r '.user.login')" - GIT_BASE_BRANCH="$(echo "$JSON" | $JQ -r '.base.ref')" - GIT_BASE_REF="$(echo "$JSON" | $JQ -r '.base.sha')" + GIT_USER="$(echo "${JSON}" | ${JQ} -r '.user.login')" + GIT_BASE_BRANCH="$(echo "${JSON}" | ${JQ} -r '.base.ref')" + GIT_BASE_REF="$(echo "${JSON}" | ${JQ} -r '.base.sha')" GIT_BRANCH="PR-${GIT_BRANCH}" else # another branch: stable-x.y, beta, or master @@ -118,20 +121,21 @@ function triggerGitHubActionsBuild() { fi local DATA="{ - \"event_type\": \"$GIT_EVENT_TYPE\", \ + \"event_type\": \"${GIT_EVENT_TYPE}\", \ \"client_payload\": { \ - \"buildSha\": \"$GIT_BUILD_SHA\", \ - \"branch\": \"$GIT_BRANCH\", \ - \"baseBranch\": \"$GIT_BASE_BRANCH\", \ - \"baseRef\": \"$GIT_BASE_REF\", \ - \"user\": \"$GIT_USER\", \ - \"isTestBuild\": \"$IS_TEST_BUILD\", \ - \"buildLevel\": \"$BUILD_LEVEL\" \ + \"buildSha\": \"${GIT_BUILD_SHA}\", \ + \"branch\": \"${GIT_BRANCH}\", \ + \"baseBranch\": \"${GIT_BASE_BRANCH}\", \ + \"baseRef\": \"${GIT_BASE_REF}\", \ + \"user\": \"${GIT_USER}\", \ + \"isTestBuild\": \"${IS_TEST_BUILD}\", \ + \"buildLevel\": \"${BUILD_LEVEL}\", \ + \"skipApiCheck\": \"${SKIP_API_CHECK}\" \ }}" - echo "GitHub Action Data: $DATA" + echo "GitHub Action Data: ${DATA}" - if [[ "$GIT_BUILD_SHA" == null ]]; then + if [[ "${GIT_BUILD_SHA}" == null ]]; then echo "Invalid GIT_BUILD_SHA" exit 1 fi @@ -141,8 +145,8 @@ function triggerGitHubActionsBuild() { call_curl "${GITHUB_SERVER}/dispatches" \ --request POST \ --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ - --data "$DATA" + --header "Authorization: token ${GITHUB_TOKEN}" \ + --data "${DATA}" echo } @@ -157,31 +161,32 @@ function call_curl() { local curl_result local http_code - local output_file=$(mktemp) + local output_file + output_file=$(mktemp) set +e if [[ $# -gt 0 ]]; then - http_code=$(curl --silent --output $output_file --no-progress-meter --write-out "%{http_code}" "$@" "$url") + http_code=$(curl --silent --output "${output_file}" --no-progress-meter --write-out "%{http_code}" "$@" "${url}") else - http_code=$(curl --silent --output $output_file --no-progress-meter --write-out "%{http_code}" "$url") + http_code=$(curl --silent --output "${output_file}" --no-progress-meter --write-out "%{http_code}" "${url}") fi curl_result=$? if [[ ${curl_result} != 0 ]]; then echo "curl call to '${url}' failed with code '${curl_result}'" >& 2 - >&2 cat $output_file - rm -f $output_file - exit $curl_result + >&2 cat "${output_file}" + rm -f "${output_file}" + exit ${curl_result} fi if [[ ${http_code} -lt 200 || ${http_code} -gt 299 ]] ; then echo "curl call to '${url}' failed with HTTP error code '${http_code}'" >& 2 - >&2 cat $output_file - rm -f $output_file + >&2 cat "${output_file}" + rm -f "${output_file}" exit 22 fi set -e - cat $output_file - rm -f $output_file + cat "${output_file}" + rm -f "${output_file}" } diff --git a/resources/build/ci/trigger-definitions.inc.sh b/resources/build/ci/trigger-definitions.inc.sh index 74380e7311..ab08734804 100644 --- a/resources/build/ci/trigger-definitions.inc.sh +++ b/resources/build/ci/trigger-definitions.inc.sh @@ -56,7 +56,7 @@ watch_ios='web|common/web' watch_linux='core|common/test/keyboards/baseline' watch_mac='core' watch_web='common/web|core' -watch_windows='common|core|web' +watch_windows='common|core' watch_developer='common|core|web' # Note, currently common_web build is a no-op because we rely on running the web @@ -89,7 +89,7 @@ bc_test_linux=(KeymanLinux_TestPullRequests Keyman_Linux_Test_Integration Keyman bc_test_mac=(Keyman_KeymanMac_PullRequests Keyman_Common_KPAPI_TestPullRequests_macOS) bc_test_windows=(KeymanDesktop_TestPullRequests KeymanDesktop_TestPrRenderOnScreenKeyboards Keyman_Common_KPAPI_TestPullRequests_Windows) bc_test_web=(Keymanweb_TestPullRequests Keyman_Common_KPAPI_TestPullRequests_WASM) -bc_test_developer=(Keyman_Developer_Test Keyman_Test_Developer_Mac Keyman_Test_Developer_Linux) +bc_test_developer=(Keyman_Developer_Test Keyman_Test_Developer_Mac Keyman_Test_Developer_Linux npm-publish_GitHub) bc_test_common_web=(Keyman_Test_Common_Web) bc_test_common_windows=(Keyman_Test_Common_Windows) @@ -112,11 +112,11 @@ vcs_test=HttpsGithubComKeymanappKeymanPRs bc_master_android=(KeymanAndroid_Build) bc_master_ios=(Keyman_iOS_Master) -bc_master_linux=(KeymanLinux_Master deb-release-packaging_GitHub) +bc_master_linux=(KeymanLinux_Master deb-release-packaging_GitHub build-test-publish-docker_GitHub) bc_master_mac=(KeymanMac_Master) bc_master_windows=(Keyman_Build) bc_master_web=(Keymanweb_Build) -bc_master_developer=(Keyman_Developer_Release) +bc_master_developer=(Keyman_Developer_Release npm-publish_GitHub) vcs_master=HttpsGithubComKeymanappKeyman @@ -128,7 +128,7 @@ bc_beta_linux=(KeymanLinux_Master deb-release-packaging_GitHub) bc_beta_mac=(KeymanMac_Master) bc_beta_windows=(Keyman_Build) bc_beta_web=(Keymanweb_Build) -bc_beta_developer=(Keyman_Developer_Release) +bc_beta_developer=(Keyman_Developer_Release npm-publish_GitHub) vcs_beta=HttpsGithubComKeymanappKeyman @@ -140,7 +140,7 @@ bc_stable_18_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub) bc_stable_18_0_mac=(KeymanMac_Master) bc_stable_18_0_windows=(Keyman_Build) bc_stable_18_0_web=(Keymanweb_Build) -bc_stable_18_0_developer=(Keyman_Developer_Release) +bc_stable_18_0_developer=(Keyman_Developer_Release npm-publish_GitHub) vcs_stable_18_0=HttpsGithubComKeymanappKeyman @@ -152,6 +152,6 @@ bc_stable_19_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub) bc_stable_19_0_mac=(KeymanMac_Master) bc_stable_19_0_windows=(Keyman_Build) bc_stable_19_0_web=(Keymanweb_Build) -bc_stable_19_0_developer=(Keyman_Developer_Release) +bc_stable_19_0_developer=(Keyman_Developer_Release npm-publish_GitHub) vcs_stable_19_0=HttpsGithubComKeymanappKeyman diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index 5bcbf3cc12..0ceaef15b0 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -3,7 +3,7 @@ # This is a list of minimum, maximum, and specific versions of any external # components or dependencies found in Keyman. # -# Original (outdated) reference +# Original (outdated) reference # https://docs.google.com/document/d/1Uy3U2YXeA4rCEbUbT7O6QzUGJDZBeViFecL8fjUOLkE/edit?usp=sharing # NOTE: After changing any values, you should run publish-minimum-versions.sh. @@ -29,8 +29,8 @@ KEYMAN_MIN_TARGET_VERSION_WEB_SAFARI=13.0 # iOS 13.0, macOS 10.13.6+ KEYMAN_MIN_VERSION_NODE_MAJOR=20 # node version source of truth is /package.json:/engines/node; use KEYMAN_USE_NVM to automatically update KEYMAN_MIN_VERSION_NPM=10.5.1 # 10.5.0 has bug, discussed in #10350 KEYMAN_MIN_VERSION_EMSCRIPTEN=3.1.58 # Use KEYMAN_USE_EMSDK to automatically update to this version -KEYMAN_MIN_VERSION_VISUAL_STUDIO=2019 -KEYMAN_MIN_VERSION_MESON=1.0.0 +KEYMAN_MIN_VERSION_VISUAL_STUDIO=2022 # Visual Studio 2022, see /docs/build/windows.md for workloads and components +KEYMAN_MIN_VERSION_MESON=1.0.0 # TODO: rename meson_options.txt to meson.options when updating to >= 1.1, see #15127. KEYMAN_VERSION_GRADLE=8.12 # See /android/KMEA/gradle/wrapper/gradle-wrapper.properties @@ -50,4 +50,4 @@ KEYMAN_VERSION_CLDR=46 # LDML Keyboards version KEYMAN_VERSION_ISO639_3=2024-05-22 # Date of last import KEYMAN_VERSION_LANGTAGS=2025-02-18 # _version value KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY=2025-03-10 # Date from first line of language-subtag-registry -KEYMAN_VERSION_UNICODE=16.0.0 # UCD + related data +KEYMAN_VERSION_UNICODE=17.0.0 # UCD + related data diff --git a/resources/build/node.inc.sh b/resources/build/node.inc.sh index 15e66da78c..cf4da8131c 100644 --- a/resources/build/node.inc.sh +++ b/resources/build/node.inc.sh @@ -22,7 +22,12 @@ node_select_version_and_npm_ci() { # Also, a developer can set KEYMAN_USE_NVM variable to get this behaviour # automatically too (see /docs/build/node.md) if [[ "$KEYMAN_VERSION_ENVIRONMENT" != local || ! -z "${KEYMAN_USE_NVM+x}" ]]; then - _node_select_version_with_nvm + # For npm publishing, we currently need to use an alternate version of node + # that includes npm 11.5.1 or later (see #15040). Once we move to node v24, + # we can probably remove this check + if [[ -z "${KEYMAN_CI_SKIP_NVM+x}" ]]; then + _node_select_version_with_nvm + fi fi # Check if Node.JS/npm is installed. diff --git a/resources/build/pr-build-status/build.sh b/resources/build/pr-build-status/build.sh index ca87de813d..42683bc3bc 100755 --- a/resources/build/pr-build-status/build.sh +++ b/resources/build/pr-build-status/build.sh @@ -19,7 +19,7 @@ function do_build() { local YML_PREFIX=$(cat "$YML_PREFIX_FILE") local SCRIPT_CLONE=$(sed -n '/CLONE:START/,/CLONE:END/{//!p}' < "$MJS_FILE") local SCRIPT_SUFFIX=$(sed -n '/CLONE-COMMENTED:START/,/CLONE-COMMENTED:END/{//!p}' < "$MJS_FILE" | sed 's/\/\/ //g') - echo -e "# GENERATED FILE - DO NOT EDIT!\n$YML_PREFIX\n$SCRIPT_CLONE\n$SCRIPT_SUFFIX\n" > "$YML_FILE" + echo $'# GENERATED FILE - DO NOT EDIT!\n'"$YML_PREFIX"$'\n'"$SCRIPT_CLONE"$'\n'"$SCRIPT_SUFFIX"$'\n' > "$YML_FILE" } builder_run_action build do_build diff --git a/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json new file mode 100644 index 0000000000..2544408133 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json @@ -0,0 +1,660 @@ +[ + { + "id": 50109411782, + "name": "Build Outcome", + "node_id": "CR_kwDOAY2xT88AAAALqsDxxg", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50109411782", + "html_url": "https://github.com/keymanapp/keyman/runs/50109411782", + "details_url": "https://github.com/keymanapp/keyman/runs/50109411782", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T05:29:44Z", + "completed_at": "2025-09-11T05:29:45Z", + "output": { + "title": "21 checks completed successfully ✅", + "summary": "", + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50109411782/annotations" + }, + "check_suite": { + "id": 45240794914 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107239504, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAALqp_MUA", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107239504", + "html_url": "https://github.com/keymanapp/keyman/runs/50107239504", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:38:47Z", + "completed_at": "2025-09-11T04:38:48Z", + "output": { + "title": "No secrets detected ✅", + "summary": "**2** commits were scanned without uncovering any secrets.\n", + "text": "Commits scanned: **2**\n\n\n\n- Pull request #14728: `chore/common/unicode-17` 👉 `master`\n \n\n🦉 [GitGuardian](https://dashboard.gitguardian.com/auth/login/?utm_medium=checkruns&utm_source=github&utm_campaign=cr1) detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
\n", + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107239504/annotations" + }, + "check_suite": { + "id": 45240794052 + }, + "app": { + "id": 46505, + "client_id": "Iv1.ec3c001966b4cc5a", + "slug": "gitguardian", + "node_id": "MDM6QXBwNDY1MDU=", + "owner": { + "login": "GitGuardian", + "id": 27360172, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI3MzYwMTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/27360172?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/GitGuardian", + "html_url": "https://github.com/GitGuardian", + "followers_url": "https://api.github.com/users/GitGuardian/followers", + "following_url": "https://api.github.com/users/GitGuardian/following{/other_user}", + "gists_url": "https://api.github.com/users/GitGuardian/gists{/gist_id}", + "starred_url": "https://api.github.com/users/GitGuardian/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/GitGuardian/subscriptions", + "organizations_url": "https://api.github.com/users/GitGuardian/orgs", + "repos_url": "https://api.github.com/users/GitGuardian/repos", + "events_url": "https://api.github.com/users/GitGuardian/events{/privacy}", + "received_events_url": "https://api.github.com/users/GitGuardian/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitGuardian", + "description": "# 🦉 What is GitGuardian?\r\n\r\nGitGuardian Secrets Detection detects and fixes vulnerabilities in source code at every step of the software development lifecycle, covering 350+ types of secrets like API keys, database connection strings, private keys, certificates, and more. The platform’s policy engine enables security teams to monitor and enforce rules across their VCS, DevOps tools, and infrastructure-as-code configurations. Our automated remediation playbooks and collaboration features bring security and development teams together to resolve incidents fast and in full.\r\n\r\n## 1. Scan your codebase for 350+ types of secrets\r\nGitGuardian scans your GitHub repositories and raises alerts only for critical secrets, such as API keys or other credentials. At scale, GitGuardian’s detection algorithm has been battle-tested on over three years of activity in all public GitHub repositories – totaling over 1 billion scanned commits!\r\n\r\n## 2. Quickly remediate your hard-coded secrets\r\nIf you ever experience a leak involving a credential, we have a complete remediation guide used by 100k+ developers each year. We’ll show you how to revoke the secret and remove it from your git history.\r\n\r\n## 3. Prevent secrets from reaching GitHub\r\nInstall ggshield, the GitGuardian CLI, and add secrets detection to your local development workflow using pre-commit and pre-push git hooks integrations.\r\n\r\n# 🙋‍♂️ FAQ\r\n\r\n**What is your pricing?**\r\nGitGuardian is free for teams under 25 developers and offers a 30-day trial for larger teams.\r\n\r\n**How can I be sure that GitGuardian won’t raise too many false positives?**\r\nWe have scanned billions of commits, sent millions of alerts since 2018, and integrated each feedback to improve our algorithm. Our alerts currently receive 91% “true positive” feedback from developers.\r\n\r\n**My repositories are private; why should I install automated secret detection?**\r\nImagine if there were a plain text file with all your credit card numbers inside, you wouldn’t put this file inside your company’s git repository. Secrets are just as sensitive and should be handled with special care.\r\n\r\n**Is GitGuardian available to be installed on-premise?**\r\nYes, you can contact one of our security specialists to look over the possibility of installing GitGuardian on-premise on your repositories.\r\n\r\n# ⚒️ Installation notes \r\n\r\nYou should install GitGuardian directly through your [GitGuardian](https://dashboard.gitguardian.com/) workspace on the Integration settings page. \r\n\r\nSo that you know, your GitHub organization or GitHub account can only be associated with a single GitGuardian workspace.\r\n\r\n# 👋 Support\r\n\r\nIf you experience any difficulties or have any questions, please reach out to us by email ([support@gitguardian.com](mailto:support@gitguardian.com)).", + "external_url": "https://dashboard.gitguardian.com", + "html_url": "https://github.com/apps/gitguardian", + "created_at": "2019-11-12T15:44:31Z", + "updated_at": "2023-07-18T09:06:22Z", + "permissions": { + "checks": "write", + "contents": "read", + "emails": "read", + "issues": "write", + "members": "read", + "metadata": "read", + "organization_hooks": "write", + "pull_requests": "write" + }, + "events": [ + "check_run", + "check_suite", + "commit_comment", + "create", + "delete", + "organization", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "repository" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107238217, + "name": "build", + "node_id": "CR_kwDOAY2xT88AAAALqp_HSQ", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "584213e9-9caa-5e7a-be2b-4a4fb7afc574", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238217", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634180116/job/50107238217", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634180116/job/50107238217", + "status": "completed", + "conclusion": "skipped", + "started_at": "2025-09-11T04:38:46Z", + "completed_at": "2025-09-11T04:38:45Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238217/annotations" + }, + "check_suite": { + "id": 45240899113 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107238052, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAALqp_GpA", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "90e392c4-1805-5238-9d01-622e4d746355", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238052", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634180101/job/50107238052", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634180101/job/50107238052", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:38:47Z", + "completed_at": "2025-09-11T04:38:52Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238052/annotations" + }, + "check_suite": { + "id": 45240899080 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107108425, + "name": "Summarize build status checks", + "node_id": "CR_kwDOAY2xT88AAAALqp3MSQ", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "7e5f7f3d-df60-5a09-9991-ce263cfb45c6", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107108425", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634132698/job/50107108425", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634132698/job/50107108425", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:35:52Z", + "completed_at": "2025-09-11T04:35:55Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107108425/annotations" + }, + "check_suite": { + "id": 45240794914 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + } +] \ No newline at end of file diff --git a/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json new file mode 100644 index 0000000000..213931a7ed --- /dev/null +++ b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json @@ -0,0 +1,3038 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175572529, + "node_id": "SC_kwDOAY2xT88AAAAJHwvMMQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T05:29:36Z", + "updated_at": "2025-09-11T05:29:36Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175567982, + "node_id": "SC_kwDOAY2xT88AAAAJHwu6bg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T05:29:14Z", + "updated_at": "2025-09-11T05:29:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175162089, + "node_id": "SC_kwDOAY2xT88AAAAJHwWI6Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T05:00:11Z", + "updated_at": "2025-09-11T05:00:11Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175160705, + "node_id": "SC_kwDOAY2xT88AAAAJHwWDgQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T05:00:07Z", + "updated_at": "2025-09-11T05:00:07Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175159732, + "node_id": "SC_kwDOAY2xT88AAAAJHwV_tA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T05:00:02Z", + "updated_at": "2025-09-11T05:00:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175118416, + "node_id": "SC_kwDOAY2xT88AAAAJHwTeUA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:56:41Z", + "updated_at": "2025-09-11T04:56:41Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175115406, + "node_id": "SC_kwDOAY2xT88AAAAJHwTSjg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:56:27Z", + "updated_at": "2025-09-11T04:56:27Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175107216, + "node_id": "SC_kwDOAY2xT88AAAAJHwSykA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:55:44Z", + "updated_at": "2025-09-11T04:55:44Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175106689, + "node_id": "SC_kwDOAY2xT88AAAAJHwSwgQ", + "state": "success", + "description": "API verification succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634394358", + "context": "API Verification", + "created_at": "2025-09-11T04:55:42Z", + "updated_at": "2025-09-11T04:55:42Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175098588, + "node_id": "SC_kwDOAY2xT88AAAAJHwSQ3A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:55:00Z", + "updated_at": "2025-09-11T04:55:00Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175098146, + "node_id": "SC_kwDOAY2xT88AAAAJHwSPIg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:54:58Z", + "updated_at": "2025-09-11T04:54:58Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175098035, + "node_id": "SC_kwDOAY2xT88AAAAJHwSOsw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:54:58Z", + "updated_at": "2025-09-11T04:54:58Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175097199, + "node_id": "SC_kwDOAY2xT88AAAAJHwSLbw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:54:54Z", + "updated_at": "2025-09-11T04:54:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175096032, + "node_id": "SC_kwDOAY2xT88AAAAJHwSG4A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:54:47Z", + "updated_at": "2025-09-11T04:54:47Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175095550, + "node_id": "SC_kwDOAY2xT88AAAAJHwSE_g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T04:54:45Z", + "updated_at": "2025-09-11T04:54:45Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175095475, + "node_id": "SC_kwDOAY2xT88AAAAJHwSEsw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:54:44Z", + "updated_at": "2025-09-11T04:54:44Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175080948, + "node_id": "SC_kwDOAY2xT88AAAAJHwRL9A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:53:36Z", + "updated_at": "2025-09-11T04:53:36Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175080759, + "node_id": "SC_kwDOAY2xT88AAAAJHwRLNw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:53:35Z", + "updated_at": "2025-09-11T04:53:35Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175078800, + "node_id": "SC_kwDOAY2xT88AAAAJHwRDkA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:53:25Z", + "updated_at": "2025-09-11T04:53:25Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175078429, + "node_id": "SC_kwDOAY2xT88AAAAJHwRCHQ", + "state": "pending", + "description": "API verification started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634394358", + "context": "API Verification", + "created_at": "2025-09-11T04:53:23Z", + "updated_at": "2025-09-11T04:53:23Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175076234, + "node_id": "SC_kwDOAY2xT88AAAAJHwQ5ig", + "state": "success", + "description": "Package build succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634187943", + "context": "Ubuntu Packaging", + "created_at": "2025-09-11T04:53:11Z", + "updated_at": "2025-09-11T04:53:11Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175068238, + "node_id": "SC_kwDOAY2xT88AAAAJHwQaTg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:52:31Z", + "updated_at": "2025-09-11T04:52:31Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175063216, + "node_id": "SC_kwDOAY2xT88AAAAJHwQGsA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:52:10Z", + "updated_at": "2025-09-11T04:52:10Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175058340, + "node_id": "SC_kwDOAY2xT88AAAAJHwPzpA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:51:48Z", + "updated_at": "2025-09-11T04:51:48Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175051151, + "node_id": "SC_kwDOAY2xT88AAAAJHwPXjw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:51:21Z", + "updated_at": "2025-09-11T04:51:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175051020, + "node_id": "SC_kwDOAY2xT88AAAAJHwPXDA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:51:21Z", + "updated_at": "2025-09-11T04:51:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175049448, + "node_id": "SC_kwDOAY2xT88AAAAJHwPQ6A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:51:13Z", + "updated_at": "2025-09-11T04:51:13Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175048733, + "node_id": "SC_kwDOAY2xT88AAAAJHwPOHQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:51:09Z", + "updated_at": "2025-09-11T04:51:09Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175044363, + "node_id": "SC_kwDOAY2xT88AAAAJHwO9Cw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:50:47Z", + "updated_at": "2025-09-11T04:50:47Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175044256, + "node_id": "SC_kwDOAY2xT88AAAAJHwO8oA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:50:46Z", + "updated_at": "2025-09-11T04:50:46Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175038088, + "node_id": "SC_kwDOAY2xT88AAAAJHwOkiA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:50:14Z", + "updated_at": "2025-09-11T04:50:14Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175033010, + "node_id": "SC_kwDOAY2xT88AAAAJHwOQsg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:49:50Z", + "updated_at": "2025-09-11T04:49:50Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175032935, + "node_id": "SC_kwDOAY2xT88AAAAJHwOQZw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:49:49Z", + "updated_at": "2025-09-11T04:49:49Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175026018, + "node_id": "SC_kwDOAY2xT88AAAAJHwN1Yg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:49:12Z", + "updated_at": "2025-09-11T04:49:12Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175005335, + "node_id": "SC_kwDOAY2xT88AAAAJHwMklw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:47:30Z", + "updated_at": "2025-09-11T04:47:30Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175005126, + "node_id": "SC_kwDOAY2xT88AAAAJHwMjxg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:47:29Z", + "updated_at": "2025-09-11T04:47:29Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175001040, + "node_id": "SC_kwDOAY2xT88AAAAJHwMT0A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:47:09Z", + "updated_at": "2025-09-11T04:47:09Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175000940, + "node_id": "SC_kwDOAY2xT88AAAAJHwMTbA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:47:09Z", + "updated_at": "2025-09-11T04:47:09Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174999511, + "node_id": "SC_kwDOAY2xT88AAAAJHwMN1w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T04:47:02Z", + "updated_at": "2025-09-11T04:47:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174999409, + "node_id": "SC_kwDOAY2xT88AAAAJHwMNcQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:47:02Z", + "updated_at": "2025-09-11T04:47:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993947, + "node_id": "SC_kwDOAY2xT88AAAAJHwL4Gw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:46:36Z", + "updated_at": "2025-09-11T04:46:36Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993870, + "node_id": "SC_kwDOAY2xT88AAAAJHwL3zg", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:46:35Z", + "updated_at": "2025-09-11T04:46:35Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993783, + "node_id": "SC_kwDOAY2xT88AAAAJHwL3dw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:46:35Z", + "updated_at": "2025-09-11T04:46:35Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174990184, + "node_id": "SC_kwDOAY2xT88AAAAJHwLpaA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:18Z", + "updated_at": "2025-09-11T04:46:18Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989998, + "node_id": "SC_kwDOAY2xT88AAAAJHwLorg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:17Z", + "updated_at": "2025-09-11T04:46:17Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989781, + "node_id": "SC_kwDOAY2xT88AAAAJHwLn1Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:16Z", + "updated_at": "2025-09-11T04:46:16Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989098, + "node_id": "SC_kwDOAY2xT88AAAAJHwLlKg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:12Z", + "updated_at": "2025-09-11T04:46:12Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985891, + "node_id": "SC_kwDOAY2xT88AAAAJHwLYow", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:45:56Z", + "updated_at": "2025-09-11T04:45:56Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985810, + "node_id": "SC_kwDOAY2xT88AAAAJHwLYUg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:45:56Z", + "updated_at": "2025-09-11T04:45:56Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985723, + "node_id": "SC_kwDOAY2xT88AAAAJHwLX-w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:45:55Z", + "updated_at": "2025-09-11T04:45:55Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985645, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXrQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:45:55Z", + "updated_at": "2025-09-11T04:45:55Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985556, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXVA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:45:54Z", + "updated_at": "2025-09-11T04:45:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985505, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXIQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:45:54Z", + "updated_at": "2025-09-11T04:45:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174984029, + "node_id": "SC_kwDOAY2xT88AAAAJHwLRXQ", + "state": "success", + "description": "⚠️ Warning: keymanweb.js is 170 bytes (0%) larger than 19.0.116, now 432,283 bytes", + "target_url": null, + "context": "check/web/file-size", + "created_at": "2025-09-11T04:45:46Z", + "updated_at": "2025-09-11T04:45:46Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958855, + "node_id": "SC_kwDOAY2xT88AAAAJHwJvBw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:43:34Z", + "updated_at": "2025-09-11T04:43:34Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958775, + "node_id": "SC_kwDOAY2xT88AAAAJHwJutw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:43:33Z", + "updated_at": "2025-09-11T04:43:33Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958372, + "node_id": "SC_kwDOAY2xT88AAAAJHwJtJA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:43:31Z", + "updated_at": "2025-09-11T04:43:31Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174938339, + "node_id": "SC_kwDOAY2xT88AAAAJHwIe4w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:41:54Z", + "updated_at": "2025-09-11T04:41:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174938237, + "node_id": "SC_kwDOAY2xT88AAAAJHwIefQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:41:53Z", + "updated_at": "2025-09-11T04:41:53Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174932962, + "node_id": "SC_kwDOAY2xT88AAAAJHwIJ4g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T04:41:25Z", + "updated_at": "2025-09-11T04:41:25Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174932882, + "node_id": "SC_kwDOAY2xT88AAAAJHwIJkg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:41:24Z", + "updated_at": "2025-09-11T04:41:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39174908967, + "node_id": "SC_kwDOAY2xT88AAAAJHwGsJw", + "state": "pending", + "description": "Ubuntu packaging started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634187943", + "context": "Ubuntu Packaging", + "created_at": "2025-09-11T04:39:26Z", + "updated_at": "2025-09-11T04:39:26Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908607, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqvw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:39:24Z", + "updated_at": "2025-09-11T04:39:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908507, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqWw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:39:24Z", + "updated_at": "2025-09-11T04:39:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908427, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqCw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:39:23Z", + "updated_at": "2025-09-11T04:39:23Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908273, + "node_id": "SC_kwDOAY2xT88AAAAJHwGpcQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908169, + "node_id": "SC_kwDOAY2xT88AAAAJHwGpCQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908061, + "node_id": "SC_kwDOAY2xT88AAAAJHwGonQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907908, + "node_id": "SC_kwDOAY2xT88AAAAJHwGoBA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:39:21Z", + "updated_at": "2025-09-11T04:39:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907798, + "node_id": "SC_kwDOAY2xT88AAAAJHwGnlg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:39:20Z", + "updated_at": "2025-09-11T04:39:20Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907673, + "node_id": "SC_kwDOAY2xT88AAAAJHwGnGQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T04:39:20Z", + "updated_at": "2025-09-11T04:39:20Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907586, + "node_id": "SC_kwDOAY2xT88AAAAJHwGmwg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907492, + "node_id": "SC_kwDOAY2xT88AAAAJHwGmZA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907382, + "node_id": "SC_kwDOAY2xT88AAAAJHwGl9g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907255, + "node_id": "SC_kwDOAY2xT88AAAAJHwGldw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:39:18Z", + "updated_at": "2025-09-11T04:39:18Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907127, + "node_id": "SC_kwDOAY2xT88AAAAJHwGk9w", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:39:17Z", + "updated_at": "2025-09-11T04:39:17Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907022, + "node_id": "SC_kwDOAY2xT88AAAAJHwGkjg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:39:17Z", + "updated_at": "2025-09-11T04:39:17Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906933, + "node_id": "SC_kwDOAY2xT88AAAAJHwGkNQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906844, + "node_id": "SC_kwDOAY2xT88AAAAJHwGj3A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906746, + "node_id": "SC_kwDOAY2xT88AAAAJHwGjeg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906635, + "node_id": "SC_kwDOAY2xT88AAAAJHwGjCw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:39:15Z", + "updated_at": "2025-09-11T04:39:15Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906548, + "node_id": "SC_kwDOAY2xT88AAAAJHwGitA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906463, + "node_id": "SC_kwDOAY2xT88AAAAJHwGiXw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906355, + "node_id": "SC_kwDOAY2xT88AAAAJHwGh8w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906251, + "node_id": "SC_kwDOAY2xT88AAAAJHwGhiw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:39:13Z", + "updated_at": "2025-09-11T04:39:13Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906139, + "node_id": "SC_kwDOAY2xT88AAAAJHwGhGw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906037, + "node_id": "SC_kwDOAY2xT88AAAAJHwGgtQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905929, + "node_id": "SC_kwDOAY2xT88AAAAJHwGgSQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905842, + "node_id": "SC_kwDOAY2xT88AAAAJHwGf8g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:39:11Z", + "updated_at": "2025-09-11T04:39:11Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905756, + "node_id": "SC_kwDOAY2xT88AAAAJHwGfnA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:39:11Z", + "updated_at": "2025-09-11T04:39:11Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905667, + "node_id": "SC_kwDOAY2xT88AAAAJHwGfQw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:39:10Z", + "updated_at": "2025-09-11T04:39:10Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174900789, + "node_id": "SC_kwDOAY2xT88AAAAJHwGMNQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:38:46Z", + "updated_at": "2025-09-11T04:38:46Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + } +] \ No newline at end of file diff --git a/resources/build/pr-build-status/package-lock.json b/resources/build/pr-build-status/package-lock.json index ff65ee2506..e2980aed2c 100644 --- a/resources/build/pr-build-status/package-lock.json +++ b/resources/build/pr-build-status/package-lock.json @@ -12,8 +12,7 @@ }, "devDependencies": { "chai": "^5.1.0", - "mocha": "^11.2.2", - "mocha-teamcity-reporter": "^4.0.0" + "mocha": "^11.2.2" } }, "node_modules/@actions/core": { @@ -686,9 +685,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { @@ -783,9 +782,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -904,19 +903,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha-teamcity-reporter": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-4.2.0.tgz", - "integrity": "sha512-H08IvAIsiCcdXAEObzp/VvJHFfLummxt5wpr0gU4yxx6pTb7ZmKnVjyXkFi+3gYi3bu/j2iJdoK9Aknz16mbBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "mocha": ">=6" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/resources/build/pr-build-status/package.json b/resources/build/pr-build-status/package.json index 5e9dc1bc70..1d57f981a5 100644 --- a/resources/build/pr-build-status/package.json +++ b/resources/build/pr-build-status/package.json @@ -6,9 +6,8 @@ "@actions/github": "^6.0.1" }, "devDependencies": { - "mocha": "^11.2.2", - "mocha-teamcity-reporter": "^4.0.0", - "chai": "^5.1.0" + "chai": "^5.1.0", + "mocha": "^11.2.2" }, "license": "MIT", "main": "pr-build-status.mjs", diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 091377d942..2af588f206 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -27,43 +27,63 @@ // check/web/file-size // + function addLog(message) { + console.log(message); + return message + String.fromCharCode(10); + } + + function addStatus(o, type, context, state) { + if(!o[context]) { + o[context] = {type, state}; + return addLog(`Found ${context}: ${state}`); + } else { + return addLog(`Skipping repeated ${context}: ${state}`); + } + } + function reduceStatuses(statuses) { + let summary = ''; const filtered_statuses = statuses.reduce((o, status) => { if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { - if(!o[status.context]) o[status.context] = {type: 'user-test', state: status.state};; + summary += addStatus(o, 'user-test', status.context, status.state); } else if(status.context == 'API Verification') { - if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + summary += addStatus(o, 'check', status.context, status.state); } else if(status.context == 'Ubuntu Packaging') { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); + } else if(status.context == 'npm pack/publish') { + summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'check/web/file-size') { // Ignore check/web/file-size -- we won't block automerge for this at this point + summary += addLog(`Skipping ${status.context}`); } else { // We fail with an 'unknown status' response if we get a new status check // so we can be sure we are not skipping known status checks + summary += addLog(`Found unknown ${status.context}: ${status.state}`); o[status.context] = {type: 'unknown', state: status.state}; } return o; }, {}); - return filtered_statuses; + return { filtered_statuses, summary }; } // // Given the collection of status checks we care about, return - // an aggregate status -- error, failed, pending, or success, + // an aggregate status -- error, failure, pending, or success, // and a summary description // - function calculateFinalStatus(filtered_statuses) { + function calculateFinalStatus(filtered_statuses, summary) { const counts = {}; let hasBuilds = false; for(const context of Object.keys(filtered_statuses)) { const { state, type } = filtered_statuses[context]; if(type == 'unknown') { // We special-case for unknown status checks, and never permit them + console.error(`An unknown context ${context} was found, cannot calculate build status.`); return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.` + 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; } if(type == 'build') { @@ -74,12 +94,13 @@ // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Builds have not yet been triggered ⌛']; + console.log(`Builds not yet triggered`); + return ['pending', 'Builds have not yet been triggered ⌛', summary]; } const state = counts.error ? 'error' : - counts.failed ? 'failed' : + counts.failure ? 'failure' : counts.pending ? 'pending' : 'success'; @@ -90,14 +111,16 @@ description += `${count} check${count == 1 ? '' : 's'} ${state}`; } appendDescription(counts.error, 'in an error state ❌'); - appendDescription(counts.failed, 'failed ❌'); + appendDescription(counts.failure, 'failed ❌'); appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); - return [ state, description ]; + console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); + return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { + console.log(`Getting commit statuses for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { owner, repo, @@ -110,6 +133,7 @@ } async function getCommitCheckRuns(github, owner, repo, sha) { + console.log(`Getting commit check runs for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { owner, repo, @@ -123,13 +147,11 @@ function calculateCheckResult(statuses) { if(!Array.isArray(statuses)) { - return ['error', 'Failed to retrieve status checks from GitHub ❌']; + return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; } - const filtered_statuses = reduceStatuses(statuses); - - const result = calculateFinalStatus(filtered_statuses); - return result; + const { filtered_statuses, summary } = reduceStatuses(statuses); + return calculateFinalStatus(filtered_statuses, summary); } async function test(github, owner, repo, sha) { @@ -149,7 +171,7 @@ return check.data.id; } - async function updateCheck(github, owner, repo, checkRunId, status, description) { + async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); @@ -161,7 +183,7 @@ conclusion, output: { title: description, - summary: '' + summary } }); } @@ -179,7 +201,7 @@ // const checkRunId = await createCheck(github, owner, repo, sha); // const res = await test(github, owner, repo, sha); - // await updateCheck(github, owner, repo, checkRunId, res[0], res[1]); + // await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); // CLONE-COMMENTED:END diff --git a/resources/build/pr-build-status/pr-build-status.tests.mjs b/resources/build/pr-build-status/pr-build-status.tests.mjs index bfb49fe64b..fae7f65628 100644 --- a/resources/build/pr-build-status/pr-build-status.tests.mjs +++ b/resources/build/pr-build-status/pr-build-status.tests.mjs @@ -1,6 +1,7 @@ import * as fs from 'node:fs'; import 'mocha'; import { assert } from 'chai'; +import sinon from 'sinon'; import { unitTestEndpoints } from './pr-build-status.mjs'; @@ -9,9 +10,10 @@ import { getOctokit } from '@actions/github'; import * as process from 'node:process'; const commits = { + '390a5bc6dba73cd32bc8c7290ac73321f6504deb': [ 'failure', '3 checks failed ❌; 21 checks completed successfully ✅'], '825f7d2df0069fa56a8f2a534b42a49ee1f21ca1': [ 'success', '24 checks completed successfully ✅' ], - '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 17 checks completed successfully ✅' ], - '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'success', '22 checks completed successfully ✅' ], + '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 1 check failed ❌; 17 checks completed successfully ✅' ], + '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'failure', '2 checks failed ❌; 22 checks completed successfully ✅' ], '8fd5ccc9026b85d6a780d8bcf862380e7a89aefb': [ 'pending', 'Builds have not yet been triggered ⌛' ], '153683cfb007c6066c9dcf71d25afa4c66efa17f': [ 'pending', 'Builds have not yet been triggered ⌛' ], }; @@ -32,13 +34,23 @@ if(debug) { } describe('pr-build-status', function () { + beforeEach(() => { + // just silencing the logging from pr-build-status bot + // in order to make the test results clearer + sinon.stub(console, 'log'); + }); + + afterEach(() => { + console.log.restore(); + }); + for(const sha of Object.keys(commits)) { it(`should return '${commits[sha][1]}' for sha ${sha}`, function () { const json = JSON.parse(fs.readFileSync(`fixtures/${sha}-statuses.json`,'utf-8')); const status = unitTestEndpoints.calculateCheckResult(json); assert.isNotNull(status); assert.isArray(status); - assert.deepStrictEqual(status, commits[sha]); + assert.deepStrictEqual(status.splice(0, 2), commits[sha]); }); } }); diff --git a/resources/build/publish-minimum-versions.sh b/resources/build/publish-minimum-versions.sh index 5da012737a..a9d6098c72 100755 --- a/resources/build/publish-minimum-versions.sh +++ b/resources/build/publish-minimum-versions.sh @@ -35,7 +35,7 @@ generate_table() { KEYMAN_VERSION | KEYMAN_VERSION_MAJOR | KEYMAN_VERSION_MINOR | \ KEYMAN_VERSION_PATCH | KEYMAN_VERSION_RELEASE | KEYMAN_VERSION_WIN | \ KEYMAN_VERSION_TAG | KEYMAN_VERSION_WITH_TAG | \ - KEYMAN_VERSION_ENVIRONMENT | KEYMAN_VERSION_GIT_TAG) + KEYMAN_VERSION_ENVIRONMENT | KEYMAN_VERSION_GIT_TAG | KEYMAN_VERSION_FOR_FILENAME) # ignore the variables that contain the Keyman version ;; *) diff --git a/resources/build/test/build-bot/trigger-build-bot.test.sh b/resources/build/test/build-bot/trigger-build-bot.test.sh index a39b8361ae..f7ca8f91bd 100755 --- a/resources/build/test/build-bot/trigger-build-bot.test.sh +++ b/resources/build/test/build-bot/trigger-build-bot.test.sh @@ -216,7 +216,25 @@ _do_test_test_bot_check_pr_body() { #---------------------------------------------------------------------------------------------------- +test_is_skip_keyman_api_check() { + builder_echo start test_is_skip_keyman_api_check 'START TEST: test_is_skip_keyman_api_check' + result=$(is_skip_keyman_api_check 1 '{ "body": "Keyman-Api-Check: skip" }') + assert-equal "${result}" "true" "PR #1: 'Keyman-Api-Check: skip' should return true" + result=$(is_skip_keyman_api_check 2 '{ "body": "PR message" }') + assert-equal "${result}" "false" "PR #2: omitted 'Keyman-Api-Check' should return false" + result=$(is_skip_keyman_api_check 3 '{ "body": "Keyman-Api-Check: invalid" }') + assert-equal "${result}" "false" "PR #3: invalid data for 'Keyman-Api-Check' should return false" + result=$(is_skip_keyman_api_check 4 '{ "body": "Some message\nKeyman-Api-Check: skip" }') + assert-equal "${result}" "true" "PR #4: 'Keyman-Api-Check: skip' at start of line should return true" + result=$(is_skip_keyman_api_check 5 '{ "body": "Here Keyman-Api-Check: skip\nis in the middle of the line" }') + assert-equal "${result}" "false" "PR #5: 'Keyman-Api-Check: skip' in the middle of the line should return false" + builder_echo end test_is_skip_keyman_api_check success 'SUCCESS: test_is_skip_keyman_api_check' +} + +#---------------------------------------------------------------------------------------------------- + test_build_bot_verify_platforms test_build_bot_update_commands test_build_bot_check_messages test_test_bot_check_pr_body +test_is_skip_keyman_api_check diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index 12530335af..f13a68c727 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -2,6 +2,9 @@ set -eu +# Avoid timing reports in unit tests +export _builder_timings=false + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -243,7 +246,7 @@ if [[ $KEYMAN_VERSION_ENVIRONMENT == "local" ]]; then expected="$(builder_echo grey "Local build environment detected: setting --debug")"$'\n'"$(builder_echo setmark "test.sh parameters: <--feature xyzzy --bar abc --baz def test --debug>")" fi if [[ "${parse_output[*]}" != "${expected}" ]]; then - builder_die "FAIL: Wrong output for '--feature xyzzy --bar abc --baz def test':\n Actual : ${parse_output[*]}\n Expected: ${expected}" + builder_die "FAIL: Wrong output for '--feature xyzzy --bar abc --baz def test':\n Actual : '${parse_output[*]}'\n Expected: '${expected}'" fi #---------------------------------------------------------------------- @@ -300,12 +303,12 @@ function builder_echo_start_end_tests() { TEAMCITY_GIT_PATH="" _builder_debug_internal=false _builder_is_child=0 - expected="" + expected="$(builder_echo heading "description")" result=$(builder_echo start "foo" "description") if [[ "${result[*]}" != "${expected}" ]]; then builder_die "FAIL: Wrong output for 'builder_echo start foo description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}" fi - expected="" + expected="$(builder_echo success "description")" result=$(builder_echo end "foo" success "description") if [[ "${result[*]}" != "${expected}" ]]; then builder_die "FAIL: Wrong output for 'builder_echo end foo success description' (no debug, is child):\n Actual : ${result[*]}\n Expected: ${expected}" @@ -436,7 +439,7 @@ echo # Finally, run with --help so we can see what it looks like; note: # builder_parse calls `exit 0` on a --help run, so running in a subshell echo -e "${COLOR_BLUE}## Testing --help${COLOR_RESET}" - builder_parse --no-color --help + builder_parse --no-color --help --no-timings ) || builder_die "FAIL: builder-parse unexpectedly returned failure code $?" echo -e "${COLOR_GREEN}======================================================${COLOR_RESET}" diff --git a/resources/build/typescript.inc.sh b/resources/build/typescript.inc.sh index 54e3956951..ef6f58d68a 100644 --- a/resources/build/typescript.inc.sh +++ b/resources/build/typescript.inc.sh @@ -10,21 +10,33 @@ # 1: coverage_threshold optional, minimum coverage for c8 to pass tests, # defaults to 90 (percent) typescript_run_eslint_mocha_tests() { - local MOCHA_FLAGS= + local MOCHA_FLAGS=() + local TEST_DIR if builder_is_running_on_teamcity; then # we're running in TeamCity - MOCHA_FLAGS="-reporter mocha-teamcity-reporter" + MOCHA_FLAGS+=(--reporter "${KEYMAN_ROOT}/common/test/resources/mocha-teamcity-reporter/teamcity.cjs" --reporter-options parentFlowId="unit_tests") + echo "##teamcity[flowStarted flowId='unit_tests']" fi eslint . - tsc --build test/ + + # TODO: Unify test directory names (#14878) + if [[ -d test/ ]]; then + TEST_DIR="test/" + elif [[ -d tests/ ]]; then + TEST_DIR="tests/" + else + builder_die "No test/ or tests/ directory found." + fi + + tsc --build "${TEST_DIR}" local THRESHOLD_PARAMS= local C8_THRESHOLD= if [[ $# -gt 0 ]]; then C8_THRESHOLD=$1 - THRESHOLD_PARAMS="--lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD" + THRESHOLD_PARAMS="--lines ${C8_THRESHOLD} --statements ${C8_THRESHOLD} --branches ${C8_THRESHOLD} --functions ${C8_THRESHOLD}" else # Seems like a bug in TeamCity reporter if we don't list default thresholds, # see #13418. @@ -34,10 +46,15 @@ typescript_run_eslint_mocha_tests() { THRESHOLD_PARAMS="--lines 90 --statements 90 --branches 80 --functions 80" fi - c8 --reporter=lcov --reporter=text --exclude-after-remap --check-coverage $THRESHOLD_PARAMS mocha ${MOCHA_FLAGS} "${builder_extra_params[@]}" + c8 --reporter=lcov --reporter=text --exclude-after-remap --check-coverage ${THRESHOLD_PARAMS} mocha "${MOCHA_FLAGS[@]}" "${builder_extra_params[@]}" - if [[ ! -z "$C8_THRESHOLD" ]]; then - builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." + if [[ ! -z "${C8_THRESHOLD}" ]]; then + builder_echo warning "Coverage thresholds are currently ${C8_THRESHOLD}%, which is lower than ideal." builder_echo warning "Please increase threshold in build.sh as test coverage improves." fi + + if builder_is_running_on_teamcity; then + # we're running in TeamCity + echo "##teamcity[flowFinished flowId='unit_tests']" + fi } diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 40c4de6db5..37a055d001 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -58,7 +58,11 @@ generate_uuid() { run_in_vs_env() { ( - builder_echo heading "### visual_studio: $@" + if [[ "${1-x}" == "--quiet" ]]; then + shift + else + builder_echo heading "### visual_studio: $*" + fi source "$KEYMAN_ROOT/resources/build/win/visualstudio_environment.inc.sh" "$@" ) @@ -66,7 +70,11 @@ run_in_vs_env() { run_in_delphi_env() { ( - builder_echo heading "### delphi: $@" + if [[ "${1-x}" == "--quiet" ]]; then + shift + else + builder_echo heading "### delphi: $*" + fi source "$KEYMAN_ROOT/resources/build/win/delphi_environment.inc.sh" "$@" ) diff --git a/resources/build/win/signtime.inc.sh b/resources/build/win/signtime.inc.sh index e561d9f0c5..358dec7f34 100644 --- a/resources/build/win/signtime.inc.sh +++ b/resources/build/win/signtime.inc.sh @@ -20,10 +20,12 @@ signtime() { } function verify-all-executable-signatures-in-folder() { - # ignore return value from sigcheck; we validate the response with verify_signatures - # "*" needs to be inside the quotes as it is passed as-is to sigcheck, not as an expansion - # note: it seems that sigcheck rejects forward-slash in paths, so cygpath is our friend - ("$SIGCHECK" -q -s -e -v -accepteula "$(cygpath -w "$1")\\*" || true) > sig1 - "$VERIFY_SIGNATURES" -d "$KEYMAN_ROOT/VERSION.md" < sig1 - rm -f sig1 + if builder_is_ci_build; then + # ignore return value from sigcheck; we validate the response with verify_signatures + # "*" needs to be inside the quotes as it is passed as-is to sigcheck, not as an expansion + # note: it seems that sigcheck rejects forward-slash in paths, so cygpath is our friend + ("$SIGCHECK" -q -s -e -v -accepteula "$(cygpath -w "$1")\\*" || true) > sig1 + "$VERIFY_SIGNATURES" -d "$KEYMAN_ROOT/VERSION.md" < sig1 + rm -f sig1 + fi } diff --git a/resources/build/win/wix.inc.sh b/resources/build/win/wix.inc.sh index a9e084b10a..0ad63e85b7 100644 --- a/resources/build/win/wix.inc.sh +++ b/resources/build/win/wix.inc.sh @@ -1,5 +1,5 @@ -WIXPATH="$ProgramFilesx86/WiX Toolset v3.11/bin" +WIXPATH="$ProgramFilesx86/WiX Toolset v3.14/bin" # !IFDEF LINT WIXLIGHTLINT=() diff --git a/resources/build/zip.inc.sh b/resources/build/zip.inc.sh index a1747680f2..8ab4aa77fc 100644 --- a/resources/build/zip.inc.sh +++ b/resources/build/zip.inc.sh @@ -1,4 +1,4 @@ -# shellscript shell=bash +# shellcheck shell=bash # # This script contains zip and unzip (TODO) utilities to function for all environments. # unzip is available in all environments @@ -46,29 +46,29 @@ function add_zip_files() { case "$1" in -r) # recursive paths - Identical flag to zip and 7z - ZIP_FLAGS+=($1) - SEVENZ_FLAGS+=($1) + ZIP_FLAGS+=("$1") + SEVENZ_FLAGS+=("$1") shift ;; -x@*) # Filename for a file containing list of files to exclude from the archive - Identical flag to zip and 7z - ZIP_FLAGS+=($1) - SEVENZ_FLAGS+=($1) + ZIP_FLAGS+=("$1") + SEVENZ_FLAGS+=("$1") shift ;; -xr!*) # Recursively exclude the file after -xr! from the archive EXCLUDE_FILE=$(mktemp) - find . -name ${1##-xr!} > "${EXCLUDE_FILE}" + find . -name "${1##-xr!}" > "${EXCLUDE_FILE}" ZIP_FLAGS+=("-x@${EXCLUDE_FILE}") - SEVENZ_FLAGS+=($1) + SEVENZ_FLAGS+=("$1") shift ;; # Zip flags that have a corresponding 7z flag -q) # quiet mode -> disable progress indicator, set output log level 0 - ZIP_FLAGS+=($1) + ZIP_FLAGS+=("$1") SEVENZ_FLAGS+=("-bd") SEVENZ_FLAGS+=("-bb0") shift @@ -79,7 +79,7 @@ function add_zip_files() { # -1 indicates low compression (fastest) # -9 indicates ultra compression (slowest) HAS_COMPRESSION=true - ZIP_FLAGS+=($1) + ZIP_FLAGS+=("$1") if [[ $1 =~ -([0-9]) ]]; then SEVENZ_FLAGS+=("-mx${BASH_REMATCH[1]}") fi @@ -88,7 +88,7 @@ function add_zip_files() { -*) # Remaining zip flags that don't apply to 7z - ZIP_FLAGS+=($1) + ZIP_FLAGS+=("$1") shift ;; @@ -119,12 +119,15 @@ function add_zip_files() { _verify_input "${INCLUDE[@]}" local COMPRESS_CMD=zip - if ! command -v zip 2>&1 > /dev/null; then + if ! command -v zip > /dev/null 2>&1; then # Fallback to 7z if [[ -z "${SEVENZ+x}" ]]; then if builder_is_windows; then - # shellcheck disable=2154 - SEVENZ="${SEVENZ_HOME}/7z.exe" + if [[ -z "${SEVENZ_HOME+x}" ]]; then + SEVENZ="$(command -v 7z.exe)" + else + SEVENZ="${SEVENZ_HOME}/7z.exe" + fi else SEVENZ=7z fi @@ -140,7 +143,7 @@ function add_zip_files() { # Create archive # builder_echo_debug "${COMPRESS_CMD} ${SEVENZ_FLAGS[@]} ${ZIP_FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]}" - "${COMPRESS_CMD}" ${SEVENZ_FLAGS[@]} ${ZIP_FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]} + "${COMPRESS_CMD}" "${SEVENZ_FLAGS[@]}" "${ZIP_FLAGS[@]}" "${ZIP_FILE}" "${INCLUDE[@]}" if [[ -n "${EXCLUDE_FILE:-}" ]]; then rm "${EXCLUDE_FILE}" diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index dc2ffd9b44..0ef28808a6 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -30,6 +30,11 @@ set -eu # SHLVL=0 +# A regex of reserved builder parameters, must be kept in sync with +# the matching of builder parameters in _builder_parse_expanded_parameters +_builder_reserved_parameters='^(--help|-h|--color|--no-color|--verbose|-v|--timings|--no-timings|--debug|--release|--deps|--no-deps|--force-deps|--builder-dep-parent|--builder-child|--builder-report-dependencies|--builder-completion-describe|--offline|--builder-ignore-unknown-options)$' + + # _builder_init is called internally at the bottom of this file after we have # all function declarations in place. function _builder_init() { @@ -51,9 +56,7 @@ function _builder_init() { } function _builder_findRepoRoot() { - # We don't need readlink here because our standard script prolog does a - # readlink -f already so we will have already escaped from any symlinks - REPO_ROOT="${BASH_SOURCE[0]%/*/*}" + REPO_ROOT="$(readlink -f "${BASH_SOURCE[0]%/*/*}")" readonly REPO_ROOT } @@ -182,7 +185,7 @@ function builder_heading() { builder_echo() { - local color=white message= mark= block= action= do_output=true test= + local color=white message= mark= block= action= test= local echo_target=echo if [[ $# -gt 1 ]]; then @@ -196,9 +199,6 @@ builder_echo() { shift 2 action="start" color="heading" - if ! builder_is_running_on_teamcity && builder_is_child_build; then - do_output=${_builder_debug_internal:-false} - fi elif [[ $1 == "end" ]] || [[ $1 == "endTest" ]]; then # builder_echo end block status message test="$1" @@ -206,9 +206,6 @@ builder_echo() { color="$3" shift 3 action="end" - if [[ "${color}" != "error" ]] && ! builder_is_running_on_teamcity && builder_is_child_build; then - do_output=${_builder_debug_internal:-false} - fi fi fi message="$*" @@ -221,30 +218,28 @@ builder_echo() { fi fi - if ${do_output}; then - if [[ ! -z ${COLOR_RED+x} ]]; then - case $color in - white) color="$COLOR_WHITE" ;; - grey) color="$COLOR_GREY" ;; - green|success) color="$COLOR_GREEN" ;; - blue|heading) color="$COLOR_BLUE" ;; - yellow|warning) color="$COLOR_YELLOW" ;; - red|error) color="$COLOR_RED"; echo_target=_builder_error_echo ;; - purple) color="$COLOR_PURPLE" ;; - brightwhite) color="$COLOR_BRIGHTWHITE" ;; - teal|debug) color="$COLOR_TEAL" ;; - setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;; - esac + if [[ ! -z ${COLOR_RED+x} ]]; then + case $color in + white) color="$COLOR_WHITE" ;; + grey) color="$COLOR_GREY" ;; + green|success) color="$COLOR_GREEN" ;; + blue|heading) color="$COLOR_BLUE" ;; + yellow|warning) color="$COLOR_YELLOW" ;; + red|error) color="$COLOR_RED"; echo_target=_builder_error_echo ;; + purple) color="$COLOR_PURPLE" ;; + brightwhite) color="$COLOR_BRIGHTWHITE" ;; + teal|debug) color="$COLOR_TEAL" ;; + setmark) mark="$HEADING_SETMARK" color="$COLOR_PURPLE" ;; + esac - if builder_is_dep_build; then - $echo_target -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" - else - $echo_target -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" - fi + if builder_is_dep_build; then + $echo_target -e "$mark$COLOR_GREY[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" else - # Cope with the case of pre-init message and just emit plain text - $echo_target -e "$message" + $echo_target -e "$mark$BUILDER_BOLD$COLOR_BRIGHT_WHITE[$THIS_SCRIPT_IDENTIFIER]$COLOR_RESET $color$message$COLOR_RESET" fi + else + # Cope with the case of pre-init message and just emit plain text + $echo_target -e "$message" fi if [[ "${action}" == "end" ]] && builder_is_running_on_teamcity; then @@ -379,7 +374,7 @@ _builder_item_is_target() { function _builder_warn_if_incomplete() { if [ -n "${_builder_current_action}" ]; then - builder_echo warning "$_builder_current_action never reported success or failure" + builder_echo warning "WARNING: $_builder_current_action never reported success or failure" # exit 1 # If we wanted this scenario to result in a forced build-script fail. fi @@ -394,6 +389,7 @@ _builder_failure_trap() { local action target _builder_cleanup_deps + _builder_timing_cleanup_and_report $trappedExitCode # Since 'exit' is also trapped, we can also handle end-of-script incomplete actions. if [[ $trappedExitCode == 0 ]]; then @@ -442,20 +438,70 @@ _builder_cleanup_deps() { # Child scripts #------------------------------------------------------------------------------------------ -_builder_execute_child() { - local action=$1 - local target=$2 +# +# Starts a child script build, passing current build dependency, inheritable +# options, and timing status through as parameters. This should be used rather +# than calling the build script directly, to avoid multiple builds of +# dependencies. +# +# Do not use builder standard options such as `--deps` or `--debug`. +# +# ### Parameters +# +# * 1: `script` path to script, relative to root of repo, with leading slash +# * 2+: `parameters` action(s), target(s), parameters for the child script to run +# +# ### Example +# +# ```bash +# builder_launch /core/build.sh configure,build:wasm --no-tests +# ``` +# +builder_launch() { + local script="$1" + local param + shift + if [[ ! "$script" =~ ^/(.+) ]]; then + builder_die "Error: builder_launch: script path must start with /, and is relative to repo root" + fi + + for param in "$@"; do + if [[ "${param}" =~ ${_builder_reserved_parameters} ]]; then + builder_die "Error: builder_launch: reserved parameter '${param}' used" + fi + done + + builder_echo grey "## script '$script $*' launched..." + _builder_execute_child_script "${KEYMAN_ROOT}${script}" "$@" + builder_echo grey "## script '$script $*' completed" +} + +_builder_execute_child() { + local action="$1" + local target="$2" local script="$THIS_SCRIPT_PATH/${_builder_target_paths[$target]}/build.sh" - builder_echo start "$action$target" "## $action$target starting..." + builder_echo grey "## child $action$target starting..." + _builder_execute_child_script "$script" "$action" + builder_echo grey "## child $action$target completed successfully" +} + +# ### Parameters +# +# * 1: `script` path to script +# * 2+: `parameters` action(s), target(s), parameters for the child script to run +_builder_execute_child_script() { + local script="$1" + # subsequent parameters passed as $@/$* below + shift # Build array of specified inheritable options local child_options=() local opt for opt in "${_builder_options_inheritable[@]}"; do - if builder_has_option $opt; then - child_options+=($opt) + if builder_has_option "${opt}"; then + child_options+=("${opt}") fi done @@ -471,16 +517,14 @@ _builder_execute_child() { --builder-child \ $_builder_build_deps \ $dep_flag "$dep_module" \ - $action \ - ${child_options[@]} \ + "$@" \ + "${child_options[@]}" \ $builder_verbose \ $builder_debug \ $_builder_offline \ - && ( - builder_echo end "$action$target" success "## $action$target completed successfully" - ) || ( + || ( result=$? - builder_echo end "$action$target" error "## $action$target failed with exit code $result" + builder_echo error "## child script '$script $*' failed with exit code $result" exit $result ) || exit $? # Required due to above subshell masking exit } @@ -681,6 +725,9 @@ builder_start_action() { fi _builder_current_action="${_builder_matched_action}" + # Note: we include the action's dependencies in timing + _builder_timing_start "${_builder_matched_action}" + # Build dependencies as required _builder_do_build_deps "${_builder_matched_action}" return 0 @@ -689,6 +736,139 @@ builder_start_action() { fi } +################################################################################################### +# Timing +################################################################################################### + +_builder_timing_setup() { + if $_builder_timings; then + export _builder_timing_report_file=`mktemp` + _builder_timing_start "build.sh:top" + fi +} + +_builder_timing_start() { + local context="$1" + if [[ ! -z "${_builder_timing_report_file:+x}" ]]; then + echo "START $THIS_SCRIPT_IDENTIFIER:$context $(date +%s.%N)" >> "$_builder_timing_report_file" + fi +} + +_builder_timing_stop() { + local context="$1" + if [[ ! -z "${_builder_timing_report_file:+x}" ]]; then + echo "STOP $THIS_SCRIPT_IDENTIFIER:$context $(date +%s.%N)" >> "$_builder_timing_report_file" + fi +} + +# +# Removes temporary `_builder_timing_report_file` file when top-level build +# script finishes and reports on timings if they were requested. +# +_builder_timing_cleanup_and_report() { + local build_exit_code="$1" + if builder_is_dep_build || builder_is_child_build || [[ -z ${_builder_timing_report_file:+x} ]]; then + return 0 + fi + + if [[ "$build_exit_code" != 0 ]]; then + # Hide the timings report if build failed, but still cleanup + _builder_timings=false + fi + + if $_builder_timings; then + _builder_timing_stop "build.sh:top" + + if ! command -v node > /dev/null; then + builder_warn "WARNING: node.js not found, skipping timings report" + else + builder_heading "## Build timings" + + # for simplicity of distribution, we embed the node.js script here + + # shellcheck disable=SC2016 + node -e ' + const fs = require("node:fs"); + const process = require("node:process"); + + let + COLOR_RED="", COLOR_GREEN="", COLOR_YELLOW="", COLOR_BLUE="", COLOR_PURPLE="", COLOR_TEAL="", + COLOR_WHITE="", COLOR_BRIGHT_WHITE="", COLOR_GREY="", COLOR_RESET="", BUILDER_BOLD=""; + + if(process.stdout.isTTY) { + COLOR_RED="\x1b[31m"; COLOR_GREEN="\x1b[32m"; COLOR_YELLOW="\x1b[33m"; COLOR_BLUE="\x1b[34m"; + COLOR_PURPLE="\x1b[35m"; COLOR_TEAL="\x1b[36m"; COLOR_WHITE="\x1b[38;5;252m"; + COLOR_BRIGHT_WHITE="\x1b[38;5;255m"; COLOR_GREY="\x1b[90m"; COLOR_RESET="\x1b(B\x1b[m"; + BUILDER_BOLD="\x1b[1m"; + } + + const log = fs.readFileSync(process.argv[process.argv.length-1], "utf8").split("\n"); + + let top = null; + const modules = {}; + const tree = []; + + for(const line of log) { + if(line.trim() == "") continue; + const [event, area, time] = line.split(" "); + const [module, action, target, sub] = area.split(":"); + if(event == "START") { + const mod = {area, module, action, target: target ? ":"+target : "", sub: sub ?? "", start:parseFloat(time), stop:null, modules:[]}; + if(!top) top = mod; + modules[area] = mod; + if(tree.length) { + tree[tree.length-1].modules.push(mod); + } + tree.push(mod); + } else if(event == "STOP") { + if(tree.length) { + const mod = tree.pop(); + if(mod.area != area) { + console.error(`Expected ${area} to equal ${mod.area}`); + } + } + const mod = modules[area]; + if(!mod) { + console.error(`START not found for ${area}`); + } else { + mod.stop = parseFloat(time); + } + } else { + console.error(`Unrecognized line: "${line}"`); + } + } + + if(tree.length) { + console.error(`Expected tree to be empty`); + console.dir(tree); + } + + if(!Object.keys(modules).length || !top) { + console.error(`Expected report to be non-empty`); + process.exit(0); + } + + // Write out the tree with Pretty Printing + + printModule("", top); + + function printModule(indent, mod) { + const duration = Math.round((mod.stop - mod.start) * 10) / 10; + console.log(`${indent}${BUILDER_BOLD}[${mod.module}]${COLOR_RESET} ${COLOR_GREEN}${mod.action}${mod.target}${COLOR_RESET} ${mod.sub} ${duration}`); + for(const submod of mod.modules) { + printModule(indent+" ", submod); + } + } + ' "${_builder_timing_report_file}" + fi + fi + + rm -f "$_builder_timing_report_file" + _builder_timing_report_file= +} + +################################################################################################### + # # Returns 0 if the user has --option on the command line # @@ -1098,6 +1278,7 @@ _builder_get_default_description() { :engine) description="engine module" ;; :module) description="this module" ;; :tools) description="build tools for this project" ;; + --timings) description="report on timings" ;; --debug) description="debug build" ;; --release) description="release build (prevents --debug in local-env builds)" ;; esac @@ -1227,10 +1408,16 @@ _builder_define_default_internal_dep() { # build:mac build:mac-x86_64 \ # build:mac build:mac-arm64 # -# Note: actions and targets must be fully specified, and this _must_ -# be called before either builder_describe_outputs or builder_parse in -# order for dependencies to be resolved. +# Note: actions and targets must be fully specified, and this _must_ be called +# before both of builder_describe_outputs and builder_parse in order for +# dependencies to be resolved. builder_describe_internal_dependency() { + _builder_record_function_call builder_describe_internal_dependency + + if _builder_has_function_been_called builder_parse && _builder_has_function_been_called builder_describe_outputs; then + builder_warn "WARNING: builder_describe_internal_dependency needs to be called before builder_parse and builder_describe_outputs have both been called" + fi + while [[ $# -gt 0 ]]; do local action_target=$1 dep_action_target=$2 [[ -z ${_builder_internal_dep[$action_target]+x} ]] && @@ -1320,6 +1507,16 @@ builder_parse() { _builder_parse_expanded_parameters() { _builder_build_deps=--deps builder_verbose= + + if [[ -z "${_builder_timings+x}" ]]; then + # Only set _builder_timings if not already set + if builder_is_ci_build; then + _builder_timings=true + else + _builder_timings=false + fi + export _builder_timings + fi builder_debug= local is_release=false local _params=($@) @@ -1356,7 +1553,7 @@ _builder_parse_expanded_parameters() { action=$new_action ;; *) - builder_warn "Parameter $action has $? matches, could mean any of {$new_action}" + builder_warn "ERROR: Parameter $action has $? matches, could mean any of {$new_action}" exit 1 ;; esac @@ -1367,7 +1564,7 @@ _builder_parse_expanded_parameters() { target=$new_target ;; *) - builder_warn "Parameter $target has $? matches, could mean any of {$new_target}" + builder_warn "ERROR: Parameter $target has $? matches, could mean any of {$new_target}" exit 1 ;; esac @@ -1425,6 +1622,7 @@ _builder_parse_expanded_parameters() { fi else + # See also _builder_reserved_parameters case "$key" in --help|-h) builder_display_usage @@ -1440,6 +1638,12 @@ _builder_parse_expanded_parameters() { _builder_chosen_options+=(--verbose) builder_verbose=--verbose ;; + --timings) + _builder_timings=true + ;; + --no-timings) + _builder_timings=false + ;; --debug) _builder_chosen_options+=(--debug) builder_debug=--debug @@ -1531,18 +1735,23 @@ _builder_parse_expanded_parameters() { fi if builder_is_dep_build; then - if [[ -z ${_builder_deps_built+x} ]]; then - builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" - fi + _builder_verify_expected_sub_process_variables elif builder_is_child_build; then - if [[ -z ${_builder_deps_built+x} ]]; then - builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" - fi + _builder_verify_expected_sub_process_variables else + + # For now, we'll leave this warning disabled, but may re-enable in #15130 + # if [[ ! -z "${_builder_deps_built+x}" ]]; then + # builder_warn "WARNING: Child build '${THIS_SCRIPT_IDENTIFIER}' instantiated without using builder_launch" + # fi + # This is a top-level invocation, so we want to track which dependencies # have been built, so they don't get built multiple times. export _builder_deps_built=`mktemp` + # We will also track timings + _builder_timing_setup + # Per #11106, local builds use --debug by default. # Second condition prevents the block (and message) from executing when --debug is already specified explicitly. if [[ ${KEYMAN_VERSION_ENVIRONMENT:-} == "local" ]] && [[ $builder_debug != --debug ]] && ! $is_release; then @@ -1577,6 +1786,17 @@ _builder_parse_expanded_parameters() { fi } +_builder_verify_expected_sub_process_variables() { + if [[ -z ${_builder_deps_built+x} ]]; then + builder_die "FATAL ERROR: Expected '_builder_deps_built' variable to be set" + fi + if $_builder_timings; then + if [[ -z ${_builder_timing_report_file+x} ]]; then + builder_die "FATAL ERROR: Expected '_builder_timing_report_file' variable to be set" + fi + fi +} + # # Returns 0 (true) if --builder-ignore-unknown-options flag has been specified # in the command line @@ -1598,21 +1818,14 @@ _builder_completion_describe() { echo -n "; " printf '%s ' "${_builder_targets[@]}" echo -n "; " - # Remove all '+' suffixes from options; they're a config on the option, not part - # of the actual option text itself. - local _builder_opts=() - for e in "${!_builder_params[@]}"; do - if [[ $e =~ ^-- ]]; then - _builder_opts+=(${e%+*}) - fi - done - # Add default options - _builder_opts+=( --verbose --debug --color --no-color --offline --help ) + local opts=("${_builder_options[@]}") + # Add default options; see also _builder_reserved_parameters + opts+=( --verbose --debug --release --color --no-color --offline --help --timings --no-timings ) if builder_has_dependencies; then - _builder_opts+=( --deps --no-deps --force-deps ) + opts+=( --deps --no-deps --force-deps ) fi - printf '%s ' "${_builder_opts[@]}" + printf '%s ' "${opts[@]}" } builder_display_usage() { @@ -1688,6 +1901,7 @@ builder_display_usage() { fi done + # See also _builder_reserved_parameters _builder_pad $width " --verbose, -v" "Verbose logging" _builder_pad $width " --debug, -d" "Debug build" _builder_pad $width " --color" "Force colorized output" @@ -1757,6 +1971,8 @@ builder_finish_action() { builder_echo end "$action_name" error "## $action_name failed with message: $result" fi + _builder_timing_stop "${_builder_matched_action}" + # Remove $action$target from the array; it is no longer a current action _builder_current_action= else @@ -1835,6 +2051,8 @@ _builder_do_build_deps() { return 0 fi + _builder_timing_start "$1:dependencies" + for dep in "${_builder_deps[@]}"; do # Don't attempt to build dependencies that don't match the current # action:target (wildcards supported for matches here) @@ -1863,7 +2081,7 @@ _builder_do_build_deps() { $_builder_build_deps \ --builder-dep-parent "$THIS_SCRIPT_IDENTIFIER" && ( if $_builder_debug_internal; then - builder_echo success "## Dependency $dep$dep_target for $_builder_matched_action_name successfully" + builder_echo success "## Dependency $dep$dep_target for $_builder_matched_action_name completed successfully" fi ) || ( result=$? @@ -1871,6 +2089,8 @@ _builder_do_build_deps() { exit $result ) || exit $? # Required due to above subshell masking exit done + + _builder_timing_stop "$1:dependencies" } # @@ -2117,13 +2337,13 @@ builder_describe_platform() { # For testing, we can override the current platform if [[ ! -z "${BUILDER_PLATFORM_OVERRIDE+x}" ]]; then - builder_warn "BUILDER_PLATFORM_OVERRIDE variable found. Overriding detected platform '$builder_platform' with '$BUILDER_PLATFORM_OVERRIDE'" + builder_warn "WARNING: BUILDER_PLATFORM_OVERRIDE variable found. Overriding detected platform '$builder_platform' with '$BUILDER_PLATFORM_OVERRIDE'" builder_platform="$BUILDER_PLATFORM_OVERRIDE" fi # For testing, we can override the current tools if [[ ! -z "${BUILDER_TOOLS_OVERRIDE+x}" ]]; then - builder_warn "BUILDER_TOOLS_OVERRIDE variable found. Overriding detected tools (${builder_installed_tools[@]}) with (${BUILDER_TOOLS_OVERRIDE[@]})" + builder_warn "WARNING: BUILDER_TOOLS_OVERRIDE variable found. Overriding detected tools (${builder_installed_tools[@]}) with (${BUILDER_TOOLS_OVERRIDE[@]})" builder_installed_tools=("${BUILDER_TOOLS_OVERRIDE[@]}") fi diff --git a/resources/devbox/macos/keyman.macos.env.sh b/resources/devbox/macos/keyman.macos.env.sh index 5ffb36eaaf..3c6a93634c 100755 --- a/resources/devbox/macos/keyman.macos.env.sh +++ b/resources/devbox/macos/keyman.macos.env.sh @@ -15,7 +15,7 @@ export PATH=$ANDROID_HOME/platform-tools:$PATH export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH export PATH=$ANDROID_HOME/build-tools/30.0.3:$PATH -if [ -z "$HOMEBREW_PREFIX" ]; then +if [ -z "${HOMEBREW_PREFIX+x}" ]; then HOMEBREW_PREFIX=`brew --prefix` fi diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index a659226528..2406ba7dee 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -57,9 +57,9 @@ reason is that the Docker images use a different user, so that paths will be different. **Warning:** On Windows, don't mix building in Windows/git-bash and WSL/Docker -without a full clean (`git clean -fdx`) of the repository. Mixed building will +without a full clean (`git clean -fdx`) of the repository. Mixed building will fail for many reasons, including, among others: -* many CLI tools have different names, and references are cached in configure +* many CLI tools have different names, and references are cached in configure steps * cached paths may be stored with backslashes which work only on Windows * Keyman Core has varying targets based on the build platform @@ -74,3 +74,87 @@ To run the tests locally, use the `run.sh` script: # Run common/web tests resources/docker-images/run.sh :web -- common/web/build.sh --debug test ``` + +## Using a Docker container registry + +It is possible to keep the Docker images in a container registry. + +The parameters for `run.sh` and `build.sh` are +* `--registry` + + the container registry used to keep the images +* `--username` + + a user name for the container registry +* `--password` + + the user's password + +`--username` and `--password` are not required, if the registry can be +accessed without username and password. + +The default for the registry is `ghcr.io`. + +Building the images for a registry and pushing them to it, looks like: + +```shell +resources/docker-images/build.sh --registry 'myregistry:5678' --username 'user' --password 'password' build,publish: +``` + +Running tests locally with the `run.sh` looks like: + +```shell +resources/docker-images/run.sh :web --registry 'myregistry:5678' --username 'user' --password 'password' -- web/build.sh test +``` + +## Running GitHub Actions locally + +There exist [GitHub actions for building the Docker images](../../.github/workflows/build-test-publish-docker.yml). +For testing and developing the workflow can be started locally. +As a prerequisite [act](https://nektosact.com/) must be installed. + +This is an exmaple for running the workflow with act: + +```bash +#!/bin/bash +# runs GitHub workflow locally for building, testing and publishing Docker images +# requires act (https://nektosact.com/) +# requires a GitHub token with repo permissions (e.g. GITHUB_TOKEN from a personal access token) +# requires Docker with buildx support + +tempfile=/tmp/event.json + +cat > ${tempfile} <> /usr/bin/bashwrapper +if [[ -v CHANGE_PERMISSIONS ]]; then + uid=\$(stat -c "%u" /home/build/build) + gid=\$(stat -c "%g" /home/build/build) + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Adjusting build directory ownership to uid/gid \$(id -un):\$(id -gn)" + sudo chown -R build:build /home/build/build + fi +fi + if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then /usr/bin/run-tests.sh "\${@:-bash}" else "\${@:-bash}" fi + +if [[ -v CHANGE_PERMISSIONS ]]; then + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Reverting build directory ownership to uid/gid \${uid}:\${gid}" + sudo chown -R \${uid}:\${gid} /home/build/build + fi +fi EOF # now, switch to build user diff --git a/resources/docker-images/base/Dockerfile b/resources/docker-images/base/Dockerfile index 835806d63c..0cba2b73dc 100644 --- a/resources/docker-images/base/Dockerfile +++ b/resources/docker-images/base/Dockerfile @@ -53,6 +53,7 @@ EOF # Install NVM RUN NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) && \ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash +RUN chmod -R +x,+r $HOME/ RUN <> /usr/bin/bashwrapper PATH=/home/build/.keyman/node:\$PATH export NVM_DIR="$HOME/.nvm" diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 46cf7d7618..c07e732efb 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -23,8 +23,12 @@ builder_describe \ "--distro=DISTRO The distribution to use for the base image (debian or ubuntu, default: ubuntu)" \ "--distro-version=DISTRO_VERSION The Ubuntu/Debian version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ "--no-cache Force rebuild of docker images" \ + "--registry=REGISTRY The container registry to keep the images (default: ghcr.io)" \ + "--username=REGISTRY_USERNAME The user for the container registry" \ + "--password=REGISTRY_PASSWORD The password for the container registry user" \ "build Build docker images" \ - "test Test the docker images by running configure,build,test for all or the specified platforms" + "test Test the docker images by running configure,build,test for all or the specified platforms" \ + "publish Push the docker images into the container registry" builder_parse "$@" @@ -47,17 +51,35 @@ build_action() { # shellcheck disable=SC2164 cd "${platform}" export DOCKER_BUILDKIT=1 - # shellcheck disable=SC2248,SC2086 - docker_wrapper build ${OPTION_NO_CACHE:-} -t "keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . + # shellcheck disable=SC2248,SC2086,SC2154 + docker_wrapper build ${OPTION_NO_CACHE:-} -t "${registry_slash}keymanapp/keyman-${platform}-ci:${build_version}" "${build_args[@]}" . # If the user didn't specify particular versions we will additionaly create an image # with the tag 'default'. if is_default_values; then builder_echo debug "Setting default tag for ${platform}" - docker_wrapper build -t "keymanapp/keyman-${platform}-ci:default" "${build_args[@]}" . + docker_wrapper tag "${registry_slash}keymanapp/keyman-${platform}-ci:${build_version}" \ + "${registry_slash}keymanapp/keyman-${platform}-ci:default" fi # shellcheck disable=SC2164,SC2103 cd - - builder_echo success "Docker image 'keymanapp/keyman-${platform}-ci:${build_version}' built" + # shellcheck disable=SC2154 + builder_echo success "Docker image '${registry_slash}keymanapp/keyman-${platform}-ci:${build_version}' built" +} + +publish_action() { + local platform=$1 + local version_list=("${build_version}") + + builder_echo debug "publishing image for ${platform}." + + if is_default_values; then + version_list+=("default") + fi + + for version in "${version_list[@]}"; do + builder_echo debug "Pushing image ${registry_slash}keymanapp/keyman-${platform}-ci:${version}" + docker_wrapper push "${registry_slash}keymanapp/keyman-${platform}-ci:${version}" + done } test_action() { @@ -65,12 +87,13 @@ test_action() { builder_echo debug "Testing image for ${platform}" ./run.sh --distro "${DISTRO}" --distro-version "${DISTRO_VERSION}" \ - ":${platform}" -- ./build.sh configure,build,test:"${platform}" + ":${platform}" "${registry_parameters:-}" -- ./build.sh configure,build,test:"${platform}" } check_buildx_available check_for_default_values convert_parameters_to_args +setup_container_registry if builder_has_action build; then build_action base @@ -87,3 +110,10 @@ builder_run_action test:web test_action web # Android uses artifacts from web, so it has to come after web builder_run_action test:android test_action android builder_run_action test:developer test_action developer + +builder_run_action publish:base publish_action base +builder_run_action publish:core publish_action core +builder_run_action publish:linux publish_action linux +builder_run_action publish:web publish_action web +builder_run_action publish:android publish_action android +builder_run_action publish:developer publish_action developer diff --git a/resources/docker-images/core/Dockerfile b/resources/docker-images/core/Dockerfile index 8b3e2b4c7a..0589b8c2ee 100644 --- a/resources/docker-images/core/Dockerfile +++ b/resources/docker-images/core/Dockerfile @@ -6,7 +6,8 @@ # - ARG REQUIRED_EMSCRIPTEN_VERSION ARG BASE_VERSION=default -FROM keymanapp/keyman-base-ci:${BASE_VERSION} +ARG REGISTRY= +FROM ${REGISTRY}keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" @@ -33,10 +34,26 @@ RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/ # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper -if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then - /usr/bin/run-tests.sh "\${@:-bash}" -else - "\${@:-bash}" +if [[ -v CHANGE_PERMISSIONS ]]; then + uid=\$(stat -c "%u" /home/build/build) + gid=\$(stat -c "%g" /home/build/build) + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Adjusting build directory ownership to uid/gid \$(id -un):\$(id -gn)" + sudo chown -R build:build /home/build/build + fi +fi + + if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then + /usr/bin/run-tests.sh "\${@:-bash}" + else + "\${@:-bash}" + fi + +if [[ -v CHANGE_PERMISSIONS ]]; then + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Reverting build directory ownership to uid/gid \${uid}:\${gid}" + sudo chown -R \${uid}:\${gid} /home/build/build + fi fi EOF diff --git a/resources/docker-images/developer/Dockerfile b/resources/docker-images/developer/Dockerfile index aaec7b55cf..19c69c5e98 100644 --- a/resources/docker-images/developer/Dockerfile +++ b/resources/docker-images/developer/Dockerfile @@ -6,7 +6,8 @@ # - ARG REQUIRED_EMSCRIPTEN_VERSION=unset ARG BASE_VERSION=default -FROM keymanapp/keyman-base-ci:${BASE_VERSION} +ARG REGISTRY= +FROM ${REGISTRY}keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" @@ -33,11 +34,27 @@ RUN echo "export EMSCRIPTEN_BASE=/home/build/emsdk/upstream/emscripten" >> /usr/ # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper +if [[ -v CHANGE_PERMISSIONS ]]; then + uid=\$(stat -c "%u" /home/build/build) + gid=\$(stat -c "%g" /home/build/build) + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Adjusting build directory ownership to uid/gid \$(id -un):\$(id -gn)" + sudo chown -R build:build /home/build/build + fi +fi + if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then /usr/bin/run-tests.sh "\${@:-bash}" else "\${@:-bash}" fi + +if [[ -v CHANGE_PERMISSIONS ]]; then + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Reverting build directory ownership to uid/gid \${uid}:\${gid}" + sudo chown -R \${uid}:\${gid} /home/build/build + fi +fi EOF # now, switch to build user diff --git a/resources/docker-images/docker-build.inc.sh b/resources/docker-images/docker-build.inc.sh index 5183db9719..e6427a75cb 100644 --- a/resources/docker-images/docker-build.inc.sh +++ b/resources/docker-images/docker-build.inc.sh @@ -83,9 +83,45 @@ docker_wrapper() { } setup_docker() { + DOCKER_RUN_ARGS=() if [[ "${MSYSTEM:-}" == "MINGW64" ]]; then - DOCKER_RUN_ARGS="--env DOCKER_RUN_AS_ROOT=1" + DOCKER_RUN_ARGS+=(--env DOCKER_RUN_AS_ROOT=1) + fi + if builder_is_running_on_gha ; then + DOCKER_RUN_ARGS+=(--env CHANGE_PERMISSIONS=1) else - DOCKER_RUN_ARGS= + DOCKER_RUN_ARGS+=(-t) + fi + + if builder_has_option --remote-debug; then + DOCKER_RUN_ARGS+=(-p 2345:2345) fi } + +setup_container_registry() { + registry=${REGISTRY:-ghcr.io} + local username=${REGISTRY_USERNAME:-} + local password=${REGISTRY_PASSWORD:-} + registry_slash='' + + builder_echo debug "Setting up container registry '${registry}'." + + registry_slash="${registry}/" + registry_parameters="--registry ${registry}" + build_args+=("--build-arg=REGISTRY=${registry_slash}") + + if [[ -n ${username:-} ]] ; then + if [[ -z ${password:-} ]] ; then + builder_echo debug "No password specified for container registry user '${username}'. Waiting for password on stdin." + read -r password + fi + docker_wrapper login "${registry}" -u "${username}" --password-stdin << EOF +${password} +EOF + registry_parameters="${registry_parameters} --username ${username} --password ${password}" + else + builder_echo debug "No username specified for container registry '${registry}'." + fi + +} + diff --git a/resources/docker-images/linux/Dockerfile b/resources/docker-images/linux/Dockerfile index da6bf59906..084b0f16c7 100644 --- a/resources/docker-images/linux/Dockerfile +++ b/resources/docker-images/linux/Dockerfile @@ -2,7 +2,8 @@ # Keyman is copyright (C) SIL Global. MIT License. ARG BASE_VERSION=default -FROM keymanapp/keyman-base-ci:${BASE_VERSION} +ARG REGISTRY= +FROM ${REGISTRY}keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" LABEL org.opencontainers.image.title="Keyman Linux Build Image" @@ -11,7 +12,7 @@ LABEL org.opencontainers.image.title="Keyman Linux Build Image" ADD control /tmp/control # Answer 'yes' to install questions RUN apt-get update && \ - apt-get install -qy python3 python3-setuptools python3-coverage \ + apt-get install -qy python3 python3-setuptools python3-coverage jq \ devscripts equivs libdatetime-perl lcov gcovr xvfb \ xserver-xephyr metacity mutter dbus-x11 weston xwayland && \ (yes | mk-build-deps --install /tmp/control) || true && \ @@ -41,11 +42,28 @@ COPY run-tests.sh /usr/bin/run-tests.sh # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper +if [[ -v CHANGE_PERMISSIONS ]]; then + uid=\$(stat -c "%u" /home/build/build) + gid=\$(stat -c "%g" /home/build/build) + + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Adjusting build directory ownership to uid/gid \$(id -un):\$(id -gn)" + sudo chown -R build:build /home/build/build + fi +fi + if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then /usr/bin/run-tests.sh "\${@:-bash}" else "\${@:-bash}" fi + +if [[ -v CHANGE_PERMISSIONS ]]; then + if [[ "\${uid}" != "\$(id -u)" && "\${gid}" != "\$(id -g)" ]]; then + echo "Reverting build directory ownership to uid/gid \${uid}:\${gid}" + sudo chown -R \${uid}:\${gid} /home/build/build + fi +fi EOF # now, switch to build user diff --git a/resources/docker-images/run.sh b/resources/docker-images/run.sh index 06541f7614..534e84eebf 100755 --- a/resources/docker-images/run.sh +++ b/resources/docker-images/run.sh @@ -6,9 +6,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +# shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh" . "${KEYMAN_ROOT}/resources/docker-images/docker-build.inc.sh" -. "$KEYMAN_ROOT/resources/build/utils.inc.sh" +. "${KEYMAN_ROOT}/resources/build/utils.inc.sh" ################################ Main script ################################ @@ -21,13 +22,18 @@ builder_describe \ ":web" \ "run+ Run command in docker image" \ "--distro=DISTRO The distribution (debian or ubuntu, default: ubuntu)" \ - "--distro-version=DISTRO_VERSION The Ubuntu/Debian version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" + "--distro-version=DISTRO_VERSION The Ubuntu/Debian version (default: ${KEYMAN_DEFAULT_VERSION_UBUNTU_CONTAINER})" \ + "--registry=REGISTRY The container registry to keep the images (default: ghcr.io/keymanapp)" \ + "--username=REGISTRY_USERNAME The user for the container registry (default: keymanapp)" \ + "--password=REGISTRY_PASSWORD The password for the container registry user (default: password)" \ + "--remote-debug Expose the container's port 2345 for remote debugging" builder_parse "$@" check_for_default_values convert_parameters_to_args setup_docker +setup_container_registry if is_default_values; then image_version=default @@ -38,42 +44,44 @@ else fi run_android() { - docker_wrapper run ${DOCKER_RUN_ARGS} -it --rm -v "${KEYMAN_ROOT}":/home/build/build \ + docker_wrapper run "${DOCKER_RUN_ARGS[@]}" -i --rm -v "${KEYMAN_ROOT}":/home/build/build \ -v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \ - "keymanapp/keyman-android-ci:${image_version}" \ + "${registry_slash}keymanapp/keyman-android-ci:${image_version}" \ "${builder_extra_params[@]}" } run_core() { - docker_wrapper run ${DOCKER_RUN_ARGS} -it --rm -v "${KEYMAN_ROOT}":/home/build/build \ + docker_wrapper run "${DOCKER_RUN_ARGS[@]}" -i --rm -v "${KEYMAN_ROOT}":/home/build/build \ -v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \ - "keymanapp/keyman-core-ci:${image_version}" \ + "${registry_slash}keymanapp/keyman-core-ci:${image_version}" \ "${builder_extra_params[@]}" } run_developer() { - docker_wrapper run ${DOCKER_RUN_ARGS} -it --rm -v "${KEYMAN_ROOT}":/home/build/build \ + docker_wrapper run "${DOCKER_RUN_ARGS[@]}" -i --rm -v "${KEYMAN_ROOT}":/home/build/build \ -v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \ - "keymanapp/keyman-developer-ci:${image_version}" \ + "${registry_slash}keymanapp/keyman-developer-ci:${image_version}" \ "${builder_extra_params[@]}" } run_linux() { mkdir -p "${KEYMAN_ROOT}/linux/build/docker-linux/${build_dir}" mkdir -p "${KEYMAN_ROOT}/linux/keyman-system-service/build/docker-linux/${build_dir}" - docker_wrapper run ${DOCKER_RUN_ARGS} -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ + mkdir -p "${KEYMAN_ROOT}/linux/mcompile/keymap/build/docker-linux/${build_dir}" + docker_wrapper run "${DOCKER_RUN_ARGS[@]}" -i --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ -v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \ -v "${KEYMAN_ROOT}/linux/build/docker-linux/${build_dir}":/home/build/build/linux/build \ -v "${KEYMAN_ROOT}/linux/keyman-system-service/build/docker-linux/${build_dir}":/home/build/build/linux/keyman-system-service/build \ + -v "${KEYMAN_ROOT}/linux/mcompile/keymap/build/docker-linux/${build_dir}":/home/build/build/linux/mcompile/keymap/build \ -e DESTDIR=/tmp \ - "keymanapp/keyman-linux-ci:${image_version}" \ + "${registry_slash}keymanapp/keyman-linux-ci:${image_version}" \ "${builder_extra_params[@]}" } run_web() { - docker_wrapper run ${DOCKER_RUN_ARGS} -it --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ + docker_wrapper run "${DOCKER_RUN_ARGS[@]}" -i --privileged --rm -v "${KEYMAN_ROOT}":/home/build/build \ -v "${KEYMAN_ROOT}/core/build/docker-core/${build_dir}":/home/build/build/core/build \ - "keymanapp/keyman-web-ci:${image_version}" \ + "${registry_slash}keymanapp/keyman-web-ci:${image_version}" \ "${builder_extra_params[@]}" } diff --git a/resources/docker-images/web/Dockerfile b/resources/docker-images/web/Dockerfile index 655a2d8592..fddc8a18b4 100644 --- a/resources/docker-images/web/Dockerfile +++ b/resources/docker-images/web/Dockerfile @@ -6,7 +6,8 @@ # - ARG REQUIRED_EMSCRIPTEN_VERSION=unset ARG BASE_VERSION=default -FROM keymanapp/keyman-base-ci:${BASE_VERSION} +ARG REGISTRY= +FROM ${REGISTRY}keymanapp/keyman-base-ci:${BASE_VERSION} LABEL org.opencontainers.image.authors="SIL Global." LABEL org.opencontainers.image.url="https://github.com/keymanapp/keyman.git" @@ -57,11 +58,24 @@ RUN curl https://packages.mozilla.org/apt/repo-signing-key.gpg > /etc/apt/keyrin # Finish bashwrapper script and adjust permissions RUN <> /usr/bin/bashwrapper +uid=\$(stat -c "%u" /home/build/build) +gid=\$(stat -c "%g" /home/build/build) + +if [[ "\${uid}" != "\$(id -u)" || "\${gid}" != "\$(id -g)" ]]; then + echo "Adjusting build directory ownership to uid/gid \$(id -un):\$(id -gn)" + sudo chown -R build:build /home/build/build +fi + if [[ "\$@" =~ test ]] && [ -f /usr/bin/run-tests.sh ]; then /usr/bin/run-tests.sh "\${@:-bash}" else "\${@:-bash}" fi + +if [[ "\${uid}" != "\$(id -u)" || "\${gid}" != "\$(id -g)" ]]; then + echo "Reverting build directory ownership to uid/gid \${uid}:\${gid}" + sudo chown -R \${uid}:\${gid} /home/build/build +fi EOF # now, switch to build user diff --git a/resources/git-hooks/prepare-commit-msg b/resources/git-hooks/prepare-commit-msg index 4fd7bdffea..0db32abff3 100755 --- a/resources/git-hooks/prepare-commit-msg +++ b/resources/git-hooks/prepare-commit-msg @@ -114,18 +114,25 @@ function prepend_scope() { fi fi + commentchar="#" + if [[ -n "$(git config core.commentString)" ]]; then + commentchar="$(git config core.commentString)" + elif [[ -n "$(git config core.commentChar)" ]]; then + commentchar="$(git config core.commentChar)" + fi + postfix=$( cat < -# - Use imperative, present tense ('attach' not 'attaches', 'attached' etc) -# - Don't include a period at the end of the title -# - Always include a blank line before trailers -# - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes +\n${commentchar} Keyman Conventional Commit suggestions: +${commentchar} +${commentchar} - Link to a Sentry issue with git trailer: +${commentchar} Fixes: _MODULE_-_ID_ +${commentchar} - Give credit to co-authors: +${commentchar} Co-authored-by: _Name_ <_email_> +${commentchar} - Use imperative, present tense ('attach' not 'attaches', 'attached' etc) +${commentchar} - Don't include a period at the end of the title +${commentchar} - Always include a blank line before trailers +${commentchar} - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes EOF ) @@ -170,4 +177,4 @@ case $COMMIT_SOURCE in # Known possibilities: message (-m), template (-t) exit 0 ;; -esac \ No newline at end of file +esac diff --git a/resources/standards-data/readme.md b/resources/standards-data/readme.md index 51d2079929..caeff56d19 100644 --- a/resources/standards-data/readme.md +++ b/resources/standards-data/readme.md @@ -57,13 +57,9 @@ See also [ldml-keyboards/readme.md](ldml-keyboards/readme.md). ### unicode-character-database -Data from : +Data from : -```bash -UNICODE_VERSION=15.1.0 -wget https://www.unicode.org/Public/${UNICODE_VERSION}/ucd/Blocks.txt -O unicode-character-database/Blocks.txt -wget https://www.unicode.org/Public/${UNICODE_VERSION}/ucd/UnicodeData.txt -O unicode-character-database/UnicodeData.txt -``` +See [download.sh](./unicode-character-database/download.sh) ### windows-lcid-to-bcp-47 diff --git a/resources/standards-data/unicode-character-database/Blocks.txt b/resources/standards-data/unicode-character-database/Blocks.txt index 1517dde0ce..5c24ab60cb 100644 --- a/resources/standards-data/unicode-character-database/Blocks.txt +++ b/resources/standards-data/unicode-character-database/Blocks.txt @@ -1,6 +1,6 @@ -# Blocks-16.0.0.txt -# Date: 2024-02-02 -# © 2024 Unicode®, Inc. +# Blocks-17.0.0.txt +# Date: 2025-08-01 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -228,6 +228,7 @@ FFF0..FFFF; Specials 108E0..108FF; Hatran 10900..1091F; Phoenician 10920..1093F; Lydian +10940..1095F; Sidetic 10980..1099F; Meroitic Hieroglyphs 109A0..109FF; Meroitic Cursive 10A00..10A5F; Kharoshthi @@ -279,11 +280,13 @@ FFF0..FFFF; Specials 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 11AC0..11AFF; Pau Cin Hau 11B00..11B5F; Devanagari Extended-A +11B60..11B7F; Sharada Supplement 11BC0..11BFF; Sunuwar 11C00..11C6F; Bhaiksuki 11C70..11CBF; Marchen 11D00..11D5F; Masaram Gondi 11D60..11DAF; Gunjala Gondi +11DB0..11DEF; Tolong Siki 11EE0..11EFF; Makasar 11F00..11F5F; Kawi 11FB0..11FBF; Lisu Supplement @@ -304,12 +307,14 @@ FFF0..FFFF; Specials 16B00..16B8F; Pahawh Hmong 16D40..16D7F; Kirat Rai 16E40..16E9F; Medefaidrin +16EA0..16EDF; Beria Erfe 16F00..16F9F; Miao 16FE0..16FFF; Ideographic Symbols and Punctuation 17000..187FF; Tangut 18800..18AFF; Tangut Components 18B00..18CFF; Khitan Small Script 18D00..18D7F; Tangut Supplement +18D80..18DFF; Tangut Components Supplement 1AFF0..1AFFF; Kana Extended-B 1B000..1B0FF; Kana Supplement 1B100..1B12F; Kana Extended-A @@ -318,6 +323,7 @@ FFF0..FFFF; Specials 1BC00..1BC9F; Duployan 1BCA0..1BCAF; Shorthand Format Controls 1CC00..1CEBF; Symbols for Legacy Computing Supplement +1CEC0..1CEFF; Miscellaneous Symbols Supplement 1CF00..1CFCF; Znamenny Musical Notation 1D000..1D0FF; Byzantine Musical Symbols 1D100..1D1FF; Musical Symbols @@ -336,6 +342,7 @@ FFF0..FFFF; Specials 1E2C0..1E2FF; Wancho 1E4D0..1E4FF; Nag Mundari 1E5D0..1E5FF; Ol Onal +1E6C0..1E6FF; Tai Yo 1E7E0..1E7FF; Ethiopic Extended-B 1E800..1E8DF; Mende Kikakui 1E900..1E95F; Adlam @@ -367,6 +374,7 @@ FFF0..FFFF; Specials 2F800..2FA1F; CJK Compatibility Ideographs Supplement 30000..3134F; CJK Unified Ideographs Extension G 31350..323AF; CJK Unified Ideographs Extension H +323B0..3347F; CJK Unified Ideographs Extension J E0000..E007F; Tags E0100..E01EF; Variation Selectors Supplement F0000..FFFFF; Supplementary Private Use Area-A diff --git a/resources/standards-data/unicode-character-database/UnicodeData.txt b/resources/standards-data/unicode-character-database/UnicodeData.txt index 64258a3739..fca68e3e15 100644 --- a/resources/standards-data/unicode-character-database/UnicodeData.txt +++ b/resources/standards-data/unicode-character-database/UnicodeData.txt @@ -659,7 +659,7 @@ 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; -0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; +0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Lo;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; @@ -2121,6 +2121,7 @@ 088C;ARABIC LETTER TAH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 088D;ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 088E;ARABIC VERTICAL TAIL;Lo;0;AL;;;;;N;;;;; +088F;ARABIC LETTER NOON WITH RING ABOVE;Lo;0;AL;;;;;N;;;;; 0890;ARABIC POUND MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0891;ARABIC PIASTRE MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0897;ARABIC PEPET;Mn;230;NSM;;;;;N;;;;; @@ -2862,6 +2863,7 @@ 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; +0C5C;TELUGU ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0C5D;TELUGU LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; @@ -2958,6 +2960,7 @@ 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; +0CDC;KANNADA ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0CDD;KANNADA LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; @@ -6137,6 +6140,33 @@ 1ACC;COMBINING LATIN SMALL LETTER INSULAR G;Mn;230;NSM;;;;;N;;;;; 1ACD;COMBINING LATIN SMALL LETTER INSULAR R;Mn;230;NSM;;;;;N;;;;; 1ACE;COMBINING LATIN SMALL LETTER INSULAR T;Mn;230;NSM;;;;;N;;;;; +1ACF;COMBINING DOUBLE CARON;Mn;230;NSM;;;;;N;;;;; +1AD0;COMBINING VERTICAL-LINE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD1;COMBINING GRAVE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD2;COMBINING VERTICAL-LINE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD3;COMBINING ACUTE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD4;COMBINING VERTICAL-LINE-MACRON;Mn;230;NSM;;;;;N;;;;; +1AD5;COMBINING MACRON-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD6;COMBINING VERTICAL-LINE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD7;COMBINING VERTICAL-LINE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD8;COMBINING MACRON-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD9;COMBINING SHARP SIGN;Mn;230;NSM;;;;;N;;;;; +1ADA;COMBINING FLAT SIGN;Mn;230;NSM;;;;;N;;;;; +1ADB;COMBINING DOWN TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1ADC;COMBINING DIAERESIS WITH RAISED LEFT DOT;Mn;230;NSM;;;;;N;;;;; +1ADD;COMBINING DOT-AND-RING BELOW;Mn;220;NSM;;;;;N;;;;; +1AE0;COMBINING LEFT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE1;COMBINING RIGHT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE2;COMBINING MINUS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE3;COMBINING INVERTED BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE4;COMBINING SQUARE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE5;COMBINING SEAGULL ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE6;COMBINING DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;;;;; +1AE7;COMBINING DOUBLE ARCH ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE8;COMBINING EQUALS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE9;COMBINING LEFT ANGLE CENTRED ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEA;COMBINING UPWARDS ARROW ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEB;COMBINING DOUBLE RIGHTWARDS ARROW ABOVE;Mn;234;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -7545,6 +7575,7 @@ 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20BF;BITCOIN SIGN;Sc;0;ET;;;;;N;;;;; 20C0;SOM SIGN;Sc;0;ET;;;;;N;;;;; +20C1;SAUDI RIYAL SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; @@ -10239,6 +10270,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B96;EQUALS SIGN WITH INFINITY ABOVE;So;0;ON;;;;;N;;;;; 2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -14274,10 +14306,14 @@ A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 A7CB;LATIN CAPITAL LETTER RAMS HORN;Lu;0;L;;;;;N;;;;0264; A7CC;LATIN CAPITAL LETTER S WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A7CD; A7CD;LATIN SMALL LETTER S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A7CC;;A7CC +A7CE;LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE;Lu;0;L;;;;;N;;;;A7CF; +A7CF;LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;;;A7CE;;A7CE A7D0;LATIN CAPITAL LETTER CLOSED INSULAR G;Lu;0;L;;;;;N;;;;A7D1; A7D1;LATIN SMALL LETTER CLOSED INSULAR G;Ll;0;L;;;;;N;;;A7D0;;A7D0 -A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;;; -A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;;; +A7D2;LATIN CAPITAL LETTER DOUBLE THORN;Lu;0;L;;;;;N;;;;A7D3; +A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;A7D2;;A7D2 +A7D4;LATIN CAPITAL LETTER DOUBLE WYNN;Lu;0;L;;;;;N;;;;A7D5; +A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;A7D4;;A7D4 A7D6;LATIN CAPITAL LETTER MIDDLE SCOTS S;Lu;0;L;;;;;N;;;;A7D7; A7D7;LATIN SMALL LETTER MIDDLE SCOTS S;Ll;0;L;;;;;N;;;A7D6;;A7D6 A7D8;LATIN CAPITAL LETTER SIGMOID S;Lu;0;L;;;;;N;;;;A7D9; @@ -14285,6 +14321,7 @@ A7D9;LATIN SMALL LETTER SIGMOID S;Ll;0;L;;;;;N;;;A7D8;;A7D8 A7DA;LATIN CAPITAL LETTER LAMBDA;Lu;0;L;;;;;N;;;;A7DB; A7DB;LATIN SMALL LETTER LAMBDA;Ll;0;L;;;;;N;;;A7DA;;A7DA A7DC;LATIN CAPITAL LETTER LAMBDA WITH STROKE;Lu;0;L;;;;;N;;;;019B; +A7F1;MODIFIER LETTER CAPITAL S;Lm;0;L; 0053;;;;N;;;;; A7F2;MODIFIER LETTER CAPITAL C;Lm;0;L; 0043;;;;N;;;;; A7F3;MODIFIER LETTER CAPITAL F;Lm;0;L; 0046;;;;N;;;;; A7F4;MODIFIER LETTER CAPITAL Q;Lm;0;L; 0051;;;;N;;;;; @@ -15925,6 +15962,22 @@ FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBC2;ARABIC SYMBOL WASLA ABOVE;Sk;0;AL;;;;;N;;;;; +FBC3;ARABIC LIGATURE JALLA WA-ALAA;So;0;ON;;;;;N;;;;; +FBC4;ARABIC LIGATURE DAAMAT BARAKAATUHUM;So;0;ON;;;;;N;;;;; +FBC5;ARABIC LIGATURE RAHMATU ALLAAHI TAAALAA ALAYH;So;0;ON;;;;;N;;;;; +FBC6;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIM;So;0;ON;;;;;N;;;;; +FBC7;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIMAA;So;0;ON;;;;;N;;;;; +FBC8;ARABIC LIGATURE RAHIMAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBC9;ARABIC LIGATURE RAHIMAHUMAA ALLAAH;So;0;ON;;;;;N;;;;; +FBCA;ARABIC LIGATURE RAHIMAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUM;So;0;ON;;;;;N;;;;; +FBCC;ARABIC LIGATURE HAFIZAHU ALLAAH;So;0;ON;;;;;N;;;;; +FBCD;ARABIC LIGATURE HAFIZAHU ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCE;ARABIC LIGATURE HAFIZAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCF;ARABIC LIGATURE HAFIZAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBD0;ARABIC LIGATURE SALLALLAAHU TAAALAA ALAYHI WA-SALLAM;So;0;ON;;;;;N;;;;; +FBD1;ARABIC LIGATURE AJJAL ALLAAHU FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FBD2;ARABIC LIGATURE ALAYHI AR-RAHMAH;So;0;ON;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; @@ -16370,6 +16423,8 @@ FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; +FD90;ARABIC LIGATURE RAHMATU ALLAAHI ALAYH;So;0;ON;;;;;N;;;;; +FD91;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA;So;0;ON;;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; @@ -16424,6 +16479,13 @@ FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; +FDC8;ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA;So;0;ON;;;;;N;;;;; +FDC9;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANH;So;0;ON;;;;;N;;;;; +FDCA;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHAA;So;0;ON;;;;;N;;;;; +FDCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUMAA;So;0;ON;;;;;N;;;;; +FDCC;ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM;So;0;ON;;;;;N;;;;; +FDCD;ARABIC LIGATURE AJJAL ALLAAHU TAAALAA FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FDCE;ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH;So;0;ON;;;;;N;;;;; FDCF;ARABIC LIGATURE SALAAMUHU ALAYNAA;So;0;ON;;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; @@ -18708,6 +18770,32 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; +10940;SIDETIC LETTER N01;Lo;0;R;;;;;N;;;;; +10941;SIDETIC LETTER N02;Lo;0;R;;;;;N;;;;; +10942;SIDETIC LETTER N03;Lo;0;R;;;;;N;;;;; +10943;SIDETIC LETTER N04;Lo;0;R;;;;;N;;;;; +10944;SIDETIC LETTER N05;Lo;0;R;;;;;N;;;;; +10945;SIDETIC LETTER N06;Lo;0;R;;;;;N;;;;; +10946;SIDETIC LETTER N07;Lo;0;R;;;;;N;;;;; +10947;SIDETIC LETTER N08;Lo;0;R;;;;;N;;;;; +10948;SIDETIC LETTER N09;Lo;0;R;;;;;N;;;;; +10949;SIDETIC LETTER N10;Lo;0;R;;;;;N;;;;; +1094A;SIDETIC LETTER N11;Lo;0;R;;;;;N;;;;; +1094B;SIDETIC LETTER N12;Lo;0;R;;;;;N;;;;; +1094C;SIDETIC LETTER N13;Lo;0;R;;;;;N;;;;; +1094D;SIDETIC LETTER N14;Lo;0;R;;;;;N;;;;; +1094E;SIDETIC LETTER N15;Lo;0;R;;;;;N;;;;; +1094F;SIDETIC LETTER N16;Lo;0;R;;;;;N;;;;; +10950;SIDETIC LETTER N17;Lo;0;R;;;;;N;;;;; +10951;SIDETIC LETTER N18;Lo;0;R;;;;;N;;;;; +10952;SIDETIC LETTER N19;Lo;0;R;;;;;N;;;;; +10953;SIDETIC LETTER N20;Lo;0;R;;;;;N;;;;; +10954;SIDETIC LETTER N21;Lo;0;R;;;;;N;;;;; +10955;SIDETIC LETTER N22;Lo;0;R;;;;;N;;;;; +10956;SIDETIC LETTER N23;Lo;0;R;;;;;N;;;;; +10957;SIDETIC LETTER N24;Lo;0;R;;;;;N;;;;; +10958;SIDETIC LETTER N25;Lo;0;R;;;;;N;;;;; +10959;SIDETIC LETTER N26;Lo;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; @@ -19541,6 +19629,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EC2;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC3;ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC4;ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; +10EC5;ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW;Lm;0;AL;;;;;N;;;;; +10EC6;ARABIC LETTER THIN NOON;Lo;0;AL;;;;;N;;;;; +10EC7;ARABIC LETTER YEH WITH FOUR DOTS BELOW;Lo;0;AL;;;;;N;;;;; +10ED0;ARABIC BIBLICAL END OF VERSE;Po;0;ON;;;;;N;;;;; +10ED1;ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED2;ARABIC LIGATURE ALAYHIM AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED3;ARABIC LIGATURE ALAYHIMAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED4;ARABIC LIGATURE QADDASA ALLAAHU SIRRAH;So;0;ON;;;;;N;;;;; +10ED5;ARABIC LIGATURE QUDDISA SIRRUHUM;So;0;ON;;;;;N;;;;; +10ED6;ARABIC LIGATURE QUDDISA SIRRUHUMAA;So;0;ON;;;;;N;;;;; +10ED7;ARABIC LIGATURE QUDDISAT ASRAARUHUM;So;0;ON;;;;;N;;;;; +10ED8;ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH;So;0;ON;;;;;N;;;;; +10EFA;ARABIC DOUBLE VERTICAL BAR BELOW;Mn;220;NSM;;;;;N;;;;; +10EFB;ARABIC SMALL LOW NOON;Mn;220;NSM;;;;;N;;;;; 10EFC;ARABIC COMBINING ALEF OVERLAY;Mn;0;NSM;;;;;N;;;;; 10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; 10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; @@ -21521,6 +21623,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; +11B60;SHARADA VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; +11B61;SHARADA VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; +11B62;SHARADA VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; +11B63;SHARADA VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; +11B64;SHARADA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; +11B65;SHARADA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; +11B66;SHARADA VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; +11B67;SHARADA VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 11BC0;SUNUWAR LETTER DEVI;Lo;0;L;;;;;N;;;;; 11BC1;SUNUWAR LETTER TASLA;Lo;0;L;;;;;N;;;;; 11BC2;SUNUWAR LETTER EKO;Lo;0;L;;;;;N;;;;; @@ -21868,6 +21978,60 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +11DB0;TOLONG SIKI LETTER I;Lo;0;L;;;;;N;;;;; +11DB1;TOLONG SIKI LETTER E;Lo;0;L;;;;;N;;;;; +11DB2;TOLONG SIKI LETTER U;Lo;0;L;;;;;N;;;;; +11DB3;TOLONG SIKI LETTER O;Lo;0;L;;;;;N;;;;; +11DB4;TOLONG SIKI LETTER A;Lo;0;L;;;;;N;;;;; +11DB5;TOLONG SIKI LETTER AA;Lo;0;L;;;;;N;;;;; +11DB6;TOLONG SIKI LETTER P;Lo;0;L;;;;;N;;;;; +11DB7;TOLONG SIKI LETTER PH;Lo;0;L;;;;;N;;;;; +11DB8;TOLONG SIKI LETTER B;Lo;0;L;;;;;N;;;;; +11DB9;TOLONG SIKI LETTER BH;Lo;0;L;;;;;N;;;;; +11DBA;TOLONG SIKI LETTER M;Lo;0;L;;;;;N;;;;; +11DBB;TOLONG SIKI LETTER T;Lo;0;L;;;;;N;;;;; +11DBC;TOLONG SIKI LETTER TH;Lo;0;L;;;;;N;;;;; +11DBD;TOLONG SIKI LETTER D;Lo;0;L;;;;;N;;;;; +11DBE;TOLONG SIKI LETTER DH;Lo;0;L;;;;;N;;;;; +11DBF;TOLONG SIKI LETTER N;Lo;0;L;;;;;N;;;;; +11DC0;TOLONG SIKI LETTER TT;Lo;0;L;;;;;N;;;;; +11DC1;TOLONG SIKI LETTER TTH;Lo;0;L;;;;;N;;;;; +11DC2;TOLONG SIKI LETTER DD;Lo;0;L;;;;;N;;;;; +11DC3;TOLONG SIKI LETTER DDH;Lo;0;L;;;;;N;;;;; +11DC4;TOLONG SIKI LETTER NN;Lo;0;L;;;;;N;;;;; +11DC5;TOLONG SIKI LETTER C;Lo;0;L;;;;;N;;;;; +11DC6;TOLONG SIKI LETTER CH;Lo;0;L;;;;;N;;;;; +11DC7;TOLONG SIKI LETTER J;Lo;0;L;;;;;N;;;;; +11DC8;TOLONG SIKI LETTER JH;Lo;0;L;;;;;N;;;;; +11DC9;TOLONG SIKI LETTER NY;Lo;0;L;;;;;N;;;;; +11DCA;TOLONG SIKI LETTER K;Lo;0;L;;;;;N;;;;; +11DCB;TOLONG SIKI LETTER KH;Lo;0;L;;;;;N;;;;; +11DCC;TOLONG SIKI LETTER G;Lo;0;L;;;;;N;;;;; +11DCD;TOLONG SIKI LETTER GH;Lo;0;L;;;;;N;;;;; +11DCE;TOLONG SIKI LETTER NG;Lo;0;L;;;;;N;;;;; +11DCF;TOLONG SIKI LETTER Y;Lo;0;L;;;;;N;;;;; +11DD0;TOLONG SIKI LETTER R;Lo;0;L;;;;;N;;;;; +11DD1;TOLONG SIKI LETTER L;Lo;0;L;;;;;N;;;;; +11DD2;TOLONG SIKI LETTER V;Lo;0;L;;;;;N;;;;; +11DD3;TOLONG SIKI LETTER NNY;Lo;0;L;;;;;N;;;;; +11DD4;TOLONG SIKI LETTER S;Lo;0;L;;;;;N;;;;; +11DD5;TOLONG SIKI LETTER H;Lo;0;L;;;;;N;;;;; +11DD6;TOLONG SIKI LETTER X;Lo;0;L;;;;;N;;;;; +11DD7;TOLONG SIKI LETTER RR;Lo;0;L;;;;;N;;;;; +11DD8;TOLONG SIKI LETTER RRH;Lo;0;L;;;;;N;;;;; +11DD9;TOLONG SIKI SIGN SELA;Lm;0;L;;;;;N;;;;; +11DDA;TOLONG SIKI SIGN HECAKA;Lo;0;L;;;;;N;;;;; +11DDB;TOLONG SIKI UNGGA;Lo;0;L;;;;;N;;;;; +11DE0;TOLONG SIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11DE1;TOLONG SIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11DE2;TOLONG SIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11DE3;TOLONG SIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11DE4;TOLONG SIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11DE5;TOLONG SIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11DE6;TOLONG SIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11DE7;TOLONG SIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11DE8;TOLONG SIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11DE9;TOLONG SIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;; 11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;; 11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;; @@ -22088,8 +22252,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; -12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; -12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; +12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;1;N;;;;; +12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;1;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; @@ -22153,7 +22317,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; -12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; +12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;1;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; @@ -22582,12 +22746,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; -12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; +12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;1/2;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; -1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; +1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;2;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; @@ -22811,9 +22975,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; -1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; +1230B;CUNEIFORM SIGN U;Lo;0;L;;;;1;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; -1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; +1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;3;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; @@ -22953,7 +23117,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; -12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; +12399;CUNEIFORM SIGN U U;Lo;0;L;;;;2;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; @@ -30124,6 +30288,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;; 16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;; 16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;; +16EA0;BERIA ERFE CAPITAL LETTER ARKAB;Lu;0;L;;;;;N;;;;16EBB; +16EA1;BERIA ERFE CAPITAL LETTER BASIGNA;Lu;0;L;;;;;N;;;;16EBC; +16EA2;BERIA ERFE CAPITAL LETTER DARBAI;Lu;0;L;;;;;N;;;;16EBD; +16EA3;BERIA ERFE CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;16EBE; +16EA4;BERIA ERFE CAPITAL LETTER FITKO;Lu;0;L;;;;;N;;;;16EBF; +16EA5;BERIA ERFE CAPITAL LETTER GOWAY;Lu;0;L;;;;;N;;;;16EC0; +16EA6;BERIA ERFE CAPITAL LETTER HIRDEABO;Lu;0;L;;;;;N;;;;16EC1; +16EA7;BERIA ERFE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16EC2; +16EA8;BERIA ERFE CAPITAL LETTER DJAI;Lu;0;L;;;;;N;;;;16EC3; +16EA9;BERIA ERFE CAPITAL LETTER KOBO;Lu;0;L;;;;;N;;;;16EC4; +16EAA;BERIA ERFE CAPITAL LETTER LAKKO;Lu;0;L;;;;;N;;;;16EC5; +16EAB;BERIA ERFE CAPITAL LETTER MERI;Lu;0;L;;;;;N;;;;16EC6; +16EAC;BERIA ERFE CAPITAL LETTER NINI;Lu;0;L;;;;;N;;;;16EC7; +16EAD;BERIA ERFE CAPITAL LETTER GNA;Lu;0;L;;;;;N;;;;16EC8; +16EAE;BERIA ERFE CAPITAL LETTER NGAY;Lu;0;L;;;;;N;;;;16EC9; +16EAF;BERIA ERFE CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;16ECA; +16EB0;BERIA ERFE CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;16ECB; +16EB1;BERIA ERFE CAPITAL LETTER ERIGO;Lu;0;L;;;;;N;;;;16ECC; +16EB2;BERIA ERFE CAPITAL LETTER ERIGO TAMURA;Lu;0;L;;;;;N;;;;16ECD; +16EB3;BERIA ERFE CAPITAL LETTER SERI;Lu;0;L;;;;;N;;;;16ECE; +16EB4;BERIA ERFE CAPITAL LETTER SHEP;Lu;0;L;;;;;N;;;;16ECF; +16EB5;BERIA ERFE CAPITAL LETTER TATASOUE;Lu;0;L;;;;;N;;;;16ED0; +16EB6;BERIA ERFE CAPITAL LETTER UI;Lu;0;L;;;;;N;;;;16ED1; +16EB7;BERIA ERFE CAPITAL LETTER WASSE;Lu;0;L;;;;;N;;;;16ED2; +16EB8;BERIA ERFE CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;16ED3; +16EBB;BERIA ERFE SMALL LETTER ARKAB;Ll;0;L;;;;;N;;;16EA0;;16EA0 +16EBC;BERIA ERFE SMALL LETTER BASIGNA;Ll;0;L;;;;;N;;;16EA1;;16EA1 +16EBD;BERIA ERFE SMALL LETTER DARBAI;Ll;0;L;;;;;N;;;16EA2;;16EA2 +16EBE;BERIA ERFE SMALL LETTER EH;Ll;0;L;;;;;N;;;16EA3;;16EA3 +16EBF;BERIA ERFE SMALL LETTER FITKO;Ll;0;L;;;;;N;;;16EA4;;16EA4 +16EC0;BERIA ERFE SMALL LETTER GOWAY;Ll;0;L;;;;;N;;;16EA5;;16EA5 +16EC1;BERIA ERFE SMALL LETTER HIRDEABO;Ll;0;L;;;;;N;;;16EA6;;16EA6 +16EC2;BERIA ERFE SMALL LETTER I;Ll;0;L;;;;;N;;;16EA7;;16EA7 +16EC3;BERIA ERFE SMALL LETTER DJAI;Ll;0;L;;;;;N;;;16EA8;;16EA8 +16EC4;BERIA ERFE SMALL LETTER KOBO;Ll;0;L;;;;;N;;;16EA9;;16EA9 +16EC5;BERIA ERFE SMALL LETTER LAKKO;Ll;0;L;;;;;N;;;16EAA;;16EAA +16EC6;BERIA ERFE SMALL LETTER MERI;Ll;0;L;;;;;N;;;16EAB;;16EAB +16EC7;BERIA ERFE SMALL LETTER NINI;Ll;0;L;;;;;N;;;16EAC;;16EAC +16EC8;BERIA ERFE SMALL LETTER GNA;Ll;0;L;;;;;N;;;16EAD;;16EAD +16EC9;BERIA ERFE SMALL LETTER NGAY;Ll;0;L;;;;;N;;;16EAE;;16EAE +16ECA;BERIA ERFE SMALL LETTER OI;Ll;0;L;;;;;N;;;16EAF;;16EAF +16ECB;BERIA ERFE SMALL LETTER PI;Ll;0;L;;;;;N;;;16EB0;;16EB0 +16ECC;BERIA ERFE SMALL LETTER ERIGO;Ll;0;L;;;;;N;;;16EB1;;16EB1 +16ECD;BERIA ERFE SMALL LETTER ERIGO TAMURA;Ll;0;L;;;;;N;;;16EB2;;16EB2 +16ECE;BERIA ERFE SMALL LETTER SERI;Ll;0;L;;;;;N;;;16EB3;;16EB3 +16ECF;BERIA ERFE SMALL LETTER SHEP;Ll;0;L;;;;;N;;;16EB4;;16EB4 +16ED0;BERIA ERFE SMALL LETTER TATASOUE;Ll;0;L;;;;;N;;;16EB5;;16EB5 +16ED1;BERIA ERFE SMALL LETTER UI;Ll;0;L;;;;;N;;;16EB6;;16EB6 +16ED2;BERIA ERFE SMALL LETTER WASSE;Ll;0;L;;;;;N;;;16EB7;;16EB7 +16ED3;BERIA ERFE SMALL LETTER AY;Ll;0;L;;;;;N;;;16EB8;;16EB8 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; @@ -30280,8 +30494,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; 16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; 16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; +16FF2;CHINESE SMALL SIMPLIFIED ER;Lm;0;L;;;;;N;;;;; +16FF3;CHINESE SMALL TRADITIONAL ER;Lm;0;L;;;;;N;;;;; +16FF4;YANGQIN SIGN SLOW ONE BEAT;Nl;0;L;;;;1;N;;;;; +16FF5;YANGQIN SIGN SLOW THREE HALF BEATS;Nl;0;L;;;;3/2;N;;;;; +16FF6;YANGQIN SIGN SLOW TWO BEATS;Nl;0;L;;;;2;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; -187F7;;Lo;0;L;;;;;N;;;;; +187FF;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; @@ -31522,7 +31741,122 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; 18CFF;KHITAN SMALL SCRIPT CHARACTER-18CFF;Lo;0;L;;;;;N;;;;; 18D00;;Lo;0;L;;;;;N;;;;; -18D08;;Lo;0;L;;;;;N;;;;; +18D1E;;Lo;0;L;;;;;N;;;;; +18D80;TANGUT COMPONENT-769;Lo;0;L;;;;;N;;;;; +18D81;TANGUT COMPONENT-770;Lo;0;L;;;;;N;;;;; +18D82;TANGUT COMPONENT-771;Lo;0;L;;;;;N;;;;; +18D83;TANGUT COMPONENT-772;Lo;0;L;;;;;N;;;;; +18D84;TANGUT COMPONENT-773;Lo;0;L;;;;;N;;;;; +18D85;TANGUT COMPONENT-774;Lo;0;L;;;;;N;;;;; +18D86;TANGUT COMPONENT-775;Lo;0;L;;;;;N;;;;; +18D87;TANGUT COMPONENT-776;Lo;0;L;;;;;N;;;;; +18D88;TANGUT COMPONENT-777;Lo;0;L;;;;;N;;;;; +18D89;TANGUT COMPONENT-778;Lo;0;L;;;;;N;;;;; +18D8A;TANGUT COMPONENT-779;Lo;0;L;;;;;N;;;;; +18D8B;TANGUT COMPONENT-780;Lo;0;L;;;;;N;;;;; +18D8C;TANGUT COMPONENT-781;Lo;0;L;;;;;N;;;;; +18D8D;TANGUT COMPONENT-782;Lo;0;L;;;;;N;;;;; +18D8E;TANGUT COMPONENT-783;Lo;0;L;;;;;N;;;;; +18D8F;TANGUT COMPONENT-784;Lo;0;L;;;;;N;;;;; +18D90;TANGUT COMPONENT-785;Lo;0;L;;;;;N;;;;; +18D91;TANGUT COMPONENT-786;Lo;0;L;;;;;N;;;;; +18D92;TANGUT COMPONENT-787;Lo;0;L;;;;;N;;;;; +18D93;TANGUT COMPONENT-788;Lo;0;L;;;;;N;;;;; +18D94;TANGUT COMPONENT-789;Lo;0;L;;;;;N;;;;; +18D95;TANGUT COMPONENT-790;Lo;0;L;;;;;N;;;;; +18D96;TANGUT COMPONENT-791;Lo;0;L;;;;;N;;;;; +18D97;TANGUT COMPONENT-792;Lo;0;L;;;;;N;;;;; +18D98;TANGUT COMPONENT-793;Lo;0;L;;;;;N;;;;; +18D99;TANGUT COMPONENT-794;Lo;0;L;;;;;N;;;;; +18D9A;TANGUT COMPONENT-795;Lo;0;L;;;;;N;;;;; +18D9B;TANGUT COMPONENT-796;Lo;0;L;;;;;N;;;;; +18D9C;TANGUT COMPONENT-797;Lo;0;L;;;;;N;;;;; +18D9D;TANGUT COMPONENT-798;Lo;0;L;;;;;N;;;;; +18D9E;TANGUT COMPONENT-799;Lo;0;L;;;;;N;;;;; +18D9F;TANGUT COMPONENT-800;Lo;0;L;;;;;N;;;;; +18DA0;TANGUT COMPONENT-801;Lo;0;L;;;;;N;;;;; +18DA1;TANGUT COMPONENT-802;Lo;0;L;;;;;N;;;;; +18DA2;TANGUT COMPONENT-803;Lo;0;L;;;;;N;;;;; +18DA3;TANGUT COMPONENT-804;Lo;0;L;;;;;N;;;;; +18DA4;TANGUT COMPONENT-805;Lo;0;L;;;;;N;;;;; +18DA5;TANGUT COMPONENT-806;Lo;0;L;;;;;N;;;;; +18DA6;TANGUT COMPONENT-807;Lo;0;L;;;;;N;;;;; +18DA7;TANGUT COMPONENT-808;Lo;0;L;;;;;N;;;;; +18DA8;TANGUT COMPONENT-809;Lo;0;L;;;;;N;;;;; +18DA9;TANGUT COMPONENT-810;Lo;0;L;;;;;N;;;;; +18DAA;TANGUT COMPONENT-811;Lo;0;L;;;;;N;;;;; +18DAB;TANGUT COMPONENT-812;Lo;0;L;;;;;N;;;;; +18DAC;TANGUT COMPONENT-813;Lo;0;L;;;;;N;;;;; +18DAD;TANGUT COMPONENT-814;Lo;0;L;;;;;N;;;;; +18DAE;TANGUT COMPONENT-815;Lo;0;L;;;;;N;;;;; +18DAF;TANGUT COMPONENT-816;Lo;0;L;;;;;N;;;;; +18DB0;TANGUT COMPONENT-817;Lo;0;L;;;;;N;;;;; +18DB1;TANGUT COMPONENT-818;Lo;0;L;;;;;N;;;;; +18DB2;TANGUT COMPONENT-819;Lo;0;L;;;;;N;;;;; +18DB3;TANGUT COMPONENT-820;Lo;0;L;;;;;N;;;;; +18DB4;TANGUT COMPONENT-821;Lo;0;L;;;;;N;;;;; +18DB5;TANGUT COMPONENT-822;Lo;0;L;;;;;N;;;;; +18DB6;TANGUT COMPONENT-823;Lo;0;L;;;;;N;;;;; +18DB7;TANGUT COMPONENT-824;Lo;0;L;;;;;N;;;;; +18DB8;TANGUT COMPONENT-825;Lo;0;L;;;;;N;;;;; +18DB9;TANGUT COMPONENT-826;Lo;0;L;;;;;N;;;;; +18DBA;TANGUT COMPONENT-827;Lo;0;L;;;;;N;;;;; +18DBB;TANGUT COMPONENT-828;Lo;0;L;;;;;N;;;;; +18DBC;TANGUT COMPONENT-829;Lo;0;L;;;;;N;;;;; +18DBD;TANGUT COMPONENT-830;Lo;0;L;;;;;N;;;;; +18DBE;TANGUT COMPONENT-831;Lo;0;L;;;;;N;;;;; +18DBF;TANGUT COMPONENT-832;Lo;0;L;;;;;N;;;;; +18DC0;TANGUT COMPONENT-833;Lo;0;L;;;;;N;;;;; +18DC1;TANGUT COMPONENT-834;Lo;0;L;;;;;N;;;;; +18DC2;TANGUT COMPONENT-835;Lo;0;L;;;;;N;;;;; +18DC3;TANGUT COMPONENT-836;Lo;0;L;;;;;N;;;;; +18DC4;TANGUT COMPONENT-837;Lo;0;L;;;;;N;;;;; +18DC5;TANGUT COMPONENT-838;Lo;0;L;;;;;N;;;;; +18DC6;TANGUT COMPONENT-839;Lo;0;L;;;;;N;;;;; +18DC7;TANGUT COMPONENT-840;Lo;0;L;;;;;N;;;;; +18DC8;TANGUT COMPONENT-841;Lo;0;L;;;;;N;;;;; +18DC9;TANGUT COMPONENT-842;Lo;0;L;;;;;N;;;;; +18DCA;TANGUT COMPONENT-843;Lo;0;L;;;;;N;;;;; +18DCB;TANGUT COMPONENT-844;Lo;0;L;;;;;N;;;;; +18DCC;TANGUT COMPONENT-845;Lo;0;L;;;;;N;;;;; +18DCD;TANGUT COMPONENT-846;Lo;0;L;;;;;N;;;;; +18DCE;TANGUT COMPONENT-847;Lo;0;L;;;;;N;;;;; +18DCF;TANGUT COMPONENT-848;Lo;0;L;;;;;N;;;;; +18DD0;TANGUT COMPONENT-849;Lo;0;L;;;;;N;;;;; +18DD1;TANGUT COMPONENT-850;Lo;0;L;;;;;N;;;;; +18DD2;TANGUT COMPONENT-851;Lo;0;L;;;;;N;;;;; +18DD3;TANGUT COMPONENT-852;Lo;0;L;;;;;N;;;;; +18DD4;TANGUT COMPONENT-853;Lo;0;L;;;;;N;;;;; +18DD5;TANGUT COMPONENT-854;Lo;0;L;;;;;N;;;;; +18DD6;TANGUT COMPONENT-855;Lo;0;L;;;;;N;;;;; +18DD7;TANGUT COMPONENT-856;Lo;0;L;;;;;N;;;;; +18DD8;TANGUT COMPONENT-857;Lo;0;L;;;;;N;;;;; +18DD9;TANGUT COMPONENT-858;Lo;0;L;;;;;N;;;;; +18DDA;TANGUT COMPONENT-859;Lo;0;L;;;;;N;;;;; +18DDB;TANGUT COMPONENT-860;Lo;0;L;;;;;N;;;;; +18DDC;TANGUT COMPONENT-861;Lo;0;L;;;;;N;;;;; +18DDD;TANGUT COMPONENT-862;Lo;0;L;;;;;N;;;;; +18DDE;TANGUT COMPONENT-863;Lo;0;L;;;;;N;;;;; +18DDF;TANGUT COMPONENT-864;Lo;0;L;;;;;N;;;;; +18DE0;TANGUT COMPONENT-865;Lo;0;L;;;;;N;;;;; +18DE1;TANGUT COMPONENT-866;Lo;0;L;;;;;N;;;;; +18DE2;TANGUT COMPONENT-867;Lo;0;L;;;;;N;;;;; +18DE3;TANGUT COMPONENT-868;Lo;0;L;;;;;N;;;;; +18DE4;TANGUT COMPONENT-869;Lo;0;L;;;;;N;;;;; +18DE5;TANGUT COMPONENT-870;Lo;0;L;;;;;N;;;;; +18DE6;TANGUT COMPONENT-871;Lo;0;L;;;;;N;;;;; +18DE7;TANGUT COMPONENT-872;Lo;0;L;;;;;N;;;;; +18DE8;TANGUT COMPONENT-873;Lo;0;L;;;;;N;;;;; +18DE9;TANGUT COMPONENT-874;Lo;0;L;;;;;N;;;;; +18DEA;TANGUT COMPONENT-875;Lo;0;L;;;;;N;;;;; +18DEB;TANGUT COMPONENT-876;Lo;0;L;;;;;N;;;;; +18DEC;TANGUT COMPONENT-877;Lo;0;L;;;;;N;;;;; +18DED;TANGUT COMPONENT-878;Lo;0;L;;;;;N;;;;; +18DEE;TANGUT COMPONENT-879;Lo;0;L;;;;;N;;;;; +18DEF;TANGUT COMPONENT-880;Lo;0;L;;;;;N;;;;; +18DF0;TANGUT COMPONENT-881;Lo;0;L;;;;;N;;;;; +18DF1;TANGUT COMPONENT-882;Lo;0;L;;;;;N;;;;; +18DF2;TANGUT COMPONENT-883;Lo;0;L;;;;;N;;;;; 1AFF0;KATAKANA LETTER MINNAN TONE-2;Lm;0;L;;;;;N;;;;; 1AFF1;KATAKANA LETTER MINNAN TONE-3;Lm;0;L;;;;;N;;;;; 1AFF2;KATAKANA LETTER MINNAN TONE-4;Lm;0;L;;;;;N;;;;; @@ -32629,6 +32963,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CCF7;OUTLINED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1CCF8;OUTLINED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1CCF9;OUTLINED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1CCFA;SNAKE SYMBOL;So;0;ON;;;;;N;;;;; +1CCFB;FLYING SAUCER SYMBOL;So;0;ON;;;;;N;;;;; +1CCFC;NOSE SYMBOL;So;0;ON;;;;;N;;;;; 1CD00;BLOCK OCTANT-3;So;0;ON;;;;;N;;;;; 1CD01;BLOCK OCTANT-23;So;0;ON;;;;;N;;;;; 1CD02;BLOCK OCTANT-123;So;0;ON;;;;;N;;;;; @@ -33065,6 +33402,46 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CEB1;KEYHOLE;So;0;ON;;;;;N;;;;; 1CEB2;OLD PERSONAL COMPUTER WITH MONITOR IN PORTRAIT ORIENTATION;So;0;ON;;;;;N;;;;; 1CEB3;BLACK RIGHT TRIANGLE CARET;So;0;ON;;;;;N;;;;; +1CEBA;FRAGILE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBB;OFFICE BUILDING SYMBOL;So;0;ON;;;;;N;;;;; +1CEBC;TREE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBD;APPLE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBE;CHERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEBF;STRAWBERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEC0;HEBE;So;0;ON;;;;;N;;;;; +1CEC1;IRIS;So;0;ON;;;;;N;;;;; +1CEC2;FLORA;So;0;ON;;;;;N;;;;; +1CEC3;METIS;So;0;ON;;;;;N;;;;; +1CEC4;PARTHENOPE;So;0;ON;;;;;N;;;;; +1CEC5;VICTORIA;So;0;ON;;;;;N;;;;; +1CEC6;EGERIA;So;0;ON;;;;;N;;;;; +1CEC7;IRENE;So;0;ON;;;;;N;;;;; +1CEC8;EUNOMIA;So;0;ON;;;;;N;;;;; +1CEC9;PSYCHE;So;0;ON;;;;;N;;;;; +1CECA;THETIS;So;0;ON;;;;;N;;;;; +1CECB;MELPOMENE;So;0;ON;;;;;N;;;;; +1CECC;FORTUNA;So;0;ON;;;;;N;;;;; +1CECD;ASTRONOMICAL SYMBOL FOR ASTEROID PROSERPINA;So;0;ON;;;;;N;;;;; +1CECE;BELLONA;So;0;ON;;;;;N;;;;; +1CECF;AMPHITRITE;So;0;ON;;;;;N;;;;; +1CED0;LEUKOTHEA;So;0;ON;;;;;N;;;;; +1CEE0;GEOMANTIC FIGURE POPULUS;So;0;ON;;;;;N;;;;; +1CEE1;GEOMANTIC FIGURE TRISTITIA;So;0;ON;;;;;N;;;;; +1CEE2;GEOMANTIC FIGURE ALBUS;So;0;ON;;;;;N;;;;; +1CEE3;GEOMANTIC FIGURE FORTUNA MAJOR;So;0;ON;;;;;N;;;;; +1CEE4;GEOMANTIC FIGURE RUBEUS;So;0;ON;;;;;N;;;;; +1CEE5;GEOMANTIC FIGURE ACQUISITIO;So;0;ON;;;;;N;;;;; +1CEE6;GEOMANTIC FIGURE CONJUNCTIO;So;0;ON;;;;;N;;;;; +1CEE7;GEOMANTIC FIGURE CAPUT DRACONIS;So;0;ON;;;;;N;;;;; +1CEE8;GEOMANTIC FIGURE LAETITIA;So;0;ON;;;;;N;;;;; +1CEE9;GEOMANTIC FIGURE CARCER;So;0;ON;;;;;N;;;;; +1CEEA;GEOMANTIC FIGURE AMISSIO;So;0;ON;;;;;N;;;;; +1CEEB;GEOMANTIC FIGURE PUELLA;So;0;ON;;;;;N;;;;; +1CEEC;GEOMANTIC FIGURE FORTUNA MINOR;So;0;ON;;;;;N;;;;; +1CEED;GEOMANTIC FIGURE PUER;So;0;ON;;;;;N;;;;; +1CEEE;GEOMANTIC FIGURE CAUDA DRACONIS;So;0;ON;;;;;N;;;;; +1CEEF;GEOMANTIC FIGURE VIA;So;0;ON;;;;;N;;;;; +1CEF0;MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 1CF00;ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF01;ZNAMENNY COMBINING MARK NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF02;ZNAMENNY COMBINING MARK TSATA ON LEFT;Mn;0;NSM;;;;;N;;;;; @@ -36004,6 +36381,61 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E5F9;OL ONAL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E5FA;OL ONAL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E5FF;OL ONAL ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; +1E6C0;TAI YO LETTER LOW KO;Lo;0;L;;;;;N;;;;; +1E6C1;TAI YO LETTER HIGH KO;Lo;0;L;;;;;N;;;;; +1E6C2;TAI YO LETTER LOW KHO;Lo;0;L;;;;;N;;;;; +1E6C3;TAI YO LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; +1E6C4;TAI YO LETTER GO;Lo;0;L;;;;;N;;;;; +1E6C5;TAI YO LETTER NGO;Lo;0;L;;;;;N;;;;; +1E6C6;TAI YO LETTER CO;Lo;0;L;;;;;N;;;;; +1E6C7;TAI YO LETTER LOW XO;Lo;0;L;;;;;N;;;;; +1E6C8;TAI YO LETTER HIGH XO;Lo;0;L;;;;;N;;;;; +1E6C9;TAI YO LETTER LOW NYO;Lo;0;L;;;;;N;;;;; +1E6CA;TAI YO LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; +1E6CB;TAI YO LETTER DO;Lo;0;L;;;;;N;;;;; +1E6CC;TAI YO LETTER LOW TO;Lo;0;L;;;;;N;;;;; +1E6CD;TAI YO LETTER HIGH TO;Lo;0;L;;;;;N;;;;; +1E6CE;TAI YO LETTER THO;Lo;0;L;;;;;N;;;;; +1E6CF;TAI YO LETTER NO;Lo;0;L;;;;;N;;;;; +1E6D0;TAI YO LETTER BO;Lo;0;L;;;;;N;;;;; +1E6D1;TAI YO LETTER LOW PO;Lo;0;L;;;;;N;;;;; +1E6D2;TAI YO LETTER HIGH PO;Lo;0;L;;;;;N;;;;; +1E6D3;TAI YO LETTER PHO;Lo;0;L;;;;;N;;;;; +1E6D4;TAI YO LETTER LOW FO;Lo;0;L;;;;;N;;;;; +1E6D5;TAI YO LETTER HIGH FO;Lo;0;L;;;;;N;;;;; +1E6D6;TAI YO LETTER MO;Lo;0;L;;;;;N;;;;; +1E6D7;TAI YO LETTER YO;Lo;0;L;;;;;N;;;;; +1E6D8;TAI YO LETTER LO;Lo;0;L;;;;;N;;;;; +1E6D9;TAI YO LETTER VO;Lo;0;L;;;;;N;;;;; +1E6DA;TAI YO LETTER LOW HO;Lo;0;L;;;;;N;;;;; +1E6DB;TAI YO LETTER HIGH HO;Lo;0;L;;;;;N;;;;; +1E6DC;TAI YO LETTER QO;Lo;0;L;;;;;N;;;;; +1E6DD;TAI YO LETTER LOW KVO;Lo;0;L;;;;;N;;;;; +1E6DE;TAI YO LETTER HIGH KVO;Lo;0;L;;;;;N;;;;; +1E6E0;TAI YO LETTER AA;Lo;0;L;;;;;N;;;;; +1E6E1;TAI YO LETTER I;Lo;0;L;;;;;N;;;;; +1E6E2;TAI YO LETTER UE;Lo;0;L;;;;;N;;;;; +1E6E3;TAI YO SIGN UE;Mn;230;NSM;;;;;N;;;;; +1E6E4;TAI YO LETTER U;Lo;0;L;;;;;N;;;;; +1E6E5;TAI YO LETTER AE;Lo;0;L;;;;;N;;;;; +1E6E6;TAI YO SIGN AU;Mn;230;NSM;;;;;N;;;;; +1E6E7;TAI YO LETTER O;Lo;0;L;;;;;N;;;;; +1E6E8;TAI YO LETTER E;Lo;0;L;;;;;N;;;;; +1E6E9;TAI YO LETTER IA;Lo;0;L;;;;;N;;;;; +1E6EA;TAI YO LETTER UEA;Lo;0;L;;;;;N;;;;; +1E6EB;TAI YO LETTER UA;Lo;0;L;;;;;N;;;;; +1E6EC;TAI YO LETTER OO;Lo;0;L;;;;;N;;;;; +1E6ED;TAI YO LETTER AUE;Lo;0;L;;;;;N;;;;; +1E6EE;TAI YO SIGN AY;Mn;230;NSM;;;;;N;;;;; +1E6EF;TAI YO SIGN ANG;Mn;230;NSM;;;;;N;;;;; +1E6F0;TAI YO LETTER AN;Lo;0;L;;;;;N;;;;; +1E6F1;TAI YO LETTER AM;Lo;0;L;;;;;N;;;;; +1E6F2;TAI YO LETTER AK;Lo;0;L;;;;;N;;;;; +1E6F3;TAI YO LETTER AT;Lo;0;L;;;;;N;;;;; +1E6F4;TAI YO LETTER AP;Lo;0;L;;;;;N;;;;; +1E6F5;TAI YO SIGN OM;Mn;230;NSM;;;;;N;;;;; +1E6FE;TAI YO SYMBOL MUEANG;Lo;0;L;;;;;N;;;;; +1E6FF;TAI YO XAM LAI;Lm;0;L;;;;;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -38079,6 +38511,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6D8;LANDSLIDE;So;0;ON;;;;;N;;;;; 1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; @@ -38228,6 +38661,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; 1F775;OCCULTATION;So;0;ON;;;;;N;;;;; 1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F777;VESTA FORM TWO;So;0;ON;;;;;N;;;;; +1F778;ASTRAEA FORM TWO;So;0;ON;;;;;N;;;;; +1F779;HYGIEA FORM TWO;So;0;ON;;;;;N;;;;; +1F77A;PARTHENOPE FORM TWO;So;0;ON;;;;;N;;;;; 1F77B;HAUMEA;So;0;ON;;;;;N;;;;; 1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; 1F77D;GONGGONG;So;0;ON;;;;;N;;;;; @@ -38498,6 +38935,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8BB;SOUTH WEST ARROW FROM BAR;So;0;ON;;;;;N;;;;; 1F8C0;LEFTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 1F8C1;RIGHTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; +1F8D0;LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; +1F8D1;LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D2;LONG RIGHTWARDS HARPOON ABOVE SHORT LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D3;SHORT RIGHTWARDS HARPOON ABOVE LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D4;LONG LEFTWARDS HARPOON ABOVE SHORT RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D5;SHORT LEFTWARDS HARPOON ABOVE LONG RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D6;LONG RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; +1F8D7;LONG RIGHTWARDS ARROW WITH DOUBLE SLASH;Sm;0;ON;;;;;N;;;;; +1F8D8;LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE;Sm;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -38838,6 +39284,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;; 1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;; 1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;; +1FA54;WHITE CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA55;WHITE CHESS ALFIL;So;0;ON;;;;;N;;;;; +1FA56;BLACK CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA57;BLACK CHESS ALFIL;So;0;ON;;;;;N;;;;; 1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;; 1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;; 1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;; @@ -38875,6 +39325,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA87;MARACAS;So;0;ON;;;;;N;;;;; 1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA89;HARP;So;0;ON;;;;;N;;;;; +1FA8A;TROMBONE;So;0;ON;;;;;N;;;;; +1FA8E;TREASURE CHEST;So;0;ON;;;;;N;;;;; 1FA8F;SHOVEL;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; @@ -38931,6 +39383,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; 1FAC6;FINGERPRINT;So;0;ON;;;;;N;;;;; +1FAC8;HAIRY CREATURE;So;0;ON;;;;;N;;;;; +1FACD;ORCA;So;0;ON;;;;;N;;;;; 1FACE;MOOSE;So;0;ON;;;;;N;;;;; 1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; @@ -38957,6 +39411,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; 1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAE9;FACE WITH BAGS UNDER EYES;So;0;ON;;;;;N;;;;; +1FAEA;DISTORTED FACE;So;0;ON;;;;;N;;;;; +1FAEF;FIGHT CLOUD;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -39215,14 +39671,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1FBFA;ALARM BELL SYMBOL;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6DF;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; -2B739;;Lo;0;L;;;;;N;;;;; +2B73F;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; -2CEA1;;Lo;0;L;;;;;N;;;;; +2CEAD;;Lo;0;L;;;;;N;;;;; 2CEB0;;Lo;0;L;;;;;N;;;;; 2EBE0;;Lo;0;L;;;;;N;;;;; 2EBF0;;Lo;0;L;;;;;N;;;;; @@ -39773,6 +40230,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 3134A;;Lo;0;L;;;;;N;;;;; 31350;;Lo;0;L;;;;;N;;;;; 323AF;;Lo;0;L;;;;;N;;;;; +323B0;;Lo;0;L;;;;;N;;;;; +33479;;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/resources/standards-data/unicode-character-database/WordBreakProperty.txt b/resources/standards-data/unicode-character-database/WordBreakProperty.txt index e557c3d0de..20fa24e375 100644 --- a/resources/standards-data/unicode-character-database/WordBreakProperty.txt +++ b/resources/standards-data/unicode-character-database/WordBreakProperty.txt @@ -1,6 +1,6 @@ -# WordBreakProperty-16.0.0.txt -# Date: 2024-07-29, 16:27:36 GMT -# © 2024 Unicode®, Inc. +# WordBreakProperty-17.0.0.txt +# Date: 2025-06-30, 06:20:49 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -283,7 +283,8 @@ FB46..FB4F ; Hebrew_Letter # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW 1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT 1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Extend # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Extend # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; Extend # Mc BALINESE SIGN BISAH 1B34 ; Extend # Mn BALINESE SIGN REREKAN @@ -409,7 +410,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D69..10D6D ; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EFA..10EFF ; Extend # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11000 ; Extend # Mc BRAHMI SIGN CANDRABINDU @@ -544,6 +545,12 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A97 ; Extend # Mc SOYOMBO SIGN VISARGA 11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Extend # Mn SHARADA VOWEL SIGN OE +11B61 ; Extend # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Extend # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Extend # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Extend # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Extend # Mc SHARADA VOWEL SIGN CANDRA O 11C2F ; Extend # Mc BHAIKSUKI VOWEL SIGN AA 11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA @@ -617,13 +624,17 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI 1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Extend # Mn TAI YO SIGN UE +1E6E6 ; Extend # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Extend # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Extend # Mn TAI YO SIGN OM 1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2605 +# Total code points: 2647 # ================================================ @@ -679,6 +690,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0061..007A ; ALetter # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z 00AA ; ALetter # Lo FEMININE ORDINAL INDICATOR 00B5 ; ALetter # L& MICRO SIGN +00B8 ; ALetter # Sk CEDILLA 00BA ; ALetter # Lo MASCULINE ORDINAL INDICATOR 00C0..00D6 ; ALetter # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS 00D8..00F6 ; ALetter # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS @@ -687,8 +699,8 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 01BC..01BF ; ALetter # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ALetter # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ALetter # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ALetter # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ALetter # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ALetter # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ALetter # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ALetter # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C2..02C5 ; ALetter # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD 02C6..02D1 ; ALetter # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON @@ -745,7 +757,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0840..0858 ; ALetter # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; ALetter # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ALetter # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ALetter # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ALetter # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; ALetter # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ALetter # Lm ARABIC SMALL FARSI YEH 0904..0939 ; ALetter # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -813,7 +825,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0C2A..0C39 ; ALetter # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; ALetter # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; ALetter # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ALetter # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ALetter # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ALetter # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; ALetter # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; ALetter # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -822,7 +834,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0CAA..0CB3 ; ALetter # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; ALetter # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; ALetter # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; ALetter # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ALetter # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ALetter # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; ALetter # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; ALetter # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -1002,11 +1014,8 @@ A788 ; ALetter # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A789..A78A ; ALetter # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN A78B..A78E ; ALetter # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ALetter # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ALetter # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ALetter # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ALetter # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ALetter # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ALetter # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ALetter # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ALetter # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ALetter # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ALetter # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ALetter # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -1115,6 +1124,7 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 108F4..108F5 ; ALetter # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ALetter # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ALetter # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ALetter # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ALetter # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ALetter # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ALetter # Lo KHAROSHTHI LETTER A @@ -1142,6 +1152,8 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 10E80..10EA9 ; ALetter # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; ALetter # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ALetter # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; ALetter # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ALetter # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; ALetter # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ALetter # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ALetter # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -1232,6 +1244,9 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 11D67..11D68 ; ALetter # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; ALetter # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; ALetter # Lo GUNJALA GONDI OM +11DB0..11DD8 ; ALetter # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ALetter # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ALetter # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; ALetter # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; ALetter # Lo KAWI SIGN REPHA 11F04..11F10 ; ALetter # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -1258,6 +1273,8 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 16D43..16D6A ; ALetter # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; ALetter # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; ALetter # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ALetter # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ALetter # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ALetter # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; ALetter # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; ALetter # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 @@ -1311,6 +1328,13 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1E4EB ; ALetter # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; ALetter # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; ALetter # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; ALetter # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ALetter # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; ALetter # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; ALetter # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; ALetter # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; ALetter # Lo TAI YO SYMBOL MUEANG +1E6FF ; ALetter # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ALetter # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ALetter # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -1355,7 +1379,7 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1F150..1F169 ; ALetter # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; ALetter # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 33791 +# Total code points: 33973 # ================================================ @@ -1467,6 +1491,7 @@ FF10..FF19 ; Numeric # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE 11C50..11C59 ; Numeric # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE 11D50..11D59 ; Numeric # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE 11DA0..11DA9 ; Numeric # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DE0..11DE9 ; Numeric # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11F50..11F59 ; Numeric # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE 16130..16139 ; Numeric # Nd [10] GURUNG KHEMA DIGIT ZERO..GURUNG KHEMA DIGIT NINE 16A60..16A69 ; Numeric # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE @@ -1482,7 +1507,7 @@ FF10..FF19 ; Numeric # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE 1E950..1E959 ; Numeric # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE 1FBF0..1FBF9 ; Numeric # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE -# Total code points: 774 +# Total code points: 784 # ================================================ diff --git a/resources/standards-data/unicode-character-database/emoji-data.txt b/resources/standards-data/unicode-character-database/emoji-data.txt index ff99028248..450252c4df 100644 --- a/resources/standards-data/unicode-character-database/emoji-data.txt +++ b/resources/standards-data/unicode-character-database/emoji-data.txt @@ -1,11 +1,11 @@ # emoji-data.txt -# Date: 2024-05-01, 21:25:24 GMT -# © 2024 Unicode®, Inc. +# Date: 2025-07-25, 17:54:31 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Used with Emoji Version 16.0 and subsequent minor revisions (if any) +# Version: 17.0 # # For documentation and usage, see https://www.unicode.org/reports/tr51 # @@ -340,6 +340,7 @@ 1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji # E17.0 [1] (🛘) landslide 1F6DC ; Emoji # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat @@ -408,6 +409,8 @@ 1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji # E16.0 [1] (🪉) harp +1FA8A ; Emoji # E17.0 [1] (🪊) trombone +1FA8E ; Emoji # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock @@ -421,6 +424,8 @@ 1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -430,10 +435,12 @@ 1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1431 +# Total elements: 1438 # ================================================ @@ -640,6 +647,7 @@ 1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji_Presentation # E17.0 [1] (🛘) landslide 1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival @@ -704,6 +712,8 @@ 1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji_Presentation # E16.0 [1] (🪉) harp +1FA8A ; Emoji_Presentation # E17.0 [1] (🪊) trombone +1FA8E ; Emoji_Presentation # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji_Presentation # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock @@ -717,6 +727,8 @@ 1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji_Presentation # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji_Presentation # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji_Presentation # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -726,10 +738,12 @@ 1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji_Presentation # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji_Presentation # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji_Presentation # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1212 +# Total elements: 1219 # ================================================ @@ -827,7 +841,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right 231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done 2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard -2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL 23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button 23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button 23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button @@ -844,106 +857,63 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud 2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman 2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet -2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR -2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION 260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone -260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX 2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check -2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X 2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE 2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock -2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX 261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up -261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX 2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones -2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN 2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard -2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH 2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross -2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM 262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent -262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE 262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol 262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang -2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH 2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face 263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face -263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY 2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign -2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH 2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign -2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO 2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces -2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT 265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn 2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit -2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT 2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit -2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT 2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit -2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT 2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs -2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS 267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol -267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL 267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity 267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol -2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG 2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick 2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor 2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords 2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol 2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic -2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER 2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear -269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES 269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis -269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT 26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage -26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN 26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol -26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN 26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle -26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL 26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn -26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE 26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball -26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING 26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud -26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN 26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain -26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR 26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus 26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick -26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING 26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet -26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES 26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains 26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry -26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD 26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine 26EA ; Extended_Pictographic# E0.6 [1] (⛪) church -26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE 26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground 26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole 26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry 26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat -26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS 26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball 26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent -26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL 26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump -26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS 2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors -2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS 2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button 2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand 270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand -270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL 270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil -2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB 2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib 2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark 2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply @@ -959,7 +929,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark 2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation 2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart -2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET 2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide 27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow 27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop @@ -973,19 +942,19 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark 3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button 3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button -1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND 1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon -1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F02C..1F02F ; Extended_Pictographic# E0.0 [4] (🀬..🀯) .. +1F094..1F09F ; Extended_Pictographic# E0.0 [12] (🂔..🂟) .. +1F0AF..1F0B0 ; Extended_Pictographic# E0.0 [2] (🂯..🂰) .. +1F0C0 ; Extended_Pictographic# E0.0 [1] (🃀) 1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker -1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. -1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH -1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL -1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F0D0 ; Extended_Pictographic# E0.0 [1] (🃐) +1F0F6..1F0FF ; Extended_Pictographic# E0.0 [10] (🃶..🃿) .. 1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) 1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button 1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) 1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F1AE..1F1E5 ; Extended_Pictographic# E0.0 [56] (🆮..🇥) .. 1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button 1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. 1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button @@ -994,7 +963,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. 1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. 1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F252..1F25F ; Extended_Pictographic# E0.0 [14] (🉒..🉟) .. +1F266..1F2FF ; Extended_Pictographic# E0.0 [154] (🉦..🋿) .. 1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way 1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas 1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia @@ -1010,7 +980,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face 1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star 1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer -1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN 1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face 1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito 1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling @@ -1026,11 +995,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate 1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn 1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap -1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS 1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon -1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS 1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs -1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES 1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets 1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing 1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal @@ -1045,11 +1011,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office 1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office 1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle -1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT 1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag 1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag 1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette -1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE 1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label 1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora 1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit @@ -1086,7 +1050,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash 1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette 1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector -1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO 1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button 1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows 1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker @@ -1097,51 +1060,30 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol 1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope 1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button -1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS 1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove 1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah -1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA 1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock 1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty -1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK 1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock -1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY 1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick 1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing -1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE 1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips -1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL 1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon -1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN 1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed -1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND 1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute -1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX 1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart 1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer -1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS 1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer -1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE 1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball -1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE 1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture -1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER 1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet -1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES 1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar -1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL 1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper -1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART 1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger -1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS 1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head -1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT 1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble -1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE 1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble -1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD 1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot -1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK 1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map 1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai 1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face @@ -1210,26 +1152,22 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower 1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath 1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage -1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL 1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp 1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed 1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed 1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship 1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA 1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator -1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6D8 ; Extended_Pictographic# E17.0 [1] (🛘) landslide +1F6D9..1F6DB ; Extended_Pictographic# E0.0 [3] (🛙..🛛) .. 1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat -1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE 1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane -1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival 1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. 1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite -1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE 1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship 1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe 1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer @@ -1237,8 +1175,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw 1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate 1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. -1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS -1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7DA..1F7DF ; Extended_Pictographic# E0.0 [6] (🟚..🟟) .. 1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square 1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. 1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign @@ -1247,7 +1184,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. 1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. 1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. -1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F8AE..1F8AF ; Extended_Pictographic# E0.0 [2] (🢮..🢯) .. +1F8BC..1F8BF ; Extended_Pictographic# E0.0 [4] (🢼..🢿) .. +1F8C2..1F8CF ; Extended_Pictographic# E0.0 [14] (🣂..🣏) .. +1F8D9..1F8FF ; Extended_Pictographic# E0.0 [39] (🣙..🣿) .. 1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers 1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand 1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns @@ -1293,7 +1233,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person 1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks 1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA58..1FA5F ; Extended_Pictographic# E0.0 [8] (🩘..🩟) .. +1FA6E..1FA6F ; Extended_Pictographic# E0.0 [2] (🩮..🩯) .. 1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal 1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart @@ -1304,7 +1245,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Extended_Pictographic# E16.0 [1] (🪉) harp -1FA8A..1FA8E ; Extended_Pictographic# E0.0 [5] (🪊..🪎) .. +1FA8A ; Extended_Pictographic# E17.0 [1] (🪊) trombone +1FA8B..1FA8D ; Extended_Pictographic# E0.0 [3] (🪋..🪍) .. +1FA8E ; Extended_Pictographic# E17.0 [1] (🪎) treasure chest 1FA8F ; Extended_Pictographic# E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock @@ -1318,7 +1261,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Extended_Pictographic# E16.0 [1] (🫆) fingerprint -1FAC7..1FACD ; Extended_Pictographic# E0.0 [7] (🫇..🫍) .. +1FAC7 ; Extended_Pictographic# E0.0 [1] (🫇) +1FAC8 ; Extended_Pictographic# E17.0 [1] (🫈) hairy creature +1FAC9..1FACC ; Extended_Pictographic# E0.0 [4] (🫉..🫌) .. +1FACD ; Extended_Pictographic# E17.0 [1] (🫍) orca 1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -1329,12 +1275,14 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face 1FAE9 ; Extended_Pictographic# E16.0 [1] (🫩) face with bags under eyes -1FAEA..1FAEF ; Extended_Pictographic# E0.0 [6] (🫪..🫯) .. +1FAEA ; Extended_Pictographic# E17.0 [1] (🫪) distorted face +1FAEB..1FAEE ; Extended_Pictographic# E0.0 [4] (🫫..🫮) .. +1FAEF ; Extended_Pictographic# E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand 1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. 1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. -# Total elements: 3537 +# Total elements: 2848 #EOF diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh index c125a9bfe7..936fafab1b 100755 --- a/resources/teamcity/android/android-actions.inc.sh +++ b/resources/teamcity/android/android-actions.inc.sh @@ -4,17 +4,16 @@ android_clean_action() { builder_echo start "clean" "Cleaning artifact directories" # shellcheck disable=SC2154 - "${KEYMAN_ROOT}/android/build.sh" clean + builder_launch /android/build.sh clean builder_echo end "clean" success "Finished cleaning artifact directories" } android_build_action() { builder_echo start "build" "Building Keyman for Android" local TARGETS="$1" - local ARGS="$2" # REVIEW: is it deliberate that we `configure` all targets but only `build,test` `$TARGETS`? - "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" ${ARGS} + builder_launch /android/build.sh configure build,test:"${TARGETS}" builder_echo end "build" success "Finished building Keyman for Android" } @@ -22,7 +21,7 @@ android_publish_symbols() { local PUBTARGETS="$1" builder_echo start "publish to Sentry" "Publishing release to Sentry" - "${KEYMAN_ROOT}/android/build.sh" "publish-symbols:${PUBTARGETS}" + builder_launch /android/build.sh "publish-symbols:${PUBTARGETS}" builder_echo end "publish to Sentry" success "Finished publishing release to Sentry" } diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index c385f5cd42..e9a136dcb0 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -44,7 +44,7 @@ cd "${KEYMAN_ROOT}/android" function _create_zip_archive() { builder_echo start "archive" "Copying release artifacts to upload/ and creating zip" - "${KEYMAN_ROOT}/android/build.sh" archive + builder_launch /android/build.sh archive builder_echo end "archive" success "Finished copying release artifacts to upload/ and creating zip" } @@ -66,7 +66,7 @@ function _publish_to_playstore() { local PUBTARGETS="$1" builder_echo start "publish to Google Play Store" "Publishing release to Google Play Store" - "${KEYMAN_ROOT}/android/build.sh" "publish-play-store:${PUBTARGETS}" + builder_launch /android/build.sh "publish-play-store:${PUBTARGETS}" builder_echo end "publish to Google Play Store" success "Finished publishing release to Google Play Store" } @@ -107,10 +107,10 @@ fi if builder_has_action all; then android_clean_action - android_build_action "${TARGETS}" --release + android_build_action "${TARGETS}" do_publish else builder_run_action clean android_clean_action - builder_run_action build android_build_action "${TARGETS}" --release + builder_run_action build android_build_action "${TARGETS}" builder_run_action publish do_publish fi diff --git a/resources/teamcity/android/keyman-android-test-samples.sh b/resources/teamcity/android/keyman-android-test-samples.sh index 58e1a7a1d8..675d339dcf 100755 --- a/resources/teamcity/android/keyman-android-test-samples.sh +++ b/resources/teamcity/android/keyman-android-test-samples.sh @@ -29,9 +29,7 @@ builder_parse "$@" cd "${KEYMAN_ROOT}/android" function do_build() { - "${KEYMAN_ROOT}/android/build.sh" \ - configure,build:engine,sample1,sample2,keyboardharness \ - --debug + builder_launch /android/build.sh configure,build:engine,sample1,sample2,keyboardharness } if builder_has_action all; then diff --git a/resources/teamcity/android/keyman-android-test.sh b/resources/teamcity/android/keyman-android-test.sh index 3a48154eee..4060b01075 100755 --- a/resources/teamcity/android/keyman-android-test.sh +++ b/resources/teamcity/android/keyman-android-test.sh @@ -39,10 +39,10 @@ fi if builder_has_action all; then android_clean_action - android_build_action "${TARGETS}" --debug - android_publish_symbols "${TARGETS}" --debug + android_build_action "${TARGETS}" + android_publish_symbols "${TARGETS}" else builder_run_action clean android_clean_action - builder_run_action build android_build_action "${TARGETS}" --debug - builder_run_action publish android_publish_symbols "${TARGETS}" --debug + builder_run_action build android_build_action "${TARGETS}" + builder_run_action publish android_publish_symbols "${TARGETS}" fi diff --git a/resources/teamcity/common/common-test-common-web.sh b/resources/teamcity/common/common-test-common-web.sh index 31961f4aea..f865249fbc 100755 --- a/resources/teamcity/common/common-test-common-web.sh +++ b/resources/teamcity/common/common-test-common-web.sh @@ -18,4 +18,4 @@ builder_describe \ builder_parse "$@" -builder_run_action all "${KEYMAN_ROOT}/common/web/build.sh" configure build test +builder_run_action all builder_launch /common/web/build.sh configure build test diff --git a/resources/teamcity/common/common-test-common.sh b/resources/teamcity/common/common-test-common.sh index e238236c66..b334724a21 100755 --- a/resources/teamcity/common/common-test-common.sh +++ b/resources/teamcity/common/common-test-common.sh @@ -18,4 +18,4 @@ builder_describe \ builder_parse "$@" -builder_run_action all "${KEYMAN_ROOT}/common/build.sh" configure build test +builder_run_action all builder_launch /common/build.sh configure build test diff --git a/resources/teamcity/common/common-test-core-wasm.sh b/resources/teamcity/common/common-test-core-wasm.sh index 2764aaa247..65f13409bc 100755 --- a/resources/teamcity/common/common-test-core-wasm.sh +++ b/resources/teamcity/common/common-test-core-wasm.sh @@ -19,4 +19,4 @@ builder_describe \ builder_parse "$@" -builder_run_action all "${KEYMAN_ROOT}/core/build.sh" configure,build,test:wasm +builder_run_action all builder_launch /core/build.sh configure,build,test:wasm diff --git a/resources/teamcity/common/common-test-core.sh b/resources/teamcity/common/common-test-core.sh index 6d061ec4c0..7b838182fa 100755 --- a/resources/teamcity/common/common-test-core.sh +++ b/resources/teamcity/common/common-test-core.sh @@ -34,7 +34,8 @@ else fi function do_all() { - "${KEYMAN_ROOT}/core/build.sh" configure,build,test:${ARCH} + builder_launch /core/tools/ldml-const-builder/build.sh clean,configure,build,run,test + builder_launch /core/build.sh configure,build,test:${ARCH} } builder_run_action all do_all diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index b24978de0d..ea990efc4d 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -41,7 +41,7 @@ cd "${KEYMAN_ROOT}/developer/src" function _build_developer() { builder_echo start "build developer" "Building Keyman Developer" - ./build.sh configure build test api publish --npm-publish + builder_launch /developer/src/build.sh configure build test api publish builder_echo end "build developer" success "Finished building Keyman Developer" } @@ -49,7 +49,7 @@ function _build_developer() { function _build_testkeyboards() { builder_echo start "build testkeyboards" "Building test keyboards" - "${KEYMAN_ROOT}/common/test/keyboards/build.sh" + builder_launch /common/test/keyboards/build.sh builder_echo end "build testkeyboards" success "Finished building test keyboards" } @@ -57,7 +57,7 @@ function _build_testkeyboards() { function _publish_sentry() { builder_echo start "publish sentry" "Publishing debug information files to Sentry" - "${KEYMAN_ROOT}/developer/src/tools/sentry-upload-difs.sh" + builder_launch /developer/src/tools/sentry-upload-difs.sh builder_echo end "publish sentry" success "Finished publishing debug information files to Sentry" } diff --git a/resources/teamcity/developer/keyman-developer-test-linux-macos.sh b/resources/teamcity/developer/keyman-developer-test-linux-macos.sh index 90fa341021..7eb8106b37 100755 --- a/resources/teamcity/developer/keyman-developer-test-linux-macos.sh +++ b/resources/teamcity/developer/keyman-developer-test-linux-macos.sh @@ -32,7 +32,7 @@ cd "${KEYMAN_ROOT}/developer/src" function build_developer_action() { builder_echo start "build developer" "Building Keyman Developer" - "${KEYMAN_ROOT}/developer/src/build.sh" configure build test + builder_launch /developer/src/build.sh configure build test builder_echo end "build developer" success "Finished building Keyman Developer" } diff --git a/resources/teamcity/developer/keyman-developer-test-windows.sh b/resources/teamcity/developer/keyman-developer-test-windows.sh index 668e7a9c04..ed5b634274 100755 --- a/resources/teamcity/developer/keyman-developer-test-windows.sh +++ b/resources/teamcity/developer/keyman-developer-test-windows.sh @@ -41,7 +41,7 @@ function build_developer_action() { function _build_developer() { builder_echo start "build developer" "Building and testing Keyman Developer" - ./build.sh configure build test + builder_launch /developer/src/build.sh configure build test builder_echo end "build developer" success "Finished building and testing Keyman Developer" } @@ -49,19 +49,19 @@ function _build_developer() { function _build_testkeyboards() { builder_echo start "build testkeyboards" "Building test keyboards" - "${KEYMAN_ROOT}/common/test/keyboards/build.sh" --zip-source --index + builder_launch /common/test/keyboards/build.sh --zip-source --index builder_echo end "build testkeyboards" success "Finished building test keyboards" } function publish_sentry_action() { - builder_echo start "publish" "Dry-run publish and api" - ./build.sh api publish --dry-run - builder_echo end "publish" "Dry-run publish and api" + builder_echo start "publish" "publish modules locally and prep api" + builder_launch /developer/src/build.sh api publish + builder_echo end "publish" "publish modules locally and prep api" builder_echo start "publish sentry" "Publishing debug information files to Sentry" - # TODO: move this into build.sh publish? re-scope --dry-run into build.sh? - "${KEYMAN_ROOT}/developer/src/tools/sentry-upload-difs.sh" + # TODO: move this into build.sh publish? + builder_launch /developer/src/tools/sentry-upload-difs.sh builder_echo end "publish sentry" success "Finished publishing debug information files to Sentry" } diff --git a/resources/teamcity/includes/tc-linux.inc.sh b/resources/teamcity/includes/tc-linux.inc.sh index 362873189d..cd02838682 100644 --- a/resources/teamcity/includes/tc-linux.inc.sh +++ b/resources/teamcity/includes/tc-linux.inc.sh @@ -95,15 +95,15 @@ ba_linux_start_xvfb() { local PID_FILE=/tmp/keymanweb-pids builder_echo "Starting Xvfb..." Xvfb -screen 0 1024x768x24 :33 &> /dev/null & - echo "kill -9 $! || true" > "${PID_FILE}" + echo "kill -9 $! &> /dev/null || true" > "${PID_FILE}" sleep 1 builder_echo "Starting Xephyr..." DISPLAY=:33 Xephyr :32 -screen 1024x768 &> /dev/null & - echo "kill -9 $! || true" >> "${PID_FILE}" + echo "kill -9 $! &> /dev/null || true" >> "${PID_FILE}" sleep 1 builder_echo "Starting metacity" metacity --display=:32 &> /dev/null & - echo "kill -9 $! || true" >> "${PID_FILE}" + echo "kill -9 $! &> /dev/null || true" >> "${PID_FILE}" export DISPLAY=:32 } diff --git a/resources/teamcity/ios/keyman-ios-test-samples.sh b/resources/teamcity/ios/keyman-ios-test-samples.sh index fb109f3a2a..8d4587dae8 100755 --- a/resources/teamcity/ios/keyman-ios-test-samples.sh +++ b/resources/teamcity/ios/keyman-ios-test-samples.sh @@ -25,13 +25,13 @@ cd "${KEYMAN_ROOT}/ios" function _build_sample1() { builder_echo start "kmsample1" "Building KMSample1" - "${KEYMAN_ROOT}/ios/samples/KMSample1/build.sh" clean configure build --debug + builder_launch /ios/samples/KMSample1/build.sh clean configure build builder_echo end "kmsample1" success "Finished building KMSample1" } function _build_sample2() { builder_echo start "kmsample2" "Building KMSample2" - "${KEYMAN_ROOT}/ios/samples/KMSample2/build.sh" clean configure build --debug + builder_launch /ios/samples/KMSample2/build.sh clean configure build builder_echo end "kmsample2" success "Finished building KMSample2" } diff --git a/resources/teamcity/ios/keyman-ios-test.sh b/resources/teamcity/ios/keyman-ios-test.sh index 1c6f156238..63c48cc7dc 100755 --- a/resources/teamcity/ios/keyman-ios-test.sh +++ b/resources/teamcity/ios/keyman-ios-test.sh @@ -52,16 +52,14 @@ function __do_upload_to_testflight() { local FASTLANE_ITC_PROVIDER="$1" local FASTLANE_API_KEY_ID="$2" local APP_IPA_DST - local BUILD_NUMBER export FASTLANE_ITC_TEAM_ID="$3" export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 - BUILD_NUMBER=$(cat ../VERSION.md) - APP_IPA_DST="$4-ios-${BUILD_NUMBER}.ipa" + APP_IPA_DST="$4-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" - cd "upload/${BUILD_NUMBER}" + cd "upload/${KEYMAN_VERSION}" command -v fastlane # shellcheck disable=SC2154 fastlane pilot upload \ diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index 6053980a55..6a90fc1613 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -108,7 +108,7 @@ function _publish_to_launchpad() { git reset --hard /usr/lib/gnupg/gpg-preset-passphrase --passphrase "${GPGKEYPW}" --preset "${GPGKEYGRIP}" - UPLOAD=yes scripts/launchpad.sh + UPLOAD=yes "${KEYMAN_ROOT}/linux/scripts/launchpad.sh" /usr/lib/gnupg/gpg-preset-passphrase --forget "${GPGKEYGRIP}" || true builder_echo end "upload to launchpad" success "Upload to launchpad" @@ -117,12 +117,9 @@ function _publish_to_launchpad() { function _publish_linux_help() { builder_echo start "upload linux help" "Upload new Keyman Linux help to help.keyman.com" - ( - cd "${KEYMAN_ROOT}/../help.keyman.com" || exit 1 - # shellcheck disable=SC2016 - git config credential.helper '!f() { sleep 1; echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f' - "${KEYMAN_ROOT}/resources/build/ci/help-keyman-com.sh" linux - ) + # shellcheck disable=SC2016 + git config credential.helper '!f() { sleep 1; echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f' + "${KEYMAN_ROOT}/resources/build/ci/help-keyman-com.sh" linux builder_echo end "upload linux help" success "Upload new Keyman Linux help to help.keyman.com" } diff --git a/resources/teamcity/linux/keyman-linux-test-integration.sh b/resources/teamcity/linux/keyman-linux-test-integration.sh index 4a19ebcb97..fdb94e4255 100755 --- a/resources/teamcity/linux/keyman-linux-test-integration.sh +++ b/resources/teamcity/linux/keyman-linux-test-integration.sh @@ -33,7 +33,7 @@ cd "${KEYMAN_ROOT}/linux" function clean_action() { builder_heading "Cleaning up" # shellcheck disable=SC2154 - "${KEYMAN_ROOT}/linux/build.sh" clean + builder_launch /linux/build.sh clean } if builder_has_action all; then @@ -41,7 +41,9 @@ if builder_has_action all; then linux_install_dependencies_action linux_additional_test_dependencies_action tc_set_variables_for_nvm + # shellcheck disable=SC2119 linux_build_action + # shellcheck disable=SC2119 linux_unit_tests_action else builder_run_action clean clean_action diff --git a/resources/teamcity/linux/keyman-linux-test.sh b/resources/teamcity/linux/keyman-linux-test.sh index 2779e0f803..d89a57a75e 100755 --- a/resources/teamcity/linux/keyman-linux-test.sh +++ b/resources/teamcity/linux/keyman-linux-test.sh @@ -30,10 +30,14 @@ builder_parse "$@" cd "${KEYMAN_ROOT}/linux" function make_source_tarball_action() { - builder_echo start "make source tarball" "Make source tarball" - rm -rf dist - make tmpsources - builder_echo end "make source tarball" success "Finished making source tarball" + if builder_is_ci_build_level_release; then + builder_echo start "make source tarball" "Make source tarball" + rm -rf dist + PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" "${KEYMAN_ROOT}/linux/scripts/dist.sh" + builder_echo end "make source tarball" success "Finished making source tarball" + else + builder_echo "Skipping source tarball creation - not release build level" + fi } if builder_has_action all; then diff --git a/resources/teamcity/linux/linux-actions.inc.sh b/resources/teamcity/linux/linux-actions.inc.sh index 0b96f4c07c..ab464e4a25 100644 --- a/resources/teamcity/linux/linux-actions.inc.sh +++ b/resources/teamcity/linux/linux-actions.inc.sh @@ -38,7 +38,7 @@ linux_additional_test_dependencies_action() { linux_build_action() { INSTALLDIR="$(mktemp -d)" # shellcheck disable=SC2068 - DESTDIR="${INSTALLDIR}" "${KEYMAN_ROOT}/linux/build.sh" clean configure build install $@ + DESTDIR="${INSTALLDIR}" builder_launch /linux/build.sh clean configure build install $@ } # Run unit tests for Keyman for Linux. @@ -52,6 +52,6 @@ linux_unit_tests_action() { export NO_AT_BRIDGE=1 # shellcheck disable=SC2068 - "${KEYMAN_ROOT}/linux/build.sh" test $@ + builder_launch /linux/build.sh test $@ builder_echo endTest unit_tests success "Finished running unit tests" } diff --git a/resources/teamcity/macos/macos-actions.inc.sh b/resources/teamcity/macos/macos-actions.inc.sh index 832de1aebe..efb5336904 100755 --- a/resources/teamcity/macos/macos-actions.inc.sh +++ b/resources/teamcity/macos/macos-actions.inc.sh @@ -9,6 +9,6 @@ macos_clean_action() { macos_build_action() { builder_echo start "build" "Building Keyman for macOS" # shellcheck disable=SC2154 - "${KEYMAN_ROOT}/mac/build.sh" configure build test publish + builder_launch /mac/build.sh configure build test publish builder_echo end "build" success "Finished building Keyman for macOS" } diff --git a/resources/teamcity/triggers/trigger-release-builds.sh b/resources/teamcity/triggers/trigger-release-builds.sh index f914a804f4..460bddf306 100755 --- a/resources/teamcity/triggers/trigger-release-builds.sh +++ b/resources/teamcity/triggers/trigger-release-builds.sh @@ -98,7 +98,7 @@ echo "trigger-release-builds.sh: building resources/build/version" pushd "$KEYMAN_ROOT" npm ci -"$KEYMAN_ROOT/resources/build/version/build.sh" +builder_launch /resources/build/version/build.sh echo "trigger-release-builds.sh: running resources/build/version" pushd "$KEYMAN_ROOT" diff --git a/resources/teamcity/triggers/trigger-test-builds.sh b/resources/teamcity/triggers/trigger-test-builds.sh index 967312b18a..d7dc4de1d3 100755 --- a/resources/teamcity/triggers/trigger-test-builds.sh +++ b/resources/teamcity/triggers/trigger-test-builds.sh @@ -12,6 +12,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +# shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/build/ci/trigger-definitions.inc.sh" . "${KEYMAN_ROOT}/resources/build/ci/trigger-builds.inc.sh" . "${KEYMAN_ROOT}/resources/build/ci/trigger-build-bot.inc.sh" @@ -29,15 +30,15 @@ if ! builder_has_option --branch; then builder_die "--branch parameter is required" fi -if [[ ! "$PRNUM" =~ ^([0-9]+|master|beta|stable-[0-9]+\.[0-9]+)$ ]]; then - builder_die "Invalid parameter --branch '$PRNUM'; expected 'master', 'beta', 'stable-x.y', or PR number" +if [[ ! "${PRNUM}" =~ ^([0-9]+|master|beta|stable-[0-9]+\.[0-9]+)$ ]]; then + builder_die "Invalid parameter --branch '${PRNUM}'; expected 'master', 'beta', 'stable-x.y', or PR number" fi # # Debug logging # function debug_echo() { - if [ ! -z ${DEBUG:-} ]; then + if [[ ! -z ${DEBUG:-} ]]; then echo "DEBUG: $1" fi true @@ -59,44 +60,45 @@ function triggerTestBuilds() { # Cancel any already running builds for this branch if builder_has_option --dry-run; then - builder_echo "DRY RUN: cancel current builds for $branch" + builder_echo "DRY RUN: cancel current builds for ${branch}" else - node "$KEYMAN_ROOT/resources/build/ci/cancel-builds/cancel-test-builds.mjs" "$branch" "$TEAMCITY_TOKEN" + node "${KEYMAN_ROOT}/resources/build/ci/cancel-builds/cancel-test-builds.mjs" "${branch}" "${TEAMCITY_TOKEN}" fi for platform in "${!platforms[@]}"; do - local platformBuildLevel=${platforms[$platform]} - if [[ $platformBuildLevel == skip ]]; then - builder_echo heading "$platform: skipping build" + local platformBuildLevel=${platforms[${platform}]} + if [[ ${platformBuildLevel} == skip ]]; then + builder_echo heading "${platform}: skipping build" continue fi - builder_echo heading "$platform: checking for git changes" - eval test_builds='(${'bc_test_$platform'[@]})' + builder_echo heading "${platform}: checking for git changes" + eval test_builds='(${'bc_test_${platform}'[@]})' + # shellcheck disable=SC2154 for test_build in "${test_builds[@]}"; do - if [[ $test_build == "" ]]; then continue; fi + if [[ ${test_build} == "" ]]; then continue; fi found_build=true - if [ "${test_build:(-7)}" == "_GitHub" ]; then + if [[ "${test_build:(-7)}" == "_GitHub" ]]; then local job=${test_build%_GitHub} if builder_has_option --dry-run; then - builder_echo "DRY RUN: Triggering GitHub action build $job/$branch, level = $platformBuildLevel" + builder_echo "DRY RUN: Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" else - builder_echo "Triggering GitHub action build $job/$branch, level = $platformBuildLevel" - triggerGitHubActionsBuild true $platformBuildLevel "$job" "$branch" + builder_echo "Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" + triggerGitHubActionsBuild true "${platformBuildLevel}" "${job}" "${branch}" "${KEYMAN_API_CHECK_SKIP}" fi else if builder_has_option --dry-run; then - builder_echo "DRY RUN: Triggering build configuration $test_build on teamcity for $branch, level = $platformBuildLevel" + builder_echo "DRY RUN: Triggering build configuration ${test_build} on teamcity for ${branch}, level = ${platformBuildLevel}" else - builder_echo "Triggering build configuration $test_build on teamcity for $branch, level = $platformBuildLevel" - triggerTeamCityBuild true $platformBuildLevel "$test_build" "$vcs_test" "$branch" + builder_echo "Triggering build configuration ${test_build} on teamcity for ${branch}, level = ${platformBuildLevel}" + triggerTeamCityBuild true "${platformBuildLevel}" "${test_build}" "${vcs_test}" "${branch}" fi fi done done - if [[ $found_build == false ]]; then + if [[ ${found_build} == false ]]; then postSkippedBuildsStatusResult fi } @@ -111,21 +113,57 @@ function postSkippedBuildsStatusResult() { curl --silent --write-out '\n' \ --request POST \ --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ + --header "Authorization: token ${GITHUB_TOKEN}" \ --data '{"state":"success","description":"Skipping since no platform builds necessary","context":"Test Build (Keyman)"}' \ "https://api.github.com/repos/keymanapp/keyman/statuses/${BUILD_VCS_NUMBER}" fi } +# +# Find the platforms that have changes based on the watch_ variables in trigger-definitions.inc.sh +# +function find_platform_changes() { + builder_echo grey "# Find platforms that have changes" + declare -gA build_platforms + local platform watch + + # Scan the files found + while IFS= read -r line; do + # for each platform + echo "$line" + for platform in "${available_platforms[@]}"; do + if [[ ! " ${!build_platforms[*]} " =~ " ${platform} " ]]; then + # Build a variable name for the current platform + watch="watch_${platform}" + # And then get the variable value + watch="${!watch}" + # Add common patterns to the watch list + watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity|docker-images)|teamcity/(${platform}|includes)|docker-images/(${platform}|base))|${watch})" + # Since bash doesn't support negative look-aheads we use perl to test + # (grep has a --perl-regexp option, but not the version on macOS) + if perl -e 'exit($ARGV[0] =~ /$ARGV[1]/ ? 0 : 1)' "${line}" "${watch}"; then + # By default, we'll build a 'release' test build for touched platforms + echo " adding default build platform ${platform}" + build_platforms[${platform}]=${build_level_release} + fi + fi + done + done <<< "${prfiles}" + + builder_echo blue "Default build platforms: ${!build_platforms[*]}" +} + +KEYMAN_API_CHECK_SKIP="false" + # # Following is not an error; this script can run against master/beta/stable # branches as well as pull requests # -if [[ ! "$PRNUM" =~ ^[[:digit:]]+$ ]]; then +if [[ ! "${PRNUM}" =~ ^[[:digit:]]+$ ]]; then # branch name is 'master', 'beta', or 'stable-x.y' - builder_echo "Branch $PRNUM needs to pass tests on all platforms." - triggerTestBuilds main_branch_platform_build_levels $PRNUM + builder_echo "Branch ${PRNUM} needs to pass tests on all platforms." + triggerTestBuilds main_branch_platform_build_levels "${PRNUM}" exit 0 fi @@ -136,20 +174,20 @@ fi # targeted by the PR other than to ask GitHub, anyway.) # -builder_echo grey "# Get information about pull request #$PRNUM from GitHub" -prinfo=`curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/keymanapp/keyman/pulls/$PRNUM` -prbase=`echo ${prinfo} | "$JQ" -r '.base.ref'` -prhead=`echo ${prinfo} | "$JQ" -r '.head.ref'` -prbaserepo=`echo ${prinfo} | "$JQ" -r '.base.repo.html_url'` -prheadrepo=`echo ${prinfo} | "$JQ" -r '.head.repo.html_url'` +builder_echo grey "# Get information about pull request #${PRNUM} from GitHub" +prinfo=$(curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer ${GITHUB_TOKEN}" "https://api.github.com/repos/keymanapp/keyman/pulls/${PRNUM}") +prbase=$(echo "${prinfo}" | "${JQ}" -r '.base.ref') +prhead=$(echo "${prinfo}" | "${JQ}" -r '.head.ref') +prbaserepo=$(echo "${prinfo}" | "${JQ}" -r '.base.repo.html_url') +prheadrepo=$(echo "${prinfo}" | "${JQ}" -r '.head.repo.html_url') -debug_echo "PRNUM: $PRNUM" -debug_echo "BASE: $prbase ($prbaserepo)" -debug_echo "HEAD: $prhead ($prheadrepo)" +debug_echo "PRNUM: ${PRNUM}" +debug_echo "BASE: ${prbase} (${prbaserepo})" +debug_echo "HEAD: ${prhead} (${prheadrepo})" prremote=origin -if [ "$prbaserepo" != "https://github.com/keymanapp/keyman" ]; then - echo "Unknown base repository $prbaserepo." +if [[ "${prbaserepo}" != "https://github.com/keymanapp/keyman" ]]; then + echo "Unknown base repository ${prbaserepo}." exit 1 fi @@ -160,7 +198,7 @@ fi # (Ensure we are within the repository before running git calls) builder_echo grey "# Ensure our local branch is up to date" -pushd "$KEYMAN_ROOT" > /dev/null +pushd "${KEYMAN_ROOT}" > /dev/null git fetch origin > /dev/null # @@ -169,10 +207,10 @@ git fetch origin > /dev/null # on untrusted forks. # -if [ "$prheadrepo" != "https://github.com/keymanapp/keyman" ]; then +if [[ "${prheadrepo}" != "https://github.com/keymanapp/keyman" ]]; then prremote=external-rrtb - git remote add "$prremote" "$prheadrepo" - git fetch "$prremote" > /dev/null + git remote add "${prremote}" "${prheadrepo}" + git fetch "${prremote}" > /dev/null fi # @@ -181,56 +219,24 @@ fi # builder_echo grey "# Get list of changed files in the pull request" -prfiles=`git diff --name-only "origin/$prbase"..."$prremote/$prhead" || ( if [ $? == 128 ]; then echo abort; else exit $?; fi )` -if [ "$prfiles" == "abort" ]; then +prfiles=$(git diff --name-only "origin/${prbase}...${prremote}/${prhead}" || ( if [[ $? == 128 ]]; then echo abort; else exit $?; fi )) +if [[ "${prfiles}" == "abort" ]]; then # Don't trigger any builds, exit with success - if [ "$prremote" != "origin" ]; then - git remote remove "$prremote" + if [[ "${prremote}" != "origin" ]]; then + git remote remove "${prremote}" fi - echo "Remote branch origin/$prhead has gone away; probably an automatic pull request. Skipping build." + echo "Remote branch origin/${prhead} has gone away; probably an automatic pull request. Skipping build." exit 0 fi -if [ "$prremote" != "origin" ]; then - git remote remove "$prremote" +if [[ "${prremote}" != "origin" ]]; then + git remote remove "${prremote}" fi -debug_echo "Files found: ${prfiles[*]}" +echo "Found $(echo "${prfiles}" | wc -l) changed file(s)" popd > /dev/null -# -# Find the platforms that have changes based on the watch_ variables in trigger-definitions.inc.sh -# - -function find_platform_changes() { - builder_echo grey "# Find platforms that have changes" - declare -gA build_platforms - local platform watch - - # Scan the files found - while IFS= read -r line; do - # for each platform - for platform in "${available_platforms[@]}"; do - if [[ ! " ${!build_platforms[@]} " =~ " $platform " ]]; then - # Which platform are we watching? - eval watch='$'watch_$platform - - # Add common patterns to the watch list - watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity)|teamcity/(${platform}|includes))|${watch})" - # Since bash doesn't support negative look-aheads we use perl to test - # (grep has a --perl-regexp option, but not the version on macOS) - if perl -e 'exit($ARGV[0] =~ /$ARGV[1]/ ? 0 : 1)' "${line}" "${watch}"; then - # By default, we'll build a 'release' test build for touched platforms - build_platforms[$platform]=$build_level_release - fi - fi - done - done <<< "$prfiles" - - builder_echo blue "Default build platforms: ${!build_platforms[@]}" -} - find_platform_changes # @@ -238,11 +244,21 @@ find_platform_changes # This will modify the build_platforms array # -if [ "$prremote" == "origin" ]; then +if [[ "${prremote}" == "origin" ]]; then # We only accept Build-bot commands on trusted local origin PRs - prcommits=`curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/keymanapp/keyman/pulls/$PRNUM/commits` - test_bot_check_pr_body "$PRNUM" "$prinfo" - build_bot_check_messages $PRNUM "$prinfo" "$prcommits" + cd "${KEYMAN_ROOT}/resources/build/ci/github" + npm ci + if builder_is_windows; then + # See https://stackoverflow.com/questions/45890339/stdout-is-not-a-tty-using-bash-for-node-tape-tap-spec + NODE_WRAPPER=node.exe + else + NODE_WRAPPER=node + fi + # Note: we may move all of this into javascript at some point! + prcommits=$(${NODE_WRAPPER} api-pull-commits.mjs "${GITHUB_TOKEN}" "${PRNUM}") + test_bot_check_pr_body "${PRNUM}" "${prinfo}" + build_bot_check_messages "${PRNUM}" "${prinfo}" "${prcommits}" + KEYMAN_API_CHECK_SKIP="$(is_skip_keyman_api_check "${PRNUM}" "${prinfo}")" fi # @@ -251,7 +267,7 @@ fi if [[ ! -z "${build_platforms[*]:-}" ]] && (( ${#build_platforms[@]} > 0)); then builder_echo heading "Start test builds" - triggerTestBuilds build_platforms $PRNUM + triggerTestBuilds build_platforms "${PRNUM}" else builder_echo heading "No builds to start" postSkippedBuildsStatusResult diff --git a/resources/teamcity/web/web-actions.inc.sh b/resources/teamcity/web/web-actions.inc.sh index 437254b975..324fa81663 100644 --- a/resources/teamcity/web/web-actions.inc.sh +++ b/resources/teamcity/web/web-actions.inc.sh @@ -52,7 +52,7 @@ web_test_action() { trap ERR fi - "${KEYMAN_ROOT}/web/build.sh" coverage + builder_launch /web/build.sh coverage builder_echo end web_test success "Finished running tests for native KeymanWeb" } diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 51d95a6082..d21b4ce916 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -84,7 +84,7 @@ function _publish_to_downloads_keyman_com() { # TODO: is this still needed? if [[ -f "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then cp "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}" - write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman Desktop and Keyman Developer debug files" zip win + write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman for Windows and Keyman Developer debug files" zip win fi cd upload @@ -102,7 +102,7 @@ function windows_publish_action() { export RSYNC_HOST export RSYNC_ROOT - "${KEYMAN_ROOT}/windows/build.sh" publish + builder_launch /windows/build.sh publish windows_upload_symbols_to_sentry ba_win_download_symbol_server_index ba_win_publish_new_symbols diff --git a/resources/teamcity/windows/keyman-windows-test.sh b/resources/teamcity/windows/keyman-windows-test.sh index fc83b5c362..a0b74f86e5 100755 --- a/resources/teamcity/windows/keyman-windows-test.sh +++ b/resources/teamcity/windows/keyman-windows-test.sh @@ -37,7 +37,7 @@ fi function windows_publish_action() { builder_echo start "publish windows" "Publishing Keyman for Windows" - "${KEYMAN_ROOT}/windows/build.sh" publish + builder_launch /windows/build.sh publish windows_upload_symbols_to_sentry builder_echo end "publish windows" success "Finished publishing Keyman for Windows" } diff --git a/resources/teamcity/windows/windows-actions.inc.sh b/resources/teamcity/windows/windows-actions.inc.sh index 3b5699447c..7f99964806 100755 --- a/resources/teamcity/windows/windows-actions.inc.sh +++ b/resources/teamcity/windows/windows-actions.inc.sh @@ -4,16 +4,16 @@ windows_build_action() { builder_echo start "build windows" "Building Keyman for Windows" # shellcheck disable=SC2154 - "${KEYMAN_ROOT}/windows/build.sh" configure build + builder_launch /windows/build.sh configure build builder_echo end "build windows" success "Finished building Keyman for Windows" } windows_test_action() { builder_echo start "test windows" "Running Keyman for Windows tests" - "${KEYMAN_ROOT}/windows/build.sh" test + builder_launch /windows/build.sh test builder_echo end "test windows" success "Finished running Keyman for Windows tests" } windows_upload_symbols_to_sentry() { - "${KEYMAN_ROOT}/windows/src/buildtools/sentry-upload-difs.sh" + builder_launch /windows/src/buildtools/sentry-upload-difs.sh } diff --git a/web/build.sh b/web/build.sh index 4dc5189353..2d7aed9a48 100755 --- a/web/build.sh +++ b/web/build.sh @@ -204,7 +204,7 @@ builder_run_action build:_all build_action # Run tests builder_run_child_actions test -builder_run_action test:_all ./test.sh +builder_run_action test:_all builder_launch /web/test.sh test function do_test_help() { check-markdown "$KEYMAN_ROOT/web/docs/engine" diff --git a/web/ci.sh b/web/ci.sh index 02545cf447..9008d56a50 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -64,7 +64,7 @@ function build_action() { # - also useful when validating this script on a local dev machine! # - build: then do the ACTUAL build. # one option: - ./build.sh configure clean build + builder_launch /web/build.sh configure clean build if builder_is_ci_build && builder_is_ci_build_level_release; then # Upload the sentry-configuration engine used by the mobile apps to sentry @@ -81,7 +81,7 @@ function build_action() { } function test_action() { - ./build.sh test + builder_launch /web/build.sh test } function post_test_action() { diff --git a/web/common.inc.sh b/web/common.inc.sh index fed2af8c7b..32249094a3 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -100,18 +100,24 @@ function test-headless() { tsc --project "${KEYMAN_ROOT}/web/src/test/auto/tsconfig.json" fi - TEST_OPTS= - if builder_is_ci_build; then - TEST_OPTS="--reporter mocha-teamcity-reporter" + TEST_OPTS=() + if builder_is_running_on_teamcity; then + TEST_OPTS+=(--reporter "${KEYMAN_ROOT}/common/test/resources/mocha-teamcity-reporter/teamcity.cjs" --reporter-options parentFlowId="unit_tests") + echo "##teamcity[flowStarted flowId='unit_tests']" fi - if [[ -n "$TEST_EXTENSIONS" ]]; then - TEST_OPTS="$TEST_OPTS --extension $TEST_EXTENSIONS" + if [[ -n "${TEST_EXTENSIONS}" ]]; then + TEST_OPTS+=(--extension "${TEST_EXTENSIONS}") fi if [[ -e .c8rc.json ]]; then - c8 mocha --recursive "${TEST_BASE}${TEST_FOLDER}" $TEST_OPTS + c8 mocha --recursive "${TEST_BASE}${TEST_FOLDER}" "${TEST_OPTS[@]}" else - mocha --recursive "${TEST_BASE}${TEST_FOLDER}" $TEST_OPTS + mocha --recursive "${TEST_BASE}${TEST_FOLDER}" "${TEST_OPTS[@]}" + fi + + if builder_is_running_on_teamcity; then + # we're running in TeamCity + echo "##teamcity[flowFinished flowId='unit_tests']" fi } diff --git a/web/docs/engine/index.md b/web/docs/engine/index.md index 5515112f88..377b1ae66f 100644 --- a/web/docs/engine/index.md +++ b/web/docs/engine/index.md @@ -20,7 +20,7 @@ computer browsers. -[What's New](whats-new) +[What's New](whatsnew) : What's new in Keyman Engine for Web 19.0 diff --git a/web/docs/engine/reference/core/helpURL.md b/web/docs/engine/reference/core/helpURL.md index 16f86eb3ca..242779ec6b 100644 --- a/web/docs/engine/reference/core/helpURL.md +++ b/web/docs/engine/reference/core/helpURL.md @@ -22,7 +22,7 @@ Read only ### Return Value -Presently set to [`"http://help.keymanweb.com/go"`](). +Presently set to [`"https://help.keyman.com/go"`](). ## Description diff --git a/web/docs/engine/troubleshooting/embedded_fonts.md b/web/docs/engine/troubleshooting/embedded_fonts.md index 81215add20..1e5e7ddcd2 100644 --- a/web/docs/engine/troubleshooting/embedded_fonts.md +++ b/web/docs/engine/troubleshooting/embedded_fonts.md @@ -36,11 +36,11 @@ needed to display correctly. | Tibetan | ![](images/TibetanSample.png) | བོད་ཡུལ། | བོད་ཡུལ། | --- -**If the embedded font display for a language is not exactly the same as in the corresponding image,** +**If the embedded font display for a language is not exactly the same as in the corresponding image,** for example, with script characters shown but incorrectly positioned or ordered, you will need to install the -Keyman Desktop package for your language onto your computer. You can -download the Keyman Desktop package by clicking the "Help on this +Keyman for Windows package for your language onto your computer. You can +download the Keyman for Windows package by clicking the "Help on this keyboard" link in the On Screen Keyboard. This is likely to be observed with scripts such as Burmese, Khmer or Tibetan if your operating system does not fully support the display of the font used for those scripts. diff --git a/web/docs/engine/troubleshooting/index.md b/web/docs/engine/troubleshooting/index.md index 019401f8ee..e7c7a777a0 100644 --- a/web/docs/engine/troubleshooting/index.md +++ b/web/docs/engine/troubleshooting/index.md @@ -29,8 +29,8 @@ the images on the right, please check the following: - If you copy and paste text that you have typed in KeymanWeb into another application such as Microsoft Word, you may find that it does not display. In this case, you will need to installing the - Keyman Desktop package for the keyboard onto your computer. You can - download the Keyman Desktop package by clicking the "Help on this + Keyman for Windows package for the keyboard onto your computer. You can + download the Keyman for Windows package by clicking the "Help on this keyboard" link in the On Screen Keyboard. - An [embedded font test page](embedded_fonts) is available for further testing of embedded font support on your computer. @@ -43,8 +43,8 @@ the images on the right, please check the following: If you find that you can see the characters for a language on screen, but they don't display quite right, you may be experiencing a limitation in your operating system. An example of this is visible in the Tibetan -script on the right. Installing Keyman Desktop or Keyman for Mac may -help resolve these issues. +script on the right. Installing Keyman for Windows, Keyman for Linux, +or Keyman for macOS may help resolve these issues. @@ -89,4 +89,4 @@ please check the following: If you are experiencing continued difficulty in using KeymanWeb and have not been able to find a solution on this page, [click here](/contact). - + diff --git a/web/docs/engine/whats-new.md b/web/docs/engine/whats-new.md deleted file mode 100644 index e220fb9c13..0000000000 --- a/web/docs/engine/whats-new.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: What's New in KeymanWeb 19.0 ---- diff --git a/web/docs/engine/whatsnew.md b/web/docs/engine/whatsnew.md new file mode 100644 index 0000000000..8828f3b81f --- /dev/null +++ b/web/docs/engine/whatsnew.md @@ -0,0 +1,7 @@ +--- +title: What's New in KeymanWeb 19.0 +--- + +## See also + +* [Keyman Engine for Web Documentation](index) diff --git a/web/src/app/browser/src/debug-main.ts b/web/src/app/browser/src/debug-main.ts index 973e0d7efc..aa6b23a2a4 100644 --- a/web/src/app/browser/src/debug-main.ts +++ b/web/src/app/browser/src/debug-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ - const scripts = document.getElementsByTagName('script'); - const ss = scripts[scripts.length-1].src; - const sPath = ss.substr(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/browser/src/release-main.ts b/web/src/app/browser/src/release-main.ts index faf96e9097..da6b5be109 100644 --- a/web/src/app/browser/src/release-main.ts +++ b/web/src/app/browser/src/release-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { Worker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ - const scripts = document.getElementsByTagName('script'); - const ss = scripts[scripts.length-1].src; - const sPath = ss.substr(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file diff --git a/web/src/app/ui/kmwuifloat.ts b/web/src/app/ui/kmwuifloat.ts index 38fabb5bd7..f67463cf48 100644 --- a/web/src/app/ui/kmwuifloat.ts +++ b/web/src/app/ui/kmwuifloat.ts @@ -130,7 +130,7 @@ if(!keymanweb) { this.outerDiv = util.createElement('div'); // Container for UI (visible when KeymanWeb is active) this.innerDiv = util.createElement('div'); // inner div for UI this.kbdIcon = util.createElement('img'); - this.outerDiv.innerHTML = "" + this.outerDiv.innerHTML = "" + "KeymanWeb"; /* I2081 */ let s=this.outerDiv.style; diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh index 403aaec4e4..32534e6950 100755 --- a/web/src/app/webview/build.sh +++ b/web/src/app/webview/build.sh @@ -78,7 +78,7 @@ compile_and_copy() { node map-polyfill-bundler.js # For dependent test pages. - "$KEYMAN_ROOT/web/src/test/manual/embed/android-harness/build.sh" + builder_launch /web/src/test/manual/embed/android-harness/build.sh configure,build } builder_run_action configure node_select_version_and_npm_ci diff --git a/web/src/app/webview/src/debug-main.ts b/web/src/app/webview/src/debug-main.ts index 646c8f1687..aa6b23a2a4 100644 --- a/web/src/app/webview/src/debug-main.ts +++ b/web/src/app/webview/src/debug-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ -const scripts = document.getElementsByTagName('script'); -const ss = scripts[scripts.length-1].src; -const sPath = ss.substring(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath); \ No newline at end of file diff --git a/web/src/app/webview/src/release-main.ts b/web/src/app/webview/src/release-main.ts index ba70bfef9b..da6b5be109 100644 --- a/web/src/app/webview/src/release-main.ts +++ b/web/src/app/webview/src/release-main.ts @@ -1,16 +1,12 @@ import { KeymanEngine } from './keymanEngine.js' import { Worker } from '@keymanapp/lexical-model-layer/web' - /** - * Determine path and protocol of executing script, setting them as - * construction defaults. - * - * This can only be done during load when the active script will be the - * last script loaded. Otherwise the script must be identified by name. - */ -const scripts = document.getElementsByTagName('script'); -const ss = scripts[scripts.length-1].src; -const sPath = ss.substring(0,ss.lastIndexOf('/')+1); +/** +* Determine path and protocol of executing script, setting them as +* construction defaults. +*/ +const ss = (document.currentScript as HTMLScriptElement)?.src; +const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './'; // @ts-ignore window['keyman'] = new KeymanEngine(Worker, sPath); \ No newline at end of file diff --git a/web/src/engine/common/web-utils/build.sh b/web/src/engine/common/web-utils/build.sh index 2ff9a03b06..23d38cb952 100755 --- a/web/src/engine/common/web-utils/build.sh +++ b/web/src/engine/common/web-utils/build.sh @@ -9,13 +9,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "${KEYMAN_ROOT}/web/common.inc.sh" -. "$KEYMAN_ROOT/resources/build/utils.inc.sh" -. "$KEYMAN_ROOT/resources/build/node.inc.sh" +. "${KEYMAN_ROOT}/resources/build/utils.inc.sh" +. "${KEYMAN_ROOT}/resources/build/node.inc.sh" SUBPROJECT_NAME=engine/common/web-utils BUILD_DIR="/web/src/engine/common/web-utils/build" -BUNDLE_CMD="node $KEYMAN_ROOT/web/src/tools/es-bundling/build/common-bundle.mjs" +BUNDLE_CMD="node ${KEYMAN_ROOT}/web/src/tools/es-bundling/build/common-bundle.mjs" ################################ Main script ################################ @@ -32,7 +32,7 @@ builder_describe_outputs \ builder_parse "$@" function do_build() { - compile $SUBPROJECT_NAME + compile "${SUBPROJECT_NAME}" # May be useful one day, for building a mass .d.ts for KMW as a whole. # So... tsc does declaration-bundling on its own pretty well, at least for local development. @@ -40,7 +40,7 @@ function do_build() { # One of the functions (timedPromise) is quite helpful for automated testing, even in the DOM. # So, to make sure it's easily-accessible for the DOM-based tests... - $BUNDLE_CMD "${KEYMAN_ROOT}/${BUILD_DIR}/obj/index.js" \ + ${BUNDLE_CMD} "${KEYMAN_ROOT}/${BUILD_DIR}/obj/index.js" \ --out "${KEYMAN_ROOT}/${BUILD_DIR}/lib/index.mjs" \ --format esm } @@ -48,13 +48,19 @@ function do_build() { function do_test() { builder_heading "Running web-utils test suite" - local FLAGS= - if builder_is_ci_build; then + local FLAGS=() + if builder_is_running_on_teamcity; then echo "Replacing user-friendly test reports with CI-friendly versions." - FLAGS="$FLAGS --reporter mocha-teamcity-reporter" + FLAGS+=(--reporter "${KEYMAN_ROOT}/common/test/resources/mocha-teamcity-reporter/teamcity.cjs" --reporter-options parentFlowId="unit_tests") + echo "##teamcity[flowStarted flowId='unit_tests']" fi - c8 mocha --recursive $FLAGS ./src/tests/ + c8 mocha --recursive "${FLAGS[@]}" ./src/tests/ + + if builder_is_running_on_teamcity; then + # we're running in TeamCity + echo "##teamcity[flowFinished flowId='unit_tests']" + fi } builder_run_action configure node_select_version_and_npm_ci diff --git a/web/src/engine/common/web-utils/package.json b/web/src/engine/common/web-utils/package.json index 85e194a648..557878d43e 100644 --- a/web/src/engine/common/web-utils/package.json +++ b/web/src/engine/common/web-utils/package.json @@ -27,7 +27,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/resources-gosh": "*", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "type": "module", diff --git a/web/src/engine/js-processor/build.sh b/web/src/engine/js-processor/build.sh index 0fc0ef7e7a..6d5989b515 100755 --- a/web/src/engine/js-processor/build.sh +++ b/web/src/engine/js-processor/build.sh @@ -36,7 +36,12 @@ do_build () { --format esm } +do_test() { + test-headless "${SUBPROJECT_NAME}" "" + test-headless-typescript "${SUBPROJECT_NAME}" +} + builder_run_action configure node_select_version_and_npm_ci builder_run_action clean rm -rf "${KEYMAN_ROOT}/web/build/${SUBPROJECT_NAME}" builder_run_action build do_build -builder_run_action test test-headless "${SUBPROJECT_NAME}" "" +builder_run_action test do_test diff --git a/web/src/engine/js-processor/src/deadkeys.ts b/web/src/engine/js-processor/src/deadkeys.ts index 53cf0d5995..fed03b39ed 100644 --- a/web/src/engine/js-processor/src/deadkeys.ts +++ b/web/src/engine/js-processor/src/deadkeys.ts @@ -1,6 +1,6 @@ // Defines the base Deadkey-tracking object. export class Deadkey { - p: number; // Position of deadkey + p: number; // Position of deadkey in the textstore (deadkey comes before the character at this position) d: number; // Numerical id of the deadkey o: number; // Ordinal value of the deadkey (resolves same-place conflicts) matched: number; @@ -11,10 +11,11 @@ export class Deadkey { this.p = pos; this.d = id; this.o = Deadkey.ordinalSeed++; + this.matched = 0; } - match(p: number, d: number): boolean { - const result:boolean = (this.p == p && this.d == d); + match(pos: number, deadkey: number): boolean { + const result:boolean = (this.p == pos && this.d == deadkey); return result; } @@ -39,7 +40,7 @@ export class Deadkey { } equal(other: Deadkey) { - return this.d == other.d && this.p == other.d && this.o == other.o; + return this.d == other.d && this.p == other.p && this.o == other.o; } /** @@ -81,22 +82,25 @@ export class DeadkeyTracker { * Function isMatch * Scope Public * @param {number} caretPos current cursor position - * @param {number} n expected offset of deadkey from cursor - * @param {number} d deadkey - * @return {boolean} True if deadkey found selected context matches val + * @param {number} offset expected offset to add to deadkey position to get to caretPos + * @param {number} deadkey deadkey to match + * @return {boolean} True if deadkey found at offset position * Description Match deadkey at current cursor position */ - isMatch(caretPos: number, n: number, d: number): boolean { + isMatch(caretPos: number, offset: number, deadkey: number): boolean { if(this.dks.length == 0) { return false; // I3318 } - const sp=caretPos; - n = sp - n; + // Calculate position of deadkey from beginning of context. + // Caret position is always after the deadkey position (although this + // doesn't really matter for the calculation). + const dkPosition = caretPos - offset; + for(let i = 0; i < this.dks.length; i++) { // Don't re-match an already-matched deadkey. It's possible to have two identical // entries, and they should be kept separately. - if(this.dks[i].match(n, d) && !this.dks[i].matched) { + if (this.dks[i].match(dkPosition, deadkey) && !this.dks[i].matched) { this.dks[i].set(); // Assumption: since we match the first possible entry in the array, we // match the entry with the lower ordinal - the 'first' deadkey in the position. @@ -115,7 +119,11 @@ export class DeadkeyTracker { remove(dk: Deadkey) { const index = this.dks.indexOf(dk); - this.dks.splice(index, 1); + if (index > -1) { + this.dks.splice(index, 1); + } else { + console.warn(`Deadkey ${JSON.stringify(dk)} to remove not found in DeadkeyTracker.`); + } } clear() { @@ -160,17 +168,15 @@ export class DeadkeyTracker { return false; } - const otherDks = other.dks; - const matchedDks: Deadkey[] = []; - - for(const dk of this.dks) { - const match = otherDks.find((otherDk) => dk.equal(otherDk)); - if(!match) { + const sortedDeadkeys = this.toSortedArray(); + const otherDks = other.toSortedArray(); + for(let i = 0; i < sortedDeadkeys.length; i++) { + if(!sortedDeadkeys[i].equal(otherDks[i])) { return false; } } - return matchedDks.length == otherDks.length; + return true; } count(): number { diff --git a/web/src/engine/js-processor/src/index.ts b/web/src/engine/js-processor/src/index.ts index 1755499119..42ad8d94d1 100644 --- a/web/src/engine/js-processor/src/index.ts +++ b/web/src/engine/js-processor/src/index.ts @@ -9,3 +9,12 @@ export { default as OutputTarget } from "./outputTarget.js"; export * from "./outputTarget.js"; export { Mock } from "./mock.js"; export * from "./stringDivergence.js"; + +import { DeadkeyTracker } from './deadkeys.js'; + +/** + * these are exported only for unit tests, do not use + */ +export const unitTestEndpoints = { + DeadkeyTracker, +}; diff --git a/web/src/engine/js-processor/src/kbdInterface.ts b/web/src/engine/js-processor/src/kbdInterface.ts index ff4fffb467..1b4d7747c1 100644 --- a/web/src/engine/js-processor/src/kbdInterface.ts +++ b/web/src/engine/js-processor/src/kbdInterface.ts @@ -613,6 +613,8 @@ export default class KeyboardInterface extends KeyboardHarness { * @param {number} d deadkey * @return {boolean} True if deadkey found selected context matches val * Description Match deadkey at current cursor position + * + * Deprecated: use `fullContextMatch` instead. */ deadkeyMatch(n: number, outputTarget: OutputTarget, d: number): boolean { return outputTarget.hasDeadkeyMatch(n, d); diff --git a/web/src/engine/osk/gesture-processor/build.sh b/web/src/engine/osk/gesture-processor/build.sh index 53c50c3d39..77c5e3454e 100755 --- a/web/src/engine/osk/gesture-processor/build.sh +++ b/web/src/engine/osk/gesture-processor/build.sh @@ -60,6 +60,6 @@ function do_test_tools() { builder_run_action configure do_configure builder_run_action clean rm -rf build/ builder_run_action build:module do_build_module -builder_run_action build:tools src/tools/build.sh build +builder_run_action build:tools builder_launch /web/src/engine/osk/gesture-processor/src/tools/build.sh build builder_run_action test:module test-headless-typescript "${SUBPROJECT_NAME}" builder_run_action test:tools do_test_tools diff --git a/web/src/engine/osk/gesture-processor/package.json b/web/src/engine/osk/gesture-processor/package.json index 8aad89f94c..c74ff5a3de 100644 --- a/web/src/engine/osk/gesture-processor/package.json +++ b/web/src/engine/osk/gesture-processor/package.json @@ -3,7 +3,6 @@ "description": "The core gesture-recognition engine used by Keyman's Web-based OSKs.", "devDependencies": { "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10", "typescript": "^5.4.5" }, diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 2774885e04..000b491bf6 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -797,6 +797,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke if (this.usesFixedWidthScaling) { let baseWidth = this.width; baseWidth -= this._borderWidth * 2; + baseWidth = (isNaN(baseWidth) || baseWidth < 0) ? 0 : baseWidth; return ParsedLengthStyle.inPixels(baseWidth); } else { return ParsedLengthStyle.forScalar(1); @@ -821,7 +822,9 @@ export default class VisualKeyboard extends EventEmitter implements Ke // Touch OSKs may apply internal padding to prevent row cropping at the edges. // ... why not precompute both, rather than recalculate each time? // - appears to contribute to layout reflow costs on layer swaps! - return ParsedLengthStyle.inPixels(this.layoutHeight.val - this._borderWidth * 2 - this.layerGroup.verticalPadding); + let baseHeight = this.layoutHeight.val - this._borderWidth * 2 - this.layerGroup.verticalPadding; + baseHeight = (isNaN(baseHeight) || baseHeight < 0) ? 0 : baseHeight; + return ParsedLengthStyle.inPixels(baseHeight); } else { return ParsedLengthStyle.forScalar(1); } diff --git a/web/src/engine/predictive-text/templates/package.json b/web/src/engine/predictive-text/templates/package.json index fd80f2d43e..cbe3fb8747 100644 --- a/web/src/engine/predictive-text/templates/package.json +++ b/web/src/engine/predictive-text/templates/package.json @@ -55,7 +55,6 @@ "@keymanapp/web-utils": "*", "@types/mocha": "^7.0.2", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "dependencies": { diff --git a/web/src/engine/predictive-text/templates/src/trie-model.ts b/web/src/engine/predictive-text/templates/src/trie-model.ts index d22dc91e58..938553330d 100644 --- a/web/src/engine/predictive-text/templates/src/trie-model.ts +++ b/web/src/engine/predictive-text/templates/src/trie-model.ts @@ -84,23 +84,23 @@ export interface TrieModelOptions { punctuation?: LexicalModelPunctuation; } -class Traversal implements LexiconTraversal { +export class Traversal implements LexiconTraversal { /** * The lexical prefix corresponding to the current traversal state. */ - prefix: String; + readonly prefix: string; /** * The current traversal node. Serves as the 'root' of its own sub-Trie, * and we cannot navigate back to its parent. */ - root: Node; + readonly root: Node; /** * The max weight for the Trie being 'traversed'. Needed for probability * calculations. */ - totalWeight: number; + readonly totalWeight: number; constructor(root: Node, prefix: string, totalWeight: number) { this.root = root; @@ -108,7 +108,7 @@ class Traversal implements LexiconTraversal { this.totalWeight = totalWeight; } - child(char: string): LexiconTraversal | undefined { + child(char: string): Traversal | undefined { // May result for blank tokens resulting immediately after whitespace. if(char == '') { return this; @@ -153,7 +153,7 @@ class Traversal implements LexiconTraversal { } } - *children(): Generator<{char: string, traversal: () => LexiconTraversal}> { + *children(): Generator<{char: string, traversal: () => Traversal}> { let root = this.root; // We refer to the field multiple times in this method, and it doesn't change. @@ -352,7 +352,7 @@ export default class TrieModel implements LexicalModel { return this.breakWords; } - public traverseFromRoot(): LexiconTraversal { + public traverseFromRoot(): Traversal { return this._trie.traverseFromRoot(); } }; @@ -448,7 +448,7 @@ class Trie { this.totalWeight = totalWeight; } - public traverseFromRoot(): LexiconTraversal { + public traverseFromRoot(): Traversal { return new Traversal(this.root, '', this.totalWeight); } diff --git a/web/src/engine/predictive-text/wordbreakers/build.sh b/web/src/engine/predictive-text/wordbreakers/build.sh index 21617a89ed..8deb49493e 100755 --- a/web/src/engine/predictive-text/wordbreakers/build.sh +++ b/web/src/engine/predictive-text/wordbreakers/build.sh @@ -48,13 +48,19 @@ function do_build() { } function do_test() { - local FLAGS= + local FLAGS=() - if builder_is_ci_build; then - FLAGS="-reporter mocha-teamcity-reporter" + if builder_is_running_on_teamcity; then + FLAGS+=(--reporter "${KEYMAN_ROOT}/common/test/resources/mocha-teamcity-reporter/teamcity.cjs" --reporter-options parentFlowId="unit_tests") + echo "##teamcity[flowStarted flowId='unit_tests']" fi - c8 mocha ${FLAGS} tests + c8 mocha "${FLAGS[@]}" tests + + if builder_is_running_on_teamcity; then + # we're running in TeamCity + echo "##teamcity[flowFinished flowId='unit_tests']" + fi } builder_run_action configure do_configure diff --git a/web/src/engine/predictive-text/wordbreakers/package.json b/web/src/engine/predictive-text/wordbreakers/package.json index c3eebde3be..0f73f19f69 100644 --- a/web/src/engine/predictive-text/wordbreakers/package.json +++ b/web/src/engine/predictive-text/wordbreakers/package.json @@ -58,7 +58,6 @@ "@keymanapp/resources-gosh": "*", "@types/mocha": "^7.0.2", "c8": "^7.12.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "type": "module" diff --git a/web/src/engine/predictive-text/worker-main/package.json b/web/src/engine/predictive-text/worker-main/package.json index 2ef91e27f7..535532d680 100644 --- a/web/src/engine/predictive-text/worker-main/package.json +++ b/web/src/engine/predictive-text/worker-main/package.json @@ -44,7 +44,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/resources-gosh": "*", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "dependencies": { diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/test.sh b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh index 4bf6fe8030..96176c7c0b 100755 --- a/web/src/engine/predictive-text/worker-main/unit_tests/test.sh +++ b/web/src/engine/predictive-text/worker-main/unit_tests/test.sh @@ -25,24 +25,23 @@ builder_describe "Runs all tests for the language-modeling / predictive-text lay builder_parse "$@" -if builder_start_action configure; then - node_select_version_and_npm_ci - builder_finish_action success configure -fi +function do_test_headless() { + MOCHA_FLAGS=(${FLAGS}) -if builder_start_action test:headless; then - MOCHA_FLAGS=$FLAGS - - if builder_is_ci_build; then - MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter" + if builder_is_running_on_teamcity; then + MOCHA_FLAGS+=(--reporter "${KEYMAN_ROOT}/common/test/resources/mocha-teamcity-reporter/teamcity.cjs" --reporter-options parentFlowId="unit_tests") + echo "##teamcity[flowStarted flowId='unit_tests']" fi - mocha --recursive $MOCHA_FLAGS ./headless/*.js ./headless/**/*.js + mocha --recursive "${MOCHA_FLAGS[@]}" ./headless/*.js ./headless/**/*.js - builder_finish_action success test:headless -fi + if builder_is_running_on_teamcity; then + # we're running in TeamCity + echo "##teamcity[flowFinished flowId='unit_tests']" + fi +} -if builder_start_action test:browser; then +function do_test_browser() { WTR_CONFIG= WTR_INSPECT= @@ -55,6 +54,8 @@ if builder_start_action test:browser; then fi web-test-runner --config in_browser/web-test-runner${WTR_CONFIG}.config.mjs ${WTR_INSPECT} +} - builder_finish_action success test:browser -fi +builder_run_action configure node_select_version_and_npm_ci +builder_run_action test:headless do_test_headless +builder_run_action test:browser do_test_browser diff --git a/web/src/engine/predictive-text/worker-thread/package.json b/web/src/engine/predictive-text/worker-thread/package.json index f210f51000..f0807d074e 100644 --- a/web/src/engine/predictive-text/worker-thread/package.json +++ b/web/src/engine/predictive-text/worker-thread/package.json @@ -33,7 +33,6 @@ "@keymanapp/resources-gosh": "*", "c8": "^7.12.0", "combine-source-map": "^0.8.0", - "mocha-teamcity-reporter": "^4.0.0", "typescript": "^5.4.5" }, "dependencies": { diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts index 33b8a64783..5597cb50ce 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts @@ -2,13 +2,6 @@ import { SENTINEL_CODE_UNIT } from '@keymanapp/models-templates'; export type EditOperation = 'insert' | 'delete' | 'match' | 'substitute' | 'transpose-start' | 'transpose-end' | 'transpose-insert' | 'transpose-delete'; -/** - * Represents the lowest-level unit for comparison during edit-distance calculations. - */ -export interface EditToken { - key: TUnit; -} - // A semi-optimized 'online'/iterative Damerau-Levenshtein calculator with the following features: // - may add new character to the 'input' string or to the 'match' string, reusing all old calculations efficiently. // - allows a 'focused' evaluation that seeks if the edit distance is within a specific range. Designed for use in match-searching, @@ -23,7 +16,7 @@ export interface EditToken { // // Reference: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm#Possible_modifications // - Motivating statement: "if we are only interested in the distance if it is smaller than a threshold..." -export class ClassicalDistanceCalculation = EditToken, TMatch extends EditToken = EditToken> { +export class ClassicalDistanceCalculation { /** * Stores ONLY the computed diagonal elements, nothing else. * @@ -57,12 +50,12 @@ export class ClassicalDistanceCalculation); - constructor(other?: ClassicalDistanceCalculation) { + constructor(other: ClassicalDistanceCalculation); + constructor(other?: ClassicalDistanceCalculation) { if(other) { // Clone class properties. let rowCount = other.resolvedDistances.length; @@ -118,7 +111,7 @@ export class ClassicalDistanceCalculation; + let buffer = this as ClassicalDistanceCalculation; let val = buffer.getHeuristicFinalCost(); while(val > buffer.diagonalWidth) { @@ -145,7 +138,7 @@ export class ClassicalDistanceCalculation; + let buffer = this as ClassicalDistanceCalculation; let val = buffer.getHeuristicFinalCost(); let guaranteedBound = this.diagonalWidth; @@ -239,21 +232,21 @@ export class ClassicalDistanceCalculation, TMatch extends EditToken>( - buffer: ClassicalDistanceCalculation, + private static getTransposeParent( + buffer: ClassicalDistanceCalculation, r: number, c: number ): [number, number] { // Block any transpositions where the tokens are identical. // Other operations will be cheaper. Also, block cases where 'parents' are impossible. - if(r < 0 || c < 0 || buffer.inputSequence[r].key == buffer.matchSequence[c].key) { + if(r < 0 || c < 0 || buffer.inputSequence[r] == buffer.matchSequence[c]) { return [-1, -1]; } // Transposition checks let lastInputIndex = -1; for(let i = r-1; i >= 0; i--) { - if(buffer.inputSequence[i].key == buffer.matchSequence[c].key) { + if(buffer.inputSequence[i] == buffer.matchSequence[c]) { lastInputIndex = i; break; } @@ -261,7 +254,7 @@ export class ClassicalDistanceCalculation= 0; i--) { - if(buffer.matchSequence[i].key == buffer.inputSequence[r].key) { + if(buffer.matchSequence[i] == buffer.inputSequence[r]) { lastMatchIndex = i; break; } @@ -270,13 +263,13 @@ export class ClassicalDistanceCalculation, TMatch extends EditToken>( - buffer: ClassicalDistanceCalculation, + private static initialCostAt( + buffer: ClassicalDistanceCalculation, r: number, c: number, insertCost?: number, deleteCost?: number) { - var baseSubstitutionCost = buffer.inputSequence[r].key == buffer.matchSequence[c].key ? 0 : 1; + var baseSubstitutionCost = buffer.inputSequence[r] == buffer.matchSequence[c] ? 0 : 1; var substitutionCost: number = buffer.getCostAt(r-1, c-1) + baseSubstitutionCost; var insertionCost: number = insertCost || buffer.getCostAt(r, c-1) + 1; // If set meaningfully, will never equal zero. var deletionCost: number = deleteCost || buffer.getCostAt(r-1, c) + 1; // If set meaningfully, will never equal zero. @@ -290,7 +283,7 @@ export class ClassicalDistanceCalculation { + getSubset(inputLength: number, matchLength: number): ClassicalDistanceCalculation { let trimmedInstance = new ClassicalDistanceCalculation(this); if(inputLength > this.inputSequence.length || matchLength > this.matchSequence.length) { @@ -336,7 +329,7 @@ export class ClassicalDistanceCalculation { + addInputChar(token: TUnit): ClassicalDistanceCalculation { let returnBuffer = new ClassicalDistanceCalculation(this); let r = returnBuffer.inputSequence.length; @@ -359,7 +352,7 @@ export class ClassicalDistanceCalculation { + addMatchChar(token: TUnit): ClassicalDistanceCalculation { let returnBuffer = new ClassicalDistanceCalculation(this); let c = returnBuffer.matchSequence.length; @@ -379,7 +372,7 @@ export class ClassicalDistanceCalculation { + public increaseMaxDistance(): ClassicalDistanceCalculation { let returnBuffer = new ClassicalDistanceCalculation(this); returnBuffer.diagonalWidth++; @@ -388,7 +381,7 @@ export class ClassicalDistanceCalculation[], closure: (axisIndex: number, diagIndex: number) => void) { + function forPossibleTranspositionsInDiagonal(startPos: number, fixedChar: TUnit, lookupString: TUnit[], closure: (axisIndex: number, diagIndex: number) => void) { let diagonalCap = 2 * (returnBuffer.diagonalWidth - 1); // The maximum diagonal index permitted let axisCap = lookupString.length - 1; // The maximum index supported by the axis of iteration @@ -397,7 +390,7 @@ export class ClassicalDistanceCalculation c+2): new transposition source let transposeRow = r+2; if(r+2 < this.inputSequence.length) { // Row to check for transposes must exist. - let rowChar = returnBuffer.inputSequence[r+1].key; + let rowChar = returnBuffer.inputSequence[r+1]; // First possible match in input could be at index c + 2, which adjusts col c+2's cost. Except that entry in r+2 // doesn't exist yet - so we start with c+3 instead. forPossibleTranspositionsInDiagonal(c + 3, rowChar, returnBuffer.matchSequence, function(axisIndex, diagIndex) { @@ -461,7 +454,7 @@ export class ClassicalDistanceCalculation r+2, c+2): new transposition source let transposeCol = c+2; if(c+2 < this.matchSequence.length) { // Row to check for transposes must exist. - let colChar = returnBuffer.matchSequence[r+1].key; + let colChar = returnBuffer.matchSequence[r+1]; // First possible match in input could be at index r + 2, which adjusts row r+2's cost. Except that entry in c+2 // doesn't exist yet - so we start with r+3 instead. forPossibleTranspositionsInDiagonal(r+3, colChar, returnBuffer.inputSequence, function(axisIndex, diagIndex) { @@ -480,8 +473,8 @@ export class ClassicalDistanceCalculation, TMatch extends EditToken>( - buffer: ClassicalDistanceCalculation, + private static propagateUpdateFrom( + buffer: ClassicalDistanceCalculation, r: number, c: number, value: number, @@ -512,13 +505,13 @@ export class ClassicalDistanceCalculation value.key).join(''); - let matchString = this.matchSequence.map((value) => value.key).join(''); + let inputString = this.inputSequence.join(''); + let matchString = this.matchSequence.join(''); return inputString + SENTINEL_CODE_UNIT + matchString + SENTINEL_CODE_UNIT + this.diagonalWidth; } - get lastInputEntry(): TInput { + get lastInputEntry(): TUnit { return this.inputSequence[this.inputSequence.length-1]; } - get lastMatchEntry(): TMatch { + get lastMatchEntry(): TUnit { return this.matchSequence[this.matchSequence.length-1]; } - static computeDistance, TMatch extends EditToken>( - input: TInput[], - match: TMatch[], + static computeDistance( + input: TUnit[], + match: TUnit[], bandSize: number = 1) { // Initialize the calculation buffer, setting the diagonal width (as appropriate) in advance. - let buffer = new ClassicalDistanceCalculation(); + let buffer = new ClassicalDistanceCalculation(); bandSize = bandSize || 1; buffer.diagonalWidth = bandSize; diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts index 375e414b1b..d9639a6afd 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts @@ -356,8 +356,8 @@ export class ContextTracker extends CircularArray { // Inverted order, since 'match' existed before our new context. let mapping = ClassicalDistanceCalculation.computeDistance( - matchContext.map(value => ({key: value})), - tokenizedContext.map(value => ({key: value.text})), + matchContext, + tokenizedContext.map(value => value.text), // Must be at least 2, as adding a single whitespace after a token tends // to add two tokens: one for whitespace, one for the empty token to // follow it. diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts index 6404d2eb8f..03b5500d7e 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/distance-modeler.ts @@ -1,10 +1,11 @@ -import { isHighSurrogate, SENTINEL_CODE_UNIT } from '@keymanapp/models-templates'; +import { SENTINEL_CODE_UNIT } from '@keymanapp/models-templates'; import { QueueComparator as Comparator, PriorityQueue } from '@keymanapp/web-utils'; import { LexicalModelTypes } from '@keymanapp/common-types'; -import { ClassicalDistanceCalculation, EditToken } from './classical-calculation.js'; +import { ClassicalDistanceCalculation } from './classical-calculation.js'; import { ExecutionTimer, STANDARD_TIME_BETWEEN_DEFERS } from './execution-timer.js'; +import { subsetByChar, subsetByInterval, mergeSubset, TransformSubset } from '../transform-subsets.js'; import Distribution = LexicalModelTypes.Distribution; import LexicalModel = LexicalModelTypes.LexicalModel; @@ -29,30 +30,98 @@ enum TimedTaskTypes { CORRECTING = 2 } -// Represents a processed node for the correction-search's search-space's tree-like graph. May represent -// internal and 'leaf' nodes on said graph, as well as the overall root of the search. Also used to represent -// edges on the graph TO said nodes - there's a bit of overloading here. Either way, it stores the cost of the -// optimum path used to reach the ndoe. -// -// The stored path cost may be an overestimate when the edit distance is greater than the current search threshold. The -// first version of the node to be dequeued from SearchSpace's priority queue hierarchy 'wins' and is taken as the absolute -// minimum; subsequent versions are ignored as suboptimal. -// -// Provides functions usable to enumerate across the node's outward edges to new nodes for continued search. -// Most of the actual calculations occur as part of this process. -// -// For nodes with raw edit-distance cost within the current threshold for correction searches, we do have admissibility. -// If not enough nodes are available within that threshold, however, admissibility may be lost, leaving our search as a -// heuristic. -// -export class SearchNode { - calculation: ClassicalDistanceCalculation, TraversableToken>; +/** + * This type models a partially-processed subset of Transforms to be processed + * as a batch due to sharing similar properties. + */ +export interface PartialSearchEdge { + /** + * A set of `Transform`s all sharing matching, batchable properties for the full portion + * that has been processed, including the portions of the `.insert` property preceding + * `subsetSubindex` but not those after. + */ + transformSubset: TransformSubset; - currentTraversal: LexiconTraversal; - toKey: (wordform: string) => string = str => str; + /** + * Indicates the depth in the insert string of the most recently added input + * that should next be incorporated into the search path. + */ + subsetSubindex: number; + + /** + * Indicates whether the subset is for a substitution/match edge pattern (true) or + * a deletion pattern (false) of the dynamic search-graph construction. + */ + doSubsetMatching: boolean; +} + +// Represents a processed node for the correction-search's search-space's +// tree-like graph. May represent internal and 'leaf' nodes on said graph, as +// well as the overall root of the search. Also used to represent edges on the +// graph TO said nodes - there's a bit of overloading here. Either way, it +// stores the cost of the optimum path used to reach the node. +// +// For cases where incoming transforms have multiple inserted characters, this +// class can step through the characters, one at a time, in an +// efficiently-batched manner. Also of note: this class will use the "sentinel" +// character in the calculation input sequence for any path where the input does +// not match the corresponding character from the lexicon, even for deletions - +// batching _mismatches_ efficiently. +// +// The stored path cost may be an overestimate when the edit distance is greater +// than the current search threshold. The first version of the node to be +// dequeued from SearchSpace's priority queue hierarchy 'wins' and is taken as +// the absolute minimum; subsequent versions are ignored as suboptimal. +// +// Provides functions usable to enumerate across the node's outward edges to new +// nodes for continued search. Most of the actual calculations occur as part of +// this process. +// +// For nodes with raw edit-distance cost within the current threshold for +// correction searches, we do have admissibility. If not enough nodes are +// available within that threshold, however, admissibility may be lost, leaving +// our search as a heuristic. + +/** + * Represents a step in a correction-search path used to match potential input sequences + * against entries in the active LexicalModel's lexicon and the functionality used to take + * more steps until valid search endpoints are reached. + */ +export class SearchNode { + /** + * The search-term keying method used by the active LexicalModel + * @param str + * @returns + */ + readonly toKey: (wordform: string) => string = str => str; + + /** + * Calculations used to determine the edit-distance required for the path represented by + * this SearchNode instance. + */ + calculation: ClassicalDistanceCalculation; + + /** + * The Traversals (2d lexicon iterator) representing each prior step into the + * lexicon for the prefix currently represented by this SearchNode instance's + * represented search path. + */ + matchedTraversals: LexiconTraversal[]; + + /** + * The actual Transform input sequence being considered as a potential correction. + */ priorInput: RealizedInput; - // Internal lazy-cache for .inputSamplingCost, as it's a bit expensive to re-compute. + /** + * When defined, indicates that this instance models a set of transforms that have not + * yet been fully input into the search path. + */ + private partialEdge?: PartialSearchEdge; + + /** + * Internal lazy-cache for .inputSamplingCost; it's a bit expensive to re-compute. + */ private _inputCost?: number; constructor(rootTraversal: LexiconTraversal, toKey?: (arg0: string) => string); @@ -61,16 +130,21 @@ export class SearchNode { toKey = toKey || (x => x); if(rootTraversal instanceof SearchNode) { - let priorNode = rootTraversal; - this.calculation = priorNode.calculation; - this.currentTraversal = priorNode.currentTraversal; - this.priorInput = priorNode.priorInput; - this.toKey = priorNode.toKey; + const priorNode = rootTraversal; + + Object.assign(this, priorNode); + if(this.partialEdge) { + this.partialEdge = Object.assign({}, this.partialEdge); + } + this.priorInput = priorNode.priorInput.slice(0); + this.matchedTraversals = priorNode.matchedTraversals.slice(); + // Do NOT copy over _inputCost; this is a helper-constructor for methods // building new nodes... which will have a different cost. + delete this._inputCost; } else { this.calculation = new ClassicalDistanceCalculation(); - this.currentTraversal = rootTraversal; + this.matchedTraversals = [rootTraversal]; this.priorInput = []; this.toKey = toKey; } @@ -81,10 +155,27 @@ export class SearchNode { * by the correction-search in order to match the input with the lexical path represented * by the current node. */ - get knownCost(): number { + get editCount(): number { return this.calculation.getHeuristicFinalCost(); } + /** + * Indicates this search node has only processed _part_ of a recent input set; no new + * inputs should be received or processed while this returns `true`. + */ + get hasPartialInput(): boolean { + return !!this.partialEdge; + } + + /** + * The Traversal (2d lexicon iterator) representing the lexicon's contents for + * the prefix currently represented by this SearchNode instance's represented + * search path. + */ + get currentTraversal(): LexiconTraversal { + return this.matchedTraversals[this.matchedTraversals.length - 1]; + } + /** * Returns the effective "cost" for choosing the inputs leading to the current node. * The less likely the keystroke sequence, the higher the cost. @@ -99,6 +190,11 @@ export class SearchNode { // TODO: probably more efficient to instead use actual 'probability space'... but that'll involve extra changes. this._inputCost = this.priorInput.map(mass => mass.p > MIN_P ? mass.p : MIN_P).reduce((previous, current) => previous - Math.log(current), 0); + // For a partially-processed set, we do include the set's full modelled probability mass. + if(this.partialEdge) { + const mass = this.partialEdge.transformSubset.cumulativeMass; + this._inputCost -= Math.log(mass > MIN_P ? mass : MIN_P); + } return this._inputCost; } } @@ -122,33 +218,32 @@ export class SearchNode { // p = 1 / (e^4) = 0.01831563888. This still exceeds many neighboring keys! // p = 1 / (e^5) = 0.00673794699. Strikes a good balance. // Should easily give priority to neighboring keys before edit-distance kicks in (when keys are a bit ambiguous) - return SearchSpace.EDIT_DISTANCE_COST_SCALE * this.knownCost + this.inputSamplingCost; + return SearchSpace.EDIT_DISTANCE_COST_SCALE * this.editCount + this.inputSamplingCost; } /** - * Adds outbound paths from the current Node that model the insertion of a character - * not seen in the input, as if the user accidentally skipped typing it. No new input - * will be expected, but the search will continue one character deeper in the backing - * lexicon. - * @returns An array of SearchNodes corresponding to lexical entries that are prefixed - * with the lexicon entry represented by the current Node's matchSequence text. + * Adds outbound paths from the current Node that model the insertion of a + * character not seen in the input, as if the user accidentally skipped typing + * it. No new input will be expected, but the search will continue one + * character deeper in the backing lexicon. + * @returns An array of SearchNodes corresponding to lexical entries that are + * prefixed with the lexicon entry represented by the current Node's + * matchSequence text. */ buildInsertionEdges(): SearchNode[] { + if(this.hasPartialInput) { + throw new Error("Invalid state: will not take new input while still processing Transform subset"); + } + let edges: SearchNode[] = []; for(let lexicalChild of this.currentTraversal.children()) { - let traversal = lexicalChild.traversal(); - let matchToken = { - key: lexicalChild.char, - traversal: traversal - } - - let childCalc = this.calculation.addMatchChar(matchToken); + let childCalc = this.calculation.addMatchChar(lexicalChild.char); let searchChild = new SearchNode(this); searchChild.calculation = childCalc; searchChild.priorInput = this.priorInput; - searchChild.currentTraversal = traversal; + searchChild.matchedTraversals.push(lexicalChild.traversal()); edges.push(searchChild); } @@ -157,68 +252,235 @@ export class SearchNode { } /** - * Adds paths that delete the next Transform from the input to better match words from - * the lexicon. This aims to model (and ignore) when the user accidentally double-taps - * an input key. - * - * @returns An array of SearchNodes corresponding to search paths that skip the next - * input keystroke. + * This method is used while stepping through intermediate deletion 'edges' + * for transforms with multi-character insert strings. + * @returns */ - buildDeletionEdges(inputDistribution: Distribution): SearchNode[] { - let edges: SearchNode[] = []; + private processDeletionSubset(): SearchNode { + // We're on easy street: all transforms are already essentially merged into + // a single mass here. + let calculation = this.calculation.addInputChar(SENTINEL_CODE_UNIT); - /* - * If the probability of an input is less than the highest probability * the base edit-distance likelihood, - * don't build an edge for it; just rely on edits from the highest-probability edge. - * - * We may be able to be stricter, but this should be a decent start. - * - * Note: thanks to ModelCompositor.predict, we know the distribution is pre-sorted. - */ - for(let probMass of inputDistribution) { - if(probMass.p < inputDistribution[0].p * Math.exp(-SearchSpace.EDIT_DISTANCE_COST_SCALE)) { - // Again, we're pre-sorted. All further entries will be too low-cost to consider. - break; - } + // If on a 'delete' style path, we just build out the paths into a single + // merged path. + const node = new SearchNode(this); + node.calculation = calculation; + // no update to the match string or traversal to be found here... + node.partialEdge.subsetSubindex++; + return node; + } - let edgeCalc = this.calculation; - let transform = probMass.sample; - if(transform.deleteLeft) { - edgeCalc = edgeCalc.getSubset(edgeCalc.inputSequence.length - transform.deleteLeft, edgeCalc.matchSequence.length); - } + /** + * Finalizes the results of search nodes that represent the last step for + * processing multi-character insert transforms. + * @returns + */ + private tryFinalize() { + const subset = this.partialEdge?.transformSubset; + if(!subset || subset.key > this.partialEdge.subsetSubindex) { + // Not yet ready for finalization. Just exit. + return this; + } - // TODO: transform.deleteRight currently not supported. + // Finalization time! We can safely transition the result node out + // of 'subset' mode. + delete this.partialEdge; - let inputPath = this.priorInput.slice(0); - inputPath.push(probMass); - // Tokenize and iterate over input chars, adding them into the calc. - for(let i=0; i < transform.insert.length; i++) { - let char = transform.insert[i]; - if(isHighSurrogate(char)) { - i++; - char = char + transform.insert[i]; + // Whatever entries are in the subset, they actually resolve down to the + // same net edit, as specified here. It's more efficient to build the + // transform insert string on the subset, rather than mass-editing a group + // at each step and then consolidating them at the end. + this.priorInput.push({ + sample: { insert: subset.insert, deleteLeft: subset.entries[0]?.sample.deleteLeft ?? 0 }, + p: subset.cumulativeMass + }); + return this; + } + + /** + * For nodes modeling partially-processed inputs (partway through a + * multi-character insert Transform), this method will build the next step of + * the search path, iterating one character deeper within the + * partially-processed input. + * @returns + */ + processSubsetEdge(): SearchNode[] { + const partialEdge = this.partialEdge; + if(!partialEdge) { + throw new Error("Invalid state: not currently processing a Transform subset"); + } + + const startSubset = partialEdge.transformSubset; + const subIndex = partialEdge.subsetSubindex; + + // For raw backspaces - if no insert string, we can already finalize! + if(partialEdge.subsetSubindex >= partialEdge.transformSubset.key) { + return [this.tryFinalize()]; + } + + if(!partialEdge.doSubsetMatching) { + return [this.processDeletionSubset().tryFinalize()]; + } + + // After this, it's all substitution / matching. + const traversal = this.currentTraversal; + let nodesToReturn: SearchNode[] = []; + let keySet: Set = new Set(); + + const subsetMap = subsetByChar(startSubset, subIndex, this.toKey); + for(const [char, subset] of subsetMap.entries()) { + // build new node for the next char. + let calculation = this.calculation; + let childTraversal: LexiconTraversal; + if(char) { + childTraversal = traversal.child(char); + // These cases - where there's no match in the lexicon - are bundled + // after this for-loop. + // + // ... except for when there ARE no children at all. But, for those + // cases... a substitution would be invalid - we can't substitute for + // each char in the insert string, so abort. It's better matched by an + // 'insert' edge or a by different input from a sibling subset. + if(!childTraversal) { + continue; } - // In case of NFD input, filter out any empty-strings that may arise - // when 'keying' raw diacritics. - let keyedChar = this.toKey(char); - if(keyedChar) { - edgeCalc = edgeCalc.addInputChar({key: keyedChar}); - } + calculation = calculation.addInputChar(char); + calculation = calculation.addMatchChar(char); + } // else we COULD bundle these as a single subset... but it's likely not that important. + // after all, the cost ISN'T shifting, so we'll process 'em almost immediately and move on. + + keySet.add(char); + + const node = new SearchNode(this); + node.calculation = calculation; + node.partialEdge.subsetSubindex++; + // Append the newly-processed char to the subset's `insert` string. + node.partialEdge.transformSubset = {...subset, key: startSubset.key, insert: subset.insert + char}; + // '' inserts do not traverse deeper; do not add an empty traversal entry! + if(childTraversal) { + node.matchedTraversals.push(childTraversal); + } + nodesToReturn.push(node); + }; + + // These come at a notably higher cost (due to required edit) and are less likely to be processed. + // It's best to batch them so we only need a single in-memory node to represent them, rather than + // one node per transform / subset, which can scale very rapidly. + let calculation = this.calculation.addInputChar(SENTINEL_CODE_UNIT); + + for(const child of traversal.children()) { + const childTraversal = child.traversal(); + let childCalc = calculation.addMatchChar(child.char); + let childSubset: TransformSubset; + + if(keySet.has(child.char)) { + // OK, so we built a set that successfully matched this char. Other paths that + // emit a char after keying don't match it - we want to combine that path here. + // If there are multiple 'insert' chars, missing on one shouldn't force a 'miss' + // on the other subpaths that may follow this with valid char matches. + const nonMatchSubsets = [...subsetMap.values()].filter(c => { + // Ignore entries that 'key out' - there's no char left to act as a substitute. + // Also ignore the entry that actually did match - it's a match, not substitute. + return c.key != child.char && c.key != ''; + }); + childSubset = mergeSubset(nonMatchSubsets, startSubset.key); + } else { + // We didn't build any that match? Guess nothing matched, then. + // Clone so that our .insert tweak below does not have unintended effects. + childSubset = {...startSubset}; } - let childEdge = new SearchNode(this); - childEdge.calculation = edgeCalc; - childEdge.priorInput = inputPath; + // May happen in unit tests or when corrections are disabled. + if(!childSubset.entries.length) { + continue; + } - edges.push(childEdge); + // Append a match-failure marker for the non-matching char set onto the subset's + // `insert` string. + childSubset.insert += SENTINEL_CODE_UNIT; + + const node = new SearchNode(this); + node.calculation = childCalc; + node.matchedTraversals.push(childTraversal); + node.partialEdge.subsetSubindex++; + node.partialEdge.transformSubset = childSubset; + nodesToReturn.push(node); + } + + return nodesToReturn.map(n => n.tryFinalize()); + } + + /** + * Called by `buildDeletionEdges` and `buildSubstitutionEdges` to construct + * intermediate TransformSubset-based nodes that extend the search path one + * step into the incoming input transforms in an efficiently-batched manner. + * + * When an incoming character cannot match the next character for the node's + * represented lexicon prefix - be it due to not adding one (deletions) or + * due to not being the same character, all mismatching cases are merged into + * one, reducing the rate of expansion for the search graph. + * @param inputDistribution + * @param isSubstitution + * @returns + */ + private setupSubsetProcessing(inputDistribution: Distribution, isSubstitution: boolean ) { + if(this.hasPartialInput) { + throw new Error("Invalid state: will not take new input while still processing Transform subset"); + } + + const edges: SearchNode[] = []; + const subsets = subsetByInterval(inputDistribution); + + for(let dl = 0; dl < subsets.length; dl++) { + const dlSubset = subsets[dl]; + if(!dlSubset) { + continue; + } + + const calc = this.calculation; + const newMatchLength = Math.max(0, calc.matchSequence.length - dl); + const edgeCalc = calc.getSubset(calc.inputSequence.length - dl, newMatchLength); + + for(let ins = 0; ins < dlSubset.length; ins++) { + const insSubset = dlSubset[ins]; + if(!insSubset) { + continue; + } + + const node = new SearchNode(this); + node.calculation = edgeCalc; + node.partialEdge = { + doSubsetMatching: isSubstitution, + subsetSubindex: 0, + transformSubset: isSubstitution ? insSubset : { + ...insSubset, + entries: [ { sample: { insert: SENTINEL_CODE_UNIT.repeat(ins), deleteLeft: dl }, p: insSubset.cumulativeMass}] + } + } + // Get the traversal at the new end location. (Root is always at index 0.) + node.matchedTraversals = this.matchedTraversals.slice(0, newMatchLength+1); + + edges.push(node); + } } return edges; } - // While this may SEEM to be unnecessary, note that sometimes substitutions (which are computed - // via insert + delete) may be lower cost than both just-insert and just-delete. + /** + * Adds paths that consider the next Transform from the input without matching it to + * extended paths from the lexicon. This aims to model (and ignore) when the user + * accidentally double-taps an input key by not extending the match string alongside + * the input. + * + * @returns An array of SearchNodes corresponding to search paths that skip the next + * input keystroke. + */ + buildDeletionEdges(inputDistribution: Distribution): SearchNode[] { + return this.setupSubsetProcessing(inputDistribution, false); + } + /** * Adds paths that seek to: * 1. Match the next input transform in the sequence with matching prefixes @@ -229,30 +491,11 @@ export class SearchNode { * replace the next currently-unprocessed input. */ buildSubstitutionEdges(inputDistribution: Distribution): SearchNode[] { - // Handles the 'input' component. - let intermediateEdges = this.buildDeletionEdges(inputDistribution); - let edges: SearchNode[] = []; - - for(let lexicalChild of this.currentTraversal.children()) { - for(let edge of intermediateEdges) { - let traversal = lexicalChild.traversal(); - let matchToken = { - key: lexicalChild.char, - traversal: traversal - } - - let childCalc = edge.calculation.addMatchChar(matchToken); - - let searchChild = new SearchNode(this); - searchChild.calculation = childCalc; - searchChild.priorInput = edge.priorInput; - searchChild.currentTraversal = traversal; - - edges.push(searchChild); - } - } - - return edges; + // Note: due to the batching approach used via TransformSubsets, + // substitutions are _not_ adequately represented by one 'insertion' + one + // 'deletion' step. Explicit substitution / match-oriented processing is + // required. + return this.setupSubsetProcessing(inputDistribution, true); } /** @@ -264,11 +507,20 @@ export class SearchNode { * completed paths are possible, and so children can be re-built in such a case. */ get pathKey(): string { - let inputString = this.priorInput.map((value) => '+' + value.sample.insert + '-' + value.sample.deleteLeft).join(''); - let matchString = this.calculation.matchSequence.map((value) => value.key).join(''); + // Note: deleteLefts apply before inserts, so order them that way. + let inputString = this.priorInput.map((value) => '-' + value.sample.deleteLeft + '+' + value.sample.insert).join(''); + const partialEdge = this.partialEdge; + // Make sure the subset progress contributes to the key! + if(partialEdge) { + const subset = partialEdge.transformSubset; + // We need a copy of at least one insert string in the subset here. Without that, all subsets + // at the same level of the search, against the same match, look identical - not cool! + inputString += `-${subset.entries[0].sample.deleteLeft}+<${subset.key},${partialEdge.subsetSubindex},${subset.entries[0].sample.insert}>`; + } + let matchString = this.calculation.matchSequence.join(''); // TODO: might should also track diagonalWidth. - return inputString + SENTINEL_CODE_UNIT + matchString; + return inputString + ' @@ ' + matchString; } /** @@ -278,7 +530,7 @@ export class SearchNode { get resultKey(): string { // Filter out any duplicated match sequences. The same match sequence may be reached via // different input sequences, after all. - return this.calculation.matchSequence.map(value => value.key).join(''); + return this.calculation.matchSequence.join(''); } /** @@ -288,10 +540,13 @@ export class SearchNode { */ get isFullReplacement(): boolean { // Logic exception: 0 cost, 0 length != a "replacement". - return this.knownCost && this.knownCost == this.priorInput.length; + return (!!this.editCount) && this.editCount == this.priorInput.length; } } +/** + * Categorizes Nodes by how many input Transforms (edges) deep they are within the search tree. + */ class SearchSpaceTier { correctionQueue: PriorityQueue; processed: SearchNode[] = []; @@ -339,7 +594,7 @@ export class SearchResult { } get matchSequence(): TraversableToken[] { - return this.resultNode.calculation.matchSequence; + return this.resultNode.calculation.matchSequence.map((char, i) => ({key: char, traversal: this.resultNode.matchedTraversals[i+1]})); }; get matchString(): string { @@ -354,7 +609,7 @@ export class SearchResult { * `totalCost`.) */ get knownCost(): number { - return this.resultNode.knownCost; + return this.resultNode.editCount; } /** @@ -497,7 +752,6 @@ export class SearchSpace { let index1 = space1.index; let index2 = space2.index; - let tierMinCost: number = 0; let sign = 1; if(index2 < index1) { @@ -508,15 +762,21 @@ export class SearchSpace { sign = -1; } - // Boost the cost of the lower tier by the minimum cost possible for the missing inputs between them. - // In essence, compare the nodes as if the lower tier had the most likely input appended for each such - // input missing at the lower tier. + // Boost the cost of the lower tier by the minimum cost possible for the + // missing inputs between them. In essence, compare the nodes as if the + // lower tier had the most likely input appended for each such input + // missing at the lower tier. // - // A 100% admissible heuristic to favor a deeper search, since the added cost is guaranteed if the path - // is traversed further. + // A 100% admissible heuristic to favor a deeper search assuming no + // deleteLefts follow as later inputs. The added cost is guaranteed if + // the path is traversed further - even with subset use. A subset that + // doesn't match the char instantly carries higher cost than this due to + // the edit distance, even if the bin has max probability. // - // Remember, tier index i's last used input was from input index i-1. - // As a result, i is the first needed input index, with index2 - 1 the last entry needed to match them. + // Remember, tier index i's last used input was from input index i-1. As a + // result, i is the first needed input index, with index2 - 1 the last + // entry needed to match them. + let tierMinCost: number = 0; for(let i=index1; i < index2; i++) { tierMinCost = tierMinCost + searchSpace.minInputCost[i]; } @@ -563,7 +823,11 @@ export class SearchSpace { let deletions = node.buildDeletionEdges(inputDistribution); let substitutions = node.buildSubstitutionEdges(inputDistribution); - return deletions.concat(substitutions); + const batch = deletions.concat(substitutions); + + // Skip the queue for the first pass; there will ALWAYS be at least one pass, + // and queue-enqueing does come with a cost. Avoid the unnecessary overhead. + return batch.flatMap(e => e.processSubsetEdge()); }); // Don't forget to reset the array; the contained nodes no longer reach the search's end. @@ -634,9 +898,9 @@ export class SearchSpace { // Forbid a raw edit-distance of greater than 2. // Note: .knownCost is not scaled, while its contribution to .currentCost _is_ scaled. let substitutionsOnly = false; - if(currentNode.knownCost > 2) { + if(currentNode.editCount > 2) { return unmatchedResult; - } else if(currentNode.knownCost == 2) { + } else if(currentNode.editCount == 2) { // Hard restriction: no further edits will be supported. This helps keep the search // more narrowly focused. substitutionsOnly = true; @@ -655,7 +919,17 @@ export class SearchSpace { return unmatchedResult; } - // Stage 2: build remaining edges + // Stage 2: process subset further OR build remaining edges + + if(currentNode.hasPartialInput) { + // Re-use the current queue; the number of total inputs considered still holds. + bestTier.correctionQueue.enqueueAll(currentNode.processSubsetEdge()); + this.selectionQueue.enqueue(bestTier); + return unmatchedResult; + } + + // OK, we fully crossed a graph edge and have landed on a transition point; + // time to build more edges / edge batches. // Always possible, as this does not require any new input. if(!substitutionsOnly) { @@ -685,12 +959,18 @@ export class SearchSpace { if(!substitutionsOnly) { deletionEdges = currentNode.buildDeletionEdges(this.inputSequence[inputIndex-1]); } - let substitutionEdges = currentNode.buildSubstitutionEdges(this.inputSequence[inputIndex-1]); + const substitutionEdges = currentNode.buildSubstitutionEdges(this.inputSequence[inputIndex-1]); + let batch = deletionEdges.concat(substitutionEdges); + + // Skip the queue for the first pass; there will ALWAYS be at least one pass, + // and queue-enqueing does come with a cost - avoid unnecessary overhead here. + batch = batch.flatMap(e => e.processSubsetEdge()); // Note: we're live-modifying the tier's cost here! The priority queue loses its guarantees as a result. - nextTier.correctionQueue.enqueueAll(deletionEdges.concat(substitutionEdges)); + nextTier.correctionQueue.enqueueAll(batch); // So, we simply rebuild the selection queue. + // Also re-adds `bestTier`, which we'd de-queued. this.selectionQueue = new PriorityQueue(this.QUEUE_SPACE_COMPARATOR, this.tierOrdering); // We didn't reach an end-node, so we just end the iteration and continue the search. diff --git a/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts b/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts index 51001ef69d..5a2b5c6c84 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts @@ -7,4 +7,5 @@ export { ModelCompositor } from './model-compositor.js'; export { tokenizeTransform, tokenizeTransformDistribution } from './correction/transform-tokenization.js'; export * from './predict-helpers.js'; export { default as TransformUtils } from './transformUtils.js' -export { default as LMLayerWorker } from './index.js' \ No newline at end of file +export { default as LMLayerWorker } from './index.js' +export * from './transform-subsets.js'; \ No newline at end of file diff --git a/web/src/engine/predictive-text/worker-thread/src/main/transform-subsets.ts b/web/src/engine/predictive-text/worker-thread/src/main/transform-subsets.ts new file mode 100644 index 0000000000..cf5dcaca57 --- /dev/null +++ b/web/src/engine/predictive-text/worker-thread/src/main/transform-subsets.ts @@ -0,0 +1,144 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by jahorton on 2025-08-21 + * + * This file defines a method used to 'catalog' transforms based on the area of + * the context that they impact, allowing groups of similar transforms to be + * handled as related groups more easily. + */ + +import { LexicalModelTypes } from '@keymanapp/common-types'; +import { KMWString } from '@keymanapp/web-utils'; + +import Distribution = LexicalModelTypes.Distribution; +import Transform = LexicalModelTypes.Transform; + +/** + * Represents a subset of Transforms, taken from the distribution of input + * Transform, where all members meet certain criteria that others from the + * source distribution do not. + * @typeParam Key - the type of the criteria uniquely keying this Transform + * subset. + */ +export interface TransformSubset { + /** + * The value of whatever property was used as a criteria when building this + * distinct subset. + */ + key: Key; + + /** + * All Transforms meeting the criteria defining this TransformSubset + */ + entries: Distribution; + + /** + * The summed probability mass for all Transforms in the subset + */ + cumulativeMass: number; + + /** + * The currently-processed and commonly-held portion of the `insert` field + * for all Transforms in this subset. + */ + insert: string; +} + +/** + * Places all Transforms in the distribution into distinct subsets where BOTH + * the following properties match for all entries in the same set: + * 1. `deleteLeft` + * 2. `insert.length` + * + * The subset instances are keyed by just `insert.length`, despite both serving + * as criteria for building the subsets. + * + * It also computes the sum total probability mass represented by the set. + * + * Array index: the deleteLeft value (which is always non-negative) Inner map + * index: the length of the insert string. + * @param distribution + */ +export function subsetByInterval( + distribution: Distribution +): TransformSubset[][] { + const returnRoot: TransformSubset[][] = []; + + distribution.forEach((entry) => { + const { sample, p } = entry; + + const dl = sample.deleteLeft; + const deleteBin = returnRoot[dl] ?? []; + + const ins = KMWString.length(sample.insert); + const subset: TransformSubset = deleteBin[ins] ?? { key: ins, insert: "", entries: [], cumulativeMass: 0 }; + + subset.entries.push(entry); + subset.cumulativeMass += p; + + deleteBin[ins] = subset; + returnRoot[dl] = deleteBin; + }); + + return returnRoot; +} + +/** + * Places all Transforms in the distribution into distinct subsets where the + * character at the specified index within their Transform matches. + * + * It also computes the sum total probability mass represented by the set. + * + * Map key: the character shared (at the specified subindex) by each Transform + * in the subset + * @param intervalSubset + * @param subIndex + */ +export function subsetByChar(intervalSubset: TransformSubset, subIndex: number, toKey?: (s: string) => string): Map> { + toKey = toKey ?? (x => x); + const returnRoot: Map> = new Map(); + + // If there the input catalog doesn't support the subindex, just return everything in + // one bulk catalog. + if(intervalSubset.key <= subIndex) { + const everything: TransformSubset = {...intervalSubset, key: ''}; + returnRoot.set('', everything); + return returnRoot; + } + + intervalSubset.entries.forEach((entry) => { + const { sample, p } = entry; + const char = toKey(KMWString.charAt(sample.insert, subIndex)); + + const subset: TransformSubset = returnRoot.get(char) ?? { key: char, insert: intervalSubset.insert, entries: [], cumulativeMass: 0 }; + + subset.entries.push(entry); + subset.cumulativeMass += p; + + returnRoot.set(char, subset); + }); + + return returnRoot; +} + +/** + * Merges two or more TransformCatalog instances into a single instance keyed by + * the value passed in as a parameter. + * @param subsets + * @param key + * @returns + */ +export function mergeSubset(subsets: TransformSubset[], key: K): TransformSubset { + return subsets.reduce((accum, current) => { + accum.entries = accum.entries.concat(current.entries); + accum.cumulativeMass += current.cumulativeMass; + return accum; + }, { + key: key, + // May occur during some unit tests (with mocked & limited fat-fingering) + insert: subsets[0]?.insert, + entries: [], + cumulativeMass: 0 + }); +} \ No newline at end of file diff --git a/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts new file mode 100644 index 0000000000..ecd6ed9523 --- /dev/null +++ b/web/src/test/auto/headless/engine/js-processor/deadkeyTracker.tests.ts @@ -0,0 +1,373 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ + +import { assert } from 'chai'; +import { Deadkey, unitTestEndpoints } from 'keyman/engine/js-processor'; + +const DeadkeyTracker = unitTestEndpoints.DeadkeyTracker; + +describe('DeadkeyTracker', function() { + it('can add deadkeys and return count', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.equal(tracker.dks.length, 3); + assert.equal(tracker.count(), 3); + }); + + it('sorts deadkeys by position, latest first', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + const sortedDeadkeys = tracker.toSortedArray(); + + assert.equal(sortedDeadkeys[0].p, 10); + assert.equal(sortedDeadkeys[1].p, 5); + assert.equal(sortedDeadkeys[2].p, 3); + }); + + it('can adjust positions', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.adjustPositions(4, 2); // adjust deadkeys after position 4 by +2 + + const sortedDeadkeys = tracker.toSortedArray(); + assert.equal(sortedDeadkeys[0].p, 12); // 10 + 2 + assert.equal(sortedDeadkeys[1].p, 7); // 5 + 2 + assert.equal(sortedDeadkeys[2].p, 3); // unchanged + }); + + describe('equal', function () { + it('treats cloned tracker as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + + assert.isTrue(tracker1.equal(tracker2)); + }); + + it('treats two trackers with identical deadkeys as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk2); + tracker1.add(dk3); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isTrue(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys create separately not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(3, 1)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(10, 3)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys in different order not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(10, 3)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(3, 1)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats two trackers with same deadkeys in different places not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(7, 2)); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(12, 3)); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(new Deadkey(10, 3)); + tracker2.add(new Deadkey(5, 2)); + tracker2.add(new Deadkey(3, 1)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats a tracker with additional deadkeys not as equal', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(3, 1)); + tracker1.add(new Deadkey(5, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + tracker2.add(new Deadkey(15, 4)); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats non-indentical trackers with duplicate deadkeys not as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk1); // add same again + tracker1.add(dk2); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isFalse(tracker1.equal(tracker2)); + }); + + it('treats indentical trackers with duplicate deadkeys as equal', function () { + const dk1 = new Deadkey(3, 1); + const dk2 = new Deadkey(5, 2); + const dk3 = new Deadkey(10, 3); + const tracker1 = new DeadkeyTracker(); + tracker1.add(dk1); + tracker1.add(dk1); // add same again + tracker1.add(dk2); + tracker1.add(dk3); + + const tracker2 = new DeadkeyTracker(); + tracker2.add(dk1); + tracker2.add(dk1); // add same again + tracker2.add(dk2); + tracker2.add(dk3); + + assert.isTrue(tracker1.equal(tracker2)); + }); + }); + + it('can clear deadkeys', function() { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.clear(); + + assert.equal(tracker.count(), 0); + }); + + describe('remove', function () { + it('can remove existing deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + const dk = new Deadkey(5, 2); + tracker.add(dk); + tracker.add(new Deadkey(10, 3)); + + tracker.remove(dk); + + assert.equal(tracker.count(), 2); + assert.equal(tracker.dks[0].p, 3); + assert.equal(tracker.dks[1].p, 10); + }); + + it('does not remove similar deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + tracker.remove(new Deadkey(5, 2)); + + assert.equal(tracker.count(), 3); + assert.equal(tracker.dks[0].p, 3); + assert.equal(tracker.dks[1].p, 5); + assert.equal(tracker.dks[2].p, 10); + }); + }); + + it('sorts deadkeys in cloned tracker', function () { + const tracker1 = new DeadkeyTracker(); + tracker1.add(new Deadkey(5, 1)); + tracker1.add(new Deadkey(3, 2)); + tracker1.add(new Deadkey(10, 3)); + + const tracker2 = tracker1.clone(); + + assert.equal(tracker2.count(), 3); + assert.equal(tracker2.dks[0].p, 10); + assert.equal(tracker2.dks[1].p, 5); + assert.equal(tracker2.dks[2].p, 3); + }); + + it('can delete matched deadkeys', function () { + const dk1 = new Deadkey(5, 1); + const dk2 = new Deadkey(3, 2); + const dk3 = new Deadkey(10, 3); + const tracker = new DeadkeyTracker(); + tracker.add(dk1); + tracker.add(dk2); + tracker.add(dk3); + dk2.set(); + dk3.set(); + + tracker.deleteMatched(); + + assert.equal(tracker.count(), 1); + assert.equal(tracker.dks[0].p, 5); + }); + + it('can reset matched deadkeys', function () { + const dk1 = new Deadkey(5, 1); + const dk2 = new Deadkey(3, 2); + const dk3 = new Deadkey(10, 3); + const tracker = new DeadkeyTracker(); + tracker.add(dk1); + tracker.add(dk2); + tracker.add(dk3); + dk2.set(); + dk3.set(); + + tracker.resetMatched(); + + assert.equal(tracker.count(), 3); + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + describe('isMatch', function () { + it('returns false without having any deadkeys', function () { + const tracker = new DeadkeyTracker(); + + assert.isFalse(tracker.isMatch(0, 1, 2)); + }); + + it('returns false for non-matching deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isFalse(tracker.isMatch(0, 88, 99)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns false if offset is wrong', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isFalse(tracker.isMatch(0, 3, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns true if offset is correct and we have multiple deadkeys with same id', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(7, 2, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 1); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('matches entry with lowest ordinal value', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('matches entry with lowest ordinal value if caret is after deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(5, 2, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns true for matching deadkey and sets matched flag on deadkey', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns true for matching deadkey if caret on dk', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + + assert.isTrue(tracker.isMatch(3, 0, 1)); + + assert.equal(tracker.dks[0].matched, 1); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + + it('returns false for matching deadkey if already matched and resets matched flag', function () { + const tracker = new DeadkeyTracker(); + tracker.add(new Deadkey(3, 1)); + tracker.add(new Deadkey(5, 2)); + tracker.add(new Deadkey(10, 3)); + tracker.dks[0].matched = 1; + tracker.dks[1].matched = 1; + tracker.dks[2].matched = 1; + + assert.isFalse(tracker.isMatch(0, -3, 1)); + + assert.equal(tracker.dks[0].matched, 0); + assert.equal(tracker.dks[1].matched, 0); + assert.equal(tracker.dks[2].matched, 0); + }); + }); +}); diff --git a/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts new file mode 100644 index 0000000000..991c4be98b --- /dev/null +++ b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts @@ -0,0 +1,241 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { assert } from 'chai'; +import sinon from 'sinon'; +// @ts-ignore // Type info unavailable; doing a npm-install for it breaks things much worse in other ways. +import { JSDOM } from 'jsdom'; + +import { VisualKeyboard } from 'keyman/engine/osk'; +import { ActiveLayout, Keyboard, KeyboardProperties } from 'keyman/engine/keyboard'; +import { StylesheetManager } from 'keyman/engine/dom-utils'; +import { DeviceSpec } from "@keymanapp/web-utils"; + +describe('VisualKeyboard', () => { + let dom: JSDOM; + let mockConfig: any; + + beforeEach(() => { + dom = new JSDOM('

Hello world

'); + global.document = dom.window.document; + global.getComputedStyle = dom.window.getComputedStyle; + + const mockDevice = { browser: DeviceSpec.Browser.Chrome, formFactor: DeviceSpec.FormFactor.Desktop, OS: DeviceSpec.OperatingSystem.Windows, touchable: false }; + const mockLayout = sinon.createStubInstance(ActiveLayout) as any; + + const keyboard = sinon.createStubInstance(Keyboard) as any; + keyboard.layout.returns(mockLayout); + sinon.stub(keyboard, 'isRTL').get(() => false); + sinon.stub(keyboard, 'id').get(() => 'test'); + sinon.stub(keyboard, 'name').get(() => 'Test Keyboard'); + + mockConfig = { + device: mockDevice, + hostDevice: mockDevice, + pathConfig: { fonts: '', resources: '' }, + keyboard: keyboard, + keyboardMetadata: sinon.createStubInstance(KeyboardProperties), + topContainer: document.createElement('div'), + styleSheetManager: sinon.createStubInstance(StylesheetManager), + }; + }); + + function createVisualKeyboard(fixedScaling: boolean = true): VisualKeyboard { + const vk = new VisualKeyboard(mockConfig); + vk.usesFixedHeightScaling = fixedScaling; + vk.usesFixedWidthScaling = fixedScaling; + vk['_borderWidth'] = 3; + return vk; + } + + describe('LayoutHeight', () => { + it('calculates LayoutHeight correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('calculates LayoutHeight correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100"; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('accepts small heights for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "5"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores non-number string for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100px"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores invalid string for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "invalid"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores undefined value for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = undefined; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores null value for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = null; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('calculates LayoutHeight correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = 100; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('calculates LayoutHeight correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = "100px"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores invalid borderWidth for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.layoutHeight.val, 0); + }); + }); + + describe('LayoutWidth', () => { + it('calculates LayoutWidth correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_width'] = 100; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('calculates LayoutWidth correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "100"; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('accepts small heights for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "5"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores non-number string for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "100px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores invalid string for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "invalid"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores undefined value for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = undefined; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores null value for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = null; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('calculates LayoutWidth correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_width'] = 100; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('calculates LayoutWidth correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_width'] = "100px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores invalid borderWidth for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + }); + + describe('InternalHeight', () => { + it('calculates InternalHeight correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + assert.equal(vk.internalHeight.val, 88); + }); + + it('calculates InternalHeight correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100"; + assert.equal(vk.internalHeight.val, 88); + }); + + it('accepts small heights for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "5"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores non-number string for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100px"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores invalid string for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "invalid"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores undefined value for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = undefined; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores null value for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = null; + assert.equal(vk.internalHeight.val, 0); + }); + + it('calculates InternalHeight correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = 100; + assert.equal(vk.internalHeight.val, 88); + }); + + it('calculates InternalHeight correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = "100px"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores invalid borderWidth for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.internalHeight.val, 0); + }); + + }); +}); diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts index 998a8b2ec7..61b2f7e7a7 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts @@ -32,28 +32,20 @@ function compute(input: string, match: string, mode?: string, bandSize?: number) switch(mode || "InputThenMatch") { case "InputThenMatch": for(let i = 0; i < input.length; i++) { - buffer = buffer.addInputChar({ - key: input.charAt(i) - }); + buffer = buffer.addInputChar(input.charAt(i)); } for(let j = 0; j < match.length; j++) { - buffer = buffer.addMatchChar({ - key: match.charAt(j) - }); + buffer = buffer.addMatchChar(match.charAt(j)); } break; case "MatchThenInput": for(let j = 0; j < match.length; j++) { - buffer = buffer.addMatchChar({ - key: match.charAt(j) - }); + buffer = buffer.addMatchChar(match.charAt(j)); } for(let i = 0; i < input.length; i++) { - buffer = buffer.addInputChar({ - key: input.charAt(i) - }); + buffer = buffer.addInputChar(input.charAt(i)); } break; default: @@ -165,6 +157,26 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { assert.equal(compute("aadddres", "address", "MatchThenInput").getFinalCost(), 3); }); + it("handles empty-char inputs / matches appropriately", () => { + let buffer = new ClassicalDistanceCalculation(); + + // One transpose ('' vs 'b'), one insert ('' at end) + const input = ['a', '', 'b', 'c', 'd']; + const match = ['a', 'b', '', 'c', 'd', '']; + + for(let i = 0; i < input.length; i++) { + buffer = buffer.addInputChar(input[i]); + } + + for(let j = 0; j < match.length; j++) { + buffer = buffer.addMatchChar(match[j]); + } + + assert.equal(buffer.getFinalCost(), 2); + const path = buffer.editPath(); + assert.includeMembers(path, ['transpose-start', 'transpose-end', 'insert']); + }); + describe("Intermediate cost tests", function() { it("'abc' -> 'cab' (width 1) = 2", function() { // Technically a heuristic here, but it gets the right value b/c all changes are on the diagonal. @@ -402,12 +414,12 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { let expectedInput = ['t', 'e', 'a']; for(let i=0; i < expectedInput.length; i++) { - assert.equal(trimmedBuffer.inputSequence[i].key, expectedInput[i]); + assert.equal(trimmedBuffer.inputSequence[i], expectedInput[i]); } let expectedMatch = ['t', 'h', 'e']; for(let i=0; i < expectedMatch.length; i++) { - assert.equal(trimmedBuffer.matchSequence[i].key, expectedMatch[i]); + assert.equal(trimmedBuffer.matchSequence[i], expectedMatch[i]); } }); diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts index deb8d03d16..da9ce9acef 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/distance-modeler.tests.ts @@ -1,12 +1,39 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by jahorton on 2020-08-27 + * + * This file defines tests for core structures and methods of the + * predictive-text correction-search engine. + */ + import { assert } from 'chai'; import { PriorityQueue } from '@keymanapp/web-utils'; import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs'; +import { LexicalModelTypes } from '@keymanapp/common-types'; import { correction, models } from '@keymanapp/lm-worker/test-index'; +import SENTINEL_CODE_UNIT = models.SENTINEL_CODE_UNIT; +import Distribution = LexicalModelTypes.Distribution; +import SearchNode = correction.SearchNode; +import SearchResult = correction.SearchResult; +import Transform = LexicalModelTypes.Transform; import TrieModel = models.TrieModel; +// Exposes an extra property that's very useful for validating behaviors during unit testing: +// - `prefix`. +type TrieTraversal = ReturnType; + +const testModel = new TrieModel(jsonFixture('models/tries/english-1000')); +const toKey = (s: string) => testModel.toKey(s); + +/** + * The total number of top-level child paths in the lexicon. + */ +const FIRST_CHAR_VARIANTS = 24; + function buildTestTimer() { return new correction.ExecutionTimer(Number.MAX_VALUE, Number.MAX_VALUE); } @@ -20,27 +47,286 @@ function edgeHasChars(edge: correction.SearchNode, input: string, match: string) return false; } - return edge.calculation.lastMatchEntry.key == match; + return edge.calculation.lastMatchEntry == match; } -function findEdgeWithChars(edgeArray: correction.SearchNode[], input: string, match: string) { +function findEdgesWithChars(edgeArray: correction.SearchNode[], match: string) { let results = edgeArray.filter(function(value) { - return value.calculation.lastMatchEntry.key == match && value.priorInput[value.priorInput.length - 1].sample.insert == input; + return value.calculation.lastMatchEntry == match; }); - assert.equal(results.length, 1); - return results[0]; + assert.isAtLeast(results.length, 1); + return results; +} + +function fetchCommonTENode() { + const rootNode = new SearchNode(testModel.traverseFromRoot(), toKey); + + // Establish desired source node: prefix 'te'. + const firstLayerTransforms: Distribution = [{ + sample: { + insert: 't', + deleteLeft: 0 + }, + p: 0.75 + }, { + sample: { + insert: 'r', + deleteLeft: 0 + }, + p: 0.25 + }]; + const firstLayerNodes = rootNode + .buildSubstitutionEdges(firstLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + assert.isAbove(firstLayerNodes.length, FIRST_CHAR_VARIANTS); + firstLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const tNode = firstLayerNodes[0]; + assert.equal(tNode.resultKey, 't'); + assert.sameDeepMembers(tNode.priorInput, [firstLayerTransforms[0]]); + assert.isFalse(tNode.hasPartialInput); + + const secondLayerTransforms: Distribution = [{ + sample: { + insert: 'e', + deleteLeft: 0 + }, + p: 0.8 + }, { + sample: { + insert: 'r', + deleteLeft: 0 + }, + p: 0.2 + }]; + const secondLayerNodes = tNode + .buildSubstitutionEdges(secondLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + // The field narrows down at this point, but still has a decent number + // of variants (11). + assert.isAbove(secondLayerNodes.length, 10); + secondLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const teNode = secondLayerNodes[0]; + + assert.equal(teNode.resultKey, 'te'); + assert.equal(teNode.editCount, 0); + + return teNode; } describe('Correction Distance Modeler', function() { - describe('SearchNode + SearchEdge', function() { - var testModel: TrieModel; + describe('SearchNode', function() { + it('constructs a fresh instance from a traversal + keyingFunction', () => { + const rootNode = new SearchNode(testModel.traverseFromRoot(), toKey); + assert.equal(rootNode.resultKey, ''); - before(function() { - testModel = new TrieModel(jsonFixture('models/tries/english-1000')); + assert.equal(rootNode.editCount, 0); + assert.equal(rootNode.inputSamplingCost, 0); + assert.equal(rootNode.currentCost, 0); + + assert.equal((rootNode.currentTraversal as TrieTraversal).prefix, ''); + assert.isFalse(rootNode.hasPartialInput); + assert.isFalse(rootNode.isFullReplacement) + assert.isUndefined(rootNode.calculation.lastInputEntry); + assert.isUndefined(rootNode.calculation.lastMatchEntry); + assert.equal(rootNode.toKey, toKey); }); - it('SearchNode.buildInsertionEdges() - from root', function() { + describe('supports the cloning constructor pattern', () => { + it('properly deep-copies the root node', () => { + const originalNode = new SearchNode(testModel.traverseFromRoot(), toKey); + const clonedNode = new SearchNode(originalNode); + + // Root node properties; may as well re-assert 'em. + assert.equal(clonedNode.resultKey, ''); + + assert.equal(clonedNode.editCount, 0); + assert.equal(clonedNode.inputSamplingCost, 0); + assert.equal(clonedNode.currentCost, 0); + + assert.equal((clonedNode.currentTraversal as TrieTraversal).prefix, ''); + assert.isFalse(clonedNode.hasPartialInput); + assert.isFalse(clonedNode.isFullReplacement) + assert.isUndefined(clonedNode.calculation.lastInputEntry); + assert.isUndefined(clonedNode.calculation.lastMatchEntry); + assert.equal(clonedNode.toKey, toKey); + + // Avoid aliasing for properties holding mutable objects + assert.notEqual(clonedNode.priorInput, originalNode.priorInput); + + // Verify aliasing for properties holding immutable objects + assert.equal(clonedNode.calculation, originalNode.calculation); + assert.equal(clonedNode.currentTraversal, originalNode.currentTraversal); + }); + + it('properly deep-copies fully-processed nodes later in the search path', () => { + const rootNode = new SearchNode(testModel.traverseFromRoot(), toKey); + + // Establish desired source node: prefix 'te'. + const firstLayerTransforms: Distribution = [{ + sample: { + insert: 't', + deleteLeft: 0 + }, + p: 0.75 + }, { + sample: { + insert: 'r', + deleteLeft: 0 + }, + p: 0.25 + }]; + const firstLayerNodes = rootNode + .buildSubstitutionEdges(firstLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + assert.isAbove(firstLayerNodes.length, FIRST_CHAR_VARIANTS); + firstLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const tNode = firstLayerNodes[0]; + assert.equal(tNode.resultKey, 't'); + assert.sameDeepMembers(tNode.priorInput, [firstLayerTransforms[0]]); + assert.isFalse(tNode.hasPartialInput); + + const secondLayerTransforms: Distribution = [{ + sample: { + insert: 'e', + deleteLeft: 0 + }, + p: 0.8 + }, { + sample: { + insert: 'r', + deleteLeft: 0 + }, + p: 0.2 + }]; + const secondLayerNodes = tNode + .buildSubstitutionEdges(secondLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + assert.isAbove(secondLayerNodes.length, 10); + secondLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const teNode = secondLayerNodes[0]; + + // ***** + + function assertSourceNodeProps(node: SearchNode) { + assert.equal(node.resultKey, 'te'); + + assert.equal(node.editCount, 0); + assert.equal(node.inputSamplingCost, -Math.log(firstLayerTransforms[0].p) - Math.log(secondLayerTransforms[0].p)); + assert.equal(node.currentCost, node.inputSamplingCost); + + assert.isFalse(node.hasPartialInput); + assert.isFalse(node.isFullReplacement) + assert.equal(node.calculation.lastInputEntry, 'e'); + assert.equal(node.calculation.lastMatchEntry, 'e'); + assert.equal((node.currentTraversal as TrieTraversal).prefix, 'te'); + assert.equal(node.toKey, toKey); + } + + assertSourceNodeProps(teNode); + + const clonedNode = new SearchNode(teNode); + + // Root node properties; may as well re-assert 'em. + assertSourceNodeProps(clonedNode); + + // Avoid aliasing for properties holding mutable objects + assert.notEqual(clonedNode.priorInput, teNode.priorInput); + + // Verify aliasing for properties holding immutable objects + assert.equal(clonedNode.calculation, teNode.calculation); + assert.equal(clonedNode.currentTraversal, teNode.currentTraversal); + }); + + it('properly deep-copies partially-processed edges later in the search path', () => { + const rootNode = new SearchNode(testModel.traverseFromRoot(), toKey); + + // Establish desired source node: prefix 'te', with 'e' the first letter of + // a multi-char insert Transform. + const firstLayerTransforms: Distribution = [{ + sample: { + insert: 't', + deleteLeft: 0 + }, + p: 0.75 + }, { + sample: { + insert: 'r', + deleteLeft: 0 + }, + p: 0.25 + }]; + const firstLayerNodes = rootNode + .buildSubstitutionEdges(firstLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + assert.isAbove(firstLayerNodes.length, FIRST_CHAR_VARIANTS); + firstLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const tNode = firstLayerNodes[0]; + assert.equal(tNode.resultKey, 't'); + assert.sameDeepMembers(tNode.priorInput, [firstLayerTransforms[0]]); + assert.isFalse(tNode.hasPartialInput); + + const secondLayerTransforms: Distribution = [{ + sample: { + insert: 'ests', + deleteLeft: 0 + }, + p: 0.8 + }, { + sample: { + insert: 'rial', + deleteLeft: 0 + }, + p: 0.2 + }]; + const secondLayerNodes = tNode + .buildSubstitutionEdges(secondLayerTransforms) + .flatMap(node => node.processSubsetEdge()); + assert.isAbove(secondLayerNodes.length, 10); + secondLayerNodes.sort((a, b) => a.currentCost - b.currentCost); + + const teNode = secondLayerNodes[0]; + + // ***** + + function assertSourceNodeProps(node: SearchNode) { + assert.equal(node.resultKey, 'te'); + + assert.equal(node.editCount, 0); + assert.equal(node.inputSamplingCost, -Math.log(firstLayerTransforms[0].p) - Math.log(secondLayerTransforms[0].p)); + assert.equal(node.currentCost, node.inputSamplingCost); + + assert.isTrue(node.hasPartialInput); + assert.isFalse(node.isFullReplacement) + assert.equal(node.calculation.lastInputEntry, 'e'); + assert.equal(node.calculation.lastMatchEntry, 'e'); + assert.equal((node.currentTraversal as TrieTraversal).prefix, 'te'); + assert.equal(node.toKey, toKey); + } + + assertSourceNodeProps(teNode); + + const clonedNode = new SearchNode(teNode); + + // Root node properties; may as well re-assert 'em. + assertSourceNodeProps(clonedNode); + + // Avoid aliasing for properties holding mutable objects + assert.notEqual(clonedNode.priorInput, teNode.priorInput); + + // Verify aliasing for properties holding immutable objects + assert.equal(clonedNode.calculation, teNode.calculation); + assert.equal(clonedNode.currentTraversal, teNode.currentTraversal); + }); + }); + + // Consider adding more, deeper? + it('builds insertion edges based on lexicon, from root', function() { let rootTraversal = testModel.traverseFromRoot(); assert.isNotEmpty(rootTraversal); @@ -54,7 +340,7 @@ describe('Correction Distance Modeler', function() { for(let child of rootTraversal.children()) { expectedChildCount++; - let childEdge = edges.filter(value => value.calculation.lastMatchEntry.key == child.char)[0]; + let childEdge = edges.filter(value => value.calculation.lastMatchEntry == child.char)[0]; assert.isOk(childEdge); assert.isEmpty(childEdge.priorInput); assert.isEmpty(childEdge.calculation.inputSequence); @@ -64,104 +350,713 @@ describe('Correction Distance Modeler', function() { assert.equal(edges.length, expectedChildCount); }); - it('SearchNode.buildDeletionEdges() - from root', function() { - let rootTraversal = testModel.traverseFromRoot(); - assert.isNotEmpty(rootTraversal); - - let rootNode = new correction.SearchNode(rootTraversal); - assert.equal(rootNode.calculation.getHeuristicFinalCost(), 0); - - let synthDistribution = [ - {sample: {insert: 't', deleteLeft: 0}, p: 0.75}, // Transform, probability - {sample: {insert: 'h', deleteLeft: 0}, p: 0.25} + describe('buildDeletionEdges @ token root', () => { + const synthDistribution = [ + // Transform, probability + // insert 1, deleteLeft 0, p: 0.50 + {sample: {insert: 't', deleteLeft: 0}, p: 0.30}, + {sample: {insert: 'h', deleteLeft: 0}, p: 0.15}, + {sample: {insert: 'x', deleteLeft: 0}, p: 0.05}, + // insert 0 deleteLeft 1, p: 0.1 + {sample: {insert: '', deleteLeft: 1}, p: 0.1}, + // insert 2 deleteLeft 0, p: 0.15 + {sample: {insert: 'ch', deleteLeft: 0}, p: 0.15}, + // insert 2 deleteLeft 1, p: 0.25 + {sample: {insert: 'th', deleteLeft: 1}, p: 0.1}, + {sample: {insert: 'tr', deleteLeft: 1}, p: 0.15}, + // 4 distinct subsets. ]; - let edges = rootNode.buildDeletionEdges(synthDistribution); - assert.equal(edges.length, 2); + it('step 1: batches deletion edge(s) for input transforms', () => { + let rootTraversal = testModel.traverseFromRoot(); + assert.isNotEmpty(rootTraversal); - let highCost, lowCost; - for(let edge of edges) { - assert.isNotEmpty(edge.priorInput); - assert.isEmpty(edge.calculation.matchSequence); + let rootNode = new correction.SearchNode(rootTraversal); + assert.equal(rootNode.calculation.getHeuristicFinalCost(), 0); - if(edge.priorInput[0].p == 0.75) { - // 't'. - lowCost = edge.currentCost; // higher prob = lower cost. - } else if(edge.priorInput[0].p == 0.25) { - // 'h' - highCost = edge.currentCost; - } else { - assert.fail(); + const subsetNodes = rootNode.buildDeletionEdges(synthDistribution); + assert.equal(subsetNodes.length, 4); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x)) + // The known subs for the subsets defined above. + for(let i=0; i < expectedCosts.length; i++) { + assert.isTrue(subsetNodes[i].hasPartialInput); + // From root: the deleteLeft 1 entries have nothing to delete. + assert.equal((subsetNodes[i].currentTraversal as TrieTraversal).prefix, ''); + + // Allow a little value wiggle due to double-precision limitations. + assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8); + // No actual edit-tracking is done yet, so these should also match. + assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8); } - } + }); - assert.isAbove(highCost, lowCost); + it('step 2: first processing layer resolves zero + one char inserts', () => { + // From "step 1" above, assertions removed + let rootTraversal = testModel.traverseFromRoot(); + let rootNode = new correction.SearchNode(rootTraversal); + const subsetNodes = rootNode.buildDeletionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + const processedNodes = subsetNodes.flatMap(n => n.processSubsetEdge()); + // All delete-oriented Transform subsets condense down to a single + // transform (and edge) each. + assert.equal(processedNodes.length, 4); + + // Sorted index 0: 1 insert - should be processed already, in a single + // step. + assert.isFalse(processedNodes[0].hasPartialInput); + assert.equal(processedNodes[0].editCount, 1); + assert.equal(processedNodes[0].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[0].inputSamplingCost, subsetNodes[0].inputSamplingCost); + assert.isAbove(processedNodes[0].currentCost, subsetNodes[0].currentCost); + + // Sorted index 3: 0 insert - should be processed already, in a single + // step. + assert.isFalse(processedNodes[3].hasPartialInput); + // No insert string => no sentinel char to delete. + assert.equal(processedNodes[3].editCount, 0); + assert.isUndefined(processedNodes[3].calculation.lastInputEntry); + assert.equal(processedNodes[3].inputSamplingCost, subsetNodes[3].inputSamplingCost); + assert.equal(processedNodes[3].currentCost, subsetNodes[3].currentCost); + + // Sorted indices 2, 3: both had 2 inserts. + assert.isTrue(processedNodes[1].hasPartialInput); + assert.equal(processedNodes[1].editCount, 1); + assert.equal(processedNodes[1].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[1].inputSamplingCost, subsetNodes[1].inputSamplingCost); + assert.isAbove(processedNodes[1].currentCost, subsetNodes[1].currentCost); + + assert.isTrue(processedNodes[2].hasPartialInput); + assert.equal(processedNodes[2].editCount, 1); + assert.equal(processedNodes[2].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[2].inputSamplingCost, subsetNodes[2].inputSamplingCost); + assert.isAbove(processedNodes[2].currentCost, subsetNodes[2].currentCost); + }); + + it('step 3: second processing layer resolves two char inserts', () => { + // From "steps 0, 1" above, assertions removed + let rootTraversal = testModel.traverseFromRoot(); + let rootNode = new correction.SearchNode(rootTraversal); + const subsetNodes = rootNode.buildDeletionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + // Two nodes were unprocessed at the end of the last step; we handle + // them now and filter the others out. We need the indices here to be + // aligned. + const step2Nodes = subsetNodes.flatMap(n => n.processSubsetEdge()).filter(n => n.hasPartialInput); + const processedNodes = step2Nodes.flatMap(n => n.processSubsetEdge()); + + // All delete-oriented Transform subsets condense down to a single + // transform (and edge) each. + assert.equal(processedNodes.length, 2); + // All nodes are now done processing. + processedNodes.forEach((processedNode, index) => { + assert.isFalse(processedNode.hasPartialInput); + assert.isFalse(processedNode.hasPartialInput); + assert.equal(processedNode.editCount, 2); + assert.equal(processedNode.calculation.inputSequence.length, 2); + assert.equal(processedNode.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNode.inputSamplingCost, step2Nodes[index].inputSamplingCost); + assert.isAbove(processedNode.currentCost, step2Nodes[index].currentCost); + }); + }); }); - it('SearchNode.buildSubstitutionEdges() - from root', function() { - // The combinatorial effect here is a bit much to fully test. - let rootTraversal = testModel.traverseFromRoot(); - assert.isNotEmpty(rootTraversal); + describe(`buildDeletionEdges starting @ 'te' prefix`, () => { + // start prefix 'te' - let rootNode = new correction.SearchNode(rootTraversal); - assert.equal(rootNode.calculation.getHeuristicFinalCost(), 0); - - let synthDistribution = [ - {sample: {insert: 't', deleteLeft: 0}, p: 0.75}, // Transform, probability - {sample: {insert: 'h', deleteLeft: 0}, p: 0.25} + const synthDistribution = [ + // Transform, probability + // insert 1, deleteLeft 0, p: 0.50 + {sample: {insert: 'r', deleteLeft: 0}, p: 0.30}, // ter(m) + {sample: {insert: 'l', deleteLeft: 0}, p: 0.15}, // tel(l) + {sample: {insert: 'x', deleteLeft: 0}, p: 0.05}, // no entry available + // insert 0 deleteLeft 1, p: 0.1 + {sample: {insert: '', deleteLeft: 1}, p: 0.1}, // t (LOTS of followups) + // insert 2 deleteLeft 0, p: 0.15 + {sample: {insert: 'ch', deleteLeft: 0}, p: 0.15}, // tech(nical) + // insert 2 deleteLeft 1, p: 0.25 + {sample: {insert: 'ak', deleteLeft: 1}, p: 0.1}, // tak(e) + {sample: {insert: 'al', deleteLeft: 1}, p: 0.15}, // tal(k) + // 4 distinct subsets. ]; - let edges = rootNode.buildSubstitutionEdges(synthDistribution); - assert.isAbove(edges.length, 0); + it('step 1: batches deletion edge(s) for input transforms', () => { + const teNode = fetchCommonTENode(); + assert.equal(teNode.calculation.getHeuristicFinalCost(), 0); - let expectedChildCount = 0; - for(let mass of synthDistribution) { // probability may vary here - let highCost, lowCost; + const subsetNodes = teNode.buildDeletionEdges(synthDistribution); + assert.equal(subsetNodes.length, 4); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); - // Within here, cost only varies based on edit-distance changes, allowing - // us to make an assertion on the relationship between costs for each edit type. - for(let child of rootTraversal.children()) { - expectedChildCount++; - let matchingEdge = findEdgeWithChars(edges, mass.sample.insert, child.char); + const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x) + teNode.currentCost); + // The known subs for the subsets defined above. + for(let i=0; i < expectedCosts.length; i++) { + assert.isTrue(subsetNodes[i].hasPartialInput); + // From a 'te' prefix, the deleteLeft 1 entries do have something to delete. + assert.equal((subsetNodes[i].currentTraversal as TrieTraversal).prefix, (i == 0 || i == 2) ? 'te' : 't'); - // Substitution - matching char - if(mass.sample.insert == matchingEdge.calculation.lastMatchEntry.key) { - if(lowCost) { - assert.equal(matchingEdge.currentCost, lowCost); - } - lowCost = matchingEdge.currentCost; - } else { - // not matching char. - if(highCost) { - assert.equal(matchingEdge.currentCost, highCost); - } - highCost = matchingEdge.currentCost; - } + // Allow a little value wiggle due to double-precision limitations. + assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8); + // No actual edit-tracking is done yet, so these should also match. + assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8); } - assert.isAbove(highCost, lowCost); - } + }); - assert.equal(edges.length, expectedChildCount); + it('step 2: first processing layer resolves zero + one char inserts', () => { + // From "step 1" above, assertions removed + const teNode = fetchCommonTENode(); + const subsetNodes = teNode.buildDeletionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); - // One final bit, which is a bit of integration - we know the top two nodes that should result. - let queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, edges); + const processedNodes = subsetNodes.flatMap(n => n.processSubsetEdge()); + // All delete-oriented Transform subsets condense down to a single + // transform (and edge) each. + assert.equal(processedNodes.length, 4); - let firstEdge = queue.dequeue(); - assert.equal(firstEdge.priorInput[0].sample.insert, 't'); - assert.equal(firstEdge.calculation.lastMatchEntry.key, 't'); - assert.isAbove(firstEdge.currentCost, 0); + // Sorted index 0: 1 insert - should be processed already, in a single + // step. + assert.isFalse(processedNodes[0].hasPartialInput); + assert.equal(processedNodes[0].editCount, 1); + assert.equal(processedNodes[0].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[0].inputSamplingCost, subsetNodes[0].inputSamplingCost); + assert.isAbove(processedNodes[0].currentCost, subsetNodes[0].currentCost); - let secondEdge = queue.dequeue(); - assert.equal(secondEdge.priorInput[0].sample.insert, 'h'); - assert.equal(secondEdge.calculation.lastMatchEntry.key, 'h'); - assert.isAbove(secondEdge.currentCost, firstEdge.currentCost); + // Sorted index 3: 0 insert - should be processed already, in a single + // step. + assert.isFalse(processedNodes[3].hasPartialInput); + // No insert string => no sentinel char to delete. + assert.equal(processedNodes[3].editCount, 0); + // ... and a prior character exists. + assert.equal(processedNodes[3].calculation.lastInputEntry, 't'); + assert.equal(processedNodes[3].inputSamplingCost, subsetNodes[3].inputSamplingCost); + assert.equal(processedNodes[3].currentCost, subsetNodes[3].currentCost); - // After this, a 't' input without a matching char. - let nextEdge = queue.dequeue(); - assert.equal(nextEdge.priorInput[0].sample.insert, 't'); - assert.notEqual(nextEdge.calculation.lastMatchEntry.key, 't'); - assert.isAbove(nextEdge.currentCost, secondEdge.currentCost); + // Sorted indices 2, 3: both had 2 inserts. + assert.isTrue(processedNodes[1].hasPartialInput); + assert.equal(processedNodes[1].editCount, 1); + assert.equal(processedNodes[1].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[1].inputSamplingCost, subsetNodes[1].inputSamplingCost); + assert.isAbove(processedNodes[1].currentCost, subsetNodes[1].currentCost); + + assert.isTrue(processedNodes[2].hasPartialInput); + assert.equal(processedNodes[2].editCount, 1); + assert.equal(processedNodes[2].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNodes[2].inputSamplingCost, subsetNodes[2].inputSamplingCost); + assert.isAbove(processedNodes[2].currentCost, subsetNodes[2].currentCost); + }); + + it('step 3: second processing layer resolves two char inserts', () => { + const teNode = fetchCommonTENode(); + const subsetNodes = teNode.buildDeletionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + // Two nodes were unprocessed at the end of the last step; we handle + // them now and filter the others out. We need the indices here to be + // aligned. + const step2Nodes = subsetNodes.flatMap(n => n.processSubsetEdge()).filter(n => n.hasPartialInput); + + function assertExpectedProperties(processedNodes: SearchNode[], baseNode: SearchNode, dl: number) { + // All delete-oriented Transform subsets condense down to a single + // transform (and edge) each. + assert.equal(processedNodes.length, 1); + // All nodes are now done processing. + processedNodes.forEach((processedNode) => { + assert.isFalse(processedNode.hasPartialInput); + assert.isFalse(processedNode.hasPartialInput); + assert.equal(processedNode.editCount, 2); + assert.equal(processedNode.calculation.inputSequence.length, 4 - dl); + assert.equal(processedNode.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(processedNode.inputSamplingCost, baseNode.inputSamplingCost); + assert.isAbove(processedNode.currentCost, baseNode.currentCost); + }); + } + + assertExpectedProperties(step2Nodes[0].processSubsetEdge(), step2Nodes[0], 1); + assertExpectedProperties(step2Nodes[1].processSubsetEdge(), step2Nodes[1], 0); + }); + }); + + describe('buildSubstitutionEdges @ token root', () => { + const synthDistribution = [ + // Transform, probability + // insert 1, deleteLeft 0, p: 0.50 + {sample: {insert: 't', deleteLeft: 0}, p: 0.30}, + {sample: {insert: 'h', deleteLeft: 0}, p: 0.15}, + {sample: {insert: 'x', deleteLeft: 0}, p: 0.05}, + // insert 0 deleteLeft 1, p: 0.1 + {sample: {insert: '', deleteLeft: 1}, p: 0.1}, + // insert 2 deleteLeft 0, p: 0.15 + {sample: {insert: 'ch', deleteLeft: 0}, p: 0.15}, + // insert 2 deleteLeft 1, p: 0.25 + {sample: {insert: 'th', deleteLeft: 1}, p: 0.1}, + {sample: {insert: 'tr', deleteLeft: 1}, p: 0.15}, + // 4 distinct subsets. + ]; + + it('step 1: batches substitution edge(s) for input transforms', () => { + let rootTraversal = testModel.traverseFromRoot(); + assert.isNotEmpty(rootTraversal); + + let rootNode = new correction.SearchNode(rootTraversal); + assert.equal(rootNode.calculation.getHeuristicFinalCost(), 0); + + const subsetNodes = rootNode.buildSubstitutionEdges(synthDistribution); + assert.equal(subsetNodes.length, 4); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x)) + // The known subs for the subsets defined above. + for(let i=0; i < expectedCosts.length; i++) { + assert.isTrue(subsetNodes[i].hasPartialInput); + // From root: the deleteLeft 1 entries have nothing to insert. + assert.equal((subsetNodes[i].currentTraversal as TrieTraversal).prefix, ''); + + // Allow a little value wiggle due to double-precision limitations. + assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8); + // No actual edit-tracking is done yet, so these should also match. + assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8); + } + }); + + it('step 2: first processing layer resolves zero + one char inserts', () => { + // From "step 1" above, assertions removed + let rootTraversal = testModel.traverseFromRoot(); + let rootNode = new correction.SearchNode(rootTraversal); + const subsetNodes = rootNode.buildSubstitutionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + // Set 0: set for ins 1, dl 0 + const ins1_dl0 = subsetNodes[0].processSubsetEdge(); + // 3 transforms went in... but 1 ('x') had no lexical match. + assert.equal(ins1_dl0.length, FIRST_CHAR_VARIANTS + 3 - 1); + + ins1_dl0.sort((a, b) => a.currentCost - b.currentCost); + ins1_dl0.forEach(n => assert.isFalse(n.hasPartialInput)); // all fully-processed. + assert.equal(ins1_dl0[0].calculation.lastInputEntry, 't'); + assert.equal(ins1_dl0[0].calculation.lastMatchEntry, 't'); + assert.equal(ins1_dl0[0].editCount, 0); + assert.equal(ins1_dl0[1].calculation.lastInputEntry, 'h'); + assert.equal(ins1_dl0[1].calculation.lastMatchEntry, 'h'); + assert.equal(ins1_dl0[1].editCount, 0); + assert.isBelow(ins1_dl0[0].inputSamplingCost, ins1_dl0[1].inputSamplingCost); + assert.isBelow(ins1_dl0[0].currentCost, ins1_dl0[1].currentCost); + + // Correction of _other_ input characters to the 't' and the 'h' come + // after ALL other corrections - these don't get both 't' and 'h' input + // weights merged into them! + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + // 't' input is more likely, so 't' gives more edit-contribution to 'h' than + // 'h' gives to 't' + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS].calculation.lastMatchEntry, 'h'); + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS].editCount, 1); + assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS-1].inputSamplingCost, ins1_dl0[FIRST_CHAR_VARIANTS].inputSamplingCost); + assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS-1].currentCost, ins1_dl0[FIRST_CHAR_VARIANTS].currentCost); + + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS+1].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS+1].calculation.lastMatchEntry, 't'); + assert.equal(ins1_dl0[FIRST_CHAR_VARIANTS+1].editCount, 1); + assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS].inputSamplingCost, ins1_dl0[FIRST_CHAR_VARIANTS+1].inputSamplingCost); + assert.isBelow(ins1_dl0[FIRST_CHAR_VARIANTS].currentCost, ins1_dl0[FIRST_CHAR_VARIANTS+1].currentCost); + + // For everything in between... well, the input-sampling weight is uniform, and + // all require a full edit. + const fullProbEditNodes = ins1_dl0.slice(2, -2); + const fullInputCost = subsetNodes[0].inputSamplingCost; // the cumulative weight of the ins1_dl0 subset. + fullProbEditNodes.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + + // END: the ins 1, del 0 subset. + + // ************ + // Set 3: set for ins 0, dl 1 + const ins0_dl1 = subsetNodes[3].processSubsetEdge(); + + // No inserts, so no insert variants are possible. + assert.equal(ins0_dl1.length, 1); + assert.isFalse(ins0_dl1[0].hasPartialInput); + + // No insert string => no sentinel char to delete. + assert.equal(ins0_dl1[0].editCount, 0); + assert.isUndefined(ins0_dl1[0].calculation.lastInputEntry); + assert.equal(ins0_dl1[0].inputSamplingCost, subsetNodes[3].inputSamplingCost); + assert.equal(ins0_dl1[0].currentCost, subsetNodes[3].currentCost); + + // ************ + // Set 1: set for ins 2, dl 1 - 'tr' + 'th'. + const ins2_dl1 = subsetNodes[1].processSubsetEdge(); + + // 2 transforms went in, but start with the same char - and importantly, + // there are no other alternatives for that char as lead! + assert.equal(ins2_dl1.length, FIRST_CHAR_VARIANTS + 1 - 1); + ins2_dl1.sort((a, b) => a.currentCost - b.currentCost); + // only one char is processed at this stage. + ins2_dl1.forEach(n => assert.isTrue(n.hasPartialInput)); + + assert.equal(ins2_dl1[0].calculation.lastInputEntry, 't'); + assert.equal(ins2_dl1[0].calculation.lastMatchEntry, 't'); + assert.equal(ins2_dl1[0].editCount, 0); + // The subset hasn't yet split! + assert.equal(ins2_dl1[0].currentCost, subsetNodes[1].currentCost); + assert.isBelow(ins2_dl1[0].currentCost, ins2_dl1[1].currentCost); + + // All other (non-'t') entries get full subset probability with edit count 1; + // they're all substitutions, as they fail to match against a non-'t' path. + assert.equal(ins2_dl1[0].inputSamplingCost, ins2_dl1[1].inputSamplingCost); + + const fullProbEditNodes1 = ins2_dl1.slice(1); + const fullInputCost1 = subsetNodes[1].inputSamplingCost; // the cumulative weight of the ins2_dl1 subset. + fullProbEditNodes1.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost1); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + + + // ************ + // Set 2: set for ins 2, dl 0 - 'ch'. Single entry. + const ins2_dl0 = subsetNodes[2].processSubsetEdge(); + + // Only 1 transforms went in - importantly, there are no other + // alternatives for that char as lead! + assert.equal(ins2_dl0.length, FIRST_CHAR_VARIANTS + 1 - 1); + ins2_dl0.sort((a, b) => a.currentCost - b.currentCost); + // only one char is processed at this stage. + ins2_dl0.forEach(n => assert.isTrue(n.hasPartialInput)); + + assert.equal(ins2_dl0[0].calculation.lastInputEntry, 'c'); + assert.equal(ins2_dl0[0].calculation.lastMatchEntry, 'c'); + assert.equal(ins2_dl0[0].editCount, 0); + // The subset won't split. + assert.equal(ins2_dl0[0].currentCost, subsetNodes[2].currentCost); + assert.isBelow(ins2_dl0[0].currentCost, ins2_dl0[1].currentCost); + + // All other (non-'c') entries get full subset probability with edit count 1; + // they're all substitutions, as they fail to match against a non-'t' path. + assert.equal(ins2_dl0[0].inputSamplingCost, ins2_dl0[1].inputSamplingCost); + + const fullProbEditNodes2 = ins2_dl0.slice(1); + const fullInputCost2 = subsetNodes[2].inputSamplingCost; // the cumulative weight of the ins2_dl0 subset. + fullProbEditNodes2.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost2); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + }); + + it('step 3: second processing layer resolves two char inserts', () => { + // From "steps 0, 1" above, assertions removed + let rootTraversal = testModel.traverseFromRoot(); + let rootNode = new correction.SearchNode(rootTraversal); + const subsetNodes = rootNode.buildSubstitutionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + + // ************ + // The intermediate, not-fully-processed node sets. + const ins2_dl1 = subsetNodes[1].processSubsetEdge(); // 'th', 'tr' + const ins2_dl0 = subsetNodes[2].processSubsetEdge(); // 'ch' + + const fin_in2_dl1 = ins2_dl1.flatMap(n => n.processSubsetEdge()); + const fin_in2_dl0 = ins2_dl0.flatMap(n => n.processSubsetEdge()); + + // All should be finished now! + fin_in2_dl1.forEach(n => assert.isFalse(n.hasPartialInput)); + + fin_in2_dl1.sort((a, b) => a.currentCost - b.currentCost); + fin_in2_dl0.sort((a, b) => a.currentCost - b.currentCost); + + // Not going to define hard counts here, but there should easily + // be more. + assert.isAbove(fin_in2_dl1.length, ins2_dl1.length); + assert.isAbove(fin_in2_dl1.length, 100 /* grows significantly! */); + assert.isAbove(fin_in2_dl0.length, ins2_dl0.length); + assert.isAbove(fin_in2_dl0.length, 100 /* grows significantly! */); + + // 'tr' had a notably higher base probability and wins. + assert.equal(fin_in2_dl1[0].resultKey, 'tr'); + assert.equal(fin_in2_dl1[0].editCount, 0); + assert.equal(fin_in2_dl1[1].resultKey, 'th'); + assert.equal(fin_in2_dl1[1].editCount, 0); + + // We should also have single-char replacement entries for cases where one char matches... + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'ty' && n.calculation.inputSequence.map(i => i).join('') == 't' + SENTINEL_CODE_UNIT && n.editCount == 1 + }).length, 1, "did not find expected result type"); + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'ch' && n.calculation.inputSequence.map(i => i).join('') == SENTINEL_CODE_UNIT + 'h' && n.editCount == 1 + }).length, 1, "did not find expected result type"); + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'ar' && n.calculation.inputSequence.map(i => i).join('') == SENTINEL_CODE_UNIT + 'r' && n.editCount == 1 + }).length, 1, "did not find expected result type"); + + // And even cases where none match, though at twice the edit cost. + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'an' && n.calculation.inputSequence.map(i => i).join('') == SENTINEL_CODE_UNIT + SENTINEL_CODE_UNIT && n.editCount == 2 + }).length, 1, "did not find expected result type"); + + // ***** + // Now for the other set... + assert.equal(fin_in2_dl0[0].resultKey, 'ch'); + assert.equal(fin_in2_dl0[0].editCount, 0); + + // We should also have single-char replacement entries for cases where one char matches... + assert.isAtLeast(fin_in2_dl0.filter(n => { + return n.resultKey == 'ca' && n.calculation.inputSequence.map(i => i).join('') == 'c' + SENTINEL_CODE_UNIT && n.editCount == 1 + }).length, 1, "did not find expected result type"); + assert.isAtLeast(fin_in2_dl0.filter(n => { + return n.resultKey == 'th' && n.calculation.inputSequence.map(i => i).join('') == SENTINEL_CODE_UNIT + 'h' && n.editCount == 1 + }).length, 1, "did not find expected result type"); + + // And even cases where none match, though at twice the edit cost. + assert.isAtLeast(fin_in2_dl0.filter(n => { + return n.resultKey == 'an' && n.calculation.inputSequence.map(i => i).join('') == SENTINEL_CODE_UNIT + SENTINEL_CODE_UNIT && n.editCount == 2 + }).length, 1, "did not find expected result type"); + }); + }); + + describe(`buildSubstitutionEdges starting @ 'te' prefix`, () => { + // start prefix 'te' + + const synthDistribution = [ + // Transform, probability + // insert 1, deleteLeft 0, p: 0.50 + {sample: {insert: 'r', deleteLeft: 0}, p: 0.30}, // ter(m) + {sample: {insert: 'l', deleteLeft: 0}, p: 0.15}, // tel(l) + {sample: {insert: 'x', deleteLeft: 0}, p: 0.05}, // no entry available + // insert 0 deleteLeft 1, p: 0.1 + {sample: {insert: '', deleteLeft: 1}, p: 0.1}, // t (LOTS of followups) + // insert 2 deleteLeft 0, p: 0.15 + {sample: {insert: 'ch', deleteLeft: 0}, p: 0.15}, // tech(nical) + // insert 2 deleteLeft 1, p: 0.25 + {sample: {insert: 'ak', deleteLeft: 1}, p: 0.1}, // tak(e) + {sample: {insert: 'al', deleteLeft: 1}, p: 0.15}, // tal(k) + // 4 distinct subsets. + ]; + + it('step 1: batches substitution edge(s) for input transforms', () => { + const teNode = fetchCommonTENode(); + assert.equal(teNode.calculation.getHeuristicFinalCost(), 0); + + const subsetNodes = teNode.buildSubstitutionEdges(synthDistribution); + assert.equal(subsetNodes.length, 4); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + const expectedCosts = [0.5, .25, 0.15, 0.1].map(x => -Math.log(x) + teNode.currentCost); + // The known subs for the subsets defined above. + for(let i=0; i < expectedCosts.length; i++) { + assert.isTrue(subsetNodes[i].hasPartialInput); + // From root: the deleteLeft 1 entries have nothing to insert. + assert.equal((subsetNodes[i].currentTraversal as TrieTraversal).prefix, (i == 0 || i == 2) ? 'te' : 't'); + + // Allow a little value wiggle due to double-precision limitations. + assert.approximately(subsetNodes[i].inputSamplingCost, expectedCosts[i], 1e-8); + // No actual edit-tracking is done yet, so these should also match. + assert.approximately(subsetNodes[i].currentCost, expectedCosts[i], 1e-8); + } + }); + + it('step 2: first processing layer resolves zero + one char inserts', () => { + // From "step 1" above, assertions removed + const teNode = fetchCommonTENode(); + const subsetNodes = teNode.buildSubstitutionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + // Set 0: set for ins 1, dl 0 + const ins1_dl0 = subsetNodes[0].processSubsetEdge(); + const TE_CHILD_PATH_COUNT = 6; // 'l', 'n', 'r', 'm', 'c', 's' + // 3 transforms went in... but 1 ('x') had no lexical match. + assert.equal(ins1_dl0.length, TE_CHILD_PATH_COUNT + 3 - 1); + + ins1_dl0.sort((a, b) => a.currentCost - b.currentCost); + ins1_dl0.forEach(n => assert.isFalse(n.hasPartialInput)); // all fully-processed. + assert.equal(ins1_dl0[0].calculation.lastInputEntry, 'r'); + assert.equal(ins1_dl0[0].calculation.lastMatchEntry, 'r'); + assert.equal(ins1_dl0[0].editCount, 0); + assert.equal(ins1_dl0[1].calculation.lastInputEntry, 'l'); + assert.equal(ins1_dl0[1].calculation.lastMatchEntry, 'l'); + assert.equal(ins1_dl0[1].editCount, 0); + assert.isBelow(ins1_dl0[0].inputSamplingCost, ins1_dl0[1].inputSamplingCost); + assert.isBelow(ins1_dl0[0].currentCost, ins1_dl0[1].currentCost); + + // Correction of _other_ input characters to the 't' and the 'h' come + // after ALL other corrections - these don't get both 't' and 'h' input + // weights merged into them! + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + // 't' input is more likely, so 't' gives more edit-contribution to 'h' than + // 'h' gives to 't' + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT].calculation.lastMatchEntry, 'l'); + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT].editCount, 1); + assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT-1].inputSamplingCost, ins1_dl0[TE_CHILD_PATH_COUNT].inputSamplingCost); + assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT-1].currentCost, ins1_dl0[TE_CHILD_PATH_COUNT].currentCost); + + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT+1].calculation.lastInputEntry, SENTINEL_CODE_UNIT); + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT+1].calculation.lastMatchEntry, 'r'); + assert.equal(ins1_dl0[TE_CHILD_PATH_COUNT+1].editCount, 1); + assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT].inputSamplingCost, ins1_dl0[TE_CHILD_PATH_COUNT+1].inputSamplingCost); + assert.isBelow(ins1_dl0[TE_CHILD_PATH_COUNT].currentCost, ins1_dl0[TE_CHILD_PATH_COUNT+1].currentCost); + + // For everything in between... well, the input-sampling weight is uniform, and + // all require a full edit. + const fullProbEditNodes = ins1_dl0.slice(2, -2); + const fullInputCost = subsetNodes[0].inputSamplingCost; // the cumulative weight of the ins1_dl0 subset. + fullProbEditNodes.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + + // END: the ins 1, del 0 subset. + + // ************ + // Set 3: set for ins 0, dl 1 + const ins0_dl1 = subsetNodes[3].processSubsetEdge(); + + // No inserts, so no insert variants are possible. + assert.equal(ins0_dl1.length, 1); + assert.isFalse(ins0_dl1[0].hasPartialInput); + + // No insert string => no sentinel char to delete. + assert.equal(ins0_dl1[0].editCount, 0); + assert.equal(ins0_dl1[0].calculation.lastInputEntry, 't'); + assert.equal(ins0_dl1[0].inputSamplingCost, subsetNodes[3].inputSamplingCost); + assert.equal(ins0_dl1[0].currentCost, subsetNodes[3].currentCost); + + // ************ + // Set 1: set for ins 2, dl 1 - 'ak' + 'ax'. + const ins2_dl1 = subsetNodes[1].processSubsetEdge(); + + // 2 transforms went in, but start with the same char - and importantly, + // there are no other alternatives for that char as lead! + const T_CHILD_PATH_COUNT = 9; // 'h', 'o', 'i', 'w', 'a', 'u', 'e', 'r', 'y' + assert.equal(ins2_dl1.length, T_CHILD_PATH_COUNT + 1 - 1); + ins2_dl1.sort((a, b) => a.currentCost - b.currentCost); + // only one char is processed at this stage. + ins2_dl1.forEach(n => assert.isTrue(n.hasPartialInput)); + + assert.equal(ins2_dl1[0].calculation.lastInputEntry, 'a'); + assert.equal(ins2_dl1[0].calculation.lastMatchEntry, 'a'); + assert.equal(ins2_dl1[0].editCount, 0); + // The subset hasn't yet split! + assert.equal(ins2_dl1[0].currentCost, subsetNodes[1].currentCost); + assert.isBelow(ins2_dl1[0].currentCost, ins2_dl1[1].currentCost); + + // All other (non-'t') entries get full subset probability with edit count 1; + // they're all substitutions, as they fail to match against a non-'t' path. + assert.equal(ins2_dl1[0].inputSamplingCost, ins2_dl1[1].inputSamplingCost); + + const fullProbEditNodes1 = ins2_dl1.slice(1); + const fullInputCost1 = subsetNodes[1].inputSamplingCost; // the cumulative weight of the ins2_dl1 subset. + fullProbEditNodes1.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost1); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + + + // ************ + // Set 2: set for ins 2, dl 0 - 'ch'. Single entry. + const ins2_dl0 = subsetNodes[2].processSubsetEdge(); + + // Only 1 transform went in - importantly, there are no other + // alternatives for that char as lead! + assert.equal(ins2_dl0.length, TE_CHILD_PATH_COUNT + 1 - 1); + ins2_dl0.sort((a, b) => a.currentCost - b.currentCost); + // only one char is processed at this stage. + ins2_dl0.forEach(n => assert.isTrue(n.hasPartialInput)); + + assert.equal(ins2_dl0[0].calculation.lastInputEntry, 'c'); + assert.equal(ins2_dl0[0].calculation.lastMatchEntry, 'c'); + assert.equal(ins2_dl0[0].editCount, 0); + // The subset won't split. + assert.equal(ins2_dl0[0].currentCost, subsetNodes[2].currentCost); + assert.isBelow(ins2_dl0[0].currentCost, ins2_dl0[1].currentCost); + + // All other (non-'c') entries get full subset probability with edit count 1; + // they're all substitutions, as they fail to match against a non-'t' path. + assert.equal(ins2_dl0[0].inputSamplingCost, ins2_dl0[1].inputSamplingCost); + + const fullProbEditNodes2 = ins2_dl0.slice(1); + const fullInputCost2 = subsetNodes[2].inputSamplingCost; // the cumulative weight of the ins2_dl0 subset. + fullProbEditNodes2.forEach(n => { + assert.equal(n.inputSamplingCost, fullInputCost2); + assert.equal(n.editCount, 1); + assert.equal(n.calculation.lastInputEntry, SENTINEL_CODE_UNIT); + }); + }); + + it('step 3: second processing layer resolves two char inserts', () => { + // From "steps 0, 1" above, assertions removed + const teNode = fetchCommonTENode(); + const subsetNodes = teNode.buildSubstitutionEdges(synthDistribution); + subsetNodes.sort((a, b) => a.currentCost - b.currentCost); + + // ************ + // The intermediate, not-fully-processed node sets. + const ins2_dl1 = subsetNodes[1].processSubsetEdge(); // 'tak', 'tax' + const ins2_dl0 = subsetNodes[2].processSubsetEdge(); // 'tech' + + const fin_in2_dl1 = ins2_dl1.flatMap(n => n.processSubsetEdge()); + const fin_in2_dl0 = ins2_dl0.flatMap(n => n.processSubsetEdge()); + + // All should be finished now! + fin_in2_dl1.forEach(n => assert.isFalse(n.hasPartialInput)); + + fin_in2_dl1.sort((a, b) => a.currentCost - b.currentCost); + fin_in2_dl0.sort((a, b) => a.currentCost - b.currentCost); + + // Not going to define hard counts here, but there should easily + // be more. + assert.isAbove(fin_in2_dl1.length, ins2_dl1.length); + assert.isAbove(fin_in2_dl1.length, 20 /* grows significantly - lots of 't'-prefixed words! */); + // Not so many 'te' prefixed words, though - this is a very selective model. + // 'ten' was possible after one inserted char, but the second inserted char blocks it as a + // substitution target! + assert.equal(fin_in2_dl0.length, ins2_dl0.length - 1); + + // 'tal' had a notably higher base probability and wins. + assert.equal(fin_in2_dl1[0].resultKey, 'tal'); + assert.equal(fin_in2_dl1[0].editCount, 0); + assert.equal(fin_in2_dl1[1].resultKey, 'tak'); + assert.equal(fin_in2_dl1[1].editCount, 0); + + // We should also have single-char replacement entries for cases where one char matches... + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'tax' && n.calculation.inputSequence.map(i => i).join('') == 'ta' + SENTINEL_CODE_UNIT && n.editCount == 1 + }).length, 1, "did not find expected result type"); // 'tax' + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'tol' && n.calculation.inputSequence.map(i => i).join('') == 't' + SENTINEL_CODE_UNIT + 'l' && n.editCount == 1 + }).length, 1, "did not find expected result type"); + assert.equal(fin_in2_dl1.filter(n => { + // No other 't_k' entries. :() + return n.resultKey == 'tir' && n.calculation.inputSequence.map(i => i).join('') == 't' + SENTINEL_CODE_UNIT + 'r' && n.editCount == 1 + }).length, 0, "found unexpected result type"); + + // And even cases where none match, though at twice the edit cost. + assert.isAtLeast(fin_in2_dl1.filter(n => { + return n.resultKey == 'tim' && n.calculation.inputSequence.map(i => i).join('') == 't' + SENTINEL_CODE_UNIT + SENTINEL_CODE_UNIT && n.editCount == 2 + }).length, 1, "did not find expected result type"); + + // ***** + // Now for the other set... + assert.equal(fin_in2_dl0[0].resultKey, 'tech'); + assert.equal(fin_in2_dl0[0].editCount, 0); + + assert.equal(fin_in2_dl0.filter(n => { + return n.resultKey == 'tec_' && n.calculation.inputSequence.map(i => i).join('') == 'tec' + SENTINEL_CODE_UNIT && n.editCount == 1 + }).length, 0, "found unexpected result type"); // No other 'tec'-prefixed entries. + assert.isAtLeast(fin_in2_dl0.filter(n => { + return n.resultKey == 'te_h' && n.calculation.inputSequence.map(i => i).join('') == 'te' + SENTINEL_CODE_UNIT + 'h' && n.editCount == 1 + }).length, 0, "found unexpected result type"); // No other 'te_h' entries here. + + // Cases where none match should exist, though at twice the edit cost. + assert.isAtLeast(fin_in2_dl0.filter(n => { + return n.resultKey == 'temp' && n.calculation.inputSequence.map(i => i).join('') == 'te' + SENTINEL_CODE_UNIT + SENTINEL_CODE_UNIT && n.editCount == 2 + }).length, 1, "did not find expected result type"); // temperature + }); }); it('Small integration test: "teh" => "ten", "the"', function() { @@ -187,13 +1082,17 @@ describe('Correction Distance Modeler', function() { {sample: {insert: 'n', deleteLeft: 0}, p: 0.25} ]; - let layer1Edges = rootNode.buildSubstitutionEdges(synthDistribution1); + let layer1Edges = rootNode.buildSubstitutionEdges(synthDistribution1) + // No 2+ inserts here; we're fine with just one call. + .flatMap(e => e.processSubsetEdge()); let layer1Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer1Edges); let tEdge = layer1Queue.dequeue(); assertEdgeChars(tEdge, 't', 't'); - let layer2Edges = tEdge.buildSubstitutionEdges(synthDistribution2); + let layer2Edges = tEdge.buildSubstitutionEdges(synthDistribution2) + // No 2+ inserts here; we're fine with just one call. + .flatMap(e => e.processSubsetEdge()); let layer2Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer2Edges); let eEdge = layer2Queue.dequeue(); @@ -203,21 +1102,25 @@ describe('Correction Distance Modeler', function() { assertEdgeChars(hEdge, 'h', 'h'); // Needed for a proper e <-> h transposition. - let ehEdge = findEdgeWithChars(layer2Edges, 'e', 'h'); + let ehEdge = findEdgesWithChars(layer2Edges, 'h')[0]; assert.isOk(ehEdge); // Final round: we'll use three nodes and throw all of their results into the same priority queue. - let layer3eEdges = eEdge.buildSubstitutionEdges(synthDistribution3); - let layer3hEdges = hEdge.buildSubstitutionEdges(synthDistribution3); - let layer3ehEdges = ehEdge.buildSubstitutionEdges(synthDistribution3); + let layer3eEdges = eEdge.buildSubstitutionEdges(synthDistribution3) + // No 2+ inserts here; we're fine with just one call. + .flatMap(e => e.processSubsetEdge()); + let layer3hEdges = hEdge.buildSubstitutionEdges(synthDistribution3) + .flatMap(e => e.processSubsetEdge()); + let layer3ehEdges = ehEdge.buildSubstitutionEdges(synthDistribution3) + .flatMap(e => e.processSubsetEdge()); let layer3Queue = new PriorityQueue(correction.QUEUE_NODE_COMPARATOR, layer3eEdges.concat(layer3hEdges).concat(layer3ehEdges)); // Find the first result with an actual word directly represented. let bestEdge; do { bestEdge = layer3Queue.dequeue(); - } while(bestEdge.calculation.lastMatchEntry.traversal.entries.length == 0); + } while(bestEdge.currentTraversal.entries.length == 0); assertEdgeChars(bestEdge, 'n', 'n'); // 'ten' - perfect edit distance of 0, though less-likely input sequence. // No cost assumptions here. @@ -225,11 +1128,11 @@ describe('Correction Distance Modeler', function() { var sibling1; do { sibling1 = layer3Queue.dequeue(); - } while(sibling1.calculation.lastMatchEntry.traversal.entries.length == 0); + } while(sibling1.currentTraversal.entries.length == 0); // Both have a raw edit distance of 1 while using the same input-sequence root. ('th') - let tenFlag = edgeHasChars(sibling1, 'h', 'n'); // subs out the 'h' entirely. Could also occur with 'a', but is too unlikely. - let theFlag = edgeHasChars(sibling1, 'h', 'e'); // looks for transposed 'h' and 'e'. + let tenFlag = edgeHasChars(sibling1, SENTINEL_CODE_UNIT, 'n'); // subs out the 'h' entirely. Could also occur with 'a', but is too unlikely. + let theFlag = edgeHasChars(sibling1, SENTINEL_CODE_UNIT, 'e'); // looks for transposed 'h' and 'e'. assert.isTrue(tenFlag || theFlag); assert.isAbove(sibling1.currentCost, bestEdge.currentCost); @@ -237,25 +1140,18 @@ describe('Correction Distance Modeler', function() { var sibling2; do { sibling2 = layer3Queue.dequeue(); - } while(sibling2.calculation.lastMatchEntry.traversal.entries.length == 0); + } while(sibling2.currentTraversal.entries.length == 0); - tenFlag = tenFlag || edgeHasChars(sibling2, 'h', 'n'); - theFlag = theFlag || edgeHasChars(sibling2, 'h', 'e'); + tenFlag = tenFlag || edgeHasChars(sibling2, SENTINEL_CODE_UNIT, 'n'); + theFlag = theFlag || edgeHasChars(sibling2, SENTINEL_CODE_UNIT, 'e'); assert.isTrue(tenFlag && theFlag); - assert.equal(sibling2.currentCost, sibling1.currentCost); - assert.isAbove(sibling2.currentCost, bestEdge.currentCost); + assert.isAbove(sibling2.currentCost, sibling1.currentCost); }); }); describe('SearchSpaceTier + SearchSpace', function() { - var testModel: TrieModel; - - before(function() { - testModel = new TrieModel(jsonFixture('models/tries/english-1000')); - }); - - let checkResults_teh = async function(iter: AsyncGenerator) { + let checkRepeatableResults_teh = async function(iter: AsyncGenerator) { let firstIterResult = await iter.next(); // {value: , done: } assert.isFalse(firstIterResult.done); @@ -263,11 +1159,12 @@ describe('Correction Distance Modeler', function() { // No checks on the first set's cost. assert.equal(firstResult.matchString, "ten"); + // All start with 'te' but one, and invoke one edit of the same cost. + // 'th' has an 'h' at the same cost (input 3) of the 'e' (input 2). let secondBatch = [ - 'beh', 'te', 'tec', - 'tech', 'tel', 'tem', - 'ter', 'tes', 'th', - 'the' + 'tec', 'tel', 'tem', + 'ter', 'tes', 'th', + 'te' ]; async function checkBatch(batch: string[], prevCost: number) { @@ -294,15 +1191,33 @@ describe('Correction Distance Modeler', function() { const secondCost = await checkBatch(secondBatch, firstResult.totalCost); + // Single hard edit, all other input probability aspects are equal let thirdBatch = [ - 'cen', 'en', 'gen', - 'ken', 'len', 'men', - 'sen', 'tha', 'then', - 'thi', 'tho', 'thr', - 'thu', 'wen' + // 't' -> 'b' (sub) + 'beh', + // '' -> 'c' (insertion) + 'tech' ]; await checkBatch(thirdBatch, secondCost); + + // All replace the low-likelihood case for the third input. + let fourthBatch = [ + 'the', 'thi', 'tho', 'thr', + 'thu', 'tha' + ]; + + await checkBatch(fourthBatch, secondCost); + + // Replace the _first_ input's char OR insert an extra char, + // also matching the low-likelihood third-char option. + let fifthBatch = [ + 'cen', 'en', 'gen', + 'ken', 'len', 'men', + 'sen', 'then', 'wen' + ]; + + await checkBatch(fifthBatch, secondCost); } it('Simple search without input', async function() { @@ -317,6 +1232,7 @@ describe('Correction Distance Modeler', function() { assert.isFalse(firstResult.done); }); + // Hmm... how best to update this... it('Simple search (paralleling "Small integration test")', async function() { // The combinatorial effect here is a bit much to fully test. let rootTraversal = testModel.traverseFromRoot(); @@ -344,7 +1260,7 @@ describe('Correction Distance Modeler', function() { searchSpace.addInput(synthDistribution3); let iter = searchSpace.getBestMatches(buildTestTimer()); // disables the correction-search timeout. - await checkResults_teh(iter); + await checkRepeatableResults_teh(iter); }); it('Allows reiteration (sequentially)', async function() { @@ -374,12 +1290,12 @@ describe('Correction Distance Modeler', function() { searchSpace.addInput(synthDistribution3); let iter = searchSpace.getBestMatches(buildTestTimer()); // disables the correction-search timeout. - await checkResults_teh(iter); + await checkRepeatableResults_teh(iter); // The key: do we get the same results the second time? // Reset the iterator first... let iter2 = searchSpace.getBestMatches(buildTestTimer()); // disables the correction-search timeout. - await checkResults_teh(iter2); + await checkRepeatableResults_teh(iter2); }); it('Empty search space, loaded model', async function() { @@ -393,13 +1309,24 @@ describe('Correction Distance Modeler', function() { // While there's no input, insertion operations can produce suggestions. let resultState = await iter.next(); - let result = resultState.value; + let result: SearchResult = resultState.value; - // Just one suggestion should be returned. + // Just one suggestion root should be returned as the first result. assert.equal(result.totalCost, 0); // Gives a perfect match assert.equal(result.inputSequence.length, 0); // for a state with no input and assert.equal(result.matchString, ''); // an empty match string. assert.isFalse(resultState.done); + + // Should be able to reach more, though. + let laterResultState = await iter.next(); + let laterResult: SearchResult = laterResultState.value; + + // Edit required: an 'insertion' edge (no input matched, but char pulled + // from lexicon) + assert.isAbove(laterResult.totalCost, 0); + // The most likely word in the lexicon starts with 't'. + assert.equal(laterResult.matchString, 't'); + assert.isFalse(resultState.done); }); }); }); diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/transform-subsets.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/transform-subsets.tests.ts new file mode 100644 index 0000000000..11eb7a1fa5 --- /dev/null +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/transform-subsets.tests.ts @@ -0,0 +1,212 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by jahorton on 2025-08-21 + * + * This file defines tests for the transform-cataloging methods + * used to assist the predictive-text correction-search heuristic. + */ + +import { assert } from 'chai'; + +import { LexicalModelTypes } from '@keymanapp/common-types'; + +import { subsetByChar, subsetByInterval, TransformSubset} from '@keymanapp/lm-worker/test-index'; + +import Transform = LexicalModelTypes.Transform; + +function validateSubset(subset: TransformSubset, key: T, entryTexts: string[]) { + assert.sameMembers(subset.entries.map(t => t.sample.insert), entryTexts); + assert.equal(subset.cumulativeMass, entryTexts.length); + assert.equal(subset.key, key); +} + +describe('subsetByInterval', () => { + it('properly arranges transforms into subsets based on range of Context application', () => { + const transforms: Transform[] = [ + { insert: 'a', deleteLeft: 0 }, + { insert: 'b', deleteLeft: 0 }, + { insert: 'c', deleteLeft: 0 }, + { insert: 'de', deleteLeft: 0 }, + { insert: 'fg', deleteLeft: 0 }, + { insert: 'hij', deleteLeft: 0 }, + { insert: 'k', deleteLeft: 1 }, + { insert: 'l', deleteLeft: 1 }, + { insert: 'm', deleteLeft: 1 }, + { insert: 'no', deleteLeft: 1 }, + { insert: 'pq', deleteLeft: 1 }, + { insert: 'rs', deleteLeft: 2 }, + { insert: 'tu', deleteLeft: 2 }, + { insert: 'vw', deleteLeft: 2 }, + { insert: 'xyz', deleteLeft: 2 }, + { insert: '', deleteLeft: 4} + ]; + + const distribution = transforms.map(t => ({sample: t, p: 1})); + const catalogs = subsetByInterval(distribution); + + assert.equal(catalogs.length, 5); // all keys <= 4, the max deleteLeft. + catalogs.forEach((k, i) => assert.isOk(catalogs[i])); + + assert.equal(catalogs[0].length, 4); // max insert.length = 3 + assert.isNotOk(catalogs[0][0]); + validateSubset(catalogs[0][1], 1, ['a', 'b', 'c']); + validateSubset(catalogs[0][2], 2, ['de', 'fg']); + validateSubset(catalogs[0][3], 3, ['hij']); + assert.equal(catalogs[1].length, 3); // max insert.length = 2 + assert.isNotOk(catalogs[1][0]); + validateSubset(catalogs[1][1], 1, ['k', 'l', 'm']); + validateSubset(catalogs[1][2], 2, ['no', 'pq']); + assert.isNotOk(catalogs[2][0]); + assert.isNotOk(catalogs[2][1]); + assert.equal(catalogs[2].length, 4); // max insert.length = 3 + validateSubset(catalogs[2][2], 2, ['rs', 'tu', 'vw']); + assert.isNotOk(catalogs[3]); // no deleteLeft = 3 entries + assert.equal(catalogs[4].length, 1); // max insert.length = 0 + validateSubset(catalogs[4][0], 0, ['']); + }); +}); + +describe('subsetByChar', () => { + describe('without search-term keying', () => { + const transforms: Transform[] = [ + { insert: 'ace', deleteLeft: 0 }, + { insert: 'add', deleteLeft: 0 }, + { insert: 'ade', deleteLeft: 0 }, + { insert: 'ado', deleteLeft: 0 }, + { insert: 'cab', deleteLeft: 0 }, + { insert: 'cad', deleteLeft: 0 }, + { insert: 'can', deleteLeft: 0 }, + { insert: 'cod', deleteLeft: 0 }, + { insert: 'con', deleteLeft: 0 }, + { insert: 'dab', deleteLeft: 0 }, + { insert: 'dad', deleteLeft: 0 }, + { insert: 'dan', deleteLeft: 0 }, + { insert: 'don', deleteLeft: 0 } + ]; + + const sourceSet: TransformSubset = { + key: 3, + entries: transforms.map((t => ({sample: t, p: 1}))), + cumulativeMass: transforms.length, + insert: '' + }; + + it('places transforms into subsets based on char at index 0', () => { + const subsetMap = subsetByChar(sourceSet, 0); + + assert.sameMembers([...subsetMap.keys()], ['a', 'c', 'd']); + + validateSubset(subsetMap.get('a'), 'a', ['ace', 'add', 'ade', 'ado']); + validateSubset(subsetMap.get('c'), 'c', ['cab', 'cad', 'can', 'cod', 'con']); + validateSubset(subsetMap.get('d'), 'd', ['dab', 'dad', 'dan', 'don']); + }); + + it('places transforms into subsets based on char at later index', () => { + const subsetMap1 = subsetByChar(sourceSet, 1); + + assert.sameMembers([...subsetMap1.keys()], ['a', 'c', 'd', 'o']); + + validateSubset(subsetMap1.get('a'), 'a', ['cab', 'cad', 'can', 'dab', 'dad', 'dan']); + validateSubset(subsetMap1.get('c'), 'c', ['ace']); + validateSubset(subsetMap1.get('d'), 'd', ['add', 'ade', 'ado']); + validateSubset(subsetMap1.get('o'), 'o', ['cod', 'con', 'don']); + + const subsetMap2 = subsetByChar(sourceSet, 2); + + assert.sameMembers([...subsetMap2.keys()], ['b', 'd', 'e', 'n', 'o']); + + validateSubset(subsetMap2.get('b'), 'b', ['cab', 'dab']); + validateSubset(subsetMap2.get('d'), 'd', ['add', 'cad', 'cod', 'dad']); + validateSubset(subsetMap2.get('e'), 'e', ['ace', 'ade']); + validateSubset(subsetMap2.get('n'), 'n', ['can', 'con', 'dan', 'don']); + validateSubset(subsetMap2.get('o'), 'o', ['ado']); + }); + + it('returns full set as \'\'-keyed subset when subindex is beyond range', () => { + const subsetMap = subsetByChar(sourceSet, 4); + + assert.sameMembers([...subsetMap.keys()], ['']); + + validateSubset(subsetMap.get(''), '', transforms.map(t => t.insert)); + }); + }); + + describe('with search-term keying', () => { + // In essence, our original version of the search-term keyer function. + // It's best to keep this one stable and locked for these tests. + function toKey(wordform: string): string { + /** + * N.B.: this is (slightly) DIFFERENT than the version in + * keymanapp/lexical-model-compiler/build-trie + * as this is for compatibility for models built + * BEFORE the searchTermToKey function was bundled with + * all models. + * + * This compatibility version lowercases AFTER removing diacritics; + * the new version (bundled in future models) lowercases, + * NFD normalizes, THEN removes diacritics. + */ + return wordform + .normalize('NFD') + // Remove all combining diacritics (if input is in NFD) + // common to Latin-orthographies. + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase(); + } + + const transforms: Transform[] = [ + { insert: 'acé', deleteLeft: 0 }, + { insert: 'Add', deleteLeft: 0 }, + { insert: 'aDe', deleteLeft: 0 }, + { insert: 'àdo', deleteLeft: 0 }, + { insert: 'cab', deleteLeft: 0 }, + { insert: 'càd', deleteLeft: 0 }, + { insert: 'çan', deleteLeft: 0 }, + { insert: 'cÕd', deleteLeft: 0 }, + { insert: 'çon', deleteLeft: 0 }, + { insert: 'dab', deleteLeft: 0 }, + { insert: 'dàd', deleteLeft: 0 }, + { insert: 'dän', deleteLeft: 0 }, + { insert: 'doN', deleteLeft: 0 } + ]; + + const sourceSet: TransformSubset = { + key: 3, + entries: transforms.map((t => ({sample: t, p: 1}))), + cumulativeMass: transforms.length, + insert: '' + }; + + it('places transforms into subsets based on char at index 0', () => { + const subsetMap = subsetByChar(sourceSet, 0, toKey); + + assert.sameMembers([...subsetMap.keys()], ['a', 'c', 'd']); + + validateSubset(subsetMap.get('a'), 'a', ['acé', 'Add', 'aDe', 'àdo']); + validateSubset(subsetMap.get('c'), 'c', ['cab', 'càd', 'çan', 'cÕd', 'çon']); + validateSubset(subsetMap.get('d'), 'd', ['dab', 'dàd', 'dän', 'doN']); + }); + + it('places transforms into subsets based on char at later index', () => { + const subsetMap1 = subsetByChar(sourceSet, 1, toKey); + + assert.sameMembers([...subsetMap1.keys()], ['a', 'c', 'd', 'o']); + + validateSubset(subsetMap1.get('a'), 'a', ['cab', 'càd', 'çan', 'dab', 'dàd', 'dän']); + validateSubset(subsetMap1.get('c'), 'c', ['acé']); + validateSubset(subsetMap1.get('d'), 'd', ['Add', 'aDe', 'àdo']); + validateSubset(subsetMap1.get('o'), 'o', ['cÕd', 'çon', 'doN']); + + const subsetMap2 = subsetByChar(sourceSet, 2, toKey); + + assert.sameMembers([...subsetMap2.keys()], ['b', 'd', 'e', 'n', 'o']); + + validateSubset(subsetMap2.get('b'), 'b', ['cab', 'dab']); + validateSubset(subsetMap2.get('d'), 'd', ['Add', 'càd', 'cÕd', 'dàd']); + validateSubset(subsetMap2.get('e'), 'e', ['acé', 'aDe']); + validateSubset(subsetMap2.get('n'), 'n', ['çan', 'çon', 'dän', 'doN']); + validateSubset(subsetMap2.get('o'), 'o', ['àdo']); + }); + }) +}); \ No newline at end of file diff --git a/web/src/test/manual/build.sh b/web/src/test/manual/build.sh index b2eed53d0a..0a3cd59173 100755 --- a/web/src/test/manual/build.sh +++ b/web/src/test/manual/build.sh @@ -41,7 +41,7 @@ GESTURE_PROCESSOR_BUILD="$KEYMAN_ROOT/web/src/engine/osk/gesture-processor/build GESTURE_PROCESSOR_TARGET="$KEYMAN_ROOT/web/build/engine/gesture-processor/lib/" function do_copy() { - mkdir -p "$KEYMAN_ROOT/$DEST" + mkdir -p "$KEYMAN_ROOT/$DEST/keyboards" # The next four lines are needed for the sentry-integration manual test page. cp "$SENTRY_MANAGER_SRC" "$KEYMAN_ROOT/$DEST/sentry-manager.js" @@ -49,6 +49,11 @@ function do_copy() { mkdir -p "$GESTURE_PROCESSOR_TARGET" cp -a "$GESTURE_PROCESSOR_BUILD" "$GESTURE_PROCESSOR_TARGET" + + # copy common test (resources) keyboards + cp -f "$KEYMAN_ROOT/common/test/keyboards/platform-rules/platformtest.js" "$KEYMAN_ROOT/$DEST/keyboards/" + cp -f "$KEYMAN_ROOT/common/test/keyboards/test9469/build/test9469.js" "$KEYMAN_ROOT/$DEST/keyboards/" + cp -f "$KEYMAN_ROOT/common/test/resources/keyboards/"*.js "$KEYMAN_ROOT/$DEST/keyboards/" } builder_run_action clean rm -rf "$KEYMAN_ROOT/$DEST" && rm -rf "$GESTURE_PROCESSOR_TARGET" diff --git a/web/src/test/manual/web/chirality/utilities.js b/web/src/test/manual/web/chirality/utilities.js index 158a05c08e..16c9caaf02 100644 --- a/web/src/test/manual/web/chirality/utilities.js +++ b/web/src/test/manual/web/chirality/utilities.js @@ -7,7 +7,7 @@ function loadKeyboards() languages:{ id:'en',name:'English',region:'North America' }, - filename:'chirality.js' + filename:'../../../../../build/test-resources/keyboards/chirality.js' }); // A testing keyboard using 10.0 format and the KLS layout specifier diff --git a/web/src/test/manual/web/issue9469/index.html b/web/src/test/manual/web/issue9469/index.html index 5db68244a2..78c3a15f95 100644 --- a/web/src/test/manual/web/issue9469/index.html +++ b/web/src/test/manual/web/issue9469/index.html @@ -40,7 +40,8 @@ kmw.init({ attachType:'auto' }).then(function() { - kmw.addKeyboards({id:'test9469',name:'test9469',languages:{id:'en',name:'English'}, filename:'test9469/build/test9469.js'}); + kmw.addKeyboards({id:'test9469',name:'test9469',languages:{id:'en',name:'English'}, + filename:'../../../../../build/test-resources/keyboards/test9469.js'}); var pageRef = (window.location.protocol == 'file:') ? window.location.href.substr(0, window.location.href.lastIndexOf('/')+1) diff --git a/web/src/test/manual/web/platform/index.html b/web/src/test/manual/web/platform/index.html index c528862ead..7f007561fd 100644 --- a/web/src/test/manual/web/platform/index.html +++ b/web/src/test/manual/web/platform/index.html @@ -53,12 +53,12 @@

KeymanWeb Sample Page - Platform Testing

-

This page is designed to test the Platform statement for consistency. - Refer to PR #969. - See also /windows/src/test/manual-tests/platform-rules/platform-results.xlsx. +

This page is designed to test the Platform statement for consistency. + Refer to PR #969.
+ See also /common/test/keyboards/platform-rules/platform-results.xlsx.
To run the test, just press [a]. This can be pasted into the above spreadsheet.

-

Be sure to reference the developer console for additional feedback.

-
+

Be sure to reference the developer console for additional feedback.

+
[`IsOnlineUpdateCheckOpen()`](IsOnlineUpdateCheckOpen) -: Returns `True` if the Keyman Desktop online update check tool is +: Returns `True` if the Keyman for Windows online update check tool is currently running. [`IsTextEditorOpen()`](IsTextEditorOpen) -: Returns `True` if the Keyman Desktop text editor is currently +: Returns `True` if the Keyman for Windows text editor is currently running. @@ -72,17 +72,17 @@ and provides methods to trigger Keyman Engine's user interface. [`OpenHelp()`](OpenHelp) -: Opens Keyman Desktop help at the specific topic. +: Opens Keyman for Windows help at the specific topic. [`OpenTextEditor()`](OpenTextEditor) -: Opens Keyman Desktop text editor. +: Opens Keyman for Windows text editor. [`OpenUpdateCheck()`](OpenUpdateCheck) -: Opens Keyman Desktop online update check tool. +: Opens Keyman for Windows online update check tool. diff --git a/windows/docs/engine/index.md b/windows/docs/engine/index.md index 0687433412..3d7e597382 100644 --- a/windows/docs/engine/index.md +++ b/windows/docs/engine/index.md @@ -14,5 +14,6 @@ versions 14.0 and up. ## See also +- [Keyman Engine for Windows 14.0-17.0](/developer/engine/desktop/14.0/) - [Keyman Engine for Desktop 10.0-13.0](/developer/engine/desktop/10.0/) - [Keyman Engine for Desktop 9.0](/developer/engine/desktop/9.0/) diff --git a/windows/docs/help/common/unicode.md b/windows/docs/help/common/unicode.md index 614934e72c..fd567c8ce4 100644 --- a/windows/docs/help/common/unicode.md +++ b/windows/docs/help/common/unicode.md @@ -3,5 +3,5 @@ title: Does Keyman support Unicode? --- Yes, since Keyman 5, Keyman fully supports the Unicode Standard, up to -and including Unicode 13.0. We also update Keyman with each new release +and including Unicode 17.0. We also update Keyman with each new release of the Unicode Standard. diff --git a/windows/src/desktop/build.sh b/windows/src/desktop/build.sh index f5401e22b5..6626a2046f 100755 --- a/windows/src/desktop/build.sh +++ b/windows/src/desktop/build.sh @@ -6,7 +6,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/build/utils.inc.sh" -. "$KEYMAN_ROOT/developer/src/packages.inc.sh" builder_describe \ "Keyman for Windows (app)" \ @@ -45,7 +44,7 @@ function do_prepublish() { if [[ ! -f "$DEVTOOLS" ]]; then # We'll build devtools here directly because we are before the configure / # build steps which would trigger it in via dependencies - "$KEYMAN_ROOT"/common/windows/delphi/tools/devtools/build.sh configure build prepublish + builder_launch /common/windows/delphi/tools/devtools/build.sh configure build prepublish fi # @@ -55,7 +54,7 @@ function do_prepublish() { if [[ ! -f /common/windows/delphi/tools/test-klog/$WIN32_TARGET_PATH/test_klog.exe ]]; then # We'll build test_klog here directly because we are before the configure / # build steps which would trigger it in via dependencies - "$KEYMAN_ROOT"/common/windows/delphi/tools/test-klog/build.sh configure build prepublish + builder_launch /common/windows/delphi/tools/test-klog/build.sh configure build prepublish fi "$DEVTOOLS" -rt diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index f3fefa9c58..14da0b7427 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -14,8 +14,8 @@ builder_describe "Installation files for Keyman for Windows" \ # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete -builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe +builder_if_release_build_level builder_describe_outputs \ + publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -90,13 +90,9 @@ function do_publish() { rm -f setup.inf cat "$WINDOWS_PROGRAM_APP/setup-redist.exe" keymandesktop.zip > keymandesktop.exe rm -f keymandesktop.zip - - # - # Sign the installer - # wrap-signcode //d "Keyman for Windows" keymandesktop.exe - copy-installer + builder_if_release_build_level copy-installer } function copy-installer() { @@ -104,10 +100,10 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp keymandesktop.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keymandesktop.msi" - cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe" + cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_FILENAME}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" - builder_if_release_build_level verify-installer-signatures + verify-installer-signatures # Copy the unsigned setup.exe for use in bundling scenarios; zip it up for clarity ( diff --git a/windows/src/desktop/inst/keymandesktop.wxs b/windows/src/desktop/inst/keymandesktop.wxs index f1e02adc9b..27e0a02e4d 100644 --- a/windows/src/desktop/inst/keymandesktop.wxs +++ b/windows/src/desktop/inst/keymandesktop.wxs @@ -50,6 +50,30 @@ + + + diff --git a/windows/src/desktop/inst/license.html b/windows/src/desktop/inst/license.html index e6bc9dc16e..c4682e2979 100644 --- a/windows/src/desktop/inst/license.html +++ b/windows/src/desktop/inst/license.html @@ -13,7 +13,7 @@

The MIT License

-

Copyright (c) 2017-2022 SIL International

+

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 diff --git a/windows/src/desktop/inst/license.rtf b/windows/src/desktop/inst/license.rtf index 2d9c288de8..25bd35b0ec 100644 Binary files a/windows/src/desktop/inst/license.rtf and b/windows/src/desktop/inst/license.rtf differ diff --git a/windows/src/desktop/kmshell/.gitignore b/windows/src/desktop/kmshell/.gitignore index 2eb6794530..c12be1e9ba 100644 --- a/windows/src/desktop/kmshell/.gitignore +++ b/windows/src/desktop/kmshell/.gitignore @@ -1,2 +1,3 @@ xml/sentry.init.js icons.res +locale/index.xml \ No newline at end of file diff --git a/windows/src/desktop/kmshell/build.sh b/windows/src/desktop/kmshell/build.sh index f4e95eadc0..03d4b786fa 100755 --- a/windows/src/desktop/kmshell/build.sh +++ b/windows/src/desktop/kmshell/build.sh @@ -45,17 +45,28 @@ function do_build() { } function do_build_data() { + builder_echo do_build_data + replaceVersionStrings_Mkver xml/sentry.init.js.in xml/sentry.init.js rm -rf "$WINDOWS_PROGRAM_APP/xml" mkdir -p "$WINDOWS_PROGRAM_APP/xml" cp -r xml/* "$WINDOWS_PROGRAM_APP/xml/" + do_build_locale_index "locale" + rm -rf "$WINDOWS_PROGRAM_APP/locale" mkdir -p "$WINDOWS_PROGRAM_APP/locale" cp -r locale/* "$WINDOWS_PROGRAM_APP/locale/" } +function do_build_locale_index() { + builder_echo "do_build_locale_index $1" + local locale_root="$1" + local locale_index="$1/index.xml" + "$DEVTOOLS" -buildlocaleindex "$locale_root" "$locale_index" +} + function do_publish() { # test that (a) linked manifest exists and correct wrap-mt -nologo -inputresource:"$WINDOWS_PROGRAM_APP/kmshell.exe" -validate_manifest diff --git a/windows/src/desktop/kmshell/locale/am-ET/strings.xml b/windows/src/desktop/kmshell/locale/am-ET/strings.xml index cb11dbc397..7f5f4f8ba7 100644 --- a/windows/src/desktop/kmshell/locale/am-ET/strings.xml +++ b/windows/src/desktop/kmshell/locale/am-ET/strings.xml @@ -884,7 +884,7 @@ - ኪይማን ማረሚያ መረጃ %LOCALAPPDATA%\Keyman\Diag\system#.etl (# ቁጥር ባለበት) ተብሎ በሚጠራው መዝገብ ውስጥ ይቀመጣል። ይህንን ፋይል ለመሰረዝ ከመሞከርዎ በፊት ከኪማን መውጣት አለብዎት። \n\nማስጠንቀቂያ: ይህ ፋይል በፍጥነት ሊያድግ ይችላል። ኢኔብሊንግ ዲበጊንግ (enabling debugging) ሲስተምዎን ሊያዘገይ ይችላል እና መደረግ ያለበት በቴክኒክ ድጋፍ ብቻ ከሆነ ነው። \n\nየግል ማስጠንቀቂያ: እባክዎን ዲበጊንግ ሎግ ፋይል ሪከርድስ (debug logfile records) ሁሉንም የሚተይቡትን ቁልፍ ጭነቶች ይመዘግባል። ዲበጊንግ ሎግ (debug log) ማስታወሻውን ለምርመራ ወይም ለምርመራ ክፍለ ጊዜ ብቻ ማብራት አለብዎት። + ኪይማን ማረሚያ መረጃ %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (# ቁጥር ባለበት) ተብሎ በሚጠራው መዝገብ ውስጥ ይቀመጣል። ይህንን ፋይል ለመሰረዝ ከመሞከርዎ በፊት ከኪማን መውጣት አለብዎት። \n\nማስጠንቀቂያ: ይህ ፋይል በፍጥነት ሊያድግ ይችላል። ኢኔብሊንግ ዲበጊንግ (enabling debugging) ሲስተምዎን ሊያዘገይ ይችላል እና መደረግ ያለበት በቴክኒክ ድጋፍ ብቻ ከሆነ ነው። \n\nየግል ማስጠንቀቂያ: እባክዎን ዲበጊንግ ሎግ ፋይል ሪከርድስ (debug logfile records) ሁሉንም የሚተይቡትን ቁልፍ ጭነቶች ይመዘግባል። ዲበጊንግ ሎግ (debug log) ማስታወሻውን ለምርመራ ወይም ለምርመራ ክፍለ ጊዜ ብቻ ማብራት አለብዎት። diff --git a/windows/src/desktop/kmshell/locale/az-AZ/strings.xml b/windows/src/desktop/kmshell/locale/az-AZ/strings.xml index 68a6aa00b9..404e51c51a 100644 --- a/windows/src/desktop/kmshell/locale/az-AZ/strings.xml +++ b/windows/src/desktop/kmshell/locale/az-AZ/strings.xml @@ -863,7 +863,7 @@ - Keyman ayıklama məlumatları %LOCALAPPDATA%\Keyman\Diag\system#.etl adlı bir günlük sənədində saxlanacaqdır (burada # bir rəqəm bilidirir). Bu faylı silməyə başlamazdan əvvəl Keyman-dan çıxmalısınız. \n\n\XƏBƏRDARLIQ: Bu fayl çox sürətlə böyüyə bilər. Həta ayıklamanın aktivləşdirilməsi sisteminizi ləngidə bilər və yalnız texniki dəstək tərəfindən tövsiyə edildikdə edilməlidir.\n\nGİZLİLİK XƏBƏRDARLIĞI: Nəzərə alın ki, ayıklama qeyd sənədində yazdığınız bütün vuruşları qeyd edir Hata ayıklama jurnalını yalnız bir ayıklama və ya diaqnostik seans müddətində açmalısınız. + Keyman ayıklama məlumatları %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl adlı bir günlük sənədində saxlanacaqdır (burada # bir rəqəm bilidirir). Bu faylı silməyə başlamazdan əvvəl Keyman-dan çıxmalısınız. \n\n\XƏBƏRDARLIQ: Bu fayl çox sürətlə böyüyə bilər. Həta ayıklamanın aktivləşdirilməsi sisteminizi ləngidə bilər və yalnız texniki dəstək tərəfindən tövsiyə edildikdə edilməlidir.\n\nGİZLİLİK XƏBƏRDARLIĞI: Nəzərə alın ki, ayıklama qeyd sənədində yazdığınız bütün vuruşları qeyd edir Hata ayıklama jurnalını yalnız bir ayıklama və ya diaqnostik seans müddətində açmalısınız. diff --git a/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml b/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml index 76aaaac053..28cdb065aa 100644 --- a/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/bwr-NG/strings.xml @@ -864,7 +864,7 @@ Keyboard natǝ ga ana hara kǝthlǝr kari akwa Windows. Keyboardyeri ar Keyman a - Mda ata ɗuwa labar na ar debug na ar Keyman akwa log fauil natǝ mda ana kǝgani %LOCALAPPDATA%\Keyman\Diag\system#.etl (natǝ # lamba laga). Kara lǝɓǝla akwa Keyman kwapu gǝ ngkǝrtǝ cijimta fayil ngǝni.\n\nNDƏKƏR THLƏM: Fayil ngǝni ata mbiku ka nzi ola akwa kuta saka rakka. Nam debugging ka tsa haraata tsoktǝ vu ka system ni aɗi kǝthlǝr ka huva wa antǝ ka mdǝ hara ngǝni tǝng sai ka dlara na ar mallǝm na sǝndǝ suni ɓɗaku.\n\nNƏKƏR THLƏM ATAR SU NA ASHIR: Kara sǝnda abbǝr wa fayil na ar debug ata sǝnda ka kǝtǝ shanga suayeri natǝgǝ ntsari saka tǝ ga akwa hara taipin. Kara kǝlar dubu tǝng sai saka tǝ ga akwa hara debugging wala saka tǝ ga akwa namɓǝla suayeri. + Mda ata ɗuwa labar na ar debug na ar Keyman akwa log fauil natǝ mda ana kǝgani %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (natǝ # lamba laga). Kara lǝɓǝla akwa Keyman kwapu gǝ ngkǝrtǝ cijimta fayil ngǝni.\n\nNDƏKƏR THLƏM: Fayil ngǝni ata mbiku ka nzi ola akwa kuta saka rakka. Nam debugging ka tsa haraata tsoktǝ vu ka system ni aɗi kǝthlǝr ka huva wa antǝ ka mdǝ hara ngǝni tǝng sai ka dlara na ar mallǝm na sǝndǝ suni ɓɗaku.\n\nNƏKƏR THLƏM ATAR SU NA ASHIR: Kara sǝnda abbǝr wa fayil na ar debug ata sǝnda ka kǝtǝ shanga suayeri natǝgǝ ntsari saka tǝ ga akwa hara taipin. Kara kǝlar dubu tǝng sai saka tǝ ga akwa hara debugging wala saka tǝ ga akwa namɓǝla suayeri. diff --git a/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml b/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml index 454936a6ee..dd994697df 100644 --- a/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/ckl-NG/strings.xml @@ -884,7 +884,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman debug information will be stored in a log file called %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. + Keyman debug information will be stored in a log file called %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. diff --git a/windows/src/desktop/kmshell/locale/de/strings.xml b/windows/src/desktop/kmshell/locale/de/strings.xml index aa4f02dd81..90a2e26638 100644 --- a/windows/src/desktop/kmshell/locale/de/strings.xml +++ b/windows/src/desktop/kmshell/locale/de/strings.xml @@ -883,7 +883,7 @@ - Keyman Debug-Informationen werden in einer Protokolldatei mit dem Namen %LOCALAPPDATA%\Keyman\Diag\system#.etl gespeichert (wobei # eine Nummer ist). Sie sollten Keyman beenden, bevor Sie diese Datei löschen.\n\nWARNUNG: Diese Datei kann sehr schnell groß werden. Das Aktivieren der Debug-Protokollierung kann Ihr System verlangsamen und sollte nur auf Anweisung durch den technischen Support erfolgen.\n\nDATENSCHUTZ-HINWEIS: Bitte beachten Sie, dass die Debug-Logdatei alle von Ihnen eingegebenen Tastenanschläge aufzeichnet. Sie sollten das Debug-Log nur für die Dauer einer Debugging- oder Diagnosesitzung aktivieren. + Keyman Debug-Informationen werden in einer Protokolldatei mit dem Namen %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl gespeichert (wobei # eine Nummer ist). Sie sollten Keyman beenden, bevor Sie diese Datei löschen.\n\nWARNUNG: Diese Datei kann sehr schnell groß werden. Das Aktivieren der Debug-Protokollierung kann Ihr System verlangsamen und sollte nur auf Anweisung durch den technischen Support erfolgen.\n\nDATENSCHUTZ-HINWEIS: Bitte beachten Sie, dass die Debug-Logdatei alle von Ihnen eingegebenen Tastenanschläge aufzeichnet. Sie sollten das Debug-Log nur für die Dauer einer Debugging- oder Diagnosesitzung aktivieren. diff --git a/windows/src/desktop/kmshell/locale/el-polyton/strings.xml b/windows/src/desktop/kmshell/locale/el-polyton/strings.xml index 90987532a2..f9699f61de 100644 --- a/windows/src/desktop/kmshell/locale/el-polyton/strings.xml +++ b/windows/src/desktop/kmshell/locale/el-polyton/strings.xml @@ -928,7 +928,7 @@ - Οἱ πληροφορίες ἐντοπισμοῦ σφαλμάτων τοῦ Keyman θὰ ἀποθηκεύονται σὲ ἀρχεῖο-ἀναφορὰ μὲ τὸ ὄνομα %LOCALAPPDATA%\Keyman\Diag\system#.etl (ὅπου # εἶναι ἀριθμός). >>Γιὰ νὰ διαγράψετε αὐτὸ τὸ ἀρχεῖο, πρέπει νὰ βγῆτε ἀπὸ τὸ Keyman.\n\n\ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αὐτὸ τὸ ἀρχεῖο μπορεῖ νὰ μεγαλώσει πολὺ γρήγορα. Ἡ ἐνεργοποίηση τοῦ ἐντοπισμοῦ σφαλμάτων μπορεῖ νὰ καταστήσει πολὺ βραδύτερο τὸ σύστημά σας, γι\' αὐτὸ θὰ πρέπει νὰ ἐκτελεῖται μόνον κατόπιν συστάσεως τῆς τεχνικῆς ὑποστήριξης.\n\nΠΡΟΕΙΔΟΠΟΙΗΣΗ ΙΔΙΩΤΙΚΟΤΗΤΟΣ: Παρακαλοῦμε σημειῶστε ὅτι στὸ ἀρχεῖο καταγραφῆς προβλημάτων ἀποθηκεύεται ὅ,τι πληκτρολογεῖτε στὸ πληκτρολόγιο. Ἡ ἐνεργοποίηση τοῦ ἀρχείου καταγραφῆς θὰ πρέπει νὰ γίνεται μόνον ὅσο διαρκεῖ μία ἀναζήτηση ἢ διάγνωση προβλήματος. + Οἱ πληροφορίες ἐντοπισμοῦ σφαλμάτων τοῦ Keyman θὰ ἀποθηκεύονται σὲ ἀρχεῖο-ἀναφορὰ μὲ τὸ ὄνομα %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (ὅπου # εἶναι ἀριθμός). >>Γιὰ νὰ διαγράψετε αὐτὸ τὸ ἀρχεῖο, πρέπει νὰ βγῆτε ἀπὸ τὸ Keyman.\n\n\ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Αὐτὸ τὸ ἀρχεῖο μπορεῖ νὰ μεγαλώσει πολὺ γρήγορα. Ἡ ἐνεργοποίηση τοῦ ἐντοπισμοῦ σφαλμάτων μπορεῖ νὰ καταστήσει πολὺ βραδύτερο τὸ σύστημά σας, γι\' αὐτὸ θὰ πρέπει νὰ ἐκτελεῖται μόνον κατόπιν συστάσεως τῆς τεχνικῆς ὑποστήριξης.\n\nΠΡΟΕΙΔΟΠΟΙΗΣΗ ΙΔΙΩΤΙΚΟΤΗΤΟΣ: Παρακαλοῦμε σημειῶστε ὅτι στὸ ἀρχεῖο καταγραφῆς προβλημάτων ἀποθηκεύεται ὅ,τι πληκτρολογεῖτε στὸ πληκτρολόγιο. Ἡ ἐνεργοποίηση τοῦ ἀρχείου καταγραφῆς θὰ πρέπει νὰ γίνεται μόνον ὅσο διαρκεῖ μία ἀναζήτηση ἢ διάγνωση προβλήματος. diff --git a/windows/src/desktop/kmshell/locale/es-419/strings.xml b/windows/src/desktop/kmshell/locale/es-419/strings.xml index 43c160151a..61cf66cb46 100644 --- a/windows/src/desktop/kmshell/locale/es-419/strings.xml +++ b/windows/src/desktop/kmshell/locale/es-419/strings.xml @@ -948,7 +948,7 @@ - La información de depuración de Keyman se almacenará en un archivo log llamado %LOCALAPPDATA%\Keyman\Diag\system#.etl (donde # es un número). Debes salir de Keyman antes de intentar eliminar este archivo.\n\n\ADVERTENCIA: este archivo puede crecer muy rápidamente. Habilitar la depuración puede ralentizar su sistema y sólo deberías hacerlo si te lo aconseja el soporte técnico.\n\nADVERTENCIA DE PRIVACIDAD: ten en cuenta que el archivo log de depuración registra todas las teclas que escribes. Sólo debes activar el log de depuración durante una sesión de depuración o diagnóstico. + La información de depuración de Keyman se almacenará en un archivo log llamado %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (donde # es un número). Debes salir de Keyman antes de intentar eliminar este archivo.\n\n\ADVERTENCIA: este archivo puede crecer muy rápidamente. Habilitar la depuración puede ralentizar su sistema y sólo deberías hacerlo si te lo aconseja el soporte técnico.\n\nADVERTENCIA DE PRIVACIDAD: ten en cuenta que el archivo log de depuración registra todas las teclas que escribes. Sólo debes activar el log de depuración durante una sesión de depuración o diagnóstico. diff --git a/windows/src/desktop/kmshell/locale/es-ES/strings.xml b/windows/src/desktop/kmshell/locale/es-ES/strings.xml index 711760afc9..639debd6fd 100644 --- a/windows/src/desktop/kmshell/locale/es-ES/strings.xml +++ b/windows/src/desktop/kmshell/locale/es-ES/strings.xml @@ -948,7 +948,7 @@ - La información de depuración de Keyman se almacenará en un archivo de registro llamado %LOCALAPPDATA%\Keyman\Diag\system#.etl (donde # es un número). Debe salir de Keyman antes de intentar borrar este archivo.\n\n ADVERTENCIA: Este archivo puede crecer muy rápidamente. La activación de la depuración puede ralentizar el sistema y sólo debe hacerse si así lo aconseja el servicio de asistencia técnica.\n\nADVERTENCIA DE PRIVACIDAD: Tenga en cuenta que el archivo de registro de depuración registra todas las pulsaciones de teclas que se realizan. Sólo debe activar el registro de depuración durante una sesión de depuración o diagnóstico. + La información de depuración de Keyman se almacenará en un archivo de registro llamado %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (donde # es un número). Debe salir de Keyman antes de intentar borrar este archivo.\n\n ADVERTENCIA: Este archivo puede crecer muy rápidamente. La activación de la depuración puede ralentizar el sistema y sólo debe hacerse si así lo aconseja el servicio de asistencia técnica.\n\nADVERTENCIA DE PRIVACIDAD: Tenga en cuenta que el archivo de registro de depuración registra todas las pulsaciones de teclas que se realizan. Sólo debe activar el registro de depuración durante una sesión de depuración o diagnóstico. diff --git a/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml b/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml index f74c2499bb..119a12f37b 100644 --- a/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml +++ b/windows/src/desktop/kmshell/locale/ff-ZA/strings.xml @@ -888,10 +888,10 @@ Jokku\? - L\'information de débogage de Keyman sera stockée sur le Bureau dans un fichier texte nommé %LOCALAPPDATA%\Keyman\Diag\system#.etl. Vous devez quitter Keyman avant de lire ou de supprimer ce fichier. - + L\'information de débogage de Keyman sera stockée sur le Bureau dans un fichier texte nommé %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl. Vous devez quitter Keyman avant de lire ou de supprimer ce fichier. + ATTENTION. Ce fichier peut devenir important très rapidement. Activer le débogage ralentira le sytème et ne doit être fait qu\'à la demande du support Tavultesoft. - + CONFIDENTIALITÉ: Veuillez noter que le fichier journal de débogage enregistre toutes les frappes que vous exécutez. Vous ne devez activer le journal de débogage que pendant la durée de la session de débogage ou de diagnostic, et vous devez effacer le fichier [Bureau]\keymanlog\system.log file dès que possible. diff --git a/windows/src/desktop/kmshell/locale/fr/strings.xml b/windows/src/desktop/kmshell/locale/fr/strings.xml index 0274bf3f41..2ed3f704bd 100644 --- a/windows/src/desktop/kmshell/locale/fr/strings.xml +++ b/windows/src/desktop/kmshell/locale/fr/strings.xml @@ -883,7 +883,7 @@ - Les informations de débogage de Keyman seront stockées dans un fichier journal appelé %LOCALAPPDATA%\Keyman\Diag\system#.etl (où # est un nombre). Vous devriez quitter Keyman avant d\'essayer de supprimer ce fichier.\n\n\AVERTISSEMENT : Ce fichier peut grossir très rapidement. L\'activation du débogage peut ralentir votre système et ne devrait se faire que si le support technique le conseille.\n\nAVERTISSEMENT DE CONFIDENTIALITÉ : Veuillez noter que le fichier journal de débogage enregistre tous les traits de touches que vous tapez. Vous ne devriez activer le journal de débogage que pour la durée d\'une session de débogage ou de diagnostic. + Les informations de débogage de Keyman seront stockées dans un fichier journal appelé %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (où # est un nombre). Vous devriez quitter Keyman avant d\'essayer de supprimer ce fichier.\n\n\AVERTISSEMENT : Ce fichier peut grossir très rapidement. L\'activation du débogage peut ralentir votre système et ne devrait se faire que si le support technique le conseille.\n\nAVERTISSEMENT DE CONFIDENTIALITÉ : Veuillez noter que le fichier journal de débogage enregistre tous les traits de touches que vous tapez. Vous ne devriez activer le journal de débogage que pour la durée d\'une session de débogage ou de diagnostic. diff --git a/windows/src/desktop/kmshell/locale/hia-NG/strings.xml b/windows/src/desktop/kmshell/locale/hia-NG/strings.xml index ed2542b71b..bb61880158 100644 --- a/windows/src/desktop/kmshell/locale/hia-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/hia-NG/strings.xml @@ -864,7 +864,7 @@ keyboard unda da mogka thlina nda Windows. Keyman keyboards na da spat sosaya t - Keyman t klunt tina kol ɗughwana habara da fata an log file lo hogal %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). Da thlavataka t Keyman kajuwa tikanta k masat tana file na.\n\n\WARNING: Nana file nana da guluk kada kudeɗ kudeɗa. Da klunt tina kol ɗughwana kaɗa da mogan hankalɗe t kompitagha nda kawai da mog thlina vita kwara kwara la sinanta mog thlina technical support.\n\nPRIVACY WARNING: Gamsa asinana klunt tina kol ɗughwana an logfile da fat sma keystrokes unda vindataka. Kawai da gunataka t debug log anga sarta klun tina kol ɗughwana ko sarta naghata. + Keyman t klunt tina kol ɗughwana habara da fata an log file lo hogal %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). Da thlavataka t Keyman kajuwa tikanta k masat tana file na.\n\n\WARNING: Nana file nana da guluk kada kudeɗ kudeɗa. Da klunt tina kol ɗughwana kaɗa da mogan hankalɗe t kompitagha nda kawai da mog thlina vita kwara kwara la sinanta mog thlina technical support.\n\nPRIVACY WARNING: Gamsa asinana klunt tina kol ɗughwana an logfile da fat sma keystrokes unda vindataka. Kawai da gunataka t debug log anga sarta klun tina kol ɗughwana ko sarta naghata. diff --git a/windows/src/desktop/kmshell/locale/id-ID/strings.xml b/windows/src/desktop/kmshell/locale/id-ID/strings.xml index 50f2fe6979..b99c489289 100644 --- a/windows/src/desktop/kmshell/locale/id-ID/strings.xml +++ b/windows/src/desktop/kmshell/locale/id-ID/strings.xml @@ -863,7 +863,7 @@ - Informasi debug Keyman akan disimpan di %LOCALAPPDATA%\Keyman\Diag\system#.etl (# adalah angka). Anda perlu keluar dari Keynman sebelum mencoba menghapus berkas ini. + Informasi debug Keyman akan disimpan di %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (# adalah angka). Anda perlu keluar dari Keynman sebelum mencoba menghapus berkas ini. diff --git a/windows/src/desktop/kmshell/locale/it-IT/strings.xml b/windows/src/desktop/kmshell/locale/it-IT/strings.xml index 0d728cfb16..aaa1a928ab 100644 --- a/windows/src/desktop/kmshell/locale/it-IT/strings.xml +++ b/windows/src/desktop/kmshell/locale/it-IT/strings.xml @@ -949,7 +949,7 @@ che usi in Windows. Le tastiere Keyman si adatteranno automaticamente al layout - Le informazioni di debug di Keyman saranno memorizzate in un file di log chiamato %LOCALAPPDATA%\Keyman\Diag\system#.etl (dove # è un numero). Devi uscire da Keyman prima di tentare di eliminare questo file.\n\n\ATTENZIONE: Questo file può crescere molto velocemente. Abilitare il debug potrebbe rallentare il sistema e dovrebbe essere fatto solo se consigliato dal supporto tecnico.\n\nATTENZIONE PRIVACY: Si prega di notare che il file di registro di debug registra tutte le sequenze di tasti che digitate. È necessario attivare il registro di debug solo per la durata di una sessione di debug o di diagnostica. + Le informazioni di debug di Keyman saranno memorizzate in un file di log chiamato %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (dove # è un numero). Devi uscire da Keyman prima di tentare di eliminare questo file.\n\n\ATTENZIONE: Questo file può crescere molto velocemente. Abilitare il debug potrebbe rallentare il sistema e dovrebbe essere fatto solo se consigliato dal supporto tecnico.\n\nATTENZIONE PRIVACY: Si prega di notare che il file di registro di debug registra tutte le sequenze di tasti che digitate. È necessario attivare il registro di debug solo per la durata di una sessione di debug o di diagnostica. diff --git a/windows/src/desktop/kmshell/locale/km-KH/strings.xml b/windows/src/desktop/kmshell/locale/km-KH/strings.xml index ac6ce921ce..16deccb41d 100644 --- a/windows/src/desktop/kmshell/locale/km-KH/strings.xml +++ b/windows/src/desktop/kmshell/locale/km-KH/strings.xml @@ -948,7 +948,7 @@ - ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា​ Keyman នឹង​ត្រូវ​បាន​រក្សា​ទុក​ក្នុង​ឯកសារ​កំណត់ហេតុ​ដែល​មាន​ឈ្មោះ​ថា %LOCALAPPDATA%\Keyman\Diag\system#.etl (# ជា​តួ​លេខ)។ អ្នក​គួរ​តែ​បិទ Keyman មុន​ពេល​លុប​ឯកសារ​នេះ។\n\nការព្រមាន៖ ឯកសារ​នេះ​អាច​មាន​ទំហំ​ធំទៅៗ យ៉ាង​លឿន។ ការបើក​ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា (debugging) អាច​ធ្វើ​ឱ្យ​កុំព្យូទ័រ​របស់​អ្នក​ដើរ​យឺត ដូច្នេះ​អ្នក​គួរ​តែ​បើក​វា​តែ​នៅ​ពេល​ដែល​អ្នក​ជំនាញ​បច្ចេកទេសប្រាប់​អ្នក​ឱ្យបើក​ប៉ុណ្ណោះ។\n\nការព្រមាន​អំពី​ឯកជនភាព៖ សូម​ចំណាំ​ថា​ឯកសារកំណត់ហេតុ​ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា​រក្សា​ទុក​គ្រប់​ការចុចទាំង​អស់​របស់​អ្នក។ អ្នក​គួរ​បើក​វា​តែ​នៅ​ក្នុង​អំឡុង​ពេល​ដែល​អ្នក​កំពុង​ដោះស្រាយ​បញ្ហា ឬ​វិភាគ​បញ្ហា​ប៉ុណ្ណោះ។ + ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា​ Keyman នឹង​ត្រូវ​បាន​រក្សា​ទុក​ក្នុង​ឯកសារ​កំណត់ហេតុ​ដែល​មាន​ឈ្មោះ​ថា %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (# ជា​តួ​លេខ)។ អ្នក​គួរ​តែ​បិទ Keyman មុន​ពេល​លុប​ឯកសារ​នេះ។\n\nការព្រមាន៖ ឯកសារ​នេះ​អាច​មាន​ទំហំ​ធំទៅៗ យ៉ាង​លឿន។ ការបើក​ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា (debugging) អាច​ធ្វើ​ឱ្យ​កុំព្យូទ័រ​របស់​អ្នក​ដើរ​យឺត ដូច្នេះ​អ្នក​គួរ​តែ​បើក​វា​តែ​នៅ​ពេល​ដែល​អ្នក​ជំនាញ​បច្ចេកទេសប្រាប់​អ្នក​ឱ្យបើក​ប៉ុណ្ណោះ។\n\nការព្រមាន​អំពី​ឯកជនភាព៖ សូម​ចំណាំ​ថា​ឯកសារកំណត់ហេតុ​ព័ត៌មាន​សម្រាប់​ដោះស្រាយ​បញ្ហា​រក្សា​ទុក​គ្រប់​ការចុចទាំង​អស់​របស់​អ្នក។ អ្នក​គួរ​បើក​វា​តែ​នៅ​ក្នុង​អំឡុង​ពេល​ដែល​អ្នក​កំពុង​ដោះស្រាយ​បញ្ហា ឬ​វិភាគ​បញ្ហា​ប៉ុណ្ណោះ។ diff --git a/windows/src/desktop/kmshell/locale/kn/strings.xml b/windows/src/desktop/kmshell/locale/kn/strings.xml index e013bff77f..0425ab7a58 100644 --- a/windows/src/desktop/kmshell/locale/kn/strings.xml +++ b/windows/src/desktop/kmshell/locale/kn/strings.xml @@ -883,7 +883,7 @@ - ಕೀಮ್ಯಾನ್ ಡೀಬಗ್ ಮಾಹಿತಿಯನ್ನು ನಿಮ್ಮ ಡೆಸ್ಕ್‌ಟಾಪ್‌ನಲ್ಲಿ %LOCALAPPDATA%\Keyman\Diag\system#.etl (ಇಲ್ಲಿ # ಒಂದು ಸಂಖ್ಯೆ) ಕರೆಯಲಾಗುವ ಪಠ್ಯ ಫೈಲ್‌ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ. ಈ ಫೈಲ್ ಅನ್ನು ಓದುವ ಅಥವಾ ಅಳಿಸುವ ಮೊದಲು ನೀವು ಕೀಮ್ಯಾನ್ ನಿರ್ಗಮಿಸಬೇಕು. \n\nಎಚ್ಚರಿಕೆ: ಈ ಫೈಲ್ ಬಹಳ ಬೇಗನೆ ದೊಡ್ಡ ಗಾತ್ರವಾಗಿ ಬೆಳೆಯುತ್ತದೆ. ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ನಿಮ್ಮ ಸಿಸ್ಟಮ್ ಅನ್ನು ನಿಧಾನಗೊಳಿಸುತ್ತದೆ ಹಾಗಾಗಿ \"ತಾಂತ್ರಿಕ ಬೆಂಬಲ\"ದಿಂದ ಸಲಹೆ ನೀಡಿದರೆ ಮಾತ್ರ ಇದನ್ನು ಸ್ಥಾಪಿಸಬೇಕು. \n\nಗೌಪ್ಯತೆಯ ಎಚ್ಚರಿಕೆ: ನೀವು ಟೈಪ್ ಮಾಡುವ ಎಲ್ಲಾ ಕೀಲಿಗಳನ್ನು ಡೀಬಗ್ ಲಾಗ್‌ಫೈಲ್ ದಾಖಲಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ದಯವಿಟ್ಟು ಗಮನಿಸಿ. ಡೀಬಗ್ ಮಾಡುವಿಕೆ ಅಥವಾ ಡಯಗ್ನೊಸ್ಟಿಕ್ ಆಗುವ ಅವಧಿಯಲ್ಲಿ ಮಾತ್ರ ನೀವು ಡೀಬಗ್ ಸ್ಥಾಪಿಸಬೇಕು. + ಕೀಮ್ಯಾನ್ ಡೀಬಗ್ ಮಾಹಿತಿಯನ್ನು ನಿಮ್ಮ ಡೆಸ್ಕ್‌ಟಾಪ್‌ನಲ್ಲಿ %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (ಇಲ್ಲಿ # ಒಂದು ಸಂಖ್ಯೆ) ಕರೆಯಲಾಗುವ ಪಠ್ಯ ಫೈಲ್‌ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗುತ್ತದೆ. ಈ ಫೈಲ್ ಅನ್ನು ಓದುವ ಅಥವಾ ಅಳಿಸುವ ಮೊದಲು ನೀವು ಕೀಮ್ಯಾನ್ ನಿರ್ಗಮಿಸಬೇಕು. \n\nಎಚ್ಚರಿಕೆ: ಈ ಫೈಲ್ ಬಹಳ ಬೇಗನೆ ದೊಡ್ಡ ಗಾತ್ರವಾಗಿ ಬೆಳೆಯುತ್ತದೆ. ಡೀಬಗ್ ಮಾಡುವುದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವುದರಿಂದ ನಿಮ್ಮ ಸಿಸ್ಟಮ್ ಅನ್ನು ನಿಧಾನಗೊಳಿಸುತ್ತದೆ ಹಾಗಾಗಿ \"ತಾಂತ್ರಿಕ ಬೆಂಬಲ\"ದಿಂದ ಸಲಹೆ ನೀಡಿದರೆ ಮಾತ್ರ ಇದನ್ನು ಸ್ಥಾಪಿಸಬೇಕು. \n\nಗೌಪ್ಯತೆಯ ಎಚ್ಚರಿಕೆ: ನೀವು ಟೈಪ್ ಮಾಡುವ ಎಲ್ಲಾ ಕೀಲಿಗಳನ್ನು ಡೀಬಗ್ ಲಾಗ್‌ಫೈಲ್ ದಾಖಲಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ದಯವಿಟ್ಟು ಗಮನಿಸಿ. ಡೀಬಗ್ ಮಾಡುವಿಕೆ ಅಥವಾ ಡಯಗ್ನೊಸ್ಟಿಕ್ ಆಗುವ ಅವಧಿಯಲ್ಲಿ ಮಾತ್ರ ನೀವು ಡೀಬಗ್ ಸ್ಥಾಪಿಸಬೇಕು. diff --git a/windows/src/desktop/kmshell/locale/kr-NG/strings.xml b/windows/src/desktop/kmshell/locale/kr-NG/strings.xml index 6bb0b1bf79..ce4aee3edd 100644 --- a/windows/src/desktop/kmshell/locale/kr-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/kr-NG/strings.xml @@ -864,7 +864,7 @@ keyboard do suro Windows ye dǝlan faidatǝmin ma. Keyman keyboards dǝye adai - Keyman debug information will be stored in a log file called %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. + Keyman debug information will be stored in a log file called %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. diff --git a/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml index 608abb0f26..6f9c21529a 100644 --- a/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/mfi-NG/strings.xml @@ -864,7 +864,7 @@ keyboard na ká ga slera anikwa na am Windows. Keyman keyboards anuga maga sler - Keyman debug labare ŋanna anuga a de puwa am log file a ɗavan %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). Ká nuga shigashe am Keyman kabu ká gwada gagga file.\n\n\WARNING: File ŋanna anuga nye ba ƴaike a ba at watsire. Magave debugging anaba gulla maga slera system antara sawari a sawa aza nda technical support.\n\nPRIVACY WARNING: Tazlaŋa diyasdiya gé kini debug logfile a maga rakodini baɗumma keystrokes ŋanna ká vinda ká. Ká kunna debug log gé naza lokte debugging mtu diagnostic session. + Keyman debug labare ŋanna anuga a de puwa am log file a ɗavan %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). Ká nuga shigashe am Keyman kabu ká gwada gagga file.\n\n\WARNING: File ŋanna anuga nye ba ƴaike a ba at watsire. Magave debugging anaba gulla maga slera system antara sawari a sawa aza nda technical support.\n\nPRIVACY WARNING: Tazlaŋa diyasdiya gé kini debug logfile a maga rakodini baɗumma keystrokes ŋanna ká vinda ká. Ká kunna debug log gé naza lokte debugging mtu diagnostic session. diff --git a/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml b/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml index 8dda7d33c2..72b2a4f362 100644 --- a/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml +++ b/windows/src/desktop/kmshell/locale/mrt-NG/strings.xml @@ -863,7 +863,7 @@ - Labar nur Keyman debug mji ara ɗuwana nda akwa log file %LOCALAPPDATA%\Keyman\Diag\system#.etl (iviku # aga lamba). kuga ngeri Keyman kabi kukulna file.\n\n\WARNING: File ku acitu hu ŋga kakaɗu. Ngeri debugging acitu fia kwahir nasara ginyi kija na saɗu baɗa tsu kuga iu thlir duri ma ga thlia sawari ara technical support.\n\nPRIVACY WARNING: Manki kuga sini debug logfile naja vur iu records nur ca keystrokes dugu a ruɓutsini. Kuga pahu debug log anu odikura latu nur debugging akiya mai nur diagnostic. + Labar nur Keyman debug mji ara ɗuwana nda akwa log file %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (iviku # aga lamba). kuga ngeri Keyman kabi kukulna file.\n\n\WARNING: File ku acitu hu ŋga kakaɗu. Ngeri debugging acitu fia kwahir nasara ginyi kija na saɗu baɗa tsu kuga iu thlir duri ma ga thlia sawari ara technical support.\n\nPRIVACY WARNING: Manki kuga sini debug logfile naja vur iu records nur ca keystrokes dugu a ruɓutsini. Kuga pahu debug log anu odikura latu nur debugging akiya mai nur diagnostic. diff --git a/windows/src/desktop/kmshell/locale/nl-NL/strings.xml b/windows/src/desktop/kmshell/locale/nl-NL/strings.xml index 5c14f42c13..c6e48c924e 100644 --- a/windows/src/desktop/kmshell/locale/nl-NL/strings.xml +++ b/windows/src/desktop/kmshell/locale/nl-NL/strings.xml @@ -864,7 +864,7 @@ dat u in Windows gebruikt. Keyman toetsenborden worden automatisch aangepast aan - Keyman debug informatie zal worden opgeslagen in een log bestand genaamd %LOCALAPPDATA%\Keyman\Diag\system#.etl (waar # een nummer is). U moet Keyman afsluiten voordat u probeert dit bestand te verwijderen.\n\n\WAARSCHUWING: Dit bestand kan zeer snel groot worden. Het inschakelen van debugging kan uw systeem vertragen en moet alleen worden gedaan als het wordt aangeraden door technische ondersteuning.\n\nPRIVACY WAARSCHUWING: Houd er rekening mee dat het debuglogbestand alle toetsaanslagen opslaat die u typt. U moet alleen het debuglogboek inschakelen voor de duur van een foutopsporings- of diagnostische sessie. + Keyman debug informatie zal worden opgeslagen in een log bestand genaamd %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (waar # een nummer is). U moet Keyman afsluiten voordat u probeert dit bestand te verwijderen.\n\n\WAARSCHUWING: Dit bestand kan zeer snel groot worden. Het inschakelen van debugging kan uw systeem vertragen en moet alleen worden gedaan als het wordt aangeraden door technische ondersteuning.\n\nPRIVACY WAARSCHUWING: Houd er rekening mee dat het debuglogbestand alle toetsaanslagen opslaat die u typt. U moet alleen het debuglogboek inschakelen voor de duur van een foutopsporings- of diagnostische sessie. diff --git a/windows/src/desktop/kmshell/locale/pl-PL/strings.xml b/windows/src/desktop/kmshell/locale/pl-PL/strings.xml index b815f87e7d..d6a75be6e9 100644 --- a/windows/src/desktop/kmshell/locale/pl-PL/strings.xml +++ b/windows/src/desktop/kmshell/locale/pl-PL/strings.xml @@ -864,7 +864,7 @@ której używasz w systemie Windows. Klawiatury Keyman automatycznie dostosują - Informacje debugowania Keyman będą przechowywane w pliku dziennika o nazwie %LOCALAPPDATA%\Keyman\Diag\system#.etl (gdzie # jest liczbą). Powinieneś wyjść z Keyman zanim spróbujesz usunąć ten plik.\n\n\UWAGA: Ten plik może się rozwinąć bardzo szybko. Włączenie debugowania może spowolnić Twój system i powinno być wykonane tylko wtedy, gdy zostanie to zalecone przez wsparcie techniczne.\n\nOSTRZEŻENIE PRYWATNE: Pamiętaj, że plik debugowania zapisuje wszystkie klawisze które wpisujesz. Powinieneś włączyć tylko dziennik debugowania na czas trwania sesji debugowania lub diagnostyki. + Informacje debugowania Keyman będą przechowywane w pliku dziennika o nazwie %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (gdzie # jest liczbą). Powinieneś wyjść z Keyman zanim spróbujesz usunąć ten plik.\n\n\UWAGA: Ten plik może się rozwinąć bardzo szybko. Włączenie debugowania może spowolnić Twój system i powinno być wykonane tylko wtedy, gdy zostanie to zalecone przez wsparcie techniczne.\n\nOSTRZEŻENIE PRYWATNE: Pamiętaj, że plik debugowania zapisuje wszystkie klawisze które wpisujesz. Powinieneś włączyć tylko dziennik debugowania na czas trwania sesji debugowania lub diagnostyki. diff --git a/windows/src/desktop/kmshell/locale/pt-BR/strings.xml b/windows/src/desktop/kmshell/locale/pt-BR/strings.xml index 85a112112a..d3dd5437c8 100644 --- a/windows/src/desktop/kmshell/locale/pt-BR/strings.xml +++ b/windows/src/desktop/kmshell/locale/pt-BR/strings.xml @@ -884,7 +884,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman debug information will be stored in a log file called %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. + Keyman debug information will be stored in a log file called %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. diff --git a/windows/src/desktop/kmshell/locale/pt-PT/strings.xml b/windows/src/desktop/kmshell/locale/pt-PT/strings.xml index d7a37da3dc..6fa51cf467 100644 --- a/windows/src/desktop/kmshell/locale/pt-PT/strings.xml +++ b/windows/src/desktop/kmshell/locale/pt-PT/strings.xml @@ -913,7 +913,7 @@ - As informações de depuração do keyman serão armazenadas em um ficheiro de log chamado %LOCALAPPDATA%\Keyman\Diag\system#.etl (onde # é um número). Deverá sair do Keyman antes de tentar excluir este ficheiro.\n\n\AVISO: Este ficheiro pode crescer grande rapidamente. Permitir a depuração pode tornar o sistema mais lento e só deve ser feito se recomendado pelo suporte técnico.\n\nATENÇÃO: Por favor, note que o ficheiro de log de depuração registra todos os traços de tecla que digita. Só deve ativar o log de depuração durante uma sessão de diagnóstico ou depuração. + As informações de depuração do keyman serão armazenadas em um ficheiro de log chamado %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (onde # é um número). Deverá sair do Keyman antes de tentar excluir este ficheiro.\n\n\AVISO: Este ficheiro pode crescer grande rapidamente. Permitir a depuração pode tornar o sistema mais lento e só deve ser feito se recomendado pelo suporte técnico.\n\nATENÇÃO: Por favor, note que o ficheiro de log de depuração registra todos os traços de tecla que digita. Só deve ativar o log de depuração durante uma sessão de diagnóstico ou depuração. diff --git a/windows/src/desktop/kmshell/locale/ru-RU/strings.xml b/windows/src/desktop/kmshell/locale/ru-RU/strings.xml index 426162225c..4fdf78818a 100644 --- a/windows/src/desktop/kmshell/locale/ru-RU/strings.xml +++ b/windows/src/desktop/kmshell/locale/ru-RU/strings.xml @@ -884,7 +884,7 @@ - Информация о отладочном ключе будет храниться в файле журнала %LOCALAPPDATA%\Keyman\Diag\system#.etl (номер которого #). Вы должны выйти из Keyman перед попыткой удалить этот файл.\n\n\ПРЕДУПРЕЖДЕНИЕ: Этот файл может увеличиться очень быстро. Включение отладки может замедлить работу вашей системы и только при условии технической поддержки.\n\nПРЕДУПРЕЖДЕНИЕ ПРИВЕДЕНИЯ: Обратите внимание, что лог файла отладки записывает все нажатия клавиш, которые вы набираете. Вы должны включить журнал отладки только на время отладки или диагностики. + Информация о отладочном ключе будет храниться в файле журнала %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (номер которого #). Вы должны выйти из Keyman перед попыткой удалить этот файл.\n\n\ПРЕДУПРЕЖДЕНИЕ: Этот файл может увеличиться очень быстро. Включение отладки может замедлить работу вашей системы и только при условии технической поддержки.\n\nПРЕДУПРЕЖДЕНИЕ ПРИВЕДЕНИЯ: Обратите внимание, что лог файла отладки записывает все нажатия клавиш, которые вы набираете. Вы должны включить журнал отладки только на время отладки или диагностики. diff --git a/windows/src/desktop/kmshell/locale/shu-latn/strings.xml b/windows/src/desktop/kmshell/locale/shu-latn/strings.xml index 62de6067a1..06c95b9f6d 100644 --- a/windows/src/desktop/kmshell/locale/shu-latn/strings.xml +++ b/windows/src/desktop/kmshell/locale/shu-latn/strings.xml @@ -883,7 +883,7 @@ - Khabar hana debug hana Keyman bihuttu fi bikan albinadu %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). Taddar takharum min Keyman hadar tahawul channan da..\n\nAFKURU: Alshu\'ul da biddar bibga kabir aljala bas. Dassasan debugging biddar bingus ajala hana kompita hanak wa eisawwu bey nadum al\'aruf faoug da..\n\nAFKUR LAYL SYR: Albarak afkur debug bihut shu\'ul al\'assarta chatta. Taddar ta\'assural shu\'ul albihuttual debug fi waqat altasaw debugging walla waqat tasaw miwasa faouga. + Khabar hana debug hana Keyman bihuttu fi bikan albinadu %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). Taddar takharum min Keyman hadar tahawul channan da..\n\nAFKURU: Alshu\'ul da biddar bibga kabir aljala bas. Dassasan debugging biddar bingus ajala hana kompita hanak wa eisawwu bey nadum al\'aruf faoug da..\n\nAFKUR LAYL SYR: Albarak afkur debug bihut shu\'ul al\'assarta chatta. Taddar ta\'assural shu\'ul albihuttual debug fi waqat altasaw debugging walla waqat tasaw miwasa faouga. diff --git a/windows/src/desktop/kmshell/locale/sv-SE/strings.xml b/windows/src/desktop/kmshell/locale/sv-SE/strings.xml index 074b88dc4b..bec6400258 100644 --- a/windows/src/desktop/kmshell/locale/sv-SE/strings.xml +++ b/windows/src/desktop/kmshell/locale/sv-SE/strings.xml @@ -884,7 +884,7 @@ tangentbord som du använder i Windows. Keyman tangentbord kommer att anpassas a - Keyman debug information kommer att lagras i en loggfil som heter %LOCALAPPDATA%\Keyman\Diag\system#.etl (där # är ett nummer). Du bör avsluta Keyman innan du försöker ta bort filen.\n\n\VARNING: Denna fil kan växa sig stor mycket snabbt. Aktivering av felsökning kan sakta ner ditt system och bör endast göras om det rekommenderas av teknisk support.\n\nINTEGRITET VARNING: Observera att felsökningsloggfilen registrerar alla tangenttryckningar som du skriver. Du bör bara aktivera felsökningsloggen under hela en felsöknings- eller diagnostisk session. + Keyman debug information kommer att lagras i en loggfil som heter %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (där # är ett nummer). Du bör avsluta Keyman innan du försöker ta bort filen.\n\n\VARNING: Denna fil kan växa sig stor mycket snabbt. Aktivering av felsökning kan sakta ner ditt system och bör endast göras om det rekommenderas av teknisk support.\n\nINTEGRITET VARNING: Observera att felsökningsloggfilen registrerar alla tangenttryckningar som du skriver. Du bör bara aktivera felsökningsloggen under hela en felsöknings- eller diagnostisk session. diff --git a/windows/src/desktop/kmshell/locale/uk-UA/strings.xml b/windows/src/desktop/kmshell/locale/uk-UA/strings.xml index 57d4fb1ab4..6e54690f54 100644 --- a/windows/src/desktop/kmshell/locale/uk-UA/strings.xml +++ b/windows/src/desktop/kmshell/locale/uk-UA/strings.xml @@ -884,7 +884,7 @@ - Інформація для налагодження Keyman буде зберігатися в файлі журналу %LOCALAPPDATA%\Keyman\Diag\system#.etl (де # це номер). Перед тим, як видалити цей файл потрібно вийти з Keymank.\n\n\УВАГА: Цей файл може рости дуже швидко. Включення налагодження може сповільнити вашу систему і повинно бути зроблено лише за умови технічної підтримки.\n\nPRIVACY УВАГА: Будь ласка, зверніть увагу, що налагоджувальні записи всіх натискань, які ви друкуєте. Ви повинні включити тільки журнал налагодження для тривалості діагностики або діагностичної сесії. + Інформація для налагодження Keyman буде зберігатися в файлі журналу %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (де # це номер). Перед тим, як видалити цей файл потрібно вийти з Keymank.\n\n\УВАГА: Цей файл може рости дуже швидко. Включення налагодження може сповільнити вашу систему і повинно бути зроблено лише за умови технічної підтримки.\n\nPRIVACY УВАГА: Будь ласка, зверніть увагу, що налагоджувальні записи всіх натискань, які ви друкуєте. Ви повинні включити тільки журнал налагодження для тривалості діагностики або діагностичної сесії. diff --git a/windows/src/desktop/kmshell/locale/vi-VN/strings.xml b/windows/src/desktop/kmshell/locale/vi-VN/strings.xml index f7b3fc102d..8e75db2fb0 100644 --- a/windows/src/desktop/kmshell/locale/vi-VN/strings.xml +++ b/windows/src/desktop/kmshell/locale/vi-VN/strings.xml @@ -466,14 +466,22 @@ Các bản cập nhật hiện có sẽ được áp dụng khi Windows được khởi động lại lần tiếp theo: + + + + Phiên bản hiện tại + + + + Phiên bản mới - Việc cập nhật ngay bây giờ yêu cầu khởi động lại máy tính. + Cài đặt bản cập nhật ngay bây giờ sẽ yêu cầu khởi động lại máy tính của bạn trước khi bạn có thể sử dụng Keyman một lần nữa. Bạn có cập nhật ngay bây giờ không? - Bản cập nhật Keyman đã sẵn sàng để cài đặt. + Bản cập nhật Keyman đã được tải xuống và sẵn sàng cài đặt. @@ -569,6 +577,10 @@ Cập nhật + + Cập nhật ngay + + Các thành phần Keyman đã cập nhật khả dụng @@ -638,12 +650,20 @@ Thoát &kiểm tra cập nhật trực tuyến + + + + Để sau Áp dụng cập nhật ngay + + Cài đặt bản cập nhật ngay + + Kiểm tra các bản cập nhật mới @@ -928,7 +948,7 @@ - Thông tin gỡ lỗi Keyman sẽ được lưu trữ trong tệp nhật ký có tên %LOCALAPPDATA%\Keyman\Diag\system#.etl (trong đó # là một số). Bạn nên thoát khỏi Keyman trước khi cố gắng xóa tệp này.\n\n\CẢNH BÁO: Tệp này có thể tăng kích thước rất nhanh. Việc bật gỡ lỗi có thể làm chậm hệ thống của bạn và chỉ nên thực hiện nếu được bộ phận hỗ trợ kỹ thuật khuyên dùng.\n\nCẢNH BÁO VỀ QUYỀN RIÊNG TƯ: Xin lưu ý rằng tệp nhật ký gỡ lỗi ghi lại tất cả các lần nhấn phím mà bạn nhập. Bạn chỉ nên bật nhật ký gỡ lỗi trong thời gian diễn ra phiên gỡ lỗi hoặc chẩn đoán. + Thông tin gỡ lỗi Keyman sẽ được lưu trữ trong tệp nhật ký có tên %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (trong đó # là một số). Bạn nên thoát khỏi Keyman trước khi cố gắng xóa tệp này.\n\n\CẢNH BÁO: Tệp này có thể tăng kích thước rất nhanh. Việc bật gỡ lỗi có thể làm chậm hệ thống của bạn và chỉ nên thực hiện nếu được bộ phận hỗ trợ kỹ thuật khuyên dùng.\n\nCẢNH BÁO VỀ QUYỀN RIÊNG TƯ: Xin lưu ý rằng tệp nhật ký gỡ lỗi ghi lại tất cả các lần nhấn phím mà bạn nhập. Bạn chỉ nên bật nhật ký gỡ lỗi trong thời gian diễn ra phiên gỡ lỗi hoặc chẩn đoán. diff --git a/windows/src/desktop/kmshell/locale/zh-CN/strings.xml b/windows/src/desktop/kmshell/locale/zh-CN/strings.xml index 8a2aeeb020..ae99513afb 100644 --- a/windows/src/desktop/kmshell/locale/zh-CN/strings.xml +++ b/windows/src/desktop/kmshell/locale/zh-CN/strings.xml @@ -884,7 +884,7 @@ - Keyman 调试信息将存储在一个名为 %LOCALAPPDATA%\Keyman\Diag\system#.etl 的日志文件(其中# 是一个数字)。 您应该在尝试删除此文件之前退出Keyman。\n\n\警告: 此文件可以快速扩展。 启用调试可能会减慢您的系统,并且只有在得到技术支持的情况下才能完成。\n\nPRIVACY 警告:请注意,调试日志文件会记录所有您键入的按键。 您只应该在调试或诊断会话期间打开调试日志。 + Keyman 调试信息将存储在一个名为 %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl 的日志文件(其中# 是一个数字)。 您应该在尝试删除此文件之前退出Keyman。\n\n\警告: 此文件可以快速扩展。 启用调试可能会减慢您的系统,并且只有在得到技术支持的情况下才能完成。\n\nPRIVACY 警告:请注意,调试日志文件会记录所有您键入的按键。 您只应该在调试或诊断会话期间打开调试日志。 diff --git a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm index e1f754763f..2f44143138 100644 --- a/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm +++ b/windows/src/desktop/kmshell/main/Keyman.Configuration.UI.UfrmStartInstall.dfm @@ -151,7 +151,7 @@ object frmStartInstall: TfrmStartInstall Top = 200 Width = 75 Height = 25 - Caption = 'Update now' + Caption = '&Update now' ModalResult = 1 TabOrder = 0 end @@ -160,7 +160,7 @@ object frmStartInstall: TfrmStartInstall Top = 200 Width = 75 Height = 25 - Caption = 'Later' + Caption = '&Later' ModalResult = 8 TabOrder = 1 end diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 7b831b97c4..e70a0c2867 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -88,14 +88,19 @@ type procedure HandleFirstRun; function CurrentStateName: string; (** - * Checks if Keyman is the WaitingRestartState and that - * Keyman has not run in this Windows session. - * The sole purpose is for the calling code then produce - * a UI to confirm the user wants to continue install. + * Checks if Keyman is the WaitingRestartState, the cache is valid + * and that Keyman has not run in this Windows session. It also confirms + * that automatic updates are still enabled #14295. + * The purpose is for the calling code can choose to produce + * a UI to confirm the user wants to install an update, + * if everything is ready to start a update. + * + * NOTE: If it finds the cache is invalid it will abort causing + * the State Machine to change state, returning to idle. * * @returns True if the Keyman is ready to install. *) - function ReadyToInstall: Boolean; + function ValidateReadyToInstall: Boolean; function IsInstallingState: Boolean; function GetAutomaticUpdates: Boolean; @@ -558,14 +563,27 @@ begin Result := CurrentState.ClassName; end; -function TUpdateStateMachine.ReadyToInstall: Boolean; +function TUpdateStateMachine.ValidateReadyToInstall: Boolean; begin if not IsCurrentStateAssigned then Exit(False); - if (CurrentState is WaitingRestartState) and not HasKeymanRun then - Result := True - else - Result := False; + + if not (CurrentState is WaitingRestartState) then + Exit(False); + + // Verify conditions since entering WaitingRestartState: + // If the cache is not valid or automatic updates have + // been disabled clear the cache and return to the idle state. + if not TUpdateCheckStorage.CheckMetaDataForUpdate or not Self.GetAutomaticUpdates then + begin + HandleAbort; + Exit(False); + end; + + if not HasKeymanRun then + Exit(True); + + Result := False; end; function TUpdateStateMachine.IsInstallingState: Boolean; @@ -630,6 +648,8 @@ var CheckForUpdates: TRemoteUpdateCheck; UpdateCheckResult: TRemoteUpdateCheckResult; begin + // ensure the cache is empty + bucStateContext.RemoveCachedFiles; // Remote manages the last check time therefore // we will allow it to return early if it hasn't reached // the configured time between checks. @@ -955,7 +975,7 @@ begin // Return to Idle state and check for Updates state ChangeState(IdleState); bucStateContext.CurrentState.HandleCheck; - Result := kmShellExit; + Result := kmShellContinue; end else begin diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 8a9e037de6..1f02c696e3 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -668,20 +668,11 @@ function ShouldSendToBUpdateSM(FSilent: Boolean; BUpdateSM: TUpdateStateMachine; // UI elements from the state machine we have bring some of logic here. var frmStartInstall: TfrmStartInstall; - ReadyToInstall: Boolean; + ValidateReadyToInstall: Boolean; begin - ReadyToInstall := BUpdateSM.ReadyToInstall; - // The setting for automatic updates may have been set to disable - // after updates were checked and downloaded. The user should - // not be prompted for an update in this case and the update should - // abort. - if not BUpdateSM.GetAutomaticUpdates and ReadyToInstall then - begin - BUpdateSM.HandleAbort; - Exit(False); - end; + ValidateReadyToInstall := BUpdateSM.ValidateReadyToInstall; - if not FSilent and ReadyToInstall and + if not FSilent and ValidateReadyToInstall and (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin @@ -692,7 +683,7 @@ begin frmStartInstall.Free; end; end - else if FSilent and ReadyToInstall then + else if FSilent and ValidateReadyToInstall then Result := False else Result := True; diff --git a/windows/src/desktop/kmshell/util/UILanguages.pas b/windows/src/desktop/kmshell/util/UILanguages.pas index 7c90a7d622..83519c23d0 100644 --- a/windows/src/desktop/kmshell/util/UILanguages.pas +++ b/windows/src/desktop/kmshell/util/UILanguages.pas @@ -1,18 +1,18 @@ (* Name: UILanguages Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 25 May 2010 Modified Date: 18 May 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 25 May 2010 - mcdurdin - I1694 - Select Keyman UI language rework 18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support *) @@ -111,8 +111,6 @@ begin FLanguagesAvailableString := MsgFromId(SKDefaultLanguageCode)+#13#10+GetAvailableLanguages; FLanguages.Text := FLanguagesAvailableString; - // TODO: with 8 languages this takes about 1/8 second on a fast computer - // So as translations grow this may become a bottleneck when starting Keyman. for i := 0 to FLanguages.Count - 1 do FLanguages[i] := FLanguages[i] + '=' + MessageFromID(StringFromMsgId(SKUILanguageNameWithEnglish), FLanguages[i]); end; diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml index 468ac75ad4..b414fd891c 100644 --- a/windows/src/desktop/kmshell/xml/strings.xml +++ b/windows/src/desktop/kmshell/xml/strings.xml @@ -1233,7 +1233,7 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to - Keyman debug information will be stored in a log file called %LOCALAPPDATA%\Keyman\Diag\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. + Keyman debug information will be stored in a log file called %LOCALAPPDATA%\\Keyman\\Diag\\system#.etl (where # is a number). You should exit Keyman before attempting to delete this file.\n\n\WARNING: This file can grow large very quickly. Enabling debugging may slow down your system and should only be done if advised by technical support.\n\nPRIVACY WARNING: Please note that the debug logfile records all keystrokes that you type. You should only turn on the debug log for the duration of a debugging or diagnostic session. diff --git a/windows/src/desktop/setup/Keyman.Setup.System.SetupUILanguageManager.pas b/windows/src/desktop/setup/Keyman.Setup.System.SetupUILanguageManager.pas index 0eca0b41ce..9588d3d3d5 100644 --- a/windows/src/desktop/setup/Keyman.Setup.System.SetupUILanguageManager.pas +++ b/windows/src/desktop/setup/Keyman.Setup.System.SetupUILanguageManager.pas @@ -5,19 +5,29 @@ interface uses System.Classes, System.Generics.Collections, + System.Generics.Defaults, + System.Math, Keyman.System.UILanguageManager, SetupStrings; type + TLanguageName = record + OrgLanguageName: string; // original name (not necessarily latin based) + ExtendedLanguageName: string;// name being used for displaying + LanguageName4Sort: string; // name being used for sorting + end; TSetupUILanguageManager = class(TUILanguageManager) public type TLocaleArray = array[TInstallInfoText] of string; - TSetupLocales = TDictionary; + TSetupLocales = TDictionary; private class var FStrings: TDictionary; FActiveLocale: string; FLocales: TSetupLocales; + FIndexMapping: TDictionary; + FHasChanged: Boolean; + FSortedValues: TList< TPair>; private class procedure CreateStatic; class procedure DestroyStatic; @@ -27,8 +37,11 @@ type class function Get(id: TInstallInfoText): string; class function Locales: TSetupLocales; static; + class function IndexMapping: TDictionary; static; + class function SortedValues: TList< TPair>; static; class property ActiveLocale: string read FActiveLocale write FActiveLocale; class procedure RegisterSetupStrings(const tag: string; const locale: TLocaleArray); + class procedure Reorder; end; implementation @@ -37,24 +50,88 @@ uses System.SysUtils, Winapi.Windows; + (** + * Checks whether a string contains only Latin-based characters. + * + * The following Unicode ranges are considered Latin-based: + * [$0000,$007F] (Basic Latin) + * [$0080,$00FF] (Latin-1 Supplement) + * + * The following ranges are **not** considered Latin-based: + * [$0100,$017F] (Latin Extended-A) + * [$0180,$024F] (Latin Extended-B) + * [$1E00,$1EFF] (Latin Extended Additional) + * + * @param name The string to check. + * @return True if all characters are Latin-based; False otherwise. + *) + function isLatinBasedOnly(name: string): Boolean; + var + ch: Char; + code: Integer; + begin + Result := True; + for ch in name do + begin + code := Ord(ch); + Result := Result and (InRange(code, $0000, $007F) or // Basic Latin + InRange(code, $0080, $00FF) // Latin-1 Supplement + ); + end; + end; + class procedure TSetupUILanguageManager.CreateStatic; begin if not Assigned(FStrings) then begin FStrings := TDictionary.Create; FLocales := TSetupLocales.Create; + FIndexMapping := TDictionary.Create; end; end; class function TSetupUILanguageManager.Locales: TSetupLocales; begin + if FHasChanged then + begin + Reorder; + FHasChanged := False; + end; Result := FLocales; end; +(* + Returns the mapping between sorted index positions and original index positions. + The mapping is regenerated in Reorder. + @returns A dictionary mapping sorted index (key) to original index (value) +*) +class function TSetupUILanguageManager.IndexMapping: TDictionary; +begin + Result := FIndexMapping; +end; + +(** + * Returns the list of locale key-value pairs where + * Key = BCP 47 locale tag, and Value = TLanguageName record) + * This list is regenerated in Reorder. + * @returns A list of sorted locale pairs. + *) +class function TSetupUILanguageManager.SortedValues: TList< TPair>; +begin + if FHasChanged then + begin + Reorder; + FHasChanged := False; + end; + Result := FSortedValues; +end; + class procedure TSetupUILanguageManager.DestroyStatic; begin FreeAndNil(FStrings); FreeAndNil(FLocales); + FreeAndNil(FIndexMapping); + FreeAndNil(FSortedValues); end; /// Sets the Setup UI language to the first matching user preferred UI language, if any found. @@ -95,12 +172,63 @@ end; class procedure TSetupUILanguageManager.RegisterSetupStrings(const tag: string; const locale: TLocaleArray); +var + languageTriple : TLanguageName; begin CreateStatic; if FActiveLocale = '' then FActiveLocale := tag; FStrings.Add(tag, locale); - FLocales.Add(tag, locale[ssLanguageName]); + languageTriple.OrgLanguageName := locale[ssLanguageName]; + if isLatinBasedOnly(locale[ssLanguageName]) then + begin + languageTriple.ExtendedLanguageName := locale[ssLanguageName]; + languageTriple.LanguageName4Sort := locale[ssLanguageName]; + end + else + begin + languageTriple.ExtendedLanguageName := locale[ssLanguageName] + ' (' + locale[ssLanguageNameInEnglish] + ')' ; + languageTriple.LanguageName4Sort := locale[ssLanguageNameInEnglish]; + end; + FLocales.Add(tag, languageTriple); + FHasChanged := True; +end; +(** + * Rebuilds and sorts FSortedValues based on the LanguageName4Sort field. + * By using this field, the sort order will be by the original language names for latin script + * languages, and the English name for non- Latin script languages. + *) +class procedure TSetupUILanguageManager.Reorder; +var + item: TPair; + i: integer; + j: integer; + sortedPairA: TList< TPair>; +begin + sortedPairA := TList< TPair>.Create(); + for item in FLocales do + begin + sortedPairA.Add(item); + end; + sortedPairA.Sort( + TComparer>.Construct( + function(const Left, Right: TPair): Integer + begin + // Sort by the Key value + Result := CompareText(Left.Value.LanguageName4Sort, Right.Value.LanguageName4Sort); + end + ) + ); + FreeAndNil(FSortedValues); + FSortedValues := sortedPairA; + i := 0; + FIndexMapping.Clear; + for item in FLocales do + begin + j := sortedPairA.IndexOf(item) ; + FIndexMapping.Add(j,i); + i := i+1; + end; end; initialization diff --git a/windows/src/desktop/setup/SetupStrings.pas b/windows/src/desktop/setup/SetupStrings.pas index 69227320eb..2e036cdde5 100644 --- a/windows/src/desktop/setup/SetupStrings.pas +++ b/windows/src/desktop/setup/SetupStrings.pas @@ -26,6 +26,7 @@ interface type TInstallInfoText = ( ssLanguageName, + ssLanguageNameInEnglish, ssApplicationTitle, ssTitle, ssInstallSuccess, diff --git a/windows/src/desktop/setup/UfrmRunDesktop.pas b/windows/src/desktop/setup/UfrmRunDesktop.pas index 08d80e52eb..5ee7718425 100644 --- a/windows/src/desktop/setup/UfrmRunDesktop.pas +++ b/windows/src/desktop/setup/UfrmRunDesktop.pas @@ -51,6 +51,7 @@ uses System.SysUtils, System.UITypes, System.Variants, + System.Math, Vcl.AppEvnts, Vcl.ComCtrls, Vcl.Controls, @@ -64,7 +65,6 @@ uses Keyman.Setup.System.InstallInfo, RunTools, UfrmDownloadProgress; - type TfrmRunDesktop = class(TForm) imgTitle: TImage; @@ -584,17 +584,23 @@ end; procedure TfrmRunDesktop.FillLanguageList; var - item: TPair; + item: TPair; n: Integer; + m: Integer; // width of ComboBox when it is dropped begin - for item in TSetupUILanguageManager.Locales do + cbLanguage.Canvas.Font.Assign(cbLanguage.Font); + m := cbLanguage.Width; + for item in TSetupUILanguageManager.SortedValues do begin - n := cbLanguage.Items.Add(item.Value); + n := cbLanguage.Items.Add(item.Value.ExtendedLanguageName); + m := Max(cbLanguage.Canvas.TextWidth(item.Value.ExtendedLanguageName), m); if TSetupUILanguageManager.ActiveLocale = item.Key then cbLanguage.ItemIndex := n; end; cbLanguage.Visible := cbLanguage.Items.Count > 1; lblGlobe.Visible := cbLanguage.Visible; + // set width of ComboBox when it is dropped (also consider width of vertical scroll bar) + SendMessage(cbLanguage.Handle,CB_SETDROPPEDWIDTH,m+GetSystemMetrics(SM_CXVSCROLL)+4,0); end; procedure TfrmRunDesktop.FillActionText; @@ -958,8 +964,11 @@ begin end; procedure TfrmRunDesktop.cbLanguageClick(Sender: TObject); +var + n : Integer; begin - TSetupUILanguageManager.ActiveLocale := TSetupUILanguageManager.Locales.Keys.ToArray[cbLanguage.ItemIndex]; + n := TSetupUILanguageManager.IndexMapping[cbLanguage.ItemIndex]; + TSetupUILanguageManager.ActiveLocale := TSetupUILanguageManager.Locales.Keys.ToArray[n]; FillStrings; end; diff --git a/windows/src/desktop/setup/locale/am-ET/strings.xml b/windows/src/desktop/setup/locale/am-ET/strings.xml index b67dc6851a..9d84b528c1 100644 --- a/windows/src/desktop/setup/locale/am-ET/strings.xml +++ b/windows/src/desktop/setup/locale/am-ET/strings.xml @@ -1,6 +1,7 @@ አማርኛ + Amharic $APPNAME $VERSION አዘገጃጀት ጫን $APPNAME $VERSION $APPNAME $VERSION በጥሩ ሁኔታ ተጭኗል። diff --git a/windows/src/desktop/setup/locale/az-AZ/strings.xml b/windows/src/desktop/setup/locale/az-AZ/strings.xml index 8d55bf892a..eaf0aa1db0 100644 --- a/windows/src/desktop/setup/locale/az-AZ/strings.xml +++ b/windows/src/desktop/setup/locale/az-AZ/strings.xml @@ -1,6 +1,7 @@ Azərbaycanca (Azəricə) + Azerbaijani $APPNAME $VERSION Quraşdırma $APPNAME $VERSION quraşdırmaq $APPNAME $VERSION uğurla quraşdırıldı. diff --git a/windows/src/desktop/setup/locale/bwr-NG/strings.xml b/windows/src/desktop/setup/locale/bwr-NG/strings.xml index 0e57bf4ae9..3d8b48e942 100644 --- a/windows/src/desktop/setup/locale/bwr-NG/strings.xml +++ b/windows/src/desktop/setup/locale/bwr-NG/strings.xml @@ -1,6 +1,7 @@ Bura-Pabir + Bura-Pabir $APPNAME $VERSION Setup Instal $APPNAME $VERSION $APPNAME $VERSION kǝ wutǝ ka hara instal ɓɗakǝlaka. diff --git a/windows/src/desktop/setup/locale/ckl-NG/strings.xml b/windows/src/desktop/setup/locale/ckl-NG/strings.xml index 8e057ac2d0..282584f45e 100644 --- a/windows/src/desktop/setup/locale/ckl-NG/strings.xml +++ b/windows/src/desktop/setup/locale/ckl-NG/strings.xml @@ -1,6 +1,7 @@ Nya Nasara + Nya Nasara $APPNAME $VERSION Setup Vu $APPNAME $VERSION $APPNAME $VERSION ndǝ kurta vu tǝni kuraka bwanai. diff --git a/windows/src/desktop/setup/locale/cs-CZ/strings.xml b/windows/src/desktop/setup/locale/cs-CZ/strings.xml index 196dc26a81..d55430b486 100644 --- a/windows/src/desktop/setup/locale/cs-CZ/strings.xml +++ b/windows/src/desktop/setup/locale/cs-CZ/strings.xml @@ -1,6 +1,7 @@ čeština + Czech $APPNAME $VERSION nastavení Nainstalovat $APPNAME $VERSION $APPNAME $VERSION byl úspěšně nainstalován. diff --git a/windows/src/desktop/setup/locale/de/strings.xml b/windows/src/desktop/setup/locale/de/strings.xml index 42fd7ab30d..999c755f77 100644 --- a/windows/src/desktop/setup/locale/de/strings.xml +++ b/windows/src/desktop/setup/locale/de/strings.xml @@ -1,6 +1,7 @@ Deutsch + German $APPNAME $VERSION Setup Installiere $APPNAME $VERSION $APPNAME $VERSION wurde erfolgreich installiert. diff --git a/windows/src/desktop/setup/locale/el-polyton/strings.xml b/windows/src/desktop/setup/locale/el-polyton/strings.xml index ddacddd5b8..5c870e4c8c 100644 --- a/windows/src/desktop/setup/locale/el-polyton/strings.xml +++ b/windows/src/desktop/setup/locale/el-polyton/strings.xml @@ -1,6 +1,7 @@ Ἑλληνικά Πολυτονικά + Greek Polytonic Ἐγκατάσταση $APPNAME $VERSION Ἐγκαταστῆστε $APPNAME $VERSION Τὸ $APPNAME $VERSION ἐγκατεστάθη ἐπιτυχῶς. diff --git a/windows/src/desktop/setup/locale/en/strings.xml b/windows/src/desktop/setup/locale/en/strings.xml index b5155ecf5b..9db4fc063e 100644 --- a/windows/src/desktop/setup/locale/en/strings.xml +++ b/windows/src/desktop/setup/locale/en/strings.xml @@ -1,5 +1,6 @@  English + English $APPNAME $VERSION Setup Install $APPNAME $VERSION $APPNAME $VERSION has been installed successfully. @@ -101,4 +102,4 @@ Restart now? $APPNAME Setup could not connect to keyman.com to download additional resources. Please check that you are online, and give $APPNAME Setup permission to access the Internet in your firewall settings. Click Abort to exit Setup, Retry to try and download resources again, or Ignore to continue offline. - \ No newline at end of file + diff --git a/windows/src/desktop/setup/locale/es-419/strings.xml b/windows/src/desktop/setup/locale/es-419/strings.xml index 6243061b4d..33bcb4b087 100644 --- a/windows/src/desktop/setup/locale/es-419/strings.xml +++ b/windows/src/desktop/setup/locale/es-419/strings.xml @@ -1,6 +1,7 @@ Español - Latinoamérica + Spanish - Latin America Instalador de $APPNAME $VERSION Instalar $APPNAME $VERSION $APPNAME $VERSION se ha instalado correctamente. diff --git a/windows/src/desktop/setup/locale/es-ES/strings.xml b/windows/src/desktop/setup/locale/es-ES/strings.xml index 48096974b7..206173a1ba 100644 --- a/windows/src/desktop/setup/locale/es-ES/strings.xml +++ b/windows/src/desktop/setup/locale/es-ES/strings.xml @@ -1,6 +1,7 @@ Español + Spanish $APPNAME $VERSION Configuración Instalar $APPNAME $VERSION $APPNAME $VERSION se ha instalado correctamente. diff --git a/windows/src/desktop/setup/locale/ff-NG/strings.xml b/windows/src/desktop/setup/locale/ff-NG/strings.xml index 02247aea4d..394404351b 100644 --- a/windows/src/desktop/setup/locale/ff-NG/strings.xml +++ b/windows/src/desktop/setup/locale/ff-NG/strings.xml @@ -1,6 +1,7 @@ Ɗemngal Fulfulde + Fulfulde Nigeria $APPNAME $VERSION Wa`uggo Wa`unu $APPNAME $VERSION $APPNAME $VERSION wa`aama no haandi. diff --git a/windows/src/desktop/setup/locale/ff-ZA/strings.xml b/windows/src/desktop/setup/locale/ff-ZA/strings.xml index b682c441f2..b5a36262d1 100644 --- a/windows/src/desktop/setup/locale/ff-ZA/strings.xml +++ b/windows/src/desktop/setup/locale/ff-ZA/strings.xml @@ -1,6 +1,7 @@ Pulaar-Fulfulde + Fulah Teeltagol $APPNAME $VERSION AAT $APPNAME $VERSION $APPNAME $VERSION aafaama no haanirta nih. diff --git a/windows/src/desktop/setup/locale/fr/strings.xml b/windows/src/desktop/setup/locale/fr/strings.xml index c48f1d3d0c..3d37a87b9c 100644 --- a/windows/src/desktop/setup/locale/fr/strings.xml +++ b/windows/src/desktop/setup/locale/fr/strings.xml @@ -1,6 +1,7 @@ Français + French Configuration de $APPNAME $VERSION Installer $APPNAME $VERSION $APPNAME $VERSION a été installé avec succès. diff --git a/windows/src/desktop/setup/locale/ha-HG/strings.xml b/windows/src/desktop/setup/locale/ha-HG/strings.xml index 42b626c579..2902242561 100644 --- a/windows/src/desktop/setup/locale/ha-HG/strings.xml +++ b/windows/src/desktop/setup/locale/ha-HG/strings.xml @@ -1,6 +1,7 @@ Hausa + Hausa Saita $APPNAME $VERSION Shigar da $APPNAME $VERSION An sami nasarar shigar da $APPNAME $VERSION. diff --git a/windows/src/desktop/setup/locale/hia-NG/strings.xml b/windows/src/desktop/setup/locale/hia-NG/strings.xml index a9dd54cbca..0dbbb47782 100644 --- a/windows/src/desktop/setup/locale/hia-NG/strings.xml +++ b/windows/src/desktop/setup/locale/hia-NG/strings.xml @@ -1,6 +1,7 @@ Waha + Waha $APPNAME $VERSION Setup An fa t $APPNAME $VERSION $APPNAME $VERSION fan falo jama. diff --git a/windows/src/desktop/setup/locale/id-ID/strings.xml b/windows/src/desktop/setup/locale/id-ID/strings.xml index ad20708eb3..b474ab486f 100644 --- a/windows/src/desktop/setup/locale/id-ID/strings.xml +++ b/windows/src/desktop/setup/locale/id-ID/strings.xml @@ -1,6 +1,7 @@ Bahasa Indonesia + Indonesian $APPNAME $VERSION Setup Pasang $APPNAME $VERSION $APPNAME $VERSION kini telah terpasang. diff --git a/windows/src/desktop/setup/locale/it-IT/strings.xml b/windows/src/desktop/setup/locale/it-IT/strings.xml index 281c086f09..ca2575f602 100644 --- a/windows/src/desktop/setup/locale/it-IT/strings.xml +++ b/windows/src/desktop/setup/locale/it-IT/strings.xml @@ -1,6 +1,7 @@ Italiana + Italian $APPNAME $VERSION Configurazione Installa $APPNAME $VERSION $APPNAME $VERSION è stato installato con successo. diff --git a/windows/src/desktop/setup/locale/km-KH/strings.xml b/windows/src/desktop/setup/locale/km-KH/strings.xml index 5e27226e7c..8720805dde 100644 --- a/windows/src/desktop/setup/locale/km-KH/strings.xml +++ b/windows/src/desktop/setup/locale/km-KH/strings.xml @@ -1,6 +1,7 @@ ភាសាខ្មែរ + Khmer រៀបចំ $APPNAME $VERSION ដំឡើង $APPNAME $VERSION $APPNAME $VERSION ត្រូវ​បាន​ដំឡើង​ដោយ​ជោគជ័យ។ diff --git a/windows/src/desktop/setup/locale/kn/strings.xml b/windows/src/desktop/setup/locale/kn/strings.xml index f2e8443827..7eaaf08ae1 100644 --- a/windows/src/desktop/setup/locale/kn/strings.xml +++ b/windows/src/desktop/setup/locale/kn/strings.xml @@ -1,6 +1,7 @@ ಕನ್ನಡ + Kannada $APPNAME $VERSION ಸೆಟ್ಅಪ್ $APPNAME $VERSION ಸ್ಥಾಪಿಸಿ $APPNAME $VERSION ಯಶಸ್ವಿಯಾಗಿ ಸ್ಥಾಪಿಸಲಾಗಿದೆ. diff --git a/windows/src/desktop/setup/locale/kr-NG/strings.xml b/windows/src/desktop/setup/locale/kr-NG/strings.xml index 7b8cbb7eb6..a5dc1485ff 100644 --- a/windows/src/desktop/setup/locale/kr-NG/strings.xml +++ b/windows/src/desktop/setup/locale/kr-NG/strings.xml @@ -1,6 +1,7 @@ Nasara + Nasara $APPNAME $VERSION Setup Install $APPNAME $VERSION $APPNAME $VERSION has been installed successfully. diff --git a/windows/src/desktop/setup/locale/mfi-NG/strings.xml b/windows/src/desktop/setup/locale/mfi-NG/strings.xml index aeb9063c8d..962431dd5d 100644 --- a/windows/src/desktop/setup/locale/mfi-NG/strings.xml +++ b/windows/src/desktop/setup/locale/mfi-NG/strings.xml @@ -1,6 +1,7 @@ Mandara + Mandara $APPNAME $VERSION Setup Femhe$APPNAME $VERSION $APPNAME $VERSION zlawzla nde a fa ne ba shagera. diff --git a/windows/src/desktop/setup/locale/mnw-MM/strings.xml b/windows/src/desktop/setup/locale/mnw-MM/strings.xml index 8c0c53a4a7..6267122222 100644 --- a/windows/src/desktop/setup/locale/mnw-MM/strings.xml +++ b/windows/src/desktop/setup/locale/mnw-MM/strings.xml @@ -1,6 +1,7 @@ မန် (Mon) + Mon ပလေဝ်ပလေတ် $APPNAME $VERSION စုတ် $APPNAME $VERSION $APPNAME $VERSION ဂှ်ပ္တိုန်စုတ်တုဲအာ ဗွဲမအံၚ်ဇၞးရ။ diff --git a/windows/src/desktop/setup/locale/mrt-NG/strings.xml b/windows/src/desktop/setup/locale/mrt-NG/strings.xml index 7b1b15c135..26aab6e7b8 100644 --- a/windows/src/desktop/setup/locale/mrt-NG/strings.xml +++ b/windows/src/desktop/setup/locale/mrt-NG/strings.xml @@ -1,6 +1,7 @@ Ŋyar nasara + Marghi $APPNAME $VERSION Setup Install $APPNAME $VERSION $APPNAME $VERSION has been installed successfully. diff --git a/windows/src/desktop/setup/locale/nl-NL/strings.xml b/windows/src/desktop/setup/locale/nl-NL/strings.xml index b46614752f..c094de3b87 100644 --- a/windows/src/desktop/setup/locale/nl-NL/strings.xml +++ b/windows/src/desktop/setup/locale/nl-NL/strings.xml @@ -1,6 +1,7 @@ Nederlands + Dutch $APPNAME $VERSION Installatie Installeer $APPNAME $VERSION $APPNAME $VERSION is succesvol geïnstalleerd. diff --git a/windows/src/desktop/setup/locale/pl-PL/strings.xml b/windows/src/desktop/setup/locale/pl-PL/strings.xml index 8e1b65184a..474102f2c1 100644 --- a/windows/src/desktop/setup/locale/pl-PL/strings.xml +++ b/windows/src/desktop/setup/locale/pl-PL/strings.xml @@ -1,6 +1,7 @@ Polski + Polish $APPNAME $VERSION Konfiguracja Zainstaluj $APPNAME $VERSION $APPNAME $VERSION została zainstalowana pomyślnie. diff --git a/windows/src/desktop/setup/locale/pt-PT/strings.xml b/windows/src/desktop/setup/locale/pt-PT/strings.xml index 20c4c7e843..51dc053e7b 100644 --- a/windows/src/desktop/setup/locale/pt-PT/strings.xml +++ b/windows/src/desktop/setup/locale/pt-PT/strings.xml @@ -1,6 +1,7 @@ Português de Portugal + Portuguese Configuração de $APPNAME $VERSION Instalar $APPNAME $VERSION $APPNAME $VERSION foi instalado com sucesso. diff --git a/windows/src/desktop/setup/locale/ru-RU/strings.xml b/windows/src/desktop/setup/locale/ru-RU/strings.xml index deee88a632..d568faa7f7 100644 --- a/windows/src/desktop/setup/locale/ru-RU/strings.xml +++ b/windows/src/desktop/setup/locale/ru-RU/strings.xml @@ -1,6 +1,7 @@ Русский + Russian Настройки $APPNAME $VERSION Установить $APPNAME $VERSION $APPNAME $VERSION успешно установлен. diff --git a/windows/src/desktop/setup/locale/shu-latn/strings.xml b/windows/src/desktop/setup/locale/shu-latn/strings.xml index d9216d9b95..c1282d2ca0 100644 --- a/windows/src/desktop/setup/locale/shu-latn/strings.xml +++ b/windows/src/desktop/setup/locale/shu-latn/strings.xml @@ -1,6 +1,7 @@ Shuwa Latin + Shuwa Latin $APPNAME $VERSION Miwasa Dyss $APPNAME $VERSION $APPNAME $VERSION dakhal zayn. diff --git a/windows/src/desktop/setup/locale/sv-SE/strings.xml b/windows/src/desktop/setup/locale/sv-SE/strings.xml index 42287cf498..bc628734ce 100644 --- a/windows/src/desktop/setup/locale/sv-SE/strings.xml +++ b/windows/src/desktop/setup/locale/sv-SE/strings.xml @@ -1,6 +1,7 @@ svenska + Swedish $APPNAME $VERSION Konfiguration Installera $APPNAME $VERSION $APPNAME $VERSION har installerats. diff --git a/windows/src/desktop/setup/locale/uk-UA/strings.xml b/windows/src/desktop/setup/locale/uk-UA/strings.xml index 16869053a9..c27a02fda6 100644 --- a/windows/src/desktop/setup/locale/uk-UA/strings.xml +++ b/windows/src/desktop/setup/locale/uk-UA/strings.xml @@ -1,6 +1,7 @@ Українська + Ukrainian $APPNAME $VERSION налаштування Встановити $APPNAME $VERSION $APPNAME $VERSION було успішно встановлено. diff --git a/windows/src/desktop/setup/locale/zh-CN/strings.xml b/windows/src/desktop/setup/locale/zh-CN/strings.xml index 699833fe39..f359ee1281 100644 --- a/windows/src/desktop/setup/locale/zh-CN/strings.xml +++ b/windows/src/desktop/setup/locale/zh-CN/strings.xml @@ -1,6 +1,7 @@ 中文 (简体) + Chinese Simplified $APPNAME $VERSION 设置 安装 $APPNAME $VERSION $APPNAME $VERSION 已成功安装。 diff --git a/windows/src/engine/engine.sln b/windows/src/engine/engine.sln index f45c0bcacc..2bc81be7c0 100644 --- a/windows/src/engine/engine.sln +++ b/windows/src/engine/engine.sln @@ -13,9 +13,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mcompile", "mcompile\mcompi EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kmrefresh", "kmrefresh\kmrefresh.vcxproj", "{A3B8F75F-E63E-434E-8A2A-034AD9B66571}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman-engine-tests", "keyman32\tests\keyman-engine-tests\keyman-engine-tests.vcxproj", "{46399ED7-79A1-4E7E-BD96-4B1AEEC838AE}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman-engine", "keyman32\keyman-engine.vcxproj", "{A7069186-5AE7-4504-8926-861B7A34BA9E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman32.tests", "keyman32\tests\keyman32.tests.vcxproj", "{46399ED7-79A1-4E7E-BD96-4B1AEEC838AE}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testhost", "testhost\testhost.vcxproj", "{12818DA3-0472-49A5-96FC-A4808A1A77FA}" EndProject diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 7f948d0518..a33e69cea4 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -292,6 +292,8 @@ type procedure UnregisterControllerWindows; // I4731 function IsSysTrayWindow(AHandle: THandle): Boolean; procedure GetTrayIconHandle; // I4731 + procedure WatchDogKeyEvent; + protected procedure DoInterfaceHotkey(Target: Integer); @@ -849,6 +851,14 @@ begin if (TKeymanHint(lParam) = KH_EXITPRODUCT) and (wParam = mrOk) then UnloadProduct; end; + KMC_WATCHDOG_KEYEVENT: + WatchDogKeyEvent; + KMC_WATCHDOG_FAKEFREEZE: + begin + TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze begin', []); + Sleep(5000); + TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze end', []); + end; //TOUCH KMC_CONTEXT: //TOUCH begin //TOUCH if LParam <> 0 then @@ -857,6 +867,13 @@ begin end; end; +procedure TfrmKeyman7Main.WatchDogKeyEvent; +begin + TDebugLogClient.Instance.WriteMessage('Attempting to send WatchDogKeyEvent', []); + if kmint.KeymanEngineControl <> nil then + kmint.KeymanEngineControl.WatchDogKeyEvent; +end; + procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer); begin if not Assigned(FRunningProduct) then diff --git a/windows/src/engine/keyman/viskbd/UfrmOSKOnScreenKeyboard.pas b/windows/src/engine/keyman/viskbd/UfrmOSKOnScreenKeyboard.pas index 022d1d621c..db88b5c28b 100644 --- a/windows/src/engine/keyman/viskbd/UfrmOSKOnScreenKeyboard.pas +++ b/windows/src/engine/keyman/viskbd/UfrmOSKOnScreenKeyboard.pas @@ -1,18 +1,18 @@ (* Name: UfrmOSKOnScreenKeyboard Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 14 Sep 2006 Modified Date: 8 Apr 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 14 Sep 2006 - mcdurdin - Initial version 04 Dec 2006 - mcdurdin - Support exporting and printing 04 Dec 2006 - mcdurdin - Fix shift states when pressing keys @@ -72,6 +72,7 @@ type VKI: TVisualKeyboardInfo; VisualKeyboards: TVisualKeyboardInfoList; FCachedShiftState: TExtShiftState; + IsSimulatedLControlDown: Boolean; function GetAsyncShiftState: TExtShiftState; procedure UpdateKeyboard(FLoading: Boolean); @@ -89,6 +90,7 @@ type procedure PrintKeyboard; procedure ResetShiftStates; // I1144 + procedure OskModifierEvent(VKCode, Flags: DWORD); function HasVisualKeyboard: Boolean; overload; function HasVisualKeyboard(KeymanID: Integer): Boolean; overload; @@ -222,6 +224,58 @@ begin ShiftStateChange(fkcss, ass); end; +(** + * Handles OSK modifier events received from + * keyman32:PostVisualKeyboardModifierEvent in k32_visualkeyboardinterface.cpp. + * + * This takes the chiral VK_LCONTROL / VK_RCONTROL / VK_LMENU / VK_RMENU virtual + * key codes. + * + * This currently deals only with one situation: when Windows posts a simulated + * LControl key when AltGr is pressed on a European layout. See + * serialkeyeventserver.cpp:WndProc for a deeper discussion of the key events + * that are generated in this scenario. + * + * @param VKCode virtual key code of the modifier key, chiral for Alt and Ctrl + * @param Flags as follows: + * KEYEVENTF_EXTENDEDKEY 0x00000001 - extended bit is set on the scan code + * KEYEVENTF_KEYUP 0x00000002 - key is being released + * scan code mask 0x0FFF0000 - 12 bits for scan code + * all other bits reserved + *) +procedure TfrmOSKOnScreenKeyboard.OskModifierEvent(VKCode, Flags: DWORD); +var + isUp: Boolean; + scanCode: DWORD; +const + /// The normal scan code for left Control key + SCAN_LEFT_CONTROL = $1D; + /// The scan code generated by Windows when it simulates pressing left + /// Control, when AltGr is pressed on a European layout; this is generated by + /// Windows for compatibility reasons. + SCAN_LEFT_CONTROL_SIMULATED = $21D; +begin + // not used: extended := (Flags and KEYEVENTF_EXTENDEDKEY) <> 0; + isUp := (Flags and KEYEVENTF_KEYUP) <> 0; + + // In order to identify the Windows simulated left control scan code, we need + // to match on more bits than the normal 8 bit scan code. + scanCode := (Flags and $0FFF0000) shr 16; + + // If the Left Control key is simulated by Windows, then we want to ignore it + // because in reality, AltGr has been pressed on a European keyboard. However, + // if the Left Control key is being released, then we should reset the flag + // whether it comes from the Windows simulation or from another source (real + // or injected), so that we don't end up with a stuck left control key + if (scanCode = SCAN_LEFT_CONTROL_SIMULATED) and not isUp then + IsSimulatedLControlDown := True + else if ((scanCode = SCAN_LEFT_CONTROL) or (scanCode = SCAN_LEFT_CONTROL_SIMULATED)) and isUp then + IsSimulatedLControlDown := False; + + // TODO: in the future, we might be able to eliminate tmrCheck and make all + // modifier updates go through the WH_KEYBOARD_LL hook. +end; + procedure TfrmOSKOnScreenKeyboard.ShiftStateChange(kbdShift, asyncShift: TExtShiftState); procedure PrepState(fkcss, ass: TExtShiftState; shift: TExtShiftStateValue; vk: Integer); var @@ -317,10 +371,10 @@ begin FNewShiftState := FNewShiftState - [essLCtrl, essRCtrl]; FChanged := True; end; - + if FChanged then ShiftStateChange(FNewShiftState, FShiftState); - + FShiftState := FNewShiftState; kbd.ShiftState := FShiftState; UpdateKeyboard(False); @@ -357,36 +411,28 @@ begin end; function TfrmOSKOnScreenKeyboard.GetAsyncShiftState: TExtShiftState; +var + lctrl: SHORT; + ralt: SHORT; begin Result := []; if (GetAsyncKeyState(VK_SHIFT) and $8000) = $8000 then Result := Result + [essShift]; if kbd.LRShift then begin - if (GetAsyncKeyState(VK_LCONTROL) and $8000) = $8000 then Result := Result + [essLCtrl]; + lctrl := GetAsyncKeyState(VK_LCONTROL); + ralt := GetAsyncKeyState(VK_RMENU); + + if not IsSimulatedLControlDown and ((lctrl and $8000) = $8000) then + Result := Result + [essLCtrl]; if (GetAsyncKeyState(VK_RCONTROL) and $8000) = $8000 then Result := Result + [essRCtrl]; if (GetAsyncKeyState(VK_LMENU) and $8000) = $8000 then Result := Result + [essLAlt]; - if (GetAsyncKeyState(VK_RMENU) and $8000) = $8000 then Result := Result + [essRAlt]; + if (ralt and $8000) = $8000 then Result := Result + [essRAlt]; end else begin if (GetAsyncKeyState(VK_CONTROL) and $8000) = $8000 then Result := Result + [essCtrl]; if (GetAsyncKeyState(VK_MENU) and $8000) = $8000 then Result := Result + [essAlt]; end; - -(*Result := []; - if (GetAsyncKeyState(VK_SHIFT) and $8000) = $8000 then Result := Result + [essShift]; - if kbd.LRShift then - begin - if (GetAsyncKeyState(VK_LCONTROL) and $8000) = $8000 then Result := Result + [essLCtrl]; - if (GetAsyncKeyState(VK_RCONTROL) and $8000) = $8000 then Result := Result + [essRCtrl]; - if (GetAsyncKeyState(VK_LMENU) and $8000) = $8000 then Result := Result + [essLAlt]; - if (GetAsyncKeyState(VK_RMENU) and $8000) = $8000 then Result := Result + [essRAlt]; - end - else - begin - if (GetAsyncKeyState(VK_CONTROL) and $8000) = $8000 then Result := Result + [essCtrl]; - if (GetAsyncKeyState(VK_MENU) and $8000) = $8000 then Result := Result + [essAlt]; - end;*) end; function TfrmOSKOnScreenKeyboard.HasVisualKeyboard(KeymanID: Integer): Boolean; @@ -474,7 +520,7 @@ begin ss := ExtShiftStateToVkShiftState(kbd.ShiftState); ssi := ValidExtShiftStateIndex(kbd.ShiftState); end; - + for i := 0 to VKI.Keyboard.Keys.Count - 1 do begin if not FPositional then diff --git a/windows/src/engine/keyman/viskbd/UfrmVisualKeyboard.pas b/windows/src/engine/keyman/viskbd/UfrmVisualKeyboard.pas index c1d1dedaa0..5fc1d0ed9d 100644 --- a/windows/src/engine/keyman/viskbd/UfrmVisualKeyboard.pas +++ b/windows/src/engine/keyman/viskbd/UfrmVisualKeyboard.pas @@ -203,6 +203,7 @@ type procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST; procedure WMUserFormShown(var Message: TMessage); message WM_USER_FormShown; // I4225 + procedure WMKeymanOskModifierEvent(var Message: TMessage); message WM_KEYMAN_OSK_MODIFIER_EVENT; procedure UpdatePanels; procedure SetActivePage(Page: TOSKActivePage); @@ -998,6 +999,12 @@ begin inherited; end; +procedure TfrmVisualKeyboard.WMKeymanOskModifierEvent(var Message: TMessage); +begin + if Assigned(FOnScreenKeyboard) then + FOnScreenKeyboard.OskModifierEvent(Message.WParam, Message.LParam); +end; + procedure TfrmVisualKeyboard.AdjustToClosestMonitor(var x, y, cx, cy: Integer); var m: TMonitor; diff --git a/windows/src/engine/keyman32/LowLevelHookWatchDog.cpp b/windows/src/engine/keyman32/LowLevelHookWatchDog.cpp new file mode 100644 index 0000000000..3da1c11feb --- /dev/null +++ b/windows/src/engine/keyman32/LowLevelHookWatchDog.cpp @@ -0,0 +1,105 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-17 + * + * Handle situations where Windows silently uninstalls our low level keyboard + * hook, and dynamically reinstall it. The hook can be uninstalled when + * keyman.exe becomes unresponsive for more than 200msec (default timeout) -- + * this can be due to something Keyman is doing, but it could also happen during + * high system load. The hook will only be uninstalled if a key is pressed while + * Keyman is unresponsive. + * + * Per Windows documentation: + * https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks + * + * This works by tracking last event times for both the WH_KEYBOARD_LL hook and + * the WH_GETMESSAGE hook. These two hooks both receive key events, but the + * WH_GETMESSAGE hook runs in the focused thread context, whereas WH_KEYBOARD_LL + * runs in keyman.exe main thread context. The WH_KEYBOARD_LL hook receives the + * key event first. + * + * This (static) class is only used in the keyman.exe main thread context. + * + * Notification of HookIsAlive is a straightforward call from the low level + * keyboard hook procedure, but the KeyEventReceivedInGetMessageProc function + * must be signalled across processes. We have chosen to do this with a posted + * message to the master controller, which is then handled by + * UfrmKeyman7Main.pas, and passed through Keyman_WatchDogKeyEvent. + * + * The message handler was implemented in UfrmKeyman7Main.pas rather than in + * kmhook_getmessage.cpp, because it appears that kmnGetMessageProc does not see + * messages that were posted by it or child functions (this is by observation, not + * documentation -- I was not able to find documentation on this, but surmise it + * is to prevent deadlock/infinite loop scenarios, which could easily lockup + * Windows entirely). + * + * No key data is passed in the event, only the information that a key was + * pressed. + */ + +#include "pch.h" + +/** + * minimum number of milliseconds between the last LowLevel and GetMessage + * events before we assume the low level hook has been uninstalled, and we need + * to reinstall it. + */ +#define WATCHDOG_THRESHOLD 1000 + +static ULONGLONG LastLowLevelEventTick = 0; +static ULONGLONG LastGetMessageEventTick = 0; + +void LowLevelHookWatchDog::HookIsAlive() { + // ULONGLONG Previous = LastLowLevelEventTick; + LastLowLevelEventTick = GetTickCount64(); + // SendDebugMessageFormat("LowLevelHookWatchDog::HookIsAlive currentLL=%llu currentGM=%llu (lastLL=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous); +} + +void LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc() { + // ULONGLONG Previous = LastGetMessageEventTick; + LastGetMessageEventTick = GetTickCount64(); + // SendDebugMessageFormat("LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc currentLL=%llu currentGM=%llu (lastGM=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous); + + // This is a good place to check if we are still alive -- shortly after each + // keystroke event in the GetMessage hook, as this means at worst we'll have + // one or two keystrokes where Keyman must recover + if(!CheckIfHookIsAlive()) { + ReinstallHook(); + } +} + +bool LowLevelHookWatchDog::CheckIfHookIsAlive() { + if(LastGetMessageEventTick < LastLowLevelEventTick) { + // this shouldn't be possible but rather safe than sorry + return true; + } + + return LastGetMessageEventTick - LastLowLevelEventTick < WATCHDOG_THRESHOLD; +} + +void LowLevelHookWatchDog::ReinstallHook() { + //keyman32.cpp: + SendDebugMessageFormat( + "Attempting to reinstall hook because watchdog threshold exceeded by %llu msec (last LL=%llu last GM=%llu)", + LastGetMessageEventTick - LastLowLevelEventTick, + LastLowLevelEventTick, + LastGetMessageEventTick + ); + + if(!RestartLowLevelHook()) { + SendDebugMessage("Attempt to reinstall low level hook may have failed, see previous log messages"); + } else { + SendDebugMessage("Attempt to reinstall low level hook succeeded"); + } + + // We should assume the hook is alive at this point to avoid repeated resets + HookIsAlive(); +} + +#ifndef _WIN64 +extern "C" void __declspec(dllexport) WINAPI Keyman_WatchDogKeyEvent() { + SendDebugMessageFormat("Keyman_WatchDogKeyEvent"); + LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc(); +} +#endif \ No newline at end of file diff --git a/windows/src/engine/keyman32/LowLevelHookWatchDog.h b/windows/src/engine/keyman32/LowLevelHookWatchDog.h new file mode 100644 index 0000000000..cd2ea07ff1 --- /dev/null +++ b/windows/src/engine/keyman32/LowLevelHookWatchDog.h @@ -0,0 +1,32 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-17 + */ + +/** + * @brief Watch for scenarios where the low level keyboard hook may be + * uninstalled by Windows, e.g. when there is heavy system load, and + * reinstall it. + */ +class LowLevelHookWatchDog { +public: + /** + * @brief Update the watchdog timestamp to current time, because the low + * level hook is receiving messages successfully + */ + static void HookIsAlive(); + + /** + * @brief Update the watchdog GetMessageProc timestamp to current time; + * this is called by the Keyman_WatchDogKeyEvent function, when + * a message posted from the GetMessageProc hook is received by + * keyman.exe. + */ + static void KeyEventReceivedInGetMessageProc(); + +private: + static bool CheckIfHookIsAlive(); + static void ReinstallHook(); +}; + diff --git a/windows/src/engine/keyman32/RightAltEmulationCheck.cpp b/windows/src/engine/keyman32/RightAltEmulationCheck.cpp new file mode 100644 index 0000000000..7c1caed8aa --- /dev/null +++ b/windows/src/engine/keyman32/RightAltEmulationCheck.cpp @@ -0,0 +1,171 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Track Windows use of Left Control + Right Alt emulation for Right Alt key + */ + +#include "pch.h" +#include "kbd.h" /* DDK kbdlayout */ + +#ifndef _WIN64 +BOOL IsWow64(); + +// Note that this has been simplified to remove types as we do not need any data +// other than the flag fLocaleFlags. +typedef struct tagKbdLayer_WOW64 { + /* + * Modifier keys + */ + DWORD pCharModifiers; + DWORD filler1; + + /* + * Characters + */ + DWORD pVkToWcharTable; // ptr to tbl of ptrs to tbl + DWORD filler2; + + /* + * Diacritics + */ + DWORD pDeadKey; + DWORD filler3; + + /* + * Names of Keys + */ + DWORD pKeyNames; + DWORD filler4; + DWORD pKeyNamesExt; + DWORD filler5; + DWORD pKeyNamesDead; + DWORD filler6; + + /* + * Scan codes to Virtual Keys + */ + DWORD pusVSCtoVK; + DWORD filler7; + DWORD bMaxVSCtoVK; + DWORD filler8; + DWORD pVSCtoVK_E0; // Scancode has E0 prefix + DWORD filler9; + DWORD pVSCtoVK_E1; // Scancode has E1 prefix + DWORD fillerA; + + /* + * Locale-specific special processing + */ + DWORD fLocaleFlags; + DWORD fillerB; +} KBDTABLES_WOW64, *PKBDTABLES_WOW64; + +typedef PKBDTABLES_WOW64 (WINAPI *PKBDLAYERDESCRIPTORWOW64FUNC)(VOID); + +#endif + +HMODULE LoadKbdLibrary(const char* keyboardLayoutName); +BOOL ReadAltGrFlagFromKbdDll(const char *keyboardLayoutName); + +typedef PKBDTABLES (WINAPI *PKBDLAYERDESCRIPTORFUNC)(VOID); + +static HKL lastActiveHkl = 0; +static BOOL altGrFlag; + +/** + * Checks whether the active Windows system keyboard has mappings for RightAlt, + * according to the flag KLLF_ALTGR from KBD.h, which implies that + * LeftCtrl+RightAlt will be generated. + + * Background: when a Windows system keyboard that maps RightAlt (AltGr) is + * active, Windows will internally generate LeftCtrl+RightAlt events when the + * RightAlt key is pressed, so we need to recognize this and mask out the Left + * Control modifier state when it happens. + */ + +BOOL KeyboardGivesCtrlRAltForRAlt() { + HKL hkl = GetKeyboardLayout(0); + + if(hkl != lastActiveHkl) { + lastActiveHkl = hkl; + + char keyboardLayoutName[KL_NAMELENGTH+1]; + GetKeyboardLayoutName(keyboardLayoutName); + + altGrFlag = ReadAltGrFlagFromKbdDll(keyboardLayoutName); + } + + return altGrFlag; +} + +BOOL ReadAltGrFlagFromKbdDll(const char *keyboardLayoutName) { + BOOL result = FALSE; + + HMODULE hKbdLibrary = LoadKbdLibrary(keyboardLayoutName); + if(!hKbdLibrary) { + SendDebugMessageFormat("Exit -- could not load library (error=%d)", GetLastError()); + return FALSE; + } + +#ifndef _WIN64 + // In a WOW64 process (i.e. 32 bit process on a 64 bit architecture), the + // KBDTABLES structure returned from a keyboard DLL is 64-bit aligned, so we + // need to account for that. + if(IsWow64()) { + // 64 bit structure in 32 bit process! + PKBDLAYERDESCRIPTORWOW64FUNC KbdLayerDescriptorFunc = (PKBDLAYERDESCRIPTORWOW64FUNC) GetProcAddress(hKbdLibrary, "KbdLayerDescriptor"); + if(KbdLayerDescriptorFunc) { + PKBDTABLES_WOW64 KbdTables = (*KbdLayerDescriptorFunc)(); + if(KbdTables) { + result = (KbdTables->fLocaleFlags & KLLF_ALTGR) ? TRUE : FALSE; + } + } + } else { +#else + { +#endif + // Native architecture - 32 bit structure in 32 bit process, or 64 bit structure in 64 bit process + PKBDLAYERDESCRIPTORFUNC KbdLayerDescriptorFunc = (PKBDLAYERDESCRIPTORFUNC) GetProcAddress(hKbdLibrary, "KbdLayerDescriptor"); + if(KbdLayerDescriptorFunc) { + PKBDTABLES KbdTables = (*KbdLayerDescriptorFunc)(); + if(KbdTables) { + result = (KbdTables->fLocaleFlags & KLLF_ALTGR) ? TRUE : FALSE; + } + } + } + + FreeLibrary(hKbdLibrary); + + return result; +} + +HMODULE LoadKbdLibrary(const char* keyboardLayoutName) { + char registryKey[128], layoutFile[MAX_PATH+1]; + + RegistryReadOnly r(HKEY_LOCAL_MACHINE); + + wsprintf(registryKey, "System\\CurrentControlSet\\Control\\keyboard layouts\\%s", keyboardLayoutName); + if(!r.OpenKeyReadOnly(registryKey)) { + return FALSE; + } + if(!r.ReadString("Layout File", layoutFile, sizeof(layoutFile)/sizeof(layoutFile[0]))) { + return FALSE; + } + + return LoadLibrary(layoutFile); +} + +BOOL fStored = FALSE; +BOOL bIsWow64 = FALSE; + +BOOL IsWow64() { + if(fStored) { + return bIsWow64; + } + if(!IsWow64Process(GetCurrentProcess(), &bIsWow64)) { + // Assume 32 bit machine on failure + bIsWow64 = FALSE; + } + fStored = TRUE; + return bIsWow64; +} diff --git a/windows/src/engine/keyman32/build.sh b/windows/src/engine/keyman32/build.sh index 1c52948921..408632481e 100755 --- a/windows/src/engine/keyman32/build.sh +++ b/windows/src/engine/keyman32/build.sh @@ -36,6 +36,12 @@ function do_clean_x64() { clean_windows_project_files } +function do_configure() { + # Install NuGet packages for keyman32.test.vcxproj + run_in_vs_env msbuild.exe tests/keyman32.tests.vcxproj -t:Restore "-clp:Verbosity=minimal" -nologo "-p:RestorePackagesConfig=true" + configure_windows_build_environment +} + function do_build_x86() { create-windows-output-folders build_version.res @@ -74,12 +80,40 @@ function do_install_x64() { cp "$WINDOWS_PROGRAM_ENGINE/keyman64.dll" "$INSTALLPATH_KEYMANENGINE/keyman64.dll" } +# Parameters: +# 1: Platform = Win32 | x64 +# Note Platform should not be 'x86' but rather 'Win32' for 32-bit +function do_test() { + local Platform="$1" + local Configuration=Release + if builder_is_debug_build; then + Configuration=Debug + fi + + # We run these builds with minimal verbosity because generally any + # environmental details that could be helpful in logs will have been revealed + # in the build action, and it's cleaner when running testing locally to have + # minimal logs. + # + # This is a non-standard build target, e.g. "Debug Static Library", so we + # can't use our vs_msbuild wrapper here. + run_in_vs_env --quiet msbuild.exe keyman32.vcxproj "-clp:Verbosity=minimal" -noLogo "//p:Configuration=${Configuration} Static Library" "//t:Build" "//p:Platform=${Platform}" + # pushd tests >/dev/null + run_in_vs_env --quiet msbuild.exe tests/keyman32.tests.vcxproj "-clp:Verbosity=minimal" -noLogo "//p:Configuration=${Configuration}" //t:Build "//p:Platform=${Platform}" + # popd >/dev/null + echo + + "./tests/bin/${Platform}/${Configuration}/keyman32.tests.exe" +} + builder_run_action clean:x86 do_clean_x86 builder_run_action clean:x64 do_clean_x64 -builder_run_action configure configure_windows_build_environment +builder_run_action configure do_configure + builder_run_action build:x86 do_build_x86 builder_run_action build:x64 do_build_x64 -# builder_run_action test:project do_test +builder_run_action test:x86 do_test Win32 +builder_run_action test:x64 do_test x64 builder_run_action publish:x86 do_publish_x86 builder_run_action publish:x64 do_publish_x64 builder_run_action install:x86 do_install_x86 diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index 2fa5319d68..807b246466 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -133,18 +133,14 @@ PKEYMAN64THREADDATA Globals_InitThread() void Globals_UninitThread() { - OutputThreadDebugString("Globals_UninitThread"); + // OutputThreadDebugString("Globals_UninitThread"); if (!Globals_ProcessInitialised()) { - OutputThreadDebugString("Globals_UninitThread aborted without cleanup"); + // OutputThreadDebugString("Globals_UninitThread aborted without cleanup"); return; } ISerialKeyEventClient::Shutdown(); - PKEYMAN64THREADDATA _td = ThreadGlobals(); - if (_td) { - delete _td->pSerialKeyEventClient; - } CloseThreadSharedBufferManager(); EnterCriticalSection(&csGlobals); diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index ab1bb57985..4b40846b93 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -141,6 +141,8 @@ LRESULT _kmnLowLevelKeyboardProc( SendDebugEntry(); + LowLevelHookWatchDog::HookIsAlive(); + PKBDLLHOOKSTRUCT hs = (PKBDLLHOOKSTRUCT) lParam; BOOL extended = hs->flags & LLKHF_EXTENDED ? TRUE : FALSE; @@ -176,10 +178,15 @@ LRESULT _kmnLowLevelKeyboardProc( if (GetKeyState(VK_LSHIFT) < 0) { FHotkeyShiftState |= HK_SHIFT; } + if (GetKeyState(VK_RSHIFT) < 0) { FHotkeyShiftState |= AllowRightModifierHotkey ? HK_SHIFT : HK_RSHIFT_INVALID; } + if(isModifierKey(hs->vkCode)) { + PostVisualKeyboardModifierEvent(hs->vkCode, LLKHFFlagstoWMKeymanKeyEventFlags(hs)); + } + //TODO: #8064. Can remove debug message once issue #8064 is resolved SendDebugMessageFormat("[FHotkeyShiftState:%x]", FHotkeyShiftState); diff --git a/windows/src/engine/keyman32/k32_visualkeyboardinterface.cpp b/windows/src/engine/keyman32/k32_visualkeyboardinterface.cpp new file mode 100644 index 0000000000..bee8f75d62 --- /dev/null +++ b/windows/src/engine/keyman32/k32_visualkeyboardinterface.cpp @@ -0,0 +1,65 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-10-08 + * + * Interface between keyman32.dll and the visual keyboard module hosted in + * keyman.exe + */ +#include "pch.h" + +// These messages should correspond to the UfrmOSKOnScreenKeyboard section in +// UserMessages.pas. Only messages used in keyman32 are currently defined here. +#define WM_KEYMAN_OSK_MODIFIER_EVENT (WM_USER + 141) + +/** + * Tell the visual keyboard module that modifier keys on the physical keyboard + * have been pressed or released, in order to update the display of the visual + * keyboard. This is sent from the low level keyboard hook, so is an in-process + * single thread event. + * + * This takes the chiral VK_LCONTROL / VK_RCONTROL / VK_LMENU / VK_RMENU virtual + * key codes. + * + * This function exists primarily to support workaround of the Windows hack of + * generating LCtrl+RAlt when AltGr is pressed on European layouts. The visual + * keyboard module includes special handling for the custom scan code of 0x21D + * for the fudged LCtrl key. + * + * @param vkCode virtual key code of the modifier key, chiral for Alt and Ctrl + * @param flags as follows: + * KEYEVENTF_EXTENDEDKEY 0x00000001 - extended bit is set on the scan code + * KEYEVENTF_KEYUP 0x00000002 - key is being released + * scan code mask 0x0FFF0000 - 12 bits for scan code + * all other bits reserved + */ +void PostVisualKeyboardModifierEvent(UINT vkCode, DWORD flags) { + PostMessageToVisualKeyboardWindowIfExists(WM_KEYMAN_OSK_MODIFIER_EVENT, vkCode, flags); +} + +/** + * Find the handle to the Visual Keyboard window in keyman.exe, if it is open, + * and returns NULL if not found. + * + * @return HWND Handle to the Visual Keyboard window + */ +HWND GetVisualKeyboardWindow() { + return FindWindow("TfrmVisualKeyboard", NULL); +} + +/** + * Post a message to the Visual Keyboard window in keyman.exe, if it is open. + * + * @param msg Windows message + * @param wParam WPARAM + * @param lParam LPARAM + * @return BOOL FALSE if window does exist or on PostMessage failure + */ +BOOL PostMessageToVisualKeyboardWindowIfExists(UINT msg, WPARAM wParam, LPARAM lParam) { + HWND hwnd = GetVisualKeyboardWindow(); + if(hwnd == NULL) { + return FALSE; + } + + return PostMessage(hwnd, msg, wParam, lParam); +} diff --git a/windows/src/engine/keyman32/k32_visualkeyboardinterface.h b/windows/src/engine/keyman32/k32_visualkeyboardinterface.h new file mode 100644 index 0000000000..4c7d39787f --- /dev/null +++ b/windows/src/engine/keyman32/k32_visualkeyboardinterface.h @@ -0,0 +1,14 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-10-08 + * + * Interface between keyman32.dll and the visual keyboard module hosted in + * keyman.exe + */ + + #pragma once + +HWND GetVisualKeyboardWindow(); +void PostVisualKeyboardModifierEvent(UINT vkCode, DWORD flags); +BOOL PostMessageToVisualKeyboardWindowIfExists(UINT msg, WPARAM wParam, LPARAM lParam); diff --git a/windows/src/engine/keyman32/keyman-engine.vcxproj b/windows/src/engine/keyman32/keyman-engine.vcxproj deleted file mode 100644 index f958f9a797..0000000000 --- a/windows/src/engine/keyman32/keyman-engine.vcxproj +++ /dev/null @@ -1,402 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {A7069186-5AE7-4504-8926-861B7A34BA9E} - Keyman32 - 10.0 - - - - StaticLibrary - false - v142 - - - StaticLibrary - false - v142 - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - AllRules.ruleset - - - AllRules.ruleset - - - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\include;$(IncludePath) - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - ./ - ./ - ./ - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keymancore.lib;libicuuc.a;libicuin.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - ./Keyman32.dll - true - keyman32.def - true - ./Keyman32.pdb - true - ./Keyman32.map - false - Windows - true - true - 0x1c100000 - ./Keyman32.lib - MachineX86 - true - true - - - true - ./Keyman32.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - ./ - ./ - ./ - true - Level4 - true - true - EditAndContinue - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - keymancore.lib;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - ./Keyman32.dll - true - keyman32.def - true - ./Keyman32.pdb - true - ./Keyman32.map - Windows - 0x1C100000 - ./Keyman32.lib - MachineX86 - true - true - - - true - ./Keyman32.bsc - - - - - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - Create - Create - - - - - - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - - - - - Document - false - false - mc %(FullPath) - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - mc %(FullPath) - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - Designer - - - - - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman-engine.vcxproj.filters b/windows/src/engine/keyman32/keyman-engine.vcxproj.filters deleted file mode 100644 index f11552a824..0000000000 --- a/windows/src/engine/keyman32/keyman-engine.vcxproj.filters +++ /dev/null @@ -1,256 +0,0 @@ - - - - - {2dc5904b-4033-47e6-88ac-ebe442c06c2e} - cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 - - - {27baf0f2-3926-4720-b5f9-045e47cd975c} - h;hpp;hxx;hm;inl;fi;fd - - - {088a2b68-c9db-41f4-9f45-37fdf758931e} - ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Source Files - - - Resource Files - - - Resource Files - - - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Source Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - - \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman32.cpp b/windows/src/engine/keyman32/keyman32.cpp index aadd695906..43be38f105 100644 --- a/windows/src/engine/keyman32/keyman32.cpp +++ b/windows/src/engine/keyman32/keyman32.cpp @@ -119,27 +119,20 @@ BOOL __stdcall DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID reserved) switch(fdwReason) { case DLL_PROCESS_ATTACH: - //OutputThreadDebugString("DLL_PROCESS_ATTACH"); if(!Globals_InitProcess()) return FALSE; break; case DLL_PROCESS_DETACH: if (reserved == NULL) { // If reserved == NULL, that means the library is being unloaded, but // the process is not terminating. - //OutputThreadDebugString("DLL_PROCESS_DETACH not terminating"); UninitialiseProcess(FALSE); Globals_UninitProcess(); - } - else { - //OutputThreadDebugString("DLL_PROCESS_DETACH terminating"); } break; case DLL_THREAD_ATTACH: - //OutputThreadDebugString("DLL_THREAD_ATTACH"); Globals_InitThread(); break; case DLL_THREAD_DETACH: - //OutputThreadDebugString("DLL_THREAD_DETACH"); UninitialiseProcess(FALSE); Globals_UninitThread(); break; @@ -279,6 +272,24 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_GetInitialised(BOOL *FSingleA return Globals::get_Keyman_Initialised(); } +#ifndef _WIN64 +BOOL InitLowLevelHook() { + HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME); + + *Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124 + return Globals::get_hhookLowLevelKeyboardProc() != NULL; +} + +BOOL UninitLowLevelHook() { + BOOL RetVal = TRUE; + if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124 + RetVal = FALSE; + + *Globals::hhookLowLevelKeyboardProc() = NULL; + return RetVal; +} +#endif + BOOL InitHooks() { HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME); @@ -286,7 +297,7 @@ BOOL InitHooks() *Globals::hhookGetMessage() = SetWindowsHookExW(WH_GETMESSAGE, (HOOKPROC) kmnGetMessageProc, hinst, Globals::get_FSingleThread()); *Globals::hhookCallWndProc() = SetWindowsHookExW(WH_CALLWNDPROC, (HOOKPROC) kmnCallWndProc, hinst, Globals::get_FSingleThread()); #ifndef _WIN64 - *Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124 + InitLowLevelHook(); #endif; return @@ -304,19 +315,14 @@ BOOL UninitHooks() if(Globals::get_hhookGetMessage() && !UnhookWindowsHookEx(Globals::get_hhookGetMessage())) RetVal = FALSE; - else - *Globals::hhookGetMessage() = NULL; + *Globals::hhookGetMessage() = NULL; if(Globals::get_hhookCallWndProc() && !UnhookWindowsHookEx(Globals::get_hhookCallWndProc())) RetVal = FALSE; - else - *Globals::hhookCallWndProc() = NULL; + *Globals::hhookCallWndProc() = NULL; #ifndef _WIN64 - if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124 - RetVal = FALSE; - else - *Globals::hhookLowLevelKeyboardProc() = NULL; + RetVal = UninitLowLevelHook() && RetVal; #endif return RetVal; @@ -473,6 +479,27 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_RestartEngine() return TRUE; } +BOOL RestartLowLevelHook() { + BOOL result=true; + +#ifndef _WIN64 + if(!Globals::get_Keyman_Initialised()) { + return FALSE; + } + + if(!UninitLowLevelHook()) { + SendDebugMessageFormat("Failed to uninstall low level hook. GetLastError = %d", GetLastError()); + result = FALSE; + } + if(!InitLowLevelHook()) { + SendDebugMessageFormat("Failed to install low level hook. GetLastError = %d", GetLastError()); + result = FALSE; + } + +#endif + return result; +} + //--------------------------------------------------------------------------------------------------------- // // Utility guff functions @@ -838,7 +865,7 @@ void RefreshKeyboards(BOOL Initialising) void ReleaseKeyboards(BOOL Lock) { - OutputThreadDebugString("ReleaseKeyboards"); + // OutputThreadDebugString("ReleaseKeyboards"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if(!_td || !_td->lpKeyboards) return; diff --git a/windows/src/engine/keyman32/keyman32.def b/windows/src/engine/keyman32/keyman32.def index a61a78b463..f457075ce8 100644 --- a/windows/src/engine/keyman32/keyman32.def +++ b/windows/src/engine/keyman32/keyman32.def @@ -49,3 +49,5 @@ EXPORTS Keyman_UnregisterControllerThread SetCustomPostKeyCallback + + Keyman_WatchDogKeyEvent diff --git a/windows/src/engine/keyman32/keyman32.sln b/windows/src/engine/keyman32/keyman32.sln index c435faea64..a9af69c174 100644 --- a/windows/src/engine/keyman32/keyman32.sln +++ b/windows/src/engine/keyman32/keyman32.sln @@ -1,26 +1,41 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Keyman32", "Keyman32.vcxproj", "{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.ActiveCfg = Debug|Win32 - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.Build.0 = Debug|Win32 - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|x64.ActiveCfg = Debug|Win32 - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.ActiveCfg = Release|Win32 - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.Build.0 = Release|Win32 - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|x64.ActiveCfg = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36616.10 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "keyman32", "Keyman32.vcxproj", "{BD5564FB-35A5-4A3C-B96A-4A6578E2B593}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Static Library|Win32 = Debug Static Library|Win32 + Debug Static Library|x64 = Debug Static Library|x64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release Static Library|Win32 = Release Static Library|Win32 + Release Static Library|x64 = Release Static Library|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug Static Library|Win32.ActiveCfg = Debug Static Library|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug Static Library|Win32.Build.0 = Debug Static Library|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug Static Library|x64.ActiveCfg = Debug Static Library|x64 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug Static Library|x64.Build.0 = Debug Static Library|x64 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.ActiveCfg = Debug|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|Win32.Build.0 = Debug|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Debug|x64.ActiveCfg = Debug|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release Static Library|Win32.ActiveCfg = Release Static Library|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release Static Library|Win32.Build.0 = Release Static Library|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release Static Library|x64.ActiveCfg = Release Static Library|x64 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release Static Library|x64.Build.0 = Release Static Library|x64 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.ActiveCfg = Release|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|Win32.Build.0 = Release|Win32 + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593}.Release|x64.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F17E1FA3-34C9-4C73-98ED-EFB4036F92CC} + EndGlobalSection +EndGlobal diff --git a/windows/src/engine/keyman32/keyman32.vcxproj b/windows/src/engine/keyman32/keyman32.vcxproj index 8f75aeb126..d865fcef44 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj +++ b/windows/src/engine/keyman32/keyman32.vcxproj @@ -1,464 +1,809 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {BD5564FB-35A5-4A3C-B96A-4A6578E2B593} - Keyman32 - 10.0 - keyman32 - - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - DynamicLibrary - false - v142 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - keyman32 - - - $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) - keyman64 - - - $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x86\$(Configuration);$(LibraryPath) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) - keyman32 - - - $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\core\build\rust\x64\$(Configuration);$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) - keyman64 - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman32.def - true - true - false - Windows - true - true - 0x1c100000 - MachineX86 - true - true - ..\..\..\lib\keyman32.lib - - - true - ./Keyman32.bsc - - - - - - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - ./Keyman32.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - MultiThreaded - true - Use - pch.h - All - Level4 - true - true - ProgramDatabase - - - NDEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman64.def - true - true - false - Windows - true - true - 0x1c100000 - true - true - ..\..\..\lib\keyman64.lib - - - true - ./Keyman64.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - true - Level4 - true - true - EditAndContinue - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman32.def - true - true - Windows - 0x1C100000 - MachineX86 - true - true - ..\..\..\lib\keyman32.lib - - - true - ./Keyman32.bsc - - - - - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - ./Keyman32.tlb - - - - - Disabled - .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - MultiThreadedDebug - Use - pch.h - All - true - Level4 - true - true - ProgramDatabase - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) - libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) - true - keyman64.def - true - true - Windows - 0x1C100000 - true - true - ..\..\..\lib\keyman64.lib - - - true - ./Keyman32.bsc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - - - - - - Document - false - false - false - false - mc %(FullPath) - mc %(FullPath) - Compiling keyman-debug-etw - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - %(Filename).rc;%(Filename.h);MSG00001.bin - mc %(FullPath) - mc %(FullPath) - Compiling keyman-debug-etw - Compiling keyman-debug-etw - %(Filename).rc;%(Filename.h);MSG00001.bin - %(Filename).rc;%(Filename.h);MSG00001.bin - Designer - - - true - true - - - - - true - true - - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - true - true - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug Static Library + Win32 + + + Debug Static Library + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release Static Library + Win32 + + + Release Static Library + x64 + + + Release + Win32 + + + Release + x64 + + + + {BD5564FB-35A5-4A3C-B96A-4A6578E2B593} + Keyman32 + 10.0 + keyman32 + + + + DynamicLibrary + false + v143 + + + StaticLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + StaticLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + StaticLibrary + false + v143 + + + DynamicLibrary + false + v143 + + + StaticLibrary + false + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\bin\$(Platform)\Release\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\bin\$(Platform)\Release\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\obj\$(Platform)\Release\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\obj\$(Platform)\Release\ + false + false + false + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\bin\$(Platform)\Debug\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\bin\$(Platform)\Debug\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\obj\$(Platform)\Debug\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)lib\obj\$(Platform)\Debug\ + true + true + true + true + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + + + + + + + + + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + AllRules.ruleset + + + + + + + + + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x86\Debug\src;$(ProjectDir)..\..\..\..\core\build\x86\Debug\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\Debug\subprojects\icu\source\i18n;$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\Debug\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) + keyman64 + + + $(ProjectDir)..\..\..\..\core\build\x64\Debug\src;$(ProjectDir)..\..\..\..\core\build\x64\Debug\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\Debug\subprojects\icu\source\i18n;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\Debug\include;$(IncludePath) + keyman64 + + + $(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x86\Debug\src;$(ProjectDir)..\..\..\..\core\build\x86\Debug\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\Debug\subprojects\icu\source\i18n;$(LibraryPath) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x86\Release\include;$(IncludePath) + keyman32 + + + $(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\$(Configuration)\include;$(IncludePath) + keyman64 + + + $(ProjectDir)..\..\..\..\core\build\x64\Release\src;$(ProjectDir)..\..\..\..\core\build\x64\Release\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x64\Release\subprojects\icu\source\i18n;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(ProjectDir)..\..\..\..\common\include;$(ProjectDir)..\..\..\..\core\include;$(ProjectDir)..\..\..\..\core\build\x64\Release\include;$(IncludePath) + keyman64 + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + false + Windows + true + true + 0x1c100000 + MachineX86 + true + true + ..\..\..\lib\keyman32.lib + + + true + ./Keyman32.bsc + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + false + Windows + true + true + 0x1c100000 + MachineX86 + true + true + + + true + ./Keyman32.bsc + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + false + Windows + true + true + 0x1c100000 + true + true + ..\..\..\lib\keyman64.lib + + + true + ./Keyman64.bsc + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + MultiThreaded + true + Use + pch.h + All + Level4 + true + true + ProgramDatabase + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + false + Windows + true + true + 0x1c100000 + true + true + + + true + ./Keyman64.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + Windows + 0x1C100000 + MachineX86 + true + true + ..\..\..\lib\keyman32.lib + + + true + ./Keyman32.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman32.def + true + true + Windows + 0x1C100000 + MachineX86 + true + true + + + true + ./Keyman32.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + Windows + 0x1C100000 + true + true + ..\..\..\lib\keyman64.lib + + + true + ./Keyman32.bsc + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + ./Keyman32.tlb + + + + + Disabled + .\;..\..\global\inc;.;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + Use + pch.h + All + true + Level4 + true + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + /verbose:lib /section:.SHARDATA,rws %(AdditionalOptions) + libicuuc.a;libicuin.a;libkeymancore.a;psapi.lib;rpcrt4.lib;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + true + keyman64.def + true + true + Windows + 0x1C100000 + true + true + + + true + ./Keyman32.bsc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + + Document + false + false + false + false + false + false + false + false + mc %(FullPath) + mc %(FullPath) + mc %(FullPath) + mc %(FullPath) + Compiling keyman-debug-etw + Compiling keyman-debug-etw + Compiling keyman-debug-etw + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + mc %(FullPath) + mc %(FullPath) + mc %(FullPath) + mc %(FullPath) + Compiling keyman-debug-etw + Compiling keyman-debug-etw + Compiling keyman-debug-etw + Compiling keyman-debug-etw + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + %(Filename).rc;%(Filename.h);MSG00001.bin + Designer + + + true + true + true + true + + + + + true + true + true + true + + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + true + true + true + true + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman32.vcxproj.filters b/windows/src/engine/keyman32/keyman32.vcxproj.filters index 5627c72b04..678197d8f1 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj.filters +++ b/windows/src/engine/keyman32/keyman32.vcxproj.filters @@ -1,265 +1,274 @@ - - - - - {2dc5904b-4033-47e6-88ac-ebe442c06c2e} - cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 - - - {27baf0f2-3926-4720-b5f9-045e47cd975c} - h;hpp;hxx;hm;inl;fi;fd - - - {088a2b68-c9db-41f4-9f45-37fdf758931e} - ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Source Files - - - Resource Files - - - Resource Files - - - Source Files - - - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Source Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - + + + + + {2dc5904b-4033-47e6-88ac-ebe442c06c2e} + cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 + + + {27baf0f2-3926-4720-b5f9-045e47cd975c} + h;hpp;hxx;hm;inl;fi;fd + + + {088a2b68-c9db-41f4-9f45-37fdf758931e} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Resource Files + + + Resource Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Source Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keymancontrol.h b/windows/src/engine/keyman32/keymancontrol.h index 70a0c5d39e..73f30f77de 100644 --- a/windows/src/engine/keyman32/keymancontrol.h +++ b/windows/src/engine/keyman32/keymancontrol.h @@ -47,6 +47,12 @@ #define KMC_PROFILECHANGED 18 // 9.0.426.0 // I3933 +#define KMC_HINTRESPONSE 19 // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum + +#define KMC_WATCHDOG_FAKEFREEZE 20 // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability +#define KMC_WATCHDOG_KEYEVENT 21 // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread + + #define PC_UPDATE 0 // Tell Keyman to update its display of active keyboard #define PC_UPDATE_LANGUAGESWITCH 1 // Tell Keyman to update its display of active keyboard and then open language switch form #define PC_HOTKEYCHANGE 2 // Tell Keyman that a hotkey was pressed to switch keyboard diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index 058ea5cf17..2304bc5f38 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -140,8 +140,6 @@ BOOL LoadlpKeyboard(int i); PSTR wstrtostr(PCWSTR in); PWSTR strtowstr(PSTR in); -WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags); - void SelectApplicationIntegration(); // I4287 void PostDummyKeyEvent(); // I3301 - Handle I3250 regression with inadvertent menu activation with Alt keys // I3534 // I4844 @@ -262,6 +260,8 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd); #include "..\..\..\..\common\windows\cpp\include\crc32.h" #include "k32_tsf.h" +#include "k32_visualkeyboardinterface.h" +#include "LowLevelHookWatchDog.h" void ReportActiveKeyboard(WORD wCommand); // I3933 // I3949 void SelectKeyboardHKL(DWORD hkl, BOOL foreground); // I3933 // I3949 // I4271 @@ -272,4 +272,6 @@ void ProcessModifierChange(UINT key, BOOL isUp, BOOL isExtended); // I4793 BOOL SetupCoreEnvironment(km_core_option_item **test_env_opts); void DeleteCoreEnvironment(km_core_option_item *test_env_opts); +BOOL RestartLowLevelHook(); + #endif // _KEYMANENGINE_H diff --git a/windows/src/engine/keyman32/kmhook_getmessage.cpp b/windows/src/engine/keyman32/kmhook_getmessage.cpp index f9aa15784c..4fee835254 100644 --- a/windows/src/engine/keyman32/kmhook_getmessage.cpp +++ b/windows/src/engine/keyman32/kmhook_getmessage.cpp @@ -146,6 +146,15 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) } if ((mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN || mp->message == WM_KEYUP || mp->message == WM_SYSKEYUP)) { // I4642 + + // To help our low level keyboard hook recover if system load causes it to + // be uninstalled, we tell the controller process that we should have seen a + // keystroke event in the low level keyboard hook; this gets passed into the + // LowLevelHookWatchDog via keyman.exe + if(mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN) { + Globals::PostMasterController(wm_keyman_control, KMC_WATCHDOG_KEYEVENT, 0); + } + BYTE scan = KEYMSG_LPARAM_SCAN(mp->lParam); CheckScheduledRefresh(); _td->LastScanCode = scan; @@ -170,10 +179,11 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) } // I1337 - move mousewheel message checking before the PM_REMOVE handling - otherwise we can't pre-modify the message - // TODO: Move to WH_MOUSE_LL proc in main process + // TODO: Move to WH_MOUSE_LL proc in main process? if(mp->message == WM_MOUSEWHEEL) { - HWND hwndMouseWheel = FindWindow("TfrmVisualKeyboard", NULL); + // TODO: move most of this logic to k32_visualkeyboardinterface.cpp + HWND hwndMouseWheel = GetVisualKeyboardWindow(); if(hwndMouseWheel && GetWindowThreadProcessId(mp->hwnd, NULL) != GetWindowThreadProcessId(hwndMouseWheel, NULL)) { RECT rcMouseWheel; diff --git a/windows/src/engine/keyman32/preservedkeymap.cpp b/windows/src/engine/keyman32/preservedkeymap.cpp index 6170d40bda..1bf473d17e 100644 --- a/windows/src/engine/keyman32/preservedkeymap.cpp +++ b/windows/src/engine/keyman32/preservedkeymap.cpp @@ -26,8 +26,6 @@ // I3623 // I4169 // I4575 // I4575 #include "pch.h" -WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 - struct PreservedKey { GUID guid; diff --git a/windows/src/engine/keyman32/serialkeyeventclient.cpp b/windows/src/engine/keyman32/serialkeyeventclient.cpp index 5cf31b8189..676d44dd9b 100644 --- a/windows/src/engine/keyman32/serialkeyeventclient.cpp +++ b/windows/src/engine/keyman32/serialkeyeventclient.cpp @@ -22,25 +22,25 @@ public: m_hKeyEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, GLOBAL_KEY_EVENT_NAME); if (m_hKeyEvent == 0) { - DebugLastError("OpenEvent"); + // DebugLastError("OpenEvent"); return; } m_hKeyMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, GLOBAL_KEY_MUTEX_NAME); if (m_hKeyMutex == 0) { - DebugLastError("OpenMutex"); + // DebugLastError("OpenMutex"); return; } m_hMMF = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, GLOBAL_FILE_MAPPING_NAME); if (m_hMMF == 0) { - DebugLastError("OpenFileMapping"); + // DebugLastError("OpenFileMapping"); return; } m_pSharedData = (SerialKeyEventSharedData *)MapViewOfFile(m_hMMF, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SerialKeyEventSharedData)); if (!m_pSharedData) { - DebugLastError("MapViewOfFile"); + // DebugLastError("MapViewOfFile"); return; } } @@ -48,25 +48,25 @@ public: SerialKeyEventClient::~SerialKeyEventClient() { if (m_pSharedData != NULL) { if (!UnmapViewOfFile(m_pSharedData)) { - DebugLastError("UnmapViewOfFile"); + // DebugLastError("UnmapViewOfFile"); } } if (m_hMMF != NULL) { if (!CloseHandle(m_hMMF)) { - DebugLastError("CloseHandle(m_hMMF)"); + // DebugLastError("CloseHandle(m_hMMF)"); } } if (m_hKeyMutex != NULL) { if (!CloseHandle(m_hKeyMutex)) { - DebugLastError("CloseHandle(m_hKeyMutex)"); + // DebugLastError("CloseHandle(m_hKeyMutex)"); } } if (m_hKeyEvent != NULL) { if (!CloseHandle(m_hKeyEvent)) { - DebugLastError("CloseHandle(m_hKeyEvent)"); + // DebugLastError("CloseHandle(m_hKeyEvent)"); } } } @@ -149,7 +149,7 @@ public: }; void ISerialKeyEventClient::Startup() { - OutputThreadDebugString("ISerialKeyEventClient::Startup"); + // OutputThreadDebugString("ISerialKeyEventClient::Startup"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if (_td) { _td->pSerialKeyEventClient = new SerialKeyEventClient(); @@ -157,7 +157,7 @@ void ISerialKeyEventClient::Startup() { } void ISerialKeyEventClient::Shutdown() { - OutputThreadDebugString("ISerialKeyEventClient::Shutdown"); + // OutputThreadDebugString("ISerialKeyEventClient::Shutdown"); PKEYMAN64THREADDATA _td = ThreadGlobals(); if (_td && _td->pSerialKeyEventClient) { delete _td->pSerialKeyEventClient; diff --git a/windows/src/engine/keyman32/serialkeyeventserver.cpp b/windows/src/engine/keyman32/serialkeyeventserver.cpp index e2c381bbd3..13e78f3836 100644 --- a/windows/src/engine/keyman32/serialkeyeventserver.cpp +++ b/windows/src/engine/keyman32/serialkeyeventserver.cpp @@ -475,6 +475,10 @@ private: There is a possibility that some apps may try and sniff that 0x21D scan code and get confused because Keyman doesn't emit it. Hopefully this is rare. + + See also: + * PostVisualKeyboardModifierEvent in k32_visualkeyboardinterface.cpp + * TfrmOSKOnScreenKeyboard.OskModifierEvent in UfrmOSKOnScreenKeyboard.cpp */ INPUT input[2]; input[0].type = INPUT_KEYBOARD; diff --git a/windows/src/engine/keyman32/syskbd.cpp b/windows/src/engine/keyman32/syskbd.cpp index 7d01ef376c..3b0d9ac841 100644 --- a/windows/src/engine/keyman32/syskbd.cpp +++ b/windows/src/engine/keyman32/syskbd.cpp @@ -36,12 +36,11 @@ #include "pch.h" -extern WCHAR CharFromVK_NT(WORD *VKey, UINT ShiftFlags); // I4582 - WORD VkToVkByScanCode[256] = {0}; WORD USVkToVkByScanCode[256] = {0}; // I3762 -extern BOOL KeyboardGivesCtrlRAltForRAlt_NT(); +WORD VKToScanCodeToVK(WORD VKey); +WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags); /************************************************************************/ /* CharFromVK: Return a character code from a virtual key code */ @@ -55,8 +54,6 @@ WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags) // I4582 *VKey = VKToScanCodeToVK(*VKey); return MapVirtualKeys(*VKey, ShiftFlags); - - //return CharFromVK_NT(VKey, ShiftFlags); } /* I3358 - reserved keys for mapping. Note that some of these will not get here in AIWin2000Unicode mode due to being blocked in KeyboardProc. They'll still get here via TSF though */ // I3535 @@ -229,14 +226,3 @@ WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags) return 0; //keyCode; } - -/* - For some reason, keyboards that use RAlt as a shifter modify it internally to Ctrl+RAlt, which - just confuses the heck out of keyboards that use RAlt. So we work around by recognising this and - adding Ctrl to the tests for these layouts. -*/ - -BOOL KeyboardGivesCtrlRAltForRAlt() -{ - return KeyboardGivesCtrlRAltForRAlt_NT(); -} diff --git a/windows/src/engine/keyman32/syskbd.h b/windows/src/engine/keyman32/syskbd.h index 608c4c3e8f..23ef2d752a 100644 --- a/windows/src/engine/keyman32/syskbd.h +++ b/windows/src/engine/keyman32/syskbd.h @@ -1,18 +1,18 @@ /* Name: syskbd Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 11 Dec 2009 Modified Date: 3 Feb 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 30 Nov 2009 - mcdurdin - I934 - Prep for x64 - change UINT to WORD for vkeys 16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard 03 Feb 2015 - mcdurdin - I4582 - V9.0 - Most underlying layout code in Keyman32 is now obsolete and needs to be removed @@ -36,10 +36,8 @@ #define VK_xDF 0xDF WCHAR CharFromVK(WORD *VKey, UINT ShiftFlags); // I4582 +WORD USVKToScanCodeToLayoutVK(WORD VKey); // I3762 -WORD VKToScanCodeToVK(WORD VKey); - -WCHAR MapVirtualKeys(WORD keyCode, UINT shiftFlags); BOOL KeyboardGivesCtrlRAltForRAlt(); #endif diff --git a/windows/src/engine/keyman32/syskbdnt.cpp b/windows/src/engine/keyman32/syskbdnt.cpp deleted file mode 100644 index 8a82780f22..0000000000 --- a/windows/src/engine/keyman32/syskbdnt.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - Name: syskbdnt - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 22 Jan 2007 - - Modified Date: 6 Feb 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 22 Jan 2007 - mcdurdin - Don't translate any number pad keys - 13 Jul 2007 - mcdurdin - I911 - Fix for Ctrl+BKSP - 10 Sep 2008 - mcdurdin - I1635 - mnemonic layouts on x64 fail - 11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888 - 30 Nov 2009 - mcdurdin - I934 - Prep for x64 - change UINT to WORD for vkeys - 30 Nov 2009 - mcdurdin - I2155 - Fix crash when RALT pressed on wow64 - 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version - 12 Mar 2010 - mcdurdin - I934 - x64 - Complete - 12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings - 04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value - 16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard - 03 Feb 2015 - mcdurdin - I4582 - V9.0 - Most underlying layout code in Keyman32 is now obsolete and needs to be removed - 06 Feb 2015 - mcdurdin - I4583 - V9.0 - Remove altgr lookup test from keyman32 and put it into the registry -*/ - // I4169 // I4582 // I4583 -//////////////////////////////////////////////////////////////////////////// -// -// Use Windows NT/2000 keyboard DLLs for mapping virtual keys to characters -// -//////////////////////////////////////////////////////////////////////////// - -#include "pch.h" -#include "kbd.h" /* DDK kbdlayout */ - -// This file is used only in keyman32.dll; it maps syskbd for x86 builds of Windows -#ifndef _WIN64 - -extern BOOL KeyboardGivesCtrlRAltForRAlt_NT_x64(); -extern BOOL IsWow64(); - -#endif - -typedef PKBDTABLES (WINAPI *PKBDLAYERDESCRIPTORFUNC)(VOID); - -static WCHAR PreviousDeadkey_NT = 0; -static HKL ActiveHKL_NT = 0; -static WORD VkToVkByChar_NT[256] = {0}; - -static HMODULE hKbdLibrary = NULL; -static PKBDTABLES KbdTables = NULL; - -BOOL LoadNewLibrary() -{ - char buf[128], buf2[KL_NAMELENGTH+1]; - - memset(VkToVkByChar_NT, 0, sizeof(VkToVkByChar_NT)); - - if(hKbdLibrary) FreeLibrary(hKbdLibrary); - - PreviousDeadkey_NT = 0; - - RegistryReadOnly *r = new RegistryReadOnly(HKEY_LOCAL_MACHINE); - __try - { - GetKeyboardLayoutName(buf2); - wsprintf(buf, "System\\CurrentControlSet\\Control\\keyboard layouts\\%s", buf2); - if(r->OpenKeyReadOnly(buf)) - { - if(r->ReadString("Layout File", buf, 32)) - { - hKbdLibrary = LoadLibrary(buf); - if(!hKbdLibrary) - { - SendDebugMessageFormat("Exit -- could not load library"); - return FALSE; - } - PKBDLAYERDESCRIPTORFUNC KbdLayerDescriptorFunc = (PKBDLAYERDESCRIPTORFUNC) GetProcAddress(hKbdLibrary, "KbdLayerDescriptor"); - if(KbdLayerDescriptorFunc) - { - KbdTables = (*KbdLayerDescriptorFunc)(); - if(KbdTables) - return TRUE; - } - - FreeLibrary(hKbdLibrary); - } - } - - hKbdLibrary = NULL; - KbdTables = NULL; - } - __finally - { - delete r; - } - SendDebugMessageFormat("Exit -- failed to find function"); - return FALSE; -} - -BOOL KeyboardGivesCtrlRAltForRAlt_NT() -{ -#ifndef _WIN64 - if(IsWow64()) return KeyboardGivesCtrlRAltForRAlt_NT_x64(); // I2155 -#endif - - HKL hkl = GetKeyboardLayout(0); - - /* Find the appropriate keyboard tables */ - - if(hkl != ActiveHKL_NT) - { - ActiveHKL_NT = hkl; - LoadNewLibrary(); - } - - if(!KbdTables) /* Could not load keyboard DLL */ - return FALSE; - - return (KbdTables->fLocaleFlags & 0x1) ? TRUE : FALSE; -} diff --git a/windows/src/engine/keyman32/syskbdnt64.cpp b/windows/src/engine/keyman32/syskbdnt64.cpp deleted file mode 100644 index 31dd308e41..0000000000 --- a/windows/src/engine/keyman32/syskbdnt64.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - Name: syskbdnt64 - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 10 Sep 2008 - - Modified Date: 6 Feb 2015 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 10 Sep 2008 - mcdurdin - I1635 - Initial version - 11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888 - 30 Nov 2009 - mcdurdin - I934 - Prep for x64 - change UINT to WORD for vkeys - 12 Mar 2010 - mcdurdin - I934 - x64 - Complete - 12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings - 04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value - 16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard - 03 Feb 2015 - mcdurdin - I4582 - V9.0 - Most underlying layout code in Keyman32 is now obsolete and needs to be removed - 06 Feb 2015 - mcdurdin - I4583 - V9.0 - Remove altgr lookup test from keyman32 and put it into the registry -*/ - // I4169 // I4582 // I4583 -//////////////////////////////////////////////////////////////////////////// -// -// Use Windows NT/2000 keyboard DLLs for mapping virtual keys to characters -// -//////////////////////////////////////////////////////////////////////////// - -#include "pch.h" - -// This file is used only in keyman32.dll; it maps syskbd for x64 builds of Windows -#ifndef _WIN64 - -#include "kbd.h" /* DDK kbdlayout */ - -//#pragma warning ( disable : 4200 ) - -typedef struct { - PVK_TO_BIT pVkToBit; // Virtual Keys -> Mod bits - DWORD filler; - WORD wMaxModBits; // max Modification bit combination value - BYTE ModNumber[1]; // Mod bits -> Modification Number -} MODIFIERS_x64, *PMODIFIERS_x64; - -//#pragma warning ( default : 4200 ) - -typedef struct _VK_TO_WCHAR_TABLE_x64 { - PVK_TO_WCHARS1 pVkToWchars; - DWORD filler; - BYTE nModifications; - BYTE cbSize; - BYTE filler2[2]; - DWORD filler3; -} VK_TO_WCHAR_TABLE_x64, *PVK_TO_WCHAR_TABLE_x64; - - -/***************************************************************************\ -* VSC_VK - Associate a Virtual Scancode with a Virtual Key -* Vsc - Virtual Scancode -* Vk - Virtual Key | flags -* Used by VKFromVSC() for scancodes prefixed 0xE0 or 0xE1 -\***************************************************************************/ -typedef struct _VSC_VK_x64 { - BYTE Vsc; - USHORT Vk; -} VSC_VK_x64, *PVSC_VK_x64; - -/***************************************************************************\ -* VK_VSC - Associate a Virtual Key with a Virtual Scancode -* Vk - Virtual Key -* Vsc - Virtual Scancode -* Used by MapVirtualKey for Virtual Keys not appearing in ausVK[] -\***************************************************************************/ -typedef struct _VK_VSC_x64 { - BYTE Vk; - BYTE Vsc; -} VK_VSC_x64, *PVK_VSC_x64; - - -typedef struct tagKdbLayer_x64 -{ - /* - * Modifier keys - */ - PMODIFIERS_x64 pCharModifiers; - DWORD filler1; - - /* - * Characters - */ - VK_TO_WCHAR_TABLE_x64 *pVkToWcharTable; // ptr to tbl of ptrs to tbl - DWORD filler2; - - /* - * Diacritics - */ - PDEADKEY pDeadKey; - DWORD filler3; - - /* - * Names of Keys - */ - VSC_LPWSTR *pKeyNames; - DWORD filler4; - VSC_LPWSTR *pKeyNamesExt; - DWORD filler5; - LPWSTR *pKeyNamesDead; - DWORD filler6; - - /* - * Scan codes to Virtual Keys - */ - USHORT *pusVSCtoVK; - DWORD filler7; - BYTE bMaxVSCtoVK; - DWORD filler8; - PVSC_VK_x64 pVSCtoVK_E0; // Scancode has E0 prefix - DWORD filler9; - PVSC_VK_x64 pVSCtoVK_E1; // Scancode has E1 prefix - DWORD fillerA; - - /* - * Locale-specific special processing - */ - DWORD fLocaleFlags; - DWORD fillerB; -} KBDTABLES_x64, *PKBDTABLES_x64; - - -typedef PKBDTABLES_x64 (WINAPI *PKBDLAYERDESCRIPTORFUNC)(VOID); - -static HKL ActiveHKL_NT_x64 = 0; - -static HMODULE hKbdLibrary_x64 = NULL; -static PKBDTABLES_x64 KbdTables_x64 = NULL; - - -BOOL LoadNewLibrary_x64() -{ - char buf[128], buf2[KL_NAMELENGTH+1]; - - if(hKbdLibrary_x64) FreeLibrary(hKbdLibrary_x64); - - RegistryReadOnly *r = new RegistryReadOnly(HKEY_LOCAL_MACHINE); - __try - { - GetKeyboardLayoutName(buf2); - wsprintf(buf, "System\\CurrentControlSet\\Control\\keyboard layouts\\%s", buf2); - if(r->OpenKeyReadOnly(buf)) - { - if(r->ReadString("Layout File", buf, 32)) - { - hKbdLibrary_x64 = LoadLibrary(buf); - if(!hKbdLibrary_x64) - { - SendDebugMessageFormat("Exit -- could not load library"); - return FALSE; - } - PKBDLAYERDESCRIPTORFUNC KbdLayerDescriptorFunc = (PKBDLAYERDESCRIPTORFUNC) GetProcAddress(hKbdLibrary_x64, "KbdLayerDescriptor"); - if(KbdLayerDescriptorFunc) - { - KbdTables_x64 = (*KbdLayerDescriptorFunc)(); - if(KbdTables_x64) - return TRUE; - } - - FreeLibrary(hKbdLibrary_x64); - } - } - - hKbdLibrary_x64 = NULL; - KbdTables_x64 = NULL; - } - __finally - { - delete r; - } - SendDebugMessageFormat("Exit -- failed to find function"); - return FALSE; -} - -BOOL KeyboardGivesCtrlRAltForRAlt_NT_x64() -{ - HKL hkl = GetKeyboardLayout(0); - - /* Find the appropriate keyboard tables */ - - if(hkl != ActiveHKL_NT_x64) - { - ActiveHKL_NT_x64 = hkl; - LoadNewLibrary_x64(); - } - - if(!KbdTables_x64) /* Could not load keyboard DLL */ - return FALSE; - - return (KbdTables_x64->fLocaleFlags & 0x1) ? TRUE : FALSE; -} - - -typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); - -LPFN_ISWOW64PROCESS -fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress( -GetModuleHandle("kernel32"),"IsWow64Process"); -BOOL fStored = FALSE; -BOOL bIsWow64 = FALSE; - -BOOL IsWow64() -{ - if(fStored) return bIsWow64; - bIsWow64 = FALSE; - - if (NULL != fnIsWow64Process) - { - if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) - { - // handle error - } - } - fStored = TRUE; - return bIsWow64; -} - -#endif diff --git a/windows/src/engine/keyman32/tests/RightAltEmulationCheck.tests.cpp b/windows/src/engine/keyman32/tests/RightAltEmulationCheck.tests.cpp new file mode 100644 index 0000000000..7cede5bc9a --- /dev/null +++ b/windows/src/engine/keyman32/tests/RightAltEmulationCheck.tests.cpp @@ -0,0 +1,20 @@ +#include "pch.h" + +extern BOOL ReadAltGrFlagFromKbdDll(const char *keyboardLayoutName); + + +TEST(RightAltEmulationCheck, ReadAltGrFlagFromKbdDll) { + // GetKeyboardLayoutName only returns active keyboard layout name; there is no + // Windows API to return an arbitrary keyboard layout, so we need to pass in + // a known keyboard layout + + // These keyboards do not use AltGr + + EXPECT_EQ(ReadAltGrFlagFromKbdDll("00000409"), FALSE); // kbdus.dll - English (US) + EXPECT_EQ(ReadAltGrFlagFromKbdDll("00000401"), FALSE); // kbda1.dll - Arabic 101 + + // These keyboards use AltGr + + EXPECT_EQ(ReadAltGrFlagFromKbdDll("0000040C"), TRUE); // kbdfr.dll - French AZERTY (Legacy) + EXPECT_EQ(ReadAltGrFlagFromKbdDll("00000405"), TRUE); // kbdcz.dll - Czech +} diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/appinttests.cpp b/windows/src/engine/keyman32/tests/appint.tests.cpp similarity index 100% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/appinttests.cpp rename to windows/src/engine/keyman32/tests/appint.tests.cpp diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp b/windows/src/engine/keyman32/tests/gtest_main.cpp similarity index 92% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp rename to windows/src/engine/keyman32/tests/gtest_main.cpp index 0b5471de2f..53f21bc7f8 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/gtest_main.cpp +++ b/windows/src/engine/keyman32/tests/gtest_main.cpp @@ -42,9 +42,10 @@ private: }; } // namespace testing -GTEST_API_ int +//GTEST_API_ +int main(int argc, char** argv) { - cout << "Running main() from gtest_main.cpp" << endl; + cout << "Running main() from gtest_main.cpp with memory leak detection" << endl; InitGoogleTest(&argc, argv); UnitTest::GetInstance()->listeners().Append(new MemoryLeakDetector()); diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyboardoptionstests.cpp b/windows/src/engine/keyman32/tests/keyboardoptions.tests.cpp similarity index 100% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/keyboardoptionstests.cpp rename to windows/src/engine/keyman32/tests/keyboardoptions.tests.cpp diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj b/windows/src/engine/keyman32/tests/keyman32.tests.vcxproj similarity index 50% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj rename to windows/src/engine/keyman32/tests/keyman32.tests.vcxproj index 48afed6fdd..6cf2c6d0fd 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/keyman-engine-tests.vcxproj +++ b/windows/src/engine/keyman32/tests/keyman32.tests.vcxproj @@ -1,160 +1,165 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {46399ed7-79a1-4e7e-bd96-4b1aeec838ae} - Win32Proj - 10.0.19041.0 - Application - v142 - Unicode - keyman-engine-tests - - - - - - - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\kmtip\bin\Win32\$(Configuration);$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) - true - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)..\..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\..\core\include;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(ProjectDir)..\..\..\..\global\inc;$(ProjectDir)..\..;$(IncludePath) - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - {a7069186-5ae7-4504-8926-861b7a34ba9e} - - - - - - - - - - Use - pch.h - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - %(AdditionalIncludeDirectories) - - - true - Console - version.lib;psapi.lib;imm32.lib;libkeymancore.a;libicuuc.a;libicuin.a;%(AdditionalDependencies) - - - false - - - - - Use - pch.h - Disabled - X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - Level3 - - - true - Console - - - - - Use - pch.h - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - Level3 - ProgramDatabase - - - true - Console - true - true - - - - - Use - pch.h - X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - Level3 - ProgramDatabase - - - true - Console - true - true - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {9e3a0b15-9312-4489-a42a-b4416f647d83} + Win32Proj + 10.0.26100.0 + Application + v143 + Unicode + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + $(ProjectDir)..\;$(ProjectDir)..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\core\include;$(IncludePath) + $(ProjectDir)..\lib\bin\$(Platform)\$(Configuration);$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + true + + + $(ProjectDir)..\;$(ProjectDir)..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\core\include;$(IncludePath) + $(ProjectDir)..\lib\bin\$(Platform)\$(Configuration);$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + true + + + $(ProjectDir)..\;$(ProjectDir)..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\core\include;$(IncludePath) + $(ProjectDir)..\lib\bin\$(Platform)\$(Configuration);$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\src;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\core\build\x86\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + true + + + $(ProjectDir)..\;$(ProjectDir)..\..\..\..\..\common\include;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\include;$(ProjectDir)..\..\..\..\..\core\include;$(IncludePath) + $(ProjectDir)..\lib\bin\$(Platform)\$(Configuration);$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\src;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\common;$(ProjectDir)..\..\..\..\..\core\build\x64\$(Configuration)\subprojects\icu\source\i18n;$(LibraryPath) + true + + + + + Use + pch.h + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + + + true + Console + version.lib;psapi.lib;imm32.lib;libkeymancore.a;keyman32.lib;libicuuc.a;libicuin.a;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Use + pch.h + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + Level3 + + + true + Console + version.lib;psapi.lib;imm32.lib;libkeymancore.a;keyman64.lib;libicuuc.a;libicuin.a;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Use + pch.h + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + + + true + Console + true + version.lib;psapi.lib;imm32.lib;libkeymancore.a;keyman32.lib;libicuuc.a;libicuin.a;$(CoreLibraryDependencies);%(AdditionalDependencies) + true + + + + + Use + pch.h + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + + + true + Console + true + version.lib;psapi.lib;imm32.lib;libkeymancore.a;keyman64.lib;libicuuc.a;libicuin.a;$(CoreLibraryDependencies);%(AdditionalDependencies) + true + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp b/windows/src/engine/keyman32/tests/kmprocessactions.tests.cpp similarity index 89% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp rename to windows/src/engine/keyman32/tests/kmprocessactions.tests.cpp index 5e99afe291..e7386ce5fb 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp +++ b/windows/src/engine/keyman32/tests/kmprocessactions.tests.cpp @@ -1,5 +1,5 @@ #include "pch.h" -#include "kmprocessactions.cpp" +#include "kmprocessactions.cpp" // TODO: should this be pulled in from keyman32/64.lib and use externs? seems cleaner? // Test the Process Actions private functions // TODO: The following actions are not tested KM_CORE_IT_ALERT, KM_CORE_IT_PERSIST_OPT, KM_CORE_IT_EMIT_KEYSTROKE @@ -29,7 +29,6 @@ public: // action queue TEST_F(KMPROCESSACTIONS, processUnicodeChartest) { // just check something has been added to the que - WCHAR callbuf[MAXCONTEXT]; AITIP testApp; int expectedQueueSize = 3; // 1 + surrogate pair km_core_usv output_string[] = {0x0041, 0x010000, 0}; diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/packages.config b/windows/src/engine/keyman32/tests/packages.config similarity index 67% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/packages.config rename to windows/src/engine/keyman32/tests/packages.config index cbf03ab848..a14c30a16a 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/packages.config +++ b/windows/src/engine/keyman32/tests/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/pch.cpp b/windows/src/engine/keyman32/tests/pch.cpp similarity index 100% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/pch.cpp rename to windows/src/engine/keyman32/tests/pch.cpp diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/pch.h b/windows/src/engine/keyman32/tests/pch.h similarity index 100% rename from windows/src/engine/keyman32/tests/keyman-engine-tests/pch.h rename to windows/src/engine/keyman32/tests/pch.h diff --git a/windows/src/engine/keymanmc/build.sh b/windows/src/engine/keymanmc/build.sh index 8ad933abb7..389cc9ecb1 100755 --- a/windows/src/engine/keymanmc/build.sh +++ b/windows/src/engine/keymanmc/build.sh @@ -30,10 +30,10 @@ function do_build() { function do_install() { cp "$WINDOWS_PROGRAM_ENGINE/keymanmc.dll" "$INSTALLPATH_KEYMANENGINE/keymanmc.dll" - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v CategoryCount //t REG_DWORD //d 1 //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v CategoryMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v EventMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f - reg add 'HKLM\\SYSTEM\\CurrentControlSet\\services\\eventlog\\Application\\Keyman' //v TypesSupported //t REG_DWORD //d 7 //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v CategoryCount //t REG_DWORD //d 1 //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v CategoryMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v EventMessageFile //t REG_SZ //d "$INSTALLPATH_KEYMANENGINE\\keymanmc.dll" //f + reg add 'HKLM\SYSTEM\CurrentControlSet\services\eventlog\Application\Keyman' //v TypesSupported //t REG_DWORD //d 7 //f } builder_run_action clean:project rm -f keymanmc.dll keymanmc.h keymanmc.rc keymanmc.res MSG00409.bin diff --git a/windows/src/engine/keymanx64/keymanx64.vcxproj b/windows/src/engine/keymanx64/keymanx64.vcxproj index 82b3bcb59f..2fd5955560 100644 --- a/windows/src/engine/keymanx64/keymanx64.vcxproj +++ b/windows/src/engine/keymanx64/keymanx64.vcxproj @@ -1,153 +1,153 @@ - - - - - Debug - x64 - - - Release - x64 - - - - {A34650EA-D6E8-4229-8091-6BF1443565B5} - keymanx64 - Win32Proj - 10.0 - - - - Application - Unicode - true - v142 - - - Application - Unicode - v142 - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - false - false - AllRules.ruleset - - - AllRules.ruleset - - - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - - - - X64 - - - Disabled - ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - false - true - EnableFastChecks - MultiThreadedDebug - - - Level4 - true - ProgramDatabase - true - - - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmtd.lib;%(AdditionalDependencies) - ..\..\..\lib;%(AdditionalLibraryDirectories) - false - true - Windows - MachineX64 - true - sentry.dll - - - - - - - - - - - - - - - X64 - - - ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - false - MultiThreaded - - - Level4 - true - ProgramDatabase - true - - - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmt.lib;%(AdditionalDependencies) - ..\..\..\lib;%(AdditionalLibraryDirectories) - false - true - Windows - true - true - MachineX64 - true - true - sentry.dll - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + x64 + + + Release + x64 + + + + {A34650EA-D6E8-4229-8091-6BF1443565B5} + keymanx64 + Win32Proj + 10.0 + + + + Application + Unicode + true + v143 + + + Application + Unicode + v143 + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + + + + X64 + + + Disabled + ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + false + true + EnableFastChecks + MultiThreadedDebug + + + Level4 + true + ProgramDatabase + true + + + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmtd.lib;%(AdditionalDependencies) + ..\..\..\lib;%(AdditionalLibraryDirectories) + false + true + Windows + MachineX64 + true + sentry.dll + + + + + + + + + + + + + + + X64 + + + ..\..\global\inc;..\..\..\..\common\windows\delphi\ext\sentry;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + false + MultiThreaded + + + Level4 + true + ProgramDatabase + true + + + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.x64.lib;keyman64.lib;libcmt.lib;%(AdditionalDependencies) + ..\..\..\lib;%(AdditionalLibraryDirectories) + false + true + Windows + true + true + MachineX64 + true + true + sentry.dll + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/kmcomapi/build.sh b/windows/src/engine/kmcomapi/build.sh index 935cb91c6a..78b57e792b 100755 --- a/windows/src/engine/kmcomapi/build.sh +++ b/windows/src/engine/kmcomapi/build.sh @@ -76,5 +76,5 @@ builder_run_action build:project do_build # builder_run_action test:project do_test builder_run_action publish:project do_publish builder_run_action install:project do_install -builder_run_action edit:project start kmcompapi.dproj +builder_run_action edit:project start kmcomapi.dproj diff --git a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas index 90bab26746..ef6fa8be83 100644 --- a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas +++ b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas @@ -76,6 +76,7 @@ type TKeyman32ControllerSendMessageFunction = function(msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; TKeyman32ControllerPostMessageFunction = procedure(msg: UINT; wParam: WPARAM; lParam: LPARAM); stdcall; TKeyman32UpdateTouchPanelVisibilityFunction = procedure(Value: BOOL); stdcall; + TKeyman32WatchDogKeyEvent = procedure; stdcall; TKeymanControl = class(TKeymanAutoObject, IKeymanCustomisationAccess, IIntKeymanControl, IKeymanControl, IKeymanEngineControl) private @@ -97,6 +98,7 @@ type FKeyman_PostMasterController: TKeyman32ControllerPostMessageFunction; FKeyman_StartExit: TKeyman32ExitFunction; // I3092 FKeyman_UpdateTouchPanelVisibility: TKeyman32UpdateTouchPanelVisibilityFunction; + FKeyman_WatchDogKeyEvent: TKeyman32WatchDogKeyEvent; procedure LoadKeyman32; procedure StartKeyman32; procedure Do_Keyman_Exit; @@ -148,6 +150,7 @@ type procedure DisableUserInterface; safecall; procedure EnableUserInterface; safecall; procedure UpdateTouchPanelVisibility(Value: Boolean); safecall; + procedure WatchDogKeyEvent; safecall; // 32 bit only procedure DiagnosticTestException; safecall; @@ -524,6 +527,25 @@ begin {$ENDIF} end; +(** + * Tell the LowLevelHookWatchDog that a key event has been received + *) +procedure TKeymanControl.WatchDogKeyEvent; +begin +{$IFDEF WIN64} + Error(Cardinal(E_NOTIMPL)); +{$ELSE} + KL.MethodEnter(Self, 'WatchDogKeyEvent', []); + try + LoadKeyman32; + FKeyman_WatchDogKeyEvent; + finally + KL.MethodExit(Self, 'WatchDogKeyEvent'); + end; +{$ENDIF} +end; + + procedure TKeymanControl.RegisterControllerThread(Value: LongWord); begin {$IFDEF WIN64} @@ -750,6 +772,9 @@ begin @FKeyman_SendMasterController := ProcAddr('Keyman_SendMasterController'); @FKeyman_PostMasterController := ProcAddr('Keyman_PostMasterController'); @FKeyman_UpdateTouchPanelVisibility := ProcAddr('Keyman_UpdateTouchPanelVisibility'); +{$IFNDEF WIN64} + @FKeyman_WatchDogKeyEvent := ProcAddr('Keyman_WatchDogKeyEvent'); +{$ENDIF} end; end; diff --git a/windows/src/engine/kmrefresh/kmrefresh.vcxproj b/windows/src/engine/kmrefresh/kmrefresh.vcxproj index a978cb947e..0b252895f7 100644 --- a/windows/src/engine/kmrefresh/kmrefresh.vcxproj +++ b/windows/src/engine/kmrefresh/kmrefresh.vcxproj @@ -1,157 +1,157 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {A3B8F75F-E63E-434E-8A2A-034AD9B66571} - kmrefresh - 10.0 - - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - - - - - - - - - - - - - - - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x86 - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectName).x86 - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectName).x64 - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Level3 - Disabled - true - true - MultiThreadedDebug - - - Windows - - - - - Level3 - Disabled - true - true - MultiThreadedDebug - - - Windows - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - Windows - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - MultiThreaded - - - Windows - true - true - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {A3B8F75F-E63E-434E-8A2A-034AD9B66571} + kmrefresh + 10.0 + + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectName).x64 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectName).x86 + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectName).x86 + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectName).x64 + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Level3 + Disabled + true + true + MultiThreadedDebug + + + Windows + + + + + Level3 + Disabled + true + true + MultiThreadedDebug + + + Windows + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + Windows + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + MultiThreaded + + + Windows + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/kmtip/kmtip.vcxproj b/windows/src/engine/kmtip/kmtip.vcxproj index f3fb74116f..173853f507 100644 --- a/windows/src/engine/kmtip/kmtip.vcxproj +++ b/windows/src/engine/kmtip/kmtip.vcxproj @@ -1,432 +1,432 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {93391ECB-E2F1-4D23-B85E-6FDEB7ACF9B4} - 10.0 - - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - DynamicLibrary - false - MultiByte - v142 - - - - - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.30319.1 - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - false - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - true - true - true - true - AllRules.ruleset - AllRules.ruleset - - - - - AllRules.ruleset - AllRules.ruleset - - - - - .dll - .dll - .dll - .dll - kmtip64 - kmtip64 - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmtip.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - true - MultiThreaded - true - All - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip.def - true - 0x4C100000 - MachineX86 - true - true - - - true - ./kmtip.bsc - - - - - NDEBUG;%(PreprocessorDefinitions) - true - true - ./kmtip.tlb - - - - - MaxSpeed - OnlyExplicitInline - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - true - MultiThreaded - true - All - Level3 - true - true - Use - pch.h - - - NDEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip64.def - true - 0x4C100000 - true - true - - - true - ./kmtip.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - ./kmtip.tlb - - - - - Disabled - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - true - Level3 - true - EditAndContinue - true - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip.def - true - true - 0x4C100000 - MachineX86 - true - - - true - ./kmtip.bsc - - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - ./kmtip.tlb - - - - - Disabled - .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) - WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - true - Level3 - true - ProgramDatabase - true - Use - pch.h - - - _DEBUG;%(PreprocessorDefinitions) - 0x0c09 - - - ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) - true - .\kmtip64.def - true - true - 0x4C100000 - true - - - true - ./kmtip.bsc - - - - - - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - Create - Create - Create - Create - - - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - true - true - - - true - true - - - - - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - %(PreprocessorDefinitions) - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {93391ECB-E2F1-4D23-B85E-6FDEB7ACF9B4} + 10.0 + + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + DynamicLibrary + false + MultiByte + v143 + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + true + false + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + true + true + true + true + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + .dll + .dll + .dll + .dll + kmtip64 + kmtip64 + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./kmtip.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + true + All + Level3 + true + true + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip.def + true + 0x4C100000 + MachineX86 + true + true + + + true + ./kmtip.bsc + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + ./kmtip.tlb + + + + + MaxSpeed + OnlyExplicitInline + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + true + All + Level3 + true + true + Use + pch.h + + + NDEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip64.def + true + 0x4C100000 + true + true + + + true + ./kmtip.bsc + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + ./kmtip.tlb + + + + + Disabled + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + Level3 + true + EditAndContinue + true + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman32.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip.def + true + true + 0x4C100000 + MachineX86 + true + + + true + ./kmtip.bsc + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + ./kmtip.tlb + + + + + Disabled + .\;..\..\global\inc;..\..\resellers;C:\Program Files\Microsoft Platform SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;KMTIP_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + true + Level3 + true + ProgramDatabase + true + Use + pch.h + + + _DEBUG;%(PreprocessorDefinitions) + 0x0c09 + + + ..\..\..\lib\keyman64.lib;version.lib;wintrust.lib;crypt32.lib;imagehlp.lib;%(AdditionalDependencies) + true + .\kmtip64.def + true + true + 0x4C100000 + true + + + true + ./kmtip.bsc + + + + + + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + Create + Create + Create + Create + + + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + true + true + + + true + true + + + + + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + %(PreprocessorDefinitions) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/mcompile/mcompile.vcxproj b/windows/src/engine/mcompile/mcompile.vcxproj index e9c426a330..99f60fa722 100644 --- a/windows/src/engine/mcompile/mcompile.vcxproj +++ b/windows/src/engine/mcompile/mcompile.vcxproj @@ -1,134 +1,134 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {5C22DDDA-CEAD-45F1-96B0-1473111156AA} - Win32Proj - mtop - mcompile - 10.0 - - - - Application - true - Unicode - v142 - - - Application - false - true - Unicode - v142 - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - false - *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - false - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDebug - ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc - pch.h - - - Console - true - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - Level3 - Use - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreaded - ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc - pch.h - - - Console - true - true - true - ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - sentry.dll - - - - - - - - - - - - - - - - NotUsing - NotUsing - - - - - - - - - - Create - Create - pch.h - pch.h - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + + {5C22DDDA-CEAD-45F1-96B0-1473111156AA} + Win32Proj + mtop + mcompile + 10.0 + + + + Application + true + Unicode + v143 + + + Application + false + true + Unicode + v143 + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + false + *.cdf;*.cache;*.obj;*.ilk;*.resources;*.tlb;*.tli;*.tlh;*.tmp;*.rsp;*.pgc;*.pgd;*.meta;*.tlog;*.manifest;*.res;*.pch;*.exp;*.idb;*.rep;*.xdc;*.pdb;*_manifest.rc;*.bsc;*.sbr;*.metagen;*.bi + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + false + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc + pch.h + + + Console + true + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + ..\..\..\..\common\windows\delphi\ext\sentry;..\..\global\inc + pch.h + + + Console + true + true + true + ..\..\..\..\common\windows\delphi\ext\sentry\sentry.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + sentry.dll + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + Create + Create + pch.h + pch.h + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/testhost/testhost.vcxproj b/windows/src/engine/testhost/testhost.vcxproj index 8b965e942c..e9d06a87a6 100644 --- a/windows/src/engine/testhost/testhost.vcxproj +++ b/windows/src/engine/testhost/testhost.vcxproj @@ -1,172 +1,172 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {12818da3-0472-49a5-96fc-a4808a1a77fa} - testhost - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Level3 - true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - true - true - - - - - Level3 - true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - - - - - Level3 - true - true - true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - stdcpp17 - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {12818da3-0472-49a5-96fc-a4808a1a77fa} + testhost + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + stdcpp17 + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/engine/tsysinfo/build.sh b/windows/src/engine/tsysinfo/build.sh index e3390b899b..640abb5ef8 100755 --- a/windows/src/engine/tsysinfo/build.sh +++ b/windows/src/engine/tsysinfo/build.sh @@ -36,7 +36,7 @@ function do_build() { # Embed tsysinfox64 into a resource; we have to do a special signcode for # tsysinfox64.exe as we embed the executable into tsysinfo.exe - ../tsysinfox64/build.sh publish --no-deps + builder_launch /windows/src/engine/tsysinfox64/build.sh publish cp "$WINDOWS_PROGRAM_ENGINE/tsysinfox64.exe" tsysinfox64.bin run_in_vs_env rc tsysinfo_x64.rc rm -f tsysinfox64.bin diff --git a/windows/src/engine/tsysinfox64/build.sh b/windows/src/engine/tsysinfox64/build.sh index 0a97e01c22..d7b3e9a355 100755 --- a/windows/src/engine/tsysinfox64/build.sh +++ b/windows/src/engine/tsysinfox64/build.sh @@ -39,15 +39,10 @@ function do_publish() { wrap-signcode //d "Keyman Engine for Windows" "$WINDOWS_PROGRAM_ENGINE/tsysinfox64.exe" } -function do_install() { - cp "$WINDOWS_PROGRAM_ENGINE/tsysinfox64.exe" "$INSTALLPATH_KEYMANENGINE/tsysinfox64.exe" - cp *.xslt "$INSTALLPATH_KEYMANENGINE/" -} - builder_run_action clean:project clean_windows_project_files builder_run_action configure:project configure_windows_build_environment builder_run_action build:project do_build -# builder_run_action test:project do_test +# builder_run_action test:project (no current tests) builder_run_action publish:project do_publish -builder_run_action install:project do_install +# builder_run_action install:project (installed as part of tsysinfo) builder_run_action edit:project start tsysinfox64.dproj diff --git a/windows/src/global/delphi/cust/CustomisationMessages.pas b/windows/src/global/delphi/cust/CustomisationMessages.pas index 4f8055c756..d79e9b3264 100644 --- a/windows/src/global/delphi/cust/CustomisationMessages.pas +++ b/windows/src/global/delphi/cust/CustomisationMessages.pas @@ -34,18 +34,29 @@ interface uses System.Classes, + System.Generics.Collections, System.SysUtils, System.TypInfo, Winapi.msxml, Winapi.Windows; type + TCustomisationLocale = class + ID: string; + Path: string; + Name: string; + NameWithEnglish: string; + end; + + TCustomisationLocaleList = class(TObjectDictionary) + end; + TCustomisationMessageManager = class(TComponent) private FLanguageCode: string; FLocaleDoc: IXMLDOMDocument; FDefaultLocaleDoc: IXMLDOMDocument; - FLanguages: TStringList; + FLanguages: TCustomisationLocaleList; FCustStorageFilename: string; procedure SetLanguageCode(Value: string); function GetAvailableLanguages: string; @@ -70,6 +81,7 @@ uses Keyman.System.AndroidStringToKeymanLocaleString, KLog, ErrorControlledRegistry, + MessageIdentifierConsts, RegistryKeys; { TCustomisationMessageManager } @@ -77,7 +89,7 @@ uses constructor TCustomisationMessageManager.Create(ACustStorageFilename: string; ALoadLocale: Boolean = True); begin inherited Create(nil); - FLanguages := TStringList.Create; + FLanguages := TCustomisationLocaleList.Create; FCustStorageFilename := ACustStorageFilename; Load(ALoadLocale); end; @@ -89,44 +101,69 @@ begin end; function TCustomisationMessageManager.GetAvailableLanguages: string; +var + FLocaleIndexDoc: IXmlDomDocument; + node: IXMLDOMNode; + locale: TCustomisationLocale; + FLocalePath: WideString; - function IsValidLocaleFile(const path: string): Boolean; // I3192 + function LoadLocaleData(node: IXMLDOMNode; locale: TCustomisationLocale): Boolean; var - FLocaleDoc: IXmlDomDocument; + v: IXMLDOMNode; begin - try - FLocaleDoc := CoDomDocument.Create; - FLocaleDoc.preserveWhiteSpace := True; - FLocaleDoc.async := False; - FLocaleDoc.validateOnParse := False; - FLocaleDoc.load(path); - if FLocaleDoc.documentElement <> nil - then Result := (FLocaleDoc.documentElement.tagName = 'resources') - else Result := False; - except - Result := False; - end; + v := node.attributes.getNamedItem('SKLanguageCode'); + if v = nil then Exit(False); + locale.ID := v.nodeValue; + locale.Path := FLocalePath + locale.ID + '\strings.xml'; + + v := node.attributes.getNamedItem('SKUILanguageName'); + if v = nil then Exit(False); + locale.Name := v.nodeValue; + + v := node.attributes.getNamedItem('SKUILanguageNameWithEnglish'); + if v = nil then Exit(False); + locale.NameWithEnglish := v.nodeValue; + + Result := True; end; -var - f: TSearchRec; - FLocalePath: WideString; begin Result := ''; FLocalePath := ExtractFilePath(FCustStorageFileName)+'locale\'; FLanguages.Clear; - if FindFirst(FLocalePath + '*', faDirectory, f) = 0 then - begin - repeat - if ((f.Attr and faDirectory) = faDirectory) and (f.Name <> '.') and (f.Name <> '..') then + + try + FLocaleIndexDoc := CoDomDocument.Create; + FLocaleIndexDoc.preserveWhiteSpace := True; + FLocaleIndexDoc.async := False; + FLocaleIndexDoc.validateOnParse := False; + FLocaleIndexDoc.load(FLocalePath + 'index.xml'); + if (FLocaleIndexDoc.documentElement = nil) or (FLocaleIndexDoc.documentElement.tagName <> 'locales') then + Exit; + + node := FLocaleIndexDoc.documentElement.firstChild; + if not Assigned(node) then + Exit; + + while node <> nil do + begin + if node.nodeName = 'locale' then begin - if IsValidLocaleFile(FLocalePath+f.Name+'\strings.xml') then // I3192 - FLanguages.Add(f.Name+'='+FLocalePath+f.Name+'\strings.xml'); - if Result <> '' then Result := Result + #13#10; - Result := Result + f.Name; + locale := TCustomisationLocale.Create; + if not LoadLocaleData(node, locale) then + locale.Free + else + begin + FLanguages.Add(locale.ID, locale); + Result := Result + locale.ID + #13#10; + end; end; - until FindNext(f) <> 0; - System.SysUtils.FindClose(f); + node := node.nextSibling; + end; + except + // If any locale items are invalid, we will have only English + // Currently, we cannot report errors to Sentry from kmcomapi + Result := ''; end; end; @@ -159,10 +196,15 @@ end; function TCustomisationMessageManager.GetLocalePathForLocale( LocaleName: WideString): WideString; +var + locale: TCustomisationLocale; begin - if LocaleName = '' - then Result := ExtractFilePath(FCustStorageFileName)+'locale\' - else Result := FLanguages.Values[LocaleName]; + if LocaleName = '' then + Result := ExtractFilePath(FCustStorageFileName)+'locale\' + else if not FLanguages.TryGetValue(LocaleName, locale) then + Result := '' + else + Result := locale.Path; end; procedure TCustomisationMessageManager.Load(ALoadLocale: Boolean = True); @@ -195,6 +237,14 @@ begin FDefaultLocaleDoc.load(ExtractFilePath(FCustStorageFileName) + 'xml\strings.xml'); end; +(** + * Look up a localized string for the currently active localization. If + * not found, looks up the string from the default (i.e. en) + * localization, and finally falls back to the ID itself on failure. + * + * @param ID ID of string to lookup. + * @return Localized string value. + *) function TCustomisationMessageManager.MessageFromID(ID: WideString): WideString; var node: IXMLDOMNode; @@ -211,22 +261,40 @@ begin end; end; +(** + * Look up a localized string for a specific localization. This function + * can return values for only `SKLanguageCode`, `SKUILanguageName` + * and `SKUILanguageNameWithEnglish`. Other strings will be returned + * from the currently active localization. + * + * @param ID ID of string to lookup. + * @param LanguageCode The BCP-47 language tag. + * @return Localized string value. + *) function TCustomisationMessageManager.MessageFromID(ID, LanguageCode: WideString): WideString; var - mm: TCustomisationMessageManager; + locale: TCustomisationLocale; begin - if LanguageCode = Self.FLanguageCode then - Result := MessageFromID(ID) + if not FLanguages.TryGetValue(LanguageCode, locale) then + Exit(MessageFromID(ID)); + + if ID = StringFromMsgId(SKUILanguageName) then + begin + Result := locale.Name; + end + else if ID = StringFromMsgId(SKUILanguageNameWithEnglish) then + begin + Result := locale.NameWithEnglish; + end + else if ID = StringFromMsgId(SKLanguageCode) then + begin + Result := locale.ID; + end else begin - mm := TCustomisationMessageManager.Create(FCustStorageFilename, False); - try - mm.LanguageCode := LanguageCode; - Result := mm.MessageFromID(ID); - finally - mm.Free; - end; + // Other tags are no longer supported for performance reasons + Result := MessageFromID(ID); end; end; diff --git a/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas b/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas index 9937d476c3..52ad48dd52 100644 --- a/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas +++ b/windows/src/global/delphi/debug/Keyman.System.DebugLogManager.pas @@ -149,7 +149,7 @@ begin pSessionProperties.Wnode.Flags := WNODE_FLAG_TRACED_GUID; pSessionProperties.Wnode.ClientContext := 1; //QPC clock resolution pSessionProperties.Wnode.Guid := SessionGuid; - pSessionProperties.LogFileMode := EVENT_TRACE_FILE_MODE_SEQUENTIAL; + pSessionProperties.LogFileMode := EVENT_TRACE_FILE_MODE_CIRCULAR; pSessionProperties.MaximumFileSize := 256; // 256 MB pSessionProperties.LoggerNameOffset := sizeof(EVENT_TRACE_PROPERTIES); pSessionProperties.LogFileNameOffset := sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME); diff --git a/windows/src/global/delphi/general/KeymanControlMessages.pas b/windows/src/global/delphi/general/KeymanControlMessages.pas index 0fd5a6a1b5..67712e1747 100644 --- a/windows/src/global/delphi/general/KeymanControlMessages.pas +++ b/windows/src/global/delphi/general/KeymanControlMessages.pas @@ -54,6 +54,9 @@ const KMC_HINTRESPONSE = 19; // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum + KMC_WATCHDOG_FAKEFREEZE = 20; // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability + KMC_WATCHDOG_KEYEVENT = 21; // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread + PC_UPDATE = 0; PC_UPDATE_LANGUAGESWITCH = 1; PC_HOTKEYCHANGE = 2; diff --git a/windows/src/global/delphi/general/KeymanEngineControl.pas b/windows/src/global/delphi/general/KeymanEngineControl.pas index 5afd6f9013..762755c172 100644 --- a/windows/src/global/delphi/general/KeymanEngineControl.pas +++ b/windows/src/global/delphi/general/KeymanEngineControl.pas @@ -1,18 +1,18 @@ (* Name: KeymanControlRestart Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 19 Jun 2007 Modified Date: 19 Jun 2007 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 19 Jun 2007 - mcdurdin - Initial version *) unit KeymanEngineControl; @@ -43,6 +43,10 @@ type procedure UnregisterMasterController(Value: LongWord); safecall; procedure RegisterControllerThread(Value: LongWord); safecall; procedure UnregisterControllerThread(Value: LongWord); safecall; + + // New in 19.0 + + procedure WatchDogKeyEvent; safecall; // 32 bit only end; implementation diff --git a/windows/src/support/etl2log/etl2log.vcxproj b/windows/src/support/etl2log/etl2log.vcxproj index d8e0372cb2..839d6f2886 100644 --- a/windows/src/support/etl2log/etl2log.vcxproj +++ b/windows/src/support/etl2log/etl2log.vcxproj @@ -1,182 +1,182 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {8754791A-42D7-4E93-84F1-97DEF9D644EB} - Win32Proj - etl2log - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - tdh.lib;%(AdditionalDependencies) - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - tdh.lib;%(AdditionalDependencies) - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {8754791A-42D7-4E93-84F1-97DEF9D644EB} + Win32Proj + etl2log + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + tdh.lib;%(AdditionalDependencies) + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + tdh.lib;%(AdditionalDependencies) + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/fakefreeze/.gitignore b/windows/src/support/fakefreeze/.gitignore new file mode 100644 index 0000000000..2959b40eec --- /dev/null +++ b/windows/src/support/fakefreeze/.gitignore @@ -0,0 +1 @@ +x64/ diff --git a/windows/src/support/fakefreeze/fakefreeze.cpp b/windows/src/support/fakefreeze/fakefreeze.cpp new file mode 100644 index 0000000000..4e78f43657 --- /dev/null +++ b/windows/src/support/fakefreeze/fakefreeze.cpp @@ -0,0 +1,39 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by mcdurdin on 2025-11-17 + * + * Tell keyman.exe to pause for 5 seconds to force Windows to silently uninstall + * the low level keyboard hook, per Windows documentation at + * https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks + * + * See LowLevelHookWatchDog.cpp for more detail + */ +#include +#include +#include + +#define KMC_WATCHDOG_FAKEFREEZE 20 + +int main() { + std::cout << "Posting a freeze message to Keyman master controller\n"; + UINT wm_keyman_control = RegisterWindowMessage(L"WM_KEYMAN_CONTROL"); + HWND hwnd = FindWindow(L"TfrmKeyman7Main", NULL); + if (hwnd == NULL) { + std::cout << "Keyman master controller window not found\n"; + return 1; + } + + if (!PostMessage(hwnd, wm_keyman_control, KMC_WATCHDOG_FAKEFREEZE, 0)) { + std::cout << "Error calling Keyman KMC_WATCHDOG_FAKEFREEZE\n"; + } + std::cout << "Sleeping 5 seconds...\n"; + + for (int i = 1; i <= 5; i++) { + Sleep(1000); + std::cout << "..." << i << "\n"; + } + std::cout << "Keyman should be responsive again now\n"; + + return 0; +} diff --git a/windows/src/support/fakefreeze/fakefreeze.sln b/windows/src/support/fakefreeze/fakefreeze.sln new file mode 100644 index 0000000000..f9c6b43a48 --- /dev/null +++ b/windows/src/support/fakefreeze/fakefreeze.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36616.10 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fakefreeze", "fakefreeze.vcxproj", "{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.ActiveCfg = Debug|x64 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.Build.0 = Debug|x64 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.ActiveCfg = Debug|Win32 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.Build.0 = Debug|Win32 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.ActiveCfg = Release|x64 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.Build.0 = Release|x64 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.ActiveCfg = Release|Win32 + {BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA12682B-94DC-4E10-AE3F-743D64317F12} + EndGlobalSection +EndGlobal diff --git a/windows/src/support/fakefreeze/fakefreeze.vcxproj b/windows/src/support/fakefreeze/fakefreeze.vcxproj new file mode 100644 index 0000000000..62aaefeecf --- /dev/null +++ b/windows/src/support/fakefreeze/fakefreeze.vcxproj @@ -0,0 +1,132 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {bdde094f-b409-4df5-aa9e-6dec09b362ce} + fakefreeze + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/fakefreeze/fakefreeze.vcxproj.filters b/windows/src/support/fakefreeze/fakefreeze.vcxproj.filters new file mode 100644 index 0000000000..8b54f2cabc --- /dev/null +++ b/windows/src/support/fakefreeze/fakefreeze.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/windows/src/support/texteditor/Editor.vcxproj b/windows/src/support/texteditor/Editor.vcxproj index 4b01793829..30879810b3 100644 --- a/windows/src/support/texteditor/Editor.vcxproj +++ b/windows/src/support/texteditor/Editor.vcxproj @@ -1,175 +1,175 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {6804767e-857f-48cc-b843-22bf357a668c} - Editor - 10.0 - - - - Application - true - v142 - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - false - v142 - true - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - true - v142 - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - Application - false - v142 - true - Unicode - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - - - - - - - - - - - - - - - - - - true - $(ProjectName)32 - - - false - $(ProjectName)32 - - - true - $(ProjectName)64 - - - false - $(ProjectName)64 - - - - Level3 - true - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - $(OutDir)$(TargetName)$(TargetExt) - - - - - Level3 - true - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - Level3 - true - _DEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - - - - - Level3 - true - true - true - NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {6804767e-857f-48cc-b843-22bf357a668c} + Editor + 10.0 + + + + Application + true + v143 + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + false + v143 + true + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + true + v143 + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + Application + false + v143 + true + Unicode + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + + + + + + + + + + + + + + + + + + true + $(ProjectName)32 + + + false + $(ProjectName)32 + + + true + $(ProjectName)64 + + + false + $(ProjectName)64 + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/wow64kbd/.gitignore b/windows/src/support/wow64kbd/.gitignore new file mode 100644 index 0000000000..c756957466 --- /dev/null +++ b/windows/src/support/wow64kbd/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ \ No newline at end of file diff --git a/windows/src/support/wow64kbd/README.md b/windows/src/support/wow64kbd/README.md new file mode 100644 index 0000000000..36a1f371a6 --- /dev/null +++ b/windows/src/support/wow64kbd/README.md @@ -0,0 +1,14 @@ +# wow64kbd support app for researching WOW64 reqs + +This app reports on WOW64 identification function results, and reports +on kbdxx.dll file alignment by printing a hex dump of the first 44 bytes +(which is the 32 bit size of the structure). Manual review of the hex +dump should be possible to determine if we have the expected alignment, +which should be 32 bit on a 32 bit Windows installation, but 64 bit on +any 64 bit other Windows installations, even with a 32 bit process. + +wow64kbd is not included in the normal build. Build when needed; you can +copy entire folder to a target computer and run `run.bat` to get a full +report. + +`./build.sh test` will also run the same test on the local machine. diff --git a/windows/src/support/wow64kbd/build.sh b/windows/src/support/wow64kbd/build.sh new file mode 100755 index 0000000000..38d273ca58 --- /dev/null +++ b/windows/src/support/wow64kbd/build.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/builder-full.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe "test wow64 kbdxx.dll loading" \ + clean configure build test \ + :x86 :x64 :arm64 :arm64ec + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" + +WIN32_TARGET="bin/Win32/$TARGET_PATH/wow64kbd.exe" +X64_TARGET="bin/x64/$TARGET_PATH/wow64kbd.exe" +ARM64_TARGET="bin/ARM64/$TARGET_PATH/wow64kbd.exe" +ARM64EC_TARGET="bin/ARM64EC/$TARGET_PATH/wow64kbd.exe" + +builder_describe_outputs \ + configure /resources/build/win/delphi_environment_generated.inc.sh \ + build:x86 /windows/src/support/wow64kbd/$WIN32_TARGET \ + build:x64 /windows/src/support/wow64kbd/$X64_TARGET \ + build:arm64 /windows/src/support/wow64kbd/$ARM64_TARGET \ + build:arm64ec /windows/src/support/wow64kbd/$ARM64EC_TARGET + +#------------------------------------------------------------------------------------------------------------------- + +function do_clean() { + local Platform="$1" + vs_msbuild wow64kbd.vcxproj -t:Clean "-p:Platform=${Platform}" -Verbosity:minimal + clean_windows_project_files +} + +function do_build() { + local Platform="$1" + vs_msbuild wow64kbd.vcxproj -t:Build "-p:Platform=${Platform}" -Verbosity:minimal +} + +function do_test() { + local Platform="$1" + "./bin/${Platform}/${TARGET_PATH}/wow64kbd.exe" +} + +function do_skip_test() { + local Platform="$1" + builder_echo "Skipping test for platform $1 because current processor architecture is ${PROCESSOR_ARCHITECTURE-unknown}" +} + +builder_run_action clean:x86 do_clean Win32 +builder_run_action clean:x64 do_clean x64 +builder_run_action clean:arm64 do_clean ARM64 +builder_run_action clean:arm64ec do_clean ARM64EC +builder_run_action configure configure_windows_build_environment +builder_run_action build:x86 do_build Win32 +builder_run_action build:x64 do_build x64 +builder_run_action build:arm64 do_build ARM64 +builder_run_action build:arm64ex do_build ARM64EC +builder_run_action test:x86 do_test Win32 +builder_run_action test:x64 do_test x64 + +if [[ "${PROCESSOR_ARCHITECTURE-x}" == ARM64 ]]; then + builder_run_action test:arm64 do_test ARM64 + builder_run_action test:arm64ec do_test ARM64EC +else + builder_run_action test:arm64 do_skip_test ARM64 + builder_run_action test:arm64ec do_skip_test ARM64EC +fi diff --git a/windows/src/support/wow64kbd/dirs.props b/windows/src/support/wow64kbd/dirs.props new file mode 100644 index 0000000000..3774966a87 --- /dev/null +++ b/windows/src/support/wow64kbd/dirs.props @@ -0,0 +1,7 @@ + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + \ No newline at end of file diff --git a/windows/src/support/wow64kbd/run.bat b/windows/src/support/wow64kbd/run.bat new file mode 100644 index 0000000000..63e96d952c --- /dev/null +++ b/windows/src/support/wow64kbd/run.bat @@ -0,0 +1,11 @@ +@echo off +rem Assuming debug build, running from same folder + +echo ---- ARM64 ---- +if %PROCESSOR_ARCHITECTURE% == ARM64 ( .\bin\ARM64\Debug\wow64kbd.exe ) else ( echo Skipping because arch=%PROCESSOR_ARCHITECTURE% ) +echo ---- ARM64EC ---- +if %PROCESSOR_ARCHITECTURE% == ARM64 ( .\bin\ARM64EC\Debug\wow64kbd.exe ) else ( echo Skipping because arch=%PROCESSOR_ARCHITECTURE% ) +echo ---- x64 ---- +.\bin\x64\Debug\wow64kbd.exe +echo ---- x86 (Win32) ---- +.\bin\Win32\Debug\wow64kbd.exe diff --git a/windows/src/support/wow64kbd/wow64kbd.cpp b/windows/src/support/wow64kbd/wow64kbd.cpp new file mode 100644 index 0000000000..7e3da6f32f --- /dev/null +++ b/windows/src/support/wow64kbd/wow64kbd.cpp @@ -0,0 +1,121 @@ +#include +#include + +void hexDump(const void* addr, const int len, int perLine); + +typedef PBYTE(WINAPI* PKBDLAYERDESCRIPTORFUNC)(VOID); + +int main(void) { + BOOL bIsWow64; + if (!IsWow64Process(GetCurrentProcess(), &bIsWow64)) { + _tprintf(TEXT("IsWow64Process failed with %d\n"), GetLastError()); + } else { + _tprintf(TEXT("IsWow64Process returned %d.\n"), bIsWow64); + } + + USHORT processMachine, nativeMachine; + if (!IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine)) { + _tprintf(TEXT("Failed to call IsWow64Process2 with err=%d\n"), GetLastError()); + } else { + _tprintf(TEXT("IsWow64Process2: processMachine=%x nativeMachine=%x\n"), processMachine, nativeMachine); + } + + // For this test, we'll just load kbdus.dll from system32 + + HMODULE hKbdLibrary = LoadLibrary(TEXT("kbdus.dll")); + if (!hKbdLibrary) { + _tprintf(TEXT("Failed to load kbdus.dll err=%d\n"), GetLastError()); + return 1; + } + + PKBDLAYERDESCRIPTORFUNC KbdLayerDescriptorFunc = + (PKBDLAYERDESCRIPTORFUNC)GetProcAddress(hKbdLibrary, "KbdLayerDescriptor"); + + if (!KbdLayerDescriptorFunc) { + _tprintf(TEXT("Failed to get KbdLayerDescriptor err=%d\n"), GetLastError()); + return 1; + } + + const PBYTE data = (*KbdLayerDescriptorFunc)(); + if (!data) { + _tprintf(TEXT("Failed to get data from KbdLayerDescriptor err=%d\n"), GetLastError()); + return 1; + } + + _tprintf(TEXT("Address of data: %p\n"), data); + // print hex dump of first 44 bytes, which is 32 bit kbdtables size + hexDump(data, 44, 16); + + return 0; +} + + +// https://stackoverflow.com/a/7776146/1836776 +// +// Usage: +// hexDump(addr, len, perLine); +// addr: the address to start dumping from. +// len: the number of bytes to dump. +// perLine: number of bytes on each output line. + +void hexDump(const void* addr, const int len, int perLine) { + // Silently ignore silly per-line values. + + if (perLine < 4 || perLine > 64) + perLine = 16; + + int i; + unsigned char buff[64 + 1]; + const unsigned char* pc = (const unsigned char*)addr; + + // Length checks. + + if (len == 0) { + _tprintf(TEXT(" ZERO LENGTH\n")); + return; + } + if (len < 0) { + _tprintf(TEXT(" NEGATIVE LENGTH: %d\n"), len); + return; + } + + // Process every byte in the data. + + for (i = 0; i < len; i++) { + // Multiple of perLine means new or first line (with line offset). + + if ((i % perLine) == 0) { + // Only print previous-line ASCII buffer for lines beyond first. + + if (i != 0) + _tprintf(TEXT(" %hs\n"), buff); + + // Output the offset of current line. + + _tprintf(TEXT(" %04x "), i); + } + + // Now the hex code for the specific character. + + _tprintf(TEXT(" %02x"), pc[i]); + + // And buffer a printable ASCII character for later. + + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) // isprint() may be better. + buff[i % perLine] = '.'; + else + buff[i % perLine] = pc[i]; + buff[(i % perLine) + 1] = '\0'; + } + + // Pad out last line if not exactly perLine characters. + + while ((i % perLine) != 0) { + _tprintf(TEXT(" ")); + i++; + } + + // And print the final ASCII buffer. + + _tprintf(TEXT(" %hs\n"), buff); +} diff --git a/windows/src/support/wow64kbd/wow64kbd.sln b/windows/src/support/wow64kbd/wow64kbd.sln new file mode 100644 index 0000000000..ae8bdc6fe5 --- /dev/null +++ b/windows/src/support/wow64kbd/wow64kbd.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36616.10 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wow64kbd", "wow64kbd.vcxproj", "{6524CBE6-7AC8-4C31-92D2-F88889E57831}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|ARM64EC = Debug|ARM64EC + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 + Release|ARM64EC = Release|ARM64EC + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|ARM64.Build.0 = Debug|ARM64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|ARM64EC.Build.0 = Debug|ARM64EC + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|x64.ActiveCfg = Debug|x64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|x64.Build.0 = Debug|x64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|x86.ActiveCfg = Debug|Win32 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Debug|x86.Build.0 = Debug|Win32 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|ARM64.ActiveCfg = Release|ARM64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|ARM64.Build.0 = Release|ARM64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|ARM64EC.ActiveCfg = Release|ARM64EC + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|ARM64EC.Build.0 = Release|ARM64EC + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|x64.ActiveCfg = Release|x64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|x64.Build.0 = Release|x64 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|x86.ActiveCfg = Release|Win32 + {6524CBE6-7AC8-4C31-92D2-F88889E57831}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6DA4C84-4667-4D0A-BEEC-F4982B976146} + EndGlobalSection +EndGlobal diff --git a/windows/src/support/wow64kbd/wow64kbd.vcxproj b/windows/src/support/wow64kbd/wow64kbd.vcxproj new file mode 100644 index 0000000000..3676490b9d --- /dev/null +++ b/windows/src/support/wow64kbd/wow64kbd.vcxproj @@ -0,0 +1,242 @@ + + + + + Debug + ARM64 + + + Debug + ARM64EC + + + Debug + Win32 + + + Release + ARM64 + + + Release + ARM64EC + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {6524cbe6-7ac8-4c31-92d2-f88889e57831} + wow64kbd + 10.0 + + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + false + v143 + true + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + \ No newline at end of file diff --git a/windows/src/support/wow64kbd/wow64kbd.vcxproj.filters b/windows/src/support/wow64kbd/wow64kbd.vcxproj.filters new file mode 100644 index 0000000000..ba03c960a9 --- /dev/null +++ b/windows/src/support/wow64kbd/wow64kbd.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/windows/src/test/manual-tests/keyboard_ll_identifier/README.md b/windows/src/test/manual-tests/keyboard_ll_identifier/README.md new file mode 100644 index 0000000000..5efc1cc91b --- /dev/null +++ b/windows/src/test/manual-tests/keyboard_ll_identifier/README.md @@ -0,0 +1,9 @@ +# keyboard_ll_identifier + +This test app allows us to view details of key events as they are received by +the low level keyboard hook. There are subtle differences between these key +events and those received in WM_KEY* messages, e.g. see discussions on +simulated left control in k32_visualkeyboardinterface.cpp. + +The app currently only reports on modifier keys, but it's easy to tweak for +other reporting as needed. \ No newline at end of file diff --git a/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dpr b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dpr new file mode 100644 index 0000000000..b7261ee398 --- /dev/null +++ b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dpr @@ -0,0 +1,14 @@ +program keyboard_ll_identifier; + +uses + Vcl.Forms, + keyboard_ll_identifier_unit in 'keyboard_ll_identifier_unit.pas' {Form1}; + +{$R *.res} + +begin + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. diff --git a/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dproj b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dproj new file mode 100644 index 0000000000..b169e40baf --- /dev/null +++ b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier.dproj @@ -0,0 +1,970 @@ + + + {C945C654-1D70-4248-8513-09FBD3715ACE} + 18.8 + VCL + keyboard_ll_identifier.dpr + True + Debug + Win32 + 1 + Application + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + keyboard_ll_identifier + + + DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;mbColorLibD10;dsnapcon;FireDACADSDriver;scFontCombo;FireDACMSAccDriver;fmxFireDAC;vclimg;TeeDB;FireDAC;Jcl;vcltouch;JvCore;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;inetdb;CEF4Delphi;FMXTee;soaprtl;DbxCommonDriver;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;Tee;fmxobj;vclwinx;common_components;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;browse4folder;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;TeeUI;VCLRESTComponents;soapserver;dbxcds;VclSmp;JvDocking;adortl;developer_components;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;keyman_components;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + + + DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;TeeDB;FireDAC;Jcl;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;inetdb;CEF4Delphi;FMXTee;soaprtl;DbxCommonDriver;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;Tee;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;TeeUI;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + true + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + PerMonitorV2 + + + + MainSource + + +

Form1
+ dfm + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + Application + + + + keyboard_ll_identifier.dpr + + + + + + keyboard_ll_identifier.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + classes + 1 + + + classes + 1 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + True + False + + + 12 + + + + + diff --git a/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.dfm b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.dfm new file mode 100644 index 0000000000..465d1801b2 --- /dev/null +++ b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.dfm @@ -0,0 +1,27 @@ +object Form1: TForm1 + Left = 0 + Top = 0 + Caption = 'Form1' + ClientHeight = 347 + ClientWidth = 728 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + OnCreate = FormCreate + OnDestroy = FormDestroy + PixelsPerInch = 96 + TextHeight = 13 + object Memo1: TMemo + Left = 8 + Top = 8 + Width = 697 + Height = 321 + Lines.Strings = ( + 'Memo1') + TabOrder = 0 + end +end diff --git a/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.pas b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.pas new file mode 100644 index 0000000000..ba897f415c --- /dev/null +++ b/windows/src/test/manual-tests/keyboard_ll_identifier/keyboard_ll_identifier_unit.pas @@ -0,0 +1,85 @@ +unit keyboard_ll_identifier_unit; + +interface + +uses + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, + Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; + +type + TForm1 = class(TForm) + Memo1: TMemo; + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + private + procedure AppOnMessage(var Msg: TMsg; var Handled: Boolean); + { Private declarations } + public + { Public declarations } + end; + +var + Form1: TForm1; + +implementation + +{$R *.dfm} + +var + hh: Cardinal; + +type + TKbdLlHookStruct = record + vkCode, scanCode, flags, time: DWORD; + dwExtraInfo: ULONG_PTR; + end; + PKbdLlHookStruct = ^TKbdLlHookStruct; + +function kbdll(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; +var + s: PKbdllhookstruct; +begin + if nCode < 0 then + begin + Exit(CallNextHookEx(hh, nCode, wParam, lParam)); + end; + + s := PkbdllHookStruct(lParam); + + if s.vkCode in [VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, VK_LSHIFT, VK_RSHIFT, VK_CONTROL, VK_SHIFT, VK_MENU] then + if Assigned(Form1) then + begin + Form1.memo1.Lines.Add(Format('%08.8x %08.8x %08.8x', [s.vkCode, s.scanCode, s.flags])); + end; + + Exit(CallNextHookEx(hh, nCode, wParam, lParam)); +end; + +procedure TForm1.FormCreate(Sender: TObject); +begin +// Uncomment to monitor WM_KEY* and WM_SYSKEY* messages +// Application.OnMessage := AppOnMessage; + + + hh := SetWindowsHookEx(WH_KEYBOARD_LL, kbdll, GetModuleHandle(nil), 0); +end; + +procedure TForm1.FormDestroy(Sender: TObject); +begin + Form1 := nil; + UnhookWindowsHookEx(hh); +end; + +procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean); +begin + if (Msg.message = WM_KEYDOWN) or (Msg.message = WM_SYSKEYDOWN) or (Msg.message = WM_KEYUP) or (Msg.message = WM_SYSKEYUP) then + begin + if Msg.wParam in [VK_CONTROL, VK_SHIFT, VK_MENU] then + if (Msg.LParam and $FFFF) = 1 then + begin + memo1.Lines.Add(Format('%08.8x %08.8x', [Msg.wParam, Msg.lParam])); + end; + end; +end; + +end. diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.js b/windows/src/test/manual-tests/platform-rules/platformtest.js deleted file mode 100644 index 0a1c8bfa78..0000000000 --- a/windows/src/test/manual-tests/platform-rules/platformtest.js +++ /dev/null @@ -1,380 +0,0 @@ - -KeymanWeb.KR(new Keyboard_platformtest()); - -function Keyboard_platformtest() -{ - - this.KI = "Keyboard_platformtest"; - this.KN = "PlatformTest"; - this.KMINVER = "9.0"; - this.KV = null; - this.KH = ''; - this.KM = 0; - this.KBVER = "1.0"; - this.KMBM = 0x0000; - this.s17 = "touch"; - this.s18 = "hardware"; - this.s21 = "windows"; - this.s22 = "android"; - this.s23 = "ios"; - this.s24 = "macosx"; - this.s25 = "linux"; - this.s28 = "desktop"; - this.s29 = "tablet"; - this.s30 = "phone"; - this.s33 = "native"; - this.s34 = "web"; - this.s37 = "ie"; - this.s38 = "chrome"; - this.s39 = "edge"; - this.s40 = "firefox"; - this.s41 = "safari"; - this.s42 = "opera"; - this.s45 = "touch"; - this.s46 = "hardware"; - this.s49 = "platform-x"; - this.s52 = "WinDOWS"; - this.s53 = "ANDroid"; - this.s54 = "iOS"; - this.s55 = "macOSX"; - this.s56 = "LINUX"; - this.s58 = "touch"; - this.s59 = "hardware"; - this.s60 = "windows"; - this.s61 = "android"; - this.s62 = "ios"; - this.s63 = "macosx"; - this.s64 = "linux"; - this.s65 = "desktop"; - this.s66 = "tablet"; - this.s67 = "phone"; - this.s68 = "native"; - this.s69 = "web"; - this.s70 = "ie"; - this.s71 = "chrome"; - this.s72 = "edge"; - this.s73 = "firefox"; - this.s74 = "safari"; - this.s75 = "opera"; - this.s76 = "touch"; - this.s77 = "hardware"; - this.s78 = "platform-x"; - this.s79 = "WinDOWS"; - this.s80 = "ANDroid"; - this.s81 = "iOS"; - this.s82 = "macOSX"; - this.s83 = "LINUX"; - this.KVER = "10.0.1109.0"; - this.gs = function(t, e) { - return this.g0(t, e); - }; - this.g0 = function(t, e) { - var k = KeymanWeb, - r = 0, - m = 0; - if (k.KKM(e, 16384, 65)) { - r = m = 1; - r = this.g1(t, e); - } - if (!m && k.KIK(e)) { - r = 1; - r = this.g9(t, e); - } - return r; - }; - this.g1 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s17, t)) { - m = 1; - k.KO(0, t, "touch"); - } else if (k.KIFS(31, this.s18, t)) { - m = 1; - k.KO(0, t, "hardware"); - } - if (m) { - r = this.g2(t, e); - } - if (!m) { - k.KO(-1, t, "undefined"); - r = this.g2(t, e); - } - return r; - }; - this.g2 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s21, t)) { - m = 1; - k.KO(0, t, " windows"); - } else if (k.KIFS(31, this.s22, t)) { - m = 1; - k.KO(0, t, " android"); - } else if (k.KIFS(31, this.s23, t)) { - m = 1; - k.KO(0, t, " ios"); - } else if (k.KIFS(31, this.s24, t)) { - m = 1; - k.KO(0, t, " macosx"); - } else if (k.KIFS(31, this.s25, t)) { - m = 1; - k.KO(0, t, " linux"); - } - if (m) { - r = this.g3(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g3(t, e); - } - return r; - }; - this.g3 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s28, t)) { - m = 1; - k.KO(0, t, " desktop"); - } else if (k.KIFS(31, this.s29, t)) { - m = 1; - k.KO(0, t, " tablet"); - } else if (k.KIFS(31, this.s30, t)) { - m = 1; - k.KO(0, t, " phone"); - } - if (m) { - r = this.g4(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g4(t, e); - } - return r; - }; - this.g4 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s33, t)) { - m = 1; - k.KO(0, t, " native"); - } else if (k.KIFS(31, this.s34, t)) { - m = 1; - k.KO(0, t, " web"); - } - if (m) { - r = this.g5(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g5(t, e); - } - return r; - }; - this.g5 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s37, t)) { - m = 1; - k.KO(0, t, " ie"); - } else if (k.KIFS(31, this.s38, t)) { - m = 1; - k.KO(0, t, " chrome"); - } else if (k.KIFS(31, this.s39, t)) { - m = 1; - k.KO(0, t, " edge"); - } else if (k.KIFS(31, this.s40, t)) { - m = 1; - k.KO(0, t, " firefox"); - } else if (k.KIFS(31, this.s41, t)) { - m = 1; - k.KO(0, t, " safari"); - } else if (k.KIFS(31, this.s42, t)) { - m = 1; - k.KO(0, t, " opera"); - } - if (m) { - r = this.g6(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g6(t, e); - } - return r; - }; - this.g6 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (!k.KIFS(31, this.s45, t)) { - m = 1; - k.KO(0, t, " !touch"); - } else if (!k.KIFS(31, this.s46, t)) { - m = 1; - k.KO(0, t, " !hardware"); - } - if (m) { - r = this.g7(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g7(t, e); - } - return r; - }; - this.g7 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s49, t)) { - m = 1; - k.KO(0, t, " platform-x"); - } - if (m) { - r = this.g8(t, e); - } - if (!m) { - k.KO(-1, t, " undefined"); - r = this.g8(t, e); - } - return r; - }; - this.g8 = function(t, e) { - var k = KeymanWeb, - r = 1, - m = 0; - if (k.KIFS(31, this.s52, t)) { - m = 1; - k.KO(0, t, " windows"); - } else if (k.KIFS(31, this.s53, t)) { - m = 1; - k.KO(0, t, " android"); - } else if (k.KIFS(31, this.s54, t)) { - m = 1; - k.KO(0, t, " ios"); - } else if (k.KIFS(31, this.s55, t)) { - m = 1; - k.KO(0, t, " macosx"); - } else if (k.KIFS(31, this.s56, t)) { - m = 1; - k.KO(0, t, " linux"); - } - if (!m) { - k.KO(-1, t, " undefined"); - } - return r; - }; - this.g9 = function(t, e) { - var k = KeymanWeb, - r = 0, - m = 0; - if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s79, t)) { - r = m = 1; - k.KO(0, t, " Windows"); - } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s80, t)) { - r = m = 1; - k.KO(0, t, " Android"); - } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s81, t)) { - r = m = 1; - k.KO(0, t, " iOS"); - } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s82, t)) { - r = m = 1; - k.KO(0, t, " macOS"); - } else if (k.KKM(e, 16384, 69) && k.KIFS(31, this.s83, t)) { - r = m = 1; - k.KO(0, t, " Linux"); - } else if (k.KKM(e, 16384, 69)) { - r = m = 1; - k.KO(0, t, " [OS Undefined (case insensitive test)]"); - } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s65, t)) { - r = m = 1; - k.KO(0, t, " Desktop"); - } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s66, t)) { - r = m = 1; - k.KO(0, t, " Tablet"); - } else if (k.KKM(e, 16384, 73) && k.KIFS(31, this.s67, t)) { - r = m = 1; - k.KO(0, t, " Phone"); - } else if (k.KKM(e, 16384, 73)) { - r = m = 1; - k.KO(0, t, " [FF Undefined]"); - } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s60, t)) { - r = m = 1; - k.KO(0, t, " Windows"); - } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s61, t)) { - r = m = 1; - k.KO(0, t, " Android"); - } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s62, t)) { - r = m = 1; - k.KO(0, t, " iOS"); - } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s63, t)) { - r = m = 1; - k.KO(0, t, " OSX"); - } else if (k.KKM(e, 16384, 79) && k.KIFS(31, this.s64, t)) { - r = m = 1; - k.KO(0, t, " Linux"); - } else if (k.KKM(e, 16384, 79)) { - r = m = 1; - k.KO(0, t, " [OS Undefined]"); - } else if (k.KKM(e, 16384, 80) && k.KIFS(31, this.s58, t)) { - r = m = 1; - k.KO(0, t, "Touch"); - } else if (k.KKM(e, 16384, 80) && k.KIFS(31, this.s59, t)) { - r = m = 1; - k.KO(0, t, "Hardware"); - } else if (k.KKM(e, 16384, 80)) { - r = m = 1; - k.KO(0, t, "[UI Undefined]"); - } else if (k.KKM(e, 16384, 82) && k.KIFS(31, this.s78, t)) { - r = m = 1; - k.KO(0, t, " Platform-X"); - } else if (k.KKM(e, 16384, 82)) { - r = m = 1; - k.KO(0, t, " [Platform-X Undefined]"); - } else if (k.KKM(e, 16384, 84) && !k.KIFS(31, this.s76, t)) { - r = m = 1; - k.KO(0, t, " !Touch"); - } else if (k.KKM(e, 16384, 84) && !k.KIFS(31, this.s77, t)) { - r = m = 1; - k.KO(0, t, " !Hardware"); - } else if (k.KKM(e, 16384, 84)) { - r = m = 1; - k.KO(0, t, " [Inverted OS Undefined]"); - } else if (k.KKM(e, 16384, 85) && k.KIFS(31, this.s68, t)) { - r = m = 1; - k.KO(0, t, " Native"); - } else if (k.KKM(e, 16384, 85) && k.KIFS(31, this.s69, t)) { - r = m = 1; - k.KO(0, t, " Web"); - } else if (k.KKM(e, 16384, 85)) { - r = m = 1; - k.KO(0, t, " [Nativeness Undefined]"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s70, t)) { - r = m = 1; - k.KO(0, t, " IE"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s71, t)) { - r = m = 1; - k.KO(0, t, " Chrome"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s72, t)) { - r = m = 1; - k.KO(0, t, " Edge"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s73, t)) { - r = m = 1; - k.KO(0, t, " Firefox"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s74, t)) { - r = m = 1; - k.KO(0, t, " Safari"); - } else if (k.KKM(e, 16384, 89) && k.KIFS(31, this.s75, t)) { - r = m = 1; - k.KO(0, t, " Opera"); - } else if (k.KKM(e, 16384, 89)) { - r = m = 1; - k.KO(0, t, " [Browser Undefined]"); - } - return r; - }; -} \ No newline at end of file diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.kmp b/windows/src/test/manual-tests/platform-rules/platformtest.kmp deleted file mode 100644 index c6a939f9ae..0000000000 Binary files a/windows/src/test/manual-tests/platform-rules/platformtest.kmp and /dev/null differ diff --git a/windows/src/test/manual-tests/platform-rules/platformtest.kmx b/windows/src/test/manual-tests/platform-rules/platformtest.kmx deleted file mode 100644 index b9c9de160e..0000000000 Binary files a/windows/src/test/manual-tests/platform-rules/platformtest.kmx and /dev/null differ diff --git a/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj b/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj index 77f601a5c5..8c76a595a8 100644 --- a/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj +++ b/windows/src/test/unit-tests/shared-data/cpp/cppshareddata.vcxproj @@ -1,176 +1,176 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {748219AC-3BDE-40FC-8E93-7516C4BCE81D} - Win32Proj - cppshareddata - 10.0 - - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - Application - true - v142 - Unicode - - - Application - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - true - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - false - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - Use - Level3 - Disabled - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - - - - - Use - Level3 - Disabled - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - - - - - Use - Level3 - MaxSpeed - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - true - true - - - - - Use - Level3 - MaxSpeed - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - false - pch.h - - - Console - true - true - true - - - - - - - - - Create - Create - Create - Create - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {748219AC-3BDE-40FC-8E93-7516C4BCE81D} + Win32Proj + cppshareddata + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + true + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + false + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + + + Console + true + true + true + + + + + + + + + Create + Create + Create + Create + + + + + \ No newline at end of file