mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-25 07:17:41 +00:00
Merge branch 'master' into fix/windows/15333-simplify-keyboard-refresh
This commit is contained in:
commit
3c44f8ab5b
38 changed files with 505 additions and 289 deletions
16
HISTORY.md
16
HISTORY.md
|
|
@ -1,5 +1,21 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.276 alpha 2026-08-28
|
||||
|
||||
* fix(windows): add manifest to insthelper and set trustinfo to asInvoker (#16430)
|
||||
* fix(windows): resync caps lock state when Keyman keyboard is activated (#16423)
|
||||
|
||||
## 19.0.275 alpha 2026-08-27
|
||||
|
||||
* docs(mac): refresh build/setup docs and remove APPSTORECONNECT_PROVIDER (#16432)
|
||||
* chore(mac): update setup document (#16415)
|
||||
* change(web): enhance missing-stubs error (#16437)
|
||||
|
||||
## 19.0.274 alpha 2026-08-24
|
||||
|
||||
* maint(windows): move to vs2026 arm runner for GHA (#16418)
|
||||
* chore(web): skip guide-examples.tests.ts until race is fixed (#16427)
|
||||
|
||||
## 19.0.273 alpha 2026-08-22
|
||||
|
||||
* feat(linux): use display language for keyboard search (#16393)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.274
|
||||
19.0.277
|
||||
|
|
@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../resources/build/builder-full.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "${KEYMAN_ROOT}/resources/build/ci/sentry-control.inc.sh"
|
||||
|
||||
# ################################ Main script ################################
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ builder_describe "Builds Keyman Engine for Android." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test Runs lint and unit tests." \
|
||||
"publish-symbols Publishes symbols to Sentry." \
|
||||
":engine Builds Engine"
|
||||
|
||||
builder_parse "$@"
|
||||
|
|
@ -79,6 +81,13 @@ do_test() {
|
|||
./gradlew $GRADLE_DAEMON $TEST_FLAGS
|
||||
}
|
||||
|
||||
builder_run_action clean:engine rm -rf build app/build
|
||||
builder_run_action build:engine do_build
|
||||
builder_run_action test:engine do_test
|
||||
do_publish_symbols() {
|
||||
if builder_is_ci_build && builder_is_ci_build_level_release; then
|
||||
sentry_upload_web "${KEYMAN_ROOT}/web/build/app/webview/release/"
|
||||
fi
|
||||
}
|
||||
|
||||
builder_run_action clean:engine rm -rf build app/build
|
||||
builder_run_action build:engine do_build
|
||||
builder_run_action test:engine do_test
|
||||
builder_run_action publish-symbols do_publish_symbols
|
||||
|
|
|
|||
|
|
@ -326,10 +326,20 @@ int u16ncmp(const KMX_WCHAR* p, const KMX_WCHAR* q, size_t count) {
|
|||
* @return Pointer to the first token in p
|
||||
*/
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx) {
|
||||
if(ch == 0 || !ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!p) {
|
||||
p = *ctx;
|
||||
if (!p)
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// skip initial delimiter
|
||||
while (*p == ch) {
|
||||
p++;
|
||||
}
|
||||
|
||||
KMX_WCHAR* q = p;
|
||||
|
|
@ -356,10 +366,20 @@ KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR ch, KMX_WCHAR** ctx) {
|
|||
* @return Pointer to the first token in p
|
||||
*/
|
||||
KMX_WCHAR* u16tok(KMX_WCHAR* p, const KMX_WCHAR* delimiters, KMX_WCHAR** ctx) {
|
||||
if(!ctx || !delimiters || !(*delimiters)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!p) {
|
||||
p = *ctx;
|
||||
if (!p)
|
||||
if (!p) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// skip initial delimiters
|
||||
while (*p && u16chr(delimiters, *p)) {
|
||||
p++;
|
||||
}
|
||||
|
||||
KMX_WCHAR* q = p;
|
||||
|
|
|
|||
|
|
@ -896,22 +896,36 @@ end;
|
|||
procedure TfrmCharacterMapNew.gridMouseWheelDown(Sender: TObject;
|
||||
Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
|
||||
begin
|
||||
Handled := True;
|
||||
if ssCtrl in Shift then
|
||||
begin
|
||||
Handled := True;
|
||||
// Zoom
|
||||
if tbSize.Position < tbSize.Max then
|
||||
tbSize.Position := tbSize.Position + tbSize.PageSize;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Scroll
|
||||
if grid.TopRow < grid.RowCount - grid.VisibleRowCount then
|
||||
grid.TopRow := grid.TopRow + 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmCharacterMapNew.gridMouseWheelUp(Sender: TObject;
|
||||
Shift: TShiftState; MousePos: TPoint; var Handled: Boolean);
|
||||
begin
|
||||
Handled := True;
|
||||
if ssCtrl in Shift then
|
||||
begin
|
||||
Handled := True;
|
||||
// Zoom
|
||||
if tbSize.Position > tbSize.Min then
|
||||
tbSize.Position := tbSize.Position - tbSize.PageSize;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Scroll
|
||||
if grid.TopRow > 0 then
|
||||
grid.TopRow := grid.TopRow - 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import fs from 'node:fs';
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { KmnCompiler } from '../src/main.js';
|
||||
import { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
import { KmxFileReader } from '@keymanapp/common-types';
|
||||
import { compileTestKeyboard } from './helpers/index.js';
|
||||
import { KmnCompiler } from '../src/main.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url)).replace(/\\/g, '/');
|
||||
const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/';
|
||||
|
|
@ -13,6 +18,16 @@ const baselineDir = keyboardsDir + 'baseline/';
|
|||
describe('Compiler class', function() {
|
||||
const callbacks = new TestCompilerCallbacks(this);
|
||||
|
||||
this.beforeEach(function() {
|
||||
callbacks.clear();
|
||||
});
|
||||
|
||||
this.afterEach(function() {
|
||||
if(this.currentTest?.isFailed()) {
|
||||
callbacks.printMessages();
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw on failure', async function() {
|
||||
const compiler = new KmnCompiler();
|
||||
const callbacks : any = null; // ERROR
|
||||
|
|
@ -129,4 +144,20 @@ describe('Compiler class', function() {
|
|||
assert.deepEqual(kvkData, kvkFixtureData);
|
||||
});
|
||||
|
||||
it('should trim all whitespace for `&targets` store', async function() {
|
||||
const result = await compileTestKeyboard(callbacks, ['keyboards', 'targets-with-whitespace.kmn']);
|
||||
assert.isNotNull(result);
|
||||
|
||||
// Verify implictly that `&targets` store was interpreted correctly as 'any'
|
||||
// because the compiler generated both JS and KMX targets (#13721)
|
||||
assert.isNotNull(result.artifacts.js);
|
||||
assert.isNotNull(result.artifacts.kmx);
|
||||
|
||||
// Then verify directly that the `&targets` store was trimmed by looking at
|
||||
// the final value in the kmx data
|
||||
const reader = new KmxFileReader();
|
||||
const keyboard = reader.read(result.artifacts.kmx.data);
|
||||
assert.equal(keyboard.targets, 'any');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
11
developer/src/kmc-kmn/test/fixtures/keyboards/targets-with-whitespace.kmn
vendored
Normal file
11
developer/src/kmc-kmn/test/fixtures/keyboards/targets-with-whitespace.kmn
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
store(&NAME) 'targets_with_whitespace'
|
||||
store(&VERSION) '10.0'
|
||||
|
||||
c this used to generate KM0207B: At least one compile target must be specified
|
||||
store(&TARGETS) ' any'
|
||||
|
||||
begin unicode > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
||||
+ 'x' > 'y'
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
/**
|
||||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Helpers and utilities for the Mocha tests.
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { assert } from 'chai';
|
||||
import { CompilerCallbacks } from '@keymanapp/developer-utils';
|
||||
import { KmnCompiler } from '../../src/compiler/compiler.js';
|
||||
|
||||
/**
|
||||
* Builds a path to the fixture with the given path components.
|
||||
|
|
@ -15,3 +20,13 @@ import { fileURLToPath } from 'url';
|
|||
export function makePathToFixture(...components: string[]): string {
|
||||
return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url));
|
||||
}
|
||||
|
||||
export async function compileTestKeyboard(callbacks: CompilerCallbacks, fixture: string[]) {
|
||||
const compiler = new KmnCompiler();
|
||||
assert(await compiler.init(callbacks, {saveDebug: true, shouldAddCompilerVersion: false}));
|
||||
assert(compiler.verifyInitialized());
|
||||
|
||||
const kmnPath = makePathToFixture(...fixture);
|
||||
|
||||
return await compiler.run(kmnPath, null);
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token);
|
|||
KMX_DWORD GetRHS(PFILE_KEYBOARD fk, PKMX_WCHAR p, PKMX_WCHAR buf, int bufsize, int offset, int IsUnicode);
|
||||
PKMX_WCHAR GetDelimitedString(PKMX_WCHAR *p, KMX_WCHAR const * Delimiters, KMX_WORD Flags);
|
||||
KMX_DWORD GetXString(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int isVKey, int isUnicode);
|
||||
KMX_BOOL GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets);
|
||||
KMX_BOOL GetCompileTargetsFromTargetsStore(KMX_WCHAR *store, int &targets);
|
||||
|
||||
int GetGroupNum(PFILE_KEYBOARD fk, PKMX_WCHAR p);
|
||||
|
||||
|
|
@ -1331,7 +1331,16 @@ KMX_BOOL ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE s
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
KMX_BOOL GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets) {
|
||||
/**
|
||||
* Extract the compile targets from the &targets store, and rewrite the
|
||||
* &targets dpString value to remove unnecessary whitespace. Does not
|
||||
* reallocate sp->dpString, but overwrites its value with a string the
|
||||
* same length or shorter.
|
||||
* @param store store value to rewrite
|
||||
* @param targets (output)
|
||||
* @return FALSE if no targets found or invalid targets found
|
||||
*/
|
||||
KMX_BOOL GetCompileTargetsFromTargetsStore(KMX_WCHAR *store, int &targets) {
|
||||
// Compile to .kmx
|
||||
const std::vector<std::u16string> KMXKeymanTargets{
|
||||
u"windows", u"macosx", u"linux", u"desktop"
|
||||
|
|
@ -1347,10 +1356,17 @@ KMX_BOOL GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets)
|
|||
|
||||
targets = 0;
|
||||
auto p = new KMX_WCHAR[u16len(store)+1];
|
||||
u16cpy(p, store);
|
||||
auto q = p;
|
||||
*p = 0;
|
||||
KMX_WCHAR* ctx;
|
||||
auto token = u16tok(p, u" ", &ctx);
|
||||
auto token = u16tok(store, u" ", &ctx);
|
||||
while(token) {
|
||||
if(q > p) {
|
||||
// Insert a delimiter between tokens, if
|
||||
// more than one token
|
||||
*q++ = ' ';
|
||||
*q = 0;
|
||||
}
|
||||
bool found = false;
|
||||
if(*token) {
|
||||
if(AnyTarget == token) {
|
||||
|
|
@ -1378,9 +1394,16 @@ KMX_BOOL GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets)
|
|||
targets = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Append the token to the output, we know it is long enough
|
||||
// because the buffer is the same length as the input
|
||||
u16cpy(q, token);
|
||||
q = const_cast<KMX_WCHAR*>(u16chr(q, 0));
|
||||
*q = 0;
|
||||
}
|
||||
token = u16tok(nullptr, u" ", &ctx);
|
||||
}
|
||||
u16cpy(store, p);
|
||||
delete[] p;
|
||||
|
||||
if(targets == 0) {
|
||||
|
|
@ -2079,7 +2102,7 @@ int LineTokenType(PKMX_WCHAR *str)
|
|||
switch (towupper(*p))
|
||||
{
|
||||
case 'C':
|
||||
if (iswspace(*(p + 1))) return T_COMMENT;
|
||||
if (iswspace(*(p + 1)) || *(p + 1) == 0) return T_COMMENT;
|
||||
break;
|
||||
case 0:
|
||||
return T_BLANK;
|
||||
|
|
|
|||
|
|
@ -71,66 +71,90 @@ void test_kmcmp_CompileKeyboard(char *kmn_file) {
|
|||
unlink(kmn_file);
|
||||
}
|
||||
|
||||
extern KMX_BOOL GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets);
|
||||
extern KMX_BOOL GetCompileTargetsFromTargetsStore(KMX_WCHAR* buf, int &targets);
|
||||
|
||||
KMX_BOOL do_GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store, int &targets, const KMX_WCHAR* expected = nullptr) {
|
||||
KMX_WCHAR* buf;
|
||||
buf = new KMX_WCHAR[u16len(store)+1];
|
||||
u16cpy(buf, store);
|
||||
KMX_BOOL result = GetCompileTargetsFromTargetsStore(buf, targets);
|
||||
|
||||
if(expected) {
|
||||
test_assert(u16cmp(expected, buf) == 0);
|
||||
}
|
||||
|
||||
delete[] buf;
|
||||
return result;
|
||||
}
|
||||
|
||||
void test_GetCompileTargetsFromTargetsStore() {
|
||||
int targets = 0;
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"any", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"any", targets, u"any"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == (COMPILETARGETS_KMX | COMPILETARGETS_JS));
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"windows", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"windows", targets, u"windows"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == COMPILETARGETS_KMX);
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"desktop", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"desktop", targets, u"desktop"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == COMPILETARGETS_KMX);
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"mobile", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"mobile", targets, u"mobile"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == COMPILETARGETS_JS);
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"web", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"web", targets, u"web"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == COMPILETARGETS_JS);
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"desktop mobile", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"desktop mobile", targets, u"desktop mobile"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == (COMPILETARGETS_KMX | COMPILETARGETS_JS));
|
||||
|
||||
setup();
|
||||
test_assert(GetCompileTargetsFromTargetsStore(u"desktop tablet", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u"desktop tablet", targets, u"desktop tablet"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == (COMPILETARGETS_KMX | COMPILETARGETS_JS));
|
||||
|
||||
setup();
|
||||
test_assert(!GetCompileTargetsFromTargetsStore(u"foo bar baz", targets));
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u" desktop tablet", targets, u"desktop tablet"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == (COMPILETARGETS_KMX | COMPILETARGETS_JS));
|
||||
|
||||
setup();
|
||||
test_assert(do_GetCompileTargetsFromTargetsStore(u" windows androidphone ", targets, u"windows androidphone"));
|
||||
test_assert(error_vec.size() == 0);
|
||||
test_assert(targets == (COMPILETARGETS_KMX | COMPILETARGETS_JS));
|
||||
|
||||
setup();
|
||||
test_assert(!do_GetCompileTargetsFromTargetsStore(u"foo bar baz", targets));
|
||||
test_assert(error_vec.size() == 1);
|
||||
test_assert(error_vec[0] == KmnCompilerMessages::ERROR_InvalidTarget);
|
||||
test_assert(targets == 0);
|
||||
|
||||
setup();
|
||||
test_assert(!GetCompileTargetsFromTargetsStore(u"windows chromeos", targets));
|
||||
test_assert(!do_GetCompileTargetsFromTargetsStore(u"windows chromeos", targets));
|
||||
test_assert(error_vec.size() == 1);
|
||||
test_assert(error_vec[0] == KmnCompilerMessages::ERROR_InvalidTarget);
|
||||
test_assert(targets == 0);
|
||||
|
||||
setup();
|
||||
test_assert(!GetCompileTargetsFromTargetsStore(u" ", targets));
|
||||
test_assert(!do_GetCompileTargetsFromTargetsStore(u" ", targets));
|
||||
test_assert(error_vec.size() == 1);
|
||||
test_assert(error_vec[0] == KmnCompilerMessages::ERROR_NoTargetsSpecified);
|
||||
test_assert(targets == 0);
|
||||
|
||||
setup();
|
||||
test_assert(!GetCompileTargetsFromTargetsStore(u"", targets));
|
||||
test_assert(!do_GetCompileTargetsFromTargetsStore(u"", targets));
|
||||
test_assert(error_vec.size() == 1);
|
||||
test_assert(error_vec[0] == KmnCompilerMessages::ERROR_NoTargetsSpecified);
|
||||
test_assert(targets == 0);
|
||||
|
|
|
|||
|
|
@ -663,7 +663,7 @@ TEST_F(CompilerTest, LineTokenType_test) {
|
|||
// comment without following space ... potential bug, but ReadLine() currently ensures following space
|
||||
u16cpy(str, u"c");
|
||||
p = str;
|
||||
EXPECT_EQ(T_UNKNOWN, LineTokenType(&p));
|
||||
EXPECT_EQ(T_COMMENT, LineTokenType(&p));
|
||||
EXPECT_EQ(0, p - str);
|
||||
|
||||
// T_KEYTOKEY
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ TEST(km_u16_Test, u16tok_char_delim) {
|
|||
KMX_WCHAR str[LINESIZE];
|
||||
KMX_WCHAR *ctx = nullptr;
|
||||
|
||||
// invalid parameters
|
||||
EXPECT_EQ(nullptr, u16tok(str, ' ', nullptr));
|
||||
EXPECT_EQ(nullptr, u16tok(str, (KMX_WCHAR)0, &ctx));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, ' ', nullptr));
|
||||
|
||||
// sequence of tokens
|
||||
u16cpy(str, u"test a space and two");
|
||||
ctx = nullptr;
|
||||
|
|
@ -55,6 +60,13 @@ TEST(km_u16_Test, u16tok_char_delim) {
|
|||
EXPECT_TRUE(!u16cmp(u"b", u16tok(nullptr, ' ', &ctx)));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, ' ', &ctx));
|
||||
|
||||
// delimiters at start
|
||||
u16cpy(str, u" a b");
|
||||
ctx = nullptr;
|
||||
EXPECT_TRUE(!u16cmp(u"a", u16tok(str, ' ', &ctx)));
|
||||
EXPECT_TRUE(!u16cmp(u"b", u16tok(nullptr, ' ', &ctx)));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, ' ', &ctx));
|
||||
|
||||
// no string, no context
|
||||
ctx = nullptr;
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, ' ', &ctx));
|
||||
|
|
@ -73,6 +85,12 @@ TEST(km_u16_Test, u16tok_str_delim) {
|
|||
KMX_WCHAR str[LINESIZE];
|
||||
KMX_WCHAR *ctx = nullptr;
|
||||
|
||||
// invalid parameters
|
||||
EXPECT_EQ(nullptr, u16tok(str, u" ", nullptr));
|
||||
EXPECT_EQ(nullptr, u16tok(str, nullptr, &ctx));
|
||||
EXPECT_EQ(nullptr, u16tok(str, u"", &ctx));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, u" ", nullptr));
|
||||
|
||||
// sequence of tokens
|
||||
u16cpy(str, u"test a space and two");
|
||||
ctx = nullptr;
|
||||
|
|
@ -96,6 +114,13 @@ TEST(km_u16_Test, u16tok_str_delim) {
|
|||
EXPECT_TRUE(!u16cmp(u"b", u16tok(nullptr, u" ", &ctx)));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, u" ", &ctx));
|
||||
|
||||
// delimiters at start
|
||||
u16cpy(str, u" a b");
|
||||
ctx = nullptr;
|
||||
EXPECT_TRUE(!u16cmp(u"a", u16tok(str, u" ", &ctx)));
|
||||
EXPECT_TRUE(!u16cmp(u"b", u16tok(nullptr, u" ", &ctx)));
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, ' ', &ctx));
|
||||
|
||||
// no string, no context
|
||||
ctx = nullptr;
|
||||
EXPECT_EQ(nullptr, u16tok(nullptr, u"", &ctx));
|
||||
|
|
@ -130,6 +155,9 @@ TEST(km_u16_Test, u16tok_str_compare_to_strtok) {
|
|||
// Compare behaviour of strtok:
|
||||
char str[LINESIZE];
|
||||
|
||||
// Note: strtok behavior is undefined with invalid parameters so we don't do a reference test
|
||||
// of invalid parameters
|
||||
|
||||
// sequence of tokens
|
||||
strcpy(str, "test a space and two");
|
||||
EXPECT_TRUE(!strcmp("test", strtok(str, " ")));
|
||||
|
|
@ -150,6 +178,12 @@ TEST(km_u16_Test, u16tok_str_compare_to_strtok) {
|
|||
EXPECT_TRUE(!strcmp("b", strtok(nullptr, " ")));
|
||||
EXPECT_EQ(nullptr, strtok(nullptr, " "));
|
||||
|
||||
// delimiters at start
|
||||
strcpy(str, " a b");
|
||||
EXPECT_TRUE(!strcmp("a", strtok(str, " ")));
|
||||
EXPECT_TRUE(!strcmp("b", strtok(nullptr, " ")));
|
||||
EXPECT_EQ(nullptr, strtok(nullptr, " "));
|
||||
|
||||
// multiple delimiters
|
||||
strcpy(str, "abc<def>ghi");
|
||||
const char *delim = "<>";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
import { extractVersionData } from './version-data.js';
|
||||
// TODO: environment should be just KEYMAN_VERSION
|
||||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Environmental variables and paths
|
||||
*/
|
||||
import path from 'node:path';
|
||||
|
||||
import KEYMAN_VERSION from "@keymanapp/keyman-version";
|
||||
export const environment = extractVersionData(KEYMAN_VERSION.VERSION_WITH_TAG);
|
||||
/**
|
||||
* @returns base path for the running server -- where index.js is stored
|
||||
*/
|
||||
export function serverBasePath() {
|
||||
return path.join(import.meta.dirname, '../../build/src');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns path where the site public files can be found -- index.html
|
||||
*/
|
||||
export function serverSitePath() {
|
||||
return path.join(serverBasePath(), 'site');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
import { KeymanUrls } from '@keymanapp/developer-utils';
|
||||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* API: return list of packages under testing and urls for installing in mobile
|
||||
* apps
|
||||
*/
|
||||
import * as express from 'express';
|
||||
import { KeymanUrls } from '@keymanapp/developer-utils';
|
||||
import KEYMAN_VERSION from '@keymanapp/keyman-version';
|
||||
import { data } from "../../data.js";
|
||||
import { environment } from '../../environment.js';
|
||||
|
||||
export default function handleIncPackagesJson (req: express.Request, res: express.Response) {
|
||||
const packages = Object.keys(data.packages).map(id => { return { id: id, filename: id+'.kmp', name: data.packages[id].name} });
|
||||
res.send({
|
||||
packages: packages,
|
||||
urls: {
|
||||
installLinkAndroid: KeymanUrls.KeymanDeveloper_KeymanForAndroidDownload(environment.versionRelease),
|
||||
installLinkIos: KeymanUrls.KeymanDeveloper_KeymanForIosDownload(environment.versionRelease),
|
||||
installLinkAndroid: KeymanUrls.KeymanDeveloper_KeymanForAndroidDownload(KEYMAN_VERSION.VERSION_RELEASE),
|
||||
installLinkIos: KeymanUrls.KeymanDeveloper_KeymanForIosDownload(KEYMAN_VERSION.VERSION_RELEASE),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Keyman Developer Server main program
|
||||
*/
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import chalk from 'chalk';
|
||||
import express from 'express';
|
||||
import multer from 'multer';
|
||||
import * as ws from 'ws';
|
||||
import KEYMAN_VERSION from '@keymanapp/keyman-version';
|
||||
import { KeymanSentry } from './KeymanSentry.js';
|
||||
import { standardPaths } from './standardPaths.js';
|
||||
import { environment } from './environment.js';
|
||||
import setupRoutes from './routes.js';
|
||||
import { shutdown } from './shutdown.js';
|
||||
import { initTray } from './tray.js';
|
||||
|
|
@ -23,7 +28,7 @@ const options = {
|
|||
// set of options if an error occurs.
|
||||
await loadOptions();
|
||||
|
||||
console.log(`Starting Keyman Developer Server ${environment.versionWithTag}, listening on port ${getOption('web host port')}.`);
|
||||
console.log(`Starting Keyman Developer Server ${KEYMAN_VERSION.VERSION_WITH_TAG}, listening on port ${getOption('web host port')}.`);
|
||||
|
||||
KeymanSentry.init();
|
||||
try {
|
||||
|
|
@ -74,7 +79,7 @@ export async function run() {
|
|||
|
||||
/* Setup routes */
|
||||
|
||||
setupRoutes(app, upload, wsServer, environment);
|
||||
setupRoutes(app, upload, wsServer);
|
||||
|
||||
/* Start the web server */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Setup URL routes for Keyman Developer Server
|
||||
*/
|
||||
import * as express from 'express';
|
||||
import * as ws from 'ws';
|
||||
import * as multer from 'multer';
|
||||
import KEYMAN_VERSION from '@keymanapp/keyman-version';
|
||||
import handleIncKeyboardsJs from './handlers/inc/keyboards-js.js';
|
||||
import { data, DebugFont, DebugKeyboard, DebugModel, DebugObject, DebugPackage, isValidId } from './data.js';
|
||||
import apiGet from './handlers/api/debugobject/get.js';
|
||||
|
|
@ -11,22 +17,20 @@ import apiUnregister from './handlers/api/debugobject/unregister.js';
|
|||
import handleIncPackagesJson from './handlers/inc/packages-json.js';
|
||||
import apiPackageRegister from './handlers/api/package/register.js';
|
||||
import handleIncKeyboardsCss from './handlers/inc/keyboards-css.js';
|
||||
import { Environment } from './version-data.js';
|
||||
import { standardPaths } from './standardPaths.js';
|
||||
import chalk from 'chalk';
|
||||
import { shutdown } from './shutdown.js';
|
||||
import { getOption } from './options.js';
|
||||
import { serverSitePath } from './environment.js';
|
||||
|
||||
export default function setupRoutes(app: express.Express, upload: multer.Multer, wsServer: ws.WebSocketServer, environment: Environment ) {
|
||||
export default function setupRoutes(app: express.Express, upload: multer.Multer, wsServer: ws.WebSocketServer ) {
|
||||
|
||||
/* Middleware - JSON and logging */
|
||||
|
||||
app.use(express.json()); // for parsing application/json
|
||||
|
||||
app.use(function (req, _res, next) {
|
||||
// if(environment.environment == 'local') {
|
||||
console.log(req.method + ' ' + req.path);
|
||||
// }
|
||||
console.log(req.method + ' ' + req.path);
|
||||
next();
|
||||
});
|
||||
|
||||
|
|
@ -51,7 +55,7 @@ export default function setupRoutes(app: express.Express, upload: multer.Multer,
|
|||
|
||||
/* All routes */
|
||||
|
||||
app.use('/', express.static('build/src/site'));
|
||||
app.use('/', express.static(serverSitePath()));
|
||||
|
||||
app.post('/upload', localhostOnly, upload.single('file'), (req, res, next) => {
|
||||
const name = req.file.originalname;
|
||||
|
|
@ -93,7 +97,7 @@ export default function setupRoutes(app: express.Express, upload: multer.Multer,
|
|||
app.get('/inc/packages.json', handleIncPackagesJson);
|
||||
|
||||
app.get('/api-public/version', (req,res,next)=>{
|
||||
res.json({version: environment.versionWithTag, isApiAvailable: isLocalhost(req)});
|
||||
res.json({version: KEYMAN_VERSION.VERSION_WITH_TAG, isApiAvailable: isLocalhost(req)});
|
||||
next();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
Version information from builder-basic.inc.sh:
|
||||
|
||||
# KEYMAN_VERSION: Full current build version, e.g. "14.0.1"
|
||||
# [KEYMAN_VERSION_WIN: Full current build version for Windows, e.g. "14.0.1.0"]
|
||||
# KEYMAN_VERSION_RELEASE: Current release version, e.g. "14.0"
|
||||
# KEYMAN_VERSION_MAJOR: Major version, e.g. "14"
|
||||
# KEYMAN_VERSION_MINOR: Minor version, e.g. "0"
|
||||
# KEYMAN_VERSION_PATCH: Patch version, e.g. "1"
|
||||
# KEYMAN_TIER: Current tier, one of "alpha", "beta" or "stable"
|
||||
# KEYMAN_VERSION_TAG: Tier + Pull Request + Location of build [-alpha|-beta][-test[-1234]][-local]
|
||||
# KEYMAN_VERSION_WITH_TAG: e.g. "14.0.1-alpha-test-1234" or "14.0.5-beta-local" or "14.0.1-alpha-test"
|
||||
# [KEYMAN_ROOT: fully resolved root path of Keyman repository]
|
||||
# KEYMAN_VERSION_ENVIRONMENT: One of: local, test, alpha, beta, stable
|
||||
# KEYMAN_VERSION_GIT_TAG: Git tag for the release, "release@$KEYMAN_VERSION_WITH_TAG", e.g. "release@14.0.1-alpha-test-1234"
|
||||
*/
|
||||
|
||||
export interface Environment {
|
||||
version: string;
|
||||
versionRelease: string;
|
||||
versionMajor: string;
|
||||
versionMinor: string;
|
||||
versionPatch: string;
|
||||
tier: string;
|
||||
versionTag: string;
|
||||
versionWithTag: string;
|
||||
versionEnvironment: string;
|
||||
versionGitTag: string;
|
||||
// Pull Request Data
|
||||
pr: string;
|
||||
}
|
||||
|
||||
export function extractVersionData(version: string): Environment {
|
||||
const versionData = /^(\d+)\.(\d+)\.(\d+)((?:-(alpha|beta))?(?:-(test|local))?(?:-(\d+))?)$/.exec(version);
|
||||
if(!versionData) return null;
|
||||
|
||||
return {
|
||||
version: `${versionData[1]}.${versionData[2]}.${versionData[3]}`,
|
||||
versionRelease: `${versionData[1]}.${versionData[2]}`,
|
||||
versionMajor: versionData[1],
|
||||
versionMinor: versionData[2],
|
||||
versionPatch: versionData[3],
|
||||
tier: versionData[5] || 'stable',
|
||||
versionTag: versionData[4],
|
||||
versionWithTag: version,
|
||||
versionEnvironment: versionData[6] || versionData[5] || 'stable',
|
||||
versionGitTag: 'release@'+version,
|
||||
|
||||
pr: versionData[7] || ''
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -71,6 +71,11 @@ export class Win32Tray {
|
|||
open(ngrokAddress);
|
||||
break;
|
||||
case 'item-id-show-console':
|
||||
// #9694 - the first time, this has to be called twice for the console
|
||||
// window to appear. This could probably be fixed in the source
|
||||
// node-hide-console-window component, but this works well enough and
|
||||
// has little in the way of side-effects.
|
||||
WindowsConsole.showConsole();
|
||||
WindowsConsole.showConsole();
|
||||
break;
|
||||
case 'item-id-hide-console':
|
||||
|
|
|
|||
20
developer/src/server/test/environment.tests.ts
Normal file
20
developer/src/server/test/environment.tests.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import {assert} from 'chai';
|
||||
import 'mocha';
|
||||
import { serverBasePath, serverSitePath } from '../src/environment.js';
|
||||
|
||||
describe('serverBasePath', function() {
|
||||
it('should find index.js in the base path', function() {
|
||||
assert.isTrue(fs.existsSync(path.join(serverBasePath(), 'index.js')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('serverSitePath', function() {
|
||||
it('should find index.html in the base path', function() {
|
||||
assert.isTrue(fs.existsSync(path.join(serverSitePath(), 'index.html')));
|
||||
});
|
||||
});
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import {assert} from 'chai';
|
||||
import 'mocha';
|
||||
import { extractVersionData } from '../src/version-data.js';
|
||||
|
||||
describe('extractVersionData', function() {
|
||||
it('should parse version strings', function() {
|
||||
let v = extractVersionData('14.0.283');
|
||||
assert.equal(v.version, '14.0.283');
|
||||
assert.equal(v.versionRelease, '14.0');
|
||||
assert.equal(v.versionMajor, '14');
|
||||
assert.equal(v.versionMinor, '0');
|
||||
assert.equal(v.versionPatch, '283');
|
||||
assert.equal(v.tier, 'stable');
|
||||
assert.equal(v.versionTag, '');
|
||||
assert.equal(v.versionWithTag, '14.0.283');
|
||||
assert.equal(v.versionEnvironment, 'stable');
|
||||
assert.equal(v.pr, '');
|
||||
|
||||
v = extractVersionData('14.0.283-beta');
|
||||
assert.equal(v.version, '14.0.283');
|
||||
assert.equal(v.versionRelease, '14.0');
|
||||
assert.equal(v.versionMajor, '14');
|
||||
assert.equal(v.versionMinor, '0');
|
||||
assert.equal(v.versionPatch, '283');
|
||||
assert.equal(v.tier, 'beta');
|
||||
assert.equal(v.versionTag, '-beta');
|
||||
assert.equal(v.versionWithTag, '14.0.283-beta');
|
||||
assert.equal(v.versionEnvironment, 'beta');
|
||||
assert.equal(v.pr, '');
|
||||
|
||||
v = extractVersionData('14.0.283-alpha-local');
|
||||
assert.equal(v.version, '14.0.283');
|
||||
assert.equal(v.versionRelease, '14.0');
|
||||
assert.equal(v.versionMajor, '14');
|
||||
assert.equal(v.versionMinor, '0');
|
||||
assert.equal(v.versionPatch, '283');
|
||||
assert.equal(v.tier, 'alpha');
|
||||
assert.equal(v.versionTag, '-alpha-local');
|
||||
assert.equal(v.versionWithTag, '14.0.283-alpha-local');
|
||||
assert.equal(v.versionEnvironment, 'local');
|
||||
assert.equal(v.pr, '');
|
||||
|
||||
v = extractVersionData('14.0.283-alpha-test-1234');
|
||||
assert.equal(v.version, '14.0.283');
|
||||
assert.equal(v.versionRelease, '14.0');
|
||||
assert.equal(v.versionMajor, '14');
|
||||
assert.equal(v.versionMinor, '0');
|
||||
assert.equal(v.versionPatch, '283');
|
||||
assert.equal(v.tier, 'alpha');
|
||||
assert.equal(v.versionTag, '-alpha-test-1234');
|
||||
assert.equal(v.versionWithTag, '14.0.283-alpha-test-1234');
|
||||
assert.equal(v.versionEnvironment, 'test');
|
||||
assert.equal(v.pr, '1234');
|
||||
});
|
||||
});
|
||||
16
docs/build/macos.md
vendored
16
docs/build/macos.md
vendored
|
|
@ -28,6 +28,8 @@ The following projects **cannot** be built on macOS:
|
|||
## System Requirements
|
||||
|
||||
* Minimum macOS version: macOS Catalina 10.15 or Big Sur 11.0
|
||||
(For using Xcode 26.2 or 26.3 macOS Sequoia 15.6 or later is required.
|
||||
Make sure bash 4.0 is installed and used as modern macOS defaults to Zsh as its primary system shell)
|
||||
|
||||
**Note:** to make a fully M1-compatible release build of Keyman for macOS (for
|
||||
the setup Applescript), Big Sur 11.0 is required, as osacompile on earlier
|
||||
|
|
@ -39,8 +41,9 @@ Rosetta 2 installed.
|
|||
|
||||
Many dependencies are only required for specific projects.
|
||||
|
||||
* XCode (iOS, macOS) 12.4 or later is needed only for Keyman for macOS and Keyman
|
||||
for iOS.
|
||||
* XCode (iOS, macOS) 26.2 or 26.3 (not higher at this time, see #16416)
|
||||
is needed only for Keyman for macOS and Keyman for iOS ().
|
||||
|
||||
* Install from App Store
|
||||
* Accept the Xcode license: `sudo xcodebuild -license accept`
|
||||
|
||||
|
|
@ -105,7 +108,7 @@ PATH="$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
|
|||
|
||||
## KeymanWeb Dependencies
|
||||
|
||||
* node.js, emscripten 3.1.46 or later
|
||||
* node.js 20.0, emscripten 3.1.64
|
||||
|
||||
### node.js
|
||||
|
||||
|
|
@ -141,7 +144,8 @@ If you are updating an existing install of Emscripten:
|
|||
cd emsdk
|
||||
git pull
|
||||
emsdk install 3.1.58
|
||||
emsdk activate 3.1.58
|
||||
./emsdk install 3.1.64
|
||||
./emsdk activate 3.1.64
|
||||
cd upstream/emscripten
|
||||
npm install
|
||||
```
|
||||
|
|
@ -204,7 +208,9 @@ components such as emulator images and SDK updates.
|
|||
|
||||
```shell
|
||||
brew install node emscripten
|
||||
```
|
||||
* node.js 20.0, emscripten 3.1.64
|
||||
|
||||
See section KeymanWeb Dependencies for installation
|
||||
|
||||
## Optional Tools
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ builder_describe "Builds Keyman Engine and the Keyman app for use on iOS devices
|
|||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
"publish-symbols Publishes symbols to Sentry" \
|
||||
":engine Builds KeymanEngine.xcframework, usable by our main app and by third-party apps" \
|
||||
":app=keyman Builds the Keyman app for iOS platforms" \
|
||||
":help Online documentation" \
|
||||
|
|
@ -50,3 +51,4 @@ function do_test_help() {
|
|||
}
|
||||
|
||||
builder_run_action test:help do_test_help
|
||||
builder_run_child_actions publish-symbols
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "$KEYMAN_ROOT/resources/build/utils.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/mac/mac.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-download-resources.sh"
|
||||
. "${KEYMAN_ROOT}/resources/build/ci/sentry-control.inc.sh"
|
||||
|
||||
builder_describe "Builds Keyman Engine for use on iOS devices - iPhone and iPad." \
|
||||
"@/web/src/app/webview build" \
|
||||
|
|
@ -16,7 +17,8 @@ builder_describe "Builds Keyman Engine for use on iOS devices - iPhone and iPad.
|
|||
"clean" \
|
||||
"configure" \
|
||||
"build" \
|
||||
"--sim-artifact Also outputs a simulator-friendly test artifact corresponding to the build"
|
||||
"publish-symbols Publishes symbols to Sentry." \
|
||||
"--sim-artifact Also outputs a simulator-friendly test artifact corresponding to the build"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -138,6 +140,13 @@ function build_engine() {
|
|||
fi
|
||||
}
|
||||
|
||||
builder_run_action clean do_clean
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action build build_engine
|
||||
do_publish_symbols() {
|
||||
if builder_is_ci_build && builder_is_ci_build_level_release; then
|
||||
sentry_upload_web "${KEYMAN_ROOT}/web/build/app/webview/release/"
|
||||
fi
|
||||
}
|
||||
|
||||
builder_run_action clean do_clean
|
||||
builder_run_action configure do_configure
|
||||
builder_run_action build build_engine
|
||||
builder_run_action publish-symbols do_publish_symbols
|
||||
|
|
|
|||
119
mac/README.md
119
mac/README.md
|
|
@ -9,94 +9,121 @@ Keyman for macOS can be built from a command line (preferred) or Xcode.
|
|||
|
||||
### Setting up your code signing and notarization environment.
|
||||
|
||||
With macOS 10.14 and later, Keyman must be notarized in order to be permitted to interact with
|
||||
keyboard input. You have two options for local builds:
|
||||
With macOS 10.14 and later, Keyman must be notarized in order to be permitted to
|
||||
interact with keyboard input. You have two options for local builds:
|
||||
|
||||
1. You can disable security checks for the system with the command:
|
||||
|
||||
`sudo spctl --master-disable`
|
||||
`sudo spctl --master-disable`
|
||||
|
||||
This has obvious security implications and the risk is up to you. However, builds are
|
||||
much, much, faster than with the alternative option below, and for extensive local
|
||||
debugging is far less painful.
|
||||
This has obvious security implications and the risk is up to you. However,
|
||||
builds are much, much, faster than with the alternative option below, and for
|
||||
extensive local debugging is far less painful.
|
||||
|
||||
2. Or, you must sign and notarize every build. See below. (Use `-deploy local`)
|
||||
2. Or, you must sign and notarize every build. This requires an Apple Developer
|
||||
Program subscription or access to Developer ID code signing certificates -- a
|
||||
self-signed certificate will not work.
|
||||
|
||||
### Signing and notarizing builds
|
||||
|
||||
Keyman must be signed then notarized by Apple, even for local test builds. This requires additional
|
||||
configuration for your build environment.
|
||||
Unless security checks are disabled, Keyman must be signed then notarized by
|
||||
Apple, even for local test builds. This requires additional configuration for
|
||||
your build environment.
|
||||
|
||||
1. First, open XCode, Settings, Accounts, and select Manage Certificates for the identity
|
||||
you wish to use for signing. Click **+** and select **Apple Development**. A
|
||||
certificate will then be generated and listed in your Keychain.
|
||||
1. First, open XCode, Settings, Apple Accounts, `<apple id>`, and select Manage
|
||||
Certificates for the identity you wish to use for signing. Find the
|
||||
appropriate Development ID certificate.
|
||||
|
||||
2. Find the SHA-1 hash. To find the certificate in terminal:
|
||||
|
||||
`security find-certificate -Z -c "<your-apple-id>" -a`
|
||||
`security find-certificate -Z -c "<apple-id>" -a`
|
||||
|
||||
(If you have more than one, you may need to use Keychain Access to differentiate).
|
||||
Copy the SHA-1 hash from this command's output.
|
||||
(If you have more than one, you may need to use Keychain Access to
|
||||
differentiate). Copy the SHA-1 hash from this command's output.
|
||||
|
||||
Take note also of the Development Team ID, found in parentheses at the end of the
|
||||
`labl` blob line.
|
||||
Take note also of the Development Team ID, found in parentheses at the end of
|
||||
the `labl` blob line.
|
||||
|
||||
2. Determine the Apple ID details in order to run a build. You may wish to create an
|
||||
App-Specific Password at https://appleid.apple.com/ and use this. Your Shortname will
|
||||
be either your 10-digit Team Identifier or a shortname that can be extracted with
|
||||
the following command:
|
||||
2. Determine the Apple ID details in order to run a build. You will need to
|
||||
create an App-Specific Password at https://appleid.apple.com/.
|
||||
|
||||
`/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter -m provider -u '<Username>' -p '<Password>' -account_type itunes_connect -v off`
|
||||
Note: Use your Apple ID for `<Username>` and the app-specific password you
|
||||
generated above for `<Password>`. You'll use these in the next step.
|
||||
|
||||
Note: Use your Apple ID for `<Username>` and the app-specific password you generated above
|
||||
for `<Password>`. You'll use these in the next step as well.
|
||||
3. Add the following environment variables to `mac/localenv.sh` (or to
|
||||
`~/.bashrc`), replacing with the values you collected in the previous steps:
|
||||
|
||||
3. Add the following environment variables to mac/localenv.sh (or to your .bashrc file), replacing with the
|
||||
values you collected in the previous steps:
|
||||
|
||||
export CERTIFICATE_ID=<SHA1-Fingerprint>
|
||||
export APPSTORECONNECT_PROVIDER=<Shortname>
|
||||
export APPSTORECONNECT_USERNAME=<Username>
|
||||
export APPSTORECONNECT_PASSWORD=<Password>
|
||||
export DEVELOPMENT_TEAM=<TeamID>
|
||||
```sh
|
||||
export CERTIFICATE_ID=<SHA1-Fingerprint>
|
||||
export APPSTORECONNECT_USERNAME=<Username>
|
||||
export APPSTORECONNECT_PASSWORD=<Password>
|
||||
export DEVELOPMENT_TEAM=<TeamID>
|
||||
```
|
||||
|
||||
### Compiling from Command Line
|
||||
|
||||
To build Keyman for macOS, do the following:
|
||||
|
||||
1. Open a Terminal window.
|
||||
2. Build using `mac/build.sh clean configure build`
|
||||
|
||||
Note: If Carthage prompts you to allow it access to your github credentials, it's fine to click Deny.
|
||||
Note: If Carthage prompts you to allow it access to your github credentials,
|
||||
it's fine to click Deny.
|
||||
|
||||
### Running Keyman
|
||||
|
||||
1. Deploy Keyman locally using `mac/build.sh install`.
|
||||
* This will notarize the app, signing with your local credentials if not already signed, and copy **keyman/mac/Keyman4MacIM/build/Debug/Keyman.app** to **~/Library/Input Methods**
|
||||
2. If running for the first time, follow the installation instructions at [Install Keyman for macOS].
|
||||
1. Deploy Keyman locally:
|
||||
|
||||
You can also use `mac/build.sh build install` to do a single-step build, notarize, and deploy (see above for faster options).
|
||||
* Without notarization (requires `spctl --master-disable`), recommended:
|
||||
|
||||
`mac/build.sh install --quick`
|
||||
|
||||
This will copy **keyman/mac/Keyman4MacIM/build/Debug/Keyman.app**
|
||||
to **~/Library/Input Methods**
|
||||
|
||||
* With notarization (requires appropriate certificates / Apple Developer
|
||||
subscription):
|
||||
|
||||
`mac/build.sh install`
|
||||
|
||||
This will notarize the app, signing with your local credentials if not
|
||||
already signed, and copy **keyman/mac/Keyman4MacIM/build/Debug/Keyman.app**
|
||||
to **~/Library/Input Methods**
|
||||
|
||||
2. If running for the first time, follow the installation instructions at
|
||||
[Install Keyman for macOS].
|
||||
|
||||
You can also use `mac/build.sh build install [--quick]` to do a single-step
|
||||
build, notarize, and deploy (see above for faster options).
|
||||
|
||||
### Compiling from Xcode
|
||||
|
||||
To build using Xcode, you will need to build KeymanEngine4Mac first and then build Keyman4MacIM. The very _first_ time after getting the source code (and any time the Podfile is edited; e.g., to install new pods), you need to go to **keyman/mac/Keyman4MacIM** and run "pod install" in Terminal.
|
||||
To build using Xcode, you will need to build KeymanEngine4Mac first and then
|
||||
build Keyman4MacIM. The very _first_ time after getting the source code (and any
|
||||
time the Podfile is edited; e.g., to install new pods), you need to go to
|
||||
**keyman/mac/Keyman4MacIM** and run `pod install` in Terminal.
|
||||
|
||||
1. Launch Xcode
|
||||
2. Open **keyman/mac/KeymanEngine4Mac/KeymanEngine4Mac.xcodeproj**
|
||||
3. Build the project: Product > Build (or Cmd-B)
|
||||
4. Open **keyman/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj**
|
||||
5. If you do not have signing credentials from the core development team, disable code signing in Xcode.
|
||||
1. Open the Project Navigator: View > Navigators > Show Project Navigator (Cmd-1)
|
||||
5. If you do not have signing credentials from the core development team,
|
||||
disable code signing in Xcode.
|
||||
1. Open the Project Navigator: View > Navigators > Show Project Navigator
|
||||
(Cmd-1)
|
||||
2. Select Keyman4MacIM and click Build Settings
|
||||
3. In the Signing section, change Code Signing Identity to Don't Code Sign. This will modify
|
||||
**Keyman4MacIM.xcodeproj**. Do not commit the change.
|
||||
6. Build the project. Refer to [Running Keyman](#running-keyman) on how to install the app.
|
||||
3. In the Signing section, change Code Signing Identity to Don't Code Sign.
|
||||
This will modify **Keyman4MacIM.xcodeproj**. Do not commit the change.
|
||||
6. Build the project. Refer to [Running Keyman](#running-keyman) on how to
|
||||
install the app.
|
||||
|
||||
### Testing
|
||||
|
||||
The Keyman4Mac project builds a test-bed app that can be used to test keyboards without installing the input method.
|
||||
It can also be used as reference for the usage of Keyman Engine.
|
||||
The Keyman4Mac project builds a test-bed app that can be used to test keyboards
|
||||
without installing the input method. It can also be used as reference for the
|
||||
usage of Keyman Engine.
|
||||
|
||||
Keyman4Mac tests are run using `./build.sh -test -no-codesign`.
|
||||
Keyman4Mac tests are run using `./build.sh test`.
|
||||
|
||||
### A note about Homebrew and xcodebuild
|
||||
|
||||
|
|
|
|||
|
|
@ -87,9 +87,8 @@ UPLOAD_SENTRY=false
|
|||
# Import local environment variables for build
|
||||
#
|
||||
# /mac/localenv.sh can be used to define CERTIFICATE_ID,
|
||||
# APPSTORECONNECT_PROVIDER, APPSTORECONNECT_USERNAME,
|
||||
# APPSTORECONNECT_PASSWORD, DEVELOPMENT_TEAM variables;
|
||||
# see /mac/README.md for details.
|
||||
# APPSTORECONNECT_USERNAME, APPSTORECONNECT_PASSWORD, DEVELOPMENT_TEAM
|
||||
# variables; see /mac/README.md for details.
|
||||
#
|
||||
if [[ -f "$THIS_SCRIPT_PATH/localenv.sh" ]]; then
|
||||
. "$THIS_SCRIPT_PATH/localenv.sh"
|
||||
|
|
@ -223,7 +222,7 @@ do_notarize() {
|
|||
builder_die "Notarization and signed executable is required for deployment, even locally. Specify CERTIFICATE_ID environment variable for custom certificate."
|
||||
fi
|
||||
|
||||
if [ -z "${APPSTORECONNECT_PROVIDER+x}" ] || [ -z "${APPSTORECONNECT_USERNAME+x}" ] || [ -z "${APPSTORECONNECT_PASSWORD+x}" ]; then
|
||||
if [ -z "${APPSTORECONNECT_USERNAME+x}" ] || [ -z "${APPSTORECONNECT_PASSWORD+x}" ]; then
|
||||
builder_die "Appstoreconnect Apple ID credentials must be configured in environment. See README.md for details."
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
function isSentryConfigured() {
|
||||
if [ -z "${SENTRY_AUTH_TOKEN-}" ] || [ -z "${SENTRY_ORG-}" ] || [ -z "${SENTRY_URL-}" ]; then
|
||||
echo "WARNING: Sentry environment variables SENTRY_AUTH_TOKEN, SENTRY_ORG and SENTRY_URL must be configured."
|
||||
builder_warn "WARNING: Sentry environment variables SENTRY_AUTH_TOKEN, SENTRY_ORG and SENTRY_URL must be configured."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
|
|
@ -20,7 +20,7 @@ function isSentryConfigured() {
|
|||
|
||||
function isSentryCliAvailable() {
|
||||
which sentry-cli > /dev/null && return 0
|
||||
echo "WARNING: sentry-cli could not be found. Skipping all sentry integration."
|
||||
builder_warn "WARNING: sentry-cli could not be found. Skipping all sentry integration."
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -28,15 +28,35 @@ function makeSentryRelease() {
|
|||
if isSentryConfigured; then
|
||||
if isSentryCliAvailable; then
|
||||
# This version tag matches the repository version tag release@x.y.z
|
||||
echo "Making a Sentry release for tag $KEYMAN_VERSION_GIT_TAG"
|
||||
builder_echo "Making a Sentry release for tag $KEYMAN_VERSION_GIT_TAG"
|
||||
sentry-cli releases new -p keyman-android -p keyman-developer -p keyman-ios -p keyman-linux -p keyman-mac -p keyman-web -p keyman-windows $KEYMAN_VERSION_GIT_TAG
|
||||
|
||||
echo "Setting commits for release tag $KEYMAN_VERSION_GIT_TAG"
|
||||
builder_echo "Setting commits for release tag $KEYMAN_VERSION_GIT_TAG"
|
||||
sentry-cli releases set-commits --auto $KEYMAN_VERSION_GIT_TAG
|
||||
|
||||
echo "Finalizing release tag $KEYMAN_VERSION_GIT_TAG"
|
||||
builder_echo "Finalizing release tag $KEYMAN_VERSION_GIT_TAG"
|
||||
sentry-cli releases finalize "$KEYMAN_VERSION_GIT_TAG"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Upload sourcemaps to Sentry
|
||||
#
|
||||
# Parameters:
|
||||
# 1: Directory containing sourcemaps to upload
|
||||
#
|
||||
function sentry_upload_web () {
|
||||
local SOURCEMAP_DIR="$1"
|
||||
if ! isSentryConfigured; then
|
||||
builder_warn "Skipping Sentry upload: SENTRY_ORG and/or SENTRY_PROJECT are unset."
|
||||
return
|
||||
fi
|
||||
|
||||
builder_echo "Uploading ${SOURCEMAP_DIR} to Sentry..."
|
||||
|
||||
sentry-cli releases files "${KEYMAN_VERSION_GIT_TAG}" upload-sourcemaps --strip-common-prefix \
|
||||
"${SOURCEMAP_DIR}" --rewrite --ext js --ext map --ext ts
|
||||
|
||||
builder_echo "Upload successful."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,18 @@
|
|||
|
||||
ios_build() {
|
||||
builder_echo start "build" "Building KeymanEngine + Keyman for iOS"
|
||||
# shellcheck disable=SC2154
|
||||
"${KEYMAN_ROOT}/ios/ci.sh" build
|
||||
builder_launch /ios/ci.sh build
|
||||
builder_echo end "build" success "Finished building KeymanEngine + Keyman for iOS"
|
||||
}
|
||||
|
||||
ios_capture_build_artifacts() {
|
||||
builder_echo start "prep-release" "Using prep-release script to capture build artifacts"
|
||||
"${KEYMAN_ROOT}/ios/tools/prepRelease.sh"
|
||||
builder_launch /ios/tools/prepRelease.sh
|
||||
builder_echo end "prep-release" success "Finished capturing build artifacts"
|
||||
}
|
||||
|
||||
ios_publish_symbols() {
|
||||
builder_echo start "publish to Sentry" "Publishing source map to Sentry"
|
||||
builder_launch /ios/build.sh "publish-symbols"
|
||||
builder_echo end "publish to Sentry" success "Finished publishing source map to Sentry"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ function do_build() {
|
|||
|
||||
function do_publish() {
|
||||
_publish_to_downloads_keyman_com
|
||||
ios_publish_symbols
|
||||
tc_upload_help "Keyman for iOS" ios
|
||||
|
||||
if [[ "${KEYMAN_TIER}" == "stable" ]]; then
|
||||
|
|
|
|||
21
web/ci.sh
21
web/ci.sh
|
|
@ -15,6 +15,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${KEYMAN_ROOT}/resources/build/utils.inc.sh"
|
||||
. "${KEYMAN_ROOT}/resources/build/zip.inc.sh"
|
||||
. "${KEYMAN_ROOT}/resources/build/ci/pull-requests.inc.sh"
|
||||
. "${KEYMAN_ROOT}/resources/build/ci/sentry-control.inc.sh"
|
||||
|
||||
# This script runs from its own folder
|
||||
cd "${THIS_SCRIPT_PATH}"
|
||||
|
|
@ -42,21 +43,6 @@ builder_parse "$@"
|
|||
KEYMAN_TIER=$(cat ../TIER.md)
|
||||
BUILD_NUMBER=$(cat ../VERSION.md)
|
||||
|
||||
function web_sentry_upload () {
|
||||
if [[ -z "${SENTRY_ORG:-}" ]] || [[ -z "${SENTRY_PROJECT:-}" ]]; then
|
||||
echo "Skipping Sentry upload: SENTRY_ORG and/or SENTRY_PROJECT are unset."
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Uploading $1 to Sentry..."
|
||||
|
||||
# --strip-common-prefix does not take an argument, unlike --strip-prefix. It auto-detects
|
||||
# the most common prefix instead.
|
||||
sentry-cli releases files "${KEYMAN_VERSION_GIT_TAG}" upload-sourcemaps --strip-common-prefix "$1" \
|
||||
--rewrite --ext js --ext map --ext ts
|
||||
echo "Upload successful."
|
||||
}
|
||||
|
||||
function build_action() {
|
||||
# Build step: since CI builds start (and should start) from scratch, run the following
|
||||
# three actions:
|
||||
|
|
@ -73,11 +59,10 @@ function build_action() {
|
|||
for sourcemap in "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/"*.map; do
|
||||
node "${KEYMAN_ROOT}/web/build/tools/building/sourcemap-root/index.js" null "${sourcemap}" --clean
|
||||
done
|
||||
web_sentry_upload "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/"
|
||||
sentry_upload_web "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/"
|
||||
|
||||
# And, of course, the main build-products too
|
||||
web_sentry_upload "${KEYMAN_ROOT}/web/build/app/webview/release/"
|
||||
web_sentry_upload "${KEYMAN_ROOT}/web/build/publish/release/"
|
||||
sentry_upload_web "${KEYMAN_ROOT}/web/build/publish/release/"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -327,7 +327,8 @@ export abstract class ContextManagerBase<MainConfig extends EngineConfiguration>
|
|||
|
||||
if(!requestedStub) {
|
||||
if(keyboardId) {
|
||||
throw new Error("No matching stub has been registered.");
|
||||
const availableStubList = this.keyboardCache.getStubList().map(stub => `${stub.KI}@${stub.KLC}`);
|
||||
throw new Error(`No matching stub has been registered for keyboard ${keyboardId}. Available stubs: ${JSON.stringify(availableStubList)}`);
|
||||
} else {
|
||||
return {
|
||||
keyboard: Promise.resolve(null),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ async function setTimeoutAndLoadPage(page: Page, url: string): Promise<void> {
|
|||
await loadPage(page, url);
|
||||
}
|
||||
|
||||
test.describe('First example from the guide', function () {
|
||||
test.describe.skip('First example from the guide', function () {
|
||||
const beforeEach = async (page: Page) => {
|
||||
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__first-example.html');
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ test.describe('First example from the guide', function () {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe('Auto-control example from the guide', function () {
|
||||
test.describe.skip('Auto-control example from the guide', function () {
|
||||
const beforeEach = async (page: Page) => {
|
||||
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__auto-control.html');
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ test.describe('Auto-control example from the guide', function () {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe('Control-by-control example from the guide', function () {
|
||||
test.describe.skip('Control-by-control example from the guide', function () {
|
||||
const beforeEach = async (page: Page) => {
|
||||
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__control-by-control.html');
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ test.describe('Control-by-control example from the guide', function () {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe('Full manual control example from the guide', function () {
|
||||
test.describe.skip('Full manual control example from the guide', function () {
|
||||
const beforeEach = async (page: Page) => {
|
||||
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__full-manual-control.html');
|
||||
}
|
||||
|
|
@ -203,7 +203,7 @@ test.describe('Full manual control example from the guide', function () {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe('Manual control example from the guide', function () {
|
||||
test.describe.skip('Manual control example from the guide', function () {
|
||||
const beforeEach = async (page: Page) => {
|
||||
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__manual-control.html');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ builder_describe_outputs \
|
|||
function do_build() {
|
||||
create-windows-output-folders
|
||||
build_version.res
|
||||
# TODO: why no manifest?
|
||||
# build_manifest.res
|
||||
build_manifest.res
|
||||
delphi_msbuild insthelp.dproj "//p:Platform=Win32"
|
||||
sentrytool_delphiprep "$WIN32_TARGET" insthelp.dpr
|
||||
tds2dbg "$WIN32_TARGET"
|
||||
|
|
@ -38,8 +37,7 @@ function do_build() {
|
|||
|
||||
function do_publish() {
|
||||
# test that (a) linked manifest exists and correct
|
||||
# TODO: no manifest included?
|
||||
# wrap-mt -nologo -inputresource:"$WINDOWS_PROGRAM_APP/insthelp.exe" -validate_manifest
|
||||
wrap-mt -nologo -inputresource:"$WINDOWS_PROGRAM_APP/insthelp.exe" -validate_manifest
|
||||
|
||||
wrap-signcode //d "Keyman for Windows Install Helper" "$WINDOWS_PROGRAM_APP/insthelp.exe"
|
||||
wrap-symstore "$WINDOWS_PROGRAM_APP/insthelp.exe" //t keyman-windows
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ uses
|
|||
UserMessages in '..\..\..\..\common\windows\delphi\general\UserMessages.pas';
|
||||
|
||||
{$R version.res}
|
||||
{-R manifest.res}
|
||||
{$R manifest.res}
|
||||
|
||||
begin
|
||||
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
|
||||
|
|
|
|||
|
|
@ -6,15 +6,13 @@
|
|||
version="$VersionWin"
|
||||
processorArchitecture="x86"/>
|
||||
<description>Keyman Installation Assistant</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel
|
||||
level="requireAdministrator"
|
||||
uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<!-- Switch on various compatibility values -->
|
||||
|
||||
|
|
@ -27,4 +25,4 @@
|
|||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- Windows 10 -->
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
</assembly>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateKeyboard(GUID *profile)
|
|||
if(!_td) return FALSE;
|
||||
|
||||
RefreshThreadKeyboardsIfRequired();
|
||||
RefreshToggleState();
|
||||
|
||||
if(profile != NULL) {
|
||||
for(int i = 0; i < _td->nKeyboards; i++) {
|
||||
|
|
|
|||
|
|
@ -29,3 +29,21 @@
|
|||
BOOL IsCapsLockOn(void) {
|
||||
return GetKeyState(VK_CAPITAL) & 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resync the Caps Lock and Num Lock state cache.
|
||||
*
|
||||
* Use when state may be stale after focus changes or when the toggle keys
|
||||
* changed while this Keyman engine instance was not processing key events.
|
||||
*/
|
||||
void RefreshToggleState(void) {
|
||||
DWORD previousShiftState = Globals::get_ShiftState();
|
||||
|
||||
if (GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG;
|
||||
else *Globals::ShiftState() &= ~CAPITALFLAG;
|
||||
|
||||
if (GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG;
|
||||
else *Globals::ShiftState() &= ~NUMLOCKFLAG;
|
||||
|
||||
SendDebugMessageFormat("Enter: %x Exit: %x", previousShiftState, Globals::get_ShiftState());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@
|
|||
#define __CAPSSTATE_H
|
||||
|
||||
BOOL IsCapsLockOn(void);
|
||||
void RefreshToggleState(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam);
|
||||
void ProcessWMKeymanControl(WPARAM wParam, LPARAM lParam);
|
||||
void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam);
|
||||
void GetCapsAndNumlockState();
|
||||
void RefreshModifierState();
|
||||
|
||||
/*
|
||||
BOOL SysTabCtrl(HWND hwnd)
|
||||
|
|
@ -354,7 +354,7 @@ void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam)
|
|||
if(IsFocusedThread())
|
||||
{
|
||||
if(_td->app) _td->app->ResetQueue();
|
||||
GetCapsAndNumlockState();
|
||||
RefreshModifierState();
|
||||
UpdateActiveWindows();
|
||||
}
|
||||
}
|
||||
|
|
@ -413,20 +413,19 @@ ProcessWMKeymanControl(WPARAM wParam, LPARAM lParam) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
GetCapsAndNumlockState:
|
||||
/**
|
||||
* Refresh Keyman's internal keyboard modifier and toggle state according
|
||||
* to the Windows current keyboard state (not async state). The state may
|
||||
* have been reset while Keyman was not aware of it. This is used only when
|
||||
* focus changes. Note that the global values are per-architecture, so
|
||||
* these may track separately.
|
||||
*
|
||||
* @see ProcessModifierChange()
|
||||
*/
|
||||
void RefreshModifierState() { // I4793
|
||||
DWORD previousShiftState = Globals::get_ShiftState();
|
||||
|
||||
Updates the global caps and numlock state when a window is focused, because it
|
||||
may have been reset while Keyman was not aware of it
|
||||
*/
|
||||
void GetCapsAndNumlockState() { // I4793
|
||||
DWORD n = Globals::get_ShiftState();
|
||||
|
||||
if(GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG;
|
||||
else *Globals::ShiftState() &= ~NUMLOCKFLAG;
|
||||
|
||||
if(GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG;
|
||||
else *Globals::ShiftState() &= ~CAPITALFLAG;
|
||||
RefreshToggleState();
|
||||
|
||||
if(GetKeyState(VK_SHIFT) < 0) *Globals::ShiftState() |= K_SHIFTFLAG;
|
||||
else *Globals::ShiftState() &= ~K_SHIFTFLAG;
|
||||
|
|
@ -443,18 +442,19 @@ void GetCapsAndNumlockState() { // I4793
|
|||
if(GetKeyState(VK_RMENU) < 0) *Globals::ShiftState() |= RALTFLAG;
|
||||
else *Globals::ShiftState() &= ~RALTFLAG;
|
||||
|
||||
SendDebugMessageFormat("Enter: %x Exit: %x", n, Globals::get_ShiftState());
|
||||
SendDebugMessageFormat("Enter: %x Exit: %x", previousShiftState, Globals::get_ShiftState());
|
||||
}
|
||||
|
||||
/*
|
||||
Update the Keyman shift state based on the key event. This has to be
|
||||
done from both the GetMessage hook and the TSF methods, because we don't
|
||||
consistently receive modifier events in some applications (e.g. ALT in Firefox)
|
||||
via TSF, and we don't receive the notifications via the GetMessage hook when
|
||||
in UWP apps (#4369).
|
||||
|
||||
TODO: test whether we still need the GetMessage hook for reading modifier state.
|
||||
*/
|
||||
/**
|
||||
* Update the Keyman shift state based on the key event. This has to be done
|
||||
* from both the GetMessage hook and the TSF methods, because we don't
|
||||
* consistently receive modifier events in some applications (e.g. ALT in
|
||||
* Firefox) via TSF, and we don't receive the notifications via the GetMessage
|
||||
* hook when in UWP apps (#4369).
|
||||
*
|
||||
* TODO: test whether we still need the GetMessage hook for reading modifier
|
||||
* state.
|
||||
*/
|
||||
void ProcessModifierChange(UINT key, BOOL isUp, BOOL isExtended) { // I4793
|
||||
UINT flag = 0;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue