Merge branch 'fix/core/10605-app-context-copy-strip-markers' into fix/core/10615-10616-10617-core-surrogates-and-app-context-markers-and-memory-leak

This commit is contained in:
Marc Durdin 2024-02-05 13:45:03 +11:00 • committed by GitHub
commit fe8eba176c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 246174 additions and 609 deletions

View file

@ -115,7 +115,7 @@ jobs:
strategy:
fail-fast: true
matrix:
dist: [focal, jammy, lunar, mantic]
dist: [focal, jammy, mantic, noble]
arch: [amd64]
runs-on: ubuntu-latest
@ -241,15 +241,12 @@ jobs:
esac
echo -e "::group::${COLOR_GREEN}Upload source package"
cd keyman-srcpkg
dput_retry -U llso:ubuntu/jammy${destination} *_source.changes
echo "::endgroup::"
echo -e "::group::${COLOR_GREEN}Uploading binary packages"
cd ../keyman-binarypkgs
pattern="keyman_${{ needs.sourcepackage.outputs.VERSION }}.+\+(.*)[0-9]_[^.]+.changes"
for f in *.changes; do
for f in *_amd64.changes; do
if [[ $f =~ $pattern ]]; then
dist=${BASH_REMATCH[1]}
dput_retry -U llso:ubuntu/${dist}${destination} $f

View file

@ -1,5 +1,48 @@
# Keyman Version History
## 17.0.260 alpha 2024-02-03
* chore(oem/fv): Update versions in keyboards.csv (#10606)
* fix(linux): Fix path to artifacts when uploading to llso (#10609)
* docs(linux): Update sample settings (#10610)
* chore(linux): Fix API check if lines got removed in .symbols file (#10612)
## 17.0.259 alpha 2024-02-02
* feat(developer): double markers once again (#10541)
* feat(core): ldml double marker, C++ side (#10563)
* feat(core,developer): simplify markers (#10565)
* chore(common): cancel earlier builds when new test builds triggered (#10593)
* chore(linux): Use correct format specifier for `g_utf8_strlen` (#10599)
* fix(linux): Fix version comparison (#10576)
* chore(common): Add entries from 16.0.145 HISTORY.md (#10603)
* chore(common): history missing message fixup (#10601)
* chore(linux): Update debian changelog (#10596)
## 17.0.258 alpha 2024-02-01
* chore(developer): add unicode-license.txt for ldml keyboards data (#10568)
* feat(core): ldml reorder marker processing (#10539)
* chore(developer): kmc ldml build - call c8 directly for tests (#10522)
* fix(web): app/webview control flow for initial layer (#10571)
* chore(linux): Upgrade artifacts to v4 (#10577)
* chore(linux): Fix the path of downloaded artifacts (#10594)
## 17.0.257 alpha 2024-01-31
* epic: Keyman Core normalization (#10390)
* fix(web): OSK resource-loading promise's font-wait (#10506)
* fix(web): documentation-keyboard font size, font scaling (#10507)
* feat(web): testing page for font-load / key-scale interactions (#10534)
* fix(web): proper await for special-osk font, use of it in key scaling (#10535)
* fix(web): ensure use of actual key-cap / label font in key-scale ops (#10555)
* docs: Update websites README.md (#10566)
* fix(developer): kmc exit needs to wait for Sentry (#10558)
* fix(developer): make sourcePath calculation for kmc-keyboard-info more lenient (#10559)
* chore(developer): disable .js output for LDML keyboard compiler for 17.0 (#10560)
* chore(linux): Replace Lunar with Noble (#10561)
* chore(linux): Adjust test expectations for ICU >= 73 (#10573)
## 17.0.256 alpha 2024-01-30
* fix(linux): packaging on Ubuntu and Debian servers should give message instead of warning because we don't have patched ibus there (#10537)
@ -26,7 +69,7 @@
* fix(common): ldml: fix typo in kmc error message (#10484)
* chore(developer): `--enable-source-maps` parameter for kmc wrapper (#10496)
* fix(android/engine): Fix OSK widths (#10442)
* (#10508)
* fix(web): banner suggestion resizing after device rotation (#10508)
## 17.0.251 alpha 2024-01-24
@ -38,7 +81,7 @@
* fix(web): osk responsiveness to held modifiers on legacy kbds (#10437)
* fix(android/engine): Skip final globe key action (#10465)
* feat(developer): marker normalization in .ts (#10443)
* (#10445)
* fix(android): hardware keystrokes now include mnemonic processing (#10445)
## 17.0.249 alpha 2024-01-22
@ -1196,7 +1239,7 @@
## 17.0.67 alpha 2023-03-14
* (developer):Update copyright period in License.rtf (#8425)
* chore(developer): Update copyright period in License.rtf (#8425)
* chore(common): support shorthand for build.sh (#8415)
* chore(common): build script performance improvements (#8416)
* chore(common): TS updates to non-sync'd packages, feature-esmodule merge conflict prevention (#8429)
@ -1573,6 +1616,16 @@
* chore: move to 17.0-alpha (#7577)
* chore: Move to 17.0 alpha
## 16.0.145 stable 2024-02-01
* chore(linux): Add support for loong64 architecture (#10108)
* chore(linux): Update debian changelog (#10122)
* fix(web): Fix null error with legacy keyboards (#10177)
* chore(ios): FV certificate key (#10229)
* fix(oem/fv): Add fv_hulquminum_combine and fv_kwadacha_tsekene (#10285)
* chore(developer): upgrade ngrok to v3 (#10360)
* chore(linux): Build with webkitgtk 4.1 instead of 4.0 (#10574)
## 16.0.144 stable 2023-11-30
* chore(linux): Update debian changelog (#10046)

View file

@ -1 +1 @@
17.0.257
17.0.261

View file

@ -145,95 +145,8 @@ export class MarkerParser {
* @returns the normalized string
*/
public static nfd_markers(s: string, forMatch?: boolean) : string {
// Note: parallel to ldml::normalize_nfd_markers()
if(!s) return s;
/** Accumulate output normalized string */
let out = '';
/** 's' split into code point units */
let a = [...s];
/** index into `a` where the current segment starts */
let seg_start = 0;
/** index into 'a' following end of current segment */
let seg_end = 0;
/** iterator over 'a' */
let i = 0;
/** for parallelism with C++, end of string */
let str_end = a.length;
/** map of all markers in the string */
const mall : MarkerMap = [];
// preprocess string, also check for markers
const all_no_markers = MarkerParser.remove_markers(s, mall, forMatch);
if(mall.length == 0) {
// no markers, so just normalize as a single segment
return all_no_markers.normalize("NFD");
}
// split segments on grapheme cluster boundaries for processing
const raw_segments = Array.from(graphemeSegmenter.segment(all_no_markers))
.map(({ segment }) => segment)
.map((c) => c.codePointAt(0));
/**
* Set of all code points which have a boundary before.
* This parallels nfd->hasBoundaryBefore().
*/
const hasBoundaryBefore = new Set(raw_segments);
// now we'll loop through looking for normalization-safe subsegments of [seg_start, seg_end)
// For example (sentinel mode) the following would be segments:
// - A
// - E\u0320\u0302
// - U\u0320\m{marker}\u0300
//
// The marker will typically 'look' like an NFD-safe boundary, but it's not! It's part of the
// subsegment, we want to skip over it. This is why we use parse_next_marker to look-ahead to see
// if there is actually a marker under the iterator.
//
// We don't assume that the marker sentinel or regex appears as an NFD boundary, that is why
// the parse function and the nfd function are called in parallel.
do {
/** remainder of string i..end, for match */
const rest = a.slice(i).join('');
const p = MarkerParser.parse_next_marker(rest, forMatch);
const have_marker = !!(p?.match);
// First, categorize the current character.
if (i === str_end) {
// at end of string, so this is also the end of the segment.
seg_end = i;
} else if (hasBoundaryBefore.has(rest.codePointAt(0)) && !have_marker) {
// it's an NFD safe boundary, but NOT a marker
// (if it were a marker, we would fall through to consume the marker)
// segment ends before this code point
seg_end = i;
// move the index past the NFD boundary
i++;
} else if(have_marker) {
// we have a marker. move the index past the marker.
i += [...p.match].length;
} else {
// non boundary. just move the index forward
i++;
}
// Next, if we found a segment boundary (which includes
// end of string) process it
if(seg_end !== seg_start) {
// re-join the segment from the iterated code points
const segment = a.slice(seg_start, seg_end).join('');
const m : MarkerMap = [];
// process the subsegment
const segment_nfd = MarkerParser.nfd_markers_segment(segment, m, forMatch);
// append the processed segment to the output buffer
out = out + segment_nfd;
// the end of this segment is the start of the next one.
seg_start = seg_end;
}
} while (seg_end != str_end); // until the last codepoint processed
return out;
const m : MarkerMap = [];
return this.nfd_markers_segment(s, m, forMatch);
}
/**
@ -283,8 +196,8 @@ export class MarkerParser {
}
/** output string */
let out = '';
/** for checking: the total number of markers expected */
const max_markers = map.length;
/** for checking: the total number of markers expected, skipping end markers */
const max_markers = map.filter(({ end }) => !end).length;
/** for checking: the number of markers we've written */
let written_markers = 0;
/** we are going to mutate the map, so copy it */
@ -292,39 +205,36 @@ export class MarkerParser {
// First, add back all 'MARKER_BEFORE_EOT' markers
while (map2.length && map2[map2.length - 1].ch === MARKER_BEFORE_EOT) {
// remove from list
const { marker } = map2.pop();
out = MarkerParser.prepend_marker(out, marker, forMatch);
written_markers++;
const { marker, end } = map2.pop();
if (!end) {
out = MarkerParser.prepend_marker(out, marker, forMatch);
written_markers++;
}
}
// Then, take each codepoint (from back to front)
for (let p of [...s].reverse()) {
// reverse order code units, prepend to out
out = p + out;
// 1. we write any markers which match the output string in order,
// and pop them off the list as dones
while(map2.length > 0 && map2[map2.length-1].ch === p) {
const { marker } = map2.pop();
if (marker !== constants.marker_no_index) { // if not already written
out = MarkerParser.prepend_marker(out, marker, forMatch);
written_markers++;
// no need to update .marker here, we're about to pop it
}
}
// 2. now, any out of order markers. iterate with an index so we can record.
// will skip this if map2.length < 2
for (let i = map2.length - 2; i >= 0; i--) {
const { ch, marker } = map2[i]; // don't pop here, as it might not be the right char
if (ch === p && marker !== constants.marker_no_index) {
out = MarkerParser.prepend_marker(out, marker, forMatch);
written_markers++;
map2[i].marker = constants.marker_no_index; // mark as written
for (let i = map2.length - 1; i >= 0; i--) {
const { ch, marker, processed, end } = map2[i];
if (ch === p && !processed) {
map2[i].processed = true; // mark as processed
if (end) {
break; // exit loop
} else {
out = MarkerParser.prepend_marker(out, marker, forMatch);
written_markers++;
}
} else if (map2[map2.length-1]?.processed) {
// keep the list as short as possible
map2.pop();
}
}
}
// validate that we consumed all markers
if(written_markers !== max_markers) {
throw Error(`Internal Error: We should have written ${max_markers} markers but only wrote ${written_markers}`);
throw Error(`Internal Error: should have written ${max_markers} markers but only wrote ${written_markers}`);
}
return out;
}
@ -343,18 +253,31 @@ export class MarkerParser {
let last_markers: number[] = [];
/** input string, split into codepoint runs */
let a: string[] = [...s];
/** were any markers found? */
let had_markers = false;
/**
* subfunc: add all markers in the pending (last_markers) queue
* @param l string the marker is 'glued' to, or '' for end
*/
function add_pending_markers(l: string): void {
// first char, or, marker-before-eot. Must be first char of NFD sequence
const ch = (l === '') ? MARKER_BEFORE_EOT : [...(l.normalize("NFD"))][0];
function add_pending_markers(l: string){
// first char, or, marker-before-eot
const glueChars = (l === '') ? [MARKER_BEFORE_EOT] : [...(l.normalize("NFD"))];
const glue = glueChars[0];
// push the 'end' value
map.push({ ch: glue, end: true });
while(last_markers.length) {
const marker = last_markers[0];
last_markers = last_markers.slice(1); // pop from front
map.push({ ch, marker });
map.push({ ch: glue, marker });
}
// now, push the rest of the glue chars as an NFD sequence.
// For example, `\m{m}\u0344` will create the following stream:
// { ch: 0308, end: true}
// { ch: 0308, marker: 1}
// { ch: 0301, end: true} // added because of decomp
for(const ch of glueChars.slice(1)) {
map.push({ ch, end: true });
}
}
@ -369,6 +292,7 @@ export class MarkerParser {
a = a.slice(1); // move forward 1 codepoint
} else {
// found a marker
had_markers = true;
const { marker, match } = p;
if ((marker == constants.marker_any_index) ||
(marker >= constants.marker_min_index && marker <= constants.marker_max_index)) {
@ -379,8 +303,12 @@ export class MarkerParser {
a = a.slice([...match].length); // move forward over matched marker
}
}
// finally, add any remaining markers at the end of the string
// add any remaining markers at the end of the string
add_pending_markers('');
if (!had_markers) {
// no markers were found. clear out the map.
map = [];
}
return out;
}
@ -422,19 +350,21 @@ export class MarkerParser {
};
/** special noncharacter value denoting end of string */
const MARKER_BEFORE_EOT = '\ufffe';
export const MARKER_BEFORE_EOT = '\ufffe';
/** matcher for a sentinel */
const PARSE_SENTINEL_MARKER = new RegExp(`^${MarkerParser.ANY_MARKER_MATCH}`);
/** matcher for a regex marker, either single or any */
const PARSE_REGEX_MARKER = /^\\uffff\\u0008(?:(\\u[0-9a-fA-F]{4})|(\[\\u[0-9a-fA-F]{4}-\\u[0-9a-fA-F]{4}\]))/;
/** a grapheme cluster segmenter for finding NFD boundaries */
const graphemeSegmenter = new Intl.Segmenter(['und'], { granularity: 'grapheme' });
export interface MarkerEntry {
/** code point 'glued' to, or MARKER_BEFORE_EOT */
/** code point 'glued' to, or MARKER_BEFORE_EOT */
ch? : string;
/** marker number, 1-based */
marker? : number;
/** true if processed */
processed? : boolean;
/** true if the end of the entries */
end? : boolean;
};
/** list of marker entries, from remove_markers */

View file

@ -1,6 +1,6 @@
import 'mocha';
import { assert } from 'chai';
import { ElementParser, ElementSegment, ElementType, MarkerParser, MarkerResult, OrderedStringList, VariableParser } from '../../src/ldml-keyboard/pattern-parser.js';
import { ElementParser, ElementSegment, ElementType, MARKER_BEFORE_EOT, MarkerMap, MarkerParser, MarkerResult, OrderedStringList, VariableParser } from '../../src/ldml-keyboard/pattern-parser.js';
import { constants } from '@keymanapp/ldml-keyboard-constants';
import { KMXFile } from '../../src/kmx/kmx.js';
import Hexy from 'hexy';
@ -339,6 +339,18 @@ describe('Test of nfd_markers()', () => {
assert.equal(dst, <string>expect, `mapping ${src} with forMatch=${forMatch}`);
});
});
it('should be able to remove_markers() with composed chars', () => {
const src = '\uffff\u0008\u0001\u0344';
const m : MarkerMap = [];
const dst = MarkerParser.remove_markers(src, m, false);
assert.equal(dst, '\u0344'); // nfc
assert.sameDeepOrderedMembers(m, [
{ ch: '\u0308', end: true },
{ ch: '\u0308', marker: 1},
{ ch: '\u0301', end: true},
{ ch: MARKER_BEFORE_EOT, end: true },
]);
});
it('should normalize as expected', () => {
// this is a little bit simpler in structure than what's in test_transforms.cpp,
// see there for more complicated cases and discussion
@ -379,6 +391,30 @@ describe('Test of nfd_markers()', () => {
["\u0995\uFFFF\u0008\u0001\u09CB", "\u0995\uFFFF\u0008\u0001\u09C7\u09BE"],
["\u0995\u09BE\uFFFF\u0008\u0001\u09C7"],
["\u03B5\uFFFF\u0008\u0001\u0344", "\u03B5\uFFFF\u0008\u0001\u0308\u0301"],
// double marker -
["e\uffff\u0008\u0001\u0300\u0320\u0300", "e\u0320\uffff\u0008\u0001\u0300\u0300"],
// double marker - no change
["e\u0320\uffff\u0008\u0001\u0300\u0300", "e\u0320\uffff\u0008\u0001\u0300\u0300"],
// Double marker with greek!
["\u03B5\uFFFF\u0008\u0001\u0344\uFFFF\u0008\u0002\u0344\uFFFF\u0008\u0003",
"\u03B5\uFFFF\u0008\u0001\u0308\u0301\uFFFF\u0008\u0002\u0308\u0301\uFFFF\u0008\u0003"],
// double marker -
["e\uffff\u0008\u0001\u0300\u0320\u0300", "e\u0320\uffff\u0008\u0001\u0300\u0300"],
// double marker - no change
["e\u0320\uffff\u0008\u0001\u0300\u0300", "e\u0320\uffff\u0008\u0001\u0300\u0300"],
// double marker - in front of second, no change
["e\u0320\u0300\uffff\u0008\u0001\u0300", "e\u0320\u0300\uffff\u0008\u0001\u0300"],
// double marker - in front of second, with segment reordering
["e\u0300\u0320\uffff\u0008\u0001\u0300", "e\u0320\u0300\uffff\u0008\u0001\u0300"],
// double marker - alternate pattern with reordering needed
["e\u0300\uffff\u0008\u0001\u0300\u0320", "e\u0320\u0300\uffff\u0008\u0001\u0300"],
// triple diacritic + marker - reordering needed
["e\u0300\uffff\u0008\u0001\u0300\u0320\u0300", "e\u0320\u0300\uffff\u0008\u0001\u0300\u0300"],
];
for (let i = 0; i < src_expect.length; i++) {
@ -390,7 +426,14 @@ describe('Test of nfd_markers()', () => {
// regex
forMatch = true;
}
const dst = MarkerParser.nfd_markers(src, forMatch);
let dst;
try {
dst = MarkerParser.nfd_markers(src, forMatch);
} catch(e) {
console.error(e);
assert.fail(`#${i+1} normalizing '${src}'`);
}
assert.ok(dst, `#${i+1} normalizing '${src}'`);
assert.equal(dst, exp, `#${i+1} normalizing '${src}' forMatch=${forMatch} (got \n${hex_str(dst)} expected \n${hex_str(exp)}) `);
}
});

View file

@ -58,7 +58,30 @@ bool normalize_nfd(std::u16string &str) {
return normalize(nfd, str, status);
}
static void add_back_markers(std::u32string &str, const std::u32string &src, marker_map &map, marker_encoding encoding) {
marker_entry::marker_entry(char32_t c) : ch(c), marker(LDML_MARKER_NO_INDEX), processed(false), end(true) {
}
marker_entry::marker_entry(char32_t c, marker_num n) : ch(c), marker(n), processed(false), end(false) {
}
size_t count_markers(const marker_map &map) {
size_t m = 0;
for (auto i = map.begin(); i < map.end(); i++) {
// add all actual markers
if (i->marker != LDML_MARKER_NO_INDEX) {
m++;
}
}
return m;
}
void add_back_markers(std::u32string &str, const std::u32string &src, marker_map &map, marker_encoding encoding) {
if (map.empty()) {
// quick check, nothing to do if no markers
str = src;
return;
}
// need to reconstitute.
marker_map map2(map); // make a copy of the map
// clear the string
@ -66,13 +89,16 @@ static void add_back_markers(std::u32string &str, const std::u32string &src, mar
// iterator over the marker map
auto marki = map2.rbegin();
// number of markers left to process
size_t processed_count = map2.size();
size_t max_markers = count_markers(map);
size_t processed_markers = 0;
// add any end-of-text markers
while(marki != map2.rend() && marki->first == MARKER_BEFORE_EOT) {
prepend_marker(str, marki->second, encoding);
processed_count--;
marki->second = 0; // mark as done
while(marki != map2.rend() && marki->ch == MARKER_BEFORE_EOT) {
if (!marki->end) {
prepend_marker(str, marki->marker, encoding);
processed_markers++;
}
marki->processed = true; // mark as done
marki++;
}
@ -81,26 +107,25 @@ static void add_back_markers(std::u32string &str, const std::u32string &src, mar
const auto ch = *p;
str.insert(0, 1, ch); // prepend
// add the markers at the end of the list first.
for (; marki != map2.rend() && marki->first == ch; marki++) {
if (marki->second != 0) {
// set to '0' if already applied
prepend_marker(str, marki->second, encoding);
marki->second = 0; // mark as already applied
processed_count--;
}
// remove all processed entries, outside of an iterator
while (!map2.empty() && map2.back().processed) {
map2.pop_back();
}
// now, add any out of order markers.
for (auto marki2 = marki; marki2 != map2.rend(); marki2++) {
if (marki2->second != 0 && marki2->first == ch) {
prepend_marker(str, marki2->second, encoding);
marki2->second = 0; // mark as already applied
processed_count--;
for (auto i = map2.rbegin(); i != map2.rend(); i++) {
if ((i->ch == ch) && !(i->processed)) {
i->processed = true;
if (i->end) {
break;
} else {
prepend_marker(str, i->marker, encoding);
processed_markers++;
}
}
}
}
assert(processed_count == 0); // assert that we consumed all marks
assert(max_markers == processed_markers); // assert that we consumed all marks
}
bool normalize_nfd_markers_segment(std::u32string &str, marker_map &map, marker_encoding encoding) {
@ -110,9 +135,6 @@ bool normalize_nfd_markers_segment(std::u32string &str, marker_map &map, marker_
std::u32string str_unmarked_nfd = str_unmarked;
if(!normalize_nfd(str_unmarked_nfd)) {
return false; // normalize failed.
} else if (map.size() == 0) {
// no markers. Return the normalized unmarked str
str = str_unmarked_nfd;
} else if (str_unmarked_nfd == str_unmarked) {
// Normalization produced no change when markers were removed.
// So, we'll call this a no-op.
@ -198,84 +220,8 @@ bool normalize_nfd_markers(std::u32string &str, marker_encoding encoding) {
// quick check - don't bother if the string is empty
if(str.empty()) return true;
// we're going to need an NFD normalizer
UErrorCode status = U_ZERO_ERROR;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
if (!UASSERT_SUCCESS(status)) {
return false;
}
/**
* output string, we'll accumulate the normalized string here
*/
std::u32string out;
/**
* this is the beginning of the current segment to process.
* it will also be the beginning of the string OR the end of the previous segment.
*/
std::u32string::const_iterator seg_start = str.begin();
/** end of the current segment. This will be == seg_start unless a new segment is identified. */
std::u32string::const_iterator seg_end = str.begin();
/** iterator through this loop */
std::u32string::const_iterator i = str.begin();
// now we'll loop through looking for normalization-safe subsegments of [seg_start, seg_end)
// For example (sentinel mode) the following would be segments:
// - A
// - E\u0320\u0302
// - U\u0320\m{marker}\u0300
//
// The marker will typically 'look' like an NFD-safe boundary, but it's not! It's part of the
// subsegment, we want to skip over it. This is why we use parse_next_marker to look-ahead to see
// if there is actually a marker under the iterator.
//
// We don't assume that the marker sentinel or regex appears as an NFD boundary, that is why
// the parse function and the nfd function are called in parallel.
do {
// First, check if there's a marker.
// temporary iterator so we don't move 'i' unnecessarily. points to end of marker sequence.
std::u32string::const_iterator marker_end = i;
// true if marker detected
bool have_marker = parse_next_marker(marker_end, str.end(), encoding) != LDML_MARKER_NO_INDEX;
// Now, categorize the string. Is there a segment boundary BEFORE 'i'?
if (i == str.end()) {
// end of string, mark as the end of a segment
// this will cause the final segment to be processed and the loop exitted.
seg_end = i;
} else if (nfd->hasBoundaryBefore(*i) && !have_marker) {
// 'i' is the beginning of an NFD safe boundary (such as a base character).
// but it's also not on a marker (which would be included in the segment)
seg_end = i;
// we also need to advance so we can pick up the next char.
i++;
} else if (have_marker) {
// it's actually a marker. so, advance over it.
i = marker_end;
} else {
// some other non boundary char.
// advance without further drama
i++;
}
// Finally, process any identified segment.
if (seg_end != seg_start) { // is the segment non-empty?
std::u32string segment(seg_start, seg_end);
marker_map m;
if (!normalize_nfd_markers_segment(segment, m, encoding)) {
return false;
}
out.append(segment);
seg_start = seg_end;
}
} while(seg_end != str.end()); // repeat until the last codepoint has been processed in a segment
// update output string
str = out;
return true;
marker_map m;
return normalize_nfd_markers_segment(str, m, encoding);
}
// TODO-LDML: cleanup
@ -380,14 +326,6 @@ KMX_DWORD parse_hex_quad(const km_core_usv hex_str[]) {
return mark_no;
}
/** add the list to the map */
inline void add_markers_to_map(marker_map &markers, char32_t marker_ch, const marker_list &list) {
for (auto i = list.begin(); i < list.end(); i++) {
// marker_ch is duplicate, but keeps the structure more shallow.
markers.emplace_back(marker_ch, *i);
}
}
/**
* Add any markers, if needed. Inlined because we need to run it twice.
* @param markers marker map or nullptr
@ -402,16 +340,16 @@ add_pending_markers(
const std::u32string::const_iterator &end,
const icu::Normalizer2 *nfd) {
// quick check to see if there's no work to do.
if(markers == nullptr || last_markers.empty()) {
if(markers == nullptr) {
return;
}
/** which character this marker is 'glued' to. */
char32_t marker_ch;
icu::UnicodeString decomposition;
if (last == end) {
// at end of text, so use a special value to indicate 'EOT'.
marker_ch = MARKER_BEFORE_EOT;
} else {
icu::UnicodeString decomposition;
auto ch = *last; // non-normalized character
// if the character is composed, we need to use the first decomposed char
@ -419,14 +357,28 @@ add_pending_markers(
if(!nfd->getDecomposition(ch, decomposition)) {
// char does not have a decomposition - so it may be used for the glue
marker_ch = ch;
decomposition.remove(); // no other entries needed
} else {
// 'glue' is the first codepoint of the decomposition.
marker_ch = decomposition.char32At(0);
if (decomposition.countChar32() == 1) {
decomposition.remove(); // no other entries needed
} // else: will add the remainder below
}
}
markers->emplace_back(marker_ch);
// now, update the map with these markers (in order) on this character.
add_markers_to_map(*markers, marker_ch, last_markers);
for (auto i = last_markers.begin(); i < last_markers.end(); i++) {
// marker_ch is duplicate, but keeps the structure more shallow.
markers->emplace_back(marker_ch, *i);
}
// add any further entries due to decomposition
if (!decomposition.isEmpty()) {
// We already added the base char above, add teh rest
for (auto i=1; i<decomposition.countChar32(); i++) {
markers->emplace_back(decomposition.char32At(i));
}
}
// clear the list
last_markers.clear();
}

View file

@ -44,12 +44,33 @@ typedef KMX_DWORD marker_num;
/** list of marker numbers */
typedef std::deque<marker_num> marker_list;
/** map from one char to one entry */
typedef std::pair<char32_t, marker_num> marker_entry;
struct marker_entry {
/** code point glued to or MARKER_BEFORE_EOT */
char32_t ch;
/** marker number */
marker_num marker;
/** true if processed */
bool processed;
/** true if an end of this codepoint */
bool end;
/** add an 'end' entry */
marker_entry(char32_t ch);
/** add a 'marker' entry */
marker_entry(char32_t ch, marker_num marker);
bool operator==(const marker_entry &o) const {
// don't test 'processed'
return (ch == o.ch) && (marker == o.marker) && (end == o.end);
}
};
/** map from following-char to marker numbers, in front to back order */
typedef std::deque<marker_entry> marker_map;
/** count number of non-end entries */
size_t count_markers(const marker_map &map);
/** Normalize a u32string inplace to NFD. @return false on failure */
bool normalize_nfd(std::u32string &str);
/** Normalize a u16string inplace to NFD. @return false on failure */
@ -93,6 +114,8 @@ void prepend_hex_quad(std::u32string &str, KMX_DWORD marker);
/** parse 0001...FFFF into a KMX_DWORD. Returns 0 on failure */
KMX_DWORD parse_hex_quad(const km_core_usv hex_str[]);
/** re-add markers */
void add_back_markers(std::u32string &str, const std::u32string &src, marker_map &map, marker_encoding encoding);
// bool normalize_nfc_markers(std::u16string &str, marker_encoding encoding) {
// marker_map m;

View file

@ -40,7 +40,7 @@ namespace core {
ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t> &data)
: abstract_processor(
keyboard_attributes(kb_path.stem(), KM_CORE_LMDL_PROCESSOR_VERSION, kb_path.parent(), {})
), _valid(false), transforms(), bksp_transforms(), keys()
), _valid(false), transforms(), bksp_transforms(), keys(), normalization_disabled(false)
{
if(data.size() <= sizeof(kmx::COMP_KEYBOARD_EX)) {
@ -106,6 +106,11 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t>
return; // failed to load
}
}
if (kplus.meta != nullptr) {
if (kplus.meta->settings & LDML_META_SETTINGS_NORMALIZATION_DISABLED) {
normalization_disabled = true;
}
}
// Only valid if we reach here
DebugLog("_valid = true");
_valid = true;

View file

@ -83,7 +83,7 @@ class ldml_processor : public abstract_processor {
bool
supports_normalization() const override {
return true;
return !normalization_disabled;
}
private:
@ -111,6 +111,7 @@ class ldml_processor : public abstract_processor {
bool _valid;
std::unique_ptr<ldml::transforms> transforms, bksp_transforms;
ldml::vkeys keys;
bool normalization_disabled;
};

View file

@ -331,15 +331,19 @@ reorder_group::apply(std::u32string &str) const {
/** did we match anything */
bool some_match = false;
// markers need to 'pass through' reorders. remove and re-add if needed
marker_map markers;
std::u32string out = remove_markers(str, markers, plain_sentinel);
// get a baseline sort key
auto sort_keys = reorder_sort_key::from(str);
auto sort_keys = reorder_sort_key::from(out);
// apply ALL reorders in the group.
for (const auto &r : list) {
// work backward from end of string forward
// That is, see if "abc" matches "abc" or "ab" or "a"
for (size_t s = str.size(); s > 0; s--) {
size_t submatch = r.match_end(str, 0, s);
for (size_t s = out.size(); s > 0; s--) {
size_t submatch = r.match_end(out, 0, s);
if (submatch != 0) {
#if KMXPLUS_DEBUG_TRANSFORM
DebugTran("Matched: %S (off=%d, len=%d)", str.c_str(), 0, s);
@ -367,18 +371,6 @@ reorder_group::apply(std::u32string &str) const {
}
#endif
// TODO-LDML: for now, assume matches entire string.
// A needed optimization here would be to detect a common substring
// at the end of the old and new strings, and keep the match_len
// minimal. This could reduce thrash in core's context.
// However, the calling code does check for a common substring with mismatch()
size_t match_len = str.size();
// 'prefix' is the unmatched string before the match
// TODO-LDML: right now, this is empty, because match_len is the entire size.
std::u32string prefix = str;
prefix.resize(str.size() - match_len); // just the part before the matched part.
// Now, we need to actually do the sorting, but we must only sort
// 'runs' beginning with 0-weight keys.
@ -420,7 +412,7 @@ reorder_group::apply(std::u32string &str) const {
}
// recombine into a string by pulling out the 'ch' value
// that's in each sortkey element.
std::u32string newSuffix;
out.clear(); // will re-add all text
signed char q = sort_keys.begin()->quaternary; // start with the first quaternary
for (auto e = sort_keys.begin(); e < sort_keys.end(); e++, q++) {
if (q != e->quaternary) {
@ -428,13 +420,12 @@ reorder_group::apply(std::u32string &str) const {
applied = true;
}
// collect the characters
newSuffix.append(1, e->ch);
out.append(1, e->ch);
}
if (applied) {
str.resize(prefix.size());
str.append(newSuffix);
} else {
if (!applied) {
DebugTran("Skip: sorting caused no reordering");
// exit early to avoid string copying and possibly marker re-adding.
return false; // no change
}
#if KMXPLUS_DEBUG_TRANSFORM
DebugTran("Sorted sortkey");
@ -442,7 +433,8 @@ reorder_group::apply(std::u32string &str) const {
r.dump();
}
#endif
return applied;
add_back_markers(str, out, markers, plain_sentinel);
return true; // updated
}
transform_entry::transform_entry(const transform_entry &other)

View file

@ -21,4 +21,46 @@
<check result="a\u0332\u0305xz" />
</test>
</tests>
<tests name="onekey" >
<!-- same as above, but a single key-->
<test name="1short">
<startContext to="" />
<keystroke key="test0" />
<check result="a\u0305x" />
<keystroke key="o"/>
<backspace />
<!-- no change -->
<check result="a\u0305x" />
</test>
<test name="2longer">
<startContext to="" />
<keystroke key="test1" />
<check result="a\u0332\u0305xz" />
<keystroke key="o"/>
<backspace />
<!-- no change -->
<check result="a\u0332\u0305xz" />
</test>
</tests>
<tests name="marker" >
<!-- same as above, but a single key with markers -->
<test name="1short">
<startContext to="" />
<keystroke key="markertest0" />
<check result="a\u0305x" />
<keystroke key="o"/>
<backspace />
<!-- no change -->
<check result="a\u0305x" />
</test>
<test name="2longer">
<startContext to="" />
<keystroke key="markertest1" />
<check result="a\u0332\u0305xz" />
<keystroke key="o"/>
<backspace />
<!-- no change -->
<check result="a\u0332\u0305xz" />
</test>
</tests>
</keyboardTest3>

View file

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Test Keyboard from Spec
see https://keyman.com/keyboards/sil_boonkit
-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en-t-k0-esk" conformsTo="techpreview">
<info author="srl295" indicator="🙀" layout="qwerty" name="esk reorder test"/>
@ -19,6 +14,10 @@
<key id="overbar" output="${overbar}" />
<key id="underbar" output="${underbar}" />
<key id="circumflex" output="${circumflex}" />
<key id="test0" output="ax${overbar}" />
<key id="markertest0" output="a\m{m4}x\m{marker0}${overbar}" />
<key id="test1" output="az${overbar}x${underbar}" />
<key id="markertest1" output="a\m{m0}z\m{m1}${overbar}\m{m2}x\m{m3}${underbar}\m{m3}" />
</keys>
<layers formId="us">
@ -29,6 +28,12 @@
<row keys="z x" />
<row keys="space" />
</layer>
<layer modifiers="shift">
<row keys="gap test0 test1" />
</layer>
<layer modifiers="altR shift">
<row keys="gap markertest0 markertest1" />
</layer>
</layers>
<variables>

View file

@ -55,19 +55,40 @@ std::u32string
marker_map_to_string(const marker_map &m) {
std::u32string s;
for (auto i = m.rbegin(); i < m.rend(); i++) {
prepend_hex_oct(s, i->first);
s.insert(0, U"=U+");
if (i->end) {
s.insert(0, U"<END>");
}
if (i->processed) {
s.insert(0, U"<PROCESSED?>");
}
if (i->ch == MARKER_BEFORE_EOT) {
s.insert(0, U"<EOT>");
} else {
prepend_hex_oct(s, i->ch);
s.insert(0, U"=U+");
}
prepend_hex_quad(s, i->second);
s.insert(0, U" \\m0x");
if (i->marker != LDML_MARKER_NO_INDEX) {
prepend_hex_quad(s, i->marker);
s.insert(0, U" \\m0x");
}
}
return s;
}
bool
_assert_marker_map_equal(const char *f, int l, const marker_map a, const marker_map x) {
if (a == x)
_assert_marker_map_equal(const char *f, int l, const marker_map orig, const marker_map x) {
marker_map a;
// copy everything but 'end' entries
std::copy_if(
orig.begin(), orig.end(), std::back_inserter(a), [](const marker_entry &m) { return m.marker != LDML_MARKER_NO_INDEX; });
// now check equality
if (a == x) {
return true;
}
// Not equal. Print out why.
std::wcerr << f << ":" << l << ": " << console_color::fg(console_color::BRIGHT_RED);
std::wcerr << "got: " << marker_map_to_string(a);
std::wcerr << " expected: " << marker_map_to_string(x);
@ -636,7 +657,7 @@ test_strutils() {
const std::u32string src = U"abc";
const std::u32string dst = remove_markers(src, map);
zassert_string_equal(dst, src); // unchanged
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -647,7 +668,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm = {{U'e', 0x1L}};
assert_marker_map_equal(map, expm); // marker 1 @ e
assert_equal(map.size(), 1);
assert_equal(count_markers(map), 1);
}
{
marker_map map;
@ -656,7 +677,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -665,7 +686,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src; // 'q' removed
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -674,7 +695,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -683,7 +704,7 @@ test_strutils() {
const std::u32string dst = remove_markers(src, map);
const std::u32string expect = src;
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -694,7 +715,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm({{MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 1);
assert_equal(count_markers(map), 1);
}
{
marker_map map;
@ -707,7 +728,7 @@ test_strutils() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x0320, 0x2L}, {0x0300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 4);
assert_equal(count_markers(map), 4);
}
{
std::cout << __FILE__ << ":" << __LINE__ << " - prepend hex quad" << std::endl;
@ -752,7 +773,7 @@ test_normalize() {
std::u32string dst = src;
assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
marker_map map;
@ -762,7 +783,7 @@ test_normalize() {
std::u32string dst = src;
assert(normalize_nfd_markers_segment(dst, map));
zassert_string_equal(dst, expect);
assert_equal(map.size(), 0);
assert_equal(count_markers(map), 0);
}
{
@ -777,7 +798,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x320, 0x2L}, {0x300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 4);
assert_equal(count_markers(map), 4);
}
{
@ -792,7 +813,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x320, 0x2L}, {0x300, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 4);
assert_equal(count_markers(map), 4);
}
{
marker_map map;
@ -812,7 +833,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{U'e', 0x1L}, {0x300, 0x2L}, {0x320, 0x3L}, {MARKER_BEFORE_EOT, 0x4L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 4);
assert_equal(count_markers(map), 4);
}
{
@ -830,7 +851,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x1L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 1);
assert_equal(count_markers(map), 1);
}
{
@ -848,7 +869,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 2);
assert_equal(count_markers(map), 2);
}
{
@ -868,7 +889,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 2);
assert_equal(count_markers(map), 2);
}
{
// from tests - regex edition
@ -885,7 +906,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, LDML_MARKER_ANY_INDEX}, {MARKER_BEFORE_EOT, 0x1L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 2);
assert_equal(count_markers(map), 2);
}
{
@ -903,7 +924,7 @@ test_normalize() {
zassert_string_equal(dst, expect);
marker_map expm({{0x320, 0x2L}, {0x320, 0x2L}});
assert_marker_map_equal(map, expm);
assert_equal(map.size(), 2);
assert_equal(count_markers(map), 2);
}
{
@ -1030,6 +1051,87 @@ test_normalize() {
assert_marker_map_equal(map, expm);
}
// doubled markers tests
{
marker_map map;
std::cout << __FILE__ << ":" << __LINE__ << " - doubled marker1 " << std::endl;
const std::u32string src = U"e\uffff\u0008\u0001\u0300\u0320\u0300";
const std::u32string expect_rem = U"e\u0300\u0320\u0300";
const std::u32string expect_nfd = U"e\u0320\uffff\u0008\u0001\u0300\u0300";
auto dst_rem = remove_markers(src, &map);
marker_map expm({{0x0300, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
}
zassert_string_equal(dst_nfd, expect_nfd);
assert_marker_map_equal(map, expm);
}
{
marker_map map;
std::cout << __FILE__ << ":" << __LINE__ << " - doubled unchanged marker " << std::endl;
const std::u32string src = U"e\u0320\uffff\u0008\u0001\u0300\u0300";
const std::u32string expect_rem = U"e\u0320\u0300\u0300";
const std::u32string expect_nfd = src;
auto dst_rem = remove_markers(src, &map);
marker_map expm({{0x300, 0x1L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
}
zassert_string_equal(dst_nfd, expect_nfd);
assert_marker_map_equal(map, expm);
}
{
marker_map map;
std::cout << __FILE__ << ":" << __LINE__ << " - marker-before-double-greek " << std::endl;
const std::u32string src = U"\u03B5\uFFFF\u0008\u0001\u0344\uFFFF\u0008\u0002\u0344\uFFFF\u0008\u0003";
const std::u32string expect_rem = U"\u03B5\u0344\u0344";
const std::u32string expect_nfd = U"\u03B5\uFFFF\u0008\u0001\u0308\u0301\uFFFF\u0008\u0002\u0308\u0301\uFFFF\u0008\u0003";
auto dst_rem = remove_markers(src, &map);
marker_map expm({{0x0308, 0x1L},{0x0308, 0x2L},{MARKER_BEFORE_EOT, 0x3L}});
zassert_string_equal(dst_rem, expect_rem);
std::u32string dst_nfd = src;
assert(normalize_nfd_markers(dst_nfd));
if (dst_nfd != expect_nfd) {
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl;
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl;
}
zassert_string_equal(dst_nfd, expect_nfd);
assert_marker_map_equal(map, expm);
}
// macro for moving tests from test-pattern-parser.ts
#define TEST_NFD_PLAIN(x, y) \
{ \
marker_map map; \
std::cout << __FILE__ << ":" << __LINE__ << ": nfd test " << std::endl; \
const std::u32string src = x; \
const std::u32string expect_nfd = y; \
std::u32string dst_nfd = src; \
assert(normalize_nfd_markers(dst_nfd)); \
if (dst_nfd != expect_nfd) { \
std::cout << "dst: " << Debug_UnicodeString(dst_nfd) << std::endl; \
std::cout << "exp: " << Debug_UnicodeString(expect_nfd) << std::endl; \
} \
zassert_string_equal(dst_nfd, expect_nfd); \
}
// double marker - in front of second, no change
TEST_NFD_PLAIN(U"e\u0320\u0300\uffff\u0008\u0001\u0300", U"e\u0320\u0300\uffff\u0008\u0001\u0300")
// double marker - in front of second, with segment reordering
TEST_NFD_PLAIN(U"e\u0300\u0320\uffff\u0008\u0001\u0300", U"e\u0320\u0300\uffff\u0008\u0001\u0300")
// double marker - alternate pattern with reordering needed
TEST_NFD_PLAIN(U"e\u0300\uffff\u0008\u0001\u0300\u0320", U"e\u0320\u0300\uffff\u0008\u0001\u0300")
// triple diacritic + marker - reordering needed
TEST_NFD_PLAIN(U"e\u0300\uffff\u0008\u0001\u0300\u0320\u0300", U"e\u0320\u0300\uffff\u0008\u0001\u0300\u0300")
return EXIT_SUCCESS;
}

View file

@ -12,7 +12,7 @@ let isInit = false;
export class KeymanSentry {
static async isEnabled() {
static isEnabled() {
if(process.argv.includes('--no-error-reporting')) {
return false;
}

View file

@ -202,6 +202,9 @@
<Component>
<File Id="LdmlImportReadme" Name="README.md" KeyPath="yes" />
</Component>
<Component>
<File Id="LdmlImportLicense" Name="unicode-license.txt" Source="..\..\..\resources\standards-data\ldml-keyboards\unicode-license.txt" />
</Component>
<Component>
<File Name="keys-Latn-implied.xml" KeyPath="yes" />
</Component>

View file

@ -74,7 +74,11 @@ fi
#-------------------------------------------------------------------------------------------------------------------
if builder_start_action test; then
npm test
eslint .
cd test
tsc -b
cd ..
c8 --reporter=lcov --reporter=text mocha "${builder_extra_params[@]}"
builder_finish_action success test
fi

View file

@ -17,7 +17,7 @@
"scripts": {
"build": "tsc -b",
"lint": "eslint .",
"test": "npm run lint && cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha"
"test": "gosh ./build.sh test"
},
"author": "Marc Durdin <marc@keyman.com> (https://github.com/mcdurdin)",
"license": "MIT",
@ -33,6 +33,7 @@
},
"devDependencies": {
"@keymanapp/developer-test-helpers": "*",
"@keymanapp/resources-gosh": "*",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",

View file

@ -18,7 +18,7 @@ import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
import { KmnCompiler } from '@keymanapp/kmc-kmn';
import { KMXPlusMetadataCompiler } from './metadata-compiler.js';
import { LdmlKeyboardVisualKeyboardCompiler } from './visual-keyboard-compiler.js';
import { LdmlKeyboardKeymanWebCompiler } from './keymanweb-compiler.js';
//KMW17.0: import { LdmlKeyboardKeymanWebCompiler } from './keymanweb-compiler.js';
export const SECTION_COMPILERS = [
// These are in dependency order.
@ -97,10 +97,11 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
// const tlcompiler = new kmc.TouchLayoutCompiler();
// const tl = tlcompiler.compile(source);
// const tlwriter = new TouchLayoutFileWriter();
const kmwcompiler = new LdmlKeyboardKeymanWebCompiler(this.callbacks, compilerOptions);
const kmw_string = kmwcompiler.compile(inputFilename, source);
const encoder = new TextEncoder();
const kmw_binary = encoder.encode(kmw_string);
//KMW17.0: const kmwcompiler = new LdmlKeyboardKeymanWebCompiler(this.callbacks, compilerOptions);
//KMW17.0: const kmw_string = kmwcompiler.compile(inputFilename, source);
//KMW17.0: const encoder = new TextEncoder();
//KMW17.0: const kmw_binary = encoder.encode(kmw_string);
outputFilename = outputFilename ?? inputFilename.replace(/\.xml$/, '.kmx');
@ -108,7 +109,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
artifacts: {
kmx: { data: kmx_binary, filename: outputFilename },
kvk: { data: kvk_binary, filename: outputFilename.replace(/\.kmx$/, '.kvk') },
js: { data: kmw_binary, filename: outputFilename.replace(/\.kmx$/, '.js') },
//KMW17.0: js: { data: kmw_binary, filename: outputFilename.replace(/\.kmx$/, '.js') },
}
};
}

View file

@ -66,7 +66,8 @@ fi
#-------------------------------------------------------------------------------------------------------------------
if builder_start_action build; then
npm run build
tsc -b
cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/unicode-license.txt" ./build/unicode-license.txt
builder_finish_action success build
fi

View file

@ -51,7 +51,8 @@
"supports-color": "^9.4.0"
},
"files": [
"build/src/"
"build/src/",
"build/unicode-license.txt"
],
"devDependencies": {
"@sentry/cli": "^2.19.4",

View file

@ -7,6 +7,7 @@ import { CompilerCallbacks, CompilerLogLevel } from '@keymanapp/common-types';
import { AnalyzeOskCharacterUse, AnalyzeOskRewritePua } from '@keymanapp/kmc-analyze';
import { BaseOptions } from '../util/baseOptions.js';
import { runOnFiles } from '../util/projectRunner.js';
import { exitProcess } from '../util/sysexits.js';
interface AnalysisActivityOptions /* not inheriting from CompilerBaseOptions */ {
/**
@ -45,7 +46,7 @@ function declareOskCharUse(command: Command) {
if(!await analyze(analyzeOskCharUse, filenames, options)) {
// Once a file fails to build, we bail on subsequent builds
process.exit(1);
await exitProcess(1);
}
});
@ -65,7 +66,7 @@ function declareOskRewrite(command: Command) {
if(!await analyze(analyzeOskRewritePua, filenames, options)) {
// Once a file fails to build, we bail on subsequent builds
process.exit(1);
await exitProcess(1);
}
});
}

View file

@ -12,6 +12,7 @@ import { isProject } from '../util/projectLoader.js';
import { buildTestData } from './buildTestData/index.js';
import { buildWindowsPackageInstaller } from './buildWindowsPackageInstaller/index.js';
import { commandOptionsToCompilerOptions } from '../util/extendedCompilerOptions.js';
import { exitProcess } from '../util/sysexits.js';
export function declareBuild(program: Command) {
// TODO: localization?
@ -79,7 +80,7 @@ async function buildFile(filenames: string[], _options: any, commander: any) {
const commanderOptions/*:{TODO?} CommandLineCompilerOptions*/ = commander.optsWithGlobals();
const options = initialize(commanderOptions);
if(!options) {
process.exit(1);
await exitProcess(1);
}
const callbacks = new NodeCompilerCallbacks(options);
@ -93,17 +94,17 @@ async function buildFile(filenames: string[], _options: any, commander: any) {
if(filenames.length > 1 && commanderOptions.outFile) {
// -o can only be specified with a single input file
callbacks.reportMessage(InfrastructureMessages.Error_OutFileCanOnlyBeSpecifiedWithSingleInfile());
process.exit(1);
await exitProcess(1);
}
if(!expandFileLists(filenames, callbacks)) {
process.exit(1);
await exitProcess(1);
}
for(let filename of filenames) {
if(!await build(filename, commanderOptions.outFile, callbacks, options)) {
// Once a file fails to build, we bail on subsequent builds
process.exit(1);
await exitProcess(1);
}
}
}

View file

@ -4,6 +4,7 @@ import { CompilerCallbacks, defaultCompilerOptions } from '@keymanapp/common-typ
import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js';
import { WindowsPackageInstallerCompiler, WindowsPackageInstallerSources } from '@keymanapp/kmc-package';
import { CommandLineBaseOptions } from 'src/util/baseOptions.js';
import { exitProcess } from '../../util/sysexits.js';
interface WindowsPackageInstallerCommandLineOptions extends CommandLineBaseOptions {
msi: string;
@ -38,7 +39,7 @@ export async function buildWindowsPackageInstaller(infile: string, _options: any
const callbacks: CompilerCallbacks = new NodeCompilerCallbacks({...defaultCompilerOptions, ...options});
const compiler = new WindowsPackageInstallerCompiler();
if(!await compiler.init(callbacks, {...options, sources})) {
process.exit(1);
await exitProcess(1);
}
const fileBaseName = outfile ?? infile;
@ -49,10 +50,10 @@ export async function buildWindowsPackageInstaller(infile: string, _options: any
const result = await compiler.run(infile, outFileExe);
if(!result) {
// errors will have been reported already
process.exit(1);
await exitProcess(1);
}
if(!await compiler.write(result.artifacts)) {
process.exit(1);
await exitProcess(1);
}
}

View file

@ -9,9 +9,12 @@ import { declareAnalyze } from './commands/analyze.js';
import { KeymanSentry, loadOptions } from '@keymanapp/developer-utils';
import KEYMAN_VERSION from "@keymanapp/keyman-version";
import { TestKeymanSentry } from './util/TestKeymanSentry.js';
import { exitProcess } from './util/sysexits.js';
await TestKeymanSentry.runTestIfCLRequested();
KeymanSentry.init();
if(KeymanSentry.isEnabled()) {
KeymanSentry.init();
}
try {
await run();
} catch(e) {
@ -20,7 +23,7 @@ try {
// Ensure any messages reported to Sentry have had time to be uploaded before we
// exit. In most cases, this will be a no-op so should not affect performance.
await KeymanSentry.close();
await exitProcess(0);
async function run() {
await loadOptions();
@ -42,10 +45,6 @@ async function run() {
.addOption(new Option('--no-error-reporting', 'Disable error reporting to keyman.com (overriding user settings)'))
.addOption(new Option('--error-reporting', 'Enable error reporting to keyman.com (overriding user settings)'));
if(await KeymanSentry.isEnabled()) {
KeymanSentry.init();
}
declareBuild(program);
declareAnalyze(program);

View file

@ -9,9 +9,9 @@
*/
export function calculateSourcePath(projectFilename: string): string {
projectFilename = projectFilename.replace(/\\/g, '/');
const result = /([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)\.kpj$/.exec(projectFilename);
const result = /(release|legacy|experimental)\/([^\/]+)\/([^\/]+)\/([^\/]+)\.kpj$/.exec(projectFilename);
if (!result) {
throw new Error(`Invalid path ${projectFilename}`);
return undefined;
}
return `${result[1]}/${result[2]}/${result[3]}`;
}

View file

@ -1,3 +1,5 @@
import { KeymanSentry } from "@keymanapp/developer-utils";
/**
* Exit codes defined in <sysexits.h>:
* https://www.freebsd.org/cgi/man.cgi?query=sysexits&apropos=0&sektion=0&manpath=FreeBSD+4.3-RELEASE&format=html
@ -6,3 +8,8 @@ export const enum SysExits {
EX_USAGE = 64,
EX_DATAERR = 65,
};
export async function exitProcess(exitCode?: number): Promise<never> {
await KeymanSentry.close();
process.exit(exitCode);
}

View file

@ -73,14 +73,14 @@
},
{
"name": "Python: Current File",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "km-config",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "linux/keyman-config/km-config",
"cwd": "${workspaceFolder}",
@ -91,7 +91,7 @@
},
{
"name": "km-package-install",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "linux/keyman-config/km-package-install",
"cwd": "${workspaceFolder}",

View file

@ -27,6 +27,12 @@ Docker tends to throttle Docker image downloads, so some developer offices may w
]
```
### Using Website-Local-Proxy
Rather than remembering localhost port values below, you can clone and run [website-local-proxy](https://github.com/keymanapp/website-local-proxy).
Follow https://github.com/keymanapp/website-local-proxy?tab=readme-ov-file#using-website-local-proxy and you can access the local websites at
http://*keyman.com.localhost
## Builder BASH Script Actions
#### Stop the Docker container
@ -40,12 +46,12 @@ This stops the Docker container for the site.
This downloads and builds the Docker images needed for the site.
#### Configure
1. Run `./build.sh configure`.
1. Run `./build.sh configure`
This step typically downloads _common/ website files from [shared-sites](https://github.com/keymanapp/shared-sites/tree/main/_common).
#### Start the Docker container
1. Run `./build.sh start`.
1. Run `./build.sh start`
This maps the local directory to the the Docker image.
@ -54,10 +60,14 @@ The link file also appears locally.
After this, you can access the website at the following ports:
| Website | URL |
|--------------|-----------------------|
|help.keyman | http://localhost:8055 |
|keymanweb.com | http://localhost:8057 |
| Website | URL | with website-local-proxy running |
|----------------|-----------------------|-----------------------------------|
| keyman.com | http://localhost:8053 | http://keyman.com.localhost |
| s.keyman.com | http://localhost:8054 | http://s.keyman.com.localhost |
| help.keyman | http://localhost:8055 | http://help.keyman.com.localhost |
| keymanweb.com | http://localhost:8057 | http://keymanweb.com.localhost |
| | | http://web.keyman.com.localhost |
| api.keyman.com | http://localhost:8058 | http://api.keyman.com.localhost |
#### Remove the Docker container and image
1. Run `./build.sh clean`.
@ -66,13 +76,6 @@ After this, you can access the website at the following ports:
Checks for broken links
1. Run `./build.sh test`
#### Using Website-Local-Proxy
Rather than remembering localhost port values, you can clone and run [website-local-proxy](https://github.com/keymanapp/website-local-proxy).
Follow https://github.com/keymanapp/website-local-proxy?tab=readme-ov-file#using-website-local-proxy and you can access the local websites at
http://*keyman.com.localhost
---------
## Kubernetes Deployment

2
linux/.gitignore vendored
View file

@ -6,7 +6,7 @@ ltmain.sh
__pycache__
*.egg-info
build-*
deb-*
./deb-*
builddebs
keyman-config/make_deb
tmp/

View file

@ -14,7 +14,7 @@ DEBIAN_SUITES=($UNSTABLE_CODENAME $TESTING_CODENAME $STABLE_CODENAME $STABLE_BAC
"experimental" "unstable" "testing" "stable")
# List of Ubuntu suites. Update these when needed.
UBUNTU_SUITES=("mantic" "lunar" "jammy" "focal")
UBUNTU_SUITES=("noble" "mantic" "jammy" "focal")
# Mirrors to use. Update these to your preferred mirror.
DEBIAN_MIRROR="deb.debian.org"

View file

@ -1,9 +1,11 @@
keyman (16.0.144-2) UNRELEASED; urgency=medium
keyman (16.0.145-1) unstable; urgency=medium
[Jeremy Bicha]
* Build with webkitgtk 4.1 instead of 4.0 (closes: #1061396)
* New upstream release.
* Re-release to Debian
-- Eberhard Beilharz <eb1@sil.org> Wed, 24 Jan 2024 17:38:11 +0100
-- Eberhard Beilharz <eb1@sil.org> Thu, 01 Feb 2024 12:58:00 +0100
keyman (16.0.144-1) unstable; urgency=medium

View file

@ -237,7 +237,7 @@ get_context_debug(IBusEngine *engine) {
static gchar *
debug_utf8_with_codepoints(const gchar *utf8) {
GString *output = g_string_new("");
g_string_append_printf(output, "|%s| len:%zu) [", utf8, g_utf8_strlen(utf8, -1));
g_string_append_printf(output, "|%s| len:%ld) [", utf8, g_utf8_strlen(utf8, -1));
gunichar2 *utf16 = g_utf8_to_utf16(utf8, -1, NULL, NULL, NULL);
for (int i = 0; utf16[i] != '\0'; i++) {
g_string_append_printf(output, "U+%04x ", utf16[i]);

View file

@ -212,6 +212,44 @@ get_engine_for_language(
return engine_desc;
}
int _get_version(const gchar **pver) {
g_assert(pver);
const gchar *ver = *pver;
int version = 0;
while (*ver && *ver != '.') {
if (*ver >= '0' && *ver <= '9') {
version = version * 10 + (*ver - '0');
ver++;
} else {
// stop comparison on first non-digit
while (*ver)
ver++;
}
}
*pver = ver;
return version;
}
int
keyman_compare_version(const gchar *ver1, const gchar *ver2) {
for (; *ver1 || *ver2; ) {
int version1 = _get_version(&ver1);
int version2 = _get_version(&ver2);
if (version1 < version2)
return -1;
if (version1 > version2)
return +1;
if (*ver1)
ver1++;
if (*ver2)
ver2++;
}
return 0;
}
gboolean
keyman_list_contains_keyboard(
GList *engines_list,
@ -225,8 +263,7 @@ keyman_list_contains_keyboard(
// If we already have an engine for this keyboard (in a different area), we
// don't want to add it again since we wouldn't add anything new
// if it's the same version
// TODO: fix version comparison (#9593)
if (g_strcmp0(kmx_file, keyboard->kmx_file) == 0 && g_strcmp0(version, keyboard->version) >= 0) {
if (g_strcmp0(kmx_file, keyboard->kmx_file) == 0 && keyman_compare_version(version, keyboard->version) >= 0) {
g_debug("keyboard %s already exists at version %s which is newer or same as %s", kmx_file, version, keyboard->version);
return TRUE;
}

View file

@ -42,6 +42,7 @@ void keyman_set_custom_keyboards(gchar ** keyboards);
GHashTable * keyman_get_custom_keyboard_dictionary();
void keyman_add_keyboard(gpointer data, gpointer user_data);
void keyman_add_keyboards_from_dir(gpointer data, gpointer user_data);
int keyman_compare_version(const gchar *version1, const gchar *version2);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(IBusEngineDesc, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(IBusComponent, g_object_unref)

View file

@ -1033,6 +1033,51 @@ test_keyman_add_keyboards_from_dir__one_language_plus_two_different_custom() {
g_assert_cmpstr(ibus_engine_desc_get_name(desc), ==, expected_name);
}
void
test_keyman_compare_version_equal() {
g_assert_cmpint(keyman_compare_version("1.0", "1.0"), ==, 0);
}
void
test_keyman_compare_version_equal_with_patch() {
g_assert_cmpint(keyman_compare_version("1.0", "1.0.0"), ==, 0);
}
void
test_keyman_compare_version_equal_nondigit() {
g_assert_cmpint(keyman_compare_version("1.1", "1.1-beta2"), ==, 0);
}
void
test_keyman_compare_version_equal_doubledot() {
g_assert_cmpint(keyman_compare_version("1..2", "1.0.2"), ==, 0);
}
void
test_keyman_compare_version_less() {
g_assert_cmpint(keyman_compare_version("1.0", "1.1"), <, 0);
}
void
test_keyman_compare_version_minor_less_with_patch() {
g_assert_cmpint(keyman_compare_version("1.0.1", "1.1"), <, 0);
}
void
test_keyman_compare_version_patch_less() {
g_assert_cmpint(keyman_compare_version("1.0", "1.0.1"), <, 0);
}
void
test_keyman_compare_version_less_twodigit() {
g_assert_cmpint(keyman_compare_version("99.9", "100"), <, 0);
}
void
test_keyman_compare_version_greater() {
g_assert_cmpint(keyman_compare_version("1.10", "1.9"), >, 0);
}
//----------------------------------------------------------------------------------------------
void
print_usage() {
@ -1110,10 +1155,9 @@ int main(int argc, char* argv[]) {
g_test_add_func(
"/keymanutil/keyman_add_keyboard/prev_engine_adding_newer_version",
test_keyman_add_keyboard__prev_engine_adding_newer_version);
// #9593
// g_test_add_func(
// "/keymanutil/keyman_add_keyboard/prev_engine_adding_newer_version_versioncompare",
// test_keyman_add_keyboard__prev_engine_adding_newer_version_9593);
g_test_add_func(
"/keymanutil/keyman_add_keyboard/prev_engine_adding_newer_version_versioncompare",
test_keyman_add_keyboard__prev_engine_adding_newer_version_9593);
g_test_add_func(
"/keymanutil/keyman_add_keyboard/prev_engine_adding_older_version",
test_keyman_add_keyboard__prev_engine_adding_older_version);
@ -1126,6 +1170,16 @@ int main(int argc, char* argv[]) {
"/keymanutil/keyman_add_keyboards_from_dir/one_language_plus_two_different_custom",
test_keyman_add_keyboards_from_dir__one_language_plus_two_different_custom);
g_test_add_func("/keymanutil/keyman_compare_version/1.0==1.0", test_keyman_compare_version_equal);
g_test_add_func("/keymanutil/keyman_compare_version/1.0==1.0.0", test_keyman_compare_version_equal_with_patch);
g_test_add_func("/keymanutil/keyman_compare_version/1.1==1.1-beta", test_keyman_compare_version_equal_nondigit);
g_test_add_func("/keymanutil/keyman_compare_version/1..2==1.0.2", test_keyman_compare_version_equal_doubledot);
g_test_add_func("/keymanutil/keyman_compare_version/1.0<1.1", test_keyman_compare_version_less);
g_test_add_func("/keymanutil/keyman_compare_version/1.0.1<1.1", test_keyman_compare_version_minor_less_with_patch);
g_test_add_func("/keymanutil/keyman_compare_version/1.0<1.0.1", test_keyman_compare_version_patch_less);
g_test_add_func("/keymanutil/keyman_compare_version/99.9<100",test_keyman_compare_version_less_twodigit);
g_test_add_func("/keymanutil/keyman_compare_version/1.10>1.9", test_keyman_compare_version_greater);
// Run tests
int retVal = g_test_run();

View file

@ -1,4 +1,4 @@
# Editor configuration, see https://editorconfig.org
[*]
indent_style = space
indent_size = 4
indent_size = 2

View file

@ -3,7 +3,7 @@
# If needed set cowbuilder up for building Keyman Debian packages
# Then cowbuilder update
distributions='focal jammy lunar mantic'
distributions='focal jammy mantic noble'
if ! dpkg-query -l cowbuilder; then
echo "installing pbuilder and cowbuilder"

View file

@ -103,6 +103,13 @@ is_symbols_file_changed() {
return 0
}
get_changes() {
local WHAT_CHANGED
WHAT_CHANGED=$(git diff -I "^${PKG_NAME}.so" "$1".."$2" -- "debian/${PKG_NAME}.symbols" | diffstat -m -t | tail -n +2)
IFS=',' read -r -a CHANGES <<< "${WHAT_CHANGED:-0,0,0}"
}
check_updated_version_number() {
# Checks that the package version number got updated in the .symbols file if it got changed
# shellcheck disable=SC2310
@ -114,7 +121,16 @@ check_updated_version_number() {
# is out of date when it is merged to the release branch (master/beta/stable-x.y). If this
# is considered important, then make sure the branch is up to date, and wait for test
# builds to complete, before merging.
if ! git log -p -1 -- "debian/${PKG_NAME}.symbols" | grep -q "${VERSION}"; then
get_changes "${GIT_BASE}" "${GIT_SHA}"
INSERTED="${CHANGES[0]}"
DELETED="${CHANGES[1]}"
MODIFIED="${CHANGES[2]}"
if (( DELETED > 0 )) && (( MODIFIED == 0 )) && (( INSERTED == 0)); then
# If only lines got removed we basically skip this test. A later check will
# test that the API version got updated.
output_ok "${PKG_NAME}.symbols file did change but only removed lines"
elif ! git log -p -1 -- "debian/${PKG_NAME}.symbols" | grep -q "${VERSION}"; then
output_error "${PKG_NAME}.symbols file got changed without changing the package version number of the symbol"
EXIT_CODE=1
else
@ -135,20 +151,40 @@ get_api_version_in_symbols_file() {
}
is_api_version_updated() {
local NEW_VERSION OLD_VERSION
local NEW_VERSION OLD_VERSION TIER
git checkout "${GIT_BASE}" -- "debian/${PKG_NAME}.symbols"
OLD_VERSION=$(get_api_version_in_symbols_file)
git checkout "${GIT_SHA}" -- "debian/${PKG_NAME}.symbols"
NEW_VERSION=$(get_api_version_in_symbols_file)
if (( NEW_VERSION > OLD_VERSION )); then
return 0
echo "0"
return
fi
return 1
# API version didn't change. However, that might be ok if we're in alpha
# and a major change happened previously.
TIER=$(cat "${REPO_ROOT}/TIER.md")
case ${TIER} in
alpha)
local STABLE_VERSION
STABLE_VERSION=$((${VERSION%%.*} - 1))
git checkout "stable-${STABLE_VERSION}.0" -- "debian/${PKG_NAME}.symbols"
STABLE_API_VERSION=$(get_api_version_in_symbols_file)
git checkout "${GIT_SHA}" -- "debian/${PKG_NAME}.symbols"
if (( NEW_VERSION > STABLE_API_VERSION )); then
echo "2"
return
fi ;;
*)
;;
esac
echo "1"
}
check_for_major_api_changes() {
# Checks that API version number gets updated if API changes
local WHAT_CHANGED CHANGES INSERTED DELETED MODIFIED
local WHAT_CHANGED CHANGES INSERTED DELETED MODIFIED UPDATED
# shellcheck disable=2310
if ! is_symbols_file_changed; then
@ -156,19 +192,19 @@ check_for_major_api_changes() {
return
fi
WHAT_CHANGED=$(git diff "${GIT_BASE}".."${GIT_SHA}" -- "debian/${PKG_NAME}.symbols" | diffstat -m -t | tail -1)
IFS=',' read -r -a CHANGES <<< "${WHAT_CHANGED}"
get_changes "${GIT_BASE}" "${GIT_SHA}"
INSERTED="${CHANGES[0]}"
DELETED="${CHANGES[1]}"
MODIFIED="${CHANGES[2]}"
if (( DELETED > 0 )) || (( MODIFIED > 0 )); then
builder_echo "Major API change: ${DELETED} lines deleted and ${MODIFIED} lines modified"
# shellcheck disable=2310
if ! is_api_version_updated; then
UPDATED=$(is_api_version_updated)
if [[ ${UPDATED} == 1 ]]; then
output_error "Major API change without updating API version number in ${PKG_NAME}.symbols file"
EXIT_CODE=2
elif [[ ${UPDATED} == 2 ]]; then
output_ok "API version number got previously updated in ${PKG_NAME}.symbols file after major API change; no change within alpha necessary"
else
output_ok "API version number got updated in ${PKG_NAME}.symbols file after major API change"
fi

View file

@ -11,7 +11,7 @@
set -e
all_distributions="focal jammy lunar mantic"
all_distributions="focal jammy mantic noble"
distributions=""
echo "all_distributions: ${all_distributions}"

View file

@ -33,7 +33,7 @@ else
fi
echo "ppa: ${ppa}"
distributions="${DIST:-focal jammy lunar mantic}"
distributions="${DIST:-focal jammy mantic noble}"
packageversion="${PACKAGEVERSION:-1~sil1}"
BASEDIR=$(pwd)

View file

@ -0,0 +1,202 @@
#!/bin/bash
set -eu
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh"
## END STANDARD BUILD SCRIPT INCLUDE
mockDebPkgTools() {
echo "#!/bin/bash
" > ${tmpDir}/dpkg
chmod +x ${tmpDir}/dpkg
cp ${tmpDir}/dpkg ${tmpDir}/dpkg-gensymbols
PATH=${tmpDir}:${PATH}
}
createBase() {
TIER=$1
workDir=$(mktemp -d)
cd ${workDir}
git init .
mkdir -p linux/debian
echo "libkeymancore.so.1 libkeymancore #MINVER#
* Build-Depends-Package: libkeymancore-dev
km_core_actions_dispose@Base 17.0.197
km_core_context_clear@Base 17.0.195
km_core_context_get@Base 17.0.195
km_core_context_item_list_size@Base 17.0.195
km_core_context_items_dispose@Base 17.0.195
" > linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
mkdir -p core
echo "1.0.0" > core/CORE_API_VERSION.md
git add core/CORE_API_VERSION.md
echo "16.0.145" > VERSION.md
git add VERSION.md
echo "stable" > TIER.md
git add TIER.md
mkdir -p linux/scripts
cp -r ${REPO_ROOT}/linux/scripts/* linux/scripts
git add linux/scripts
mkdir -p resources/build
cp -r ${REPO_ROOT}/resources/build/* resources/build
cp ${REPO_ROOT}/resources/builder.inc.sh resources/
git add resources
git commit -m "Initial"
git branch -c stable-16.0
git tag -m "16.0.145" release-16.0.145
echo "${TIER}" > TIER.md
git add TIER.md
echo "17.0.255" > VERSION.md
git add VERSION.md
git commit -m "Commit for Alpha"
git checkout -b chore
BINPKG_NAME=${tmpDir}/libkeymancore_17.0.257-1+noble1_amd64.deb
touch "${BINPKG_NAME}"
}
setup() {
OLDPATH=${PATH}
tmpDir=$(mktemp -d)
mockDebPkgTools
}
teardown() {
PATH=${OLDPATH}
rm -rf ${tmpDir}
}
run_test() {
setup
$1 > /tmp/$1.log 2>&1 && echo -e "${COLOR_GREEN}$1: OK${COLOR_RESET}" || echo -e "${COLOR_RED}$1: FAILED${COLOR_RESET}"
teardown
}
test_check_updated_version_number_NoChange_OK() {
createBase alpha
echo "readme" > README.md
git add README.md
git commit -m "Some change on the branch"
pwd
linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify
}
test_check_updated_version_number_LineAdded_OK() {
createBase alpha
sed -i 's/ km_core_actions_dispose@Base 17.0.197/ km_core_actions_dispose@Base 17.0.197\n km_core_added@Base 17.0.255/' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method added"
pwd
linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify
}
test_check_updated_version_number_LineAddedWithoutVerUpd_ERROR() {
createBase alpha
sed -i 's/ km_core_actions_dispose@Base 17.0.197/ km_core_actions_dispose@Base 17.0.197\n km_core_added@Base 17.0.197/' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method added"
pwd
output=$(linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify || true)
echo "${output[*]}" # for logging
[[ "${output[*]}" == *"ERROR: libkeymancore.symbols file got changed without changing the package version number of the symbol"* ]]
}
test_check_updated_version_number_LineRemovedWithAPIUpd_OK() {
createBase alpha
echo "2.0.0" > core/CORE_API_VERSION.md
git add core/CORE_API_VERSION.md
sed -i 's/libkeymancore.so.1/libkeymancore.so.2/' linux/debian/libkeymancore.symbols
sed -i '6d' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method removed"
pwd
linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify
}
test_check_updated_version_number_LineRemoved_InAlpha_OK() {
createBase alpha
git checkout master
# simulate a commit that already introduced an API version change
echo "2.0.0" > core/CORE_API_VERSION.md
git add core/CORE_API_VERSION.md
sed -i 's/libkeymancore.so.1/libkeymancore.so.2/' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API version change"
git checkout chore
git rebase master
sed -i '6d' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method removed"
pwd
linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify
}
test_check_updated_version_number_LineRemoved_ChangeFromStable_ERROR() {
createBase alpha
sed -i '6d' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method removed"
pwd
output=$(linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base master verify || true)
echo "${output[*]}" # for logging
[[ "${output[*]}" == *" ERROR: Major API change without updating API version number in libkeymancore.symbols file"* ]]
}
test_check_updated_version_number_LineRemoved_ChangeInBeta_ERROR() {
createBase beta
# simulate a commit that already introduced an API version change in Beta
git checkout -b beta
echo "2.0.0" > core/CORE_API_VERSION.md
git add core/CORE_API_VERSION.md
sed -i 's/libkeymancore.so.1/libkeymancore.so.2/' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API version change"
git checkout chore
git rebase beta
sed -i '6d' linux/debian/libkeymancore.symbols
git add linux/debian/libkeymancore.symbols
git commit -m "API method removed"
pwd
output=$(linux/scripts/deb-packaging.sh --bin-pkg "${BINPKG_NAME}" --git-sha "$(git rev-parse HEAD)" --git-base beta verify || true)
echo "${output[*]}" # for logging
[[ "${output[*]}" == *" ERROR: Major API change without updating API version number in libkeymancore.symbols file"* ]]
}
echo "(test logs are in /tmp/<testname>.log)"
run_test test_check_updated_version_number_NoChange_OK
run_test test_check_updated_version_number_LineAdded_OK
run_test test_check_updated_version_number_LineAddedWithoutVerUpd_ERROR
run_test test_check_updated_version_number_LineRemovedWithAPIUpd_OK
run_test test_check_updated_version_number_LineRemoved_InAlpha_OK
run_test test_check_updated_version_number_LineRemoved_ChangeFromStable_ERROR
run_test test_check_updated_version_number_LineRemoved_ChangeInBeta_ERROR
# TODO: still some test cases missing for the different checks

164
oem/firstvoices/keyboards.csv Executable file → Normal file
View file

@ -1,99 +1,99 @@
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,ikt-Latn,Inuinnaqtun (Latin)
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,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)
fv,fv_uummarmiutun,Uummarmiutun,Arctic,fv_uummarmiutun_kmw-9.0.js,9.1,ikt-Latn,Inuinnaqtun (Latin)
fv,fv_eastern_canadian_inuktitut,ᐃᓄᒃᑎᑐᑦ (Eastern Canadian Inuktitut),Arctic,fv_eastern_canadian_inuktitut_kmw-9.0.js,9.2,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.1,mic-Latn,Mi'kmaq (Latin)
fv,fv_skicinuwatuwewakon,Skicinuwatuwewakon,Atlantic,fv_skicinuwatuwewakon_kmw-9.0.js,9.1,pqm-Latn,Malecite-Passamaquoddy (Latin)
fv,fv_uwikala,'Uwik̓ala,BC Coast,fv_uwikala_kmw-9.0.js,9.1.1,hei-Latn,Heiltsuk (Latin)
fv,fv_dexwlesucid,Dəxʷləšucid,BC Coast,fv_dexwlesucid_kmw-9.0.js,9.2,lut-Latn,Lushootseed (Latin)
fv,fv_diitiidatx,Diidiitidq,BC Coast,fv_diitiidatx_kmw-9.0.js,9.1.1,nuk-Latn,Nuu-chah-nulth (Latin)
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)
fv,fv_skicinuwatuwewakon,Skicinuwatuwewakon,Atlantic,fv_skicinuwatuwewakon_kmw-9.0.js,9.1.1,pqm-Latn,Malecite-Passamaquoddy (Latin)
fv,fv_uwikala,'Uwik̓ala,BC Coast,fv_uwikala_kmw-9.0.js,9.3,hei-Latn,Heiltsuk (Latin)
fv,fv_dexwlesucid,Dəxʷləšucid,BC Coast,fv_dexwlesucid_kmw-9.0.js,9.2.1,lut-Latn,Lushootseed (Latin)
fv,fv_diitiidatx,Diidiitidq,BC Coast,fv_diitiidatx_kmw-9.0.js,9.1.3,nuk-Latn,Nuu-chah-nulth (Latin)
fv,fv_gitsenimx,Gitsenimx̱,BC Coast,fv_gitsenimx_kmw-9.0.js,9.1.1,git-Latn,Gitxsan (Latin)
fv,fv_hailzaqvla,Haiɫzaqvla,BC Coast,fv_hailzaqvla_kmw-9.0.js,9.4,hei,Heiltsuk (Latin)
fv,fv_hailzaqvla,Haiɫzaqvla,BC Coast,fv_hailzaqvla_kmw-9.0.js,9.4.1,hei,Heiltsuk (Latin)
fv,fv_haisla,Haisla,BC Coast,fv_haisla.js,1.0,has-Latn,Haisla (Latin)
fv,fv_halqemeylem,Halq'eméylem,BC Coast,fv_halqemeylem_kmw-9.0.js,9.1.1,hur-Latn,Halkomelem (Latin)
fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,9.1.1,hur-Latn,Halkomelem (Latin)
fv,fv_halqemeylem,Halq'eméylem,BC Coast,fv_halqemeylem_kmw-9.0.js,9.1.3,hur-Latn,Halkomelem (Latin)
fv,fv_henqeminem,Hǝn̓q̓ǝmin̓ǝm,BC Coast,fv_henqeminem_kmw-9.0.js,9.1.3,hur-Latn,Halkomelem (Latin)
fv,fv_klahoose,Homalco-Klahoose-Sliammon,BC Coast,fv_klahoose_kmw-9.0.js,9.2,coo-Latn,Comox (Latin)
fv,fv_hulquminum,Hul’q’umi’num’,BC Coast,fv_hulquminum_kmw-9.0.js,9.1,hur-Latn,Halkomelem (Latin)
fv,fv_hulquminum_combine,Hul̓q̓umin̓um̓,BC Coast,fv_hulquminum_combine_kmw-9.0.js,1.0,hur-Latn,Halkomelem (Latin)
fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.2.2,kwk-Latn,Kwakiutl (Latin)
fv,fv_kwakwala,Kwak̕wala,BC Coast,fv_kwakwala_kmw-9.0.js,9.1.1,kwk-Latn,Kwakiutl (Latin)
fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,9.2,clm-Latn,Clallam (Latin)
fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.1.1,ncg-Latn,Nisga'a (Latin)
fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.1.1,nuk-Latn,Nuu-chah-nulth (Latin)
fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,9.1,blc-Latn,Bella Coola (Latin)
fv,fv_sencoten,SENĆOŦEN,BC Coast,fv_sencoten_kmw-9.0.js,9.1.1,str-Latn,Straits Salish (Latin)
fv,fv_shashishalhem,Shashishalhem,BC Coast,fv_shashishalhem_kmw-9.0.js,9.1.1,sec-Latn,Sechelt (Latin)
fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,9.2,squ-Latn,Squamish (Latin)
fv,fv_smalgyax,Sm'algya̱x,BC Coast,fv_smalgyax_kmw-9.0.js,9.1.1,tsi-Latn,Tsimshian (Latin)
fv,fv_kwakwala_liqwala,Kʷak̓ʷala,BC Coast,fv_kwakwala_liqwala_kmw-9.0.js,9.2.5,kwk-Latn,Kwakiutl (Latin)
fv,fv_kwakwala,Kwak̕wala,BC Coast,fv_kwakwala_kmw-9.0.js,9.1.2,kwk-Latn,Kwakiutl (Latin)
fv,fv_nexwslayemucen,Nəxʷsƛ̓ay̓əmúcən,BC Coast,fv_nexwslayemucen_kmw-9.0.js,9.2.1,clm-Latn,Clallam (Latin)
fv,fv_nisgaa,Nisg̱a'a,BC Coast,fv_nisgaa_kmw-9.0.js,9.1.2,ncg-Latn,Nisga'a (Latin)
fv,fv_nuucaanul,Nuučaan̓uł,BC Coast,fv_nuucaanul_kmw-9.0.js,9.1.4,nuk-Latn,Nuu-chah-nulth (Latin)
fv,fv_nuxalk,Nuxalk,BC Coast,fv_nuxalk_kmw-9.0.js,10.0,blc-Latn,Bella Coola (Latin)
fv,fv_sencoten,SENĆOŦEN,BC Coast,fv_sencoten_kmw-9.0.js,9.2.1,str-Latn,Straits Salish (Latin)
fv,fv_shashishalhem,Shashishalhem,BC Coast,fv_shashishalhem_kmw-9.0.js,9.1.3,sec-Latn,Sechelt (Latin)
fv,fv_skwxwumesh_snichim,Sḵwx̱wúmesh sníchim,BC Coast,fv_skwxwumesh_snichim_kmw-9.0.js,9.2.1,squ-Latn,Squamish (Latin)
fv,fv_smalgyax,Sm'algya̱x,BC Coast,fv_smalgyax_kmw-9.0.js,9.1.3,tsi-Latn,Tsimshian (Latin)
fv,fv_xaislakala,X̄a'ʼislak̓ala,BC Coast,fv_xaislakala_kmw-9.0.js,9.1.1,has-Latn,Haisla (Latin)
fv,fv_hlgaagilda_xaayda_kil,X̱aayda-X̱aad Kil,BC Coast,fv_hlgaagilda_xaayda_kil_kmw-9.0.js,9.2,hax-Latn,Southern Haida (Latin)
fv,fv_dakelh,Dakelh,BC Interior,fv_dakelh_kmw-9.0.js,9.1.2,caf-Latn,Southern Carrier (Latin)
fv,fv_ktunaxa,Ktunaxa,BC Interior,fv_ktunaxa_kmw-9.0.js,9.1.1,kut-Latn,Kutenai (Latin)
fv,fv_dakelh,Dakelh,BC Interior,fv_dakelh_kmw-9.0.js,9.1.4,caf-Latn,Southern Carrier (Latin)
fv,fv_ktunaxa,Ktunaxa,BC Interior,fv_ktunaxa_kmw-9.0.js,9.1.3,kut-Latn,Kutenai (Latin)
fv,fv_kwadacha_tsekene,Kwadacha Tsek’ene,BC Interior,fv_kwadacha_tsekene_kmw-9.0.js,1.0,sek-Latn,Sekani (Latin)
fv,fv_natwits,Nedut’en-Witsuwit'en,BC Interior,fv_natwits_kmw-9.0.js,9.1.1,caf-Latn,Southern Carrier (Latin)
fv,fv_nlekepmxcin,Nłeʔkepmxcin,BC Interior,fv_nlekepmxcin_kmw-9.0.js,9.2.1,thp-Latn,Thompson (Latin)
fv,fv_nlha7kapmxtsin,Nlha7kapmxtsin,BC Interior,fv_nlha7kapmxtsin_kmw-9.0.js,9.1,thp-Latn,Thompson (Latin)
fv,fv_nsilxcen,Nsilxcən,BC Interior,fv_nsilxcen_kmw-9.0.js,9.1.1,oka-Latn,Okanagan (Latin)
fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.1.1,shs-Latn,Shuswap (Latin)
fv,fv_stlatlimxec,Sƛ̓aƛ̓imxəc,BC Interior,fv_stlatlimxec_kmw-9.0.js,9.2.1,lil-Latn,Lillooet (Latin)
fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.1.1,lil-Latn,Lillooet (Latin)
fv,fv_taltan,Tāłtān,BC Interior,fv_taltan_kmw-9.0.js,9.1.2,tht-Latn,Tahltan (Latin)
fv,fv_tsekehne,Tsek'ehne,BC Interior,fv_tsekehne_kmw-9.0.js,9.1.1,sek-Latn,Sekani (Latin)
fv,fv_tsilhqotin,Tŝilhqot'in,BC Interior,fv_tsilhqotin_kmw-9.0.js,9.1.1,clc-Latn,Chilcotin (Latin)
fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,9.2,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics)
fv,fv_anicinapemi8in,Anicinapemi8in/Anishinàbemiwin,Eastern Subarctic,fv_anicinapemi8in_kmw-9.0.js,9.1,alq-Latn,Algonquin (Latin)
fv,fv_atikamekw,Atikamekw,Eastern Subarctic,fv_atikamekw_kmw-9.0.js,9.1,atj-Latn,Atikamekw (Latin)
fv,fv_ilnu_innu_aimun,Ilnu-Innu Aimun,Eastern Subarctic,fv_ilnu_innu_aimun_kmw-9.0.js,9.1,moe-Latn,Montagnais (Latin)
fv,fv_swampy_cree,ᐃᓂᓂᒧᐎᐣ (Swampy Cree),Eastern Subarctic,fv_swampy_cree_kmw-9.0.js,9.2,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,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,crl-Cans,Northern East Cree (Unified Canadian Aboriginal Syllabics)
fv,fv_severn_ojibwa,ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa),Eastern Subarctic,fv_severn_ojibwa_kmw-9.0.js,9.3,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics)
fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (Ojibwa),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,9.3,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics)
fv,fv_naskapi,ᓇᔅᑲᐱ (Naskapi),Eastern Subarctic,fv_naskapi_kmw-9.0.js,9.3,nsk-Cans,Naskapi (Unified Canadian Aboriginal Syllabics)
sil,sil_euro_latin,English,European,european2-1.6.js,1.9.5,en,English
basic,basic_kbdcan,Français,European,canadian_french-1.0.js,1.1,fr-CA,French (Canada)
fv,fv_anishinaabemowin,Anishinaabemowin,Great Lakes - St. Lawrence,fv_anishinaabemowin_kmw-9.0.js,9.1,oj-Cans,Ojibwa (Unified Canadian Aboriginal Syllabics)
fv,fv_bodewadminwen,Bodéwadminwen-Nishnabémwen,Great Lakes - St. Lawrence,fv_bodewadminwen_kmw-9.0.js,9.1,pot-Latn,Potawatomi (Latin)
fv,fv_goyogohono,Goyogo̱hó:nǫ',Great Lakes - St. Lawrence,fv_goyogohono_kmw-9.0.js,9.2,cay-Latn,Cayuga (Latin)
fv,fv_kanienkeha_e,Kanien'kéha-Kanyen'kéha,Great Lakes - St. Lawrence,fv_kanienkeha_e_kmw-9.0.js,9.1.1,moh-Latn,Mohawk (Latin)
fv,fv_lunaapeewi_huluniixsuwaakan,Lunáapeewi-hulŭniixsuwáakan,Great Lakes - St. Lawrence,fv_lunaapeewi_huluniixsuwaakan_kmw-9.0.js,9.1,umu-Latn,Munsee (Latin)
fv,fv_onodagega_onondagega,Onǫda'géga'-Onoñda'géga',Great Lakes - St. Lawrence,fv_onodagega_onondagega_kmw-9.0.js,9.2,ono-Latn,Onondaga (Latin)
fv,fv_onodowaga,Onödowága,Great Lakes - St. Lawrence,fv_onodowaga_kmw-9.0.js,9.2,see-Latn,Seneca (Latin)
fv,fv_onayotaaka,Onʌyota'a:ka,Great Lakes - St. Lawrence,fv_onayotaaka_kmw-9.0.js,9.2,one-Latn,Oneida (Latin)
fv,fv_skaru_re,Skarù·reʔ,Great Lakes - St. Lawrence,fv_skaru_re_kmw-9.0.js,9.2,tus-Latn,Tuscarora (Latin)
fv,fv_wendat,Wendat,Great Lakes - St. Lawrence,fv_wendat_kmw-9.0.js,9.1,wya-Latn,Wyandot (Latin)
fv,fv_wobanakiodwawogan,Wôbanakiôdwawôgan,Great Lakes - St. Lawrence,fv_wobanakiodwawogan_kmw-9.0.js,9.1,abe-Latn,Western Abnaki (Latin)
fv,fv_australian,Australian,Pacific,fv_australian_kmw-9.0.js,9.3,pjt-Latn,Pitjantjatjara (Latin)
fv,fv_maori,Māori,Pacific,fv_maori_kmw-9.0.js,9.1,mi-Latn,Maori (Latin)
fv,fv_blackfoot,Blackfoot,Prairies,fv_blackfoot_kmw-9.0.js,9.2,bla-Latn,Siksika (Latin)
fv,fv_cree_latin,Cree - Roman Orthography,Prairies,fv_cree_latin_kmw-9.0.js,9.1,cr-Latn,Cree (Latin)
fv,fv_dakota_mb,Dakota,Prairies,fv_dakota_mb_kmw-9.0.js,9.1,dak-Latn,Dakota (Latin)
fv,fv_dakota_sk,Dakot̄a,Prairies,fV_dakota_sk_kmw-9.0.js,9.1,dak-Latn,Dakota (Latin)
fv,fv_isga_iabi,Isga Iʔabi,Prairies,fv_isga_iabi_kmw-9.0.js,9.1,sto-Latn,Stoney (Latin)
fv,fv_lakota,Lak̇ot̄a,Prairies,fv_lakota-9.0.js,9.1,lkt-Latn,Lakota (Latin)
fv,fv_nakoda,Nakoda,Prairies,fv_nakoda_kmw-9.0.js,9.1,asb-Latn,Assiniboine (Latin)
fv,fv_tsuutina,Tsúùt'ínà,Prairies,fv_tsuutina_kmw-9.0.js,9.1,srs-Latn,Sarsi (Latin)
fv,fv_natwits,Nedut’en-Witsuwit'en,BC Interior,fv_natwits_kmw-9.0.js,9.1.3,caf-Latn,Southern Carrier (Latin)
fv,fv_nlekepmxcin,Nłeʔkepmxcin,BC Interior,fv_nlekepmxcin_kmw-9.0.js,9.2.3,thp-Latn,Thompson (Latin)
fv,fv_nlha7kapmxtsin,Nlha7kapmxtsin,BC Interior,fv_nlha7kapmxtsin_kmw-9.0.js,9.1.1,thp-Latn,Thompson (Latin)
fv,fv_nsilxcen,Nsilxcən,BC Interior,fv_nsilxcen_kmw-9.0.js,9.2,oka-Latn,Okanagan (Latin)
fv,fv_secwepemctsin,Secwepemctsín,BC Interior,fv_secwepemctsin_kmw-9.0.js,9.1.2,shs-Latn,Shuswap (Latin)
fv,fv_stlatlimxec,Sƛ̓aƛ̓imxəc,BC Interior,fv_stlatlimxec_kmw-9.0.js,9.2.3,lil-Latn,Lillooet (Latin)
fv,fv_statimcets,St̓át̓imcets,BC Interior,fv_statimcets_kmw-9.0.js,9.1.3,lil-Latn,Lillooet (Latin)
fv,fv_taltan,Tāłtān,BC Interior,fv_taltan_kmw-9.0.js,9.1.5,tht-Latn,Tahltan (Latin)
fv,fv_tsekehne,Tsek'ehne,BC Interior,fv_tsekehne_kmw-9.0.js,9.1.2,sek-Latn,Sekani (Latin)
fv,fv_tsilhqotin,Tŝilhqot'in,BC Interior,fv_tsilhqotin_kmw-9.0.js,9.1.3,clc-Latn,Chilcotin (Latin)
fv,fv_southern_carrier,ᑐᑊᘁᗕᑋᗸ (Southern Carrier),BC Interior,fv_southern_carrier_kmw-9.0.js,9.2.1,caf-Cans,Southern Carrier (Unified Canadian Aboriginal Syllabics)
fv,fv_anicinapemi8in,Anicinapemi8in/Anishinàbemiwin,Eastern Subarctic,fv_anicinapemi8in_kmw-9.0.js,9.1.1,alq-Latn,Algonquin (Latin)
fv,fv_atikamekw,Atikamekw,Eastern Subarctic,fv_atikamekw_kmw-9.0.js,9.1.1,atj-Latn,Atikamekw (Latin)
fv,fv_ilnu_innu_aimun,Ilnu-Innu Aimun,Eastern Subarctic,fv_ilnu_innu_aimun_kmw-9.0.js,9.1.1,moe-Latn,Montagnais (Latin)
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,9.3.1,ojs-Cans,Severn Ojibwa (Unified Canadian Aboriginal Syllabics)
fv,fv_ojibwa,ᐊᓂᔑᓇᐯᒧᐎᓐ (Ojibwa),Eastern Subarctic,fv_ojibwa_kmw-9.0.js,9.3.1,ojb-Cans,Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics)
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,2.0.4,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,9.1.1,oj-Cans,Ojibwa (Unified Canadian Aboriginal Syllabics)
fv,fv_bodewadminwen,Bodéwadminwen-Nishnabémwen,Great Lakes - St. Lawrence,fv_bodewadminwen_kmw-9.0.js,9.1.1,pot-Latn,Potawatomi (Latin)
fv,fv_goyogohono,Goyogo̱hó:nǫ',Great Lakes - St. Lawrence,fv_goyogohono_kmw-9.0.js,9.2.1,cay-Latn,Cayuga (Latin)
fv,fv_kanienkeha_e,Kanien'kéha-Kanyen'kéha,Great Lakes - St. Lawrence,fv_kanienkeha_e_kmw-9.0.js,9.1.3,moh-Latn,Mohawk (Latin)
fv,fv_lunaapeewi_huluniixsuwaakan,Lunáapeewi-hulŭniixsuwáakan,Great Lakes - St. Lawrence,fv_lunaapeewi_huluniixsuwaakan_kmw-9.0.js,9.1.1,umu-Latn,Munsee (Latin)
fv,fv_onodagega_onondagega,Onǫda'géga'-Onoñda'géga',Great Lakes - St. Lawrence,fv_onodagega_onondagega_kmw-9.0.js,9.2.1,ono-Latn,Onondaga (Latin)
fv,fv_onodowaga,Onödowága,Great Lakes - St. Lawrence,fv_onodowaga_kmw-9.0.js,9.2.1,see-Latn,Seneca (Latin)
fv,fv_onayotaaka,Onʌyota'a:ka,Great Lakes - St. Lawrence,fv_onayotaaka_kmw-9.0.js,9.2.1,one-Latn,Oneida (Latin)
fv,fv_skaru_re,Skarù·reʔ,Great Lakes - St. Lawrence,fv_skaru_re_kmw-9.0.js,9.2.1,tus-Latn,Tuscarora (Latin)
fv,fv_wendat,Wendat,Great Lakes - St. Lawrence,fv_wendat_kmw-9.0.js,9.1.1,wya-Latn,Wyandot (Latin)
fv,fv_wobanakiodwawogan,Wôbanakiôdwawôgan,Great Lakes - St. Lawrence,fv_wobanakiodwawogan_kmw-9.0.js,9.1.1,abe-Latn,Western Abnaki (Latin)
fv,fv_australian,Australian,Pacific,fv_australian_kmw-9.0.js,9.3.1,pjt-Latn,Pitjantjatjara (Latin)
fv,fv_maori,Māori,Pacific,fv_maori_kmw-9.0.js,9.1.1,mi-Latn,Maori (Latin)
fv,fv_blackfoot,Blackfoot,Prairies,fv_blackfoot_kmw-9.0.js,9.2.1,bla-Latn,Siksika (Latin)
fv,fv_cree_latin,Cree - Roman Orthography,Prairies,fv_cree_latin_kmw-9.0.js,9.1.1,cr-Latn,Cree (Latin)
fv,fv_dakota_mb,Dakota,Prairies,fv_dakota_mb_kmw-9.0.js,9.1.1,dak-Latn,Dakota (Latin)
fv,fv_dakota_sk,Dakot̄a,Prairies,fV_dakota_sk_kmw-9.0.js,9.1.1,dak-Latn,Dakota (Latin)
fv,fv_isga_iabi,Isga Iʔabi,Prairies,fv_isga_iabi_kmw-9.0.js,9.1.1,sto-Latn,Stoney (Latin)
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,10.0.2,crk-Cans,ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics)
fv,fv_dine_bizaad,Diné Bizaad,South West,fv_dine_bizaad_kmw-9.0.js,9.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.2,bea,Beaver
fv,fv_dene_dzage,Dene Dzage,Western Subarctic,fv_dene_dzage_kmw-9.0.js,9.1,kkz-Latn,Kaska (Latin)
fv,fv_dene_zhatie,Dene Zhatié,Western Subarctic,fv_dene_zhatie_kmw-9.0.js,9.1,xsl-Latn,South Slavey (Latin)
fv,fv_denesuline_epsilon,Dënesųłıné,Western Subarctic,fv_denesuline_epsilon_kmw-9.0.js,9.1.1,chp-Latn,Chipewyan (Latin)
fv,fv_denesuline,Dɛnɛsųłiné,Western Subarctic,fv_denesuline_kmw-9.0.js,9.1,chp-Latn,Chipewyan (Latin)
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.3,bea,Beaver
fv,fv_dene_dzage,Dene Dzage,Western Subarctic,fv_dene_dzage_kmw-9.0.js,9.1.1,kkz-Latn,Kaska (Latin)
fv,fv_dene_zhatie,Dene Zhatié,Western Subarctic,fv_dene_zhatie_kmw-9.0.js,10.0.2,xsl-Latn,South Slavey (Latin)
fv,fv_denesuline_epsilon,Dënesųłıné,Western Subarctic,fv_denesuline_epsilon_kmw-9.0.js,9.1.4,chp-Latn,Chipewyan (Latin)
fv,fv_denesuline,Dɛnɛsųłiné,Western Subarctic,fv_denesuline_kmw-9.0.js,9.1.1,chp-Latn,Chipewyan (Latin)
fv,fv_gwichin,Gwich'in,Western Subarctic,fv_gwichin_kmw-9.0.js,9.2.1,gwi-Latn,Gwichʼin (Latin)
fv,fv_han,Hän,Western Subarctic,fv_han_kmw-9.0.js,9.2,haa-Latn,Han (Latin)
fv,fv_kashogotine_yati,K'áshogot'ı̨nę́ Yatı̨́,Western Subarctic,fv_kashogotine_yati_kmw-9.0.js,9.1,scs-Latn,North Slavey (Latin)
fv,fv_tlingit,Łingít,Western Subarctic,fv_tlingit_kmw-9.0.js,9.2,tli-Latn,Tlingit (Latin)
fv,fv_neeaandeg,Nee'aanděg',Western Subarctic,fv_neeaandeg_kmw-9.0.js,9.1,tcb-Latn,Tanacross (Latin)
fv,fv_kashogotine_yati,K'áshogot'ı̨nę́ Yatı̨́,Western Subarctic,fv_kashogotine_yati_kmw-9.0.js,9.1.1,scs-Latn,North Slavey (Latin)
fv,fv_tlingit,Łingít,Western Subarctic,fv_tlingit_kmw-9.0.js,10.0.1,tli-Latn,Tlingit (Latin)
fv,fv_neeaandeg,Nee'aanděg',Western Subarctic,fv_neeaandeg_kmw-9.0.js,9.1.1,tcb-Latn,Tanacross (Latin)
fv,fv_neeaaneegn,Nee'aaneegn',Western Subarctic,fv_neeaaneegn_kmw-9.0.js,9.1,tau-Latn,Upper Tanana (Latin)
fv,fv_northern_tutchone,Northern Tutchone,Western Subarctic,fv_northern_tutchone_kmw-9.0.js,9.2,ttm-Latn,Northern Tutchone (Latin)
fv,fv_sahugotine_yati,Sahtúgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_sahugotine_yati_kmw-9.0.js,9.1,scs-Latn,North Slavey (Latin)
fv,fv_shihgotine_yati,Shıhgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_shihgotine_yati_kmw-9.0.js,9.1,scs-Latn,North Slavey (Latin)
fv,fv_southern_tutchone,Southern Tutchone,Western Subarctic,fv_southern_tutchone_kmw-9.0.js,9.2.1,tce-Latn,Southern Tutchone (Latin)
fv,fv_tagizi_dene,Tāgizi Dene,Western Subarctic,fv_tagizi_dene_kmw-9.0.js,9.2,tgx-Latn,Tagish (Latin)
fv,fv_tlicho_yatii,Tłı̨chǫ Yatıı̀,Western Subarctic,fv_tlicho_yatii_kmw-9.0.js,9.1,dgr-Latn,Dogrib (Latin)
fv,fv_dene_mb,ᑌᓀ ᔭᕠᐁ (Dene MB),Western Subarctic,fv_dene_mb_kmw-9.0.js,9.2,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)
fv,fv_dene_nt,ᑌᓀ ᔭᕱᐁ (Dene NT),Western Subarctic,fv_dene_nt_kmw-9.0.js,9.2,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)
fv,fv_sahugotine_yati,Sahtúgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_sahugotine_yati_kmw-9.0.js,9.1.1,scs-Latn,North Slavey (Latin)
fv,fv_shihgotine_yati,Shıhgot'ı̨nę́ Yatı̨́,Western Subarctic,fv_shihgotine_yati_kmw-9.0.js,9.1.1,scs-Latn,North Slavey (Latin)
fv,fv_southern_tutchone,Southern Tutchone,Western Subarctic,fv_southern_tutchone_kmw-9.0.js,9.2.2,tce-Latn,Southern Tutchone (Latin)
fv,fv_tagizi_dene,Tāgizi Dene,Western Subarctic,fv_tagizi_dene_kmw-9.0.js,9.2.1,tgx-Latn,Tagish (Latin)
fv,fv_tlicho_yatii,Tłı̨chǫ Yatıı̀,Western Subarctic,fv_tlicho_yatii_kmw-9.0.js,9.1.1,dgr-Latn,Dogrib (Latin)
fv,fv_dene_mb,ᑌᓀ ᔭᕠᐁ (Dene MB),Western Subarctic,fv_dene_mb_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)
fv,fv_dene_nt,ᑌᓀ ᔭᕱᐁ (Dene NT),Western Subarctic,fv_dene_nt_kmw-9.0.js,9.2.1,chp-Cans,Chipewyan (Unified Canadian Aboriginal Syllabics)

1 Shortname ID Name Region 9.0 Web Keyboard Version Language ID Language Name
2 fv fv_inuvialuktun Inuvialuktun Arctic fv_inuvialuktun_kmw-9.0.js 9.1 9.1.1 ikt-Latn Inuinnaqtun (Latin)
3 i inuktitut_pirurvik Kiputtijjut | ᑭᐳᑦᑎᔾᔪᑦ Arctic inuktitut_pirurvik-1.1.js 1.4 ike-Cans Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
4 i inuktitut_latin Qaliujaaqpait | ᖃᓕᐅᔮᖅᐸᐃᑦ Arctic inuktitut_latin-1.1.js 1.4 ike-Latn Eastern Canadian Inuktitut (Latin)
5 i inuktitut_naqittaut Qaniujaaqpait | ᖃᓂᐅᔮᖅᐸᐃᑦ Arctic inuktitut_naqittaut-1.3.js 1.6 ike-Cans Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
6 fv fv_uummarmiutun Uummarmiutun Arctic fv_uummarmiutun_kmw-9.0.js 9.1 9.1.1 ikt-Latn Inuinnaqtun (Latin)
7 fv fv_eastern_canadian_inuktitut ᐃᓄᒃᑎᑐᑦ (Eastern Canadian Inuktitut) Arctic fv_eastern_canadian_inuktitut_kmw-9.0.js 9.2 9.2.1 ike-Cans Eastern Canadian Inuktitut (Unified Canadian Aboriginal Syllabics)
8 fv fv_migmaq Mi'gmawi'simg / Mi'kmawi'simk Atlantic fv_migmaq_kmw-9.0.js 9.1.1 9.1.2 mic-Latn Mi'kmaq (Latin)
9 fv fv_skicinuwatuwewakon Skicinuwatuwewakon Atlantic fv_skicinuwatuwewakon_kmw-9.0.js 9.1 9.1.1 pqm-Latn Malecite-Passamaquoddy (Latin)
10 fv fv_uwikala 'Uwik̓ala BC Coast fv_uwikala_kmw-9.0.js 9.1.1 9.3 hei-Latn Heiltsuk (Latin)
11 fv fv_dexwlesucid Dəxʷləšucid BC Coast fv_dexwlesucid_kmw-9.0.js 9.2 9.2.1 lut-Latn Lushootseed (Latin)
12 fv fv_diitiidatx Diidiitidq BC Coast fv_diitiidatx_kmw-9.0.js 9.1.1 9.1.3 nuk-Latn Nuu-chah-nulth (Latin)
13 fv fv_gitsenimx Gitsenimx̱ BC Coast fv_gitsenimx_kmw-9.0.js 9.1.1 git-Latn Gitxsan (Latin)
14 fv fv_hailzaqvla Haiɫzaqvla BC Coast fv_hailzaqvla_kmw-9.0.js 9.4 9.4.1 hei Heiltsuk (Latin)
15 fv fv_haisla Haisla BC Coast fv_haisla.js 1.0 has-Latn Haisla (Latin)
16 fv fv_halqemeylem Halq'eméylem BC Coast fv_halqemeylem_kmw-9.0.js 9.1.1 9.1.3 hur-Latn Halkomelem (Latin)
17 fv fv_henqeminem Hǝn̓q̓ǝmin̓ǝm BC Coast fv_henqeminem_kmw-9.0.js 9.1.1 9.1.3 hur-Latn Halkomelem (Latin)
18 fv fv_klahoose Homalco-Klahoose-Sliammon BC Coast fv_klahoose_kmw-9.0.js 9.2 coo-Latn Comox (Latin)
19 fv fv_hulquminum Hul’q’umi’num’ BC Coast fv_hulquminum_kmw-9.0.js 9.1 hur-Latn Halkomelem (Latin)
20 fv fv_hulquminum_combine Hul̓q̓umin̓um̓ BC Coast fv_hulquminum_combine_kmw-9.0.js 1.0 hur-Latn Halkomelem (Latin)
21 fv fv_kwakwala_liqwala Kʷak̓ʷala BC Coast fv_kwakwala_liqwala_kmw-9.0.js 9.2.2 9.2.5 kwk-Latn Kwakiutl (Latin)
22 fv fv_kwakwala Kwak̕wala BC Coast fv_kwakwala_kmw-9.0.js 9.1.1 9.1.2 kwk-Latn Kwakiutl (Latin)
23 fv fv_nexwslayemucen Nəxʷsƛ̓ay̓əmúcən BC Coast fv_nexwslayemucen_kmw-9.0.js 9.2 9.2.1 clm-Latn Clallam (Latin)
24 fv fv_nisgaa Nisg̱a'a BC Coast fv_nisgaa_kmw-9.0.js 9.1.1 9.1.2 ncg-Latn Nisga'a (Latin)
25 fv fv_nuucaanul Nuučaan̓uł BC Coast fv_nuucaanul_kmw-9.0.js 9.1.1 9.1.4 nuk-Latn Nuu-chah-nulth (Latin)
26 fv fv_nuxalk Nuxalk BC Coast fv_nuxalk_kmw-9.0.js 9.1 10.0 blc-Latn Bella Coola (Latin)
27 fv fv_sencoten SENĆOŦEN BC Coast fv_sencoten_kmw-9.0.js 9.1.1 9.2.1 str-Latn Straits Salish (Latin)
28 fv fv_shashishalhem Shashishalhem BC Coast fv_shashishalhem_kmw-9.0.js 9.1.1 9.1.3 sec-Latn Sechelt (Latin)
29 fv fv_skwxwumesh_snichim Sḵwx̱wúmesh sníchim BC Coast fv_skwxwumesh_snichim_kmw-9.0.js 9.2 9.2.1 squ-Latn Squamish (Latin)
30 fv fv_smalgyax Sm'algya̱x BC Coast fv_smalgyax_kmw-9.0.js 9.1.1 9.1.3 tsi-Latn Tsimshian (Latin)
31 fv fv_xaislakala X̄a'ʼislak̓ala BC Coast fv_xaislakala_kmw-9.0.js 9.1.1 has-Latn Haisla (Latin)
32 fv fv_hlgaagilda_xaayda_kil X̱aayda-X̱aad Kil BC Coast fv_hlgaagilda_xaayda_kil_kmw-9.0.js 9.2 hax-Latn Southern Haida (Latin)
33 fv fv_dakelh Dakelh BC Interior fv_dakelh_kmw-9.0.js 9.1.2 9.1.4 caf-Latn Southern Carrier (Latin)
34 fv fv_ktunaxa Ktunaxa BC Interior fv_ktunaxa_kmw-9.0.js 9.1.1 9.1.3 kut-Latn Kutenai (Latin)
35 fv fv_kwadacha_tsekene Kwadacha Tsek’ene BC Interior fv_kwadacha_tsekene_kmw-9.0.js 1.0 sek-Latn Sekani (Latin)
36 fv fv_natwits Nedut’en-Witsuwit'en BC Interior fv_natwits_kmw-9.0.js 9.1.1 9.1.3 caf-Latn Southern Carrier (Latin)
37 fv fv_nlekepmxcin Nłeʔkepmxcin BC Interior fv_nlekepmxcin_kmw-9.0.js 9.2.1 9.2.3 thp-Latn Thompson (Latin)
38 fv fv_nlha7kapmxtsin Nlha7kapmxtsin BC Interior fv_nlha7kapmxtsin_kmw-9.0.js 9.1 9.1.1 thp-Latn Thompson (Latin)
39 fv fv_nsilxcen Nsilxcən BC Interior fv_nsilxcen_kmw-9.0.js 9.1.1 9.2 oka-Latn Okanagan (Latin)
40 fv fv_secwepemctsin Secwepemctsín BC Interior fv_secwepemctsin_kmw-9.0.js 9.1.1 9.1.2 shs-Latn Shuswap (Latin)
41 fv fv_stlatlimxec Sƛ̓aƛ̓imxəc BC Interior fv_stlatlimxec_kmw-9.0.js 9.2.1 9.2.3 lil-Latn Lillooet (Latin)
42 fv fv_statimcets St̓át̓imcets BC Interior fv_statimcets_kmw-9.0.js 9.1.1 9.1.3 lil-Latn Lillooet (Latin)
43 fv fv_taltan Tāłtān BC Interior fv_taltan_kmw-9.0.js 9.1.2 9.1.5 tht-Latn Tahltan (Latin)
44 fv fv_tsekehne Tsek'ehne BC Interior fv_tsekehne_kmw-9.0.js 9.1.1 9.1.2 sek-Latn Sekani (Latin)
45 fv fv_tsilhqotin Tŝilhqot'in BC Interior fv_tsilhqotin_kmw-9.0.js 9.1.1 9.1.3 clc-Latn Chilcotin (Latin)
46 fv fv_southern_carrier ᑐᑊᘁᗕᑋᗸ (Southern Carrier) BC Interior fv_southern_carrier_kmw-9.0.js 9.2 9.2.1 caf-Cans Southern Carrier (Unified Canadian Aboriginal Syllabics)
47 fv fv_anicinapemi8in Anicinapemi8in/Anishinàbemiwin Eastern Subarctic fv_anicinapemi8in_kmw-9.0.js 9.1 9.1.1 alq-Latn Algonquin (Latin)
48 fv fv_atikamekw Atikamekw Eastern Subarctic fv_atikamekw_kmw-9.0.js 9.1 9.1.1 atj-Latn Atikamekw (Latin)
49 fv fv_ilnu_innu_aimun Ilnu-Innu Aimun Eastern Subarctic fv_ilnu_innu_aimun_kmw-9.0.js 9.1 9.1.1 moe-Latn Montagnais (Latin)
50 fv fv_swampy_cree ᐃᓂᓂᒧᐎᐣ (Swampy Cree) Eastern Subarctic fv_swampy_cree_kmw-9.0.js 9.2 9.2.1 csw-Cans Swampy Cree (Unified Canadian Aboriginal Syllabics)
51 fv fv_moose_cree ᐃᓕᓖᒧᐎᓐ (Moose Cree) Eastern Subarctic fv_moose_cree_kmw-9.0.js 9.2 9.2.1 crm-Cans Moose Cree (Unified Canadian Aboriginal Syllabics)
52 fv fv_northern_east_cree ᐄᔨᔫ-ᐄᓅ ᐊᔨᒨᓐ (Northern East Cree) Eastern Subarctic fv_northern_east_cree_kmw-9.0.js 9.2 9.2.1 crl-Cans Northern East Cree (Unified Canadian Aboriginal Syllabics)
53 fv fv_severn_ojibwa ᐊᓂᔑᓂᓂᒧᐎᐣ (Severn Ojibwa) Eastern Subarctic fv_severn_ojibwa_kmw-9.0.js 9.3 9.3.1 ojs-Cans Severn Ojibwa (Unified Canadian Aboriginal Syllabics)
54 fv fv_ojibwa ᐊᓂᔑᓇᐯᒧᐎᓐ (Ojibwa) Eastern Subarctic fv_ojibwa_kmw-9.0.js 9.3 9.3.1 ojb-Cans Northwestern Ojibwa (Unified Canadian Aboriginal Syllabics)
55 fv fv_naskapi ᓇᔅᑲᐱ (Naskapi) Eastern Subarctic fv_naskapi_kmw-9.0.js 9.3 9.3.1 nsk-Cans Naskapi (Unified Canadian Aboriginal Syllabics)
56 sil sil_euro_latin English European european2-1.6.js 1.9.5 2.0.4 en English
57 basic basic_kbdcan Français European canadian_french-1.0.js 1.1 1.1.1 fr-CA French (Canada)
58 fv fv_anishinaabemowin Anishinaabemowin Great Lakes - St. Lawrence fv_anishinaabemowin_kmw-9.0.js 9.1 9.1.1 oj-Cans Ojibwa (Unified Canadian Aboriginal Syllabics)
59 fv fv_bodewadminwen Bodéwadminwen-Nishnabémwen Great Lakes - St. Lawrence fv_bodewadminwen_kmw-9.0.js 9.1 9.1.1 pot-Latn Potawatomi (Latin)
60 fv fv_goyogohono Goyogo̱hó:nǫ' Great Lakes - St. Lawrence fv_goyogohono_kmw-9.0.js 9.2 9.2.1 cay-Latn Cayuga (Latin)
61 fv fv_kanienkeha_e Kanien'kéha-Kanyen'kéha Great Lakes - St. Lawrence fv_kanienkeha_e_kmw-9.0.js 9.1.1 9.1.3 moh-Latn Mohawk (Latin)
62 fv fv_lunaapeewi_huluniixsuwaakan Lunáapeewi-hulŭniixsuwáakan Great Lakes - St. Lawrence fv_lunaapeewi_huluniixsuwaakan_kmw-9.0.js 9.1 9.1.1 umu-Latn Munsee (Latin)
63 fv fv_onodagega_onondagega Onǫda'géga'-Onoñda'géga' Great Lakes - St. Lawrence fv_onodagega_onondagega_kmw-9.0.js 9.2 9.2.1 ono-Latn Onondaga (Latin)
64 fv fv_onodowaga Onödowága Great Lakes - St. Lawrence fv_onodowaga_kmw-9.0.js 9.2 9.2.1 see-Latn Seneca (Latin)
65 fv fv_onayotaaka Onʌyota'a:ka Great Lakes - St. Lawrence fv_onayotaaka_kmw-9.0.js 9.2 9.2.1 one-Latn Oneida (Latin)
66 fv fv_skaru_re Skarù·reʔ Great Lakes - St. Lawrence fv_skaru_re_kmw-9.0.js 9.2 9.2.1 tus-Latn Tuscarora (Latin)
67 fv fv_wendat Wendat Great Lakes - St. Lawrence fv_wendat_kmw-9.0.js 9.1 9.1.1 wya-Latn Wyandot (Latin)
68 fv fv_wobanakiodwawogan Wôbanakiôdwawôgan Great Lakes - St. Lawrence fv_wobanakiodwawogan_kmw-9.0.js 9.1 9.1.1 abe-Latn Western Abnaki (Latin)
69 fv fv_australian Australian Pacific fv_australian_kmw-9.0.js 9.3 9.3.1 pjt-Latn Pitjantjatjara (Latin)
70 fv fv_maori Māori Pacific fv_maori_kmw-9.0.js 9.1 9.1.1 mi-Latn Maori (Latin)
71 fv fv_blackfoot Blackfoot Prairies fv_blackfoot_kmw-9.0.js 9.2 9.2.1 bla-Latn Siksika (Latin)
72 fv fv_cree_latin Cree - Roman Orthography Prairies fv_cree_latin_kmw-9.0.js 9.1 9.1.1 cr-Latn Cree (Latin)
73 fv fv_dakota_mb Dakota Prairies fv_dakota_mb_kmw-9.0.js 9.1 9.1.1 dak-Latn Dakota (Latin)
74 fv fv_dakota_sk Dakot̄a Prairies fV_dakota_sk_kmw-9.0.js 9.1 9.1.1 dak-Latn Dakota (Latin)
75 fv fv_isga_iabi Isga Iʔabi Prairies fv_isga_iabi_kmw-9.0.js 9.1 9.1.1 sto-Latn Stoney (Latin)
76 fv fv_lakota Lak̇ot̄a Prairies fv_lakota-9.0.js 9.1 9.1.1 lkt-Latn Lakota (Latin)
77 fv fv_nakoda Nakoda Prairies fv_nakoda_kmw-9.0.js 9.1 9.1.1 asb-Latn Assiniboine (Latin)
78 fv fv_tsuutina Tsúùt'ínà Prairies fv_tsuutina_kmw-9.0.js 9.1 9.1.1 srs-Latn Sarsi (Latin)
79 fv fv_plains_cree ᓀᐦᐃᔭᐍᐏᐣ (Plains Cree) Prairies fv_plains_cree_kmw-9.0.js 10.0.2 crk-Cans ᓀᐦᐃᔭᐍᐏᐣ (Cree syllabics)
80 fv fv_dine_bizaad Diné Bizaad South West fv_dine_bizaad_kmw-9.0.js 9.1 9.1.1 nv-Latn Navajo (Latin)
81 fv fv_dane_zaa_zaage Dane-Z̲aa Z̲áágéʔ Western Subarctic fv_dane_zaa_zaage_kmw-9.0.js 9.2 9.3 bea Beaver
82 fv fv_dene_dzage Dene Dzage Western Subarctic fv_dene_dzage_kmw-9.0.js 9.1 9.1.1 kkz-Latn Kaska (Latin)
83 fv fv_dene_zhatie Dene Zhatié Western Subarctic fv_dene_zhatie_kmw-9.0.js 9.1 10.0.2 xsl-Latn South Slavey (Latin)
84 fv fv_denesuline_epsilon Dënesųłıné Western Subarctic fv_denesuline_epsilon_kmw-9.0.js 9.1.1 9.1.4 chp-Latn Chipewyan (Latin)
85 fv fv_denesuline Dɛnɛsųłiné Western Subarctic fv_denesuline_kmw-9.0.js 9.1 9.1.1 chp-Latn Chipewyan (Latin)
86 fv fv_gwichin Gwich'in Western Subarctic fv_gwichin_kmw-9.0.js 9.2.1 gwi-Latn Gwichʼin (Latin)
87 fv fv_han Hän Western Subarctic fv_han_kmw-9.0.js 9.2 haa-Latn Han (Latin)
88 fv fv_kashogotine_yati K'áshogot'ı̨nę́ Yatı̨́ Western Subarctic fv_kashogotine_yati_kmw-9.0.js 9.1 9.1.1 scs-Latn North Slavey (Latin)
89 fv fv_tlingit Łingít Western Subarctic fv_tlingit_kmw-9.0.js 9.2 10.0.1 tli-Latn Tlingit (Latin)
90 fv fv_neeaandeg Nee'aanděg' Western Subarctic fv_neeaandeg_kmw-9.0.js 9.1 9.1.1 tcb-Latn Tanacross (Latin)
91 fv fv_neeaaneegn Nee'aaneegn' Western Subarctic fv_neeaaneegn_kmw-9.0.js 9.1 tau-Latn Upper Tanana (Latin)
92 fv fv_northern_tutchone Northern Tutchone Western Subarctic fv_northern_tutchone_kmw-9.0.js 9.2 ttm-Latn Northern Tutchone (Latin)
93 fv fv_sahugotine_yati Sahtúgot'ı̨nę́ Yatı̨́ Western Subarctic fv_sahugotine_yati_kmw-9.0.js 9.1 9.1.1 scs-Latn North Slavey (Latin)
94 fv fv_shihgotine_yati Shıhgot'ı̨nę́ Yatı̨́ Western Subarctic fv_shihgotine_yati_kmw-9.0.js 9.1 9.1.1 scs-Latn North Slavey (Latin)
95 fv fv_southern_tutchone Southern Tutchone Western Subarctic fv_southern_tutchone_kmw-9.0.js 9.2.1 9.2.2 tce-Latn Southern Tutchone (Latin)
96 fv fv_tagizi_dene Tāgizi Dene Western Subarctic fv_tagizi_dene_kmw-9.0.js 9.2 9.2.1 tgx-Latn Tagish (Latin)
97 fv fv_tlicho_yatii Tłı̨chǫ Yatıı̀ Western Subarctic fv_tlicho_yatii_kmw-9.0.js 9.1 9.1.1 dgr-Latn Dogrib (Latin)
98 fv fv_dene_mb ᑌᓀ ᔭᕠᐁ (Dene MB) Western Subarctic fv_dene_mb_kmw-9.0.js 9.2 9.2.1 chp-Cans Chipewyan (Unified Canadian Aboriginal Syllabics)
99 fv fv_dene_nt ᑌᓀ ᔭᕱᐁ (Dene NT) Western Subarctic fv_dene_nt_kmw-9.0.js 9.2 9.2.1 chp-Cans Chipewyan (Unified Canadian Aboriginal Syllabics)

1
package-lock.json generated
View file

@ -1788,6 +1788,7 @@
},
"devDependencies": {
"@keymanapp/developer-test-helpers": "*",
"@keymanapp/resources-gosh": "*",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",

View file

@ -0,0 +1,90 @@
import { testBuildConfigurations } from './trigger_definitions.mjs';
if(process.argv.length != 4) {
console.error('Invalid parameters!');
console.error('Usage: node cancel-test-builds.mjs PR|branch TEAMCITY_TOKEN');
console.error(' PR number should be a pull request number, without preceding #');
console.error(' branch can be one of master, beta or stable-x.y, where x.y is a stable release');
process.exit(1);
}
const allTestBuildConfigurations = Object.keys(testBuildConfigurations).flatMap(item => testBuildConfigurations[item]);
const branchName = process.argv[2];
const authorizationHeader = "Bearer "+process.argv[3];
console.log(`Cancelling all queued and running test builds for ${branchName}`);
async function getTeamCity(api) {
const response = await fetch('https://build.palaso.org/app/rest/'+api, {
headers: {
"Authorization": authorizationHeader,
"Accept": "application/json"
}
});
if(!response.ok) {
return null;
}
return await response.json();
}
async function postTeamCity(api, body) {
const response = await fetch('https://build.palaso.org/app/rest/'+api, {
method: 'POST',
headers: {
"Authorization": authorizationHeader,
"Content-Type": "application/json",
},
body: body
});
if(!response.ok) {
console.warn(`API ${api} failed with HTTP/${response.status}: ${response.statusText}`);
return null;
}
return true;
}
async function getQueuedBuilds() {
return await getTeamCity('buildQueue?locator=count:200');
}
async function cancelQueuedBuild(id, buildTypeId) {
console.log(`Cancelling queued build ${id} on ${branchName} for ${buildTypeId}`);
if(await postTeamCity('buildQueue/id:'+id, `{ "buildCancelRequest": { "comment": "Cancelling build due to new commits on branch.", "readdIntoQueue": "false" } }`)) {
console.log(' Build successfully cancelled.');
}
}
async function getRunningBuilds() {
return await getTeamCity('builds?locator=running:true,count:200');
}
async function cancelRunningBuild(id, buildTypeId) {
console.log(`Cancelling running build ${id} on ${branchName} for ${buildTypeId}`);
if(await postTeamCity('builds/id:'+id, `{ "buildCancelRequest": { "comment": "Cancelling build due to new commits on branch.", "readdIntoQueue": "false" } }`)) {
console.log(' Build successfully cancelled.');
}
}
try {
const queuedBuilds = await getQueuedBuilds();
if(queuedBuilds) {
for(const build of queuedBuilds.build) {
if(allTestBuildConfigurations.includes(build.buildTypeId) && build.branchName == branchName) {
await cancelQueuedBuild(build.id, build.buildTypeId);
}
}
}
const runningBuilds = await getRunningBuilds();
if(runningBuilds) {
for(const build of runningBuilds.build) {
if(allTestBuildConfigurations.includes(build.buildTypeId) && build.branchName == branchName) {
await cancelRunningBuild(build.id, build.buildTypeId);
}
}
}
} catch(e) {
// we don't want to fail a trigger because of a hiccup in this script
console.trace(e);
process.exit(0);
}

View file

@ -0,0 +1,15 @@
// Maps to trigger-definitions.inc.sh and must be kept in sync
export const testBuildConfigurations = {
'android': ['KeymanAndroid_TestPullRequests', 'KeymanAndroid_TestSamplesAndTestProjects'],
'ios': ['Keyman_iOS_TestPullRequests', 'Keyman_iOS_TestSamplesAndTestProjects'],
'linux': ['KeymanLinux_TestPullRequests', 'Keyman_Linux_Test_Integration', 'Keyman_Common_KPAPI_TestPullRequests_Linux', 'deb-pr-packaging_GitHub'],
'mac': ['Keyman_KeymanMac_PullRequests', 'Keyman_Common_KPAPI_TestPullRequests_macOS'],
'windows': ['KeymanDesktop_TestPullRequests', 'KeymanDesktop_TestPrRenderOnScreenKeyboards', 'Keyman_Common_KPAPI_TestPullRequests_Windows'],
'web': ['Keymanweb_TestPullRequests', 'Keyman_Common_LMLayer_TestPullRequests', 'Keyman_Common_KPAPI_TestPullRequests_WASM'],
'developer': ['Keyman_Developer_Test'],
'common_web': ['Keyman_Test_Common_Web'],
'common_windows': ['Keyman_Test_Common_Windows'],
'common_mac': ['Keyman_Test_Common_Mac'],
'common_linux': ['Keyman_Test_Common_Linux'],
};

View file

@ -49,6 +49,9 @@ function triggerTestBuilds() {
local branch="$2"
local force="${3:-false}"
# Cancel any already running builds for this branch
node "$THIS_SCRIPT_PATH/ci/cancel-builds/cancel-test-builds.mjs" "$branch" "$TEAMCITY_TOKEN"
for platform in "${platforms[@]}"; do
echo "# $platform: checking for changes"
eval test_builds='(${'bc_test_$platform'[@]})'

View file

@ -2,6 +2,9 @@
#
# This file maps specific paths to build triggers
#
# Maps to ci/cancel-builds/trigger-definitions.mjs and must be kept in sync
#
available_platforms=(android common_web common_windows common_mac common_linux ios linux mac web windows developer)

View file

@ -20,3 +20,7 @@ Each directory contains:
- change /keyboard/keys/key type to "array"
- `imports/` - the importable data files (TODO-LDML)
## License
- [unicode-license.txt](unicode-license.txt) - Unicode license

View file

@ -0,0 +1,39 @@
UNICODE LICENSE V3
COPYRIGHT AND PERMISSION NOTICE
Copyright © 1991-2024 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.
THE DATA FILES AND SOFTWARE ARE 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 OF
THIRD PARTY RIGHTS.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.

View file

@ -647,7 +647,7 @@ export default class KeymanEngine extends KeymanEngineBase<BrowserConfiguration,
* @param {string} PInternalName internal name of keyboard, with or without Keyboard_ prefix
* @param {number} Pstatic static keyboard flag (unselectable elements)
* @param {string=} argFormFactor layout form factor, defaulting to 'desktop'
* @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'
* @param {(string)=} argLayerId name or index of layer to show, defaulting to 'default'
* @return {Object} DIV object with filled keyboard layer content
*
* See https://help.keyman.com/developer/engine/web/current-version/reference/osk/BuildVisualKeyboard
@ -656,7 +656,7 @@ export default class KeymanEngine extends KeymanEngineBase<BrowserConfiguration,
PInternalName: string,
Pstatic: number,
argFormFactor?: DeviceSpec.FormFactor,
argLayerId?: string|number
argLayerId?: string
): HTMLElement {
let PKbd: Keyboard = null;
@ -679,7 +679,7 @@ export default class KeymanEngine extends KeymanEngineBase<BrowserConfiguration,
return VisualKeyboard.buildDocumentationKeyboard(
PKbd,
Pstub,
this.config.paths.fonts,
this.config.paths,
argFormFactor,
argLayerId,
targetHeight

View file

@ -6,10 +6,15 @@ type FontFamilyStyleMap = {[family: string]: HTMLStyleElement};
export class StylesheetManager {
private fontStyleDefinitions: { [os: string]: FontFamilyStyleMap} = {};
private linkedSheets: HTMLStyleElement[] = [];
private fontPromises: Promise<FontFace>[] = [];
private doCacheBusting: boolean;
public readonly linkNode: Node;
public get sheets(): readonly HTMLStyleElement[] {
return this.linkedSheets;
}
public constructor(linkNode?: Node, doCacheBusting?: boolean) {
if(!linkNode) {
let _ElemHead=document.getElementsByTagName('HEAD');
@ -24,6 +29,10 @@ export class StylesheetManager {
}
linkStylesheet(sheet: HTMLStyleElement) {
if(!(sheet instanceof HTMLLinkElement) && !sheet.innerHTML) {
return;
}
this.linkedSheets.push(sheet);
this.linkNode.appendChild(sheet);
}
@ -32,21 +41,36 @@ export class StylesheetManager {
* Provides a `Promise` that resolves when all currently-linked stylesheets have loaded.
* Any change to the set of linked sheets after the initial call will be ignored.
*/
async allLoadedPromise() {
async allLoadedPromise(): Promise<void> {
const promises: Promise<void>[] = [];
for(const sheetElem of this.linkedSheets) {
// Based on https://stackoverflow.com/a/21147238
if(sheetElem.sheet?.cssRules) {
promises.push(Promise.resolve());
} else if(sheetElem.innerHTML) {
// NOT at the StackOverflow link, but something I found experimentally.
// Needed for live-constructed sheets with no corresponding file.
promises.push(Promise.resolve());
} else {
const promise = new ManagedPromise<void>();
sheetElem.addEventListener('load', () => promise.resolve());
sheetElem.addEventListener('error', () => promise.reject());
promises.push(promise.corePromise);
}
}
await Promise.all(promises);
const allPromises = promises.concat(this.fontPromises as Promise<any>[]);
if(Promise.allSettled) {
// allSettled - Chrome 76 / Safari 13
// Delays for settling (either then OR catch) for ALL promises.
await Promise.allSettled(allPromises)
} else {
// all - Chrome 32
// If an error happens, .all instantly resolves regardless of state of
// other Promises.
await Promise.all(allPromises);
}
}
/**
@ -67,8 +91,9 @@ export class StylesheetManager {
}
const fontKey = fd.family;
let source: string;
let i, ttf='', woff='', eot='', svg='', fList=[];
let i, ttf='', woff='', fList=[];
// TODO: 22 Aug 2014: check that font path passed from cloud is actually used!
@ -98,8 +123,6 @@ export class StylesheetManager {
if(fList[i].toLowerCase().indexOf('.otf') > 0) ttf=fList[i];
if(fList[i].toLowerCase().indexOf('.ttf') > 0) ttf=fList[i];
if(fList[i].toLowerCase().indexOf('.woff') > 0) woff=fList[i];
if(fList[i].toLowerCase().indexOf('.eot') > 0) eot=fList[i];
if(fList[i].toLowerCase().indexOf('.svg') > 0) svg=fList[i];
}
// Font path qualified to support page-relative fonts (build 347)
@ -111,14 +134,6 @@ export class StylesheetManager {
woff = fontPathRoot+woff;
}
if(eot != '' && (eot.indexOf('/') < 0)) {
eot = fontPathRoot+eot;
}
if(svg != '' && (svg.indexOf('/') < 0)) {
svg = fontPathRoot+svg;
}
// Build the font-face definition according to the browser being used
var s='@font-face {\nfont-family:'
+ fd.family + ';\nfont-style:normal;\nfont-weight:normal;\n';
@ -132,54 +147,43 @@ export class StylesheetManager {
if(this.doCacheBusting) {
ttf = this.cacheBust(ttf);
}
s=s+'src:url(\''+ttf+'\') format(\'truetype\');';
} else {
return null;
source = "url('"+encodeURI(ttf)+"') format('truetype')";
}
} else {
var s0 = [];
if(os == DeviceSpec.OperatingSystem.Android) {
// Android 4.2 and 4.3 have bugs in their rendering for some scripts
// with embedded ttf or woff. svg mostly works so is a better initial
// choice on the Android browser.
if(svg != '') {
s0.push("url('"+svg+"') format('svg')");
}
if(woff != '') {
s0.push("url('"+woff+"') format('woff')");
}
if(ttf != '') {
s0.push("url('"+ttf+"') format('truetype')");
}
} else {
if(woff != '') {
s0.push("url('"+woff+"') format('woff')");
}
if(ttf != '') {
s0.push("url('"+ttf+"') format('truetype')");
}
if(svg != '') {
s0.push("url('"+svg+"') format('svg')");
}
if(woff != '') {
source = "url('"+encodeURI(woff)+"') format('woff')";
}
if(s0.length == 0) {
return null;
if(ttf != '') {
source = "url('"+encodeURI(ttf)+"') format('truetype')";
}
s += 'src:'+s0.join(',')+';';
}
if(!source) {
return null;
}
s += 'src:'+source+';';
s=s+'\n}\n';
const sheet = createStyleSheet(s);
fontStyleMap[fontKey] = sheet;
/* https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API
* Compat: Chrome 35... _just_ on the unupdated-Android 5.0 threshold.
*
* Note: this could probably wholesale-replace the stylesheet!
* Would need: `document.fonts.add(fontFace)` - does not have to wait for the load() Promise.
*
* For now, we're using this solely to detect when the font has been succesfully loaded.
*/
const fontFace = new FontFace(fd.family, source);
const clearPromise = () => this.fontPromises = this.fontPromises.filter((entry) => entry != loadPromise);
const loadPromise = fontFace.load().then(clearPromise).catch(clearPromise);
this.fontPromises.push(loadPromise);
this.linkStylesheet(sheet);
return sheet;
@ -197,15 +201,15 @@ export class StylesheetManager {
*
* @param {string} href path to stylesheet file
*/
linkExternalSheet(href: string): void {
linkExternalSheet(href: string, force?: boolean): HTMLStyleElement {
try {
if(document.querySelector("link[href="+JSON.stringify(href)+"]") != null) {
if(!force && document.querySelector("link[href="+JSON.stringify(href)+"]") != null) {
// We've already linked this stylesheet, don't do it again
return;
return null;
}
} catch(e) {
// We've built an invalid href, somehow?
return;
return null;
}
const linkElement=document.createElement('link');
@ -214,6 +218,7 @@ export class StylesheetManager {
linkElement.href=href;
this.linkStylesheet(linkElement);
return linkElement;
}
public unlink(stylesheet: HTMLStyleElement) {

View file

@ -156,6 +156,11 @@ export default class KeymanEngine<
this.osk.activeKeyboard = kbd;
this.osk.present();
}
// Needed to ensure the correct layer is displayed.
// Needs to be after the OSK has loaded for the keyboard in case the default
// layer should be something other than "default" for the current context.
this.core.resetContext(this.contextManager.activeTarget);
});
this.contextManager.on('keyboardasyncload', (metadata) => {

View file

@ -176,22 +176,28 @@ export default abstract class OSKKey {
* @returns font size as a style string
*/
getIdealFontSize(vkbd: VisualKeyboard, text: string, style: {height?: string, fontFamily?: string, fontSize: string}, override?: boolean): string {
let buttonStyle = getComputedStyle(this.btn);
let buttonStyle: typeof style & {width?: string} = getComputedStyle(this.btn);
let keyWidth = parseFloat(buttonStyle.width);
let emScale = 1;
let emScale = vkbd.getKeyEmFontSize();
// Among other things, ensures we use SpecialOSK styling for special key text.
// It's set on the key-span, not on the button.
//
// Also helps ensure that the stub's font-family name is used for keys, should
// that mismatch the font-family name specified within the keyboard's touch layout.
const capFont = !this.label ? undefined: getComputedStyle(this.label).fontFamily;
if(capFont) {
buttonStyle = {
fontFamily: capFont,
fontSize: buttonStyle.fontSize,
height: buttonStyle.height
}
}
const originalSize = getFontSizeStyle(style.fontSize || '1em');
// Not yet available; it'll be handled in a later layout pass.
if(!buttonStyle.fontSize) {
// NOTE: preserves old behavior for use in documentation keyboards, for now.
// Once we no longer need to maintain this code block, we can drop all current
// method parameters safely.
//
// Recompute the new width for use in autoscaling calculations below, just in case.
emScale = vkbd.getKeyEmFontSize();
keyWidth = this.getKeyWidth(vkbd);
} else if(!override) {
if(!override) {
// When available, just use computedStyle instead.
style = buttonStyle;
}
@ -334,11 +340,13 @@ export default abstract class OSKKey {
// space bar may not define the text span!
if(this.label) {
if(!this.label.classList.contains('kmw-spacebar-caption')) {
this.label.style.fontSize = this.getIdealFontSize(vkbd, this.keyText, this.btn.style);
// Do not use `this.keyText` - it holds *___* codes for special keys, not the actual glyph!
const keyCapText = this.label.textContent;
this.label.style.fontSize = this.getIdealFontSize(vkbd, keyCapText, this.btn.style);
} else {
// Remove any custom setting placed on it before recomputing its inherited style info.
this.label.style.fontSize = '';
const fontSize = this.getIdealFontSize(vkbd, this.label.textContent, getComputedStyle(this.label), true);
const fontSize = this.getIdealFontSize(vkbd, this.label.textContent, this.btn.style);
// Since the kmw-spacebar-caption version uses !important, we must specify
// it directly on the element too; otherwise, scaling gets ignored.

View file

@ -116,6 +116,14 @@ export interface EventMap {
pointerinteraction: (promise: Promise<void>) => void;
}
export function getResourcePath(config: Configuration) {
let resourcePathExt = 'osk/';
if(config.isEmbedded) {
resourcePathExt = '';
}
return `${config.pathConfig.resources}/${resourcePathExt}`
}
export default abstract class OSKView
extends EventEmitter<EventMap>
implements MinimalCodesInterface, KeyEventSourceInterface<EventMap> {
@ -198,7 +206,7 @@ export default abstract class OSKView
private mouseEnterPromise?: ManagedPromise<void>;
private touchEventPromiseManager = new TouchEventPromiseMap();
private static readonly STYLESHEET_FILES = ['kmwosk.css', 'globe-hint.css'];
static readonly STYLESHEET_FILES = ['kmwosk.css', 'globe-hint.css'];
constructor(configuration: Configuration) {
super();
@ -710,12 +718,10 @@ export default abstract class OSKView
// Install the default OSK stylesheets - but don't have it managed by the keyboard-specific stylesheet manager.
// We wish to maintain kmwosk.css whenever keyboard-specific styles are reset/removed.
// Temp-hack: embedded products prefer their stylesheet, etc linkages without the /osk path component.
let subpath = 'osk/';
if(this.config.isEmbedded) {
subpath = '';
}
const resourcePath = getResourcePath(this.config);
for(let sheetFile of OSKView.STYLESHEET_FILES) {
const sheetHref = `${this.config.pathConfig.resources}/${subpath}${sheetFile}`;
const sheetHref = `${resourcePath}${sheetFile}`;
this.uiStyleSheetManager.linkExternalSheet(sheetHref);
}
@ -801,6 +807,8 @@ export default abstract class OSKView
private _GenerateVisualKeyboard(keyboard: Keyboard, keyboardMetadata: KeyboardProperties): VisualKeyboard {
let device = this.targetDevice;
const resourcePath = getResourcePath(this.config);
// Root element sets its own classes, one of which is 'kmw-osk-inner-frame'.
let vkbd = new VisualKeyboard({
keyboard: keyboard,
@ -811,7 +819,12 @@ export default abstract class OSKView
styleSheetManager: this.kbdStyleSheetManager,
pathConfig: this.config.pathConfig,
embeddedGestureConfig: this.config.embeddedGestureConfig,
isEmbedded: this.config.isEmbedded
isEmbedded: this.config.isEmbedded,
specialFont: {
family: 'SpecialOSK',
files: [`${resourcePath}/keymanweb-osk.ttf`],
path: '' // Not actually used.
}
});
vkbd.on('keyevent', (keyEvent, callback) => this.emit('keyevent', keyEvent, callback));

View file

@ -42,6 +42,7 @@ import KeyTip from './keytip.interface.js';
import OSKKey from './keyboard-layout/oskKey.js';
import OSKLayer from './keyboard-layout/oskLayer.js';
import OSKLayerGroup from './keyboard-layout/oskLayerGroup.js';
import OSKView from './views/oskView.js';
import { LengthStyle, ParsedLengthStyle } from './lengthStyle.js';
import { defaultFontSize, getFontSizeStyle } from './fontSizeUtils.js';
import PhoneKeyTip from './input/gestures/browser/keytip.js';
@ -59,6 +60,7 @@ import Modipress from './input/gestures/browser/modipress.js';
import Flick, { buildFlickScroller } from './input/gestures/browser/flick.js';
import { GesturePreviewHost } from './keyboard-layout/gesturePreviewHost.js';
import OSKBaseKey from './keyboard-layout/oskBaseKey.js';
import { OSKResourcePathConfiguration } from './index.js';
interface KeyRuleEffects {
contextToken?: number,
@ -98,6 +100,11 @@ export interface VisualKeyboardConfiguration extends CommonConfiguration {
* want to remove that one when swapping keyboards.
*/
styleSheetManager: StylesheetManager;
/**
* A promise for loading of the font used by special keys.
*/
specialFont?: InternalKeyboardFont;
}
interface BoundingRect {
@ -1350,6 +1357,10 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
this.styleSheetManager.addStyleSheetForFont(kfd, this.fontRootPath, this.device.OS);
this.styleSheetManager.addStyleSheetForFont(ofd, this.fontRootPath, this.device.OS);
if(this.config.specialFont) {
this.styleSheetManager.addStyleSheetForFont(this.config.specialFont, '', this.device.OS);
}
// Build the style string to USE the fonts and append (or replace) the font style sheet
// Note: Some browsers do not download the font-face font until it is applied,
// so must apply style before testing for font availability
@ -1358,12 +1369,13 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
if (activeKeyboard != null && typeof (activeKeyboard.oskStyling) == 'string') // KMEW-129
customStyle = customStyle + activeKeyboard.oskStyling;
this.styleSheet = createStyleSheet(customStyle); //Build 360
this.styleSheet.addEventListener('load', () => {
// Once any related fonts are loaded, we can re-adjust key-cap scaling.
this.refreshLayout();
})
this.styleSheetManager.linkStylesheet(this.styleSheet);
if(customStyle) {
this.styleSheet = createStyleSheet(customStyle); //Build 360
this.styleSheetManager.linkStylesheet(this.styleSheet);
}
// Once any related fonts are loaded, we can re-adjust key-cap scaling.
this.styleSheetManager.allLoadedPromise().then(() => this.refreshLayout());
}
/**
@ -1400,14 +1412,23 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
* Create copy of the OSK that can be used for embedding in documentation or help
* The currently active keyboard will be returned if PInternalName is null
*
* @param {Object} PKbd the keyboard object to be displayed
* @param {string=} argFormFactor layout form factor, defaulting to 'desktop'
* @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'
* @param {number} height Target height for the rendered keyboard
* @param {Keyboard} PKbd the keyboard object to be displayed
* @param {KeyboardProperties} kbdProperties the metadata stub for the keyboard
* @param {Object} pathConfig an OSK path-configuration instance
* @param {string=} argFormFactor layout form factor, defaulting to 'desktop'
* @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'
* @param {number} height Target height for the rendered keyboard
* (currently required for legacy reasons)
* @return {Object} DIV object with filled keyboard layer content
*/
static buildDocumentationKeyboard(PKbd: Keyboard, kbdProperties: KeyboardProperties, fontRootPath: string, argFormFactor: DeviceSpec.FormFactor, argLayerId, height: number): HTMLElement { // I777
static buildDocumentationKeyboard(
PKbd: Keyboard,
kbdProperties: KeyboardProperties,
pathConfig: OSKResourcePathConfiguration,
argFormFactor: DeviceSpec.FormFactor,
argLayerId: string,
height: number
): HTMLElement { // I777
if (!PKbd) {
return null;
}
@ -1439,12 +1460,9 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
hostDevice: deviceSpec,
isStatic: true,
topContainer: null,
pathConfig: {
fonts: fontRootPath,
resources: '' // ignored
},
pathConfig: pathConfig,
styleSheetManager: null
}); //
});
kbdObj.layerGroup.element.className = kbdObj.kbdDiv.className; // may contain multiple classes
kbdObj.layerGroup.element.classList.add(device.formFactor + '-static');
@ -1469,11 +1487,10 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
// the Web OSK-Core design.
kbdObj.setSize(800, height); // Probably need something for width, too, rather than
kbdObj.fontSize = defaultFontSize(deviceSpec, height, false);
classWrapper.style.fontSize = kbdObj.element.style.fontSize;
// assuming 100%.
kbdObj.refreshLayout(); // Necessary for the row heights to be properly set!
// Relocates the font size definition from the main VisualKeyboard wrapper, since we don't return the whole thing.
kbd.style.fontSize = kbdObj.kbdDiv.style.fontSize;
kbd.style.height = kbdObj.kbdDiv.style.height;
kbd.style.maxHeight = kbdObj.kbdDiv.style.maxHeight;
} else {
@ -1485,39 +1502,58 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
kbdObj.updateState(); // double-ensure that the 'default' layer is properly displayed.
// Once the element is inserted into the DOM, refresh the layout so that proper text scaling may apply.
const detectAndHandleInsertion = () => {
const detectAndHandleInsertion = async () => {
if(document.contains(kbd)) {
// Yay, insertion!
try {
// Are there font-size attributes we may safely adjust? If so, do that!
if(getComputedStyle(kbd) && kbd.style.fontSize) {
kbdObj.fontSize = new ParsedLengthStyle(kbd.style.fontSize);
// Wait for full loading/connection before manipulating stylesheet locations.
await kbdObj.styleSheetManager.allLoadedPromise();
const mainSheet = kbdObj.styleSheet;
if(mainSheet) {
kbd.appendChild(mainSheet);
}
// Make sure that the stylesheet is attached, now that the keyboard-doc's been inserted.
// The stylesheet is currently built + constructed in the same code that attaches it to
// the page.
kbdObj.appendStyleSheet();
// Unlinking sheets will mutate the original array; make a backup
// copy of the array to iterate over.
const sheets = [].concat(kbdObj.styleSheetManager.sheets);
// Grab a reference to the stylesheet.
const stylesheet = kbdObj.styleSheet;
const stylesheetParentElement = stylesheet.parentElement;
/*
* Re-attach the font stylesheets... to the <head> element.
* They need re-attachment for the fonts to work properly for inactive keyboards.
*
* For future reference: as of early 2024, Chrome does not support
* @font-face style declaration within Shadow DOM elements. The
* declaration needs to be part of the main HTML doc.
*
* References:
* - https://stackoverflow.com/q/63710162
* - https://github.com/mdn/interactive-examples/issues/887#issuecomment-432418008
*/
for(let sheet of sheets) {
if(sheet == mainSheet) {
// Don't need to relocate the custom stylesheet.
continue;
} else if(sheet.href) {
// Don't relocate kmwosk.css or similar.
continue;
}
kbdObj.styleSheetManager.unlink(sheet);
document.head.appendChild(sheet);
}
// Don't reset top-level stuff; just the visible layer.
// kbdObj.currentLayer.refreshLayout(kbdObj, kbdObj.height);
// // Should we ever remove ALL related stylesheets during .shutdown()...
// kbdObj.config.styleSheetManager = new StylesheetManager(kbdObj.element);
// We refresh the full layout so that font-size is properly detected & stored
// on the documentation keyboard.
kbdObj.refreshLayout();
kbd.style.fontSize = kbdObj.kbdDiv.style.fontSize;
// We no longer need a reference to the constructing VisualKeyboard, so we should let
// it clean up its <head> stylesheet links. This detaches the stylesheet, though.
kbdObj.styleSheet = null; // is directly checked in shutdown; prevent removal.
kbdObj.shutdown();
// Now that shutdown is done, re-attach the stylesheet - but to the layer group.
kbd.appendChild(stylesheet);
} finally {
insertionObserver.disconnect();
}
@ -1530,7 +1566,22 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
subtree: true
});
// Ensure the main keyboard root is the first child element of the top-level div.
classWrapper.append(kbd);
// Ensure that the OSK's style-sheet is included by the top-level div standing in for the OSKView.
for(let sheetFile of OSKView.STYLESHEET_FILES) {
const sheetHref = `${pathConfig.resources}/osk/${sheetFile}`;
const sheet = kbdObj.styleSheetManager.linkExternalSheet(sheetHref, true);
sheet.parentNode.removeChild(sheet);
classWrapper.appendChild(sheet);
}
// Make sure that the stylesheet is attached, now that the keyboard-doc's been inserted.
// The stylesheet is currently built + constructed in the same code that attaches it to
// the page.
kbdObj.appendStyleSheet();
return classWrapper;
}

View file

@ -7,13 +7,6 @@
kmwosk.css: main CSS for keymanweb on-screen keyboard and other objects.
*/
@font-face {
font-family: SpecialOSK;
font-style: normal;
font-weight: normal;
src: url('keymanweb-osk.ttf') format('truetype');
}
/* kmw-key-square applies only to OSK key elements, kmw-key-square-ex applies only to popup key elements */
/* Common key-layout properties (all form factor + OS combinations) */
@ -340,7 +333,16 @@
/* Vertical centering of text labels on keys */
.kmw-key {text-align:center; white-space:nowrap;}
.kmw-key::before {content:'.'; display:inline-block; height:100%; vertical-align:middle; max-width:0px; visibility:hidden;}
.kmw-key span {display:inline-block}
.kmw-key span {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
}
.desktop .kmw-osk-frame{position:absolute;width:auto;height:auto;left:0;top:0;display:none;margin:0;padding:0;
@ -494,8 +496,8 @@
.phone .kmw-key-shift, .phone .kmw-key-shift-on{font-size:0.7em !important;}
/* Set special font and style for modifier and special key images */
body div.kmw-key-shift span.kmw-key-text {font-family:SpecialOSK !important;font-size:1em !important;}
body div.kmw-key-shift-on span.kmw-key-text {font-family:SpecialOSK !important;font-size:1em !important;}
body div.kmw-key-shift span.kmw-key-text {font-family:SpecialOSK !important;font-size:1em;}
body div.kmw-key-shift-on span.kmw-key-text {font-family:SpecialOSK !important;font-size:1em;}
#kmw-popup-keys div.kmw-key-shift span.kmw-key-text {font-family:SpecialOSK !important;}
#kmw-keytip {
@ -779,7 +781,7 @@ div.android div.kmw-keytip-cap {
.kmw-alert-text{margin:10px;white-space:default;font-family:Arial,sans-serif;}
/* Note: font size gets overwritten in source code! */
.kmw-spacebar-caption{font:1em Arial !important;color:rgba(0,0,0,0.25);}
.kmw-spacebar-caption{font-family: Arial !important;color:rgba(0,0,0,0.25);}
/* Probably best to make this its own CSS that can be optionally included? */
@media (prefers-color-scheme: dark) {

View file

@ -52,17 +52,19 @@
};
let shadowfy = function(container) {
const docBase = container.children[0];
container.removeChild(docBase);
const shadow = container.attachShadow({ mode: "open" });
shadow.appendChild(docBase);
const docBase = container.children[0];
const baseOSKStyle = document.createElement('link');
baseOSKStyle.href = "../../../../../build/app/resources/osk/kmwosk.css";
baseOSKStyle.rel="stylesheet";
baseOSKStyle.type="text/css";
shadow.appendChild(baseOSKStyle);
while(docBase.children.length > 1) {
const sheet = docBase.children[1];
docBase.removeChild(docBase.children[1]);
shadow.appendChild(sheet);
}
// Do this one last - that way, all relevant sheets are in place when the
// layout-refresh is triggered.
container.removeChild(docBase);
shadow.appendChild(docBase);
}
let documentKeyboard = async function(kbdid, langid, formFactor, layer) {

View file

@ -73,6 +73,7 @@
<h2><a href="./default-subkey/index.html">Tests handling of new default-subkey feature (#9430)</a></h2>
<h2><a href="./issue9469/index.html">Test special characters rendering with keymanweb-osk.ttf (#9469)</a></h2>
<h2><a href="./text_selection_tests_9073/index.html">Test text selection (#9073)</a></h2>
<h2><a href="./pr10506/index.html">Test key-cap scaling / font load interactions (#10506)</a></h2>
<h1>Other</h1>
<h2><a href="./regression-tests/index.html">Keystroke processing regression test engine.</a></h2>
<hr>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
build/

View file

@ -0,0 +1,6 @@
dotty_keys Change History
====================
1.0 (2024-01-26)
----------------
* Created by SIL International

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright © 2024 SIL International
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,31 @@
dotty_keys keyboard
==============
Description
-----------
A keyboard designed to test key-cap scaling in relation to font loading.
All base a-z keys have a "dot" glyph instead.
Links
-----
Keyboard Homepage: https://keyman.com/keyboards/dotty_keys
Copyright
---------
See [LICENSE.md](LICENSE.md)
Supported Platforms
-------------------
* Windows
* macOS
* Linux
* Web
* iPhone
* iPad
* Android phone
* Android tablet
* Mobile devices
* Desktop devices
* Tablet devices

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<KeymanDeveloperProject>
<Options>
<Version>2.0</Version>
<CompilerWarningsAsErrors>True</CompilerWarningsAsErrors>
<CheckFilenameConventions>True</CheckFilenameConventions>
</Options>
</KeymanDeveloperProject>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,656 @@
{
"phone": {
"font": "Dotty Keys",
"fontsize": "4em",
"layer": [
{
"id": "default",
"row": [
{
"id": 1,
"key": [
{
"id": "K_Q",
"text": "q"
},
{
"id": "K_W",
"text": "w"
},
{
"id": "K_E",
"text": "e"
},
{
"id": "K_R",
"text": "r"
},
{
"id": "K_T",
"text": "t"
},
{
"id": "K_Y",
"text": "y"
},
{
"id": "K_U",
"text": "u"
},
{
"id": "K_I",
"text": "i"
},
{
"id": "K_O",
"text": "o"
},
{
"id": "K_P",
"text": "p"
}
]
},
{
"id": 2,
"key": [
{
"id": "K_A",
"text": "a",
"pad": "50"
},
{
"id": "K_S",
"text": "s"
},
{
"id": "K_D",
"text": "d"
},
{
"id": "K_F",
"text": "f"
},
{
"id": "K_G",
"text": "g"
},
{
"id": "K_H",
"text": "h"
},
{
"id": "K_J",
"text": "j"
},
{
"id": "K_K",
"text": "k"
},
{
"id": "K_L",
"text": "l"
},
{
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 3,
"key": [
{
"id": "K_SHIFT",
"text": "*Shift*",
"sp": "1",
"nextlayer": "shift"
},
{
"id": "K_Z",
"text": "z"
},
{
"id": "K_X",
"text": "x"
},
{
"id": "K_C",
"text": "c"
},
{
"id": "K_V",
"text": "v"
},
{
"id": "K_B",
"text": "b"
},
{
"id": "K_N",
"text": "n"
},
{
"id": "K_M",
"text": "m"
},
{
"id": "K_PERIOD",
"text": ".",
"sk": [
{
"text": ",",
"id": "K_COMMA"
},
{
"text": "!",
"id": "K_1",
"layer": "shift"
},
{
"text": "?",
"id": "K_SLASH",
"layer": "shift"
},
{
"text": "'",
"id": "K_QUOTE"
},
{
"text": "\"",
"id": "K_QUOTE",
"layer": "shift"
},
{
"text": "\\",
"id": "K_BKSLASH"
},
{
"text": ":",
"id": "K_COLON",
"layer": "shift"
},
{
"text": ";",
"id": "K_COLON"
}
]
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"width": "100",
"sp": "1"
}
]
},
{
"id": 4,
"key": [
{
"id": "K_NUMLOCK",
"text": "*123*",
"width": "150",
"sp": "1",
"nextlayer": "numeric"
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": "120",
"sp": "1"
},
{
"id": "K_SPACE",
"text": "",
"width": "610",
"sp": "0"
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": "150",
"sp": "1"
}
]
}
]
},
{
"id": "shift",
"row": [
{
"id": 1,
"key": [
{
"id": "K_Q",
"text": "Q"
},
{
"id": "K_W",
"text": "W"
},
{
"id": "K_E",
"text": "E"
},
{
"id": "K_R",
"text": "R"
},
{
"id": "K_T",
"text": "T"
},
{
"id": "K_Y",
"text": "Y"
},
{
"id": "K_U",
"text": "U"
},
{
"id": "K_I",
"text": "I"
},
{
"id": "K_O",
"text": "O"
},
{
"id": "K_P",
"text": "P"
}
]
},
{
"id": 2,
"key": [
{
"id": "K_A",
"text": "A",
"pad": "50"
},
{
"id": "K_S",
"text": "S"
},
{
"id": "K_D",
"text": "D"
},
{
"id": "K_F",
"text": "F"
},
{
"id": "K_G",
"text": "G"
},
{
"id": "K_H",
"text": "H"
},
{
"id": "K_J",
"text": "J"
},
{
"id": "K_K",
"text": "K"
},
{
"id": "K_L",
"text": "L"
},
{
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 3,
"key": [
{
"id": "K_SHIFT",
"text": "*Shift*",
"sp": "2",
"nextlayer": "default"
},
{
"id": "K_Z",
"text": "Z"
},
{
"id": "K_X",
"text": "X"
},
{
"id": "K_C",
"text": "C"
},
{
"id": "K_V",
"text": "V"
},
{
"id": "K_B",
"text": "B"
},
{
"id": "K_N",
"text": "N"
},
{
"id": "K_M",
"text": "M"
},
{
"id": "K_PERIOD",
"text": ".",
"layer": "default",
"sk": [
{
"text": ",",
"id": "K_COMMA",
"layer": "default"
},
{
"text": "!",
"id": "K_1",
"layer": "shift"
},
{
"text": "?",
"id": "K_SLASH",
"layer": "shift"
},
{
"text": "'",
"id": "K_QUOTE",
"layer": "default"
},
{
"text": "\"",
"id": "K_QUOTE",
"layer": "shift"
},
{
"text": "\\",
"id": "K_BKSLASH",
"layer": "default"
},
{
"text": ":",
"id": "K_COLON",
"layer": "shift"
},
{
"text": ";",
"id": "K_COLON",
"layer": "default"
}
]
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"sp": "1"
}
]
},
{
"id": 4,
"key": [
{
"id": "K_NUMLOCK",
"text": "*123*",
"width": "150",
"sp": "1",
"nextlayer": "numeric"
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": "120",
"sp": "1"
},
{
"id": "K_SPACE",
"text": "",
"width": "610",
"sp": "0"
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": "150",
"sp": "1"
}
]
}
]
},
{
"id": "numeric",
"row": [
{
"id": 1,
"key": [
{
"id": "K_1",
"text": "1"
},
{
"id": "K_2",
"text": "2"
},
{
"id": "K_3",
"text": "3"
},
{
"id": "K_4",
"text": "4"
},
{
"id": "K_5",
"text": "5"
},
{
"id": "K_6",
"text": "6"
},
{
"id": "K_7",
"text": "7"
},
{
"id": "K_8",
"text": "8"
},
{
"id": "K_9",
"text": "9"
},
{
"id": "K_0",
"text": "0"
}
]
},
{
"id": 2,
"key": [
{
"id": "K_4",
"layer": "shift",
"text": "$",
"pad": "50"
},
{
"id": "K_2",
"layer": "shift",
"text": "@"
},
{
"id": "K_3",
"layer": "shift",
"text": "#"
},
{
"id": "K_5",
"layer": "shift",
"text": "%"
},
{
"id": "K_7",
"layer": "shift",
"text": "&"
},
{
"id": "K_HYPHEN",
"layer": "shift",
"text": "_"
},
{
"id": "K_EQUAL",
"text": "=",
"layer": "default"
},
{
"id": "K_BKSLASH",
"layer": "shift",
"text": "|"
},
{
"id": "K_BKSLASH",
"text": "\\",
"layer": "default"
},
{
"text": "",
"width": "10",
"sp": "10"
}
]
},
{
"id": 3,
"key": [
{
"id": "K_LBRKT",
"text": "[",
"pad": "110",
"sk": [
{
"id": "U_00AB",
"text": "\u00AB"
},
{
"id": "K_COMMA",
"text": "<",
"layer": "shift"
},
{
"id": "K_LBRKT",
"text": "{",
"layer": "shift"
}
]
},
{
"id": "K_9",
"layer": "shift",
"text": "("
},
{
"id": "K_0",
"layer": "shift",
"text": ")"
},
{
"id": "K_RBRKT",
"text": "]",
"sk": [
{
"id": "U_00BB",
"text": "\u00BB"
},
{
"id": "K_PERIOD",
"text": ">",
"layer": "shift"
},
{
"id": "K_RBRKT",
"text": "}",
"layer": "shift"
}
]
},
{
"id": "K_EQUAL",
"layer": "shift",
"text": "+"
},
{
"id": "K_HYPHEN",
"text": "-"
},
{
"id": "K_8",
"layer": "shift",
"text": "*"
},
{
"id": "K_SLASH",
"text": "/"
},
{
"id": "K_BKSP",
"text": "*BkSp*",
"width": "100",
"sp": "1"
}
]
},
{
"id": 4,
"key": [
{
"id": "K_LOWER",
"text": "*abc*",
"width": "150",
"sp": "1",
"nextlayer": "default"
},
{
"id": "K_LOPT",
"text": "*Menu*",
"width": "120",
"sp": "1"
},
{
"id": "K_SPACE",
"text": "",
"width": "610",
"sp": "0"
},
{
"id": "K_ENTER",
"text": "*Enter*",
"width": "150",
"sp": "1"
}
]
}
]
}
]
}
}

View file

@ -0,0 +1,14 @@
c dotty_keys generated from template at 2024-01-26 12:44:31
c with name "dotty_keys"
store(&VERSION) '10.0'
store(&NAME) 'dotty_keys'
store(&COPYRIGHT) 'Copyright © SIL International'
store(&KEYBOARDVERSION) '1.0'
store(&TARGETS) 'any'
store(&BITMAP) 'dotty_keys.ico'
store(&VISUALKEYBOARD) 'dotty_keys.kvks'
store(&LAYOUTFILE) 'dotty_keys.keyman-touch-layout'
begin Unicode > use(main)
group(main) using keys

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>17.0.238.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Options>
<ExecuteProgram></ExecuteProgram>
<ReadMeFile>readme.htm</ReadMeFile>
<LicenseFile>..\LICENSE.md</LicenseFile>
<MSIFileName></MSIFileName>
<MSIOptions></MSIOptions>
<FollowKeyboardVersion/>
</Options>
<StartMenu>
<Folder></Folder>
<Items/>
</StartMenu>
<Info>
<Name URL="">dotty_keys</Name>
<Copyright URL="">Copyright © SIL International</Copyright>
<Author URL="">SIL International</Author>
<Description URL="">A keyboard designed to test key-cap scaling in relation to font loading.
All base a-z keys have a "dot" glyph instead.</Description>
<Version URL=""></Version>
</Info>
<Files>
<File>
<Name>..\build\dotty_keys.kmx</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
<File>
<Name>..\build\dotty_keys.js</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.js</FileType>
</File>
<File>
<Name>..\build\dotty_keys.kvk</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.kvk</FileType>
</File>
<File>
<Name>welcome.htm</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.htm</FileType>
</File>
<File>
<Name>readme.htm</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.htm</FileType>
</File>
<File>
<Name>..\LICENSE.md</Name>
<Description></Description>
<CopyLocation>0</CopyLocation>
<FileType>.md</FileType>
</File>
<File>
<Name>DejaVuDots.ttf</Name>
<Description>Font DejaVu Sans</Description>
<CopyLocation>0</CopyLocation>
<FileType>.ttf</FileType>
</File>
</Files>
<Keyboards>
<Keyboard>
<Name>dotty_keys</Name>
<ID>dotty_keys</ID>
<Version>1.0</Version>
<Languages>
<Language ID="und">Undetermined</Language>
</Languages>
<Examples/>
</Keyboard>
</Keyboards>
<RelatedPackages/>
<Strings/>
</Package>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<visualkeyboard>
<header>
<version>10.0</version>
<kbdname>dotty_keys</kbdname>
<flags/>
</header>
<encoding name="unicode" fontname="Arial" fontsize="48">
<layer shift="">
<key vkey="K_Q">q</key>
<key vkey="K_W">w</key>
<key vkey="K_E">e</key>
<key vkey="K_R">r</key>
<key vkey="K_T">t</key>
<key vkey="K_Y">y</key>
<key vkey="K_U">u</key>
<key vkey="K_I">i</key>
<key vkey="K_O">o</key>
<key vkey="K_P">p</key>
<key vkey="K_A">a</key>
<key vkey="K_S">s</key>
<key vkey="K_D">d</key>
<key vkey="K_F">f</key>
<key vkey="K_G">g</key>
<key vkey="K_H">h</key>
<key vkey="K_K">k</key>
<key vkey="K_J">j</key>
<key vkey="K_L">l</key>
<key vkey="K_COLON">;</key>
<key vkey="K_QUOTE">'</key>
<key vkey="K_LBRKT">[</key>
<key vkey="K_RBRKT">]</key>
<key vkey="K_1">1</key>
<key vkey="K_2">2</key>
<key vkey="K_3">3</key>
<key vkey="K_4">4</key>
<key vkey="K_5">5</key>
<key vkey="K_6">6</key>
<key vkey="K_7">7</key>
<key vkey="K_8">8</key>
<key vkey="K_9">9</key>
<key vkey="K_0">0</key>
<key vkey="K_HYPHEN">-</key>
<key vkey="K_EQUAL">=</key>
<key vkey="K_Z">z</key>
<key vkey="K_X">x</key>
<key vkey="K_C">c</key>
<key vkey="K_V">v</key>
<key vkey="K_B">b</key>
<key vkey="K_N">n</key>
<key vkey="K_M">m</key>
<key vkey="K_COMMA">,</key>
<key vkey="K_PERIOD">.</key>
<key vkey="K_SLASH">/</key>
</layer>
</encoding>
</visualkeyboard>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>dotty_keys</title>
<style type="text/css">
p { font: 10pt Tahoma; }
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
h2 { font: bold 12pt Tahoma; color: #4444cc; }
</style>
</head>
<body>
<h1>dotty_keys</h1>
<p>
A keyboard designed to test key-cap scaling in relation to font loading.
All base a-z keys have a "dot" glyph instead of the actual output character,
and the touch-layout fontsize is set to be extremely large in order to force
application of key-cap scaling.
</p>
<p>Copyright © SIL International</p>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Start Using dotty_keys</title>
<style type="text/css">
p { font: 10pt Tahoma; }
h1 { font: bold 16pt Tahoma; color: #4444cc; margin-bottom: 2px }
h2 { font: bold 12pt Tahoma; color: #4444cc; }
</style>
</head>
<body>
<h1>Start Using dotty_keys</h1>
<p>
A keyboard designed to test key-cap scaling in relation to font loading.
All base a-z keys have a "dot" glyph instead of the actual output character,
and the touch-layout fontsize is set to be extremely large in order to force
application of key-cap scaling.
</p>
<h1>Keyboard Layout</h1>
<!-- Insert Keyboard Layout Images or HTML here -->
</body>
</html>

View file

@ -0,0 +1,15 @@
function loadKeyboards()
{
var kmw=keyman;
kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'},
filename:('../us-1.0.js')});
// Add a fully-specified, locally-sourced, keyboard with custom font
kmw.addKeyboards({id:'dotty_keys',name:'Dotty Keys',
languages:{
id:'und',name:'Special',region:'World',
font:{family:'DejaVu Dots',source:['./DejaVu-Dots.ttf']}
},
filename:'./dotty_keys.js'
});
}

View file

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- Set the viewport width to match phone and tablet device widths -->
<meta name="viewport" content="width=device-width,user-scalable=no" />
<!-- Allow KeymanWeb to be saved to the iPhone home screen -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Enable IE9 Standards mode -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>KeymanWeb PR 10506 - Key-cap scaling / font-load delay</title>
<!-- Your page CSS -->
<style type='text/css'>
body {font-family: Tahoma,helvetica;}
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
#KeymanWebControl {width:50%;min-width:600px;}
</style>
<!-- Insert uncompiled KeymanWeb source scripts -->
<script src="../../../../../build/publish/debug/keymanweb.js" type="application/javascript"></script>
<!--
For desktop browsers, a script for the user interface must be inserted here.
Standard UIs are toggle, button, float and toolbar.
The toolbar UI is best for any page designed to support keyboards for
a large number of languages.
-->
<script src="../../../../../build/publish/debug/kmwuitoggle.js"></script>
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
<script>
var kmw=window.keyman;
kmw.init({
attachType: 'auto'
});
</script>
<!-- Add keyboard management script for local selection of keyboards to use -->
<script src="./header.js"></script>
</head>
<!-- Sample page HTML -->
<body onload='loadKeyboards();'>
<h2>KeymanWeb Test Page - Key-cap scaling / font-load delay interactions</h2>
<p>To run the test, simply change from the default (English) keyboard to the "Special" keyboard.</p>
<p>When things are working properly, the base keys on the "Special" keyboard should contain
evenly-sized dots. On touch devices, they should be fairly large.</p>
<p>If the dots are small and unevenly-sized instead, key-cap scaling is occurring before the keyboard's
font has loaded. (Pay special attention to the QWERTY-layout 'w' and 'm' keys.)</p>
<hr/>
<div>
<!--
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
If omitted, those User Interfaces will appear at the top of the document body.
(It is ignored by other User Interfaces.)
-->
<div id='KeymanWebControl'>
<h3>Type in your language in this text area:</h3>
<textarea id='ta1' class='test' placeholder='Type here'></textarea>
</div>
</div>
<hr/>
<h3><a href="../index.html">Return to testing home page</a></h3>
</body>
<!--
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
*
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
* to display the special characters used in the On-Screen Keyboard.
*
* To work around this Firefox bug, navigate to <b>about:config</b>
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
* while testing.
*
* Firefox resolves website-based CSS URI references correctly without needing
* any configuration change, so this change should only be made for file-based testing.
*
***
-->
</html>

View file

@ -0,0 +1,3 @@
DejaVuSans.sfd can be edited with [FontForge](https://fontforge.org/).
Is based on DejaVuSans.ttf, sloppily edited for the purposes of this test.

View file

@ -212,6 +212,8 @@ typedef struct tagKEYMAN64THREADDATA
BOOL TIPFUpdateable, TIPFPreserved; // I4290
BOOL CoreProcessEventRun; // True if core process event has been run
// TODO: #10583 remove core_actions cache
km_core_actions const *core_actions;
BOOL FInRefreshKeyboards;
BOOL RefreshRequired;

View file

@ -23,37 +23,39 @@ static void processAlert(AITIP* app) {
app->QueueAction(QIT_BELL, 0);
}
static BOOL
static void
processBack(AITIP* app, const unsigned int code_points_to_delete, const km_core_usv* delete_context) {
if (app->IsLegacy()) {
for (unsigned int i = 0; i < code_points_to_delete; i++) {
app->QueueAction(QIT_BACK, BK_DEFAULT);
}
return TRUE;
return;
}
if (!app->IsLegacy()) {
while (*delete_context) {
if (Uni_IsSMP(*delete_context)) {
km_core_usv const* delete_context_ptr = delete_context;
while (*delete_context_ptr) {
delete_context_ptr++;
}
delete_context_ptr--;
for (; delete_context_ptr >= delete_context; delete_context_ptr--) {
if (Uni_IsSMP(*delete_context_ptr)) {
app->QueueAction(QIT_BACK, BK_DEFAULT | BK_SURROGATE);
}
else {
app->QueueAction(QIT_BACK, BK_DEFAULT);
}
delete_context++;
}
return TRUE;
return;
}
return FALSE;
}
static void
processPersistOpt(km_core_actions const* actions, LPINTKEYBOARDINFO activeKeyboard
) {
for (auto option = actions->persist_options; option->key; option++) {
// Put the keyboard option into Windows Registry
// log"Saving keyboard option to registry");
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessHook: Saving option to registry for keyboard [%s].", activeKeyboard->Name);
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessPersistOpt: Saving option to registry for keyboard [%s].", activeKeyboard->Name);
size_t value_length = wcslen(reinterpret_cast<LPCWSTR>(option->value));
LPWSTR value = new WCHAR[value_length + 1];
wcscpy_s(value, value_length + 1, reinterpret_cast<LPCWSTR>(option->value));
@ -101,24 +103,32 @@ BOOL ProcessActions(BOOL* emitKeyStroke)
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) return FALSE;
// TODO: #10583 Remove caching action_struct
if (!_td->core_actions) {
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessActions: core_actions not set");
_td->core_actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
}
_td->CoreProcessEventRun = FALSE;
// Process the action items from the core. This actions will modify the windows context (AppContext).
// Therefore it is not required to copy the context from the core to the windows context.
km_core_actions const* actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
processBack(_td->app, actions->code_points_to_delete, actions->deleted_context);
processUnicodeChar(_td->app, actions->output);
if (actions->persist_options != NULL) {
processPersistOpt(actions, _td->lpActiveKeyboard);
processBack(_td->app, _td->core_actions->code_points_to_delete, _td->core_actions->deleted_context);
processUnicodeChar(_td->app, _td->core_actions->output);
if (_td->core_actions->persist_options != NULL) {
processPersistOpt(_td->core_actions, _td->lpActiveKeyboard);
}
if (actions->do_alert) {
if (_td->core_actions->do_alert) {
processAlert(_td->app);
}
if (actions->emit_keystroke) {
if (_td->core_actions->emit_keystroke) {
*emitKeyStroke = TRUE;
}
processCapsLock(actions->new_caps_lock_state, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
processCapsLock(_td->core_actions->new_caps_lock_state, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
// TODO: #10583 remove dispose
km_core_actions_dispose(_td->core_actions);
_td->core_actions = nullptr;
return TRUE;
}
@ -133,10 +143,16 @@ ProcessActionsNonUpdatableParse(BOOL* emitKeyStroke) {
return FALSE;
}
_td->CoreProcessEventRun = TRUE;
// TODO: #10583 remove dispose
if (_td->core_actions) {
km_core_actions_dispose(_td->core_actions);
_td->core_actions = nullptr;
}
km_core_actions const* acts = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
processCapsLock(acts->new_caps_lock_state, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
if (acts->emit_keystroke) {
_td->core_actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessActionsNonUpdatableParse: km_core_state_get_actions");
processCapsLock(_td->core_actions->new_caps_lock_state, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
if (_td->core_actions->emit_keystroke) {
*emitKeyStroke = TRUE;
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessActionsNonUpdatableParse EMIT_KEYSTROKE");
_td->CoreProcessEventRun = FALSE; // If we emit the key stroke on this parse we don't need the second parse
@ -150,7 +166,11 @@ ProcessActionsExternalEvent() {
if (!_td) {
return FALSE;
}
km_core_actions const* acts = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
processCapsLock(acts->new_caps_lock_state, !_td->state.isDown, FALSE, TRUE);
if (!_td->core_actions) { // when ideponent we will not need this
return FALSE;
}
// TODO: #10583 remove and call get actions here directly
//km_core_actions const* acts = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
processCapsLock(_td->core_actions->new_caps_lock_state, !_td->state.isDown, FALSE, TRUE);
return TRUE;
}