From 0055ad84ebad76404478441f284a48eb5cc7f2af Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 13 Aug 2024 13:41:35 +0700 Subject: [PATCH 01/14] feat(web): test skipped prediction round handling Fixes: #11624 --- .../prediction/predictionContext.spec.js | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) 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..ff1e6f23ccf 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,67 @@ 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; + + // TODO: adjust the following. + + // 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. From 7ab7ec9bd65ef475490514f85df3c532a9ceab9d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 13 Aug 2024 13:43:17 +0700 Subject: [PATCH 02/14] chore(web): minor comment cleanup --- .../engine/interfaces/prediction/predictionContext.spec.js | 2 -- 1 file changed, 2 deletions(-) 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 ff1e6f23ccf..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 @@ -161,8 +161,6 @@ describe("PredictionContext", () => { await Promise.all([skippedPromise, expectedPromise]); const expected = await expectedPromise; - // TODO: adjust the following. - // 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); From 613bdb6c19454f420c828d3d43f702273e0fe936 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 13 Aug 2024 15:04:07 +0200 Subject: [PATCH 03/14] refactor(core): move utfcodec to common Refactor to support codecvt cleanup work in kmcmplib. --- {core/src => common/cpp}/utfcodec.cpp | 0 {core/src => common/include}/utfcodec.hpp | 0 core/doc/meson.build | 2 +- core/src/meson.build | 2 +- core/tests/meson.build | 5 ++++- core/tests/unit/utftest/meson.build | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) rename {core/src => common/cpp}/utfcodec.cpp (100%) rename {core/src => common/include}/utfcodec.hpp (100%) 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/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) From 6554bd615e7c35259dafcdd7fc23c0a6be6d3a47 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 6 Aug 2024 16:20:09 +0200 Subject: [PATCH 04/14] chore(common): replace codecvt in module u16 --- developer/src/kmcmplib/src/kmx_u16.cpp | 425 ++++++++++++++++--------- developer/src/kmcmplib/src/kmx_u16.h | 90 ++++-- 2 files changed, 350 insertions(+), 165 deletions(-) diff --git a/developer/src/kmcmplib/src/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp index 02e59f8c12b..6d1281c1ff3 100644 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ b/developer/src/kmcmplib/src/kmx_u16.cpp @@ -10,150 +10,224 @@ #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); +#include "utfcodec.hpp" + +/** 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 + +/** 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) { - std::wstring_convert, wchar_t> converter; - return converter.from_bytes(str); + return convert((const std::string)str); } -//u16String <- string +/** 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) { - std::wstring_convert, char16_t> converter; - return converter.from_bytes(str); + return convert((const std::string)str); } -//string <- u16string -std::string string_from_u16string(std::u16string const str) { - std::wstring_convert, char16_t> converter; - return converter.to_bytes(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); } -// 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; +/** 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); +} - // 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; +/** 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); } -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; +/** + * @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; } - 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 +/** wstring <- u16string + * @brief Obtain a std::wstring from a std::u16tring + * the same as an above function but since it is used in Keyman we wrap it here + * @param str16 the std::string to be converted + * @return a std::wstring + */ +std::wstring u16fmt(const KMX_WCHAR* str16) { + return wstring_from_u16string(str16); +} + +/** + * @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst + * the same as an above function but since it is used in Keyman we wrap it here + * @param ch16 the char16 to be converted + * @return a std::wstring + */ +std::wstring convert_pchar16T_To_wstr(KMX_WCHAR* ch16) { + std::u16string u16str(ch16); std::string stri = string_from_u16string(u16str); - // std::string -> std::wstring - std::wstring wstr = wstring_from_string(stri); + std::wstring wstr = wstring_from_string(stri); return wstr; - } +} -long int u16tol(const KMX_WCHAR* str, KMX_WCHAR** endptr, int base) -{ +/** + * @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()); + 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; + 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) { +/** + * @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); + dst = (KMX_WCHAR*)u16chr(dst, 0); + // max -= (dst-o); while (*src && max > 0) { *dst++ = *src++; max--; } - if(max > 0) - *dst = 0; + if (max > 0) + *dst = 0; return o; } -const KMX_WCHAR* u16rchr_slash(KMX_WCHAR const* Name) -{ +/** + * @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, '\\'); + cp = u16rchr(name, '\\'); if (cp == NULL) - cp = u16rchr(Name, '/'); + cp = u16rchr(name, '/'); return cp; } -KMX_CHAR* strrchr_slash(KMX_CHAR* Name) -{ +/** + * @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, '\\'); + cp = strrchr(name, '\\'); if (cp == NULL) - cp = strrchr(Name, '/'); + 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 +/** + * @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; + 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) { +/** + * @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; + 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; +/** + * @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++; } @@ -161,19 +235,31 @@ const KMX_WCHAR * u16cpy(KMX_WCHAR *dst, const KMX_WCHAR *src) { return o; } -const KMX_WCHAR * u16ncpy(KMX_WCHAR *dst, const KMX_WCHAR *src, size_t max) { - KMX_WCHAR *o = dst; +/** + * @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) { + if (max > 0) { *dst = 0; } return o; } -size_t u16len(const KMX_WCHAR *p) { +/** + * @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++; @@ -182,18 +268,35 @@ size_t u16len(const KMX_WCHAR *p) { return i; } -int u16cmp(const KMX_WCHAR *p, const KMX_WCHAR *q) { +/** + * @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; + if (*p != *q) + return *p - *q; p++; q++; } return *p - *q; } -int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) { +/** + * @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; + if (toupper(*p) != toupper(*q)) + return *p - *q; p++; q++; count--; @@ -203,18 +306,35 @@ int u16nicmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) { return 0; } -int u16icmp(const KMX_WCHAR *p, const KMX_WCHAR *q) { +/** + * @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; + 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) { +/** + * @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; + if (*p != *q) + return *p - *q; p++; q++; count--; @@ -224,70 +344,89 @@ int u16ncmp(const KMX_WCHAR *p, const KMX_WCHAR *q, size_t count) { return 0; } -KMX_WCHAR * u16tok(KMX_WCHAR *p, const KMX_WCHAR ch, KMX_WCHAR **ctx) { +/** + * @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; + if (!p) + return NULL; } - KMX_WCHAR *q = p; + KMX_WCHAR* q = p; while (*q && *q != ch) { q++; } if (*q) { *q = 0; q++; - while (*q == ch) q++; + while (*q == ch) + q++; *ctx = q; - } - else { + } 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; - } +/** + * @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; } -double u16tof( KMX_WCHAR* str) -{ - double val = 0; - int offsetdot=0; - char digit; +/** + * @brief Convert a u16string to a double + * @param str Pointer to u16string + * @return double value equivalent to the string + */ +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; + 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)); + for (size_t i = 0; i < u16len(str); i++) { + digit = static_cast(towupper(*str)); - if (i > pos_dot - 1) - offsetdot = 1; + if (i > pos_dot - 1) + offsetdot = 1; - if (digit != '.') - val =val+ ((int(digit)) - 48) * pow(10, (pos_dot - 1- i + offsetdot)); + if (digit != '.') + val = val + ((int(digit)) - 48) * pow(10, (pos_dot - 1 - i + offsetdot)); - str++; - } - return val; + str++; + } + return val; } diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/developer/src/kmcmplib/src/kmx_u16.h index 450c400e2f9..fab15fe1a3d 100644 --- a/developer/src/kmcmplib/src/kmx_u16.h +++ b/developer/src/kmcmplib/src/kmx_u16.h @@ -7,32 +7,78 @@ #include #include "kmcompx.h" -std::string string_from_wstring(std::wstring const str); +/** @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); -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); +/** @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 Obtain a std::wstring from a std::u16tring */ +std::wstring u16fmt(const KMX_WCHAR* str); + +/** @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, ...); + +std::wstring convert_pchar16T_To_wstr(KMX_WCHAR* Name); + +/** @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); From cbe712d2cd923105845af5f9840d31e566bd01b4 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 7 Aug 2024 11:30:14 +0200 Subject: [PATCH 05/14] chore(common): replace u16fmt and convert_pchar16T_To_wstr with wstring_from_u16string --- developer/src/kmcmplib/src/Compiler.cpp | 8 +++---- .../src/kmcmplib/src/UnreachableRules.cpp | 2 +- developer/src/kmcmplib/src/kmx_u16.cpp | 23 ------------------- developer/src/kmcmplib/src/kmx_u16.h | 5 ---- .../src/kmcmplib/tests/util_filesystem.cpp | 4 ++-- 5 files changed, 7 insertions(+), 35 deletions(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index 361a97c2d7b..e626b140f54 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -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 ", wstring_from_u16string(DEBUGSTORE_MATCH).c_str(), (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 ", wstring_from_u16string(DEBUGSTORE_NOMATCH).c_str(), (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 ", wstring_from_u16string(DEBUGSTORE_GROUP).c_str(), 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 ", wstring_from_u16string(DEBUGSTORE_DEADKEY).c_str(), (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/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp index 6d1281c1ff3..04054fe4ad6 100644 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ b/developer/src/kmcmplib/src/kmx_u16.cpp @@ -84,29 +84,6 @@ void u16sprintf(KMX_WCHAR* dst, const size_t sz, const wchar_t* fmt, ...) { delete[] wbuf; } -/** wstring <- u16string - * @brief Obtain a std::wstring from a std::u16tring - * the same as an above function but since it is used in Keyman we wrap it here - * @param str16 the std::string to be converted - * @return a std::wstring - */ -std::wstring u16fmt(const KMX_WCHAR* str16) { - return wstring_from_u16string(str16); -} - -/** - * @brief Convert pointer to wchar_t to pointer to char16_t and copy sz elements into dst - * the same as an above function but since it is used in Keyman we wrap it here - * @param ch16 the char16 to be converted - * @return a std::wstring - */ -std::wstring convert_pchar16T_To_wstr(KMX_WCHAR* ch16) { - std::u16string u16str(ch16); - std::string stri = string_from_u16string(u16str); - std::wstring wstr = wstring_from_string(stri); - return wstr; -} - /** * @brief Convert u16string to long integer * @param str u16string beginning with the representation of an integral number. diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/developer/src/kmcmplib/src/kmx_u16.h index fab15fe1a3d..4ebcdb601dd 100644 --- a/developer/src/kmcmplib/src/kmx_u16.h +++ b/developer/src/kmcmplib/src/kmx_u16.h @@ -25,14 +25,9 @@ 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 Obtain a std::wstring from a std::u16tring */ -std::wstring u16fmt(const KMX_WCHAR* str); - /** @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, ...); -std::wstring convert_pchar16T_To_wstr(KMX_WCHAR* Name); - /** @brief Return the length of the u16string str */ size_t u16len(const KMX_WCHAR* p); diff --git a/developer/src/kmcmplib/tests/util_filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp index 7f9edd6b367..4ca210cff93 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -132,8 +132,8 @@ FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode) { 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 From a3a7bd38ffb1e96ec6cf1323a021b39fe290ba59 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 7 Aug 2024 12:22:51 +0200 Subject: [PATCH 06/14] chore(common): use strtof() in u16tof() --- developer/src/kmcmplib/src/kmx_u16.cpp | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/developer/src/kmcmplib/src/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp index 04054fe4ad6..708c82dc04e 100644 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ b/developer/src/kmcmplib/src/kmx_u16.cpp @@ -386,24 +386,8 @@ KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx) { * @param str Pointer to u16string * @return double value equivalent to the string */ -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; +double u16tof(KMX_WCHAR* str16) { + char* pEnd; + std::string str = string_from_u16string(str16); + return strtof(str.c_str(), &pEnd); } From 07c8d13f5323bc16e1d4cc50957b2f2d6d519589 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 13 Aug 2024 15:24:19 +0200 Subject: [PATCH 07/14] chore(common):use googletests for kmx_u16 --- developer/src/kmcmplib/src/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 2b9a431e090..260df09b838 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -85,6 +85,7 @@ lib = library('kmcmplib', 'versioning.cpp', 'virtualcharkeys.cpp', 'xstring.cpp', + '../../../../common/cpp/utfcodec.cpp', '../../../../common/windows/cpp/src/ConvertUTF.c', '../../../../common/windows/cpp/src/crc32.cpp', '../../../../common/windows/cpp/src/vkeys.cpp', From 30493f94bb2dcbc45e2336f8eac9b8f02d66064b Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 13 Aug 2024 15:59:43 +0200 Subject: [PATCH 08/14] chore(common): replace DEBUGSTORE_, DEBUGSTORE_...C with DEBUGSTORE_..._L , DEBUGSTORE_..._U --- developer/src/kmcmplib/src/Compiler.cpp | 8 ++++---- developer/src/kmcmplib/src/debugstore.h | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index e626b140f54..03f39823667 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -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 ", wstring_from_u16string(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 ", wstring_from_u16string(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 ", wstring_from_u16string(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 ", wstring_from_u16string(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/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 */ From 15a35b863b95a72e9504f12edc58aed661162cb4 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 13 Aug 2024 14:03:05 -0400 Subject: [PATCH 09/14] auto: increment master version to 18.0.89 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 From d7cdd481ba9697d8d49fa3eddf447b4e249a72de Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 14 Aug 2024 09:20:02 +0700 Subject: [PATCH 10/14] fix(web): support live configuration of longpress delay --- .../osk/src/input/gestures/specsForLayout.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 943d8e91b96..2648a660d0c 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -248,7 +248,16 @@ 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: () => { + return params.longpress.waitLength + } + }); // #region Functions for implementing and/or extending path initial-state checks function withKeySpecFiltering(model: GestureModel, contactIndices: number | number[]) { @@ -281,7 +290,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 +490,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, From 75527ec738878d30cedefaeba4bfa3c80fc0167e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 14 Aug 2024 09:25:43 +0700 Subject: [PATCH 11/14] chore(web): minor simplification --- web/src/engine/osk/src/input/gestures/specsForLayout.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 2648a660d0c..622d66d8650 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -254,9 +254,7 @@ export function gestureSetForLayout(flags: LayoutGestureSupportFlags, params: Ge // `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: () => { - return params.longpress.waitLength - } + get: () => params.longpress.waitLength }); // #region Functions for implementing and/or extending path initial-state checks From afdc7be731592947d44257edb8ef3b792f26d832 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 14 Aug 2024 11:07:48 +0700 Subject: [PATCH 12/14] feat(web): add osk.gestureParams for better gesture-config persistence --- .../osk/src/config/commonConfiguration.ts | 6 ++++++ .../osk/src/input/gestures/specsForLayout.ts | 16 ++++++++++------ web/src/engine/osk/src/views/oskView.ts | 18 +++++++++++++++++- web/src/engine/osk/src/visualKeyboard.ts | 8 ++++++-- 4 files changed, 39 insertions(+), 9 deletions(-) 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 622d66d8650..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 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; From 4a57258afcebfd4b3b0d803d04c3bb4ee1b36758 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 14 Aug 2024 09:50:11 +0200 Subject: [PATCH 13/14] refactor(developer): remove unnecessary includes from kmx_u16 --- developer/src/kmcmplib/include/kmcompx.h | 5 ----- developer/src/kmcmplib/src/kmx_u16.cpp | 16 ++++++---------- developer/src/kmcmplib/src/kmx_u16.h | 5 +---- developer/src/kmcmplib/tests/util_filesystem.cpp | 13 ------------- developer/src/kmcmplib/tests/util_filesystem.h | 1 - 5 files changed, 7 insertions(+), 33 deletions(-) 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/kmx_u16.cpp b/developer/src/kmcmplib/src/kmx_u16.cpp index 708c82dc04e..884ddc663e2 100644 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ b/developer/src/kmcmplib/src/kmx_u16.cpp @@ -1,16 +1,12 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * std::u16string functions and string conversion utility functions + */ -//#include "../../kmcompx/include/kmcompx.h" -#include -#include "kmx_u16.h" - -#include -#include -#include -#include -#include #include -#include #include "utfcodec.hpp" +#include "kmx_u16.h" /** string <- wstring * @brief Obtain a std::string from a std::wstring diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/developer/src/kmcmplib/src/kmx_u16.h index 4ebcdb601dd..f0c2c2d16c1 100644 --- a/developer/src/kmcmplib/src/kmx_u16.h +++ b/developer/src/kmcmplib/src/kmx_u16.h @@ -1,11 +1,8 @@ #pragma once -#include -#include -#include #include #include -#include "kmcompx.h" +#include /** @brief Obtain a std::string from a std::wstring */ std::string string_from_wstring(std::wstring const wstr); diff --git a/developer/src/kmcmplib/tests/util_filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp index 4ca210cff93..0873ff13288 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -117,19 +117,6 @@ 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 = wstring_from_u16string(Filename); diff --git a/developer/src/kmcmplib/tests/util_filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h index 4c94dae6bf4..0e183b1a6fd 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -6,7 +6,6 @@ // 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); From dde4e2b2c3e145d3983a1ac681fa10d21a53fe41 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 14 Aug 2024 10:07:08 +0200 Subject: [PATCH 14/14] refactor: move kmx_u16 to common and rename to km_u16 --- .../src/kmx_u16.cpp => common/cpp/km_u16.cpp | 2 +- .../src/kmx_u16.h => common/include/km_u16.h | 0 developer/src/kmcmplib/src/CasedKeys.cpp | 5 +++-- developer/src/kmcmplib/src/CheckForDuplicates.cpp | 2 +- developer/src/kmcmplib/src/Compiler.cpp | 2 +- developer/src/kmcmplib/src/meson.build | 2 +- developer/src/kmcmplib/src/pch.h | 2 +- .../src/kmcmplib/tests/gtest-compiler-test.cpp | 2 +- ...test-kmx_u16-test.cpp => gtest-km_u16-test.cpp} | 14 +++++++------- developer/src/kmcmplib/tests/meson.build | 4 ++-- developer/src/kmcmplib/tests/util_filesystem.h | 2 +- 11 files changed, 19 insertions(+), 18 deletions(-) rename developer/src/kmcmplib/src/kmx_u16.cpp => common/cpp/km_u16.cpp (99%) rename developer/src/kmcmplib/src/kmx_u16.h => common/include/km_u16.h (100%) rename developer/src/kmcmplib/tests/{gtest-kmx_u16-test.cpp => gtest-km_u16-test.cpp} (94%) diff --git a/developer/src/kmcmplib/src/kmx_u16.cpp b/common/cpp/km_u16.cpp similarity index 99% rename from developer/src/kmcmplib/src/kmx_u16.cpp rename to common/cpp/km_u16.cpp index 884ddc663e2..84a6c4a75e7 100644 --- a/developer/src/kmcmplib/src/kmx_u16.cpp +++ b/common/cpp/km_u16.cpp @@ -6,7 +6,7 @@ #include #include "utfcodec.hpp" -#include "kmx_u16.h" +#include "km_u16.h" /** string <- wstring * @brief Obtain a std::string from a std::wstring diff --git a/developer/src/kmcmplib/src/kmx_u16.h b/common/include/km_u16.h similarity index 100% rename from developer/src/kmcmplib/src/kmx_u16.h rename to common/include/km_u16.h 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 03f39823667..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)) diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 260df09b838..51acf704090 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -78,13 +78,13 @@ 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', 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.h b/developer/src/kmcmplib/tests/util_filesystem.h index 0e183b1a6fd..c35967b05e8 100644 --- a/developer/src/kmcmplib/tests/util_filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -1,7 +1,7 @@ #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