diff --git a/HISTORY.md b/HISTORY.md index e9e48c447e9..f45e1e85cda 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 18.0.88 alpha 2024-08-13 + +* docs: add .kmx specification (#12163) + ## 18.0.87 alpha 2024-08-12 * chore(web): drop flaky auto-test component (#12155) diff --git a/VERSION.md b/VERSION.md index bd7ce16e23c..06ebe83646e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.88 \ No newline at end of file +18.0.89 \ No newline at end of file diff --git a/common/cpp/km_u16.cpp b/common/cpp/km_u16.cpp new file mode 100644 index 00000000000..84a6c4a75e7 --- /dev/null +++ b/common/cpp/km_u16.cpp @@ -0,0 +1,389 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * std::u16string functions and string conversion utility functions + */ + +#include +#include "utfcodec.hpp" +#include "km_u16.h" + +/** string <- wstring + * @brief Obtain a std::string from a std::wstring + * @param wstr the std::wstring to be converted + * @return a std::string + */ +std::string string_from_wstring(std::wstring const wstr) { + return convert((const std::wstring)wstr); +} + +/** wstring <- string + * @brief Obtain a std::wstring from a std::string + * @param str the std::string to be converted + * @return a std::wstring + */ +std::wstring wstring_from_string(std::string const str) { + return convert((const std::string)str); +} + +/** u16string <- string + * @brief Obtain a std::u16string from a std::string + * @param str the std::string to be converted + * @return a std::u16string + */ +std::u16string u16string_from_string(std::string const str) { + return convert((const std::string)str); +} + +/** string <- u16string + * @brief Obtain a std::string from a std::u16string + * @param str16 the std::u16string to be converted + * @return a std::string + */ +std::string string_from_u16string(std::u16string const str16) { + return convert((const std::u16string)str16); +} + +/** wstring <- u16string + * @brief Obtain a std::wstring from a std::u16string + * @param str16 the std::u16string to be converted + * @return a std::wstring + */ +std::wstring wstring_from_u16string(std::u16string const str16) { + return convert((const std::u16string)str16); +} + +/** u16string <- wstring + * @brief Obtain a std::u16string from a std::wstring + * @param wstr the std::wstring to be converted + * @return a std::u16string + */ +std::u16string u16string_from_wstring(std::wstring const wstr) { + return convert((const std::wstring)wstr); +} + +/** + * @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst + * @param dst destination + * @param sz nr of characters to be copied + * @param fmt source to convert and copy + */ +void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...) { + wchar_t* wbuf = new wchar_t[sz]; + va_list args; + va_start(args, fmt); + vswprintf(wbuf, sz, fmt, args); + va_end(args); + + std::u16string u16str = u16string_from_wstring(wbuf); + u16ncpy(dst, u16str.c_str(), sz); + delete[] wbuf; +} + +/** + * @brief Convert u16string to long integer + * @param str u16string beginning with the representation of an integral number. + * @param endptr Reference to the next character in str + * @param base Numerical base (radix) that determines the valid characters and their interpretation + * @return a long + */ +long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) { + auto s = string_from_u16string(str); + char* t; + long int result = strtol(s.c_str(), &t, base); + if (endptr != nullptr) + *endptr = (KMX_WCHAR*)str + (t - s.c_str()); + return result; +} + +std::string toHex(int num1) { + if (num1 == 0) + return "0"; + int num = num1; + std::string s = ""; + while (num) { + int temp = num % 16; + if (temp <= 9) + s += (48 + temp); + else + s += (87 + temp); + num = num / 16; + } + std::reverse(s.begin(), s.end()); + return s; +} + +/** + * @brief Append max characters from u16string + * @param dst Pointer to the destination array + * @param src u16string to be appended + * @param max Maximum number of characters to be appended. + * @return Pointer to dst + */ +const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) { + KMX_WCHAR* o = dst; + dst = (KMX_WCHAR*)u16chr(dst, 0); + // max -= (dst-o); + while (*src && max > 0) { + *dst++ = *src++; + max--; + } + if (max > 0) + *dst = 0; + return o; +} + +/** + * @brief Find last '/' or '\\' in an array of char16_t + * @param name Pointer to the source + * @return Pointer to the last slash/backslash + */ +const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* name) { + const KMX_WCHAR* cp = NULL; + cp = u16rchr(name, '\\'); + if (cp == NULL) + cp = u16rchr(name, '/'); + return cp; +} + +/** + * @brief Find last '/' or '\\' in an array of char + * @param name Pointer to the source + * @return Pointer to the last slash/backslash + */ +KMX_CHAR* strrchr_slash(KMX_CHAR* name) { + KMX_CHAR* cp = NULL; + cp = strrchr(name, '\\'); + if (cp == NULL) + cp = strrchr(name, '/'); + return cp; +} + +/** + * @brief Locate last occurrence of character in u16string + * @param p Pointer to the source + * @param ch The character to be found + * @return A pointer to the last occurrence of character in u16str + */ +const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch) { + const KMX_WCHAR* p_end = p + u16len(p) - 1; + + if (ch == '\0') + return p_end + 1; + while (p_end >= p) { + if (*p_end == ch) + return p_end; + p_end--; + } + return NULL; +} + +/** + * @brief Locate first occurrence of character in u16string + * @param p Pointer to the source + * @param ch The character to be found + * @return A pointer to the first occurrence of character in u16str + */ +const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch) { + while (*p) { + if (*p == ch) + return p; + p++; + } + return ch == 0 ? p : NULL; +} + +/** + * @brief Copy the u16string pointed to by scr into the array pointed to by dst + * @param dst Pointer to the destination + * @param src Pointer to the source to be copied + * @return Pointer to dst + */ +const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src) { + KMX_WCHAR* o = dst; + while (*src) { + *dst++ = *src++; + } + *dst = 0; + return o; +} + +/** + * @brief Copy max characters of the u16string pointed to by src into the array pointed by dst + * @param dst Pointer to the destination + * @param src Pointer to the source to be copied + * @param max Maximum number of characters to be copied + * @return Pointer to dst + */ +const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max) { + KMX_WCHAR* o = dst; + while (*src && max > 0) { + *dst++ = *src++; + max--; + } + if (max > 0) { + *dst = 0; + } + return o; +} + +/** + * @brief Return the length of the u16string str + * @param p Pointer to the source + * @return The length of u16string + */ +size_t u16len(const KMX_WCHAR* p) { + int i = 0; + while (*p) { + p++; + i++; + } + return i; +} + +/** + * @brief Compare two u16strings + * @param p Pointer one u16string + * @param q Pointer another u16string + * @return 0 if strings are equal + * ! = 0 if unequal + */ +int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q) { + while (*p && *q) { + if (*p != *q) + return *p - *q; + p++; + q++; + } + return *p - *q; +} + +/** + * @brief Case insensitive comparison of up to count characters in two strings + * @param p Pointer one u16string + * @param q Pointer another u16string + * @param count Maximum number of characters to compare + * @return 0 if strings are equal + * ! = 0 if unequal + */ +int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) { + while (*p && *q && count) { + if (toupper(*p) != toupper(*q)) + return *p - *q; + p++; + q++; + count--; + } + if (count) + return *p - *q; + return 0; +} + +/** + * @brief Case insensitive comparison of two strings + * @param p Pointer one u16string + * @param q Pointer another u16string + * @return 0 if strings are equal + * ! = 0 if unequal + */ +int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q) { + while (*p && *q) { + if (toupper(*p) != toupper(*q)) + return *p - *q; + p++; + q++; + } + return *p - *q; +} + +/** + * @brief Comparison of up to count characters in two strings + * @param p Pointer one u16string + * @param q Pointer another u16string + * @param count Maximum number of characters to compare + * @return 0 if strings are equal + * ! = 0 if unequal + */ +int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) { + while (*p && *q && count) { + if (*p != *q) + return *p - *q; + p++; + q++; + count--; + } + if (count) + return *p - *q; + return 0; +} + +/** + * @brief Split u16string into tokens + * @param p Pointer to u16string to parse. + * @param ch the delimiter character + * @param ctx the remaining string after the first delimiter + * @return Pointer to the first token in p + */ +KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx) { + if (!p) { + p = *ctx; + if (!p) + return NULL; + } + + KMX_WCHAR* q = p; + while (*q && *q != ch) { + q++; + } + if (*q) { + *q = 0; + q++; + while (*q == ch) + q++; + *ctx = q; + } else { + *ctx = NULL; + } + return *p ? p : NULL; +} + +/** + * @brief Split u16string into tokens + * @param p Pointer to u16string to parse. + * @param delimiters an array of delimiter characters + * @param ctx the remaining string after the first delimiter + * @return Pointer to the first token in p + */ +KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx) { + if (!p) { + p = *ctx; + if (!p) + return NULL; + } + + KMX_WCHAR* q = p; + while (*q && !u16chr(delimiters, *q)) { + q++; + } + if (*q) { + *q = 0; + q++; + while (*q && u16chr(delimiters, *q)) + q++; + *ctx = q; + } else { + *ctx = NULL; + } + return *p ? p : NULL; +} + +/** + * @brief Convert a u16string to a double + * @param str Pointer to u16string + * @return double value equivalent to the string + */ +double u16tof(KMX_WCHAR* str16) { + char* pEnd; + std::string str = string_from_u16string(str16); + return strtof(str.c_str(), &pEnd); +} diff --git a/core/src/utfcodec.cpp b/common/cpp/utfcodec.cpp similarity index 100% rename from core/src/utfcodec.cpp rename to common/cpp/utfcodec.cpp diff --git a/common/include/km_u16.h b/common/include/km_u16.h new file mode 100644 index 00000000000..f0c2c2d16c1 --- /dev/null +++ b/common/include/km_u16.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +/** @brief Obtain a std::string from a std::wstring */ +std::string string_from_wstring(std::wstring const wstr); + +/** @brief Obtain a std::wstring from a std::string */ +std::wstring wstring_from_string(std::string const str); + +/** @brief Obtain a std::u16string from a std::string */ +std::u16string u16string_from_string(std::string const str); + +/** @brief Obtain a std::string from a std::u16string */ +std::string string_from_u16string(std::u16string const str16); + +/** @brief Obtain a std::wstring from a std::u16string */ +std::wstring wstring_from_u16string(std::u16string const str16); + +/** @brief Obtain a std::u16string from a std::wstring */ +std::u16string u16string_from_wstring(std::wstring const wstr); + +/** @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst */ +void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...); + +/** @brief Return the length of the u16string str */ +size_t u16len(const KMX_WCHAR* p); + +/** @brief Compare two u16strings */ +int u16cmp(const KMX_WCHAR* p, const KMX_WCHAR* q); + +/** @brief Case insensitive comparison of two strings */ +int u16icmp(const KMX_WCHAR* p, const KMX_WCHAR* q); + +/** @brief Comparison of up to count characters in two strings */ +int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count); + +/** @brief Case insensitive comparison of up to count characters in two strings */ +int u16nicmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count); + +/** @brief Copy max characters of the u16string pointed to by src into the array pointed to by dst */ +const KMX_WCHAR* u16ncpy(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max); + +/** @brief Copy the u16string pointed to by src into the array pointed to by dst */ +const KMX_WCHAR* u16cpy(KMX_WCHAR* dst, const KMX_WCHAR* src); + +/** @brief Locate last occurrence of character in u16string */ +const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch); + +/** @brief Locate first occurrence of character in u16string */ +const KMX_WCHAR* u16chr(const KMX_WCHAR* p, KMX_WCHAR ch); + +/** @brief Append max characters from u16string */ +const KMX_WCHAR* u16ncat(KMX_WCHAR* dst, const KMX_WCHAR* src, size_t max); + +/** @brief Split u16string into tokens */ +KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx); + +/** @brief Split u16string into tokens */ +KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx); + +/** @brief Convert a u16string to a double */ +long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base); + +/** @brief Convert a u16string to a double */ +double u16tof(KMX_WCHAR* str); + +/** @brief find last '/' or '\\' in an array of char */ +KMX_CHAR* strrchr_slash(KMX_CHAR* Name); + +/** @brief find last '/' or '\\' in an array of char16_t */ +const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name); + +std::string toHex(int num1); diff --git a/core/src/utfcodec.hpp b/common/include/utfcodec.hpp similarity index 100% rename from core/src/utfcodec.hpp rename to common/include/utfcodec.hpp diff --git a/core/doc/meson.build b/core/doc/meson.build index 6039e2e2dda..a0f2bc4e6d5 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -20,7 +20,7 @@ if hotdoc.found() deps = files( '../include/keyman/keyman_core_api.h', '../src/jsonpp.hpp', - '../src/utfcodec.hpp' + '../../common/cpp/utfcodec.hpp' ) docs = custom_target('docs', diff --git a/core/src/meson.build b/core/src/meson.build index 212b56f1c22..41c198543cb 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -126,7 +126,7 @@ core_files = files( 'keyboard.cpp', 'state.cpp', 'jsonpp.cpp', - 'utfcodec.cpp', + '../../common/cpp/utfcodec.cpp', ) mock_files = files( diff --git a/core/tests/meson.build b/core/tests/meson.build index 84c17d4220c..90e0ab2a54b 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -10,7 +10,10 @@ cmpfiles = ['-c', 'import sys; a = open(sys.argv[1], \'r\').read(); b = open(sys.argv[2], \'r\').read(); exit(not (a==b))'] stnds = join_paths(meson.current_source_dir(), 'standards') -libsrc = include_directories(join_paths('../', 'src')) +libsrc = include_directories( + '../src', + '../../common/include' +) # kmx_test_source is required for linux builds, so always enable it even when we # disable all other tests diff --git a/core/tests/unit/utftest/meson.build b/core/tests/unit/utftest/meson.build index eff79c10b7e..4ab6cb74ca0 100644 --- a/core/tests/unit/utftest/meson.build +++ b/core/tests/unit/utftest/meson.build @@ -5,6 +5,6 @@ # e = executable('utftest', 'utftest.cpp', - objects: lib.extract_objects('utfcodec.cpp'), + objects: lib.extract_objects('../../common/cpp/utfcodec.cpp'), include_directories: [libsrc]) test('utftest', e) diff --git a/developer/src/kmcmplib/include/kmcompx.h b/developer/src/kmcmplib/include/kmcompx.h index 7e7b2704961..9d7ca75508b 100644 --- a/developer/src/kmcmplib/include/kmcompx.h +++ b/developer/src/kmcmplib/include/kmcompx.h @@ -3,11 +3,6 @@ // TODO: merge with kmcmplib.h -typedef wchar_t KMX_WCHART; -typedef KMX_DWORD * PKMX_DWORD; -typedef char * PKMX_STR; -typedef KMX_WCHAR* PKMX_WCHAR ; - // TODO: these defines are copied out of unicode.h, because unicode.h still // has windows-specific types. These should be remerged at a future date diff --git a/developer/src/kmcmplib/src/CasedKeys.cpp b/developer/src/kmcmplib/src/CasedKeys.cpp index 0a0508f11a4..0883c401ce3 100644 --- a/developer/src/kmcmplib/src/CasedKeys.cpp +++ b/developer/src/kmcmplib/src/CasedKeys.cpp @@ -1,13 +1,14 @@ #include "pch.h" +#include "km_u16.h" +#include "../../../../common/windows/cpp/include/vkeys.h" + #include "compfile.h" #include "kmn_compiler_errors.h" -#include "../../../../common/windows/cpp/include/vkeys.h" #include "kmcmplib.h" #include "CharToKeyConversion.h" -#include "kmx_u16.h" #include "xstring.h" namespace kmcmp { diff --git a/developer/src/kmcmplib/src/CheckForDuplicates.cpp b/developer/src/kmcmplib/src/CheckForDuplicates.cpp index 9e5d63b9ef8..e0fa0094bcb 100644 --- a/developer/src/kmcmplib/src/CheckForDuplicates.cpp +++ b/developer/src/kmcmplib/src/CheckForDuplicates.cpp @@ -1,11 +1,11 @@ #include "pch.h" +#include #include "compfile.h" #include #include "kmcmplib.h" #include -#include "kmx_u16.h" #include #include "CheckForDuplicates.h" diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 361a97c2d7b..3893a728559 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -102,7 +102,7 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" -#include "kmx_u16.h" +#include "km_u16.h" /* These macros are adapted from winnt.h and legacy use only */ #define MAKELANGID(p, s) ((((uint16_t)(s)) << 10) | (uint16_t)(p)) @@ -580,7 +580,7 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) { //swprintf(tstr, "%d", fk->currentGroup); /* Record a system store for the line number of the begin statement */ //wcscpy(tstr, DEBUGSTORE_MATCH); - u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_MATCH).c_str(), (int) fk->currentGroup); + u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_MATCH_L, (int) fk->currentGroup); u16ncat(tstr, gp->szName, _countof(tstr)); AddDebugStore(fk, tstr); @@ -619,7 +619,7 @@ KMX_BOOL ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) { { KMX_WCHAR tstr[128]; /* Record a system store for the line number of the begin statement */ - u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_NOMATCH).c_str(), (int) fk->currentGroup); + u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_NOMATCH_L, (int) fk->currentGroup); u16ncat(tstr, gp->szName, _countof(tstr)); AddDebugStore(fk, tstr); } @@ -683,7 +683,7 @@ KMX_BOOL ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) { KMX_WCHAR tstr[128]; /* Record a system store for the line number of the begin statement */ - u16sprintf(tstr, _countof(tstr), L"%ls%d ", u16fmt(DEBUGSTORE_GROUP).c_str(), fk->cxGroupArray - 1); + u16sprintf(tstr, _countof(tstr), L"%ls%d ", DEBUGSTORE_GROUP_L, fk->cxGroupArray - 1); u16ncat(tstr, gp->szName, _countof(tstr)); AddDebugStore(fk, tstr); } @@ -3761,7 +3761,7 @@ void kmcmp::RecordDeadkeyNames(PFILE_KEYBOARD fk) KMX_DWORD i; for (i = 0; i < fk->cxDeadKeyArray; i++) { - u16sprintf(buf, _countof(buf), L"%ls%d ", u16fmt(DEBUGSTORE_DEADKEY).c_str(), (int)i); + u16sprintf(buf, _countof(buf), L"%ls%d ", DEBUGSTORE_DEADKEY_L, (int)i); u16ncat(buf, fk->dpDeadKeyArray[i].szName, _countof(buf)); AddDebugStore(fk, buf); diff --git a/developer/src/kmcmplib/src/UnreachableRules.cpp b/developer/src/kmcmplib/src/UnreachableRules.cpp index c84bdfb1f5e..40965db4d67 100644 --- a/developer/src/kmcmplib/src/UnreachableRules.cpp +++ b/developer/src/kmcmplib/src/UnreachableRules.cpp @@ -18,7 +18,7 @@ namespace kmcmp { std::wstringstream key; key << kp->Key << "," << kp->ShiftFlags << ","; if (kp->dpContext) { - std::wstring Context_ws = u16fmt((const PKMX_WCHAR) kp->dpContext); + std::wstring Context_ws = wstring_from_u16string((const PKMX_WCHAR) kp->dpContext); key << Context_ws; } return key.str(); diff --git a/developer/src/kmcmplib/src/debugstore.h b/developer/src/kmcmplib/src/debugstore.h index c54e3b45c34..d8bd4643df0 100644 --- a/developer/src/kmcmplib/src/debugstore.h +++ b/developer/src/kmcmplib/src/debugstore.h @@ -5,16 +5,16 @@ #define DEBUGSTORE_BEGIN u"B" #define DEBUGSTORE_BEGIN_C u'B' -#define DEBUGSTORE_MATCH u"M" -#define DEBUGSTORE_MATCH_C u'M' +#define DEBUGSTORE_MATCH_U u"M" +#define DEBUGSTORE_MATCH_L L"M" -#define DEBUGSTORE_NOMATCH u"N" -#define DEBUGSTORE_NOMATCH_C u'N' +#define DEBUGSTORE_NOMATCH_U u"M" +#define DEBUGSTORE_NOMATCH_L L"M" -#define DEBUGSTORE_GROUP u"G" -#define DEBUGSTORE_GROUP_C u'G' +#define DEBUGSTORE_GROUP_U u"G" +#define DEBUGSTORE_GROUP_L L"G" -#define DEBUGSTORE_DEADKEY u"D" -#define DEBUGSTORE_DEADKEY_C u'D' +#define DEBUGSTORE_DEADKEY_U u"D" +#define DEBUGSTORE_DEADKEY_L L"D" #endif /* DEBUGSTORE_H */ diff --git a/developer/src/kmcmplib/src/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp deleted file mode 100644 index 02e59f8c12b..00000000000 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ /dev/null @@ -1,293 +0,0 @@ - -//#include "../../kmcompx/include/kmcompx.h" -#include -#include "kmx_u16.h" - -#include -#include -#include -#include -#include -#include -#include - -//String <- wstring -std::string string_from_wstring(std::wstring const str) { - std::wstring_convert, wchar_t> converter; - return converter.to_bytes(str); -} -//wstring <- string -std::wstring wstring_from_string(std::string const str) { - std::wstring_convert, wchar_t> converter; - return converter.from_bytes(str); -} - -//u16String <- string -std::u16string u16string_from_string(std::string const str) { - std::wstring_convert, char16_t> converter; - return converter.from_bytes(str); -} - -//string <- u16string -std::string string_from_u16string(std::u16string const str) { - std::wstring_convert, char16_t> converter; - return converter.to_bytes(str); -} - -// often used with c_str() e.g. u16fmt( DEBUGSTORE_MATCH).c_str() -// UTF16 (= const char16_t*) -> UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit) -std::wstring u16fmt(const KMX_WCHAR * str) { - std::wstring_convert, wchar_t> convert_wstring; - std::wstring_convert, char16_t> convert; - - // UTF16 (= const char16_t*) -> UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit) - std::string utf8str = convert.to_bytes(str); // UTF16 (= const char16_t*) -> UTF8 (= std::string) - std::wstring wstr = convert_wstring.from_bytes(utf8str); // UTF8 (= std::string) -> UTF16 ( = std::wstring 16 bit) - return wstr; -} - -void u16sprintf(KMX_WCHAR * dst, const size_t sz, const wchar_t* fmt, ...) { - // UTF16 (=const wchar_t*) -> -> std::string -> std::u16string -> UTF16 ( = char16_t*) - wchar_t* wbuf = new wchar_t[sz]; - va_list args; - va_start(args, fmt); - vswprintf(wbuf, sz, fmt, args); - va_end(args); - - std::wstring_convert, wchar_t> convert_wstring; - std::wstring_convert, char16_t> convert; - - // UTF16 (=const wchar_t*) -> -> std::string -> std::u16string -> UTF16 ( = char16_t*) - std::string utf8str = convert_wstring.to_bytes(wbuf); // UTF16 ( = const wchar_t*) -> std::string - std::u16string u16str = convert.from_bytes(utf8str); // std::string -> std::u16string - u16ncpy(dst, u16str.c_str(), sz); // std::u16string.c_str() -> char16_t* - delete[] wbuf; -} - - std::wstring convert_pchar16T_To_wstr(KMX_WCHAR *Name){ - // convert char16_t* -> std::u16string -> std::string -> std::wstring - // char16_t* -> std::u16string - std::u16string u16str(Name); - // std::u16string -> std::string - std::string stri = string_from_u16string(u16str); - // std::string -> std::wstring - std::wstring wstr = wstring_from_string(stri); - return wstr; - } - -long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) -{ - auto s = string_from_u16string(str); - char* t; - long int result = strtol(s.c_str(), &t, base); - if (endptr != nullptr) *endptr = (KMX_WCHAR*)str + (t - s.c_str()); - return result; -} - -std::string toHex(int num1) { - if (num1 == 0) - return "0"; - int num = num1; - std::string s = ""; - while (num) { - int temp = num % 16; - if (temp <= 9) - s += (48 + temp); - else - s += (87 + temp); - num = num / 16; - } - std::reverse(s.begin(), s.end()); - return s; -} - -const KMX_WCHAR * u16ncat(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max) { - KMX_WCHAR* o = dst; - dst = (KMX_WCHAR*) u16chr(dst, 0); - //max -= (dst-o); - while (*src && max > 0) { - *dst++ = *src++; - max--; - } - if(max > 0) - *dst = 0; - return o; -} - -const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name) -{ - const KMX_WCHAR* cp = NULL; - cp = u16rchr(Name, '\\'); - if (cp == NULL) - cp = u16rchr(Name, '/'); - return cp; -} - -KMX_CHAR* strrchr_slash(KMX_CHAR* Name) -{ - KMX_CHAR* cp = NULL; - cp = strrchr(Name, '\\'); - if (cp == NULL) - cp = strrchr(Name, '/'); - return cp; -} - -// u16rchr returns last occurence of ch in p; It returns NULL if ch = '\0' and NULL if ch is not found -const KMX_WCHAR* u16rchr(const KMX_WCHAR* p, KMX_WCHAR ch) { - const KMX_WCHAR* p_end = p + u16len(p) - 1; - - if (ch == '\0') return p_end + 1; - while (p_end >= p) { - if (*p_end == ch) return p_end; - p_end--; - } - return NULL; -} - -const KMX_WCHAR * u16chr(const KMX_WCHAR *p, KMX_WCHAR ch) { - while (*p) { - if (*p == ch) return p; - p++; - } - return ch == 0 ? p : NULL; -} - -const KMX_WCHAR * u16cpy(KMX_WCHAR *dst, const KMX_WCHAR *src) { - KMX_WCHAR *o = dst; - while (*src) { - *dst++ = *src++; - } - *dst = 0; - return o; -} - -const KMX_WCHAR * u16ncpy(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max) { - KMX_WCHAR *o = dst; - while (*src && max > 0) { - *dst++ = *src++; - max--; - } - if(max > 0) { - *dst = 0; - } - return o; -} - -size_t u16len(const KMX_WCHAR *p) { - int i = 0; - while (*p) { - p++; - i++; - } - return i; -} - -int u16cmp(const KMX_WCHAR *p, const KMX_WCHAR *q) { - while (*p && *q) { - if (*p != *q) return *p - *q; - p++; - q++; - } - return *p - *q; -} - -int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) { - while (*p && *q && count) { - if (toupper(*p) != toupper(*q)) return *p - *q; - p++; - q++; - count--; - } - if (count) - return *p - *q; - return 0; -} - -int u16icmp(const KMX_WCHAR *p, const KMX_WCHAR *q) { - while (*p && *q) { - if (toupper(*p) != toupper(*q)) return *p - *q; - p++; - q++; - } - return *p - *q; -} - -int u16ncmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) { - while (*p && *q && count) { - if (*p != *q) return *p - *q; - p++; - q++; - count--; - } - if (count) - return *p - *q; - return 0; -} - -KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) { - if (!p) { - p = *ctx; - if (!p) return NULL; - } - - KMX_WCHAR *q = p; - while (*q && *q != ch) { - q++; - } - if (*q) { - *q = 0; - q++; - while (*q == ch) q++; - *ctx = q; - } - else { - *ctx = NULL; - } - return *p ? p : NULL; -} - -KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* delim, KMX_WCHAR** ctx) { - if (!p) { - p = *ctx; - if (!p) return NULL; - } - - KMX_WCHAR * q = p; - while (*q && !u16chr(delim, *q)) { - q++; - } - if (*q) { - *q = 0; - q++; - while (*q && u16chr(delim, *q)) q++; - *ctx = q; - } - else { - *ctx = NULL; - } - return *p ? p : NULL; -} - -double u16tof( KMX_WCHAR* str) -{ - double val = 0; - int offsetdot=0; - char digit; - - PKMX_WCHAR q = (PKMX_WCHAR)u16chr(str, '.'); - size_t pos_dot = (q-str < 0) ? u16len(str) : q-str; - - for (size_t i = 0; i < u16len(str); i++) - { - digit = static_cast(towupper(*str)); - - if (i > pos_dot - 1) - offsetdot = 1; - - if (digit != '.') - val =val+ ((int(digit)) - 48) * pow(10, (pos_dot - 1- i + offsetdot)); - - str++; - } - return val; -} diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/developer/src/kmcmplib/src/kmx_u16.h deleted file mode 100644 index 450c400e2f9..00000000000 --- a/developer/src/kmcmplib/src/kmx_u16.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include "kmcompx.h" - -std::string string_from_wstring(std::wstring const str); -std::wstring wstring_from_string(std::string const str); -std::u16string u16string_from_string(std::string const str); -std::string string_from_u16string(std::u16string const str); - -std::wstring u16fmt(const KMX_WCHAR * str); -void u16sprintf(KMX_WCHAR * dst, const size_t sz, const wchar_t* fmt, ...) ; - -std::wstring convert_pchar16T_To_wstr(KMX_WCHAR *Name); - -size_t u16len(const KMX_WCHAR *p); -int u16cmp(const KMX_WCHAR *p, const KMX_WCHAR *q); -int u16icmp(const KMX_WCHAR *p, const KMX_WCHAR *q); -int u16ncmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count); -int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) ; -const KMX_WCHAR * u16ncpy(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max); -const KMX_WCHAR * u16cpy(KMX_WCHAR *dst, const KMX_WCHAR *src); -const KMX_WCHAR * u16rchr(const KMX_WCHAR *p, KMX_WCHAR ch) ; -const KMX_WCHAR * u16chr(const KMX_WCHAR *p, KMX_WCHAR ch) ; -const KMX_WCHAR * u16ncat(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max); -KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) ; -KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* ch, KMX_WCHAR** ctx) ; -long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) ; -double u16tof( KMX_WCHAR* str); - -KMX_CHAR* strrchr_slash(KMX_CHAR* Name); -const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name); - -std::string toHex(int num1); diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 2b9a431e090..51acf704090 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -78,13 +78,14 @@ lib = library('kmcmplib', 'cp1252.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', - 'kmx_u16.cpp', 'NamedCodeConstants.cpp', 'UnreachableRules.cpp', 'uset-api.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', 'xstring.cpp', + '../../../../common/cpp/km_u16.cpp', + '../../../../common/cpp/utfcodec.cpp', '../../../../common/windows/cpp/src/ConvertUTF.c', '../../../../common/windows/cpp/src/crc32.cpp', '../../../../common/windows/cpp/src/vkeys.cpp', diff --git a/developer/src/kmcmplib/src/pch.h b/developer/src/kmcmplib/src/pch.h index 358e28998ab..80bb639fb8b 100644 --- a/developer/src/kmcmplib/src/pch.h +++ b/developer/src/kmcmplib/src/pch.h @@ -4,7 +4,7 @@ #define USE_CHAR16_T #include -#include "kmx_u16.h" +#include #include #include "../../../../common/windows/cpp/include/crc32.h" diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp index 22b38dccc67..1c205976452 100644 --- a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -1,7 +1,7 @@ #include +#include #include "../include/kmcompx.h" #include "../include/kmcmplibapi.h" -#include "../src/kmx_u16.h" #include "../src/compfile.h" #include "../src/CompilerErrors.h" #include "../../common/include/kmn_compiler_errors.h" diff --git a/developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp similarity index 94% rename from developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp rename to developer/src/kmcmplib/tests/gtest-km_u16-test.cpp index aa1dca1de08..b67957bd317 100644 --- a/developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp +++ b/developer/src/kmcmplib/tests/gtest-km_u16-test.cpp @@ -1,9 +1,9 @@ #include -#include "../src/kmx_u16.h" +#include +#include #include "../src/compfile.h" -#include "../../../../common/include/km_types.h" -TEST(kmx_u16_Test, u16chr) { +TEST(km_u16_Test, u16chr) { KMX_WCHAR str[LINESIZE]; u16cpy(str, u"abc"); @@ -14,7 +14,7 @@ TEST(kmx_u16_Test, u16chr) { EXPECT_EQ(3, u16chr(str, '\0') - str); // locate null terminator } -TEST(kmx_u16_Test, u16chr_compare_to_strchr) { +TEST(km_u16_Test, u16chr_compare_to_strchr) { // Compare behaviour of strchr: char str[LINESIZE]; @@ -26,7 +26,7 @@ TEST(kmx_u16_Test, u16chr_compare_to_strchr) { EXPECT_EQ(3, strchr(str, '\0') - str); // locate null terminator } -TEST(kmx_u16_Test, u16tok_char_delim) { +TEST(km_u16_Test, u16tok_char_delim) { // For char delimiter: KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) ; KMX_WCHAR str[LINESIZE]; @@ -67,7 +67,7 @@ TEST(kmx_u16_Test, u16tok_char_delim) { EXPECT_TRUE(!u16cmp(u"def", ctx)); } -TEST(kmx_u16_Test, u16tok_str_delim) { +TEST(km_u16_Test, u16tok_str_delim) { // For string delimiter: KMX_WCHAR * u16tok(KMX_WCHAR* p, const KMX_WCHAR* ch, KMX_WCHAR** ctx) ; KMX_WCHAR str[LINESIZE]; @@ -126,7 +126,7 @@ TEST(kmx_u16_Test, u16tok_str_delim) { EXPECT_EQ(nullptr, ctx); } -TEST(kmx_u16_Test, u16tok_str_compare_to_strtok) { +TEST(km_u16_Test, u16tok_str_compare_to_strtok) { // Compare behaviour of strtok: char str[LINESIZE]; diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index a5b6476c889..65915d1e9d9 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -168,7 +168,7 @@ gtestcompilertest = executable('gtest-compiler-test', 'gtest-compiler-test.cpp', test('gtest-compiler-test', gtestcompilertest) -gtestkmx_u16test = executable('gtest-kmx_u16-test', 'gtest-kmx_u16-test.cpp', +gtest_km_u16_test = executable('gtest-km_u16-test', 'gtest-km_u16-test.cpp', cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, @@ -177,4 +177,4 @@ gtestkmx_u16test = executable('gtest-kmx_u16-test', 'gtest-kmx_u16-test.cpp', dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], ) -test('gtest-kmx_u16-test', gtestkmx_u16test) +test('gtest-km_u16-test', gtest_km_u16_test) diff --git a/developer/src/kmcmplib/tests/util_filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp index 7f9edd6b367..0873ff13288 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -117,23 +117,10 @@ FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode) { #endif }; -FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode) { -#ifdef _MSC_VER - std::wstring cpath = Filename; //, cmode = mode; - std::replace(cpath.begin(), cpath.end(), '/', '\\'); - return _wfsopen(cpath.c_str(), mode, _SH_DENYWR); -#else - std::string cpath, cmode; - cpath = string_from_wstring(Filename); - cmode = string_from_wstring(mode); - return fopen_wrapper(cpath.c_str(), cmode.c_str()); -#endif -}; - FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode) { #ifdef _MSC_VER - std::wstring cpath = convert_pchar16T_To_wstr((KMX_WCHAR*) Filename); - std::wstring cmode = convert_pchar16T_To_wstr((KMX_WCHAR*) mode); + std::wstring cpath = wstring_from_u16string(Filename); + std::wstring cmode = wstring_from_u16string(mode); std::replace(cpath.begin(), cpath.end(), '/', '\\'); return _wfsopen(cpath.c_str(), cmode.c_str(), _SH_DENYWR); #else diff --git a/developer/src/kmcmplib/tests/util_filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h index 4c94dae6bf4..c35967b05e8 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -1,12 +1,11 @@ #pragma once #include -#include "../src/kmx_u16.h" +#include // Opens files on windows and non-windows platforms. Datatypes for Filename and mode must be the same. // returns FILE* if file could be opened; FILE needs to be closed in calling function FILE* Open_File(const KMX_CHAR* Filename, const KMX_CHAR* mode); -FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode); FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode); KMX_BOOL kmcmp_FileExists(const KMX_CHAR *filename); KMX_BOOL kmcmp_FileExists(const KMX_WCHAR *filename); diff --git a/web/src/engine/osk/src/config/commonConfiguration.ts b/web/src/engine/osk/src/config/commonConfiguration.ts index 6782d4a0115..36d06f29749 100644 --- a/web/src/engine/osk/src/config/commonConfiguration.ts +++ b/web/src/engine/osk/src/config/commonConfiguration.ts @@ -2,6 +2,7 @@ import { DeviceSpec } from "@keymanapp/web-utils"; import EmbeddedGestureConfig from './embeddedGestureConfig.js'; import { OSKResourcePathConfiguration } from "keyman/engine/interfaces"; +import { GestureParams } from "../input/gestures/specsForLayout.js"; export default interface CommonConfiguration { /** @@ -30,4 +31,9 @@ export default interface CommonConfiguration { * embedded within a WebView. */ embeddedGestureConfig?: EmbeddedGestureConfig; + + /** + * Specifies the gesture parameterizations to use for the active keyboard. + */ + gestureParams?: GestureParams; } \ No newline at end of file diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 943d8e91b96..34d5576e236 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -21,12 +21,16 @@ import { calcLockedDistance, lockedAngleForDir, MAX_TOLERANCE_ANGLE_SKEW, type O import specs = gestures.specs; export interface GestureParams { - longpress: { + readonly longpress: { /** - * Allows enabling or disabling the longpress up-flick shortcut for keyboards that do not - * include any defined flick gestures. + * Allows enabling or disabling the longpress up-flick shortcut for + * keyboards that do not include any defined flick gestures. + * + * Will be ignored (in favor of `false`) for keyboards that do have defined + * flicks. * - * Will be ignored (in favor of `false`) for keyboards that do have defined flicks. + * Note: this is automatically overwritten during keyboard initialization + * to match the keyboard's properties. */ permitsFlick: (item?: Item) => boolean, @@ -57,7 +61,7 @@ export interface GestureParams { */ waitLength: number }, - multitap: { + readonly multitap: { /** * The duration (in ms) permitted between taps. Taps with a greater time interval * between them will be considered separate. @@ -70,7 +74,7 @@ export interface GestureParams { */ holdLength: number; }, - flick: { + readonly flick: { /** * The minimum _net_ touch-path distance that must be traversed to "lock in" on * a flick gesture. When keys support both longpresses and flicks, this distance @@ -248,7 +252,14 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge const _initialTapModel: GestureModel = deepCopy(!doRoaming ? initialTapModel(params) : initialTapModelWithReset(params)); const _simpleTapModel: GestureModel = deepCopy(!doRoaming ? simpleTapModel(params) : simpleTapModelWithReset(params)); - const _longpressModel: GestureModel = deepCopy(longpressModel(params, true, doRoaming)); + // Ensure all deep-copy operations for longpress modeling occur before the property-redefining block. + const _longpressModel: GestureModel = withKeySpecFiltering(deepCopy(longpressModel(params, true, doRoaming)), 0); + + // `deepCopy` does not preserve property definitions, instead raw-copying its value. + // We need to re-instate the longpress delay property here. + Object.defineProperty(_longpressModel.contacts[0].model.timer, 'duration', { + get: () => params.longpress.waitLength + }); // #region Functions for implementing and/or extending path initial-state checks function withKeySpecFiltering(model: GestureModel, contactIndices: number | number[]) { @@ -281,7 +292,7 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge const specialStartModel = specialKeyStartModel(); const _modipressStartModel = modipressStartModel(); const gestureModels: GestureModel[] = [ - withKeySpecFiltering(_longpressModel, 0), + _longpressModel, withKeySpecFiltering(multitapStartModel(params), 0), multitapEndModel(params), _initialTapModel, @@ -481,7 +492,8 @@ export function longpressContactModel(params: GestureParams, enabledFlicks: bool itemPriority: 0, pathResolutionAction: 'resolve', timer: { - duration: spec.waitLength, + // Needs to be a getter so that it dynamically updates if the backing value is changed. + get duration() { return spec.waitLength }, expectedResult: true }, validateItem: (_: KeyElement, baseKey: KeyElement) => !!baseKey?.key.spec.sk, diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index afdc99f882a..d55eb8a8f8d 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -26,6 +26,7 @@ import { EventListener, KeyEventHandler, KeyEventSourceInterface, LegacyEventEmi import Configuration from '../config/viewConfiguration.js'; import Activator, { StaticActivator } from './activator.js'; import TouchEventPromiseMap from './touchEventPromiseMap.js'; +import { DEFAULT_GESTURE_PARAMS, GestureParams } from '../input/gestures/specsForLayout.js'; // These will likely be eliminated from THIS file at some point.\ @@ -172,6 +173,18 @@ export default abstract class OSKView metadata: KeyboardProperties }; + /** + * Provides the current parameterization for timings and distances used by + * any gesture-supporting keyboards. Changing properties of its objects will + * automatically update keyboards to use the new configuration. + * + * If `gestureParams` was set in the configuration object passed in at + * construction time, this will be the same instance. + */ + get gestureParams(): GestureParams { + return this.config.gestureParams; + } + /** * The configured width for this OSKManager. May be `undefined` or `null` * to allow automatic width scaling. @@ -216,6 +229,8 @@ export default abstract class OSKView // Clone the config; do not allow object references to be altered later. this.config = configuration = {...configuration}; + // If gesture parameters were not provided in advance, initialize them from defaults. + this.config.gestureParams ||= DEFAULT_GESTURE_PARAMS; // `undefined` is falsy, but we want a `true` default behavior for this config property. if(this.config.allowHideAnimations === undefined) { @@ -820,7 +835,8 @@ export default abstract class OSKView family: 'SpecialOSK', files: [`${resourcePath}/keymanweb-osk.ttf`], path: '' // Not actually used. - } + }, + gestureParams: this.config.gestureParams }); vkbd.on('keyevent', (keyEvent, callback) => this.emit('keyevent', keyEvent, callback)); diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index fac43d51250..a8152a0db0a 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -151,8 +151,8 @@ export default class VisualKeyboard extends EventEmitter implements Ke /** * Tweakable gesture parameters referenced by supported gestures and the gesture engine. */ - readonly gestureParams: GestureParams = { - ...DEFAULT_GESTURE_PARAMS, + get gestureParams(): GestureParams { + return this.config.gestureParams; }; // Legacy alias, maintaining a reference for code built against older @@ -307,6 +307,10 @@ export default class VisualKeyboard extends EventEmitter implements Ke this.isStatic = config.isStatic; } + this.config.gestureParams ||= { + ...DEFAULT_GESTURE_PARAMS, + }; + this._fixedWidthScaling = this.device.touchable && !this.isStatic; this._fixedHeightScaling = this.device.touchable && !this.isStatic; diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js index ba4808e66a7..6bd0f7555cb 100644 --- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js +++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.spec.js @@ -118,6 +118,65 @@ describe("PredictionContext", () => { assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'apps'); }); + it('ignores outdated predictions', async function () { + const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); + await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. + + const kbdProcessor = new KeyboardProcessor(deviceSpec); + const predictiveContext = new PredictionContext(langProcessor, kbdProcessor); + + let updateFake = sinon.fake(); + predictiveContext.on('update', updateFake); + + let mock = new Mock("appl", 4); // "appl|", with '|' as the caret position. + const initialMock = Mock.from(mock); + const promise = predictiveContext.setCurrentTarget(mock); + + // Initial predictive state: no suggestions. context.initializeState() has not yet been called. + assert.equal(updateFake.callCount, 1); + assert.isEmpty(updateFake.firstCall.args[0]); // should have no suggestions. (if convenient for testing) + + await promise; + let suggestions; + + // Initialization results: our first set of dummy suggestions. + assert.equal(updateFake.callCount, 2); + suggestions = updateFake.secondCall.args[0]; + assert.deepEqual(suggestions.map((obj) => obj.displayAs), ['apple', 'apply', 'apples']); + assert.isNotOk(suggestions.find((obj) => obj.tag == 'keep')); + assert.isNotOk(suggestions.find((obj) => obj.transform.deleteLeft != 0)); + + const baseTranscription = mock.buildTranscriptionFrom(initialMock, null, true); + + // Mocking: corresponds to the second set of mocked predictions - round 2 of + // 'apple', 'apply', 'apples'. + const skippedPromise = langProcessor.predict(baseTranscription, kbdProcessor.layerId); + + mock.insertTextBeforeCaret('e'); // appl| + e = apple + const finalTranscription = mock.buildTranscriptionFrom(initialMock, null, true); + + // Mocking: corresponds to the third set of mocked predictions - 'applied'. + const expectedPromise = langProcessor.predict(finalTranscription, kbdProcessor.layerId); + + await Promise.all([skippedPromise, expectedPromise]); + const expected = await expectedPromise; + + // Despite two predict calls, we should only increase the counter by ONE - we ignore + // the 'outdated' / 'skipped' round because it could not respond before its followup. + assert.equal(updateFake.callCount, 3); + suggestions = updateFake.thirdCall.args[0]; + + // This does re-use the apply-revert oriented mocking. + // Should skip the (second) "apple", "apply", "apps" round, as it became outdated + // by its following request before its response could be received. + assert.deepEqual(suggestions.map((obj) => obj.displayAs), ['“apple”', 'applied']); + assert.equal(suggestions.find((obj) => obj.tag == 'keep').displayAs, '“apple”'); + assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'applied'); + // Our reused mocking doesn't directly provide the 'keep' suggestion; we + // need to remove it before testing for set equality. + assert.deepEqual(suggestions.splice(1), expected); + }); + it('sendUpdateState retrieves the most recent suggestion set', async function() { const langProcessor = new LanguageProcessor(worker, new TranscriptionCache()); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker.